mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-07-04 01:43: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;
|
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);
|
||||||
|
|
||||||
|
|
|
@ -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 (...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue