Merge branch 'wip/otte/for-main' into 'main'

testsuite: Add a test for conic gradients

See merge request GNOME/gtk!6700
This commit is contained in:
Benjamin Otte 2024-01-03 04:37:00 +00:00
commit 766048b8e4
20 changed files with 260 additions and 21 deletions

View File

@ -137,8 +137,9 @@ gdk_load_png (GBytes *bytes,
png_struct *png = NULL;
png_info *info;
guint width, height;
gsize i, stride;
int depth, color_type;
int interlace, stride;
int interlace;
GdkMemoryFormat format;
guchar *buffer = NULL;
guchar **row_pointers = NULL;
@ -263,9 +264,14 @@ gdk_load_png (GBytes *bytes,
}
bpp = gdk_memory_format_bytes_per_pixel (format);
stride = width * bpp;
if (stride % 8)
stride += 8 - stride % 8;
if (!g_size_checked_mul (&stride, width, bpp) ||
!g_size_checked_add (&stride, stride, (8 - stride % 8) % 8))
{
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE,
_("Image stride too large for image size %ux%u"), width, height);
return NULL;
}
buffer = g_try_malloc_n (height, stride);
row_pointers = g_try_malloc_n (height, sizeof (char *));
@ -281,7 +287,7 @@ gdk_load_png (GBytes *bytes,
return NULL;
}
for (int i = 0; i < height; i++)
for (i = 0; i < height; i++)
row_pointers[i] = &buffer[i * stride];
png_read_image (png, row_pointers);

View File

@ -33,11 +33,12 @@
#include "gsktransformprivate.h"
#include "gskoffloadprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdkmemoryformatprivate.h"
#include "gdk/gdkprivate.h"
#include "gdk/gdkrectangleprivate.h"
#include "gdk/gdksubsurfaceprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdktexturedownloaderprivate.h"
#include <cairo.h>
#ifdef CAIRO_HAS_SVG_SURFACE
@ -45,6 +46,11 @@
#endif
#include <hb-ot.h>
/* for oversized image fallback - we use a smaller size than Cairo actually
* allows to avoid rounding errors in Cairo */
#define MAX_CAIRO_IMAGE_WIDTH 16384
#define MAX_CAIRO_IMAGE_HEIGHT 16384
/* maximal number of rectangles we keep in a diff region before we throw
* the towel and just use the bounding box of the parent node.
* Meant to avoid performance corner cases.
@ -60,6 +66,49 @@ gsk_cairo_rectangle (cairo_t *cr,
rect->size.width, rect->size.height);
}
/* apply a rectangle that bounds @rect in
* pixel-aligned device coordinates.
*
* This is useful for clipping to minimize the rectangle
* in push_group() or when blurring.
*/
static void
gsk_cairo_rectangle_pixel_aligned (cairo_t *cr,
const graphene_rect_t *rect)
{
double x0, x1, x2, x3;
double y0, y1, y2, y3;
double xmin, xmax, ymin, ymax;
x0 = rect->origin.x;
y0 = rect->origin.y;
cairo_user_to_device (cr, &x0, &y0);
x1 = rect->origin.x + rect->size.width;
y1 = rect->origin.y;
cairo_user_to_device (cr, &x1, &y1);
x2 = rect->origin.x;
y2 = rect->origin.y + rect->size.height;
cairo_user_to_device (cr, &x2, &y2);
x3 = rect->origin.x + rect->size.width;
y3 = rect->origin.y + rect->size.height;
cairo_user_to_device (cr, &x3, &y3);
xmin = MIN (MIN (x0, x1), MIN (x2, x3));
ymin = MIN (MIN (y0, y1), MIN (y2, y3));
xmax = MAX (MAX (x0, x1), MAX (x2, x3));
ymax = MAX (MAX (y0, y1), MAX (y2, y3));
xmin = floor (xmin);
ymin = floor (ymin);
xmax = ceil (xmax);
ymax = ceil (ymax);
cairo_save (cr);
cairo_identity_matrix (cr);
cairo_rectangle (cr, xmin, ymin, xmax - xmin, ymax - ymin);
cairo_restore (cr);
}
static void
rectangle_init_from_graphene (cairo_rectangle_int_t *cairo,
const graphene_rect_t *graphene)
@ -1649,6 +1698,63 @@ gsk_texture_node_finalize (GskRenderNode *node)
parent_class->finalize (node);
}
static void
gsk_texture_node_draw_oversized (GskRenderNode *node,
cairo_t *cr)
{
GskTextureNode *self = (GskTextureNode *) node;
cairo_surface_t *surface;
int x, y, width, height;
GdkTextureDownloader downloader;
GBytes *bytes;
const guchar *data;
gsize stride;
width = gdk_texture_get_width (self->texture);
height = gdk_texture_get_height (self->texture);
gdk_texture_downloader_init (&downloader, self->texture);
gdk_texture_downloader_set_format (&downloader, GDK_MEMORY_DEFAULT);
bytes = gdk_texture_downloader_download_bytes (&downloader, &stride);
gdk_texture_downloader_finish (&downloader);
data = g_bytes_get_data (bytes, NULL);
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
cairo_clip (cr);
cairo_push_group (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
cairo_translate (cr,
node->bounds.origin.x,
node->bounds.origin.y);
cairo_scale (cr,
node->bounds.size.width / width,
node->bounds.size.height / height);
for (x = 0; x < width; x += MAX_CAIRO_IMAGE_WIDTH)
{
int tile_width = MIN (MAX_CAIRO_IMAGE_WIDTH, width - x);
for (y = 0; y < height; y += MAX_CAIRO_IMAGE_HEIGHT)
{
int tile_height = MIN (MAX_CAIRO_IMAGE_HEIGHT, height - y);
surface = cairo_image_surface_create_for_data ((guchar *) data + stride * y + 4 * x,
CAIRO_FORMAT_ARGB32,
tile_width, tile_height,
stride);
cairo_set_source_surface (cr, surface, x, y);
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
cairo_rectangle (cr, x, y, tile_width, tile_height);
cairo_fill (cr);
cairo_surface_finish (surface);
cairo_surface_destroy (surface);
}
}
cairo_pop_group_to_source (cr);
cairo_paint (cr);
}
static void
gsk_texture_node_draw (GskRenderNode *node,
cairo_t *cr)
@ -1657,14 +1763,23 @@ gsk_texture_node_draw (GskRenderNode *node,
cairo_surface_t *surface;
cairo_pattern_t *pattern;
cairo_matrix_t matrix;
int width, height;
width = gdk_texture_get_width (self->texture);
height = gdk_texture_get_height (self->texture);
if (width > MAX_CAIRO_IMAGE_WIDTH || height > MAX_CAIRO_IMAGE_HEIGHT)
{
gsk_texture_node_draw_oversized (node, cr);
return;
}
surface = gdk_texture_download_surface (self->texture);
pattern = cairo_pattern_create_for_surface (surface);
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
cairo_matrix_init_scale (&matrix,
gdk_texture_get_width (self->texture) / node->bounds.size.width,
gdk_texture_get_height (self->texture) / node->bounds.size.height);
width / node->bounds.size.width,
height / node->bounds.size.height);
cairo_matrix_translate (&matrix,
-node->bounds.origin.x,
-node->bounds.origin.y);
@ -1859,8 +1974,7 @@ gsk_texture_scale_node_draw (GskRenderNode *node,
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
cairo_surface_destroy (surface2);
gsk_cairo_rectangle (cr, &node->bounds);
cairo_fill (cr);
cairo_paint (cr);
cairo_restore (cr);
}
@ -3587,7 +3701,7 @@ gsk_opacity_node_draw (GskRenderNode *node,
GskOpacityNode *self = (GskOpacityNode *) node;
/* clip so the push_group() creates a smaller surface */
gsk_cairo_rectangle (cr, &node->bounds);
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
cairo_clip (cr);
if (has_empty_clip (cr))
@ -4008,7 +4122,7 @@ gsk_repeat_node_draw (GskRenderNode *node,
graphene_rect_t clip_bounds;
float tile_left, tile_right, tile_top, tile_bottom;
gsk_cairo_rectangle (cr, &node->bounds);
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
cairo_clip (cr);
_graphene_rect_init_from_clip_extents (&clip_bounds, cr);
@ -4736,7 +4850,7 @@ gsk_stroke_node_draw (GskRenderNode *node,
}
else
{
gsk_cairo_rectangle (cr, &self->child->bounds);
gsk_cairo_rectangle_pixel_aligned (cr, &self->child->bounds);
cairo_clip (cr);
if (has_empty_clip (cr))
return;
@ -4934,7 +5048,7 @@ gsk_shadow_node_draw (GskRenderNode *node,
gsize i;
/* clip so the blur area stays small */
gsk_cairo_rectangle (cr, &node->bounds);
gsk_cairo_rectangle_pixel_aligned (cr, &node->bounds);
cairo_clip (cr);
if (has_empty_clip (cr))
return;
@ -4949,15 +5063,17 @@ gsk_shadow_node_draw (GskRenderNode *node,
continue;
cairo_save (cr);
cr = gsk_cairo_blur_start_drawing (cr, shadow->radius, GSK_BLUR_X | GSK_BLUR_Y);
cr = gsk_cairo_blur_start_drawing (cr, 0.5 * shadow->radius, GSK_BLUR_X | GSK_BLUR_Y);
cairo_save (cr);
cairo_translate (cr, shadow->dx, shadow->dy);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
pattern = cairo_pop_group (cr);
cairo_reset_clip (cr);
gdk_cairo_set_source_rgba (cr, &shadow->color);
cairo_mask (cr, pattern);
cairo_pattern_destroy (pattern);
cairo_restore (cr);
cr = gsk_cairo_blur_finish_drawing (cr, 0.5 * shadow->radius, &shadow->color, GSK_BLUR_X | GSK_BLUR_Y);

View File

@ -711,7 +711,7 @@ parse_shadows (GtkCssParser *parser,
static void
clear_shadows (gpointer inout_shadows)
{
g_array_set_size (*(GArray **) inout_shadows, 0);
g_array_set_size (inout_shadows, 0);
}
static const struct
@ -3152,8 +3152,18 @@ base64_encode_with_linebreaks (const guchar *data,
+1 is needed for trailing \0, also check for unlikely integer overflow */
g_return_val_if_fail (len < ((G_MAXSIZE - 1) / 4 - 1) * 3, NULL);
max = (len / 3 + 1) * 4 + 1;
max += 2 * (max / 76);
/* The glib docs say:
*
* The output buffer must be large enough to fit all the data that will
* be written to it. Due to the way base64 encodes you will need
* at least: (@len / 3 + 1) * 4 + 4 bytes (+ 4 may be needed in case of
* non-zero state). If you enable line-breaking you will need at least:
* ((@len / 3 + 1) * 4 + 4) / 76 + 1 bytes of extra space.
*/
max = (len / 3 + 1) * 4;
max += ((len / 3 + 1) * 4 + 4) / 76 + 1;
/* and the null byte */
max += 1;
out = g_malloc (max);

View File

@ -527,13 +527,16 @@ main (int argc, char **argv)
if (!node2)
node2 = gsk_container_node_new (NULL, 0);
save_node (node2, node_file, "-replayed.node");
gsk_render_node_get_bounds (node, &node_bounds);
gsk_render_node_get_bounds (node2, &node2_bounds);
/* Check that the node didn't grow. */
success = success && graphene_rect_contains_rect (&node_bounds, &node2_bounds);
rendered_texture = gsk_renderer_render_texture (renderer, node, &node_bounds);
save_image (rendered_texture, node_file, "-replayed.ref.png");
rendered_texture2 = gsk_renderer_render_texture (renderer, node2, &node_bounds);
save_image (rendered_texture2, node_file, "-replayed.out.png");
g_assert_nonnull (rendered_texture);
g_assert_nonnull (rendered_texture2);
@ -542,7 +545,6 @@ main (int argc, char **argv)
if (diff_texture)
{
save_image (diff_texture, node_file, "-replayed.diff.png");
save_node (node2, node_file, "-replayed.node");
g_object_unref (diff_texture);
success = FALSE;
}

View File

@ -0,0 +1,30 @@
color {
bounds: 0 0 50 50;
color: rgb(0,255,0);
}
opacity {
opacity: 0.2;
child: rounded-clip {
clip: 0 0 50 50 / 50 0 50 0;
child: clip {
clip: 2.5 2.5 45 45;
child: shadow {
shadows: rgb(0,0,0) 20 20, rgb(0,0,0) -20 -20;
child: container {
color {
bounds: 30 10 10 10;
color: rgb(255,0,0);
}
color {
bounds: 20 20 10 10;
color: rgb(255,0,0);
}
color {
bounds: 10 30 10 10;
color: rgb(255,0,0);
}
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

View File

@ -0,0 +1,5 @@
conic-gradient {
bounds: 0 0 100 100;
center: 50 50;
stops: 0 rgb(255,0,0), 0.03125 rgb(255,0,0), 0.0625 rgb(255,0,0), 0.09375 rgb(255,0,0), 0.125 rgb(255,0,0), 0.15625 rgb(255,0,0), 0.1875 rgb(255,0,0), 0.21875 rgb(255,0,0), 0.25 rgb(255,0,0), 0.28125 rgb(255,0,0), 0.3125 rgb(255,0,0), 0.34375 rgb(255,0,0), 0.375 rgb(255,0,0), 0.40625 rgb(255,0,0), 0.4375 rgb(255,0,0), 0.46875 rgb(255,0,0), 0.5 rgb(255,0,0), 0.53125 rgb(255,0,0), 0.5625 rgb(255,0,0), 0.59375 rgb(255,0,0), 0.625 rgb(255,0,0), 0.65625 rgb(255,0,0), 0.6875 rgb(255,0,0), 0.71875 rgb(255,0,0), 0.75 rgb(255,0,0), 0.78125 rgb(255,0,0), 0.8125 rgb(255,0,0), 0.84375 rgb(255,0,0), 0.875 rgb(255,0,0), 0.90625 rgb(255,0,0), 0.9375 rgb(255,0,0), 0.96875 rgb(255,0,0), 1 rgb(255,0,0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,10 @@
clip {
clip: 0 0 50 50;
child: fill {
path: "M 0 0 h 32768 v 32768 h -32768 z";
child: color {
color: blue;
bounds: 0 0 32768 32768;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

View File

@ -0,0 +1,32 @@
/* ensure the size of this test */
color {
bounds: 0 0 50 50;
color: black;
}
clip {
/* Use a clip that isn't pixel-aligned.
Use 2 numbers that divide 255 so the expected pixel
doesn't cause rounding issues. */
clip: 3.333333 3.4 43 43;
/* Use a shadow node to force an offscreen */
child: shadow {
shadows: yellow 1 0;
/* Generate a checkerboard pattern with 1x1 tiles.
It is very obvious when those don't get pixel-aligned. */
child: repeat {
/* Make sure the bounds extend beyond the clip, so no
antialiasing artifacts are caused by the pattern. */
bounds: 0 0 50 50;
child: container {
color {
color: red;
bounds: 0 0 1 1;
}
color {
color: red;
bounds: 1 1 1 1;
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

View File

@ -0,0 +1,11 @@
clip {
clip: 0 0 50 50;
child: stroke {
path: "M 25 25 h 32718 v 32718 h -32718 z";
child: color {
color: blue;
bounds: 0 0 32768 32768;
}
line-width: 50;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

View File

@ -32,6 +32,7 @@ compare_render_tests = [
'clip-in-rounded-clip1',
'clip-in-rounded-clip2',
'clip-in-rounded-clip3',
'clip-intersection-fail-opacity-nogl',
'clipped-repeat',
'clipped_rounded_clip',
'clip-nested1',
@ -40,6 +41,7 @@ compare_render_tests = [
'color-matrix-identity',
'color-matrix-merge',
'color-matrix-parsing',
'conic-gradient-with-64-colorstops',
'crossfade-clip-both-children',
'cross-fade-clipped-with-huge-children-nogl',
'cross-fade-in-opacity',
@ -66,6 +68,7 @@ compare_render_tests = [
'empty-texture',
'empty-transform',
'fill',
'fill-clipped-nogl',
'fill-opacity',
'fill-with-3d-contents-nogl-nocairo',
'huge-height',
@ -83,6 +86,7 @@ compare_render_tests = [
'mask-modes-with-alpha',
'mask-texture-color-alpha',
'nested-rounded-clips',
'offscreen-pixel-alignment-nogl-nocairo',
'opacity_clip',
'opacity-colormatrix-combinations',
'opacity-overdraw',
@ -119,6 +123,7 @@ compare_render_tests = [
'shadow-opacity',
'shrink-rounded-border',
'stroke',
'stroke-clipped-nogl',
'stroke-opacity',
'stroke-with-3d-contents-nogl-nocairo',
'texture-coords',

View File

@ -7,3 +7,5 @@
<data>:3:11-13: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE
<data>:3:13-14: error: GTK_CSS_PARSER_ERROR_SYNTAX
<data>:3:13-14: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE
<data>:7:22-24: error: GTK_CSS_PARSER_ERROR_SYNTAX
<data>:7:2-8:1: error: GTK_CSS_PARSER_WARNING_SYNTAX

View File

@ -2,3 +2,7 @@ shadow {
shadows: 22;
shadows: 22;
}
shadow {
shadows: blue 50 50 hi:
}

View File

@ -1,5 +1,12 @@
shadow {
shadows: rgb(0,0,0) 22 0, rgb(0,0,0) 22 0;
shadows: rgb(0,0,0) 22 0;
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);
}
}
shadow {
shadows: rgb(0,0,0) 1 1;
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);

View File

@ -47,7 +47,6 @@ main (int argc,
{
/* Keep in sync with gtk_test_init() */
(g_test_init) (&argc, &argv, NULL);
g_setenv ("GTK_MODULES", "", TRUE);
setlocale (LC_ALL, "C");