From 66031fd00b9a4968e482ea9fdf2edc17d9e30199 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 13 Sep 2021 20:51:42 -0400 Subject: [PATCH 01/39] texture: Add error enum --- gdk/gdktexture.c | 2 ++ gdk/gdktexture.h | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index ec03c72370..4ef7cb5e97 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -47,6 +47,8 @@ #include +G_DEFINE_QUARK (gdk-texture-error-quark, gdk_texture_error) + /* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */ void gtk_snapshot_append_texture (GdkSnapshot *snapshot, diff --git a/gdk/gdktexture.h b/gdk/gdktexture.h index e080679ca1..927eb11bdf 100644 --- a/gdk/gdktexture.h +++ b/gdk/gdktexture.h @@ -38,6 +38,25 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkTexture, g_object_unref) typedef struct _GdkTextureClass GdkTextureClass; +#define GDK_TEXTURE_ERROR (gdk_texture_error_quark ()) + +GDK_AVAILABLE_IN_4_6 +GQuark gdk_texture_error_quark (void); + +/** + * GdkTextureError: + * @GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY: Not enough memory to handle this image + * @GDK_TEXTURE_ERROR_CORRUPT_IMAGE: The image data appears corrupted + * @GDK_TEXTURE_ERROR_UNSUPPORTED: The image format is not supported + * + * Possible errors that can be returned by `GdkTexture` constructors. + */ +typedef enum +{ + GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + GDK_TEXTURE_ERROR_CORRUPT_IMAGE, + GDK_TEXTURE_ERROR_UNSUPPORTED, +} GdkTextureError; GDK_AVAILABLE_IN_ALL GType gdk_texture_get_type (void) G_GNUC_CONST; From f51f7f85ebd32528614dc4442afc98d5384c06fb Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 10 Sep 2021 12:44:43 -0400 Subject: [PATCH 02/39] Add code to load and save pngs Using libpng instead of the lowest-common-denominator gdk-pixbuf loader. This will allow us to load >8bit data, and apply gamma and color correction in the future. For now, this still just provides RGBA8 data. As a consequence, we are now linking against libpng. --- gdk/loaders/gdkpng.c | 442 ++++++++++++++++++++++++++++++++++++ gdk/loaders/gdkpngprivate.h | 31 +++ gdk/meson.build | 4 +- meson.build | 15 ++ subprojects/libpng.wrap | 12 + 5 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 gdk/loaders/gdkpng.c create mode 100644 gdk/loaders/gdkpngprivate.h create mode 100644 subprojects/libpng.wrap diff --git a/gdk/loaders/gdkpng.c b/gdk/loaders/gdkpng.c new file mode 100644 index 0000000000..03c1095c04 --- /dev/null +++ b/gdk/loaders/gdkpng.c @@ -0,0 +1,442 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gdkpngprivate.h" + +#include "gdktexture.h" +#include "gdktextureprivate.h" +#include "gdkmemorytextureprivate.h" +#include "gsk/ngl/fp16private.h" +#include +#include + +/* The main difference between the png load/save code here and + * gdk-pixbuf is that we can support loading 16-bit data in the + * future, and we can extract gamma and colorspace information + * to produce linear, color-corrected data. + */ + +/* {{{ Callbacks */ + +/* No sigsetjmp on Windows */ +#ifndef HAVE_SIGSETJMP +#define sigjmp_buf jmp_buf +#define sigsetjmp(jb, x) setjmp(jb) +#define siglongjmp longjmp +#endif + +typedef struct +{ + guchar *data; + gsize size; + gsize position; +} png_io; + + +static void +png_read_func (png_structp png, + png_bytep data, + png_size_t size) +{ + png_io *io; + + io = png_get_io_ptr (png); + + if (io->position + size > io->size) + png_error (png, "Read past EOF"); + + memcpy (data, io->data + io->position, size); + io->position += size; +} + +static void +png_write_func (png_structp png, + png_bytep data, + png_size_t size) +{ + png_io *io; + + io = png_get_io_ptr (png); + + if (io->position > io->size || + io->size - io->position < size) + { + io->size = io->position + size; + io->data = g_realloc (io->data, io->size); + } + + memcpy (io->data + io->position, data, size); + io->position += size; +} + +static void +png_flush_func (png_structp png) +{ +} + +static png_voidp +png_malloc_callback (png_structp o, + png_size_t size) +{ + return g_try_malloc (size); +} + +static void +png_free_callback (png_structp o, + png_voidp x) +{ + g_free (x); +} + +static void +png_simple_error_callback (png_structp png, + png_const_charp error_msg) +{ + GError **error = png_get_error_ptr (png); + + if (error && !*error) + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, + "Error reading png (%s)", error_msg); + + longjmp (png_jmpbuf (png), 1); +} + +static void +png_simple_warning_callback (png_structp png, + png_const_charp error_msg) +{ +} + +/* }}} */ +/* {{{ Format conversion */ + +static void +unpremultiply (guchar *data, + int width, + int height, + int stride) +{ + gsize x, y; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + guchar *b = &data[x * 4]; + guint32 pixel; + guchar alpha; + + memcpy (&pixel, b, sizeof (guint32)); + alpha = (pixel & 0xff000000) >> 24; + if (alpha == 0) + { + b[0] = 0; + b[1] = 0; + b[2] = 0; + b[3] = 0; + } + else + { + b[0] = (((pixel & 0x00ff0000) >> 16) * 255 + alpha / 2) / alpha; + b[1] = (((pixel & 0x0000ff00) >> 8) * 255 + alpha / 2) / alpha; + b[2] = (((pixel & 0x000000ff) >> 0) * 255 + alpha / 2) / alpha; + b[3] = alpha; + } + } + data += stride; + } +} + +static inline int +multiply_alpha (int alpha, int color) +{ + int temp = (alpha * color) + 0x80; + return ((temp + (temp >> 8)) >> 8); +} + +static void +premultiply_data (png_structp png, + png_row_infop row_info, + png_bytep data) +{ + unsigned int i; + + for (i = 0; i < row_info->rowbytes; i += 4) + { + uint8_t *base = &data[i]; + uint8_t alpha = base[3]; + uint32_t p; + + if (alpha == 0) + { + p = 0; + } + else + { + uint8_t red = base[0]; + uint8_t green = base[1]; + uint8_t blue = base[2]; + + if (alpha != 0xff) + { + red = multiply_alpha (alpha, red); + green = multiply_alpha (alpha, green); + blue = multiply_alpha (alpha, blue); + } + p = ((uint32_t)alpha << 24) | (red << 16) | (green << 8) | (blue << 0); + } + memcpy (base, &p, sizeof (uint32_t)); + } +} + +static void +convert_bytes_to_data (png_structp png, + png_row_infop row_info, + png_bytep data) +{ + unsigned int i; + + for (i = 0; i < row_info->rowbytes; i += 4) + { + uint8_t *base = &data[i]; + uint8_t red = base[0]; + uint8_t green = base[1]; + uint8_t blue = base[2]; + uint32_t pixel; + + pixel = (0xffu << 24) | (red << 16) | (green << 8) | (blue << 0); + memcpy (base, &pixel, sizeof (uint32_t)); + } +} + +/* }}} */ +/* {{{ Public API */ + +GdkTexture * +gdk_load_png (GBytes *bytes, + GError **error) +{ + png_io io; + png_struct *png = NULL; + png_info *info; + guint width, height; + int depth, color_type; + int interlace, stride; + GdkMemoryFormat format; + guchar *buffer = NULL; + guchar **row_pointers = NULL; + GBytes *out_bytes; + GdkTexture *texture; + + io.data = (guchar *)g_bytes_get_data (bytes, &io.size); + io.position = 0; + + png = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING, + error, + png_simple_error_callback, + png_simple_warning_callback, + NULL, + png_malloc_callback, + png_free_callback); + if (png == NULL) + { + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + "Failed to parse png image"); + return NULL; + } + + info = png_create_info_struct (png); + if (info == NULL) + { + png_destroy_read_struct (&png, NULL, NULL); + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + "Failed to parse png image"); + return NULL; + } + + png_set_read_fn (png, &io, png_read_func); + + if (sigsetjmp (png_jmpbuf (png), 1)) + { + g_free (buffer); + g_free (row_pointers); + png_destroy_read_struct (&png, &info, NULL); + return NULL; + } + + png_read_info (png, info); + + png_get_IHDR (png, info, + &width, &height, &depth, + &color_type, &interlace, NULL, NULL); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb (png); + + if (color_type == PNG_COLOR_TYPE_GRAY) + png_set_expand_gray_1_2_4_to_8 (png); + + if (png_get_valid (png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha (png); + + if (depth < 8) + png_set_packing (png); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb (png); + + if (interlace != PNG_INTERLACE_NONE) + png_set_interlace_handling (png); + + png_set_filler (png, 0xff, PNG_FILLER_AFTER); + + png_read_update_info (png, info); + png_get_IHDR (png, info, + &width, &height, &depth, + &color_type, &interlace, NULL, NULL); + if ((depth != 8) || + !(color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_RGB_ALPHA)) + { + png_destroy_read_struct (&png, &info, NULL); + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED, + "Failed to parse png image"); + return NULL; + } + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB_ALPHA: + format = GDK_MEMORY_DEFAULT; + png_set_read_user_transform_fn (png, premultiply_data); + break; + case PNG_COLOR_TYPE_RGB: + format = GDK_MEMORY_DEFAULT; + png_set_read_user_transform_fn (png, convert_bytes_to_data); + break; + default: + g_assert_not_reached (); + } + + stride = width * gdk_memory_format_bytes_per_pixel (format); + if (stride % 4) + stride += 4 - stride % 4; + + buffer = g_try_malloc_n (height, stride); + row_pointers = g_try_malloc_n (height, sizeof (char *)); + + if (!buffer || !row_pointers) + { + g_free (buffer); + g_free (row_pointers); + png_destroy_read_struct (&png, &info, NULL); + g_set_error_literal (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + "Not enough memory to load png"); + return NULL; + } + + for (int i = 0; i < height; i++) + row_pointers[i] = &buffer[i * stride]; + + png_read_image (png, row_pointers); + png_read_end (png, info); + + out_bytes = g_bytes_new_take (buffer, height * stride); + texture = gdk_memory_texture_new (width, height, + format, + out_bytes, stride); + g_bytes_unref (out_bytes); + + g_free (row_pointers); + png_destroy_read_struct (&png, &info, NULL); + + return texture; +} + +GBytes * +gdk_save_png (GdkTexture *texture) +{ + png_struct *png = NULL; + png_info *info; + png_io io = { NULL, 0, 0 }; + guint width, height, stride; + guchar *data = NULL; + guchar *row; + int y; + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + stride = width * 4; + data = g_malloc_n (stride, height); + gdk_texture_download (texture, data, stride); + unpremultiply (data, width, height, stride); + + png = png_create_write_struct_2 (PNG_LIBPNG_VER_STRING, NULL, + png_simple_error_callback, + png_simple_warning_callback, + NULL, + png_malloc_callback, + png_free_callback); + if (!png) + return NULL; + + info = png_create_info_struct (png); + if (!info) + { + png_destroy_read_struct (&png, NULL, NULL); + return NULL; + } + + if (sigsetjmp (png_jmpbuf (png), 1)) + { + png_destroy_read_struct (&png, &info, NULL); + return NULL; + } + + png_set_write_fn (png, &io, png_write_func, png_flush_func); + + png_set_IHDR (png, info, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + png_write_info (png, info); + png_set_packing (png); + + for (y = 0, row = data; y < height; y++, row += stride) + png_write_rows (png, &row, 1); + + png_write_end (png, info); + + png_destroy_write_struct (&png, &info); + + return g_bytes_new_take (io.data, io.size); +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gdk/loaders/gdkpngprivate.h b/gdk/loaders/gdkpngprivate.h new file mode 100644 index 0000000000..ca824579a2 --- /dev/null +++ b/gdk/loaders/gdkpngprivate.h @@ -0,0 +1,31 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GDK_PNG_PRIVATE_H__ +#define __GDK_PNG_PRIVATE_H__ + +#include "gdktexture.h" +#include + +#define PNG_SIGNATURE "\x89PNG" + +GdkTexture *gdk_load_png (GBytes *bytes, + GError **error); + +GBytes *gdk_save_png (GdkTexture *texture); + +#endif diff --git a/gdk/meson.build b/gdk/meson.build index ccc1738eab..092537257f 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -51,6 +51,7 @@ gdk_public_sources = files([ 'gdktoplevelsize.c', 'gdktoplevel.c', 'gdkdragsurface.c', + 'loaders/gdkpng.c', ]) gdk_public_headers = files([ @@ -201,6 +202,7 @@ gdk_deps = [ platform_gio_dep, pangocairo_dep, vulkan_dep, + png_dep, ] if profiler_enabled @@ -257,7 +259,7 @@ endif libgdk = static_library('gdk', sources: [gdk_sources, gdk_backends_gen_headers, gdkconfig], dependencies: gdk_deps + [libgtk_css_dep], - link_with: [libgtk_css, ], + link_with: [libgtk_css], include_directories: [confinc, gdkx11_inc, wlinc], c_args: libgdk_c_args + common_cflags, link_whole: gdk_backends, diff --git a/meson.build b/meson.build index 764755bf33..24bf4b633c 100644 --- a/meson.build +++ b/meson.build @@ -207,6 +207,17 @@ foreach func : check_functions endif endforeach +# We use links() because sigsetjmp() is often a macro hidden behind other macros +cdata.set('HAVE_SIGSETJMP', + cc.links('''#define _POSIX_SOURCE + #include + int main (void) { + sigjmp_buf env; + sigsetjmp (env, 0); + return 0; + }''', name: 'sigsetjmp'), +) + # Check for __uint128_t (gcc) by checking for 128-bit division uint128_t_src = '''int main() { static __uint128_t v1 = 100; @@ -389,6 +400,10 @@ pangocairo_dep = dependency('pangocairo', version: pango_req, pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req, fallback : ['gdk-pixbuf', 'gdkpixbuf_dep'], default_options: ['png=enabled', 'jpeg=enabled', 'builtin_loaders=png,jpeg', 'man=false']) +png_dep = dependency('libpng', + fallback: ['libpng', 'libpng_dep'], + required: true) + epoxy_dep = dependency('epoxy', version: epoxy_req, fallback: ['libepoxy', 'libepoxy_dep']) harfbuzz_dep = dependency('harfbuzz', version: '>= 2.1.0', required: false, diff --git a/subprojects/libpng.wrap b/subprojects/libpng.wrap new file mode 100644 index 0000000000..9d6c6b3078 --- /dev/null +++ b/subprojects/libpng.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = libpng-1.6.37 +source_url = https://github.com/glennrp/libpng/archive/v1.6.37.tar.gz +source_filename = libpng-1.6.37.tar.gz +source_hash = ca74a0dace179a8422187671aee97dd3892b53e168627145271cad5b5ac81307 +patch_url = https://wrapdb.mesonbuild.com/v2/libpng_1.6.37-3/get_patch +patch_filename = libpng-1.6.37-3-wrap.zip +patch_hash = 6c9f32fd9150b3a96ab89be52af664e32207e10aa9f5fb9aa015989ee2dd7100 + +[provide] +libpng = libpng_dep + From a71877bf99f2731965c5dbeb257946dbc54d4aa4 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 11 Sep 2021 16:23:53 -0400 Subject: [PATCH 03/39] Load pngs without gdk-pixbuf Use our own loader for pngs, which will allow us to get e.g. 16-bit data in the future. --- gdk/gdktexture.c | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 4ef7cb5e97..8d2d1d16ca 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -46,6 +46,7 @@ #include "gdksnapshot.h" #include +#include "loaders/gdkpngprivate.h" G_DEFINE_QUARK (gdk-texture-error-quark, gdk_texture_error) @@ -423,24 +424,40 @@ GdkTexture * gdk_texture_new_from_bytes (GBytes *bytes, GError **error) { - GInputStream *stream; - GdkPixbuf *pixbuf; - GdkTexture *texture; + const char *data; + gsize size; g_return_val_if_fail (bytes != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - stream = g_memory_input_stream_new_from_bytes (bytes); - pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); - g_object_unref (stream); + data = g_bytes_get_data (bytes, &size); - if (pixbuf == NULL) - return NULL; + if (size > strlen (PNG_SIGNATURE) && + memcmp (data, PNG_SIGNATURE, strlen (PNG_SIGNATURE)) == 0) + { + return gdk_load_png (bytes, error); + } + else + { + GInputStream *stream; + GdkPixbuf *pixbuf; - texture = gdk_texture_new_for_pixbuf (pixbuf); - g_object_unref (pixbuf); + stream = g_memory_input_stream_new_from_bytes (bytes); + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); + g_object_unref (stream); - return texture; + if (pixbuf) + { + GdkTexture *texture; + + texture = gdk_texture_new_for_pixbuf (pixbuf); + g_object_unref (pixbuf); + + return texture; + } + } + + return NULL; } /** From 7949aaabb7fcafab3aa683330437737783f22665 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 12 Sep 2021 09:24:59 -0400 Subject: [PATCH 04/39] Save pngs without cairo Use our own loader for pngs, which will allow us to save e.g. 16-bit data in the future. --- gdk/gdktexture.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 8d2d1d16ca..973a6f923a 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -698,30 +698,18 @@ gboolean gdk_texture_save_to_png (GdkTexture *texture, const char *filename) { - cairo_surface_t *surface; - cairo_status_t status; + GBytes *bytes; gboolean result; g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE); g_return_val_if_fail (filename != NULL, FALSE); - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, - gdk_texture_get_width (texture), - gdk_texture_get_height (texture)); - gdk_texture_download (texture, - cairo_image_surface_get_data (surface), - cairo_image_surface_get_stride (surface)); - cairo_surface_mark_dirty (surface); - - status = cairo_surface_write_to_png (surface, filename); - - if (status != CAIRO_STATUS_SUCCESS || - cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) - result = FALSE; - else - result = TRUE; - - cairo_surface_destroy (surface); + bytes = gdk_save_png (texture); + result = g_file_set_contents (filename, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + NULL); + g_bytes_unref (bytes); return result; } From d6ce65f81c2914640746e9ae8d12e0096da6ab11 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 11 Sep 2021 20:27:13 -0400 Subject: [PATCH 05/39] Add tests for the png loader --- testsuite/gdk/image-data/image.png | Bin 0 -> 1609 bytes testsuite/gdk/image.c | 118 +++++++++++++++++++++++++++++ testsuite/gdk/meson.build | 26 +++++++ 3 files changed, 144 insertions(+) create mode 100644 testsuite/gdk/image-data/image.png create mode 100644 testsuite/gdk/image.c diff --git a/testsuite/gdk/image-data/image.png b/testsuite/gdk/image-data/image.png new file mode 100644 index 0000000000000000000000000000000000000000..49ee0d459eb88075379a3d35327bb4e5401d6453 GIT binary patch literal 1609 zcmV-P2DbT$P)@ge=+_a0E5O2H(YzNFZ>6klu^S81&uB=A=GmmTzT6qci;23?q|@@n{VG| zpM9gfdiH`Fi6Y{`Sp8~AhP>-5;GbRl! zLq;@Y$AA9rGf)t_$KY?>Z|lH?7oXdF#F59jyPW(GvU*Phb11eQnu#s@3WTW-DW!HG ztQJxReWOL|zMU7X`bp_vRZ}S{y&SuC(TpGQ@@+@q&DUPSLSC9 zV5`19CXXMBin0oXLZOsMso-7Xsm$#vfd;5ZZ``WB*5pJ@5IS~Mkm9e5Pu2bTud0B~*Zvyb1i zr!|>a^5-XdJVnaZ0RXD8As%!8eB&}i!V#T;Fh2bF1IXZn5>$(_KM5cw-GwKChGv0= zgakm*yDSgQo`5hx3I#4z;EY;6%+;^X*z>(4Hg4JojYKK{$>kMqzV+qvBrcH33x{ZAcp)bVGuEyb%BL?eIHUu7(_HD9LLc@wJ-=!9h75(Vvcog*9rh! z5perdrAfC9An7IX@M8~S>utBj{CNv<3JPFFm~Z7hEJ;2}dudV0EL{J?=3 zE#(y|AQB5P?Z^RRMl(1}2ScePn1TY~=I#X)0VH@pT9P6m0m1kQV^LQZLw;UipmycXXWyx%a&vQf`wL0+l=+;*K_eBkKBKTUfYS?Ze)OH7!`CK0J-O1 zclSVzxv2xIEzTW?a6wwSmiDDsKt+&P=a>yZ~>i`01hcY`mTLgxid-m+P?uYMwo={v|9F4_dX3o)Cv}kDPHSUo| z9d>a+QQ@PyTxaNdkq$r-0!ayZ-#G8q72I;`4d3{j?@$}7M^RBBHf-30($eB~dgI2; z2!|P>(KI9zNlQ*WZrhZS4+r!ce29kEEfQXI>G=l;DJSf)#~v6ybTAmDDAz_Q&`ysW zJ_P&hy*I{>9fRF>+XcgQUrC8AwwPps+3EKwzwg{_5}tG3S?f+d@szTG0|(-7fPN+O z@%)R=t$XPHM=H=7U2`BFk2l|b=WW2>4)c-YqZ9o9zvI6E#hK6du_U**00000NkvXX Hu0mjfn2831 literal 0 HcmV?d00001 diff --git a/testsuite/gdk/image.c b/testsuite/gdk/image.c new file mode 100644 index 0000000000..0a916eb404 --- /dev/null +++ b/testsuite/gdk/image.c @@ -0,0 +1,118 @@ +#include +#include "gdk/loaders/gdkpngprivate.h" + +static void +assert_texture_equal (GdkTexture *t1, + GdkTexture *t2) +{ + int width; + int height; + int stride; + guchar *d1; + guchar *d2; + + width = gdk_texture_get_width (t1); + height = gdk_texture_get_height (t1); + stride = 4 * width; + + g_assert_cmpint (width, ==, gdk_texture_get_width (t2)); + g_assert_cmpint (height, ==, gdk_texture_get_height (t2)); + + d1 = g_malloc (stride * height); + d2 = g_malloc (stride * height); + + gdk_texture_download (t1, d1, stride); + gdk_texture_download (t2, d2, stride); + + g_assert_cmpmem (d1, stride * height, d2, stride * height); + + g_free (d1); + g_free (d2); +} + +static void +test_load_image (gconstpointer data) +{ + const char *filename = data; + GdkTexture *texture; + char *path; + GFile *file; + GBytes *bytes; + GError *error = NULL; + + path = g_test_build_filename (G_TEST_DIST, "image-data", filename, NULL); + file = g_file_new_for_path (path); + bytes = g_file_load_bytes (file, NULL, NULL, &error); + g_assert_no_error (error); + + if (g_str_has_suffix (filename, ".png")) + texture = gdk_load_png (bytes, &error); + else + g_assert_not_reached (); + + g_assert_no_error (error); + g_assert_true (GDK_IS_TEXTURE (texture)); + g_assert_cmpint (gdk_texture_get_width (texture), ==, 32); + g_assert_cmpint (gdk_texture_get_height (texture), ==, 32); + + g_object_unref (texture); + g_bytes_unref (bytes); + g_object_unref (file); + g_free (path); +} + +static void +test_save_image (gconstpointer test_data) +{ + const char *filename = test_data; + char *path; + GFile *file; + GdkTexture *texture; + GFile *file2; + GdkTexture *texture2; + GError *error = NULL; + GBytes *bytes = NULL; + GIOStream *stream; + + path = g_test_build_filename (G_TEST_DIST, "image-data", filename, NULL); + file = g_file_new_for_path (path); + texture = gdk_texture_new_from_file (file, &error); + g_assert_no_error (error); + + if (g_str_has_suffix (filename, ".png")) + bytes = gdk_save_png (texture); + else + g_assert_not_reached (); + + file2 = g_file_new_tmp ("imageXXXXXX", (GFileIOStream **)&stream, NULL); + g_object_unref (stream); + g_file_replace_contents (file2, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + NULL, FALSE, 0, + NULL, NULL, &error); + g_assert_no_error (error); + + texture2 = gdk_texture_new_from_file (file2, &error); + g_assert_no_error (error); + + assert_texture_equal (texture, texture2); + + g_bytes_unref (bytes); + g_object_unref (texture2); + g_object_unref (file2); + g_object_unref (texture); + g_object_unref (file); + g_free (path); +} + +int +main (int argc, char *argv[]) +{ + (g_test_init) (&argc, &argv, NULL); + + g_test_add_data_func ("/image/load/png", "image.png", test_load_image); + g_test_add_data_func ("/image/save/png", "image.png", test_save_image); + + return g_test_run (); +} diff --git a/testsuite/gdk/meson.build b/testsuite/gdk/meson.build index 3eff25868d..c259be1f06 100644 --- a/testsuite/gdk/meson.build +++ b/testsuite/gdk/meson.build @@ -49,6 +49,31 @@ foreach t : tests ) endforeach +internal_tests = [ + 'image' +] + +foreach t : internal_tests + test_exe = executable(t, '@0@.c'.format(t), + c_args: common_cflags, + dependencies: libgtk_static_dep, + install: get_option('install-tests'), + install_dir: testexecdir, + ) + + test(t, test_exe, + args: [ '--tap', '-k' ], + protocol: 'tap', + env: [ + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), + 'DBUS_SESSION_BUS_ADDRESS=', + ], + suite: 'gdk', + ) +endforeach + + if get_option('install-tests') foreach t : tests test_cdata = configuration_data() @@ -63,4 +88,5 @@ if get_option('install-tests') endforeach install_subdir('clipboard-data', install_dir: testexecdir) + install_subdir('image-data', install_dir: testexecdir) endif From d7c8f927331b5d74949bcdf1fe3970a77277c4a2 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 13 Sep 2021 14:29:31 -0400 Subject: [PATCH 06/39] Add gdk_texture_save_to_png_bytes Just expose what we already have available internally, so e.g. tests can use it without static linking. --- gdk/gdktexture.c | 29 ++++++++++++++++++++++++++++- gdk/gdktexture.h | 2 ++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 973a6f923a..85b5be28bb 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -690,7 +690,8 @@ gdk_texture_get_render_data (GdkTexture *self, * This is a utility function intended for debugging and testing. * If you want more control over formats, proper error handling or * want to store to a `GFile` or other location, you might want to - * look into using the gdk-pixbuf library. + * use [method@Gdk.Texture.save_to_png_bytes] or look into the + * gdk-pixbuf library. * * Returns: %TRUE if saving succeeded, %FALSE on failure. */ @@ -713,3 +714,29 @@ gdk_texture_save_to_png (GdkTexture *texture, return result; } + +/** + * gdk_texture_save_to_png_bytes: + * @texture: a `GdkTexture` + * + * Store the given @texture in memory as a PNG file. + * Use [ctor@Gdk.Texture.new_from_bytes] to read it back. + * + * If you want to serialize a texture, this is a convenient and + * portable way to do that. + * + * If you need more control over the generated image, such as + * attaching metadata, you should look into an image handling + * library such as the gdk-pixbuf library. + * + * Returns: a newly allocated `GBytes` containing PNG data + * + * Since: 4.6 + */ +GBytes * +gdk_texture_save_to_png_bytes (GdkTexture *texture) +{ + g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL); + + return gdk_save_png (texture); +} diff --git a/gdk/gdktexture.h b/gdk/gdktexture.h index 927eb11bdf..7e7de473d8 100644 --- a/gdk/gdktexture.h +++ b/gdk/gdktexture.h @@ -91,6 +91,8 @@ void gdk_texture_download_float (GdkTexture GDK_AVAILABLE_IN_ALL gboolean gdk_texture_save_to_png (GdkTexture *texture, const char *filename); +GDK_AVAILABLE_IN_4_6 +GBytes * gdk_texture_save_to_png_bytes (GdkTexture *texture); G_END_DECLS From a03594df52534399988e7a9af4d77bd5065538e1 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 10 Sep 2021 12:47:14 -0400 Subject: [PATCH 07/39] Add code to load and save tiff files Add support for the tiff format, which is flexible enough to handle all our memory texture formats without loss. As a consequence, we are now linking against libtiff. --- gdk/loaders/gdktiff.c | 511 +++++++++++++++++++++++++++++++++++ gdk/loaders/gdktiffprivate.h | 32 +++ gdk/meson.build | 2 + meson.build | 3 + subprojects/libtiff.wrap | 12 + 5 files changed, 560 insertions(+) create mode 100644 gdk/loaders/gdktiff.c create mode 100644 gdk/loaders/gdktiffprivate.h create mode 100644 subprojects/libtiff.wrap diff --git a/gdk/loaders/gdktiff.c b/gdk/loaders/gdktiff.c new file mode 100644 index 0000000000..6f41811791 --- /dev/null +++ b/gdk/loaders/gdktiff.c @@ -0,0 +1,511 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gdktiffprivate.h" + +#include "gdktexture.h" +#include "gdktextureprivate.h" +#include "gdkmemorytextureprivate.h" +#include + +/* Our main interest in tiff as an image format is that it is + * flexible enough to save all our texture formats without + * lossy conversions. + * + * The loader isn't meant to be a very versatile. It just aims + * to load the subset that we're saving ourselves. For anything + * else, we fall back to TIFFRGBAImage, which is the same api + * that gdk-pixbuf uses. + */ + +/* {{{ IO handling */ + +typedef struct +{ + GBytes **out_bytes; + gchar *data; + gsize size; + gsize position; +} TiffIO; + +static void +tiff_io_warning (const char *module, + const char *fmt, + va_list ap) G_GNUC_PRINTF(2, 0); +static void +tiff_io_warning (const char *module, + const char *fmt, + va_list ap) +{ + g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap); +} + +static void +tiff_io_error (const char *module, + const char *fmt, + va_list ap) G_GNUC_PRINTF(2, 0); +static void +tiff_io_error (const char *module, + const char *fmt, + va_list ap) +{ + g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap); +} + +static tsize_t +tiff_io_no_read (thandle_t handle, + tdata_t buffer, + tsize_t size) +{ + return (tsize_t) -1; +} + +static tsize_t +tiff_io_read (thandle_t handle, + tdata_t buffer, + tsize_t size) +{ + TiffIO *io = (TiffIO *) handle; + gsize read; + + if (io->position >= io->size) + return 0; + + read = MIN (size, io->size - io->position); + + memcpy (buffer, io->data + io->position, read); + io->position += read; + + return (tsize_t) read; +} + +static tsize_t +tiff_io_no_write (thandle_t handle, + tdata_t buffer, + tsize_t size) +{ + return (tsize_t) -1; +} + +static tsize_t +tiff_io_write (thandle_t handle, + tdata_t buffer, + tsize_t size) +{ + TiffIO *io = (TiffIO *) handle; + + if (io->position > io->size || + io->size - io->position < size) + { + io->size = io->position + size; + io->data = g_realloc (io->data, io->size); + } + + memcpy (io->data + io->position, buffer, size); + io->position += size; + + return (tsize_t) size; +} + +static toff_t +tiff_io_seek (thandle_t handle, + toff_t offset, + int whence) +{ + TiffIO *io = (TiffIO *) handle; + + switch (whence) + { + default: + return -1; + case SEEK_SET: + break; + case SEEK_CUR: + offset += io->position; + break; + case SEEK_END: + offset += io->size; + break; + } + if (offset < 0) + return -1; + + io->position = offset; + + return offset; +} + +static int +tiff_io_close (thandle_t handle) +{ + TiffIO *io = (TiffIO *) handle; + + if (io->out_bytes) + *io->out_bytes = g_bytes_new_take (io->data, io->size); + + g_free (io); + + return 0; +} + +static toff_t +tiff_io_get_file_size (thandle_t handle) +{ + TiffIO *io = (TiffIO *) handle; + + return io->size; +} + +static TIFF * +tiff_open_read (GBytes *bytes) +{ + TiffIO *io; + + TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning); + TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error); + + io = g_new0 (TiffIO, 1); + + io->data = (char *) g_bytes_get_data (bytes, &io->size); + + return TIFFClientOpen ("GTK-read", "r", + (thandle_t) io, + tiff_io_read, + tiff_io_no_write, + tiff_io_seek, + tiff_io_close, + tiff_io_get_file_size, + NULL, NULL); +} + +static TIFF * +tiff_open_write (GBytes **result) +{ + TiffIO *io; + + TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning); + TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error); + + io = g_new0 (TiffIO, 1); + + io->out_bytes = result; + + return TIFFClientOpen ("GTK-write", "w", + (thandle_t) io, + tiff_io_no_read, + tiff_io_write, + tiff_io_seek, + tiff_io_close, + tiff_io_get_file_size, + NULL, NULL); +} + +/* }}} */ +/* {{{ Format conversion */ + +static void +flip_02 (guchar *data, + int width, + int height, + int stride) +{ + gsize x, y; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + guchar tmp; + tmp = data[x * 4]; + data[x * 4] = data[x * 4 + 2]; + data[x * 4 + 2] = tmp; + } + data += stride; + } +} + +/* }}} */ +/* {{{ Public API */ + +static struct { + GdkMemoryFormat format; + guint16 bits_per_sample; + guint16 samples_per_pixel; + guint16 sample_format; +} format_data[] = { + { GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, 8, 4, SAMPLEFORMAT_UINT }, + { GDK_MEMORY_R8G8B8, 8, 3, SAMPLEFORMAT_UINT }, + { GDK_MEMORY_R16G16B16, 16, 3, SAMPLEFORMAT_UINT }, + { GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, 16, 4, SAMPLEFORMAT_UINT }, + { GDK_MEMORY_R16G16B16_FLOAT, 16, 3, SAMPLEFORMAT_IEEEFP }, + { GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED, 16, 4, SAMPLEFORMAT_IEEEFP }, + { GDK_MEMORY_R32G32B32_FLOAT, 32, 3, SAMPLEFORMAT_IEEEFP }, + { GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED, 32, 4, SAMPLEFORMAT_IEEEFP }, +}; + +GBytes * +gdk_save_tiff (GdkTexture *texture) +{ + TIFF *tif; + int width, height, stride; + guint16 bits_per_sample = 0; + guint16 samples_per_pixel = 0; + guint16 sample_format = 0; + const guchar *line; + const guchar *data; + guchar *new_data = NULL; + GBytes *result = NULL; + GdkTexture *memory_texture; + GdkMemoryFormat format; + + tif = tiff_open_write (&result); + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + memory_texture = gdk_texture_download_texture (texture); + format = gdk_memory_texture_get_format (GDK_MEMORY_TEXTURE (memory_texture)); + + for (int i = 0; i < G_N_ELEMENTS (format_data); i++) + { + if (format == format_data[i].format) + { + data = gdk_memory_texture_get_data (GDK_MEMORY_TEXTURE (memory_texture)); + stride = gdk_memory_texture_get_stride (GDK_MEMORY_TEXTURE (memory_texture)); + bits_per_sample = format_data[i].bits_per_sample; + samples_per_pixel = format_data[i].samples_per_pixel; + sample_format = format_data[i].sample_format; + break; + } + } + + if (bits_per_sample == 0) + { + /* An 8-bit format we don't have in the table, handle + * it by converting to R8G8B8A8_PREMULTIPLIED + */ + stride = width * 4; + new_data = g_malloc (stride * height); + gdk_texture_download (memory_texture, new_data, stride); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + flip_02 (new_data, width, height, stride); +#endif + data = new_data; + bits_per_sample = 8; + samples_per_pixel = 4; + sample_format = SAMPLEFORMAT_UINT; + } + + TIFFSetField (tif, TIFFTAG_SOFTWARE, "GTK"); + TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField (tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample); + TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel); + TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, sample_format); + TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField (tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + // TODO: save gamma / colorspace + + if (samples_per_pixel > 3) + { + guint16 extra_samples[] = { EXTRASAMPLE_ASSOCALPHA }; + TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, extra_samples); + } + + TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + + line = (const guchar *)data; + for (int y = 0; y < height; y++) + { + if (TIFFWriteScanline (tif, (void *)line, y, 0) == -1) + { + TIFFClose (tif); + g_free (new_data); + g_object_unref (memory_texture); + return NULL; + } + + line += stride; + } + + TIFFFlushData (tif); + TIFFClose (tif); + + g_assert (result); + + g_free (new_data); + g_object_unref (memory_texture); + + return result; +} + +static GdkTexture * +load_fallback (TIFF *tif, + GError **error) +{ + int width, height; + guchar *data; + GBytes *bytes; + GdkTexture *texture; + + TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &height); + + data = g_malloc (width * height * 4); + + if (!TIFFReadRGBAImageOriented (tif, width, height, (uint32 *)data, ORIENTATION_TOPLEFT, 1)) + { + g_set_error_literal (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, + "Failed to load RGB data from TIFF file"); + g_free (data); + return NULL; + } + + bytes = g_bytes_new_take (data, width * height * 4); + + texture = gdk_memory_texture_new (width, height, + GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, + bytes, + width * 4); + + g_bytes_unref (bytes); + + return texture; +} + +GdkTexture * +gdk_load_tiff (GBytes *input_bytes, + GError **error) +{ + TIFF *tif; + guint16 samples_per_pixel; + guint16 bits_per_sample; + guint16 photometric; + guint16 planarconfig; + guint16 sample_format; + guint16 orientation; + guint32 width, height; + GdkMemoryFormat format; + guchar *data, *line; + gsize stride; + int bpp; + GBytes *bytes; + GdkTexture *texture; + + tif = tiff_open_read (input_bytes); + + TIFFSetDirectory (tif, 0); + + TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); + TIFFGetFieldDefaulted (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLEFORMAT, &sample_format); + TIFFGetFieldDefaulted (tif, TIFFTAG_PHOTOMETRIC, &photometric); + TIFFGetFieldDefaulted (tif, TIFFTAG_PLANARCONFIG, &planarconfig); + TIFFGetFieldDefaulted (tif, TIFFTAG_ORIENTATION, &orientation); + TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGELENGTH, &height); + + if (samples_per_pixel == 4) + { + guint16 extra; + guint16 *extra_types; + + if (!TIFFGetField (tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types)) + extra = 0; + + if (extra == 0 || extra_types[0] != EXTRASAMPLE_ASSOCALPHA) + { + texture = load_fallback (tif, error); + TIFFClose (tif); + return texture; + } + } + + format = 0; + + for (int i = 0; i < G_N_ELEMENTS (format_data); i++) + { + if (format_data[i].sample_format == sample_format && + format_data[i].bits_per_sample == bits_per_sample && + format_data[i].samples_per_pixel == samples_per_pixel) + { + format = format_data[i].format; + break; + } + } + + if (format == 0 || + photometric != PHOTOMETRIC_RGB || + planarconfig != PLANARCONFIG_CONTIG || + TIFFIsTiled (tif) || + orientation != ORIENTATION_TOPLEFT) + { + texture = load_fallback (tif, error); + TIFFClose (tif); + return texture; + } + + stride = width * gdk_memory_format_bytes_per_pixel (format); + + g_assert (TIFFScanlineSize (tif) == stride); + + data = g_try_malloc_n (height, stride); + if (!data) + { + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + "Not enough memory to read tiff"); + TIFFClose (tif); + return NULL; + } + + line = data; + for (int y = 0; y < height; y++) + { + if (TIFFReadScanline (tif, line, y, 0) == -1) + { + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, + "Reading data failed at row %d", y); + TIFFClose (tif); + g_free (data); + return NULL; + } + + line += stride; + } + + bpp = gdk_memory_format_bytes_per_pixel (format); + bytes = g_bytes_new_take (data, width * height * bpp); + + texture = gdk_memory_texture_new (width, height, + format, + bytes, width * bpp); + g_bytes_unref (bytes); + + TIFFClose (tif); + + return texture; +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gdk/loaders/gdktiffprivate.h b/gdk/loaders/gdktiffprivate.h new file mode 100644 index 0000000000..ee97884e75 --- /dev/null +++ b/gdk/loaders/gdktiffprivate.h @@ -0,0 +1,32 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GDK_TIFF_PRIVATE_H__ +#define __GDK_TIFF_PRIVATE_H__ + +#include "gdktexture.h" +#include + +#define TIFF_SIGNATURE1 "MM\x00\x2a" +#define TIFF_SIGNATURE2 "II\x2a\x00" + +GdkTexture *gdk_load_tiff (GBytes *bytes, + GError **error); + +GBytes * gdk_save_tiff (GdkTexture *texture); + +#endif diff --git a/gdk/meson.build b/gdk/meson.build index 092537257f..6ef0ec01fb 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -52,6 +52,7 @@ gdk_public_sources = files([ 'gdktoplevel.c', 'gdkdragsurface.c', 'loaders/gdkpng.c', + 'loaders/gdktiff.c', ]) gdk_public_headers = files([ @@ -203,6 +204,7 @@ gdk_deps = [ pangocairo_dep, vulkan_dep, png_dep, + tiff_dep, ] if profiler_enabled diff --git a/meson.build b/meson.build index 24bf4b633c..4aa622bbaf 100644 --- a/meson.build +++ b/meson.build @@ -403,6 +403,9 @@ pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req, png_dep = dependency('libpng', fallback: ['libpng', 'libpng_dep'], required: true) +tiff_dep = dependency('libtiff-4', + fallback: ['libtiff', 'libtiff4_dep'], + required: true) epoxy_dep = dependency('epoxy', version: epoxy_req, fallback: ['libepoxy', 'libepoxy_dep']) diff --git a/subprojects/libtiff.wrap b/subprojects/libtiff.wrap new file mode 100644 index 0000000000..7ad7f5e1f5 --- /dev/null +++ b/subprojects/libtiff.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = tiff-4.1.0 +source_url = http://download.osgeo.org/libtiff/tiff-4.1.0.zip +source_filename = tiff-4.1.0.zip +source_hash = 6f3dbed9d2ecfed33c7192b5c01884078970657fa21b4ad28e3cdf3438eb2419 +patch_filename = libtiff_4.1.0-4_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/libtiff_4.1.0-4/get_patch +patch_hash = c0fe078d06e5a7f2480a96c3897a7b3b9fa9a42c08fb76ae5f1dd59e0519a14e + +[provide] +libtiff-4 = libtiff4_dep + From f925e12e1de2c36ca488d1c0a7ec2054c40200dc Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 11 Sep 2021 16:30:37 -0400 Subject: [PATCH 08/39] Load tiffs without gdk-pixbuf This will let us load floating point data, in the future. --- gdk/gdktexture.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 85b5be28bb..4176552df8 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -47,6 +47,7 @@ #include #include "loaders/gdkpngprivate.h" +#include "loaders/gdktiffprivate.h" G_DEFINE_QUARK (gdk-texture-error-quark, gdk_texture_error) @@ -437,6 +438,13 @@ gdk_texture_new_from_bytes (GBytes *bytes, { return gdk_load_png (bytes, error); } + else if ((size > strlen (TIFF_SIGNATURE1) && + memcmp (data, TIFF_SIGNATURE1, strlen (TIFF_SIGNATURE1)) == 0) || + (size > strlen (TIFF_SIGNATURE2) && + memcmp (data, TIFF_SIGNATURE2, strlen (TIFF_SIGNATURE2)) == 0)) + { + return gdk_load_tiff (bytes, error); + } else { GInputStream *stream; From 945c2531ac70af68f47f892988dd2d8431f07e01 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 12 Sep 2021 01:07:16 -0400 Subject: [PATCH 09/39] Add tests for the tiff loader --- testsuite/gdk/image-data/image.tiff | Bin 0 -> 205344 bytes testsuite/gdk/image.c | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 testsuite/gdk/image-data/image.tiff diff --git a/testsuite/gdk/image-data/image.tiff b/testsuite/gdk/image-data/image.tiff new file mode 100644 index 0000000000000000000000000000000000000000..ddd0d03609ccdd44e2dcdd7408dbc253ba465305 GIT binary patch literal 205344 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 #include "gdk/loaders/gdkpngprivate.h" +#include "gdk/loaders/gdktiffprivate.h" static void assert_texture_equal (GdkTexture *t1, @@ -47,6 +48,8 @@ test_load_image (gconstpointer data) if (g_str_has_suffix (filename, ".png")) texture = gdk_load_png (bytes, &error); + else if (g_str_has_suffix (filename, ".tiff")) + texture = gdk_load_tiff (bytes, &error); else g_assert_not_reached (); @@ -81,6 +84,8 @@ test_save_image (gconstpointer test_data) if (g_str_has_suffix (filename, ".png")) bytes = gdk_save_png (texture); + else if (g_str_has_suffix (filename, ".tiff")) + bytes = gdk_save_tiff (texture); else g_assert_not_reached (); @@ -112,7 +117,9 @@ main (int argc, char *argv[]) (g_test_init) (&argc, &argv, NULL); g_test_add_data_func ("/image/load/png", "image.png", test_load_image); + g_test_add_data_func ("/image/load/tiff", "image.tiff", test_load_image); g_test_add_data_func ("/image/save/png", "image.png", test_save_image); + g_test_add_data_func ("/image/save/tiff", "image.tiff", test_save_image); return g_test_run (); } From d30a0296896f5d9eabbf9884cf87ead645887d9c Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 7 Sep 2021 09:20:05 -0400 Subject: [PATCH 10/39] Add gdk_texture_save_to_tiff This is a companion to gdk_texture_save_to_png, using the tiff format, which will let us avoid lossy conversion of HDR data, since we can store floating point data. --- gdk/gdktexture.c | 32 ++++++++++++++++++++++++++++++++ gdk/gdktexture.h | 3 +++ 2 files changed, 35 insertions(+) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 4176552df8..7d1907be8d 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -748,3 +748,35 @@ gdk_texture_save_to_png_bytes (GdkTexture *texture) return gdk_save_png (texture); } + +/** + * gdk_texture_save_to_tiff: + * @texture: a `GdkTexture` + * @filename: (type filename): the filename to store to + * + * Store the given @texture to the @filename as a TIFF file. + * + * GTK will attempt to store data without loss. + * Returns: %TRUE if saving succeeded, %FALSE on failure. + * + * Since: 4.6 + */ +gboolean +gdk_texture_save_to_tiff (GdkTexture *texture, + const char *filename) +{ + GBytes *bytes; + gboolean result; + + g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + bytes = gdk_save_tiff (texture); + result = g_file_set_contents (filename, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + NULL); + g_bytes_unref (bytes); + + return result; +} diff --git a/gdk/gdktexture.h b/gdk/gdktexture.h index 7e7de473d8..8d46702b79 100644 --- a/gdk/gdktexture.h +++ b/gdk/gdktexture.h @@ -93,6 +93,9 @@ gboolean gdk_texture_save_to_png (GdkTexture const char *filename); GDK_AVAILABLE_IN_4_6 GBytes * gdk_texture_save_to_png_bytes (GdkTexture *texture); +GDK_AVAILABLE_IN_4_6 +gboolean gdk_texture_save_to_tiff (GdkTexture *texture, + const char *filename); G_END_DECLS From 729ba8111a5eac146b833c7f5f943c9df583039d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 07:48:49 -0400 Subject: [PATCH 11/39] Add code to load jpegs This lets us avoid gdk-pixbuf for loading textures from the common image formats. As a consequence, we are now linking against libjpeg. --- gdk/loaders/gdkjpeg.c | 146 +++++++++++++++++++++++++++++++++++ gdk/loaders/gdkjpegprivate.h | 29 +++++++ gdk/meson.build | 2 + meson.build | 3 + 4 files changed, 180 insertions(+) create mode 100644 gdk/loaders/gdkjpeg.c create mode 100644 gdk/loaders/gdkjpegprivate.h diff --git a/gdk/loaders/gdkjpeg.c b/gdk/loaders/gdkjpeg.c new file mode 100644 index 0000000000..74a1c9623a --- /dev/null +++ b/gdk/loaders/gdkjpeg.c @@ -0,0 +1,146 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gdkjpegprivate.h" + +#include "gdktexture.h" +#include "gdkmemorytextureprivate.h" +#include +#include +#include + +/* {{{ Error handling */ + +/* No sigsetjmp on Windows */ +#ifndef HAVE_SIGSETJMP +#define sigjmp_buf jmp_buf +#define sigsetjmp(jb, x) setjmp(jb) +#define siglongjmp longjmp +#endif + +struct error_handler_data { + struct jpeg_error_mgr pub; + sigjmp_buf setjmp_buffer; + GError **error; +}; + +static void +fatal_error_handler (j_common_ptr cinfo) +{ + struct error_handler_data *errmgr; + char buffer[JMSG_LENGTH_MAX]; + + errmgr = (struct error_handler_data *) cinfo->err; + + cinfo->err->format_message (cinfo, buffer); + + if (errmgr->error && *errmgr->error == NULL) + g_set_error (errmgr->error, + GDK_TEXTURE_ERROR, + cinfo->err->msg_code == JERR_OUT_OF_MEMORY + ? GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY + : GDK_TEXTURE_ERROR_CORRUPT_IMAGE, + "Error interpreting JPEG image file (%s)", buffer); + + siglongjmp (errmgr->setjmp_buffer, 1); + + g_assert_not_reached (); +} + +static void +output_message_handler (j_common_ptr cinfo) +{ + /* do nothing */ +} + +/* }}} */ + /* {{{ Public API */ + +GdkTexture * +gdk_load_jpeg (GBytes *input_bytes, + GError **error) +{ + struct jpeg_decompress_struct info; + struct error_handler_data jerr; + struct jpeg_error_mgr err; + int width, height; + int size; + unsigned char *data; + unsigned char *row[1]; + GBytes *bytes; + GdkTexture *texture; + + info.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = fatal_error_handler; + jerr.pub.output_message = output_message_handler; + jerr.error = error; + + if (sigsetjmp (jerr.setjmp_buffer, 1)) + { + jpeg_destroy_decompress (&info); + return NULL; + } + + info.err = jpeg_std_error (&err); + jpeg_create_decompress (&info); + + jpeg_mem_src (&info, + g_bytes_get_data (input_bytes, NULL), + g_bytes_get_size (input_bytes)); + + jpeg_read_header (&info, TRUE); + jpeg_start_decompress (&info); + + width = info.output_width; + height = info.output_height; + + size = width * height * 3; + data = g_try_malloc_n (width * 3, height); + if (!data) + { + g_set_error_literal (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + "Not enough memory to load jpeg"); + jpeg_destroy_decompress (&info); + return NULL; + } + + while (info.output_scanline < info.output_height) + { + row[0] = (unsigned char *)(&data[3 *info.output_width * info.output_scanline]); + jpeg_read_scanlines (&info, row, 1); + } + + jpeg_finish_decompress (&info); + jpeg_destroy_decompress (&info); + + bytes = g_bytes_new_take (data, size); + + texture = gdk_memory_texture_new (width, height, + GDK_MEMORY_R8G8B8, + bytes, width * 3); + + g_bytes_unref (bytes); + + return texture; +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gdk/loaders/gdkjpegprivate.h b/gdk/loaders/gdkjpegprivate.h new file mode 100644 index 0000000000..a8e6bd8a82 --- /dev/null +++ b/gdk/loaders/gdkjpegprivate.h @@ -0,0 +1,29 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GDK_JPEG_PRIVATE_H__ +#define __GDK_JPEG_PRIVATE_H__ + +#include "gdkmemorytexture.h" +#include + +#define JPEG_SIGNATURE "\xff\xd8" + +GdkTexture *gdk_load_jpeg (GBytes *bytes, + GError **error); + +#endif diff --git a/gdk/meson.build b/gdk/meson.build index 6ef0ec01fb..06905233f8 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -53,6 +53,7 @@ gdk_public_sources = files([ 'gdkdragsurface.c', 'loaders/gdkpng.c', 'loaders/gdktiff.c', + 'loaders/gdkjpeg.c', ]) gdk_public_headers = files([ @@ -205,6 +206,7 @@ gdk_deps = [ vulkan_dep, png_dep, tiff_dep, + jpeg_dep, ] if profiler_enabled diff --git a/meson.build b/meson.build index 4aa622bbaf..e85daf88f5 100644 --- a/meson.build +++ b/meson.build @@ -406,6 +406,9 @@ png_dep = dependency('libpng', tiff_dep = dependency('libtiff-4', fallback: ['libtiff', 'libtiff4_dep'], required: true) +jpeg_dep = dependency('libjpeg', + fallback: ['libjpeg-turbo', 'jpeg_dep'], + required: true) epoxy_dep = dependency('epoxy', version: epoxy_req, fallback: ['libepoxy', 'libepoxy_dep']) From daf29e174fb28b6eea329fb6765efaedfdaafbad Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 11 Sep 2021 17:02:41 -0400 Subject: [PATCH 12/39] Load jpegs without gdk-pixbuf Use our own loader for jpeg files. --- gdk/gdktexture.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 7d1907be8d..7a8a324e37 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -48,6 +48,7 @@ #include #include "loaders/gdkpngprivate.h" #include "loaders/gdktiffprivate.h" +#include "loaders/gdkjpegprivate.h" G_DEFINE_QUARK (gdk-texture-error-quark, gdk_texture_error) @@ -445,6 +446,11 @@ gdk_texture_new_from_bytes (GBytes *bytes, { return gdk_load_tiff (bytes, error); } + else if (size > strlen (JPEG_SIGNATURE) && + memcmp (data, JPEG_SIGNATURE, strlen (JPEG_SIGNATURE)) == 0) + { + return gdk_load_jpeg (bytes, error); + } else { GInputStream *stream; From e30b4c61cbdeaf537792969babc6b9c767052fc9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 11 Sep 2021 23:28:06 -0400 Subject: [PATCH 13/39] Add tests for the jpeg loader --- testsuite/gdk/image-data/image.jpeg | Bin 0 -> 8397 bytes testsuite/gdk/image.c | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 testsuite/gdk/image-data/image.jpeg diff --git a/testsuite/gdk/image-data/image.jpeg b/testsuite/gdk/image-data/image.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..53ebb349bc1d7a89a583e55eb385fb8f5af7872d GIT binary patch literal 8397 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 W*1i0ivDa>*4i>9A=jfZ^yMG72f6J!; literal 0 HcmV?d00001 diff --git a/testsuite/gdk/image.c b/testsuite/gdk/image.c index ceae44df67..57a0c112c2 100644 --- a/testsuite/gdk/image.c +++ b/testsuite/gdk/image.c @@ -1,6 +1,7 @@ #include #include "gdk/loaders/gdkpngprivate.h" #include "gdk/loaders/gdktiffprivate.h" +#include "gdk/loaders/gdkjpegprivate.h" static void assert_texture_equal (GdkTexture *t1, @@ -50,6 +51,8 @@ test_load_image (gconstpointer data) texture = gdk_load_png (bytes, &error); else if (g_str_has_suffix (filename, ".tiff")) texture = gdk_load_tiff (bytes, &error); + else if (g_str_has_suffix (filename, ".jpeg")) + texture = gdk_load_jpeg (bytes, &error); else g_assert_not_reached (); @@ -118,6 +121,7 @@ main (int argc, char *argv[]) g_test_add_data_func ("/image/load/png", "image.png", test_load_image); g_test_add_data_func ("/image/load/tiff", "image.tiff", test_load_image); + g_test_add_data_func ("/image/load/jpeg", "image.jpeg", test_load_image); g_test_add_data_func ("/image/save/png", "image.png", test_save_image); g_test_add_data_func ("/image/save/tiff", "image.tiff", test_save_image); From d27bc744810680ce12b95455eccdf44e63609979 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 9 Sep 2021 22:23:37 -0400 Subject: [PATCH 14/39] Use our own loaders for content (de)serialization Use our own loader to (de)serialiaze textures to and from png and tiff. We still fall back to gdk-pixbuf for handling all the other image formats, and for pixbufs. --- gdk/gdkcontentdeserializer.c | 109 +++++++++++++++++++++++++----- gdk/gdkcontentserializer.c | 124 ++++++++++++++++++++++++++++++----- 2 files changed, 198 insertions(+), 35 deletions(-) diff --git a/gdk/gdkcontentdeserializer.c b/gdk/gdkcontentdeserializer.c index 4959b5af4d..56a62f7ae3 100644 --- a/gdk/gdkcontentdeserializer.c +++ b/gdk/gdkcontentdeserializer.c @@ -25,6 +25,8 @@ #include "filetransferportalprivate.h" #include "gdktexture.h" #include "gdkrgbaprivate.h" +#include "loaders/gdkpngprivate.h" +#include "loaders/gdktiffprivate.h" #include @@ -655,6 +657,56 @@ pixbuf_deserializer (GdkContentDeserializer *deserializer) deserializer); } +static void +texture_deserializer_finish (GObject *source, + GAsyncResult *result, + gpointer deserializer) +{ + GOutputStream *stream = G_OUTPUT_STREAM (source); + GBytes *bytes; + GError *error = NULL; + GdkTexture *texture = NULL; + gssize written; + + written = g_output_stream_splice_finish (stream, result, &error); + if (written < 0) + { + gdk_content_deserializer_return_error (deserializer, error); + return; + } + + bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream)); + + texture = gdk_texture_new_from_bytes (bytes, &error); + g_bytes_unref (bytes); + if (texture == NULL) + { + gdk_content_deserializer_return_error (deserializer, error); + return; + } + + g_value_take_object (gdk_content_deserializer_get_value (deserializer), texture); + gdk_content_deserializer_return_success (deserializer); +} + +static void +texture_deserializer (GdkContentDeserializer *deserializer) +{ + GOutputStream *output; + + output = g_memory_output_stream_new_resizable (); + + g_output_stream_splice_async (output, + gdk_content_deserializer_get_input_stream (deserializer), + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE + | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + gdk_content_deserializer_get_priority (deserializer), + gdk_content_deserializer_get_cancellable (deserializer), + texture_deserializer_finish, + deserializer); + g_object_unref (output); +} + static void string_deserializer_finish (GObject *source, GAsyncResult *result, @@ -863,48 +915,71 @@ init (void) initialized = TRUE; + gdk_content_register_deserializer ("image/png", + GDK_TYPE_TEXTURE, + texture_deserializer, + NULL, + NULL); + gdk_content_register_deserializer ("image/tiff", + GDK_TYPE_TEXTURE, + texture_deserializer, + NULL, + NULL); + gdk_content_register_deserializer ("image/jpeg", + GDK_TYPE_TEXTURE, + texture_deserializer, + NULL, + NULL); + + formats = gdk_pixbuf_get_formats (); /* Make sure png comes first */ for (f = formats; f; f = f->next) { GdkPixbufFormat *fmt = f->data; - char *name; - + char *name; + name = gdk_pixbuf_format_get_name (fmt); if (g_str_equal (name, "png")) - { - formats = g_slist_delete_link (formats, f); - formats = g_slist_prepend (formats, fmt); + { + formats = g_slist_delete_link (formats, f); + formats = g_slist_prepend (formats, fmt); - g_free (name); - - break; - } + g_free (name); + break; + } g_free (name); - } + } for (f = formats; f; f = f->next) { GdkPixbufFormat *fmt = f->data; char **mimes, **m; + char *name; + name = gdk_pixbuf_format_get_name (fmt); mimes = gdk_pixbuf_format_get_mime_types (fmt); for (m = mimes; *m; m++) - { - gdk_content_register_deserializer (*m, - GDK_TYPE_TEXTURE, - pixbuf_deserializer, - NULL, - NULL); + { + /* Turning pngs, jpegs and tiffs into textures is handled above */ + if (!g_str_equal (name, "png") && + !g_str_equal (name, "jpeg") && + !g_str_equal (name, "tiff")) + gdk_content_register_deserializer (*m, + GDK_TYPE_TEXTURE, + pixbuf_deserializer, + NULL, + NULL); gdk_content_register_deserializer (*m, GDK_TYPE_PIXBUF, pixbuf_deserializer, NULL, NULL); - } + } g_strfreev (mimes); + g_free (name); } g_slist_free (formats); diff --git a/gdk/gdkcontentserializer.c b/gdk/gdkcontentserializer.c index bc9deeb8d5..e0bf34bd53 100644 --- a/gdk/gdkcontentserializer.c +++ b/gdk/gdkcontentserializer.c @@ -26,6 +26,9 @@ #include "filetransferportalprivate.h" #include "gdktextureprivate.h" #include "gdkrgba.h" +#include "loaders/gdkpngprivate.h" +#include "loaders/gdktiffprivate.h" +#include "gdkmemorytextureprivate.h" #include #include @@ -606,6 +609,7 @@ gdk_content_serialize_finish (GAsyncResult *result, /*** SERIALIZERS ***/ + static void pixbuf_serializer_finish (GObject *source, GAsyncResult *res, @@ -658,6 +662,75 @@ pixbuf_serializer (GdkContentSerializer *serializer) g_object_unref (pixbuf); } +static void +texture_serializer_finish (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GdkContentSerializer *serializer = GDK_CONTENT_SERIALIZER (source); + GError *error = NULL; + + if (!g_task_propagate_boolean (G_TASK (res), &error)) + gdk_content_serializer_return_error (serializer, error); + else + gdk_content_serializer_return_success (serializer); +} + +static void +serialize_texture_in_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GdkContentSerializer *serializer = source_object; + const GValue *value; + GdkTexture *texture; + GBytes *bytes = NULL; + GError *error = NULL; + gboolean result = FALSE; + GInputStream *input; + gssize spliced; + + value = gdk_content_serializer_get_value (serializer); + texture = g_value_get_object (value); + + if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/png") == 0) + bytes = gdk_save_png (texture); + else if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/tiff") == 0) + bytes = gdk_save_tiff (texture); + else + g_assert_not_reached (); + + input = g_memory_input_stream_new_from_bytes (bytes); + spliced = g_output_stream_splice (gdk_content_serializer_get_output_stream (serializer), + input, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, + gdk_content_serializer_get_cancellable (serializer), + &error); + g_object_unref (input); + g_bytes_unref (bytes); + + result = spliced != -1; + + if (result) + g_task_return_boolean (task, result); + else + g_task_return_error (task, error); +} + +static void +texture_serializer (GdkContentSerializer *serializer) +{ + GTask *task; + + task = g_task_new (serializer, + gdk_content_serializer_get_cancellable (serializer), + texture_serializer_finish, + NULL); + g_task_run_in_thread (task, serialize_texture_in_thread); + g_object_unref (task); +} + static void string_serializer_finish (GObject *source, GAsyncResult *result, @@ -877,51 +950,66 @@ init (void) initialized = TRUE; + gdk_content_register_serializer (GDK_TYPE_TEXTURE, + "image/png", + texture_serializer, + NULL, NULL); + + gdk_content_register_serializer (GDK_TYPE_TEXTURE, + "image/tiff", + texture_serializer, + NULL, NULL); + formats = gdk_pixbuf_get_formats (); /* Make sure png comes first */ for (f = formats; f; f = f->next) { GdkPixbufFormat *fmt = f->data; - char *name; - + char *name; + name = gdk_pixbuf_format_get_name (fmt); if (g_str_equal (name, "png")) - { - formats = g_slist_delete_link (formats, f); - formats = g_slist_prepend (formats, fmt); + { + formats = g_slist_delete_link (formats, f); + formats = g_slist_prepend (formats, fmt); - g_free (name); - - break; - } + g_free (name); + break; + } g_free (name); - } + } for (f = formats; f; f = f->next) { GdkPixbufFormat *fmt = f->data; char **mimes, **m; + char *name; if (!gdk_pixbuf_format_is_writable (fmt)) - continue; + continue; + name = gdk_pixbuf_format_get_name (fmt); mimes = gdk_pixbuf_format_get_mime_types (fmt); for (m = mimes; *m; m++) - { - gdk_content_register_serializer (GDK_TYPE_TEXTURE, - *m, - pixbuf_serializer, - gdk_pixbuf_format_get_name (fmt), - g_free); + { + /* Turning textures into pngs or tiffs is handled above */ + if (!g_str_equal (name, "png") && + !g_str_equal (name, "tiff")) + gdk_content_register_serializer (GDK_TYPE_TEXTURE, + *m, + pixbuf_serializer, + gdk_pixbuf_format_get_name (fmt), + g_free); gdk_content_register_serializer (GDK_TYPE_PIXBUF, *m, pixbuf_serializer, gdk_pixbuf_format_get_name (fmt), g_free); - } + } g_strfreev (mimes); + g_free (name); } g_slist_free (formats); From 95a0c93839746a8fd0e391c0545dfabec8b08498 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 11 Sep 2021 21:38:31 -0400 Subject: [PATCH 15/39] Add contentserializer tests for textures --- testsuite/gdk/contentserializer.c | 71 ++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/testsuite/gdk/contentserializer.c b/testsuite/gdk/contentserializer.c index d20a60d99e..a807951707 100644 --- a/testsuite/gdk/contentserializer.c +++ b/testsuite/gdk/contentserializer.c @@ -30,6 +30,49 @@ compare_rgba_values (GValue *v1, GValue *v2) (GdkRGBA *)g_value_get_boxed (v2)); } +static gboolean +textures_equal (GdkTexture *t1, GdkTexture *t2) +{ + guchar *d1, *d2; + int width, height; + gboolean ret; + + width = gdk_texture_get_width (t1); + height = gdk_texture_get_height (t1); + + if (width != gdk_texture_get_width (t2)) + return FALSE; + if (height != gdk_texture_get_height (t2)) + return FALSE; + + d1 = g_malloc (width * height * 4); + d2 = g_malloc (width * height * 4); + + gdk_texture_download (t1, d1, width * 4); + gdk_texture_download (t2, d2, width * 4); + + ret = memcmp (d1, d2, width * height * 4) == 0; + + if (!ret) + { + gdk_texture_save_to_png (t1, "texture1.png"); + gdk_texture_save_to_png (t2, "texture2.png"); + } + g_free (d1); + g_free (d2); + + return ret; +} + +static gboolean +compare_texture_values (GValue *v1, GValue *v2) +{ + return G_VALUE_TYPE (v1) == GDK_TYPE_TEXTURE && + G_VALUE_TYPE (v2) == GDK_TYPE_TEXTURE && + textures_equal ((GdkTexture *)g_value_get_object (v1), + (GdkTexture *)g_value_get_object (v2)); +} + static gboolean compare_file_values (GValue *v1, GValue *v2) { @@ -125,7 +168,7 @@ test_content_roundtrip (const GValue *value, TestData data = { 0, }; data.ostream = g_memory_output_stream_new_resizable (); - data.mime_type = g_strdup (mime_type); + data.mime_type = mime_type; g_value_init (&data.value, G_VALUE_TYPE (value)); g_value_copy (value, &data.value); data.compare = compare; @@ -182,6 +225,30 @@ test_content_color (void) g_value_unset (&value); } +static void +test_content_texture (gconstpointer data) +{ + const char *mimetype = data; + GValue value = G_VALUE_INIT; + char *path; + GFile *file; + GdkTexture *texture; + GError *error = NULL; + + path = g_test_build_filename (G_TEST_DIST, "image-data", "image.png", NULL); + file = g_file_new_for_path (path); + texture = gdk_texture_new_from_file (file, &error); + g_assert_no_error (error); + g_object_unref (file); + g_free (path); + + g_value_init (&value, GDK_TYPE_TEXTURE); + g_value_set_object (&value, texture); + test_content_roundtrip (&value, mimetype, compare_texture_values); + g_value_unset (&value); + g_object_unref (texture); +} + static void test_content_file (void) { @@ -406,6 +473,8 @@ main (int argc, char *argv[]) g_test_add_func ("/content/text_plain_utf8", test_content_text_plain_utf8); g_test_add_func ("/content/text_plain", test_content_text_plain); g_test_add_func ("/content/color", test_content_color); + g_test_add_data_func ("/content/texture/png", "image/png", test_content_texture); + g_test_add_data_func ("/content/texture/tiff", "image/tiff", test_content_texture); g_test_add_func ("/content/file", test_content_file); g_test_add_func ("/content/files", test_content_files); g_test_add_func ("/content/custom", test_custom_format); From 5b82cf1145bbf73ce7f8ea96dc18da6eb0d83ded Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 13 Sep 2021 14:59:20 -0400 Subject: [PATCH 16/39] rendernodeparser: Use gdk_texture_save_to_png_bytes Avoid cairo, and use our own api for saving png data. Update affected test output. --- gsk/gskrendernodeparser.c | 13 +- .../gsk/nodeparser/empty-texture.ref.node | 5 +- .../gsk/nodeparser/widgetfactory.ref.node | 513 +++++++++--------- 3 files changed, 261 insertions(+), 270 deletions(-) diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index c4c721e2e3..fe5c55ac1e 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -2699,26 +2699,23 @@ render_node_print (Printer *p, case GSK_TEXTURE_NODE: { GdkTexture *texture = gsk_texture_node_get_texture (node); - cairo_surface_t *surface; - GByteArray *array; + GBytes *bytes; start_node (p, "texture"); append_rect_param (p, "bounds", &node->bounds); - surface = gdk_texture_download_surface (texture); - array = g_byte_array_new (); - cairo_surface_write_to_png_stream (surface, cairo_write_array, array); + bytes = gdk_texture_save_to_png_bytes (texture); _indent (p); g_string_append (p->str, "texture: url(\"data:image/png;base64,"); - b64 = base64_encode_with_linebreaks (array->data, array->len); + b64 = base64_encode_with_linebreaks (g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)); append_escaping_newlines (p->str, b64); g_free (b64); g_string_append (p->str, "\");\n"); end_node (p); - g_byte_array_free (array, TRUE); - cairo_surface_destroy (surface); + g_bytes_unref (bytes); } break; diff --git a/testsuite/gsk/nodeparser/empty-texture.ref.node b/testsuite/gsk/nodeparser/empty-texture.ref.node index 6b2c5ead8a..b8027cb332 100644 --- a/testsuite/gsk/nodeparser/empty-texture.ref.node +++ b/testsuite/gsk/nodeparser/empty-texture.ref.node @@ -1,7 +1,6 @@ texture { bounds: 0 0 50 50; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAAKUlE\ -QVQYlWP8z3DmPwMaYGQwYUQXY0IXwAUGUCGGoxkYGBiweXAoeAYAz44F3e3U1xUAAAAASUVORK5C\ -YII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKUlEQVQYlWP8z3DmPwMaYGQwYUQX\ +Y0IXwAUGUCGGoxkYGBiweXAoeAYAz44F3e3U1xUAAAAASUVORK5CYII=\ "); } diff --git a/testsuite/gsk/nodeparser/widgetfactory.ref.node b/testsuite/gsk/nodeparser/widgetfactory.ref.node index 3c7e8d6415..ac96d76476 100644 --- a/testsuite/gsk/nodeparser/widgetfactory.ref.node +++ b/testsuite/gsk/nodeparser/widgetfactory.ref.node @@ -39,129 +39,129 @@ transform { offset: 0.180392 0.203922 0.211765 0; child: texture { bounds: 1068.6 0 256 256; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAABmJLR0QA/wD/AP+gvaeTAAAbAUlE\ -QVR4nO2deZRfRZXHP+nOvhBIMBDWDAFMwBDWyCDIDoNA2B13FBhAZQTEASIiAZ1RBBf0jA6KolFU\ -8CCIiBoggLJEAwFDWBICISGbIQsJWZpsPX/cbtJp+vf7vaWqbr337uec7+mcpPPqVr2qeu9V3boX\ -DMMwDMMwDMMwDMOoAs3aBhiGB44CHgOOAHYBugGLgI2KNhmGEYhHgdZOehN4EBgPHIhMCoZhlIwT\ -eOfg70qLgQnAyUAvFUsNw3BKN2AKySaAjnoD+KWCvYYRJQcANwF/A7ZStiUNp5F+8LdrlYK9hhEN\ -/YDPAM+x5cAoypOxG/APsk8Ai8KbbBj67AR8A1hG7cFxoZp1yRlL9sHfCswKb7Jh6LEr8pq/lsaD\ -owXYX8fMxDxGvgng6fAmG0Z4dgJ+DKwn3QB5CRioYG8SjiTf4G8F/hrcasMIyNbA9cAasg+S24Jb\ -nYyJ5J8A7gtutWEE5JPkHyStwEcD292I/YBN5K/Xz0Mbbhgh2RM3E8AbwLCwptflTtzU66bQhhtG\ -SLoh3m8uBsujxHGOZCTi3++iTl8KbHsUNGkbYASjFZjs6FrvA8Y5ulYersJdH17q6DqGES3jcPO0\ -bAXWAWPCmr8Fw0m/k1FPHwxrfhzYG0C1eMLhtXogXoIDHF4zDVcC3R1e73WH1zKMKOmLOPW4emq2\ -ArcErYGwE/BWRntraXTQGhiGEo/gduC0IodwQvI9h7a3a6egNTAMJa7F/eBZBuwcyP5huH+LaQX6\ -BLLfMFQ5AveDpxXxxguxpjTBg+1rAthtGFHQi3zuwPV0lWfbR+Fu37+j5nq22zCiYhJ+JoCNwDEe\ -7b7Xk92VPQnochvFKA4PISfoXNOEvKLvB/yzwe82A9shi2/bt6kbcmipCdnj/ycwH3gFOb58ogeb\ -AZZ7um702ARQTR7yeO2hyKnB45BDOk3ACCQS714dNIw43ImhwuHAbAKoJn8HViNhwHxwNHJIpw9w\ -MPHGEWinRdsALWwCqCbrgMeBYz2WcarHa7tmrbYBWpgrcHXx+RlQNCr7BmATQHWxCWAz9gZgVI4n\ -kXRZhpwrqCSxrMIa4dmEbAUO1zYkAkYjC5VPUbHPAZsAqs1wxDW46vQEDgXORxbGn0YWSmOgCfF/\ -WEmFtysNPxyPH8+6omsx8Gl0P5HfhcQ8mN1m0/3K9hglZCB+fOvLosnAvplbNxuHAL+g6xOPvs9a\ -GBVkGvoDLWZtBG7Gb5LUJiRN+eMNbFmPfKoYhjNuRn+QFUGzgfdmbONaDAT+CzmNmNSOucBgx3YY\ -FeVQYAH6g6soWocM2G5ZGrsDuwHfQRb2sthxjwMbjIpzPu7j6lVFDyCnF9NyAHJa0kU0449nKN8w\ -6IdE89UeREXXa8A+Cdq7JzJYn3Jc/uvAtgnKN4y3GYL7jlhlrUCOPXfFrsBX8PuJ9dMaZRvGO9gF\ -eBH9QVM2bQAubGvjJiQi0h24TVpSTz4jMBklYS/klVV7sJRZdyi18UygN4ZRg9HAEvQHiMmfzsAw\ -umA4sBD9Dmrypx+QAzsMFIaJiF/3aGAQcgzXdyDKHYCHCZewwwjPXcCnkInAiJST6Hrmngv8CllA\ -GuG4zEHAszXKNZVDf0K2GI2IaSb5QFyARNM9D9g9R5nd8Rf33xSHHsdfQFfDIR8j+02eC/wM+CQS\ -Qjsp38hRpil+PQVsgxE9TcDzuLvxrwA/Bj5B7TeEM5BIP9qd1ORHz+D4IJAdKPDHWcjesC8WA08g\ -r4OPI9Fi/gIM8Fimocd04CjEBdgZNgH4YyqSIssw8vICEr+xUbq11FiIIT8cgQ1+ww0zkUxLzgc/\ -2ATgi0u0DTBKwRzksNFCXwXYJ4B7dkNmbXOyMvIwBzi87ac37A3APedgg9/Ixzzktd/r4Ad7A3BN\ -M/AqkvPeSMZ6ZItzJrKzsRpY0/Zv/YC+SByDPZG3qx4KNoZkIbKGNDNEYZYd2C3HYYO/EUuQMwqT\ -gEeAl5BJIAk9kIngcGRV/EjKFSBzKXK+P8jgN9yzLeKjre0wEptagN8jvhEun+DNyICZgPhBaNcz\ -j9YgOQGMgtMNuAKJFKPdqbS1EriJbAE00zIYGI88RbXrnVYbgFOdt4ihyjHI3q1259LQWuBa/CbT\ -qMVWwHVtNmi3QxJtAs720hKGOu1n8rU7WUj9kTgyDu9BMT7HrvDVAEYcdAMupvwx+Fva6hkbn0B2\ -FrTbp5bG+Ku6ERMHATPQ73A+NJPwSTTTsB8wC/126ko/8lhvIzL6IIti2p3OpaYgqaxjZxCNk29q\ -6E101koMRU6jHJF6JwL9HbeNT/oD96Pfbp31Hz4rbcTJDsTZGZPqL8gbTdHoA/wV/fbrqMlea2xE\ -S/sCYQv6nTCNnqXYYakGItF1tNuxXZuAHb3W2Iia0cBz6HfEJHqdcrg770JcTkP2GVBx+gA3o98R\ -Gz2pPuCrARQ4mXhiKN7lua5GQfgIsjKs3SG70o0e663Ft9Fv11bknvfyXFejIIwApqHfKTtqNnI0\ -t2z0Q87da7dvKxIDwDAAyfgak8/AWL/VVeV09Nu3FbjKd0WNYrEz+p2yFdmuLDsPod/Od3uvpVEo\ -/h39TtmKBOAoO0ej384LvNfSKBTfQb9TPuG9lvHwKPrtHXSL1YKCxs3B2gYgk1BV+L62AcD+2gYY\ -cdAN/e3AFRTT3TcrfYA30G3zy7zXsgP2BhAvu6B/0OZ2JLJOVViLvkNO0GAqNgHEywhtA4B7tA1Q\ -QLvONgEYALxbufwNyKm5qvEwsFGx/Fqp371gE0C87Kxc/lRkDaBqLAeeVix/F2T7N0iQEJsA4iVE\ -KO16TFEuXxPNuncHfo1kSboPOB+PfcEmgHgZqlz+i8rlaxJD3XsBJyAnROcjPgpXIkfHLaVfBZiO\ -7nbUsf6rGC3Ho+8QVE/zgVuAM4GtPbWBocyr6HayPb3XMF5GoD/Ik2o9Ep5tHBL92N4OSoJ2ViHt\ -NQhNdkB/YGfVYuAOJB9CkcO2VZ6V6HYkbSckTQagP5BdaAOyrXkxsGtXFbXXhXhZi8QE0KI7uvvh\ -mjQjg6dsTEU8He9CYlDaBBAx85FXUS0GICm3q8gA5A2srLQik1yrbQPGyyLl8gcol69J2evenqfS\ -/AAiZo5y+WUfBPUoe6qulvY/2AQQL7OVy+9y0agiDNM2wDNvn/C0CSA++iI54z+lbIf2YSRNyl73\ -t98AumtaYWxBd+Ac4MvEkSYqhuPIWtgEYASjG3AG8FXi6nhjtA1QpOx1b2n8K4ZvmpDUVFPQdxip\ -5URSRT/zrZG6a7e/T70d5t3WAMLTBJyFHPa5BzhQ15yaNAOHaRuhwFFI3cvMvPY/2AQQjl7AhcAs\ -xFd7pK45iThV2wAFTtE2IADzGv+K4Yp+iC/2a+i/+qXVCsqZD7AWfdE/gxFCloo8AIOAa4Al6N/w\ -PPqw64aJmI+i394hdIKrBjPeyXbADejH9Helx902T9RMRr+9Q2iUqwYzNjMIGI+8NmvfYNc60l0z\ -Rctx6LdzKA1sr7SdBszPQODzwCWU14f8IWR1vMw8Arxf2whHrEUW+uYALwOvdNDLdIj2bBNAPo5B\ -VvSrEHnldPSz5vjiTOA32kYk4C3klOj8Dj8Xdvq5AElvlgibAPJxJDBJ24hAzAX2AlZrG+KY/sAL\ -BM7KW4M3kFiQszv8bP/zImRB2YiIfkhQRu1vulD6lptmi4qb0G/XVmCG74oafngS/c4TSpsol6PM\ -SUidtNu1Ffi657oanvgu+p0npJYg6auKzjBgGfrt2a5DvNbW8MaH0O88oTUd2fYsKoOB59Fvx3Yt\ -wtzyC8vO6HcgDT1KMd2E+yLOTdrt11E/9Fpjwzsz0e9EGnqAYsUO3Ap4EP1266wqOFqVmhvQ70Ra\ -moZu+PKkbAc8hX57ddZs7PW/8ByKfkfS1CvEG9cA4CBkoGm3U1ca76/aRiiaEE8s7c6kqXVIMNPY\ -nMvOZ3Mc/Ni0CRjur+pGSH6MfoeKQQ8QR2zDEcT5vd9RD3qrfUJim62LzMlIiK9GrEAyuC5F9tSX\ -IvvRHX8uafu9NcjTay0SyHEtcACyAh8z64BvImsjywOXvQ3yJnIp0DNw2Wk5CfiDpgE2AbijDxL5\ -Z3Wb3kQG8Sokykz7YF/voKz7KEZQh5XA/wLfBl73XNYQZNB/lmLsTEwD9kXeBAwjFQcQjwtrErUA\ -v0eCobp8KjcjJzInIJOudj3T6CMO28GoIHei34mzaDlwN/A5YB/STQg9gdHIm9bvkNNz2vXJoleI\ -JCeHfQIUl72BZ4ikI+VgI7JFNxP5TFqFfD6BvMr3B7ZFFhaHUY6Q3ecAt2obYRSfb6P/NDOl0xTM\ -8ScYQ4FzkW/EOWy+CR/QNMohA5DQT9qd2pRMGyl/2jF1tkXi8z2GNHhXN2IW0FvLQMechX7HNiXT\ -/9W4h0ZOmti8GryGZDfjahVL/fBH9Du3qbH2qnUDjeychMRMS3sz1gD/Et5cL+xE8ROQVEF31LqB\ -RnbGk/2G3B3eXG/EFN7KVFtn1LqBGpRhNbI1x/89BTjRlSHK3It9YxaBHwDv0jainTJMAJty/v+b\ -iGNBsBvydOiT4xqfB/7hxhzDE+8CbtQ2okxcTf7Xsi8Ft3pL+rPZs+814BM5rrUnth5QBJVlK1qd\ -q8h/M1YjXmYa7I4E2exs0++A7TNecwzF842vmhYgwUmNnHwRNzdEI+3V8dQPTb2E7Om5TwY21Lm2\ -SV+/rXn3jMSMw90NCfladj7JswpNQLIQpeXChNc36SnrBG+0cQXubsZMoJdne3sDv8hg23RgZMqy\ -9slQjimsliOh5Y2MXI7bGzLOo61Dgck5bHsTebVPyvdzlGUKp/uxk7mZuQy3N2MVfhYED8TNwZ0N\ -wEUJyuuHRCTS7tymZPpM17fRaMSluL8Zf8Wtj8RZuF2VfyZBmec5LM/kX6uRLdygmCNQ1xxKsqds\ -I7ohaxS34zaN1s8S/M4FDssz/NMX+CnlCHgSlIvwNyPnOb21FbLN49qu9UiWm3rs76FcUxhd0cX9\ -NOrwGfzdjFeQ+AJpORB4yZNN9yYo/6eeyjb5Vwuye2Mk5AL83pCJJI+71wx8Ab+ZaD7YwIbtkU6k\ -3ZFN2TWV4sd6DIbPN4B2/Z7GjjgHAU96tmM5jQ8uXRugPaqmJwmfWPTyzjfW6BrXfgC19He6DiCy\ -B3ALtcOPuVSj4769gEWB2qMKWoaEIG9GnshXINmZQpS9BssbmIivEK5DtAC/QjrC9cDjhA3CcUiD\ -trCtPzfaiLhfd3Vuf3fg4UB2TMIchBpyE/odJoRmUL8zdEPSTWnbWWStQ+L1N3K5bkICz4Y4cZnn\ -aHgluAv9jhNCX2zQDsdGYGNRtQrJsZDWJ38P4AnPtiVx+qo0z6LfgXxrI407Z4jIwLeTLQBrrHoG\ -eZLnOZffHQkos86DfUuwSMJ1aSJ5GPAia2KDdtgbv2sRm5BwY+1tfgwS4dZHp/etZcDNiLenS0Yh\ -E4orO1fTeM2n8uyHfocKoUaZZH/ksewW4Mwa5Q5FnqAP4Nf3Ia/mAN8Djsbv/npv4Fvk3xFqQSZZ\ -owHXoN+5fOsN6gcKHYK/rak3gCPrlN2RrZCgpj9BPCi1260V+AOSSj00RyAJT7PYvIHaE67RgT5I\ -AE3tTuZbP2zQDtd4KnchsG+DsuuxLRJh6RrEfXk24UOUnZfD/rwMQPxD0ti7CV2bc3Mism3xfmTR\ -yufJpu+iPzhDqN4TuAcw30OZ85B03K7p2XbdE4HPIavvPtvuaA91SMuJyGTayNYNSCLboLh0MhgM\ -vAwM7PB365Cn9KvIE2BOm+a2aX7b76RhJHAd+V+TViEOHVORcFszgKXIItF6ZAbv16YRyCLPPsC/\ -Iqm4QrAU8e3fUOPfz8J9uqnXgKOQBKq+uRM43eP1hyOfI9oMRrYZP17j3zcAZwO/DGZRGy4ngOtJ\ -77+8CXFdnYN0vMVI2KsVbeqHLNpsjQzCA8gXP20l4sn3a8SLL+3kA9JmByEd90z8umv+DPhknX+f\ -RPJv9CS8igz+2Q6vWY+PAT/3dO0NyBn79Z6un4VjEXfu3Tr83RrgQ8h5k8IyhLjj0L+KnBrMElm3\ -Hk3Aacg5AR92n1an7JG43fqbTfhkqVvjb/cg1CSWlj5IPst1yBve+1StccR49Ad5V1qKhMbu6a3m\ -mzkWeN6x/UPqlOdyDeRFYMe8DZCRiQltTKtJISuRgf0RT8LC0wd5ddce7J11G/UHkA96IesTLhxk\ -Gn2DP+agjFbgObJnIHLBp2vYlVcTQlaiypyL/mDvqNXIgoomo8m/F35bgzL6IYtGecqYRvhJsjM7\ -4MeL8eshK1FlfB+GSKN5xBNOaQiy0Ji1LtclLOcisjkCPU22cGc+mIr7vnBx0BpUlL3RH/TtmkX4\ -RaxG9AZ+Q7b6XJKinL2RAZ302k8Cg3LVzC3/g/v+cFbQGlSUb6A/8FuRbUStRaxG9AD+TPo6XZmy\ -nJ7IQGq0/vAQ4rYbE4fhvk+UYnU9dmahP/iXEf+Ryf7AFNLVK+si1ihqf3rcQ/1zBVp0R+IduuwX\ -sb0Nlo7R6A/+TcAJvivqiO1IF69vBV2HpEpCE/BZtkwNditxR5q9A7d9o1HwVCMnpxI2Hl5XusF7\ -Ld0ylnT1exB5e8jKjkhykhuIP7bcp3DXL5YGtr2ynIJeAsoXCOPg45pbSVfP6cDBKpaGZSjuHijT\ -AtteaUYggzH0BHBciMp5YCAS6ilNXTch3+8foNy549LsZNTTn0IbXnW2Au4m3OBPkh4rZsaRve4L\ -gBuRI9c9QhvuGVfbgT8Jbbgh35hfJkyCjKJv8QwAXid/O6xEJt7/BMZQzE+ijrjaDvxqaMONzZyM\ -33WBR8NVxStfwn3btCBbgN8BPkzxtsJcbQd+NrThxpbshb/suOcErIdPdiXMLso/kfWDK5HPhr4h\ -KpeDrJ6THXVKcKuNdzAIuB+3nXktW0YcKjp5zgpk1QbkBODNSPi2vb3XMh0utgNHBbfa6JLuuD23\ -XvTFv85cTPgJoCtt47uiKRiMRPHJU588vhOGB87DTeSXy0Ib7plYDlO913dFUzKJ7HVZpGBvYWkK\ -VM4twPHIqnUe/uLAlph4EYlfoM2e2gZ04q4c/zeGIKCFIdQEABKB9yhk+ysLrUjIrTKxkTiSP+6u\ -bUAnfovc7yzYBJCCkBMAwFPA4WR7TZtHHE9L18TgthpbbLr5yOnJJKxCHgwTETfr4KG1i4zG6bAX\ -kGQJDyMOMUmZ48UafZZoG0C4PAdpGIeEX29Bdn/WIQ+AjYifySLkobBCy0AjH8eRLnjmH3TM9M6l\ -6C8Cvuy9lkaUhP4E6MhE4Jspfn+VL0OUWa5tAPFFCDICoTkBgOQTeFHZBm1iOKfvOmGKURC0J4C3\ -kLiCSSirc0cMTjhlXFw1EqA9AQD8MeHvlfU1dWttA5DUaUYFiWECSPptP8ynEYrEEJs/RCZgI0Ji\ -mABGJ/y9HSnnt2rS+vvieeB2ZRuMCjOB5NtVY5Rs9EUz8gYUettvGhK8JfZw6kbJeQ/pIgh9QcdM\ -b7yHcIP+OWTXZWSIihlGI5pJf+qrbM5Al+BvwG8CJgOXA8NDVcgwkvLfpO/Ua4lj1dwVk3E76Dci\ -IdOuIL4DPobxNieRPXjouQr2+mAY7kKCPQZcgGQfMoyoOZh8C1+PhTfZC1eTb9C/jmT8sW96ozDs\ -jaRtyvvEOzS04Y7pDywmW90XI4t5ZXWMMkrKSOSct4tX3qTeg7GSJTHIW8jAjz2qr2G8g9FIeGoX\ -g79d/xa0Bu7IkhpsAeXzgTAqwhhgGW4HfyswA+gVsB6uuJV09VyF+AsYRuE4DL9Zgr4VripOOIX0\ -dfyaiqWGkZPjkGOmvgZ/K7KNdmKoCuVke7J9Br1fw1jDyMNYJJabz8HfruXE/4o8AAmImqV+sWXu\ -MYy6fJB0sf5caC5xBrYEydg7kex1K/qWp1EhzkZyz4Uc/O16GdjNfxVT0Ru4k3z1Oj+41YaRgQvI\ -7t7rSguA/XxXNCHbAU+Qv06/CG24YaTlUsKkuk6iNeinEN8PmI2b+ixHPiMMI0q+iP6g70q/Rlbe\ -Q9IL+Cru10BOCFkJw0iDRo77pFoGXEQYh6HjkaxHPurx8wD2G0YmzkR/oDfSXODTuA8t3tRW/yc9\ -298CDHFsu2E4oRnJyKo9yJNoJZKq/BiyvxU0AYcAN+LuOz+Jrspor2HUxFVWmnORgVUkVgOPAE8D\ -zwIvIWfslyBbmf0RB56+SPDMUW06BBiqYO88ZJtzvULZhlGXZmA6+k/4suu8pDfEMEKT5aCLKZ1e\ -BnokvSGG0Yhmh9eagRz93cPhNY0t2QZZb3lG2xCjHLjOTLsHknSit4NrvYV8l89AIgmtRI4Wd0e+\ -wYcBR1C90FizkOhKG7QNMYyuGE/6V9t1wN+A7wIfRSaSJG8n/ZDAmNqv5qH18QRtYxgq9ETeAup1\ -4NcQT71LkVX1vG8M1zcor2x6Ebefb4bhlP3Z7A67Adlq+x7wEWBnD+X1Qg4CaQ/MkPqwk5YzDE+M\ -RaICDQhUXpZIu0XWdOLI7mwYUTAIcZLRHpghdYaTljOMkvAo4QfhOsQleKFC2VNxv5NjGIXlWsIN\ -vtXAZWz5ibM94hYd8oTkSTnbzDBKw5GEGXQrkRyH9TgMuBf/gVImp20kwygrgwjz5D8shU2HAFM8\ -23RsCnsMo9S4yj9YS1mCdDYhYcpcp0dr1yMZbDKMUvJn/A3+3+W0bVvgNg92XZPTLsMoDd/Hz+Bf\ -hruoPGNx96byZUc2GUYpuAo/E8DnHNs5ELiZfIuENvgNoxNn437wv4C/c/jHA3My2HS1J3sMo9Ac\ -jfsJYKxnm7dCPl2SvA1sAi73bI9hFJZ343bwh/S2ex/1Q4u3IMelDcOoQT/cTgCnhDWfPkiMg845\ -FhcgPgWGYTRgGW4G/zPo+drvy+aU4pPQiURsGIWkUUCSpNI+bdcTOB079msYqfgT+Qf/c9jAM0pO\ -WTv4fAfXuA5ZcTeM0lLWCWBBzv//AvAbF4YYRszYBNA1X8ee/oZRWPJkKZqDZd8xKkJZ3wDyrAF8\ -DUvAaRiFZgeyPf0XIo44hmEUmGbgViRYZ5oJ4DINYw1DiypElB2GxO8bDYxCzgrsyDuf9EvbfndV\ -QNsMQ5UqTAC1GIy41+7S9nMhcJ+qRYZhGIZhGIZhGIZhGIZhGIZhGIZhGIZhGIZhGIZhGIZhGIZh\ -GIZhGIZhGIZhGIYh/D/GS6zNWk3S1gAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,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\ +Wk3S1gAAAABJRU5ErkJggg==\ "); } } @@ -277,8 +277,8 @@ GIZhGIZhGIZhGIYh/D/GS6zNWk3S1gAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 4 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAH0lE\ -QVQ4jWNgGPKAEY3/n1R9TNRzy0CB0TAYDQOqAADB/QMKPEUKLAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAH0lEQVQ4jWNgGPKAEY3/n1R9TNRz\ +y0CB0TAYDQOqAADB/QMKPEUKLAAAAABJRU5ErkJggg==\ "); } } @@ -294,10 +294,10 @@ QVQ4jWNgGPKAEY3/n1R9TNRzy0CB0TAYDQOqAADB/QMKPEUKLAAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAfUlE\ -QVQ4je3RMQoCMRBG4Q+voEIKC/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1Fti\ -H2d/uUTwHJIVuuj1LYINbklyivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8\ -g/m2YvyJmYoPk2IlW2Wofj0AAAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfUlEQVQ4je3RMQoCMRBG4Q+voEIK\ +C/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1FtiH2d/uUTwHJIVuuj1LYINbkly\ +ivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8g/m2YvyJmYoPk2IlW2Wofj0A\ +AAAASUVORK5CYII=\ "); } } @@ -373,9 +373,9 @@ transform { offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -430,9 +430,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -473,11 +473,11 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.654902 0.666667 0.666667 0; child: texture { bounds: 0 8 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAyklE\ -QVQ4jc3S3UpCURAF4K8w8ypQA1EfpkcI8SmEEh9STYzQQNCE3sELiQiiLhy1I/ucbl0wDHt+Fmv2\ -DOeGCh4xxic2eIrYVdSM8JNqbmMeyZS9oPnnnUEZr5FY4h63aKCDdeSe8wgGEXxDNaGujvcTRRns\ -mTuJ5v3Mp5bBRwQbCYK8PwGl8N/hvxIEF4nYAZfhF+HvioqL8BCyVqgl8jVM5ewfrjGLgjW6uAnr\ -Oq5xWqSi5XgLeYfU+m+UMvqYYGt3ykP0QuUZ4hei9keg1mtPrwAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAyklEQVQ4jc3S3UpCURAF4K8w8ypQ\ +A1EfpkcI8SmEEh9STYzQQNCE3sELiQiiLhy1I/ucbl0wDHt+Fmv2DOeGCh4xxic2eIrYVdSM8JNq\ +bmMeyZS9oPnnnUEZr5FY4h63aKCDdeSe8wgGEXxDNaGujvcTRRnsmTuJ5v3Mp5bBRwQbCYK8PwGl\ +8N/hvxIEF4nYAZfhF+HvioqL8BCyVqgl8jVM5ewfrjGLgjW6uAnrOq5xWqSi5XgLeYfU+m+UMvqY\ +YGt3ykP0QuUZ4hei9keg1mtPrwAAAABJRU5ErkJggg==\ "); } } @@ -557,10 +557,10 @@ Oq5xWqSi5XgLeYfU+m+UMvqYYGt3ykP0QuUZ4hei9keg1mtPrwAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 4 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAfUlE\ -QVQ4je3RMQoCMRBG4Q+voEIKC/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1Fti\ -H2d/uUTwHJIVuuj1LYINbklyivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8\ -g/m2YvyJmYoPk2IlW2Wofj0AAAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfUlEQVQ4je3RMQoCMRBG4Q+voEIK\ +C/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1FtiH2d/uUTwHJIVuuj1LYINbkly\ +ivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8g/m2YvyJmYoPk2IlW2Wofj0A\ +AAAASUVORK5CYII=\ "); } } @@ -600,9 +600,9 @@ g/m2YvyJmYoPk2IlW2Wofj0AAAAASUVORK5CYII=\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -638,9 +638,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -679,9 +679,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -747,8 +747,8 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.615686 0.623529 0.623529 0; child: texture { bounds: 4 8 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAHElE\ -QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4jWNgGAXDADCi8f+Tqo+J\ +em4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\ "); } } @@ -767,9 +767,8 @@ QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\ offset: 0.615686 0.623529 0.623529 0; child: texture { bounds: 4 8 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAKElE\ -QVQ4jWNgGPbgPxTjBEyU2jDwBjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJg\ -gg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKElEQVQ4jWNgGPbgPxTjBEyU2jDw\ +BjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJggg==\ "); } } @@ -819,8 +818,8 @@ gg==\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 4 8 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAHElE\ -QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4jWNgGAXDADCi8f+Tqo+J\ +em4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\ "); } } @@ -846,9 +845,8 @@ QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 4 8 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAKElE\ -QVQ4jWNgGPbgPxTjBEyU2jDwBjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJg\ -gg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKElEQVQ4jWNgGPbgPxTjBEyU2jDw\ +BjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJggg==\ "); } } @@ -881,10 +879,10 @@ gg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\ -QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\ -YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\ -i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\ +XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\ +OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\ +TkSuQmCC\ "); } } @@ -947,9 +945,9 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\ -QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\ -GmjkhFgCAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\ +/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\ +\ "); } } @@ -985,10 +983,10 @@ GmjkhFgCAAAAAElFTkSuQmCC\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\ -QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\ -YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\ -i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\ +XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\ +OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\ +TkSuQmCC\ "); } } @@ -1051,9 +1049,9 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\ -QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\ -GmjkhFgCAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\ +/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\ +\ "); } } @@ -1152,9 +1150,9 @@ GmjkhFgCAAAAAElFTkSuQmCC\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\ -QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\ -GmjkhFgCAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\ +/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\ +\ "); } } @@ -1193,9 +1191,9 @@ GmjkhFgCAAAAAElFTkSuQmCC\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAASklE\ -QVQokWNgGJmAjYGBoZWBgeERFLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwD\ -sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAASklEQVQokWNgGJmAjYGBoZWBgeER\ +FLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwDsDJA/PQQihsZiAyc4QYASeYT\ +s7b/ALUAAAAASUVORK5CYII=\ "); } } @@ -1264,9 +1262,9 @@ sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 0 0 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\ -QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\ -GmjkhFgCAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\ +/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\ +\ "); } } @@ -1292,13 +1290,12 @@ GmjkhFgCAAAAAElFTkSuQmCC\ transform: translate(8, 11.5) rotate(12.2788) translate(-8, -11.5); child: texture { bounds: 0 3.5 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABE0lE\ -QVQ4jbXSPy9DURjH8U9bSqhEmog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HOb\ -G2muRuJJbnJynvP9nt9zc/iHKuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T\ -8B3mMs6OoYh8evMSnRDsZcCFkJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBG\ -hqAcgtlI0hecxjgNHKRuSVcRKyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv\ -+AhhHRfR6yeg9+4rWE0J8sjhLQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAA\ -SUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jbXSPy9DURjH8U9bSqhE\ +mog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HObG2muRuJJbnJynvP9nt9zc/iH\ +KuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T8B3mMs6OoYh8evMSnRDsZcCF\ +kJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBGhqAcgtlI0hecxjgNHKRuSVcR\ +KyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv+AhhHRfR6yeg9+4rWE0J8sjh\ +LQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAASUVORK5CYII=\ "); } } @@ -1315,13 +1312,12 @@ SUVORK5CYII=\ transform: translate(8, 11.5) rotate(12.2788) translate(-8, -11.5); child: texture { bounds: 0 3.5 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABE0lE\ -QVQ4jbXSPy9DURjH8U9bSqhEmog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HOb\ -G2muRuJJbnJynvP9nt9zc/iHKuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T\ -8B3mMs6OoYh8evMSnRDsZcCFkJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBG\ -hqAcgtlI0hecxjgNHKRuSVcRKyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv\ -+AhhHRfR6yeg9+4rWE0J8sjhLQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAA\ -SUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jbXSPy9DURjH8U9bSqhE\ +mog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HObG2muRuJJbnJynvP9nt9zc/iH\ +KuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T8B3mMs6OoYh8evMSnRDsZcCF\ +kJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBGhqAcgtlI0hecxjgNHKRuSVcR\ +KyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv+AhhHRfR6yeg9+4rWE0J8sjh\ +LQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAASUVORK5CYII=\ "); } } @@ -1459,9 +1455,9 @@ SUVORK5CYII=\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -1500,9 +1496,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.831373 0.811765 0.792157 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\ -QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\ -ZRnQomjWaAAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\ +KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\ +gg==\ "); } } @@ -1615,10 +1611,10 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 0 4 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAeUlE\ -QVQ4jcWTQQqAIBBFn9HV6i4uOmaL7hF1hVbZRkF0tMmCPnwYdeahH4RcM+AEH4AV+jNJw8EnMGkB\ -pf0MsiRDd4AAsdKBFhAywSTN8doIkBgGYLpKk0qvAVB+s0q9AHuk/zP4BLD7uiXIFWD0Re0TSd6A\ -of3uXhd1yT8mvMGiQgAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAeUlEQVQ4jcWTQQqAIBBFn9HV6i4u\ +OmaL7hF1hVbZRkF0tMmCPnwYdeahH4RcM+AEH4AV+jNJw8EnMGkBpf0MsiRDd4AAsdKBFhAywSTN\ +8doIkBgGYLpKk0qvAVB+s0q9AHuk/zP4BLD7uiXIFWD0Re0TSd6Aof3uXhd1yT8mvMGiQgAAAABJ\ +RU5ErkJggg==\ "); } } @@ -2087,19 +2083,19 @@ of3uXhd1yT8mvMGiQgAAAABJRU5ErkJggg==\ transform: translate(225, -10); child: texture { bounds: 1 1 20 25; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAABmJLR0QA/wD/AP+gvaeTAAAChklE\ -QVQ4jbXVTWsTQRgH8P88u2myu4ZEKVJqEj9Ar0V6kIQtPRWs1EpAPfegiFD8Av0C4sVbz1qt2IMe\ -fUmTXhTzKbKbEhRslrCzsyYz46Ev1jRNX4L/47Dz22eGmXkYBqReryc6nfaSaZoPtMYNpdRlADAM\ -7CrFvgHypeNkN6enp7v9c1n/QKVSuUuEF6lUys5mM2nLcpBImACAbrcLzjmCIOjEsQgB/bhYnH03\ -ENR6lba33WdEbHly8ppj2/ag4g8TRRzNph9KqdaKxepTxlbVP2C1WnmeTCaXC4WCQ2QMxQ6ilESj\ -0QjjOF4rldwVACAAqNW+LBkGnQsDACID+XzBMQxa3tr6vAgArF6vJzjvNHK5wsRpyzwpnIfwfX9H\ -Sn2dwrB9J5lMnrpnw2LbDsbGxtKMqUVizLyfyWTTF9b2k8lk04Zh3CPG1Mwo1R3EcRxojRnSGlnT\ -TIwMmqYJpeQVUgqMHTveFwtjDEREu91ub2Ss1+uBiLUJYN+jiI8Mcs6hNX0lrbvrQRB0RgWDoN1R\ -qveGxsd/vRVCRKNUyXmEOBaC83iTpqbKv7VWK81mM1RKnhuTUmJnxwsB+WR+fj4mACiVZl8pJdd9\ -3+eAPgen0Wz6XCn5qlicew3sPw57f8JDIcSW53nh2VANz2tEcSyqUrJHB6OHoOu6vVbrx0IU8U+N\ -RoNrPQzV8H2PCxFVLevSbdd1D88dHf2sXC7LVuvnkhDRR887Cd3DOOfblpVe6G8DA+/IxsaGMTFx\ -dTOVSs3l8wX771XS8DwviiJes+30rTP1lH7UslJzuVzBBgDfH44NBYG97sd554NtOze11iSEqFmW\ -cyJ2KngEfQ8Atn18z/57/gBelEdqMNUvpgAAAABJRU5ErkJggg==\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAChklEQVQ4jbXVTWsTQRgH8P88u2my\ +u4ZEKVJqEj9Ar0V6kIQtPRWs1EpAPfegiFD8Av0C4sVbz1qt2IMefUmTXhTzKbKbEhRslrCzsyYz\ +46Ev1jRNX4L/47Dz22eGmXkYBqReryc6nfaSaZoPtMYNpdRlADAM7CrFvgHypeNkN6enp7v9c1n/\ +QKVSuUuEF6lUys5mM2nLcpBImACAbrcLzjmCIOjEsQgB/bhYnH03ENR6lba33WdEbHly8ppj2/ag\ +4g8TRRzNph9KqdaKxepTxlbVP2C1WnmeTCaXC4WCQ2QMxQ6ilESj0QjjOF4rldwVACAAqNW+LBkG\ +nQsDACID+XzBMQxa3tr6vAgArF6vJzjvNHK5wsRpyzwpnIfwfX9HSn2dwrB9J5lMnrpnw2LbDsbG\ +xtKMqUVizLyfyWTTF9b2k8lk04Zh3CPG1Mwo1R3EcRxojRnSGlnTTIwMmqYJpeQVUgqMHTveFwtj\ +DEREu91ub2Ss1+uBiLUJYN+jiI8Mcs6hNX0lrbvrQRB0RgWDoN1RqveGxsd/vRVCRKNUyXmEOBaC\ +83iTpqbKv7VWK81mM1RKnhuTUmJnxwsB+WR+fj4mACiVZl8pJdd93+eAPgen0Wz6XCn5qlicew3s\ +Pw57f8JDIcSW53nh2VANz2tEcSyqUrJHB6OHoOu6vVbrx0IU8U+NRoNrPQzV8H2PCxFVLevSbdd1\ +D88dHf2sXC7LVuvnkhDRR887Cd3DOOfblpVe6G8DA+/IxsaGMTFxdTOVSs3l8wX771XS8DwviiJe\ +s+30rTP1lH7UslJzuVzBBgDfH44NBYG97sd554NtOze11iSEqFmWcyJ2KngEfQ8Atn18z/57/gBe\ +lEdqMNUvpgAAAABJRU5ErkJggg==\ "); } } @@ -2442,10 +2438,10 @@ cyJ2KngEfQ8Atn18z/57/gBelEdqMNUvpgAAAABJRU5ErkJggg==\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 14 29 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\ -QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\ -YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\ -i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\ +XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\ +OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\ +TkSuQmCC\ "); } } @@ -2459,12 +2455,12 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ offset: 0.196078 0.196078 0.196078 0; child: texture { bounds: 54 28 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAA8klE\ -QVQ4jbXSwSrEURTH8Y/YsZiUhfIGnkFJUrKSlIeQJ5AaUlPWdrKUJ9DMwhMQyZKVhSxEg7Lwz1i4\ -f925c/9jSk7dbuee8/3dczqHf7BxbOEcL3jFFeqY/A1exBM6FecZq1XwPD76wOUpMJvCY3gYAO7g\ -CCOpwMaA8CGGc+W3Msmnib+PoYjpErpNkhvhfTf4ewlcw0kscBPBx0l1S4k/hWvf4/2xZiRQYC3X\ -J6ZxF/Iu48B60kJOZEb3jmzHwVHc9xFZxnsUa2MiLW9O7yIVOAh3+faJlYoWLeBR70jjnyvh0mrY\ -xFkA3nCBnVzZf7Yvt2xyJ4TFGjYAAAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA8klEQVQ4jbXSwSrEURTH8Y/YsZiU\ +hfIGnkFJUrKSlIeQJ5AaUlPWdrKUJ9DMwhMQyZKVhSxEg7Lwz1i4f925c/9jSk7dbuee8/3dczqH\ +f7BxbOEcL3jFFeqY/A1exBM6FecZq1XwPD76wOUpMJvCY3gYAO7gCCOpwMaA8CGGc+W3Msmnib+P\ +oYjpErpNkhvhfTf4ewlcw0kscBPBx0l1S4k/hWvf4/2xZiRQYC3XJ6ZxF/Iu48B60kJOZEb3jmzH\ +wVHc9xFZxnsUa2MiLW9O7yIVOAh3+faJlYoWLeBR70jjnyvh0mrYxFkA3nCBnVzZf7Yvt2xyJ4TF\ +GjYAAAAASUVORK5CYII=\ "); } } @@ -2520,11 +2516,11 @@ xFkA3nCBnVzZf7Yvt2xyJ4TFGjYAAAAASUVORK5CYII=\ offset: 0.196078 0.196078 0.196078 0; child: texture { bounds: 54 51 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAz0lE\ -QVQ4jbXSTU4CQRAF4A/iFlbKOVywMoRDGLyAe70D4o3kBqz9uQAXICxwcM2wmAY6Y3djjL6kku7q\ -qtevfvgHDPCMd3wFe8MsvBVxhwp1xipMSsm7VsIYw5ZvlyIZZH4+oO3/xBV0Q8Ajeufqi9DHQ+z4\ -SPxSUlBrGnvE9hcEVVxCHPxT1HARLktcJ4JG6GQIljHBPEOwKCh4iS+5Md4EFW3/BpecerDCve+9\ -6CZKqEPsOiVrolmS3CpvcFsoC82GPeFVM95tOE8Psv8Ue0ISW4s5Tmr/AAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAz0lEQVQ4jbXSTU4CQRAF4A/iFlbK\ +OVywMoRDGLyAe70D4o3kBqz9uQAXICxwcM2wmAY6Y3djjL6kku7qqtevfvgHDPCMd3wFe8MsvBVx\ +hwp1xipMSsm7VsIYw5ZvlyIZZH4+oO3/xBV0Q8Ajeufqi9DHQ+z4SPxSUlBrGnvE9hcEVVxCHPxT\ +1HARLktcJ4JG6GQIljHBPEOwKCh4iS+5Md4EFW3/BpecerDCve+96CZKqEPsOiVrolmS3CpvcFso\ +C82GPeFVM95tOE8Psv8Ue0ISW4s5Tmr/AAAAAElFTkSuQmCC\ "); } } @@ -2575,10 +2571,10 @@ SPxSUlBrGnvE9hcEVVxCHPxT1HARLktcJ4JG6GQIljHBPEOwKCh4iS+5Md4EFW3/BpecerDCve+9\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 14 75 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\ -QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\ -YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\ -i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\ +XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\ +OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\ +TkSuQmCC\ "); } } @@ -2592,12 +2588,12 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\ offset: 0.196078 0.196078 0.196078 0; child: texture { bounds: 54 74 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAA/klE\ -QVQ4jaXTzypFURTH8Q9CN2FyXYmRjqmpiZkykoF0n+J6Bxl4CQ/gASTlAZjwApIRMhDiSkd0Dc6W\ -3bHPPcmv1mCv/Vvf/W9t/qmBRG4aHYzhNORecIML5LF5qFTcxjFWcYQnDKKBFjI8hyBMxsX7mAzj\ -u8TuhrGMuTJgBnsl4FsCQHHsJYzGgC1MJIxVGsFCDFhLmMrAsmZjQJYwzNcAxmPAZ8KwXgPoxYCr\ -hCHDYh9ANwYcVJg6aFbMXfNz0y1cCucq6QG7uI1yOQ6Rf3fiq6JNN/1+vgZW8BFWfccJHlPb2lC0\ -b68i7hVN1FdN7OBc8Ym6OMM2puqK/6wvIccweWvwsr0AAAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/klEQVQ4jaXTzypFURTH8Q9CN2Fy\ +XYmRjqmpiZkykoF0n+J6Bxl4CQ/gASTlAZjwApIRMhDiSkd0Dc6W3bHPPcmv1mCv/Vvf/W9t/qmB\ +RG4aHYzhNORecIML5LF5qFTcxjFWcYQnDKKBFjI8hyBMxsX7mAzju8TuhrGMuTJgBnsl4FsCQHHs\ +JYzGgC1MJIxVGsFCDFhLmMrAsmZjQJYwzNcAxmPAZ8KwXgPoxYCrhCHDYh9ANwYcVJg6aFbMXfNz\ +0y1cCucq6QG7uI1yOQ6Rf3fiq6JNN/1+vgZW8BFWfccJHlPb2lC0b68i7hVN1FdN7OBc8Ym6OMM2\ +puqK/6wvIccweWvwsr0AAAAASUVORK5CYII=\ "); } } @@ -2659,9 +2655,9 @@ b68i7hVN1FdN7OBc8Ym6OMM2puqK/6wvIccweWvwsr0AAAAASUVORK5CYII=\ offset: 0.572549 0.584314 0.584314 0; child: texture { bounds: 14 98 14 14; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAASklE\ -QVQokWNgGJmAjYGBoZWBgeERFLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwD\ -sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAASklEQVQokWNgGJmAjYGBoZWBgeER\ +FLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwDsDJA/PQQihsZiAyc4QYASeYT\ +s7b/ALUAAAAASUVORK5CYII=\ "); } } @@ -2675,14 +2671,13 @@ sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\ offset: 0.196078 0.196078 0.196078 0; child: texture { bounds: 54 97 16 16; - texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABT0lE\ -QVQ4jZXTv0vXcRDH8cdXzQQTEZFCUVAU1xZ/LCIYrrVkoTi6NLTpX+DoGrTnkoMgqOjk8hXBhqbA\ -MALTSB1EpDQp/DF874Nvv4hfPTg4uHu+7n13n0+52+01/mAYu/hdov6atWIBS+i4D1iHSXzBRfgn\ -vMWDUnAnfiZgsa+h+iawEhX4fAuc+TuUBwNe4BgHSdEpevG/KL7ACX4E8xw20Y+xRGAzxL8XxVn+\ -FbqwAUeowkBS8Bft+FcUZ/mnMcZuDrPIxUzdmMMytvAL52hAE/owGq86REUOtZjAXixxCD1oxuMQ\ -2MdOXOFj7KQRU9ki67F4hwtkPoMaia1EYv0O8CrOYnRlCl9Xfwh9xXgInSYNjpHHG3wLbjB9wXzS\ -IY8RtMVodWhR+LHySd2HVOARpmNhpUY4w3s8pHC+1JrxEs+i65MQ3cO2qytsZcAlA5qEWoLbkBcA\ -AAAASUVORK5CYII=\ + texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABT0lEQVQ4jZXTv0vXcRDH8cdXzQQT\ +EZFCUVAU1xZ/LCIYrrVkoTi6NLTpX+DoGrTnkoMgqOjk8hXBhqbAMALTSB1EpDQp/DF874Nvv4hf\ +PTg4uHu+7n13n0+52+01/mAYu/hdov6atWIBS+i4D1iHSXzBRfgnvMWDUnAnfiZgsa+h+iawEhX4\ +fAuc+TuUBwNe4BgHSdEpevG/KL7ACX4E8xw20Y+xRGAzxL8XxVn+FbqwAUeowkBS8Bft+FcUZ/mn\ +McZuDrPIxUzdmMMytvAL52hAE/owGq86REUOtZjAXixxCD1oxuMQ2MdOXOFj7KQRU9ki67F4hwtk\ +PoMaia1EYv0O8CrOYnRlCl9Xfwh9xXgInSYNjpHHG3wLbjB9wXzSIY8RtMVodWhR+LHySd2HVOAR\ +pmNhpUY4w3s8pHC+1JrxEs+i65MQ3cO2qytsZcAlA5qEWoLbkBcAAAAASUVORK5CYII=\ "); } } From c77b5c46a39577df8f26b1a3c023ae98786304b6 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 13 Sep 2021 19:36:17 -0400 Subject: [PATCH 17/39] rendernodeparser: Avoid gdk_texture_new_for_pixbuf We can just use gdk_texture_new_from_bytes here now. Update affected test output. --- gsk/gskrendernodeparser.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index fe5c55ac1e..ed08bd4431 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -85,24 +85,17 @@ parse_texture (GtkCssParser *parser, scheme = g_uri_parse_scheme (url); if (scheme && g_ascii_strcasecmp (scheme, "data") == 0) { - GInputStream *stream; - GdkPixbuf *pixbuf; GBytes *bytes; - texture = NULL; - bytes = gtk_css_data_url_parse (url, NULL, &error); if (bytes) { - stream = g_memory_input_stream_new_from_bytes (bytes); + texture = gdk_texture_new_from_bytes (bytes, &error); g_bytes_unref (bytes); - pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error); - g_object_unref (stream); - if (pixbuf != NULL) - { - texture = gdk_texture_new_for_pixbuf (pixbuf); - g_object_unref (pixbuf); - } + } + else + { + texture = NULL; } } else From b1bb7c3258f62d9d9d81f8135f0ae8af38f3c799 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 02:36:56 +0200 Subject: [PATCH 18/39] texture: Add gdk_texture_save_to_tiff_bytes() --- gdk/gdktexture.c | 34 ++++++++++++++++++++++++++++++++++ gdk/gdktexture.h | 2 ++ 2 files changed, 36 insertions(+) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 7a8a324e37..ea186a8bc0 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -743,6 +743,10 @@ gdk_texture_save_to_png (GdkTexture *texture, * attaching metadata, you should look into an image handling * library such as the gdk-pixbuf library. * + * If you are dealing with high dynamic range float data, you + * might also want to consider [method@Gdk.Texture.save_to_tiff_bytes] + * instead. + * * Returns: a newly allocated `GBytes` containing PNG data * * Since: 4.6 @@ -786,3 +790,33 @@ gdk_texture_save_to_tiff (GdkTexture *texture, return result; } + +/** + * gdk_texture_save_to_tiff_bytes: + * @texture: a `GdkTexture` + * + * Store the given @texture in memory as a TIFF file. + * + * Use [ctor@Gdk.Texture.new_from_bytes] to read it back. + * + * This function is intended to store a representation of the + * texture's data that is as accurate as possible. This is + * particularly relevant when working with high dynamic range + * images and floating-point texture data. + * + * If that is not your concern and you are interested in a + * smaller size and a more portable format, you might want to + * use [method@Gdk.Texture.save_to_png_bytes]. + * + * Returns: a newly allocated `GBytes` containing TIFF data + * + * Since: 4.6 + */ +GBytes * +gdk_texture_save_to_tiff_bytes (GdkTexture *texture) +{ + g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL); + + return gdk_save_tiff (texture); +} + diff --git a/gdk/gdktexture.h b/gdk/gdktexture.h index 8d46702b79..aba864e39f 100644 --- a/gdk/gdktexture.h +++ b/gdk/gdktexture.h @@ -96,6 +96,8 @@ GBytes * gdk_texture_save_to_png_bytes (GdkTexture GDK_AVAILABLE_IN_4_6 gboolean gdk_texture_save_to_tiff (GdkTexture *texture, const char *filename); +GDK_AVAILABLE_IN_4_6 +GBytes * gdk_texture_save_to_tiff_bytes (GdkTexture *texture); G_END_DECLS From 577bf104c0a086e254634c26a1c4fb534dc69f1b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 02:37:50 +0200 Subject: [PATCH 19/39] testsuite: Add png and tiff methods We encode the texture to a PNG or TIFF and then decode it again, in various ways. --- testsuite/gdk/memorytexture.c | 68 ++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/testsuite/gdk/memorytexture.c b/testsuite/gdk/memorytexture.c index 021a7ad11b..020d6927e4 100644 --- a/testsuite/gdk/memorytexture.c +++ b/testsuite/gdk/memorytexture.c @@ -12,6 +12,10 @@ typedef enum { TEXTURE_METHOD_LOCAL, TEXTURE_METHOD_GL, TEXTURE_METHOD_GL_RELEASED, + TEXTURE_METHOD_PNG, + TEXTURE_METHOD_PNG_PIXBUF, + TEXTURE_METHOD_TIFF, + TEXTURE_METHOD_TIFF_PIXBUF, N_TEXTURE_METHODS } TextureMethod; @@ -448,6 +452,68 @@ create_texture (GdkMemoryFormat format, gdk_gl_texture_release (GDK_GL_TEXTURE (texture)); break; + case TEXTURE_METHOD_PNG: + { + GBytes *bytes = gdk_texture_save_to_png_bytes (texture); + g_assert (bytes); + g_object_unref (texture); + texture = gdk_texture_new_from_bytes (bytes, NULL); + g_assert (texture); + g_bytes_unref (bytes); + } + break; + + case TEXTURE_METHOD_PNG_PIXBUF: + { + GInputStream *stream; + GdkPixbuf *pixbuf; + GBytes *bytes; + + bytes = gdk_texture_save_to_png_bytes (texture); + g_assert (bytes); + g_object_unref (texture); + stream = g_memory_input_stream_new_from_bytes (bytes); + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL); + g_object_unref (stream); + g_assert (pixbuf); + texture = gdk_texture_new_for_pixbuf (pixbuf); + g_assert (texture); + g_object_unref (pixbuf); + g_bytes_unref (bytes); + } + break; + + case TEXTURE_METHOD_TIFF: + { + GBytes *bytes = gdk_texture_save_to_tiff_bytes (texture); + g_assert (bytes); + g_object_unref (texture); + texture = gdk_texture_new_from_bytes (bytes, NULL); + g_assert (texture); + g_bytes_unref (bytes); + } + break; + + case TEXTURE_METHOD_TIFF_PIXBUF: + { + GInputStream *stream; + GdkPixbuf *pixbuf; + GBytes *bytes; + + bytes = gdk_texture_save_to_png_bytes (texture); + g_assert (bytes); + g_object_unref (texture); + stream = g_memory_input_stream_new_from_bytes (bytes); + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL); + g_object_unref (stream); + g_assert (pixbuf); + texture = gdk_texture_new_for_pixbuf (pixbuf); + g_assert (texture); + g_object_unref (pixbuf); + g_bytes_unref (bytes); + } + break; + case N_TEXTURE_METHODS: default: g_assert_not_reached (); @@ -584,7 +650,7 @@ add_test (const char *name, { for (method = 0; method < N_TEXTURE_METHODS; method++) { - const char *method_names[N_TEXTURE_METHODS] = { "local", "gl", "gl-released" }; + const char *method_names[N_TEXTURE_METHODS] = { "local", "gl", "gl-released", "png", "png-pixbuf", "tiff", "tiff-pixbuf" }; char *test_name = g_strdup_printf ("%s/%s/%s", name, g_enum_get_value (enum_class, format)->value_nick, From f2ca9ebbd7d5ff0b92d47c1f56b2d4a25d52bb16 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 08:52:35 -0400 Subject: [PATCH 20/39] texture: Avoid pixbufs when loading resources We can just use our own loaders here now. --- gdk/gdktexture.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index ea186a8bc0..261652ec41 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -354,18 +354,23 @@ gdk_texture_new_for_pixbuf (GdkPixbuf *pixbuf) GdkTexture * gdk_texture_new_from_resource (const char *resource_path) { - GError *error = NULL; + GBytes *bytes; GdkTexture *texture; - GdkPixbuf *pixbuf; + GError *error = NULL; g_return_val_if_fail (resource_path != NULL, NULL); - pixbuf = gdk_pixbuf_new_from_resource (resource_path, &error); - if (pixbuf == NULL) - g_error ("Resource path %s is not a valid image: %s", resource_path, error->message); + bytes = g_resources_lookup_data (resource_path, 0, &error); + if (bytes != NULL) + { + texture = gdk_texture_new_from_bytes (bytes, &error); + g_bytes_unref (bytes); + } + else + texture = NULL; - texture = gdk_texture_new_for_pixbuf (pixbuf); - g_object_unref (pixbuf); + if (texture == NULL) + g_error ("Resource path %s s not a valid image: %s", resource_path, error->message); return texture; } From 100b0bf7d92d490254e900052f2b351b29fa19fc Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 16:26:05 +0200 Subject: [PATCH 21/39] texture: Remove declaration for nonexisting function --- gdk/gdktextureprivate.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/gdk/gdktextureprivate.h b/gdk/gdktextureprivate.h index 29fca0a9ba..f4cb5eab66 100644 --- a/gdk/gdktextureprivate.h +++ b/gdk/gdktextureprivate.h @@ -35,9 +35,6 @@ struct _GdkTextureClass { gsize stride); }; -gpointer gdk_texture_new (const GdkTextureClass *klass, - int width, - int height); GdkTexture * gdk_texture_new_for_surface (cairo_surface_t *surface); cairo_surface_t * gdk_texture_download_surface (GdkTexture *texture); /* NB: GdkMemoryTexture */ From 2d3de8608cbba2ff82c5e93d16d7d18b32683222 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 17:03:49 +0200 Subject: [PATCH 22/39] texture: Split out type detection This way, the code using it becomes clearer and we can use it in multiple places without accidentally doing it wrong (hint: see next commit). --- gdk/gdktexture.c | 38 ++++++++++++------------------------ gdk/loaders/gdkjpegprivate.h | 12 ++++++++++++ gdk/loaders/gdkpngprivate.h | 12 ++++++++++++ gdk/loaders/gdktiffprivate.h | 14 +++++++++++++ 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 261652ec41..a33fc3347c 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -431,52 +431,38 @@ GdkTexture * gdk_texture_new_from_bytes (GBytes *bytes, GError **error) { - const char *data; - gsize size; - g_return_val_if_fail (bytes != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - data = g_bytes_get_data (bytes, &size); - - if (size > strlen (PNG_SIGNATURE) && - memcmp (data, PNG_SIGNATURE, strlen (PNG_SIGNATURE)) == 0) + if (gdk_is_png (bytes)) { return gdk_load_png (bytes, error); } - else if ((size > strlen (TIFF_SIGNATURE1) && - memcmp (data, TIFF_SIGNATURE1, strlen (TIFF_SIGNATURE1)) == 0) || - (size > strlen (TIFF_SIGNATURE2) && - memcmp (data, TIFF_SIGNATURE2, strlen (TIFF_SIGNATURE2)) == 0)) - { - return gdk_load_tiff (bytes, error); - } - else if (size > strlen (JPEG_SIGNATURE) && - memcmp (data, JPEG_SIGNATURE, strlen (JPEG_SIGNATURE)) == 0) + else if (gdk_is_jpeg (bytes)) { return gdk_load_jpeg (bytes, error); } + else if (gdk_is_tiff (bytes)) + { + return gdk_load_tiff (bytes, error); + } else { GInputStream *stream; GdkPixbuf *pixbuf; + GdkTexture *texture; stream = g_memory_input_stream_new_from_bytes (bytes); pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); g_object_unref (stream); + if (pixbuf == NULL) + return NULL; - if (pixbuf) - { - GdkTexture *texture; + texture = gdk_texture_new_for_pixbuf (pixbuf); + g_object_unref (pixbuf); - texture = gdk_texture_new_for_pixbuf (pixbuf); - g_object_unref (pixbuf); - - return texture; - } + return texture; } - - return NULL; } /** diff --git a/gdk/loaders/gdkjpegprivate.h b/gdk/loaders/gdkjpegprivate.h index a8e6bd8a82..4dcccbaf8d 100644 --- a/gdk/loaders/gdkjpegprivate.h +++ b/gdk/loaders/gdkjpegprivate.h @@ -26,4 +26,16 @@ GdkTexture *gdk_load_jpeg (GBytes *bytes, GError **error); +static inline gboolean +gdk_is_jpeg (GBytes *bytes) +{ + const char *data; + gsize size; + + data = g_bytes_get_data (bytes, &size); + + return size > strlen (JPEG_SIGNATURE) && + memcmp (data, JPEG_SIGNATURE, strlen (JPEG_SIGNATURE)) == 0; +} + #endif diff --git a/gdk/loaders/gdkpngprivate.h b/gdk/loaders/gdkpngprivate.h index ca824579a2..cbe073b315 100644 --- a/gdk/loaders/gdkpngprivate.h +++ b/gdk/loaders/gdkpngprivate.h @@ -28,4 +28,16 @@ GdkTexture *gdk_load_png (GBytes *bytes, GBytes *gdk_save_png (GdkTexture *texture); +static inline gboolean +gdk_is_png (GBytes *bytes) +{ + const char *data; + gsize size; + + data = g_bytes_get_data (bytes, &size); + + return size > strlen (PNG_SIGNATURE) && + memcmp (data, PNG_SIGNATURE, strlen (PNG_SIGNATURE)) == 0; +} + #endif diff --git a/gdk/loaders/gdktiffprivate.h b/gdk/loaders/gdktiffprivate.h index ee97884e75..839e8855f0 100644 --- a/gdk/loaders/gdktiffprivate.h +++ b/gdk/loaders/gdktiffprivate.h @@ -29,4 +29,18 @@ GdkTexture *gdk_load_tiff (GBytes *bytes, GBytes * gdk_save_tiff (GdkTexture *texture); +static inline gboolean +gdk_is_tiff (GBytes *bytes) +{ + const char *data; + gsize size; + + data = g_bytes_get_data (bytes, &size); + + return (size > strlen (TIFF_SIGNATURE1) && + memcmp (data, TIFF_SIGNATURE1, strlen (TIFF_SIGNATURE1)) == 0) || + (size > strlen (TIFF_SIGNATURE2) && + memcmp (data, TIFF_SIGNATURE2, strlen (TIFF_SIGNATURE2)) == 0); +} + #endif From 679c93e843d26551a7210679b3466d6edffc5eb6 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 17:40:09 +0200 Subject: [PATCH 23/39] texture: Add private can_load() function ... and use it to load textures in gtk_picture_set_from_file(). --- gdk/gdktexture.c | 7 +++++++ gdk/gdktextureprivate.h | 2 ++ gtk/gdkpixbufutils.c | 44 ++++++++++++++++++++++------------------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index a33fc3347c..a6e79bca56 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -410,6 +410,13 @@ gdk_texture_new_from_file (GFile *file, return texture; } +gboolean +gdk_texture_can_load (GBytes *bytes) +{ + return gdk_is_png (bytes) || + gdk_is_jpeg (bytes) || + gdk_is_tiff (bytes); +} /** * gdk_texture_new_from_bytes: diff --git a/gdk/gdktextureprivate.h b/gdk/gdktextureprivate.h index f4cb5eab66..814ed5d92c 100644 --- a/gdk/gdktextureprivate.h +++ b/gdk/gdktextureprivate.h @@ -35,6 +35,8 @@ struct _GdkTextureClass { gsize stride); }; +gboolean gdk_texture_can_load (GBytes *bytes); + GdkTexture * gdk_texture_new_for_surface (cairo_surface_t *surface); cairo_surface_t * gdk_texture_download_surface (GdkTexture *texture); /* NB: GdkMemoryTexture */ diff --git a/gtk/gdkpixbufutils.c b/gtk/gdkpixbufutils.c index 2b48d76943..7bbe5d5fd8 100644 --- a/gtk/gdkpixbufutils.c +++ b/gtk/gdkpixbufutils.c @@ -20,6 +20,8 @@ #include "gdkpixbufutilsprivate.h" #include "gtkscalerprivate.h" +#include "gdk/gdktextureprivate.h" + static GdkPixbuf * load_from_stream (GdkPixbufLoader *loader, GInputStream *stream, @@ -597,42 +599,44 @@ GdkPaintable * gdk_paintable_new_from_bytes_scaled (GBytes *bytes, int scale_factor) { - GdkPixbufLoader *loader; - GdkPixbuf *pixbuf = NULL; LoaderData loader_data; GdkTexture *texture; GdkPaintable *paintable; loader_data.scale_factor = scale_factor; - loader = gdk_pixbuf_loader_new (); - g_signal_connect (loader, "size-prepared", - G_CALLBACK (on_loader_size_prepared), &loader_data); + if (gdk_texture_can_load (bytes)) + { + /* We know these formats can't be scaled */ + texture = gdk_texture_new_from_bytes (bytes, NULL); + if (texture == NULL) + return NULL; + } + else + { + GdkPixbufLoader *loader; + gboolean success; - if (!gdk_pixbuf_loader_write_bytes (loader, bytes, NULL)) - goto out; + loader = gdk_pixbuf_loader_new (); + g_signal_connect (loader, "size-prepared", + G_CALLBACK (on_loader_size_prepared), &loader_data); - if (!gdk_pixbuf_loader_close (loader, NULL)) - goto out; + success = gdk_pixbuf_loader_write_bytes (loader, bytes, NULL); + /* close even when writing failed */ + success &= gdk_pixbuf_loader_close (loader, NULL); - pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); - if (pixbuf != NULL) - g_object_ref (pixbuf); + if (!success) + return NULL; - out: - gdk_pixbuf_loader_close (loader, NULL); - g_object_unref (loader); + texture = gdk_texture_new_for_pixbuf (gdk_pixbuf_loader_get_pixbuf (loader)); + g_object_unref (loader); + } - if (!pixbuf) - return NULL; - - texture = gdk_texture_new_for_pixbuf (pixbuf); if (loader_data.scale_factor != 1) paintable = gtk_scaler_new (GDK_PAINTABLE (texture), loader_data.scale_factor); else paintable = g_object_ref ((GdkPaintable *)texture); - g_object_unref (pixbuf); g_object_unref (texture); return paintable; From dcd873a6d8158326cfd17d72e8b2902c0a77f27a Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 14:55:59 -0400 Subject: [PATCH 24/39] builder: create textures without pixbufs Load textures using the GdkTexture apis, without going through a pixbuf first. --- gtk/gtkbuilder.c | 77 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 9d85e6f2ed..14d03298d7 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -2296,9 +2296,63 @@ gtk_builder_value_from_string_type (GtkBuilder *builder, break; case G_TYPE_OBJECT: case G_TYPE_INTERFACE: - if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF) || - G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE) || + if (G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE) || G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE)) + { + GObject *object = g_hash_table_lookup (priv->objects, string); + char *filename; + GError *tmp_error = NULL; + GdkTexture *texture = NULL; + + if (object) + { + if (g_type_is_a (G_OBJECT_TYPE (object), G_VALUE_TYPE (value))) + { + g_value_set_object (value, object); + return TRUE; + } + else + { + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_VALUE, + "Could not load image '%s': " + " '%s' is already used as object id for a %s", + string, string, G_OBJECT_TYPE_NAME (object)); + return FALSE; + } + } + + filename = _gtk_builder_get_resource_path (builder, string); + if (filename != NULL) + { + texture = gdk_texture_new_from_resource (filename); + } + else + { + GFile *file; + + filename = _gtk_builder_get_absolute_filename (builder, string); + file = g_file_new_for_path (filename); + texture = gdk_texture_new_from_file (file, &tmp_error); + g_object_unref (file); + } + + g_free (filename); + + if (!texture) + { + g_warning ("Could not load image '%s': %s", string, tmp_error->message); + g_error_free (tmp_error); + + texture = gdk_texture_new_from_resource (IMAGE_MISSING_RESOURCE_PATH); + } + + g_value_take_object (value, texture); + + ret = TRUE; + } + else if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF)) { char *filename; GError *tmp_error = NULL; @@ -2344,28 +2398,13 @@ gtk_builder_value_from_string_type (GtkBuilder *builder, if (pixbuf == NULL) { - g_warning ("Could not load image '%s': %s", - string, tmp_error->message); + g_warning ("Could not load image '%s': %s", string, tmp_error->message); g_error_free (tmp_error); pixbuf = _gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, "png", NULL); } - if (pixbuf) - { - if (G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE) || - G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE)) - { - GdkTexture *texture = gdk_texture_new_for_pixbuf (pixbuf); - g_value_set_object (value, texture); - g_object_unref (texture); - } - else - { - g_value_set_object (value, pixbuf); - } - g_object_unref (G_OBJECT (pixbuf)); - } + g_value_take_object (value, pixbuf); g_free (filename); From 729ad8e64a4d624738113466714416b7ae37b8d5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 15:12:39 -0400 Subject: [PATCH 25/39] cssimageurl: Just create a texture directly We don't need to use the pixbufutils api here. --- gtk/gtkcssimageurl.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/gtk/gtkcssimageurl.c b/gtk/gtkcssimageurl.c index d7271eda18..15d56ac21e 100644 --- a/gtk/gtkcssimageurl.c +++ b/gtk/gtkcssimageurl.c @@ -26,10 +26,9 @@ #include "gtkcssimageinvalidprivate.h" #include "gtkcssimagepaintableprivate.h" #include "gtkstyleproviderprivate.h" -#include "gdkpixbufutilsprivate.h" - #include "gtk/css/gtkcssdataurlprivate.h" + G_DEFINE_TYPE (GtkCssImageUrl, _gtk_css_image_url, GTK_TYPE_CSS_IMAGE) static GtkCssImage * @@ -181,28 +180,28 @@ gtk_css_image_url_parse (GtkCssImage *image, if (scheme && g_ascii_strcasecmp (scheme, "data") == 0) { GBytes *bytes; - GdkPaintable *paintable; GError *error = NULL; bytes = gtk_css_data_url_parse (url, NULL, &error); if (bytes) { - paintable = gdk_paintable_new_from_bytes_scaled (bytes, 1); + GdkTexture *texture; + + texture = gdk_texture_new_from_bytes (bytes, &error); g_bytes_unref (bytes); - if (paintable == NULL) + if (texture) + { + GdkPaintable *paintable = GDK_PAINTABLE (texture); + self->loaded_image = gtk_css_image_paintable_new (paintable, paintable); + } + else { - error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to load image from '%s'", url); gtk_css_parser_emit_error (parser, gtk_css_parser_get_start_location (parser), gtk_css_parser_get_end_location (parser), error); g_clear_error (&error); } - else - { - self->loaded_image = gtk_css_image_paintable_new (paintable, paintable); - } } else { From fecb31b70646dd8bb7d66d63297abaa492455d2e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 15:17:29 -0400 Subject: [PATCH 26/39] builder: Stop using pixbufutils We can just use gdk_pixbuf_new_from_resource here. --- gtk/gtkbuilder.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 14d03298d7..697246790b 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -210,7 +210,6 @@ #include "gtkbuilderprivate.h" -#include "gdkpixbufutilsprivate.h" #include "gtkbuildableprivate.h" #include "gtkbuilderlistitemfactory.h" #include "gtkbuilderscopeprivate.h" @@ -226,7 +225,6 @@ #include "gtktypebuiltins.h" #include "gtkicontheme.h" #include "gtkiconthemeprivate.h" -#include "gdkpixbufutilsprivate.h" #include "gtkdebug.h" @@ -2401,7 +2399,7 @@ gtk_builder_value_from_string_type (GtkBuilder *builder, g_warning ("Could not load image '%s': %s", string, tmp_error->message); g_error_free (tmp_error); - pixbuf = _gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, "png", NULL); + pixbuf = gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, NULL); } g_value_take_object (value, pixbuf); From e720f9d35d2ca66936f6d3378839fb3fdb8e1578 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 17:40:29 -0400 Subject: [PATCH 27/39] Add code to save jpegs --- gdk/loaders/gdkjpeg.c | 205 +++++++++++++++++++++++++++++++++-- gdk/loaders/gdkjpegprivate.h | 2 + 2 files changed, 196 insertions(+), 11 deletions(-) diff --git a/gdk/loaders/gdkjpeg.c b/gdk/loaders/gdkjpeg.c index 74a1c9623a..506a278aaa 100644 --- a/gdk/loaders/gdkjpeg.c +++ b/gdk/loaders/gdkjpeg.c @@ -70,7 +70,97 @@ output_message_handler (j_common_ptr cinfo) } /* }}} */ - /* {{{ Public API */ +/* {{{ Format conversion */ + +static void +convert_rgba_to_rgb (guchar *data, + int width, + int height, + int stride) +{ + gsize x, y; + guchar *src, *dest; + + for (y = 0; y < height; y++) + { + src = data; + dest = data; + + for (x = 0; x < width; x++) + { + guint32 pixel; + + memcpy (&pixel, src, sizeof (guint32)); + + dest[0] = (pixel & 0x00ff0000) >> 16; + dest[1] = (pixel & 0x0000ff00) >> 8; + dest[2] = (pixel & 0x000000ff) >> 0; + + dest += 3; + src += 4; + } + + data += stride; + } +} + +static void +convert_grayscale_to_rgb (guchar *data, + int width, + int height, + int stride) +{ + gsize x, y; + guchar *dest, *src; + + for (y = 0; y < height; y++) + { + src = data + width; + dest = data + 3 * width; + for (x = 0; x < width; x++) + { + dest -= 3; + src -= 1; + dest[0] = *src; + dest[1] = *src; + dest[2] = *src; + } + data += stride; + } +} + +static void +convert_cmyk_to_rgba (guchar *data, + int width, + int height, + int stride) +{ + gsize x, r; + guchar *dest; + + for (r = 0; r < height; r++) + { + dest = data; + for (x = 0; x < width; x++) + { + int c, m, y, k; + + c = dest[0]; + m = dest[1]; + y = dest[2]; + k = dest[3]; + dest[0] = k * c / 255; + dest[1] = k * m / 255; + dest[2] = k * y / 255; + dest[3] = 255; + dest += 4; + } + data += stride; + } +} + + /* }}} */ +/* {{{ Public API */ GdkTexture * gdk_load_jpeg (GBytes *input_bytes, @@ -78,13 +168,12 @@ gdk_load_jpeg (GBytes *input_bytes, { struct jpeg_decompress_struct info; struct error_handler_data jerr; - struct jpeg_error_mgr err; - int width, height; - int size; + guint width, height, stride; unsigned char *data; unsigned char *row[1]; GBytes *bytes; GdkTexture *texture; + GdkMemoryFormat format; info.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = fatal_error_handler; @@ -97,7 +186,6 @@ gdk_load_jpeg (GBytes *input_bytes, return NULL; } - info.err = jpeg_std_error (&err); jpeg_create_decompress (&info); jpeg_mem_src (&info, @@ -110,8 +198,27 @@ gdk_load_jpeg (GBytes *input_bytes, width = info.output_width; height = info.output_height; - size = width * height * 3; - data = g_try_malloc_n (width * 3, height); + switch ((int)info.out_color_space) + { + case JCS_GRAYSCALE: + case JCS_RGB: + stride = 3 * width; + data = g_try_malloc_n (stride, height); + format = GDK_MEMORY_R8G8B8; + break; + case JCS_CMYK: + stride = 4 * width; + data = g_try_malloc_n (stride, height); + format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; + break; + default: + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED, + "Unsupported colorspace in jpeg (%d)", info.out_color_space); + jpeg_destroy_decompress (&info); + return NULL; + } + if (!data) { g_set_error_literal (error, @@ -123,24 +230,100 @@ gdk_load_jpeg (GBytes *input_bytes, while (info.output_scanline < info.output_height) { - row[0] = (unsigned char *)(&data[3 *info.output_width * info.output_scanline]); + row[0] = (unsigned char *)(&data[stride * info.output_scanline]); jpeg_read_scanlines (&info, row, 1); } + switch ((int)info.out_color_space) + { + case JCS_GRAYSCALE: + convert_grayscale_to_rgb (data, width, height, stride); + format = GDK_MEMORY_R8G8B8; + break; + case JCS_RGB: + break; + case JCS_CMYK: + convert_cmyk_to_rgba (data, width, height, stride); + break; + default: + g_assert_not_reached (); + } + jpeg_finish_decompress (&info); jpeg_destroy_decompress (&info); - bytes = g_bytes_new_take (data, size); + bytes = g_bytes_new_take (data, stride * height); texture = gdk_memory_texture_new (width, height, - GDK_MEMORY_R8G8B8, - bytes, width * 3); + format, + bytes, stride); g_bytes_unref (bytes); return texture; } +GBytes * +gdk_save_jpeg (GdkTexture *texture) +{ + struct jpeg_compress_struct info; + struct error_handler_data jerr; + struct jpeg_error_mgr err; + guchar *data = NULL; + gulong size = 0; + guchar *input = NULL; + guchar *row; + int width, height, stride; + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + info.err = jpeg_std_error (&jerr.pub); + jerr.pub.error_exit = fatal_error_handler; + jerr.pub.output_message = output_message_handler; + jerr.error = NULL; + + if (sigsetjmp (jerr.setjmp_buffer, 1)) + { + free (data); + g_free (input); + jpeg_destroy_compress (&info); + return NULL; + } + + info.err = jpeg_std_error (&err); + jpeg_create_compress (&info); + info.image_width = width; + info.image_height = height; + info.input_components = 3; + info.in_color_space = JCS_RGB; + + jpeg_set_defaults (&info); + jpeg_set_quality (&info, 75, TRUE); + + jpeg_mem_dest (&info, &data, &size); + + stride = width * 4; + input = g_malloc (stride * height); + gdk_texture_download (texture, input, stride); + convert_rgba_to_rgb (data, width, height, stride); + + jpeg_start_compress (&info, TRUE); + + while (info.next_scanline < info.image_height) + { + row = &input[info.next_scanline * stride]; + jpeg_write_scanlines (&info, &row, 1); + } + + jpeg_finish_compress (&info); + + g_free (input); + jpeg_destroy_compress (&info); + + return g_bytes_new_with_free_func (data, size, (GDestroyNotify) free, NULL); +} + /* }}} */ /* vim:set foldmethod=marker expandtab: */ diff --git a/gdk/loaders/gdkjpegprivate.h b/gdk/loaders/gdkjpegprivate.h index 4dcccbaf8d..28e7b90f28 100644 --- a/gdk/loaders/gdkjpegprivate.h +++ b/gdk/loaders/gdkjpegprivate.h @@ -26,6 +26,8 @@ GdkTexture *gdk_load_jpeg (GBytes *bytes, GError **error); +GBytes *gdk_save_jpeg (GdkTexture *texture); + static inline gboolean gdk_is_jpeg (GBytes *bytes) { From 14280b5f5bff7720f20b79ba966da15f8437dab0 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 14 Sep 2021 17:46:11 -0400 Subject: [PATCH 28/39] contentdeserializer: Use our on jpeg loader --- gdk/gdkcontentserializer.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gdk/gdkcontentserializer.c b/gdk/gdkcontentserializer.c index e0bf34bd53..6faaa0121a 100644 --- a/gdk/gdkcontentserializer.c +++ b/gdk/gdkcontentserializer.c @@ -28,6 +28,7 @@ #include "gdkrgba.h" #include "loaders/gdkpngprivate.h" #include "loaders/gdktiffprivate.h" +#include "loaders/gdkjpegprivate.h" #include "gdkmemorytextureprivate.h" #include @@ -698,6 +699,8 @@ serialize_texture_in_thread (GTask *task, bytes = gdk_save_png (texture); else if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/tiff") == 0) bytes = gdk_save_tiff (texture); + else if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/jpeg") == 0) + bytes = gdk_save_jpeg (texture); else g_assert_not_reached (); @@ -960,6 +963,11 @@ init (void) texture_serializer, NULL, NULL); + gdk_content_register_serializer (GDK_TYPE_TEXTURE, + "image/jpeg", + texture_serializer, + NULL, NULL); + formats = gdk_pixbuf_get_formats (); /* Make sure png comes first */ @@ -994,9 +1002,10 @@ init (void) mimes = gdk_pixbuf_format_get_mime_types (fmt); for (m = mimes; *m; m++) { - /* Turning textures into pngs or tiffs is handled above */ + /* Turning textures into pngs, tiffs or jpegs is handled above */ if (!g_str_equal (name, "png") && - !g_str_equal (name, "tiff")) + !g_str_equal (name, "tiff") && + !g_str_equal (name, "jpeg")) gdk_content_register_serializer (GDK_TYPE_TEXTURE, *m, pixbuf_serializer, From b226478e8b4adf7ac5dbe2c18141518b393cc809 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 15 Sep 2021 00:41:40 -0400 Subject: [PATCH 29/39] Support 16bit formats in the png loader When loading, convert all >8-bit data to GDK_MEMORY_R16G16B16A16_PREMULTIPLIED. When saving, save all 8-bit formats as 8-bit RGBA, and save all >8-bt formats as 16-bit RGBA. --- gdk/loaders/gdkpng.c | 186 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 161 insertions(+), 25 deletions(-) diff --git a/gdk/loaders/gdkpng.c b/gdk/loaders/gdkpng.c index 03c1095c04..fa0258b0ba 100644 --- a/gdk/loaders/gdkpng.c +++ b/gdk/loaders/gdkpng.c @@ -130,8 +130,7 @@ png_simple_warning_callback (png_structp png, static void unpremultiply (guchar *data, int width, - int height, - int stride) + int height) { gsize x, y; @@ -160,7 +159,47 @@ unpremultiply (guchar *data, b[3] = alpha; } } - data += stride; + data += width * 4; + } +} + +static void +unpremultiply_float_to_16bit (guchar *data, + int width, + int height) +{ + gsize x, y; + float *src = (float *)data;; + guint16 *dest = (guint16 *)data; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + float r, g, b, a; + + r = src[0]; + g = src[1]; + b = src[2]; + a = src[3]; + if (a == 0) + { + dest[0] = 0; + dest[1] = 0; + dest[2] = 0; + dest[3] = 0; + } + else + { + dest[0] = (guint16) CLAMP (65536.f * r / a, 0.f, 65535.f); + dest[1] = (guint16) CLAMP (65536.f * g / a, 0.f, 65535.f); + dest[2] = (guint16) CLAMP (65536.f * b / a, 0.f, 65535.f); + dest[3] = (guint16) CLAMP (65536.f * a, 0.f, 65535.f); + } + + dest += 4; + src += 4; + } } } @@ -226,6 +265,30 @@ convert_bytes_to_data (png_structp png, } } +static void +premultiply_16bit (guchar *data, + int width, + int height, + int stride) +{ + gsize x, y; + guint16 *src; + + for (y = 0; y < height; y++) + { + src = (guint16 *)data; + for (x = 0; x < width; x++) + { + float alpha = src[x * 4 + 3] / 65535.f; + src[x * 4 ] = (guint16) CLAMP (src[x * 4 ] * alpha, 0.f, 65535.f); + src[x * 4 + 1] = (guint16) CLAMP (src[x * 4 + 1] * alpha, 0.f, 65535.f); + src[x * 4 + 2] = (guint16) CLAMP (src[x * 4 + 2] * alpha, 0.f, 65535.f); + } + + data += stride; + } +} + /* }}} */ /* {{{ Public API */ @@ -244,6 +307,7 @@ gdk_load_png (GBytes *bytes, guchar **row_pointers = NULL; GBytes *out_bytes; GdkTexture *texture; + int bpp; io.data = (guchar *)g_bytes_get_data (bytes, &io.size); io.position = 0; @@ -298,6 +362,9 @@ gdk_load_png (GBytes *bytes, if (png_get_valid (png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha (png); + if (depth == 8) + png_set_filler (png, 0xff, PNG_FILLER_AFTER); + if (depth < 8) png_set_packing (png); @@ -308,13 +375,11 @@ gdk_load_png (GBytes *bytes, if (interlace != PNG_INTERLACE_NONE) png_set_interlace_handling (png); - png_set_filler (png, 0xff, PNG_FILLER_AFTER); - png_read_update_info (png, info); png_get_IHDR (png, info, &width, &height, &depth, &color_type, &interlace, NULL, NULL); - if ((depth != 8) || + if ((depth != 8 && depth != 16) || !(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { @@ -328,20 +393,41 @@ gdk_load_png (GBytes *bytes, switch (color_type) { case PNG_COLOR_TYPE_RGB_ALPHA: - format = GDK_MEMORY_DEFAULT; - png_set_read_user_transform_fn (png, premultiply_data); + if (depth == 8) + { + format = GDK_MEMORY_DEFAULT; + png_set_read_user_transform_fn (png, premultiply_data); + } + else + { + format = GDK_MEMORY_R16G16B16A16_PREMULTIPLIED; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + png_set_swap (png); +#endif + } break; case PNG_COLOR_TYPE_RGB: - format = GDK_MEMORY_DEFAULT; - png_set_read_user_transform_fn (png, convert_bytes_to_data); + if (depth == 8) + { + format = GDK_MEMORY_DEFAULT; + png_set_read_user_transform_fn (png, convert_bytes_to_data); + } + else + { + format = GDK_MEMORY_R16G16B16; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + png_set_swap (png); +#endif + } break; default: g_assert_not_reached (); } - stride = width * gdk_memory_format_bytes_per_pixel (format); - if (stride % 4) - stride += 4 - stride % 4; + bpp = gdk_memory_format_bytes_per_pixel (format); + stride = width * bpp; + if (stride % 8) + stride += 8 - stride % 8; buffer = g_try_malloc_n (height, stride); row_pointers = g_try_malloc_n (height, sizeof (char *)); @@ -363,10 +449,11 @@ gdk_load_png (GBytes *bytes, png_read_image (png, row_pointers); png_read_end (png, info); + if (format == GDK_MEMORY_R16G16B16A16_PREMULTIPLIED) + premultiply_16bit (buffer, width, height, stride); + out_bytes = g_bytes_new_take (buffer, height * stride); - texture = gdk_memory_texture_new (width, height, - format, - out_bytes, stride); + texture = gdk_memory_texture_new (width, height, format, out_bytes, stride); g_bytes_unref (out_bytes); g_free (row_pointers); @@ -385,14 +472,56 @@ gdk_save_png (GdkTexture *texture) guchar *data = NULL; guchar *row; int y; + GdkTexture *mtexture; + GdkMemoryFormat format; + int png_format; + int depth; width = gdk_texture_get_width (texture); height = gdk_texture_get_height (texture); - stride = width * 4; - data = g_malloc_n (stride, height); - gdk_texture_download (texture, data, stride); - unpremultiply (data, width, height, stride); + mtexture = gdk_texture_download_texture (texture); + format = gdk_memory_texture_get_format (GDK_MEMORY_TEXTURE (mtexture)); + + switch (format) + { + case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED: + case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED: + case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED: + case GDK_MEMORY_B8G8R8A8: + case GDK_MEMORY_A8R8G8B8: + case GDK_MEMORY_R8G8B8A8: + case GDK_MEMORY_A8B8G8R8: + case GDK_MEMORY_R8G8B8: + case GDK_MEMORY_B8G8R8: + stride = width * 4; + data = g_malloc_n (stride, height); + gdk_texture_download (mtexture, data, stride); + unpremultiply (data, width, height); + + png_format = PNG_COLOR_TYPE_RGB_ALPHA; + depth = 8; + break; + + case GDK_MEMORY_R16G16B16: + case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: + case GDK_MEMORY_R16G16B16_FLOAT: + case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: + case GDK_MEMORY_R32G32B32_FLOAT: + case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: + data = g_malloc_n (width * 16, height); + gdk_texture_download_float (mtexture, (float *)data, width * 4); + unpremultiply_float_to_16bit (data, width, height); + + png_format = PNG_COLOR_TYPE_RGB_ALPHA; + stride = width * 8; + depth = 16; + break; + + case GDK_MEMORY_N_FORMATS: + default: + g_assert_not_reached (); + } png = png_create_write_struct_2 (PNG_LIBPNG_VER_STRING, NULL, png_simple_error_callback, @@ -412,20 +541,25 @@ gdk_save_png (GdkTexture *texture) if (sigsetjmp (png_jmpbuf (png), 1)) { + g_free (data); + g_free (io.data); png_destroy_read_struct (&png, &info, NULL); return NULL; } png_set_write_fn (png, &io, png_write_func, png_flush_func); - png_set_IHDR (png, info, width, height, 8, - PNG_COLOR_TYPE_RGB_ALPHA, + png_set_IHDR (png, info, width, height, depth, + png_format, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, - PNG_FILTER_TYPE_BASE); + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); png_write_info (png, info); - png_set_packing (png); + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + png_set_swap (png); +#endif for (y = 0, row = data; y < height; y++, row += stride) png_write_rows (png, &row, 1); @@ -434,6 +568,8 @@ gdk_save_png (GdkTexture *texture) png_destroy_write_struct (&png, &info); + g_free (data); + return g_bytes_new_take (io.data, io.size); } From a85f4ec6c2347fd91049d47ea8af538a3acedcce Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 17:49:40 +0200 Subject: [PATCH 30/39] icontheme: Load textures directly when possible This doesn't fix the codepaths for SVG (both for recoloring and resizing) and doesn't handle streams yet. --- gtk/gtkicontheme.c | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index 91ea519a26..c171a573ff 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -3775,7 +3775,6 @@ static void icon_ensure_texture__locked (GtkIconPaintable *icon, gboolean in_thread) { - GdkPixbuf *source_pixbuf; gint64 before; int pixel_size; GError *load_error = NULL; @@ -3795,11 +3794,10 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, /* At this point, we need to actually get the icon; either from the * builtin image or by loading the file */ - source_pixbuf = NULL; #ifdef G_OS_WIN32 if (icon->win32_icon) { - source_pixbuf = g_object_ref (icon->win32_icon); + icon->texture = gdk_texture_new_for_pixbuf (icon->win32_icon); } else #endif @@ -3807,6 +3805,8 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, { if (icon->is_svg) { + GdkPixbuf *source_pixbuf; + if (gtk_icon_paintable_is_symbolic (icon)) source_pixbuf = gtk_make_symbolic_pixbuf_from_resource (icon->filename, pixel_size, pixel_size, @@ -3817,22 +3817,20 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, "svg", pixel_size, pixel_size, TRUE, &load_error); + if (source_pixbuf) + { + icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf); + g_object_unref (source_pixbuf); + } } else - source_pixbuf = _gdk_pixbuf_new_from_resource (icon->filename, - g_str_has_suffix (icon->filename, ".xpm") ? "xpm" : "png", - &load_error); - - if (source_pixbuf == NULL) - { - g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message); - g_clear_error (&load_error); - } + icon->texture = gdk_texture_new_from_resource (icon->filename); } else { GLoadableIcon *loadable; GInputStream *stream; + GdkPixbuf *source_pixbuf; loadable = icon_get_loadable (icon); stream = g_loadable_icon_load (loadable, @@ -3865,29 +3863,24 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, g_str_has_suffix (icon->filename, ".xpm") ? "xpm" : "png", NULL, &load_error); g_object_unref (stream); + if (source_pixbuf) + { + icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf); + g_object_unref (source_pixbuf); + } } - if (source_pixbuf == NULL) - { - g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message); - g_clear_error (&load_error); - } } - if (!source_pixbuf) + if (!icon->texture) { - source_pixbuf = _gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, "png", NULL); + g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message); + g_clear_error (&load_error); + icon->texture = gdk_texture_new_from_resource (IMAGE_MISSING_RESOURCE_PATH); icon->icon_name = g_strdup ("image-missing"); icon->is_symbolic = FALSE; - g_assert (source_pixbuf != NULL); } - /* Actual scaling is done during rendering, so just keep the source pixbuf as a texture */ - icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf); - g_object_unref (source_pixbuf); - - g_assert (icon->texture != NULL); - if (GDK_PROFILER_IS_RUNNING) { gint64 end = GDK_PROFILER_CURRENT_TIME; From b5da07f0e1a61be22d8290c871670633f77c6e7b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 14 Sep 2021 18:17:58 +0200 Subject: [PATCH 31/39] icontheme: Use textures more We were going via GLoadablieIcon/GInputStream for everything previously and we have no API for that with GdkTexture. With this commit, gdk-pixbuf isn't used anymore when starting widget-factory for anything but SVG. --- gtk/gtkicontheme.c | 86 +++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index c171a573ff..a48dfb433d 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -3742,31 +3742,6 @@ gtk_icon_paintable_is_symbolic (GtkIconPaintable *icon) return icon->is_symbolic; } -static GLoadableIcon * -icon_get_loadable (GtkIconPaintable *icon) -{ - GFile *file; - GLoadableIcon *loadable; - - if (icon->loadable) - return g_object_ref (icon->loadable); - - if (icon->is_resource) - { - char *uri = g_strconcat ("resource://", icon->filename, NULL); - file = g_file_new_for_uri (uri); - g_free (uri); - } - else - file = g_file_new_for_path (icon->filename); - - loadable = G_LOADABLE_ICON (g_file_icon_new (file)); - - g_object_unref (file); - - return loadable; -} - /* This function contains the complicated logic for deciding * on the size at which to load the icon and loading it at * that size. @@ -3826,19 +3801,57 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, else icon->texture = gdk_texture_new_from_resource (icon->filename); } + else if (icon->filename) + { + if (icon->is_svg) + { + GdkPixbuf *source_pixbuf; + + if (gtk_icon_paintable_is_symbolic (icon)) + source_pixbuf = gtk_make_symbolic_pixbuf_from_path (icon->filename, + pixel_size, pixel_size, + icon->desired_scale, + &load_error); + else + { + GFile *file = g_file_new_for_path (icon->filename); + GInputStream *stream = G_INPUT_STREAM (g_file_read (file, NULL, &load_error)); + + g_object_unref (file); + if (stream) + { + source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, + "svg", + pixel_size, pixel_size, + TRUE, NULL, + &load_error); + g_object_unref (stream); + } + else + source_pixbuf = NULL; + } + if (source_pixbuf) + { + icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf); + g_object_unref (source_pixbuf); + } + } + else + { + icon->texture = gdk_texture_new_from_filename (icon->filename, &load_error); + } + } else { - GLoadableIcon *loadable; GInputStream *stream; GdkPixbuf *source_pixbuf; - loadable = icon_get_loadable (icon); - stream = g_loadable_icon_load (loadable, + g_assert (icon->loadable); + + stream = g_loadable_icon_load (icon->loadable, pixel_size, NULL, NULL, &load_error); - g_object_unref (loadable); - if (stream) { /* SVG icons are a special case - we just immediately scale them @@ -3846,17 +3859,11 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, */ if (icon->is_svg) { - if (gtk_icon_paintable_is_symbolic (icon)) - source_pixbuf = gtk_make_symbolic_pixbuf_from_path (icon->filename, + source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, + "svg", pixel_size, pixel_size, - icon->desired_scale, + TRUE, NULL, &load_error); - else - source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, - "svg", - pixel_size, pixel_size, - TRUE, NULL, - &load_error); } else source_pixbuf = _gdk_pixbuf_new_from_stream (stream, @@ -3869,7 +3876,6 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, g_object_unref (source_pixbuf); } } - } if (!icon->texture) From 4fcf54757f2da7f899fd7d7728666f40c4aeecfa Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 02:29:59 +0200 Subject: [PATCH 32/39] icontheme: Insist that people provide proper values Seriously... --- gtk/gtkicontheme.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index a48dfb433d..94f1dc4730 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -4095,14 +4095,6 @@ gtk_icon_paintable_new_for_pixbuf (GtkIconTheme *icon_theme, int scale) { GtkIconPaintable *icon; - int width, height; - - if (size <= 0) - { - width = gdk_pixbuf_get_width (pixbuf); - height = gdk_pixbuf_get_height (pixbuf); - size = MAX (width, height); - } icon = icon_paintable_new (NULL, size, scale); icon->texture = gdk_texture_new_for_pixbuf (pixbuf); @@ -4139,6 +4131,8 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *self, g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL); g_return_val_if_fail (G_IS_ICON (gicon), NULL); + g_return_val_if_fail (size > 0, NULL); + g_return_val_if_fail (scale > 0, NULL); /* We can't render emblemed icons atm, but at least render the base */ while (G_IS_EMBLEMED_ICON (gicon)) From e58f70d7bb5990bfc8a7364c7f4e5af586e43750 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 02:34:15 +0200 Subject: [PATCH 33/39] pixbufutils: Don't hardcode formats Just let the loaders figure out the file format themselves. --- gtk/gdkpixbufutils.c | 34 +++++++--------------------------- gtk/gdkpixbufutilsprivate.h | 6 ------ gtk/gtkicontheme.c | 4 ---- 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/gtk/gdkpixbufutils.c b/gtk/gdkpixbufutils.c index 7bbe5d5fd8..df8c542deb 100644 --- a/gtk/gdkpixbufutils.c +++ b/gtk/gdkpixbufutils.c @@ -93,7 +93,6 @@ size_prepared_cb (GdkPixbufLoader *loader, */ GdkPixbuf * _gdk_pixbuf_new_from_stream_scaled (GInputStream *stream, - const char *format, double scale, GCancellable *cancellable, GError **error) @@ -101,14 +100,7 @@ _gdk_pixbuf_new_from_stream_scaled (GInputStream *stream, GdkPixbufLoader *loader; GdkPixbuf *pixbuf; - if (format) - { - loader = gdk_pixbuf_loader_new_with_type (format, error); - if (!loader) - return NULL; - } - else - loader = gdk_pixbuf_loader_new (); + loader = gdk_pixbuf_loader_new (); if (scale != 0) g_signal_connect (loader, "size-prepared", @@ -155,7 +147,6 @@ size_prepared_cb2 (GdkPixbufLoader *loader, GdkPixbuf * _gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream, - const char *format, int width, int height, gboolean aspect, @@ -166,14 +157,7 @@ _gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream, GdkPixbuf *pixbuf; int scales[3]; - if (format) - { - loader = gdk_pixbuf_loader_new_with_type (format, error); - if (!loader) - return NULL; - } - else - loader = gdk_pixbuf_loader_new (); + loader = gdk_pixbuf_loader_new (); scales[0] = width; scales[1] = height; @@ -190,11 +174,10 @@ _gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream, GdkPixbuf * _gdk_pixbuf_new_from_stream (GInputStream *stream, - const char *format, GCancellable *cancellable, GError **error) { - return _gdk_pixbuf_new_from_stream_scaled (stream, format, 0, cancellable, error); + return _gdk_pixbuf_new_from_stream_scaled (stream, 0, cancellable, error); } /* Like gdk_pixbuf_new_from_resource_at_scale, but @@ -203,7 +186,6 @@ _gdk_pixbuf_new_from_stream (GInputStream *stream, */ GdkPixbuf * _gdk_pixbuf_new_from_resource_scaled (const char *resource_path, - const char *format, double scale, GError **error) { @@ -214,7 +196,7 @@ _gdk_pixbuf_new_from_resource_scaled (const char *resource_path, if (stream == NULL) return NULL; - pixbuf = _gdk_pixbuf_new_from_stream_scaled (stream, format, scale, NULL, error); + pixbuf = _gdk_pixbuf_new_from_stream_scaled (stream, scale, NULL, error); g_object_unref (stream); return pixbuf; @@ -222,15 +204,13 @@ _gdk_pixbuf_new_from_resource_scaled (const char *resource_path, GdkPixbuf * _gdk_pixbuf_new_from_resource (const char *resource_path, - const char *format, GError **error) { - return _gdk_pixbuf_new_from_resource_scaled (resource_path, format, 0, error); + return _gdk_pixbuf_new_from_resource_scaled (resource_path, 0, error); } GdkPixbuf * _gdk_pixbuf_new_from_resource_at_scale (const char *resource_path, - const char *format, int width, int height, gboolean preserve_aspect, @@ -243,7 +223,7 @@ _gdk_pixbuf_new_from_resource_at_scale (const char *resource_path, if (stream == NULL) return NULL; - pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, format, width, height, preserve_aspect, NULL, error); + pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, width, height, preserve_aspect, NULL, error); g_object_unref (stream); return pixbuf; @@ -541,7 +521,7 @@ gtk_load_symbolic_texture_from_file (GFile *file) if (stream == NULL) return NULL; - pixbuf = _gdk_pixbuf_new_from_stream (stream, "png", NULL, NULL); + pixbuf = _gdk_pixbuf_new_from_stream (stream, NULL, NULL); g_object_unref (stream); if (pixbuf == NULL) return NULL; diff --git a/gtk/gdkpixbufutilsprivate.h b/gtk/gdkpixbufutilsprivate.h index 62efe3699e..2c4ab5b305 100644 --- a/gtk/gdkpixbufutilsprivate.h +++ b/gtk/gdkpixbufutilsprivate.h @@ -23,32 +23,26 @@ G_BEGIN_DECLS GdkPixbuf *_gdk_pixbuf_new_from_stream (GInputStream *stream, - const char *format, GCancellable *cancellable, GError **error); GdkPixbuf *_gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream, - const char *format, int width, int height, gboolean aspect, GCancellable *cancellable, GError **error); GdkPixbuf *_gdk_pixbuf_new_from_stream_scaled (GInputStream *stream, - const char *format, double scale, GCancellable *cancellable, GError **error); GdkPixbuf *_gdk_pixbuf_new_from_resource (const char *resource_path, - const char *format, GError **error); GdkPixbuf *_gdk_pixbuf_new_from_resource_at_scale (const char *resource_path, - const char *format, int width, int height, gboolean preserve_aspect, GError **error); GdkPixbuf *_gdk_pixbuf_new_from_resource_scaled (const char *resource_path, - const char *format, double scale, GError **error); diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index 94f1dc4730..eaeb8e4063 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -3789,7 +3789,6 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, &load_error); else source_pixbuf = _gdk_pixbuf_new_from_resource_at_scale (icon->filename, - "svg", pixel_size, pixel_size, TRUE, &load_error); if (source_pixbuf) @@ -3821,7 +3820,6 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, if (stream) { source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, - "svg", pixel_size, pixel_size, TRUE, NULL, &load_error); @@ -3860,14 +3858,12 @@ icon_ensure_texture__locked (GtkIconPaintable *icon, if (icon->is_svg) { source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, - "svg", pixel_size, pixel_size, TRUE, NULL, &load_error); } else source_pixbuf = _gdk_pixbuf_new_from_stream (stream, - g_str_has_suffix (icon->filename, ".xpm") ? "xpm" : "png", NULL, &load_error); g_object_unref (stream); if (source_pixbuf) From b271a94f927c2b7b0c42e58be61d6fdb44480b99 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 03:25:35 +0200 Subject: [PATCH 34/39] texture: Rework error enum 1. Change INSUFFICIENT_MEMORY to TOO_LARGE GTK crashes on insufficient memory, we don't emit GErrors. 2. Split UNSUPPORTED into UNSUPPORTED_CONTENT and UNSUPPORTED_FORMAT So we know if you need to find an RPM with a loader or curse and the weird file. 3. Translate error messages, they are meant for end users. --- gdk/gdktexture.h | 13 +++++++++---- gdk/loaders/gdkjpeg.c | 18 +++++++++--------- gdk/loaders/gdkpng.c | 30 ++++++++++-------------------- gdk/loaders/gdktiff.c | 12 +++++++----- 4 files changed, 35 insertions(+), 38 deletions(-) diff --git a/gdk/gdktexture.h b/gdk/gdktexture.h index aba864e39f..6a62c9fe50 100644 --- a/gdk/gdktexture.h +++ b/gdk/gdktexture.h @@ -45,17 +45,22 @@ GQuark gdk_texture_error_quark (void); /** * GdkTextureError: - * @GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY: Not enough memory to handle this image + * @GDK_TEXTURE_ERROR_TOO_LARGE: Not enough memory to handle this image * @GDK_TEXTURE_ERROR_CORRUPT_IMAGE: The image data appears corrupted - * @GDK_TEXTURE_ERROR_UNSUPPORTED: The image format is not supported + * @GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT: The image contains features + * that cannot be loaded + * @GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT: The image format is not supported * * Possible errors that can be returned by `GdkTexture` constructors. + * + * Since: 4.6 */ typedef enum { - GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, + GDK_TEXTURE_ERROR_TOO_LARGE, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, - GDK_TEXTURE_ERROR_UNSUPPORTED, + GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT, + GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT, } GdkTextureError; GDK_AVAILABLE_IN_ALL diff --git a/gdk/loaders/gdkjpeg.c b/gdk/loaders/gdkjpeg.c index 506a278aaa..c64a0ea99d 100644 --- a/gdk/loaders/gdkjpeg.c +++ b/gdk/loaders/gdkjpeg.c @@ -19,8 +19,10 @@ #include "gdkjpegprivate.h" +#include "gdkintl.h" #include "gdktexture.h" #include "gdkmemorytextureprivate.h" + #include #include #include @@ -53,10 +55,8 @@ fatal_error_handler (j_common_ptr cinfo) if (errmgr->error && *errmgr->error == NULL) g_set_error (errmgr->error, GDK_TEXTURE_ERROR, - cinfo->err->msg_code == JERR_OUT_OF_MEMORY - ? GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY - : GDK_TEXTURE_ERROR_CORRUPT_IMAGE, - "Error interpreting JPEG image file (%s)", buffer); + GDK_TEXTURE_ERROR_CORRUPT_IMAGE, + _("Error interpreting JPEG image file (%s)"), buffer); siglongjmp (errmgr->setjmp_buffer, 1); @@ -213,17 +213,17 @@ gdk_load_jpeg (GBytes *input_bytes, break; default: g_set_error (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED, - "Unsupported colorspace in jpeg (%d)", info.out_color_space); + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT, + _("Unsupported JPEG colorspace (%d)"), info.out_color_space); jpeg_destroy_decompress (&info); return NULL; } if (!data) { - g_set_error_literal (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, - "Not enough memory to load jpeg"); + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE, + _("Not enough memory for image size %ux%u"), width, height); jpeg_destroy_decompress (&info); return NULL; } diff --git a/gdk/loaders/gdkpng.c b/gdk/loaders/gdkpng.c index fa0258b0ba..80fca2460b 100644 --- a/gdk/loaders/gdkpng.c +++ b/gdk/loaders/gdkpng.c @@ -19,9 +19,10 @@ #include "gdkpngprivate.h" +#include "gdkintl.h" +#include "gdkmemorytextureprivate.h" #include "gdktexture.h" #include "gdktextureprivate.h" -#include "gdkmemorytextureprivate.h" #include "gsk/ngl/fp16private.h" #include #include @@ -113,7 +114,7 @@ png_simple_error_callback (png_structp png, if (error && !*error) g_set_error (error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, - "Error reading png (%s)", error_msg); + _("Error reading png (%s)"), error_msg); longjmp (png_jmpbuf (png), 1); } @@ -320,22 +321,11 @@ gdk_load_png (GBytes *bytes, png_malloc_callback, png_free_callback); if (png == NULL) - { - g_set_error (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, - "Failed to parse png image"); - return NULL; - } + g_error ("Out of memory"); info = png_create_info_struct (png); if (info == NULL) - { - png_destroy_read_struct (&png, NULL, NULL); - g_set_error (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, - "Failed to parse png image"); - return NULL; - } + g_error ("Out of memory"); png_set_read_fn (png, &io, png_read_func); @@ -385,8 +375,8 @@ gdk_load_png (GBytes *bytes, { png_destroy_read_struct (&png, &info, NULL); g_set_error (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED, - "Failed to parse png image"); + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT, + _("Failed to parse png image")); return NULL; } @@ -437,9 +427,9 @@ gdk_load_png (GBytes *bytes, g_free (buffer); g_free (row_pointers); png_destroy_read_struct (&png, &info, NULL); - g_set_error_literal (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, - "Not enough memory to load png"); + g_set_error (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE, + _("Not enough memory for image size %ux%u"), width, height); return NULL; } diff --git a/gdk/loaders/gdktiff.c b/gdk/loaders/gdktiff.c index 6f41811791..62d2194f0c 100644 --- a/gdk/loaders/gdktiff.c +++ b/gdk/loaders/gdktiff.c @@ -19,9 +19,11 @@ #include "gdktiffprivate.h" +#include "gdkintl.h" +#include "gdkmemorytextureprivate.h" #include "gdktexture.h" #include "gdktextureprivate.h" -#include "gdkmemorytextureprivate.h" + #include /* Our main interest in tiff as an image format is that it is @@ -374,7 +376,7 @@ load_fallback (TIFF *tif, { g_set_error_literal (error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, - "Failed to load RGB data from TIFF file"); + _("Failed to load RGB data from TIFF file")); g_free (data); return NULL; } @@ -471,8 +473,8 @@ gdk_load_tiff (GBytes *input_bytes, if (!data) { g_set_error (error, - GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY, - "Not enough memory to read tiff"); + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE, + _("Not enough memory for image size %ux%u"), width, height); TIFFClose (tif); return NULL; } @@ -484,7 +486,7 @@ gdk_load_tiff (GBytes *input_bytes, { g_set_error (error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, - "Reading data failed at row %d", y); + _("Reading data failed at row %d"), y); TIFFClose (tif); g_free (data); return NULL; From fae32846c70069ae236afa93c562287ac6e8e9ad Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 03:42:27 +0200 Subject: [PATCH 35/39] texture: Refactor gdk_texture_new_from_bytes() 1. Split into a real loader and a fake one 2. Only try the fake one if the real one doesn't support the file. --- gdk/gdktexture.c | 89 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index a6e79bca56..fe02cb8e63 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -41,6 +41,7 @@ #include "gdktextureprivate.h" #include "gdkinternals.h" +#include "gdkintl.h" #include "gdkmemorytextureprivate.h" #include "gdkpaintable.h" #include "gdksnapshot.h" @@ -418,6 +419,52 @@ gdk_texture_can_load (GBytes *bytes) gdk_is_tiff (bytes); } +static GdkTexture * +gdk_texture_new_from_bytes_internal (GBytes *bytes, + GError **error) +{ + if (gdk_is_png (bytes)) + { + return gdk_load_png (bytes, error); + } + else if (gdk_is_jpeg (bytes)) + { + return gdk_load_jpeg (bytes, error); + } + else if (gdk_is_tiff (bytes)) + { + return gdk_load_tiff (bytes, error); + } + else + { + g_set_error_literal (error, + GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT, + _("Unknown image format.")); + return NULL; + } +} + +static GdkTexture * +gdk_texture_new_from_bytes_pixbuf (GBytes *bytes, + GError **error) +{ + GInputStream *stream; + GdkPixbuf *pixbuf; + GdkTexture *texture; + + stream = g_memory_input_stream_new_from_bytes (bytes); + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); + g_object_unref (stream); + if (pixbuf == NULL) + return NULL; + + texture = gdk_texture_new_for_pixbuf (pixbuf); + g_object_unref (pixbuf); + + return texture; +} + + /** * gdk_texture_new_from_bytes: * @bytes: a `GBytes` containing the data to load @@ -438,38 +485,26 @@ GdkTexture * gdk_texture_new_from_bytes (GBytes *bytes, GError **error) { + GdkTexture *texture; + GError *internal_error = NULL; + g_return_val_if_fail (bytes != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - if (gdk_is_png (bytes)) - { - return gdk_load_png (bytes, error); - } - else if (gdk_is_jpeg (bytes)) - { - return gdk_load_jpeg (bytes, error); - } - else if (gdk_is_tiff (bytes)) - { - return gdk_load_tiff (bytes, error); - } - else - { - GInputStream *stream; - GdkPixbuf *pixbuf; - GdkTexture *texture; + texture = gdk_texture_new_from_bytes_internal (bytes, &internal_error); + if (texture) + return texture; - stream = g_memory_input_stream_new_from_bytes (bytes); - pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error); - g_object_unref (stream); - if (pixbuf == NULL) - return NULL; - - texture = gdk_texture_new_for_pixbuf (pixbuf); - g_object_unref (pixbuf); - - return texture; + if (!g_error_matches (internal_error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT) && + !g_error_matches (internal_error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT)) + { + g_propagate_error (error, internal_error); + return NULL; } + + g_clear_error (&internal_error); + + return gdk_texture_new_from_bytes_pixbuf (bytes, error); } /** From 1b85b5597b09f7a189a321ac0ff117ea51dda981 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 04:05:40 +0200 Subject: [PATCH 36/39] texture: Implement GIcon and GLoadableIcon This is mainly for feature parity with GdkPixbuf. And it doesn't hurt anyone (I hope). --- gdk/gdktexture.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index fe02cb8e63..3f602cc5cc 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -114,9 +114,110 @@ gdk_texture_paintable_init (GdkPaintableInterface *iface) iface->get_intrinsic_height = gdk_texture_paintable_get_intrinsic_height; } +static GVariant * +gdk_texture_icon_serialize (GIcon *icon) +{ + GVariant *result; + GBytes *bytes; + + bytes = gdk_texture_save_to_png_bytes (GDK_TEXTURE (icon)); + result = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, bytes, TRUE); + g_bytes_unref (bytes); + + return g_variant_new ("(sv)", "bytes", result); +} + +static void +gdk_texture_icon_init (GIconIface *iface) +{ + iface->hash = (guint (*) (GIcon *)) g_direct_hash; + iface->equal = (gboolean (*) (GIcon *, GIcon *)) g_direct_equal; + iface->serialize = gdk_texture_icon_serialize; +} + +static GInputStream * +gdk_texture_loadable_icon_load (GLoadableIcon *icon, + int size, + char **type, + GCancellable *cancellable, + GError **error) +{ + GInputStream *stream; + GBytes *bytes; + + bytes = gdk_texture_save_to_png_bytes (GDK_TEXTURE (icon)); + stream = g_memory_input_stream_new_from_bytes (bytes); + g_bytes_unref (bytes); + + if (type) + *type = NULL; + + return stream; +} + +static void +gdk_texture_loadable_icon_load_in_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GInputStream *stream; + GBytes *bytes; + + bytes = gdk_texture_save_to_png_bytes (source_object); + stream = g_memory_input_stream_new_from_bytes (bytes); + g_bytes_unref (bytes); + g_task_return_pointer (task, stream, g_object_unref); +} + +static void +gdk_texture_loadable_icon_load_async (GLoadableIcon *icon, + int size, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (icon, cancellable, callback, user_data); + g_task_run_in_thread (task, gdk_texture_loadable_icon_load_in_thread); + g_object_unref (task); +} + +static GInputStream * +gdk_texture_loadable_icon_load_finish (GLoadableIcon *icon, + GAsyncResult *res, + char **type, + GError **error) +{ + GInputStream *result; + + g_return_val_if_fail (g_task_is_valid (res, icon), NULL); + + result = g_task_propagate_pointer (G_TASK (res), error); + if (result == NULL) + return NULL; + + if (type) + *type = NULL; + + return result; +} + +static void +gdk_texture_loadable_icon_init (GLoadableIconIface *iface) +{ + iface->load = gdk_texture_loadable_icon_load; + iface->load_async = gdk_texture_loadable_icon_load_async; + iface->load_finish = gdk_texture_loadable_icon_load_finish; +} + G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GdkTexture, gdk_texture, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, - gdk_texture_paintable_init)) + gdk_texture_paintable_init) + G_IMPLEMENT_INTERFACE (G_TYPE_ICON, + gdk_texture_icon_init) + G_IMPLEMENT_INTERFACE (G_TYPE_LOADABLE_ICON, gdk_texture_loadable_icon_init)) #define GDK_TEXTURE_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \ g_critical ("Texture of type '%s' does not implement GdkTexture::" # method, G_OBJECT_TYPE_NAME (obj)) From 37a7c6780ace8ae719cd10bc46898bd23a2b0f04 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 04:07:59 +0200 Subject: [PATCH 37/39] icontheme: Implement gtk_icon_theme_lookup_by_gicon() for textures More feature parity. Before this, it would have worked just as well - just going via a PNG bytes step inbetween. --- gtk/gtkicontheme.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index eaeb8e4063..1c2d83125c 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -4084,20 +4084,6 @@ gtk_icon_paintable_new_for_file (GFile *file, return icon; } -static GtkIconPaintable * -gtk_icon_paintable_new_for_pixbuf (GtkIconTheme *icon_theme, - GdkPixbuf *pixbuf, - int size, - int scale) -{ - GtkIconPaintable *icon; - - icon = icon_paintable_new (NULL, size, scale); - icon->texture = gdk_texture_new_for_pixbuf (pixbuf); - - return icon; -} - /** * gtk_icon_theme_lookup_by_gicon: * @self: a `GtkIconTheme` @@ -4135,11 +4121,15 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *self, gicon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (gicon)); g_assert (gicon); /* shut up gcc -Wnull-dereference */ - if (GDK_IS_PIXBUF (gicon)) + if (GDK_IS_TEXTURE (gicon)) { - GdkPixbuf *pixbuf = GDK_PIXBUF (gicon); - - icon = gtk_icon_paintable_new_for_pixbuf (self, pixbuf, size, scale); + icon = icon_paintable_new (NULL, size, scale); + icon->texture = g_object_ref (GDK_TEXTURE (icon)); + } + else if (GDK_IS_PIXBUF (gicon)) + { + icon = icon_paintable_new (NULL, size, scale); + icon->texture = gdk_texture_new_for_pixbuf (GDK_PIXBUF (icon)); } else if (G_IS_FILE_ICON (gicon)) { From 0428d8ccd6049e4424e7cb657859d4b2ae6b816a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 04:10:56 +0200 Subject: [PATCH 38/39] examples: Use textures over pixbufs --- examples/bp/bloatpad.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/bp/bloatpad.c b/examples/bp/bloatpad.c index 42d7b45f15..e923b444c9 100644 --- a/examples/bp/bloatpad.c +++ b/examples/bp/bloatpad.c @@ -603,8 +603,8 @@ bloat_pad_startup (GApplication *application) g_object_unref (icon); g_bytes_unref (bytes); - icon = G_ICON (gdk_pixbuf_new_from_resource ("/org/gtk/libgtk/icons/16x16/actions/folder-new.png", NULL)); - item = g_menu_item_new ("Pixbuf", NULL); + icon = G_ICON (gdk_texture_new_from_resource ("/org/gtk/libgtk/icons/16x16/actions/folder-new.png")); + item = g_menu_item_new ("Texture", NULL); g_menu_item_set_icon (item, icon); g_menu_append_item (menu, item); g_object_unref (item); From 3914ecbd0fdceb60d83e30de5f08124b45b2cd5b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 17 Sep 2021 04:15:40 +0200 Subject: [PATCH 39/39] gtk-demo: Use textures in listbox example --- demos/gtk-demo/listbox.c | 11 ++++------- demos/gtk-demo/listbox.ui | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/demos/gtk-demo/listbox.c b/demos/gtk-demo/listbox.c index 750f5f480c..98a6a26611 100644 --- a/demos/gtk-demo/listbox.c +++ b/demos/gtk-demo/listbox.c @@ -8,7 +8,7 @@ #include #include -static GdkPixbuf *avatar_pixbuf_other; +static GdkTexture *avatar_texture_other; static GtkWidget *window = NULL; #define GTK_TYPE_MESSAGE (gtk_message_get_type ()) @@ -196,12 +196,9 @@ gtk_message_row_update (GtkMessageRow *row) gtk_button_set_label (GTK_BUTTON (priv->resent_by_button), priv->message->resent_by); if (strcmp (priv->message->sender_nick, "@GTKtoolkit") == 0) - { - gtk_image_set_from_icon_name (priv->avatar_image, "org.gtk.Demo4"); - gtk_image_set_icon_size (priv->avatar_image, GTK_ICON_SIZE_LARGE); - } + gtk_image_set_from_icon_name (priv->avatar_image, "org.gtk.Demo4"); else - gtk_image_set_from_pixbuf (priv->avatar_image, avatar_pixbuf_other); + gtk_image_set_from_paintable (priv->avatar_image, GDK_PAINTABLE (avatar_texture_other)); } @@ -344,7 +341,7 @@ do_listbox (GtkWidget *do_widget) if (!window) { - avatar_pixbuf_other = gdk_pixbuf_new_from_resource_at_scale ("/listbox/apple-red.png", 32, 32, FALSE, NULL); + avatar_texture_other = gdk_texture_new_from_resource ("/listbox/apple-red.png"); window = gtk_window_new (); gtk_window_set_display (GTK_WINDOW (window), diff --git a/demos/gtk-demo/listbox.ui b/demos/gtk-demo/listbox.ui index a237651d82..9b5199ef7d 100644 --- a/demos/gtk-demo/listbox.ui +++ b/demos/gtk-demo/listbox.ui @@ -25,6 +25,7 @@ 8 8 image-missing + large 0 0