/* cairo - a vector graphics library with display and print output * * Copyright © 2004 David Reveman * Copyright © 2005 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of David * Reveman not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior * permission. David Reveman makes no representations about the * suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * DAVID REVEMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL DAVID REVEMAN BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: David Reveman */ #include "cairoint.h" typedef void (*cairo_shader_function_t) (unsigned char *color0, unsigned char *color1, cairo_fixed_t factor, uint32_t *pixel); typedef struct _cairo_shader_color_stop { cairo_fixed_t offset; cairo_fixed_48_16_t scale; int id; unsigned char color_char[4]; } cairo_shader_color_stop_t; typedef struct _cairo_shader_op { cairo_shader_color_stop_t *stops; int n_stops; cairo_extend_t extend; cairo_shader_function_t shader_function; } cairo_shader_op_t; #define MULTIPLY_COLORCOMP(c1, c2) \ ((unsigned char) \ ((((unsigned char) (c1)) * (int) ((unsigned char) (c2))) / 0xff)) static const cairo_solid_pattern_t cairo_solid_pattern_nil = { { CAIRO_PATTERN_SOLID, /* type */ (unsigned int)-1, /* ref_count */ CAIRO_STATUS_NO_MEMORY, /* status */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ CAIRO_FILTER_DEFAULT, /* filter */ CAIRO_EXTEND_DEFAULT }, /* extend */ { 0.0, 0.0, 0.0, 1.0, /* solid black */ 0x0, 0x0, 0x0, 0xffff } }; static const cairo_surface_pattern_t cairo_surface_pattern_nil = { { CAIRO_PATTERN_SURFACE, /* type */ (unsigned int)-1, /* ref_count */ CAIRO_STATUS_NO_MEMORY, /* status */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ CAIRO_FILTER_DEFAULT, /* filter */ CAIRO_EXTEND_DEFAULT }, /* extend */ NULL /* surface */ }; static const cairo_linear_pattern_t cairo_linear_pattern_nil = { { { CAIRO_PATTERN_LINEAR, /* type */ (unsigned int)-1, /* ref_count */ CAIRO_STATUS_NO_MEMORY, /* status */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ CAIRO_FILTER_DEFAULT, /* filter */ CAIRO_EXTEND_DEFAULT }, /* extend */ NULL, /* stops */ 0 }, /* n_stops */ { 0., 0. }, { 1.0, 1.0 } /* point0, point1 */ }; static const cairo_radial_pattern_t cairo_radial_pattern_nil = { { { CAIRO_PATTERN_RADIAL, /* type */ (unsigned int)-1, /* ref_count */ CAIRO_STATUS_NO_MEMORY, /* status */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ CAIRO_FILTER_DEFAULT, /* filter */ CAIRO_EXTEND_DEFAULT }, /* extend */ NULL, /* stops */ 0 }, /* n_stops */ { 0., 0. }, { 0.0, 0.0 }, /* center0, center1 */ 1.0, 1.0, /* radius0, radius1 */ }; static void _cairo_pattern_init (cairo_pattern_t *pattern, cairo_pattern_type_t type) { pattern->type = type; pattern->ref_count = 1; pattern->status = CAIRO_STATUS_SUCCESS; pattern->extend = CAIRO_EXTEND_DEFAULT; pattern->filter = CAIRO_FILTER_DEFAULT; cairo_matrix_init_identity (&pattern->matrix); } static void _cairo_gradient_pattern_init_copy (cairo_gradient_pattern_t *pattern, const cairo_gradient_pattern_t *other) { if (other->base.type == CAIRO_PATTERN_LINEAR) { cairo_linear_pattern_t *dst = (cairo_linear_pattern_t *) pattern; cairo_linear_pattern_t *src = (cairo_linear_pattern_t *) other; *dst = *src; } else { cairo_radial_pattern_t *dst = (cairo_radial_pattern_t *) pattern; cairo_radial_pattern_t *src = (cairo_radial_pattern_t *) other; *dst = *src; } if (other->n_stops) { pattern->stops = malloc (other->n_stops * sizeof (cairo_color_stop_t)); if (!pattern->stops) { if (other->base.type == CAIRO_PATTERN_LINEAR) _cairo_gradient_pattern_init_copy (pattern, &cairo_linear_pattern_nil.base); else _cairo_gradient_pattern_init_copy (pattern, &cairo_radial_pattern_nil.base); return; } memcpy (pattern->stops, other->stops, other->n_stops * sizeof (cairo_color_stop_t)); } } void _cairo_pattern_init_copy (cairo_pattern_t *pattern, const cairo_pattern_t *other) { switch (other->type) { case CAIRO_PATTERN_SOLID: { cairo_solid_pattern_t *dst = (cairo_solid_pattern_t *) pattern; cairo_solid_pattern_t *src = (cairo_solid_pattern_t *) other; *dst = *src; } break; case CAIRO_PATTERN_SURFACE: { cairo_surface_pattern_t *dst = (cairo_surface_pattern_t *) pattern; cairo_surface_pattern_t *src = (cairo_surface_pattern_t *) other; *dst = *src; cairo_surface_reference (dst->surface); } break; case CAIRO_PATTERN_LINEAR: case CAIRO_PATTERN_RADIAL: { cairo_gradient_pattern_t *dst = (cairo_gradient_pattern_t *) pattern; cairo_gradient_pattern_t *src = (cairo_gradient_pattern_t *) other; _cairo_gradient_pattern_init_copy (dst, src); } break; } pattern->ref_count = 1; } void _cairo_pattern_fini (cairo_pattern_t *pattern) { switch (pattern->type) { case CAIRO_PATTERN_SOLID: break; case CAIRO_PATTERN_SURFACE: { cairo_surface_pattern_t *fini = (cairo_surface_pattern_t *) pattern; cairo_surface_destroy (fini->surface); } break; case CAIRO_PATTERN_LINEAR: case CAIRO_PATTERN_RADIAL: { cairo_gradient_pattern_t *fini = (cairo_gradient_pattern_t *) pattern; if (fini->n_stops) free (fini->stops); } break; } } void _cairo_pattern_init_solid (cairo_solid_pattern_t *pattern, const cairo_color_t *color) { _cairo_pattern_init (&pattern->base, CAIRO_PATTERN_SOLID); pattern->color = *color; } void _cairo_pattern_init_for_surface (cairo_surface_pattern_t *pattern, cairo_surface_t *surface) { _cairo_pattern_init (&pattern->base, CAIRO_PATTERN_SURFACE); pattern->surface = surface; cairo_surface_reference (surface); } static void _cairo_pattern_init_gradient (cairo_gradient_pattern_t *pattern, cairo_pattern_type_t type) { _cairo_pattern_init (&pattern->base, type); pattern->stops = 0; pattern->n_stops = 0; } 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_LINEAR); pattern->point0.x = x0; pattern->point0.y = y0; pattern->point1.x = x1; pattern->point1.y = y1; } 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_RADIAL); pattern->center0.x = cx0; pattern->center0.y = cy0; pattern->radius0 = fabs (radius0); pattern->center1.x = cx1; pattern->center1.y = cy1; pattern->radius1 = fabs (radius1); } cairo_pattern_t * _cairo_pattern_create_solid (const cairo_color_t *color) { cairo_solid_pattern_t *pattern; pattern = malloc (sizeof (cairo_solid_pattern_t)); if (pattern == NULL) return (cairo_pattern_t *) &cairo_solid_pattern_nil.base; _cairo_pattern_init_solid (pattern, color); return &pattern->base; } /** * _cairo_pattern_create_in_error: * @status: an error status * * Create an empty #cairo_pattern_t object to hold an error * status. This is useful for propagating status values from an * existing object to a new #cairo_pattern_t. * * Return value: a (solid, black) #cairo_pattern_t object with status * of @status. If there is insufficient memory a pointer to a special, * static cairo_solid_pattern_nil will be returned instead with a * status of CAIRO_STATUS_NO_MEMORY rather than @status. * * Return value: **/ cairo_pattern_t * _cairo_pattern_create_in_error (cairo_status_t status) { cairo_solid_pattern_t *pattern; pattern = malloc (sizeof (cairo_solid_pattern_t)); if (pattern == NULL) return (cairo_pattern_t *) &cairo_solid_pattern_nil.base; _cairo_pattern_init_solid (pattern, CAIRO_COLOR_BLACK); pattern->base.status = status; return &pattern->base; } cairo_pattern_t * cairo_pattern_create_for_surface (cairo_surface_t *surface) { cairo_surface_pattern_t *pattern; pattern = malloc (sizeof (cairo_surface_pattern_t)); if (pattern == NULL) return (cairo_pattern_t *)&cairo_surface_pattern_nil.base; _cairo_pattern_init_for_surface (pattern, surface); /* this will go away when we completely remove the surface attributes */ if (surface->repeat) pattern->base.extend = CAIRO_EXTEND_REPEAT; else pattern->base.extend = CAIRO_EXTEND_DEFAULT; return &pattern->base; } cairo_pattern_t * cairo_pattern_create_linear (double x0, double y0, double x1, double y1) { cairo_linear_pattern_t *pattern; pattern = malloc (sizeof (cairo_linear_pattern_t)); if (pattern == NULL) return (cairo_pattern_t *) &cairo_linear_pattern_nil.base; _cairo_pattern_init_linear (pattern, x0, y0, x1, y1); return &pattern->base.base; } cairo_pattern_t * cairo_pattern_create_radial (double cx0, double cy0, double radius0, double cx1, double cy1, double radius1) { cairo_radial_pattern_t *pattern; pattern = malloc (sizeof (cairo_radial_pattern_t)); if (pattern == NULL) return (cairo_pattern_t *) &cairo_radial_pattern_nil.base; _cairo_pattern_init_radial (pattern, cx0, cy0, radius0, cx1, cy1, radius1); return &pattern->base.base; } void cairo_pattern_reference (cairo_pattern_t *pattern) { if (pattern == NULL) return; if (pattern->ref_count == (unsigned int)-1) return; pattern->ref_count++; } /** * cairo_pattern_status: * @pattern: a #cairo_pattern_t * * Checks whether an error has previously occurred for this * pattern. * * Return value: %CAIRO_STATUS_SUCCESS or %CAIRO_STATUS_NO_MEMORY **/ cairo_status_t cairo_pattern_status (cairo_pattern_t *pattern) { return pattern->status; } void cairo_pattern_destroy (cairo_pattern_t *pattern) { if (pattern == NULL) return; if (pattern->ref_count == (unsigned int)-1) return; pattern->ref_count--; if (pattern->ref_count) return; _cairo_pattern_fini (pattern); free (pattern); } static void _cairo_pattern_add_color_stop (cairo_gradient_pattern_t *pattern, double offset, cairo_color_t *color) { cairo_color_stop_t *stop; cairo_color_stop_t *new_stops; pattern->n_stops++; new_stops = realloc (pattern->stops, pattern->n_stops * sizeof (cairo_color_stop_t)); if (new_stops == NULL) { pattern->base.status = CAIRO_STATUS_NO_MEMORY; return; } pattern->stops = new_stops; stop = &pattern->stops[pattern->n_stops - 1]; stop->offset = _cairo_fixed_from_double (offset); stop->color = *color; } void cairo_pattern_add_color_stop_rgb (cairo_pattern_t *pattern, double offset, double red, double green, double blue) { cairo_color_t color; if (pattern->status) return; if (pattern->type != CAIRO_PATTERN_LINEAR && pattern->type != CAIRO_PATTERN_RADIAL) { pattern->status = CAIRO_STATUS_PATTERN_TYPE_MISMATCH; return; } _cairo_restrict_value (&offset, 0.0, 1.0); _cairo_restrict_value (&red, 0.0, 1.0); _cairo_restrict_value (&green, 0.0, 1.0); _cairo_restrict_value (&blue, 0.0, 1.0); _cairo_color_init_rgb (&color, red, green, blue); _cairo_pattern_add_color_stop ((cairo_gradient_pattern_t *) pattern, offset, &color); } void cairo_pattern_add_color_stop_rgba (cairo_pattern_t *pattern, double offset, double red, double green, double blue, double alpha) { cairo_color_t color; if (pattern->status) return; if (pattern->type != CAIRO_PATTERN_LINEAR && pattern->type != CAIRO_PATTERN_RADIAL) { pattern->status = CAIRO_STATUS_PATTERN_TYPE_MISMATCH; return; } _cairo_restrict_value (&offset, 0.0, 1.0); _cairo_restrict_value (&red, 0.0, 1.0); _cairo_restrict_value (&green, 0.0, 1.0); _cairo_restrict_value (&blue, 0.0, 1.0); _cairo_restrict_value (&alpha, 0.0, 1.0); _cairo_color_init_rgba (&color, red, green, blue, alpha); _cairo_pattern_add_color_stop ((cairo_gradient_pattern_t *) pattern, offset, &color); } void cairo_pattern_set_matrix (cairo_pattern_t *pattern, const cairo_matrix_t *matrix) { if (pattern->status) return; pattern->matrix = *matrix; } void cairo_pattern_get_matrix (cairo_pattern_t *pattern, cairo_matrix_t *matrix) { *matrix = pattern->matrix; } void cairo_pattern_set_filter (cairo_pattern_t *pattern, cairo_filter_t filter) { if (pattern->status) return; pattern->filter = filter; } cairo_filter_t cairo_pattern_get_filter (cairo_pattern_t *pattern) { return pattern->filter; } void cairo_pattern_set_extend (cairo_pattern_t *pattern, cairo_extend_t extend) { if (pattern->status) return; pattern->extend = extend; } cairo_extend_t cairo_pattern_get_extend (cairo_pattern_t *pattern) { return pattern->extend; } void _cairo_pattern_transform (cairo_pattern_t *pattern, const cairo_matrix_t *ctm_inverse) { assert (pattern->status == CAIRO_STATUS_SUCCESS); cairo_matrix_multiply (&pattern->matrix, ctm_inverse, &pattern->matrix); } #define INTERPOLATE_COLOR_NEAREST(c1, c2, factor) \ ((factor < 32768)? c1: c2) static void _cairo_pattern_shader_nearest (unsigned char *color0, unsigned char *color1, cairo_fixed_t factor, uint32_t *pixel) { *pixel = ((INTERPOLATE_COLOR_NEAREST (color0[3], color1[3], factor) << 24) | (INTERPOLATE_COLOR_NEAREST (color0[0], color1[0], factor) << 16) | (INTERPOLATE_COLOR_NEAREST (color0[1], color1[1], factor) << 8) | (INTERPOLATE_COLOR_NEAREST (color0[2], color1[2], factor) << 0)); } #undef INTERPOLATE_COLOR_NEAREST #define INTERPOLATE_COLOR_LINEAR(c1, c2, factor) \ (((c2 * factor) + (c1 * (65536 - factor))) / 65536) static void _cairo_pattern_shader_linear (unsigned char *color0, unsigned char *color1, cairo_fixed_t factor, uint32_t *pixel) { *pixel = ((INTERPOLATE_COLOR_LINEAR (color0[3], color1[3], factor) << 24) | (INTERPOLATE_COLOR_LINEAR (color0[0], color1[0], factor) << 16) | (INTERPOLATE_COLOR_LINEAR (color0[1], color1[1], factor) << 8) | (INTERPOLATE_COLOR_LINEAR (color0[2], color1[2], factor) << 0)); } #define E_MINUS_ONE 1.7182818284590452354 static void _cairo_pattern_shader_gaussian (unsigned char *color0, unsigned char *color1, cairo_fixed_t factor, uint32_t *pixel) { double f = ((double) factor) / 65536.0; factor = (cairo_fixed_t) (((exp (f * f) - 1.0) / E_MINUS_ONE) * 65536); *pixel = ((INTERPOLATE_COLOR_LINEAR (color0[3], color1[3], factor) << 24) | (INTERPOLATE_COLOR_LINEAR (color0[0], color1[0], factor) << 16) | (INTERPOLATE_COLOR_LINEAR (color0[1], color1[1], factor) << 8) | (INTERPOLATE_COLOR_LINEAR (color0[2], color1[2], factor) << 0)); } #undef INTERPOLATE_COLOR_LINEAR static int _cairo_shader_color_stop_compare (const void *elem1, const void *elem2) { cairo_shader_color_stop_t *s1 = (cairo_shader_color_stop_t *) elem1; cairo_shader_color_stop_t *s2 = (cairo_shader_color_stop_t *) elem2; return (s1->offset == s2->offset) ? /* equal offsets, sort on id */ ((s1->id < s2->id) ? -1 : 1) : /* sort on offset */ ((s1->offset < s2->offset) ? -1 : 1); } static cairo_status_t _cairo_pattern_shader_init (cairo_gradient_pattern_t *pattern, cairo_shader_op_t *op) { int i; op->stops = malloc (pattern->n_stops * sizeof (cairo_shader_color_stop_t)); if (!op->stops) return CAIRO_STATUS_NO_MEMORY; for (i = 0; i < pattern->n_stops; i++) { op->stops[i].color_char[0] = pattern->stops[i].color.red * 0xff; op->stops[i].color_char[1] = pattern->stops[i].color.green * 0xff; op->stops[i].color_char[2] = pattern->stops[i].color.blue * 0xff; op->stops[i].color_char[3] = pattern->stops[i].color.alpha * 0xff; op->stops[i].offset = pattern->stops[i].offset; op->stops[i].id = i; } /* sort stops in ascending order */ qsort (op->stops, pattern->n_stops, sizeof (cairo_shader_color_stop_t), _cairo_shader_color_stop_compare); /* this scale value is used only when computing gradient values * before the defined range, in which case stop 0 is used for both * ends of the interpolation, making the value of 'scale' not * actually matter, except that valgrind notices we're using * an undefined value. */ op->stops[0].scale = 0; for (i = 0; i < pattern->n_stops - 1; i++) { op->stops[i + 1].scale = op->stops[i + 1].offset - op->stops[i].offset; if (op->stops[i + 1].scale == 65536) op->stops[i + 1].scale = 0; } op->n_stops = pattern->n_stops; op->extend = pattern->base.extend; /* XXX: this is wrong, the filter should not be used for selecting color stop interpolation function. function should always be 'linear' and filter should be used for computing pixels. */ switch (pattern->base.filter) { case CAIRO_FILTER_FAST: case CAIRO_FILTER_NEAREST: op->shader_function = _cairo_pattern_shader_nearest; break; case CAIRO_FILTER_GAUSSIAN: op->shader_function = _cairo_pattern_shader_gaussian; break; case CAIRO_FILTER_GOOD: case CAIRO_FILTER_BEST: case CAIRO_FILTER_BILINEAR: op->shader_function = _cairo_pattern_shader_linear; break; } return CAIRO_STATUS_SUCCESS; } static void _cairo_pattern_shader_fini (cairo_shader_op_t *op) { if (op->stops) free (op->stops); } /* Find two color stops bounding the given offset. If the given offset * is before the first or after the last stop offset, the nearest * offset is returned twice. */ static void _cairo_shader_op_find_color_stops (cairo_shader_op_t *op, cairo_fixed_t offset, cairo_shader_color_stop_t *stops[2]) { int i; /* Before first stop. */ if (offset <= op->stops[0].offset) { stops[0] = &op->stops[0]; stops[1] = &op->stops[0]; return; } /* Between two stops. */ for (i = 0; i < op->n_stops - 1; i++) { if (offset <= op->stops[i + 1].offset) { stops[0] = &op->stops[i]; stops[1] = &op->stops[i + 1]; return; } } /* After last stop. */ stops[0] = &op->stops[op->n_stops - 1]; stops[1] = &op->stops[op->n_stops - 1]; } static void _cairo_pattern_calc_color_at_pixel (cairo_shader_op_t *op, cairo_fixed_t factor, uint32_t *pixel) { cairo_shader_color_stop_t *stops[2]; switch (op->extend) { case CAIRO_EXTEND_REPEAT: factor -= factor & 0xffff0000; break; case CAIRO_EXTEND_REFLECT: if (factor < 0 || factor > 65536) { if ((factor >> 16) % 2) factor = 65536 - (factor - (factor & 0xffff0000)); else factor -= factor & 0xffff0000; } break; case CAIRO_EXTEND_NONE: break; } _cairo_shader_op_find_color_stops (op, factor, stops); /* take offset as new 0 of coordinate system */ factor -= stops[0]->offset; /* difference between two offsets == 0, abrubt change */ if (stops[1]->scale) factor = ((cairo_fixed_48_16_t) factor << 16) / stops[1]->scale; op->shader_function (stops[0]->color_char, stops[1]->color_char, factor, pixel); /* multiply alpha */ if (((unsigned char) (*pixel >> 24)) != 0xff) { *pixel = (*pixel & 0xff000000) | (MULTIPLY_COLORCOMP (*pixel >> 16, *pixel >> 24) << 16) | (MULTIPLY_COLORCOMP (*pixel >> 8, *pixel >> 24) << 8) | (MULTIPLY_COLORCOMP (*pixel >> 0, *pixel >> 24) << 0); } } static cairo_status_t _cairo_image_data_set_linear (cairo_linear_pattern_t *pattern, double offset_x, double offset_y, uint32_t *pixels, int width, int height) { int x, y; cairo_point_double_t point0, point1; double a, b, c, d, tx, ty; double scale, start, dx, dy, factor; cairo_shader_op_t op; cairo_status_t status; status = _cairo_pattern_shader_init (&pattern->base, &op); if (status) return status; /* We compute the position in the linear gradient for * a point q as: * * [q . (p1 - p0) - p0 . (p1 - p0)] / (p1 - p0) ^ 2 * * The computation is done in pattern space. The * calculation could be heavily optimized by using the * fact that 'factor' increases linearly in both * directions. */ point0.x = pattern->point0.x; point0.y = pattern->point0.y; point1.x = pattern->point1.x; point1.y = pattern->point1.y; _cairo_matrix_get_affine (&pattern->base.base.matrix, &a, &b, &c, &d, &tx, &ty); dx = point1.x - point0.x; dy = point1.y - point0.y; scale = dx * dx + dy * dy; scale = (scale) ? 1.0 / scale : 1.0; start = dx * point0.x + dy * point0.y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { double qx_device = x + offset_x; double qy_device = y + offset_y; /* transform fragment into pattern space */ double qx = a * qx_device + c * qy_device + tx; double qy = b * qx_device + d * qy_device + ty; factor = ((dx * qx + dy * qy) - start) * scale; _cairo_pattern_calc_color_at_pixel (&op, factor * 65536, pixels++); } } _cairo_pattern_shader_fini (&op); return CAIRO_STATUS_SUCCESS; } static void _cairo_linear_pattern_classify (cairo_linear_pattern_t *pattern, double offset_x, double offset_y, int width, int height, cairo_bool_t *is_horizontal, cairo_bool_t *is_vertical) { cairo_point_double_t point0, point1; double a, b, c, d, tx, ty; double scale, start, dx, dy; cairo_fixed_t factors[3]; int i; /* To classidy a pattern as horizontal or vertical, we first * compute the (fixed point) factors at the corners of the * pattern. We actually only need 3/4 corners, so we skip the * fourth. */ point0.x = pattern->point0.x; point0.y = pattern->point0.y; point1.x = pattern->point1.x; point1.y = pattern->point1.y; _cairo_matrix_get_affine (&pattern->base.base.matrix, &a, &b, &c, &d, &tx, &ty); dx = point1.x - point0.x; dy = point1.y - point0.y; scale = dx * dx + dy * dy; scale = (scale) ? 1.0 / scale : 1.0; start = dx * point0.x + dy * point0.y; for (i = 0; i < 3; i++) { double qx_device = (i % 2) * (width - 1) + offset_x; double qy_device = (i / 2) * (height - 1) + offset_y; /* transform fragment into pattern space */ double qx = a * qx_device + c * qy_device + tx; double qy = b * qx_device + d * qy_device + ty; factors[i] = _cairo_fixed_from_double (((dx * qx + dy * qy) - start) * scale); } /* We consider a pattern to be vertical if the fixed point factor * at the two upper corners is the same. We could accept a small * change, but determining what change is acceptable would require * sorting the stops in the pattern and looking at the differences. * * Horizontal works the same way with the two left corners. */ *is_vertical = factors[1] == factors[0]; *is_horizontal = factors[2] == factors[0]; } static cairo_status_t _cairo_image_data_set_radial (cairo_radial_pattern_t *pattern, double offset_x, double offset_y, uint32_t *pixels, int width, int height) { int x, y, aligned_circles; cairo_point_double_t c0, c1; double px, py, ex, ey; double a, b, c, d, tx, ty; double r0, r1, c0_e_x, c0_e_y, c0_e, c1_e_x, c1_e_y, c1_e, c0_c1_x, c0_c1_y, c0_c1, angle_c0, c1_y, y_x, c0_y, c0_x, r1_2, denumerator, fraction, factor; cairo_shader_op_t op; cairo_status_t status; status = _cairo_pattern_shader_init (&pattern->base, &op); if (status) return status; c0.x = pattern->center0.x; c0.y = pattern->center0.y; r0 = pattern->radius0; c1.x = pattern->center1.x; c1.y = pattern->center1.y; r1 = pattern->radius1; if (c0.x != c1.x || c0.y != c1.y) { aligned_circles = 0; c0_c1_x = c1.x - c0.x; c0_c1_y = c1.y - c0.y; c0_c1 = sqrt (c0_c1_x * c0_c1_x + c0_c1_y * c0_c1_y); r1_2 = r1 * r1; } else { aligned_circles = 1; r1 = 1.0 / (r1 - r0); r1_2 = c0_c1 = 0.0; /* shut up compiler */ } _cairo_matrix_get_affine (&pattern->base.base.matrix, &a, &b, &c, &d, &tx, &ty); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { px = x + offset_x; py = y + offset_y; /* transform fragment */ ex = a * px + c * py + tx; ey = b * px + d * py + ty; if (aligned_circles) { ex = ex - c1.x; ey = ey - c1.y; factor = (sqrt (ex * ex + ey * ey) - r0) * r1; } else { /* y (ex, ey) c0 -------------------+---------- x \ | __-- \ | __-- \ | __-- \ | __-- r1 \ | __-- c1 -- We need to calulate distance c0->x; the distance from the inner circle center c0, through fragment position (ex, ey) to point x where it crosses the outer circle. From points c0, c1 and (ex, ey) we get angle C0. With angle C0 we calculate distance c1->y and c0->y and by knowing c1->y and r1, we also know y->x. Adding y->x to c0->y gives us c0->x. The gradient offset can then be calculated as: offset = (c0->e - r0) / (c0->x - r0) */ c0_e_x = ex - c0.x; c0_e_y = ey - c0.y; c0_e = sqrt (c0_e_x * c0_e_x + c0_e_y * c0_e_y); c1_e_x = ex - c1.x; c1_e_y = ey - c1.y; c1_e = sqrt (c1_e_x * c1_e_x + c1_e_y * c1_e_y); denumerator = -2.0 * c0_e * c0_c1; if (denumerator != 0.0) { fraction = (c1_e * c1_e - c0_e * c0_e - c0_c1 * c0_c1) / denumerator; if (fraction > 1.0) fraction = 1.0; else if (fraction < -1.0) fraction = -1.0; angle_c0 = acos (fraction); c0_y = cos (angle_c0) * c0_c1; c1_y = sin (angle_c0) * c0_c1; y_x = sqrt (r1_2 - c1_y * c1_y); c0_x = y_x + c0_y; factor = (c0_e - r0) / (c0_x - r0); } else { factor = -r0; } } _cairo_pattern_calc_color_at_pixel (&op, factor * 65536, pixels++); } } _cairo_pattern_shader_fini (&op); return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_pattern_acquire_surface_for_gradient (cairo_gradient_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_image_surface_t *image; cairo_status_t status; uint32_t *data; cairo_bool_t repeat = FALSE; if (pattern->base.type == CAIRO_PATTERN_LINEAR) { cairo_bool_t is_horizontal; cairo_bool_t is_vertical; _cairo_linear_pattern_classify ((cairo_linear_pattern_t *)pattern, x, y, width, height, &is_horizontal, &is_vertical); if (is_horizontal) { height = 1; repeat = TRUE; } if (is_vertical) { width = 1; repeat = TRUE; } } data = malloc (width * height * 4); if (!data) return CAIRO_STATUS_NO_MEMORY; if (pattern->base.type == CAIRO_PATTERN_LINEAR) { cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) pattern; status = _cairo_image_data_set_linear (linear, x, y, data, width, height); } else { cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern; status = _cairo_image_data_set_radial (radial, x, y, data, width, height); } if (status) { free (data); return status; } image = (cairo_image_surface_t *) cairo_image_surface_create_for_data ((unsigned char *) data, CAIRO_FORMAT_ARGB32, width, height, width * 4); if (image == NULL) { free (data); return CAIRO_STATUS_NO_MEMORY; } _cairo_image_surface_assume_ownership_of_data (image); status = _cairo_surface_clone_similar (dst, &image->base, out); cairo_surface_destroy (&image->base); attr->x_offset = -x; attr->y_offset = -y; cairo_matrix_init_identity (&attr->matrix); attr->extend = repeat ? CAIRO_EXTEND_REPEAT : CAIRO_EXTEND_NONE; attr->filter = CAIRO_FILTER_NEAREST; attr->acquired = FALSE; return status; } static cairo_int_status_t _cairo_pattern_acquire_surface_for_solid (cairo_solid_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 *attribs) { *out = _cairo_surface_create_similar_solid (dst, CAIRO_FORMAT_ARGB32, 1, 1, &pattern->color); if (*out == NULL) return CAIRO_STATUS_NO_MEMORY; attribs->x_offset = attribs->y_offset = 0; cairo_matrix_init_identity (&attribs->matrix); attribs->extend = CAIRO_EXTEND_REPEAT; attribs->filter = CAIRO_FILTER_NEAREST; attribs->acquired = FALSE; return CAIRO_STATUS_SUCCESS; } /** * _cairo_pattern_is_opaque_solid * * Convenience function to determine whether a pattern is an opaque * (alpha==1.0) solid color pattern. This is done by testing whether * the pattern's alpha value when converted to a byte is 255, so if a * backend actually supported deep alpha channels this function might * not do the right thing. * * Return value: %TRUE if the pattern is an opaque, solid color. **/ cairo_bool_t _cairo_pattern_is_opaque_solid (cairo_pattern_t *pattern) { cairo_solid_pattern_t *solid; if (pattern->type != CAIRO_PATTERN_SOLID) return FALSE; solid = (cairo_solid_pattern_t *) pattern; return (solid->color.alpha >= ((double)0xff00 / (double)0xffff)); } static cairo_int_status_t _cairo_pattern_acquire_surface_for_surface (cairo_surface_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_int_status_t status; int tx, ty; attr->acquired = FALSE; if (_cairo_surface_is_image (dst)) { cairo_image_surface_t *image; status = _cairo_surface_acquire_source_image (pattern->surface, &image, &attr->extra); if (status) return status; *out = &image->base; attr->acquired = TRUE; } else { status = _cairo_surface_clone_similar (dst, pattern->surface, out); } attr->extend = pattern->base.extend; attr->filter = pattern->base.filter; if (_cairo_matrix_is_integer_translation (&pattern->base.matrix, &tx, &ty)) { cairo_matrix_init_identity (&attr->matrix); attr->x_offset = tx; attr->y_offset = ty; attr->filter = CAIRO_FILTER_NEAREST; } else { attr->matrix = pattern->base.matrix; attr->x_offset = attr->y_offset = 0; } return status; } /** * _cairo_pattern_acquire_surface: * @pattern: a #cairo_pattern_t * @dst: destination surface * @x: X coordinate in source corresponding to left side of destination area * @y: Y coordinate in source corresponding to top side of destination area * @width: width of destination area * @height: height of destination area * @surface_out: location to store a pointer to a surface * @attributes: surface attributes that destination backend should apply to * the returned surface * * A convenience function to obtain a surface to use as the source for * drawing on @dst. * * Return value: %CAIRO_STATUS_SUCCESS if a surface was stored in @surface_out. **/ cairo_int_status_t _cairo_pattern_acquire_surface (cairo_pattern_t *pattern, cairo_surface_t *dst, int x, int y, unsigned int width, unsigned int height, cairo_surface_t **surface_out, cairo_surface_attributes_t *attributes) { cairo_status_t status; if (pattern->status) return pattern->status; switch (pattern->type) { case CAIRO_PATTERN_SOLID: { cairo_solid_pattern_t *src = (cairo_solid_pattern_t *) pattern; status = _cairo_pattern_acquire_surface_for_solid (src, dst, x, y, width, height, surface_out, attributes); } break; case CAIRO_PATTERN_LINEAR: case CAIRO_PATTERN_RADIAL: { cairo_gradient_pattern_t *src = (cairo_gradient_pattern_t *) pattern; /* fast path for gradients with less than 2 color stops */ if (src->n_stops < 2) { const cairo_color_t *color; cairo_solid_pattern_t solid; if (src->n_stops) color = &src->stops->color; else color = CAIRO_COLOR_TRANSPARENT; _cairo_pattern_init_solid (&solid, color); status = _cairo_pattern_acquire_surface_for_solid (&solid, dst, x, y, width, height, surface_out, attributes); } else { status = _cairo_pattern_acquire_surface_for_gradient (src, dst, x, y, width, height, surface_out, attributes); } } break; case CAIRO_PATTERN_SURFACE: { cairo_surface_pattern_t *src = (cairo_surface_pattern_t *) pattern; status = _cairo_pattern_acquire_surface_for_surface (src, dst, x, y, width, height, surface_out, attributes); } break; default: status = CAIRO_INT_STATUS_UNSUPPORTED; } return status; } /** * _cairo_pattern_release_surface: * @pattern: a #cairo_pattern_t * @info: pointer to #cairo_surface_attributes_t filled in by * _cairo_pattern_acquire_surface * * Releases resources obtained by _cairo_pattern_acquire_surface. **/ void _cairo_pattern_release_surface (cairo_surface_t *dst, cairo_surface_t *surface, cairo_surface_attributes_t *attributes) { if (attributes->acquired) { _cairo_surface_release_source_image (dst, (cairo_image_surface_t *) surface, attributes->extra); } else { cairo_surface_destroy (surface); } } cairo_int_status_t _cairo_pattern_acquire_surfaces (cairo_pattern_t *src, cairo_pattern_t *mask, cairo_surface_t *dst, int src_x, int src_y, int mask_x, int mask_y, unsigned int width, unsigned int height, cairo_surface_t **src_out, cairo_surface_t **mask_out, cairo_surface_attributes_t *src_attributes, cairo_surface_attributes_t *mask_attributes) { cairo_int_status_t status; cairo_pattern_union_t tmp; if (src->status) return src->status; if (mask && mask->status) return mask->status; /* If src and mask are both solid, then the mask alpha can be * combined into src and mask can be ignored. */ /* XXX: This optimization assumes that there is no color * information in mask, so this will need to change when we * support RENDER-style 4-channel masks. */ if (src->type == CAIRO_PATTERN_SOLID && mask && mask->type == CAIRO_PATTERN_SOLID) { cairo_color_t combined; cairo_solid_pattern_t *src_solid = (cairo_solid_pattern_t *) src; cairo_solid_pattern_t *mask_solid = (cairo_solid_pattern_t *) mask; combined = src_solid->color; _cairo_color_multiply_alpha (&combined, mask_solid->color.alpha); _cairo_pattern_init_solid (&tmp.solid, &combined); mask = NULL; } else { _cairo_pattern_init_copy (&tmp.base, src); } status = _cairo_pattern_acquire_surface (&tmp.base, dst, src_x, src_y, width, height, src_out, src_attributes); _cairo_pattern_fini (&tmp.base); if (status) return status; if (mask) { _cairo_pattern_init_copy (&tmp.base, mask); status = _cairo_pattern_acquire_surface (&tmp.base, dst, mask_x, mask_y, width, height, mask_out, mask_attributes); _cairo_pattern_fini (&tmp.base); if (status) { _cairo_pattern_release_surface (dst, *src_out, src_attributes); return status; } } else { *mask_out = NULL; } return CAIRO_STATUS_SUCCESS; }