mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-07-03 09:23:24 +00:00
Issue #4379: Add support for loading multi-layer OpenEXR images
This commit is contained in:
parent
9e072b2ea0
commit
3693f90aca
3 changed files with 172 additions and 63 deletions
|
@ -179,9 +179,9 @@ load_image (GFile *file,
|
|||
GimpPrecision image_precision;
|
||||
GimpImage *image = NULL;
|
||||
GimpImageType layer_type;
|
||||
GimpLayer *layer;
|
||||
gint layer_count = 0;
|
||||
gboolean layers_only;
|
||||
const Babl *format;
|
||||
GeglBuffer *buffer = NULL;
|
||||
gint bpp;
|
||||
gint tile_height;
|
||||
gchar *pixels = NULL;
|
||||
|
@ -285,47 +285,66 @@ load_image (GFile *file,
|
|||
gimp_image_set_color_profile (image, profile);
|
||||
}
|
||||
|
||||
layer = gimp_layer_new (image, _("Background"), width, height,
|
||||
layer_type, 100,
|
||||
gimp_image_get_default_new_layer_mode (image));
|
||||
gimp_image_insert_layer (image, layer, NULL, 0);
|
||||
exr_loader_get_layer_info (loader, &layer_count, &layers_only);
|
||||
|
||||
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
||||
format = gimp_drawable_get_format (GIMP_DRAWABLE (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)
|
||||
/* i == -1 represents an image with no named layers, just raw channels.
|
||||
* If layers_only is TRUE, there are only named layers and we skip these
|
||||
* entirely and just read the layers */
|
||||
for (gint i = (-1 + layers_only); i < layer_count; i++)
|
||||
{
|
||||
gint end;
|
||||
gint num;
|
||||
gint i;
|
||||
GimpLayer *layer;
|
||||
GeglBuffer *buffer = NULL;
|
||||
gchar *layer_name = NULL;
|
||||
|
||||
end = MIN (begin + tile_height, height);
|
||||
num = end - begin;
|
||||
if (i > -1)
|
||||
layer_name = exr_loader_get_layer_name (loader, i);
|
||||
else
|
||||
layer_name = _("Background");
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
layer = gimp_layer_new (image, layer_name, width, height,
|
||||
layer_type, 100,
|
||||
gimp_image_get_default_new_layer_mode (image));
|
||||
gimp_image_insert_layer (image, layer, NULL, -1);
|
||||
|
||||
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
||||
format = gimp_drawable_get_format (GIMP_DRAWABLE (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)
|
||||
{
|
||||
gint retval;
|
||||
gint end;
|
||||
gint num;
|
||||
|
||||
retval = exr_loader_read_pixel_row (loader,
|
||||
pixels + (i * width * bpp),
|
||||
bpp, begin + i);
|
||||
if (retval < 0)
|
||||
end = MIN (begin + tile_height, height);
|
||||
num = end - begin;
|
||||
|
||||
for (gint j = 0; j < num; j++)
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
_("Error reading pixel data from '%s'"),
|
||||
gimp_file_get_utf8_name (file));
|
||||
goto out;
|
||||
gint retval;
|
||||
|
||||
retval = exr_loader_read_pixel_row (loader,
|
||||
pixels + (j * width * bpp),
|
||||
bpp, begin + j, i);
|
||||
if (retval < 0)
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
_("Error reading pixel data from '%s'"),
|
||||
gimp_file_get_utf8_name (file));
|
||||
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);
|
||||
}
|
||||
|
||||
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, begin, width, num),
|
||||
0, NULL, pixels, GEGL_AUTO_ROWSTRIDE);
|
||||
|
||||
gimp_progress_update ((gdouble) begin / (gdouble) height);
|
||||
g_clear_object (&buffer);
|
||||
g_clear_pointer (&pixels, g_free);
|
||||
}
|
||||
|
||||
/* try to read the file comment */
|
||||
|
@ -390,8 +409,6 @@ load_image (GFile *file,
|
|||
|
||||
out:
|
||||
g_clear_object (&profile);
|
||||
g_clear_object (&buffer);
|
||||
g_clear_pointer (&pixels, g_free);
|
||||
g_clear_pointer (&comment, g_free);
|
||||
g_clear_pointer (&loader, exr_loader_unref);
|
||||
|
||||
|
|
|
@ -81,42 +81,73 @@ struct _EXRLoader
|
|||
file_(filename),
|
||||
data_window_(file_.header().dataWindow()),
|
||||
channels_(file_.header().channels())
|
||||
{
|
||||
std::set<string> layerNames;
|
||||
bool loaded;
|
||||
|
||||
channels_.layers (layerNames);
|
||||
layers_only_ = false;
|
||||
layer_count_ = layerNames.size();
|
||||
|
||||
loaded = initializeImage (false);
|
||||
|
||||
/* The OpenEXR image may have named layers only, not loose channels.
|
||||
* In that case, we need to go through each layer name and append it
|
||||
* to the call for findChannel () */
|
||||
if (! loaded)
|
||||
{
|
||||
layers_only_ = true;
|
||||
|
||||
|
||||
for (std::set<std::string>::const_iterator i = layerNames.begin();
|
||||
i != layerNames.end(); ++i)
|
||||
{
|
||||
loaded = initializeImage (true, *i + ".");
|
||||
|
||||
if (loaded)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool initializeImage (bool has_layers,
|
||||
const std::string &prefix = "")
|
||||
{
|
||||
const Channel* chan;
|
||||
|
||||
can_load_ = true;
|
||||
|
||||
if (channels_.findChannel("R") ||
|
||||
channels_.findChannel("G") ||
|
||||
channels_.findChannel("B"))
|
||||
if (channels_.findChannel(prefix + "R") ||
|
||||
channels_.findChannel(prefix + "G") ||
|
||||
channels_.findChannel(prefix + "B"))
|
||||
{
|
||||
format_string_ = "RGB";
|
||||
image_type_ = IMAGE_TYPE_RGB;
|
||||
|
||||
if ((chan = channels_.findChannel("R")))
|
||||
if ((chan = channels_.findChannel(prefix + "R")))
|
||||
pt_ = chan->type;
|
||||
else if ((chan = channels_.findChannel("G")))
|
||||
else if ((chan = channels_.findChannel(prefix + "G")))
|
||||
pt_ = chan->type;
|
||||
else
|
||||
pt_ = channels_.findChannel("B")->type;
|
||||
pt_ = channels_.findChannel(prefix + "B")->type;
|
||||
}
|
||||
else if (channels_.findChannel("Y") &&
|
||||
(channels_.findChannel("RY") ||
|
||||
channels_.findChannel("BY")))
|
||||
else if (channels_.findChannel(prefix + "Y") &&
|
||||
(channels_.findChannel(prefix + "RY") ||
|
||||
channels_.findChannel(prefix + "BY")))
|
||||
{
|
||||
format_string_ = "Y'CbCr";
|
||||
image_type_ = IMAGE_TYPE_YUV;
|
||||
|
||||
/* TODO: Use RGBA interface to incorporate
|
||||
* RY/BY chroma channels */
|
||||
pt_ = channels_.findChannel("Y")->type;
|
||||
pt_ = channels_.findChannel(prefix + "Y")->type;
|
||||
}
|
||||
else if (channels_.findChannel("Y"))
|
||||
else if (channels_.findChannel(prefix + "Y"))
|
||||
{
|
||||
format_string_ = "Y";
|
||||
image_type_ = IMAGE_TYPE_GRAY;
|
||||
|
||||
pt_ = channels_.findChannel("Y")->type;
|
||||
pt_ = channels_.findChannel(prefix + "Y")->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -152,9 +183,9 @@ struct _EXRLoader
|
|||
}
|
||||
}
|
||||
|
||||
if (channels_.findChannel("A"))
|
||||
if (channels_.findChannel(prefix + "A"))
|
||||
{
|
||||
format_string_.append("A");
|
||||
format_string_.append(prefix + "A");
|
||||
has_alpha_ = true;
|
||||
}
|
||||
else
|
||||
|
@ -177,19 +208,31 @@ struct _EXRLoader
|
|||
format_string_.append(" float");
|
||||
bpc_ = 4;
|
||||
}
|
||||
|
||||
return can_load_;
|
||||
}
|
||||
|
||||
int readPixelRow(char *pixels,
|
||||
int bpp,
|
||||
int row)
|
||||
int row,
|
||||
int layer_index)
|
||||
{
|
||||
const int actual_row = data_window_.min.y + row;
|
||||
FrameBuffer fb;
|
||||
const int actual_row = data_window_.min.y + row;
|
||||
FrameBuffer fb;
|
||||
std::set<string> layerNames;
|
||||
std::string prefix = "";
|
||||
// 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);
|
||||
|
||||
if (layer_index > -1)
|
||||
{
|
||||
channels_.layers (layerNames);
|
||||
|
||||
prefix = *std::next(layerNames.begin(), layer_index) + ".";
|
||||
}
|
||||
|
||||
switch (image_type_)
|
||||
{
|
||||
case IMAGE_TYPE_UNKNOWN_1_CHANNEL:
|
||||
|
@ -198,21 +241,21 @@ struct _EXRLoader
|
|||
|
||||
case IMAGE_TYPE_YUV:
|
||||
case IMAGE_TYPE_GRAY:
|
||||
fb.insert("Y", Slice(pt_, base, bpp, 0, 1, 1, 0.5));
|
||||
fb.insert(prefix + "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));
|
||||
fb.insert(prefix + "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));
|
||||
fb.insert(prefix + "R", Slice(pt_, base + (bpc_ * 0), bpp, 0, 1, 1, 0.0));
|
||||
fb.insert(prefix + "G", Slice(pt_, base + (bpc_ * 1), bpp, 0, 1, 1, 0.0));
|
||||
fb.insert(prefix + "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));
|
||||
fb.insert(prefix + "A", Slice(pt_, base + (bpc_ * 3), bpp, 0, 1, 1, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,6 +458,30 @@ struct _EXRLoader
|
|||
return result;
|
||||
}
|
||||
|
||||
gchar *getLayerName(int index) const {
|
||||
gchar *result = NULL;
|
||||
|
||||
if (index > -1 && index < layer_count_)
|
||||
{
|
||||
std::set<string> layerNames;
|
||||
std::string name;
|
||||
|
||||
channels_.layers (layerNames);
|
||||
|
||||
name = *std::next(layerNames.begin(), index);
|
||||
result = (gchar *) g_memdup2 (name.c_str () + '\0',
|
||||
strlen (name.c_str ()) + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int getLayerInfo(gint *num_layers, int *layers_only) const {
|
||||
*num_layers = layer_count_;
|
||||
*layers_only = layers_only_;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t refcount_;
|
||||
InputFile file_;
|
||||
const Box2i data_window_;
|
||||
|
@ -423,6 +490,8 @@ struct _EXRLoader
|
|||
int bpc_;
|
||||
EXRImageType image_type_;
|
||||
bool has_alpha_;
|
||||
bool layers_only_;
|
||||
int layer_count_;
|
||||
bool can_load_;
|
||||
std::string format_string_;
|
||||
std::string unknown_channel_name_;
|
||||
|
@ -550,17 +619,33 @@ exr_loader_get_xmp (EXRLoader *loader,
|
|||
return loader->getXmp (size);
|
||||
}
|
||||
|
||||
gchar *
|
||||
exr_loader_get_layer_name (EXRLoader *loader,
|
||||
gint index)
|
||||
{
|
||||
return loader->getLayerName (index);
|
||||
}
|
||||
|
||||
int
|
||||
exr_loader_get_layer_info (EXRLoader *loader,
|
||||
gint *num_layers,
|
||||
gboolean *layers_only)
|
||||
{
|
||||
return loader->getLayerInfo (num_layers, layers_only);
|
||||
}
|
||||
|
||||
int
|
||||
exr_loader_read_pixel_row (EXRLoader *loader,
|
||||
char *pixels,
|
||||
int bpp,
|
||||
int row)
|
||||
char *pixels,
|
||||
int bpp,
|
||||
int row,
|
||||
int layer_index)
|
||||
{
|
||||
int retval = -1;
|
||||
// Don't let any exceptions propagate to the C layer.
|
||||
try
|
||||
{
|
||||
retval = loader->readPixelRow(pixels, bpp, row);
|
||||
retval = loader->readPixelRow(pixels, bpp, row, layer_index);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
|
|
@ -60,10 +60,17 @@ guchar * exr_loader_get_exif (EXRLoader *loader,
|
|||
guchar * exr_loader_get_xmp (EXRLoader *loader,
|
||||
guint *size);
|
||||
|
||||
gchar * exr_loader_get_layer_name (EXRLoader *loader,
|
||||
gint index);
|
||||
int exr_loader_get_layer_info (EXRLoader *loader,
|
||||
gint *num_layers,
|
||||
gboolean *layers_only);
|
||||
|
||||
int exr_loader_read_pixel_row (EXRLoader *loader,
|
||||
char *pixels,
|
||||
int bpp,
|
||||
int row);
|
||||
int row,
|
||||
int layer_index);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue