From fd9edf5b8916734cfb4fabdd7ea0eca2c28f26c2 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 25 Sep 2025 15:16:11 +0200 Subject: [PATCH] Color Management: Support for HDR PNG read and write ### Color Management: Support image CICP read/write The CICP support will enable reading and writing HDR PNGs, following the recently released PNG spec: https://www.w3.org/TR/png-3/#cICP-chunk More useful for exporting to the web with reasonable file size would be AVIF support, but that will have to wait for the next library update in Blender 5.1 to add libheif. Note this will only starting working once the 5.0 libraries have landed, which should happen somewhere in the next week. Pull Request: https://projects.blender.org/blender/blender/pulls/145612 --- source/blender/imbuf/IMB_colormanagement.hh | 1 + .../blender/imbuf/intern/colormanagement.cc | 27 +++++++++---------- .../imbuf/intern/oiio/openimageio_support.cc | 22 ++++++++++++++- .../blender/imbuf/movie/intern/movie_write.cc | 3 ++- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index 5451d3cd88e..b0e6fefb660 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -78,6 +78,7 @@ blender::Vector IMB_colormanagement_space_to_icc_profile(const ColorSpace * For describing the colorspace of videos and high dynamic range image files. */ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool video, + const bool rgb_matrix, int cicp[4]); const ColorSpace *IMB_colormanagement_space_from_cicp(const int cicp[4], const bool video); diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index d1b1a5cf964..b3544ddce76 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -1410,7 +1410,10 @@ static const int CICP_MATRIX_REC2020_NCL = 9; /* Range */ static const int CICP_RANGE_FULL = 1; -bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool video, int cicp[4]) +bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, + const bool video, + const bool rgb_matrix, + int cicp[4]) { const StringRefNull interop_id = colorspace->interop_id(); if (interop_id.is_empty()) { @@ -1421,22 +1424,18 @@ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool * ASWF Color Interop Forum defined display spaces. * https://en.wikipedia.org/wiki/Coding-independent_code_points * https://www.w3.org/TR/png-3/#cICP-chunk - * - * For images we always use RGB matrix as that is the only thing supported for PNG. - * For video we specify an appropriate matrix to YUV or similar. This should also - * be used for HEIF and AVIF which are based on video codecs. */ - + */ if (interop_id == "pq_rec2020_display") { cicp[0] = CICP_PRI_REC2020; cicp[1] = CICP_TRC_PQ; - cicp[2] = (video) ? CICP_MATRIX_REC2020_NCL : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_REC2020_NCL; cicp[3] = CICP_RANGE_FULL; return true; } if (interop_id == "hlg_rec2020_display") { cicp[0] = CICP_PRI_REC2020; cicp[1] = CICP_TRC_HLG; - cicp[2] = (video) ? CICP_MATRIX_REC2020_NCL : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_REC2020_NCL; cicp[3] = CICP_RANGE_FULL; return true; } @@ -1444,7 +1443,7 @@ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool /* Rec.2020 matrix may seem odd, but follows Color Interop Forum recommendation. */ cicp[0] = CICP_PRI_P3D65; cicp[1] = CICP_TRC_PQ; - cicp[2] = (video) ? CICP_MATRIX_REC2020_NCL : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_REC2020_NCL; cicp[3] = CICP_RANGE_FULL; return true; } @@ -1452,14 +1451,14 @@ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool /* BT.709 matrix may seem odd, but follows Color Interop Forum recommendation. */ cicp[0] = CICP_PRI_P3D65; cicp[1] = CICP_TRC_G26; - cicp[2] = (video) ? CICP_MATRIX_BT709 : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_BT709; cicp[3] = CICP_RANGE_FULL; return true; } if (interop_id == "g22_rec709_display") { cicp[0] = CICP_PRI_REC709; cicp[1] = CICP_TRC_G22; - cicp[2] = (video) ? CICP_MATRIX_BT709 : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_BT709; cicp[3] = CICP_RANGE_FULL; return true; } @@ -1467,7 +1466,7 @@ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool /* There is no gamma 2.4 trc, but BT.709 is close. */ cicp[0] = CICP_PRI_REC2020; cicp[1] = CICP_TRC_BT709; - cicp[2] = (video) ? CICP_MATRIX_REC2020_NCL : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_REC2020_NCL; cicp[3] = CICP_RANGE_FULL; return true; } @@ -1475,7 +1474,7 @@ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool /* There is no gamma 2.4 trc, but BT.709 is close. */ cicp[0] = CICP_PRI_REC709; cicp[1] = CICP_TRC_BT709; - cicp[2] = (video) ? CICP_MATRIX_BT709 : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_BT709; cicp[3] = CICP_RANGE_FULL; return true; } @@ -1485,7 +1484,7 @@ bool IMB_colormanagement_space_to_cicp(const ColorSpace *colorspace, const bool * "Quicktime gamma shift bug" that complicates things. */ cicp[0] = CICP_PRI_P3D65; cicp[1] = (video) ? CICP_TRC_BT709 : CICP_TRC_SRGB; - cicp[2] = (video) ? CICP_MATRIX_BT709 : CICP_MATRIX_RGB; + cicp[2] = (rgb_matrix) ? CICP_MATRIX_RGB : CICP_MATRIX_BT709; cicp[3] = CICP_RANGE_FULL; return true; } diff --git a/source/blender/imbuf/intern/oiio/openimageio_support.cc b/source/blender/imbuf/intern/oiio/openimageio_support.cc index 611329e987e..14c7200a684 100644 --- a/source/blender/imbuf/intern/oiio/openimageio_support.cc +++ b/source/blender/imbuf/intern/oiio/openimageio_support.cc @@ -159,6 +159,17 @@ static void set_file_colorspace(ImFileColorSpace &r_colorspace, string ics = spec.get_string_attribute("oiio:ColorSpace"); STRNCPY_UTF8(r_colorspace.metadata_colorspace, ics.c_str()); } + + /* Get colorspace from CICP. */ + int cicp[4] = {}; + if (spec.getattribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp, true)) { + const bool for_video = false; + const ColorSpace *colorspace = IMB_colormanagement_space_from_cicp(cicp, for_video); + if (colorspace) { + STRNCPY_UTF8(r_colorspace.metadata_colorspace, + IMB_colormanagement_colorspace_get_name(colorspace)); + } + } } /** @@ -459,7 +470,7 @@ ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, Type } } - /* Write ICC profile if there is one associated with the colorspace. */ + /* Write ICC profile and/or CICP if there is one associated with the colorspace. */ const ColorSpace *colorspace = (ctx.mem_spec.format == TypeDesc::FLOAT) ? ctx.ibuf->float_buffer.colorspace : ctx.ibuf->byte_buffer.colorspace; @@ -470,6 +481,15 @@ ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, Type OIIO::TypeDesc(OIIO::TypeDesc::UINT8, icc_profile.size()), icc_profile.data()); } + + /* PNG only supports RGB matrix. For AVIF and HEIF we want to use a YUV matrix + * as these are based on video codecs designed to use them. */ + const bool for_video = false; + const bool rgb_matrix = STREQ(ctx.file_format, "png"); + int cicp[4]; + if (IMB_colormanagement_space_to_cicp(colorspace, for_video, rgb_matrix, cicp)) { + file_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); + } } return file_spec; diff --git a/source/blender/imbuf/movie/intern/movie_write.cc b/source/blender/imbuf/movie/intern/movie_write.cc index 59df5d2bdff..5ba54e8a0b4 100644 --- a/source/blender/imbuf/movie/intern/movie_write.cc +++ b/source/blender/imbuf/movie/intern/movie_write.cc @@ -795,9 +795,10 @@ static void set_colorspace_options(AVCodecContext *c, const ColorSpace *colorspa const AVPixFmtDescriptor *pix_fmt_desc = av_pix_fmt_desc_get(c->pix_fmt); const bool is_rgb_format = (pix_fmt_desc->flags & AV_PIX_FMT_FLAG_RGB); const bool for_video = true; + const bool rgb_matrix = false; int cicp[4]; - if (colorspace && IMB_colormanagement_space_to_cicp(colorspace, for_video, cicp)) { + if (colorspace && IMB_colormanagement_space_to_cicp(colorspace, for_video, rgb_matrix, cicp)) { /* Note ffmpeg enums are documented to match CICP. */ c->color_primaries = AVColorPrimaries(cicp[0]); c->color_trc = AVColorTransferCharacteristic(cicp[1]);