summaryrefslogtreecommitdiff
path: root/src/cairo-vg-surface.c
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2009-07-15 16:37:25 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2009-07-17 11:50:02 +0100
commit52fa8760aeef38abbab0484a6978adaf4f100f90 (patch)
tree916eda04b5bc1bc81d8c716e5b79bcc4bea9f294 /src/cairo-vg-surface.c
parent34d5b862bce3e9273391eb5d2c384394cca94d2d (diff)
Add OpenVG backend.
Based on the work by Øyvind Kolås and Pierre Tardy -- many thanks to Pierre for pushing this backend for inclusion as well as testing and reviewing my initial patch. And many more thanks to pippin for writing the backend in the first place! Hacked and chopped by myself into a suitable basis for a backend. Quite a few issues remain open, but would seem to be ready for testing on suitable hardware.
Diffstat (limited to 'src/cairo-vg-surface.c')
-rw-r--r--src/cairo-vg-surface.c1938
1 files changed, 1938 insertions, 0 deletions
diff --git a/src/cairo-vg-surface.c b/src/cairo-vg-surface.c
new file mode 100644
index 00000000..de3d0c6f
--- /dev/null
+++ b/src/cairo-vg-surface.c
@@ -0,0 +1,1938 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2008 Opened Hand Ltd.
+ * Copyright © 2009 Chris Wilson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.og/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * Contributor(s):
+ * Pierre Tardy <tardyp@gmail.com>
+ * Øyvind Kolås <pippin@gimp.org>
+ * Vladimi Vukicevic <vladimir@mozilla.com> (stubbed out base backend)
+ * Chris Wilson <chris@chris-wilson.co.uk>
+ */
+
+#include "cairoint.h"
+
+#include "cairo-vg.h"
+
+#include "cairo-path-fixed-private.h"
+#include "cairo-meta-surface-private.h"
+#include "cairo-cache-private.h"
+
+#include <pixman.h>
+#include <VG/openvg.h>
+
+//#define OPENVG_DEBUG
+
+/*
+ * Work that needs to be done:
+ * - Glyph cache / proper font support
+ *
+ * - First-class paths
+ * Paths are expensive for OpenVG, reuse paths whenever possible.
+ * So add a path cache, and first class paths!
+ */
+
+typedef struct _cairo_vg_surface cairo_vg_surface_t;
+
+/* XXX need GL specific context control. :( */
+struct _cairo_vg_context {
+ cairo_status_t status;
+ cairo_reference_count_t ref_count;
+
+ unsigned long target_id;
+
+ VGPaint paint;
+ cairo_vg_surface_t *source;
+ double alpha;
+
+ cairo_cache_t snapshot_cache;
+
+ void *display;
+ void *context;
+
+ cairo_status_t (*create_target) (cairo_vg_context_t *,
+ cairo_vg_surface_t *);
+ cairo_status_t (*set_target) (cairo_vg_context_t *,
+ cairo_vg_surface_t *);
+ void (*destroy_target) (cairo_vg_context_t *, cairo_vg_surface_t *);
+};
+
+struct _cairo_vg_surface {
+ cairo_surface_t base;
+
+ cairo_vg_context_t *context;
+
+ VGImage image;
+ VGImageFormat format;
+ int width;
+ int height;
+ cairo_bool_t own_image;
+
+ cairo_cache_entry_t snapshot_cache_entry;
+
+ cairo_bool_t clipped;
+
+ unsigned long target_id;
+};
+
+static const cairo_surface_backend_t cairo_vg_surface_backend;
+
+slim_hidden_proto (cairo_vg_surface_create);
+
+static cairo_surface_t *
+_vg_surface_create_internal (cairo_vg_context_t *context,
+ VGImage image,
+ VGImageFormat format,
+ int width, int height);
+
+static cairo_vg_context_t *
+_vg_context_reference (cairo_vg_context_t *context)
+{
+ assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&context->ref_count));
+
+ _cairo_reference_count_inc (&context->ref_count);
+
+ return context;
+}
+
+static cairo_vg_context_t *
+_vg_context_lock (cairo_vg_context_t *context)
+{
+ /* XXX if we need to add locking, then it has to be recursive */
+ return context;
+}
+
+static cairo_int_status_t
+_vg_context_set_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+ cairo_status_t status;
+
+ if (surface->target_id == 0) {
+ status = context->create_target (context, surface);
+ if (unlikely (status))
+ return status;
+ }
+
+ if (context->target_id == surface->target_id)
+ return CAIRO_STATUS_SUCCESS;
+
+ context->target_id = surface->target_id;
+
+ return context->set_target (context, surface);
+}
+
+static void
+_vg_context_destroy_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+ if (surface->target_id == 0)
+ return;
+
+ if (context->target_id == surface->target_id)
+ context->set_target (context, NULL);
+
+ context->destroy_target (context, surface);
+}
+
+static cairo_bool_t
+_vg_snapshot_cache_can_remove (const void *entry)
+{
+ return TRUE;
+}
+
+static void
+_vg_snapshot_cache_remove (void *cache_entry)
+{
+ cairo_vg_surface_t *surface = cairo_container_of (cache_entry,
+ cairo_vg_surface_t,
+ snapshot_cache_entry);
+ surface->snapshot_cache_entry.hash = 0;
+ cairo_surface_destroy (&surface->base);
+}
+
+static cairo_status_t
+_vg_context_init (cairo_vg_context_t *context)
+{
+ cairo_status_t status;
+
+ context->status = CAIRO_STATUS_SUCCESS;
+ CAIRO_REFERENCE_COUNT_INIT (&context->ref_count, 1);
+
+ status = _cairo_cache_init (&context->snapshot_cache,
+ NULL,
+ _vg_snapshot_cache_can_remove,
+ _vg_snapshot_cache_remove,
+ 16*1024*1024);
+ if (unlikely (status))
+ return status;
+
+ context->target_id = 0;
+ context->source = NULL;
+ context->alpha = 1.0;
+
+ context->paint = vgCreatePaint ();
+ vgLoadIdentity ();
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+_vg_context_destroy (cairo_vg_context_t *context)
+{
+ assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&context->ref_count));
+
+ if (! _cairo_reference_count_dec_and_test (&context->ref_count))
+ return;
+
+ if (context->paint != VG_INVALID_HANDLE)
+ vgDestroyPaint (context->paint);
+
+ _cairo_cache_fini (&context->snapshot_cache);
+ free (context);
+}
+
+static void
+_vg_context_unlock (cairo_vg_context_t *context)
+{
+}
+
+#ifdef OPENVG_DEBUG
+static void check_vg_errors(const char*function,int line)
+{
+ int err = vgGetError();
+ if (err != VG_NO_ERROR){
+ printf("%s+%d:vgError detected: 0x%08x.\n",function, line,err);
+ assert(err == VG_NO_ERROR);
+ }
+
+}
+#define CHECK_VG_ERRORS() check_vg_errors(__FILE__,__LINE__)
+#else
+#define CHECK_VG_ERRORS() do{}while(0)
+#endif //OPENVG_DEBUG
+
+static pixman_format_code_t
+_vg_format_to_pixman (VGImageFormat format,
+ cairo_bool_t *needs_premult_fixup)
+{
+ *needs_premult_fixup = FALSE;
+ switch (format) {
+ /* RGB{A,X} channel ordering */
+ case VG_sRGBX_8888: return 0; //PIXMAN_r8g8b8x8;
+ case VG_sRGBA_8888: return 0;
+ case VG_sRGBA_8888_PRE: return 0; //PIXMAN_r8b8g8a8;
+ case VG_sRGB_565: return PIXMAN_r5g6b5;
+ case VG_sRGBA_5551: return 0;
+ case VG_sRGBA_4444: return 0;
+ case VG_sL_8: return PIXMAN_g8;
+ case VG_lRGBX_8888: return 0;
+ case VG_lRGBA_8888: return 0;
+ case VG_lRGBA_8888_PRE: return 0;
+ case VG_lL_8: return 0;
+ case VG_A_8: return PIXMAN_a8;
+ case VG_BW_1: return PIXMAN_a1;
+ case VG_A_1: return PIXMAN_a1;
+ case VG_A_4: return PIXMAN_a4;
+
+ /* {A,X}RGB channel ordering */
+ case VG_sXRGB_8888: return PIXMAN_x8r8g8b8;
+ case VG_sARGB_8888: *needs_premult_fixup = TRUE; return PIXMAN_a8r8g8b8;
+ case VG_sARGB_8888_PRE: return PIXMAN_a8r8g8b8;
+ case VG_sARGB_1555: return 0;
+ case VG_sARGB_4444: return 0;
+ case VG_lXRGB_8888: return 0;
+ case VG_lARGB_8888: return 0;
+ case VG_lARGB_8888_PRE: return 0;
+
+ /* BGR{A,X} channel ordering */
+ case VG_sBGRX_8888: return PIXMAN_b8g8r8x8;
+ case VG_sBGRA_8888: *needs_premult_fixup = TRUE; return PIXMAN_b8g8r8a8;
+ case VG_sBGRA_8888_PRE: return PIXMAN_b8g8r8a8;
+ case VG_sBGR_565: return PIXMAN_b5g6r5;
+ case VG_sBGRA_5551: return 0;
+ case VG_sBGRA_4444: return 0;
+ case VG_lBGRX_8888: return 0;
+ case VG_lBGRA_8888: return 0;
+ case VG_lBGRA_8888_PRE: return 0;
+
+ /* {A,X}BGR channel ordering */
+ case VG_sXBGR_8888: return PIXMAN_x8b8g8r8;
+ case VG_sABGR_8888: *needs_premult_fixup = TRUE; return PIXMAN_a8b8g8r8;
+ case VG_sABGR_8888_PRE: return PIXMAN_a8b8g8r8;
+ case VG_sABGR_1555: return 0;
+ case VG_sABGR_4444: return 0;
+ case VG_lXBGR_8888: return 0;
+ case VG_lABGR_8888: return 0;
+ case VG_lABGR_8888_PRE: return 0;
+ default: return 0;
+ }
+}
+
+static pixman_format_code_t
+_vg_format_to_content (VGImageFormat format)
+{
+ /* XXX could use more simple bit tests */
+ switch (format) {
+ /* RGB{A,X} channel ordering */
+ case VG_sRGBX_8888: return CAIRO_CONTENT_COLOR;
+ case VG_sRGBA_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sRGBA_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sRGB_565: return CAIRO_CONTENT_COLOR;
+ case VG_sRGBA_5551: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sRGBA_4444: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sL_8: return CAIRO_CONTENT_ALPHA;
+ case VG_lRGBX_8888: return CAIRO_CONTENT_COLOR;
+ case VG_lRGBA_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lRGBA_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lL_8: return CAIRO_CONTENT_ALPHA;
+ case VG_A_8: return CAIRO_CONTENT_ALPHA;
+ case VG_A_4: return CAIRO_CONTENT_ALPHA;
+ case VG_A_1: return CAIRO_CONTENT_ALPHA;
+ case VG_BW_1: return CAIRO_CONTENT_ALPHA;
+
+ /* {A,X}RGB channel ordering */
+ case VG_sXRGB_8888: return CAIRO_CONTENT_COLOR;
+ case VG_sARGB_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sARGB_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sARGB_1555: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sARGB_4444: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lXRGB_8888: return CAIRO_CONTENT_COLOR;
+ case VG_lARGB_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lARGB_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+
+ /* BGR{A,X} channel ordering */
+ case VG_sBGRX_8888: return CAIRO_CONTENT_COLOR;
+ case VG_sBGRA_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sBGRA_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sBGR_565: return CAIRO_CONTENT_COLOR;
+ case VG_sBGRA_5551: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sBGRA_4444: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lBGRX_8888: return CAIRO_CONTENT_COLOR;
+ case VG_lBGRA_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lBGRA_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+
+ /* {A,X}BGR channel ordering */
+ case VG_sXBGR_8888: return CAIRO_CONTENT_COLOR;
+ case VG_sABGR_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sABGR_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sABGR_1555: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_sABGR_4444: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lXBGR_8888: return CAIRO_CONTENT_COLOR;
+ case VG_lABGR_8888: return CAIRO_CONTENT_COLOR_ALPHA;
+ case VG_lABGR_8888_PRE: return CAIRO_CONTENT_COLOR_ALPHA;
+ default: return 0;
+ }
+}
+
+static VGImageFormat
+_vg_format_from_pixman (pixman_format_code_t format)
+{
+ /* XXX _PRE needs fixup */
+ switch ((int) format) {
+ case PIXMAN_r5g6b5: return VG_sRGB_565;
+ case PIXMAN_g8: return VG_sL_8;
+ case PIXMAN_a8: return VG_A_8;
+ case PIXMAN_a1: return VG_BW_1;
+ case PIXMAN_x8r8g8b8: return VG_sXRGB_8888;
+ case PIXMAN_a8r8g8b8: return VG_sARGB_8888; // _PRE
+ case PIXMAN_b8g8r8x8: return VG_sBGRX_8888;
+ case PIXMAN_b8g8r8a8: return VG_sBGRA_8888; // _PRE
+ case PIXMAN_b5g6r5: return VG_sBGR_565;
+ case PIXMAN_x8b8g8r8: return VG_sXBGR_8888;
+ case PIXMAN_a8b8g8r8: return VG_sABGR_8888; // _PRE
+ default: return 0;
+ }
+}
+
+static VGImageFormat
+_vg_format_for_content (cairo_content_t content)
+{
+ switch (content) {
+ case CAIRO_CONTENT_ALPHA: return VG_A_8;
+ case CAIRO_CONTENT_COLOR: return VG_sXRGB_8888;
+ default: ASSERT_NOT_REACHED;
+ case CAIRO_CONTENT_COLOR_ALPHA: return VG_sARGB_8888; // _PRE
+ }
+}
+
+static cairo_surface_t *
+_vg_surface_create_similar (void *abstract_surface,
+ cairo_content_t content,
+ int width,
+ int height)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+
+ if (width > vgGeti (VG_MAX_IMAGE_WIDTH) ||
+ height > vgGeti (VG_MAX_IMAGE_HEIGHT))
+ {
+ return NULL;
+ }
+
+ return cairo_vg_surface_create (surface->context, content, width, height);
+}
+
+static cairo_int_status_t
+_vg_surface_intersect_clip_path (void *abstract_surface,
+ cairo_path_fixed_t *path,
+ cairo_fill_rule_t fill_rule,
+ double tolerance,
+ cairo_antialias_t antialias)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_vg_surface_t *mask;
+ cairo_rectangle_int_t extents, clip_extents;
+ cairo_solid_pattern_t white;
+ cairo_status_t status;
+
+ if (path == NULL) {
+ surface->clipped = FALSE;
+ vgMask (VG_INVALID_HANDLE,
+ VG_FILL_MASK, 0, 0, surface->width, surface->height);
+ vgSeti (VG_MASKING, VG_FALSE);
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ extents.x = extents.y = 0;
+ extents.width = surface->width;
+ extents.height = surface->height;
+ _cairo_path_fixed_approximate_clip_extents (path, &clip_extents);
+ if (! _cairo_rectangle_intersect (&clip_extents, &extents))
+ surface->clipped = TRUE;
+
+ if (surface->clipped)
+ return CAIRO_STATUS_SUCCESS;
+
+ mask = (cairo_vg_surface_t *)
+ _vg_surface_create_similar (surface, CAIRO_CONTENT_ALPHA,
+ surface->width, surface->height);
+ if (unlikely (mask == NULL))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+ if (unlikely (mask->base.status))
+ return mask->base.status;
+
+ _cairo_pattern_init_solid (&white, CAIRO_COLOR_WHITE, CAIRO_CONTENT_ALPHA);
+ status = _cairo_surface_fill (&mask->base,
+ CAIRO_OPERATOR_SOURCE, &white.base,
+ path, fill_rule, tolerance, antialias,
+ NULL);
+ if (status) {
+ cairo_surface_destroy (&mask->base);
+ return status;
+ }
+
+ vgSeti (VG_MASKING, VG_TRUE);
+ vgMask (mask->image, VG_INTERSECT_MASK, 0, 0, mask->width, mask->height);
+
+ cairo_surface_destroy (&mask->base);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_vg_surface_get_extents (void *abstract_surface,
+ cairo_rectangle_int_t *extents)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+
+ extents->x = 0;
+ extents->y = 0;
+ extents->width = surface->width;
+ extents->height = surface->height;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+#define MAX_SEG 16 /* max number of knots to upload in a batch */
+
+typedef struct _vg_path {
+ VGPath path;
+ cairo_matrix_t *ctm_inverse;
+
+ VGubyte gseg[MAX_SEG];
+ VGfloat gdata[MAX_SEG*3*2];
+ int dcount;
+ int scount;
+} vg_path_t;
+
+static cairo_status_t
+_vg_move_to (void *closure,
+ const cairo_point_t *point)
+{
+ vg_path_t *path = closure;
+ double x = _cairo_fixed_to_double (point->x);
+ double y = _cairo_fixed_to_double (point->y);
+
+ if (path->ctm_inverse)
+ cairo_matrix_transform_point (path->ctm_inverse, &x, &y);
+
+ path->gseg[path->scount++] = VG_MOVE_TO;
+ path->gdata[path->dcount++] = x;
+ path->gdata[path->dcount++] = y;
+
+ if (path->scount >= MAX_SEG-1) {
+ vgAppendPathData (path->path, path->scount, path->gseg, path->gdata);
+ path->scount = 0;
+ path->dcount = 0;
+ }
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_vg_line_to (void *closure,
+ const cairo_point_t *point)
+{
+ vg_path_t *path = closure;
+ double x = _cairo_fixed_to_double (point->x);
+ double y = _cairo_fixed_to_double (point->y);
+
+ if (path->ctm_inverse)
+ cairo_matrix_transform_point (path->ctm_inverse, &x, &y);
+
+ path->gseg[path->scount++] = VG_LINE_TO;
+ path->gdata[path->dcount++] = x;
+ path->gdata[path->dcount++] = y;
+
+ if (path->scount >= MAX_SEG-1) {
+ vgAppendPathData (path->path, path->scount, path->gseg, path->gdata);
+ path->scount = 0;
+ path->dcount = 0;
+ }
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_vg_curve_to (void *closure,
+ const cairo_point_t *p0,
+ const cairo_point_t *p1,
+ const cairo_point_t *p2)
+{
+ vg_path_t *path = closure;
+ double x0 = _cairo_fixed_to_double (p0->x);
+ double y0 = _cairo_fixed_to_double (p0->y);
+ double x1 = _cairo_fixed_to_double (p1->x);
+ double y1 = _cairo_fixed_to_double (p1->y);
+ double x2 = _cairo_fixed_to_double (p2->x);
+ double y2 = _cairo_fixed_to_double (p2->y);
+
+ if (path->ctm_inverse) {
+ cairo_matrix_transform_point (path->ctm_inverse, &x0, &y0);
+ cairo_matrix_transform_point (path->ctm_inverse, &x1, &y1);
+ cairo_matrix_transform_point (path->ctm_inverse, &x2, &y2);
+ }
+
+ path->gseg[path->scount++] = VG_CUBIC_TO;
+ path->gdata[path->dcount++] = x0;
+ path->gdata[path->dcount++] = y0;
+ path->gdata[path->dcount++] = x1;
+ path->gdata[path->dcount++] = y1;
+ path->gdata[path->dcount++] = x2;
+ path->gdata[path->dcount++] = y2;
+
+ if (path->scount >= MAX_SEG-1) {
+ vgAppendPathData(path->path, path->scount, path->gseg, path->gdata);
+ path->scount = 0;
+ path->dcount = 0;
+ }
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_vg_close_path (void *closure)
+{
+ vg_path_t *path = closure;
+
+ path->gseg[path->scount++] = VG_CLOSE_PATH;
+
+ if (path->scount >= MAX_SEG-1) {
+ vgAppendPathData (path->path, path->scount, path->gseg, path->gdata);
+ path->scount = 0;
+ path->dcount = 0;
+ }
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+_vg_path_from_cairo (vg_path_t *vg_path,
+ cairo_path_fixed_t *path)
+{
+ cairo_status_t status;
+
+ vg_path->scount = 0;
+ vg_path->dcount = 0;
+
+ status = _cairo_path_fixed_interpret (path,
+ CAIRO_DIRECTION_FORWARD,
+ _vg_move_to,
+ _vg_line_to,
+ _vg_curve_to,
+ _vg_close_path,
+ vg_path);
+ assert (status == CAIRO_STATUS_SUCCESS);
+
+ vgAppendPathData (vg_path->path,
+ vg_path->scount, vg_path->gseg, vg_path->gdata);
+ CHECK_VG_ERRORS();
+}
+
+static cairo_bool_t
+_vg_is_supported_operator (cairo_operator_t op)
+{
+ switch ((int) op) {
+ case CAIRO_OPERATOR_SOURCE:
+ case CAIRO_OPERATOR_OVER:
+ case CAIRO_OPERATOR_IN:
+ case CAIRO_OPERATOR_DEST_OVER:
+ case CAIRO_OPERATOR_DEST_IN:
+ case CAIRO_OPERATOR_ADD:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static VGBlendMode
+_vg_operator (cairo_operator_t op)
+{
+ switch ((int) op) {
+ case CAIRO_OPERATOR_SOURCE:
+ return VG_BLEND_SRC;
+ case CAIRO_OPERATOR_OVER:
+ return VG_BLEND_SRC_OVER;
+ case CAIRO_OPERATOR_IN:
+ return VG_BLEND_SRC_IN;
+ case CAIRO_OPERATOR_DEST_OVER:
+ return VG_BLEND_DST_OVER;
+ case CAIRO_OPERATOR_DEST_IN:
+ return VG_BLEND_DST_IN;
+ case CAIRO_OPERATOR_ADD:
+ return VG_BLEND_ADDITIVE;
+ default:
+ ASSERT_NOT_REACHED;
+ return VG_BLEND_SRC_OVER;
+ }
+}
+
+static VGFillRule
+_vg_fill_rule_from_cairo (cairo_fill_rule_t rule)
+{
+ switch (rule) {
+ case CAIRO_FILL_RULE_EVEN_ODD: return VG_EVEN_ODD;
+ case CAIRO_FILL_RULE_WINDING: return VG_NON_ZERO;
+ }
+
+ ASSERT_NOT_REACHED;
+ return VG_NON_ZERO;
+}
+
+static VGRenderingQuality
+_vg_rendering_quality_from_cairo (cairo_antialias_t aa)
+{
+ switch (aa) {
+ case CAIRO_ANTIALIAS_DEFAULT:
+ case CAIRO_ANTIALIAS_SUBPIXEL:
+ return VG_RENDERING_QUALITY_BETTER;
+
+ case CAIRO_ANTIALIAS_GRAY:
+ return VG_RENDERING_QUALITY_FASTER;
+
+ case CAIRO_ANTIALIAS_NONE:
+ return VG_RENDERING_QUALITY_NONANTIALIASED;
+ }
+
+ ASSERT_NOT_REACHED;
+ return VG_RENDERING_QUALITY_BETTER;
+}
+
+static VGCapStyle
+_vg_line_cap_from_cairo (cairo_line_cap_t cap)
+{
+ switch (cap) {
+ case CAIRO_LINE_CAP_BUTT: return VG_CAP_BUTT;
+ case CAIRO_LINE_CAP_ROUND: return VG_CAP_ROUND;
+ case CAIRO_LINE_CAP_SQUARE: return VG_CAP_SQUARE;
+ }
+
+ ASSERT_NOT_REACHED;
+ return VG_CAP_BUTT;
+}
+
+static VGJoinStyle
+_vg_line_join_from_cairo (cairo_line_join_t join)
+{
+ switch (join) {
+ case CAIRO_LINE_JOIN_MITER: return VG_JOIN_MITER;
+ case CAIRO_LINE_JOIN_ROUND: return VG_JOIN_ROUND;
+ case CAIRO_LINE_JOIN_BEVEL: return VG_JOIN_BEVEL;
+ }
+
+ ASSERT_NOT_REACHED;
+ return VG_JOIN_MITER;
+}
+
+static void
+_vg_matrix_from_cairo (VGfloat *dst, const cairo_matrix_t *src)
+{
+ dst[0] = /* sx */ src->xx;
+ dst[1] = /* shy */ src->yx;
+ dst[2] = /* w0 */ 0;
+ dst[3] = /* shx */ src->xy;
+ dst[4] = /* sy */ src->yy;
+ dst[5] = /* w1 */ 0;
+ dst[6] = /* tx */ src->x0;
+ dst[7] = /* ty */ src->y0;
+ dst[8] = /* w2 */ 0;
+}
+
+static cairo_status_t
+_vg_setup_gradient_stops (cairo_vg_context_t *context,
+ const cairo_gradient_pattern_t *pattern)
+{
+ VGint numstops = pattern->n_stops;
+ VGfloat *stops, stack_stops[CAIRO_STACK_ARRAY_LENGTH (VGfloat)];
+ int i;
+
+ if (numstops*5 < ARRAY_LENGTH (stack_stops)) {
+ stops = stack_stops;
+ } else {
+ stops = _cairo_malloc_ab (numstops, 5*sizeof (VGfloat));
+ if (unlikely (stops == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ for (i = 0; i < numstops; i++) {
+ stops[i*5 + 0] = pattern->stops[i].offset;
+ stops[i*5 + 1] = pattern->stops[i].color.red;
+ stops[i*5 + 2] = pattern->stops[i].color.green;
+ stops[i*5 + 3] = pattern->stops[i].color.blue;
+ stops[i*5 + 4] = pattern->stops[i].color.alpha * context->alpha;
+ }
+
+ vgSetParameterfv (context->paint,
+ VG_PAINT_COLOR_RAMP_STOPS, numstops * 5, stops);
+
+ if (stops != stack_stops)
+ free (stops);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+_vg_set_source_matrix (const cairo_pattern_t *pat)
+{
+ cairo_matrix_t mat;
+ cairo_status_t status;
+ VGfloat vmat[9];
+
+ mat = pat->matrix;
+ status = cairo_matrix_invert (&mat);
+ assert (status == CAIRO_STATUS_SUCCESS);
+
+ _vg_matrix_from_cairo (vmat, &mat);
+
+ vgSeti (VG_MATRIX_MODE, VG_MATRIX_FILL_PAINT_TO_USER);
+ vgLoadMatrix (vmat);
+ vgSeti (VG_MATRIX_MODE, VG_MATRIX_STROKE_PAINT_TO_USER);
+ vgLoadMatrix (vmat);
+ vgSeti (VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
+
+ CHECK_VG_ERRORS();
+}
+
+static cairo_status_t
+_vg_setup_linear_source (cairo_vg_context_t *context,
+ const cairo_linear_pattern_t *lpat)
+{
+ VGfloat linear[4];
+
+ linear[0] = _cairo_fixed_to_double (lpat->p1.x);
+ linear[1] = _cairo_fixed_to_double (lpat->p1.y);
+ linear[2] = _cairo_fixed_to_double (lpat->p2.x);
+ linear[3] = _cairo_fixed_to_double (lpat->p2.y);
+
+ vgSetParameteri (context->paint,
+ VG_PAINT_COLOR_RAMP_SPREAD_MODE,
+ VG_COLOR_RAMP_SPREAD_PAD);
+ vgSetParameteri (context->paint,
+ VG_PAINT_TYPE,
+ VG_PAINT_TYPE_LINEAR_GRADIENT);
+ vgSetParameterfv (context->paint,
+ VG_PAINT_LINEAR_GRADIENT, 4, linear);
+
+ _vg_set_source_matrix (&lpat->base.base);
+
+ CHECK_VG_ERRORS();
+ return _vg_setup_gradient_stops (context, &lpat->base);
+
+}
+
+static cairo_status_t
+_vg_setup_radial_source (cairo_vg_context_t *context,
+ cairo_radial_pattern_t *rpat)
+{
+ VGfloat radial[5];
+
+ radial[0] = _cairo_fixed_to_double (rpat->c1.x);
+ radial[1] = _cairo_fixed_to_double (rpat->c1.y);
+ radial[2] = _cairo_fixed_to_double (rpat->c2.x);
+ radial[3] = _cairo_fixed_to_double (rpat->c2.y);
+ radial[4] = _cairo_fixed_to_double (rpat->r2);
+
+ vgSetParameteri (context->paint,
+ VG_PAINT_COLOR_RAMP_SPREAD_MODE, VG_COLOR_RAMP_SPREAD_PAD);
+ vgSetParameteri (context->paint,
+ VG_PAINT_TYPE, VG_PAINT_TYPE_RADIAL_GRADIENT);
+ vgSetParameterfv (context->paint,
+ VG_PAINT_RADIAL_GRADIENT, 5, radial);
+
+ _vg_set_source_matrix (&rpat->base.base);
+
+ /* FIXME: copy/adapt fixes from SVG backend to add inner radius */
+
+ CHECK_VG_ERRORS();
+ return _vg_setup_gradient_stops (context, &rpat->base);
+}
+
+static cairo_status_t
+_vg_setup_solid_source (cairo_vg_context_t *context,
+ cairo_solid_pattern_t *spat)
+{
+ VGfloat color[] = {
+ spat->color.red,
+ spat->color.green,
+ spat->color.blue,
+ spat->color.alpha * context->alpha
+ };
+
+ vgSetParameteri (context->paint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
+ vgSetParameterfv (context->paint, VG_PAINT_COLOR, 4, color);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_vg_surface_t *
+_vg_clone_meta_surface (cairo_vg_context_t *context,
+ cairo_surface_t *surface)
+{
+ VGImage vg_image;
+ VGImageFormat format;
+ cairo_status_t status;
+ cairo_rectangle_int_t extents;
+ cairo_vg_surface_t *clone;
+
+ status = _cairo_surface_get_extents (surface, &extents);
+ if (status)
+ return NULL;
+
+ if (extents.width > vgGeti (VG_MAX_IMAGE_WIDTH) ||
+ extents.height > vgGeti (VG_MAX_IMAGE_HEIGHT))
+ {
+ return NULL;
+ }
+
+ format = _vg_format_for_content (surface->content);
+
+ /* NONALIASED, FASTER, BETTER */
+ vg_image = vgCreateImage (format,
+ extents.width, extents.height,
+ VG_IMAGE_QUALITY_FASTER);
+ clone = (cairo_vg_surface_t *)
+ _vg_surface_create_internal (context, vg_image, format,
+ extents.width, extents.height);
+ cairo_surface_set_device_offset (&clone->base, -extents.x, -extents.y);
+
+ status = cairo_meta_surface_replay (surface, &clone->base);
+ if (unlikely (status)) {
+ cairo_surface_destroy (&clone->base);
+ return (cairo_vg_surface_t *) _cairo_surface_create_in_error (status);
+ }
+
+ return clone;
+}
+
+static cairo_vg_surface_t *
+_vg_clone_image_surface (cairo_vg_context_t *context,
+ cairo_surface_t *surface)
+{
+ cairo_image_surface_t *image;
+ void *image_extra;
+ cairo_status_t status;
+ VGImage vg_image;
+ VGImageFormat format;
+ cairo_rectangle_int_t extents;
+ cairo_vg_surface_t *clone;
+
+ if (surface->backend->acquire_source_image == NULL)
+ return NULL;
+
+ status = _cairo_surface_get_extents (surface, &extents);
+ if (status)
+ return NULL;
+
+ if (extents.width > vgGeti (VG_MAX_IMAGE_WIDTH) ||
+ extents.height > vgGeti (VG_MAX_IMAGE_HEIGHT))
+ {
+ return NULL;
+ }
+
+ status = _cairo_surface_acquire_source_image (surface,
+ &image, &image_extra);
+ if (unlikely (status))
+ return (cairo_vg_surface_t *) _cairo_surface_create_in_error (status);
+
+ format = _vg_format_from_pixman (image->pixman_format);
+ if (format == 0)
+ format = _vg_format_for_content (image->base.content);
+
+ /* NONALIASED, FASTER, BETTER */
+ vg_image = vgCreateImage (format,
+ image->width, image->height,
+ VG_IMAGE_QUALITY_FASTER);
+ clone = (cairo_vg_surface_t *)
+ _vg_surface_create_internal (context, vg_image, format,
+ image->width, image->height);
+ if (unlikely (clone->base.status))
+ return clone;
+
+ vgImageSubData (clone->image,
+ image->data, image->stride,
+ format, 0, 0, image->width, image->height);
+
+ _cairo_surface_release_source_image (surface, image, image_extra);
+
+ return clone;
+}
+
+static void
+_vg_surface_remove_from_cache (cairo_surface_t *abstract_surface)
+{
+ cairo_vg_surface_t *surface = (cairo_vg_surface_t *) abstract_surface;
+
+ if (surface->snapshot_cache_entry.hash) {
+ cairo_vg_context_t *context;
+
+ context = _vg_context_lock (surface->context);
+ _cairo_cache_remove (&context->snapshot_cache,
+ &surface->snapshot_cache_entry);
+ _vg_context_unlock (context);
+
+ surface->snapshot_cache_entry.hash = 0;
+ }
+}
+
+static cairo_status_t
+_vg_setup_surface_source (cairo_vg_context_t *context,
+ cairo_surface_pattern_t *spat)
+{
+ cairo_surface_t *snapshot;
+ cairo_vg_surface_t *clone;
+ cairo_status_t status;
+
+ snapshot = _cairo_surface_has_snapshot (spat->surface,
+ &cairo_vg_surface_backend,
+ spat->surface->content);
+ if (snapshot != NULL) {
+ clone = (cairo_vg_surface_t *) cairo_surface_reference (snapshot);
+ goto DONE;
+ }
+
+ if (_cairo_surface_is_meta (spat->surface))
+ clone = _vg_clone_meta_surface (context, spat->surface);
+ else
+ clone = _vg_clone_image_surface (context, spat->surface);
+ if (clone == NULL)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+ if (unlikely (clone->base.status))
+ return clone->base.status;
+
+ clone->snapshot_cache_entry.hash = clone->base.unique_id;
+ status = _cairo_cache_insert (&context->snapshot_cache,
+ &clone->snapshot_cache_entry);
+ if (unlikely (status)) {
+ clone->snapshot_cache_entry.hash = 0;
+ cairo_surface_destroy (&clone->base);
+ return status;
+ }
+
+ status = _cairo_surface_attach_snapshot (spat->surface, &clone->base,
+ _vg_surface_remove_from_cache);
+ if (unlikely (status)) {
+ cairo_surface_destroy (&clone->base);
+ return status;
+ }
+
+DONE:
+ cairo_surface_destroy (&context->source->base);
+ context->source = clone;
+
+ vgSetParameteri (context->paint, VG_PAINT_TYPE, VG_PAINT_TYPE_PATTERN);
+
+ switch (spat->base.extend) {
+ case CAIRO_EXTEND_PAD:
+ vgSetParameteri (context->paint,
+ VG_PAINT_PATTERN_TILING_MODE,
+ VG_TILE_PAD);
+ break;
+
+ case CAIRO_EXTEND_NONE:
+ vgSetParameteri (context->paint,
+ VG_PAINT_PATTERN_TILING_MODE,
+ VG_TILE_FILL);
+ {
+ VGfloat color[] = {0,0,0,0};
+ vgSetfv (VG_TILE_FILL_COLOR, 4, color);
+ }
+ break;
+
+ case CAIRO_EXTEND_REPEAT:
+ vgSetParameteri (context->paint,
+ VG_PAINT_PATTERN_TILING_MODE,
+ VG_TILE_REPEAT);
+ break;
+
+ default:
+ ASSERT_NOT_REACHED;
+ case CAIRO_EXTEND_REFLECT:
+ vgSetParameteri (context->paint,
+ VG_PAINT_PATTERN_TILING_MODE,
+ VG_TILE_REFLECT);
+ break;
+ }
+ vgPaintPattern (context->paint, context->source->image);
+
+ _vg_set_source_matrix (&spat->base);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+setup_source (cairo_vg_context_t *context,
+ const cairo_pattern_t *source)
+{
+ switch (source->type) {
+ case CAIRO_PATTERN_TYPE_SOLID:
+ return _vg_setup_solid_source (context,
+ (cairo_solid_pattern_t *) source);
+ case CAIRO_PATTERN_TYPE_LINEAR:
+ return _vg_setup_linear_source (context,
+ (cairo_linear_pattern_t *) source);
+ case CAIRO_PATTERN_TYPE_RADIAL:
+ return _vg_setup_radial_source (context,
+ (cairo_radial_pattern_t *) source);
+ case CAIRO_PATTERN_TYPE_SURFACE:
+ return _vg_setup_surface_source (context,
+ (cairo_surface_pattern_t *) source);
+ default:
+ ASSERT_NOT_REACHED;
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+ }
+}
+
+static cairo_int_status_t
+_vg_surface_stroke (void *abstract_surface,
+ cairo_operator_t op,
+ const cairo_pattern_t*source,
+ cairo_path_fixed_t *path,
+ cairo_stroke_style_t *style,
+ cairo_matrix_t *ctm,
+ cairo_matrix_t *ctm_inverse,
+ double tolerance,
+ cairo_antialias_t antialias,
+ cairo_rectangle_int_t *extents)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_vg_context_t *context;
+ cairo_status_t status;
+ VGfloat state[9];
+ VGfloat strokeTransform[9];
+ vg_path_t vg_path;
+
+ if (surface->clipped)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (! _vg_is_supported_operator (op))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ context = _vg_context_lock (surface->context);
+ status = _vg_context_set_target (context, surface);
+ if (status) {
+ _vg_context_unlock (context);
+ return status;
+ }
+
+ status = setup_source (context, source);
+ if (status) {
+ _vg_context_unlock (context);
+ return status;
+ }
+
+ vg_path.path = vgCreatePath (VG_PATH_FORMAT_STANDARD,
+ VG_PATH_DATATYPE_F,
+ 1, 0, 0, 0,
+ VG_PATH_CAPABILITY_ALL);
+
+ vgGetMatrix (state);
+ _vg_matrix_from_cairo (strokeTransform, ctm);
+ vgMultMatrix (strokeTransform);
+
+ vg_path.ctm_inverse = ctm_inverse;
+
+ _vg_path_from_cairo (&vg_path, path);
+
+ /* XXX DASH_PATTERN, DASH_PHASE */
+ vgSetf (VG_STROKE_LINE_WIDTH, style->line_width);
+ vgSetf (VG_STROKE_MITER_LIMIT, style->miter_limit);
+ vgSetf (VG_STROKE_JOIN_STYLE, _vg_line_join_from_cairo (style->line_join));
+ vgSetf (VG_STROKE_CAP_STYLE, _vg_line_cap_from_cairo (style->line_cap));
+
+ vgSeti (VG_BLEND_MODE, _vg_operator (op));
+
+ vgSetPaint (context->paint, VG_STROKE_PATH);
+
+ vgDrawPath (vg_path.path, VG_STROKE_PATH);
+
+ vgDestroyPath (vg_path.path);
+
+ vgLoadMatrix (state);
+
+ CHECK_VG_ERRORS();
+ _vg_context_unlock (context);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_vg_surface_fill (void *abstract_surface,
+ cairo_operator_t op,
+ const cairo_pattern_t *source,
+ cairo_path_fixed_t *path,
+ cairo_fill_rule_t fill_rule,
+ double tolerance,
+ cairo_antialias_t antialias,
+ cairo_rectangle_int_t *extents)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_vg_context_t *context;
+ cairo_status_t status;
+ vg_path_t vg_path;
+
+ if (surface->clipped)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (op == CAIRO_OPERATOR_DEST)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (! _vg_is_supported_operator (op))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ context = _vg_context_lock (surface->context);
+ status = _vg_context_set_target (context, surface);
+ if (status) {
+ _vg_context_unlock (context);
+ return status;
+ }
+
+ status = setup_source (context, source);
+ if (status) {
+ _vg_context_unlock (context);
+ return status;
+ }
+
+ vg_path.path = vgCreatePath (VG_PATH_FORMAT_STANDARD,
+ VG_PATH_DATATYPE_F,
+ 1, 0,
+ 0, 0,
+ VG_PATH_CAPABILITY_ALL);
+ vg_path.ctm_inverse = NULL;
+
+ _vg_path_from_cairo (&vg_path, path);
+
+ /* XXX tolerance */
+
+ vgSeti (VG_BLEND_MODE, _vg_operator (op));
+ vgSetf (VG_FILL_RULE, _vg_fill_rule_from_cairo (fill_rule));
+ vgSetf (VG_RENDERING_QUALITY, _vg_rendering_quality_from_cairo (antialias));
+
+ vgSetPaint (context->paint, VG_FILL_PATH);
+
+ vgDrawPath (vg_path.path, VG_FILL_PATH);
+
+ vgDestroyPath (vg_path.path);
+
+ _vg_context_unlock (context);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_vg_surface_paint (void *abstract_surface,
+ cairo_operator_t op,
+ const cairo_pattern_t *source,
+ cairo_rectangle_int_t *extents)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_vg_context_t *context;
+ cairo_status_t status;
+
+ if (surface->clipped)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (op == CAIRO_OPERATOR_DEST)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (! _vg_is_supported_operator (op))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ context = _vg_context_lock (surface->context);
+ status = _vg_context_set_target (context, surface);
+ if (status) {
+ _vg_context_unlock (context);
+ return status;
+ }
+
+ status = setup_source (context, source);
+ if (status) {
+ _vg_context_unlock (context);
+ return status;
+ }
+
+ vgSeti (VG_BLEND_MODE, _vg_operator (op));
+ vgSetPaint (context->paint, VG_FILL_PATH);
+
+ { /* creating a rectangular path that should cover the extent */
+ VGubyte segs[] = {
+ VG_MOVE_TO_ABS, VG_LINE_TO_ABS,
+ VG_LINE_TO_ABS, VG_LINE_TO_ABS,
+ VG_CLOSE_PATH
+ };
+ VGfloat data[] = {
+ 0, 0,
+ surface->width, 0,
+ surface->width, surface->height,
+ 0, surface->height
+ };
+ VGPath fullext;
+
+ fullext = vgCreatePath (VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F,
+ 1,0,0,0, VG_PATH_CAPABILITY_ALL);
+ vgAppendPathData (fullext, sizeof(segs), segs, data);
+
+ vgDrawPath (fullext, VG_FILL_PATH);
+
+ vgDestroyPath (fullext);
+ }
+
+ _vg_context_unlock (context);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_vg_surface_mask (void *abstract_surface,
+ cairo_operator_t op,
+ const cairo_pattern_t *source,
+ const cairo_pattern_t *mask,
+ cairo_rectangle_int_t *extents)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_status_t status;
+
+ if (surface->clipped)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (! _vg_is_supported_operator (op))
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ /* Handle paint-with-alpha to do fades cheaply */
+ if (mask->type == CAIRO_PATTERN_TYPE_SOLID) {
+ cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) mask;
+ cairo_vg_context_t *context = _vg_context_lock (surface->context);
+ double alpha = context->alpha;
+
+ context->alpha = solid->color.alpha;
+ status = _vg_surface_paint (abstract_surface, op, source, extents);
+ context->alpha = alpha;
+
+ _vg_context_unlock (context);
+
+ return status;
+ }
+
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+}
+
+static void
+_vg_surface_get_font_options (void *abstract_surface,
+ cairo_font_options_t *options)
+{
+ _cairo_font_options_init_default (options);
+
+ cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON);
+}
+
+static cairo_int_status_t
+_vg_surface_show_glyphs (void *abstract_surface,
+ cairo_operator_t op,
+ const cairo_pattern_t *source,
+ cairo_glyph_t *glyphs,
+ int num_glyphs,
+ cairo_scaled_font_t *scaled_font,
+ int *remaining_glyphs,
+ cairo_rectangle_int_t *extents)
+{
+ cairo_status_t status = CAIRO_STATUS_SUCCESS;
+ cairo_path_fixed_t path;
+
+ if (num_glyphs <= 0)
+ return CAIRO_STATUS_SUCCESS;
+
+ _cairo_path_fixed_init (&path);
+
+ /* XXX Glyph cache! OpenVG font support in 1.1? */
+
+ status = _cairo_scaled_font_glyph_path (scaled_font,
+ glyphs, num_glyphs,
+ &path);
+ if (unlikely (status))
+ goto BAIL;
+
+ status = _vg_surface_fill (abstract_surface,
+ op, source, &path,
+ CAIRO_FILL_RULE_WINDING,
+ CAIRO_GSTATE_TOLERANCE_DEFAULT,
+ CAIRO_ANTIALIAS_SUBPIXEL,
+ extents);
+BAIL:
+ _cairo_path_fixed_fini (&path);
+ return status;
+}
+
+static inline int
+multiply_alpha (int alpha, int color)
+{
+ int temp = alpha * color + 0x80;
+ return (temp + (temp >> 8)) >> 8;
+}
+
+static void
+premultiply_argb (uint8_t *data,
+ int width,
+ int height,
+ int stride)
+{
+ int i;
+
+ while (height --) {
+ uint32_t *row = (uint32_t *) data;
+
+ for (i = 0; i < width; i++) {
+ uint32_t p = row[i];
+ uint8_t alpha;
+
+ alpha = p >> 24;
+ if (alpha == 0) {
+ row[i] = 0;
+ } else if (alpha != 0xff) {
+ uint8_t r = multiply_alpha (alpha, (p >> 16) & 0xff);
+ uint8_t g = multiply_alpha (alpha, (p >> 8) & 0xff);
+ uint8_t b = multiply_alpha (alpha, (p >> 0) & 0xff);
+ row[i] = (alpha << 24) | (r << 16) | (g << 8) | (b << 0);
+ }
+ }
+
+ data += stride;
+ }
+}
+
+static cairo_int_status_t
+_vg_get_image (cairo_vg_surface_t *surface,
+ int x, int y,
+ int width, int height,
+ cairo_image_surface_t **image_out)
+{
+ cairo_image_surface_t *image;
+ pixman_image_t *pixman_image;
+ pixman_format_code_t pixman_format;
+ cairo_bool_t needs_premultiply;
+
+ pixman_format = _vg_format_to_pixman (surface->format,
+ &needs_premultiply);
+ if (pixman_format == 0)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ pixman_image = pixman_image_create_bits (pixman_format,
+ width, height,
+ NULL, 0);
+ if (unlikely (pixman_image == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ vgFinish ();
+ CHECK_VG_ERRORS();
+
+ vgGetImageSubData (surface->image,
+ pixman_image_get_data (pixman_image),
+ pixman_image_get_stride (pixman_image),
+ surface->format,
+ x, y, width, height);
+
+ image = (cairo_image_surface_t *)
+ _cairo_image_surface_create_for_pixman_image (pixman_image,
+ pixman_format);
+ if (unlikely (image->base.status)) {
+ pixman_image_unref (pixman_image);
+ return image->base.status;
+ }
+
+ if (needs_premultiply)
+ premultiply_argb (image->data, width, height, image->stride);
+
+ *image_out = image;
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_vg_surface_acquire_source_image (void *abstract_surface,
+ cairo_image_surface_t **image_out,
+ void **image_extra)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+
+ CHECK_VG_ERRORS();
+ *image_extra = NULL;
+ return _vg_get_image (surface,
+ 0, 0, surface->width, surface->height,
+ image_out);
+}
+
+static void
+_vg_surface_release_source_image (void *abstract_surface,
+ cairo_image_surface_t *image,
+ void *image_extra)
+{
+ cairo_surface_destroy (&image->base);
+}
+
+static cairo_status_t
+_vg_surface_acquire_dest_image (void *abstract_surface,
+ cairo_rectangle_int_t *interest_rect,
+ cairo_image_surface_t **image_out,
+ cairo_rectangle_int_t *image_rect_out,
+ void **image_extra)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+
+ *image_rect_out = *interest_rect;
+ *image_extra = NULL;
+ return _vg_get_image (surface,
+ interest_rect->x, interest_rect->y,
+ interest_rect->width, interest_rect->height,
+ image_out);
+}
+
+static void
+unpremultiply_argb (uint8_t *data,
+ int width,
+ int height,
+ int stride)
+{
+ int i;
+
+ while (height--) {
+ uint32_t *row = (uint32_t *) data;
+
+ for (i = 0; i < width; i ++) {
+ uint32_t p = row[i];
+ uint8_t alpha;
+
+ alpha = p >> 24;
+ if (alpha == 0) {
+ row[i] = 0;
+ } else if (alpha != 0xff) {
+ uint8_t r = (((p >> 16) & 0xff) * 255 + alpha / 2) / alpha;
+ uint8_t g = (((p >> 8) & 0xff) * 255 + alpha / 2) / alpha;
+ uint8_t b = (((p >> 0) & 0xff) * 255 + alpha / 2) / alpha;
+ row[i] = (alpha << 24) | (r << 16) | (g << 8) | (b << 0);
+ }
+ }
+
+ data += stride;
+ }
+}
+
+static void
+_vg_surface_release_dest_image (void *abstract_surface,
+ cairo_rectangle_int_t *interest_rect,
+ cairo_image_surface_t *image,
+ cairo_rectangle_int_t *image_rect,
+ void *image_extra)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_bool_t needs_unpremultiply;
+
+ /* XXX clipping is incorrect
+ * We can either composite through the current clip mask using fill,
+ * or what for the clipping overhaul patches, due to land any time
+ * soon now...
+ */
+
+ _vg_format_to_pixman (surface->format, &needs_unpremultiply);
+ if (needs_unpremultiply) {
+ unpremultiply_argb (image->data,
+ image->width, image->height,
+ image->stride);
+ }
+
+ vgImageSubData (surface->image,
+ image->data, image->stride,
+ surface->format,
+ image_rect->x, image_rect->y,
+ image_rect->width, image_rect->height);
+
+ cairo_surface_destroy (&image->base);
+}
+
+static cairo_status_t
+_vg_surface_finish (void *abstract_surface)
+{
+ cairo_vg_surface_t *surface = abstract_surface;
+ cairo_vg_context_t *context = _vg_context_lock (surface->context);
+
+ if (surface->snapshot_cache_entry.hash) {
+ _cairo_cache_remove (&context->snapshot_cache,
+ &surface->snapshot_cache_entry);
+
+ surface->snapshot_cache_entry.hash = 0;
+ }
+
+ if (surface->own_image)
+ vgDestroyImage (surface->image);
+
+ _vg_context_destroy_target (context, surface);
+
+ _vg_context_unlock (context);
+ _vg_context_destroy (context);
+
+ CHECK_VG_ERRORS();
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static const cairo_surface_backend_t cairo_vg_surface_backend = {
+ CAIRO_SURFACE_TYPE_VG,
+ _vg_surface_create_similar,
+ _vg_surface_finish,
+ _vg_surface_acquire_source_image,
+ _vg_surface_release_source_image,
+ _vg_surface_acquire_dest_image,
+ _vg_surface_release_dest_image,
+ NULL, /* clone_similar */
+ NULL, /* composite */
+ NULL, /* fill_rectangles */
+ NULL, /* composite_trapezoids */
+ NULL, /* create_span_renderer */
+ NULL, /* check_span_renderer */
+ NULL, /* copy_page */
+ NULL, /* show_page */
+ NULL, /* set_clip_region */
+ _vg_surface_intersect_clip_path,
+ _vg_surface_get_extents,
+ NULL, /* old_show_glyphs */
+ _vg_surface_get_font_options, /* get_font_options */
+ NULL, /* flush */
+ NULL, /* mark_dirty_rectangle */
+ NULL, /* scaled_font_fini */
+ NULL, /* scaled_glyph_fini */
+
+ _vg_surface_paint,
+ _vg_surface_mask,
+ _vg_surface_stroke,
+ _vg_surface_fill,
+ _vg_surface_show_glyphs,
+
+ NULL, /* snapshot */
+ NULL, /* is_similar */
+ NULL, /* reset */
+};
+
+static cairo_surface_t *
+_vg_surface_create_internal (cairo_vg_context_t *context,
+ VGImage image,
+ VGImageFormat format,
+ int width, int height)
+{
+ cairo_vg_surface_t *surface;
+
+ surface = malloc (sizeof (cairo_vg_surface_t));
+ if (unlikely (surface == NULL))
+ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+
+ surface->context = _vg_context_reference (context);
+
+ surface->image = image;
+ surface->format = format;
+
+ _cairo_surface_init (&surface->base,
+ &cairo_vg_surface_backend,
+ _vg_format_to_content (format));
+
+ surface->width = width;
+ surface->height = height;
+ surface->clipped = FALSE;
+
+ surface->snapshot_cache_entry.hash = 0;
+
+ surface->target_id = 0;
+
+ /* Force an initial "clip", that resets the mask */
+ _vg_surface_intersect_clip_path (surface, NULL, 0, 0.0, 0);
+
+ CHECK_VG_ERRORS();
+ return &surface->base;
+}
+
+cairo_surface_t *
+cairo_vg_surface_create_for_image (cairo_vg_context_t *context,
+ VGImage image,
+ VGImageFormat format,
+ int width, int height)
+{
+ cairo_bool_t premult;
+
+ if (context->status)
+ return _cairo_surface_create_in_error (context->status);
+
+ if (image == VG_INVALID_HANDLE)
+ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+ if (_vg_format_to_pixman (format, &premult) == 0)
+ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
+
+ return _vg_surface_create_internal (context, image, format, width, height);
+}
+
+cairo_surface_t *
+cairo_vg_surface_create (cairo_vg_context_t *context,
+ cairo_content_t content,
+ int width,
+ int height)
+{
+ VGImage image;
+ VGImageFormat format;
+ cairo_surface_t *surface;
+
+ if (context->status)
+ return _cairo_surface_create_in_error (context->status);
+
+ if (! CAIRO_CONTENT_VALID (content))
+ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_CONTENT));
+
+ if (width > vgGeti (VG_MAX_IMAGE_WIDTH) ||
+ height > vgGeti (VG_MAX_IMAGE_HEIGHT))
+ {
+ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE));
+ }
+
+
+ format = _vg_format_for_content (content);
+ image = vgCreateImage (format, width, height, VG_IMAGE_QUALITY_BETTER);
+ if (image == VG_INVALID_HANDLE)
+ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+
+ surface = _vg_surface_create_internal (context,
+ image, format, width, height);
+ if (unlikely (surface->status))
+ return surface;
+
+ ((cairo_vg_surface_t *) surface)->own_image = TRUE;
+ return surface;
+}
+slim_hidden_def (cairo_vg_surface_create);
+
+VGImage
+cairo_vg_surface_get_image (cairo_surface_t *abstract_surface)
+{
+ cairo_vg_surface_t *surface;
+
+ if (abstract_surface->backend != &cairo_vg_surface_backend) {
+ _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+ return VG_INVALID_HANDLE;
+ }
+
+ surface = (cairo_vg_surface_t *) abstract_surface;
+ return surface->image;
+}
+
+int
+cairo_vg_surface_get_width (cairo_surface_t *abstract_surface)
+{
+ cairo_vg_surface_t *surface;
+
+ if (abstract_surface->backend != &cairo_vg_surface_backend) {
+ _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+ return 0;
+ }
+
+ surface = (cairo_vg_surface_t *) abstract_surface;
+ return surface->width;
+}
+
+int
+cairo_vg_surface_get_height (cairo_surface_t *abstract_surface)
+{
+ cairo_vg_surface_t *surface;
+
+ if (abstract_surface->backend != &cairo_vg_surface_backend) {
+ _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+ return 0;
+ }
+
+ surface = (cairo_vg_surface_t *) abstract_surface;
+ return surface->height;
+}
+
+VGImageFormat
+cairo_vg_surface_get_format (cairo_surface_t *abstract_surface)
+{
+ cairo_vg_surface_t *surface;
+
+ if (abstract_surface->backend != &cairo_vg_surface_backend) {
+ _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+ return 0;
+ }
+
+ surface = (cairo_vg_surface_t *) abstract_surface;
+ return surface->format;
+}
+
+/* GL specific context support :-(
+ *
+ * OpenVG like cairo defers creation of surface (and the necessary
+ * paraphernalia to the application.
+ */
+
+static const cairo_vg_context_t _vg_context_nil = {
+ CAIRO_STATUS_NO_MEMORY,
+ CAIRO_REFERENCE_COUNT_INVALID
+};
+
+static const cairo_vg_context_t _vg_context_nil_invalid_visual = {
+ CAIRO_STATUS_INVALID_VISUAL,
+ CAIRO_REFERENCE_COUNT_INVALID
+};
+
+#if CAIRO_HAS_GLX_FUNCTIONS
+#include <GL/glx.h>
+
+static cairo_status_t
+glx_create_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+ /* XXX hmm, magic required for creating an FBO points to VGImage! */
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+}
+
+static cairo_status_t
+glx_set_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+#if 0
+ glXMakeContextCurrent (context->display,
+ (GLXDrawable) surface->target_id,
+ (GLXDrawable) surface->target_id,
+ context->context);
+#else
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+#endif
+}
+
+static void
+glx_destroy_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+}
+
+cairo_vg_context_t *
+cairo_vg_context_create_for_glx (Display *dpy, GLXContext ctx)
+{
+ cairo_vg_context_t *context;
+ cairo_status_t status;
+
+ context = malloc (sizeof (*context));
+ if (unlikely (context == NULL))
+ return (cairo_vg_context_t *) &_vg_context_nil;
+
+ context->display = dpy;
+ context->context = ctx;
+
+ context->create_target = glx_create_target;
+ context->set_target = glx_set_target;
+ context->destroy_target = glx_destroy_target;
+
+ status = _vg_context_init (context);
+ if (unlikely (status)) {
+ free (context);
+ return (cairo_vg_context_t *) &_vg_context_nil;
+ }
+
+ return context;
+}
+#endif
+
+#if CAIRO_HAS_EGL_FUNCTIONS
+static cairo_status_t
+egl_create_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+ EGLSurface *egl_surface;
+#define RED 1
+#define GREEN 3
+#define BLUE 5
+#define ALPHA 7
+ int attribs[] = {
+ EGL_RED_SIZE, 0,
+ EGL_GREEN_SIZE, 0,
+ EGL_BLUE_SIZE, 0,
+ EGL_ALPHA_SIZE, 0,
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
+ EGL_NONE
+ };
+ pixman_format_code_t pixman_format;
+ EGLConfig config;
+ int num_configs = 0;
+ cairo_bool_t needs_premultiply;
+
+ pixman_format = _vg_format_to_pixman (surface->format, &needs_premultiply);
+ if (pixman_format == 0)
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+
+ /* XXX no control over pixel ordering! */
+ attribs[RED] = PIXMAN_FORMAT_R (pixman_format);
+ attribs[GREEN] = PIXMAN_FORMAT_G (pixman_format);
+ attribs[BLUE] = PIXMAN_FORMAT_B (pixman_format);
+ attribs[ALPHA] = PIXMAN_FORMAT_A (pixman_format);
+
+ if (! eglChooseConfig (context->display,
+ attribs,
+ &config, 1, &num_configs) ||
+ num_configs != 1)
+ {
+ fprintf(stderr, "Error: eglChooseConfig() failed.\n");
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+ }
+
+ egl_surface =
+ eglCreatePbufferFromClientBuffer (context->display,
+ EGL_OPENVG_IMAGE,
+ (EGLClientBuffer) surface->image,
+ config,
+ NULL);
+ surface->target_id = (unsigned long) egl_surface;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+egl_set_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+ if (! eglMakeCurrent (context->display,
+ (EGLSurface *) surface->target_id,
+ (EGLSurface *) surface->target_id,
+ context->context))
+ {
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+egl_destroy_target (cairo_vg_context_t *context,
+ cairo_vg_surface_t *surface)
+{
+ eglDestroySurface (context->display,
+ (EGLSurface *) surface->target_id);
+}
+
+cairo_vg_context_t *
+cairo_vg_context_create_for_egl (EGLDisplay egl_display,
+ EGLContext egl_context)
+{
+ cairo_vg_context_t *context;
+ cairo_status_t status;
+
+ context = malloc (sizeof (*context));
+ if (unlikely (context == NULL))
+ return (cairo_vg_context_t *) &_vg_context_nil;
+
+ status = _vg_context_init (context);
+ if (unlikely (status)) {
+ free (context);
+ return (cairo_vg_context_t *) &_vg_context_nil;
+ }
+
+ context->display = egl_display;
+ context->context = egl_context;
+
+ context->create_target = egl_create_target;
+ context->set_target = egl_set_target;
+ context->destroy_target = egl_destroy_target;
+
+ return context;
+}
+#endif
+
+cairo_status_t
+cairo_vg_context_status (cairo_vg_context_t *context)
+{
+ return context->status;
+}
+
+void
+cairo_vg_context_destroy (cairo_vg_context_t *context)
+{
+ if (context == NULL || CAIRO_REFERENCE_COUNT_IS_INVALID (&context->ref_count))
+ return;
+
+ _vg_context_destroy (context);
+}