0,0 → 1,642 |
/* cairo - a vector graphics library with display and print output |
* |
* Copyright © 2011 Intel Corporation. |
* |
* This library is free software; you can redistribute it and/or |
* modify it either under the terms of the GNU Lesser General Public |
* License version 2.1 as published by the Free Software Foundation |
* (the "LGPL") or, at your option, under the terms of the Mozilla |
* Public License Version 1.1 (the "MPL"). If you do not alter this |
* notice, a recipient may use your version of this file under either |
* the MPL or the LGPL. |
* |
* You should have received a copy of the LGPL along with this library |
* in the file COPYING-LGPL-2.1; if not, write to the Free Software |
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA |
* You should have received a copy of the MPL along with this library |
* in the file COPYING-MPL-1.1 |
* |
* The contents of this file are subject to the Mozilla Public License |
* Version 1.1 (the "License"); you may not use this file except in |
* compliance with the License. You may obtain a copy of the License at |
* http://www.mozilla.og/MPL/ |
* |
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY |
* OF ANY KIND, either express or implied. See the LGPL or the MPL for |
* the specific language governing rights and limitations. |
* |
* Contributor(s): |
* Robert Bragg <robert@linux.intel.com> |
*/ |
//#include "cairoint.h" |
|
#include "cairo-cogl-private.h" |
#include "cairo-cogl-gradient-private.h" |
#include "cairo-image-surface-private.h" |
|
#include <cogl/cogl2-experimental.h> |
#include <glib.h> |
|
#define DUMP_GRADIENTS_TO_PNG |
|
static unsigned long |
_cairo_cogl_linear_gradient_hash (unsigned int n_stops, |
const cairo_gradient_stop_t *stops) |
{ |
return _cairo_hash_bytes (n_stops, stops, |
sizeof (cairo_gradient_stop_t) * n_stops); |
} |
|
static cairo_cogl_linear_gradient_t * |
_cairo_cogl_linear_gradient_lookup (cairo_cogl_device_t *ctx, |
unsigned long hash, |
unsigned int n_stops, |
const cairo_gradient_stop_t *stops) |
{ |
cairo_cogl_linear_gradient_t lookup; |
|
lookup.cache_entry.hash = hash, |
lookup.n_stops = n_stops; |
lookup.stops = stops; |
|
return _cairo_cache_lookup (&ctx->linear_cache, &lookup.cache_entry); |
} |
|
cairo_bool_t |
_cairo_cogl_linear_gradient_equal (const void *key_a, const void *key_b) |
{ |
const cairo_cogl_linear_gradient_t *a = key_a; |
const cairo_cogl_linear_gradient_t *b = key_b; |
|
if (a->n_stops != b->n_stops) |
return FALSE; |
|
return memcmp (a->stops, b->stops, a->n_stops * sizeof (cairo_gradient_stop_t)) == 0; |
} |
|
cairo_cogl_linear_gradient_t * |
_cairo_cogl_linear_gradient_reference (cairo_cogl_linear_gradient_t *gradient) |
{ |
assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count)); |
|
_cairo_reference_count_inc (&gradient->ref_count); |
|
return gradient; |
} |
|
void |
_cairo_cogl_linear_gradient_destroy (cairo_cogl_linear_gradient_t *gradient) |
{ |
GList *l; |
|
assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count)); |
|
if (! _cairo_reference_count_dec_and_test (&gradient->ref_count)) |
return; |
|
for (l = gradient->textures; l; l = l->next) { |
cairo_cogl_linear_texture_entry_t *entry = l->data; |
cogl_object_unref (entry->texture); |
free (entry); |
} |
g_list_free (gradient->textures); |
|
free (gradient); |
} |
|
static int |
_cairo_cogl_util_next_p2 (int a) |
{ |
int rval = 1; |
|
while (rval < a) |
rval <<= 1; |
|
return rval; |
} |
|
static float |
get_max_color_component_range (const cairo_color_stop_t *color0, const cairo_color_stop_t *color1) |
{ |
float range; |
float max = 0; |
|
range = fabs (color0->red - color1->red); |
max = MAX (range, max); |
range = fabs (color0->green - color1->green); |
max = MAX (range, max); |
range = fabs (color0->blue - color1->blue); |
max = MAX (range, max); |
range = fabs (color0->alpha - color1->alpha); |
max = MAX (range, max); |
|
return max; |
} |
|
static int |
_cairo_cogl_linear_gradient_width_for_stops (cairo_extend_t extend, |
unsigned int n_stops, |
const cairo_gradient_stop_t *stops) |
{ |
unsigned int n; |
float max_texels_per_unit_offset = 0; |
float total_offset_range; |
|
/* Find the stop pair demanding the most precision because we are |
* interpolating the largest color-component range. |
* |
* From that we can define the relative sizes of all the other |
* stop pairs within our texture and thus the overall size. |
* |
* To determine the maximum number of texels for a given gap we |
* look at the range of colors we are expected to interpolate (so |
* long as the stop offsets are not degenerate) and we simply |
* assume we want one texel for each unique color value possible |
* for a one byte-per-component representation. |
* XXX: maybe this is overkill and just allowing 128 levels |
* instead of 256 would be enough and then we'd rely on the |
* bilinear filtering to give the full range. |
* |
* XXX: potentially we could try and map offsets to pixels to come |
* up with a more precise mapping, but we are aiming to cache |
* the gradients so we can't make assumptions about how it will be |
* scaled in the future. |
*/ |
for (n = 1; n < n_stops; n++) { |
float color_range; |
float offset_range; |
float texels; |
float texels_per_unit_offset; |
|
/* note: degenerate stops don't need to be represented in the |
* texture but we want to be sure that solid gaps get at least |
* one texel and all other gaps get at least 2 texels. |
*/ |
|
if (stops[n].offset == stops[n-1].offset) |
continue; |
|
color_range = get_max_color_component_range (&stops[n].color, &stops[n-1].color); |
if (color_range == 0) |
texels = 1; |
else |
texels = MAX (2, 256.0f * color_range); |
|
/* So how many texels would we need to map over the full [0,1] |
* gradient range so this gap would have enough texels? ... */ |
offset_range = stops[n].offset - stops[n - 1].offset; |
texels_per_unit_offset = texels / offset_range; |
|
if (texels_per_unit_offset > max_texels_per_unit_offset) |
max_texels_per_unit_offset = texels_per_unit_offset; |
} |
|
total_offset_range = fabs (stops[n_stops - 1].offset - stops[0].offset); |
return max_texels_per_unit_offset * total_offset_range; |
} |
|
/* Aim to create gradient textures without an alpha component so we can avoid |
* needing to use blending... */ |
static CoglPixelFormat |
_cairo_cogl_linear_gradient_format_for_stops (cairo_extend_t extend, |
unsigned int n_stops, |
const cairo_gradient_stop_t *stops) |
{ |
unsigned int n; |
|
/* We have to add extra transparent texels to the end of the gradient to |
* handle CAIRO_EXTEND_NONE... */ |
if (extend == CAIRO_EXTEND_NONE) |
return COGL_PIXEL_FORMAT_BGRA_8888_PRE; |
|
for (n = 1; n < n_stops; n++) { |
if (stops[n].color.alpha != 1.0) |
return COGL_PIXEL_FORMAT_BGRA_8888_PRE; |
} |
|
return COGL_PIXEL_FORMAT_BGR_888; |
} |
|
static cairo_cogl_gradient_compatibility_t |
_cairo_cogl_compatibility_from_extend_mode (cairo_extend_t extend_mode) |
{ |
switch (extend_mode) |
{ |
case CAIRO_EXTEND_NONE: |
return CAIRO_COGL_GRADIENT_CAN_EXTEND_NONE; |
case CAIRO_EXTEND_PAD: |
return CAIRO_COGL_GRADIENT_CAN_EXTEND_PAD; |
case CAIRO_EXTEND_REPEAT: |
return CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT; |
case CAIRO_EXTEND_REFLECT: |
return CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT; |
} |
|
assert (0); /* not reached */ |
return CAIRO_EXTEND_NONE; |
} |
|
cairo_cogl_linear_texture_entry_t * |
_cairo_cogl_linear_gradient_texture_for_extend (cairo_cogl_linear_gradient_t *gradient, |
cairo_extend_t extend_mode) |
{ |
GList *l; |
cairo_cogl_gradient_compatibility_t compatibility = |
_cairo_cogl_compatibility_from_extend_mode (extend_mode); |
for (l = gradient->textures; l; l = l->next) { |
cairo_cogl_linear_texture_entry_t *entry = l->data; |
if (entry->compatibility & compatibility) |
return entry; |
} |
return NULL; |
} |
|
static void |
color_stop_lerp (const cairo_color_stop_t *c0, |
const cairo_color_stop_t *c1, |
float factor, |
cairo_color_stop_t *dest) |
{ |
/* NB: we always ignore the short members in this file so we don't need to |
* worry about initializing them here. */ |
dest->red = c0->red * (1.0f-factor) + c1->red * factor; |
dest->green = c0->green * (1.0f-factor) + c1->green * factor; |
dest->blue = c0->blue * (1.0f-factor) + c1->blue * factor; |
dest->alpha = c0->alpha * (1.0f-factor) + c1->alpha * factor; |
} |
|
static size_t |
_cairo_cogl_linear_gradient_size (cairo_cogl_linear_gradient_t *gradient) |
{ |
GList *l; |
size_t size = 0; |
for (l = gradient->textures; l; l = l->next) { |
cairo_cogl_linear_texture_entry_t *entry = l->data; |
size += cogl_texture_get_width (entry->texture) * 4; |
} |
return size; |
} |
|
static void |
emit_stop (CoglVertexP2C4 **position, |
float left, |
float right, |
const cairo_color_stop_t *left_color, |
const cairo_color_stop_t *right_color) |
{ |
CoglVertexP2C4 *p = *position; |
|
guint8 lr = left_color->red * 255; |
guint8 lg = left_color->green * 255; |
guint8 lb = left_color->blue * 255; |
guint8 la = left_color->alpha * 255; |
|
guint8 rr = right_color->red * 255; |
guint8 rg = right_color->green * 255; |
guint8 rb = right_color->blue * 255; |
guint8 ra = right_color->alpha * 255; |
|
p[0].x = left; |
p[0].y = 0; |
p[0].r = lr; p[0].g = lg; p[0].b = lb; p[0].a = la; |
p[1].x = left; |
p[1].y = 1; |
p[1].r = lr; p[1].g = lg; p[1].b = lb; p[1].a = la; |
p[2].x = right; |
p[2].y = 1; |
p[2].r = rr; p[2].g = rg; p[2].b = rb; p[2].a = ra; |
|
p[3].x = left; |
p[3].y = 0; |
p[3].r = lr; p[3].g = lg; p[3].b = lb; p[3].a = la; |
p[4].x = right; |
p[4].y = 1; |
p[4].r = rr; p[4].g = rg; p[4].b = rb; p[4].a = ra; |
p[5].x = right; |
p[5].y = 0; |
p[5].r = rr; p[5].g = rg; p[5].b = rb; p[5].a = ra; |
|
*position = &p[6]; |
} |
|
#ifdef DUMP_GRADIENTS_TO_PNG |
static void |
dump_gradient_to_png (CoglTexture *texture) |
{ |
cairo_image_surface_t *image = (cairo_image_surface_t *) |
cairo_image_surface_create (CAIRO_FORMAT_ARGB32, |
cogl_texture_get_width (texture), |
cogl_texture_get_height (texture)); |
CoglPixelFormat format; |
static int gradient_id = 0; |
char *gradient_name; |
|
if (image->base.status) |
return; |
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN |
format = COGL_PIXEL_FORMAT_BGRA_8888_PRE; |
#else |
format = COGL_PIXEL_FORMAT_ARGB_8888_PRE; |
#endif |
cogl_texture_get_data (texture, |
format, |
0, |
image->data); |
gradient_name = g_strdup_printf ("./gradient%d.png", gradient_id++); |
g_print ("writing gradient: %s\n", gradient_name); |
cairo_surface_write_to_png ((cairo_surface_t *)image, gradient_name); |
g_free (gradient_name); |
} |
#endif |
|
cairo_int_status_t |
_cairo_cogl_get_linear_gradient (cairo_cogl_device_t *device, |
cairo_extend_t extend_mode, |
int n_stops, |
const cairo_gradient_stop_t *stops, |
cairo_cogl_linear_gradient_t **gradient_out) |
{ |
unsigned long hash; |
cairo_cogl_linear_gradient_t *gradient; |
cairo_cogl_linear_texture_entry_t *entry; |
cairo_gradient_stop_t *internal_stops; |
int stop_offset; |
int n_internal_stops; |
int n; |
cairo_cogl_gradient_compatibility_t compatibilities; |
int width; |
int left_padding = 0; |
cairo_color_stop_t left_padding_color; |
int right_padding = 0; |
cairo_color_stop_t right_padding_color; |
CoglPixelFormat format; |
CoglTexture2D *tex; |
GError *error = NULL; |
int un_padded_width; |
CoglHandle offscreen; |
cairo_int_status_t status; |
int n_quads; |
int n_vertices; |
float prev; |
float right; |
CoglVertexP2C4 *vertices; |
CoglVertexP2C4 *p; |
CoglPrimitive *prim; |
|
hash = _cairo_cogl_linear_gradient_hash (n_stops, stops); |
|
gradient = _cairo_cogl_linear_gradient_lookup (device, hash, n_stops, stops); |
if (gradient) { |
cairo_cogl_linear_texture_entry_t *entry = |
_cairo_cogl_linear_gradient_texture_for_extend (gradient, extend_mode); |
if (entry) { |
*gradient_out = _cairo_cogl_linear_gradient_reference (gradient); |
return CAIRO_INT_STATUS_SUCCESS; |
} |
} |
|
if (!gradient) { |
gradient = malloc (sizeof (cairo_cogl_linear_gradient_t) + |
sizeof (cairo_gradient_stop_t) * (n_stops - 1)); |
if (!gradient) |
return CAIRO_INT_STATUS_NO_MEMORY; |
|
CAIRO_REFERENCE_COUNT_INIT (&gradient->ref_count, 1); |
/* NB: we update the cache_entry size at the end before |
* [re]adding it to the cache. */ |
gradient->cache_entry.hash = hash; |
gradient->textures = NULL; |
gradient->n_stops = n_stops; |
gradient->stops = gradient->stops_embedded; |
memcpy (gradient->stops_embedded, stops, sizeof (cairo_gradient_stop_t) * n_stops); |
} else |
_cairo_cogl_linear_gradient_reference (gradient); |
|
entry = malloc (sizeof (cairo_cogl_linear_texture_entry_t)); |
if (!entry) { |
status = CAIRO_INT_STATUS_NO_MEMORY; |
goto BAIL; |
} |
|
compatibilities = _cairo_cogl_compatibility_from_extend_mode (extend_mode); |
|
n_internal_stops = n_stops; |
stop_offset = 0; |
|
/* We really need stops covering the full [0,1] range for repeat/reflect |
* if we want to use sampler REPEAT/MIRROR wrap modes so we may need |
* to add some extra stops... */ |
if (extend_mode == CAIRO_EXTEND_REPEAT || extend_mode == CAIRO_EXTEND_REFLECT) |
{ |
/* If we don't need any extra stops then actually the texture |
* will be shareable for repeat and reflect... */ |
compatibilities = (CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT | |
CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT); |
|
if (stops[0].offset != 0) { |
n_internal_stops++; |
stop_offset++; |
} |
|
if (stops[n_stops - 1].offset != 1) |
n_internal_stops++; |
} |
|
internal_stops = alloca (n_internal_stops * sizeof (cairo_gradient_stop_t)); |
memcpy (&internal_stops[stop_offset], stops, sizeof (cairo_gradient_stop_t) * n_stops); |
|
/* cairo_color_stop_t values are all unpremultiplied but we need to |
* interpolate premultiplied colors so we premultiply all the double |
* components now. (skipping any extra stops added for repeat/reflect) |
* |
* Anothing thing to note is that by premultiplying the colors |
* early we'll also reduce the range of colors to interpolate |
* which can result in smaller gradient textures. |
*/ |
for (n = stop_offset; n < n_stops; n++) { |
cairo_color_stop_t *color = &internal_stops[n].color; |
color->red *= color->alpha; |
color->green *= color->alpha; |
color->blue *= color->alpha; |
} |
|
if (n_internal_stops != n_stops) |
{ |
if (extend_mode == CAIRO_EXTEND_REPEAT) { |
compatibilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT; |
if (stops[0].offset != 0) { |
/* what's the wrap-around distance between the user's end-stops? */ |
double dx = (1.0 - stops[n_stops - 1].offset) + stops[0].offset; |
internal_stops[0].offset = 0; |
color_stop_lerp (&stops[0].color, |
&stops[n_stops - 1].color, |
stops[0].offset / dx, |
&internal_stops[0].color); |
} |
if (stops[n_stops - 1].offset != 1) { |
internal_stops[n_internal_stops - 1].offset = 1; |
internal_stops[n_internal_stops - 1].color = internal_stops[0].color; |
} |
} else if (extend_mode == CAIRO_EXTEND_REFLECT) { |
compatibilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT; |
if (stops[0].offset != 0) { |
internal_stops[0].offset = 0; |
internal_stops[0].color = stops[n_stops - 1].color; |
} |
if (stops[n_stops - 1].offset != 1) { |
internal_stops[n_internal_stops - 1].offset = 1; |
internal_stops[n_internal_stops - 1].color = stops[0].color; |
} |
} |
} |
|
stops = internal_stops; |
n_stops = n_internal_stops; |
|
width = _cairo_cogl_linear_gradient_width_for_stops (extend_mode, n_stops, stops); |
|
if (extend_mode == CAIRO_EXTEND_PAD) { |
|
/* Here we need to guarantee that the edge texels of our |
* texture correspond to the desired padding color so we |
* can use CLAMP_TO_EDGE. |
* |
* For short stop-gaps and especially for degenerate stops |
* it's possible that without special consideration the |
* user's end stop colors would not be present in our final |
* texture. |
* |
* To handle this we forcibly add two extra padding texels |
* at the edges which extend beyond the [0,1] range of the |
* gradient itself and we will later report a translate and |
* scale transform to compensate for this. |
*/ |
|
/* XXX: If we consider generating a mipmap for our 1d texture |
* at some point then we also need to consider how much |
* padding to add to be sure lower mipmap levels still have |
* the desired edge color (as opposed to a linear blend with |
* other colors of the gradient). |
*/ |
|
left_padding = 1; |
left_padding_color = stops[0].color; |
right_padding = 1; |
right_padding_color = stops[n_stops - 1].color; |
} else if (extend_mode == CAIRO_EXTEND_NONE) { |
/* We handle EXTEND_NONE by adding two extra, transparent, texels at |
* the ends of the texture and use CLAMP_TO_EDGE. |
* |
* We add a scale and translate transform so to account for our texels |
* extending beyond the [0,1] range. */ |
|
left_padding = 1; |
left_padding_color.red = 0; |
left_padding_color.green = 0; |
left_padding_color.blue = 0; |
left_padding_color.alpha = 0; |
right_padding = 1; |
right_padding_color = left_padding_color; |
} |
|
/* If we still have stops that don't cover the full [0,1] range |
* then we need to define a texture-coordinate scale + translate |
* transform to account for that... */ |
if (stops[n_stops - 1].offset - stops[0].offset < 1) { |
float range = stops[n_stops - 1].offset - stops[0].offset; |
entry->scale_x = 1.0 / range; |
entry->translate_x = -(stops[0].offset * entry->scale_x); |
} |
|
width += left_padding + right_padding; |
|
width = _cairo_cogl_util_next_p2 (width); |
width = MIN (4096, width); /* lets not go too stupidly big! */ |
format = _cairo_cogl_linear_gradient_format_for_stops (extend_mode, n_stops, stops); |
|
do { |
tex = cogl_texture_2d_new_with_size (device->cogl_context, |
width, |
1, |
format, |
&error); |
if (!tex) |
g_error_free (error); |
} while (tex == NULL && width >> 1); |
|
if (!tex) { |
status = CAIRO_INT_STATUS_NO_MEMORY; |
goto BAIL; |
} |
|
entry->texture = COGL_TEXTURE (tex); |
entry->compatibility = compatibilities; |
|
un_padded_width = width - left_padding - right_padding; |
|
/* XXX: only when we know the final texture width can we calculate the |
* scale and translate factors needed to account for padding... */ |
if (un_padded_width != width) |
entry->scale_x *= (float)un_padded_width / (float)width; |
if (left_padding) |
entry->translate_x += (entry->scale_x / (float)un_padded_width) * (float)left_padding; |
|
offscreen = cogl_offscreen_new_to_texture (tex); |
cogl_push_framebuffer (COGL_FRAMEBUFFER (offscreen)); |
cogl_ortho (0, width, 1, 0, -1, 100); |
cogl_framebuffer_clear4f (COGL_FRAMEBUFFER (offscreen), |
COGL_BUFFER_BIT_COLOR, |
0, 0, 0, 0); |
|
n_quads = n_stops - 1 + !!left_padding + !!right_padding; |
n_vertices = 6 * n_quads; |
vertices = alloca (sizeof (CoglVertexP2C4) * n_vertices); |
p = vertices; |
if (left_padding) |
emit_stop (&p, 0, left_padding, &left_padding_color, &left_padding_color); |
prev = (float)left_padding; |
for (n = 1; n < n_stops; n++) { |
right = (float)left_padding + (float)un_padded_width * stops[n].offset; |
emit_stop (&p, prev, right, &stops[n-1].color, &stops[n].color); |
prev = right; |
} |
if (right_padding) |
emit_stop (&p, prev, width, &right_padding_color, &right_padding_color); |
|
prim = cogl_primitive_new_p2c4 (COGL_VERTICES_MODE_TRIANGLES, |
n_vertices, |
vertices); |
/* Just use this as the simplest way to setup a default pipeline... */ |
cogl_set_source_color4f (0, 0, 0, 0); |
cogl_primitive_draw (prim); |
cogl_object_unref (prim); |
|
cogl_pop_framebuffer (); |
cogl_object_unref (offscreen); |
|
gradient->textures = g_list_prepend (gradient->textures, entry); |
gradient->cache_entry.size = _cairo_cogl_linear_gradient_size (gradient); |
|
#ifdef DUMP_GRADIENTS_TO_PNG |
dump_gradient_to_png (COGL_TEXTURE (tex)); |
#endif |
|
#warning "FIXME:" |
/* XXX: it seems the documentation of _cairo_cache_insert isn't true - it |
* doesn't handle re-adding the same entry gracefully - the cache will |
* just keep on growing and then it will start randomly evicting things |
* pointlessly */ |
/* we ignore errors here and just return an uncached gradient */ |
if (likely (! _cairo_cache_insert (&device->linear_cache, &gradient->cache_entry))) |
_cairo_cogl_linear_gradient_reference (gradient); |
|
*gradient_out = gradient; |
return CAIRO_INT_STATUS_SUCCESS; |
|
BAIL: |
free (entry); |
if (gradient) |
_cairo_cogl_linear_gradient_destroy (gradient); |
return status; |
} |