[gtk/gamma-shenanigans] Support loading and saving float textures
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/gamma-shenanigans] Support loading and saving float textures
- Date: Mon, 6 Sep 2021 23:52:24 +0000 (UTC)
commit 1664e8380797be4fe9628ac6d6ab7c42f814f681
Author: Matthias Clasen <mclasen redhat com>
Date: Mon Sep 6 19:49:56 2021 -0400
Support loading and saving float textures
Add support for the radiance rgbe file format
to gdk_texture_new_from_file, and add
gdk_texture_save_to_rgbe.
This format supports floating point data.
gdk/gdktexture.c | 85 +++++++++++++++-
gdk/gdktexture.h | 3 +
gdk/meson.build | 1 +
gdk/rgbe.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
gdk/rgbe.h | 34 +++++++
5 files changed, 414 insertions(+), 1 deletion(-)
---
diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c
index d12c5af02b..dc2550513f 100644
--- a/gdk/gdktexture.c
+++ b/gdk/gdktexture.c
@@ -47,6 +47,7 @@
#include <graphene.h>
#include <png.h>
+#include "rgbe.h"
/* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */
void
@@ -402,6 +403,41 @@ gdk_texture_new_from_png (GFile *file,
return texture;
}
+static GdkTexture *
+gdk_texture_new_from_rgbe (GFile *file,
+ GError **error)
+{
+ GBytes *contents;
+ GBytes *bytes;
+ GdkTexture *texture;
+ float *data;
+ int width, height;
+
+ contents = g_file_load_bytes (file, NULL, NULL, error);
+ if (!contents)
+ return NULL;
+
+ data = load_rgbe (g_bytes_get_data (contents, NULL),
+ g_bytes_get_size (contents),
+ &width, &height,
+ error);
+
+ g_bytes_unref (contents);
+
+ if (!data)
+ return NULL;
+
+ bytes = g_bytes_new_take (data, width * height * 3 * sizeof (float));
+
+ texture = gdk_memory_texture_new (width, height,
+ GDK_MEMORY_R32G32B32_FLOAT,
+ bytes, width * 3 * sizeof (float));
+
+ g_bytes_unref (bytes);
+
+ return texture;
+}
+
/**
* gdk_texture_new_from_file:
* @file: `GFile` to load
@@ -442,7 +478,7 @@ gdk_texture_new_from_file (GFile *file,
g_object_unref (stream);
stream = buffered;
- g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (stream), 8, NULL, NULL);
+ g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (stream), 10, NULL, NULL);
data = g_buffered_input_stream_peek_buffer (G_BUFFERED_INPUT_STREAM (stream), &size);
if (png_check_sig (data, 8))
@@ -451,6 +487,12 @@ gdk_texture_new_from_file (GFile *file,
return gdk_texture_new_from_png (file, error);
}
+ if (strncmp (data, "#?RADIANCE", 10) == 0)
+ {
+ g_object_unref (stream);
+ return gdk_texture_new_from_rgbe (file, error);
+ }
+
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
g_object_unref (stream);
if (pixbuf == NULL)
@@ -677,3 +719,44 @@ gdk_texture_save_to_png (GdkTexture *texture,
return result;
}
+
+/**
+ * gdk_texture_save_to_rgbe:
+ * @texture: a `GdkTexture`
+ * @filename: (type filename): the filename to store to
+ *
+ * Store the given @texture to the @filename as a RGBE file.
+ *
+ * This currently works only for floating point textures.
+ *
+ * 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.
+ *
+ * Returns: %TRUE if saving succeeded, %FALSE on failure.
+ */
+gboolean
+gdk_texture_save_to_rgbe (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_texture_download_format (texture, GDK_MEMORY_R32G32B32_FLOAT);
+ if (!bytes)
+ return FALSE;
+
+ result = save_rgbe (filename,
+ g_bytes_get_data (bytes, NULL),
+ gdk_texture_get_width (texture),
+ gdk_texture_get_height (texture),
+ NULL);
+
+ g_bytes_unref (bytes);
+
+ return result;
+}
diff --git a/gdk/gdktexture.h b/gdk/gdktexture.h
index f3d0bc9765..18d811fea4 100644
--- a/gdk/gdktexture.h
+++ b/gdk/gdktexture.h
@@ -62,6 +62,9 @@ void gdk_texture_download (GdkTexture
GDK_AVAILABLE_IN_ALL
gboolean gdk_texture_save_to_png (GdkTexture *texture,
const char *filename);
+GDK_AVAILABLE_IN_4_6
+gboolean gdk_texture_save_to_rgbe (GdkTexture *texture,
+ const char *filename);
G_END_DECLS
diff --git a/gdk/meson.build b/gdk/meson.build
index 6539c225f4..1f9e3db155 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -50,6 +50,7 @@ gdk_public_sources = files([
'gdktoplevelsize.c',
'gdktoplevel.c',
'gdkdragsurface.c',
+ 'rgbe.c'
])
gdk_public_headers = files([
diff --git a/gdk/rgbe.c b/gdk/rgbe.c
new file mode 100644
index 0000000000..93b517ef53
--- /dev/null
+++ b/gdk/rgbe.c
@@ -0,0 +1,292 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "rgbe.h"
+
+#include <glib.h>
+#include <math.h>
+#include <gio/gio.h>
+
+/* Somewhat minimal support for loading and saving rgbe data */
+
+static gboolean
+load_line (const char **p,
+ unsigned char *line,
+ int width)
+{
+ const char *q = *p;
+
+ /* check for the marker */
+ if (q[0] != 2 || q[1] != 2)
+ {
+ if (q[0] == 1 && q[1] == 1 && q[2] == 1)
+ {
+ /* this is the old scanline format */
+ return FALSE;
+ }
+ else
+ {
+ memcpy (line, q, width * 4);
+ *p += width * 4;
+ return TRUE;
+ }
+ }
+
+ /* skip the marker */
+ q += 4;
+
+ for (int i = 0; i < 4; i++)
+ {
+ for (int j = 0; j < width; )
+ {
+ guchar c = *q++;
+ (*p)++;
+ if (c > 128)
+ {
+ guchar val = *q++;
+ c &= 0x7f;
+ while (c--)
+ {
+ g_assert (j < width);
+ line[4*j + i] = val;
+ j++;
+ }
+ }
+ else
+ {
+ while (c--)
+ {
+ g_assert (j < width);
+ line[4*j + i] = *q++;
+ j++;
+ }
+ }
+ }
+ }
+
+ *p = q;
+
+ return TRUE;
+}
+
+static void
+decode_rgbe (unsigned char *rgbe, float *c)
+{
+ float f;
+
+ if (rgbe[3] == 0)
+ {
+ c[0] = c[1] = c[2] = 0.f;
+ return;
+ }
+
+ f = ldexpf (1.f, rgbe[3] - (128 + 8));
+
+ c[0] = rgbe[0] * f;
+ c[1] = rgbe[1] * f;
+ c[2] = rgbe[2] * f;
+}
+
+static inline void
+encode_rgbe (const float *c, unsigned char *rgbe)
+{
+ float max, frac;
+ int e;
+
+ max = MAX (MAX (c[0], c[1]), c[2]);
+
+ if (max <= 1e-38)
+ {
+ rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
+ return;
+ }
+
+ frac = frexp (max, &e) * 256.0 / max;
+
+ rgbe[0] = c[0] * frac;
+ rgbe[1] = c[1] * frac;
+ rgbe[2] = c[2] * frac;
+ rgbe[3] = e + 128;
+}
+
+/* Convert a line of RGBA data to half-floats, and store them
+ * as row y in data.
+ */
+static gboolean
+convert_line (unsigned char *line,
+ int width,
+ float *data,
+ int y)
+{
+ for (int x = 0; x < width; x++)
+ decode_rgbe (&line[x * 4], &data[(y * width + x) * 3]);
+
+ return TRUE;
+}
+
+float *
+load_rgbe (const char *data,
+ gsize size,
+ int *out_width,
+ int *out_height,
+ GError **error)
+{
+ const char *p;
+ char *p2;
+ int width, height;
+ float *out_data;
+ unsigned char *line;
+
+ if (strncmp (data, "#?RADIANCE", sizeof ("#?RADIANCE") - 1) != 0)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Not a radiance file (no marker)");
+ return NULL;
+ }
+
+ p = data;
+ do
+ {
+ p = strchr (p, '\n');
+ if (!p)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bad radiance file (no size line)");
+ return NULL;
+ }
+ p++;
+ }
+ while (strncmp (p, "-Y ", 3) != 0);
+
+ p += 3;
+ height = g_ascii_strtoll (p, &p2, 10);
+ p = p2;
+
+ if (strncmp (p2, " +X ", 4) != 0)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bad radiance file (could not parse size line)");
+ return NULL;
+ }
+
+ p += 4;
+ width = g_ascii_strtoll (p, &p2, 10);
+ p = p2;
+
+ if (height < 0 || width < 0)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bad radiance file (negative dimensions)");
+ return NULL;
+ }
+
+ while (*p != '\n')
+ {
+ if (*p == '\0')
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bad radiance file (no data)");
+ return NULL;
+ }
+
+ p++;
+ }
+
+ p++;
+
+ /* p points at the binary data now */
+ out_data = g_malloc (width * height * 3 * 4);
+
+ line = g_new (unsigned char, 4 * width + 4);
+ for (int y = 0; y < height; y++)
+ {
+ if (!load_line (&p, line, width))
+ {
+ g_free (out_data);
+ g_free (line);
+ g_set_error (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bad radiance file (failed to load line %d)", y);
+ return NULL;
+ }
+
+ if (!convert_line (line, width, out_data, y))
+ {
+ g_free (out_data);
+ g_free (line);
+ g_set_error (error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bad radiance file (failed to convert line %d)", y);
+ return NULL;
+ }
+ }
+
+ g_free (line);
+
+ *out_width = width;
+ *out_height = height;
+
+ return out_data;
+}
+
+gboolean
+save_rgbe (const char *file,
+ const float *data,
+ int width,
+ int height,
+ GError **error)
+{
+ char *preamble;
+ gsize preamble_len;
+ gsize out_data_len;
+ gboolean result;
+ unsigned char *out_data;
+
+ preamble = g_strdup_printf ("#?RADIANCE\n"
+ "SOFTWARE=GTK\n"
+ "FORMAT=32-bit_rle_rgbe\n"
+ "\n"
+ "-Y %d +X %d\n",
+ height, width);
+
+ preamble_len = strlen (preamble);
+
+ out_data_len = preamble_len + width * height * 4 + 1;
+ out_data = g_realloc (preamble, out_data_len);
+
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ encode_rgbe (&data[(y * width + x) * 3],
+ &out_data[preamble_len + (y * width + x) * 4]);
+ }
+ }
+
+ result = g_file_set_contents (file, (char *)out_data, out_data_len, error);
+
+ g_free (out_data);
+
+ return result;
+}
diff --git a/gdk/rgbe.h b/gdk/rgbe.h
new file mode 100644
index 0000000000..1ff3ef04ab
--- /dev/null
+++ b/gdk/rgbe.h
@@ -0,0 +1,34 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_PROFILER_PRIVATE_H__
+#define __GDK_PROFILER_PRIVATE_H__
+
+#include <glib.h>
+
+float * load_rgbe (const char *data,
+ gsize size,
+ int *out_width,
+ int *out_height,
+ GError **error);
+gboolean save_rgbe (const char *file,
+ const float *data,
+ int width,
+ int height,
+ GError **error);
+
+#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]