[gtk/gamma-shenanigans: 10/11] Support loading and saving float textures




commit b00c1ba73b69282a475423870af7796dc1fccd49
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..f82215d709
--- /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 __RGBE_H__
+#define __RGBE_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]