cairo/src/cairo-pattern.c
Carl Worth 8084fb9b09 Add new _cairo_pattern_create_in_error.
Propagate error values from cr->status to pattern->status.
2005-06-13 16:53:52 +00:00

1373 lines
37 KiB
C

/* 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 <davidr@novell.com>
*/
#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;
}