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.
This commit is contained in:
Mukund Sivaraman 2013-05-04 07:25:49 +05:30
parent 77087aba2d
commit 8d89efaff5
7 changed files with 741 additions and 0 deletions

View File

@ -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

View File

@ -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 \

7
plug-ins/file-exr/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/Makefile.in
/Makefile
/.deps
/_libs
/.libs
/file-exr
/file-exr.exe

View File

@ -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)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#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 <dernst@gmx.de>, "
"Mukund Sivaraman <muks@banu.com>",
"Dominik Ernst <dernst@gmx.de>, "
"Mukund Sivaraman <muks@banu.com>",
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;
}

View File

@ -0,0 +1,283 @@
#include "config.h"
#include "openexr-wrapper.h"
#include <ImfInputFile.h>
#include <ImfChannelList.h>
#include <ImfRgbaFile.h>
#include <ImfRgbaYca.h>
#include <ImfStandardAttributes.h>
#include <string>
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;
}

View File

@ -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 */