OpenEXR: Write colorspace metadata for multilayer EXR
Previously it was only working for the single layer case. For multipart we write the colorspace in each part. For single part we write the first non-data colorspace, and hope data passes will be identified based on channel name like Blender does (e.g. XYZ instead of RGB). Reading is unchanged and still the same as before, in that it only reads the colorspace from the first part. There is only one color space per image datablock, so we can not store anything more currently. In practice it would be unusual for all passes in a file not to either have the same colorspace or be data. All the compositor file output test images were updated to include the metadata, so that the test will check if the metadata is there. Ref #144911 Pull Request: https://projects.blender.org/blender/blender/pulls/146809
This commit is contained in:
committed by
Brecht Van Lommel
parent
be1619f4ad
commit
e91c8300a6
@@ -703,7 +703,8 @@ static float *image_exr_from_scene_linear_to_output(float *rect,
|
||||
const int height,
|
||||
const int channels,
|
||||
const ImageFormatData *imf,
|
||||
Vector<float *> &tmp_output_rects)
|
||||
Vector<float *> &tmp_output_rects,
|
||||
blender::StringRefNull &r_colorspace)
|
||||
{
|
||||
if (imf == nullptr) {
|
||||
return rect;
|
||||
@@ -722,6 +723,8 @@ static float *image_exr_from_scene_linear_to_output(float *rect,
|
||||
IMB_colormanagement_transform_float(
|
||||
output_rect, width, height, channels, from_colorspace, to_colorspace, false);
|
||||
|
||||
r_colorspace = to_colorspace;
|
||||
|
||||
return output_rect;
|
||||
}
|
||||
|
||||
@@ -805,14 +808,19 @@ static void add_exr_compositing_result(ExrHandle *exr_handle,
|
||||
|
||||
/* Compositing results is always a 4-channel RGBA. */
|
||||
const int channels_count_in_buffer = 4;
|
||||
float *output_buffer = (save_as_render) ? image_exr_from_scene_linear_to_output(
|
||||
render_view->ibuf->float_buffer.data,
|
||||
render_result->rectx,
|
||||
render_result->recty,
|
||||
channels_count_in_buffer,
|
||||
imf,
|
||||
temporary_buffers) :
|
||||
render_view->ibuf->float_buffer.data;
|
||||
float *output_buffer = render_view->ibuf->float_buffer.data;
|
||||
blender::StringRefNull colorspace = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_SCENE_LINEAR);
|
||||
|
||||
if (save_as_render) {
|
||||
output_buffer = image_exr_from_scene_linear_to_output(output_buffer,
|
||||
render_result->rectx,
|
||||
render_result->recty,
|
||||
channels_count_in_buffer,
|
||||
imf,
|
||||
temporary_buffers,
|
||||
colorspace);
|
||||
}
|
||||
|
||||
/* For multi-layer EXRs, we write the buffer as is with all its 4 channels. */
|
||||
const bool half_float = (imf && imf->depth == R_IMF_CHAN_DEPTH_16);
|
||||
@@ -821,6 +829,7 @@ static void add_exr_compositing_result(ExrHandle *exr_handle,
|
||||
"Composite.Combined",
|
||||
"RGBA",
|
||||
render_view_name,
|
||||
colorspace,
|
||||
channels_count_in_buffer,
|
||||
channels_count_in_buffer * render_result->rectx,
|
||||
output_buffer,
|
||||
@@ -843,6 +852,7 @@ static void add_exr_compositing_result(ExrHandle *exr_handle,
|
||||
"",
|
||||
"V",
|
||||
render_view_name,
|
||||
colorspace,
|
||||
1,
|
||||
render_result->rectx,
|
||||
gray_scale_output,
|
||||
@@ -857,6 +867,7 @@ static void add_exr_compositing_result(ExrHandle *exr_handle,
|
||||
"",
|
||||
channelnames,
|
||||
render_view_name,
|
||||
colorspace,
|
||||
channels_count_in_buffer,
|
||||
channels_count_in_buffer * render_result->rectx,
|
||||
output_buffer,
|
||||
@@ -928,15 +939,19 @@ bool BKE_image_render_write_exr(ReportList *reports,
|
||||
const bool pass_half_float = half_float && pass_RGBA;
|
||||
|
||||
/* Color-space conversion only happens on RGBA passes. */
|
||||
float *output_rect = (save_as_render && pass_RGBA) ?
|
||||
image_exr_from_scene_linear_to_output(
|
||||
render_pass->ibuf->float_buffer.data,
|
||||
rr->rectx,
|
||||
rr->recty,
|
||||
render_pass->channels,
|
||||
imf,
|
||||
tmp_output_rects) :
|
||||
render_pass->ibuf->float_buffer.data;
|
||||
float *output_rect = render_pass->ibuf->float_buffer.data;
|
||||
blender::StringRefNull colorspace = IMB_colormanagement_role_colorspace_name_get(
|
||||
(pass_RGBA) ? COLOR_ROLE_SCENE_LINEAR : COLOR_ROLE_DATA);
|
||||
|
||||
if (save_as_render && pass_RGBA) {
|
||||
output_rect = image_exr_from_scene_linear_to_output(output_rect,
|
||||
rr->rectx,
|
||||
rr->recty,
|
||||
render_pass->channels,
|
||||
imf,
|
||||
tmp_output_rects,
|
||||
colorspace);
|
||||
}
|
||||
|
||||
/* For multi-layer EXRs, we write the pass as is with all of its channels. */
|
||||
if (multi_layer) {
|
||||
@@ -952,6 +967,7 @@ bool BKE_image_render_write_exr(ReportList *reports,
|
||||
layer_pass_name,
|
||||
channelnames,
|
||||
viewname,
|
||||
colorspace,
|
||||
render_pass->channels,
|
||||
render_pass->channels * rr->rectx,
|
||||
output_rect,
|
||||
@@ -975,6 +991,7 @@ bool BKE_image_render_write_exr(ReportList *reports,
|
||||
"",
|
||||
channelnames,
|
||||
viewname,
|
||||
colorspace,
|
||||
render_pass->channels,
|
||||
render_pass->channels * rr->rectx,
|
||||
output_rect,
|
||||
@@ -985,8 +1002,15 @@ bool BKE_image_render_write_exr(ReportList *reports,
|
||||
* the input is RGB[A] and not single channel because it filed the condition above. */
|
||||
float *gray_scale_output = image_exr_from_rgb_to_bw(
|
||||
output_rect, rr->rectx, rr->recty, render_pass->channels, tmp_output_rects);
|
||||
IMB_exr_add_channels(
|
||||
exrhandle, "", "V", viewname, 1, rr->rectx, gray_scale_output, pass_half_float);
|
||||
IMB_exr_add_channels(exrhandle,
|
||||
"",
|
||||
"V",
|
||||
viewname,
|
||||
colorspace,
|
||||
1,
|
||||
rr->rectx,
|
||||
gray_scale_output,
|
||||
pass_half_float);
|
||||
}
|
||||
else if (render_pass->channels == 1) {
|
||||
/* In case of a single channel pass, we need to broadcast the same channel for each of
|
||||
@@ -997,6 +1021,7 @@ bool BKE_image_render_write_exr(ReportList *reports,
|
||||
"",
|
||||
std::string(1, "RGB"[i]).c_str(),
|
||||
viewname,
|
||||
colorspace,
|
||||
1,
|
||||
rr->rectx,
|
||||
output_rect,
|
||||
@@ -1010,7 +1035,7 @@ bool BKE_image_render_write_exr(ReportList *reports,
|
||||
float *alpha_output = image_exr_opaque_alpha_buffer(
|
||||
rr->rectx, rr->recty, tmp_output_rects);
|
||||
IMB_exr_add_channels(
|
||||
exrhandle, "", "A", viewname, 1, rr->rectx, alpha_output, pass_half_float);
|
||||
exrhandle, "", "A", viewname, colorspace, 1, rr->rectx, alpha_output, pass_half_float);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,13 @@ ExrHandle *IMB_exr_get_handle(bool write_multipart = false);
|
||||
* Add multiple channels to EXR file.
|
||||
* The number of channels is determined by channelnames.size() with
|
||||
* each character a channel name.
|
||||
* Layer and pass name, and view name are optional.
|
||||
* Layer and pass name, view name and colorspace are all optional.
|
||||
*/
|
||||
void IMB_exr_add_channels(ExrHandle *handle,
|
||||
blender::StringRefNull layerpassname,
|
||||
blender::StringRefNull channelnames,
|
||||
blender::StringRefNull viewname,
|
||||
blender::StringRefNull colorspace,
|
||||
size_t xstride,
|
||||
size_t ystride,
|
||||
float *rect,
|
||||
|
||||
@@ -111,6 +111,7 @@ using namespace Imath;
|
||||
static bool exr_has_multiview(MultiPartInputFile &file);
|
||||
static bool exr_has_multipart_file(MultiPartInputFile &file);
|
||||
static bool exr_has_alpha(MultiPartInputFile &file);
|
||||
static const ColorSpace *imb_exr_part_colorspace(const Header &header);
|
||||
|
||||
/* XYZ with Illuminant E */
|
||||
static Imf::Chromaticities CHROMATICITIES_XYZ_E{
|
||||
@@ -521,6 +522,29 @@ static void openexr_header_metadata_global(Header *header,
|
||||
}
|
||||
}
|
||||
|
||||
static void openexr_header_metadata_colorspace(Header *header, const ColorSpace *colorspace)
|
||||
{
|
||||
if (colorspace == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *aces_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_ACES_INTERCHANGE);
|
||||
const char *ibuf_colorspace = IMB_colormanagement_colorspace_get_name(colorspace);
|
||||
|
||||
/* Write chromaticities for ACES-2065-1, as required by ACES container format. */
|
||||
if (aces_colorspace && STREQ(aces_colorspace, ibuf_colorspace)) {
|
||||
header->insert("chromaticities", TypedAttribute<Chromaticities>(CHROMATICITIES_ACES_2065_1));
|
||||
header->insert("adoptedNeutral", TypedAttribute<V2f>(CHROMATICITIES_ACES_2065_1.white));
|
||||
}
|
||||
|
||||
/* Write interop ID if available. */
|
||||
blender::StringRefNull interop_id = IMB_colormanagement_space_get_interop_id(colorspace);
|
||||
if (!interop_id.is_empty()) {
|
||||
header->insert("colorInteropID", TypedAttribute<std::string>(interop_id));
|
||||
}
|
||||
}
|
||||
|
||||
static void openexr_header_metadata_colorspace(Header *header, ImBuf *ibuf)
|
||||
{
|
||||
/* Get colorspace from image buffer. */
|
||||
@@ -536,23 +560,7 @@ static void openexr_header_metadata_colorspace(Header *header, ImBuf *ibuf)
|
||||
colorspace = ibuf->byte_buffer.colorspace;
|
||||
}
|
||||
|
||||
if (colorspace) {
|
||||
const char *aces_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_ACES_INTERCHANGE);
|
||||
const char *ibuf_colorspace = IMB_colormanagement_colorspace_get_name(colorspace);
|
||||
|
||||
/* Write chromaticities for ACES-2065-1, as required by ACES container format. */
|
||||
if (aces_colorspace && STREQ(aces_colorspace, ibuf_colorspace)) {
|
||||
header->insert("chromaticities", TypedAttribute<Chromaticities>(CHROMATICITIES_ACES_2065_1));
|
||||
header->insert("adoptedNeutral", TypedAttribute<V2f>(CHROMATICITIES_ACES_2065_1.white));
|
||||
}
|
||||
|
||||
/* Write interop ID if available. */
|
||||
blender::StringRefNull interop_id = IMB_colormanagement_space_get_interop_id(colorspace);
|
||||
if (!interop_id.is_empty()) {
|
||||
header->insert("colorInteropID", TypedAttribute<std::string>(interop_id));
|
||||
}
|
||||
}
|
||||
openexr_header_metadata_colorspace(header, colorspace);
|
||||
}
|
||||
|
||||
static void openexr_header_metadata_callback(void *data,
|
||||
@@ -780,6 +788,9 @@ struct ExrChannel {
|
||||
/* Channel view. */
|
||||
std::string view;
|
||||
|
||||
/* Colorspace. */
|
||||
const ColorSpace *colorspace;
|
||||
|
||||
int xstride = 0, ystride = 0; /* step to next pixel, to next scan-line. */
|
||||
float *rect = nullptr; /* first pointer to write in */
|
||||
char chan_id = 0; /* quick lookup of channel char */
|
||||
@@ -899,6 +910,7 @@ void IMB_exr_add_channels(ExrHandle *handle,
|
||||
blender::StringRefNull layerpassname,
|
||||
blender::StringRefNull channelnames,
|
||||
blender::StringRefNull viewname,
|
||||
blender::StringRefNull colorspace,
|
||||
size_t xstride,
|
||||
size_t ystride,
|
||||
float *rect,
|
||||
@@ -948,6 +960,7 @@ void IMB_exr_add_channels(ExrHandle *handle,
|
||||
echan.internal_name = full_name;
|
||||
echan.part_name = part_name;
|
||||
echan.view = viewname;
|
||||
echan.colorspace = IMB_colormanagement_space_get_named(colorspace.c_str());
|
||||
|
||||
echan.xstride = xstride;
|
||||
echan.ystride = ystride;
|
||||
@@ -995,6 +1008,26 @@ bool IMB_exr_begin_write(ExrHandle *handle,
|
||||
|
||||
openexr_header_compression(&header, compress, quality);
|
||||
|
||||
if (!handle->write_multipart) {
|
||||
/* If we're writing single part, we can only add one colorspace even if there are
|
||||
* multiple passes with potentially different spaces. Prefer to write non-data
|
||||
* colorspace in that case, since readers can detect data passes based on
|
||||
* channels names being e.g. XYZ instead of RGB. */
|
||||
bool found = false;
|
||||
for (const ExrChannel &echan : handle->channels) {
|
||||
if (echan.colorspace && !IMB_colormanagement_space_is_data(echan.colorspace)) {
|
||||
openexr_header_metadata_colorspace(&header, echan.colorspace);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (const ColorSpace *colorspace = handle->channels[0].colorspace) {
|
||||
openexr_header_metadata_colorspace(&header, colorspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blender::Vector<Header> part_headers;
|
||||
|
||||
blender::StringRefNull last_part_name;
|
||||
@@ -1003,13 +1036,14 @@ bool IMB_exr_begin_write(ExrHandle *handle,
|
||||
if (part_headers.is_empty() || last_part_name != echan.part_name) {
|
||||
Header part_header = header;
|
||||
|
||||
/* When writing multipart, set name, view and type in each part. */
|
||||
/* When writing multipart, set name, view,type and colorspace in each part. */
|
||||
if (handle->write_multipart) {
|
||||
part_header.setName(echan.part_name);
|
||||
if (!echan.view.empty()) {
|
||||
part_header.insert("view", StringAttribute(echan.view));
|
||||
}
|
||||
part_header.insert("type", StringAttribute(SCANLINEIMAGE));
|
||||
openexr_header_metadata_colorspace(&part_header, echan.colorspace);
|
||||
}
|
||||
|
||||
/* Store global metadata in the first header only. Large metadata like cryptomatte would
|
||||
@@ -1552,10 +1586,19 @@ static blender::Vector<ExrChannel> exr_channels_in_multi_part_file(const MultiPa
|
||||
const bool parse_layers)
|
||||
{
|
||||
blender::Vector<ExrChannel> channels;
|
||||
const ColorSpace *global_colorspace = imb_exr_part_colorspace(file.header(0));
|
||||
|
||||
/* Get channels from each part. */
|
||||
for (int p = 0; p < file.parts(); p++) {
|
||||
const ChannelList &c = file.header(p).channels();
|
||||
|
||||
/* Parse colorspace. Per part colorspaces are not currently used, but
|
||||
* might as well populate them them for consistency with writing. */
|
||||
const ColorSpace *colorspace = imb_exr_part_colorspace(file.header(p));
|
||||
if (colorspace == nullptr) {
|
||||
colorspace = global_colorspace;
|
||||
}
|
||||
|
||||
/* There are two ways of storing multiview EXRs:
|
||||
* - Multiple views in part with multiView attribute.
|
||||
* - Each view in its own part with view attribute. */
|
||||
@@ -1609,6 +1652,7 @@ static blender::Vector<ExrChannel> exr_channels_in_multi_part_file(const MultiPa
|
||||
}
|
||||
|
||||
echan.part_number = p;
|
||||
echan.colorspace = colorspace;
|
||||
channels.append(std::move(echan));
|
||||
}
|
||||
}
|
||||
@@ -1983,6 +2027,13 @@ static void imb_exr_set_known_colorspace(const Header &header, ImFileColorSpace
|
||||
}
|
||||
}
|
||||
|
||||
static const ColorSpace *imb_exr_part_colorspace(const Header &header)
|
||||
{
|
||||
ImFileColorSpace colorspace;
|
||||
imb_exr_set_known_colorspace(header, colorspace);
|
||||
return IMB_colormanagement_space_get_named(colorspace.metadata_colorspace);
|
||||
}
|
||||
|
||||
static bool exr_get_ppm(MultiPartInputFile &file, double ppm[2])
|
||||
{
|
||||
const Header &header = file.header(0);
|
||||
|
||||
@@ -17,6 +17,7 @@ void IMB_exr_add_channels(ExrHandle * /*handle*/,
|
||||
blender::StringRefNull /*layerpassname*/,
|
||||
blender::StringRefNull /*channelnames*/,
|
||||
blender::StringRefNull /*viewname*/,
|
||||
blender::StringRefNull /*colorspace*/,
|
||||
size_t /*xstride*/,
|
||||
size_t /*ystride*/,
|
||||
float * /*rect*/,
|
||||
|
||||
BIN
tests/files/compositor/file_output/exr_multilayer_passes/0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multilayer_passes/0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_multipart0001_L.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_multipart0001_L.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_multipart0001_R.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_multipart0001_R.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_singlepart0001_L.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_singlepart0001_L.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_singlepart0001_R.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multipart/multilayer_singlepart0001_R.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_multipart/multiview_multipart0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multipart/multiview_multipart0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_multipart/multiview_singlepart0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_multipart/multiview_singlepart0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/Alpha0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/Alpha0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/CryptoObject000001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/CryptoObject000001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/CryptoObject010001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/CryptoObject010001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/Depth0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/Depth0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/DiffCol0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/DiffCol0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/Image0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/Image0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/Normal0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/Normal0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_passes/Vector0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/Vector0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_png_group_multilayer_passes/0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_png_group_multilayer_passes/0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/png_exr_single/Image0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/png_exr_single/Image0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/png_to_exr/A0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/png_to_exr/A0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/png_to_exr/B0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/png_to_exr/B0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/png_to_exr/G0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/png_to_exr/G0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/png_to_exr/Image0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/png_to_exr/Image0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/png_to_exr/R0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/png_to_exr/R0001.exr
(Stored with Git LFS)
Binary file not shown.
Reference in New Issue
Block a user