diff options
author | M Joonas Pihlaja <jpihlaja@cc.helsinki.fi> | 2009-02-15 15:10:30 +0200 |
---|---|---|
committer | M Joonas Pihlaja <jpihlaja@cc.helsinki.fi> | 2009-02-15 15:10:30 +0200 |
commit | 11ea8fbcc8634b130a22237dadf338c0d735adb1 (patch) | |
tree | 1e5a14ec69759702dc68bbda6f215af6c803db62 |
Import cairosdl.
-rw-r--r-- | README | 173 | ||||
-rw-r--r-- | cairosdl-premultiply.c | 301 | ||||
-rw-r--r-- | cairosdl.c | 412 | ||||
-rw-r--r-- | cairosdl.h | 112 |
4 files changed, 998 insertions, 0 deletions
@@ -0,0 +1,173 @@ +Cairosdl: convenience functions for setting up drawing to SDL surfaces +using cairo. + +Using cairo to draw on SDL functions is very easy. The only gotcha is +that it's only possible for surfaces whose pixel format is supported +by cairo. + + +* Quick start to cairosdl.c +--------------------------- + +For one-off rendering using cairo only: + + sdlsurf = SDL_CreateRBGSurface(flags, width, height, 32, + CAIROSDL_RMASK, CAIROSDL_GMASK, CAIROSDL_BMASK, + 0 /* or CAIROSDL_AMASK*/); + + cairo_t *cairosdl_create(sdlsurf); + + /* ... normal cairo calls ... */ + + cairosdl_destroy(sdlsurf); + +For mixed cairo/non-cairo rendering: + + sdlsurf = ... as before ...; + + cairo_surface_t *cairosurf = cairosdl_surface_create(sdlsurf); + + /* ... normal cairo calls ... and then before switching to + * non-cairo rendering: */ + + cairosdl_surface_flush_rects (cairosurf, rects, num_rects); + + /* ... non-cairo rendering to the sdlsurf ... and then before + * resuming cairo rendering mark the changed areas: */ + + cairosdl_surface_mark_dirty_rects (cairosurf, rects, num_rects); + +So the short of it is: Use cairosdl_surface_create() or +cairosdl_create() to bind your SDL_Surface to a cairo_surface_t or +cairo_t context, and do normal cairo calls to render to it. Replace +calls to cairo_surface_flush() and cairo_surface_mark_dirty() with the +corresponding cairosdl.c calls. All the cairo functions expect the +SDL_Surface to be locked or not need locking. + + +* Amask = 0 surfaces +-------------------- + +Technically there is only one common pixel format in common between +cairo and SDL: 32 bit pixels which do NOT use per pixel alpha. Cairo +calls that format CAIRO_FORMAT_RGB24. In SDL terms such surfaces are +created by: + + SDL_CreateRGBSurface (flags, width, height, 32, + CAIROSDL_RMASK, // 0x00FF0000 + CAIROSDL_GMASK, // 0x0000FF00 + CAIROSDL_BMASK, // 0x000000FF + 0); + +Using the cairosdl_surface_create(SDL_Surface*) function one can get a +cairo_surface_t* that can be used to get a cairo_t drawing context, +just as normal. As a convenience, cairosdl_create(SDL_Surface*) will +make the cairo_surface_t and then bind it to a cairo_t drawing context. + +If your SDL_Surface has this pixel format and does not need locking, +that's all you need to do. + + +* Amask = 0xFF000000 surfaces +----------------------------- + +Unfortunately SDL and cairo have an inconsolable difference of opinion +on the interpretation of 32 bit pixels with per pixel alpha. SDL uses +unpremultiplied colour components, while cairo uses premultiplied +colours: an SDL pixel with components (r,g,b,a) would be represented +in a cairo as (a*r/255, a*g/255, a*b/255, a). (Here a=255 means fully +opaque and a=0 means fully transparent.) + +The first option to consider is whether you really need such surfaces +to be SDL_Surfaces or whether cairo's image surfaces would do just as +well. After all, the screen surface almost never has per-pixel alpha, +so you could create an Amask=0 surface for it and use cairo to blit to +it from a normal cairo image surface. This is likely a little faster +than SDL's per-pixel-alpha blits even when SDL can't accelerate them. + +Regardless, sometimes you really want to draw to an Amask=0xFF000000 +surface. The good news is that cairosdl.c supports such surfaces +anyway via a backing buffer and will format shift between them on +demand. In the simple one-off rendering case you just need to +remember to call cairosdl_destroy() rather than cairo_destroy(). That +will flush the backing buffer at the end to the SDL_Surface. + +If you plan to do mixed cairo/non-cairo rendering to the surface, +cairosdl needs some coordination from you so that it can format shift +between it's backing buffer and the SDL_Surface at the right times. + +The functions to call to make the cairo-drawn stuff appear in the +SDL_Surface are: + + cairosdl_surface_flush() ; writes to the entire SDL_Surface + + cairosdl_surface_flush_rect(); writes to just a region. + + cairosdl_surface_flush_rects(); writes via a list of SDL_Rects. + +These functions do not call SDL_UpdateRect or anything like that to +make the output visible on screen. + +The functions to call to import non-cairo drawn changes from the +SDL_Surface to the backing surface are: + + cairosdl_surface_mark_dirty () ; reads the entire SDL_Surface. + + cairosdl_surface_mark_dirty_rect () ; reads just a region. + + cairosdl_surface_mark_dirty_rects () ; reads via a list of SDL_Rects. + +If you're doing mixed rendering and are already using the +flush/mark_dirty functions, then you don't want to call +cairosdl_destroy() at the end since that does an implicit final flush. + + +* Palette indexed surfaces +-------------------------- + +You could create a cairo image surface using the CAIRO_FORMAT_A8 pixel +format. Cairo will ignore your palette though. cairosdl.c doesn't +support this at the moment. + + +* SDL_Surface flags, locking and other caveats +---------------------------------------------- + +Cairo really needs direct access to the pixel data of an SDL surface, +but it doesn't know anything about locking them. Since the SDL +locking rules can be a little inconvenient, the cairosdl_* functions +assume that you're passing in an SDL_Surface which does not need +locking or is already locked. In particular, the surface->pixels +member must be valid and not change for the lifetime of a +cairo_surface_t created by cairosdl_surface_create() or +cairosdl_create(). It must also be safe to call malloc and whatever +platform specific mutex operations inside cairo while the surface is +locked. + +For most platforms I believe this means that SDL_SWSURFACEs are always +safe to use with cairo. If they don't use RLEACCEL, they don't even +need any locking at all. + +One can also use SDL_HWSURFACEs if it's okay to call malloc and other +OS facilities while they're locked. In this case the the lifetime of +the cairo_surface_t created for the SDL_Surface must be contained +within a single SDL_LockSurface/UnlockSurface pair. Again, for many +platforms this isn't a problem. + +I haven't tested GL enabled SDL surfaces, but expect there shouldn't +be a problem if GL can be configured to accept cairo's pixel formats. + +Cairo ignores SDL_Surface colorkey and treats every 32 bit surface +with alpha as if it had SDL_SRCALPHA. + + +* Tips and tricks +----------------- + +Cairo's software blits seem to be slightly faster than SDL's software +blits. The fastest combination seems to be using a 32 bit SDL_Surface +with Amask = 0, wrap that up in a cairo_surface_t, and use normal +cairo image surfaces for the per-pixel-alpha surfaces. + +The cairosdl_surface_get_target() and cairosdl_get_target() functions +return the SDL_Surface bound to the given cairo_surface_t or cairo_t. diff --git a/cairosdl-premultiply.c b/cairosdl-premultiply.c new file mode 100644 index 0000000..b07881a --- /dev/null +++ b/cairosdl-premultiply.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2009 M Joonas Pihlaja + * Copyright (c) 2009 Paul-Virak Khuong + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +/* A pixel premultiplier and an unpremultiplier using reciprocal + * multiplication. It specialises constant runs and solid runs of + * pixels with low overhead loops and uses only a 1KB table of + * reciprocals. + * + * The unpremultiplier wa snarfed from + * http://cgit.freedesktop.org/~joonas/unpremultiply + * + * See there for other faster or nonportable variations on + * unpremultiply. This one is a good all rounder that doesn't take + * that much data space. */ +#include <stddef.h> +#include "cairosdl.h" + +/* Pixel format config for a 32 bit pixel with 8 bit components. Only + * the location of alpha matters. Cairo uses ASHIFT 24. */ +#define ASHIFT CAIROSDL_ASHIFT +#define RSHIFT ((24 + ASHIFT) % 32) +#define GSHIFT ((16 + ASHIFT) % 32) +#define BSHIFT (( 8 + ASHIFT) % 32) + +#define AMASK (255U << ASHIFT) +#define RMASK (255U << RSHIFT) +#define GMASK (255U << GSHIFT) +#define BMASK (255U << BSHIFT) + +/* Set to 1 if the input can have superluminant pixels. Cairo doesn't + * produce them. */ +#define DO_CLAMP_INPUT 0 + +/* Shift x left by y bits. Supports negative y for right shifts. */ +#define SHIFT(x, y) ((y) < 0 ? (x) >> (-(y)) : (x) << (y)) + +#define ceil_div(a,b) ((a) + (b)-1) / (b) + +/* The reciprocal_table[i] entries are defined by + * + * 0 when i = 0 + * 255 / i when i > 0 + * + * represented in fixed point format with RECIPROCAL_BITS of + * precision and errors rounded up. */ +#define RECIPROCAL_BITS 16 +static unsigned const reciprocal_table[256] = { +# define R(i) ((i) ? ceil_div(255*(1<<RECIPROCAL_BITS), (i)) : 0) +# define R1(i) R(i), R(i+1), R(i+2), R(i+3) +# define R2(i) R1(i), R1(i+4), R1(i+8), R1(i+12) +# define R3(i) R2(i), R2(i+16), R2(i+32), R2(i+48) + R3(0), R3(64), R3(128), R3(192) +}; + +/* Transfer num_pixels premultiplied pixels from src[] to dst[] and + * unpremultiply them. */ +static void +unpremultiply_row( + unsigned * dst, + unsigned const * src, + size_t num_pixels) +{ + size_t i = 0; + while (i < num_pixels) { + /* We want to identify long runs of constant input pixels and + * cache the unpremultiplied. */ + unsigned const_in, const_out; + + /* Diff is the or of all bitwise differences from const_in + * during the probe period. If it is zero after the probe + * period then every input pixel was identical in the + * probe. */ + unsigned diff = 0; + + /* Accumulator for all alphas of the probe period pixels, + * biased to make the sum zero if the */ + unsigned accu = -2*255; + + { + unsigned rgba, a, r, g, b, recip; + rgba = const_in = src[i]; + a = (rgba >> ASHIFT) & 255; + accu += a; + r = (rgba >> RSHIFT) & 255; + g = (rgba >> GSHIFT) & 255; + b = (rgba >> BSHIFT) & 255; + recip = reciprocal_table[a]; +#if DO_CLAMP_INPUT + r = r < a ? r : a; + g = g < a ? g : a; + b = b < a ? b : a; +#endif + r = SHIFT(r * recip, RSHIFT - RECIPROCAL_BITS); + g = SHIFT(g * recip, GSHIFT - RECIPROCAL_BITS); + b = SHIFT(b * recip, BSHIFT - RECIPROCAL_BITS); + dst[i] = const_out = + (r & RMASK) | (g & GMASK) | (b & BMASK) | (rgba & AMASK); + } + + if (i + 1 == num_pixels) + return; + + { + unsigned rgba, a, r, g, b, recip; + rgba = src[i+1]; + a = (rgba >> ASHIFT) & 255; + accu += a; + r = (rgba >> RSHIFT) & 255; + g = (rgba >> GSHIFT) & 255; + b = (rgba >> BSHIFT) & 255; + recip = reciprocal_table[a]; +#if DO_CLAMP_INPUT + r = r < a ? r : a; + g = g < a ? g : a; + b = b < a ? b : a; +#endif + diff = rgba ^ const_in; + r = SHIFT(r * recip, RSHIFT - RECIPROCAL_BITS); + g = SHIFT(g * recip, GSHIFT - RECIPROCAL_BITS); + b = SHIFT(b * recip, BSHIFT - RECIPROCAL_BITS); + dst[i+1] = + (r & RMASK) | (g & GMASK) | (b & BMASK) | (rgba & AMASK); + } + + i += 2; + + /* Fall into special cases if we have special + * circumstances. */ + if (0 != (accu & diff)) + continue; + + if (0 == accu) { /* a run of solid pixels. */ + unsigned in; + while (AMASK == ((in = src[i]) & AMASK)) { + dst[i++] = in; + if (i == num_pixels) return; + } + } else if (0 == diff) { /* a run of constant pixels. */ + while (src[i] == const_in) { + dst[i++] = const_out; + if (i == num_pixels) return; + } + } + } +} + +/* Transfer num_pixels unpremultiplied pixels from src[] to dst[] and + * premultiply them. */ +static void +premultiply_row( + unsigned * dst, + unsigned const * src, + size_t num_pixels) +{ + size_t i = 0; + while (i < num_pixels) { + /* We want to identify long runs of constant input pixels and + * cache the unpremultiplied. */ + unsigned const_in, const_out; + + /* Diff is the or of all bitwise differences from const_in + * during the probe period. If it is zero after the probe + * period then every input pixel was identical in the + * probe. */ + unsigned diff = 0; + + /* Accumulator for all alphas of the probe period pixels, + * biased to make the sum zero if the */ + unsigned accu = -2*255; + + { + unsigned rgba, a, r, g, b; + rgba = const_in = src[i]; + a = (rgba >> ASHIFT) & 255; + accu += a; + r = (rgba >> RSHIFT) & 255; + g = (rgba >> GSHIFT) & 255; + b = (rgba >> BSHIFT) & 255; + + r = SHIFT(r*a*257 + 32768, RSHIFT - 16); + g = SHIFT(g*a*257 + 32768, GSHIFT - 16); + b = SHIFT(b*a*257 + 32768, BSHIFT - 16); + dst[i] = const_out = + (r & RMASK) | (g & GMASK) | (b & BMASK) | (rgba & AMASK); + } + + if (i + 1 == num_pixels) + return; + + { + unsigned rgba, a, r, g, b; + rgba = src[i+1]; + a = (rgba >> ASHIFT) & 255; + accu += a; + r = (rgba >> RSHIFT) & 255; + g = (rgba >> GSHIFT) & 255; + b = (rgba >> BSHIFT) & 255; + diff = rgba ^ const_in; + + r = SHIFT(r*a*257 + 32768, RSHIFT - 16); + g = SHIFT(g*a*257 + 32768, GSHIFT - 16); + b = SHIFT(b*a*257 + 32768, BSHIFT - 16); + dst[i+1] = + (r & RMASK) | (g & GMASK) | (b & BMASK) | (rgba & AMASK); + } + + i += 2; + + /* Fall into special cases if we have special + * circumstances. */ + if (0 != (accu & diff)) + continue; + + if (0 == accu) { /* a run of solid pixels. */ + unsigned in; + while (AMASK == ((in = src[i]) & AMASK)) { + dst[i++] = in; + if (i == num_pixels) return; + } + } else if (0 == diff) { /* a run of constant pixels. */ + while (src[i] == const_in) { + dst[i++] = const_out; + if (i == num_pixels) return; + } + } + } +} + +void +_cairosdl_blit_and_unpremultiply ( + void *target_buffer, + size_t target_stride, + void const *source_buffer, + size_t source_stride, + int width, + int height) +{ + unsigned char *target_bytes = + (unsigned char *)target_buffer; + unsigned char const *source_bytes = + (unsigned char const *)source_buffer; + if (width <= 0) + return; + + while (height-- > 0) { + unpremultiply_row ((unsigned *)target_bytes, + (unsigned const *)source_bytes, + width); + + target_bytes += target_stride; + source_bytes += source_stride; + } +} + +void +_cairosdl_blit_and_premultiply ( + void *target_buffer, + size_t target_stride, + void const *source_buffer, + size_t source_stride, + int width, + int height) +{ + unsigned char *target_bytes = + (unsigned char *)target_buffer; + unsigned char const *source_bytes = + (unsigned char const *)source_buffer; + if (width <= 0) + return; + + while (height-- > 0) { + premultiply_row ((unsigned *)target_bytes, + (unsigned const *)source_bytes, + width); + + target_bytes += target_stride; + source_bytes += source_stride; + } +} diff --git a/cairosdl.c b/cairosdl.c new file mode 100644 index 0000000..27dec4a --- /dev/null +++ b/cairosdl.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2009 M Joonas Pihlaja + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include <assert.h> +#include "cairosdl.h" + +/* from cairo-unpremultiply.c */ +void +_cairosdl_blit_and_unpremultiply ( + void *target_buffer, + size_t target_stride, + void const *source_buffer, + size_t source_stride, + int width, + int height); + +/* from cairo-premultiply.c */ +void +_cairosdl_blit_and_premultiply ( + void *target_buffer, + size_t target_stride, + void const *source_buffer, + size_t source_stride, + int width, + int height); + +/* + * Surface functions + */ + +/* We're hanging the SDL_Surface as a user datum on the + * cairo_surface_t representing it using this key. */ +static cairo_user_data_key_t const CAIROSDL_TARGET_KEY[1]; + +static void +sdl_surface_destroy_func (void *param) +{ + SDL_Surface *sdl_surface = (SDL_Surface *)param; + if (sdl_surface != NULL) + SDL_FreeSurface (sdl_surface); +} + +cairo_surface_t * +cairosdl_surface_create ( + SDL_Surface *sdl_surface) +{ + cairo_surface_t *target; + cairo_format_t format; + int is_dirty; + + /* Cairo only supports a limited number of pixels formats. Make + * sure the surface format is compatible. */ + if (sdl_surface->format->BytesPerPixel != 4 || + sdl_surface->format->BitsPerPixel != 32) + goto unsupported_format; + + if (sdl_surface->format->Rmask != CAIROSDL_RMASK || + sdl_surface->format->Gmask != CAIROSDL_GMASK || + sdl_surface->format->Bmask != CAIROSDL_BMASK) + goto unsupported_format; + + switch (sdl_surface->format->Amask) { + case CAIROSDL_AMASK: + format = CAIRO_FORMAT_ARGB32; + break; + case 0: + format = CAIRO_FORMAT_RGB24; + break; + default: + goto unsupported_format; + } + + /* Make the target point to either the SDL_Surface's data itself + * or a shadow image surface if we need to unpremultiply pixels. */ + if (format == CAIRO_FORMAT_RGB24) { + /* The caller is expected to have locked the surface (_if_ it + * needs locking) so that sdl_surface->pixels is valid and + * constant for the lifetime of the cairo_surface_t. However, + * we're told not to call any OS functions when a surface is + * locked, so we really shouldn't call + * cairo_image_surface_create () as it will malloc, so really + * if the surface needs locking this shouldn't be used. + * + * However, it turns out malloc is actually safe on many (all?) + * platforms so we'll just go ahead anyway. */ + target = cairo_image_surface_create_for_data (sdl_surface->pixels, + format, + sdl_surface->w, + sdl_surface->h, + sdl_surface->pitch); + is_dirty = 0; + } + else { + /* Need a shadow image surface. */ + target = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + sdl_surface->w, + sdl_surface->h); + is_dirty = 1; + } + + if (cairo_surface_status (target) == CAIRO_STATUS_SUCCESS) { + sdl_surface->refcount++; + cairo_surface_set_user_data (target, + CAIROSDL_TARGET_KEY, + sdl_surface, + sdl_surface_destroy_func); + + if (is_dirty) + cairosdl_surface_mark_dirty (target); + } + + return target; + + unsupported_format: + /* Nasty kludge to get a cairo surface in CAIRO_INVALID_FORMAT + * state. */ + return cairo_image_surface_create ( + (cairo_format_t)-1, 0, 0); +} + +SDL_Surface * +cairosdl_surface_get_target ( + cairo_surface_t *surface) +{ + return cairo_surface_get_user_data (surface, CAIROSDL_TARGET_KEY); +} + +static cairo_status_t +_cairosdl_surface_obtain_SDL_buffer( + cairo_surface_t *surface, + unsigned char **OUT_buffer, + size_t *OUT_stride, + size_t *OUT_width, + size_t *OUT_height) +{ + SDL_Surface *sdl_surface = cairosdl_surface_get_target (surface); + if (sdl_surface == NULL) + return CAIRO_STATUS_NULL_POINTER; + + if (OUT_buffer) + *OUT_buffer = sdl_surface->pixels; + if (OUT_stride) + *OUT_stride = sdl_surface->pitch; + if (OUT_width) + *OUT_width = sdl_surface->w; + if (OUT_height) + *OUT_height = sdl_surface->h; + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairosdl_surface_obtain_shadow_buffer( + cairo_surface_t *surface, + unsigned char **OUT_buffer, + size_t *OUT_stride, + size_t *OUT_width, + size_t *OUT_height) +{ + cairo_format_t format; + + if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) + return CAIRO_STATUS_SURFACE_TYPE_MISMATCH; + + format = cairo_image_surface_get_format (surface); + if (format != CAIRO_FORMAT_ARGB32) + return CAIRO_STATUS_INVALID_FORMAT; + + if (OUT_buffer != NULL) + *OUT_buffer = cairo_image_surface_get_data (surface); + if (OUT_stride != NULL) + *OUT_stride = cairo_image_surface_get_stride (surface); + if (OUT_width != NULL) + *OUT_width = cairo_image_surface_get_width (surface); + if (OUT_height != NULL) + *OUT_height = cairo_image_surface_get_height (surface); + return CAIRO_STATUS_SUCCESS; +} + +void +cairosdl_surface_flush_rects ( + cairo_surface_t *surface, + int num_rects, + SDL_Rect const *rects) +{ + unsigned char *source_bytes; + size_t source_stride; + size_t source_width; + size_t source_height; + + unsigned char *target_bytes; + size_t target_stride; + size_t target_width; + size_t target_height; + + int width, height; + cairo_status_t status; + + if (num_rects <= 0) + return; + + cairo_surface_flush (surface); + + status = _cairosdl_surface_obtain_SDL_buffer (surface, + &target_bytes, + &target_stride, + &target_width, + &target_height); + if (status != CAIRO_STATUS_SUCCESS) + return; /* no buffer -> nothing to do */ + + status = _cairosdl_surface_obtain_shadow_buffer (surface, + &source_bytes, + &source_stride, + &source_width, + &source_height); + if (status != CAIRO_STATUS_SUCCESS) + return; /* no buffer -> nothing to do */ + + width = source_width < target_width ? source_width : target_width; + height = source_height < target_height ? source_height : target_height; + assert(width >= 0 && height >= 0); + + while (num_rects-- > 0) { + Sint32 x = rects->x; + Sint32 y = rects->y; + Sint32 w = rects->w; + Sint32 h = rects->h; + rects++; + + if (x <= 0) { w += x; x = 0; } + if (y <= 0) { h += y; y = 0; } + if (x + w >= width) w = width - x; + if (y + h >= height) h = height - y; + + _cairosdl_blit_and_unpremultiply ( + target_bytes + target_stride*y + x, target_stride, + source_bytes + source_stride*y + x, source_stride, + w, h); + } +} + +void +cairosdl_surface_mark_dirty_rects ( + cairo_surface_t *surface, + int num_rects, + SDL_Rect const *rects) +{ + unsigned char *source_bytes = NULL; + size_t source_stride = 0; + size_t source_width = 32767; + size_t source_height = 32767; + + unsigned char *target_bytes = NULL; + size_t target_stride = 0; + size_t target_width = 32767; + size_t target_height = 32767; + + int width, height; + cairo_status_t status; + int have_buffers = 1; + + if (num_rects <= 0) + return; + + status = _cairosdl_surface_obtain_SDL_buffer (surface, + &source_bytes, + &source_stride, + &source_width, + &source_height); + if (status != CAIRO_STATUS_SUCCESS) + have_buffers = 0; + + status = _cairosdl_surface_obtain_shadow_buffer (surface, + &target_bytes, + &target_stride, + &target_width, + &target_height); + if (status != CAIRO_STATUS_SUCCESS) + have_buffers = 0; + + width = source_width < target_width ? source_width : target_width; + height = source_height < target_height ? source_height : target_height; + assert(width >= 0 && height >= 0); + + while (num_rects-- > 0) { + Sint32 x = rects->x; + Sint32 y = rects->y; + Sint32 w = rects->w; + Sint32 h = rects->h; + rects++; + + if (x <= 0) { w += x; x = 0; } + if (y <= 0) { h += y; y = 0; } + if (x >= width || y >= height) continue; + if (x + w >= width) w = width - x; + if (y + h >= height) h = height - y; + if (w <= 0 || h <= 0) continue; + + if (have_buffers) { + _cairosdl_blit_and_premultiply ( + target_bytes + target_stride*y + x, target_stride, + source_bytes + source_stride*y + x, source_stride, + w, h); + } + + cairo_surface_mark_dirty_rectangle (surface, x, y, w, h); + } +} + +static SDL_Rect +make_rect(int x, int y, int w, int h) +{ + static SDL_Rect const empty_rect = {0,0,0,0}; + SDL_Rect r; + + /* Clamp the rect to [0,32768). */ + if (w <= 0 || h <= 0) + return empty_rect; + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if ((unsigned)w > 32767) w = 32767; + if ((unsigned)h > 32767) h = 32767; + + r.x = x; + r.y = y; + r.w = w; + r.h = h; + return r; +} + +void +cairosdl_surface_flush_rect ( + cairo_surface_t *surface, + int x, + int y, + int w, + int h) +{ + SDL_Rect rect = make_rect(x, y, w, h); + cairosdl_surface_flush_rects (surface, 1, &rect); +} + +void +cairosdl_surface_mark_dirty_rect ( + cairo_surface_t *surface, + int x, + int y, + int w, + int h) +{ + SDL_Rect rect = make_rect(x, y, w, h); + cairosdl_surface_mark_dirty_rects (surface, 1, &rect); +} + +void +cairosdl_surface_flush (cairo_surface_t *surface) +{ + cairosdl_surface_flush_rect (surface, 0, 0, 32767, 32767); +} + +void +cairosdl_surface_mark_dirty (cairo_surface_t *surface) +{ + cairosdl_surface_mark_dirty_rect (surface, 0, 0, 32767, 32767); +} + +/* + * Context functions for convenience. + */ + +SDL_Surface * +cairosdl_get_target (cairo_t *cr) +{ + return cairosdl_surface_get_target (cairo_get_target (cr)); +} + +cairo_t * +cairosdl_create (SDL_Surface *sdl_surface) +{ + cairo_surface_t *surface = cairosdl_surface_create (sdl_surface); + cairo_t *cr = cairo_create (surface); + cairo_surface_destroy (surface); + return cr; +} + +void +cairosdl_destroy (cairo_t *cr) +{ + cairosdl_surface_flush (cairo_get_target (cr)); + cairo_destroy (cr); +} diff --git a/cairosdl.h b/cairosdl.h new file mode 100644 index 0000000..beb190d --- /dev/null +++ b/cairosdl.h @@ -0,0 +1,112 @@ +#ifndef CAIROSDL_H +#define CAIROSDL_H +/* + * Copyright (c) 2009 M Joonas Pihlaja + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#include <cairo.h> +#include <SDL.h> + +/* All cairo and cairosdl functions expect the underlying SDL_Surface + * to be locked or not need locking. The underlying condition is that + * the ->pixels member of the SDL_Surface should be valid and not + * change during the lifetime of the cairo_surface_t representing it, + * and that malloc and whatever other OS facilities are allowed to be + * called. */ + +/* Create a cairo image surface and bind the SDL_Surface to it. If + * the pixel format of the SDL_Surface isn't supported by cairo, + * returns a surface in CAIRO_STATUS_INVALID_FORMAT error state. */ +cairo_surface_t * +cairosdl_surface_create (SDL_Surface *sdl_surface); + +/* Returns the SDL_Surface bound to the image surface. */ +SDL_Surface * +cairosdl_surface_get_target (cairo_surface_t *surface); + + +/* These functions are noops for Amask=0 surfaces. For + * Amask=0xFF000000 surfaces they write the indicated area(s) of the + * SDL_Surface bound to the surface from a backing buffer. */ +void +cairosdl_surface_flush_rects (cairo_surface_t *surface, + int num_rects, + SDL_Rect const *rects); + +void +cairosdl_surface_flush_rect (cairo_surface_t *surface, + int x, + int y, + int width, + int height); + +void +cairosdl_surface_flush (cairo_surface_t *surface); + + +/* These functions are noops for Amask=0 surfaces. For + * Amask=0xFF000000 surfaces they read the indicated area(s) from the + * SDL_Surface bound to the surface into a backing buffer. */ +void +cairosdl_surface_mark_dirty_rects (cairo_surface_t *surface, + int num_rects, + SDL_Rect const *rects); + +void +cairosdl_surface_mark_dirty_rect (cairo_surface_t *surface, + int x, + int y, + int width, + int height); + +void +cairosdl_surface_mark_dirty (cairo_surface_t *surface); + + +/* Context convenience functions. */ + +/* Equivalent to cairo_create(cairosdl_surface_create(sdl_surface)); */ +cairo_t * +cairosdl_create (SDL_Surface *sdl_surface); + +/* Equivalent to cairosdl_surface_get_target(cairo_get_target(cr)); */ +SDL_Surface * +cairosdl_get_target (cairo_t *cr); + +/* Calls cairosdl_surface_flush() on the target of the cairo context + * and then destroys the context. */ +void +cairosdl_destroy (cairo_t *cr); + + +/* Cairo pixel configuration. This isn't tweakable, it just is. */ +#define CAIROSDL_ASHIFT 24 +#define CAIROSDL_RSHIFT 16 +#define CAIROSDL_GSHIFT 8 +#define CAIROSDL_BSHIFT 0 +#define CAIROSDL_AMASK (255U << CAIROSDL_ASHIFT) +#define CAIROSDL_RMASK (255U << CAIROSDL_RSHIFT) +#define CAIROSDL_GMASK (255U << CAIROSDL_GSHIFT) +#define CAIROSDL_BMASK (255U << CAIROSDL_BSHIFT) + +#endif /* CAIROSDL_H */ |