[gtk/gamma-shenanigans: 11/11] Lossless loading and saving for float textures




commit 2fdfcf4242fb1af5bf289f29531463434d03c98e
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Sep 6 22:22:19 2021 -0400

    Lossless loading and saving for float textures
    
    Add support for pfm file format, which saves
    32bit floats without loss.
    
    The format does not support alpha, so we will
    have to replace or extend it when we gain a
    floating point texture format with alpha.

 gdk/gdktexture.c |  83 ++++++++++++++++++++++++++
 gdk/gdktexture.h |   3 +
 gdk/meson.build  |   3 +-
 gdk/pfm.c        | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gdk/pfm.h        |  34 +++++++++++
 5 files changed, 296 insertions(+), 1 deletion(-)
---
diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c
index dc2550513f..a09d6bf167 100644
--- a/gdk/gdktexture.c
+++ b/gdk/gdktexture.c
@@ -48,6 +48,7 @@
 #include <graphene.h>
 #include <png.h>
 #include "rgbe.h"
+#include "pfm.h"
 
 /* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */
 void
@@ -438,6 +439,41 @@ gdk_texture_new_from_rgbe (GFile   *file,
   return texture;
 }
 
+static GdkTexture *
+gdk_texture_new_from_pfm (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_pfm (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
@@ -493,6 +529,12 @@ gdk_texture_new_from_file (GFile   *file,
       return gdk_texture_new_from_rgbe (file, error);
     }
 
+  if (strncmp (data, "PF\n", 3) == 0)
+    {
+      g_object_unref (stream);
+      return gdk_texture_new_from_pfm (file, error);
+    }
+
   pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
   g_object_unref (stream);
   if (pixbuf == NULL)
@@ -760,3 +802,44 @@ gdk_texture_save_to_rgbe (GdkTexture *texture,
 
   return result;
 }
+
+/**
+ * gdk_texture_save_to_pfm:
+ * @texture: a `GdkTexture`
+ * @filename: (type filename): the filename to store to
+ *
+ * Store the given @texture to the @filename as a PFM 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_pfm (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_pfm (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 18d811fea4..c174750843 100644
--- a/gdk/gdktexture.h
+++ b/gdk/gdktexture.h
@@ -65,6 +65,9 @@ gboolean                gdk_texture_save_to_png                (GdkTexture
 GDK_AVAILABLE_IN_4_6
 gboolean                gdk_texture_save_to_rgbe               (GdkTexture      *texture,
                                                                 const char      *filename);
+GDK_AVAILABLE_IN_4_6
+gboolean                gdk_texture_save_to_pfm                (GdkTexture      *texture,
+                                                                const char      *filename);
 
 G_END_DECLS
 
diff --git a/gdk/meson.build b/gdk/meson.build
index 1f9e3db155..5b7c9c048f 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -50,7 +50,8 @@ gdk_public_sources = files([
   'gdktoplevelsize.c',
   'gdktoplevel.c',
   'gdkdragsurface.c',
-  'rgbe.c'
+  'rgbe.c',
+  'pfm.c',
 ])
 
 gdk_public_headers = files([
diff --git a/gdk/pfm.c b/gdk/pfm.c
new file mode 100644
index 0000000000..805c6d4884
--- /dev/null
+++ b/gdk/pfm.c
@@ -0,0 +1,174 @@
+/* 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 "pfm.h"
+
+#include <glib.h>
+#include <math.h>
+#include <gio/gio.h>
+
+/* Somewhat minimal support for loading and saving pfm data */
+
+static void
+read_float (const unsigned char *data,
+            float               *c)
+{
+  union { float c; unsigned char b[4]; } x;
+
+  x.b[0] = data[0];
+  x.b[1] = data[1];
+  x.b[2] = data[2];
+  x.b[3] = data[3];
+
+  *c = x.c;
+}
+
+static void
+read_floats (const unsigned char *data,
+             float               *c)
+{
+  read_float (&data[0], &c[0]);
+  read_float (&data[4], &c[1]);
+  read_float (&data[8], &c[2]);
+}
+
+static void
+write_float (const float    c,
+             unsigned char *out)
+{
+  union { float c; unsigned char b[4]; } x;
+
+  x.c = c;
+
+  out[0] = x.b[0];
+  out[1] = x.b[1];
+  out[2] = x.b[2];
+  out[3] = x.b[3];
+}
+
+static void
+write_floats (const float   *c,
+              unsigned char *out)
+{
+  write_float (c[0], &out[0]);
+  write_float (c[1], &out[4]);
+  write_float (c[2], &out[8]);
+}
+
+
+float *
+load_pfm (const char  *data,
+          gsize        size,
+          int         *out_width,
+          int         *out_height,
+          GError     **error)
+{
+  const char *p;
+  char *p2;
+  int width, height;
+  float *out_data;
+
+  if (strncmp (data, "PF\n", 3) != 0)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Not a pfm file (no marker)");
+      return NULL;
+    }
+
+  p = data + 3;
+  width = g_ascii_strtoll (p, &p2, 10);
+  p = p2;
+  height = g_ascii_strtoll (p, &p2, 10);
+  p = p2;
+
+  if (height < 0 || width < 0 || *p != '\n')
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Bad pfm file (bad size line)");
+      return NULL;
+    }
+
+  p++;
+
+  if (strncmp (p, "-1.0\n", 5) != 0)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Bad pfm file (bad endianness line)");
+      return NULL;
+    }
+
+  p += 5;
+
+  /* p points at the binary data now */
+
+  out_data = g_malloc (width * height * 3 * 4);
+
+  for (int y = 0; y < height; y++)
+    {
+      for (int x = 0; x < width; x++)
+        {
+          read_floats ((const unsigned char *)&p[((height - 1 - y) * width + x) * 12],
+                       &out_data[(y * width + x) * 3]);
+        }
+    }
+
+  *out_width = width;
+  *out_height = height;
+
+  return out_data;
+}
+
+gboolean
+save_pfm (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 ("PF\n%d %d\n-1.0\n", width, height);
+
+  preamble_len = strlen (preamble);
+
+  out_data_len = preamble_len + width * height * 3 * 4 + 1;
+  out_data = g_realloc (preamble, out_data_len);
+
+  for (int y = 0; y < height; y++)
+    {
+      for (int x = 0; x < width; x++)
+        {
+          write_floats (&data[((height - 1 - y) * width + x) * 3],
+                        &out_data[preamble_len + (y * width + x) * 12]);
+        }
+    }
+
+  result = g_file_set_contents (file, (char *)out_data, out_data_len, error);
+
+  g_free (out_data);
+
+  return result;
+}
diff --git a/gdk/pfm.h b/gdk/pfm.h
new file mode 100644
index 0000000000..bebfc57a4d
--- /dev/null
+++ b/gdk/pfm.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 __PFM_H__
+#define __PFM_H__
+
+#include <glib.h>
+
+float *  load_pfm   (const char    *data,
+                     gsize          size,
+                     int           *out_width,
+                     int           *out_height,
+                     GError       **error);
+gboolean save_pfm   (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]