summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorM Joonas Pihlaja <jpihlaja@cc.helsinki.fi>2009-02-15 15:10:30 +0200
committerM Joonas Pihlaja <jpihlaja@cc.helsinki.fi>2009-02-15 15:10:30 +0200
commit11ea8fbcc8634b130a22237dadf338c0d735adb1 (patch)
tree1e5a14ec69759702dc68bbda6f215af6c803db62
Import cairosdl.
-rw-r--r--README173
-rw-r--r--cairosdl-premultiply.c301
-rw-r--r--cairosdl.c412
-rw-r--r--cairosdl.h112
4 files changed, 998 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..30f8917
--- /dev/null
+++ b/README
@@ -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 */