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




commit 290dd426b7844c8d7cbcbe1930e3b33bd61eed77
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Sep 7 14:25:52 2021 -0400

    Support loading and saving 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 |  73 ++++++++++++++++++++++-
 gdk/meson.build  |   1 +
 gdk/pfm.c        | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gdk/pfm.h        |  34 +++++++++++
 4 files changed, 280 insertions(+), 2 deletions(-)
---
diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c
index 4dc2395ce5..11b3376f09 100644
--- a/gdk/gdktexture.c
+++ b/gdk/gdktexture.c
@@ -47,6 +47,7 @@
 
 #include <graphene.h>
 #include <png.h>
+#include "pfm.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_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
@@ -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, "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)
@@ -678,6 +720,28 @@ gdk_texture_save_to_png (GdkTexture *texture,
   return result;
 }
 
+static gboolean
+gdk_texture_save_to_pfm (GdkTexture *texture,
+                         const char *filename)
+{
+  GBytes *bytes;
+  gboolean result;
+
+  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;
+}
+
 /**
  * gdk_texture_save_to_file:
  * @texture: a `GdkTexture`
@@ -686,7 +750,9 @@ gdk_texture_save_to_png (GdkTexture *texture,
  * Store the given @texture to the @filename.
  *
  * GTK will choose a suitable file format to save the data in
- * depending on the format of the texture.
+ * depending on the format of the texture. For floating point
+ * textures, this is currently the pfm format, for other textures
+ * it is png.
  *
  * This is a utility function intended for debugging and testing.
  * If you want more control over formats, proper error handling or
@@ -704,5 +770,8 @@ gdk_texture_save_to_file (GdkTexture *texture,
   g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE);
   g_return_val_if_fail (filename != NULL, FALSE);
 
+  if (gdk_texture_save_to_pfm (texture, filename))
+    return TRUE;
+
   return gdk_texture_save_to_png (texture, filename);
 }
diff --git a/gdk/meson.build b/gdk/meson.build
index 6539c225f4..61859bd8e8 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -50,6 +50,7 @@ gdk_public_sources = files([
   'gdktoplevelsize.c',
   'gdktoplevel.c',
   'gdkdragsurface.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]