Issue #4379: Add support for loading multi-layer OpenEXR images

This commit is contained in:
Alx Sa 2025-05-30 18:46:23 +00:00 committed by Bruno
parent 9e072b2ea0
commit 3693f90aca
3 changed files with 172 additions and 63 deletions

View file

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

View file

@ -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 (...)
{

View file

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