Add a texture dump format

This commit is contained in:
Matthias Clasen 2024-09-06 20:37:43 -04:00
parent c269b8650f
commit 427a730e78
7 changed files with 463 additions and 3 deletions

214
tools/gtk-image-tool-avif.c Normal file
View File

@ -0,0 +1,214 @@
#include <gtk.h>
#include <avif/avif.h>
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;
}

View File

@ -79,6 +79,9 @@ do_compare (int *argc,
for (int i = 0; i < 2; i++)
{
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)
{

View File

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

230
tools/gtk-image-tool-dump.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#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;
}

View File

@ -36,6 +36,9 @@ load_image_file (const char *filename)
GError *error = NULL;
GdkTexture *texture;
if (g_str_has_suffix (filename, ".texture"))
texture = gdk_texture_undump (filename, &error);
else
texture = gdk_texture_new_from_filename (filename, &error);
if (!texture)
{

View File

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

View File

@ -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 ] ],