1 /* cairo - a vector graphics library with display and print output
3 * Copyright © 2011 Intel Corporation.
5 * This library is free software; you can redistribute it and/or
6 * modify it either under the terms of the GNU Lesser General Public
7 * License version 2.1 as published by the Free Software Foundation
8 * (the "LGPL") or, at your option, under the terms of the Mozilla
9 * Public License Version 1.1 (the "MPL"). If you do not alter this
10 * notice, a recipient may use your version of this file under either
11 * the MPL or the LGPL.
13 * You should have received a copy of the LGPL along with this library
14 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
16 * You should have received a copy of the MPL along with this library
17 * in the file COPYING-MPL-1.1
19 * The contents of this file are subject to the Mozilla Public License
20 * Version 1.1 (the "License"); you may not use this file except in
21 * compliance with the License. You may obtain a copy of the License at
22 * http://www.mozilla.og/MPL/
24 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
25 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
26 * the specific language governing rights and limitations.
29 * Robert Bragg <robert@linux.intel.com>
31 //#include "cairoint.h"
33 #include "cairo-cogl-private.h"
34 #include "cairo-cogl-gradient-private.h"
35 #include "cairo-image-surface-private.h"
37 #include <cogl/cogl2-experimental.h>
40 #define DUMP_GRADIENTS_TO_PNG
43 _cairo_cogl_linear_gradient_hash (unsigned int n_stops,
44 const cairo_gradient_stop_t *stops)
46 return _cairo_hash_bytes (n_stops, stops,
47 sizeof (cairo_gradient_stop_t) * n_stops);
50 static cairo_cogl_linear_gradient_t *
51 _cairo_cogl_linear_gradient_lookup (cairo_cogl_device_t *ctx,
54 const cairo_gradient_stop_t *stops)
56 cairo_cogl_linear_gradient_t lookup;
58 lookup.cache_entry.hash = hash,
59 lookup.n_stops = n_stops;
62 return _cairo_cache_lookup (&ctx->linear_cache, &lookup.cache_entry);
66 _cairo_cogl_linear_gradient_equal (const void *key_a, const void *key_b)
68 const cairo_cogl_linear_gradient_t *a = key_a;
69 const cairo_cogl_linear_gradient_t *b = key_b;
71 if (a->n_stops != b->n_stops)
74 return memcmp (a->stops, b->stops, a->n_stops * sizeof (cairo_gradient_stop_t)) == 0;
77 cairo_cogl_linear_gradient_t *
78 _cairo_cogl_linear_gradient_reference (cairo_cogl_linear_gradient_t *gradient)
80 assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count));
82 _cairo_reference_count_inc (&gradient->ref_count);
88 _cairo_cogl_linear_gradient_destroy (cairo_cogl_linear_gradient_t *gradient)
92 assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count));
94 if (! _cairo_reference_count_dec_and_test (&gradient->ref_count))
97 for (l = gradient->textures; l; l = l->next) {
98 cairo_cogl_linear_texture_entry_t *entry = l->data;
99 cogl_object_unref (entry->texture);
102 g_list_free (gradient->textures);
108 _cairo_cogl_util_next_p2 (int a)
119 get_max_color_component_range (const cairo_color_stop_t *color0, const cairo_color_stop_t *color1)
124 range = fabs (color0->red - color1->red);
125 max = MAX (range, max);
126 range = fabs (color0->green - color1->green);
127 max = MAX (range, max);
128 range = fabs (color0->blue - color1->blue);
129 max = MAX (range, max);
130 range = fabs (color0->alpha - color1->alpha);
131 max = MAX (range, max);
137 _cairo_cogl_linear_gradient_width_for_stops (cairo_extend_t extend,
138 unsigned int n_stops,
139 const cairo_gradient_stop_t *stops)
142 float max_texels_per_unit_offset = 0;
143 float total_offset_range;
145 /* Find the stop pair demanding the most precision because we are
146 * interpolating the largest color-component range.
148 * From that we can define the relative sizes of all the other
149 * stop pairs within our texture and thus the overall size.
151 * To determine the maximum number of texels for a given gap we
152 * look at the range of colors we are expected to interpolate (so
153 * long as the stop offsets are not degenerate) and we simply
154 * assume we want one texel for each unique color value possible
155 * for a one byte-per-component representation.
156 * XXX: maybe this is overkill and just allowing 128 levels
157 * instead of 256 would be enough and then we'd rely on the
158 * bilinear filtering to give the full range.
160 * XXX: potentially we could try and map offsets to pixels to come
161 * up with a more precise mapping, but we are aiming to cache
162 * the gradients so we can't make assumptions about how it will be
163 * scaled in the future.
165 for (n = 1; n < n_stops; n++) {
169 float texels_per_unit_offset;
171 /* note: degenerate stops don't need to be represented in the
172 * texture but we want to be sure that solid gaps get at least
173 * one texel and all other gaps get at least 2 texels.
176 if (stops[n].offset == stops[n-1].offset)
179 color_range = get_max_color_component_range (&stops[n].color, &stops[n-1].color);
180 if (color_range == 0)
183 texels = MAX (2, 256.0f * color_range);
185 /* So how many texels would we need to map over the full [0,1]
186 * gradient range so this gap would have enough texels? ... */
187 offset_range = stops[n].offset - stops[n - 1].offset;
188 texels_per_unit_offset = texels / offset_range;
190 if (texels_per_unit_offset > max_texels_per_unit_offset)
191 max_texels_per_unit_offset = texels_per_unit_offset;
194 total_offset_range = fabs (stops[n_stops - 1].offset - stops[0].offset);
195 return max_texels_per_unit_offset * total_offset_range;
198 /* Aim to create gradient textures without an alpha component so we can avoid
199 * needing to use blending... */
200 static CoglPixelFormat
201 _cairo_cogl_linear_gradient_format_for_stops (cairo_extend_t extend,
202 unsigned int n_stops,
203 const cairo_gradient_stop_t *stops)
207 /* We have to add extra transparent texels to the end of the gradient to
208 * handle CAIRO_EXTEND_NONE... */
209 if (extend == CAIRO_EXTEND_NONE)
210 return COGL_PIXEL_FORMAT_BGRA_8888_PRE;
212 for (n = 1; n < n_stops; n++) {
213 if (stops[n].color.alpha != 1.0)
214 return COGL_PIXEL_FORMAT_BGRA_8888_PRE;
217 return COGL_PIXEL_FORMAT_BGR_888;
220 static cairo_cogl_gradient_compatibility_t
221 _cairo_cogl_compatibility_from_extend_mode (cairo_extend_t extend_mode)
225 case CAIRO_EXTEND_NONE:
226 return CAIRO_COGL_GRADIENT_CAN_EXTEND_NONE;
227 case CAIRO_EXTEND_PAD:
228 return CAIRO_COGL_GRADIENT_CAN_EXTEND_PAD;
229 case CAIRO_EXTEND_REPEAT:
230 return CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT;
231 case CAIRO_EXTEND_REFLECT:
232 return CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT;
235 assert (0); /* not reached */
236 return CAIRO_EXTEND_NONE;
239 cairo_cogl_linear_texture_entry_t *
240 _cairo_cogl_linear_gradient_texture_for_extend (cairo_cogl_linear_gradient_t *gradient,
241 cairo_extend_t extend_mode)
244 cairo_cogl_gradient_compatibility_t compatibility =
245 _cairo_cogl_compatibility_from_extend_mode (extend_mode);
246 for (l = gradient->textures; l; l = l->next) {
247 cairo_cogl_linear_texture_entry_t *entry = l->data;
248 if (entry->compatibility & compatibility)
255 color_stop_lerp (const cairo_color_stop_t *c0,
256 const cairo_color_stop_t *c1,
258 cairo_color_stop_t *dest)
260 /* NB: we always ignore the short members in this file so we don't need to
261 * worry about initializing them here. */
262 dest->red = c0->red * (1.0f-factor) + c1->red * factor;
263 dest->green = c0->green * (1.0f-factor) + c1->green * factor;
264 dest->blue = c0->blue * (1.0f-factor) + c1->blue * factor;
265 dest->alpha = c0->alpha * (1.0f-factor) + c1->alpha * factor;
269 _cairo_cogl_linear_gradient_size (cairo_cogl_linear_gradient_t *gradient)
273 for (l = gradient->textures; l; l = l->next) {
274 cairo_cogl_linear_texture_entry_t *entry = l->data;
275 size += cogl_texture_get_width (entry->texture) * 4;
281 emit_stop (CoglVertexP2C4 **position,
284 const cairo_color_stop_t *left_color,
285 const cairo_color_stop_t *right_color)
287 CoglVertexP2C4 *p = *position;
289 guint8 lr = left_color->red * 255;
290 guint8 lg = left_color->green * 255;
291 guint8 lb = left_color->blue * 255;
292 guint8 la = left_color->alpha * 255;
294 guint8 rr = right_color->red * 255;
295 guint8 rg = right_color->green * 255;
296 guint8 rb = right_color->blue * 255;
297 guint8 ra = right_color->alpha * 255;
301 p[0].r = lr; p[0].g = lg; p[0].b = lb; p[0].a = la;
304 p[1].r = lr; p[1].g = lg; p[1].b = lb; p[1].a = la;
307 p[2].r = rr; p[2].g = rg; p[2].b = rb; p[2].a = ra;
311 p[3].r = lr; p[3].g = lg; p[3].b = lb; p[3].a = la;
314 p[4].r = rr; p[4].g = rg; p[4].b = rb; p[4].a = ra;
317 p[5].r = rr; p[5].g = rg; p[5].b = rb; p[5].a = ra;
322 #ifdef DUMP_GRADIENTS_TO_PNG
324 dump_gradient_to_png (CoglTexture *texture)
326 cairo_image_surface_t *image = (cairo_image_surface_t *)
327 cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
328 cogl_texture_get_width (texture),
329 cogl_texture_get_height (texture));
330 CoglPixelFormat format;
331 static int gradient_id = 0;
334 if (image->base.status)
337 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
338 format = COGL_PIXEL_FORMAT_BGRA_8888_PRE;
340 format = COGL_PIXEL_FORMAT_ARGB_8888_PRE;
342 cogl_texture_get_data (texture,
346 gradient_name = g_strdup_printf ("./gradient%d.png", gradient_id++);
347 g_print ("writing gradient: %s\n", gradient_name);
348 cairo_surface_write_to_png ((cairo_surface_t *)image, gradient_name);
349 g_free (gradient_name);
354 _cairo_cogl_get_linear_gradient (cairo_cogl_device_t *device,
355 cairo_extend_t extend_mode,
357 const cairo_gradient_stop_t *stops,
358 cairo_cogl_linear_gradient_t **gradient_out)
361 cairo_cogl_linear_gradient_t *gradient;
362 cairo_cogl_linear_texture_entry_t *entry;
363 cairo_gradient_stop_t *internal_stops;
365 int n_internal_stops;
367 cairo_cogl_gradient_compatibility_t compatibilities;
369 int left_padding = 0;
370 cairo_color_stop_t left_padding_color;
371 int right_padding = 0;
372 cairo_color_stop_t right_padding_color;
373 CoglPixelFormat format;
375 GError *error = NULL;
377 CoglHandle offscreen;
378 cairo_int_status_t status;
383 CoglVertexP2C4 *vertices;
387 hash = _cairo_cogl_linear_gradient_hash (n_stops, stops);
389 gradient = _cairo_cogl_linear_gradient_lookup (device, hash, n_stops, stops);
391 cairo_cogl_linear_texture_entry_t *entry =
392 _cairo_cogl_linear_gradient_texture_for_extend (gradient, extend_mode);
394 *gradient_out = _cairo_cogl_linear_gradient_reference (gradient);
395 return CAIRO_INT_STATUS_SUCCESS;
400 gradient = malloc (sizeof (cairo_cogl_linear_gradient_t) +
401 sizeof (cairo_gradient_stop_t) * (n_stops - 1));
403 return CAIRO_INT_STATUS_NO_MEMORY;
405 CAIRO_REFERENCE_COUNT_INIT (&gradient->ref_count, 1);
406 /* NB: we update the cache_entry size at the end before
407 * [re]adding it to the cache. */
408 gradient->cache_entry.hash = hash;
409 gradient->textures = NULL;
410 gradient->n_stops = n_stops;
411 gradient->stops = gradient->stops_embedded;
412 memcpy (gradient->stops_embedded, stops, sizeof (cairo_gradient_stop_t) * n_stops);
414 _cairo_cogl_linear_gradient_reference (gradient);
416 entry = malloc (sizeof (cairo_cogl_linear_texture_entry_t));
418 status = CAIRO_INT_STATUS_NO_MEMORY;
422 compatibilities = _cairo_cogl_compatibility_from_extend_mode (extend_mode);
424 n_internal_stops = n_stops;
427 /* We really need stops covering the full [0,1] range for repeat/reflect
428 * if we want to use sampler REPEAT/MIRROR wrap modes so we may need
429 * to add some extra stops... */
430 if (extend_mode == CAIRO_EXTEND_REPEAT || extend_mode == CAIRO_EXTEND_REFLECT)
432 /* If we don't need any extra stops then actually the texture
433 * will be shareable for repeat and reflect... */
434 compatibilities = (CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT |
435 CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT);
437 if (stops[0].offset != 0) {
442 if (stops[n_stops - 1].offset != 1)
446 internal_stops = alloca (n_internal_stops * sizeof (cairo_gradient_stop_t));
447 memcpy (&internal_stops[stop_offset], stops, sizeof (cairo_gradient_stop_t) * n_stops);
449 /* cairo_color_stop_t values are all unpremultiplied but we need to
450 * interpolate premultiplied colors so we premultiply all the double
451 * components now. (skipping any extra stops added for repeat/reflect)
453 * Anothing thing to note is that by premultiplying the colors
454 * early we'll also reduce the range of colors to interpolate
455 * which can result in smaller gradient textures.
457 for (n = stop_offset; n < n_stops; n++) {
458 cairo_color_stop_t *color = &internal_stops[n].color;
459 color->red *= color->alpha;
460 color->green *= color->alpha;
461 color->blue *= color->alpha;
464 if (n_internal_stops != n_stops)
466 if (extend_mode == CAIRO_EXTEND_REPEAT) {
467 compatibilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT;
468 if (stops[0].offset != 0) {
469 /* what's the wrap-around distance between the user's end-stops? */
470 double dx = (1.0 - stops[n_stops - 1].offset) + stops[0].offset;
471 internal_stops[0].offset = 0;
472 color_stop_lerp (&stops[0].color,
473 &stops[n_stops - 1].color,
474 stops[0].offset / dx,
475 &internal_stops[0].color);
477 if (stops[n_stops - 1].offset != 1) {
478 internal_stops[n_internal_stops - 1].offset = 1;
479 internal_stops[n_internal_stops - 1].color = internal_stops[0].color;
481 } else if (extend_mode == CAIRO_EXTEND_REFLECT) {
482 compatibilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT;
483 if (stops[0].offset != 0) {
484 internal_stops[0].offset = 0;
485 internal_stops[0].color = stops[n_stops - 1].color;
487 if (stops[n_stops - 1].offset != 1) {
488 internal_stops[n_internal_stops - 1].offset = 1;
489 internal_stops[n_internal_stops - 1].color = stops[0].color;
494 stops = internal_stops;
495 n_stops = n_internal_stops;
497 width = _cairo_cogl_linear_gradient_width_for_stops (extend_mode, n_stops, stops);
499 if (extend_mode == CAIRO_EXTEND_PAD) {
501 /* Here we need to guarantee that the edge texels of our
502 * texture correspond to the desired padding color so we
503 * can use CLAMP_TO_EDGE.
505 * For short stop-gaps and especially for degenerate stops
506 * it's possible that without special consideration the
507 * user's end stop colors would not be present in our final
510 * To handle this we forcibly add two extra padding texels
511 * at the edges which extend beyond the [0,1] range of the
512 * gradient itself and we will later report a translate and
513 * scale transform to compensate for this.
516 /* XXX: If we consider generating a mipmap for our 1d texture
517 * at some point then we also need to consider how much
518 * padding to add to be sure lower mipmap levels still have
519 * the desired edge color (as opposed to a linear blend with
520 * other colors of the gradient).
524 left_padding_color = stops[0].color;
526 right_padding_color = stops[n_stops - 1].color;
527 } else if (extend_mode == CAIRO_EXTEND_NONE) {
528 /* We handle EXTEND_NONE by adding two extra, transparent, texels at
529 * the ends of the texture and use CLAMP_TO_EDGE.
531 * We add a scale and translate transform so to account for our texels
532 * extending beyond the [0,1] range. */
535 left_padding_color.red = 0;
536 left_padding_color.green = 0;
537 left_padding_color.blue = 0;
538 left_padding_color.alpha = 0;
540 right_padding_color = left_padding_color;
543 /* If we still have stops that don't cover the full [0,1] range
544 * then we need to define a texture-coordinate scale + translate
545 * transform to account for that... */
546 if (stops[n_stops - 1].offset - stops[0].offset < 1) {
547 float range = stops[n_stops - 1].offset - stops[0].offset;
548 entry->scale_x = 1.0 / range;
549 entry->translate_x = -(stops[0].offset * entry->scale_x);
552 width += left_padding + right_padding;
554 width = _cairo_cogl_util_next_p2 (width);
555 width = MIN (4096, width); /* lets not go too stupidly big! */
556 format = _cairo_cogl_linear_gradient_format_for_stops (extend_mode, n_stops, stops);
559 tex = cogl_texture_2d_new_with_size (device->cogl_context,
565 g_error_free (error);
566 } while (tex == NULL && width >> 1);
569 status = CAIRO_INT_STATUS_NO_MEMORY;
573 entry->texture = COGL_TEXTURE (tex);
574 entry->compatibility = compatibilities;
576 un_padded_width = width - left_padding - right_padding;
578 /* XXX: only when we know the final texture width can we calculate the
579 * scale and translate factors needed to account for padding... */
580 if (un_padded_width != width)
581 entry->scale_x *= (float)un_padded_width / (float)width;
583 entry->translate_x += (entry->scale_x / (float)un_padded_width) * (float)left_padding;
585 offscreen = cogl_offscreen_new_to_texture (tex);
586 cogl_push_framebuffer (COGL_FRAMEBUFFER (offscreen));
587 cogl_ortho (0, width, 1, 0, -1, 100);
588 cogl_framebuffer_clear4f (COGL_FRAMEBUFFER (offscreen),
589 COGL_BUFFER_BIT_COLOR,
592 n_quads = n_stops - 1 + !!left_padding + !!right_padding;
593 n_vertices = 6 * n_quads;
594 vertices = alloca (sizeof (CoglVertexP2C4) * n_vertices);
597 emit_stop (&p, 0, left_padding, &left_padding_color, &left_padding_color);
598 prev = (float)left_padding;
599 for (n = 1; n < n_stops; n++) {
600 right = (float)left_padding + (float)un_padded_width * stops[n].offset;
601 emit_stop (&p, prev, right, &stops[n-1].color, &stops[n].color);
605 emit_stop (&p, prev, width, &right_padding_color, &right_padding_color);
607 prim = cogl_primitive_new_p2c4 (COGL_VERTICES_MODE_TRIANGLES,
610 /* Just use this as the simplest way to setup a default pipeline... */
611 cogl_set_source_color4f (0, 0, 0, 0);
612 cogl_primitive_draw (prim);
613 cogl_object_unref (prim);
615 cogl_pop_framebuffer ();
616 cogl_object_unref (offscreen);
618 gradient->textures = g_list_prepend (gradient->textures, entry);
619 gradient->cache_entry.size = _cairo_cogl_linear_gradient_size (gradient);
621 #ifdef DUMP_GRADIENTS_TO_PNG
622 dump_gradient_to_png (COGL_TEXTURE (tex));
626 /* XXX: it seems the documentation of _cairo_cache_insert isn't true - it
627 * doesn't handle re-adding the same entry gracefully - the cache will
628 * just keep on growing and then it will start randomly evicting things
630 /* we ignore errors here and just return an uncached gradient */
631 if (likely (! _cairo_cache_insert (&device->linear_cache, &gradient->cache_entry)))
632 _cairo_cogl_linear_gradient_reference (gradient);
634 *gradient_out = gradient;
635 return CAIRO_INT_STATUS_SUCCESS;
640 _cairo_cogl_linear_gradient_destroy (gradient);