[gegl] operations: Add gegl:rgbe-load and gegl:rgbe-save
- From: Martin Nordholts <martinn src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] operations: Add gegl:rgbe-load and gegl:rgbe-save
- Date: Fri, 23 Jul 2010 13:57:14 +0000 (UTC)
commit bbdb9f68b5e6830966cd7ce2d3ace323498eb195
Author: Danny Robson <danny blubinc net>
Date: Wed Jul 21 11:39:25 2010 +0200
operations: Add gegl:rgbe-load and gegl:rgbe-save
Add a radiance (rgbe) HDR image format reader and writer. Implements
reading of uncompressed and new-rle scanlines, and writing of
uncompressed scanlines.
The RGBE load and save code is put in a separate library so it's easy
to move out of the GEGL tree or replace with something else.
Also add test cases for both loading and saving.
Makefile.am | 4 +
configure.ac | 2 +
libs/.gitignore | 2 +
libs/Makefile.am | 1 +
libs/rgbe/.gitignore | 6 +
libs/rgbe/Makefile.am | 3 +
libs/rgbe/rgbe.c | 1011 ++++++++++++++++++++++++++
libs/rgbe/rgbe.h | 96 +++
operations/external/Makefile.am | 9 +
operations/external/rgbe-load.c | 140 ++++
operations/external/rgbe-save.c | 91 +++
tests/compositions/data/car-stack-eighth.hdr | Bin 0 -> 50120 bytes
tests/compositions/reference/rgbe-load.png | Bin 0 -> 53078 bytes
tests/compositions/reference/rgbe-save.hdr | Bin 0 -> 50120 bytes
tests/compositions/rgbe-load.xml | 9 +
tests/compositions/rgbe-save.xml | 15 +
16 files changed, 1389 insertions(+), 0 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 951acf0..fc230ac 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,10 @@
AUTOMAKE_OPTIONS = dist-bzip2
+# The libs directory needs to be before anything which can depend on its
+# libraries, otherwise any potentially rebuilt libs won't be picked up until
+# the next run of make.
SUBDIRS=\
+ libs \
gegl \
operations \
bin \
diff --git a/configure.ac b/configure.ac
index e874f14..4bb568f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1006,6 +1006,8 @@ gegl/module/Makefile
gegl/operation/Makefile
gegl/process/Makefile
gegl/property-types/Makefile
+libs/Makefile
+libs/rgbe/Makefile
operations/Makefile
operations/affine/Makefile
operations/core/Makefile
diff --git a/libs/.gitignore b/libs/.gitignore
new file mode 100644
index 0000000..b336cc7
--- /dev/null
+++ b/libs/.gitignore
@@ -0,0 +1,2 @@
+/Makefile
+/Makefile.in
diff --git a/libs/Makefile.am b/libs/Makefile.am
new file mode 100644
index 0000000..933933d
--- /dev/null
+++ b/libs/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = rgbe
diff --git a/libs/rgbe/.gitignore b/libs/rgbe/.gitignore
new file mode 100644
index 0000000..47cdde9
--- /dev/null
+++ b/libs/rgbe/.gitignore
@@ -0,0 +1,6 @@
+/.deps
+/.libs
+/Makefile
+/Makefile.in
+/librgbe.la
+/librgbe_la-rgbe.lo
diff --git a/libs/rgbe/Makefile.am b/libs/rgbe/Makefile.am
new file mode 100644
index 0000000..593fa04
--- /dev/null
+++ b/libs/rgbe/Makefile.am
@@ -0,0 +1,3 @@
+noinst_LTLIBRARIES = librgbe.la
+librgbe_la_SOURCES = rgbe.c rgbe.h
+librgbe_la_CFLAGS = $(DEP_CFLAGS) -fPIC
diff --git a/libs/rgbe/rgbe.c b/libs/rgbe/rgbe.c
new file mode 100644
index 0000000..e9772f9
--- /dev/null
+++ b/libs/rgbe/rgbe.c
@@ -0,0 +1,1011 @@
+/* This file is an image processing operation for GEGL
+ *
+ * GEGL 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 3 of the License, or (at your option) any later version.
+ *
+ * GEGL 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 GEGL; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2010 Danny Robson <danny blubinc net>
+ */
+
+#include "config.h"
+
+#include "rgbe.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+
+/* Scanlines are limited to 2^(16 - 1), as RLE encoded lines only have the
+ * lower 15 bits of the R&G components to store its length.
+ */
+#define RGBE_MAX_SCANLINE_WIDTH (1 << 15)
+
+#define RGBE_MAX_SOFTWARE_LEN 63
+#define RGBE_NUM_RGB 3
+#define RGBE_NUM_RGBE 4
+#define RGBE_MAX_VARIABLE_LINE_LEN 24
+
+
+/* Describes the colour space of the pixel components. These values must
+ * index correctly into the array RGBE_FORMAT_STRINGS for correct operation.
+ */
+typedef enum
+{
+ FORMAT_RGBE,
+ FORMAT_XYZE,
+ FORMAT_UNKNOWN,
+
+ NUM_RGBE_FORMATS = FORMAT_XYZE
+} rgbe_format;
+
+/* Describes the behaviour of indices across, and between scanlines. */
+typedef enum
+{
+ ORIENT_DECREASING,
+ ORIENT_INCREASING,
+ ORIENT_UNKNOWN
+} rgbe_orientation;
+
+enum
+{
+ OFFSET_R = 0, OFFSET_X = OFFSET_R,
+ OFFSET_G = 1, OFFSET_Y = OFFSET_G,
+ OFFSET_B = 2, OFFSET_Z = OFFSET_B,
+ OFFSET_E = 3,
+ OFFSET_A = 3
+};
+
+typedef struct
+{
+ rgbe_orientation orient;
+ guint16 size;
+} rgbe_axis;
+
+typedef struct
+{
+ rgbe_format format;
+
+ gchar software[RGBE_MAX_SOFTWARE_LEN + 1];
+
+ gfloat exposure;
+ gfloat colorcorr[RGBE_NUM_RGB];
+ /* TODO: xyz primaries */
+
+ /* TODO: view parameters */
+
+ /* resolution parameters */
+ rgbe_axis x_axis,
+ y_axis;
+ gfloat pixel_aspect;
+} rgbe_header;
+
+struct _rgbe_file
+{
+ rgbe_header header;
+
+ GMappedFile *file;
+ /* Stores the address of the scanlines, or NULL */
+ const void *scanlines;
+};
+
+
+static const gchar RADIANCE_MAGIC[] = "#?RADIANCE";
+
+static const gchar *RGBE_FORMAT_STRINGS[] =
+{
+ "32-bit_rle_rgbe",
+ "32-bit_rle_xyze",
+ NULL
+};
+
+
+/**
+ * rgbe_mapped_file_remaining:
+ * @f: the file to read the image data from
+ * @data: the current file read cursor
+ *
+ * Calculates the number of bytes remaining to be read in a mapped file.
+ **/
+static guint
+rgbe_mapped_file_remaining (GMappedFile *f,
+ const void *data)
+{
+ g_return_val_if_fail (f, 0);
+ g_return_val_if_fail (GPOINTER_TO_UINT (data) >
+ GPOINTER_TO_UINT (g_mapped_file_get_contents (f)), 0);
+
+ return GPOINTER_TO_UINT (data) -
+ GPOINTER_TO_UINT (g_mapped_file_get_contents (f)) -
+ g_mapped_file_get_length (f);
+}
+
+static void
+rgbe_header_init (rgbe_header *header)
+{
+ g_return_if_fail (header);
+
+ header->format = FORMAT_UNKNOWN;
+ memset (header->software, '\0', G_N_ELEMENTS (header->software));
+
+ header->exposure = 1.0;
+ header->colorcorr[OFFSET_R] = 1.0;
+ header->colorcorr[OFFSET_G] = 1.0;
+ header->colorcorr[OFFSET_B] = 1.0;
+
+ header->pixel_aspect = 1.0;
+
+ header->x_axis.orient = header->y_axis.orient = ORIENT_UNKNOWN;
+ header->x_axis.size = header->x_axis.size = 0;
+}
+
+static gboolean
+rgbe_file_init (rgbe_file *file,
+ const gchar *path)
+{
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ rgbe_header_init (&file->header);
+ file->file = g_mapped_file_new (path, FALSE, NULL);
+ file->scanlines = NULL;
+
+ return file->file != NULL;
+}
+
+static rgbe_file*
+rgbe_file_new (const gchar *path)
+{
+ rgbe_file *file;
+
+ g_return_val_if_fail (path, NULL);
+
+ file = g_new (rgbe_file, 1);
+ if (!rgbe_file_init (file, path))
+ {
+ rgbe_file_free (file);
+ file = NULL;
+ }
+
+ return file;
+}
+
+void
+rgbe_file_free (rgbe_file *file)
+{
+ if (!file)
+ return;
+
+ g_mapped_file_unref (file->file);
+ file->scanlines = NULL;
+
+ g_free (file);
+}
+
+/* Parse the variable initialisations for an rgbe header. Returns the offset
+ * after the header delimiting newline in cursor. The incoming cursor should
+ * (most likely) be zero.
+ *
+ * Updates cursor on success.
+ */
+static gboolean
+rgbe_header_read_variables (rgbe_file *file,
+ goffset *cursor)
+{
+ const gchar *data;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (file, FALSE);
+ g_return_val_if_fail (file->file, FALSE);
+ g_return_val_if_fail (cursor && *cursor > 0, FALSE);
+
+ data = g_mapped_file_get_contents (file->file) + *cursor;
+
+ /* Keep iterating if it looks like there's enough data to satisfy another
+ * line (the estimate doesn't need to be exact, as we can run a little over
+ * due to required resolution specification which will be coming up next).
+ */
+ while (rgbe_mapped_file_remaining (file->file, data) > RGBE_MAX_VARIABLE_LINE_LEN)
+ {
+ /* Check the colourspace/type of pixels in the file */
+ if (g_str_has_prefix (data, "FORMAT="))
+ {
+ guint i;
+ data += strlen ("FORMAT=");
+
+ file->header.format = FORMAT_UNKNOWN;
+ for (i = 0; i < NUM_RGBE_FORMATS; ++i)
+ {
+ if (g_str_has_prefix (data, RGBE_FORMAT_STRINGS[i]))
+ {
+ file->header.format = (rgbe_format)i;
+ break;
+ }
+ }
+
+ if (file->header.format != FORMAT_RGBE)
+ {
+ g_warning ("Unsupported color format for rgbe format");
+ goto cleanup;
+ }
+ continue;
+ }
+
+ /* Check the exposure multiplier */
+ else if (g_str_has_prefix (data, "EXPOSURE="))
+ {
+ gdouble exposure;
+
+ data += strlen ("EXPOSURE=");
+ exposure = g_ascii_strtod (data, NULL);
+
+ if (errno)
+ {
+ g_warning ("Invalid value for exposure in radiance image file");
+ goto cleanup;
+ }
+ else
+ {
+ file->header.exposure *= exposure;
+ }
+ }
+
+ /* Parse the component multipliers */
+ else if (g_str_has_prefix (data, "COLORCORR="))
+ {
+ guint i;
+ data += strlen ("COLORCORR=");
+
+ for (i = 0; i < RGBE_NUM_RGB; ++i)
+ {
+ gdouble multiplier = g_ascii_strtod (data, (gchar**)&data);
+ if (errno)
+ {
+ g_warning ("Invalid value for COLORCORR");
+ goto cleanup;
+ }
+
+ file->header.colorcorr[i] *= multiplier;
+ }
+ }
+
+ /* Generating software identifier */
+ else if (g_str_has_prefix (data, "SOFTWARE="))
+ {
+ gchar * lineend;
+
+ data += strlen ("SOFTWARE=");
+ lineend = g_strstr_len (data,
+ MIN (rgbe_mapped_file_remaining (file->file,
+ data),
+ G_N_ELEMENTS (file->header.software)),
+ "\n");
+
+ if (!lineend)
+ {
+ g_warning ("Cannot find a usable value for SOFTWARE, ignoring");
+ }
+ else
+ {
+ guint linesize = lineend - data;
+ strncpy (file->header.software, data,
+ MIN (linesize, G_N_ELEMENTS (file->header.software) - 1));
+ }
+ }
+
+ /* Ratio of pixel height to width */
+ else if (g_str_has_prefix (data, "PIXASPECT="))
+ {
+ gdouble aspect;
+
+ data += strlen ("PIXASPECT=");
+ aspect = g_ascii_strtod (data, (gchar **)&data);
+
+ if (errno)
+ {
+ g_warning ("Invalid pixel aspect ratio");
+ goto cleanup;
+ }
+ else
+ {
+ file->header.pixel_aspect *= aspect;
+ }
+ }
+
+ /* We reached a blank line, so it's the end of the header */
+ else if (!strncmp (data, "\n", strlen ("\n")))
+ {
+ data += strlen ("\n");
+ *cursor = GPOINTER_TO_UINT (data) -
+ GPOINTER_TO_UINT (g_mapped_file_get_contents (file->file));
+ success = TRUE;
+ goto cleanup;
+ }
+
+ /* Skip past the end of the line for the next variable */
+ data = g_strstr_len (data,
+ rgbe_mapped_file_remaining (file->file, data),
+ "\n");
+ if (!data)
+ goto cleanup;
+ data += strlen ("\n");
+ }
+
+cleanup:
+ return success;
+}
+
+
+/* Convert from '-' or '+' to useful scanline index constants
+ */
+static rgbe_orientation
+rgbe_char_to_orientation (gchar c)
+{
+ switch (c)
+ {
+ case '-':
+ return ORIENT_DECREASING;
+
+ case '+':
+ return ORIENT_INCREASING;
+
+ default:
+ return ORIENT_UNKNOWN;
+ }
+}
+
+
+/* Return the axis which the scanline index character refers to.
+ */
+static rgbe_axis*
+rgbe_char_to_axis (rgbe_file *file,
+ gchar c)
+{
+ switch (c)
+ {
+ case 'y':
+ case 'Y':
+ return &file->header.y_axis;
+
+ case 'x':
+ case 'X':
+ return &file->header.x_axis;
+
+ default:
+ return NULL;
+ }
+}
+
+
+/* Parse the orientation/resolution line. The following format is repeated
+ * twice: "[+-][XY] \d+" It specifies column or row major ordering, and the
+ * direction of pixel indices (eg, mirrored).
+ *
+ * Updates cursor on success.
+ */
+static gboolean
+rgbe_header_read_orientation (rgbe_file *file,
+ goffset *cursor)
+{
+ const gchar *data;
+ rgbe_orientation orient;
+ rgbe_axis *axis;
+ gchar firstaxis = '?';
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (file, FALSE);
+ g_return_val_if_fail (file->file, FALSE);
+ g_return_val_if_fail (cursor && *cursor > 0, FALSE);
+
+ data = g_mapped_file_get_contents (file->file) + *cursor;
+
+ /* Read each direction, axis, and size until a newline is reached */
+ do
+ {
+ orient = rgbe_char_to_orientation (*data++);
+ if (orient == ORIENT_UNKNOWN)
+ goto cleanup;
+
+ /* Axis can be ordered with X major, which we don't currently handle */
+ if (firstaxis == '?' && *data != 'Y' && *data != 'y')
+ goto cleanup;
+ else
+ firstaxis = *data;
+
+ axis = rgbe_char_to_axis (file, *data++);
+ if (!axis)
+ goto cleanup;
+ axis->orient = orient;
+
+ if (*data++ != ' ')
+ goto cleanup;
+
+ axis->size = g_ascii_strtoull (data, (gchar **)&data, 0);
+ if (errno)
+ goto cleanup;
+
+ /* The termination check is simplified to a space check, as each set of
+ * axis parameters are space seperated. We double check for a newline next
+ * though.
+ */
+ } while (*data++ == ' ');
+
+ if (data[-1] != '\n')
+ goto cleanup;
+
+ *cursor = data - g_mapped_file_get_contents (file->file);
+ success = TRUE;
+
+cleanup:
+ return success;
+}
+
+
+/* Read each component of an rgbe file header. A pointer to the scanlines,
+ * immediately after the header, is cached on success.
+ */
+static gboolean
+rgbe_header_read (rgbe_file *file)
+{
+ gchar *data;
+ gboolean success = FALSE;
+ goffset cursor = 0;
+
+ g_return_val_if_fail (file, FALSE);
+ g_return_val_if_fail (file->file, FALSE);
+
+ rgbe_header_init (&file->header);
+
+ data = g_mapped_file_get_contents (file->file);
+ if (strncmp (&data[cursor], RADIANCE_MAGIC, strlen (RADIANCE_MAGIC)))
+ goto cleanup;
+ cursor += strlen (RADIANCE_MAGIC);
+
+ if (data[cursor] != '\n')
+ goto cleanup;
+ ++cursor;
+
+ if (!rgbe_header_read_variables (file, &cursor))
+ goto cleanup;
+
+ if (!rgbe_header_read_orientation (file, &cursor))
+ goto cleanup;
+
+ file->scanlines = &data[cursor];
+ success = TRUE;
+cleanup:
+ return success;
+}
+
+
+/* Convert an array of gfloat mantissas to their full values. Applies the
+ * exponent, exposure compensation, and color channel compensation.
+ */
+static void
+rgbe_apply_exponent (const rgbe_file *file,
+ gfloat *rgb,
+ gfloat e)
+{
+ gfloat mult;
+
+ g_return_if_fail (file);
+ g_return_if_fail (rgb);
+
+ if (e == 0)
+ {
+ rgb[OFFSET_R] = rgb[OFFSET_G] = rgb[OFFSET_B] = 0;
+ goto cleanup;
+ }
+
+ mult = ldexp (1.0, e - (128 + 8));
+ rgb[OFFSET_R] *= mult *
+ file->header.exposure *
+ file->header.colorcorr[OFFSET_R];
+ rgb[OFFSET_G] *= mult *
+ file->header.exposure *
+ file->header.colorcorr[OFFSET_G];
+ rgb[OFFSET_B] *= mult *
+ file->header.exposure *
+ file->header.colorcorr[OFFSET_B];
+ rgb[OFFSET_A] = 1.0f;
+
+cleanup:
+ return;
+}
+
+
+/* Convert an array of RGBE uints to their floating point format (applying
+ * exponents and compensations as required).
+ */
+static void
+rgbe_rgbe_to_float (const rgbe_file *file,
+ const guint8 *rgbe,
+ gfloat *output)
+{
+ g_return_if_fail (file);
+ g_return_if_fail (rgbe);
+ g_return_if_fail (output);
+
+ output[OFFSET_R] = rgbe[OFFSET_R];
+ output[OFFSET_G] = rgbe[OFFSET_G];
+ output[OFFSET_B] = rgbe[OFFSET_B];
+ output[OFFSET_A] = 1.0f;
+
+ rgbe_apply_exponent (file, output, rgbe[OFFSET_E]);
+}
+
+
+/* Read one uncompressed scanline row. Updates cursor on success. */
+static gboolean
+rgbe_read_uncompressed (const rgbe_file *file,
+ goffset *cursor,
+ gfloat *pixels)
+{
+ const guint8 *data;
+ guint i;
+
+ g_return_val_if_fail (file, FALSE);
+ g_return_val_if_fail (file->file, FALSE);
+ g_return_val_if_fail (cursor && *cursor > 0, FALSE);
+ g_return_val_if_fail (pixels, FALSE);
+
+ data = (guint8 *)g_mapped_file_get_contents (file->file) + *cursor;
+
+ for (i = 0; i < file->header.x_axis.size; ++i)
+ {
+ rgbe_rgbe_to_float (file, data, pixels);
+ data += RGBE_NUM_RGBE;
+ pixels += RGBE_NUM_RGBE;
+ }
+
+ *cursor = GPOINTER_TO_UINT (data) -
+ GPOINTER_TO_UINT (g_mapped_file_get_contents (file->file));
+ return TRUE;
+}
+
+
+/* Read an old style rle scanline row. Unimplemented */
+static gboolean
+rgbe_read_old_rle (const rgbe_file *file,
+ goffset *cursor,
+ gfloat *pixels)
+{
+ /* const gchar * data = g_mapped_file_get_contents (f) + *cursor; */
+
+ g_return_val_if_fail (file, FALSE);
+ g_return_val_if_fail (file->file, FALSE);
+ g_return_val_if_fail (cursor && *cursor > 0, FALSE);
+ g_return_val_if_fail (pixels, FALSE);
+
+ g_return_val_if_reached (FALSE);
+}
+
+
+/* Read one new style rle scanline row. Updates cursor on success. */
+static gboolean
+rgbe_read_new_rle (const rgbe_file *file,
+ goffset *cursor,
+ gfloat *pixels)
+{
+ const guint8 *data;
+ guint16 linesize;
+ guint i;
+ guint component;
+ gfloat *pixoffset[RGBE_NUM_RGBE] =
+ {
+ pixels + OFFSET_R,
+ pixels + OFFSET_G,
+ pixels + OFFSET_B,
+ pixels + OFFSET_E
+ };
+
+ g_return_val_if_fail (file, FALSE);
+ g_return_val_if_fail (file->file, FALSE);
+ g_return_val_if_fail (cursor && *cursor > 0, FALSE);
+ g_return_val_if_fail (pixels, FALSE);
+
+ /* Read the scanline header: two magic bytes, and two byte pixel count. We
+ * can assert on the magic as it should have been checked before
+ * dispatching to this decoding routine.
+ */
+ data = (guint8 *)g_mapped_file_get_contents (file->file) + *cursor;
+ g_return_val_if_fail (data[OFFSET_R] == 2 && data[OFFSET_G] == 2, FALSE);
+ linesize = (data[OFFSET_B] << 8) | data[OFFSET_E];
+
+ data += RGBE_NUM_RGBE;
+
+ /* Decode the rle/dump sequences for each color channel, continuing until
+ * we've reached the expected offsets for each channel. Stores the exponent
+ * values in the alpha channel temporarily.
+ */
+ for (component = 0; component < RGBE_NUM_RGBE; ++component)
+ {
+ while (pixoffset[component] < pixels + RGBE_NUM_RGBE * linesize)
+ {
+ const guint HIGH_BIT = (1 << 7);
+ gboolean rle = *data & HIGH_BIT;
+ guint length = *data & ~HIGH_BIT;
+
+ /* A dump/run of 0 is a special marker for dump 128 */
+ if (length == 0)
+ {
+ rle = FALSE;
+ length = 128;
+ }
+
+ data++;
+
+ /* A compressed run */
+ if (rle)
+ {
+ for (i = 0; i < length; ++i)
+ {
+ *pixoffset[component] = *data;
+ pixoffset[component] += RGBE_NUM_RGBE;
+ }
+
+ data++;
+ }
+ /* A dump of values */
+ else
+ {
+ for (i = 0; i < length; ++i)
+ {
+ *pixoffset[component] = *data;
+ pixoffset[component] += RGBE_NUM_RGBE;
+ data++;
+ }
+ }
+ }
+ }
+
+ /* Double check we encountered as many pixels as expected. Pixoffsets should
+ * have been incremented to just past the final pixel for each component.
+ */
+ for (component = 0; component < RGBE_NUM_RGBE; ++component)
+ {
+ g_warn_if_fail (pixoffset[component] == pixels + RGBE_NUM_RGBE * linesize + component);
+ }
+
+ /* Multiply the colours by the exponent. Remove 'transparency' by setting
+ * alpha high as a precaution, it should be discarded in any case.
+ */
+ for (i = 0; i < linesize; ++i)
+ {
+ gfloat *pixel = pixels + i * RGBE_NUM_RGBE;
+ rgbe_apply_exponent (file, pixel, pixel[OFFSET_E]);
+ }
+
+ *cursor = GPOINTER_TO_UINT (data) -
+ GPOINTER_TO_UINT (g_mapped_file_get_contents (file->file));
+
+ return TRUE;
+}
+
+
+/* Write a null terminated string (with user provided trailing newline) to
+ * the output file, freeing the line and returning an error if needed.
+ */
+static gboolean
+rgbe_write_line (FILE *f, gchar *line)
+{
+ size_t written;
+ guint len = strlen (line);
+
+ g_return_val_if_fail (g_str_has_suffix (line, "\n"), FALSE);
+ written = fwrite (line, sizeof (line[0]), len, f);
+ g_free (line);
+
+ return written == len ? TRUE : FALSE;
+}
+
+
+/* Write all rgbe header variables (which aren't defaults) out to a file. */
+static gboolean
+rgbe_header_write (const rgbe_header *header,
+ FILE *f)
+{
+ gchar *line = NULL;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (header, FALSE);
+ g_return_val_if_fail (f, FALSE);
+
+ /* Magic header bytes */
+ line = g_strconcat (RADIANCE_MAGIC, "\n", NULL);
+ if (!rgbe_write_line (f, line))
+ goto cleanup;
+
+ /* Insert the package name as the software name if not present (zero len)
+ * or we don't have a null terminated name length.
+ */
+ if ( strnlen (header->software, RGBE_MAX_SOFTWARE_LEN) == 0 ||
+ (strnlen (header->software, RGBE_MAX_SOFTWARE_LEN) == RGBE_MAX_SOFTWARE_LEN &&
+ header->software[RGBE_MAX_SOFTWARE_LEN - 1] != '\0'))
+ {
+ line = g_strconcat ("SOFTWARE=", PACKAGE_STRING, "\n", NULL);
+ }
+ else
+ {
+ line = g_strconcat ("SOFTWARE=", header->software, "\n", NULL);
+ }
+ if (!rgbe_write_line (f, line))
+ goto cleanup;
+
+ /* Type of pixel components */
+ g_return_val_if_fail (header->format < FORMAT_UNKNOWN, FALSE);
+ g_return_val_if_fail (header->format < G_N_ELEMENTS (RGBE_FORMAT_STRINGS), FALSE);
+ line = g_strconcat ("FORMAT=",
+ RGBE_FORMAT_STRINGS[header->format],
+ "\n", NULL);
+ if (!rgbe_write_line (f, line))
+ goto cleanup;
+
+ /* Exposure compensation */
+ if (header->exposure != 1.0)
+ {
+ gchar exp_line[G_ASCII_DTOSTR_BUF_SIZE + 1];
+ line = g_strconcat ("EXPOSURE=",
+ g_ascii_dtostr (exp_line,
+ G_N_ELEMENTS (exp_line),
+ header->exposure),
+ "\n",
+ NULL);
+ if (!rgbe_write_line (f, line))
+ goto cleanup;
+ }
+
+ /* Color channel correction */
+ if (header->colorcorr [OFFSET_R] != 1.0 &&
+ header->colorcorr [OFFSET_G] != 1.0 &&
+ header->colorcorr [OFFSET_B] != 1.0)
+ {
+ gchar corr_line[G_ASCII_DTOSTR_BUF_SIZE + 1][RGBE_NUM_RGB];
+ line = g_strconcat ("COLORCORR=",
+ g_ascii_dtostr (corr_line[OFFSET_R],
+ G_N_ELEMENTS (corr_line[OFFSET_R]),
+ header->colorcorr[OFFSET_R]), " ",
+ g_ascii_dtostr (corr_line[OFFSET_G],
+ G_N_ELEMENTS (corr_line[OFFSET_G]),
+ header->colorcorr[OFFSET_G]), " ",
+ g_ascii_dtostr (corr_line[OFFSET_B],
+ G_N_ELEMENTS (corr_line[OFFSET_B]),
+ header->colorcorr[OFFSET_R]),
+ "\n",
+ NULL);
+ if (!rgbe_write_line (f, line))
+ goto cleanup;
+ }
+
+
+ /* Resolution specifier */
+ {
+ const guint res_line_sz = strlen ("\n")
+ + strlen ("-Y ") * 2
+ + strlen (G_STRINGIFY (RGBE_MAX_SCANLINE_WIDTH)) * 2
+ + strlen ("\n")
+ + 1;
+ gint err;
+
+ line = g_malloc (res_line_sz * sizeof (line[0]));
+ err = snprintf (line, res_line_sz,
+ "\n-Y %hu +X %hu\n",
+ header->y_axis.size,
+ header->x_axis.size);
+ if (err < 0 || !rgbe_write_line (f, line))
+ goto cleanup;
+ }
+
+ success = TRUE;
+cleanup:
+ return success;
+}
+
+
+/* Convert an array of floats to rgbe components for file output */
+static void
+rgbe_float_to_rgbe (const gfloat *f,
+ guint8 *rgbe)
+{
+ gint exp;
+ gfloat frac, max;
+
+ g_return_if_fail (f);
+ g_return_if_fail (rgbe);
+
+ max = f[OFFSET_R];
+ max = MAX (max, f[OFFSET_G]);
+ max = MAX (max, f[OFFSET_B]);
+
+ if (max < 1e-38)
+ {
+ rgbe[OFFSET_R] = rgbe[OFFSET_G] = rgbe[OFFSET_B] = 0;
+ goto cleanup;
+ }
+
+ frac = frexp (max, &exp) * 256.0 / max;
+
+ rgbe[OFFSET_R] = f[OFFSET_R] * frac;
+ rgbe[OFFSET_G] = f[OFFSET_G] * frac;
+ rgbe[OFFSET_B] = f[OFFSET_B] * frac;
+
+ rgbe[OFFSET_E] = exp + 128;
+
+cleanup:
+ return;
+}
+
+
+/* Write the first scanline from pixels into the file. Does not use RLE. */
+static gboolean
+rgbe_write_uncompressed (const rgbe_header *header,
+ const gfloat *pixels,
+ FILE *f)
+{
+ guint x, y;
+ guint8 rgbe[RGBE_NUM_RGBE];
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (header, FALSE);
+ g_return_val_if_fail (pixels, FALSE);
+ g_return_val_if_fail (f, FALSE);
+
+ for (y = 0; y < header->y_axis.size; ++y)
+ for (x = 0; x < header->x_axis.size; ++x)
+ {
+ rgbe_float_to_rgbe (pixels, rgbe);
+
+ /* Ensure we haven't inadvertantly triggered an rle scanline */
+ g_warn_if_fail (rgbe[0] != 2 || rgbe[1] != 2);
+ g_warn_if_fail (rgbe[0] != 1 || rgbe[1] != 1 || rgbe[2] != 1);
+
+ if (G_N_ELEMENTS (rgbe) != fwrite (rgbe, sizeof (rgbe[0]), G_N_ELEMENTS (rgbe), f))
+ success = FALSE;
+ pixels += RGBE_NUM_RGB;
+ }
+
+ return success;
+}
+
+
+gboolean
+rgbe_save_path (const gchar *path,
+ guint width,
+ guint height,
+ gfloat *pixels)
+{
+ rgbe_header header;
+ FILE *f = NULL;
+ gboolean success = FALSE;
+
+ f = (!strcmp (path, "-") ? stdout : fopen(path, "wb"));
+ if (!f)
+ goto cleanup;
+
+ rgbe_header_init (&header);
+ header.x_axis.orient = ORIENT_INCREASING;
+ header.x_axis.size = width;
+ header.y_axis.orient = ORIENT_DECREASING;
+ header.y_axis.size = height;
+ header.format = FORMAT_RGBE;
+
+ success = rgbe_header_write (&header, f);
+ if (!success)
+ goto cleanup;
+
+ success = rgbe_write_uncompressed (&header, pixels, f);
+
+cleanup:
+ if (f)
+ fclose (f);
+
+ return success;
+}
+
+
+rgbe_file *
+rgbe_load_path (const gchar *path)
+{
+ gboolean success = FALSE;
+ rgbe_file *file;
+
+ file = rgbe_file_new (path);
+ if (!file)
+ goto cleanup;
+
+ if (!rgbe_header_read (file))
+ goto cleanup;
+ success = TRUE;
+
+cleanup:
+ if (!success)
+ {
+ rgbe_file_free (file);
+ file = NULL;
+ }
+ return file;
+}
+
+
+gboolean
+rgbe_get_size (rgbe_file *file,
+ guint *x,
+ guint *y)
+{
+ g_return_val_if_fail (file, FALSE);
+ *x = file->header.x_axis.size;
+ *y = file->header.y_axis.size;
+
+ return TRUE;
+}
+
+
+/* Peek on each scanline row to dispatch to decoders.
+ *
+ * - Assumes row major ordering.
+ * - Assumes cursor is at the start of a scanline
+ * - Updates cursor, which is undefined on error.
+ */
+gfloat *
+rgbe_read_scanlines (rgbe_file *file)
+{
+ guint i;
+ gboolean success = FALSE;
+ gfloat *pixels = NULL,
+ *pixel_cursor;
+ goffset offset;
+
+ g_return_val_if_fail (file, NULL);
+ g_return_val_if_fail (file->scanlines, NULL);
+
+ pixels = pixel_cursor = g_new (gfloat, file->header.x_axis.size *
+ file->header.y_axis.size *
+ RGBE_NUM_RGBE);
+ offset = GPOINTER_TO_UINT (file->scanlines) -
+ GPOINTER_TO_UINT (g_mapped_file_get_contents (file->file));
+
+ for (i = 0; i < file->header.y_axis.size; ++i)
+ {
+ const gchar *data = g_mapped_file_get_contents (file->file);
+
+ if (data[offset + OFFSET_R] == 1 &&
+ data[offset + OFFSET_G] == 1 &&
+ data[offset + OFFSET_B] == 1)
+ success = rgbe_read_old_rle (file, &offset, pixel_cursor);
+ else if (data[offset + OFFSET_R] == 2 &&
+ data[offset + OFFSET_G] == 2)
+ success = rgbe_read_new_rle (file, &offset, pixel_cursor);
+ else
+ success = rgbe_read_uncompressed (file, &offset, pixel_cursor);
+
+ if (!success)
+ {
+ g_warning ("Unable to parse rgbe scanlines, fail at row %u\n", i);
+ goto cleanup;
+ }
+ pixel_cursor += file->header.x_axis.size * RGBE_NUM_RGBE;
+ }
+
+ success = TRUE;
+
+cleanup:
+ if (!success)
+ {
+ g_free (pixels);
+ pixels = NULL;
+ }
+
+ return pixels;
+}
+
+
+
diff --git a/libs/rgbe/rgbe.h b/libs/rgbe/rgbe.h
new file mode 100644
index 0000000..e05f56b
--- /dev/null
+++ b/libs/rgbe/rgbe.h
@@ -0,0 +1,96 @@
+
+/* This file is an image processing operation for GEGL
+ *
+ * GEGL 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 3 of the License, or (at your option) any later version.
+ *
+ * GEGL 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 GEGL; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2010 Danny Robson <danny blubinc net>
+ */
+
+
+#ifndef __RGBE_H__
+#define __RGBE_H__
+
+#include <glib.h>
+
+typedef struct _rgbe_file rgbe_file;
+
+/**
+ * rgbe_save_path:
+ * @param path: the path to write the rgbe file to
+ * @param width: the width of the image
+ * @param height: the height of the image
+ * @param pixels: RGB floating point pixel data
+ *
+ * Writes raw pixel data into an RGBE format file. The pixel data should be
+ * 'width x height' elements long, and consist of RGB floats.
+ *
+ * Returns TRUE on success.
+ */
+gboolean rgbe_save_path (const gchar *path,
+ guint width,
+ guint height,
+ gfloat *pixels);
+
+
+/**
+ * rgbe_load_path:
+ * @param path: the path to an RGBE format image file
+ *
+ * Reads the metadata for an RGBE formatted image, and allows future access
+ * to the pixel data and some elements of the image metadata (see
+ * rgbe_get_size and rgbe_read_scanlines).
+ *
+ * The caller should use rgbe_file_free when finished with the file structure.
+ *
+ * Returns NULL on failure.
+ */
+rgbe_file * rgbe_load_path (const gchar *path);
+
+
+/**
+ * rgbe_file_free:
+ * @param file: file structure to dispose of
+ *
+ * Destroy resources associated with an RGBE file. The pointer will become
+ * invalid after this operation. It is safe to pass a NULL pointer to this
+ * function.
+ */
+void rgbe_file_free (rgbe_file *file);
+
+
+/**
+ * rgbe_get_size:
+ * @param file: the image file to query
+ * @param x: pointer to store the width
+ * @param y: pointer to store the height
+ *
+ * Query the image for its width and height.
+ *
+ * Returns TRUE on success.
+ */
+gboolean rgbe_get_size (rgbe_file *file,
+ guint *x,
+ guint *y);
+
+
+/**
+ * rgbe_read_scanlines:
+ * @param file: an open image structure to read from
+ *
+ * Returns the image's RGBA formatted pixel data. It is the user's
+ * responsibility to free the memory when finished.
+ */
+gfloat * rgbe_read_scanlines (rgbe_file *file);
+
+#endif /* __RGBE_H__ */
diff --git a/operations/external/Makefile.am b/operations/external/Makefile.am
index b33746e..5d4e014 100644
--- a/operations/external/Makefile.am
+++ b/operations/external/Makefile.am
@@ -104,5 +104,14 @@ ppm_load_la_LIBADD = $(op_libs)
ppm_save_la_SOURCES = ppm-save.c
ppm_save_la_LIBADD = $(op_libs)
+# Dependencies are in our source tree
+ops += rgbe-load.la rgbe-save.la
+rgbe_load_la_SOURCES = rgbe-load.c
+rgbe_load_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libs
+rgbe_load_la_LIBADD = $(op_libs) $(top_builddir)/libs/rgbe/librgbe.la
+rgbe_save_la_SOURCES = rgbe-save.c
+rgbe_save_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libs
+rgbe_save_la_LIBADD = $(op_libs) $(top_builddir)/libs/rgbe/librgbe.la
+
opdir = $(libdir)/gegl- GEGL_API_VERSION@
op_LTLIBRARIES = $(ops)
diff --git a/operations/external/rgbe-load.c b/operations/external/rgbe-load.c
new file mode 100644
index 0000000..c06e71c
--- /dev/null
+++ b/operations/external/rgbe-load.c
@@ -0,0 +1,140 @@
+
+/* This file is an image processing operation for GEGL
+ *
+ * GEGL 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 3 of the License, or (at your option) any later version.
+ *
+ * GEGL 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 GEGL; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2010 Danny Robson <danny blubinc net>
+ */
+
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#ifdef GEGL_CHANT_PROPERTIES
+
+gegl_chant_file_path (path, _("File"), "", _("Path of file to load."))
+
+#else
+
+#define GEGL_CHANT_TYPE_SOURCE
+#define GEGL_CHANT_C_FILE "rgbe-load.c"
+
+#include "gegl-chant.h"
+
+#include "rgbe/rgbe.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+
+static const gchar* FORMAT = "RGBA float";
+
+
+static GeglRectangle
+gegl_rgbe_load_get_bounding_box (GeglOperation *operation)
+{
+ GeglChantO *o = GEGL_CHANT_PROPERTIES (operation);
+ GeglRectangle result = {0,0,0,0};
+ rgbe_file *file;
+ guint width, height;
+
+ gegl_operation_set_format (operation,
+ "output",
+ babl_format (FORMAT));
+
+ file = rgbe_load_path (o->path);
+ if (!file)
+ goto cleanup;
+
+ if (!rgbe_get_size (file, &width, &height))
+ goto cleanup;
+
+ result.width = width;
+ result.height = height;
+
+cleanup:
+ rgbe_file_free (file);
+ return result;
+}
+
+
+static gboolean
+gegl_rgbe_load_process (GeglOperation *operation,
+ GeglBuffer *output,
+ const GeglRectangle *result)
+{
+ GeglChantO *o = GEGL_CHANT_PROPERTIES (operation);
+ gboolean success = FALSE;
+ gfloat *pixels = NULL;
+ rgbe_file *file;
+ guint width, height;
+
+ file = rgbe_load_path (o->path);
+ if (!file)
+ goto cleanup;
+
+ if (!rgbe_get_size (file, &width, &height))
+ goto cleanup;
+
+ if (width != result->width ||
+ height != result->height)
+ goto cleanup;
+
+ pixels = rgbe_read_scanlines (file);
+ if (!pixels)
+ goto cleanup;
+
+ gegl_buffer_set (output, result, babl_format (FORMAT), pixels,
+ GEGL_AUTO_ROWSTRIDE);
+ success = TRUE;
+
+cleanup:
+ g_free (pixels);
+ rgbe_file_free (file);
+
+ return success;
+}
+
+
+static GeglRectangle
+gegl_rgbe_load_get_cached_region (GeglOperation *operation,
+ const GeglRectangle *roi)
+{
+ return gegl_rgbe_load_get_bounding_box (operation);
+}
+
+
+static void
+gegl_chant_class_init (GeglChantClass *klass)
+{
+ GeglOperationClass *operation_class;
+ GeglOperationSourceClass *source_class;
+
+ operation_class = GEGL_OPERATION_CLASS (klass);
+ source_class = GEGL_OPERATION_SOURCE_CLASS (klass);
+
+ source_class->process = gegl_rgbe_load_process;
+ operation_class->get_bounding_box = gegl_rgbe_load_get_bounding_box;
+ operation_class->get_cached_region = gegl_rgbe_load_get_cached_region;
+
+ operation_class->name = "gegl:rgbe-load";
+ operation_class->categories = "hidden";
+ operation_class->description = _("RGBE image loader (Radiance HDR format).");
+
+ gegl_extension_handler_register (".hdr", "gegl:rgbe-load");
+ gegl_extension_handler_register (".pic", "gegl:rgbe-load");
+}
+
+#endif
+
diff --git a/operations/external/rgbe-save.c b/operations/external/rgbe-save.c
new file mode 100644
index 0000000..2f65077
--- /dev/null
+++ b/operations/external/rgbe-save.c
@@ -0,0 +1,91 @@
+
+/* This file is an image processing operation for GEGL
+ *
+ * GEGL 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 3 of the License, or (at your option) any later version.
+ *
+ * GEGL 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 GEGL; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2010 Danny Robson <danny blubinc net>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+
+#ifdef GEGL_CHANT_PROPERTIES
+
+gegl_chant_string (path, _("File"), "",
+ _("Target path and filename, use '-' for stdout."))
+
+#else
+
+#define GEGL_CHANT_TYPE_SINK
+#define GEGL_CHANT_C_FILE "rgbe-save.c"
+
+#include "gegl-chant.h"
+#include "rgbe/rgbe.h"
+
+
+static const gchar *FORMAT = "RGB float";
+
+
+static gboolean
+gegl_rgbe_save_process (GeglOperation *operation,
+ GeglBuffer *input,
+ const GeglRectangle *rect)
+{
+ GeglChantO *o = GEGL_CHANT_PROPERTIES (operation);
+ gfloat *pixels = NULL;
+ gboolean success = FALSE;
+
+ /* Write the scanlines */
+ pixels = g_malloc (rect->width *
+ rect->height *
+ sizeof (pixels[0]) *
+ babl_format_get_n_components (babl_format (FORMAT)));
+
+ gegl_buffer_get (input, 1.0, rect, babl_format (FORMAT), pixels,
+ GEGL_AUTO_ROWSTRIDE);
+
+ if (!rgbe_save_path (o->path, rect->width, rect->height, pixels))
+ goto cleanup;
+ success = TRUE;
+
+cleanup:
+ g_free (pixels);
+ return success;
+}
+
+
+static void
+gegl_chant_class_init (GeglChantClass *klass)
+{
+ GeglOperationClass *operation_class;
+ GeglOperationSinkClass *sink_class;
+
+ operation_class = GEGL_OPERATION_CLASS (klass);
+ sink_class = GEGL_OPERATION_SINK_CLASS (klass);
+
+ sink_class->process = gegl_rgbe_save_process;
+ sink_class->needs_full = TRUE;
+
+ operation_class->name = "gegl:rgbe-save";
+ operation_class->categories = "output";
+ operation_class->description =
+ _("RGBE image saver (Radiance HDR format)");
+
+ gegl_extension_handler_register_saver (".hdr", "gegl:rgbe-save");
+ gegl_extension_handler_register_saver (".pic", "gegl:rgbe-save");
+}
+
+#endif
+
diff --git a/tests/compositions/data/car-stack-eighth.hdr b/tests/compositions/data/car-stack-eighth.hdr
new file mode 100644
index 0000000..0887c6c
Binary files /dev/null and b/tests/compositions/data/car-stack-eighth.hdr differ
diff --git a/tests/compositions/reference/rgbe-load.png b/tests/compositions/reference/rgbe-load.png
new file mode 100644
index 0000000..9924755
Binary files /dev/null and b/tests/compositions/reference/rgbe-load.png differ
diff --git a/tests/compositions/reference/rgbe-save.hdr b/tests/compositions/reference/rgbe-save.hdr
new file mode 100644
index 0000000..0887c6c
Binary files /dev/null and b/tests/compositions/reference/rgbe-save.hdr differ
diff --git a/tests/compositions/rgbe-load.xml b/tests/compositions/rgbe-load.xml
new file mode 100644
index 0000000..3aba412
--- /dev/null
+++ b/tests/compositions/rgbe-load.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<gegl>
+ <node operation='gegl:rgbe-load'>
+ <params>
+ <param name='path'>data/car-stack-eighth.hdr</param>
+ </params>
+ </node>
+</gegl>
+
diff --git a/tests/compositions/rgbe-save.xml b/tests/compositions/rgbe-save.xml
new file mode 100644
index 0000000..17476d0
--- /dev/null
+++ b/tests/compositions/rgbe-save.xml
@@ -0,0 +1,15 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<gegl>
+ <node operation='gegl:scale'>
+ <params>
+ <param name='x'>0.25</param>
+ <param name='y'>0.25</param>
+ </params>
+ </node>
+ <node operation='gegl:load'>
+ <params>
+ <param name='path'>data/car-stack.png</param>
+ </params>
+ </node>
+</gegl>
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]