From 8d89efaff5bd4f397e711b2b511d2841e39b8526 Mon Sep 17 00:00:00 2001 From: Mukund Sivaraman Date: Sat, 4 May 2013 07:25:49 +0530 Subject: [PATCH] file-exr: Add initial implementation (loader) This is a basic implementation of an OpenEXR loader. This "infrastructure" is required for any further work. It consists of: * The build system changes. * A C wrapper around the OpenEXR library, which is necessary as it's not possible to intermix GIMP's code with C++ code. * A basic image loader. Chroma is not supported currently, and some other weird files like multi-view files are unsupported. These can be added when necessary. There is no UI, but it should be straightforward to add new features like this on top of this work. --- configure.ac | 31 +++ plug-ins/Makefile.am | 5 + plug-ins/file-exr/.gitignore | 7 + plug-ins/file-exr/Makefile.am | 51 +++++ plug-ins/file-exr/file-exr.c | 303 +++++++++++++++++++++++++++ plug-ins/file-exr/openexr-wrapper.cc | 283 +++++++++++++++++++++++++ plug-ins/file-exr/openexr-wrapper.h | 61 ++++++ 7 files changed, 741 insertions(+) create mode 100644 plug-ins/file-exr/.gitignore create mode 100644 plug-ins/file-exr/Makefile.am create mode 100644 plug-ins/file-exr/file-exr.c create mode 100644 plug-ins/file-exr/openexr-wrapper.cc create mode 100644 plug-ins/file-exr/openexr-wrapper.h diff --git a/configure.ac b/configure.ac index 6f3c5443ac..4c0b03ea5c 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,7 @@ m4_define([exif_required_version], [0.6.15]) m4_define([lcms_required_version], [2.2]) m4_define([libpng_required_version], [1.2.37]) m4_define([liblzma_required_version], [5.0.0]) +m4_define([openexr_required_version], [1.7.1]) AC_INIT([GIMP], [gimp_version], @@ -159,6 +160,9 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", AC_PROG_CC AM_PROG_CC_C_O +# Determine a C++ compiler to use +AC_PROG_CXX + # Initialize libtool LT_PREREQ([2.2]) LT_INIT([disable-static win32-dll]) @@ -1357,6 +1361,31 @@ AC_SUBST(FILE_XPM) AC_SUBST(XPM_LIBS) +################### +# Check for OpenEXR +################### + +AC_ARG_WITH(openexr, [ --without-openexr build without OpenEXR support]) + +have_openexr=no +if test "x$with_openexr" != xno; then + have_openexr=yes + PKG_CHECK_MODULES(OPENEXR, OpenEXR >= openexr_required_version, + FILE_EXR='file-exr$(EXEEXT)', + [have_openexr="no (OpenEXR not found)"]) +fi + +if test "x$have_openexr" = xyes; then + MIME_TYPES="$MIME_TYPES;image/x-exr" +fi + +AC_SUBST(OPENEXR_CFLAGS) +AC_SUBST(OPENEXR_LIBS) +AC_SUBST(FILE_EXR) + +AM_CONDITIONAL(HAVE_OPENEXR, test "x$have_openexr" = xyes) + + ################## # Check for webkit ################## @@ -2214,6 +2243,7 @@ plug-ins/color-rotate/Makefile plug-ins/color-rotate/images/Makefile plug-ins/file-bmp/Makefile plug-ins/file-compressor/Makefile +plug-ins/file-exr/Makefile plug-ins/file-faxg3/Makefile plug-ins/file-fits/Makefile plug-ins/file-fli/Makefile @@ -2366,6 +2396,7 @@ Optional Plug-Ins: JPEG: $jpeg_ok JPEG 2000: $have_jp2 MNG: $have_libmng + OpenEXR: $have_openexr PDF (import): $have_poppler PDF (export): $have_cairo_pdf PNG: $have_libpng diff --git a/plug-ins/Makefile.am b/plug-ins/Makefile.am index 8305398ddf..50eb64507d 100644 --- a/plug-ins/Makefile.am +++ b/plug-ins/Makefile.am @@ -4,6 +4,10 @@ if HAVE_WEBKIT help_browser = help-browser endif +if HAVE_OPENEXR +file_exr = file-exr +endif + if BUILD_JPEG file_jpeg = file-jpeg file_psd = file-psd @@ -58,6 +62,7 @@ SUBDIRS = \ color-rotate \ file-bmp \ $(file_compressor) \ + $(file_exr) \ file-faxg3 \ file-fits \ file-fli \ diff --git a/plug-ins/file-exr/.gitignore b/plug-ins/file-exr/.gitignore new file mode 100644 index 0000000000..6f8c1937d2 --- /dev/null +++ b/plug-ins/file-exr/.gitignore @@ -0,0 +1,7 @@ +/Makefile.in +/Makefile +/.deps +/_libs +/.libs +/file-exr +/file-exr.exe diff --git a/plug-ins/file-exr/Makefile.am b/plug-ins/file-exr/Makefile.am new file mode 100644 index 0000000000..e7c4ebe742 --- /dev/null +++ b/plug-ins/file-exr/Makefile.am @@ -0,0 +1,51 @@ +## Process this file with automake to produce Makefile.in + +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la + +if OS_WIN32 +mwindows = -mwindows +endif + +# if HAVE_WINDRES +# include $(top_srcdir)/build/windows/gimprc-plug-ins.rule +# file_exr_RC = file-exr.rc.o +# endif + +AM_LDFLAGS = $(mwindows) + +libexecdir = $(gimpplugindir)/plug-ins + +INCLUDES = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(GEGL_CFLAGS) \ + $(OPENEXR_CFLAGS) \ + -I$(includedir) + +libexec_PROGRAMS = file-exr + +file_exr_SOURCES = \ + file-exr.c \ + openexr-wrapper.cc \ + openexr-wrapper.h + +file_exr_LDADD = \ + $(OPENEXR_LIBS) \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpconfig) \ + $(libgimp) \ + $(libgimpcolor) \ + $(libgimpmath) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(file_exr_RC) diff --git a/plug-ins/file-exr/file-exr.c b/plug-ins/file-exr/file-exr.c new file mode 100644 index 0000000000..4758e9102d --- /dev/null +++ b/plug-ins/file-exr/file-exr.c @@ -0,0 +1,303 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimp/stdplugins-intl.h" + +#include "openexr-wrapper.h" + +#define LOAD_PROC "file-exr-load" +#define PLUG_IN_BINARY "file-exr" +#define PLUG_IN_ROLE "gimp-file-exr" +#define PLUG_IN_VERSION "0.0.0" + + +/* + * Declare some local functions. + */ +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gint32 load_image (const gchar *filename, + gboolean interactive, + GError **error); +/* + * Some global variables. + */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in the OpenEXR file format", + "This plug-in loads OpenEXR files. ", + "Dominik Ernst , " + "Mukund Sivaraman ", + "Dominik Ernst , " + "Mukund Sivaraman ", + PLUG_IN_VERSION, + N_("OpenEXR image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-exr"); + gimp_register_magic_load_handler (LOAD_PROC, + "exr", "", "0,lelong,20000630"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + GError *error = NULL; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0) + { + run_mode = param[0].data.d_int32; + + image_ID = load_image (param[1].data.d_string, + run_mode == GIMP_RUN_INTERACTIVE, &error); + + if (image_ID != -1) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static gint32 +load_image (const gchar *filename, + gboolean interactive, + GError **error) +{ + gint32 status = -1; + EXRLoader *loader; + int width; + int height; + gboolean has_alpha; + GimpImageBaseType image_type; + GimpPrecision image_precision; + gint32 image = -1; + GimpImageType layer_type; + int layer; + const Babl *format; + GeglBuffer *buffer = NULL; + int bpp; + int tile_height; + gchar *pixels = NULL; + int begin; + int end; + int num; + + loader = exr_loader_new (filename); + if (!loader) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error opening file '%s' for reading"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + width = exr_loader_get_width (loader); + height = exr_loader_get_height (loader); + if ((width < 1) || (height < 1)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error querying image dimensions from '%s'"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + has_alpha = exr_loader_has_alpha (loader) ? TRUE : FALSE; + + switch (exr_loader_get_precision (loader)) + { + case PREC_UINT: + image_precision = GIMP_PRECISION_U32; + break; + case PREC_HALF: + image_precision = GIMP_PRECISION_HALF; + break; + case PREC_FLOAT: + image_precision = GIMP_PRECISION_FLOAT; + break; + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error querying image precision from '%s'"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + switch (exr_loader_get_image_type (loader)) + { + case IMAGE_TYPE_RGB: + image_type = GIMP_RGB; + layer_type = has_alpha ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE; + break; + case IMAGE_TYPE_GRAY: + image_precision = GIMP_GRAY; + layer_type = has_alpha ? GIMP_GRAYA_IMAGE : GIMP_GRAY_IMAGE; + break; + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error querying image type from '%s'"), + gimp_filename_to_utf8 (filename)); + goto out; + } + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + image = gimp_image_new_with_precision (width, height, + image_type, image_precision); + if (image == -1) + { + g_set_error (error, 0, 0, + _("Could not create new image for '%s': %s"), + gimp_filename_to_utf8 (filename), gimp_get_pdb_error ()); + goto out; + } + + gimp_image_set_filename (image, filename); + + layer = gimp_layer_new (image, _("Background"), width, height, + layer_type, 100, GIMP_NORMAL_MODE); + gimp_image_insert_layer (image, layer, -1, 0); + + buffer = gimp_drawable_get_buffer (layer); + format = gimp_drawable_get_format (layer); + bpp = babl_format_get_bytes_per_pixel (format); + + tile_height = gimp_tile_height (); + pixels = g_new0 (gchar, tile_height * width * bpp); + + for (begin = 0; begin < height; begin += tile_height) + { + int retval; + int i; + end = MIN (begin + tile_height, height); + num = end - begin; + + for (i = 0; i < num; i++) + { + retval = exr_loader_read_pixel_row (loader, + pixels + (i * width * bpp), + bpp, begin + i); + if (retval < 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading pixel data from '%s'"), + gimp_filename_to_utf8 (filename)); + goto out; + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, begin, width, num), + 0, NULL, pixels, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((gdouble) begin / (gdouble) height); + } + + gimp_progress_update (1.0); + + status = image; + + out: + if (buffer) + g_object_unref (buffer); + + if ((status != image) && (image != -1)) + { + /* This should clean up any associated layers too. */ + gimp_image_delete (image); + } + + if (pixels) + g_free (pixels); + + exr_loader_unref (loader); + + return status; +} diff --git a/plug-ins/file-exr/openexr-wrapper.cc b/plug-ins/file-exr/openexr-wrapper.cc new file mode 100644 index 0000000000..3be6e2c90a --- /dev/null +++ b/plug-ins/file-exr/openexr-wrapper.cc @@ -0,0 +1,283 @@ +#include "config.h" + +#include "openexr-wrapper.h" + +#include +#include +#include +#include +#include + +#include + +using namespace Imf; +using namespace Imf::RgbaYca; +using namespace Imath; + +struct _EXRLoader +{ + _EXRLoader(const char* filename) : + refcount_(1), + file_(filename), + data_window_(file_.header().dataWindow()), + channels_(file_.header().channels()) + { + const Channel* chan; + + if (channels_.findChannel("R") || + channels_.findChannel("G") || + channels_.findChannel("B")) + { + format_string_ = "RGB"; + image_type_ = IMAGE_TYPE_RGB; + + if ((chan = channels_.findChannel("R"))) + pt_ = chan->type; + else if ((chan = channels_.findChannel("G"))) + pt_ = chan->type; + else + pt_ = channels_.findChannel("B")->type; + } + else if (channels_.findChannel("Y") && + (channels_.findChannel("RY") || + channels_.findChannel("BY"))) + { + format_string_ = "RGB"; + image_type_ = IMAGE_TYPE_RGB; + + pt_ = channels_.findChannel("Y")->type; + + // FIXME: no chroma handling for now. + throw; + } + else if (channels_.findChannel("Y")) + { + format_string_ = "Y"; + image_type_ = IMAGE_TYPE_GRAY; + + pt_ = channels_.findChannel("Y")->type; + } + else + { + throw; + } + + if (channels_.findChannel("A")) + { + format_string_.append("A"); + has_alpha_ = true; + } + else + { + has_alpha_ = false; + } + + switch (pt_) + { + case UINT: + format_string_.append(" u32"); + bpc_ = 4; + break; + case HALF: + format_string_.append(" half"); + bpc_ = 2; + break; + case FLOAT: + default: + format_string_.append(" float"); + bpc_ = 4; + } + } + + int readPixelRow(char* pixels, + int bpp, + int row) + { + const int actual_row = data_window_.min.y + row; + FrameBuffer fb; + // This is necessary because OpenEXR expects the buffer to begin at + // (0, 0). Though it probably results in some unmapped address, + // hopefully OpenEXR will not make use of it. :/ + char* base = pixels - (data_window_.min.x * bpp); + + switch (image_type_) + { + case IMAGE_TYPE_GRAY: + fb.insert("Y", Slice(pt_, base, bpp, 0, 1, 1, 0.5)); + if (hasAlpha()) + { + fb.insert("A", Slice(pt_, base + bpc_, bpp, 0, 1, 1, 1.0)); + } + break; + + case IMAGE_TYPE_RGB: + default: + fb.insert("R", Slice(pt_, base + (bpc_ * 0), bpp, 0, 1, 1, 0.0)); + fb.insert("G", Slice(pt_, base + (bpc_ * 1), bpp, 0, 1, 1, 0.0)); + fb.insert("B", Slice(pt_, base + (bpc_ * 2), bpp, 0, 1, 1, 0.0)); + if (hasAlpha()) + { + fb.insert("A", Slice(pt_, base + (bpc_ * 3), bpp, 0, 1, 1, 1.0)); + } + } + + file_.setFrameBuffer(fb); + file_.readPixels(actual_row); + + return 0; + } + + int getWidth() const { + return data_window_.max.x - data_window_.min.x + 1; + } + + int getHeight() const { + return data_window_.max.y - data_window_.min.y + 1; + } + + EXRPrecision getPrecision() const { + EXRPrecision prec; + + switch (pt_) + { + case UINT: + prec = PREC_UINT; + break; + case HALF: + prec = PREC_HALF; + break; + case FLOAT: + default: + prec = PREC_FLOAT; + } + + return prec; + } + + EXRImageType getImageType() const { + return image_type_; + } + + int hasAlpha() const { + return has_alpha_ ? 1 : 0; + } + + size_t refcount_; + InputFile file_; + const Box2i data_window_; + const ChannelList& channels_; + PixelType pt_; + int bpc_; + EXRImageType image_type_; + bool has_alpha_; + std::string format_string_; +}; + +EXRLoader* +exr_loader_new (const char *filename) +{ + EXRLoader* file; + + // Don't let any exceptions propagate to the C layer. + try + { + file = new EXRLoader(filename); + } + catch (...) + { + file = NULL; + } + + return file; +} + +EXRLoader* +exr_loader_ref (EXRLoader *loader) +{ + ++loader->refcount_; + return loader; +} + +void +exr_loader_unref (EXRLoader *loader) +{ + if (--loader->refcount_ == 0) + { + delete loader; + } +} + +int +exr_loader_get_width (EXRLoader *loader) +{ + int width; + // Don't let any exceptions propagate to the C layer. + try + { + width = loader->getWidth(); + } + catch (...) + { + width = -1; + } + + return width; +} + +int +exr_loader_get_height (EXRLoader *loader) +{ + int height; + // Don't let any exceptions propagate to the C layer. + try + { + height = loader->getHeight(); + } + catch (...) + { + height = -1; + } + + return height; +} + +EXRImageType +exr_loader_get_image_type (EXRLoader *loader) +{ + // This does not throw. + return loader->getImageType(); +} + +EXRPrecision +exr_loader_get_precision (EXRLoader *loader) +{ + // This does not throw. + return loader->getPrecision(); +} + +int +exr_loader_has_alpha (EXRLoader *loader) +{ + // This does not throw. + return loader->hasAlpha(); +} + +int +exr_loader_read_pixel_row (EXRLoader *loader, + char *pixels, + int bpp, + int row) +{ + int retval = -1; + // Don't let any exceptions propagate to the C layer. + try + { + retval = loader->readPixelRow(pixels, bpp, row); + } + catch (...) + { + retval = -1; + } + + return retval; +} diff --git a/plug-ins/file-exr/openexr-wrapper.h b/plug-ins/file-exr/openexr-wrapper.h new file mode 100644 index 0000000000..38276077dd --- /dev/null +++ b/plug-ins/file-exr/openexr-wrapper.h @@ -0,0 +1,61 @@ +#ifndef OPENEXR_WRAPPER_H +#define OPENEXR_WRAPPER_H + +/* Use C linkage so that the plug-in code written in C can use the + * wrapper. + */ +#ifdef __cplusplus +extern "C" { +#endif + +/* This is fully opaque on purpose, as the calling C code must not be + * exposed to more than this. + */ +typedef struct _EXRLoader EXRLoader; + +typedef enum { + PREC_UINT, + PREC_HALF, + PREC_FLOAT +} EXRPrecision; + +typedef enum { + IMAGE_TYPE_RGB, + IMAGE_TYPE_GRAY +} EXRImageType; + +EXRLoader * +exr_loader_new (const char *filename); + +EXRLoader * +exr_loader_ref (EXRLoader *loader); + +void +exr_loader_unref (EXRLoader *loader); + +int +exr_loader_get_width (EXRLoader *loader); + +int +exr_loader_get_height (EXRLoader *loader); + +EXRPrecision +exr_loader_get_precision (EXRLoader *loader); + +EXRImageType +exr_loader_get_image_type (EXRLoader *loader); + +int +exr_loader_has_alpha (EXRLoader *loader); + +int +exr_loader_read_pixel_row (EXRLoader *loader, + char *pixels, + int bpp, + int row); + +#ifdef __cplusplus +} +#endif + +#endif /* OPENEXR_WRAPPER_H */