diff --git a/src/plugin/media_reader/openexr/src/openexr.cpp b/src/plugin/media_reader/openexr/src/openexr.cpp index a668c17bd..b660d0653 100644 --- a/src/plugin/media_reader/openexr/src/openexr.cpp +++ b/src/plugin/media_reader/openexr/src/openexr.cpp @@ -500,10 +500,10 @@ void OpenEXRMediaReader::get_channel_names_by_layer( const auto &channels = header.channels(); for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { const std::string channel_name = i.name(); - const size_t dot_pos = channel_name.find("."); + const size_t dot_pos = channel_name.rfind("."); if (dot_pos != std::string::npos && dot_pos) { - // channel name has a dot separator - assume prefix is the 'layer' name which - // we shall assign as a separate MediaStream + // OpenEXR layer names may themselves contain dots. The final token is the + // channel component, so retain the full prefix as the MediaStream name. std::string layer_name = std::string(channel_name, 0, dot_pos); channel_names_by_layer[partname + layer_name].push_back(channel_name); } else { @@ -540,9 +540,11 @@ void OpenEXRMediaReader::stream_ids_from_exr_part( // make sure RGBA layer is first Stream stream_ids.emplace_back("RGBA"); } else { - // optherwise look for a layer matching *.rgb to pick as the first one + // Otherwise prefer conventional RGB layers or Blender's Combined beauty pass. for (auto p = channel_names_by_layer.begin(); p != channel_names_by_layer.end(); ++p) { - if (utility::to_lower(p->first).find(".rgb") != std::string::npos) { + const std::string layer_name = utility::to_lower(p->first); + if (layer_name.find(".rgb") != std::string::npos || + layer_name.ends_with(".combined") || layer_name == "combined") { stream_ids.emplace_back(p->first); channel_names_by_layer.erase(p); break; @@ -613,13 +615,22 @@ std::array OpenEXRMediaReader::pick_exr_channels_from_stream_ } }); + // The image buffer and shader support at most four interleaved channels. Keep + // malformed or unconventional layers from overrunning the fixed pixel type array. + if (exr_channels_to_load.size() > 4) { + exr_channels_to_load.resize(4); + } // At the moment we can handle either all channels are float 16 or all channels // are float 32 - we can't have a mix of channel types const auto &channels = header.channels(); - std::array pix_type; + std::array pix_type{ + static_cast(-1), + static_cast(-1), + static_cast(-1), + static_cast(-1)}; // fetch the channel type for each of the channels we will load int ii = 0; @@ -953,4 +964,4 @@ PixelInfo OpenEXRMediaReader::exr_buffer_pixel_picker( } return r; -} \ No newline at end of file +} diff --git a/src/plugin/media_reader/openexr/test/openexr_test.cpp b/src/plugin/media_reader/openexr/test/openexr_test.cpp index 26684e1ef..1c3adf73f 100644 --- a/src/plugin/media_reader/openexr/test/openexr_test.cpp +++ b/src/plugin/media_reader/openexr/test/openexr_test.cpp @@ -7,6 +7,8 @@ #include "xstudio/utility/caf_helpers.hpp" #include +#include + using namespace xstudio; using namespace xstudio::utility; using namespace xstudio::media_reader; @@ -35,3 +37,58 @@ TEST(OpenEXRMediaReaderTest, Test) { EXPECT_TRUE(got_image) << "Should be supported"; } + +TEST(OpenEXRMediaReaderTest, BlenderMultilayerSinglePart) { + OpenEXRMediaReader reader; + const caf::uri uri = + posix_path_to_uri(TEST_RESOURCE "/media/blender_multilayer.exr"); + media::MediaDetail detail; + ASSERT_NO_THROW(detail = reader.detail(uri)); + + std::vector stream_names; + for (const auto &stream : detail.streams_) { + stream_names.emplace_back(stream.name_); + } + EXPECT_EQ( + stream_names, + (std::vector{ + "beauty.Combined", + "beauty.CryptoAsset00", + "beauty.CryptoAsset01", + "beauty.CryptoAsset02", + "beauty.CryptoMaterial00", + "beauty.CryptoMaterial01", + "beauty.CryptoMaterial02", + "beauty.CryptoObject00", + "beauty.CryptoObject01", + "beauty.CryptoObject02", + "beauty.Depth", + "beauty.Emission", + "beauty.Noisy Image", + "volume_high.Combined", + "volume_high.Noisy Image", + "volume_low.Combined", + "volume_low.Noisy Image"})); + + const media::AVFrameID frame( + uri, + 1, + 1, + media::FS_ON_DISK, + 0, + 1.0f, + utility::FrameRate(timebase::k_flicks_24fps), + "beauty.Combined"); + ImageBufPtr image; + ASSERT_NO_THROW(image = reader.image(frame)); + + ASSERT_TRUE(image); + EXPECT_EQ(image->shader_params()["num_channels"].get(), 4); + EXPECT_EQ( + image->params()["channel_names"].get>(), + (std::vector{ + "beauty.Combined.R", + "beauty.Combined.G", + "beauty.Combined.B", + "beauty.Combined.A"})); +} diff --git a/test_resource/media/blender_multilayer.exr b/test_resource/media/blender_multilayer.exr new file mode 100644 index 000000000..be2ad09d8 Binary files /dev/null and b/test_resource/media/blender_multilayer.exr differ