[cairo] PS/PDF Gradients

Adrian Johnson ajohnson at redneon.com
Sun Jan 28 05:57:27 PST 2007


I have been testing Miklós' PDF gradient patch from
http://lists.freedesktop.org/archives/cairo/2006-August/007648.html

Looking at the patch the only obvious deficiency I could see is lack of
support for the different cairo_extend_t options. The patch has the PDF
/Extend value set to [true true] which is equivalent CAIRO_EXTEND_PAD.

Testing linear gradients with rgb stops appears to be ok. Changing the
PDF /Extend value set to [false false] which is equivalent
CAIRO_EXTEND_NONE also works.

The results of testing linear gradients with CAIRO_EXTEND_PAD and rgba
stops are:
  acroread    - ok but the colors seem to be too dark.
  evince      - broken, The padded region is opaque when it should have
some transparency.
  ghostscript - broken. all opaque colors. no transparency.

Changing the PDF /Extend value to [false false] to support
CAIRO_EXTEND_NONE also produced similar results.

I have not tested radial gradients.

I have attached a patch that adds PostScript support for linear
gradients. As this is a Level 3 feature a function to select the PS
level would be required. Following the same pattern as the svg
restrict_to function I would suggest:

typedef enum _cairo_ps_version {
    CAIRO_PS_LEVEL_2,
    CAIRO_PS_LEVEL_3
} cairo_ps_version_t;

cairo_public void
cairo_ps_surface_restrict_to_level (
       cairo_surface_t    *surface,
       cairo_ps_version_t  level);


-------------- next part --------------
>From 4ad13146019424be650b023b43f2e2fcafa96d35 Mon Sep 17 00:00:00 2001
From: Adrian Johnson <ajohnson at redneon.com>
Date: Sun, 28 Jan 2007 22:42:36 +1030
Subject: [PATCH] PS: Add support for linear gradients

---
 src/cairo-pattern.c    |    2 +-
 src/cairo-ps-surface.c |  182 +++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 174 insertions(+), 10 deletions(-)

diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c
index 35c0739..6907f24 100644
--- a/src/cairo-pattern.c
+++ b/src/cairo-pattern.c
@@ -1076,7 +1076,7 @@ _gradient_is_opaque (const cairo_gradien
     unsigned int i;
 
     for (i = 0; i < gradient->n_stops; i++)
-	if (! CAIRO_ALPHA_IS_OPAQUE (gradient->stops[i].color.alpha))
+	if (! CAIRO_ALPHA_IS_OPAQUE (gradient->stops[i].color.alpha / 65535.0))
 	    return FALSE;
 
     return TRUE;
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 45d9fe7..6d38802 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -1335,6 +1335,12 @@ pattern_supported (const cairo_pattern_t
     if (pattern->type == CAIRO_PATTERN_TYPE_SOLID)
 	return TRUE;
 
+    if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR) {
+        if (pattern->extend == CAIRO_EXTEND_NONE ||
+            pattern->extend == CAIRO_EXTEND_PAD)
+            return TRUE;
+    }
+
     if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE)
 	return surface_pattern_supported ((const cairo_surface_pattern_t *) pattern);
 
@@ -1749,11 +1755,156 @@ emit_surface_pattern (cairo_ps_surface_t
 				 "makepattern setpattern\n");
 }
 
+typedef struct _cairo_ps_color_stop {
+    double	  		offset;
+    unsigned char		color_char[3];
+} cairo_ps_color_stop_t;
+
 static void
-emit_linear_pattern (cairo_ps_surface_t *surface,
-		     cairo_linear_pattern_t *pattern)
+emit_linear_colorgradient (cairo_ps_surface_t      *surface,
+			   cairo_ps_color_stop_t  *stop1,
+			   cairo_ps_color_stop_t  *stop2)
 {
-    /* XXX: NYI */
+    _cairo_output_stream_printf (surface->stream,
+				 "            << /FunctionType 0\n"
+				 "               /Domain [ 0 1 ]\n"
+				 "               /Size [ 2 ]\n"
+				 "               /BitsPerSample 8\n"
+				 "               /Range [ 0 1 0 1 0 1 ]\n"
+				 "               /DataSource <%02x%02x%02x%02x%02x%02x>\n"
+				 "            >>\n",
+                                 stop1->color_char[0],
+                                 stop1->color_char[1],
+                                 stop1->color_char[2],
+                                 stop2->color_char[0],
+                                 stop2->color_char[1],
+                                 stop2->color_char[2]);
+}
+
+static void
+emit_stitched_colorgradient (cairo_ps_surface_t    *surface,
+                             unsigned int 	    n_stops,
+                             cairo_ps_color_stop_t  stops[])
+{
+    unsigned int i;
+
+    _cairo_output_stream_printf (surface->stream,
+				 "      << /FunctionType 3\n"
+				 "         /Domain [ 0 1 ]\n"
+				 "         /Functions [\n");
+    for (i = 0; i < n_stops - 1; i++)
+	emit_linear_colorgradient (surface, &stops[i], &stops[i+1]);
+
+    _cairo_output_stream_printf (surface->stream, "         ]\n");
+
+    _cairo_output_stream_printf (surface->stream, "         /Bounds [ ");
+    for (i = 1; i < n_stops-1; i++)
+        _cairo_output_stream_printf (surface->stream, "%f ", stops[i].offset);
+    _cairo_output_stream_printf (surface->stream, "]\n");
+
+    _cairo_output_stream_printf (surface->stream, "         /Encode [ ");
+    for (i = 1; i < n_stops; i++)
+        _cairo_output_stream_printf (surface->stream, "0 1 ");
+    _cairo_output_stream_printf (surface->stream,  "]\n");
+
+    _cairo_output_stream_printf (surface->stream, "      >>\n");
+}
+
+#define COLOR_STOP_EPSILLON 1e-6
+
+static cairo_status_t
+emit_pattern_stops (cairo_ps_surface_t       *surface,
+                    cairo_gradient_pattern_t *pattern)
+{
+    cairo_ps_color_stop_t *allstops, *stops;
+    unsigned int i, n_stops;
+
+    allstops = malloc ((pattern->n_stops + 2) * sizeof (cairo_ps_color_stop_t));
+    if (allstops == NULL)
+	return CAIRO_STATUS_NO_MEMORY;
+
+    stops = &allstops[1];
+    n_stops = pattern->n_stops;
+
+    for (i = 0; i < pattern->n_stops; i++) {
+	stops[i].color_char[0] = pattern->stops[i].color.red   >> 8;
+	stops[i].color_char[1] = pattern->stops[i].color.green >> 8;
+	stops[i].color_char[2] = pattern->stops[i].color.blue  >> 8;
+	stops[i].offset = _cairo_fixed_to_double (pattern->stops[i].x);
+    }
+
+    /* make sure first offset is 0.0 and last offset is 1.0. */
+    if (stops[0].offset > COLOR_STOP_EPSILLON) {
+	    memcpy (allstops, stops, sizeof (cairo_ps_color_stop_t));
+	    stops = allstops;
+	    stops[0].offset = 0.0;
+	    n_stops++;
+    }
+    if (stops[n_stops-1].offset < 1.0 - COLOR_STOP_EPSILLON) {
+	    memcpy (&stops[n_stops],
+		    &stops[n_stops - 1],
+		    sizeof (cairo_ps_color_stop_t));
+	    stops[n_stops].offset = 1.0;
+	    n_stops++;
+    }
+
+    if (n_stops == 2) {
+	/* no need for stitched function */
+	emit_linear_colorgradient (surface, &stops[0], &stops[1]);
+    } else {
+	/* multiple stops: stitch. XXX possible optimization: regulary spaced
+	 * stops do not require stitching. XXX */
+	emit_stitched_colorgradient (surface, n_stops,stops);
+    }
+
+    free (allstops);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+emit_linear_pattern (cairo_ps_surface_t     *surface,
+                     cairo_linear_pattern_t *pattern)
+{
+    double x0, y0, x1, y1;
+    cairo_matrix_t p2u;
+    cairo_status_t status;
+
+    p2u = pattern->base.base.matrix;
+    cairo_matrix_invert (&p2u);
+
+    x0 = _cairo_fixed_to_double (pattern->gradient.p1.x);
+    y0 = _cairo_fixed_to_double (pattern->gradient.p1.y);
+    cairo_matrix_transform_point (&p2u, &x0, &y0);
+    x1 = _cairo_fixed_to_double (pattern->gradient.p2.x);
+    y1 = _cairo_fixed_to_double (pattern->gradient.p2.y);
+    cairo_matrix_transform_point (&p2u, &x1, &y1);
+
+    _cairo_output_stream_printf (surface->stream,
+				 "<< /PatternType 2\n"
+				 "   /Shading\n"
+				 "   << /ShadingType 2\n"
+				 "      /ColorSpace /DeviceRGB\n"
+				 "      /Coords [ %f %f %f %f ]\n"
+				 "      /Function\n",
+				 x0, y0, x1, y1);
+
+    status = emit_pattern_stops (surface, &pattern->base);
+    if (status)
+        return status;
+
+    if (pattern->base.base.extend == CAIRO_EXTEND_PAD) {
+        _cairo_output_stream_printf (surface->stream,
+                                     "      /Extend [ true true ]\n");
+    } else {
+        _cairo_output_stream_printf (surface->stream,
+                                     "      /Extend [ false false ]\n");
+    }
+    _cairo_output_stream_printf (surface->stream,
+				 "   >>\n"
+				 ">> matrix makepattern setpattern\n");
+
+    return CAIRO_STATUS_SUCCESS;
 }
 
 static void
@@ -1763,9 +1914,11 @@ emit_radial_pattern (cairo_ps_surface_t
     /* XXX: NYI */
 }
 
-static void
+static cairo_status_t
 emit_pattern (cairo_ps_surface_t *surface, cairo_pattern_t *pattern)
 {
+    cairo_status_t status = CAIRO_STATUS_SUCCESS;
+
     /* FIXME: We should keep track of what pattern is currently set in
      * the postscript file and only emit code if we're setting a
      * different pattern. */
@@ -1780,13 +1933,15 @@ emit_pattern (cairo_ps_surface_t *surfac
 	break;
 
     case CAIRO_PATTERN_TYPE_LINEAR:
-	emit_linear_pattern (surface, (cairo_linear_pattern_t *) pattern);
+	status = emit_linear_pattern (surface, (cairo_linear_pattern_t *) pattern);
 	break;
 
     case CAIRO_PATTERN_TYPE_RADIAL:
 	emit_radial_pattern (surface, (cairo_radial_pattern_t *) pattern);
 	break;
     }
+
+    return status;
 }
 
 static cairo_int_status_t
@@ -1872,6 +2027,7 @@ _cairo_ps_surface_paint (void			*abstrac
     cairo_ps_surface_t *surface = abstract_surface;
     cairo_output_stream_t *stream = surface->stream;
     cairo_rectangle_int16_t extents, pattern_extents;
+    cairo_int_status_t status;
 
     if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
 	return _analyze_operation (surface, op, source);
@@ -1893,7 +2049,9 @@ _cairo_ps_surface_paint (void			*abstrac
     _cairo_pattern_get_extents (source, &pattern_extents);
     _cairo_rectangle_intersect (&extents, &pattern_extents);
 
-    emit_pattern (surface, source);
+    status = emit_pattern (surface, source);
+    if (status)
+        return status;
 
     _cairo_output_stream_printf (stream, "%d %d M\n",
 				 extents.x, extents.y);
@@ -2032,7 +2190,9 @@ _cairo_ps_surface_stroke (void			*abstra
 	}
     }
 
-    emit_pattern (surface, source);
+    status = emit_pattern (surface, source);
+    if (status)
+        return status;
 
     _cairo_output_stream_printf (stream,
 				 "gsave\n");
@@ -2099,7 +2259,9 @@ _cairo_ps_surface_fill (void		*abstract_
     _cairo_output_stream_printf (stream,
 				 "%% _cairo_ps_surface_fill\n");
 
-    emit_pattern (surface, source);
+    status = emit_pattern (surface, source);
+    if (status)
+        return status;
 
     /* We're filling not stroking, so we pass CAIRO_LINE_CAP_ROUND. */
     status = _cairo_ps_surface_emit_path (surface, stream, path,
@@ -2162,7 +2324,9 @@ _cairo_ps_surface_show_glyphs (void
 
     num_glyphs_unsigned = num_glyphs;
 
-    emit_pattern (surface, source);
+    status = emit_pattern (surface, source);
+    if (status)
+        return status;
     glyph_ids = malloc (num_glyphs_unsigned*sizeof (cairo_ps_glyph_id_t));
     if (glyph_ids == NULL)
         return CAIRO_STATUS_NO_MEMORY;
-- 
1.4.3.4



More information about the cairo mailing list