From a060e96103e8189ca92a4327a69a754a9cf75dba Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 29 Feb 2024 13:50:18 +0100 Subject: [PATCH] Fix #101227: Crash and other issues with non-multiview multipart EXR Some software stores passes or layers as parts. This case was not supported by the OpenEXR reader yet. Pull Request: https://projects.blender.org/blender/blender/pulls/118867 --- .../imbuf/intern/openexr/openexr_api.cpp | 116 ++++++++++++++---- 1 file changed, 92 insertions(+), 24 deletions(-) diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index e6f627c096c..335091fe0cb 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -1430,13 +1430,33 @@ static int imb_exr_split_token(const char *str, const char *end, const char **to return int(end - *token); } +static void imb_exr_pass_name_from_channel(char *passname, + const ExrChannel *echan, + const char *channelname, + const bool has_xyz_channels) +{ + const int passname_maxncpy = EXR_TOT_MAXNAME; + + if (echan->chan_id == 'Z' && (!has_xyz_channels || BLI_strcaseeq(channelname, "depth"))) { + BLI_strncpy(passname, "Depth", passname_maxncpy); + } + else if (echan->chan_id == 'Y' && !has_xyz_channels) { + BLI_strncpy(passname, channelname, passname_maxncpy); + } + else if (ELEM(echan->chan_id, 'R', 'G', 'B', 'A', 'V', 'X', 'Y', 'Z')) { + BLI_strncpy(passname, "Combined", passname_maxncpy); + } + else { + BLI_strncpy(passname, channelname, passname_maxncpy); + } +} + static int imb_exr_split_channel_name(ExrChannel *echan, char *layname, char *passname, bool has_xyz_channels) { const int layname_maxncpy = EXR_TOT_MAXNAME; - const int passname_maxncpy = EXR_TOT_MAXNAME; const char *name = echan->m->name.c_str(); const char *end = name + strlen(name); const char *token; @@ -1451,20 +1471,7 @@ static int imb_exr_split_channel_name(ExrChannel *echan, * versions of the listed channels. */ echan->chan_id = BLI_toupper_ascii(name[0]); layname[0] = '\0'; - - if (echan->chan_id == 'Z' && !has_xyz_channels) { - BLI_strncpy(passname, "Depth", passname_maxncpy); - } - else if (echan->chan_id == 'Y' && !has_xyz_channels) { - BLI_strncpy(passname, name, passname_maxncpy); - } - else if (ELEM(echan->chan_id, 'R', 'G', 'B', 'A', 'V', 'X', 'Y', 'Z')) { - BLI_strncpy(passname, "Combined", passname_maxncpy); - } - else { - BLI_strncpy(passname, name, passname_maxncpy); - } - + imb_exr_pass_name_from_channel(passname, echan, name, has_xyz_channels); return 1; } @@ -1520,14 +1527,20 @@ static int imb_exr_split_channel_name(ExrChannel *echan, } end -= len + 1; /* +1 to skip '.' separator */ - /* second token is pass name */ - len = imb_exr_split_token(name, end, &token); - if (len == 0) { - printf("multilayer read: bad channel name: %s\n", name); - return 0; + if (end > name) { + /* second token is pass name */ + len = imb_exr_split_token(name, end, &token); + if (len == 0) { + printf("multilayer read: bad channel name: %s\n", name); + return 0; + } + BLI_strncpy(passname, token, len + 1); + end -= len + 1; /* +1 to skip '.' separator */ + } + else { + /* Single token, determine pass name from channel name. */ + imb_exr_pass_name_from_channel(passname, echan, channelname, has_xyz_channels); } - BLI_strncpy(passname, token, len + 1); - end -= len + 1; /* +1 to skip '.' separator */ /* all preceding tokens combined as layer name */ if (end > name) { @@ -1593,10 +1606,65 @@ static bool exr_has_xyz_channels(ExrHandle *exr_handle) return x_found && y_found && z_found; } -static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data) +/* Replacement for OpenEXR GetChannelsInMultiPartFile, that also handles the + * case where parts are used for passes instead of multiview. */ +static std::vector exr_channels_in_multi_part_file( + const MultiPartInputFile &file) { std::vector channels; - GetChannelsInMultiPartFile(*data->ifile, channels); + + /* Detect if file has multiview. */ + StringVector multiview; + bool has_multiview = false; + if (file.parts() == 1) { + if (hasMultiView(file.header(0))) { + multiview = multiView(file.header(0)); + has_multiview = true; + } + } + + /* Get channels from each part. */ + for (int p = 0; p < file.parts(); p++) { + const ChannelList &c = file.header(p).channels(); + + std::string part_view = ""; + if (file.header(p).hasView()) { + part_view = file.header(p).view(); + } + std::string part_name = ""; + if (file.header(p).hasName()) { + part_name = file.header(p).name(); + } + + for (ChannelList::ConstIterator i = c.begin(); i != c.end(); i++) { + MultiViewChannelName m; + m.name = std::string(i.name()); + m.internal_name = m.name; + + if (has_multiview) { + m.view = viewFromChannelName(m.name, multiview); + m.name = removeViewName(m.internal_name, m.view); + } + else { + m.view = part_view; + } + + /* Prepend part name as potential layer or pass name. */ + if (!part_name.empty()) { + m.name = part_name + "." + m.name; + } + + m.part_number = p; + channels.push_back(m); + } + } + + return channels; +} + +static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data) +{ + std::vector channels = exr_channels_in_multi_part_file(*data->ifile); imb_exr_get_views(*data->ifile, *data->multiView);