From 427a730e7873146083c8ed2d19478aa94f17db64 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 6 Sep 2024 20:37:43 -0400 Subject: [PATCH] Add a texture dump format --- tools/gtk-image-tool-avif.c | 214 ++++++++++++++++++++++++++++++ tools/gtk-image-tool-compare.c | 5 +- tools/gtk-image-tool-convert.c | 4 +- tools/gtk-image-tool-dump.c | 230 +++++++++++++++++++++++++++++++++ tools/gtk-image-tool-utils.c | 5 +- tools/gtk-image-tool.h | 7 + tools/meson.build | 1 + 7 files changed, 463 insertions(+), 3 deletions(-) create mode 100644 tools/gtk-image-tool-avif.c create mode 100644 tools/gtk-image-tool-dump.c diff --git a/tools/gtk-image-tool-avif.c b/tools/gtk-image-tool-avif.c new file mode 100644 index 0000000000..839216d082 --- /dev/null +++ b/tools/gtk-image-tool-avif.c @@ -0,0 +1,214 @@ +#include +#include + +gboolean +gdk_texture_save_to_avif (GtkTexture *texture, + const char *filename) +{ + avifEncoder *encoder; + avifImage *image; + avifRWData output = { NULL, 0 }; + gboolean res; + uint32_t depth; + avifPixelFormat pixelFormat; + + switch (gdk_texture_get_format (texture)) + { + case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED: + depth = 8; + premultiplied = TRUE; + break; + case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED: + depth = 8; + premultiplied = TRUE; + break; + case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED: + depth = 8; + premultiplied = TRUE; + break; + case GDK_MEMORY_B8G8R8A8: + depth = 8; + break; + case GDK_MEMORY_A8R8G8B8: + depth = 8; + break; + case GDK_MEMORY_R8G8B8A8: + depth = 8; + break; + case GDK_MEMORY_A8B8G8R8: + depth = 8; + break; + case GDK_MEMORY_R8G8B8: + depth = 8; + break; + case GDK_MEMORY_B8G8R8: + depth = 8; + break; + case GDK_MEMORY_R16G16B16: + depth = 16; + break; + case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: + depth = 16; + premultiplied = TRUE; + break; + case GDK_MEMORY_R16G16B16A16: + depth = 16; + break; + case GDK_MEMORY_R16G16B16_FLOAT: + depth = 16; + break; + case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: + depth = 16; + premultiplied = TRUE; + break; + case GDK_MEMORY_R16G16B16A16_FLOAT: + depth = 16; + break; + case GDK_MEMORY_R32G32B32_FLOAT: + depth = 32; + break; + case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: + depth = 32; + premultiplied = TRUE; + break; + case GDK_MEMORY_R32G32B32A32_FLOAT: + depth = 32; + break; + case GDK_MEMORY_G8A8_PREMULTIPLIED: + depth = 32; + break; + case GDK_MEMORY_G8A8: + depth = 8; + break; + case GDK_MEMORY_G8: + depth = 8; + break; + case GDK_MEMORY_G16A16_PREMULTIPLIED: + depth = 16; + premultiplied = TRUE; + break; + case GDK_MEMORY_G16A16: + depth = 16; + break; + case GDK_MEMORY_G16: + depth = 16; + break; + case GDK_MEMORY_A8: + depth = 8; + break; + case GDK_MEMORY_A16: + depth = 16; + break; + case GDK_MEMORY_A16_FLOAT: + depth = 16; + break; + case GDK_MEMORY_A32_FLOAT: + depth = 32; + break; + case GDK_MEMORY_A8B8G8R8_PREMULTIPLIED: + depth = 8; + premultiplied = TRUE; + break; + case GDK_MEMORY_B8G8R8X8: + depth = 8; + break; + case GDK_MEMORY_X8R8G8B8: + depth = 8; + break; + case GDK_MEMORY_R8G8B8X8: + depth = 8; + break; + case GDK_MEMORY_X8B8G8R8: + depth = 8; + break; + } + + image = avifImageCreate (gdk_texture_get_width (texture), + gdk_texture_get_height (texture), + depth, + pixelFormat); + + encoder = avifEncoderCreate (); + avifEncoderWrite (encoder, image, &output); + avifEncoderDestroy (encoder); + + res = g_file_set_contents (filename, output.data, output.size, NULL); + + g_free (output.data); + + return res; +} + +GdkTexture * +gdk_load_avif (GBytes *bytes) +{ + avifDecoder *decoder; + GdkTexture *texture; + + decoder = avifDecoderCreate (); + + avifDecoderSetSource (decoder, AVIF_DECODER_SOURCE_PRIMARY_ITEM); + + decoder->ignoreExif = 1; + decoder->ignoreXMP = 1; + + if (avifDecoderSetIOMemory (decoder, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes)) != AVIF_RESULT_OK) + { + avifDecoderDestroy (decoder); + return NULL; + } + + if (avifDecoderParse (decoder) != AVIF_RESULT_OK) + { + avifDecoderDestroy (decoder); + return NULL; + } + + builder = gdk_memory_texture_builder_new (); + + gdk_memory_texture_builder_set_width (builder, decoder->image->width); + gdk_memory_texture_builder_set_height (builder, decoder->image->height); + + GdkMemoryFormat format; + + if (decoder->image->alphaPlane) + { + if (decoder->image->alpha->alphaPremultiplied) + { + if (decoder->image->depth > 8) + format = GDK_MEMORY_R16G16B16A16_PREMULTIPLIED; + else + format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; + } + else + { + if (decoder->image->depth > 8) + format = GDK_MEMORY_R16G16B16A16; + else + format = GDK_MEMORY_R8G8B8A8; + } + } + else + { + if (decoder->image->depth > 8) + format = GDK_MEMORY_R16G16B16; + else + format = GDK_MEMORY_R8G8B8; + } + + gdk_memory_texture_builder_set_format (builder, format); + +// Now available: + // * All decoder->image information other than pixel data: + // * width, height, depth + // * transformations (pasp, clap, irot, imir) + // * color profile (icc, CICP) + // * metadata (Exif, XMP) + // * decoder->alphaPresent + // * number of total images in the AVIF (decoder->imageCount) + // * overall image sequence timing (including per-frame timing with avifDecoderNthImageTiming()) + + avifDecorderDestroy (decoder); + + return texture; +} diff --git a/tools/gtk-image-tool-compare.c b/tools/gtk-image-tool-compare.c index 79de7895e2..974f5512d4 100644 --- a/tools/gtk-image-tool-compare.c +++ b/tools/gtk-image-tool-compare.c @@ -79,7 +79,10 @@ do_compare (int *argc, for (int i = 0; i < 2; i++) { - texture[i] = gdk_texture_new_from_filename (filenames[i], &error); + if (g_str_has_suffix (filenames[i], ".texture")) + texture[i] = gdk_texture_undump (filenames[i], &error); + else + texture[i] = gdk_texture_new_from_filename (filenames[i], &error); if (texture[i] == NULL) { g_printerr (_("Failed to load %s: %s\n"), filenames[i], error->message); diff --git a/tools/gtk-image-tool-convert.c b/tools/gtk-image-tool-convert.c index 79e33050ff..2af8228e40 100644 --- a/tools/gtk-image-tool-convert.c +++ b/tools/gtk-image-tool-convert.c @@ -61,7 +61,9 @@ save_image (const char *filename, texture = gdk_memory_texture_builder_build (builder); - if (g_str_has_suffix (output, ".tiff")) + if (g_str_has_suffix (output, ".texture")) + gdk_texture_dump (texture, output, NULL); + else if (g_str_has_suffix (output, ".tiff")) gdk_texture_save_to_tiff (texture, output); else gdk_texture_save_to_png (texture, output); diff --git a/tools/gtk-image-tool-dump.c b/tools/gtk-image-tool-dump.c new file mode 100644 index 0000000000..8882f7cebb --- /dev/null +++ b/tools/gtk-image-tool-dump.c @@ -0,0 +1,230 @@ +/* Copyright 2024 Red Hat, Inc. + * + * GTK 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. + * + * GTK 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 GTK; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include "gtk-image-tool.h" + + +#define ALIGN(x,y) (((x + y - 1) / y) * y) + +static guint +gdk_format_get_bpp (GdkMemoryFormat format) +{ + guint bpp[] = { + 4, 4, 4, 4, 4, 4, 4, + 3, 3, + 6, 8, 8, + 6, 8, 8, + 12, 16, 16, + 2, 2, 1, + 4, 4, 2, 1, + 2, 2, + 4, + 4, 4, 4, 4, + 0 + }; + + g_assert (format < G_N_ELEMENTS (bpp)); + + return bpp[format]; +} + +gboolean +gdk_texture_dump (GdkTexture *texture, + const char *filename, + GError **error) +{ + GString *header; + GdkColorState *cs; + GdkCicpParams *params; + GdkTextureDownloader *downloader; + GdkMemoryFormat format; + gsize width, height; + gsize stride; + guint bpp; + char buf[20]; + guchar *data; + gboolean res; + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + format = gdk_texture_get_format (texture); + + bpp = gdk_format_get_bpp (format); + stride = ALIGN (width * bpp, 8); + + cs = gdk_texture_get_color_state (texture); + params = gdk_color_state_create_cicp_params (cs); + + header = g_string_new ("GTK texture dump\n\n"); + + g_string_append_printf (header, "offset XXXX\n"); + g_string_append_printf (header, "stride %lu\n", stride); + g_string_append_printf (header, "size %lu %lu\n", width, height); + g_string_append_printf (header, "format %u\n", format); + g_string_append_printf (header, "cicp %u/%u/%u/%u\n\n", + gdk_cicp_params_get_color_primaries (params), + gdk_cicp_params_get_transfer_function (params), + gdk_cicp_params_get_matrix_coefficients (params), + gdk_cicp_params_get_range (params)); + + g_snprintf (buf, sizeof (buf), "%4lu", header->len); + g_string_replace (header, "XXXX", buf, 1); + + downloader = gdk_texture_downloader_new (texture); + gdk_texture_downloader_set_format (downloader, format); + gdk_texture_downloader_set_color_state (downloader, cs); + + data = g_malloc (header->len + height * stride); + + memcpy (data, header->str, header->len); + + gdk_texture_downloader_download_into (downloader, data + header->len, stride); + + res = g_file_set_contents (filename, (char *) data, header->len + height * stride, error); + + gdk_texture_downloader_free (downloader); + g_object_unref (params); + gdk_color_state_unref (cs); + g_free (data); + + return res; +} + +GdkTexture * +gdk_texture_undump (const char *filename, + GError **error) +{ + char *data; + guint parts; + gsize len; + gsize offset, stride, width, height; + GdkMemoryFormat format; + guint cp, tf, mc, range; + GBytes *bytes; + GdkCicpParams *params; + GdkColorState *cs; + GdkMemoryTextureBuilder *builder; + guint bpp; + GdkTexture *texture; + + if (!g_file_get_contents (filename, &data, &len, error)) + return NULL; + + parts = sscanf (data, + "GTK texture dump\n\n" + "offset %lu\n" + "stride %lu\n" + "size %lu %lu\n" + "format %u\n" + "cicp %u/%u/%u/%u\n\n", + &offset, + &stride, + &width, &height, + &format, + &cp, &tf, &mc, &range); + + if (parts < 9) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to parse header (after %d parts)", parts); + g_free (data); + return NULL; + } + + if (format >= GDK_MEMORY_N_FORMATS) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid memory format (%u)", format); + g_free (data); + return NULL; + } + + if (width == 0 || height == 0) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid size (%lu x %lu)", width, height); + g_free (data); + return NULL; + } + + bpp = gdk_format_get_bpp (format); + + if (stride < bpp * width) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid stride (%lu < %u * %lu)", stride, bpp, width); + g_free (data); + return NULL; + } + + if (len != offset + stride * height) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid size (%lu != %lu + %lu * %lu)", len, offset, stride, height); + g_free (data); + return NULL; + } + + params = gdk_cicp_params_new (); + gdk_cicp_params_set_color_primaries (params, cp); + gdk_cicp_params_set_transfer_function (params, tf); + gdk_cicp_params_set_matrix_coefficients (params, mc); + gdk_cicp_params_set_range (params, (GdkCicpRange) range); + cs = gdk_cicp_params_build_color_state (params, error); + g_object_unref (params); + + if (cs == NULL) + { + g_free (data); + return NULL; + } + + bytes = g_bytes_new_with_free_func (data + offset, stride * height, g_free, data); + + builder = gdk_memory_texture_builder_new (); + gdk_memory_texture_builder_set_bytes (builder, bytes); + gdk_memory_texture_builder_set_stride (builder, stride); + gdk_memory_texture_builder_set_width (builder, width); + gdk_memory_texture_builder_set_height (builder, height); + gdk_memory_texture_builder_set_format (builder, format); + gdk_memory_texture_builder_set_color_state (builder, cs); + + texture = gdk_memory_texture_builder_build (builder); + + g_object_unref (builder); + g_bytes_unref (bytes); + gdk_color_state_unref (cs); + + return texture; +} diff --git a/tools/gtk-image-tool-utils.c b/tools/gtk-image-tool-utils.c index d5a2f1e82c..af64bba974 100644 --- a/tools/gtk-image-tool-utils.c +++ b/tools/gtk-image-tool-utils.c @@ -36,7 +36,10 @@ load_image_file (const char *filename) GError *error = NULL; GdkTexture *texture; - texture = gdk_texture_new_from_filename (filename, &error); + if (g_str_has_suffix (filename, ".texture")) + texture = gdk_texture_undump (filename, &error); + else + texture = gdk_texture_new_from_filename (filename, &error); if (!texture) { g_printerr ("%s\n", error->message); diff --git a/tools/gtk-image-tool.h b/tools/gtk-image-tool.h index 7ad1a61901..755e6a2865 100644 --- a/tools/gtk-image-tool.h +++ b/tools/gtk-image-tool.h @@ -20,3 +20,10 @@ char * get_color_state_name (GdkColorState *color_state); GdkColorState *parse_cicp_tuple (const char *cicp_tuple, GError **error); + +gboolean gdk_texture_dump (GdkTexture *texture, + const char *filename, + GError **error); + +GdkTexture *gdk_texture_undump (const char *filename, + GError **error); diff --git a/tools/meson.build b/tools/meson.build index a5cfd739f0..95608eb2ef 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -56,6 +56,7 @@ gtk_tools = [ 'gtk-image-tool-relabel.c', 'gtk-image-tool-show.c', 'gtk-image-tool-utils.c', + 'gtk-image-tool-dump.c', '../testsuite/reftests/reftest-compare.c'], [libgtk_dep] ], ['gtk4-update-icon-cache', ['updateiconcache.c', '../gtk/gtkiconcachevalidator.c' ] + extra_update_icon_cache_objs, [ libgtk_dep ] ], ['gtk4-encode-symbolic-svg', ['encodesymbolic.c'], [ libgtk_static_dep ] ],