diff options
Diffstat (limited to 'src/cairo-pattern.c')
-rw-r--r-- | src/cairo-pattern.c | 2289 |
1 files changed, 2045 insertions, 244 deletions
diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c index 1b79011..82c2279 100644 --- a/src/cairo-pattern.c +++ b/src/cairo-pattern.c @@ -31,6 +31,11 @@ #include "cairoint.h" #include "cairo-error-private.h" #include "cairo-freed-pool-private.h" +#include "cairo-path-private.h" + +#include <float.h> + +#define PIXMAN_MAX_INT ((pixman_fixed_1 >> 1) - pixman_fixed_e) /* need to ensure deltas also fit */ /** * SECTION:cairo-pattern @@ -44,13 +49,15 @@ * brush too. * * A cairo pattern is created by using one of the many constructors, - * of the form cairo_pattern_create_<emphasis>type</emphasis>() + * of the form + * <function>cairo_pattern_create_<emphasis>type</emphasis>()</function> * or implicitly through - * cairo_set_source_<emphasis>type</emphasis>() functions. + * <function>cairo_set_source_<emphasis>type</emphasis>()</function> + * functions. */ #if HAS_FREED_POOL -static freed_pool_t freed_pattern_pool[4]; +static freed_pool_t freed_pattern_pool[5]; #endif static const cairo_solid_pattern_t _cairo_pattern_nil = { @@ -154,6 +161,9 @@ _cairo_pattern_init (cairo_pattern_t *pattern, cairo_pattern_type_t type) case CAIRO_PATTERN_TYPE_RADIAL: VALGRIND_MAKE_MEM_UNDEFINED (pattern, sizeof (cairo_radial_pattern_t)); break; + case CAIRO_PATTERN_TYPE_MESH: + VALGRIND_MAKE_MEM_UNDEFINED (pattern, sizeof (cairo_mesh_pattern_t)); + break; } #endif @@ -219,6 +229,19 @@ _cairo_gradient_pattern_init_copy (cairo_gradient_pattern_t *pattern, return CAIRO_STATUS_SUCCESS; } +static cairo_status_t +_cairo_mesh_pattern_init_copy (cairo_mesh_pattern_t *pattern, + const cairo_mesh_pattern_t *other) +{ + *pattern = *other; + + _cairo_array_init (&pattern->patches, sizeof (cairo_mesh_patch_t)); + + return _cairo_array_append_multiple (&pattern->patches, + _cairo_array_index_const (&other->patches, 0), + _cairo_array_num_elements (&other->patches)); +} + cairo_status_t _cairo_pattern_init_copy (cairo_pattern_t *pattern, const cairo_pattern_t *other) @@ -261,6 +284,18 @@ _cairo_pattern_init_copy (cairo_pattern_t *pattern, return status; } break; + case CAIRO_PATTERN_TYPE_MESH: { + cairo_mesh_pattern_t *dst = (cairo_mesh_pattern_t *) pattern; + cairo_mesh_pattern_t *src = (cairo_mesh_pattern_t *) other; + cairo_status_t status; + + VG (VALGRIND_MAKE_MEM_UNDEFINED (pattern, sizeof (cairo_mesh_pattern_t))); + + status = _cairo_mesh_pattern_init_copy (dst, src); + if (unlikely (status)) + return status; + + } break; } /* The reference count and user_data array are unique to the copy. */ @@ -293,6 +328,9 @@ _cairo_pattern_init_static_copy (cairo_pattern_t *pattern, case CAIRO_PATTERN_TYPE_RADIAL: size = sizeof (cairo_radial_pattern_t); break; + case CAIRO_PATTERN_TYPE_MESH: + size = sizeof (cairo_mesh_pattern_t); + break; } memcpy (pattern, other, size); @@ -353,6 +391,12 @@ _cairo_pattern_fini (cairo_pattern_t *pattern) if (gradient->stops && gradient->stops != gradient->stops_embedded) free (gradient->stops); } break; + case CAIRO_PATTERN_TYPE_MESH: { + cairo_mesh_pattern_t *mesh = + (cairo_mesh_pattern_t *) pattern; + + _cairo_array_fini (&mesh->patches); + } break; } #if HAVE_VALGRIND @@ -369,6 +413,9 @@ _cairo_pattern_fini (cairo_pattern_t *pattern) case CAIRO_PATTERN_TYPE_RADIAL: VALGRIND_MAKE_MEM_NOACCESS (pattern, sizeof (cairo_radial_pattern_t)); break; + case CAIRO_PATTERN_TYPE_MESH: + VALGRIND_MAKE_MEM_NOACCESS (pattern, sizeof (cairo_mesh_pattern_t)); + break; } #endif } @@ -396,6 +443,9 @@ _cairo_pattern_create_copy (cairo_pattern_t **pattern_out, case CAIRO_PATTERN_TYPE_RADIAL: pattern = malloc (sizeof (cairo_radial_pattern_t)); break; + case CAIRO_PATTERN_TYPE_MESH: + pattern = malloc (sizeof (cairo_mesh_pattern_t)); + break; default: ASSERT_NOT_REACHED; return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); @@ -450,31 +500,31 @@ _cairo_pattern_init_gradient (cairo_gradient_pattern_t *pattern, pattern->stops = NULL; } -void +static void _cairo_pattern_init_linear (cairo_linear_pattern_t *pattern, double x0, double y0, double x1, double y1) { _cairo_pattern_init_gradient (&pattern->base, CAIRO_PATTERN_TYPE_LINEAR); - pattern->p1.x = _cairo_fixed_from_double (x0); - pattern->p1.y = _cairo_fixed_from_double (y0); - pattern->p2.x = _cairo_fixed_from_double (x1); - pattern->p2.y = _cairo_fixed_from_double (y1); + pattern->pd1.x = x0; + pattern->pd1.y = y0; + pattern->pd2.x = x1; + pattern->pd2.y = y1; } -void +static void _cairo_pattern_init_radial (cairo_radial_pattern_t *pattern, double cx0, double cy0, double radius0, double cx1, double cy1, double radius1) { _cairo_pattern_init_gradient (&pattern->base, CAIRO_PATTERN_TYPE_RADIAL); - pattern->c1.x = _cairo_fixed_from_double (cx0); - pattern->c1.y = _cairo_fixed_from_double (cy0); - pattern->r1 = _cairo_fixed_from_double (fabs (radius0)); - pattern->c2.x = _cairo_fixed_from_double (cx1); - pattern->c2.y = _cairo_fixed_from_double (cy1); - pattern->r2 = _cairo_fixed_from_double (fabs (radius1)); + pattern->cd1.center.x = cx0; + pattern->cd1.center.y = cy0; + pattern->cd1.radius = fabs (radius0); + pattern->cd2.center.x = cx1; + pattern->cd2.center.y = cy1; + pattern->cd2.radius = fabs (radius1); } cairo_pattern_t * @@ -741,6 +791,186 @@ cairo_pattern_create_radial (double cx0, double cy0, double radius0, return &pattern->base.base; } +/* This order is specified in the diagram in the documentation for + * cairo_pattern_create_mesh() */ +static const int mesh_path_point_i[12] = { 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 2, 1 }; +static const int mesh_path_point_j[12] = { 0, 1, 2, 3, 3, 3, 3, 2, 1, 0, 0, 0 }; +static const int mesh_control_point_i[4] = { 1, 1, 2, 2 }; +static const int mesh_control_point_j[4] = { 1, 2, 2, 1 }; + +/** + * cairo_pattern_create_mesh: + * + * Create a new mesh pattern. + * + * Mesh patterns are tensor-product patch meshes (type 7 shadings in + * PDF). Mesh patterns may also be used to create other types of + * shadings that are special cases of tensor-product patch meshes such + * as Coons patch meshes (type 6 shading in PDF) and Gouraud-shaded + * triangle meshes (type 4 and 5 shadings in PDF). + * + * Mesh patterns consist of one or more tensor-product patches, which + * should be defined before using the mesh pattern. Using a mesh + * pattern with a partially defined patch as source or mask will put + * the context in an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * A tensor-product patch is defined by 4 Bézier curves (side 0, 1, 2, + * 3) and by 4 additional control points (P0, P1, P2, P3) that provide + * further control over the patch and complete the definition of the + * tensor-product patch. The corner C0 is the first point of the + * patch. + * + * Degenerate sides are permitted so straight lines may be used. A + * zero length line on one side may be used to create 3 sided patches. + * + * <informalexample><programlisting> + * C1 Side 1 C2 + * +---------------+ + * | | + * | P1 P2 | + * | | + * Side 0 | | Side 2 + * | | + * | | + * | P0 P3 | + * | | + * +---------------+ + * C0 Side 3 C3 + * </programlisting></informalexample> + * + * Each patch is constructed by first calling + * cairo_pattern_mesh_begin_patch(), then cairo_pattern_mesh_move_to() + * to specify the first point in the patch (C0). Then the sides are + * specified with calls to cairo_pattern_mesh_curve_to() and + * cairo_pattern_mesh_line_to(). + * + * The four additional control points (P0, P1, P2, P3) in a patch can + * be specified with cairo_pattern_mesh_set_control_point(). + * + * At each corner of the patch (C0, C1, C2, C3) a color may be + * specified with cairo_pattern_mesh_set_corner_color_rgb() or + * cairo_pattern_mesh_set_corner_color_rgba(). Any corner whose color + * is not explicitly specified defaults to transparent black. + * + * A Coons patch is a special case of the tensor-product patch where + * the control points are implicitly defined by the sides of the + * patch. The default value for any control point not specified is the + * implicit value for a Coons patch, i.e. if no control points are + * specified the patch is a Coons patch. + * + * A triangle is a special case of the tensor-product patch where the + * control points are implicitly defined by the sides of the patch, + * all the sides are lines and one of them has length 0, i.e. if the + * patch is specified using just 3 lines, it is a triangle. If the + * corners connected by the 0-length side have the same color, the + * patch is a Gouraud-shaded triangle. + * + * Patches may be oriented differently to the above diagram. For + * example the first point could be at the top left. The diagram only + * shows the relationship between the sides, corners and control + * points. Regardless of where the first point is located, when + * specifying colors, corner 0 will always be the first point, corner + * 1 the point between side 0 and side 1 etc. + * + * Calling cairo_pattern_mesh_end_patch() completes the current + * patch. If less than 4 sides have been defined, the first missing + * side is defined as a line from the current point to the first point + * of the patch (C0) and the other sides are degenerate lines from C0 + * to C0. The corners between the added sides will all be coincident + * with C0 of the patch and their color will be set to be the same as + * the color of C0. + * + * Additional patches may be added with additional calls to + * cairo_pattern_mesh_begin_patch()/cairo_pattern_mesh_end_patch(). + * + * <informalexample><programlisting> + * cairo_pattern_t *mesh = cairo_pattern_mesh_create_mesh (); + * + * /* Add a Coons patch */ + * cairo_pattern_mesh_begin_patch (mesh); + * cairo_pattern_mesh_move_to (pattern, 0, 0); + * cairo_pattern_mesh_curve_to (pattern, 30, -30, 60, 30, 100, 0); + * cairo_pattern_mesh_curve_to (pattern, 60, 30, 130, 60, 100, 100); + * cairo_pattern_mesh_curve_to (pattern, 60, 70, 30, 130, 0, 100); + * cairo_pattern_mesh_curve_to (pattern, 30, 70, -30, 30, 0, 0); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 0, 1, 0, 0); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 1, 0, 1, 0); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 2, 0, 0, 1); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 3, 1, 1, 0); + * cairo_pattern_mesh_end_patch (mesh); + * + * /* Add a Gouraud-shaded triangle */ + * cairo_pattern_mesh_begin_patch (mesh) + * cairo_pattern_mesh_move_to (pattern, 100, 100); + * cairo_pattern_mesh_line_to (pattern, 130, 130); + * cairo_pattern_mesh_line_to (pattern, 130, 70); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 0, 1, 0, 0); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 1, 0, 1, 0); + * cairo_pattern_mesh_set_corner_color_rgb (pattern, 2, 0, 0, 1); + * cairo_pattern_mesh_end_patch (mesh) + * </programlisting></informalexample> + * + * When two patches overlap, the last one that has been added is drawn + * over the first one. + * + * When a patch folds over itself, points are sorted depending on + * their parameter coordinates inside the patch. The v coordinate + * ranges from 0 to 1 when moving from side 3 to side 1; the u + * coordinate ranges from 0 to 1 when going from side 0 to side + * 2. Points with higher v coordinate hide points with lower v + * coordinate. When two points have the same v coordinate, the one + * with higher u coordinate is above. This means that points nearer to + * side 1 are above points nearer to side 3; when this is not + * sufficient to decide which point is above (for example when both + * points belong to side 1 or side 3) points nearer to side 2 are + * above points nearer to side 0. + * + * For a complete definition of tensor-product patches, see the PDF + * specification (ISO32000), which describes the parametrization in + * detail. + * + * Note: The coordinates are always in pattern space. For a new + * pattern, pattern space is identical to user space, but the + * relationship between the spaces can be changed with + * cairo_pattern_set_matrix(). + * + * Return value: the newly created #cairo_pattern_t if successful, or + * an error pattern in case of no memory. The caller owns the returned + * object and should call cairo_pattern_destroy() when finished with + * it. + * + * This function will always return a valid pointer, but if an error + * occurred the pattern status will be set to an error. To inspect the + * status of a pattern use cairo_pattern_status(). + * + * Since: 1.12 + **/ +cairo_pattern_t * +cairo_pattern_create_mesh (void) +{ + cairo_mesh_pattern_t *pattern; + + pattern = + _freed_pool_get (&freed_pattern_pool[CAIRO_PATTERN_TYPE_MESH]); + if (unlikely (pattern == NULL)) { + pattern = malloc (sizeof (cairo_mesh_pattern_t)); + if (unlikely (pattern == NULL)) { + _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); + return (cairo_pattern_t *) &_cairo_pattern_nil.base; + } + } + + CAIRO_MUTEX_INITIALIZE (); + + _cairo_pattern_init (&pattern->base, CAIRO_PATTERN_TYPE_MESH); + _cairo_array_init (&pattern->patches, sizeof (cairo_mesh_patch_t)); + pattern->current_patch = NULL; + CAIRO_REFERENCE_COUNT_INIT (&pattern->base.ref_count, 1); + + return &pattern->base; +} + /** * cairo_pattern_reference: * @pattern: a #cairo_pattern_t @@ -906,6 +1136,451 @@ cairo_pattern_set_user_data (cairo_pattern_t *pattern, key, user_data, destroy); } +/** + * cairo_pattern_mesh_begin_patch: + * @pattern: a #cairo_pattern_t + * + * Begin a patch in a mesh pattern. + * + * After calling this function, the patch shape should be defined with + * cairo_pattern_mesh_move_to(), cairo_pattern_mesh_line_to() and + * cairo_pattern_mesh_curve_to(). + * + * After defining the patch, cairo_pattern_mesh_end_patch() must be + * called before using @pattern as a source or mask. + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @pattern already has a + * current patch, it will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_begin_patch (cairo_pattern_t *pattern) +{ + cairo_mesh_pattern_t *mesh; + cairo_status_t status; + cairo_mesh_patch_t *current_patch; + int i; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + if (unlikely (mesh->current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + status = _cairo_array_allocate (&mesh->patches, 1, (void **) ¤t_patch); + if (unlikely (status)) { + _cairo_pattern_set_error (pattern, status); + return; + } + + mesh->current_patch = current_patch; + mesh->current_side = -2; /* no current point */ + + for (i = 0; i < 4; i++) + mesh->has_control_point[i] = FALSE; + + for (i = 0; i < 4; i++) + mesh->has_color[i] = FALSE; +} + + +static void +_calc_control_point (cairo_mesh_patch_t *patch, int control_point) +{ + /* The Coons patch is a special case of the Tensor Product patch + * where the four control points are: + * + * P11 = S(1/3, 1/3) + * P12 = S(1/3, 2/3) + * P21 = S(2/3, 1/3) + * P22 = S(2/3, 2/3) + * + * where S is the gradient surface. + * + * When one or more control points has not been specified + * calculated the Coons patch control points are substituted. If + * no control points are specified the gradient will be a Coons + * patch. + * + * The equations below are defined in the ISO32000 standard. + */ + cairo_point_double_t *p[3][3]; + int cp_i, cp_j, i, j; + + cp_i = mesh_control_point_i[control_point]; + cp_j = mesh_control_point_j[control_point]; + + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + p[i][j] = &patch->points[cp_i ^ i][cp_j ^ j]; + + p[0][0]->x = (- 4 * p[1][1]->x + + 6 * (p[1][0]->x + p[0][1]->x) + - 2 * (p[1][2]->x + p[2][1]->x) + + 3 * (p[2][0]->x + p[0][2]->x) + - 1 * p[2][2]->x) * (1. / 9); + + p[0][0]->y = (- 4 * p[1][1]->y + + 6 * (p[1][0]->y + p[0][1]->y) + - 2 * (p[1][2]->y + p[2][1]->y) + + 3 * (p[2][0]->y + p[0][2]->y) + - 1 * p[2][2]->y) * (1. / 9); +} + +/** + * cairo_pattern_mesh_end_patch: + * @pattern: a #cairo_pattern_t + * + * Indicates the end of the current patch in a mesh pattern. + * + * If the current patch has less than 4 sides, it is closed with a + * straight line from the current point to the first point of the + * patch as if cairo_pattern_mesh_line_to() was used. + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @pattern has no current + * patch or the current patch has no current point, @pattern will be + * put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_end_patch (cairo_pattern_t *pattern) +{ + cairo_mesh_pattern_t *mesh; + cairo_mesh_patch_t *current_patch; + int i; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + current_patch = mesh->current_patch; + if (unlikely (!current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + if (unlikely (mesh->current_side == -2)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + while (mesh->current_side < 3) { + int corner_num; + + cairo_pattern_mesh_line_to (pattern, + current_patch->points[0][0].x, + current_patch->points[0][0].y); + + corner_num = mesh->current_side + 1; + if (corner_num < 4 && ! mesh->has_color[corner_num]) { + current_patch->colors[corner_num] = current_patch->colors[0]; + mesh->has_color[corner_num] = TRUE; + } + } + + for (i = 0; i < 4; i++) { + if (! mesh->has_control_point[i]) + _calc_control_point (current_patch, i); + } + + for (i = 0; i < 4; i++) { + if (! mesh->has_color[i]) + current_patch->colors[i] = *CAIRO_COLOR_TRANSPARENT; + } + + mesh->current_patch = NULL; +} + +/** + * cairo_pattern_mesh_curve_to: + * @pattern: a #cairo_pattern_t + * @x1: the X coordinate of the first control point + * @y1: the Y coordinate of the first control point + * @x2: the X coordinate of the second control point + * @y2: the Y coordinate of the second control point + * @x3: the X coordinate of the end of the curve + * @y3: the Y coordinate of the end of the curve + * + * Adds a cubic Bézier spline to the current patch from the current + * point to position (@x3, @y3) in pattern-space coordinates, using + * (@x1, @y1) and (@x2, @y2) as the control points. + * + * If the current patch has no current point before the call to + * cairo_pattern_mesh_curve_to(), this function will behave as if + * preceded by a call to cairo_pattern_mesh_move_to(@pattern, @x1, + * @y1). + * + * After this call the current point will be (@x3, @y3). + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @pattern has no current + * patch or the current patch already has 4 sides, @pattern will be + * put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_curve_to (cairo_pattern_t *pattern, + double x1, double y1, + double x2, double y2, + double x3, double y3) +{ + cairo_mesh_pattern_t *mesh; + int current_point, i, j; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + if (unlikely (!mesh->current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + if (unlikely (mesh->current_side == 3)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + if (mesh->current_side == -2) + cairo_pattern_mesh_move_to (pattern, x1, y1); + + assert (mesh->current_side >= -1); + assert (pattern->status == CAIRO_STATUS_SUCCESS); + + mesh->current_side++; + + current_point = 3 * mesh->current_side; + + current_point++; + i = mesh_path_point_i[current_point]; + j = mesh_path_point_j[current_point]; + mesh->current_patch->points[i][j].x = x1; + mesh->current_patch->points[i][j].y = y1; + + current_point++; + i = mesh_path_point_i[current_point]; + j = mesh_path_point_j[current_point]; + mesh->current_patch->points[i][j].x = x2; + mesh->current_patch->points[i][j].y = y2; + + current_point++; + if (current_point < 12) { + i = mesh_path_point_i[current_point]; + j = mesh_path_point_j[current_point]; + mesh->current_patch->points[i][j].x = x3; + mesh->current_patch->points[i][j].y = y3; + } +} +slim_hidden_def (cairo_pattern_mesh_curve_to); + +/** + * cairo_pattern_mesh_line_to: + * @pattern: a #cairo_pattern_t + * @x: the X coordinate of the end of the new line + * @y: the Y coordinate of the end of the new line + * + * Adds a line to the current patch from the current point to position + * (@x, @y) in pattern-space coordinates. + * + * If there is no current point before the call to + * cairo_pattern_mesh_line_to() this function will behave as + * cairo_pattern_mesh_move_to(@pattern, @x, @y). + * + * After this call the current point will be (@x, @y). + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @pattern has no current + * patch or the current patch already has 4 sides, @pattern will be + * put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_line_to (cairo_pattern_t *pattern, + double x, double y) +{ + cairo_mesh_pattern_t *mesh; + cairo_point_double_t last_point; + int last_point_idx, i, j; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + if (unlikely (!mesh->current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + if (unlikely (mesh->current_side == 3)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + if (mesh->current_side == -2) { + cairo_pattern_mesh_move_to (pattern, x, y); + return; + } + + last_point_idx = 3 * (mesh->current_side + 1); + i = mesh_path_point_i[last_point_idx]; + j = mesh_path_point_j[last_point_idx]; + + last_point = mesh->current_patch->points[i][j]; + + cairo_pattern_mesh_curve_to (pattern, + (2 * last_point.x + x) * (1. / 3), + (2 * last_point.y + y) * (1. / 3), + (last_point.x + 2 * x) * (1. / 3), + (last_point.y + 2 * y) * (1. / 3), + x, y); +} +slim_hidden_def (cairo_pattern_mesh_line_to); + +/** + * cairo_pattern_mesh_move_to: + * @pattern: a #cairo_pattern_t + * @x: the X coordinate of the new position + * @y: the Y coordinate of the new position + * + * Define the first point of the current patch in a mesh pattern. + * + * After this call the current point will be (@x, @y). + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @pattern has no current + * patch or the current patch already has at leas one side, @pattern + * will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_move_to (cairo_pattern_t *pattern, + double x, double y) +{ + cairo_mesh_pattern_t *mesh; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + if (unlikely (!mesh->current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + if (unlikely (mesh->current_side >= 0)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + mesh->current_side = -1; + mesh->current_patch->points[0][0].x = x; + mesh->current_patch->points[0][0].y = y; +} +slim_hidden_def (cairo_pattern_mesh_move_to); + +/** + * cairo_pattern_mesh_set_control_point: + * @pattern: a #cairo_pattern_t + * @point_num: the control point to set the position for + * @x: the X coordinate of the control point + * @y: the Y coordinate of the control point + * + * Set an internal control point of the current patch. + * + * Valid values for @point_num are from 0 to 3 and identify the + * control points as explained in cairo_pattern_create_mesh(). + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @point_num is not valid, + * @pattern will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_INDEX. If @pattern has no current patch, + * @pattern will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_set_control_point (cairo_pattern_t *pattern, + unsigned int point_num, + double x, + double y) +{ + cairo_mesh_pattern_t *mesh; + int i, j; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + if (unlikely (point_num > 3)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_INDEX); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + if (unlikely (!mesh->current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + i = mesh_control_point_i[point_num]; + j = mesh_control_point_j[point_num]; + + mesh->current_patch->points[i][j].x = x; + mesh->current_patch->points[i][j].y = y; + mesh->has_control_point[point_num] = TRUE; +} + /* make room for at least one more color stop */ static cairo_status_t _cairo_pattern_gradient_grow (cairo_gradient_pattern_t *pattern) @@ -948,7 +1623,127 @@ _cairo_pattern_gradient_grow (cairo_gradient_pattern_t *pattern) } static void -_cairo_pattern_add_color_stop (cairo_gradient_pattern_t *pattern, +_cairo_pattern_mesh_set_corner_color (cairo_mesh_pattern_t *mesh, + unsigned int corner_num, + double red, double green, double blue, + double alpha) +{ + cairo_color_t *color; + + assert (mesh->current_patch); + assert (corner_num <= 3); + + color = &mesh->current_patch->colors[corner_num]; + color->red = red; + color->green = green; + color->blue = blue; + color->alpha = alpha; + + color->red_short = _cairo_color_double_to_short (red); + color->green_short = _cairo_color_double_to_short (green); + color->blue_short = _cairo_color_double_to_short (blue); + color->alpha_short = _cairo_color_double_to_short (alpha); + + mesh->has_color[corner_num] = TRUE; +} + +/** + * cairo_pattern_mesh_set_corner_color_rgb: + * @pattern: a #cairo_pattern_t + * @corner_num: the corner to set the color for + * @red: red component of color + * @green: green component of color + * @blue: blue component of color + * + * Sets the color of a corner of the current patch in a mesh pattern. + * + * The color is specified in the same way as in cairo_set_source_rgb(). + * + * Valid values for @corner_num are from 0 to 3 and identify the + * corners as explained in cairo_pattern_create_mesh(). + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @corner_num is not valid, + * @pattern will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_INDEX. If @pattern has no current patch, + * @pattern will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_set_corner_color_rgb (cairo_pattern_t *pattern, + unsigned int corner_num, + double red, double green, double blue) +{ + cairo_pattern_mesh_set_corner_color_rgba (pattern, corner_num, red, green, blue, 1.0); +} + +/** + * cairo_pattern_mesh_set_corner_color_rgba: + * @pattern: a #cairo_pattern_t + * @corner_num: the corner to set the color for + * @red: red component of color + * @green: green component of color + * @blue: blue component of color + * @alpha: alpha component of color + * + * Sets the color of a corner of the current patch in a mesh pattern. + * + * The color is specified in the same way as in cairo_set_source_rgba(). + * + * Valid values for @corner_num are from 0 to 3 and identify the + * corners as explained in cairo_pattern_create_mesh(). + * + * Note: If @pattern is not a mesh pattern then @pattern will be put + * into an error status with a status of + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH. If @corner_num is not valid, + * @pattern will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_INDEX. If @pattern has no current patch, + * @pattern will be put into an error status with a status of + * %CAIRO_STATUS_INVALID_MESH_CONSTRUCTION. + * + * Since: 1.12 + **/ +void +cairo_pattern_mesh_set_corner_color_rgba (cairo_pattern_t *pattern, + unsigned int corner_num, + double red, double green, double blue, + double alpha) +{ + cairo_mesh_pattern_t *mesh; + + if (unlikely (pattern->status)) + return; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + return; + } + + if (unlikely (corner_num > 3)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_INDEX); + return; + } + + mesh = (cairo_mesh_pattern_t *) pattern; + if (unlikely (!mesh->current_patch)) { + _cairo_pattern_set_error (pattern, CAIRO_STATUS_INVALID_MESH_CONSTRUCTION); + return; + } + + red = _cairo_restrict_value (red, 0.0, 1.0); + green = _cairo_restrict_value (green, 0.0, 1.0); + blue = _cairo_restrict_value (blue, 0.0, 1.0); + alpha = _cairo_restrict_value (alpha, 0.0, 1.0); + + _cairo_pattern_mesh_set_corner_color (mesh, corner_num, red, green, blue, alpha); +} +slim_hidden_def (cairo_pattern_mesh_set_corner_color_rgba); + +static void +_cairo_pattern_add_color_stop (cairo_gradient_pattern_t *pattern, double offset, double red, double green, @@ -1282,10 +2077,8 @@ _cairo_linear_pattern_classify (cairo_linear_pattern_t *pattern, * pattern. We actually only need 3/4 corners, so we skip the * fourth. */ - point0.x = _cairo_fixed_to_double (pattern->p1.x); - point0.y = _cairo_fixed_to_double (pattern->p1.y); - point1.x = _cairo_fixed_to_double (pattern->p2.x); - point1.y = _cairo_fixed_to_double (pattern->p2.y); + point0 = pattern->pd1; + point1 = pattern->pd2; _cairo_matrix_get_affine (&pattern->base.base.matrix, &a, &b, &c, &d, &tx, &ty); @@ -1333,10 +2126,11 @@ _cairo_pattern_acquire_surface_for_gradient (const cairo_gradient_pattern_t *pat cairo_image_surface_t *image; pixman_image_t *pixman_image; pixman_transform_t pixman_transform; + cairo_circle_double_t extremes[2]; + pixman_point_fixed_t p1, p2; cairo_status_t status; cairo_bool_t repeat = FALSE; - cairo_bool_t opaque = TRUE; - + int ix, iy; pixman_gradient_stop_t pixman_stops_static[2]; pixman_gradient_stop_t *pixman_stops = pixman_stops_static; unsigned int i; @@ -1359,75 +2153,26 @@ _cairo_pattern_acquire_surface_for_gradient (const cairo_gradient_pattern_t *pat pixman_stops[i].color.green = pattern->stops[i].color.green_short; pixman_stops[i].color.blue = pattern->stops[i].color.blue_short; pixman_stops[i].color.alpha = pattern->stops[i].color.alpha_short; - if (! CAIRO_ALPHA_SHORT_IS_OPAQUE (pixman_stops[i].color.alpha)) - opaque = FALSE; } - if (pattern->base.type == CAIRO_PATTERN_TYPE_LINEAR) - { - cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) pattern; - pixman_point_fixed_t p1, p2; - cairo_fixed_t xdim, ydim; - - xdim = linear->p2.x - linear->p1.x; - ydim = linear->p2.y - linear->p1.y; - - /* - * Transform the matrix to avoid overflow when converting between - * cairo_fixed_t and pixman_fixed_t (without incurring performance - * loss when the transformation is unnecessary). - * - * XXX: Consider converting out-of-range co-ordinates and transforms. - * Having a function to compute the required transformation to - * "normalize" a given bounding box would be generally useful - - * cf linear patterns, gradient patterns, surface patterns... - */ -#define PIXMAN_MAX_INT ((pixman_fixed_1 >> 1) - pixman_fixed_e) /* need to ensure deltas also fit */ - if (_cairo_fixed_integer_ceil (xdim) > PIXMAN_MAX_INT || - _cairo_fixed_integer_ceil (ydim) > PIXMAN_MAX_INT) - { - double sf; - - if (xdim > ydim) - sf = PIXMAN_MAX_INT / _cairo_fixed_to_double (xdim); - else - sf = PIXMAN_MAX_INT / _cairo_fixed_to_double (ydim); + _cairo_gradient_pattern_fit_to_range (pattern, PIXMAN_MAX_INT >> 1, &matrix, extremes); - p1.x = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p1.x) * sf); - p1.y = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p1.y) * sf); - p2.x = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p2.x) * sf); - p2.y = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p2.y) * sf); - - cairo_matrix_scale (&matrix, sf, sf); - } - else - { - p1.x = _cairo_fixed_to_16_16 (linear->p1.x); - p1.y = _cairo_fixed_to_16_16 (linear->p1.y); - p2.x = _cairo_fixed_to_16_16 (linear->p2.x); - p2.y = _cairo_fixed_to_16_16 (linear->p2.y); - } + p1.x = _cairo_fixed_16_16_from_double (extremes[0].center.x); + p1.y = _cairo_fixed_16_16_from_double (extremes[0].center.y); + p2.x = _cairo_fixed_16_16_from_double (extremes[1].center.x); + p2.y = _cairo_fixed_16_16_from_double (extremes[1].center.y); + if (pattern->base.type == CAIRO_PATTERN_TYPE_LINEAR) { pixman_image = pixman_image_create_linear_gradient (&p1, &p2, pixman_stops, pattern->n_stops); - } - else - { - cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern; - pixman_point_fixed_t c1, c2; + } else { pixman_fixed_t r1, r2; - c1.x = _cairo_fixed_to_16_16 (radial->c1.x); - c1.y = _cairo_fixed_to_16_16 (radial->c1.y); - r1 = _cairo_fixed_to_16_16 (radial->r1); + r1 = _cairo_fixed_16_16_from_double (extremes[0].radius); + r2 = _cairo_fixed_16_16_from_double (extremes[1].radius); - c2.x = _cairo_fixed_to_16_16 (radial->c2.x); - c2.y = _cairo_fixed_to_16_16 (radial->c2.y); - r2 = _cairo_fixed_to_16_16 (radial->r2); - - pixman_image = pixman_image_create_radial_gradient (&c1, &c2, - r1, r2, + pixman_image = pixman_image_create_radial_gradient (&p1, &p2, r1, r2, pixman_stops, pattern->n_stops); } @@ -1498,12 +2243,21 @@ _cairo_pattern_acquire_surface_for_gradient (const cairo_gradient_pattern_t *pat return image->base.status; } - _cairo_matrix_to_pixman_matrix (&matrix, &pixman_transform, - width/2., height/2.); - if (!pixman_image_set_transform (pixman_image, &pixman_transform)) { - cairo_surface_destroy (&image->base); - pixman_image_unref (pixman_image); - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + ix = x; + iy = y; + status = _cairo_matrix_to_pixman_matrix_offset (&matrix, + pattern->base.filter, + width/2., height/2., + &pixman_transform, + &ix, &iy); + if (status != CAIRO_INT_STATUS_NOTHING_TO_DO) { + if (unlikely (status != CAIRO_STATUS_SUCCESS) || + ! pixman_image_set_transform (pixman_image, &pixman_transform)) + { + cairo_surface_destroy (&image->base); + pixman_image_unref (pixman_image); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } } switch (pattern->base.extend) { @@ -1525,7 +2279,7 @@ _cairo_pattern_acquire_surface_for_gradient (const cairo_gradient_pattern_t *pat pixman_image, NULL, image->pixman_image, - x, y, + ix, iy, 0, 0, 0, 0, width, height); @@ -1552,6 +2306,54 @@ _cairo_pattern_acquire_surface_for_gradient (const cairo_gradient_pattern_t *pat return status; } +static cairo_int_status_t +_cairo_pattern_acquire_surface_for_mesh (const cairo_mesh_pattern_t *pattern, + cairo_surface_t *dst, + int x, + int y, + unsigned int width, + unsigned int height, + cairo_surface_t **out, + cairo_surface_attributes_t *attr) +{ + cairo_surface_t *image; + void *data; + cairo_status_t status; + int clone_offset_x, clone_offset_y; + int stride; + + image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (unlikely (image->status)) + return image->status; + + stride = cairo_image_surface_get_stride (image); + data = cairo_image_surface_get_data (image); + + _cairo_mesh_pattern_rasterize (pattern, data, width, height, stride, -x, -y); + + attr->x_offset = -x; + attr->y_offset = -y; + attr->filter = CAIRO_FILTER_NEAREST; + attr->extend = pattern->base.extend; + cairo_matrix_init_identity (&attr->matrix); + attr->has_component_alpha = pattern->base.has_component_alpha; + + if (_cairo_surface_is_image (dst)) { + *out = image; + + return CAIRO_STATUS_SUCCESS; + } + + status = _cairo_surface_clone_similar (dst, image, + 0, 0, width, height, + &clone_offset_x, + &clone_offset_y, out); + + cairo_surface_destroy (image); + + return status; +} + /* We maintain a small cache here, because we don't want to constantly * recreate surfaces for simple solid colors. */ #define MAX_SURFACE_CACHE_SIZE 16 @@ -1726,50 +2528,590 @@ _cairo_pattern_reset_solid_surface_cache (void) CAIRO_MUTEX_UNLOCK (_cairo_pattern_solid_surface_cache_lock); } +static cairo_bool_t +_linear_pattern_is_degenerate (const cairo_linear_pattern_t *linear) +{ + return fabs (linear->pd1.x - linear->pd2.x) < DBL_EPSILON && + fabs (linear->pd1.y - linear->pd2.y) < DBL_EPSILON; +} + +static cairo_bool_t +_radial_pattern_is_degenerate (const cairo_radial_pattern_t *radial) +{ + /* A radial pattern is considered degenerate if it can be + * represented as a solid or clear pattern. This corresponds to + * one of the two cases: + * + * 1) The radii are both very small: + * |dr| < DBL_EPSILON && min (r0, r1) < DBL_EPSILON + * + * 2) The two circles have about the same radius and are very + * close to each other (approximately a cylinder gradient that + * doesn't move with the parameter): + * |dr| < DBL_EPSILON && max (|dx|, |dy|) < 2 * DBL_EPSILON + * + * These checks are consistent with the assumptions used in + * _cairo_radial_pattern_box_to_parameter (). + */ + + return fabs (radial->cd1.radius - radial->cd2.radius) < DBL_EPSILON && + (MIN (radial->cd1.radius, radial->cd2.radius) < DBL_EPSILON || + MAX (fabs (radial->cd1.center.x - radial->cd2.center.x), + fabs (radial->cd1.center.y - radial->cd2.center.y)) < 2 * DBL_EPSILON); +} + static void -_extents_to_linear_parameter (const cairo_linear_pattern_t *linear, - const cairo_rectangle_int_t *extents, - double t[2]) +_cairo_linear_pattern_box_to_parameter (const cairo_linear_pattern_t *linear, + double x0, double y0, + double x1, double y1, + double range[2]) { double t0, tdx, tdy; double p1x, p1y, pdx, pdy, invsqnorm; - p1x = _cairo_fixed_to_double (linear->p1.x); - p1y = _cairo_fixed_to_double (linear->p1.y); - pdx = _cairo_fixed_to_double (linear->p2.x) - p1x; - pdy = _cairo_fixed_to_double (linear->p2.y) - p1y; + assert (! _linear_pattern_is_degenerate (linear)); + + /* + * Linear gradients are othrogonal to the line passing through + * their extremes. Because of convexity, the parameter range can + * be computed as the convex hull (one the real line) of the + * parameter values of the 4 corners of the box. + * + * The parameter value t for a point (x,y) can be computed as: + * + * t = (p2 - p1) . (x,y) / |p2 - p1|^2 + * + * t0 is the t value for the top left corner + * tdx is the difference between left and right corners + * tdy is the difference between top and bottom corners + */ + + p1x = linear->pd1.x; + p1y = linear->pd1.y; + pdx = linear->pd2.x - p1x; + pdy = linear->pd2.y - p1y; invsqnorm = 1.0 / (pdx * pdx + pdy * pdy); pdx *= invsqnorm; pdy *= invsqnorm; - t0 = (extents->x - p1x) * pdx + (extents->y - p1y) * pdy; - tdx = extents->width * pdx; - tdy = extents->height * pdy; + t0 = (x0 - p1x) * pdx + (y0 - p1y) * pdy; + tdx = (x1 - x0) * pdx; + tdy = (y1 - y0) * pdy; + + /* + * Because of the linearity of the t value, tdx can simply be + * added the t0 to move along the top edge. After this, range[0] + * and range[1] represent the parameter range for the top edge, so + * extending it to include the whole box simply requires adding + * tdy to the correct extreme. + */ - t[0] = t[1] = t0; + range[0] = range[1] = t0; if (tdx < 0) - t[0] += tdx; + range[0] += tdx; else - t[1] += tdx; + range[1] += tdx; if (tdy < 0) - t[0] += tdy; + range[0] += tdy; else - t[1] += tdy; + range[1] += tdy; } static cairo_bool_t -_linear_pattern_is_degenerate (const cairo_linear_pattern_t *linear) +_extend_range (double range[2], double value, cairo_bool_t valid) +{ + if (!valid) + range[0] = range[1] = value; + else if (value < range[0]) + range[0] = value; + else if (value > range[1]) + range[1] = value; + + return TRUE; +} + +static void +_cairo_radial_pattern_box_to_parameter (const cairo_radial_pattern_t *radial, + double x0, double y0, + double x1, double y1, + double tolerance, + double range[2]) +{ + double cx, cy, cr, dx, dy, dr; + double a, x_focus, y_focus; + double mindr, minx, miny, maxx, maxy; + cairo_bool_t valid; + + assert (! _radial_pattern_is_degenerate (radial)); + assert (x0 < x1); + assert (y0 < y1); + + tolerance = MAX (tolerance, DBL_EPSILON); + + range[0] = range[1] = 0; + valid = FALSE; + + x_focus = y_focus = 0; /* silence gcc */ + + cx = radial->cd1.center.x; + cy = radial->cd1.center.y; + cr = radial->cd1.radius; + dx = radial->cd2.center.x - cx; + dy = radial->cd2.center.y - cy; + dr = radial->cd2.radius - cr; + + /* translate by -(cx, cy) to simplify computations */ + x0 -= cx; + y0 -= cy; + x1 -= cx; + y1 -= cy; + + /* enlarge boundaries slightly to avoid rounding problems in the + * parameter range computation */ + x0 -= DBL_EPSILON; + y0 -= DBL_EPSILON; + x1 += DBL_EPSILON; + y1 += DBL_EPSILON; + + /* enlarge boundaries even more to avoid rounding problems when + * testing if a point belongs to the box */ + minx = x0 - DBL_EPSILON; + miny = y0 - DBL_EPSILON; + maxx = x1 + DBL_EPSILON; + maxy = y1 + DBL_EPSILON; + + /* we dont' allow negative radiuses, so we will be checking that + * t*dr >= mindr to consider t valid */ + mindr = -(cr + DBL_EPSILON); + + /* + * After the previous transformations, the start circle is + * centered in the origin and has radius cr. A 1-unit change in + * the t parameter corresponds to dx,dy,dr changes in the x,y,r of + * the circle (center coordinates, radius). + * + * To compute the minimum range needed to correctly draw the + * pattern, we start with an empty range and extend it to include + * the circles touching the bounding box or within it. + */ + + /* + * Focus, the point where the circle has radius == 0. + * + * r = cr + t * dr = 0 + * t = -cr / dr + * + * If the radius is constant (dr == 0) there is no focus (the + * gradient represents a cylinder instead of a cone). + */ + if (fabs (dr) >= DBL_EPSILON) { + double t_focus; + + t_focus = -cr / dr; + x_focus = t_focus * dx; + y_focus = t_focus * dy; + if (minx <= x_focus && x_focus <= maxx && + miny <= y_focus && y_focus <= maxy) + { + valid = _extend_range (range, t_focus, valid); + } + } + + /* + * Circles externally tangent to box edges. + * + * All circles have center in (dx, dy) * t + * + * If the circle is tangent to the line defined by the edge of the + * box, then at least one of the following holds true: + * + * (dx*t) + (cr + dr*t) == x0 (left edge) + * (dx*t) - (cr + dr*t) == x1 (right edge) + * (dy*t) + (cr + dr*t) == y0 (top edge) + * (dy*t) - (cr + dr*t) == y1 (bottom edge) + * + * The solution is only valid if the tangent point is actually on + * the edge, i.e. if its y coordinate is in [y0,y1] for left/right + * edges and if its x coordinate is in [x0,x1] for top/bottom + * edges. + * + * For the first equation: + * + * (dx + dr) * t = x0 - cr + * t = (x0 - cr) / (dx + dr) + * y = dy * t + * + * in the code this becomes: + * + * t_edge = (num) / (den) + * v = (delta) * t_edge + * + * If the denominator in t is 0, the pattern is tangent to a line + * parallel to the edge under examination. The corner-case where + * the boundary line is the same as the edge is handled by the + * focus point case and/or by the a==0 case. + */ +#define T_EDGE(num,den,delta,lower,upper) \ + if (fabs (den) >= DBL_EPSILON) { \ + double t_edge, v; \ + \ + t_edge = (num) / (den); \ + v = t_edge * (delta); \ + if (t_edge * dr >= mindr && (lower) <= v && v <= (upper)) \ + valid = _extend_range (range, t_edge, valid); \ + } + + /* circles tangent (externally) to left/right/top/bottom edge */ + T_EDGE (x0 - cr, dx + dr, dy, miny, maxy); + T_EDGE (x1 + cr, dx - dr, dy, miny, maxy); + T_EDGE (y0 - cr, dy + dr, dx, minx, maxx); + T_EDGE (y1 + cr, dy - dr, dx, minx, maxx); + +#undef T_EDGE + + /* + * Circles passing through a corner. + * + * A circle passing through the point (x,y) satisfies: + * + * (x-t*dx)^2 + (y-t*dy)^2 == (cr + t*dr)^2 + * + * If we set: + * a = dx^2 + dy^2 - dr^2 + * b = x*dx + y*dy + cr*dr + * c = x^2 + y^2 - cr^2 + * we have: + * a*t^2 - 2*b*t + c == 0 + */ + a = dx * dx + dy * dy - dr * dr; + if (fabs (a) < DBL_EPSILON * DBL_EPSILON) { + double b, maxd2; + + /* Ensure that gradients with both a and dr small are + * considered degenerate. + * The floating point version of the degeneracy test implemented + * in _radial_pattern_is_degenerate() is: + * + * 1) The circles are practically the same size: + * |dr| < DBL_EPSILON + * AND + * 2a) The circles are both very small: + * min (r0, r1) < DBL_EPSILON + * OR + * 2b) The circles are very close to each other: + * max (|dx|, |dy|) < 2 * DBL_EPSILON + * + * Assuming that the gradient is not degenerate, we want to + * show that |a| < DBL_EPSILON^2 implies |dr| >= DBL_EPSILON. + * + * If the gradient is not degenerate yet it has |dr| < + * DBL_EPSILON, (2b) is false, thus: + * + * max (|dx|, |dy|) >= 2*DBL_EPSILON + * which implies: + * 4*DBL_EPSILON^2 <= max (|dx|, |dy|)^2 <= dx^2 + dy^2 + * + * From the definition of a, we get: + * a = dx^2 + dy^2 - dr^2 < DBL_EPSILON^2 + * dx^2 + dy^2 - DBL_EPSILON^2 < dr^2 + * 3*DBL_EPSILON^2 < dr^2 + * + * which is inconsistent with the hypotheses, thus |dr| < + * DBL_EPSILON is false or the gradient is degenerate. + */ + assert (fabs (dr) >= DBL_EPSILON); + + /* + * If a == 0, all the circles are tangent to a line in the + * focus point. If this line is within the box extents, we + * should add the circle with infinite radius, but this would + * make the range unbounded, so we add the smallest circle whose + * distance to the desired (degenerate) circle within the + * bounding box does not exceed tolerance. + * + * The equation of the line is b==0, i.e.: + * x*dx + y*dy + cr*dr == 0 + * + * We compute the intersection of the line with the box and + * keep the intersection with maximum square distance (maxd2) + * from the focus point. + * + * In the code the intersection is represented in another + * coordinate system, whose origin is the focus point and + * which has a u,v axes, which are respectively orthogonal and + * parallel to the edge being intersected. + * + * The intersection is valid only if it belongs to the box, + * otherwise it is ignored. + * + * For example: + * + * y = y0 + * x*dx + y0*dy + cr*dr == 0 + * x = -(y0*dy + cr*dr) / dx + * + * which in (u,v) is: + * u = y0 - y_focus + * v = -(y0*dy + cr*dr) / dx - x_focus + * + * In the code: + * u = (edge) - (u_origin) + * v = -((edge) * (delta) + cr*dr) / (den) - v_focus + */ +#define T_EDGE(edge,delta,den,lower,upper,u_origin,v_origin) \ + if (fabs (den) >= DBL_EPSILON) { \ + double v; \ + \ + v = -((edge) * (delta) + cr * dr) / (den); \ + if ((lower) <= v && v <= (upper)) { \ + double u, d2; \ + \ + u = (edge) - (u_origin); \ + v -= (v_origin); \ + d2 = u*u + v*v; \ + if (maxd2 < d2) \ + maxd2 = d2; \ + } \ + } + + maxd2 = 0; + + /* degenerate circles (lines) passing through each edge */ + T_EDGE (y0, dy, dx, minx, maxx, y_focus, x_focus); + T_EDGE (y1, dy, dx, minx, maxx, y_focus, x_focus); + T_EDGE (x0, dx, dy, miny, maxy, x_focus, y_focus); + T_EDGE (x1, dx, dy, miny, maxy, x_focus, y_focus); + +#undef T_EDGE + + /* + * The limit circle can be transformed rigidly to the y=0 line + * and the circles tangent to it in (0,0) are: + * + * x^2 + (y-r)^2 = r^2 <=> x^2 + y^2 - 2*y*r = 0 + * + * y is the distance from the line, in our case tolerance; + * x is the distance along the line, i.e. sqrt(maxd2), + * so: + * + * r = cr + dr * t = (maxd2 + tolerance^2) / (2*tolerance) + * t = (r - cr) / dr = + * (maxd2 + tolerance^2 - 2*tolerance*cr) / (2*tolerance*dr) + */ + if (maxd2 > 0) { + double t_limit = maxd2 + tolerance*tolerance - 2*tolerance*cr; + t_limit /= 2 * tolerance * dr; + valid = _extend_range (range, t_limit, valid); + } + + /* + * Nondegenerate, nonlimit circles passing through the corners. + * + * a == 0 && a*t^2 - 2*b*t + c == 0 + * + * t = c / (2*b) + * + * The b == 0 case has just been handled, so we only have to + * compute this if b != 0. + */ +#define T_CORNER(x,y) \ + b = (x) * dx + (y) * dy + cr * dr; \ + if (fabs (b) >= DBL_EPSILON) { \ + double t_corner; \ + double x2 = (x) * (x); \ + double y2 = (y) * (y); \ + double cr2 = (cr) * (cr); \ + double c = x2 + y2 - cr2; \ + \ + t_corner = 0.5 * c / b; \ + if (t_corner * dr >= mindr) \ + valid = _extend_range (range, t_corner, valid); \ + } + + /* circles touching each corner */ + T_CORNER (x0, y0); + T_CORNER (x0, y1); + T_CORNER (x1, y0); + T_CORNER (x1, y1); + +#undef T_CORNER + } else { + double inva, b, c, d; + + inva = 1 / a; + + /* + * Nondegenerate, nonlimit circles passing through the corners. + * + * a != 0 && a*t^2 - 2*b*t + c == 0 + * + * t = (b +- sqrt (b*b - a*c)) / a + * + * If the argument of sqrt() is negative, then no circle + * passes through the corner. + */ +#define T_CORNER(x,y) \ + b = (x) * dx + (y) * dy + cr * dr; \ + c = (x) * (x) + (y) * (y) - cr * cr; \ + d = b * b - a * c; \ + if (d >= 0) { \ + double t_corner; \ + \ + d = sqrt (d); \ + t_corner = (b + d) * inva; \ + if (t_corner * dr >= mindr) \ + valid = _extend_range (range, t_corner, valid); \ + t_corner = (b - d) * inva; \ + if (t_corner * dr >= mindr) \ + valid = _extend_range (range, t_corner, valid); \ + } + + /* circles touching each corner */ + T_CORNER (x0, y0); + T_CORNER (x0, y1); + T_CORNER (x1, y0); + T_CORNER (x1, y1); + +#undef T_CORNER + } +} + +/** + * _cairo_gradient_pattern_box_to_parameter + * + * Compute a interpolation range sufficient to draw (within the given + * tolerance) the gradient in the given box getting the same result as + * using the (-inf, +inf) range. + * + * Assumes that the pattern is not degenerate. This can be guaranteed + * by simplifying it to a solid clear if _cairo_pattern_is_clear or to + * a solid color if _cairo_gradient_pattern_is_solid. + * + * The range isn't guaranteed to be minimal, but it tries to. + **/ +void +_cairo_gradient_pattern_box_to_parameter (const cairo_gradient_pattern_t *gradient, + double x0, double y0, + double x1, double y1, + double tolerance, + double out_range[2]) { - return linear->p1.x == linear->p2.x && linear->p1.y == linear->p2.y; + assert (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR || + gradient->base.type == CAIRO_PATTERN_TYPE_RADIAL); + + if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) { + _cairo_linear_pattern_box_to_parameter ((cairo_linear_pattern_t *) gradient, + x0, y0, x1, y1, out_range); + } else { + _cairo_radial_pattern_box_to_parameter ((cairo_radial_pattern_t *) gradient, + x0, y0, x1, y1, tolerance, out_range); + } } -static cairo_bool_t -_radial_pattern_is_degenerate (const cairo_radial_pattern_t *radial) +/** + * _cairo_gradient_pattern_interpolate + * + * Interpolate between the start and end objects of linear or radial + * gradients. The interpolated object is stored in out_circle, with + * the radius being zero in the linear gradient case. + **/ +void +_cairo_gradient_pattern_interpolate (const cairo_gradient_pattern_t *gradient, + double t, + cairo_circle_double_t *out_circle) { - return radial->r1 == radial->r2 && - (radial->r1 == 0 /* && radial->r2 == 0 */ || - (radial->c1.x == radial->c2.x && radial->c1.y == radial->c2.y)); + assert (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR || + gradient->base.type == CAIRO_PATTERN_TYPE_RADIAL); + +#define lerp(a,b) (a)*(1-t) + (b)*t + + if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) { + cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient; + out_circle->center.x = lerp (linear->pd1.x, linear->pd2.x); + out_circle->center.y = lerp (linear->pd1.y, linear->pd2.y); + out_circle->radius = 0; + } else { + cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) gradient; + out_circle->center.x = lerp (radial->cd1.center.x, radial->cd2.center.x); + out_circle->center.y = lerp (radial->cd1.center.y, radial->cd2.center.y); + out_circle->radius = lerp (radial->cd1.radius , radial->cd2.radius); + } + +#undef lerp +} + + +/** + * _cairo_gradient_pattern_fit_to_range + * + * Scale the extremes of a gradient to guarantee that the coordinates + * and their deltas are within the range (-max_value, max_value). The + * new extremes are stored in out_circle. + * + * The pattern matrix is scaled to guarantee that the aspect of the + * gradient is the same and the result is stored in out_matrix. + * + **/ +void +_cairo_gradient_pattern_fit_to_range (const cairo_gradient_pattern_t *gradient, + double max_value, + cairo_matrix_t *out_matrix, + cairo_circle_double_t out_circle[2]) +{ + double dim; + + assert (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR || + gradient->base.type == CAIRO_PATTERN_TYPE_RADIAL); + + if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) { + cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient; + + out_circle[0].center = linear->pd1; + out_circle[0].radius = 0; + out_circle[1].center = linear->pd2; + out_circle[1].radius = 0; + + dim = fabs (linear->pd1.x); + dim = MAX (dim, fabs (linear->pd1.y)); + dim = MAX (dim, fabs (linear->pd2.x)); + dim = MAX (dim, fabs (linear->pd2.y)); + dim = MAX (dim, fabs (linear->pd1.x - linear->pd2.x)); + dim = MAX (dim, fabs (linear->pd1.y - linear->pd2.y)); + } else { + cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) gradient; + + out_circle[0] = radial->cd1; + out_circle[1] = radial->cd2; + + dim = fabs (radial->cd1.center.x); + dim = MAX (dim, fabs (radial->cd1.center.y)); + dim = MAX (dim, fabs (radial->cd1.radius)); + dim = MAX (dim, fabs (radial->cd2.center.x)); + dim = MAX (dim, fabs (radial->cd2.center.y)); + dim = MAX (dim, fabs (radial->cd2.radius)); + dim = MAX (dim, fabs (radial->cd1.center.x - radial->cd2.center.x)); + dim = MAX (dim, fabs (radial->cd1.center.y - radial->cd2.center.y)); + dim = MAX (dim, fabs (radial->cd1.radius - radial->cd2.radius)); + } + + if (unlikely (dim > max_value)) { + cairo_matrix_t scale; + + dim = max_value / dim; + + out_circle[0].center.x *= dim; + out_circle[0].center.y *= dim; + out_circle[0].radius *= dim; + out_circle[1].center.x *= dim; + out_circle[1].center.y *= dim; + out_circle[1].radius *= dim; + + cairo_matrix_init_scale (&scale, dim, dim); + cairo_matrix_multiply (out_matrix, &gradient->base.matrix, &scale); + } else { + *out_matrix = gradient->base.matrix; + } } static cairo_bool_t @@ -1786,27 +3128,40 @@ _gradient_is_clear (const cairo_gradient_pattern_t *gradient, gradient->stops[0].offset == gradient->stops[gradient->n_stops - 1].offset)) return TRUE; + if (gradient->base.type == CAIRO_PATTERN_TYPE_RADIAL) { + /* degenerate radial gradients are clear */ + if (_radial_pattern_is_degenerate ((cairo_radial_pattern_t *) gradient)) + return TRUE; + } else if (gradient->base.extend == CAIRO_EXTEND_NONE) { + /* EXTEND_NONE degenerate linear gradients are clear */ + if (_linear_pattern_is_degenerate ((cairo_linear_pattern_t *) gradient)) + return TRUE; + } + /* Check if the extents intersect the drawn part of the pattern. */ - if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) { - if (gradient->base.extend == CAIRO_EXTEND_NONE) { - cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient; - /* EXTEND_NONE degenerate linear gradients are clear */ - if (_linear_pattern_is_degenerate (linear)) + if (extents != NULL && + (gradient->base.extend == CAIRO_EXTEND_NONE || + gradient->base.type == CAIRO_PATTERN_TYPE_RADIAL)) + { + double t[2]; + + _cairo_gradient_pattern_box_to_parameter (gradient, + extents->x, + extents->y, + extents->x + extents->width, + extents->y + extents->height, + DBL_EPSILON, + t); + + if (gradient->base.extend == CAIRO_EXTEND_NONE && + (t[0] >= gradient->stops[gradient->n_stops - 1].offset || + t[1] <= gradient->stops[0].offset)) + { return TRUE; - - if (extents != NULL) { - double t[2]; - _extents_to_linear_parameter (linear, extents, t); - if ((t[0] <= 0.0 && t[1] <= 0.0) || (t[0] >= 1.0 && t[1] >= 1.0)) - return TRUE; - } } - } else { - cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) gradient; - /* degenerate radial gradients are clear */ - if (_radial_pattern_is_degenerate (radial)) + + if (t[0] == t[1]) return TRUE; - /* TODO: check actual intersection */ } for (i = 0; i < gradient->n_stops; i++) @@ -1927,6 +3282,141 @@ _gradient_color_average (const cairo_gradient_pattern_t *gradient, } /** + * _cairo_pattern_alpha_range + * + * Convenience function to determine the minimum and maximum alpha in + * the drawn part of a pattern (i.e. ignoring clear parts caused by + * extend modes and/or pattern shape). + * + * If not NULL, out_min and out_max will be set respectively to the + * minimum and maximum alpha value of the pattern. + **/ +void +_cairo_pattern_alpha_range (const cairo_pattern_t *pattern, + double *out_min, + double *out_max) +{ + double alpha_min, alpha_max; + + switch (pattern->type) { + case CAIRO_PATTERN_TYPE_SOLID: { + const cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) pattern; + alpha_min = alpha_max = solid->color.alpha; + break; + } + + case CAIRO_PATTERN_TYPE_LINEAR: + case CAIRO_PATTERN_TYPE_RADIAL: { + const cairo_gradient_pattern_t *gradient = (cairo_gradient_pattern_t *) pattern; + unsigned int i; + + assert (gradient->n_stops >= 1); + + alpha_min = alpha_max = gradient->stops[0].color.alpha; + for (i = 1; i < gradient->n_stops; i++) { + if (alpha_min > gradient->stops[i].color.alpha) + alpha_min = gradient->stops[i].color.alpha; + else if (alpha_max < gradient->stops[i].color.alpha) + alpha_max = gradient->stops[i].color.alpha; + } + + break; + } + + case CAIRO_PATTERN_TYPE_MESH: { + const cairo_mesh_pattern_t *mesh = (const cairo_mesh_pattern_t *) pattern; + const cairo_mesh_patch_t *patch = _cairo_array_index_const (&mesh->patches, 0); + unsigned int i, j, n = _cairo_array_num_elements (&mesh->patches); + + assert (n >= 1); + + alpha_min = alpha_max = patch[0].colors[0].alpha; + for (i = 0; i < n; i++) { + for (j = 0; j < 4; j++) { + if (patch[i].colors[j].alpha < alpha_min) + alpha_min = patch[i].colors[j].alpha; + else if (patch[i].colors[j].alpha > alpha_max) + alpha_max = patch[i].colors[j].alpha; + } + } + + break; + } + + default: + ASSERT_NOT_REACHED; + /* fall through */ + + case CAIRO_PATTERN_TYPE_SURFACE: + alpha_min = 0; + alpha_max = 1; + break; + } + + if (out_min) + *out_min = alpha_min; + if (out_max) + *out_max = alpha_max; +} + +/** + * _cairo_mesh_pattern_coord_box + * + * Convenience function to determine the range of the coordinates of + * the points used to define the patches of the mesh. + * + * This is guaranteed to contain the pattern extents, but might not be + * tight, just like a Bezier curve is always inside the convex hull of + * the control points. + * + * This function cannot be used while the mesh is being constructed. + * + * The function returns TRUE and sets the output parametes to define + * the coodrinate range if the mesh pattern contains at least one + * patch, otherwise it returns FALSE. + **/ +cairo_bool_t +_cairo_mesh_pattern_coord_box (const cairo_mesh_pattern_t *mesh, + double *out_xmin, + double *out_ymin, + double *out_xmax, + double *out_ymax) +{ + const cairo_mesh_patch_t *patch; + unsigned int num_patches, i, j, k; + double x0, y0, x1, y1; + + assert (mesh->current_patch == NULL); + + num_patches = _cairo_array_num_elements (&mesh->patches); + + if (num_patches == 0) + return FALSE; + + patch = _cairo_array_index_const (&mesh->patches, 0); + x0 = x1 = patch->points[0][0].x; + y0 = y1 = patch->points[0][0].y; + + for (i = 0; i < num_patches; i++) { + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) { + x0 = MIN (x0, patch[i].points[j][k].x); + y0 = MIN (y0, patch[i].points[j][k].y); + x1 = MAX (x1, patch[i].points[j][k].x); + y1 = MAX (y1, patch[i].points[j][k].y); + } + } + } + + *out_xmin = x0; + *out_ymin = y0; + *out_xmax = x1; + *out_ymax = y1; + + return TRUE; +} + +/** * _cairo_gradient_pattern_is_solid * * Convenience function to determine whether a gradient pattern is @@ -1966,7 +3456,13 @@ _cairo_gradient_pattern_is_solid (const cairo_gradient_pattern_t *gradient, if (extents == NULL) return FALSE; - _extents_to_linear_parameter (linear, extents, t); + _cairo_linear_pattern_box_to_parameter (linear, + extents->x, + extents->y, + extents->x + extents->width, + extents->y + extents->height, + t); + if (t[0] < 0.0 || t[1] > 1.0) return FALSE; } @@ -1987,6 +3483,22 @@ _cairo_gradient_pattern_is_solid (const cairo_gradient_pattern_t *gradient, return TRUE; } +static cairo_bool_t +_mesh_is_clear (const cairo_mesh_pattern_t *mesh) +{ + double x1, y1, x2, y2; + cairo_bool_t is_valid; + + is_valid = _cairo_mesh_pattern_coord_box (mesh, &x1, &y1, &x2, &y2); + if (!is_valid) + return TRUE; + + if (x2 - x1 < DBL_EPSILON || y2 - y1 < DBL_EPSILON) + return TRUE; + + return FALSE; +} + /** * _cairo_pattern_is_opaque_solid * @@ -2078,7 +3590,13 @@ _gradient_is_opaque (const cairo_gradient_pattern_t *gradient, if (extents == NULL) return FALSE; - _extents_to_linear_parameter (linear, extents, t); + _cairo_linear_pattern_box_to_parameter (linear, + extents->x, + extents->y, + extents->x + extents->width, + extents->y + extents->height, + t); + if (t[0] < 0.0 || t[1] > 1.0) return FALSE; } @@ -2119,6 +3637,8 @@ _cairo_pattern_is_opaque (const cairo_pattern_t *abstract_pattern, case CAIRO_PATTERN_TYPE_LINEAR: case CAIRO_PATTERN_TYPE_RADIAL: return _gradient_is_opaque (&pattern->gradient.base, extents); + case CAIRO_PATTERN_TYPE_MESH: + return FALSE; } ASSERT_NOT_REACHED; @@ -2142,6 +3662,8 @@ _cairo_pattern_is_clear (const cairo_pattern_t *abstract_pattern) case CAIRO_PATTERN_TYPE_LINEAR: case CAIRO_PATTERN_TYPE_RADIAL: return _gradient_is_clear (&pattern->gradient.base, NULL); + case CAIRO_PATTERN_TYPE_MESH: + return _mesh_is_clear (&pattern->mesh); } ASSERT_NOT_REACHED; @@ -2543,6 +4065,12 @@ _cairo_pattern_acquire_surface (const cairo_pattern_t *pattern, surface_out, attributes); + case CAIRO_PATTERN_TYPE_MESH: + return _cairo_pattern_acquire_surface_for_mesh ((cairo_mesh_pattern_t *) pattern, + dst, x, y, width, height, + surface_out, + attributes); + default: ASSERT_NOT_REACHED; return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); @@ -2695,40 +4223,32 @@ _cairo_pattern_get_extents (const cairo_pattern_t *pattern, (const cairo_radial_pattern_t *) pattern; double cx1, cy1; double cx2, cy2; - double r, D; + double r1, r2; - if (radial->r1 == 0 && radial->r2 == 0) + if (_radial_pattern_is_degenerate (radial)) { + /* cairo-gstate should have optimised degenerate + * patterns to solid clear patterns, so we can ignore + * them here. */ goto EMPTY; + } - cx1 = _cairo_fixed_to_double (radial->c1.x); - cy1 = _cairo_fixed_to_double (radial->c1.y); - r = _cairo_fixed_to_double (radial->r1); - x1 = cx1 - r; x2 = cx1 + r; - y1 = cy1 - r; y2 = cy1 + r; - - cx2 = _cairo_fixed_to_double (radial->c2.x); - cy2 = _cairo_fixed_to_double (radial->c2.y); - r = fabs (_cairo_fixed_to_double (radial->r2)); - + /* TODO: in some cases (focus outside/on the circle) it is + * half-bounded. */ if (pattern->extend != CAIRO_EXTEND_NONE) goto UNBOUNDED; - /* We need to be careful, as if the circles are not - * self-contained, then the solution is actually unbounded. - */ - D = (cx1-cx2)*(cx1-cx2) + (cy1-cy2)*(cy1-cy2); - if (D > r*r - 1e-5) - goto UNBOUNDED; + cx1 = radial->cd1.center.x; + cy1 = radial->cd1.center.y; + r1 = radial->cd1.radius; - if (cx2 - r < x1) - x1 = cx2 - r; - if (cx2 + r > x2) - x2 = cx2 + r; + cx2 = radial->cd2.center.x; + cy2 = radial->cd2.center.y; + r2 = radial->cd2.radius; - if (cy2 - r < y1) - y1 = cy2 - r; - if (cy2 + r > y2) - y2 = cy2 + r; + x1 = MIN (cx1 - r1, cx2 - r2); + y1 = MIN (cy1 - r1, cy2 - r2); + x2 = MAX (cx1 + r1, cx2 + r2); + y2 = MAX (cy1 + r1, cy2 + r2); } break; @@ -2740,20 +4260,26 @@ _cairo_pattern_get_extents (const cairo_pattern_t *pattern, if (pattern->extend != CAIRO_EXTEND_NONE) goto UNBOUNDED; - if (linear->p1.x == linear->p2.x && linear->p1.y == linear->p2.y) + if (_linear_pattern_is_degenerate (linear)) { + /* cairo-gstate should have optimised degenerate + * patterns to solid ones, so we can again ignore + * them here. */ goto EMPTY; + } + /* TODO: to get tight extents, use the matrix to transform + * the pattern instead of transforming the extents later. */ if (pattern->matrix.xy != 0. || pattern->matrix.yx != 0.) goto UNBOUNDED; - if (linear->p1.x == linear->p2.x) { + if (linear->pd1.x == linear->pd2.x) { x1 = -HUGE_VAL; x2 = HUGE_VAL; - y1 = _cairo_fixed_to_double (MIN (linear->p1.y, linear->p2.y)); - y2 = _cairo_fixed_to_double (MAX (linear->p1.y, linear->p2.y)); - } else if (linear->p1.y == linear->p2.y) { - x1 = _cairo_fixed_to_double (MIN (linear->p1.x, linear->p2.x)); - x2 = _cairo_fixed_to_double (MAX (linear->p1.x, linear->p2.x)); + y1 = MIN (linear->pd1.y, linear->pd2.y); + y2 = MAX (linear->pd1.y, linear->pd2.y); + } else if (linear->pd1.y == linear->pd2.y) { + x1 = MIN (linear->pd1.x, linear->pd2.x); + x2 = MAX (linear->pd1.x, linear->pd2.x); y1 = -HUGE_VAL; y2 = HUGE_VAL; } else { @@ -2762,6 +4288,29 @@ _cairo_pattern_get_extents (const cairo_pattern_t *pattern, } break; + case CAIRO_PATTERN_TYPE_MESH: + { + const cairo_mesh_pattern_t *mesh = + (const cairo_mesh_pattern_t *) pattern; + double padx, pady; + cairo_bool_t is_valid; + + is_valid = _cairo_mesh_pattern_coord_box (mesh, &x1, &y1, &x2, &y2); + if (!is_valid) + goto EMPTY; + + padx = pady = 1.; + cairo_matrix_transform_distance (&pattern->matrix, &padx, &pady); + padx = fabs (padx); + pady = fabs (pady); + + x1 -= padx; + y1 -= pady; + x2 += padx; + y2 += pady; + } + break; + default: ASSERT_NOT_REACHED; } @@ -2814,10 +4363,8 @@ _cairo_pattern_get_extents (const cairo_pattern_t *pattern, static unsigned long _cairo_solid_pattern_hash (unsigned long hash, - const cairo_pattern_t *pattern) + const cairo_solid_pattern_t *solid) { - const cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) pattern; - hash = _cairo_hash_bytes (hash, &solid->color, sizeof (solid->color)); return hash; @@ -2839,7 +4386,7 @@ _cairo_gradient_color_stops_hash (unsigned long hash, sizeof (double)); hash = _cairo_hash_bytes (hash, &gradient->stops[n].color, - sizeof (cairo_color_t)); + sizeof (cairo_color_stop_t)); } return hash; @@ -2849,8 +4396,8 @@ unsigned long _cairo_linear_pattern_hash (unsigned long hash, const cairo_linear_pattern_t *linear) { - hash = _cairo_hash_bytes (hash, &linear->p1, sizeof (linear->p1)); - hash = _cairo_hash_bytes (hash, &linear->p2, sizeof (linear->p2)); + hash = _cairo_hash_bytes (hash, &linear->pd1, sizeof (linear->pd1)); + hash = _cairo_hash_bytes (hash, &linear->pd2, sizeof (linear->pd2)); return _cairo_gradient_color_stops_hash (hash, &linear->base); } @@ -2859,20 +4406,30 @@ unsigned long _cairo_radial_pattern_hash (unsigned long hash, const cairo_radial_pattern_t *radial) { - hash = _cairo_hash_bytes (hash, &radial->c1, sizeof (radial->c1)); - hash = _cairo_hash_bytes (hash, &radial->r1, sizeof (radial->r1)); - hash = _cairo_hash_bytes (hash, &radial->c2, sizeof (radial->c2)); - hash = _cairo_hash_bytes (hash, &radial->r2, sizeof (radial->r2)); + hash = _cairo_hash_bytes (hash, &radial->cd1.center, sizeof (radial->cd1.center)); + hash = _cairo_hash_bytes (hash, &radial->cd1.radius, sizeof (radial->cd1.radius)); + hash = _cairo_hash_bytes (hash, &radial->cd2.center, sizeof (radial->cd2.center)); + hash = _cairo_hash_bytes (hash, &radial->cd2.radius, sizeof (radial->cd2.radius)); return _cairo_gradient_color_stops_hash (hash, &radial->base); } static unsigned long -_cairo_surface_pattern_hash (unsigned long hash, - const cairo_pattern_t *pattern) +_cairo_mesh_pattern_hash (unsigned long hash, const cairo_mesh_pattern_t *mesh) { - const cairo_surface_pattern_t *surface = (cairo_surface_pattern_t *) pattern; + const cairo_mesh_patch_t *patch = _cairo_array_index_const (&mesh->patches, 0); + unsigned int i, n = _cairo_array_num_elements (&mesh->patches); + + for (i = 0; i < n; i++) + hash = _cairo_hash_bytes (hash, patch + i, sizeof (cairo_mesh_patch_t)); + + return hash; +} +static unsigned long +_cairo_surface_pattern_hash (unsigned long hash, + const cairo_surface_pattern_t *surface) +{ hash ^= surface->surface->unique_id; return hash; @@ -2901,62 +4458,25 @@ _cairo_pattern_hash (const cairo_pattern_t *pattern) switch (pattern->type) { case CAIRO_PATTERN_TYPE_SOLID: - return _cairo_solid_pattern_hash (hash, pattern); + return _cairo_solid_pattern_hash (hash, (cairo_solid_pattern_t *) pattern); case CAIRO_PATTERN_TYPE_LINEAR: return _cairo_linear_pattern_hash (hash, (cairo_linear_pattern_t *) pattern); case CAIRO_PATTERN_TYPE_RADIAL: return _cairo_radial_pattern_hash (hash, (cairo_radial_pattern_t *) pattern); + case CAIRO_PATTERN_TYPE_MESH: + return _cairo_mesh_pattern_hash (hash, (cairo_mesh_pattern_t *) pattern); case CAIRO_PATTERN_TYPE_SURFACE: - return _cairo_surface_pattern_hash (hash, pattern); + return _cairo_surface_pattern_hash (hash, (cairo_surface_pattern_t *) pattern); default: ASSERT_NOT_REACHED; return FALSE; } } -static unsigned long -_cairo_gradient_pattern_color_stops_size (const cairo_pattern_t *pattern) -{ - cairo_gradient_pattern_t *gradient = (cairo_gradient_pattern_t *) pattern; - - return gradient->n_stops * (sizeof (double) + sizeof (cairo_color_t)); -} - -unsigned long -_cairo_pattern_size (const cairo_pattern_t *pattern) -{ - if (pattern->status) - return 0; - - /* XXX */ - switch (pattern->type) { - case CAIRO_PATTERN_TYPE_SOLID: - return sizeof (cairo_solid_pattern_t); - break; - case CAIRO_PATTERN_TYPE_SURFACE: - return sizeof (cairo_surface_pattern_t); - break; - case CAIRO_PATTERN_TYPE_LINEAR: - return sizeof (cairo_linear_pattern_t) + - _cairo_gradient_pattern_color_stops_size (pattern); - break; - case CAIRO_PATTERN_TYPE_RADIAL: - return sizeof (cairo_radial_pattern_t) + - _cairo_gradient_pattern_color_stops_size (pattern); - default: - ASSERT_NOT_REACHED; - return 0; - } -} - - static cairo_bool_t -_cairo_solid_pattern_equal (const cairo_pattern_t *A, - const cairo_pattern_t *B) +_cairo_solid_pattern_equal (const cairo_solid_pattern_t *a, + const cairo_solid_pattern_t *b) { - const cairo_solid_pattern_t *a = (cairo_solid_pattern_t *) A; - const cairo_solid_pattern_t *b = (cairo_solid_pattern_t *) B; - return _cairo_color_equal (&a->color, &b->color); } @@ -2983,16 +4503,16 @@ cairo_bool_t _cairo_linear_pattern_equal (const cairo_linear_pattern_t *a, const cairo_linear_pattern_t *b) { - if (a->p1.x != b->p1.x) + if (a->pd1.x != b->pd1.x) return FALSE; - if (a->p1.y != b->p1.y) + if (a->pd1.y != b->pd1.y) return FALSE; - if (a->p2.x != b->p2.x) + if (a->pd2.x != b->pd2.x) return FALSE; - if (a->p2.y != b->p2.y) + if (a->pd2.y != b->pd2.y) return FALSE; return _cairo_gradient_color_stops_equal (&a->base, &b->base); @@ -3002,34 +4522,54 @@ cairo_bool_t _cairo_radial_pattern_equal (const cairo_radial_pattern_t *a, const cairo_radial_pattern_t *b) { - if (a->c1.x != b->c1.x) + if (a->cd1.center.x != b->cd1.center.x) return FALSE; - if (a->c1.y != b->c1.y) + if (a->cd1.center.y != b->cd1.center.y) return FALSE; - if (a->r1 != b->r1) + if (a->cd1.radius != b->cd1.radius) return FALSE; - if (a->c2.x != b->c2.x) + if (a->cd2.center.x != b->cd2.center.x) return FALSE; - if (a->c2.y != b->c2.y) + if (a->cd2.center.y != b->cd2.center.y) return FALSE; - if (a->r2 != b->r2) + if (a->cd2.radius != b->cd2.radius) return FALSE; return _cairo_gradient_color_stops_equal (&a->base, &b->base); } static cairo_bool_t -_cairo_surface_pattern_equal (const cairo_pattern_t *A, - const cairo_pattern_t *B) +_cairo_mesh_pattern_equal (const cairo_mesh_pattern_t *a, + const cairo_mesh_pattern_t *b) { - const cairo_surface_pattern_t *a = (cairo_surface_pattern_t *) A; - const cairo_surface_pattern_t *b = (cairo_surface_pattern_t *) B; + const cairo_mesh_patch_t *patch_a, *patch_b; + unsigned int i, num_patches_a, num_patches_b; + + num_patches_a = _cairo_array_num_elements (&a->patches); + num_patches_b = _cairo_array_num_elements (&b->patches); + if (num_patches_a != num_patches_b) + return FALSE; + + for (i = 0; i < num_patches_a; i++) { + patch_a = _cairo_array_index_const (&a->patches, i); + patch_b = _cairo_array_index_const (&a->patches, i); + if (memcmp (patch_a, patch_b, sizeof(cairo_mesh_patch_t)) != 0) + return FALSE; + } + + return TRUE; +} + +static cairo_bool_t +_cairo_surface_pattern_equal (const cairo_surface_pattern_t *a, + const cairo_surface_pattern_t *b) +{ return a->surface->unique_id == b->surface->unique_id; } @@ -3061,15 +4601,20 @@ _cairo_pattern_equal (const cairo_pattern_t *a, const cairo_pattern_t *b) switch (a->type) { case CAIRO_PATTERN_TYPE_SOLID: - return _cairo_solid_pattern_equal (a, b); + return _cairo_solid_pattern_equal ((cairo_solid_pattern_t *) a, + (cairo_solid_pattern_t *) b); case CAIRO_PATTERN_TYPE_LINEAR: return _cairo_linear_pattern_equal ((cairo_linear_pattern_t *) a, (cairo_linear_pattern_t *) b); case CAIRO_PATTERN_TYPE_RADIAL: return _cairo_radial_pattern_equal ((cairo_radial_pattern_t *) a, (cairo_radial_pattern_t *) b); + case CAIRO_PATTERN_TYPE_MESH: + return _cairo_mesh_pattern_equal ((cairo_mesh_pattern_t *) a, + (cairo_mesh_pattern_t *) b); case CAIRO_PATTERN_TYPE_SURFACE: - return _cairo_surface_pattern_equal (a, b); + return _cairo_surface_pattern_equal ((cairo_surface_pattern_t *) a, + (cairo_surface_pattern_t *) b); default: ASSERT_NOT_REACHED; return FALSE; @@ -3269,13 +4814,13 @@ cairo_pattern_get_linear_points (cairo_pattern_t *pattern, return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); if (x0) - *x0 = _cairo_fixed_to_double (linear->p1.x); + *x0 = linear->pd1.x; if (y0) - *y0 = _cairo_fixed_to_double (linear->p1.y); + *y0 = linear->pd1.y; if (x1) - *x1 = _cairo_fixed_to_double (linear->p2.x); + *x1 = linear->pd2.x; if (y1) - *y1 = _cairo_fixed_to_double (linear->p2.y); + *y1 = linear->pd2.y; return CAIRO_STATUS_SUCCESS; } @@ -3313,17 +4858,273 @@ cairo_pattern_get_radial_circles (cairo_pattern_t *pattern, return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); if (x0) - *x0 = _cairo_fixed_to_double (radial->c1.x); + *x0 = radial->cd1.center.x; if (y0) - *y0 = _cairo_fixed_to_double (radial->c1.y); + *y0 = radial->cd1.center.y; if (r0) - *r0 = _cairo_fixed_to_double (radial->r1); + *r0 = radial->cd1.radius; if (x1) - *x1 = _cairo_fixed_to_double (radial->c2.x); + *x1 = radial->cd2.center.x; if (y1) - *y1 = _cairo_fixed_to_double (radial->c2.y); + *y1 = radial->cd2.center.y; if (r1) - *r1 = _cairo_fixed_to_double (radial->r2); + *r1 = radial->cd2.radius; + + return CAIRO_STATUS_SUCCESS; +} + +/** + * cairo_pattern_mesh_get_patch_count + * @pattern: a #cairo_pattern_t + * @count: return value for the number patches, or %NULL + * + * Gets the number of patches specified in the given mesh pattern. + * + * The number only includes patches which have been finished by + * calling cairo_pattern_mesh_end_patch(). For example it will be 0 + * during the definition of the first patch. + * + * Return value: %CAIRO_STATUS_SUCCESS, or + * %CAIRO_STATUS_PATTERN_TYPE_MISMATCH if @pattern is not a mesh + * pattern. + * + * Since: 1.12 + */ +cairo_status_t +cairo_pattern_mesh_get_patch_count (cairo_pattern_t *pattern, + unsigned int *count) +{ + cairo_mesh_pattern_t *mesh = (cairo_mesh_pattern_t *) pattern; + + if (unlikely (pattern->status)) + return pattern->status; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) + return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + + if (count) { + *count = _cairo_array_num_elements (&mesh->patches); + if (mesh->current_patch) + *count -= 1; + } + + return CAIRO_STATUS_SUCCESS; +} + +/** + * cairo_pattern_mesh_get_path + * @pattern: a #cairo_pattern_t + * @patch_num: the patch number to return data for + * + * Gets path defining the patch @patch_num for a mesh + * pattern. + * + * @patch_num can range 0 to 1 less than the number returned by + * cairo_pattern_mesh_get_patch_count(). + * + * Return value: the path defining the patch, or a path with status + * %CAIRO_STATUS_INVALID_INDEX if @patch_num or @point_num is not + * valid for @pattern. If @pattern is not a mesh pattern, a path with + * status %CAIRO_STATUS_PATTERN_TYPE_MISMATCH is returned. + * + * Since: 1.12 + */ +cairo_path_t * +cairo_pattern_mesh_get_path (cairo_pattern_t *pattern, + unsigned int patch_num) +{ + cairo_mesh_pattern_t *mesh = (cairo_mesh_pattern_t *) pattern; + const cairo_mesh_patch_t *patch; + cairo_path_t *path; + cairo_path_data_t *data; + unsigned int patch_count; + int l, current_point; + + if (unlikely (pattern->status)) + return _cairo_path_create_in_error (pattern->status); + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) + return _cairo_path_create_in_error (_cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH)); + + patch_count = _cairo_array_num_elements (&mesh->patches); + if (mesh->current_patch) + patch_count--; + + if (unlikely (patch_num >= patch_count)) + return _cairo_path_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_INDEX)); + + patch = _cairo_array_index_const (&mesh->patches, patch_num); + + path = malloc (sizeof (cairo_path_t)); + if (path == NULL) + return _cairo_path_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + + path->num_data = 18; + path->data = _cairo_malloc_ab (path->num_data, + sizeof (cairo_path_data_t)); + if (path->data == NULL) { + free (path); + return _cairo_path_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + } + + data = path->data; + data[0].header.type = CAIRO_PATH_MOVE_TO; + data[0].header.length = 2; + data[1].point.x = patch->points[0][0].x; + data[1].point.y = patch->points[0][0].y; + data += data[0].header.length; + + current_point = 0; + + for (l = 0; l < 4; l++) { + int i, j, k; + + data[0].header.type = CAIRO_PATH_CURVE_TO; + data[0].header.length = 4; + + for (k = 1; k < 4; k++) { + current_point = (current_point + 1) % 12; + i = mesh_path_point_i[current_point]; + j = mesh_path_point_j[current_point]; + data[k].point.x = patch->points[i][j].x; + data[k].point.y = patch->points[i][j].y; + } + + data += data[0].header.length; + } + + path->status = CAIRO_STATUS_SUCCESS; + + return path; +} + +/** + * cairo_pattern_mesh_get_corner_color_rgba + * @pattern: a #cairo_pattern_t + * @patch_num: the patch number to return data for + * @corner_num: the corner number to return data for + * @red: return value for red component of color, or %NULL + * @green: return value for green component of color, or %NULL + * @blue: return value for blue component of color, or %NULL + * @alpha: return value for alpha component of color, or %NULL + * + * Gets the color information in corner @corner_num of patch + * @patch_num for a mesh pattern. + * + * @patch_num can range 0 to 1 less than the number returned by + * cairo_pattern_mesh_get_patch_count(). + * + * Valid values for @corner_num are from 0 to 3 and identify the + * corners as explained in cairo_pattern_create_mesh(). + * + * Return value: %CAIRO_STATUS_SUCCESS, or %CAIRO_STATUS_INVALID_INDEX + * if @patch_num or @corner_num is not valid for @pattern. If + * @pattern is not a mesh pattern, %CAIRO_STATUS_PATTERN_TYPE_MISMATCH + * is returned. + * + * Since: 1.12 + **/ +cairo_status_t +cairo_pattern_mesh_get_corner_color_rgba (cairo_pattern_t *pattern, + unsigned int patch_num, + unsigned int corner_num, + double *red, double *green, + double *blue, double *alpha) +{ + cairo_mesh_pattern_t *mesh = (cairo_mesh_pattern_t *) pattern; + unsigned int patch_count; + const cairo_mesh_patch_t *patch; + + if (unlikely (pattern->status)) + return pattern->status; + + if (unlikely (pattern->type != CAIRO_PATTERN_TYPE_MESH)) + return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + + if (unlikely (corner_num > 3)) + return _cairo_error (CAIRO_STATUS_INVALID_INDEX); + + patch_count = _cairo_array_num_elements (&mesh->patches); + if (mesh->current_patch) + patch_count--; + + if (unlikely (patch_num >= patch_count)) + return _cairo_error (CAIRO_STATUS_INVALID_INDEX); + + patch = _cairo_array_index_const (&mesh->patches, patch_num); + + if (red) + *red = patch->colors[corner_num].red; + if (green) + *green = patch->colors[corner_num].green; + if (blue) + *blue = patch->colors[corner_num].blue; + if (alpha) + *alpha = patch->colors[corner_num].alpha; + + return CAIRO_STATUS_SUCCESS; +} + +/** + * cairo_pattern_mesh_get_control_point + * @pattern: a #cairo_pattern_t + * @patch_num: the patch number to return data for + * @point_num: the control point number to return data for + * @x: return value for the x coordinate of the control point, or %NULL + * @y: return value for the y coordinate of the control point, or %NULL + * + * Gets the control point @point_num of patch @patch_num for a mesh + * pattern. + * + * @patch_num can range 0 to 1 less than the number returned by + * cairo_pattern_mesh_get_patch_count(). + * + * Valid values for @point_num are from 0 to 3 and identify the + * control points as explained in cairo_pattern_create_mesh(). + * + * Return value: %CAIRO_STATUS_SUCCESS, or %CAIRO_STATUS_INVALID_INDEX + * if @patch_num or @point_num is not valid for @pattern. If @pattern + * is not a mesh pattern, %CAIRO_STATUS_PATTERN_TYPE_MISMATCH is + * returned. + * + * Since: 1.12 + **/ +cairo_status_t +cairo_pattern_mesh_get_control_point (cairo_pattern_t *pattern, + unsigned int patch_num, + unsigned int point_num, + double *x, double *y) +{ + cairo_mesh_pattern_t *mesh = (cairo_mesh_pattern_t *) pattern; + const cairo_mesh_patch_t *patch; + unsigned int patch_count; + int i, j; + + if (pattern->status) + return pattern->status; + + if (pattern->type != CAIRO_PATTERN_TYPE_MESH) + return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH); + + if (point_num > 3) + return _cairo_error (CAIRO_STATUS_INVALID_INDEX); + + patch_count = _cairo_array_num_elements (&mesh->patches); + if (mesh->current_patch) + patch_count--; + + if (unlikely (patch_num >= patch_count)) + return _cairo_error (CAIRO_STATUS_INVALID_INDEX); + + patch = _cairo_array_index_const (&mesh->patches, patch_num); + + i = mesh_control_point_i[point_num]; + j = mesh_control_point_j[point_num]; + + if (x) + *x = patch->points[i][j].x; + if (y) + *y = patch->points[i][j].y; return CAIRO_STATUS_SUCCESS; } |