From 7edde84e44508fff002cd9bcc98df60404935826 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 7 Jun 2026 17:50:08 +0200 Subject: [PATCH 1/3] feat(color): Refactor writing color space metadata to share logic This adds new functions to get colorspace information from ImageSpec and uses them to deduplicate logic. * pvt::is_colorspace_srgb * pvt::get_colorspace_rec709_gamma * pvt::get_colorspace_icc_profile * pvt::get_colorspace_cicp There is existing consistency in that some file formats assume an empty oiio:ColorSpace to mean sRGB, and some don't. This inconsistency is preserved. An improvement is that writing gamma metadata from interop ID now consistently works with display interop IDs too, and not just scene interop IDs. Signed-off-by: Brecht Van Lommel --- src/dpx.imageio/dpxoutput.cpp | 19 +++--- src/heif.imageio/heifoutput.cpp | 9 +-- src/include/imageio_pvt.h | 25 +++++++ src/iv/imageviewer.cpp | 38 +++++------ src/iv/ivgl.cpp | 5 +- src/jpeg.imageio/jpegoutput.cpp | 62 ++++++++--------- src/jpeg2000.imageio/jpeg2000output.cpp | 17 +++-- src/jpegxl.imageio/jxloutput.cpp | 30 +++------ src/libOpenImageIO/color_ocio.cpp | 88 +++++++++++++++++++++++++ src/png.imageio/png_pvt.h | 86 ++++++------------------ src/png.imageio/pnginput.cpp | 19 ++---- src/rla.imageio/rlaoutput.cpp | 20 ++---- src/targa.imageio/targaoutput.cpp | 26 +++----- src/tiff.imageio/tiffoutput.cpp | 23 +++---- src/webp.imageio/webpoutput.cpp | 16 ++--- 15 files changed, 246 insertions(+), 237 deletions(-) diff --git a/src/dpx.imageio/dpxoutput.cpp b/src/dpx.imageio/dpxoutput.cpp index 0779efc29d..e3dc059e49 100644 --- a/src/dpx.imageio/dpxoutput.cpp +++ b/src/dpx.imageio/dpxoutput.cpp @@ -18,6 +18,8 @@ #include #include +#include "imageio_pvt.h" + OIIO_PLUGIN_NAMESPACE_BEGIN @@ -427,18 +429,15 @@ DPXOutput::prep_subimage(int s, bool allocate) m_desc = get_image_descriptor(); // transfer function - const ColorConfig& colorconfig = ColorConfig::default_colorconfig(); - std::string colorspace = spec_s.get_string_attribute("oiio:ColorSpace", ""); - if (colorconfig.equivalent(colorspace, "lin_rec709_scene")) - m_transfer = dpx::kLinear; - else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) + const float gamma = pvt::get_colorspace_rec709_gamma(spec_s); + if (pvt::is_colorspace_srgb(spec_s, false)) m_transfer = dpx::kITUR709; - else if (colorconfig.equivalent(colorspace, "g22_rec709_scene") - || colorconfig.equivalent(colorspace, "g24_rec709_scene") - || colorconfig.equivalent(colorspace, "g18_rec709_scene") - || Strutil::istarts_with(colorspace, "Gamma")) + else if (gamma == 1.0f) + m_transfer = dpx::kLinear; + else if (gamma != 0.0f) m_transfer = dpx::kUserDefined; - else if (colorconfig.equivalent(colorspace, "KodakLog")) + else if (ColorConfig::default_colorconfig().equivalent( + spec_s.get_string_attribute("oiio:ColorSpace"), "KodakLog")) m_transfer = dpx::kLogarithmic; else { std::string dpxtransfer = spec_s.get_string_attribute("dpx:Transfer", diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 5371af8f72..139261b7e7 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -10,6 +10,8 @@ #include #include +#include "imageio_pvt.h" + #include #define MAKE_LIBHEIF_VERSION(a, b, c, d) \ @@ -275,12 +277,7 @@ HeifOutput::close() std::unique_ptr nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free); - const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); - const ParamValue* p = m_spec.find_attribute("CICP", - TypeDesc(TypeDesc::INT, 4)); - string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace"); - cspan cicp = (p) ? p->as_cspan() - : colorconfig.get_cicp(colorspace); + cspan cicp = pvt::get_colorspace_cicp(m_spec); if (!cicp.empty()) { nclx->color_primaries = heif_color_primaries(cicp[0]); nclx->transfer_characteristics = heif_transfer_characteristics( diff --git a/src/include/imageio_pvt.h b/src/include/imageio_pvt.h index ee350d4e75..79a54f03a6 100644 --- a/src/include/imageio_pvt.h +++ b/src/include/imageio_pvt.h @@ -133,6 +133,31 @@ parallel_convert_from_float(const float* src, void* dst, size_t nvals, OIIO_API bool check_texture_metadata_sanity(ImageSpec& spec); +/// Returns true if for the purpose of interop, the spec's metadata +/// specifies a color space that should be encoded as sRGB. +/// +/// If default_to_srgb is true, the colorspace will be assumed to +/// be sRGB if no colorspace was specified in the spec. +OIIO_API bool +is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb = true); + +/// If the spec's metadata specifies a color space with Rec709 primaries and +/// gamma transfer function, return the gamma value. If not, return zero. +OIIO_API float +get_colorspace_rec709_gamma(const ImageSpec& spec); + +// Returns ICC profile from the spec's metadata, either from an ICCProfile +// attribute or from the colorspace if from_colorspace is true. +// Returns an empty vector if not found. +OIIO_API std::vector +get_colorspace_icc_profile(const ImageSpec& spec, bool from_colorspace = true); + +// Returns CICP from the spec's metadata, either from a CICP attribute +// or from the colorspace if from_colorspace is true. +// Returns an empty span if not found. +OIIO_API cspan +get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace = true); + /// Get the timing report from log_time entries. OIIO_API std::string timing_report(); diff --git a/src/iv/imageviewer.cpp b/src/iv/imageviewer.cpp index 0462db8220..75b0e6d451 100644 --- a/src/iv/imageviewer.cpp +++ b/src/iv/imageviewer.cpp @@ -44,22 +44,11 @@ #include #include +#include "imageio_pvt.h" #include "ivutils.h" -namespace { - -inline bool -IsSpecSrgb(const ImageSpec& spec) -{ - return equivalent_colorspace(spec.get_string_attribute("oiio:ColorSpace"), - "srgb_rec709_scene"); -} - -} // namespace - - // clang-format off static const char *s_file_filters = "" "Image Files (*.bmp *.cin *.dcm *.dds *.dpx *.fits *.gif *.hdr *.ico *.iff " @@ -1263,7 +1252,8 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel) //std::cerr << "Loading HALF-FLOAT as FLOAT\n"; read_format = TypeDesc::FLOAT; } - if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable()) { + if (pvt::is_colorspace_srgb(image_spec, false) + && !glwin->is_srgb_capable()) { // If the image is in sRGB, but OpenGL can't load sRGB textures then // we'll need to do the transformation on the CPU after loading the // image. We (so far) can only do this with UINT8 images, so make @@ -1278,7 +1268,8 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel) read_format = TypeDesc::UINT8; allow_transforms = true; - if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable()) + if (pvt::is_colorspace_srgb(image_spec, false) + && !glwin->is_srgb_capable()) srgb_transform = true; } @@ -1464,7 +1455,7 @@ ImageViewer::exposureMinusOneTenthStop() img->exposure(img->exposure() - 0.1); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1483,7 +1474,7 @@ ImageViewer::exposureMinusOneHalfStop() img->exposure(img->exposure() - 0.5); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1502,7 +1493,7 @@ ImageViewer::exposurePlusOneTenthStop() img->exposure(img->exposure() + 0.1); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1521,7 +1512,7 @@ ImageViewer::exposurePlusOneHalfStop() img->exposure(img->exposure() + 0.5); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1541,7 +1532,7 @@ ImageViewer::gammaMinus() img->gamma(img->gamma() - 0.05); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1560,7 +1551,7 @@ ImageViewer::gammaPlus() img->gamma(img->gamma() + 0.05); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1592,8 +1583,9 @@ ImageViewer::viewChannel(int c, COLOR_MODE colormode) if (!glwin->is_glsl_capable()) { IvImage* img = cur(); if (img) { - bool srgb_transform = (!glwin->is_srgb_capable() - && IsSpecSrgb(img->spec())); + bool srgb_transform + = (!glwin->is_srgb_capable() + && pvt::is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)colormode, c); } } else { @@ -2554,4 +2546,4 @@ ImageViewer::flipVertical() spec->attribute("Orientation", curr_orientation); } displayCurrentImage(); -} \ No newline at end of file +} diff --git a/src/iv/ivgl.cpp b/src/iv/ivgl.cpp index 2d773d674c..3880275592 100644 --- a/src/iv/ivgl.cpp +++ b/src/iv/ivgl.cpp @@ -18,6 +18,7 @@ # include #endif +#include "imageio_pvt.h" #include "ivutils.h" #include #include @@ -2198,9 +2199,7 @@ IvGL::typespec_to_opengl(const ImageSpec& spec, int nchannels, GLenum& gltype, break; } - bool issrgb - = equivalent_colorspace(spec.get_string_attribute("oiio:ColorSpace"), - "srgb_rec709_scene"); + bool issrgb = pvt::is_colorspace_srgb(spec, false); glinternalformat = nchannels; if (nchannels == 1) { diff --git a/src/jpeg.imageio/jpegoutput.cpp b/src/jpeg.imageio/jpegoutput.cpp index 39fea4f327..3ab6d18ab2 100644 --- a/src/jpeg.imageio/jpegoutput.cpp +++ b/src/jpeg.imageio/jpegoutput.cpp @@ -12,6 +12,8 @@ #include #include +#include "imageio_pvt.h" + #include "jpeg_pvt.h" OIIO_PLUGIN_NAMESPACE_BEGIN @@ -265,8 +267,7 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec, } } - if (equivalent_colorspace(m_spec.get_string_attribute("oiio:ColorSpace"), - "srgb_rec709_scene")) + if (pvt::is_colorspace_srgb(m_spec, false)) m_spec.attribute("Exif:ColorSpace", 1); // Write EXIF info @@ -317,37 +318,32 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec, m_spec.set_format(TypeDesc::UINT8); // JPG is only 8 bit // Write ICC profile, if we have anything - if (auto icc_profile_parameter = m_spec.find_attribute(ICC_PROFILE_ATTR)) { - cspan icc_profile((unsigned char*) - icc_profile_parameter->data(), - icc_profile_parameter->type().size()); - if (icc_profile.size() && icc_profile.data()) { - /* Calculate the number of markers we'll need, rounding up of course */ - size_t num_markers = icc_profile.size() / MAX_DATA_BYTES_IN_MARKER; - if (num_markers * MAX_DATA_BYTES_IN_MARKER - != std::size(icc_profile)) - num_markers++; - int curr_marker = 1; /* per spec, count starts at 1*/ - std::vector profile(MAX_DATA_BYTES_IN_MARKER - + ICC_HEADER_SIZE); - size_t icc_profile_length = icc_profile.size(); - while (icc_profile_length > 0) { - // length of profile to put in this marker - size_t length = std::min(icc_profile_length, - size_t(MAX_DATA_BYTES_IN_MARKER)); - icc_profile_length -= length; - // Write the JPEG marker header (APP2 code and marker length) - strcpy((char*)profile.data(), "ICC_PROFILE"); // NOSONAR - profile[11] = 0; - profile[12] = curr_marker; - profile[13] = (JOCTET)num_markers; - OIIO_ASSERT(profile.size() >= ICC_HEADER_SIZE + length); - spancpy(make_span(profile), ICC_HEADER_SIZE, icc_profile, - length * (curr_marker - 1), length); - jpeg_write_marker(&m_cinfo, JPEG_APP0 + 2, profile.data(), - ICC_HEADER_SIZE + length); - curr_marker++; - } + std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + if (icc_profile.size()) { + /* Calculate the number of markers we'll need, rounding up of course */ + size_t num_markers = icc_profile.size() / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != std::size(icc_profile)) + num_markers++; + int curr_marker = 1; /* per spec, count starts at 1*/ + std::vector profile(MAX_DATA_BYTES_IN_MARKER + ICC_HEADER_SIZE); + size_t icc_profile_length = icc_profile.size(); + while (icc_profile_length > 0) { + // length of profile to put in this marker + size_t length = std::min(icc_profile_length, + size_t(MAX_DATA_BYTES_IN_MARKER)); + icc_profile_length -= length; + // Write the JPEG marker header (APP2 code and marker length) + strcpy((char*)profile.data(), "ICC_PROFILE"); // NOSONAR + profile[11] = 0; + profile[12] = curr_marker; + profile[13] = (JOCTET)num_markers; + OIIO_ASSERT(profile.size() >= ICC_HEADER_SIZE + length); + spancpy(make_span(profile), ICC_HEADER_SIZE, + cspan(icc_profile), length * (curr_marker - 1), + length); + jpeg_write_marker(&m_cinfo, JPEG_APP0 + 2, profile.data(), + ICC_HEADER_SIZE + length); + curr_marker++; } } diff --git a/src/jpeg2000.imageio/jpeg2000output.cpp b/src/jpeg2000.imageio/jpeg2000output.cpp index a34f0f5ae5..7e3a4e746d 100644 --- a/src/jpeg2000.imageio/jpeg2000output.cpp +++ b/src/jpeg2000.imageio/jpeg2000output.cpp @@ -66,6 +66,9 @@ class Jpeg2000Output final : public ImageOutput { bool m_convert_alpha; //< Do we deassociate alpha? std::vector m_tilebuffer; std::vector m_scratch; +#if 0 + std::vector m_icc_profile; +#endif #ifdef USE_OPENJPH @@ -81,6 +84,9 @@ class Jpeg2000Output final : public ImageOutput { m_codec = NULL; m_stream = NULL; m_convert_alpha = true; +#if 0 + m_icc_profile.clear(); +#endif ioproxy_clear(); } @@ -481,10 +487,13 @@ Jpeg2000Output::create_jpeg2000_image() // someboody comes along that desperately needs JPEG2000 and ICC // profiles, maybe they will be motivated enough to track down the // problem. - const ParamValue *icc = m_spec.find_attribute ("ICCProfile"); - if (icc && icc->type().basetype == TypeDesc::UINT8 && icc->type().arraylen > 0) { - m_image->icc_profile_len = icc->type().arraylen; - m_image->icc_profile_buf = (unsigned char *) icc->data(); + // NOTE: openjpeg does not copy the profile and m_image outlives this + // function, so the bytes are kept in a member rather than a local. + m_icc_profile = pvt::get_colorspace_icc_profile(m_spec); + if (m_icc_profile.size()) { + m_image->icc_profile_len + = decltype(m_image->icc_profile_len)(m_icc_profile.size()); + m_image->icc_profile_buf = (unsigned char*)m_icc_profile.data(); } #endif diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index 37fff33817..1a86beb2d6 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -6,12 +6,13 @@ #include #include -#include #include #include #include #include +#include "imageio_pvt.h" + #include #include #include @@ -542,30 +543,19 @@ JxlOutput::save_image(const void* data) bool wrote_colorspace = false; // Write the ICC profile, if available - const ParamValue* icc_profile_parameter = m_spec.find_attribute( - "ICCProfile"); - if (icc_profile_parameter != nullptr) { - unsigned char* icc_profile - = (unsigned char*)icc_profile_parameter->data(); - uint32_t length = icc_profile_parameter->type().size(); - if (icc_profile && length) { - if (JXL_ENC_SUCCESS - != JxlEncoderSetICCProfile(m_encoder.get(), icc_profile, - length)) { - errorfmt("JxlEncoderSetICCProfile failed\n"); - } + std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + if (icc_profile.size()) { + if (JXL_ENC_SUCCESS + != JxlEncoderSetICCProfile(m_encoder.get(), icc_profile.data(), + icc_profile.size())) { + errorfmt("JxlEncoderSetICCProfile failed\n"); + } else { wrote_colorspace = true; } } // Write CICP - const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); - const ParamValue* p = m_spec.find_attribute("CICP", - TypeDesc(TypeDesc::INT, 4)); - string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace"); - cspan cicp = (p) ? p->as_cspan() - : (!wrote_colorspace) ? colorconfig.get_cicp(colorspace) - : cspan(); + cspan cicp = pvt::get_colorspace_cicp(m_spec, !wrote_colorspace); if (!cicp.empty()) { // JXL only has a subset of CICP, only write if supported. Custom // primaries and white point are not currently used but could help diff --git a/src/libOpenImageIO/color_ocio.cpp b/src/libOpenImageIO/color_ocio.cpp index a0935f06b4..488200faf2 100644 --- a/src/libOpenImageIO/color_ocio.cpp +++ b/src/libOpenImageIO/color_ocio.cpp @@ -3057,5 +3057,93 @@ set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) ColorConfig::default_colorconfig().set_colorspace_rec709_gamma(spec, gamma); } +OIIO_NAMESPACE_3_1_END + +OIIO_NAMESPACE_BEGIN + +// Parse a color space name of the form "g_rec709_(scene|display)". +static float +rec709_colorspace_gamma(string_view colorspace) +{ + if (!Strutil::parse_prefix(colorspace, "g")) + return 0.0f; + int g10 = 0; + if (!Strutil::parse_int(colorspace, g10) || g10 <= 0) + return 0.0f; + if (colorspace != "_rec709_scene" && colorspace != "_rec709_display") + return 0.0f; + return float(g10) / 10.0f; +} + +float +pvt::get_colorspace_rec709_gamma(const ImageSpec& spec) +{ + const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); + string_view colorspace = spec.get_string_attribute("oiio:ColorSpace"); + string_view interop_id = colorconfig.get_color_interop_id(colorspace); + + // Gamma interop IDs, as well as arbitrary names that do not have an + // official interop ID as generated by set_colorspace_rec709_gamma(). + const float gamma = rec709_colorspace_gamma( + interop_id.empty() ? colorspace : interop_id); + + // Backwards compatibility, scene_linear is not necessarily Rec.709 + if (colorconfig.equivalent(colorspace, "linear") + || colorconfig.equivalent(colorspace, "scene_linear") + || interop_id == "lin_rec709_scene") + return 1.0f; + else if (gamma != 0.0f) + return gamma; + // Backwards compatibility, this is DEPRECATED(3.1) + else if (Strutil::istarts_with(colorspace, "Gamma")) { + Strutil::parse_word(colorspace); + float g = Strutil::from_string(colorspace); + if (g >= 0.01f && g <= 10.0f /* sanity check */) + return g; + } + + // Obsolete "oiio:Gamma" attribute for backwards compatibility + return spec.get_float_attribute("oiio:Gamma", 0.0f); +} + +bool +pvt::is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb) +{ + string_view colorspace = spec.get_string_attribute("oiio:ColorSpace"); + if (default_to_srgb && colorspace.empty()) { + return true; + } + + const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); + string_view interop_id = colorconfig.get_color_interop_id(colorspace); + + return (interop_id == "srgb_rec709_scene" + || interop_id == "srgb_rec709_display"); +} + +std::vector +pvt::get_colorspace_icc_profile(const ImageSpec& spec, bool /*from_colorspace*/) +{ + std::vector icc_profile; + const ParamValue* p = spec.find_attribute("ICCProfile"); + if (p) { + cspan icc_profile_span = p->as_cspan(); + icc_profile.assign(icc_profile_span.begin(), icc_profile_span.end()); + } + return icc_profile; +} + +cspan +pvt::get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace) +{ + const ParamValue* p = spec.find_attribute("CICP", + TypeDesc(TypeDesc::INT, 4)); + if (p) + return p->as_cspan(); + if (!from_colorspace) + return cspan(); + const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); + return colorconfig.get_cicp(spec.get_string_attribute("oiio:ColorSpace")); +} OIIO_NAMESPACE_END diff --git a/src/png.imageio/png_pvt.h b/src/png.imageio/png_pvt.h index 6073ed9aee..ddd8585ec7 100644 --- a/src/png.imageio/png_pvt.h +++ b/src/png.imageio/png_pvt.h @@ -18,6 +18,7 @@ #include #include +#include "imageio_pvt.h" #define OIIO_LIBPNG_VERSION \ (PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 \ @@ -39,7 +40,6 @@ For further information see the following mailing list threads: OIIO_PLUGIN_NAMESPACE_BEGIN -#define ICC_PROFILE_ATTR "ICCProfile" #define CICP_ATTR "CICP" namespace PNG_pvt { @@ -635,78 +635,36 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, convert_alpha = spec.alpha_channel != -1 && !spec.get_int_attribute("oiio:UnassociatedAlpha", 0); - string_view colorspace = spec.get_string_attribute("oiio:ColorSpace", - "srgb_rec709_scene"); - const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); OIIO_MAYBE_UNUSED bool wrote_colorspace = false; srgb = false; - if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) { - srgb = true; + if (pvt::is_colorspace_srgb(spec)) { gamma = 1.0f; - } else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")) { - gamma = 2.2f; - } else if (colorconfig.equivalent(colorspace, "g24_rec709_scene")) { - gamma = 2.4f; - } else if (colorconfig.equivalent(colorspace, "g18_rec709_scene")) { - gamma = 1.8f; - } else { - gamma = spec.get_float_attribute("oiio:Gamma", 1.0f); - // obsolete "oiio:Gamma" attrib for back compatibility - } - - if (colorconfig.equivalent(colorspace, "scene_linear") - || colorconfig.equivalent(colorspace, "lin_rec709_scene")) { - if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) - return "Could not set PNG gAMA chunk"; - png_set_gAMA(sp, ip, 1.0); - srgb = false; - wrote_colorspace = true; - } else if (Strutil::istarts_with(colorspace, "Gamma")) { - // Back compatible, this is DEPRECATED(3.1) - Strutil::parse_word(colorspace); - float g = Strutil::from_string(colorspace); - if (g >= 0.01f && g <= 10.0f /* sanity check */) - gamma = g; - if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) - return "Could not set PNG gAMA chunk"; - png_set_gAMA(sp, ip, 1.0f / gamma); - srgb = false; - wrote_colorspace = true; - } else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")) { - gamma = 2.2f; - if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) - return "Could not set PNG gAMA chunk"; - png_set_gAMA(sp, ip, 1.0f / gamma); - srgb = false; - wrote_colorspace = true; - } else if (colorconfig.equivalent(colorspace, "g18_rec709_scene")) { - gamma = 1.8f; - if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) - return "Could not set PNG gAMA chunk"; - png_set_gAMA(sp, ip, 1.0f / gamma); - srgb = false; - wrote_colorspace = true; - } else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) { + srgb = true; if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) return "Could not set PNG gAMA and cHRM chunk"; png_set_sRGB_gAMA_and_cHRM(sp, ip, PNG_sRGB_INTENT_ABSOLUTE); - srgb = true; wrote_colorspace = true; + } else { + gamma = pvt::get_colorspace_rec709_gamma(spec); + if (gamma != 0.0f) { + if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) + return "Could not set PNG gAMA chunk"; + png_set_gAMA(sp, ip, 1.0 / gamma); + srgb = false; + wrote_colorspace = true; + } else { + gamma = 1.0f; + } } // Write ICC profile, if we have anything - const ParamValue* icc_profile_parameter = spec.find_attribute( - ICC_PROFILE_ATTR); - if (icc_profile_parameter != nullptr) { - unsigned int length = icc_profile_parameter->type().size(); + std::vector icc_profile = pvt::get_colorspace_icc_profile(spec); + if (icc_profile.size()) { if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) return "Could not set PNG iCCP chunk"; - unsigned char* icc_profile - = (unsigned char*)icc_profile_parameter->data(); - if (icc_profile && length) { - png_set_iCCP(sp, ip, "Embedded Profile", 0, icc_profile, length); - wrote_colorspace = true; - } + png_set_iCCP(sp, ip, "Embedded Profile", 0, icc_profile.data(), + icc_profile.size()); + wrote_colorspace = true; } if (false && !spec.find_attribute("DateTime")) { @@ -764,11 +722,7 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, #ifdef PNG_cICP_SUPPORTED // Only automatically determine CICP from oiio::ColorSpace if we didn't // write colorspace metadata yet. - const ParamValue* p = spec.find_attribute(CICP_ATTR, - TypeDesc(TypeDesc::INT, 4)); - cspan cicp = (p) ? p->as_cspan() - : (!wrote_colorspace) ? colorconfig.get_cicp(colorspace) - : cspan(); + cspan cicp = pvt::get_colorspace_cicp(spec, !wrote_colorspace); if (!cicp.empty()) { png_byte vals[4]; for (int i = 0; i < 4; ++i) diff --git a/src/png.imageio/pnginput.cpp b/src/png.imageio/pnginput.cpp index 55afc89ee8..07ecdaca51 100644 --- a/src/png.imageio/pnginput.cpp +++ b/src/png.imageio/pnginput.cpp @@ -192,22 +192,15 @@ PNGInput::open(const std::string& name, ImageSpec& newspec) return false; } - string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace", - "srgb_rec709_scene"); - const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); - m_srgb = false; - if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) { + if (pvt::is_colorspace_srgb(m_spec)) { m_srgb = true; m_gamma = 1.0f; - } else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")) { - m_gamma = 2.2f; - } else if (colorconfig.equivalent(colorspace, "g24_rec709_scene")) { - m_gamma = 2.4f; - } else if (colorconfig.equivalent(colorspace, "g18_rec709_scene")) { - m_gamma = 1.8f; } else { - m_gamma = m_spec.get_float_attribute("oiio:Gamma", 1.0f); - // obsolete "oiio:Gamma" attrib for back compatibility + m_srgb = false; + m_gamma = pvt::get_colorspace_rec709_gamma(m_spec); + if (m_gamma == 0.0f) { + m_gamma = 1.0f; + } } newspec = spec(); diff --git a/src/rla.imageio/rlaoutput.cpp b/src/rla.imageio/rlaoutput.cpp index 6afcad128f..17bfa00c91 100644 --- a/src/rla.imageio/rlaoutput.cpp +++ b/src/rla.imageio/rlaoutput.cpp @@ -15,6 +15,8 @@ #include #include +#include "imageio_pvt.h" + #include "rla_pvt.h" @@ -256,21 +258,9 @@ RLAOutput::open(const std::string& name, const ImageSpec& userspec, // << m_rla.NumOfMatteChannels << " z " << m_rla.NumOfAuxChannels << "\n"; m_rla.Revision = 0xFFFE; - const ColorConfig& colorconfig = ColorConfig::default_colorconfig(); - string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace"); - if (colorconfig.equivalent(colorspace, "linear") - || colorconfig.equivalent(colorspace, "scene_linear")) - Strutil::safe_strcpy(m_rla.Gamma, "1.0", sizeof(m_rla.Gamma)); - else if (colorconfig.equivalent(colorspace, "g22_rec709")) - Strutil::safe_strcpy(m_rla.Gamma, "2.2", sizeof(m_rla.Gamma)); - else if (colorconfig.equivalent(colorspace, "g18_rec709")) - Strutil::safe_strcpy(m_rla.Gamma, "1.8", sizeof(m_rla.Gamma)); - else if (Strutil::istarts_with(colorspace, "Gamma")) { - Strutil::parse_word(colorspace); - float g = Strutil::from_string(colorspace); - if (!(g >= 0.01f && g <= 10.0f /* sanity check */)) - g = m_spec.get_float_attribute("oiio:Gamma", 1.f); - safe_format_to(m_rla.Gamma, "{:.10}", g); + const float gamma = pvt::get_colorspace_rec709_gamma(m_spec); + if (gamma != 0.0f) { + safe_format_to(m_rla.Gamma, "{:.5g}", gamma); } const ParamValue* p; diff --git a/src/targa.imageio/targaoutput.cpp b/src/targa.imageio/targaoutput.cpp index a1af3a8921..78fe2ba3ba 100644 --- a/src/targa.imageio/targaoutput.cpp +++ b/src/targa.imageio/targaoutput.cpp @@ -15,6 +15,8 @@ #include #include +#include "imageio_pvt.h" + #include "targa_pvt.h" OIIO_PLUGIN_NAMESPACE_BEGIN @@ -181,7 +183,9 @@ TGAOutput::open(const std::string& name, const ImageSpec& userspec, m_convert_alpha = m_spec.alpha_channel != -1 && !m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0); - m_gamma = m_spec.get_float_attribute("oiio:Gamma", 1.0); + m_gamma = pvt::get_colorspace_rec709_gamma(m_spec); + if (m_gamma == 0.0f) + m_gamma = 1.0f; // prepare and write Targa header tga_header tga; @@ -360,27 +364,13 @@ TGAOutput::write_tga20_data_fields() } // gamma -- two shorts, giving a ratio - const ColorConfig& colorconfig = ColorConfig::default_colorconfig(); - string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace"); - if (colorconfig.equivalent(colorspace, "g22_rec709")) { - m_gamma = 2.2f; - write(uint16_t(m_gamma * 10.0f)); - write(uint16_t(10)); - } else if (colorconfig.equivalent(colorspace, "g18_rec709")) { - m_gamma = 1.8f; - write(uint16_t(m_gamma * 10.0f)); - write(uint16_t(10)); - } else if (Strutil::istarts_with(colorspace, "Gamma")) { - // Extract gamma value from color space, if it's there - Strutil::parse_word(colorspace); - float g = Strutil::from_string(colorspace); - if (g >= 0.01f && g <= 10.0f /* sanity check */) - m_gamma = g; + const float gamma = pvt::get_colorspace_rec709_gamma(m_spec); + if (gamma != 0.0f) { // FIXME: invent a smarter way to convert to a vulgar fraction? // NOTE: the spec states that only 1 decimal place of precision // is needed, thus the expansion by 10 // numerator - write(uint16_t(std::round(m_gamma * 10.0f))); + write(uint16_t(std::round(gamma * 10.0f))); write(uint16_t(10)); } else { // just dump two zeros in there diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index 6f59678ec5..6ccf8fd3a7 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -399,9 +399,6 @@ TIFFOutput::supports(string_view feature) const } -#define ICC_PROFILE_ATTR "ICCProfile" - - // Do all elements of vector d have value v? template inline bool @@ -883,18 +880,14 @@ TIFFOutput::open(const std::string& name, const ImageSpec& userspec, } // Write ICC profile, if we have anything - const ParamValue* icc_profile_parameter = m_spec.find_attribute( - ICC_PROFILE_ATTR); - if (icc_profile_parameter != NULL) { - unsigned char* icc_profile - = (unsigned char*)icc_profile_parameter->data(); - uint32_t length = icc_profile_parameter->type().size(); - if (icc_profile && length) - TIFFSetField(m_tif, TIFFTAG_ICCPROFILE, length, icc_profile); - } - - if (equivalent_colorspace(m_spec.get_string_attribute("oiio:ColorSpace"), - "srgb_rec709_scene")) + std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + if (icc_profile.size()) { + uint32_t icc_profile_size = uint32_t(icc_profile.size()); + TIFFSetField(m_tif, TIFFTAG_ICCPROFILE, icc_profile_size, + icc_profile.data()); + } + + if (pvt::is_colorspace_srgb(m_spec, false)) m_spec.attribute("Exif:ColorSpace", 1); // Deal with missing XResolution or YResolution, or a PixelAspectRatio diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 784575963e..3634e66b4b 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -11,6 +11,8 @@ #include #include +#include "imageio_pvt.h" + OIIO_PLUGIN_NAMESPACE_BEGIN namespace webp_pvt { @@ -142,16 +144,8 @@ bool WebpOutput::write_complete_data() { // Check if we have an optional ICC Profile to write. - const unsigned char* icc_data = nullptr; - uint32_t icc_data_length = 0; - bool has_icc_data = false; - const ParamValue* icc_profile_parameter = m_spec.find_attribute( - "ICCProfile"); - if (icc_profile_parameter != nullptr) { - icc_data = (const unsigned char*)icc_profile_parameter->data(); - icc_data_length = icc_profile_parameter->type().size(); - has_icc_data = (icc_data && icc_data_length > 0); - } + std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + const bool has_icc_data = icc_profile.size() > 0; // If we have ICC data, encode to memory first. This is required in order // to use the WebPMux assembly API below. @@ -203,7 +197,7 @@ WebpOutput::write_complete_data() WebPData image_data = { wrt.mem, wrt.size }; WebPMuxSetImage(mux, &image_data, false); - WebPData icc_chunk = { icc_data, size_t(icc_data_length) }; + WebPData icc_chunk = { icc_profile.data(), icc_profile.size() }; WebPMuxSetChunk(mux, "ICCP", &icc_chunk, false); WebPData assembly; From 702495be06e11414b900e21eae0c17ff47e19b0a Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 11 Aug 2026 00:27:09 +0200 Subject: [PATCH 2/3] Move helper functions to public API Signed-off-by: Brecht Van Lommel --- src/doc/imageioapi.rst | 8 ++++++ src/dpx.imageio/dpxoutput.cpp | 6 ++--- src/heif.imageio/heifoutput.cpp | 4 +-- src/include/OpenImageIO/imageio.h | 36 +++++++++++++++++++++++++ src/include/imageio_pvt.h | 25 ----------------- src/iv/imageviewer.cpp | 19 +++++++------ src/iv/ivgl.cpp | 3 +-- src/jpeg.imageio/jpegoutput.cpp | 6 ++--- src/jpeg2000.imageio/jpeg2000output.cpp | 2 +- src/jpegxl.imageio/jxloutput.cpp | 6 ++--- src/libOpenImageIO/color_ocio.cpp | 14 ++++------ src/png.imageio/png_pvt.h | 9 +++---- src/png.imageio/pnginput.cpp | 4 +-- src/rla.imageio/rlaoutput.cpp | 4 +-- src/targa.imageio/targaoutput.cpp | 6 ++--- src/tiff.imageio/tiffoutput.cpp | 4 +-- src/webp.imageio/webpoutput.cpp | 4 +-- 17 files changed, 79 insertions(+), 81 deletions(-) diff --git a/src/doc/imageioapi.rst b/src/doc/imageioapi.rst index a674ffdb31..588b2b0ca7 100644 --- a/src/doc/imageioapi.rst +++ b/src/doc/imageioapi.rst @@ -264,6 +264,14 @@ just exist in the OIIO namespace as general utilities. (See .. doxygenfunction:: OIIO::set_colorspace_rec709_gamma +.. doxygenfunction:: OIIO::is_colorspace_srgb + +.. doxygenfunction:: OIIO::get_colorspace_rec709_gamma + +.. doxygenfunction:: OIIO::get_colorspace_icc_profile + +.. doxygenfunction:: OIIO::get_colorspace_cicp + .. doxygenfunction:: OIIO::equivalent_colorspace | diff --git a/src/dpx.imageio/dpxoutput.cpp b/src/dpx.imageio/dpxoutput.cpp index e3dc059e49..5f74bf4b4d 100644 --- a/src/dpx.imageio/dpxoutput.cpp +++ b/src/dpx.imageio/dpxoutput.cpp @@ -18,8 +18,6 @@ #include #include -#include "imageio_pvt.h" - OIIO_PLUGIN_NAMESPACE_BEGIN @@ -429,8 +427,8 @@ DPXOutput::prep_subimage(int s, bool allocate) m_desc = get_image_descriptor(); // transfer function - const float gamma = pvt::get_colorspace_rec709_gamma(spec_s); - if (pvt::is_colorspace_srgb(spec_s, false)) + const float gamma = get_colorspace_rec709_gamma(spec_s); + if (is_colorspace_srgb(spec_s, false)) m_transfer = dpx::kITUR709; else if (gamma == 1.0f) m_transfer = dpx::kLinear; diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 139261b7e7..0d31d0065a 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -10,8 +10,6 @@ #include #include -#include "imageio_pvt.h" - #include #define MAKE_LIBHEIF_VERSION(a, b, c, d) \ @@ -277,7 +275,7 @@ HeifOutput::close() std::unique_ptr nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free); - cspan cicp = pvt::get_colorspace_cicp(m_spec); + cspan cicp = get_colorspace_cicp(m_spec); if (!cicp.empty()) { nclx->color_primaries = heif_color_primaries(cicp[0]); nclx->transfer_characteristics = heif_transfer_characteristics( diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index a20af2284c..156c1591b7 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -4377,6 +4377,38 @@ OIIO_API void set_colorspace(ImageSpec& spec, string_view name); /// @version 3.0 OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma); +/// Returns true if for the purpose of interop, the spec's metadata specifies +/// a color space that should be encoded as sRGB. +/// +/// If `default_to_srgb` is true, the color space will be assumed to be sRGB +/// if no color space was specified in the spec. +/// +/// @version 3.1 +OIIO_API bool is_colorspace_srgb(const ImageSpec& spec, + bool default_to_srgb = true); + +/// If the spec's metadata specifies a color space with Rec709 primaries and +/// gamma transfer function, return the gamma value. If not, return zero. +/// +/// @version 3.1 +OIIO_API float get_colorspace_rec709_gamma(const ImageSpec& spec); + +/// Returns the ICC profile from the spec's metadata, either from an +/// "ICCProfile" attribute or from the color space if `from_colorspace` is +/// true. Returns an empty vector if not found. +/// +/// @version 3.1 +OIIO_API std::vector +get_colorspace_icc_profile(const ImageSpec& spec, bool from_colorspace = true); + +/// Returns the CICP code from the spec's metadata, either from a "CICP" +/// attribute or from the color space if `from_colorspace` is true. Returns a +/// cspan of 4 ints, or an empty span if not found. +/// +/// @version 3.1 +OIIO_API cspan get_colorspace_cicp(const ImageSpec& spec, + bool from_colorspace = true); + /// Are the two named color spaces equivalent, based on the default color /// config in effect? @@ -4764,6 +4796,9 @@ using v3_1::debugfmt; using v3_1::declare_imageio_format; using v3_1::equivalent_colorspace; using v3_1::errorfmt; +using v3_1::get_colorspace_cicp; +using v3_1::get_colorspace_icc_profile; +using v3_1::get_colorspace_rec709_gamma; using v3_1::get_extension_map; using v3_1::get_float_attribute; using v3_1::get_int_attribute; @@ -4771,6 +4806,7 @@ using v3_1::get_string_attribute; using v3_1::getattribute; using v3_1::geterror; using v3_1::has_error; +using v3_1::is_colorspace_srgb; using v3_1::is_imageio_format_name; using v3_1::log_time; using v3_1::openimageio_version; diff --git a/src/include/imageio_pvt.h b/src/include/imageio_pvt.h index 79a54f03a6..ee350d4e75 100644 --- a/src/include/imageio_pvt.h +++ b/src/include/imageio_pvt.h @@ -133,31 +133,6 @@ parallel_convert_from_float(const float* src, void* dst, size_t nvals, OIIO_API bool check_texture_metadata_sanity(ImageSpec& spec); -/// Returns true if for the purpose of interop, the spec's metadata -/// specifies a color space that should be encoded as sRGB. -/// -/// If default_to_srgb is true, the colorspace will be assumed to -/// be sRGB if no colorspace was specified in the spec. -OIIO_API bool -is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb = true); - -/// If the spec's metadata specifies a color space with Rec709 primaries and -/// gamma transfer function, return the gamma value. If not, return zero. -OIIO_API float -get_colorspace_rec709_gamma(const ImageSpec& spec); - -// Returns ICC profile from the spec's metadata, either from an ICCProfile -// attribute or from the colorspace if from_colorspace is true. -// Returns an empty vector if not found. -OIIO_API std::vector -get_colorspace_icc_profile(const ImageSpec& spec, bool from_colorspace = true); - -// Returns CICP from the spec's metadata, either from a CICP attribute -// or from the colorspace if from_colorspace is true. -// Returns an empty span if not found. -OIIO_API cspan -get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace = true); - /// Get the timing report from log_time entries. OIIO_API std::string timing_report(); diff --git a/src/iv/imageviewer.cpp b/src/iv/imageviewer.cpp index 75b0e6d451..f96bc97fc9 100644 --- a/src/iv/imageviewer.cpp +++ b/src/iv/imageviewer.cpp @@ -44,7 +44,6 @@ #include #include -#include "imageio_pvt.h" #include "ivutils.h" @@ -1252,7 +1251,7 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel) //std::cerr << "Loading HALF-FLOAT as FLOAT\n"; read_format = TypeDesc::FLOAT; } - if (pvt::is_colorspace_srgb(image_spec, false) + if (is_colorspace_srgb(image_spec, false) && !glwin->is_srgb_capable()) { // If the image is in sRGB, but OpenGL can't load sRGB textures then // we'll need to do the transformation on the CPU after loading the @@ -1268,7 +1267,7 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel) read_format = TypeDesc::UINT8; allow_transforms = true; - if (pvt::is_colorspace_srgb(image_spec, false) + if (is_colorspace_srgb(image_spec, false) && !glwin->is_srgb_capable()) srgb_transform = true; } @@ -1455,7 +1454,7 @@ ImageViewer::exposureMinusOneTenthStop() img->exposure(img->exposure() - 0.1); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1474,7 +1473,7 @@ ImageViewer::exposureMinusOneHalfStop() img->exposure(img->exposure() - 0.5); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1493,7 +1492,7 @@ ImageViewer::exposurePlusOneTenthStop() img->exposure(img->exposure() + 0.1); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1512,7 +1511,7 @@ ImageViewer::exposurePlusOneHalfStop() img->exposure(img->exposure() + 0.5); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1532,7 +1531,7 @@ ImageViewer::gammaMinus() img->gamma(img->gamma() - 0.05); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1551,7 +1550,7 @@ ImageViewer::gammaPlus() img->gamma(img->gamma() + 0.05); if (!glwin->is_glsl_capable()) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)current_color_mode(), current_channel()); displayCurrentImage(); @@ -1585,7 +1584,7 @@ ImageViewer::viewChannel(int c, COLOR_MODE colormode) if (img) { bool srgb_transform = (!glwin->is_srgb_capable() - && pvt::is_colorspace_srgb(img->spec(), false)); + && is_colorspace_srgb(img->spec(), false)); img->pixel_transform(srgb_transform, (int)colormode, c); } } else { diff --git a/src/iv/ivgl.cpp b/src/iv/ivgl.cpp index 3880275592..e96e523514 100644 --- a/src/iv/ivgl.cpp +++ b/src/iv/ivgl.cpp @@ -18,7 +18,6 @@ # include #endif -#include "imageio_pvt.h" #include "ivutils.h" #include #include @@ -2199,7 +2198,7 @@ IvGL::typespec_to_opengl(const ImageSpec& spec, int nchannels, GLenum& gltype, break; } - bool issrgb = pvt::is_colorspace_srgb(spec, false); + bool issrgb = is_colorspace_srgb(spec, false); glinternalformat = nchannels; if (nchannels == 1) { diff --git a/src/jpeg.imageio/jpegoutput.cpp b/src/jpeg.imageio/jpegoutput.cpp index 3ab6d18ab2..aabec5fcc2 100644 --- a/src/jpeg.imageio/jpegoutput.cpp +++ b/src/jpeg.imageio/jpegoutput.cpp @@ -12,8 +12,6 @@ #include #include -#include "imageio_pvt.h" - #include "jpeg_pvt.h" OIIO_PLUGIN_NAMESPACE_BEGIN @@ -267,7 +265,7 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec, } } - if (pvt::is_colorspace_srgb(m_spec, false)) + if (is_colorspace_srgb(m_spec, false)) m_spec.attribute("Exif:ColorSpace", 1); // Write EXIF info @@ -318,7 +316,7 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec, m_spec.set_format(TypeDesc::UINT8); // JPG is only 8 bit // Write ICC profile, if we have anything - std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + std::vector icc_profile = get_colorspace_icc_profile(m_spec); if (icc_profile.size()) { /* Calculate the number of markers we'll need, rounding up of course */ size_t num_markers = icc_profile.size() / MAX_DATA_BYTES_IN_MARKER; diff --git a/src/jpeg2000.imageio/jpeg2000output.cpp b/src/jpeg2000.imageio/jpeg2000output.cpp index 7e3a4e746d..c8650904f6 100644 --- a/src/jpeg2000.imageio/jpeg2000output.cpp +++ b/src/jpeg2000.imageio/jpeg2000output.cpp @@ -489,7 +489,7 @@ Jpeg2000Output::create_jpeg2000_image() // problem. // NOTE: openjpeg does not copy the profile and m_image outlives this // function, so the bytes are kept in a member rather than a local. - m_icc_profile = pvt::get_colorspace_icc_profile(m_spec); + m_icc_profile = get_colorspace_icc_profile(m_spec); if (m_icc_profile.size()) { m_image->icc_profile_len = decltype(m_image->icc_profile_len)(m_icc_profile.size()); diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index 1a86beb2d6..9f444f82ed 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -11,8 +11,6 @@ #include #include -#include "imageio_pvt.h" - #include #include #include @@ -543,7 +541,7 @@ JxlOutput::save_image(const void* data) bool wrote_colorspace = false; // Write the ICC profile, if available - std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + std::vector icc_profile = get_colorspace_icc_profile(m_spec); if (icc_profile.size()) { if (JXL_ENC_SUCCESS != JxlEncoderSetICCProfile(m_encoder.get(), icc_profile.data(), @@ -555,7 +553,7 @@ JxlOutput::save_image(const void* data) } // Write CICP - cspan cicp = pvt::get_colorspace_cicp(m_spec, !wrote_colorspace); + cspan cicp = get_colorspace_cicp(m_spec, !wrote_colorspace); if (!cicp.empty()) { // JXL only has a subset of CICP, only write if supported. Custom // primaries and white point are not currently used but could help diff --git a/src/libOpenImageIO/color_ocio.cpp b/src/libOpenImageIO/color_ocio.cpp index 488200faf2..74b3b7dcb0 100644 --- a/src/libOpenImageIO/color_ocio.cpp +++ b/src/libOpenImageIO/color_ocio.cpp @@ -3057,10 +3057,6 @@ set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) ColorConfig::default_colorconfig().set_colorspace_rec709_gamma(spec, gamma); } -OIIO_NAMESPACE_3_1_END - -OIIO_NAMESPACE_BEGIN - // Parse a color space name of the form "g_rec709_(scene|display)". static float rec709_colorspace_gamma(string_view colorspace) @@ -3076,7 +3072,7 @@ rec709_colorspace_gamma(string_view colorspace) } float -pvt::get_colorspace_rec709_gamma(const ImageSpec& spec) +get_colorspace_rec709_gamma(const ImageSpec& spec) { const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); string_view colorspace = spec.get_string_attribute("oiio:ColorSpace"); @@ -3107,7 +3103,7 @@ pvt::get_colorspace_rec709_gamma(const ImageSpec& spec) } bool -pvt::is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb) +is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb) { string_view colorspace = spec.get_string_attribute("oiio:ColorSpace"); if (default_to_srgb && colorspace.empty()) { @@ -3122,7 +3118,7 @@ pvt::is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb) } std::vector -pvt::get_colorspace_icc_profile(const ImageSpec& spec, bool /*from_colorspace*/) +get_colorspace_icc_profile(const ImageSpec& spec, bool /*from_colorspace*/) { std::vector icc_profile; const ParamValue* p = spec.find_attribute("ICCProfile"); @@ -3134,7 +3130,7 @@ pvt::get_colorspace_icc_profile(const ImageSpec& spec, bool /*from_colorspace*/) } cspan -pvt::get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace) +get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace) { const ParamValue* p = spec.find_attribute("CICP", TypeDesc(TypeDesc::INT, 4)); @@ -3146,4 +3142,4 @@ pvt::get_colorspace_cicp(const ImageSpec& spec, bool from_colorspace) return colorconfig.get_cicp(spec.get_string_attribute("oiio:ColorSpace")); } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/png.imageio/png_pvt.h b/src/png.imageio/png_pvt.h index ddd8585ec7..cadc0eae07 100644 --- a/src/png.imageio/png_pvt.h +++ b/src/png.imageio/png_pvt.h @@ -18,7 +18,6 @@ #include #include -#include "imageio_pvt.h" #define OIIO_LIBPNG_VERSION \ (PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 \ @@ -637,7 +636,7 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, OIIO_MAYBE_UNUSED bool wrote_colorspace = false; srgb = false; - if (pvt::is_colorspace_srgb(spec)) { + if (is_colorspace_srgb(spec)) { gamma = 1.0f; srgb = true; if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) @@ -645,7 +644,7 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, png_set_sRGB_gAMA_and_cHRM(sp, ip, PNG_sRGB_INTENT_ABSOLUTE); wrote_colorspace = true; } else { - gamma = pvt::get_colorspace_rec709_gamma(spec); + gamma = get_colorspace_rec709_gamma(spec); if (gamma != 0.0f) { if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) return "Could not set PNG gAMA chunk"; @@ -658,7 +657,7 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, } // Write ICC profile, if we have anything - std::vector icc_profile = pvt::get_colorspace_icc_profile(spec); + std::vector icc_profile = get_colorspace_icc_profile(spec); if (icc_profile.size()) { if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) return "Could not set PNG iCCP chunk"; @@ -722,7 +721,7 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, #ifdef PNG_cICP_SUPPORTED // Only automatically determine CICP from oiio::ColorSpace if we didn't // write colorspace metadata yet. - cspan cicp = pvt::get_colorspace_cicp(spec, !wrote_colorspace); + cspan cicp = get_colorspace_cicp(spec, !wrote_colorspace); if (!cicp.empty()) { png_byte vals[4]; for (int i = 0; i < 4; ++i) diff --git a/src/png.imageio/pnginput.cpp b/src/png.imageio/pnginput.cpp index 07ecdaca51..29ba2d9d60 100644 --- a/src/png.imageio/pnginput.cpp +++ b/src/png.imageio/pnginput.cpp @@ -192,12 +192,12 @@ PNGInput::open(const std::string& name, ImageSpec& newspec) return false; } - if (pvt::is_colorspace_srgb(m_spec)) { + if (is_colorspace_srgb(m_spec)) { m_srgb = true; m_gamma = 1.0f; } else { m_srgb = false; - m_gamma = pvt::get_colorspace_rec709_gamma(m_spec); + m_gamma = get_colorspace_rec709_gamma(m_spec); if (m_gamma == 0.0f) { m_gamma = 1.0f; } diff --git a/src/rla.imageio/rlaoutput.cpp b/src/rla.imageio/rlaoutput.cpp index 17bfa00c91..2b237cdd52 100644 --- a/src/rla.imageio/rlaoutput.cpp +++ b/src/rla.imageio/rlaoutput.cpp @@ -15,8 +15,6 @@ #include #include -#include "imageio_pvt.h" - #include "rla_pvt.h" @@ -258,7 +256,7 @@ RLAOutput::open(const std::string& name, const ImageSpec& userspec, // << m_rla.NumOfMatteChannels << " z " << m_rla.NumOfAuxChannels << "\n"; m_rla.Revision = 0xFFFE; - const float gamma = pvt::get_colorspace_rec709_gamma(m_spec); + const float gamma = get_colorspace_rec709_gamma(m_spec); if (gamma != 0.0f) { safe_format_to(m_rla.Gamma, "{:.5g}", gamma); } diff --git a/src/targa.imageio/targaoutput.cpp b/src/targa.imageio/targaoutput.cpp index 78fe2ba3ba..1554bb45c8 100644 --- a/src/targa.imageio/targaoutput.cpp +++ b/src/targa.imageio/targaoutput.cpp @@ -15,8 +15,6 @@ #include #include -#include "imageio_pvt.h" - #include "targa_pvt.h" OIIO_PLUGIN_NAMESPACE_BEGIN @@ -183,7 +181,7 @@ TGAOutput::open(const std::string& name, const ImageSpec& userspec, m_convert_alpha = m_spec.alpha_channel != -1 && !m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0); - m_gamma = pvt::get_colorspace_rec709_gamma(m_spec); + m_gamma = get_colorspace_rec709_gamma(m_spec); if (m_gamma == 0.0f) m_gamma = 1.0f; @@ -364,7 +362,7 @@ TGAOutput::write_tga20_data_fields() } // gamma -- two shorts, giving a ratio - const float gamma = pvt::get_colorspace_rec709_gamma(m_spec); + const float gamma = get_colorspace_rec709_gamma(m_spec); if (gamma != 0.0f) { // FIXME: invent a smarter way to convert to a vulgar fraction? // NOTE: the spec states that only 1 decimal place of precision diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index 6ccf8fd3a7..0379adb21d 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -880,14 +880,14 @@ TIFFOutput::open(const std::string& name, const ImageSpec& userspec, } // Write ICC profile, if we have anything - std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + std::vector icc_profile = get_colorspace_icc_profile(m_spec); if (icc_profile.size()) { uint32_t icc_profile_size = uint32_t(icc_profile.size()); TIFFSetField(m_tif, TIFFTAG_ICCPROFILE, icc_profile_size, icc_profile.data()); } - if (pvt::is_colorspace_srgb(m_spec, false)) + if (is_colorspace_srgb(m_spec, false)) m_spec.attribute("Exif:ColorSpace", 1); // Deal with missing XResolution or YResolution, or a PixelAspectRatio diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 3634e66b4b..e96f6e2313 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -11,8 +11,6 @@ #include #include -#include "imageio_pvt.h" - OIIO_PLUGIN_NAMESPACE_BEGIN namespace webp_pvt { @@ -144,7 +142,7 @@ bool WebpOutput::write_complete_data() { // Check if we have an optional ICC Profile to write. - std::vector icc_profile = pvt::get_colorspace_icc_profile(m_spec); + std::vector icc_profile = get_colorspace_icc_profile(m_spec); const bool has_icc_data = icc_profile.size() > 0; // If we have ICC data, encode to memory first. This is required in order From 05ba5d6fa3742b99248a2275701be46ca86e86e5 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 11 Aug 2026 00:52:23 +0200 Subject: [PATCH 3/3] Add Python bindings Signed-off-by: Brecht Van Lommel --- src/doc/pythonbindings.rst | 65 +++++++++++++++++++++++ src/include/OpenImageIO/imageio.h | 17 +++--- src/python/py_oiio.cpp | 34 ++++++++++++ src/python/stubs/OpenImageIO/__init__.pyi | 4 ++ src/python/stubs/generate_stubs.py | 6 +++ testsuite/python-oiio/ref/out.txt | 10 ++++ testsuite/python-oiio/src/test_oiio.py | 22 ++++++++ 7 files changed, 150 insertions(+), 8 deletions(-) diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index 9ba85b323f..90043903ae 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -4138,6 +4138,71 @@ details. This function was added in OpenImageIO 3.0. +.. py:method:: is_colorspace_srgb (spec, default_to_srgb = True) + + Returns `True` if for the purpose of interop, the metadata of the `spec` + specifies a color space that should be encoded as sRGB. + + If `default_to_srgb` is `True`, the color space will be assumed to be + sRGB if no color space was specified in the spec. + + Example: + + .. code-block:: python + + if oiio.is_colorspace_srgb (spec) : + print ("The image is sRGB") + + This function was added in OpenImageIO 3.1. + + +.. py:method:: get_colorspace_rec709_gamma (spec) + + If the metadata of the `spec` specifies a color space with Rec709 + primaries and gamma transfer function, return the gamma value. If not, + return zero. + + Example: + + .. code-block:: python + + gamma = oiio.get_colorspace_rec709_gamma (spec) + + This function was added in OpenImageIO 3.1. + + +.. py:method:: get_colorspace_icc_profile (spec, from_colorspace = True) + + Returns the ICC profile from the metadata of the `spec` as a `bytes` + object, either from an "ICCProfile" attribute or from the color space if + `from_colorspace` is `True`. Returns `None` if not found. + + Example: + + .. code-block:: python + + icc_profile = oiio.get_colorspace_icc_profile (spec) + + This function was added in OpenImageIO 3.1. + + +.. py:method:: get_colorspace_cicp (spec, from_colorspace = True) + + Returns the CICP code from the metadata of the `spec` as a list of 4 + ints, either from a "CICP" attribute or from the color space if + `from_colorspace` is `True`. Returns `None` if not found. + + Example: + + .. code-block:: python + + cicp = oiio.get_colorspace_cicp (spec) + if cicp: + primaries, transfer, matrix, range = cicp + + This function was added in OpenImageIO 3.1. + + .. py:method:: equivalent_colorspace (a, b) Return `True` if the color spaces `a` and `b` are equivalent in the diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 156c1591b7..d5234312f9 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -4377,8 +4377,8 @@ OIIO_API void set_colorspace(ImageSpec& spec, string_view name); /// @version 3.0 OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma); -/// Returns true if for the purpose of interop, the spec's metadata specifies -/// a color space that should be encoded as sRGB. +/// Returns true if for the purpose of interop, the metadata of the `spec` +/// specifies a color space that should be encoded as sRGB. /// /// If `default_to_srgb` is true, the color space will be assumed to be sRGB /// if no color space was specified in the spec. @@ -4387,13 +4387,14 @@ OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma); OIIO_API bool is_colorspace_srgb(const ImageSpec& spec, bool default_to_srgb = true); -/// If the spec's metadata specifies a color space with Rec709 primaries and -/// gamma transfer function, return the gamma value. If not, return zero. +/// If the metadata of the `spec` specifies a color space with Rec709 +/// primaries and gamma transfer function, return the gamma value. If not, +/// return zero. /// /// @version 3.1 OIIO_API float get_colorspace_rec709_gamma(const ImageSpec& spec); -/// Returns the ICC profile from the spec's metadata, either from an +/// Returns the ICC profile from the metadata of the `spec`, either from an /// "ICCProfile" attribute or from the color space if `from_colorspace` is /// true. Returns an empty vector if not found. /// @@ -4401,9 +4402,9 @@ OIIO_API float get_colorspace_rec709_gamma(const ImageSpec& spec); OIIO_API std::vector get_colorspace_icc_profile(const ImageSpec& spec, bool from_colorspace = true); -/// Returns the CICP code from the spec's metadata, either from a "CICP" -/// attribute or from the color space if `from_colorspace` is true. Returns a -/// cspan of 4 ints, or an empty span if not found. +/// Returns the CICP code from the metadata of the `spec`, either from a +/// "CICP" attribute or from the color space if `from_colorspace` is true. +/// Returns a cspan of 4 ints, or an empty span if not found. /// /// @version 3.1 OIIO_API cspan get_colorspace_cicp(const ImageSpec& spec, diff --git a/src/python/py_oiio.cpp b/src/python/py_oiio.cpp index 7a2a8d1c67..4c3d2da49b 100644 --- a/src/python/py_oiio.cpp +++ b/src/python/py_oiio.cpp @@ -4,7 +4,9 @@ #include "py_oiio.h" +#include #include +#include #include #if defined(OIIO_PY_BACKEND_NANOBIND) @@ -545,6 +547,38 @@ declare_global_attribute_functions(py_module& m) set_colorspace_rec709_gamma(spec, gamma); }, "spec"_a, "gamma"_a); + m.def( + "is_colorspace_srgb", + [](const ImageSpec& spec, bool default_to_srgb) { + return is_colorspace_srgb(spec, default_to_srgb); + }, + "spec"_a, "default_to_srgb"_a = true); + m.def( + "get_colorspace_rec709_gamma", + [](const ImageSpec& spec) { return get_colorspace_rec709_gamma(spec); }, + "spec"_a); + m.def( + "get_colorspace_icc_profile", + [](const ImageSpec& spec, + bool from_colorspace) -> std::optional { + std::vector icc_profile + = get_colorspace_icc_profile(spec, from_colorspace); + if (icc_profile.empty()) + return std::nullopt; + return py::bytes(reinterpret_cast(icc_profile.data()), + icc_profile.size()); + }, + "spec"_a, "from_colorspace"_a = true); + m.def( + "get_colorspace_cicp", + [](const ImageSpec& spec, + bool from_colorspace) -> std::optional> { + cspan cicp = get_colorspace_cicp(spec, from_colorspace); + if (cicp.empty()) + return std::nullopt; + return std::array({ cicp[0], cicp[1], cicp[2], cicp[3] }); + }, + "spec"_a, "from_colorspace"_a = true); m.def( "equivalent_colorspace", [](const std::string& a, const std::string& b) { diff --git a/src/python/stubs/OpenImageIO/__init__.pyi b/src/python/stubs/OpenImageIO/__init__.pyi index ea307f2e7f..e5b62510db 100644 --- a/src/python/stubs/OpenImageIO/__init__.pyi +++ b/src/python/stubs/OpenImageIO/__init__.pyi @@ -1630,6 +1630,9 @@ def attribute(arg0: str, arg1: str, /) -> None: ... def attribute(arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... def equivalent_colorspace(arg0: str, arg1: str, /) -> bool: ... def get_bytes_attribute(name: str, defaultval: str = ...) -> bytes: ... +def get_colorspace_cicp(spec: ImageSpec, from_colorspace: bool = ...) -> list[int] | None: ... +def get_colorspace_icc_profile(spec: ImageSpec, from_colorspace: bool = ...) -> bytes | None: ... +def get_colorspace_rec709_gamma(spec: ImageSpec) -> float: ... def get_float_attribute(name: str, defaultval: typing.SupportsFloat = ...) -> float: ... def get_int_attribute(name: str, defaultval: typing.SupportsInt = ...) -> int: ... def get_roi(arg0: ImageSpec, /) -> ROI: ... @@ -1638,6 +1641,7 @@ def get_string_attribute(name: str, defaultval: str = ...) -> str: ... def getattribute(arg0: str, arg1: TypeDesc | BASETYPE | str, /) -> typing.Any: ... def geterror(clear: bool = ...) -> str: ... def intersection(arg0: ROI, arg1: ROI, /) -> ROI: ... +def is_colorspace_srgb(spec: ImageSpec, default_to_srgb: bool = ...) -> bool: ... def is_imageio_format_name(name: str) -> bool: ... def set_colorspace(spec: ImageSpec, name: str) -> None: ... def set_colorspace_rec709_gamma(arg0: ImageSpec, arg1: typing.SupportsFloat, /) -> None: ... diff --git a/src/python/stubs/generate_stubs.py b/src/python/stubs/generate_stubs.py index 336acd99f4..35ad5ab088 100644 --- a/src/python/stubs/generate_stubs.py +++ b/src/python/stubs/generate_stubs.py @@ -36,6 +36,9 @@ class OIIOSignatureGenerator(AdvancedSignatureGenerator): # signatures for these special methods include many inaccurate overloads "*.__ne__": "(self, other: object) -> bool", "*.__eq__": "(self, other: object) -> bool", + # stubgen cannot parse the result of these, override below + "*.get_colorspace_cicp": "(spec: ImageSpec, from_colorspace: bool = ...) -> object", + "*.get_colorspace_icc_profile": "(spec: ImageSpec, from_colorspace: bool = ...) -> object", }, arg_type_overrides={ # FIXME: Buffer may in fact be more accurate here @@ -89,6 +92,9 @@ class OIIOSignatureGenerator(AdvancedSignatureGenerator): ("*.ImageBufAlgo.isConstantColor", "*"): "tuple[float, ...] | None", ("*.ImageBufAlgo.color_range_check", "*"): "tuple[int, ...] | None", + ("*.get_colorspace_cicp", "object"): "list[int] | None", + ("*.get_colorspace_icc_profile", "object"): "bytes | None", + ("*.TextureSystem.imagespec", "object"): "ImageSpec | None", ("*.TextureSystem.texture", "tuple"): "tuple[float, ...]", ("*.TextureSystem.texture3d", "tuple"): "tuple[float, ...]", diff --git a/testsuite/python-oiio/ref/out.txt b/testsuite/python-oiio/ref/out.txt index 97ec265539..21cc18bae5 100644 --- a/testsuite/python-oiio/ref/out.txt +++ b/testsuite/python-oiio/ref/out.txt @@ -26,6 +26,16 @@ Testing set_colorspace module helpers: after set_colorspace(empty): after set_colorspace_rec709_gamma(2.2): g22_rec709_scene +Testing get_colorspace module helpers: + is_colorspace_srgb(unset): True + is_colorspace_srgb(unset, False): False + get_colorspace_rec709_gamma(g22): 2.2 + is_colorspace_srgb(g22): False + get_colorspace_icc_profile(no profile): None + get_colorspace_icc_profile(dummy profile): b'\x00\x01\x02\xfd\xfe\xff' + get_colorspace_cicp(no attribute): None + get_colorspace_cicp(attribute): [9, 16, 9, 1] + Testing global attribute() one-arg: plugin_searchpath str: path/A:path/B threads int: 6 diff --git a/testsuite/python-oiio/src/test_oiio.py b/testsuite/python-oiio/src/test_oiio.py index 6075f875f6..ae382cad5b 100644 --- a/testsuite/python-oiio/src/test_oiio.py +++ b/testsuite/python-oiio/src/test_oiio.py @@ -73,6 +73,28 @@ def version_code(major: int, minor: int, patch: int) -> int: spec.get_string_attribute ("oiio:ColorSpace")) print ("") + print ("Testing get_colorspace module helpers:") + spec = oiio.ImageSpec() + print (" is_colorspace_srgb(unset):", oiio.is_colorspace_srgb (spec)) + print (" is_colorspace_srgb(unset, False):", + oiio.is_colorspace_srgb (spec, False)) + oiio.set_colorspace_rec709_gamma (spec, 2.2) + print (" get_colorspace_rec709_gamma(g22):", + round (oiio.get_colorspace_rec709_gamma (spec), 4)) + print (" is_colorspace_srgb(g22):", oiio.is_colorspace_srgb (spec)) + print (" get_colorspace_icc_profile(no profile):", + oiio.get_colorspace_icc_profile (spec)) + spec.attribute ("ICCProfile", oiio.TypeDesc("uint8[6]"), + bytes([0, 1, 2, 253, 254, 255])) + print (" get_colorspace_icc_profile(dummy profile):", + oiio.get_colorspace_icc_profile (spec)) + print (" get_colorspace_cicp(no attribute):", + oiio.get_colorspace_cicp (spec, False)) + spec.attribute ("CICP", oiio.TypeDesc("int[4]"), (9, 16, 9, 1)) + print (" get_colorspace_cicp(attribute):", + oiio.get_colorspace_cicp (spec, False)) + print ("") + print ("Testing global attribute() one-arg:") oiio.attribute ("plugin_searchpath", "path/A:path/B") print (" plugin_searchpath str:",