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; GimpPrecision image_precision;
GimpImage *image = NULL; GimpImage *image = NULL;
GimpImageType layer_type; GimpImageType layer_type;
GimpLayer *layer; gint layer_count = 0;
gboolean layers_only;
const Babl *format; const Babl *format;
GeglBuffer *buffer = NULL;
gint bpp; gint bpp;
gint tile_height; gint tile_height;
gchar *pixels = NULL; gchar *pixels = NULL;
@ -285,10 +285,26 @@ load_image (GFile *file,
gimp_image_set_color_profile (image, profile); gimp_image_set_color_profile (image, profile);
} }
layer = gimp_layer_new (image, _("Background"), width, height, exr_loader_get_layer_info (loader, &layer_count, &layers_only);
/* 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++)
{
GimpLayer *layer;
GeglBuffer *buffer = NULL;
gchar *layer_name = NULL;
if (i > -1)
layer_name = exr_loader_get_layer_name (loader, i);
else
layer_name = _("Background");
layer = gimp_layer_new (image, layer_name, width, height,
layer_type, 100, layer_type, 100,
gimp_image_get_default_new_layer_mode (image)); gimp_image_get_default_new_layer_mode (image));
gimp_image_insert_layer (image, layer, NULL, 0); gimp_image_insert_layer (image, layer, NULL, -1);
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)); buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
format = gimp_drawable_get_format (GIMP_DRAWABLE (layer)); format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
@ -301,18 +317,17 @@ load_image (GFile *file,
{ {
gint end; gint end;
gint num; gint num;
gint i;
end = MIN (begin + tile_height, height); end = MIN (begin + tile_height, height);
num = end - begin; num = end - begin;
for (i = 0; i < num; i++) for (gint j = 0; j < num; j++)
{ {
gint retval; gint retval;
retval = exr_loader_read_pixel_row (loader, retval = exr_loader_read_pixel_row (loader,
pixels + (i * width * bpp), pixels + (j * width * bpp),
bpp, begin + i); bpp, begin + j, i);
if (retval < 0) if (retval < 0)
{ {
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
@ -328,6 +343,10 @@ load_image (GFile *file,
gimp_progress_update ((gdouble) begin / (gdouble) height); gimp_progress_update ((gdouble) begin / (gdouble) height);
} }
g_clear_object (&buffer);
g_clear_pointer (&pixels, g_free);
}
/* try to read the file comment */ /* try to read the file comment */
comment = exr_loader_get_comment (loader); comment = exr_loader_get_comment (loader);
if (comment) if (comment)
@ -390,8 +409,6 @@ load_image (GFile *file,
out: out:
g_clear_object (&profile); g_clear_object (&profile);
g_clear_object (&buffer);
g_clear_pointer (&pixels, g_free);
g_clear_pointer (&comment, g_free); g_clear_pointer (&comment, g_free);
g_clear_pointer (&loader, exr_loader_unref); g_clear_pointer (&loader, exr_loader_unref);

View file

@ -81,42 +81,73 @@ struct _EXRLoader
file_(filename), file_(filename),
data_window_(file_.header().dataWindow()), data_window_(file_.header().dataWindow()),
channels_(file_.header().channels()) 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; const Channel* chan;
can_load_ = true; can_load_ = true;
if (channels_.findChannel("R") || if (channels_.findChannel(prefix + "R") ||
channels_.findChannel("G") || channels_.findChannel(prefix + "G") ||
channels_.findChannel("B")) channels_.findChannel(prefix + "B"))
{ {
format_string_ = "RGB"; format_string_ = "RGB";
image_type_ = IMAGE_TYPE_RGB; image_type_ = IMAGE_TYPE_RGB;
if ((chan = channels_.findChannel("R"))) if ((chan = channels_.findChannel(prefix + "R")))
pt_ = chan->type; pt_ = chan->type;
else if ((chan = channels_.findChannel("G"))) else if ((chan = channels_.findChannel(prefix + "G")))
pt_ = chan->type; pt_ = chan->type;
else else
pt_ = channels_.findChannel("B")->type; pt_ = channels_.findChannel(prefix + "B")->type;
} }
else if (channels_.findChannel("Y") && else if (channels_.findChannel(prefix + "Y") &&
(channels_.findChannel("RY") || (channels_.findChannel(prefix + "RY") ||
channels_.findChannel("BY"))) channels_.findChannel(prefix + "BY")))
{ {
format_string_ = "Y'CbCr"; format_string_ = "Y'CbCr";
image_type_ = IMAGE_TYPE_YUV; image_type_ = IMAGE_TYPE_YUV;
/* TODO: Use RGBA interface to incorporate /* TODO: Use RGBA interface to incorporate
* RY/BY chroma channels */ * 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"; format_string_ = "Y";
image_type_ = IMAGE_TYPE_GRAY; image_type_ = IMAGE_TYPE_GRAY;
pt_ = channels_.findChannel("Y")->type; pt_ = channels_.findChannel(prefix + "Y")->type;
} }
else 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; has_alpha_ = true;
} }
else else
@ -177,19 +208,31 @@ struct _EXRLoader
format_string_.append(" float"); format_string_.append(" float");
bpc_ = 4; bpc_ = 4;
} }
return can_load_;
} }
int readPixelRow(char *pixels, int readPixelRow(char *pixels,
int bpp, int bpp,
int row) int row,
int layer_index)
{ {
const int actual_row = data_window_.min.y + row; const int actual_row = data_window_.min.y + row;
FrameBuffer fb; FrameBuffer fb;
std::set<string> layerNames;
std::string prefix = "";
// This is necessary because OpenEXR expects the buffer to begin at // This is necessary because OpenEXR expects the buffer to begin at
// (0, 0). Though it probably results in some unmapped address, // (0, 0). Though it probably results in some unmapped address,
// hopefully OpenEXR will not make use of it. :/ // hopefully OpenEXR will not make use of it. :/
char* base = pixels - (data_window_.min.x * bpp); 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_) switch (image_type_)
{ {
case IMAGE_TYPE_UNKNOWN_1_CHANNEL: case IMAGE_TYPE_UNKNOWN_1_CHANNEL:
@ -198,21 +241,21 @@ struct _EXRLoader
case IMAGE_TYPE_YUV: case IMAGE_TYPE_YUV:
case IMAGE_TYPE_GRAY: 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()) 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; break;
case IMAGE_TYPE_RGB: case IMAGE_TYPE_RGB:
default: default:
fb.insert("R", Slice(pt_, base + (bpc_ * 0), bpp, 0, 1, 1, 0.0)); fb.insert(prefix + "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(prefix + "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 + "B", Slice(pt_, base + (bpc_ * 2), bpp, 0, 1, 1, 0.0));
if (hasAlpha()) 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; 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_; size_t refcount_;
InputFile file_; InputFile file_;
const Box2i data_window_; const Box2i data_window_;
@ -423,6 +490,8 @@ struct _EXRLoader
int bpc_; int bpc_;
EXRImageType image_type_; EXRImageType image_type_;
bool has_alpha_; bool has_alpha_;
bool layers_only_;
int layer_count_;
bool can_load_; bool can_load_;
std::string format_string_; std::string format_string_;
std::string unknown_channel_name_; std::string unknown_channel_name_;
@ -550,17 +619,33 @@ exr_loader_get_xmp (EXRLoader *loader,
return loader->getXmp (size); 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 int
exr_loader_read_pixel_row (EXRLoader *loader, exr_loader_read_pixel_row (EXRLoader *loader,
char *pixels, char *pixels,
int bpp, int bpp,
int row) int row,
int layer_index)
{ {
int retval = -1; int retval = -1;
// Don't let any exceptions propagate to the C layer. // Don't let any exceptions propagate to the C layer.
try try
{ {
retval = loader->readPixelRow(pixels, bpp, row); retval = loader->readPixelRow(pixels, bpp, row, layer_index);
} }
catch (...) catch (...)
{ {

View file

@ -60,10 +60,17 @@ guchar * exr_loader_get_exif (EXRLoader *loader,
guchar * exr_loader_get_xmp (EXRLoader *loader, guchar * exr_loader_get_xmp (EXRLoader *loader,
guint *size); 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, int exr_loader_read_pixel_row (EXRLoader *loader,
char *pixels, char *pixels,
int bpp, int bpp,
int row); int row,
int layer_index);
G_END_DECLS G_END_DECLS