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
);