From 97a8c5d01acd9b9c182bdd433548a8db38832e3b Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Sat, 13 Sep 2025 14:36:19 -0700 Subject: [PATCH 001/508] feat(webp): Support reading/writing the ICCProfile attribute (#4878) Enable ICCProfile attribute support for WEBP for both input and output. Like other formats this support is passive; pixel data will not be altered by the mere presence of the ICCProfile attribute (i.e. it functions like other informational metadata only). The WebP API is a bit odd when it comes to writing the ICCProfile. We must use the "mux" API set; the "advanced" API is apparently not advanced enough to add the metadata to the file. This wouldn't be too bad except that the mux API is at odds with how we write out the data. Before: When it comes time to do the file write, we would pass our own file writer into webp. The writes would go straight to disk. Now: If ICC profile data is necessary to write, we let webp use its own writer to write to memory. We then assemble the in-memory file and the ICC profile data together and pass that entire package to our writer as a final step. When no ICC profile data is used, the old write behavior is kept. Added `oiiotool` test variations which uses ICC profiles with WEBP. --------- Signed-off-by: Jesse Yurkovich Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/builtinplugins.rst | 4 + src/webp.imageio/CMakeLists.txt | 2 +- src/webp.imageio/webpinput.cpp | 17 ++- src/webp.imageio/webpoutput.cpp | 126 +++++++++++++----- testsuite/oiiotool-attribs/ref/out-jpeg9d.txt | 22 +++ testsuite/oiiotool-attribs/ref/out.txt | 22 +++ testsuite/oiiotool-attribs/ref/test-webp.icc | Bin 0 -> 560 bytes testsuite/oiiotool-attribs/run.py | 4 + 8 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 testsuite/oiiotool-attribs/ref/test-webp.icc diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 847010c9d4..f660bd8684 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -3073,6 +3073,10 @@ open standard for lossy-compressed images for use on the web. * - ImageSpec Attribute - Type - WebP header data or explanation + * - ``ICCProfile`` + - uint8[] + - The ICC color profile. A variety of other ``ICCProfile:*`` attributes + may also be present, extracted from the main profile. * - ``oiio:Movie`` - int - If nonzero, indicates that it's a multi-subimage file intended to diff --git a/src/webp.imageio/CMakeLists.txt b/src/webp.imageio/CMakeLists.txt index 1c09dc634d..dc33f06461 100644 --- a/src/webp.imageio/CMakeLists.txt +++ b/src/webp.imageio/CMakeLists.txt @@ -4,7 +4,7 @@ if (WebP_FOUND) add_oiio_plugin (webpinput.cpp webpoutput.cpp - LINK_LIBRARIES WebP::webp WebP::webpdemux + LINK_LIBRARIES WebP::webp WebP::webpdemux WebP::libwebpmux DEFINITIONS "USE_WEBP=1") else () message (STATUS "WebP plugin will not be built") diff --git a/src/webp.imageio/webpinput.cpp b/src/webp.imageio/webpinput.cpp index 408310b88a..09cc08c4cd 100644 --- a/src/webp.imageio/webpinput.cpp +++ b/src/webp.imageio/webpinput.cpp @@ -195,11 +195,20 @@ WebpInput::open(const std::string& name, ImageSpec& spec, } if (m_demux_flags & ICCP_FLAG && WebPDemuxGetChunk(m_demux, "ICCP", 1, &chunk_iter)) { - // FIXME: This is where we would extract an ICC profile. Come back - // to this when I have found an example webp containing an ICC - // profile that I can use as a test case, otherwise I'm just - // guessing. + cspan icc_span(chunk_iter.chunk.bytes, chunk_iter.chunk.size); + m_spec.attribute("ICCProfile", + TypeDesc(TypeDesc::UINT8, icc_span.size_bytes()), + icc_span.data()); + + std::string errormsg; + const bool ok = decode_icc_profile(icc_span, m_spec, errormsg); WebPDemuxReleaseChunkIterator(&chunk_iter); + + if (!ok && OIIO::get_int_attribute("imageinput:strict")) { + errorfmt("Possible corrupt file, could not decode ICC profile: {}\n", + errormsg); + return false; + } } // Make space for the decoded image diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 281f95f004..3e65ed30e8 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -44,6 +45,8 @@ class WebpOutput final : public ImageOutput { m_scanline_size = 0; ioproxy_clear(); } + + bool write_complete_data(); }; @@ -119,11 +122,9 @@ WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) // Lossless encoding (0=lossy(default), 1=lossless). m_webp_config.lossless = int(is_lossless); - m_webp_picture.use_argb = m_webp_config.lossless; - m_webp_picture.width = m_spec.width; - m_webp_picture.height = m_spec.height; - m_webp_picture.writer = WebpImageWriter; - m_webp_picture.custom_ptr = (void*)ioproxy(); + m_webp_picture.use_argb = m_webp_config.lossless; + m_webp_picture.width = m_spec.width; + m_webp_picture.height = m_spec.height; // forcing UINT8 format m_spec.set_format(TypeDesc::UINT8); @@ -137,6 +138,94 @@ WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) } +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); + } + + // If we have ICC data, encode to memory first. This is required in order + // to use the WebPMux assembly API below. + WebPMemoryWriter wrt; + if (has_icc_data) { + WebPMemoryWriterInit(&wrt); + m_webp_picture.writer = WebPMemoryWrite; + m_webp_picture.custom_ptr = &wrt; + } else { + m_webp_picture.writer = WebpImageWriter; + m_webp_picture.custom_ptr = (void*)ioproxy(); + } + + if (m_spec.nchannels == 4) { + if (m_convert_alpha) { + // WebP requires unassociated alpha, and it's sRGB. + // Handle this all by wrapping an IB around it. + ImageSpec specwrap(m_spec.width, m_spec.height, 4, TypeUInt8); + ImageBuf bufwrap(specwrap, cspan(m_uncompressed_image)); + ROI rgbroi(0, m_spec.width, 0, m_spec.height, 0, 1, 0, 3); + ImageBufAlgo::pow(bufwrap, bufwrap, 2.2f, rgbroi); + ImageBufAlgo::unpremult(bufwrap, bufwrap); + ImageBufAlgo::pow(bufwrap, bufwrap, 1.0f / 2.2f, rgbroi); + } + + WebPPictureImportRGBA(&m_webp_picture, m_uncompressed_image.data(), + m_scanline_size); + } else { + WebPPictureImportRGB(&m_webp_picture, m_uncompressed_image.data(), + m_scanline_size); + } + + if (!WebPEncode(&m_webp_config, &m_webp_picture)) { + errorfmt("Failed to encode {} as WebP image", m_filename); + if (has_icc_data) { + WebPMemoryWriterClear(&wrt); + } + close(); + return false; + } + + // If there's no ICC data to write, we are done at this point. + bool ok = true; + + // Otherwise, assemble the final WebP package and write it out. + if (has_icc_data) { + WebPMux* mux = WebPMuxNew(); + + WebPData image_data = { wrt.mem, wrt.size }; + WebPMuxSetImage(mux, &image_data, false); + + WebPData icc_chunk = { icc_data, size_t(icc_data_length) }; + WebPMuxSetChunk(mux, "ICCP", &icc_chunk, false); + + WebPData assembly; + if (WebPMuxAssemble(mux, &assembly) != WEBP_MUX_OK) { + errorfmt("Failed to assemble {} as WebP image", m_filename); + WebPMuxDelete(mux); + WebPMemoryWriterClear(&wrt); + return false; + } + + ok = ioproxy()->write(assembly.bytes, assembly.size) == assembly.size; + + WebPDataClear(&assembly); + WebPMuxDelete(mux); + WebPMemoryWriterClear(&wrt); + } + + return ok; +} + + + bool WebpOutput::write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) @@ -150,32 +239,9 @@ WebpOutput::write_scanline(int y, int z, TypeDesc format, const void* data, data = to_native_scanline(format, data, xstride, scratch, m_dither, y, z); memcpy(&m_uncompressed_image[y * m_scanline_size], data, m_scanline_size); + /* If this was the final scanline, we are done. */ if (y == m_spec.height - 1) { - if (m_spec.nchannels == 4) { - if (m_convert_alpha) { - // WebP requires unassociated alpha, and it's sRGB. - // Handle this all by wrapping an IB around it. - ImageSpec specwrap(m_spec.width, m_spec.height, 4, TypeUInt8); - ImageBuf bufwrap(specwrap, - cspan(m_uncompressed_image)); - ROI rgbroi(0, m_spec.width, 0, m_spec.height, 0, 1, 0, 3); - ImageBufAlgo::pow(bufwrap, bufwrap, 2.2f, rgbroi); - ImageBufAlgo::unpremult(bufwrap, bufwrap); - ImageBufAlgo::pow(bufwrap, bufwrap, 1.0f / 2.2f, rgbroi); - } - - WebPPictureImportRGBA(&m_webp_picture, m_uncompressed_image.data(), - m_scanline_size); - } else { - WebPPictureImportRGB(&m_webp_picture, m_uncompressed_image.data(), - m_scanline_size); - } - - if (!WebPEncode(&m_webp_config, &m_webp_picture)) { - errorfmt("Failed to encode {} as WebP image", m_filename); - close(); - return false; - } + return write_complete_data(); } return true; } diff --git a/testsuite/oiiotool-attribs/ref/out-jpeg9d.txt b/testsuite/oiiotool-attribs/ref/out-jpeg9d.txt index 44c9f84775..04737c6557 100644 --- a/testsuite/oiiotool-attribs/ref/out-jpeg9d.txt +++ b/testsuite/oiiotool-attribs/ref/out-jpeg9d.txt @@ -157,3 +157,25 @@ tahoe-icc.jpg : 128 x 96, 3 channel, uint8 jpeg ICCProfile:rendering_intent: "Perceptual" jpeg:subsampling: "4:2:0" oiio:ColorSpace: "srgb_rec709_scene" +Reading tahoe-icc.webp +tahoe-icc.webp : 128 x 96, 3 channel, uint8 webp + SHA-1: 407D97F3D62F90C7F9A78AF85989ED3C06C59523 + channel list: R, G, B + ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1094992453 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "Copyright 1999 Adobe Systems Incorporated" + ICCProfile:creation_date: "1999:06:03 00:00:00" + ICCProfile:creator_signature: "41444245" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "6e6f6e65" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "Adobe RGB (1998)" + ICCProfile:profile_size: 560 + ICCProfile:profile_version: "2.1.0" + ICCProfile:rendering_intent: "Perceptual" + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/oiiotool-attribs/ref/out.txt b/testsuite/oiiotool-attribs/ref/out.txt index 482ae7a842..b51b530a60 100644 --- a/testsuite/oiiotool-attribs/ref/out.txt +++ b/testsuite/oiiotool-attribs/ref/out.txt @@ -157,3 +157,25 @@ tahoe-icc.jpg : 128 x 96, 3 channel, uint8 jpeg ICCProfile:rendering_intent: "Perceptual" jpeg:subsampling: "4:2:0" oiio:ColorSpace: "srgb_rec709_scene" +Reading tahoe-icc.webp +tahoe-icc.webp : 128 x 96, 3 channel, uint8 webp + SHA-1: 407D97F3D62F90C7F9A78AF85989ED3C06C59523 + channel list: R, G, B + ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1094992453 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "Copyright 1999 Adobe Systems Incorporated" + ICCProfile:creation_date: "1999:06:03 00:00:00" + ICCProfile:creator_signature: "41444245" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "6e6f6e65" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "Adobe RGB (1998)" + ICCProfile:profile_size: 560 + ICCProfile:profile_version: "2.1.0" + ICCProfile:rendering_intent: "Perceptual" + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/oiiotool-attribs/ref/test-webp.icc b/testsuite/oiiotool-attribs/ref/test-webp.icc new file mode 100644 index 0000000000000000000000000000000000000000..fe2cab55e60201fc39020cceb18e1cd340e45d9c GIT binary patch literal 560 zcmZQzU@~xYadKr6U|`72D=7+ccT$Lmj8b4f&%nmO%m4<7$;AbZ0RcWBPF{XqDnt~S z{C16j5yZc&3o;8?h6pxSazRlEP~9IOHcCk?PG(?WGyt-*%S#G?;*4{EY>}jFFna@t zT@(`J3=}^CWb>s%*jGU8BnbNnh+PEq1W?Tvkot5mn~4L&PJ*yyKRDfvmM3c;1dC8@c^3Z8k%`9%f!MTsS;DL}Ol_knaV2tpKsLQDgw z(Lxg}N<{(`4-n%%2ZF Date: Sun, 14 Sep 2025 01:50:42 +0200 Subject: [PATCH 002/508] feat(ffmpeg): Read CICP metadata, and add test (#4882) The actual CICP reading code is trivial. Also recognize mkv (Matroska) extension for the test. Add test using Matroska + VP9, to maximize the chance of an ffmpeg build supporting them. They are royalty free, built into ffmpeg and relatively old. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/testing.cmake | 3 + src/doc/builtinplugins.rst | 3 + src/ffmpeg.imageio/ffmpeginput.cpp | 12 +++- testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt | 87 ++++++++++++++++++++++++ testsuite/ffmpeg/ref/out-ffmpeg8.0.txt | 87 ++++++++++++++++++++++++ testsuite/ffmpeg/ref/vp9_display_p3.mkv | Bin 0 -> 5455 bytes testsuite/ffmpeg/run.py | 10 +++ 7 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt create mode 100644 testsuite/ffmpeg/ref/out-ffmpeg8.0.txt create mode 100644 testsuite/ffmpeg/ref/vp9_display_p3.mkv create mode 100755 testsuite/ffmpeg/run.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 0bbc77c716..113b993242 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -250,6 +250,9 @@ macro (oiio_add_all_tests) ENABLEVAR ENABLE_FITS IMAGEDIR fits-images URL http://www.cv.nrao.edu/fits/data/tests/) + oiio_add_tests (ffmpeg + ENABLEVAR ENABLE_FFMPEG + FOUNDVAR FFmpeg_FOUND) oiio_add_tests (gif FOUNDVAR GIF_FOUND ENABLEVAR ENABLE_GIF IMAGEDIR oiio-images/gif URL "Recent checkout of OpenImageIO-images") diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index f660bd8684..81d72c911a 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1433,6 +1433,9 @@ Some special attributes are used for movie files: * - ``ffmpeg:TimeCode`` - string - Start time timecode + * - ``CICP`` + - int[4] + - Coding-independent code points to describe the color profile. diff --git a/src/ffmpeg.imageio/ffmpeginput.cpp b/src/ffmpeg.imageio/ffmpeginput.cpp index 7cfaf601b2..ae1be13b7d 100644 --- a/src/ffmpeg.imageio/ffmpeginput.cpp +++ b/src/ffmpeg.imageio/ffmpeginput.cpp @@ -180,9 +180,9 @@ ffmpeg_input_imageio_create() // QuickTime / MOV // raw MPEG-4 video // MPEG-1 Systems / MPEG program stream -OIIO_EXPORT const char* ffmpeg_input_extensions[] = { - "avi", "mov", "qt", "mp4", "m4a", "3gp", "3g2", "mj2", "m4v", "mpg", nullptr -}; +OIIO_EXPORT const char* ffmpeg_input_extensions[] + = { "avi", "mov", "qt", "mp4", "m4a", "3gp", + "3g2", "mj2", "m4v", "mpg", "mkv", nullptr }; OIIO_PLUGIN_EXPORTS_END @@ -531,6 +531,12 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) m_spec.attribute("oiio:BitsPerSample", m_codec_context->bits_per_raw_sample); m_spec.attribute("ffmpeg:codec_name", m_codec_context->codec->long_name); + /* The ffmpeg enums are documented to match CICP values, except the color range. */ + const int cicp[4] + = { m_codec_context->color_primaries, m_codec_context->color_trc, + m_codec_context->colorspace, + m_codec_context->color_range == AVCOL_RANGE_MPEG ? 0 : 1 }; + m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); m_nsubimages = m_frames; spec = m_spec; m_filename = name; diff --git a/testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt b/testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt new file mode 100644 index 0000000000..82b00b1a10 --- /dev/null +++ b/testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt @@ -0,0 +1,87 @@ +Reading ref/vp9_display_p3.mkv +ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie + 7 subimages: 192x108 [u8,u8,u8] + subimage 0: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: E65FAFBDA14543571188F8A84F75E1567E13148C + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 1: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 8D54A542C4E6A47A4F8829993001786BA62288DF + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 2: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 8D54A542C4E6A47A4F8829993001786BA62288DF + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 3: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 8D54A542C4E6A47A4F8829993001786BA62288DF + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 4: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: D9B0B8782048AEE24C4DF767E2B0969EBAB9D36B + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 5: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: C6B5666BB0A937A59762B8FE846BF50FED40889E + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 6: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 63516D95A1BB56E9E9483F9BCFC4305851A8BC54 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 diff --git a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt new file mode 100644 index 0000000000..988ef28210 --- /dev/null +++ b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt @@ -0,0 +1,87 @@ +Reading ref/vp9_display_p3.mkv +ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie + 7 subimages: 192x108 [u8,u8,u8] + subimage 0: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: C882152FF48CB068931C2710175659A9D0493C08 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 1: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 1E2EB62F7BDD2DAC5C6FBDE582EE19978DC7C290 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 2: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 1E2EB62F7BDD2DAC5C6FBDE582EE19978DC7C290 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 3: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 1E2EB62F7BDD2DAC5C6FBDE582EE19978DC7C290 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 4: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 35565DB895AB1CA413192B7F25A1890718CFC0F5 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 5: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 338C9A82B74CE3B291DAD6564AE7C060AE6D9267 + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 + subimage 6: 192 x 108, 3 channel, uint8 FFmpeg movie + SHA-1: 90BC91EAA745A9C897EEDEC0D34BAAF1D40B1C7C + channel list: R, G, B + CICP: 12, 1, 1, 1 + DATE: "2025/09/13 21:58:33" + ENCODER: "Lavf61.7.100" + FramesPerSecond: 24/1 (24) + SCENE: "Scene" + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 0 + oiio:Movie: 1 + oiio:subimages: 7 diff --git a/testsuite/ffmpeg/ref/vp9_display_p3.mkv b/testsuite/ffmpeg/ref/vp9_display_p3.mkv new file mode 100644 index 0000000000000000000000000000000000000000..b98a42fa3b19987bbb3bde6eab99ccaf27c72e38 GIT binary patch literal 5455 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 literal 0 HcmV?d00001 diff --git a/testsuite/ffmpeg/run.py b/testsuite/ffmpeg/run.py new file mode 100755 index 0000000000..06b8436b80 --- /dev/null +++ b/testsuite/ffmpeg/run.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +imagedir = "ref/" +files = [ "vp9_display_p3.mkv" ] +for f in files: + command = command + info_command (os.path.join(imagedir, f)) From 09dab6f613a4dcd09379e2cdc164cc7eaab3516e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 13 Sep 2025 16:52:03 -0700 Subject: [PATCH 003/508] ci: fix analysis workflow configuration (#4881) Something must have changed with the containers, it was failing on the build step, before ever getting to the actual sonar analysis. Switch to the same container and OCIO version that we use for the regular 2024 era container CI. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/analysis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 92bd9a6916..bdaa0f8c81 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -87,13 +87,14 @@ jobs: fail-fast: false matrix: include: - - desc: sonar gcc11/C++17 py310 exr3.2 ocio2.3 + - desc: sonar gcc11/C++17 py311 exr3.2 ocio2.3 nametag: static-analysis-sonar os: ubuntu-latest - container: aswf/ci-osl:2024-clang17 + container: aswf/ci-oiio:2024.2 cxx_std: 17 python_ver: "3.11" simd: "avx2,f16c" + opencolorio_ver: v2.3.2 fmt_ver: 10.1.1 pybind11_ver: v2.12.0 coverage: 1 From 27bd26b73b4d9cfae026d9f6d2fa78e8ce05278d Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 13 Sep 2025 19:02:33 -0700 Subject: [PATCH 004/508] api: Versioned namespace to preserve ABI compatibility between minor releases (#4869) This patch implements the new scheme that we hope will preserve ABI compatibility across minor (yearly) releases. Brief summary: * We will strive to have annual minor releases (e.g., 3.1.x -> 3.2.x) be ABI/link backwards-compatible, as strongly as monthly patch releases (3.1.5 -> 3.1.6) always have been. Every-several-years major releases (3.x -> 4.x) are still a potential full compatibility break. * Consumers of OpenImageIO don't need to change their code, and can just continue to refer to `OIIO::Foo` or `OIIO::Bar()` as always. * Internally, all items in the public OIIO APIs will be in a versioned inner namespace, and will continue to live in the versioned namespace where they were first introduced, forever. (Well, until the next first-digit-changing major release, at which point we will clean up the old cruft and version everything up.) If anything needs to change in an ABI-breaking way, it will be *duplicated* in the newer namespaces, so the old version continues to be available in the old namespace. * The new header nsversions.h contains a long and detailed comment explaining how all the new declarations work. --- Detailed explanation: "Major" or "first digit" releases, which only happen once every several years for us, are full ABI+API breaks. Previously, "minor" or "second digit" releases, which are annual, have fully incompatible ABIs, with separate namespaces to enforce it. Here, we are striving to allow our annual/minor releases to no longer break ABI, to make upgrading easier for downstream users. The basics are that once a symbol is introduced, it will forever live in the namespace of that release. Subsequent release years will have their own namespaces, but will either alias or duplicate the original symbols. This is enabled by our recent (in the lead-up to 3.1) introduction of a split 2-part namespacing scheme. So 3.2 is the first release that has the potential to perserve compatibility with 3.1 in this way. In this PR, I have done this for the whole codebase. It works! -- as measured by being able to have our automated ABI checker show 100% back compatibility with 3.1. So currently, even though this is the nascent 3.2 tree, everything in the public APIs live in the 3.1 ABI namespace. And so they will remain, except for changes that cannot be done without breaking ABI back-compatibility, which will then end up with multiple versions in different versioned namespaces. Let's look this over and decide if we like it. It does involve our developers to do some hoop jumping and take extra care as we develop, to keep things associated with the right namespaces. Users of OpenImageIO shouldn't need to change anything on their end, nor to be aware of this at all. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 2 +- .github/workflows/ci.yml | 2 +- src/build-scripts/ci-abicheck.bash | 2 +- src/doc/Doxyfile | 2 + src/include/OpenImageIO/argparse.h | 17 +- src/include/OpenImageIO/atomic.h | 18 +- src/include/OpenImageIO/attrdelegate.h | 11 +- src/include/OpenImageIO/benchmark.h | 20 +- src/include/OpenImageIO/bit.h | 17 +- src/include/OpenImageIO/color.h | 31 ++- src/include/OpenImageIO/deepdata.h | 9 +- src/include/OpenImageIO/detail/farmhash.h | 19 +- src/include/OpenImageIO/detail/fmt.h | 17 +- .../OpenImageIO/detail/pugixml/pugixml.cpp | 32 +-- .../OpenImageIO/detail/pugixml/pugixml.hpp | 10 +- src/include/OpenImageIO/errorhandler.h | 4 +- src/include/OpenImageIO/filesystem.h | 29 +- src/include/OpenImageIO/filter.h | 4 +- src/include/OpenImageIO/fmath.h | 20 ++ src/include/OpenImageIO/fstream_mingw.h | 12 +- src/include/OpenImageIO/function_view.h | 8 +- src/include/OpenImageIO/hash.h | 16 +- src/include/OpenImageIO/image_span.h | 35 +-- src/include/OpenImageIO/image_view.h | 4 +- src/include/OpenImageIO/imagebuf.h | 18 +- src/include/OpenImageIO/imagebufalgo.h | 19 +- src/include/OpenImageIO/imagebufalgo_opencv.h | 4 +- src/include/OpenImageIO/imagebufalgo_util.h | 11 +- src/include/OpenImageIO/imagecache.h | 17 +- src/include/OpenImageIO/imageio.h | 93 +++++-- src/include/OpenImageIO/memory.h | 15 +- src/include/OpenImageIO/nsversions.h | 252 ++++++++++++++++++ src/include/OpenImageIO/oiioversion.h.in | 44 +-- src/include/OpenImageIO/optparser.h | 11 +- src/include/OpenImageIO/parallel.h | 53 +++- src/include/OpenImageIO/paramlist.h | 21 +- src/include/OpenImageIO/platform.h | 42 ++- src/include/OpenImageIO/plugin.h | 13 +- src/include/OpenImageIO/refcnt.h | 14 +- src/include/OpenImageIO/simd.h | 30 +-- src/include/OpenImageIO/span.h | 121 +++++---- src/include/OpenImageIO/strided_ptr.h | 9 +- src/include/OpenImageIO/string_view.h | 15 +- src/include/OpenImageIO/strongparam.h | 9 +- src/include/OpenImageIO/strutil.h | 17 +- src/include/OpenImageIO/sysutil.h | 4 +- src/include/OpenImageIO/texture.h | 34 ++- src/include/OpenImageIO/thread.h | 169 +++--------- src/include/OpenImageIO/tiffutils.h | 23 ++ src/include/OpenImageIO/timer.h | 4 +- src/include/OpenImageIO/type_traits.h | 12 +- src/include/OpenImageIO/typedesc.h | 67 ++++- .../OpenImageIO/unordered_map_concurrent.h | 10 +- src/include/OpenImageIO/ustring.h | 23 +- src/include/OpenImageIO/vecparam.h | 20 +- src/include/imageio_pvt.h | 11 +- src/libOpenImageIO/color_ocio.cpp | 20 +- src/libOpenImageIO/deepdata.cpp | 7 +- src/libOpenImageIO/exif.cpp | 26 +- src/libOpenImageIO/formatspec.cpp | 103 +++++-- src/libOpenImageIO/icc.cpp | 5 +- src/libOpenImageIO/imagebuf.cpp | 90 ++----- src/libOpenImageIO/imagebufalgo.cpp | 20 +- src/libOpenImageIO/imagebufalgo_addsub.cpp | 8 +- src/libOpenImageIO/imagebufalgo_channels.cpp | 8 +- src/libOpenImageIO/imagebufalgo_compare.cpp | 24 +- src/libOpenImageIO/imagebufalgo_copy.cpp | 14 +- src/libOpenImageIO/imagebufalgo_deep.cpp | 12 +- src/libOpenImageIO/imagebufalgo_demosaic.cpp | 6 +- .../imagebufalgo_demosaic_prv.h | 4 +- src/libOpenImageIO/imagebufalgo_draw.cpp | 47 ++-- src/libOpenImageIO/imagebufalgo_mad.cpp | 6 +- .../imagebufalgo_minmaxchan.cpp | 8 +- src/libOpenImageIO/imagebufalgo_muldiv.cpp | 10 +- src/libOpenImageIO/imagebufalgo_orient.cpp | 16 +- src/libOpenImageIO/imagebufalgo_pixelmath.cpp | 40 +-- src/libOpenImageIO/imagebufalgo_test.cpp | 34 +-- src/libOpenImageIO/imagebufalgo_xform.cpp | 14 +- src/libOpenImageIO/imagebufalgo_yee.cpp | 4 +- src/libOpenImageIO/imageinput.cpp | 60 ++--- src/libOpenImageIO/imageio.cpp | 37 ++- src/libOpenImageIO/imageioplugin.cpp | 36 ++- src/libOpenImageIO/imageoutput.cpp | 33 ++- src/libOpenImageIO/iptc.cpp | 5 +- src/libOpenImageIO/maketexture.cpp | 15 +- src/libOpenImageIO/xmp.cpp | 5 +- src/libtexture/environment.cpp | 4 +- src/libtexture/imagecache.cpp | 19 +- src/libtexture/imagecache_memory_pvt.h | 4 +- src/libtexture/imagecache_pvt.h | 22 +- src/libtexture/texoptions.cpp | 4 +- src/libtexture/texture3d.cpp | 4 +- src/libtexture/texture_pvt.h | 10 +- src/libtexture/texturesys.cpp | 8 +- src/libutil/SHA1.cpp | 4 +- src/libutil/SHA1.h | 4 +- src/libutil/argparse.cpp | 8 +- src/libutil/benchmark.cpp | 21 +- src/libutil/errorhandler.cpp | 4 +- src/libutil/farmhash.cpp | 29 +- src/libutil/filesystem.cpp | 26 +- src/libutil/filter.cpp | 4 +- src/libutil/hashes.cpp | 4 +- src/libutil/paramlist.cpp | 43 +-- src/libutil/plugin.cpp | 4 +- src/libutil/strutil.cpp | 55 ++-- src/libutil/strutil_test.cpp | 2 +- src/libutil/sysutil.cpp | 24 +- src/libutil/thread.cpp | 93 +++++-- src/libutil/timer.cpp | 4 +- src/libutil/typedesc.cpp | 108 ++++---- src/libutil/ustring.cpp | 4 +- src/libutil/xxhash.cpp | 4 +- src/oiiotool/oiiotool.cpp | 4 +- 114 files changed, 1779 insertions(+), 1008 deletions(-) create mode 100644 src/include/OpenImageIO/nsversions.h diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index b75e84c32c..cbc2133223 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -203,7 +203,7 @@ jobs: time make sphinx - name: Upload testsuite debugging artifacts uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - if: ${{ failure() || inputs.build_docs == '1' || inputs.benchmark == '1' }} + if: ${{ failure() || inputs.build_docs == '1' || inputs.benchmark == '1' || inputs.abi_check != '' }} with: name: oiio-${{github.job}}-${{inputs.nametag}} path: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 197dd8480b..bdeebf4cb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -388,7 +388,7 @@ jobs: simd: "avx2,f16c" skip_tests: 1 # abi_check: v3.1.3.0 - abi_check: ae5dca7865c9956b43cc04dee00a65ad893e251e + abi_check: 9bfcce725a3806a3f70c7e838d9d98d6d95c917a setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 diff --git a/src/build-scripts/ci-abicheck.bash b/src/build-scripts/ci-abicheck.bash index 88c9c56ad0..5c83f5259e 100755 --- a/src/build-scripts/ci-abicheck.bash +++ b/src/build-scripts/ci-abicheck.bash @@ -41,6 +41,7 @@ for lib in $LIBS ; do fgrep "Binary compatibility:" ${lib}-abi-results.txt echo -e "\x1b[33;0m" done +cp -r compat_reports ${BUILDDIR_NEW}/compat_reports || true # # If the "Binary compatibility" summary results say anything other than 100%, @@ -48,7 +49,6 @@ done # for lib in $LIBS ; do if [[ `fgrep "Binary compatibility:" ${lib}-abi-results.txt | grep -v 100\%` != "" ]] ; then - cp -r compat_reports ${BUILDDIR_NEW}/compat_reports exit 1 fi done diff --git a/src/doc/Doxyfile b/src/doc/Doxyfile index 82d533b7a4..64358b8ed3 100644 --- a/src/doc/Doxyfile +++ b/src/doc/Doxyfile @@ -2189,6 +2189,8 @@ PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS \ OIIO_NAMESPACE_3_0_END="}" \ OIIO_NAMESPACE_3_1_BEGIN="namespace OIIO {" \ OIIO_NAMESPACE_3_1_END="}" \ + OIIO_NAMESPACE_3_2_BEGIN="namespace OIIO {" \ + OIIO_NAMESPACE_3_2_END="}" \ OIIO_NS_BEGIN="namespace OIIO {" \ OIIO_NS_END="}" \ OIIO_CONSTEXPR17=constexpr \ diff --git a/src/include/OpenImageIO/argparse.h b/src/include/OpenImageIO/argparse.h index 5d323830e1..4b598fdf73 100644 --- a/src/include/OpenImageIO/argparse.h +++ b/src/include/OpenImageIO/argparse.h @@ -20,8 +20,12 @@ #include #include +// Define symbols that let client applications determine if newly added +// features are supported. +#define OIIO_ARGPARSE_SUPPORTS_BRIEFUSAGE 1 +#define OIIO_ARGPARSE_SUPPORTS_HUMAN_PARAMNAME 1 -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN ///////////////////////////////////////////////////////////////////////////// @@ -173,7 +177,7 @@ OIIO_NAMESPACE_BEGIN class OIIO_UTIL_API ArgParse { public: - class Arg; // Forward declarion of Arg + class Arg; // Forward declaration of Arg // ------------------------------------------------------------------ /// @defgroup Setting up an ArgParse @@ -782,11 +786,4 @@ class OIIO_UTIL_API ArgParse { void usage() const { print_help(); } }; - - -// Define symbols that let client applications determine if newly added -// features are supported. -#define OIIO_ARGPARSE_SUPPORTS_BRIEFUSAGE 1 -#define OIIO_ARGPARSE_SUPPORTS_HUMAN_PARAMNAME 1 - -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/atomic.h b/src/include/OpenImageIO/atomic.h index 1a91c2a3f0..2113d7d3d8 100644 --- a/src/include/OpenImageIO/atomic.h +++ b/src/include/OpenImageIO/atomic.h @@ -19,11 +19,11 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using std::atomic; -typedef atomic atomic_int; -typedef atomic atomic_ll; +using atomic_int = atomic; +using atomic_ll = atomic; @@ -79,5 +79,17 @@ atomic_fetch_add(atomic& a, double f) } while (true); } +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using std::atomic; +using atomic_int = atomic; +using atomic_ll = atomic; +using v3_1::atomic_fetch_add; +using v3_1::atomic_max; +using v3_1::atomic_min; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/attrdelegate.h b/src/include/OpenImageIO/attrdelegate.h index f0fc28b0d0..2fadd00da8 100644 --- a/src/include/OpenImageIO/attrdelegate.h +++ b/src/include/OpenImageIO/attrdelegate.h @@ -14,7 +14,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { @@ -229,4 +229,13 @@ template class AttrDelegate { }; +OIIO_NAMESPACE_3_1_END + + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::AttrDelegate; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/benchmark.h b/src/include/OpenImageIO/benchmark.h index 2c8b23a6ef..bfcb8b8235 100644 --- a/src/include/OpenImageIO/benchmark.h +++ b/src/include/OpenImageIO/benchmark.h @@ -24,7 +24,7 @@ #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// DoNotOptimize(val) is a helper function for timing benchmarks that fools /// the compiler into thinking the the location 'val' is used and will not @@ -470,6 +470,24 @@ OIIO_FORCEINLINE void clobber_all_memory() { } #endif +OIIO_UTIL_API std::ostream& operator<<(std::ostream& out, + const Benchmarker& bench); + +OIIO_NAMESPACE_3_1_END +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::Benchmarker; +using v3_1::clobber; +using v3_1::clobber_all_memory; +using v3_1::DoNotOptimize; +using v3_1::time_trial; +using v3_1::timed_thread_wedge; +using v3_1::operator<<; +namespace pvt { +using v3_1::pvt::use_char_ptr; +} +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/bit.h b/src/include/OpenImageIO/bit.h index 1b8265b591..08526f3b85 100644 --- a/src/include/OpenImageIO/bit.h +++ b/src/include/OpenImageIO/bit.h @@ -10,7 +10,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Standards-compliant bit cast of two equally sized types. This is used @@ -273,4 +273,19 @@ rotl64(uint64_t x, int k) +OIIO_NAMESPACE_3_1_END + + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::bitcast; +using v3_1::bitcast_to_float; +using v3_1::bitcast_to_int; +using v3_1::byteswap; +using v3_1::rotl; +using v3_1::rotl32; +using v3_1::rotl64; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/color.h b/src/include/OpenImageIO/color.h index e284e6c337..46676efe76 100644 --- a/src/include/OpenImageIO/color.h +++ b/src/include/OpenImageIO/color.h @@ -12,6 +12,9 @@ #include #include +// Preprocessor symbol to allow conditional compilation depending on +// whether the ColorProcessor class is exposed (it was not prior to OIIO 1.9). +#define OIIO_HAS_COLORPROCESSOR 1 // // Some general color management information materials to have handy: @@ -23,7 +26,12 @@ // -OIIO_NAMESPACE_BEGIN +// Preprocessor symbol to allow conditional compilation depending on +// whether the ColorConfig returns ColorProcessor shared pointers or raw. +#define OIIO_COLORCONFIG_USES_SHARED_PTR 1 + + +OIIO_NAMESPACE_3_1_BEGIN /// The ColorProcessor encapsulates a baked color transformation, suitable for /// application to raw pixels, or ImageBuf(s). These are generated using @@ -50,17 +58,9 @@ class OIIO_API ColorProcessor { } }; -// Preprocessor symbol to allow conditional compilation depending on -// whether the ColorProcessor class is exposed (it was not prior to OIIO 1.9). -#define OIIO_HAS_COLORPROCESSOR 1 - - -typedef std::shared_ptr ColorProcessorHandle; -// Preprocessor symbol to allow conditional compilation depending on -// whether the ColorConfig returns ColorProcessor shared pointers or raw. -#define OIIO_COLORCONFIG_USES_SHARED_PTR 1 +using ColorProcessorHandle = std::shared_ptr; @@ -445,7 +445,18 @@ class OIIO_API ColorConfig { Impl* getImpl() const { return m_impl.get(); } }; +OIIO_NAMESPACE_3_1_END + +// Compatibility +#ifndef OIIO_DOXYGEN +OIIO_NAMESPACE_BEGIN +using v3_1::ColorProcessorHandle; +OIIO_NAMESPACE_END +#endif + + +OIIO_NAMESPACE_BEGIN /// Utility -- convert sRGB value to linear transfer function, without /// any change in color primaries. diff --git a/src/include/OpenImageIO/deepdata.h b/src/include/OpenImageIO/deepdata.h index d814914872..deec7bebd5 100644 --- a/src/include/OpenImageIO/deepdata.h +++ b/src/include/OpenImageIO/deepdata.h @@ -10,12 +10,7 @@ #include #include -OIIO_NAMESPACE_BEGIN - - -struct TypeDesc; -class ImageSpec; - +OIIO_NAMESPACE_3_1_BEGIN /// A `DeepData` holds the contents of an image of ``deep'' pixels (multiple @@ -209,4 +204,4 @@ class OIIO_API DeepData { }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/detail/farmhash.h b/src/include/OpenImageIO/detail/farmhash.h index c5c00652d1..99e9903b7f 100644 --- a/src/include/OpenImageIO/detail/farmhash.h +++ b/src/include/OpenImageIO/detail/farmhash.h @@ -216,7 +216,7 @@ STATIC_INLINE void simpleSwap(T &a, T &b) { #define Hash128to64 OIIO::farmhash::Hash128to64 // namespace NAMESPACE_FOR_HASH_FUNCTIONS { -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace farmhash { namespace inlined { @@ -379,7 +379,8 @@ STATIC_INLINE uint64_t Rotate64(uint64_t val, int shift) { // } // namespace NAMESPACE_FOR_HASH_FUNCTIONS } /*end namespace inlined */ -} /*end namespace farmhash*/ OIIO_NAMESPACE_END +} /*end namespace farmhash*/ +OIIO_NAMESPACE_3_1_END // FARMHASH PORTABILITY LAYER: debug mode or max speed? // One may use -DFARMHASH_DEBUG=1 or -DFARMHASH_DEBUG=0 to force the issue. @@ -506,7 +507,7 @@ STATIC_INLINE uint64_t Rotate64(uint64_t val, int shift) { // namespace NAMESPACE_FOR_HASH_FUNCTIONS { -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN //using namespace OIIO::farmhash; namespace farmhash { namespace inlined { @@ -2184,8 +2185,16 @@ STATIC_INLINE uint128_t Fingerprint128(const char* s, size_t len) { #undef Murk #undef Chunk -OIIO_NAMESPACE_END - #undef STATIC_INLINE +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +namespace farmhash { +using namespace OIIO::v3_1::farmhash; +} // namespace farmhash +#endif +OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/detail/fmt.h b/src/include/OpenImageIO/detail/fmt.h index 33fed3cb37..b68ea14e02 100644 --- a/src/include/OpenImageIO/detail/fmt.h +++ b/src/include/OpenImageIO/detail/fmt.h @@ -21,12 +21,12 @@ #if OIIO_VERSION_LESS(3, 1, 2) /* DEPRECATED -- remove at next ABI compatibility boundary */ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { OIIO_UTIL_API void log_fmt_error(const char* message); }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END #endif // Use the grisu fast floating point formatting for old fmt versions @@ -78,7 +78,7 @@ OIIO_PRAGMA_WARNING_POP #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { @@ -197,4 +197,15 @@ struct array_formatter : format_parser_with_separator { } // namespace pvt +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +namespace pvt { +using v3_1::pvt::array_formatter; +using v3_1::pvt::index_formatter; +} // namespace pvt +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/detail/pugixml/pugixml.cpp b/src/include/OpenImageIO/detail/pugixml/pugixml.cpp index 6b381f9cc3..d764c28f9c 100644 --- a/src/include/OpenImageIO/detail/pugixml/pugixml.cpp +++ b/src/include/OpenImageIO/detail/pugixml/pugixml.cpp @@ -150,17 +150,17 @@ using std::memset; // We put implementation details into an anonymous namespace in source mode, but have to keep it in non-anonymous namespace in header-only mode to prevent binary bloat. #ifdef PUGIXML_HEADER_ONLY -# define PUGI__NS_BEGIN OIIO_NAMESPACE_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } OIIO_NAMESPACE_END +# define PUGI__NS_BEGIN OIIO_NAMESPACE_3_1_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } OIIO_NAMESPACE_3_1_END # define PUGI__FN inline # define PUGI__FN_NO_INLINE inline #else # if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces -# define PUGI__NS_BEGIN OIIO_NAMESPACE_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } OIIO_NAMESPACE_END +# define PUGI__NS_BEGIN OIIO_NAMESPACE_3_1_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } OIIO_NAMESPACE_3_1_END # else -# define PUGI__NS_BEGIN OIIO_NAMESPACE_BEGIN namespace pugi { namespace impl { namespace { -# define PUGI__NS_END } } } OIIO_NAMESPACE_END +# define PUGI__NS_BEGIN OIIO_NAMESPACE_3_1_BEGIN namespace pugi { namespace impl { namespace { +# define PUGI__NS_END } } } OIIO_NAMESPACE_3_1_END # endif # define PUGI__FN # define PUGI__FN_NO_INLINE PUGI__NO_INLINE @@ -168,7 +168,7 @@ using std::memset; // uintptr_t #if (defined(_MSC_VER) && _MSC_VER < 1600) || (defined(__BORLANDC__) && __BORLANDC__ < 0x561) -OIIO_NAMESPACE_BEGIN namespace pugi +OIIO_NAMESPACE_3_1_BEGIN namespace pugi { # ifndef _UINTPTR_T_DEFINED typedef size_t uintptr_t; @@ -177,7 +177,7 @@ OIIO_NAMESPACE_BEGIN namespace pugi typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; -} OIIO_NAMESPACE_END +} OIIO_NAMESPACE_3_1_END #else # include #endif @@ -1048,7 +1048,7 @@ PUGI__NS_END #endif #ifdef PUGIXML_COMPACT -OIIO_NAMESPACE_BEGIN namespace pugi +OIIO_NAMESPACE_3_1_BEGIN namespace pugi { struct xml_attribute_struct { @@ -1091,9 +1091,9 @@ OIIO_NAMESPACE_BEGIN namespace pugi impl::compact_pointer first_attribute; }; -} OIIO_NAMESPACE_END +} OIIO_NAMESPACE_3_1_END #else -OIIO_NAMESPACE_BEGIN namespace pugi +OIIO_NAMESPACE_3_1_BEGIN namespace pugi { struct xml_attribute_struct { @@ -1132,7 +1132,7 @@ OIIO_NAMESPACE_BEGIN namespace pugi xml_attribute_struct* first_attribute; }; -} OIIO_NAMESPACE_END +} OIIO_NAMESPACE_3_1_END #endif PUGI__NS_BEGIN @@ -5071,7 +5071,7 @@ PUGI__NS_BEGIN }; PUGI__NS_END -OIIO_NAMESPACE_BEGIN namespace pugi +OIIO_NAMESPACE_3_1_BEGIN namespace pugi { PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) { @@ -7358,7 +7358,7 @@ OIIO_NAMESPACE_BEGIN namespace pugi { return impl::xml_memory::deallocate; } -} OIIO_NAMESPACE_END +} OIIO_NAMESPACE_3_1_END #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) namespace std @@ -12172,7 +12172,7 @@ PUGI__NS_BEGIN } PUGI__NS_END -OIIO_NAMESPACE_BEGIN namespace pugi +OIIO_NAMESPACE_3_1_BEGIN namespace pugi { #ifndef PUGIXML_NO_EXCEPTIONS PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) @@ -12953,7 +12953,7 @@ OIIO_NAMESPACE_BEGIN namespace pugi { return query.evaluate_node(*this); } -} OIIO_NAMESPACE_END +} OIIO_NAMESPACE_3_1_END #endif diff --git a/src/include/OpenImageIO/detail/pugixml/pugixml.hpp b/src/include/OpenImageIO/detail/pugixml/pugixml.hpp index a7f1680f36..b39fa832ea 100644 --- a/src/include/OpenImageIO/detail/pugixml/pugixml.hpp +++ b/src/include/OpenImageIO/detail/pugixml/pugixml.hpp @@ -134,13 +134,13 @@ #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pugi {} -OIIO_NAMESPACE_END -namespace pugi = OIIO::pugi; +OIIO_NAMESPACE_3_1_END +namespace pugi = OIIO::v3_1::pugi; -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pugi { // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE @@ -1457,7 +1457,7 @@ namespace pugi allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) namespace std diff --git a/src/include/OpenImageIO/errorhandler.h b/src/include/OpenImageIO/errorhandler.h index 007386903f..ac96204bbd 100644 --- a/src/include/OpenImageIO/errorhandler.h +++ b/src/include/OpenImageIO/errorhandler.h @@ -10,7 +10,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// ErrorHandler is a simple class that accepts error messages /// (classified as errors, severe errors, warnings, info, messages, or @@ -136,4 +136,4 @@ class OIIO_UTIL_API ErrorHandler { int m_verbosity; }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/filesystem.h b/src/include/OpenImageIO/filesystem.h index 98eaf617b9..1f4084d93d 100644 --- a/src/include/OpenImageIO/filesystem.h +++ b/src/include/OpenImageIO/filesystem.h @@ -44,20 +44,22 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN #if OIIO_FILESYSTEM_USE_STDIO_FILEBUF // MingW uses GCC to build, but does not support having a wchar_t* passed as argument // of ifstream::open or ofstream::open. To properly support UTF-8 encoding on MingW we must // use the __gnu_cxx::stdio_filebuf GNU extension that can be used with _wfsopen and returned // into a istream which share the same API as ifsteam. The same reasoning holds for ofstream. -typedef basic_ifstream ifstream; -typedef basic_ofstream ofstream; +using ifstream = basic_ifstream; +using ofstream = basic_ofstream; #else -typedef std::ifstream ifstream; -typedef std::ofstream ofstream; +using ifstream = std::ifstream; +using ofstream = std::ofstream; #endif + + /// @namespace Filesystem /// /// @brief Platform-independent utilities for manipulating file names, @@ -243,12 +245,12 @@ OIIO_UTIL_API std::string current_path (); /// Version of std::ifstream.open that can handle UTF-8 paths /// -OIIO_UTIL_API void open (OIIO::ifstream &stream, string_view path, +OIIO_UTIL_API void open (ifstream &stream, string_view path, std::ios_base::openmode mode = std::ios_base::in); /// Version of std::ofstream.open that can handle UTF-8 paths /// -OIIO_UTIL_API void open (OIIO::ofstream &stream, string_view path, +OIIO_UTIL_API void open (ofstream &stream, string_view path, std::ios_base::openmode mode = std::ios_base::out); /// Version of C open() that can handle UTF-8 paths, returning an integer @@ -286,7 +288,7 @@ OIIO_UTIL_API bool write_text_file (string_view filename, string_view str); template bool write_binary_file (string_view filename, cspan data) { - OIIO::ofstream out; + ofstream out; Filesystem::open(out, filename, std::ios::out | std::ios::binary); out.write((const char*)data.data(), data.size() * sizeof(T)); return out.good(); @@ -584,4 +586,15 @@ class OIIO_UTIL_API IOMemReader : public IOProxy { }; // namespace Filesystem +OIIO_NAMESPACE_3_1_END + +// Compatibility +#ifndef OIIO_DOXYGEN +OIIO_NAMESPACE_BEGIN +using ifstream = v3_1::ifstream; +using ofstream = v3_1::ofstream; +namespace Filesystem { +using namespace OIIO::v3_1::Filesystem; +}; // namespace Filesystem OIIO_NAMESPACE_END +#endif diff --git a/src/include/OpenImageIO/filter.h b/src/include/OpenImageIO/filter.h index 93d1a9c099..5bc00f0e1f 100644 --- a/src/include/OpenImageIO/filter.h +++ b/src/include/OpenImageIO/filter.h @@ -12,7 +12,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Quick structure that describes a filter. /// @@ -156,4 +156,4 @@ class OIIO_UTIL_API Filter2D { }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/fmath.h b/src/include/OpenImageIO/fmath.h index e97a748c1b..bb73ff14ea 100644 --- a/src/include/OpenImageIO/fmath.h +++ b/src/include/OpenImageIO/fmath.h @@ -46,6 +46,12 @@ OIIO_NAMESPACE_BEGIN +// NOTE: Almost everything in fmath.h is either inline or templated, and +// therefore can be left in the "current" OIIO namespace, they are never +// visible between translation units. But if this ever comes up as a problem, +// we can just move them into an older v3_1 namespace and appropriate `using` +// directives to alias it into other namespaces. + /// If the caller defines OIIO_FMATH_HEADER_ONLY to nonzero, then 100% of the /// implementation of fmath functions will be defined directly in this header @@ -743,7 +749,10 @@ scaled_conversion(const S& src, F scale, F min, F max) } } +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN /// Convert n consecutive values from the type of S to the type of D. /// The conversion is not a simple cast, but correctly remaps the @@ -1017,6 +1026,11 @@ convert_type (const S &src) } } +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN +using v3_1::convert_type; /// Helper function to convert channel values between different bit depths. @@ -2272,6 +2286,12 @@ interpolate_linear (float x, span_strided y) // (end miscellaneous numerical methods) //////////////////////////////////////////////////////////////////////////// +OIIO_NAMESPACE_END +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::convert_type; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/fstream_mingw.h b/src/include/OpenImageIO/fstream_mingw.h index 4ac577474f..6a4e860729 100644 --- a/src/include/OpenImageIO/fstream_mingw.h +++ b/src/include/OpenImageIO/fstream_mingw.h @@ -24,7 +24,7 @@ # include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template> @@ -309,7 +309,15 @@ basic_ofstream<_CharT, _Traits>::close() } // basic_fstream -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +# ifndef OIIO_DOXYGEN +using v3_1::basic_ifstream; +using v3_1::basic_ofstream; +# endif +OIIO_NAMESPACE_END #endif // #if defined(_WIN32) && defined(__GLIBCXX__) diff --git a/src/include/OpenImageIO/function_view.h b/src/include/OpenImageIO/function_view.h index 5df9842a2e..e945bfe5cf 100644 --- a/src/include/OpenImageIO/function_view.h +++ b/src/include/OpenImageIO/function_view.h @@ -47,7 +47,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// function_view is a lightweight non-owning generic callable @@ -105,4 +105,10 @@ template class function_view { }; +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +using v3_1::function_view; OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/hash.h b/src/include/OpenImageIO/hash.h index 12e58d1616..8424869a97 100644 --- a/src/include/OpenImageIO/hash.h +++ b/src/include/OpenImageIO/hash.h @@ -27,7 +27,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using std::hash; using std::unordered_map; @@ -563,7 +563,7 @@ inline uint128_t Fingerprint128(const Str& s) { class CSHA1; // opaque forward declaration -/// Class that encapsulates SHA-1 hashing, a crypticographic-strength +/// Class that encapsulates SHA-1 hashing, a cryptographic-strength /// 160-bit hash function. It's not as fast as our other hashing /// methods, but has an extremely low chance of having collisions. class OIIO_API SHA1 { @@ -616,4 +616,16 @@ class OIIO_API SHA1 { }; +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +// Replicate the hashing namespaces in v3_1:: inside OIIO:: +namespace fasthash { using namespace OIIO::v3_1::fasthash; } +namespace xxhash { using namespace OIIO::v3_1::xxhash; } +namespace bjhash { using namespace OIIO::v3_1::bjhash; } +namespace murmur { using namespace OIIO::v3_1::murmur; } +namespace farmhash { using namespace OIIO::v3_1::farmhash; } +using v3_1::SHA1; OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/image_span.h b/src/include/OpenImageIO/image_span.h index 5f685eb6ad..2684c15504 100644 --- a/src/include/OpenImageIO/image_span.h +++ b/src/include/OpenImageIO/image_span.h @@ -11,24 +11,8 @@ #include -OIIO_NAMESPACE_BEGIN - -#ifndef OIIO_STRIDE_T_DEFINED -# define OIIO_STRIDE_T_DEFINED -/// Type we use to express how many pixels (or bytes) constitute an image, -/// tile, or scanline. -using imagesize_t = uint64_t; - -/// Type we use for stride lengths between pixels, scanlines, or image -/// planes. -using stride_t = int64_t; - -/// Special value to indicate a stride length that should be -/// auto-computed. -inline constexpr stride_t AutoStride = std::numeric_limits::min(); -#endif - +OIIO_NAMESPACE_3_1_BEGIN /// image_span : a non-owning reference to an image-like n-D array having /// between 2 and 4 dimensions representing channel, x, y, z with each @@ -52,6 +36,8 @@ template class image_span { using stride_type = int64_t; using size_type = uint32_t; + static constexpr stride_t AutoStride = std::numeric_limits::min(); + /// Default ctr -- points to nothing image_span() = default; @@ -385,6 +371,8 @@ as_image_span_writable_bytes(const image_span& src) noexcept src.ystride(), src.zstride(), src.chansize()); } + + /// Verify that the image_span has all its contents lying within the /// contiguous span. OIIO_API bool @@ -402,4 +390,17 @@ image_span_within_span(const image_span& ispan, as_bytes(contiguous)); } +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::as_image_span_bytes; +using v3_1::as_image_span_writable_bytes; +using v3_1::image1d_span; +using v3_1::image2d_span; +using v3_1::image3d_span; +using v3_1::image_span; +using v3_1::image_span_within_span; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/image_view.h b/src/include/OpenImageIO/image_view.h index 6d1bae6102..6811bac5e7 100644 --- a/src/include/OpenImageIO/image_view.h +++ b/src/include/OpenImageIO/image_view.h @@ -12,7 +12,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// image_view : a non-owning reference to an image-like array (indexed by @@ -123,4 +123,4 @@ class OIIO_DEPRECATED("image_view is deprecated. Consider image_span.") }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/imagebuf.h b/src/include/OpenImageIO/imagebuf.h index 2015e923db..ad55228101 100644 --- a/src/include/OpenImageIO/imagebuf.h +++ b/src/include/OpenImageIO/imagebuf.h @@ -37,13 +37,7 @@ -OIIO_NAMESPACE_BEGIN - -class ImageBuf; -class ImageBufImpl; // Opaque type for the unique_ptr. -class ImageCache; -class ImageCacheTile; - +OIIO_NAMESPACE_3_1_BEGIN /// Return pixel data window for this ImageSpec as a ROI. @@ -2042,4 +2036,14 @@ class OIIO_API ImageBuf { }; +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +using v3_1::get_roi; +using v3_1::get_roi_full; +using v3_1::InitializePixels; +using v3_1::set_roi; +using v3_1::set_roi_full; OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index 5f8063ccfc..f70cbf72e6 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -18,18 +18,14 @@ #include #include #include +#include #include #include #include -OIIO_NAMESPACE_BEGIN - -// forward declarations -class ColorConfig; -class ColorProcessor; -class Filter2D; +OIIO_NAMESPACE_3_1_BEGIN /// @defgroup ImageBufAlgo_intro (ImageBufAlgo common principles) @@ -2707,3 +2703,14 @@ inline bool fit(ImageBuf &dst, const ImageBuf &src, Filter2D *filter, } // end namespace ImageBufAlgo OIIO_NAMESPACE_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::Image_or_Const; +namespace ImageBufAlgo { +using namespace OIIO::v3_1::ImageBufAlgo; +} +#endif +OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/imagebufalgo_opencv.h b/src/include/OpenImageIO/imagebufalgo_opencv.h index 56b0e349a4..7ce0373632 100644 --- a/src/include/OpenImageIO/imagebufalgo_opencv.h +++ b/src/include/OpenImageIO/imagebufalgo_opencv.h @@ -59,7 +59,7 @@ OIIO_PRAGMA_WARNING_POP -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace ImageBufAlgo { @@ -294,4 +294,4 @@ ImageBufAlgo::capture_image(int cameranum, TypeDesc convert) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/imagebufalgo_util.h b/src/include/OpenImageIO/imagebufalgo_util.h index afbc944208..86b145dcab 100644 --- a/src/include/OpenImageIO/imagebufalgo_util.h +++ b/src/include/OpenImageIO/imagebufalgo_util.h @@ -13,11 +13,8 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN -using std::bind; -using std::cref; -using std::ref; using namespace std::placeholders; using std::placeholders::_1; @@ -808,4 +805,10 @@ perpixel_op(const ImageBuf& srcA, const ImageBuf& srcB, } // end namespace ImageBufAlgo +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN +using namespace std::placeholders; +using std::placeholders::_1; OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/imagecache.h b/src/include/OpenImageIO/imagecache.h index 008b33bf34..20b784601f 100644 --- a/src/include/OpenImageIO/imagecache.h +++ b/src/include/OpenImageIO/imagecache.h @@ -25,16 +25,7 @@ -OIIO_NAMESPACE_BEGIN - -// Forward declarations -class TextureOpt_v2; - -class ImageCachePerThreadInfo; -class ImageCacheFile; -class ImageCacheTile; -class ImageCacheImpl; - +OIIO_NAMESPACE_3_1_BEGIN /// Define an API to an abstract class that manages image files, @@ -1343,8 +1334,8 @@ class OIIO_API ImageCache { ~ImageCache(); private: - friend class TextureSystem; - friend class TextureSystemImpl; + friend class OIIO::TextureSystem; + friend class OIIO::TextureSystemImpl; // PIMPL idiom using Impl = ImageCacheImpl; @@ -1356,4 +1347,4 @@ class OIIO_API ImageCache { }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index f7576a2060..1002d8cf0b 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -39,14 +39,9 @@ #include #include -OIIO_NAMESPACE_BEGIN -class DeepData; -class ImageBuf; -class Timer; -#ifndef OIIO_STRIDE_T_DEFINED -# define OIIO_STRIDE_T_DEFINED +OIIO_NAMESPACE_3_1_BEGIN /// Type we use to express how many pixels (or bytes) constitute an image, /// tile, or scanline. using imagesize_t = uint64_t; @@ -58,7 +53,21 @@ using stride_t = int64_t; /// Special value to indicate a stride length that should be /// auto-computed. inline constexpr stride_t AutoStride = std::numeric_limits::min(); + +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::imagesize_t; +using v3_1::stride_t; +using v3_1::AutoStride; #endif +OIIO_NAMESPACE_END + + + +OIIO_NAMESPACE_3_1_BEGIN // Signal that this version of ImageBuf has constructors from spans #define OIIO_IMAGEINPUT_IMAGE_SPAN_SUPPORT 1 @@ -75,11 +84,6 @@ typedef bool (*ProgressCallback)(void *opaque_data, float portion_done); -// Forward declaration of IOProxy -namespace Filesystem { - class IOProxy; -} - /// ROI is a small helper struct describing a rectangular region of interest /// in an image. The region is [xbegin,xend) x [begin,yend) x [zbegin,zend), @@ -4107,7 +4111,7 @@ inline bool getattribute(string_view name, TypeDesc type, span value) { OIIO_DASSERT(BaseTypeFromC::value == type.basetype && type.size() == value.size_bytes()); - return OIIO::getattribute(name, type, OIIO::as_writable_bytes(value)); + return OIIO::v3_1::getattribute(name, type, OIIO::as_writable_bytes(value)); } /// A version of `getattribute()` that stores the value in a span of @@ -4222,12 +4226,12 @@ inline std::map> get_extension_map() { std::map> map; - auto all_extensions = OIIO::get_string_attribute("extension_list"); - for (auto oneformat : OIIO::Strutil::splitsv(all_extensions, ";")) { - auto format_exts = OIIO::Strutil::splitsv(oneformat, ":", 2); + auto all_extensions = get_string_attribute("extension_list"); + for (auto oneformat : Strutil::splitsv(all_extensions, ";")) { + auto format_exts = Strutil::splitsv(oneformat, ":", 2); if (format_exts.size() != 2) continue; // something went wrong - map[format_exts[0]] = OIIO::Strutil::splits(format_exts[1], ","); + map[format_exts[0]] = Strutil::splits(format_exts[1], ","); } return map; } @@ -4510,15 +4514,18 @@ OIIO_API void log_time(string_view key, const Timer& timer, int count = 1); // to force correct linkage on some systems OIIO_API void _ImageIO_force_link (); +OIIO_NAMESPACE_3_1_END + ////////////////////////////////////////////////////////////////////////// // DEPRECATED things // -// These are all hidden from ocumentation and internal use, and should trigger -// deprecation warnings if used externally. They will most likely be removed -// entirely before the final release of OIIO 3.0. +// These are all hidden from documentation and internal use, and should +// trigger deprecation warnings if used externally. They will most likely be +// removed entirely before the final release of OIIO 3.0. // #if !defined(OIIO_INTERNAL) && !defined(OIIO_DOXYGEN) +OIIO_NAMESPACE_BEGIN #if OIIO_DISABLE_DEPRECATED < OIIO_MAKE_VERSION(2, 1, 0) // DEPRECATED(2.1): old name @@ -4556,13 +4563,61 @@ inline void debug (const char* fmt, const T1& v1, const Args&... args) } #endif +OIIO_NAMESPACE_END #endif // ////////////////////////////////////////////////////////////////////////// + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +// clang-format on +using v3_1::add_dither; +using v3_1::attribute; +using v3_1::convert_image; +using v3_1::convert_pixel_values; +using v3_1::copy_image; +using v3_1::cspan_from_buffer; +using v3_1::debug; +using v3_1::debugfmt; +using v3_1::declare_imageio_format; +using v3_1::equivalent_colorspace; +using v3_1::errorfmt; +using v3_1::get_extension_map; +using v3_1::get_float_attribute; +using v3_1::get_int_attribute; +using v3_1::get_string_attribute; +using v3_1::getattribute; +using v3_1::geterror; +using v3_1::has_error; +using v3_1::is_imageio_format_name; +using v3_1::log_time; +using v3_1::openimageio_version; +using v3_1::parallel_convert_image; +using v3_1::premult; +using v3_1::ProgressCallback; +using v3_1::roi_intersection; +using v3_1::roi_union; +using v3_1::set_colorspace; +using v3_1::set_colorspace_rec709_gamma; +using v3_1::shutdown; +using v3_1::span_from_buffer; +using v3_1::wrap_black; +using v3_1::wrap_clamp; +using v3_1::wrap_impl; +using v3_1::wrap_mirror; +using v3_1::wrap_periodic; +using v3_1::wrap_periodic_pow2; +namespace pvt { +using v3_1::pvt::append_error; +} +#endif OIIO_NAMESPACE_END + #if FMT_VERSION >= 100000 FMT_BEGIN_NAMESPACE template<> struct formatter : ostream_formatter {}; diff --git a/src/include/OpenImageIO/memory.h b/src/include/OpenImageIO/memory.h index e9b8379fbe..cb71ec4c00 100644 --- a/src/include/OpenImageIO/memory.h +++ b/src/include/OpenImageIO/memory.h @@ -18,7 +18,7 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { @@ -127,4 +127,15 @@ footprint(const std::vector& vec) } // namespace pvt -OIIO_NAMESPACE_END \ No newline at end of file +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +namespace pvt { +using v3_1::pvt::footprint; +using v3_1::pvt::heapsize; +} // namespace pvt +#endif +OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/nsversions.h b/src/include/OpenImageIO/nsversions.h new file mode 100644 index 0000000000..51ae50777f --- /dev/null +++ b/src/include/OpenImageIO/nsversions.h @@ -0,0 +1,252 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +// clang-format off + +#ifndef OPENIMAGEIO_NSVERSIONS_H +#define OPENIMAGEIO_NSVERSIONS_H + +#ifndef OPENIMAGEIO_VERSION_H +# error "oiioversion.h must always be included before nsversions.h" +#endif + + +// Establish the namespaces. +// +// The outer namespace defaults to OpenImageIO, but can be overriden at build +// time. "OIIO" is always defined as an alias to this namespace, so client +// software can always say `OIIO:Foo` without needing to know the custom outer +// namespace. +// +// The primary inner namespace is vMAJ_MIN (or in main, vMAJ_MIN_PAT). The +// outer namespace declares the inner namespace as "inline", so anything in it +// is visible in OIIO by default. +// +// But we also keep around all the symbols for older vOLDMAJ_OLDMIN, so that +// we don't lose link/ABI compatibility with apps compiled for older minor +// versions within the same major version family. +// +// +// Here is the scheme we use to maintain ABI compatibility across minor +// version boundaries. +// +// 1. Declarations in the current new namespace +// +// * Pure OIIO internals that are not exposed in the public APIs or +// headers, and therefore generate no externally visible linker symbols. +// * NEW items that will break ABI or API, in main (will be moved into a +// specific version namespace before release). +// +// These declarations perpetually live in the "current" namespace: +// +// OIIO_NAMESPACE_BEGIN +// inline int myfunc() { ... } +// constexpr int myconst = 3; +// template Mytemplate { ... } +// // ONLY if Mytemplate is not used as the type of a parameter +// // or return value of any exposed public OIIO API function! +// // If it ever is, it should be moved to a versioned namespace. +// OIIO_NAMESPACE_END +// +// Because of the inline inner default namespace, user code can just refer +// to these as `OIIO::myfunc()`, `OIIO::myconst`, etc. +// +// 2. Declarations carried over from previous versions and aliased into +// later/current namespaces: +// +// * Anything that was introduced in earlier versions and is enclosed in a +// namespace (like `Filesystem`, `Strutil`, or `ImageBufAlgo`). +// * Existing classes that were introduced in earlier OIIO versions, and +// any modifications that can be amended without breaking their ABIs +// (such as adding a non-virtual method). This also includes templated +// classes used to pass parameters or return values by OIIO's APIs. +// * Functions that were introduced in earlier versions and can simply +// be aliased into subsequent namespaces with `using`. +// * NEW items in the above categories that are logically related to +// existing things should be placed in the oldest namespace where the +// rest of its cohort lives, if that can be done without conflicting with +// existing symbols. +// +// These live in the namespace of the version where they were first +// introduced. Later versions alias these into their namespaces with +// `using`. +// +// OIIO_NAMESPACE_3_1_BEGIN +// class Myclass { ... }; +// template Mytemplate { ... } +// int standalone_func(); +// namespace Group { +// int func_in_group(); +// } +// OIIO_NAMESPACE_3_1_END +// +// // Alias these items into subsequent version namespaces +// OIIO_NAMESPACE_BEGIN +// using v3_1::Myclass; +// using v3_1::Mytemplate; +// using v3_1::standalone_func; +// namespace Group { +// using namespace v3_1::Group; +// // Makes EVERYTHING declared in v3_1::Group alias to +// // within the current namespace's Group. +// } +// OIIO_NAMESPACE_END +// +// 3. Declarations that must be separately defined in each namespace. +// +// * Classes or functions whose declarations in newer versions have changed +// in an ABI-incompatible way. +// * Standalone functions or globals that are not enclosed in a namespace +// and for whatever reason can't simply be pulled into later namespaces +// with the `using` directive. +// +// These must have separate symbols in each versioned namespace to preserve +// ABI compatibility. But it is customary to make the full and most +// efficient implementation in the newer namespace, and the others be +// trivial wrappers (with some small penalty for the double call, but +// that's ok because it's only incurred when linking against a too-new +// version). +// +// Declarations in the .h header: +// +// OIIO_NAMESPACE_3_1_BEGIN +// int myfunc(); +// class Myclass { int foo; ... } +// int anotherfunc(Myclass& m); +// OIIO_NAMESPACE_3_1_END +// +// // Duplicate in subsequent version namespaces +// OIIO_NAMESPACE_BEGIN +// float myfunc(); // changed return value +// class Myclass { float foo; ... } // data layout changed +// int anotherfunc(Myclass& m); // DIFFERENT Myclass! +// OIIO_NAMESPACE_END +// +// Implementation in the .cpp file: +// +// OIIO_NAMESPACE_BEGIN +// // Current namespace gets a full implementation of myfunc: +// float myfunc() { ... } +// +// // New anotherfunc takes new definition of Myclass: +// int anotherfunc(Myclass& m) { ... } +// OIIO_NAMESPACE_END +// +// OIIO_NAMESPACE_3_1_BEGIN +// // Old version +// int myfunc() { ... } +// +// // Old anotherfunc takes old definition of Myclass: +// int anotherfunc(v3_1::Myclass& m) { ... } +// OIIO_NAMESPACE_3_1_END +// + + +// Macros for defining namespaces with an explicit version +#define OIIO_NS_BEGIN(ver) namespace OIIO_OUTER_NAMESPACE { namespace ver { +#define OIIO_NS_END } } + +// Specialty macro: Make something ABI compatible with 3.1 +#define OIIO_NAMESPACE_3_1_BEGIN OIIO_NS_BEGIN(v3_1) +#define OIIO_NAMESPACE_3_1_END OIIO_NS_END + +// Specialty macro: Make something ABI compatible with 3.2 +#define OIIO_NAMESPACE_3_2_BEGIN OIIO_NS_BEGIN(v3_2) +#define OIIO_NAMESPACE_3_2_END OIIO_NS_END + + + +// Forward declarations of important classes +OIIO_NAMESPACE_3_1_BEGIN +// libOpenImageIO_Util +class ArgParse; +class ColorConfig; +class ColorProcessor; +class ErrorHandler; +class Filter1D; +class Filter2D; +class FilterDesc; +class ParamValue; +class ParamValueList; +class ParamValueSpan; +class ScopedTimer; +class Timer; +struct TypeDesc; +class ustring; +class ustringhash; + +// libOpenImageIO +class DeepData; +class ImageBuf; +class ImageBufImpl; +class ImageCache; +class ImageCachePerThreadInfo; +class ImageCacheFile; +class ImageCacheImpl; +class ImageCacheTile; +class ImageInput; +class ImageOutput; +class ImageSpec; +class paropt; +struct ROI; +class TextureOptBatch_v1; +class TextureOpt_v2; +using TextureOpt = TextureOpt_v2; +class TextureSystem; +class TextureSystemImpl; +namespace Filesystem { + class IOProxy; +} +namespace ImageBufAlgo { } +namespace Strutil { } +namespace Sysutil { } +namespace simd { } +OIIO_NAMESPACE_3_1_END + +OIIO_NAMESPACE_BEGIN +// libOpenImageIO_Util +using v3_1::ArgParse; +using v3_1::ColorConfig; +using v3_1::ColorProcessor; +using v3_1::ErrorHandler; +using v3_1::Filter1D; +using v3_1::Filter2D; +using v3_1::FilterDesc; +using v3_1::ParamValue; +using v3_1::ParamValueList; +using v3_1::ParamValueSpan; +using v3_1::ScopedTimer; +using v3_1::Timer; +using v3_1::TypeDesc; +using v3_1::ustring; +using v3_1::ustringhash; + +// libOpenImageIO +using v3_1::DeepData; +using v3_1::ImageBuf; +using v3_1::ImageBufImpl; +using v3_1::ImageCache; +using v3_1::ImageCachePerThreadInfo; +using v3_1::ImageCacheFile; +using v3_1::ImageCacheImpl; +using v3_1::ImageCacheTile; +using v3_1::ImageInput; +using v3_1::ImageOutput; +using v3_1::ImageSpec; +using v3_1::paropt; +using v3_1::ROI; +using v3_1::TextureOptBatch_v1; +using v3_1::TextureOpt_v2; +using TextureOpt = v3_1::TextureOpt_v2; +using TextureOptBatch = v3_1::TextureOptBatch_v1; +using v3_1::TextureSystem; +using v3_1::TextureSystemImpl; +namespace Filesystem { using namespace v3_1::Filesystem; } +namespace ImageBufAlgo { using namespace v3_1::ImageBufAlgo; } +namespace Strutil { using namespace v3_1::Strutil; } +namespace Sysutil { using namespace v3_1::Sysutil; } +namespace simd { using namespace v3_1::simd; } +OIIO_NAMESPACE_END + +#endif /* defined(OPENIMAGEIO_NSVERSIONS_H) */ diff --git a/src/include/OpenImageIO/oiioversion.h.in b/src/include/OpenImageIO/oiioversion.h.in index b2a1d23ca1..7307df5160 100644 --- a/src/include/OpenImageIO/oiioversion.h.in +++ b/src/include/OpenImageIO/oiioversion.h.in @@ -126,39 +126,16 @@ // The primary inner namespace is vMAJ_MIN (or in main, vMAJ_MIN_PAT). The // outer namespace declares the inner namespace as "inline", so anything in it // is visible in OIIO by default. -// -// Current API symbols should declare things in the default namespace, which -// has its version incremented with every minor (yearly) release: -// -// // Foo.h -// OIIO_NAMESPACE_BEGIN /* implicitly means current for this version */ -// void foo(); -// void bar(); -// OIIO_NAMESPACE_END -// -// Because the default inner namespace is "inline", client code can just refer -// to `OIIO::Foo`, and `OIIO::v3_1::Foo` will be found (assuming that v3_1 is -// the current inner namespace). -// -// Things can also be put in an explicit inner namespace, such as -// -// OIIO_NS_BEGIN(v3_0) -// // for declarations that should be visible by explicit request only, -// // such as OIIO::v3_0::bar(). -// int bar(); -// OIIO_NS_END -// -// Currently, everything is defined with OIIO_NAMESPACE_BEGIN/END, and so is -// put in the current-minor-release namespace. But the mechanisms outlined -// above gives us the ability in the future to have multiple ABI (minor -// release) generations of the same facilities existing simultaneously, to -// preserve ABI compatibility even with minor version bumps. -// + +#define OIIO_OUTER_NAMESPACE @PROJ_NAMESPACE@ +#define OIIO_CURRENT_INNER_NAMESPACE @PROJ_VERSION_NAMESPACE@ namespace @PROJ_NAMESPACE@ { // Current version's new inner namespace is inline so it's used by default. inline namespace @PROJ_VERSION_NAMESPACE@ { } + // Legacy namespaces: + namespace v3_1 { } namespace v3_0 { } } namespace OIIO = @PROJ_NAMESPACE@; @@ -169,16 +146,7 @@ namespace OIIO = @PROJ_NAMESPACE@; #define OIIO_NAMESPACE_END } } #define OIIO_CURRENT_NAMESPACE @PROJ_NAMESPACE@::@PROJ_VERSION_NAMESPACE@ -// Macros for defining legacy namespaces with an explicit version -#define OIIO_NS_BEGIN(ver) namespace @PROJ_NAMESPACE@ { namespace ver { -#define OIIO_NS_END } } - -// Specialty macro: Make something ABI compatible with 3.1 -#define OIIO_NAMESPACE_3_1_BEGIN OIIO_NS_BEGIN(v3_1) -#define OIIO_NAMESPACE_3_1_END OIIO_NS_END - -// Macro to use names without explicit qualifier -#define OIIO_NAMESPACE_USING using namespace @PROJ_NAMESPACE@::@PROJ_VERSION_NAMESPACE@; +#include /// Each imageio DSO/DLL should include this statement: diff --git a/src/include/OpenImageIO/optparser.h b/src/include/OpenImageIO/optparser.h index 7286b3d331..b272eb8915 100644 --- a/src/include/OpenImageIO/optparser.h +++ b/src/include/OpenImageIO/optparser.h @@ -15,7 +15,7 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Parse a string of the form "name=value" and then call @@ -101,5 +101,14 @@ optparser(C& system, const std::string& optstring) return ok; } +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::optparse1; +using v3_1::optparser; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/parallel.h b/src/include/OpenImageIO/parallel.h index 1e4709e4d9..399a187129 100644 --- a/src/include/OpenImageIO/parallel.h +++ b/src/include/OpenImageIO/parallel.h @@ -17,7 +17,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Split strategies /// DEPRECATED(2.4) @@ -197,6 +197,32 @@ parallel_for_chunked(int64_t begin, int64_t end, int64_t chunksize, paropt opt = paropt(0, paropt::SplitDir::Y, 1)); +#ifndef OIIO_DOXYGEN +// DEPRECATED(3.2) -- old version makes a copy of task instead of &&, ick +OIIO_UTIL_API void +parallel_for(int32_t begin, int32_t end, function_view task, + paropt opt = 0); + +// DEPRECATED(3.2) -- old version makes a copy of task instead of &&, ick +OIIO_UTIL_API void +parallel_for(int64_t begin, int64_t end, function_view task, + paropt opt = 0); + +// DEPRECATED(3.2) -- old version makes a copy of task instead of &&, ick +OIIO_UTIL_API void +parallel_for(uint32_t begin, uint32_t end, function_view task, + paropt opt = 0); + +// DEPRECATED(3.2) -- old version makes a copy of task instead of &&, ick +OIIO_UTIL_API void +parallel_for(uint64_t begin, uint64_t end, function_view task, + paropt opt = 0); +#endif + +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN /// Parallel "for" loop, for a task that takes a single integer index, run /// it on all indices on the range [begin,end): @@ -211,21 +237,25 @@ parallel_for_chunked(int64_t begin, int64_t end, int64_t chunksize, /// (to aid data coherence and minimize the amount of thread queue /// diddling). The chunk size is chosen automatically. OIIO_UTIL_API void -parallel_for(int32_t begin, int32_t end, function_view task, +parallel_for(int32_t begin, int32_t end, function_view&& task, paropt opt = 0); OIIO_UTIL_API void -parallel_for(int64_t begin, int64_t end, function_view task, +parallel_for(int64_t begin, int64_t end, function_view&& task, paropt opt = 0); OIIO_UTIL_API void -parallel_for(uint32_t begin, uint32_t end, function_view task, +parallel_for(uint32_t begin, uint32_t end, function_view&& task, paropt opt = 0); OIIO_UTIL_API void -parallel_for(uint64_t begin, uint64_t end, function_view task, +parallel_for(uint64_t begin, uint64_t end, function_view&& task, paropt opt = 0); +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN /// Parallel "for" loop, for a task that takes an integer range, run it /// on all indices on the range [begin,end): @@ -295,6 +325,19 @@ parallel_for_2D(int64_t xbegin, int64_t xend, int64_t ybegin, int64_t yend, std::function&& task, paropt opt = 0); +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::parallel_for_2D; +using v3_1::parallel_for_chunked; +using v3_1::parallel_for_chunked_2D; +using v3_1::parallel_for_range; +using v3_1::parallel_options; +using v3_1::paropt; +using v3_1::SplitDir; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/paramlist.h b/src/include/OpenImageIO/paramlist.h index 4906c16be9..5667a75ed0 100644 --- a/src/include/OpenImageIO/paramlist.h +++ b/src/include/OpenImageIO/paramlist.h @@ -22,7 +22,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// ParamValue holds a named parameter and typed data. Usually, it owns the /// data (holding it in the struct itself if small enough, dynamically @@ -372,15 +372,19 @@ class OIIO_UTIL_API ParamValue { template friend size_t pvt::heapsize(const T&); }; + + /// heapsize specialization for `ParamValue` template<> OIIO_API size_t pvt::heapsize(const ParamValue&); + + /// Factory for a ParamValue that holds a single value of any type supported /// by a corresponding ParamValue constructor (such as int, float, string). template -static ParamValue +inline ParamValue make_pv(string_view name, const T& val) { return ParamValue(name, val); @@ -390,7 +394,7 @@ make_pv(string_view name, const T& val) /// will be interpreted as a C string (TypeString), but all other pointer /// types will just get stored as an opaque pointer (TypePointer). template -static ParamValue +inline ParamValue make_pv(string_view name, T* val) { return ParamValue(name, BaseTypeFromC::value, 1, span(&val, 1)); @@ -842,4 +846,15 @@ class OIIO_UTIL_API ParamValueSpan : public cspan { }; +OIIO_NAMESPACE_3_1_END + + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::make_pv; +using v3_1::ParamValue; +using v3_1::ParamValueList; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/platform.h b/src/include/OpenImageIO/platform.h index 51b16122ec..e4760e5545 100644 --- a/src/include/OpenImageIO/platform.h +++ b/src/include/OpenImageIO/platform.h @@ -531,7 +531,7 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Class for describing endianness. Test for endianness as /// `if (endian::native == endian::little)` or @@ -615,8 +615,8 @@ inline bool cpu_has_avx512bw() {int i[4]; cpuid(i,7,0); return (i[1] & (1<<30)) inline bool cpu_has_avx512vl() {int i[4]; cpuid(i,7,0); return (i[1] & (0x80000000 /*1<<31*/)) != 0; } // portable aligned malloc -OIIO_API void* aligned_malloc(std::size_t size, std::size_t align); -OIIO_API void aligned_free(void* ptr); +OIIO_UTIL_API void* aligned_malloc(std::size_t size, std::size_t align); +OIIO_UTIL_API void aligned_free(void* ptr); // basic wrappers to new/delete over-aligned types since this isn't guaranteed to be supported until C++17 template @@ -641,6 +641,9 @@ inline void aligned_delete(T* t) { // DEPRECATED(2.6) using std::enable_if_t; +OIIO_NAMESPACE_3_1_END + + // An enable_if helper to be used in template parameters which results in // much shorter symbols: https://godbolt.org/z/sWw4vP // Borrowed from fmtlib. @@ -648,4 +651,37 @@ using std::enable_if_t; # define OIIO_ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0 #endif + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::endian; +using v3_1::littleendian; +using v3_1::bigendian; +using v3_1::aligned_delete; +using v3_1::aligned_free; +using v3_1::aligned_malloc; +using v3_1::aligned_new; +using v3_1::cpuid; +using v3_1::cpu_has_sse2; +using v3_1::cpu_has_sse3; +using v3_1::cpu_has_ssse3; +using v3_1::cpu_has_fma; +using v3_1::cpu_has_sse41; +using v3_1::cpu_has_sse42; +using v3_1::cpu_has_popcnt; +using v3_1::cpu_has_avx; +using v3_1::cpu_has_f16c; +using v3_1::cpu_has_rdrand; +using v3_1::cpu_has_avx2; +using v3_1::cpu_has_avx512f; +using v3_1::cpu_has_avx512dq; +using v3_1::cpu_has_avx512ifma; +using v3_1::cpu_has_avx512pf; +using v3_1::cpu_has_avx512er; +using v3_1::cpu_has_avx512cd; +using v3_1::cpu_has_avx512bw; +using v3_1::cpu_has_avx512vl; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/plugin.h b/src/include/OpenImageIO/plugin.h index 75b32e104c..fc6c0e7d09 100644 --- a/src/include/OpenImageIO/plugin.h +++ b/src/include/OpenImageIO/plugin.h @@ -20,7 +20,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace Plugin { @@ -74,4 +74,15 @@ geterror(bool clear = true); } // namespace Plugin +OIIO_NAMESPACE_3_1_END + + + +// Compatibility: inherit everything from v3_1::Plugin +#ifndef OIIO_DOXYGEN +OIIO_NAMESPACE_BEGIN +namespace Plugin { +using namespace OIIO::v3_1::Plugin; +} OIIO_NAMESPACE_END +#endif diff --git a/src/include/OpenImageIO/refcnt.h b/src/include/OpenImageIO/refcnt.h index 68cacd319c..a234fab19f 100644 --- a/src/include/OpenImageIO/refcnt.h +++ b/src/include/OpenImageIO/refcnt.h @@ -19,7 +19,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN @@ -254,4 +254,16 @@ footprint(const intrusive_ptr& ref) } // namespace pvt +OIIO_NAMESPACE_3_1_END + + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::intrusive_ptr; +using v3_1::intrusive_ptr_add_ref; +using v3_1::intrusive_ptr_release; +using v3_1::RefCnt; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/simd.h b/src/include/OpenImageIO/simd.h index 11578c1b7c..9bb0b97093 100644 --- a/src/include/OpenImageIO/simd.h +++ b/src/include/OpenImageIO/simd.h @@ -281,7 +281,7 @@ #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace simd { @@ -10284,41 +10284,37 @@ OIIO_FORCEINLINE vfloat16 nmsub (const simd::vfloat16& a, const simd::vfloat16& } // end namespace simd -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END /// Custom fmtlib formatters for our SIMD types. -namespace fmt { -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::index_formatter {}; -template<> struct formatter +template<> struct fmt::formatter : OIIO::pvt::array_formatter {}; -} // namespace fmt // Allow C++ metaprogramming to understand that the simd types are trivially // copyable (i.e. memcpy to copy simd types is fine). -namespace std { // not necessary in C++17, just say std::is_trivially_copyable #if defined(__INTEL_COMPILER) // Necessary because we have to define the vint types copy constructors on icc -template<> struct is_trivially_copyable : std::true_type {}; -template<> struct is_trivially_copyable : std::true_type {}; -template<> struct is_trivially_copyable : std::true_type {}; +template<> struct std::is_trivially_copyable : std::true_type {}; +template<> struct std::is_trivially_copyable : std::true_type {}; +template<> struct std::is_trivially_copyable : std::true_type {}; #endif -} // namespace std #undef SIMD_DO diff --git a/src/include/OpenImageIO/span.h b/src/include/OpenImageIO/span.h index 72c6bc2d40..f1c49dafdc 100644 --- a/src/include/OpenImageIO/span.h +++ b/src/include/OpenImageIO/span.h @@ -26,8 +26,6 @@ // https://github.com/tcbrindle/span/blob/master/include/tcb/span.hpp -OIIO_NAMESPACE_BEGIN - // Our pre-3.0 implementation had span::size() as a signed value, because we // wrote it at a time that the draft of std::span said it should be signed. // The final C++20 std::span ended up with an unsigned size, like all the @@ -38,8 +36,10 @@ OIIO_NAMESPACE_BEGIN # define OIIO_SPAN_SIZE_IS_UNSIGNED #endif +OIIO_NAMESPACE_3_1_BEGIN + using span_size_t = size_t; -using oiio_span_size_type = OIIO::span_size_t; // back-compat alias +using oiio_span_size_type = span_size_t; // back-compat alias inline constexpr span_size_t dynamic_extent = std::numeric_limits::max(); @@ -263,6 +263,30 @@ class span { return const_reverse_iterator(m_data - 1); } + /// Compare all elements of two spans for equality + template + constexpr bool operator==(span r) { +#if OIIO_CPLUSPLUS_VERSION >= 20 + return std::equal (begin(), end(), r.begin(), r.end()); +#else + auto lsize = size(); + bool same = (lsize == r.size()); + if (lsize != r.size()) + return false; + for (span_size_t i = 0; i < lsize; ++i) + same &= (m_data[i] == r.m_data[i]); + // Note: If they're not the same size, the body of the loop won't run, + // so there can't be a buffer overrun here. + return same; +#endif + } + + /// Compare all elements of two spans for inequality + template + constexpr bool operator!= (span r) { + return !((*this) == r); + } + private: pointer m_data = nullptr; size_type m_size = 0; @@ -276,27 +300,6 @@ using cspan = span; -/// Compare all elements of two spans for equality -template -constexpr bool operator== (span l, span r) { -#if OIIO_CPLUSPLUS_VERSION >= 20 - return std::equal (l.begin(), l.end(), r.begin(), r.end()); -#else - auto lsize = l.size(); - bool same = (lsize == r.size()); - for (span_size_t i = 0; same && i < lsize; ++i) - same &= (l[i] == r[i]); - return same; -#endif -} - -/// Compare all elements of two spans for inequality -template -constexpr bool operator!= (span l, span r) { - return !(l == r); -} - - /// span_strided : a non-owning, mutable reference to a contiguous /// array with known length and optionally non-default strides through the @@ -385,6 +388,26 @@ class span_strided { constexpr reference back() const noexcept { return (*this)[size()-1]; } constexpr pointer data() const noexcept { return m_data; } + /// Compare all elements of two spans for equality + template + constexpr bool operator== (span_strided r) { + auto lsize = size(); + if (lsize != r.size()) + return false; + for (span_size_t i = 0; i < lsize; ++i) + if ((*this)[i] != r[i]) + return false; + // Note: If they're not the same size, the body of the loop won't run, + // so there can't be a buffer overrun here. + return true; + } + + /// Compare all elements of two spans for inequality + template + constexpr bool operator!= (span_strided r) { + return !((*this) == r); + } + private: pointer m_data = nullptr; size_type m_size = 0; @@ -399,25 +422,6 @@ using cspan_strided = span_strided; -/// Compare all elements of two spans for equality -template -constexpr bool operator== (span_strided l, span_strided r) { - auto lsize = l.size(); - if (lsize != r.size()) - return false; - for (span_size_t i = 0; i < lsize; ++i) - if (l[i] != r[i]) - return false; - return true; -} - -/// Compare all elements of two spans for inequality -template -constexpr bool operator!= (span_strided l, span_strided r) { - return !(l == r); -} - - // clang-format on @@ -712,6 +716,34 @@ check_span(span s, const PtrType* ptr, size_t len = 1) } +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::as_bytes; +using v3_1::as_bytes_ref; +using v3_1::as_writable_bytes; +using v3_1::check_span; +using v3_1::cspan; +using v3_1::cspan_strided; +using v3_1::dynamic_extent; +using v3_1::make_cspan; +using v3_1::make_span; +using v3_1::oiio_span_size_type; +using v3_1::span; +using v3_1::span_cast; +using v3_1::span_memcpy; +using v3_1::span_size_t; +using v3_1::span_strided; +using v3_1::span_within; +using v3_1::spancpy; +using v3_1::spanset; +using v3_1::spanzero; +#endif +OIIO_NAMESPACE_END + /// OIIO_ALLOCASPAN is used to allocate smallish amount of memory on the /// stack, equivalent of C99 type var_name[size], and then return a span @@ -719,9 +751,6 @@ check_span(span s, const PtrType* ptr, size_t len = 1) #define OIIO_ALLOCA_SPAN(type, size) span(OIIO_ALLOCA(type, size), size) -OIIO_NAMESPACE_END - - // Declare std::size and std::ssize for our span. namespace std { diff --git a/src/include/OpenImageIO/strided_ptr.h b/src/include/OpenImageIO/strided_ptr.h index b210088fdb..bac6ec71c5 100644 --- a/src/include/OpenImageIO/strided_ptr.h +++ b/src/include/OpenImageIO/strided_ptr.h @@ -11,7 +11,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// strided_ptr looks like a 'T*', but it incorporates a stride, so @@ -133,5 +133,12 @@ template class strided_ptr { }; +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::strided_ptr; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/string_view.h b/src/include/OpenImageIO/string_view.h index 2d5048fcd9..b959bb9d5d 100644 --- a/src/include/OpenImageIO/string_view.h +++ b/src/include/OpenImageIO/string_view.h @@ -32,7 +32,7 @@ #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// A `string_view` is a non-owning, non-copying, non-allocating reference @@ -509,11 +509,22 @@ OIIO_UTIL_API const char* c_str(string_view str); // DEPRECATED(3.0) template<> inline const char* basic_string_view::c_str() const { - return OIIO::c_str(*this); + return OIIO::v3_1::c_str(*this); } +OIIO_NAMESPACE_3_1_END + + +// Compatibility +#ifndef OIIO_DOXYGEN +OIIO_NAMESPACE_BEGIN +using v3_1::basic_string_view; +using v3_1::c_str; +using v3_1::string_view; +using v3_1::wstring_view; OIIO_NAMESPACE_END +#endif #if FMT_VERSION >= 100000 diff --git a/src/include/OpenImageIO/strongparam.h b/src/include/OpenImageIO/strongparam.h index 9126a49973..62a287e7bb 100644 --- a/src/include/OpenImageIO/strongparam.h +++ b/src/include/OpenImageIO/strongparam.h @@ -9,7 +9,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// StrongParam is used to construct an implementation of a derived type @@ -110,6 +110,7 @@ template struct StrongParam { static_assert(std::is_trivial::value, "Need trivial type"); }; +OIIO_NAMESPACE_3_1_END /// Convenience macro for making strong parameter type Name that is Basetype @@ -121,4 +122,10 @@ template struct StrongParam { } + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::StrongParam; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/strutil.h b/src/include/OpenImageIO/strutil.h index 32d5af41bd..c446c49689 100644 --- a/src/include/OpenImageIO/strutil.h +++ b/src/include/OpenImageIO/strutil.h @@ -56,7 +56,8 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN + /// @namespace Strutil /// /// @brief String-related utilities. @@ -690,6 +691,14 @@ template<> inline uint64_t from_string(string_view s) { auto r = strtoull(std::string(s).c_str(), nullptr, 10); return static_cast(r); } + +template<> inline short from_string(string_view s) { + return static_cast(Strutil::stoi(s)); +} + +template<> inline unsigned short from_string(string_view s) { + return static_cast(Strutil::stoi(s)); +} #endif @@ -1163,7 +1172,11 @@ eval_as_bool(string_view value); } // namespace Strutil +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN // Bring the ever-useful Strutil::print into the OIIO namespace. using Strutil::print; - OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/sysutil.h b/src/include/OpenImageIO/sysutil.h index 16d587668c..00552ff396 100644 --- a/src/include/OpenImageIO/sysutil.h +++ b/src/include/OpenImageIO/sysutil.h @@ -30,7 +30,7 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// @namespace Sysutil /// @@ -186,4 +186,4 @@ class OIIO_UTIL_API Term { } // namespace Sysutil -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/texture.h b/src/include/OpenImageIO/texture.h index e79558e10d..ef9d5a84f8 100644 --- a/src/include/OpenImageIO/texture.h +++ b/src/include/OpenImageIO/texture.h @@ -44,23 +44,7 @@ OIIO_CONCAT_VERSION(TextureOptBatch_v, OIIO_TEXTUREOPTBATCH_VERSION) -#ifndef INCLUDED_IMATHVEC_H -// Placeholder declaration for Imath::V3f if no Imath headers have been -// included. -namespace Imath { -template class Vec3; -using V3f = Vec3; -} -#endif - - -OIIO_NAMESPACE_BEGIN - -// Forward declarations - -class ImageCache; -class TextureSystemImpl; - +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { @@ -289,7 +273,7 @@ class OIIO_API TextureOpt_v2 { }; -using TextureOpt = TextureOpt_current; +using TextureOpt = TextureOpt_v2; @@ -1782,5 +1766,19 @@ class OIIO_API TextureSystem { // Always use TextureSystem::create() and TextureSystem::destroy(). }; +OIIO_NAMESPACE_3_1_END + + +// Compatibility +#ifndef OIIO_DOXYGEN +OIIO_NAMESPACE_BEGIN +namespace Tex { +using namespace v3_1::Tex; +} +namespace pvt { +using v3_1::pvt::EnvLayout; +using v3_1::pvt::TexFormat; +} // namespace pvt OIIO_NAMESPACE_END +#endif diff --git a/src/include/OpenImageIO/thread.h b/src/include/OpenImageIO/thread.h index 18b8c30ffb..b2af0b4122 100644 --- a/src/include/OpenImageIO/thread.h +++ b/src/include/OpenImageIO/thread.h @@ -54,8 +54,7 @@ // http://en.cppreference.com/w/cpp/atomic - -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Null mutex that can be substituted for a real one to test how much /// overhead is associated with a particular mutex. @@ -82,9 +81,9 @@ template class null_lock { using std::mutex; using std::recursive_mutex; using std::thread; -typedef std::lock_guard lock_guard; -typedef std::lock_guard recursive_lock_guard; -typedef std::lock_guard recursive_timed_lock_guard; +using lock_guard = std::lock_guard; +using recursive_lock_guard = std::lock_guard; +using recursive_timed_lock_guard = std::lock_guard; @@ -270,128 +269,9 @@ class spin_mutex { }; -typedef spin_mutex::lock_guard spin_lock; - - - -#if 0 - -// OLD CODE vvvvvvvv - - -/// Spinning reader/writer mutex. This is just like spin_mutex, except -/// that there are separate locking mechanisms for "writers" (exclusive -/// holders of the lock, presumably because they are modifying whatever -/// the lock is protecting) and "readers" (non-exclusive, non-modifying -/// tasks that may access the protectee simultaneously). -class spin_rw_mutex { -public: - /// Default constructor -- initialize to unlocked. - /// - spin_rw_mutex (void) { m_readers = 0; } - - ~spin_rw_mutex (void) { } - - /// Copy constructor -- initialize to unlocked. - /// - spin_rw_mutex (const spin_rw_mutex &) { m_readers = 0; } - - /// Assignment does not do anything, since lockedness should not - /// transfer. - const spin_rw_mutex& operator= (const spin_rw_mutex&) { return *this; } - - /// Acquire the reader lock. - /// - void read_lock () { - // Spin until there are no writers active - m_locked.lock(); - // Register ourself as a reader - ++m_readers; - // Release the lock, to let other readers work - m_locked.unlock(); - } - - /// Release the reader lock. - /// - void read_unlock () { - --m_readers; // it's atomic, no need to lock to release - } - - /// Acquire the writer lock. - /// - void write_lock () { - // Make sure no new readers (or writers) can start - m_locked.lock(); - // Spin until the last reader is done, at which point we will be - // the sole owners and nobody else (reader or writer) can acquire - // the resource until we release it. -#if OIIO_THREAD_ALLOW_DCLP - while (*(volatile int *)&m_readers > 0) - ; -#else - while (m_readers > 0) - ; -#endif - } - - /// Release the writer lock. - /// - void write_unlock () { - // Let other readers or writers get the lock - m_locked.unlock (); - } - - /// Acquire an exclusive ("writer") lock. - void lock () { write_lock(); } - - /// Release an exclusive ("writer") lock. - void unlock () { write_unlock(); } - - /// Acquire a shared ("reader") lock. - void lock_shared () { read_lock(); } - - /// Release a shared ("reader") lock. - void unlock_shared () { read_unlock(); } - - /// Helper class: scoped read lock for a spin_rw_mutex -- grabs the - /// read lock upon construction, releases the lock when it exits scope. - class read_lock_guard { - public: - read_lock_guard (spin_rw_mutex &fm) : m_fm(fm) { m_fm.read_lock(); } - ~read_lock_guard () { m_fm.read_unlock(); } - private: - read_lock_guard(); // Do not implement - read_lock_guard(const read_lock_guard& other); // Do not implement - read_lock_guard& operator = (const read_lock_guard& other); // Do not implement - spin_rw_mutex & m_fm; - }; - - /// Helper class: scoped write lock for a spin_rw_mutex -- grabs the - /// read lock upon construction, releases the lock when it exits scope. - class write_lock_guard { - public: - write_lock_guard (spin_rw_mutex &fm) : m_fm(fm) { m_fm.write_lock(); } - ~write_lock_guard () { m_fm.write_unlock(); } - private: - write_lock_guard(); // Do not implement - write_lock_guard(const write_lock_guard& other); // Do not implement - write_lock_guard& operator = (const write_lock_guard& other); // Do not implement - spin_rw_mutex & m_fm; - }; - -private: - OIIO_CACHE_ALIGN - spin_mutex m_locked; // write lock - char pad1_[OIIO_CACHE_LINE_SIZE-sizeof(spin_mutex)]; - OIIO_CACHE_ALIGN - atomic_int m_readers; // number of readers - char pad2_[OIIO_CACHE_LINE_SIZE-sizeof(atomic_int)]; -}; - +using spin_lock = spin_mutex::lock_guard; -#else -// vvv New spin rw lock Oct 2017 /// Spinning reader/writer mutex. This is just like spin_mutex, except /// that there are separate locking mechanisms for "writers" (exclusive @@ -511,11 +391,10 @@ class spin_rw_mutex { std::atomic m_bits { 0 }; }; -#endif -typedef spin_rw_mutex::read_lock_guard spin_rw_read_lock; -typedef spin_rw_mutex::write_lock_guard spin_rw_write_lock; +using spin_rw_read_lock = spin_rw_mutex::read_lock_guard; +using spin_rw_write_lock = spin_rw_mutex::write_lock_guard; @@ -859,4 +738,38 @@ class OIIO_UTIL_API task_set { }; +OIIO_NAMESPACE_3_1_END + + + +// Compatibility -- current version just reuses these old items +// clang-format off +#ifndef OIIO_DOXYGEN +OIIO_NAMESPACE_BEGIN +using v3_1::atomic_backoff; +using v3_1::default_thread_pool; +using v3_1::default_thread_pool_shutdown; +using v3_1::mutex_pool; +using v3_1::null_mutex; +using v3_1::null_lock; +using v3_1::pause; +using v3_1::spin_mutex; +using v3_1::spin_rw_mutex; +using v3_1::task_set; +using v3_1::thread_group; +using v3_1::thread_pool; +using v3_1::yield; + +using std::mutex; +using std::recursive_mutex; +using std::thread; + +using lock_guard = std::lock_guard; +using recursive_lock_guard = std::lock_guard; +using recursive_timed_lock_guard = std::lock_guard; +using spin_lock = spin_mutex::lock_guard; +using spin_rw_read_lock = spin_rw_mutex::read_lock_guard; +using spin_rw_write_lock = spin_rw_mutex::write_lock_guard; OIIO_NAMESPACE_END +#endif +// clang-format on diff --git a/src/include/OpenImageIO/tiffutils.h b/src/include/OpenImageIO/tiffutils.h index 0803634271..c631665b5d 100644 --- a/src/include/OpenImageIO/tiffutils.h +++ b/src/include/OpenImageIO/tiffutils.h @@ -130,7 +130,10 @@ enum TIFFTAG { EXIF_GAMMA = 42240, }; +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN /// Given a TIFF data type code (defined in tiff.h) and a count, return the /// equivalent TypeDesc where one exists. Return TypeUnknown if there is no @@ -250,5 +253,25 @@ OIIO_API const TagInfo* tag_lookup (string_view domain, int tag); OIIO_API const TagInfo* tag_lookup (string_view domain, string_view tagname); +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::decode_exif; +using v3_1::decode_icc_profile; +using v3_1::decode_iptc_iim; +using v3_1::decode_xmp; +using v3_1::encode_iptc_iim; +using v3_1::encode_xmp; +using v3_1::exif_tag_lookup; +using v3_1::tag_lookup; +using v3_1::tag_table; +using v3_1::TagInfo; +using v3_1::tiff_data_size; +using v3_1::tiff_datatype_to_typedesc; +using v3_1::tiff_dir_data; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/timer.h b/src/include/OpenImageIO/timer.h index bc5d361256..04929ab14f 100644 --- a/src/include/OpenImageIO/timer.h +++ b/src/include/OpenImageIO/timer.h @@ -30,7 +30,7 @@ #define OIIO_TIMER_LINUX_USE_clock_gettime 1 -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN /// Simple timer class. /// @@ -275,4 +275,4 @@ class ScopedTimer { -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/include/OpenImageIO/type_traits.h b/src/include/OpenImageIO/type_traits.h index a4ce7fd362..149f176544 100644 --- a/src/include/OpenImageIO/type_traits.h +++ b/src/include/OpenImageIO/type_traits.h @@ -15,8 +15,6 @@ #include #include -OIIO_NAMESPACE_BEGIN - // An enable_if helper to be used in template parameters which results in // much shorter symbols: https://godbolt.org/z/sWw4vP @@ -26,6 +24,8 @@ OIIO_NAMESPACE_BEGIN #endif +OIIO_NAMESPACE_3_1_BEGIN + /// has_size_method::value is true if T has a size() method and it returns /// an integral type. template struct has_size_method : std::false_type { }; @@ -45,6 +45,14 @@ template struct has_subscript()[0])>> : std::true_type { }; +OIIO_NAMESPACE_3_1_END + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::has_size_method; +using v3_1::has_subscript; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index 91aa88ccc9..04bf3f9a8c 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -34,7 +34,7 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN ///////////////////////////////////////////////////////////////////////////// /// A TypeDesc describes simple data types. @@ -434,7 +434,6 @@ template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETY template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; -class ustring; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; template struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::STRING; }; @@ -497,8 +496,6 @@ template<> struct TypeDescFromC { static const constexpr TypeDesc template constexpr TypeDesc TypeDescFromC_v = TypeDescFromC>::value(); -class ustringhash; // forward declaration - /// A template mechanism for getting C type of TypeDesc::BASETYPE. @@ -594,7 +591,7 @@ tostring(TypeDesc type, const void* data, const tostring_formatting& fmt = {}); /// * If dsttype is int32 or uint32: other integer types will do their best /// (caveat emptor if you mix signed/unsigned). Also a source string will /// convert to int if and only if its characters form a valid integer. -/// * If dsttype is float: inteegers and other float types will do +/// * If dsttype is float: integers and other float types will do /// their best conversion; strings will convert if and only if their /// characters form a valid float number. OIIO_UTIL_API bool @@ -602,19 +599,68 @@ convert_type(TypeDesc srctype, const void* src, TypeDesc dsttype, void* dst, int n = 1); +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::tostring_formatting; +using v3_1::tostring; +using v3_1::convert_type; +using v3_1::BaseTypeFromC; +using v3_1::BaseTypeFromC_v; +using v3_1::TypeDescFromC; +using v3_1::TypeDescFromC_v; +using v3_1::CType; + +using v3_1::TypeUnknown; +using v3_1::TypeFloat; +using v3_1::TypeColor; +using v3_1::TypePoint; +using v3_1::TypeVector; +using v3_1::TypeNormal; +using v3_1::TypeMatrix33; +using v3_1::TypeMatrix44; +using v3_1::TypeMatrix; +using v3_1::TypeFloat2; +using v3_1::TypeVector2; +using v3_1::TypeFloat4; +using v3_1::TypeVector4; +using v3_1::TypeString; +using v3_1::TypeInt; +using v3_1::TypeUInt; +using v3_1::TypeInt32; +using v3_1::TypeUInt32; +using v3_1::TypeInt16; +using v3_1::TypeUInt16; +using v3_1::TypeInt8; +using v3_1::TypeUInt8; +using v3_1::TypeInt64; +using v3_1::TypeUInt64; +using v3_1::TypeVector2i; +using v3_1::TypeVector3i; +using v3_1::TypeBox2; +using v3_1::TypeBox3; +using v3_1::TypeBox2i; +using v3_1::TypeBox3i; +using v3_1::TypeHalf; +using v3_1::TypeTimeCode; +using v3_1::TypeKeyCode; +using v3_1::TypeRational; +using v3_1::TypePointer; +using v3_1::TypeUstringhash; +#endif OIIO_NAMESPACE_END // Supply a fmtlib compatible custom formatter for TypeDesc. #if FMT_VERSION >= 100000 -FMT_BEGIN_NAMESPACE -template<> struct formatter : ostream_formatter {}; -FMT_END_NAMESPACE +template<> struct fmt::formatter : ostream_formatter {}; #else -FMT_BEGIN_NAMESPACE template <> -struct formatter { +struct fmt::formatter { // Parses format specification // C++14: constexpr auto parse(format_parse_context& ctx) const { auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11 @@ -640,5 +686,4 @@ struct formatter { return format_to(ctx.out(), "{}", t.c_str()); } }; -FMT_END_NAMESPACE #endif diff --git a/src/include/OpenImageIO/unordered_map_concurrent.h b/src/include/OpenImageIO/unordered_map_concurrent.h index 95eb6c6d65..2c82981185 100644 --- a/src/include/OpenImageIO/unordered_map_concurrent.h +++ b/src/include/OpenImageIO/unordered_map_concurrent.h @@ -9,7 +9,7 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { @@ -599,4 +599,12 @@ class unordered_map_concurrent { }; +OIIO_NAMESPACE_3_1_END + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::unordered_map_concurrent; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/ustring.h b/src/include/OpenImageIO/ustring.h index 2d82d6bd10..aba90ffdf5 100644 --- a/src/include/OpenImageIO/ustring.h +++ b/src/include/OpenImageIO/ustring.h @@ -25,8 +25,6 @@ #include -OIIO_NAMESPACE_BEGIN - // Feature tests #define OIIO_USTRING_HAS_USTRINGHASH 1 #define OIIO_USTRING_HAS_CTR_FROM_USTRINGHASH 1 @@ -34,8 +32,7 @@ OIIO_NAMESPACE_BEGIN #define OIIO_HAS_USTRINGHASH_FORMATTER 1 -class ustringhash; // forward declaration - +OIIO_NAMESPACE_3_1_BEGIN /// A ustring is an alternative to char* or std::string for storing @@ -1008,8 +1005,12 @@ inline ustring::ustring(ustringhash hash) } #endif +OIIO_NAMESPACE_3_1_END + +OIIO_NAMESPACE_BEGIN + /// ustring string literal operator inline ustring operator""_us(const char* str, std::size_t len) @@ -1025,8 +1026,11 @@ operator""_ush(const char* str, std::size_t len) return ustringhash(str, len); } +OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_BEGIN + /// Functor class to use for comparisons when sorting ustrings, if you /// want the strings sorted lexicographically. class ustringLess { @@ -1101,6 +1105,17 @@ to_string(const ustringhash& value) } // end namespace Strutil +OIIO_NAMESPACE_3_1_END + + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::iequals; +using v3_1::ustringLess; +using v3_1::ustringPtrIsLess; +#endif OIIO_NAMESPACE_END diff --git a/src/include/OpenImageIO/vecparam.h b/src/include/OpenImageIO/vecparam.h index b2bf70c6cc..bd47d78851 100644 --- a/src/include/OpenImageIO/vecparam.h +++ b/src/include/OpenImageIO/vecparam.h @@ -11,7 +11,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN // NOTE: These interoperable type templates were copied from the // [Imath project](http://github.com/AcademySoftwareFoundation/imath), @@ -388,4 +388,22 @@ using M33fParam = MatrixParam; using M44fParam = MatrixParam; +OIIO_NAMESPACE_3_1_END + + + +// Compatibility +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::has_double_subscript_RC; +using v3_1::has_subscript_N; +using v3_1::has_xy; +using v3_1::has_xyz; +using v3_1::has_xyzw; +using v3_1::M33fParam; +using v3_1::M44fParam; +using v3_1::MatrixParam; +using v3_1::V3fParam; +using v3_1::Vec3Param; +#endif OIIO_NAMESPACE_END diff --git a/src/include/imageio_pvt.h b/src/include/imageio_pvt.h index ae28898fa3..273375cd77 100644 --- a/src/include/imageio_pvt.h +++ b/src/include/imageio_pvt.h @@ -4,7 +4,7 @@ /// \file -/// Declarations for things that are used privately by ImageIO. +/// Declarations for things that are used privately by OpenImageIO. #ifndef OPENIMAGEIO_IMAGEIO_PVT_H @@ -17,11 +17,10 @@ OIIO_NAMESPACE_BEGIN - -namespace ImageBufAlgo { -struct PixelStats; -} - +// Note: Everything in pvt namespace is expected to be local to the library +// and does not appear in exported headers that client software will see. +// Therefore, it should all stay in the current namespace except where +// specifically noted. namespace pvt { diff --git a/src/libOpenImageIO/color_ocio.cpp b/src/libOpenImageIO/color_ocio.cpp index 03623221de..26f45c8d2b 100644 --- a/src/libOpenImageIO/color_ocio.cpp +++ b/src/libOpenImageIO/color_ocio.cpp @@ -29,7 +29,7 @@ namespace OCIO = OCIO_NAMESPACE; -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace { // Some test colors we use to interrogate transformations @@ -806,7 +806,7 @@ ColorConfig::Impl::init(string_view filename) filename = Sysutil::getenv("OCIO"); if (filename.empty() && !disable_builtin_configs) filename = "ocio://default"; - if (filename.size() && !Filesystem::exists(filename) + if (filename.size() && !OIIO::Filesystem::exists(filename) && !Strutil::istarts_with(filename, "ocio://")) { error("Requested non-existent OCIO config \"{}\"", filename); } else { @@ -867,7 +867,7 @@ ColorConfig::Impl::init(string_view filename) bool ColorConfig::reset(string_view filename) { - pvt::LoggedTimer logtime("ColorConfig::reset"); + OIIO::pvt::LoggedTimer logtime("ColorConfig::reset"); if (m_impl && (filename == getImpl()->configname() || (filename == "" @@ -2010,7 +2010,7 @@ ImageBufAlgo::colorconvert(ImageBuf& dst, const ImageBuf& src, string_view from, const ColorConfig* colorconfig, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::colorconvert"); + OIIO::pvt::LoggedTimer logtime("IBA::colorconvert"); if (from.empty() || from == "current") { from = src.spec().get_string_attribute("oiio:Colorspace", "scene_linear"); @@ -2071,7 +2071,7 @@ ImageBufAlgo::colormatrixtransform(ImageBuf& dst, const ImageBuf& src, M44fParam M, bool unpremult, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::colormatrixtransform"); + OIIO::pvt::LoggedTimer logtime("IBA::colormatrixtransform"); ColorProcessorHandle processor = ColorConfig::default_colorconfig().createMatrixTransform(M); logtime.stop(); // transition to other colorconvert @@ -2253,7 +2253,7 @@ ImageBufAlgo::colorconvert(ImageBuf& dst, const ImageBuf& src, const ColorProcessor* processor, bool unpremult, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::colorconvert"); + OIIO::pvt::LoggedTimer logtime("IBA::colorconvert"); // If the processor is NULL, return false (error) if (!processor) { dst.errorfmt( @@ -2318,7 +2318,7 @@ ImageBufAlgo::ociolook(ImageBuf& dst, const ImageBuf& src, string_view looks, bool inverse, string_view key, string_view value, const ColorConfig* colorconfig, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::ociolook"); + OIIO::pvt::LoggedTimer logtime("IBA::ociolook"); if (from.empty() || from == "current") { auto linearspace = colorconfig->resolve("scene_linear"); from = src.spec().get_string_attribute("oiio:Colorspace", linearspace); @@ -2381,7 +2381,7 @@ ImageBufAlgo::ociodisplay(ImageBuf& dst, const ImageBuf& src, bool inverse, string_view key, string_view value, const ColorConfig* colorconfig, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::ociodisplay"); + OIIO::pvt::LoggedTimer logtime("IBA::ociodisplay"); ColorProcessorHandle processor; { if (!colorconfig) @@ -2453,7 +2453,7 @@ ImageBufAlgo::ociofiletransform(ImageBuf& dst, const ImageBuf& src, const ColorConfig* colorconfig, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::ociofiletransform"); + OIIO::pvt::LoggedTimer logtime("IBA::ociofiletransform"); if (name.empty()) { dst.errorfmt("Unknown filetransform name"); return false; @@ -2512,7 +2512,7 @@ ImageBufAlgo::ocionamedtransform(ImageBuf& dst, const ImageBuf& src, const ColorConfig* colorconfig, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::ocionamedtransform"); + OIIO::pvt::LoggedTimer logtime("IBA::ocionamedtransform"); ColorProcessorHandle processor; { if (!colorconfig) diff --git a/src/libOpenImageIO/deepdata.cpp b/src/libOpenImageIO/deepdata.cpp index a06661d633..35b9ebc8c0 100644 --- a/src/libOpenImageIO/deepdata.cpp +++ b/src/libOpenImageIO/deepdata.cpp @@ -16,7 +16,7 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN // Each pixel has a capacity (number of samples allocated) and a number of @@ -32,6 +32,9 @@ OIIO_NAMESPACE_BEGIN class DeepData::Impl { // holds all the nontrivial stuff + // NOTE: Because the definition of DeepData::Impl is not exposed + // externally, it can change at will even though it's inside the v3_1 + // namespace. friend class DeepData; public: @@ -1273,4 +1276,4 @@ DeepData::occlusion_cull(int64_t pixel) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index 5b3252c88b..ccf63979a5 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -119,7 +119,10 @@ TagMap::mapname() const return m_impl->m_mapname; } +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN const TagInfo* tag_lookup(string_view domain, int tag) @@ -229,8 +232,11 @@ tiff_dir_data(const TIFFDirEntry& td, cspan data) return cspan(data.data() + begin, len); } +OIIO_NAMESPACE_3_1_END +OIIO_NAMESPACE_BEGIN + #if DEBUG_EXIF_READ || DEBUG_EXIF_WRITE static bool print_dir_entry(std::ostream& out, const TagMap& tagmap, @@ -613,7 +619,10 @@ pvt::gps_tagmap_ref() return T; } +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN cspan tag_table(string_view tablename) @@ -626,6 +635,10 @@ tag_table(string_view tablename) return cspan(tiff_tag_table); } +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN /// Add one EXIF directory entry's data to spec under the given 'name'. @@ -1152,6 +1165,10 @@ pvt::append_tiff_dir_entry(std::vector& dirs, } +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN bool decode_exif(string_view exif, ImageSpec& spec) @@ -1236,8 +1253,8 @@ decode_exif(cspan exif, ImageSpec& spec) if (makernote_offset > 0) { if (Strutil::iequals(spec.get_string_attribute("Make"), "Canon")) { if (!decode_ifd(exif, makernote_offset, spec, - pvt::canon_maker_tagmap_ref(), ifd_offsets_seen, - swab)) + OIIO::pvt::canon_maker_tagmap_ref(), + ifd_offsets_seen, swab)) return false; } // Now we can erase the attrib we used to pass the message about @@ -1360,7 +1377,7 @@ encode_exif(const ImageSpec& spec, std::vector& blob, // If we're a canon camera, construct the dirs for the Makernote, // with the data adding to the main blob. if (Strutil::iequals(spec.get_string_attribute("Make"), "Canon")) - pvt::encode_canon_makernote(blob, makerdirs, spec, tiffstart); + OIIO::pvt::encode_canon_makernote(blob, makerdirs, spec, tiffstart); #if DEBUG_EXIF_WRITE std::cerr << "Blob header size " << blob.size() << "\n"; @@ -1526,5 +1543,4 @@ exif_tag_lookup(string_view name, int& tag, int& tifftype, int& count) return true; } - -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/formatspec.cpp b/src/libOpenImageIO/formatspec.cpp index ec317e0f14..81374b9624 100644 --- a/src/libOpenImageIO/formatspec.cpp +++ b/src/libOpenImageIO/formatspec.cpp @@ -97,6 +97,10 @@ pvt::get_default_quantize(TypeDesc format, long long& quant_min, } } +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN ImageSpec::ImageSpec(TypeDesc format) noexcept @@ -659,8 +663,11 @@ ImageSpec::channelindex(string_view name) const return -1; } +OIIO_NAMESPACE_3_1_END +OIIO_NAMESPACE_BEGIN + std::string pvt::explain_justprint(const ParamValue& p, const void* extradata) { @@ -910,7 +917,10 @@ static ExplanationTableEntry explanation[] = { } // namespace +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN std::string ImageSpec::metadata_val(const ParamValue& p, bool human) @@ -1263,6 +1273,76 @@ ImageSpec::decode_compression_metadata(string_view defaultcomp, +void +ImageSpec::set_colorspace(string_view colorspace) +{ + ColorConfig::default_colorconfig().set_colorspace(*this, colorspace); + // Invalidate potentially contradictory metadata + erase_attribute("CICP"); +} + + + +ROI +get_roi(const ImageSpec& spec) +{ + return ROI(spec.x, spec.x + spec.width, spec.y, spec.y + spec.height, + spec.z, spec.z + spec.depth, 0, spec.nchannels); +} + + + +ROI +get_roi_full(const ImageSpec& spec) +{ + return ROI(spec.full_x, spec.full_x + spec.full_width, spec.full_y, + spec.full_y + spec.full_height, spec.full_z, + spec.full_z + spec.full_depth, 0, spec.nchannels); +} + + + +void +set_roi(ImageSpec& spec, const ROI& newroi) +{ + spec.x = newroi.xbegin; + spec.y = newroi.ybegin; + spec.z = newroi.zbegin; + spec.width = newroi.width(); + spec.height = newroi.height(); + spec.depth = newroi.depth(); +} + + + +void +set_roi_full(ImageSpec& spec, const ROI& newroi) +{ + spec.full_x = newroi.xbegin; + spec.full_y = newroi.ybegin; + spec.full_z = newroi.zbegin; + spec.full_width = newroi.width(); + spec.full_height = newroi.height(); + spec.full_depth = newroi.depth(); +} + + + +template<> +size_t +pvt::heapsize(const ImageSpec& is) +{ + size_t size = pvt::heapsize(is.channelformats); + size += pvt::heapsize(is.channelnames); + size += pvt::heapsize(is.extra_attribs); + return size; +} + +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN + bool pvt::check_texture_metadata_sanity(ImageSpec& spec) { @@ -1290,27 +1370,4 @@ pvt::check_texture_metadata_sanity(ImageSpec& spec) } - -void -ImageSpec::set_colorspace(string_view colorspace) -{ - ColorConfig::default_colorconfig().set_colorspace(*this, colorspace); - // Invalidate potentially contradictory metadata - erase_attribute("CICP"); -} - - - -template<> -size_t -pvt::heapsize(const ImageSpec& is) -{ - size_t size = pvt::heapsize(is.channelformats); - size += pvt::heapsize(is.channelnames); - size += pvt::heapsize(is.extra_attribs); - return size; -} - - - OIIO_NAMESPACE_END diff --git a/src/libOpenImageIO/icc.cpp b/src/libOpenImageIO/icc.cpp index a27ae35bbe..13d22478bc 100644 --- a/src/libOpenImageIO/icc.cpp +++ b/src/libOpenImageIO/icc.cpp @@ -232,7 +232,10 @@ extract(cspan iccdata, size_t& offset, ICCTag& result, } // namespace +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN bool decode_icc_profile(cspan iccdata, ImageSpec& spec, std::string& error) @@ -397,4 +400,4 @@ decode_icc_profile(cspan iccdata, ImageSpec& spec, std::string& error) return true; } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index eab61a75cc..e25c1f1fc2 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -38,52 +38,10 @@ std::atomic IB_total_open_time(0.0f); std::atomic IB_total_image_read_time(0.0f); } // namespace pvt +OIIO_NAMESPACE_END -ROI -get_roi(const ImageSpec& spec) -{ - return ROI(spec.x, spec.x + spec.width, spec.y, spec.y + spec.height, - spec.z, spec.z + spec.depth, 0, spec.nchannels); -} - - - -ROI -get_roi_full(const ImageSpec& spec) -{ - return ROI(spec.full_x, spec.full_x + spec.full_width, spec.full_y, - spec.full_y + spec.full_height, spec.full_z, - spec.full_z + spec.full_depth, 0, spec.nchannels); -} - - - -void -set_roi(ImageSpec& spec, const ROI& newroi) -{ - spec.x = newroi.xbegin; - spec.y = newroi.ybegin; - spec.z = newroi.zbegin; - spec.width = newroi.width(); - spec.height = newroi.height(); - spec.depth = newroi.depth(); -} - - - -void -set_roi_full(ImageSpec& spec, const ROI& newroi) -{ - spec.full_x = newroi.xbegin; - spec.full_y = newroi.ybegin; - spec.full_z = newroi.zbegin; - spec.full_width = newroi.width(); - spec.full_height = newroi.height(); - spec.full_depth = newroi.depth(); -} - - +OIIO_NAMESPACE_3_1_BEGIN span span_from_buffer(void* data, TypeDesc format, int nchannels, int width, @@ -615,7 +573,7 @@ ImageBufImpl::~ImageBufImpl() // Upon destruction, print uncaught errors to help users who don't know // how to properly check for errors. if (!m_err.empty() /* Note: safe becausethis is the dtr */ - && pvt::imagebuf_print_uncaught_errors) { + && OIIO::pvt::imagebuf_print_uncaught_errors) { OIIO::print( "An ImageBuf was destroyed with a pending error message that was never\n" "retrieved via ImageBuf::geterror(). This was the error message:\n{}\n", @@ -782,16 +740,16 @@ ImageBufImpl::new_pixels(ImageBuf::IBStorage storage, size_t size, m_bufspan = {}; } m_allocated_size = size; - pvt::IB_local_mem_current += m_allocated_size; - atomic_max(pvt::IB_local_mem_peak, - static_cast(pvt::IB_local_mem_current)); + OIIO::pvt::IB_local_mem_current += m_allocated_size; + atomic_max(OIIO::pvt::IB_local_mem_peak, + static_cast(OIIO::pvt::IB_local_mem_current)); } if (data && size) memcpy(m_pixels.get(), data, size); - if (pvt::oiio_print_debug > 1) + if (OIIO::pvt::oiio_print_debug > 1) OIIO::debugfmt("IB allocated {} MB, global IB memory now {} MB\n", - size >> 20, pvt::IB_local_mem_current >> 20); + size >> 20, OIIO::pvt::IB_local_mem_current >> 20); eval_contiguous(); return m_pixels.get(); } @@ -801,11 +759,11 @@ void ImageBufImpl::free_pixels() { if (m_allocated_size) { - if (pvt::oiio_print_debug > 1) + if (OIIO::pvt::oiio_print_debug > 1) OIIO::debugfmt("IB freed {} MB, global IB memory now {} MB\n", m_allocated_size >> 20, - pvt::IB_local_mem_current >> 20); - pvt::IB_local_mem_current -= m_allocated_size; + OIIO::pvt::IB_local_mem_current >> 20); + OIIO::pvt::IB_local_mem_current -= m_allocated_size; m_allocated_size = 0; } m_pixels.reset(); @@ -934,7 +892,7 @@ ImageBufImpl::reset(string_view filename, int subimage, int miplevel, { clear(); m_name = ustring(filename); - if (m_imagecache || pvt::imagebuf_use_imagecache) { + if (m_imagecache || OIIO::pvt::imagebuf_use_imagecache) { // Invalidate the image in cache. Do so unconditionally if there's a // chance that configuration hints may have changed. invalidate(m_name, config || m_configspec); @@ -1146,13 +1104,13 @@ ImageBufImpl::init_spec(string_view filename, int subimage, int miplevel, && m_current_subimage == subimage && m_current_miplevel == miplevel) return true; // Already done - pvt::LoggedTimer logtime("IB::init_spec"); + OIIO::pvt::LoggedTimer logtime("IB::init_spec"); m_name = filename; // If we weren't given an imagecache but "imagebuf:use_imagecache" // attribute was set, use a shared IC. - if (!m_imagecache && pvt::imagebuf_use_imagecache) + if (!m_imagecache && OIIO::pvt::imagebuf_use_imagecache) m_imagecache = ImageCache::create(true); if (m_imagecache) { @@ -1279,20 +1237,20 @@ ImageBufImpl::init_spec(string_view filename, int subimage, int miplevel, auto input = ImageInput::open(filename, m_configspec.get(), m_rioproxy); if (!input) { error("Could not open file: {}", OIIO::geterror()); - atomic_fetch_add(pvt::IB_total_open_time, float(timer())); + atomic_fetch_add(OIIO::pvt::IB_total_open_time, float(timer())); return false; } m_spec = input->spec(subimage, miplevel); m_nativespec = m_spec; if (input->has_error()) { errorfmt("Error reading: {}", input->geterror()); - atomic_fetch_add(pvt::IB_total_open_time, float(timer())); + atomic_fetch_add(OIIO::pvt::IB_total_open_time, float(timer())); return false; } if (m_spec.format == TypeUnknown) { errorfmt("Could not seek to subimage={} miplevel={}", subimage, miplevel); - atomic_fetch_add(pvt::IB_total_open_time, float(timer())); + atomic_fetch_add(OIIO::pvt::IB_total_open_time, float(timer())); return false; } m_badfile = false; @@ -1320,7 +1278,7 @@ ImageBufImpl::init_spec(string_view filename, int subimage, int miplevel, m_current_subimage = subimage; m_current_miplevel = miplevel; m_pixelaspect = m_spec.get_float_attribute("pixelaspectratio", 1.0f); - atomic_fetch_add(pvt::IB_total_open_time, float(timer())); + atomic_fetch_add(OIIO::pvt::IB_total_open_time, float(timer())); } return !m_badfile; } @@ -1374,7 +1332,7 @@ ImageBufImpl::read(int subimage, int miplevel, int chbegin, int chend, return false; } - pvt::LoggedTimer logtime("IB::read"); + OIIO::pvt::LoggedTimer logtime("IB::read"); m_current_subimage = subimage; m_current_miplevel = miplevel; if (chend < 0 || chend > nativespec().nchannels) @@ -1399,7 +1357,7 @@ ImageBufImpl::read(int subimage, int miplevel, int chbegin, int chend, } else { error(input->geterror()); } - atomic_fetch_add(pvt::IB_total_image_read_time, float(timer())); + atomic_fetch_add(OIIO::pvt::IB_total_image_read_time, float(timer())); return ok; } @@ -1511,7 +1469,7 @@ ImageBufImpl::read(int subimage, int miplevel, int chbegin, int chend, m_pixels_valid = false; error(OIIO::geterror()); } - atomic_fetch_add(pvt::IB_total_image_read_time, float(timer())); + atomic_fetch_add(OIIO::pvt::IB_total_image_read_time, float(timer())); // Since we have read in the entire image now, if we are using an // IOProxy, we invalidate any cache entry to avoid lifetime issues // related to the IOProxy. This helps to eliminate trouble emerging @@ -1623,7 +1581,7 @@ ImageBuf::write(ImageOutput* out, ProgressCallback progress_callback, } bool ok = true; ok &= m_impl->validate_pixels(); - pvt::LoggedTimer logtime("IB::write inner"); + OIIO::pvt::LoggedTimer logtime("IB::write inner"); if (out->supports("thumbnail") && has_thumbnail()) { auto thumb = get_thumbnail(); // Strutil::print("IB::write: has thumbnail ROI {}\n", thumb->roi()); @@ -1749,7 +1707,7 @@ ImageBuf::write(string_view _filename, TypeDesc dtype, string_view _fileformat, ProgressCallback progress_callback, void* progress_callback_data) const { - pvt::LoggedTimer logtime("IB::write"); + OIIO::pvt::LoggedTimer logtime("IB::write"); string_view filename = _filename.size() ? _filename : string_view(name()); string_view fileformat = _fileformat.size() ? _fileformat : filename; if (filename.size() == 0) { @@ -3700,4 +3658,4 @@ ImageBuf::unlock() const } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo.cpp b/src/libOpenImageIO/imagebufalgo.cpp index 483327093d..2b346737b7 100644 --- a/src/libOpenImageIO/imagebufalgo.cpp +++ b/src/libOpenImageIO/imagebufalgo.cpp @@ -57,7 +57,7 @@ /////////////////////////////////////////////////////////////////////////// -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN bool @@ -819,7 +819,7 @@ ImageBufAlgo::convolve(ImageBuf& dst, const ImageBuf& src, const ImageBuf& kernel, bool normalize, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::convolve"); + OIIO::pvt::LoggedTimer logtime("IBA::convolve"); if (!IBAprep(roi, &dst, &src, IBAprep_REQUIRE_SAME_NCHANNELS)) return false; bool ok; @@ -1130,7 +1130,7 @@ bool ImageBufAlgo::median_filter(ImageBuf& dst, const ImageBuf& src, int width, int height, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::median_filter"); + OIIO::pvt::LoggedTimer logtime("IBA::median_filter"); if (!IBAprep(roi, &dst, &src, IBAprep_REQUIRE_SAME_NCHANNELS | IBAprep_NO_SUPPORT_VOLUME)) return false; @@ -1212,7 +1212,7 @@ bool ImageBufAlgo::dilate(ImageBuf& dst, const ImageBuf& src, int width, int height, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::dilate"); + OIIO::pvt::LoggedTimer logtime("IBA::dilate"); if (!IBAprep(roi, &dst, &src, IBAprep_REQUIRE_SAME_NCHANNELS | IBAprep_NO_SUPPORT_VOLUME)) return false; @@ -1243,7 +1243,7 @@ bool ImageBufAlgo::erode(ImageBuf& dst, const ImageBuf& src, int width, int height, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::erode"); + OIIO::pvt::LoggedTimer logtime("IBA::erode"); if (!IBAprep(roi, &dst, &src, IBAprep_REQUIRE_SAME_NCHANNELS | IBAprep_NO_SUPPORT_VOLUME)) return false; @@ -1308,7 +1308,7 @@ hfft_(ImageBuf& dst, const ImageBuf& src, bool inverse, bool unitary, ROI roi, bool ImageBufAlgo::fft(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::fft"); + OIIO::pvt::LoggedTimer logtime("IBA::fft"); if (src.spec().depth > 1) { dst.errorfmt("ImageBufAlgo::fft does not support volume images"); return false; @@ -1379,7 +1379,7 @@ ImageBufAlgo::fft(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) bool ImageBufAlgo::ifft(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::ifft"); + OIIO::pvt::LoggedTimer logtime("IBA::ifft"); if (src.nchannels() != 2 || src.spec().format != TypeDesc::FLOAT) { dst.errorfmt("ifft can only be done on 2-channel float images"); return false; @@ -1505,7 +1505,7 @@ bool ImageBufAlgo::polar_to_complex(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::polar_to_complex"); + OIIO::pvt::LoggedTimer logtime("IBA::polar_to_complex"); if (src.nchannels() != 2) { dst.errorfmt("polar_to_complex can only be done on 2-channel"); return false; @@ -1542,7 +1542,7 @@ bool ImageBufAlgo::complex_to_polar(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::complex_to_polar"); + OIIO::pvt::LoggedTimer logtime("IBA::complex_to_polar"); if (src.nchannels() != 2) { dst.errorfmt("complex_to_polar can only be done on 2-channel"); return false; @@ -1670,4 +1670,4 @@ ImageBufAlgo::fillholes_pushpull(const ImageBuf& src, ROI roi, int nthreads) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_addsub.cpp b/src/libOpenImageIO/imagebufalgo_addsub.cpp index 7f85acc039..c7a4d83e9c 100644 --- a/src/libOpenImageIO/imagebufalgo_addsub.cpp +++ b/src/libOpenImageIO/imagebufalgo_addsub.cpp @@ -21,7 +21,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -88,7 +88,7 @@ bool ImageBufAlgo::add(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::add"); + OIIO::pvt::LoggedTimer logtime("IBA::add"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B)) @@ -175,7 +175,7 @@ bool ImageBufAlgo::sub(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::sub"); + OIIO::pvt::LoggedTimer logtime("IBA::sub"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B)) @@ -245,4 +245,4 @@ ImageBufAlgo::sub(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_channels.cpp b/src/libOpenImageIO/imagebufalgo_channels.cpp index 0946af3f4a..f8416ecb55 100644 --- a/src/libOpenImageIO/imagebufalgo_channels.cpp +++ b/src/libOpenImageIO/imagebufalgo_channels.cpp @@ -21,7 +21,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -63,7 +63,7 @@ ImageBufAlgo::channels(ImageBuf& dst, const ImageBuf& src, int nchannels, return ok; } - pvt::LoggedTimer logtime("IBA::channels"); + OIIO::pvt::LoggedTimer logtime("IBA::channels"); // Not intended to create 0-channel images. if (nchannels <= 0) { dst.errorfmt("{}-channel images not supported", nchannels); @@ -228,7 +228,7 @@ bool ImageBufAlgo::channel_append(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::channel_append"); + OIIO::pvt::LoggedTimer logtime("IBA::channel_append"); // If the region is not defined, set it to the union of the valid // regions of the two source images. if (!roi.defined()) @@ -294,4 +294,4 @@ ImageBufAlgo::channel_append(const ImageBuf& A, const ImageBuf& B, ROI roi, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_compare.cpp b/src/libOpenImageIO/imagebufalgo_compare.cpp index f8cc4f58fc..9d96280fb6 100644 --- a/src/libOpenImageIO/imagebufalgo_compare.cpp +++ b/src/libOpenImageIO/imagebufalgo_compare.cpp @@ -21,7 +21,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN void @@ -187,7 +187,7 @@ computePixelStats_(const ImageBuf& src, ImageBufAlgo::PixelStats& stats, ImageBufAlgo::PixelStats ImageBufAlgo::computePixelStats(const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::computePixelStats"); + OIIO::pvt::LoggedTimer logtimer("IBA::computePixelStats"); ImageBufAlgo::PixelStats stats; if (!roi.defined()) roi = get_roi(src.spec()); @@ -347,7 +347,7 @@ ImageBufAlgo::compare(const ImageBuf& A, const ImageBuf& B, float failthresh, float warnthresh, float failrelative, float warnrelative, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::compare"); + OIIO::pvt::LoggedTimer logtimer("IBA::compare"); ImageBufAlgo::CompareResults result; result.error = true; @@ -463,7 +463,7 @@ bool ImageBufAlgo::isConstantColor(const ImageBuf& src, float threshold, span color, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::isConstantColor"); + OIIO::pvt::LoggedTimer logtimer("IBA::isConstantColor"); // If no ROI is defined, use the data window of src. if (!roi.defined()) roi = get_roi(src.spec()); @@ -519,7 +519,7 @@ bool ImageBufAlgo::isConstantChannel(const ImageBuf& src, int channel, float val, float threshold, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::isConstantChannel"); + OIIO::pvt::LoggedTimer logtimer("IBA::isConstantChannel"); // If no ROI is defined, use the data window of src. if (!roi.defined()) roi = get_roi(src.spec()); @@ -580,7 +580,7 @@ bool ImageBufAlgo::isMonochrome(const ImageBuf& src, float threshold, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::isMonochrome"); + OIIO::pvt::LoggedTimer logtimer("IBA::isMonochrome"); // If no ROI is defined, use the data window of src. if (!roi.defined()) roi = get_roi(src.spec()); @@ -632,7 +632,7 @@ ImageBufAlgo::color_count(const ImageBuf& src, imagesize_t* count, int ncolors, cspan color, cspan eps, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::color_count"); + OIIO::pvt::LoggedTimer logtimer("IBA::color_count"); // If no ROI is defined, use the data window of src. if (!roi.defined()) roi = get_roi(src.spec()); @@ -698,7 +698,7 @@ ImageBufAlgo::color_range_check(const ImageBuf& src, imagesize_t* lowcount, imagesize_t* inrangecount, cspan low, cspan high, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::color_range_check"); + OIIO::pvt::LoggedTimer logtimer("IBA::color_range_check"); // If no ROI is defined, use the data window of src. if (!roi.defined()) roi = get_roi(src.spec()); @@ -753,7 +753,7 @@ deep_nonempty_region(const ImageBuf& src, ROI roi) ROI ImageBufAlgo::nonzero_region(const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::nonzero_region"); + OIIO::pvt::LoggedTimer logtimer("IBA::nonzero_region"); roi = roi_intersection(roi, src.roi()); if (src.deep()) { @@ -860,7 +860,7 @@ std::string ImageBufAlgo::computePixelHashSHA1(const ImageBuf& src, string_view extrainfo, ROI roi, int blocksize, int nthreads) { - pvt::LoggedTimer logtimer("IBA::computePixelHashSHA1"); + OIIO::pvt::LoggedTimer logtimer("IBA::computePixelHashSHA1"); if (!roi.defined()) roi = get_roi(src.spec()); @@ -941,7 +941,7 @@ std::vector ImageBufAlgo::histogram(const ImageBuf& src, int channel, int bins, float min, float max, bool ignore_empty, ROI roi, int nthreads) { - pvt::LoggedTimer logtimer("IBA::histogram"); + OIIO::pvt::LoggedTimer logtimer("IBA::histogram"); std::vector h; // Sanity checks @@ -979,4 +979,4 @@ ImageBufAlgo::histogram(const ImageBuf& src, int channel, int bins, float min, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_copy.cpp b/src/libOpenImageIO/imagebufalgo_copy.cpp index 0a98cf8905..51a138596f 100644 --- a/src/libOpenImageIO/imagebufalgo_copy.cpp +++ b/src/libOpenImageIO/imagebufalgo_copy.cpp @@ -16,7 +16,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -105,7 +105,7 @@ bool ImageBufAlgo::paste(ImageBuf& dst, int xbegin, int ybegin, int zbegin, int chbegin, const ImageBuf& src, ROI srcroi, int nthreads) { - pvt::LoggedTimer logtime("IBA::paste"); + OIIO::pvt::LoggedTimer logtime("IBA::paste"); if (!srcroi.defined()) srcroi = get_roi(src.spec()); @@ -189,7 +189,7 @@ bool ImageBufAlgo::copy(ImageBuf& dst, const ImageBuf& src, TypeDesc convert, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::copy"); + OIIO::pvt::LoggedTimer logtime("IBA::copy"); if (&dst == &src) // trivial copy to self return true; @@ -253,7 +253,7 @@ ImageBufAlgo::copy(const ImageBuf& src, TypeDesc convert, ROI roi, int nthreads) bool ImageBufAlgo::crop(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::crop"); + OIIO::pvt::LoggedTimer logtime("IBA::crop"); dst.clear(); roi.chend = std::min(roi.chend, src.nchannels()); if (!IBAprep(roi, &dst, &src, IBAprep_SUPPORT_DEEP)) @@ -307,7 +307,7 @@ ImageBufAlgo::crop(const ImageBuf& src, ROI roi, int nthreads) bool ImageBufAlgo::cut(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - // pvt::LoggedTimer logtime("IBA::cut"); + // OIIO::pvt::LoggedTimer logtime("IBA::cut"); // Don't log, because all the work is inside crop, which already logs bool ok = crop(dst, src, roi, nthreads); if (!ok) @@ -369,7 +369,7 @@ bool ImageBufAlgo::circular_shift(ImageBuf& dst, const ImageBuf& src, int xshift, int yshift, int zshift, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::circular_shift"); + OIIO::pvt::LoggedTimer logtime("IBA::circular_shift"); if (!IBAprep(roi, &dst, &src)) return false; bool ok; @@ -395,4 +395,4 @@ ImageBufAlgo::circular_shift(const ImageBuf& src, int xshift, int yshift, -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_deep.cpp b/src/libOpenImageIO/imagebufalgo_deep.cpp index b2195583a3..ee8cacb342 100644 --- a/src/libOpenImageIO/imagebufalgo_deep.cpp +++ b/src/libOpenImageIO/imagebufalgo_deep.cpp @@ -20,7 +20,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN // FIXME -- NOT CORRECT! This code assumes sorted, non-overlapping samples. @@ -88,7 +88,7 @@ flatten_(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) bool ImageBufAlgo::flatten(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::flatten"); + OIIO::pvt::LoggedTimer logtime("IBA::flatten"); if (!src.deep()) { // For some reason, we were asked to flatten an already-flat image. // So just copy it. @@ -138,7 +138,7 @@ bool ImageBufAlgo::deepen(ImageBuf& dst, const ImageBuf& src, float zvalue, ROI roi, int /*nthreads*/) { - pvt::LoggedTimer logtime("IBA::deepen"); + OIIO::pvt::LoggedTimer logtime("IBA::deepen"); if (src.deep()) { // For some reason, we were asked to deepen an already-deep image. // So just copy it. @@ -242,7 +242,7 @@ bool ImageBufAlgo::deep_merge(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B, bool occlusion_cull, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::deep_merge"); + OIIO::pvt::LoggedTimer logtime("IBA::deep_merge"); if (!A.deep() || !B.deep()) { // For some reason, we were asked to merge a flat image. dst.errorfmt("deep_merge can only be performed on deep images"); @@ -366,7 +366,7 @@ bool ImageBufAlgo::deep_holdout(ImageBuf& dst, const ImageBuf& src, const ImageBuf& thresh, ROI roi, int /*nthreads*/) { - pvt::LoggedTimer logtime("IBA::deep_holdout"); + OIIO::pvt::LoggedTimer logtime("IBA::deep_holdout"); if (!src.deep() || !thresh.deep()) { dst.errorfmt("deep_holdout can only be performed on deep images"); return false; @@ -444,4 +444,4 @@ ImageBufAlgo::deep_holdout(const ImageBuf& src, const ImageBuf& thresh, ROI roi, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_demosaic.cpp b/src/libOpenImageIO/imagebufalgo_demosaic.cpp index f63ba3e148..9a34554b8d 100644 --- a/src/libOpenImageIO/imagebufalgo_demosaic.cpp +++ b/src/libOpenImageIO/imagebufalgo_demosaic.cpp @@ -12,7 +12,7 @@ #include "imagebufalgo_demosaic_prv.h" #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace { @@ -966,7 +966,7 @@ demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, ROI roi, int nthreads) { bool ok = false; - pvt::LoggedTimer logtime("IBA::demosaic"); + OIIO::pvt::LoggedTimer logtime("IBA::demosaic"); std::string pattern; std::string algorithm; @@ -1234,4 +1234,4 @@ mosaic_uint8(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, } // namespace ImageBufAlgo -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_demosaic_prv.h b/src/libOpenImageIO/imagebufalgo_demosaic_prv.h index 204a5f7bb2..9f6678cb3b 100644 --- a/src/libOpenImageIO/imagebufalgo_demosaic_prv.h +++ b/src/libOpenImageIO/imagebufalgo_demosaic_prv.h @@ -7,7 +7,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace ImageBufAlgo { @@ -33,4 +33,4 @@ mosaic_uint8(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, } // namespace ImageBufAlgo -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_draw.cpp b/src/libOpenImageIO/imagebufalgo_draw.cpp index 39460bd04f..d3236eba7f 100644 --- a/src/libOpenImageIO/imagebufalgo_draw.cpp +++ b/src/libOpenImageIO/imagebufalgo_draw.cpp @@ -94,11 +94,16 @@ fill_corners_(ImageBuf& dst, const float* topleft, const float* topright, return true; } +OIIO_NAMESPACE_END + + + +OIIO_NAMESPACE_3_1_BEGIN bool ImageBufAlgo::fill(ImageBuf& dst, cspan pixel, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::fill"); + OIIO::pvt::LoggedTimer logtime("IBA::fill"); if (!IBAprep(roi, &dst)) return false; bool ok; @@ -113,7 +118,7 @@ bool ImageBufAlgo::fill(ImageBuf& dst, cspan top, cspan bottom, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::fill"); + OIIO::pvt::LoggedTimer logtime("IBA::fill"); if (!IBAprep(roi, &dst)) return false; bool ok; @@ -130,7 +135,7 @@ ImageBufAlgo::fill(ImageBuf& dst, cspan topleft, cspan topright, cspan bottomleft, cspan bottomright, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::fill"); + OIIO::pvt::LoggedTimer logtime("IBA::fill"); if (!IBAprep(roi, &dst)) return false; bool ok; @@ -185,7 +190,7 @@ ImageBufAlgo::fill(cspan topleft, cspan topright, bool ImageBufAlgo::zero(ImageBuf& dst, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::zero"); + OIIO::pvt::LoggedTimer logtime("IBA::zero"); if (!IBAprep(roi, &dst)) return false; OIIO_ASSERT(dst.localpixels()); @@ -243,7 +248,7 @@ bool ImageBufAlgo::render_point(ImageBuf& dst, int x, int y, cspan color, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::render_point"); + OIIO::pvt::LoggedTimer logtime("IBA::render_point"); if (!IBAprep(roi, &dst)) return false; IBA_FIX_PERCHAN_LEN_DEF(color, dst.nchannels()); @@ -368,7 +373,7 @@ ImageBufAlgo::render_line(ImageBuf& dst, int x1, int y1, int x2, int y2, cspan color, bool skip_first_point, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::render_line"); + OIIO::pvt::LoggedTimer logtime("IBA::render_line"); if (!IBAprep(roi, &dst)) return false; IBA_FIX_PERCHAN_LEN_DEF(color, dst.nchannels()); @@ -426,7 +431,7 @@ bool ImageBufAlgo::render_box(ImageBuf& dst, int x1, int y1, int x2, int y2, cspan color, bool fill, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::render_box"); + OIIO::pvt::LoggedTimer logtime("IBA::render_box"); if (!IBAprep(roi, &dst)) return false; IBA_FIX_PERCHAN_LEN_DEF(color, dst.nchannels()); @@ -505,7 +510,7 @@ ImageBufAlgo::checker(ImageBuf& dst, int width, int height, int depth, cspan color1, cspan color2, int xoffset, int yoffset, int zoffset, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::checker"); + OIIO::pvt::LoggedTimer logtime("IBA::checker"); if (!IBAprep(roi, &dst)) return false; IBA_FIX_PERCHAN_LEN_DEF(color1, dst.nchannels()); @@ -636,6 +641,7 @@ static bool noise_blue_(ImageBuf& dst, float min, float max, bool mono, int seed, ROI roi, int nthreads) { + using OIIO::pvt::bluenoise_4chan_ptr; ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { for (ImageBuf::Iterator p(dst, roi); !p.done(); ++p) { float n = 0.0; @@ -643,8 +649,8 @@ noise_blue_(ImageBuf& dst, float min, float max, bool mono, int seed, ROI roi, for (int c = roi.chbegin; c < roi.chend; ++c) { if (c == roi.chbegin || !mono) { if (!bn || !(c & 3)) - bn = pvt::bluenoise_4chan_ptr(p.x(), p.y(), p.z(), - roi.chbegin & ~3, seed); + bn = bluenoise_4chan_ptr(p.x(), p.y(), p.z(), + roi.chbegin & ~3, seed); n = lerp(min, max, bn[c & 3]); } p[c] = p[c] + n; @@ -660,7 +666,7 @@ bool ImageBufAlgo::noise(ImageBuf& dst, string_view noisetype, float A, float B, bool mono, int seed, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::noise"); + OIIO::pvt::LoggedTimer logtime("IBA::noise"); if (!IBAprep(roi, &dst)) return false; bool ok; @@ -709,7 +715,8 @@ namespace { inline ImageSpec bnspec() { - ImageSpec spec(pvt::bntable_res, pvt::bntable_res, 4, TypeFloat); + using OIIO::pvt::bntable_res; + ImageSpec spec(bntable_res, bntable_res, 4, TypeFloat); spec.channelnames = { "X", "Y", "Z", "W" }; spec.alpha_channel = -1; return spec; @@ -720,13 +727,16 @@ const ImageBuf& ImageBufAlgo::bluenoise_image() { // This ImageBuf "wraps" the table. - using namespace pvt; + using namespace OIIO::pvt; static ImageBuf img(bnspec(), make_cspan(&bluenoise_table[0][0][0], bntable_res * bntable_res * 4)); return img; } +OIIO_NAMESPACE_3_1_END + +OIIO_NAMESPACE_BEGIN static std::vector font_search_dirs; static std::vector all_font_files; @@ -1114,11 +1124,15 @@ resolve_font(string_view font_, std::string& result) #endif +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN ROI ImageBufAlgo::text_size(string_view text, int fontsize, string_view font_) { - pvt::LoggedTimer logtime("IBA::text_size"); + OIIO::pvt::LoggedTimer logtime("IBA::text_size"); ROI size; #ifdef USE_FREETYPE // Thread safety @@ -1164,7 +1178,7 @@ ImageBufAlgo::render_text(ImageBuf& R, int x, int y, string_view text, TextAlignY aligny, int shadow, ROI roi, int /*nthreads*/) { - pvt::LoggedTimer logtime("IBA::render_text"); + OIIO::pvt::LoggedTimer logtime("IBA::render_text"); if (R.spec().depth > 1) { R.errorfmt("ImageBufAlgo::render_text does not support volume images"); return false; @@ -1316,8 +1330,11 @@ ImageBufAlgo::render_text(ImageBuf& R, int x, int y, string_view text, #endif } +OIIO_NAMESPACE_3_1_END +OIIO_NAMESPACE_BEGIN + const std::vector& pvt::font_family_list() { diff --git a/src/libOpenImageIO/imagebufalgo_mad.cpp b/src/libOpenImageIO/imagebufalgo_mad.cpp index 8c484826e2..5707fcd6ac 100644 --- a/src/libOpenImageIO/imagebufalgo_mad.cpp +++ b/src/libOpenImageIO/imagebufalgo_mad.cpp @@ -15,7 +15,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN @@ -142,7 +142,7 @@ bool ImageBufAlgo::mad(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, Image_or_Const C_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::mad"); + OIIO::pvt::LoggedTimer logtime("IBA::mad"); // Canonicalize so that if one of A,B is a constant, A is an image. if (A_.is_val() && B_.is_img()) // canonicalize to A_img, B_val @@ -251,4 +251,4 @@ ImageBufAlgo::invert(const ImageBuf& A, ROI roi, int nthreads) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_minmaxchan.cpp b/src/libOpenImageIO/imagebufalgo_minmaxchan.cpp index 1025220a98..6af4281eae 100644 --- a/src/libOpenImageIO/imagebufalgo_minmaxchan.cpp +++ b/src/libOpenImageIO/imagebufalgo_minmaxchan.cpp @@ -18,7 +18,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -43,7 +43,7 @@ minchan_impl(ImageBuf& R, const ImageBuf& A, ROI roi, int nthreads) bool ImageBufAlgo::minchan(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::minchan"); + OIIO::pvt::LoggedTimer logtime("IBA::minchan"); if (!roi.defined()) roi = get_roi(src.spec()); roi.chend = std::min(roi.chend, src.nchannels()); @@ -95,7 +95,7 @@ maxchan_impl(ImageBuf& R, const ImageBuf& A, ROI roi, int nthreads) bool ImageBufAlgo::maxchan(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::maxchan"); + OIIO::pvt::LoggedTimer logtime("IBA::maxchan"); if (!roi.defined()) roi = get_roi(src.spec()); roi.chend = std::min(roi.chend, src.nchannels()); @@ -124,4 +124,4 @@ ImageBufAlgo::maxchan(const ImageBuf& src, ROI roi, int nthreads) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_muldiv.cpp b/src/libOpenImageIO/imagebufalgo_muldiv.cpp index e393c8a22a..4fa1a6cba0 100644 --- a/src/libOpenImageIO/imagebufalgo_muldiv.cpp +++ b/src/libOpenImageIO/imagebufalgo_muldiv.cpp @@ -22,7 +22,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -47,7 +47,7 @@ bool ImageBufAlgo::scale(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B, KWArgs options, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::scale"); + OIIO::pvt::LoggedTimer logtime("IBA::scale"); bool ok = false; if (B.nchannels() == 1) { if (IBAprep(roi, &dst, &A, &B)) @@ -147,7 +147,7 @@ bool ImageBufAlgo::mul(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::mul"); + OIIO::pvt::LoggedTimer logtime("IBA::mul"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B, IBAprep_CLAMP_MUTUAL_NCHANNELS)) @@ -220,7 +220,7 @@ bool ImageBufAlgo::div(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::div"); + OIIO::pvt::LoggedTimer logtime("IBA::div"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B, IBAprep_CLAMP_MUTUAL_NCHANNELS)) @@ -276,4 +276,4 @@ ImageBufAlgo::div(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads) -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_orient.cpp b/src/libOpenImageIO/imagebufalgo_orient.cpp index 9a758d2d86..fb1e656ce8 100644 --- a/src/libOpenImageIO/imagebufalgo_orient.cpp +++ b/src/libOpenImageIO/imagebufalgo_orient.cpp @@ -16,7 +16,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -45,7 +45,7 @@ ImageBufAlgo::flip(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) tmp.swap(const_cast(src)); return flip(dst, tmp, roi, nthreads); } - pvt::LoggedTimer logtime("IBA::flip"); + OIIO::pvt::LoggedTimer logtime("IBA::flip"); ROI src_roi = roi.defined() ? roi : src.roi(); ROI src_roi_full = src.roi_full(); @@ -95,7 +95,7 @@ ImageBufAlgo::flop(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) return flop(dst, tmp, roi, nthreads); } - pvt::LoggedTimer logtime("IBA::flop"); + OIIO::pvt::LoggedTimer logtime("IBA::flop"); ROI src_roi = roi.defined() ? roi : src.roi(); ROI src_roi_full = src.roi_full(); int offset = src_roi.xbegin - src_roi_full.xbegin; @@ -167,7 +167,7 @@ ImageBufAlgo::rotate90(ImageBuf& dst, const ImageBuf& src, ROI roi, return rotate90(dst, tmp, roi, nthreads); } - pvt::LoggedTimer logtime("IBA::rotate90"); + OIIO::pvt::LoggedTimer logtime("IBA::rotate90"); ROI src_roi = roi.defined() ? roi : src.roi(); ROI src_roi_full = src.roi_full(); @@ -230,7 +230,7 @@ ImageBufAlgo::rotate180(ImageBuf& dst, const ImageBuf& src, ROI roi, return rotate180(dst, tmp, roi, nthreads); } - pvt::LoggedTimer logtime("IBA::rotate180"); + OIIO::pvt::LoggedTimer logtime("IBA::rotate180"); ROI src_roi = roi.defined() ? roi : src.roi(); ROI src_roi_full = src.roi_full(); int xoffset = src_roi.xbegin - src_roi_full.xbegin; @@ -281,7 +281,7 @@ ImageBufAlgo::rotate270(ImageBuf& dst, const ImageBuf& src, ROI roi, return rotate270(dst, tmp, roi, nthreads); } - pvt::LoggedTimer logtime("IBA::rotate270"); + OIIO::pvt::LoggedTimer logtime("IBA::rotate270"); ROI src_roi = roi.defined() ? roi : src.roi(); ROI src_roi_full = src.roi_full(); @@ -420,7 +420,7 @@ bool ImageBufAlgo::transpose(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::transpose"); + OIIO::pvt::LoggedTimer logtime("IBA::transpose"); if (!roi.defined()) roi = get_roi(src.spec()); roi.chend = std::min(roi.chend, src.nchannels()); @@ -459,4 +459,4 @@ ImageBufAlgo::transpose(const ImageBuf& src, ROI roi, int nthreads) return result; } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_pixelmath.cpp b/src/libOpenImageIO/imagebufalgo_pixelmath.cpp index 33c98a709a..04daeaad27 100644 --- a/src/libOpenImageIO/imagebufalgo_pixelmath.cpp +++ b/src/libOpenImageIO/imagebufalgo_pixelmath.cpp @@ -22,7 +22,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN template @@ -63,7 +63,7 @@ bool ImageBufAlgo::min(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::min"); + OIIO::pvt::LoggedTimer logtime("IBA::min"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B)) @@ -160,7 +160,7 @@ bool ImageBufAlgo::max(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::max"); + OIIO::pvt::LoggedTimer logtime("IBA::max"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B)) @@ -245,7 +245,7 @@ bool ImageBufAlgo::clamp(ImageBuf& dst, const ImageBuf& src, cspan min, cspan max, bool clampalpha01, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::clamp"); + OIIO::pvt::LoggedTimer logtime("IBA::clamp"); if (!IBAprep(roi, &dst, &src)) return false; const float big = std::numeric_limits::max(); @@ -313,7 +313,7 @@ bool ImageBufAlgo::absdiff(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::absdiff"); + OIIO::pvt::LoggedTimer logtime("IBA::absdiff"); if (!IBAprep(roi, &dst, A_.imgptr(), B_.imgptr(), nullptr, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; @@ -412,7 +412,7 @@ bool ImageBufAlgo::pow(ImageBuf& dst, const ImageBuf& A, cspan b, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::pow"); + OIIO::pvt::LoggedTimer logtime("IBA::pow"); if (!IBAprep(roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; IBA_FIX_PERCHAN_LEN_DEF(b, dst.nchannels()); @@ -524,7 +524,7 @@ bool ImageBufAlgo::channel_sum(ImageBuf& dst, const ImageBuf& src, cspan weights, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::channel_sum"); + OIIO::pvt::LoggedTimer logtime("IBA::channel_sum"); if (!roi.defined()) roi = get_roi(src.spec()); roi.chend = std::min(roi.chend, src.nchannels()); @@ -742,7 +742,7 @@ bool ImageBufAlgo::rangecompress(ImageBuf& dst, const ImageBuf& src, bool useluma, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::rangecompress"); + OIIO::pvt::LoggedTimer logtime("IBA::rangecompress"); if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; bool ok; @@ -758,7 +758,7 @@ bool ImageBufAlgo::rangeexpand(ImageBuf& dst, const ImageBuf& src, bool useluma, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::rangeexpand"); + OIIO::pvt::LoggedTimer logtime("IBA::rangeexpand"); if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; bool ok; @@ -838,7 +838,7 @@ bool ImageBufAlgo::unpremult(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::unpremult"); + OIIO::pvt::LoggedTimer logtime("IBA::unpremult"); if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; if (src.spec().alpha_channel < 0 @@ -917,7 +917,7 @@ premult_(ImageBuf& R, const ImageBuf& A, bool preserve_alpha0, ROI roi, bool ImageBufAlgo::premult(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::premult"); + OIIO::pvt::LoggedTimer logtime("IBA::premult"); if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; if (src.spec().alpha_channel < 0) { @@ -953,7 +953,7 @@ bool ImageBufAlgo::repremult(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::repremult"); + OIIO::pvt::LoggedTimer logtime("IBA::repremult"); if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; if (src.spec().alpha_channel < 0) { @@ -1074,7 +1074,7 @@ ImageBufAlgo::contrast_remap(ImageBuf& dst, const ImageBuf& src, cspan scontrast, cspan sthresh, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::contrast_remap"); + OIIO::pvt::LoggedTimer logtime("IBA::contrast_remap"); if (!IBAprep(roi, &dst, &src)) return false; // Force all the input spans to have values for all channels. @@ -1148,7 +1148,7 @@ bool ImageBufAlgo::saturate(ImageBuf& dst, const ImageBuf& src, float scale, int firstchannel, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::saturate"); + OIIO::pvt::LoggedTimer logtime("IBA::saturate"); if (!IBAprep(roi, &dst, &src, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; @@ -1230,7 +1230,7 @@ ImageBufAlgo::color_map(ImageBuf& dst, const ImageBuf& src, int srcchannel, int nknots, int channels, cspan knots, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::color_map"); + OIIO::pvt::LoggedTimer logtime("IBA::color_map"); if (srcchannel >= src.nchannels()) { dst.errorfmt("invalid source channel selected"); return false; @@ -1358,7 +1358,7 @@ bool ImageBufAlgo::color_map(ImageBuf& dst, const ImageBuf& src, int srcchannel, string_view mapname, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::color_map"); + OIIO::pvt::LoggedTimer logtime("IBA::color_map"); if (srcchannel >= src.nchannels()) { dst.errorfmt("invalid source channel selected"); return false; @@ -1565,7 +1565,7 @@ ImageBufAlgo::fixNonFinite(ImageBuf& dst, const ImageBuf& src, NonFiniteFixMode mode, int* pixelsFixed, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::fixNonFinite"); + OIIO::pvt::LoggedTimer logtime("IBA::fixNonFinite"); if (mode != ImageBufAlgo::NONFINITE_NONE && mode != ImageBufAlgo::NONFINITE_BLACK && mode != ImageBufAlgo::NONFINITE_BOX3 @@ -1750,7 +1750,7 @@ bool ImageBufAlgo::over(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::over"); + OIIO::pvt::LoggedTimer logtime("IBA::over"); if (!IBAprep(roi, &dst, &A, &B, NULL, IBAprep_REQUIRE_ALPHA | IBAprep_REQUIRE_SAME_NCHANNELS)) return false; @@ -1793,7 +1793,7 @@ bool ImageBufAlgo::zover(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B, bool z_zeroisinf, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::zover"); + OIIO::pvt::LoggedTimer logtime("IBA::zover"); if (!IBAprep(roi, &dst, &A, &B, NULL, IBAprep_REQUIRE_ALPHA | IBAprep_REQUIRE_Z | IBAprep_REQUIRE_SAME_NCHANNELS)) @@ -1819,4 +1819,4 @@ ImageBufAlgo::zover(const ImageBuf& A, const ImageBuf& B, bool z_zeroisinf, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_test.cpp b/src/libOpenImageIO/imagebufalgo_test.cpp index 3ea14c46bc..940e2a8ff5 100644 --- a/src/libOpenImageIO/imagebufalgo_test.cpp +++ b/src/libOpenImageIO/imagebufalgo_test.cpp @@ -1316,15 +1316,15 @@ test_simple_perpixel() template std::string -mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, - const std::string& pattern, const float (&white_balance)[4], - int nthreads); +do_mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads); template<> std::string -mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, - const std::string& pattern, const float (&white_balance)[4], - int nthreads) +do_mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) { return ImageBufAlgo::mosaic_float(dst, src, x_offset, y_offset, pattern, white_balance, nthreads); @@ -1332,9 +1332,9 @@ mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, template<> std::string -mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, - const std::string& pattern, const float (&white_balance)[4], - int nthreads) +do_mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) { return ImageBufAlgo::mosaic_half(dst, src, x_offset, y_offset, pattern, white_balance, nthreads); @@ -1342,9 +1342,9 @@ mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, template<> std::string -mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, - const std::string& pattern, const float (&white_balance)[4], - int nthreads) +do_mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, + int y_offset, const std::string& pattern, + const float (&white_balance)[4], int nthreads) { return ImageBufAlgo::mosaic_uint16(dst, src, x_offset, y_offset, pattern, white_balance, nthreads); @@ -1352,9 +1352,9 @@ mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, template<> std::string -mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, - const std::string& pattern, const float (&white_balance)[4], - int nthreads) +do_mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, + int y_offset, const std::string& pattern, + const float (&white_balance)[4], int nthreads) { return ImageBufAlgo::mosaic_uint8(dst, src, x_offset, y_offset, pattern, white_balance, nthreads); @@ -1421,8 +1421,8 @@ test_demosaic(const DemosaicTestConfig& config, const ImageBuf& src_image, ImageSpec dst_spec(src_spec.width, src_spec.height, 1, type); ImageBuf mosaiced_image(dst_spec); - std::string layout = mosaic(mosaiced_image, src_image, x, y, - config.pattern, wb, 0); + std::string layout = do_mosaic(mosaiced_image, src_image, x, y, + config.pattern, wb, 0); mosaiced_image.specmod().attribute("raw:FilterPattern", layout); mosaiced_image.specmod().attribute("raw:WhiteBalance", diff --git a/src/libOpenImageIO/imagebufalgo_xform.cpp b/src/libOpenImageIO/imagebufalgo_xform.cpp index 5de60fb497..0abbb1ace8 100644 --- a/src/libOpenImageIO/imagebufalgo_xform.cpp +++ b/src/libOpenImageIO/imagebufalgo_xform.cpp @@ -21,7 +21,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace { @@ -378,7 +378,7 @@ warp_impl(ImageBuf& dst, const ImageBuf& src, const Imath::M33f& M, const Filter2D* filter, bool recompute_roi, ImageBuf::WrapMode wrap, bool edgeclamp, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::warp"); + OIIO::pvt::LoggedTimer logtime("IBA::warp"); ROI src_roi_full = src.roi_full(); ROI dst_roi, dst_roi_full; if (dst.initialized()) { @@ -860,7 +860,7 @@ bool ImageBufAlgo::resize(ImageBuf& dst, const ImageBuf& src, KWArgs options, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::resize"); + OIIO::pvt::LoggedTimer logtime("IBA::resize"); static const ustring recognized[] = { filtername_us, @@ -929,7 +929,7 @@ bool ImageBufAlgo::fit(ImageBuf& dst, const ImageBuf& src, KWArgs options, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::fit"); + OIIO::pvt::LoggedTimer logtime("IBA::fit"); static const ustring recognized[] = { filtername_us, @@ -1148,7 +1148,7 @@ bool ImageBufAlgo::resample(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::resample"); + OIIO::pvt::LoggedTimer logtime("IBA::resample"); if (!IBAprep(roi, &dst, &src, IBAprep_NO_SUPPORT_VOLUME | IBAprep_NO_COPY_ROI_FULL | IBAprep_SUPPORT_DEEP)) @@ -1459,7 +1459,7 @@ ImageBufAlgo::st_warp(ImageBuf& dst, const ImageBuf& src, const ImageBuf& stbuf, const Filter2D* filter, int chan_s, int chan_t, bool flip_s, bool flip_t, ROI roi, int nthreads) { - pvt::LoggedTimer logtime("IBA::st_warp"); + OIIO::pvt::LoggedTimer logtime("IBA::st_warp"); if (!check_st_warp_args(dst, src, stbuf, chan_s, chan_t, roi)) { return false; @@ -1534,4 +1534,4 @@ ImageBufAlgo::st_warp(const ImageBuf& src, const ImageBuf& stbuf, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imagebufalgo_yee.cpp b/src/libOpenImageIO/imagebufalgo_yee.cpp index 39daa77248..7ee875ebe5 100644 --- a/src/libOpenImageIO/imagebufalgo_yee.cpp +++ b/src/libOpenImageIO/imagebufalgo_yee.cpp @@ -28,7 +28,7 @@ powf(const Imath::Vec3& x, float y) -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace { @@ -336,4 +336,4 @@ ImageBufAlgo::compare_Yee(const ImageBuf& img0, const ImageBuf& img1, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imageinput.cpp b/src/libOpenImageIO/imageinput.cpp index 4ae22eb7fc..03af1297c5 100644 --- a/src/libOpenImageIO/imageinput.cpp +++ b/src/libOpenImageIO/imageinput.cpp @@ -22,8 +22,9 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace pvt; +using namespace OIIO::pvt; // store an error message per thread, for a specific ImageInput @@ -35,7 +36,7 @@ class ImageInput::Impl { public: Impl() : m_id(++input_next_id) - , m_threads(pvt::oiio_threads) + , m_threads(OIIO::pvt::oiio_threads) { } @@ -322,7 +323,7 @@ ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend, int z, int chbegin, int chend, TypeDesc format, void* data, stride_t xstride, stride_t ystride) { - pvt::LoggedTimer logtime("II::read_scanlines"); + OIIO::pvt::LoggedTimer logtime("II::read_scanlines"); ImageSpec spec; int rps = 0; { @@ -520,7 +521,7 @@ bool ImageInput::read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, span data) { - if (pvt::oiio_print_debug + if (OIIO::pvt::oiio_print_debug #ifndef NDEBUG || true #endif @@ -544,7 +545,7 @@ ImageInput::read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, int chbegin, int chend, span data) { - if (pvt::oiio_print_debug + if (OIIO::pvt::oiio_print_debug #ifndef NDEBUG || true #endif @@ -967,7 +968,7 @@ bool ImageInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend, int ybegin, int yend, span data) { - if (pvt::oiio_print_debug + if (OIIO::pvt::oiio_print_debug #ifndef NDEBUG || true #endif @@ -991,7 +992,7 @@ ImageInput::read_native_tiles(int subimage, int miplevel, int xbegin, int xend, int ybegin, int yend, int chbegin, int chend, span data) { - if (pvt::oiio_print_debug + if (OIIO::pvt::oiio_print_debug #ifndef NDEBUG || true #endif @@ -1017,7 +1018,7 @@ ImageInput::read_native_volumetric_tiles(int subimage, int miplevel, int xbegin, int zbegin, int zend, span data) { - if (pvt::oiio_print_debug + if (OIIO::pvt::oiio_print_debug #ifndef NDEBUG || true #endif @@ -1043,7 +1044,7 @@ ImageInput::read_native_volumetric_tiles(int subimage, int miplevel, int xbegin, int zbegin, int zend, int chbegin, int chend, span data) { - if (pvt::oiio_print_debug + if (OIIO::pvt::oiio_print_debug #ifndef NDEBUG || true #endif @@ -1070,7 +1071,7 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, ProgressCallback progress_callback, void* progress_callback_data) { - pvt::LoggedTimer logtime("II::read_image"); + OIIO::pvt::LoggedTimer logtime("II::read_image"); ImageSpec spec; int rps = 0; { @@ -1181,7 +1182,7 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, return read_image(subimage, miplevel, chbegin, chend, format, data.data(), data.xstride(), data.ystride(), data.zstride()); #else - pvt::LoggedTimer logtime("II::read_image"); + OIIO::pvt::LoggedTimer logtime("II::read_image"); ImageSpec spec; int rps = 0; { @@ -1573,21 +1574,22 @@ ImageInput::check_open(const ImageSpec& spec, ROI range, uint64_t /*flags*/) format_name(), spec.nchannels); return false; } - if (pvt::limit_channels && spec.nchannels > pvt::limit_channels) { + if (OIIO::pvt::limit_channels + && spec.nchannels > OIIO::pvt::limit_channels) { errorfmt( "{} channels exceeds \"limits:channels\" = {}. Possible corrupt input?\nIf you're sure this is a valid file, raise the OIIO global attribute \"limits:channels\".", - spec.nchannels, pvt::limit_channels); + spec.nchannels, OIIO::pvt::limit_channels); return false; } - if (pvt::limit_imagesize_MB + if (OIIO::pvt::limit_imagesize_MB && spec.image_bytes(true) - > pvt::limit_imagesize_MB * imagesize_t(1024 * 1024)) { + > OIIO::pvt::limit_imagesize_MB * imagesize_t(1024 * 1024)) { errorfmt( "Uncompressed image size {:.1f} MB exceeds the {} MB limit.\n" "Image claimed to be {}x{}, {}-channel {}. Possible corrupt input?\n" "If this is a valid file, raise the OIIO attribute \"limits:imagesize_MB\".", float(m_spec.image_bytes(true)) / float(1024 * 1024), - pvt::limit_imagesize_MB, m_spec.width, m_spec.height, + OIIO::pvt::limit_imagesize_MB, m_spec.width, m_spec.height, m_spec.nchannels, m_spec.format); return false; } @@ -1618,20 +1620,11 @@ ImageInput::valid_raw_span_size(cspan buf, const ImageSpec& spec, -template<> -inline size_t -pvt::heapsize(const ImageInput::Impl& impl) -{ - return impl.m_io_local ? sizeof(Filesystem::IOProxy) : 0; -} - - - size_t ImageInput::heapsize() const { - size_t size = pvt::heapsize(m_impl); - size += pvt::heapsize(m_spec); + size_t size = OIIO::pvt::heapsize(m_impl); + size += OIIO::pvt::heapsize(m_spec); return size; } @@ -1645,6 +1638,15 @@ ImageInput::footprint() const +template<> +inline size_t +pvt::heapsize(const ImageInput::Impl& impl) +{ + return impl.m_io_local ? sizeof(Filesystem::IOProxy) : 0; +} + + + template<> size_t pvt::heapsize(const ImageInput& input) @@ -1661,6 +1663,4 @@ pvt::footprint(const ImageInput& input) return input.footprint(); } - - -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index 8c28508958..909f8529d4 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -269,7 +269,10 @@ oiio_build_platform() return platform; } +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN void shutdown() @@ -358,7 +361,7 @@ attribute(string_view name, TypeDesc type, const void* val) } if (Strutil::starts_with(name, "gpu:") || Strutil::starts_with(name, "cuda:")) { - return pvt::gpu_attribute(name, type, val); + return OIIO::pvt::gpu_attribute(name, type, val); } // Things below here need to buarded by the attrib_mutex @@ -490,7 +493,7 @@ getattribute(string_view name, TypeDesc type, void* val) } if (Strutil::starts_with(name, "gpu:") || Strutil::starts_with(name, "cuda:")) { - return pvt::gpu_getattribute(name, type, val); + return OIIO::pvt::gpu_getattribute(name, type, val); } // Things below here need to buarded by the attrib_mutex @@ -509,31 +512,31 @@ getattribute(string_view name, TypeDesc type, void* val) } if (name == "format_list" && type == TypeString) { if (format_list.empty()) - pvt::catalog_all_plugins(plugin_searchpath.string()); + OIIO::pvt::catalog_all_plugins(plugin_searchpath.string()); *(ustring*)val = ustring(format_list); return true; } if (name == "input_format_list" && type == TypeString) { if (input_format_list.empty()) - pvt::catalog_all_plugins(plugin_searchpath.string()); + OIIO::pvt::catalog_all_plugins(plugin_searchpath.string()); *(ustring*)val = ustring(input_format_list); return true; } if (name == "output_format_list" && type == TypeString) { if (output_format_list.empty()) - pvt::catalog_all_plugins(plugin_searchpath.string()); + OIIO::pvt::catalog_all_plugins(plugin_searchpath.string()); *(ustring*)val = ustring(output_format_list); return true; } if (name == "extension_list" && type == TypeString) { if (extension_list.empty()) - pvt::catalog_all_plugins(plugin_searchpath.string()); + OIIO::pvt::catalog_all_plugins(plugin_searchpath.string()); *(ustring*)val = ustring(extension_list); return true; } if (name == "library_list" && type == TypeString) { if (library_list.empty()) - pvt::catalog_all_plugins(plugin_searchpath.string()); + OIIO::pvt::catalog_all_plugins(plugin_searchpath.string()); *(ustring*)val = ustring(library_list); return true; } @@ -714,8 +717,11 @@ getattribute(string_view name, TypeDesc type, void* val) return false; } +OIIO_NAMESPACE_3_1_END +OIIO_NAMESPACE_BEGIN + namespace { /// Type-independent template for turning potentially @@ -907,6 +913,10 @@ pvt::parallel_convert_from_float(const float* src, void* dst, size_t nvals, return dst; } +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN bool @@ -921,7 +931,7 @@ convert_pixel_values(TypeDesc src_type, const void* src, TypeDesc dst_type, if (dst_type == TypeFloat) { // Special case -- converting non-float to float - pvt::convert_to_float(src, (float*)dst, n, src_type); + OIIO::pvt::convert_to_float(src, (float*)dst, n, src_type); return true; } @@ -937,7 +947,7 @@ convert_pixel_values(TypeDesc src_type, const void* src, TypeDesc dst_type, tmp.reset(new float[n]); // Freed when tmp exists its scope buf = tmp.get(); } - pvt::convert_to_float(src, buf, n, src_type); + OIIO::pvt::convert_to_float(src, buf, n, src_type); } // Convert float to 'dst_type' @@ -1219,10 +1229,9 @@ add_bluenoise(int nchannels, int width, int height, int depth, float* data, int channel = c + chorigin; if (channel == alpha_channel || channel == z_channel) continue; - float dither - = pvt::bluenoise_4chan_ptr(x + xorigin, y + yorigin, - z + zorigin, channel & (~3), - ditherseed)[channel & 3]; + float dither = OIIO::pvt::bluenoise_4chan_ptr( + x + xorigin, y + yorigin, z + zorigin, channel & (~3), + ditherseed)[channel & 3]; *val += ditheramplitude * (dither - 0.5f); } } @@ -1432,4 +1441,4 @@ image_span_within_span(const image_span& ispan, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imageioplugin.cpp b/src/libOpenImageIO/imageioplugin.cpp index 3089427445..68462cebf1 100644 --- a/src/libOpenImageIO/imageioplugin.cpp +++ b/src/libOpenImageIO/imageioplugin.cpp @@ -140,7 +140,10 @@ declare_imageio_format_locked(const std::string& format_name, } } +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN /// Register the input and output 'create' routine and list of file /// extensions for a particular format. @@ -151,7 +154,7 @@ declare_imageio_format(const std::string& format_name, ImageOutput::Creator output_creator, const char** output_extensions, const char* lib_version) { - std::lock_guard lock(pvt::imageio_mutex); + std::lock_guard lock(OIIO::pvt::imageio_mutex); declare_imageio_format_locked(format_name, input_creator, input_extensions, output_creator, output_extensions, lib_version); @@ -169,7 +172,7 @@ is_imageio_format_name(string_view name) if (!format_list_vector.size()) { lock.unlock(); // catalog_all_plugins() will lock imageio_mutex. - pvt::catalog_all_plugins(pvt::plugin_searchpath.string()); + OIIO::pvt::catalog_all_plugins(OIIO::pvt::plugin_searchpath.string()); lock.lock(); } for (const auto& n : format_list_vector) @@ -178,7 +181,11 @@ is_imageio_format_name(string_view name) return false; } +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN static void catalog_plugin(const std::string& format_name, @@ -518,6 +525,10 @@ pvt::is_procedural_plugin(const std::string& name) return procedural_plugins.find(name) != procedural_plugins.end(); } +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN std::unique_ptr @@ -548,9 +559,10 @@ ImageOutput::create(string_view filename, Filesystem::IOProxy* ioproxy, if (found == output_formats.end()) { lock.unlock(); // catalog_all_plugins() will lock imageio_mutex - catalog_all_plugins(plugin_searchpath.size() - ? plugin_searchpath - : string_view(pvt::plugin_searchpath)); + catalog_all_plugins( + plugin_searchpath.size() + ? plugin_searchpath + : string_view(OIIO::pvt::plugin_searchpath)); lock.lock(); found = output_formats.find(format); } @@ -647,7 +659,7 @@ ImageInput::create(string_view filename, bool do_open, const ImageSpec* config, InputPluginMap::const_iterator found = input_formats.find(format); if (found == input_formats.end()) { if (plugin_searchpath.empty()) - plugin_searchpath = pvt::plugin_searchpath; + plugin_searchpath = OIIO::pvt::plugin_searchpath; lock.unlock(); // catalog_all_plugins() will lock imageio_mutex. catalog_all_plugins(plugin_searchpath); @@ -700,7 +712,7 @@ ImageInput::create(string_view filename, bool do_open, const ImageSpec* config, create_function = nullptr; if (in) { specific_error = in->geterror(); - if (pvt::oiio_print_debug > 1) + if (OIIO::pvt::oiio_print_debug > 1) OIIO::debugfmt( "ImageInput::create: \"{}\" did not open using format \"{}\".\n", filename, in->format_name()); @@ -709,7 +721,7 @@ ImageInput::create(string_view filename, bool do_open, const ImageSpec* config, } } - if (!create_function && pvt::oiio_try_all_readers) { + if (!create_function && OIIO::pvt::oiio_try_all_readers) { // If a plugin can't be found that was explicitly designated for // this extension, then just try every one we find and see if // any will open the file. Add a configuration request that @@ -748,7 +760,7 @@ ImageInput::create(string_view filename, bool do_open, const ImageSpec* config, && !in->valid_file(filename))) { // Since we didn't need to open it, we just checked whether // it was a valid file, and it's not. Try the next one. - if (pvt::oiio_print_debug > 1) + if (OIIO::pvt::oiio_print_debug > 1) OIIO::debugfmt( "ImageInput::create: \"{}\" did not open using format \"{}\" {} [valid_file was false].\n", filename, plugin->first, in->format_name()); @@ -764,13 +776,13 @@ ImageInput::create(string_view filename, bool do_open, const ImageSpec* config, if (ok) { if (!do_open) in->close(); - if (pvt::oiio_print_debug > 1) + if (OIIO::pvt::oiio_print_debug > 1) OIIO::debugfmt( "ImageInput::create: \"{}\" succeeded using format \"{}\".\n", filename, plugin->first); return in; } - if (pvt::oiio_print_debug > 1) + if (OIIO::pvt::oiio_print_debug > 1) OIIO::debugfmt( "ImageInput::create: \"{}\" did not open using format \"{}\" {}.\n", filename, plugin->first, in->format_name()); @@ -809,4 +821,4 @@ ImageInput::create(string_view filename, bool do_open, const ImageSpec* config, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 41e815aabf..1b9edaede5 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -25,8 +25,9 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace pvt; +using namespace OIIO::pvt; // store an error message per thread, for a specific ImageInput @@ -38,7 +39,7 @@ class ImageOutput::Impl { public: Impl() : m_id(++output_next_id) - , m_threads(pvt::oiio_threads) + , m_threads(OIIO::pvt::oiio_threads) { } @@ -600,7 +601,7 @@ ImageOutput::write_image(TypeDesc format, const void* data, stride_t xstride, ProgressCallback progress_callback, void* progress_callback_data) { - pvt::LoggedTimer logtime("ImageOutput::write image"); + OIIO::pvt::LoggedTimer logtime("ImageOutput::write image"); bool native = (format == TypeDesc::UNKNOWN); stride_t pixel_bytes = native ? (stride_t)m_spec.pixel_bytes(native) : format.size() * m_spec.nchannels; @@ -1140,20 +1141,11 @@ ImageOutput::check_open(OpenMode mode, const ImageSpec& userspec, ROI range, -template<> -inline size_t -pvt::heapsize(const ImageOutput::Impl& impl) -{ - return impl.m_io_local ? sizeof(Filesystem::IOProxy) : 0; -} - - - size_t ImageOutput::heapsize() const { - size_t size = pvt::heapsize(m_impl); - size += pvt::heapsize(m_spec); + size_t size = OIIO::pvt::heapsize(m_impl); + size += OIIO::pvt::heapsize(m_spec); return size; } @@ -1167,6 +1159,15 @@ ImageOutput::footprint() const +template<> +inline size_t +pvt::heapsize(const ImageOutput::Impl& impl) +{ + return impl.m_io_local ? sizeof(Filesystem::IOProxy) : 0; +} + + + template<> size_t pvt::heapsize(const ImageOutput& output) @@ -1183,6 +1184,4 @@ pvt::footprint(const ImageOutput& output) return output.footprint(); } - - -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/iptc.cpp b/src/libOpenImageIO/iptc.cpp index aa739eb950..b8ee573059 100644 --- a/src/libOpenImageIO/iptc.cpp +++ b/src/libOpenImageIO/iptc.cpp @@ -95,7 +95,10 @@ static IIMtag iimtag[] = { } // anonymous namespace +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN bool decode_iptc_iim(const void* iptc, int length, ImageSpec& spec) @@ -234,4 +237,4 @@ encode_iptc_iim(const ImageSpec& spec, std::vector& iptc) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/maketexture.cpp b/src/libOpenImageIO/maketexture.cpp index 934d17b56e..ca38206523 100644 --- a/src/libOpenImageIO/maketexture.cpp +++ b/src/libOpenImageIO/maketexture.cpp @@ -29,8 +29,7 @@ #include "imageio_pvt.h" -using namespace OIIO; - +OIIO_NAMESPACE_BEGIN static spin_mutex maketx_mutex; // for anything that needs locking @@ -2042,14 +2041,18 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, return ok; } +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN + bool ImageBufAlgo::make_texture(ImageBufAlgo::MakeTextureMode mode, string_view filename, string_view outputfilename, const ImageSpec& configspec, std::ostream* outstream) { - pvt::LoggedTimer logtime("IBA::make_texture"); + OIIO::pvt::LoggedTimer logtime("IBA::make_texture"); bool ok = make_texture_impl(mode, NULL, filename, outputfilename, configspec, outstream); if (!ok && outstream && OIIO::has_error()) { @@ -2067,7 +2070,7 @@ ImageBufAlgo::make_texture(ImageBufAlgo::MakeTextureMode mode, string_view outputfilename, const ImageSpec& configspec, std::ostream* outstream) { - pvt::LoggedTimer logtime("IBA::make_texture"); + OIIO::pvt::LoggedTimer logtime("IBA::make_texture"); bool ok = make_texture_impl(mode, NULL, filenames[0], outputfilename, configspec, outstream); if (!ok && outstream && OIIO::has_error()) { @@ -2084,7 +2087,7 @@ ImageBufAlgo::make_texture(ImageBufAlgo::MakeTextureMode mode, const ImageBuf& input, string_view outputfilename, const ImageSpec& configspec, std::ostream* outstream) { - pvt::LoggedTimer logtime("IBA::make_texture"); + OIIO::pvt::LoggedTimer logtime("IBA::make_texture"); bool ok = make_texture_impl(mode, &input, "", outputfilename, configspec, outstream); if (!ok && outstream && OIIO::has_error()) { @@ -2093,3 +2096,5 @@ ImageBufAlgo::make_texture(ImageBufAlgo::MakeTextureMode mode, } return ok; } + +OIIO_NAMESPACE_3_1_END diff --git a/src/libOpenImageIO/xmp.cpp b/src/libOpenImageIO/xmp.cpp index 2301e8abc5..88e9f3fe42 100644 --- a/src/libOpenImageIO/xmp.cpp +++ b/src/libOpenImageIO/xmp.cpp @@ -513,7 +513,10 @@ decode_xmp_node(pugi::xml_node node, ImageSpec& spec, int level = 1, } // anonymous namespace +OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_BEGIN bool decode_xmp(cspan xml, ImageSpec& spec) @@ -857,4 +860,4 @@ encode_xmp(const ImageSpec& spec, bool minimal) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libtexture/environment.cpp b/src/libtexture/environment.cpp index a103bfcbd1..e1fc9cf87d 100644 --- a/src/libtexture/environment.cpp +++ b/src/libtexture/environment.cpp @@ -199,7 +199,7 @@ convention is dictated by OpenEXR. */ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace pvt; using namespace simd; using SubimageInfo = ImageCacheFile::SubimageInfo; @@ -643,4 +643,4 @@ TextureSystemImpl::environment(ustring filename, TextureOptBatch& options, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libtexture/imagecache.cpp b/src/libtexture/imagecache.cpp index e28d8a0e5e..deaf583ed5 100644 --- a/src/libtexture/imagecache.cpp +++ b/src/libtexture/imagecache.cpp @@ -36,7 +36,7 @@ #include "imageio_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace pvt; using ImageDims = ImageCacheFile::ImageDims; using LevelInfo = ImageCacheFile::LevelInfo; @@ -1048,7 +1048,7 @@ ImageCacheFile::init_from_spec() } // Squash some problematic texture metadata if we suspect it's wrong - pvt::check_texture_metadata_sanity(this->spec(0)); + OIIO::pvt::check_texture_metadata_sanity(this->spec(0)); // See if there's a SHA-1 hash in the image description string_view fing = spec.get_string_attribute("oiio:SHA-1"); @@ -2991,8 +2991,9 @@ ImageCacheImpl::resolve_filename(const std::string& filename) const { // Ask if the format can generate imagery procedurally. If so, don't // go looking for a file. - if (pvt::is_procedural_plugin(filename) - || pvt::is_procedural_plugin(Filesystem::extension(filename, false))) + if (OIIO::pvt::is_procedural_plugin(filename) + || OIIO::pvt::is_procedural_plugin( + Filesystem::extension(filename, false))) return filename; if (m_searchdirs.empty() || Filesystem::path_is_absolute(filename, true)) { // Don't bother with the searchpath_find call since it will do an @@ -4390,8 +4391,8 @@ ImageCacheImpl::append_error(string_view message) const size_t ImageCacheImpl::heapsize() const { - using OIIO::pvt::footprint; - using OIIO::pvt::heapsize; + using pvt::footprint; + using pvt::heapsize; size_t size = 0; // strings @@ -4419,8 +4420,8 @@ ImageCacheImpl::heapsize() const size_t ImageCacheImpl::footprint(ImageCacheFootprint& output) const { - using OIIO::pvt::footprint; - using OIIO::pvt::heapsize; + using pvt::footprint; + using pvt::heapsize; // strings output.ic_str_count = m_searchdirs.size() + 2; @@ -4950,4 +4951,4 @@ ImageCache::reset_stats() } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libtexture/imagecache_memory_pvt.h b/src/libtexture/imagecache_memory_pvt.h index a1e2a75310..2d078b6d2c 100644 --- a/src/libtexture/imagecache_memory_pvt.h +++ b/src/libtexture/imagecache_memory_pvt.h @@ -14,7 +14,7 @@ #include "imagecache_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { @@ -78,4 +78,4 @@ heapsize(const ImageCacheImpl& ic) } // namespace pvt -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libtexture/imagecache_pvt.h b/src/libtexture/imagecache_pvt.h index f81d78ec98..a147ce8a52 100644 --- a/src/libtexture/imagecache_pvt.h +++ b/src/libtexture/imagecache_pvt.h @@ -22,9 +22,6 @@ #include #include -OIIO_NAMESPACE_BEGIN - - #ifndef NDEBUG # define IMAGECACHE_TIME_STATS 1 #else @@ -39,22 +36,25 @@ OIIO_NAMESPACE_BEGIN #define TILE_CACHE_SHARDS 128 - -struct TileID; -class ImageCacheImpl; +OIIO_NAMESPACE_BEGIN struct ImageCacheFootprint; +OIIO_NAMESPACE_END -namespace pvt { +OIIO_NAMESPACE_3_1_BEGIN +namespace pvt { const char* texture_format_name(TexFormat f); const char* texture_type_name(TexFormat f); - } // namespace pvt +struct TileID; + + + /// Structure to hold IC and TS statistics. We combine into a single /// structure to minimize the number of costly ImageCachePerThreadInfo /// retrievals. If somebody is using the ImageCache without a @@ -158,8 +158,8 @@ struct UdimInfo { /// thread-specific IC data including microcache and statistics. /// class OIIO_API ImageCacheFile final : public RefCnt { - using TexFormat = pvt::TexFormat; - using EnvLayout = pvt::EnvLayout; + using TexFormat = v3_1::pvt::TexFormat; + using EnvLayout = v3_1::pvt::EnvLayout; using UdimInfo = pvt::UdimInfo; public: @@ -1397,7 +1397,7 @@ class ImageCacheImpl { }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END #endif // OPENIMAGEIO_IMAGECACHE_PVT_H diff --git a/src/libtexture/texoptions.cpp b/src/libtexture/texoptions.cpp index 3b3a72d91a..a634cef2eb 100644 --- a/src/libtexture/texoptions.cpp +++ b/src/libtexture/texoptions.cpp @@ -11,7 +11,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace { // anonymous @@ -94,4 +94,4 @@ Tex::parse_wrapmodes(const char* wrapmodes, Tex::Wrap& swrapcode, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libtexture/texture3d.cpp b/src/libtexture/texture3d.cpp index 19b2dfc2e0..7d492c9da3 100644 --- a/src/libtexture/texture3d.cpp +++ b/src/libtexture/texture3d.cpp @@ -22,7 +22,7 @@ #include "imagecache_pvt.h" #include "texture_pvt.h" -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace pvt; using LevelInfo = ImageCacheFile::LevelInfo; using SubimageInfo = ImageCacheFile::SubimageInfo; @@ -766,4 +766,4 @@ TextureSystemImpl::texture3d(ustring filename, TextureOptBatch& options, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libtexture/texture_pvt.h b/src/libtexture/texture_pvt.h index d5e5ce67a6..c1de13890a 100644 --- a/src/libtexture/texture_pvt.h +++ b/src/libtexture/texture_pvt.h @@ -14,11 +14,6 @@ #include OIIO_NAMESPACE_BEGIN - -class ImageCache; -class TextureSystemImpl; -class Filter1D; - #ifndef OPENIMAGEIO_IMAGECACHE_PVT_H class ImageCacheFile; class ImageCacheTile; @@ -26,8 +21,11 @@ class ImageCacheTileRef; class ImageCacheTileID; class ImageCacheImpl; #endif +OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_BEGIN + /// Working implementation of the abstract TextureSystem class. class TextureSystemImpl { @@ -554,6 +552,6 @@ TextureSystemImpl::st_to_texel(float s, float t, TextureFile& texturefile, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END #endif // OPENIMAGEIO_TEXTURE_PVT_H diff --git a/src/libtexture/texturesys.cpp b/src/libtexture/texturesys.cpp index 6b6bae186c..249c47b7aa 100644 --- a/src/libtexture/texturesys.cpp +++ b/src/libtexture/texturesys.cpp @@ -34,14 +34,16 @@ #define TEX_FAST_MATH 1 +using namespace OIIO::simd; -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace pvt; -using namespace simd; +using namespace OIIO::pvt; using LevelInfo = ImageCacheFile::LevelInfo; using SubimageInfo = ImageCacheFile::SubimageInfo; using ImageDims = ImageCacheFile::ImageDims; + namespace { // anonymous // We would like shared_texturesys to be a shared_ptr so that it is @@ -3642,4 +3644,4 @@ TextureSystem::unit_test_hash() } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/SHA1.cpp b/src/libutil/SHA1.cpp index 0ac8959bec..08d246871e 100644 --- a/src/libutil/SHA1.cpp +++ b/src/libutil/SHA1.cpp @@ -49,7 +49,7 @@ #pragma warning(disable: 4127) #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN SHA1::SHA1 (const void *data, size_t size) { @@ -317,4 +317,4 @@ bool CSHA1::GetHash(UINT_8* pbDest20) const #pragma warning(pop) #endif -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/SHA1.h b/src/libutil/SHA1.h index 80970214ee..fca69aee68 100644 --- a/src/libutil/SHA1.h +++ b/src/libutil/SHA1.h @@ -219,7 +219,7 @@ /////////////////////////////////////////////////////////////////////////// // Declare SHA-1 workspace -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN typedef union { @@ -288,7 +288,7 @@ class CSHA1 SHA1_WORKSPACE_BLOCK* m_block; // SHA1 pointer to the byte array above }; -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END #ifndef DO_NOT_UNDEFINE_SHA1 #undef TCHAR diff --git a/src/libutil/argparse.cpp b/src/libutil/argparse.cpp index 6653ee992c..9ad566fac1 100644 --- a/src/libutil/argparse.cpp +++ b/src/libutil/argparse.cpp @@ -21,7 +21,9 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN +// All of ArgParse is still in the v3_1 namespace until it needs to break ABI +// compatibility. class ArgOption final : public ArgParse::Arg { public: @@ -151,7 +153,7 @@ class ArgParse::Impl { : m_argparse(parent) , m_argc(argc) , m_argv(argv) - , m_prog(Filesystem::filename(Sysutil::this_program_path())) + , m_prog(OIIO::Filesystem::filename(Sysutil::this_program_path())) { } @@ -1195,4 +1197,4 @@ ArgParse::set_next_arg(int nextarg) m_impl->m_next_arg = nextarg; } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/benchmark.cpp b/src/libutil/benchmark.cpp index 5642358035..f8e0d2a833 100644 --- a/src/libutil/benchmark.cpp +++ b/src/libutil/benchmark.cpp @@ -10,11 +10,11 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace pvt { -void OIIO_API +void OIIO_UTIL_API #if __has_attribute(__optnone__) __attribute__((__optnone__)) #endif @@ -27,7 +27,7 @@ void OIIO_API // Implementation of clobber_ptr is trivial, but the code in other modules // doesn't know that. -void OIIO_API +void OIIO_UTIL_API #if __has_attribute(__optnone__) __attribute__((__optnone__)) #endif @@ -110,7 +110,7 @@ Benchmarker::compute_stats(std::vector& times, size_t iterations) -OIIO_API +OIIO_UTIL_API std::ostream& operator<<(std::ostream& out, const Benchmarker& bench) { @@ -144,7 +144,7 @@ operator<<(std::ostream& out, const Benchmarker& bench) if (bench.indent()) OIIO::print(out, "{}", std::string(bench.indent(), ' ')); if (unit == int(Benchmarker::Unit::s)) - OIIO::print(out, "{:16}: {}", bench.m_name, + OIIO::print(out, "{:16}: {}", bench.name(), Strutil::timeintervalformat(avg, 2)); else OIIO::print(out, "{:16}: {:6.1f} {} (+/- {:.1f}{}), ", bench.name(), @@ -175,9 +175,14 @@ operator<<(std::ostream& out, const Benchmarker& bench) return out; } +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN -OIIO_API std::vector + +OIIO_UTIL_API std::vector timed_thread_wedge(function_view task, function_view pretask, function_view posttask, std::ostream* out, int maxthreads, int total_iterations, int ntrials, @@ -220,7 +225,7 @@ timed_thread_wedge(function_view task, function_view pretask, -OIIO_API void +OIIO_UTIL_API void timed_thread_wedge(function_view task, int maxthreads, int total_iterations, int ntrials, cspan threadcounts) { @@ -229,4 +234,4 @@ timed_thread_wedge(function_view task, int maxthreads, ntrials, threadcounts); } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/errorhandler.cpp b/src/libutil/errorhandler.cpp index a9baeb79d7..d0d1a2c385 100644 --- a/src/libutil/errorhandler.cpp +++ b/src/libutil/errorhandler.cpp @@ -12,7 +12,7 @@ -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN @@ -58,4 +58,4 @@ ErrorHandler::operator()(int errcode, const std::string& msg) fflush(stderr); } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/farmhash.cpp b/src/libutil/farmhash.cpp index 128c63e1c7..98773d8dba 100644 --- a/src/libutil/farmhash.cpp +++ b/src/libutil/farmhash.cpp @@ -33,9 +33,9 @@ // namespace NAMESPACE_FOR_HASH_FUNCTIONS { - OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN - namespace farmhash { +namespace farmhash { // BASIC STRING HASHING @@ -43,7 +43,7 @@ // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint32_t Hash32(const char* s, size_t len) { - return farmhash::inlined::Hash32(s, len); + return OIIO::farmhash::inlined::Hash32(s, len); } // Hash function for a byte array. For convenience, a 32-bit seed is also @@ -51,7 +51,7 @@ uint32_t Hash32(const char* s, size_t len) { // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed) { - return farmhash::inlined::Hash32WithSeed(s, len, seed); + return OIIO::farmhash::inlined::Hash32WithSeed(s, len, seed); } // Hash function for a byte array. For convenience, a 64-bit seed is also @@ -59,14 +59,14 @@ uint32_t Hash32WithSeed(const char* s, size_t len, uint32_t seed) { // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint64_t Hash64(const char* s, size_t len) { - return farmhash::inlined::Hash64(s, len); + return OIIO::farmhash::inlined::Hash64(s, len); } // Hash function for a byte array. // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. size_t Hash(const char* s, size_t len) { - return farmhash::inlined::Hash(s, len); + return OIIO::farmhash::inlined::Hash(s, len); } // Hash function for a byte array. For convenience, a 64-bit seed is also @@ -74,7 +74,7 @@ size_t Hash(const char* s, size_t len) { // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed) { - return farmhash::inlined::Hash64WithSeed(s, len, seed); + return OIIO::farmhash::inlined::Hash64WithSeed(s, len, seed); } // Hash function for a byte array. For convenience, two seeds are also @@ -82,14 +82,14 @@ uint64_t Hash64WithSeed(const char* s, size_t len, uint64_t seed) { // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint64_t Hash64WithSeeds(const char* s, size_t len, uint64_t seed0, uint64_t seed1) { - return farmhash::inlined::Hash64WithSeeds(s, len, seed0, seed1); + return OIIO::farmhash::inlined::Hash64WithSeeds(s, len, seed0, seed1); } // Hash function for a byte array. // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint128_t Hash128(const char* s, size_t len) { - return farmhash::inlined::Hash128(s, len); + return OIIO::farmhash::inlined::Hash128(s, len); } // Hash function for a byte array. For convenience, a 128-bit seed is also @@ -97,7 +97,7 @@ uint128_t Hash128(const char* s, size_t len) { // May change from time to time, may differ on different platforms, may differ // depending on NDEBUG. uint128_t Hash128WithSeed(const char* s, size_t len, uint128_t seed) { - return farmhash::inlined::Hash128WithSeed(s, len, seed); + return OIIO::farmhash::inlined::Hash128WithSeed(s, len, seed); } // BASIC NON-STRING HASHING @@ -106,21 +106,22 @@ uint128_t Hash128WithSeed(const char* s, size_t len, uint128_t seed) { // Fingerprint function for a byte array. Most useful in 32-bit binaries. uint32_t Fingerprint32(const char* s, size_t len) { - return farmhash::inlined::Fingerprint32(s, len); + return OIIO::farmhash::inlined::Fingerprint32(s, len); } // Fingerprint function for a byte array. uint64_t Fingerprint64(const char* s, size_t len) { - return farmhash::inlined::Fingerprint64(s, len); + return OIIO::farmhash::inlined::Fingerprint64(s, len); } // Fingerprint function for a byte array. uint128_t Fingerprint128(const char* s, size_t len) { - return farmhash::inlined::Fingerprint128(s, len); + return OIIO::farmhash::inlined::Fingerprint128(s, len); } // Older and still available but perhaps not as fast as the above: // farmhashns::Hash32{,WithSeed}() // } // namespace NAMESPACE_FOR_HASH_FUNCTIONS -} /*end namespace farmhash*/ OIIO_NAMESPACE_END +} /*end namespace farmhash*/ +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/filesystem.cpp b/src/libutil/filesystem.cpp index 5ede2126c3..e19e35a40f 100644 --- a/src/libutil/filesystem.cpp +++ b/src/libutil/filesystem.cpp @@ -40,7 +40,7 @@ using std::error_code; -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN inline filesystem::path @@ -571,7 +571,7 @@ Filesystem::getline(FILE* file, size_t maxlen) void -Filesystem::open(OIIO::ifstream& stream, string_view path, +Filesystem::open(ifstream& stream, string_view path, std::ios_base::openmode mode) { #ifdef _WIN32 @@ -588,7 +588,7 @@ Filesystem::open(OIIO::ifstream& stream, string_view path, void -Filesystem::open(OIIO::ofstream& stream, string_view path, +Filesystem::open(ofstream& stream, string_view path, std::ios_base::openmode mode) { #ifdef _WIN32 @@ -1195,7 +1195,8 @@ Filesystem::IOFile::IOFile(string_view filename, Mode mode) { // Call Filesystem::fopen since it handles UTF-8 file paths on Windows, // which std fopen does not. - m_file = Filesystem::fopen(m_filename, m_mode == Write ? "w+b" : "rb"); + m_file = OIIO::Filesystem::fopen(m_filename, + m_mode == Write ? "w+b" : "rb"); if (!m_file) { m_mode = Closed; int e = errno; @@ -1204,7 +1205,7 @@ Filesystem::IOFile::IOFile(string_view filename, Mode mode) } m_auto_close = true; if (m_mode == Read) - m_size = Filesystem::file_size(filename); + m_size = OIIO::Filesystem::file_size(filename); } Filesystem::IOFile::IOFile(FILE* file, Mode mode) @@ -1212,10 +1213,12 @@ Filesystem::IOFile::IOFile(FILE* file, Mode mode) , m_file(file) { if (m_mode == Read) { - m_pos = Filesystem::ftell(m_file); // save old position - Filesystem::fseek(m_file, 0, SEEK_END); // seek to end - m_size = size_t(Filesystem::ftell(m_file)); // size is end position - Filesystem::fseek(m_file, m_pos, SEEK_SET); // restore old position + m_pos = OIIO::Filesystem::ftell(m_file); // save old position + OIIO::Filesystem::fseek(m_file, 0, SEEK_END); // seek to end + m_size = size_t( + OIIO::Filesystem::ftell(m_file)); // size is end position + OIIO::Filesystem::fseek(m_file, m_pos, + SEEK_SET); // restore old position } } @@ -1241,7 +1244,7 @@ Filesystem::IOFile::seek(int64_t offset) if (!m_file) return false; m_pos = offset; - return Filesystem::fseek(m_file, offset, SEEK_SET) == 0; + return OIIO::Filesystem::fseek(m_file, offset, SEEK_SET) == 0; } size_t @@ -1404,5 +1407,4 @@ Filesystem::IOMemReader::pread(void* buf, size_t size, int64_t offset) return size; } - -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/filter.cpp b/src/libutil/filter.cpp index a39a0d84a9..eb3a346db2 100644 --- a/src/libutil/filter.cpp +++ b/src/libutil/filter.cpp @@ -30,7 +30,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN // Below are the implementations of several 2D filters. They all // inherit their interface from Filter2D. Each must redefine two @@ -1047,4 +1047,4 @@ Filter2D::destroy(Filter2D* filt) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/hashes.cpp b/src/libutil/hashes.cpp index 52a2e753dd..251269c784 100644 --- a/src/libutil/hashes.cpp +++ b/src/libutil/hashes.cpp @@ -12,7 +12,7 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace bjhash { @@ -782,4 +782,4 @@ uint32_t hashbig( const void *key, size_t length, uint32_t initval) } // end namespace bjhash -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/paramlist.cpp b/src/libutil/paramlist.cpp index 2721a3fc0c..026e80520b 100644 --- a/src/libutil/paramlist.cpp +++ b/src/libutil/paramlist.cpp @@ -12,7 +12,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN void @@ -102,25 +102,6 @@ ParamValue::operator=(ParamValue&& p) noexcept -namespace Strutil { -template<> -inline short -from_string(string_view s) -{ - return static_cast(Strutil::stoi(s)); -} - - -template<> -inline unsigned short -from_string(string_view s) -{ - return static_cast(Strutil::stoi(s)); -} -} // namespace Strutil - - - // helper to parse a list from a string template static void @@ -379,17 +360,6 @@ ParamValue::clear_value() noexcept -template<> -size_t -pvt::heapsize(const ParamValue& pv) -{ - return (pv.m_nonlocal && pv.m_copy) - ? pv.m_nvalues * static_cast(pv.m_type.size()) - : 0; -} - - - ParamValueList::const_iterator ParamValueList::find(ustring name, TypeDesc type, bool casesensitive) const { @@ -953,4 +923,13 @@ ParamValueSpan::getattribute_indexed(string_view name, int index, -OIIO_NAMESPACE_END +template<> +size_t +pvt::heapsize(const ParamValue& pv) +{ + return (pv.m_nonlocal && pv.m_copy) + ? pv.m_nvalues * static_cast(pv.m_type.size()) + : 0; +} + +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/plugin.cpp b/src/libutil/plugin.cpp index a8451c9c9a..8ec0a97979 100644 --- a/src/libutil/plugin.cpp +++ b/src/libutil/plugin.cpp @@ -25,7 +25,7 @@ OIIO_PRAGMA_WARNING_POP #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN using namespace Plugin; @@ -147,4 +147,4 @@ Plugin::geterror(bool clear) return e; } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/strutil.cpp b/src/libutil/strutil.cpp index ef0d80e7a7..224bafaf68 100644 --- a/src/libutil/strutil.cpp +++ b/src/libutil/strutil.cpp @@ -59,6 +59,18 @@ OIIO_PRAGMA_WARNING_POP OIIO_NAMESPACE_BEGIN +namespace pvt { +static const char* oiio_debug_env = getenv("OPENIMAGEIO_DEBUG"); +#ifdef NDEBUG +OIIO_UTIL_API int + oiio_print_debug(oiio_debug_env ? Strutil::stoi(oiio_debug_env) : 0); +#else +OIIO_UTIL_API int + oiio_print_debug(oiio_debug_env ? Strutil::stoi(oiio_debug_env) : 1); +#endif +OIIO_UTIL_API int oiio_print_uncaught_errors(1); +} // namespace pvt + namespace { @@ -77,6 +89,18 @@ static _locale_t c_loc = _create_locale(LC_ALL, "C"); +#if OIIO_VERSION_LESS(3, 1, 2) /* remove at next ABI compatibility boundary */ +void +pvt::log_fmt_error(const char* message) +{ + print("fmt exception: {}\n", message); + Strutil::pvt::append_error(std::string("fmt exception: ") + message); +} +#endif + +OIIO_NAMESPACE_END + + // Locale-independent quickie ASCII digit and alphanum tests, good enough // for our parsing. inline int @@ -101,6 +125,7 @@ isdigit(char c) } +OIIO_NAMESPACE_3_1_BEGIN OIIO_NO_SANITIZE_ADDRESS const char* c_str(string_view str) @@ -166,20 +191,6 @@ Strutil::sync_output(std::ostream& file, string_view str, bool flush) -namespace pvt { -static const char* oiio_debug_env = getenv("OPENIMAGEIO_DEBUG"); -#ifdef NDEBUG -OIIO_UTIL_API int - oiio_print_debug(oiio_debug_env ? Strutil::stoi(oiio_debug_env) : 0); -#else -OIIO_UTIL_API int - oiio_print_debug(oiio_debug_env ? Strutil::stoi(oiio_debug_env) : 1); -#endif -OIIO_UTIL_API int oiio_print_uncaught_errors(1); -} // namespace pvt - - - // ErrorHolder houses a string, with the addition that when it is destroyed, // it will disgorge any un-retrieved error messages, in an effort to help // beginning users diagnose their problems if they have forgotten to call @@ -189,7 +200,7 @@ struct ErrorHolder { ~ErrorHolder() { - if (!error_msg.empty() && pvt::oiio_print_uncaught_errors) { + if (!error_msg.empty() && OIIO::pvt::oiio_print_uncaught_errors) { OIIO::print( "OpenImageIO exited with a pending error message that was never\n" "retrieved via OIIO::geterror(). This was the error message:\n{}\n", @@ -246,17 +257,6 @@ Strutil::pvt::geterror(bool clear) } -#if OIIO_VERSION_LESS(3, 1, 2) /* remove at next ABI compatibility boundary */ -void -pvt::log_fmt_error(const char* message) -{ - print("fmt exception: {}\n", message); - Strutil::pvt::append_error(std::string("fmt exception: ") + message); -} -#endif - - - void Strutil::pvt::debug(string_view message) { @@ -2008,4 +2008,5 @@ Strutil::eval_as_bool(string_view value) } } -OIIO_NAMESPACE_END + +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/strutil_test.cpp b/src/libutil/strutil_test.cpp index fba21ee41f..a9b8bed758 100644 --- a/src/libutil/strutil_test.cpp +++ b/src/libutil/strutil_test.cpp @@ -1315,7 +1315,7 @@ test_string_view() OIIO_CHECK_EQUAL(OIIO::c_str(string_view(sr.data(), 2)), std::string("01")); Strutil::print("addr cstr={:p}, s={:p}, ustring={:p}, sr={:p}, c_str(sr)={:p}\n", (void*)cstr, (void*)s.c_str(), (void*)ustring(cstr).c_str(), (void*)sr.data(), - (void*)c_str(sr)); + (void*)OIIO::c_str(sr)); } diff --git a/src/libutil/sysutil.cpp b/src/libutil/sysutil.cpp index e89af9cfaa..03e9127473 100644 --- a/src/libutil/sysutil.cpp +++ b/src/libutil/sysutil.cpp @@ -76,9 +76,7 @@ OIIO_INTEL_PRAGMA(warning disable 2196) -OIIO_NAMESPACE_BEGIN - -using namespace Sysutil; +OIIO_NAMESPACE_3_1_BEGIN size_t @@ -369,9 +367,6 @@ isatty(int fd) #endif -Term::Term(FILE* file) { m_is_console = isatty(fileno((file))); } - - #ifdef _WIN32 // from https://msdn.microsoft.com/fr-fr/library/windows/desktop/mt638032%28v=vs.85%29.aspx @@ -404,6 +399,11 @@ enableVTMode() +namespace Sysutil { + +Term::Term(FILE* file) { m_is_console = isatty(fileno((file))); } + + Term::Term(const std::ostream& stream) { m_is_console = (&stream == &std::cout && isatty(fileno(stdout))) @@ -525,6 +525,8 @@ Term::ansi_bgcolor(int r, int g, int b) return ret; } +} // end namespace Sysutil + bool @@ -598,7 +600,8 @@ Sysutil::max_open_files() -void* +// Backward link compatibility +OIIO_UTIL_API void* aligned_malloc(std::size_t size, std::size_t align) { #if defined(_WIN32) @@ -609,9 +612,8 @@ aligned_malloc(std::size_t size, std::size_t align) #endif } - - -void +// Backward link compatibility +OIIO_UTIL_API void aligned_free(void* ptr) { #if defined(_WIN32) @@ -685,4 +687,4 @@ Sysutil::setup_crash_stacktrace(string_view filename) } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/thread.cpp b/src/libutil/thread.cpp index 87dd488b1c..d6e605841f 100644 --- a/src/libutil/thread.cpp +++ b/src/libutil/thread.cpp @@ -87,10 +87,15 @@ OIIO_NAMESPACE_END OIIO_NAMESPACE_BEGIN - namespace pvt { OIIO_UTIL_API int oiio_use_tbb(0); // Use TBB if available } +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN +// Thread utils still in the v3_1 namespace until it needs to break ABI +// compatibility. static int @@ -345,7 +350,7 @@ class thread_pool::Impl { std::vector> threads; std::vector>> flags; - mutable pvt::ThreadsafeQueue*> q; + mutable OIIO::pvt::ThreadsafeQueue*> q; std::atomic isDone; std::atomic isStop; std::atomic nWaiting; // how many threads are waiting @@ -576,20 +581,6 @@ task_set::wait(bool block) -// Helper function to keep track of the recursve depth of our use of the -// thread pool. Call with the adjustment (i.e., parallel_recursive_depth(1) -// to enter, parallel_recursive_depth(-1) to exit), and it will return the -// new value. Call with default args (0) to just return the current depth. -static int -parallel_recursive_depth(int change = 0) -{ - thread_local int depth = 0; // let's only allow one level of parallel work - depth += change; - return depth; -} - - - void paropt::resolve() { @@ -603,6 +594,20 @@ paropt::resolve() +// Helper function to keep track of the recursive depth of our use of the +// thread pool. Call with the adjustment (i.e., parallel_recursive_depth(1) +// to enter, parallel_recursive_depth(-1) to exit), and it will return the +// new value. Call with default args (0) to just return the current depth. +static int +parallel_recursive_depth(int change = 0) +{ + thread_local int depth = 0; // let's only allow one level of parallel work + depth += change; + return depth; +} + + + void parallel_for_chunked_id(int64_t begin, int64_t end, int64_t chunksize, std::function&& task, @@ -652,7 +657,7 @@ parallel_for_chunked(int64_t begin, int64_t end, int64_t chunksize, template inline void -parallel_for_impl(Index begin, Index end, function_view task, +parallel_for_impl(Index begin, Index end, function_view&& task, paropt opt) { if (opt.maxthreads() == 1) { @@ -664,7 +669,7 @@ parallel_for_impl(Index begin, Index end, function_view task, #if OIIO_TBB if (opt.strategy() == paropt::ParStrategy::TryTBB || (opt.strategy() == paropt::ParStrategy::Default - && pvt::oiio_use_tbb)) { + && OIIO::pvt::oiio_use_tbb)) { if (opt.maxthreads()) { tbb::task_arena arena(opt.maxthreads()); arena.execute([=] { tbb::parallel_for(begin, end, task); }); @@ -685,37 +690,79 @@ parallel_for_impl(Index begin, Index end, function_view task, +// DEPRECATED void parallel_for(int begin, int end, function_view task, paropt opt) { - parallel_for_impl(begin, end, task, opt); + parallel_for_impl(begin, end, std::move(task), opt); } +// DEPRECATED void parallel_for(uint32_t begin, uint32_t end, function_view task, paropt opt) { - parallel_for_impl(begin, end, task, opt); + parallel_for_impl(begin, end, std::move(task), opt); } +// DEPRECATED void parallel_for(int64_t begin, int64_t end, function_view task, paropt opt) { - parallel_for_impl(begin, end, task, opt); + parallel_for_impl(begin, end, std::move(task), opt); } +// DEPRECATED void parallel_for(uint64_t begin, uint64_t end, function_view task, paropt opt) { - parallel_for_impl(begin, end, task, opt); + parallel_for_impl(begin, end, std::move(task), opt); +} + +OIIO_NAMESPACE_3_1_END + + +OIIO_NAMESPACE_BEGIN + +void +parallel_for(int begin, int end, function_view&& task, paropt opt) +{ + parallel_for_impl(begin, end, std::move(task), opt); +} + + +void +parallel_for(uint32_t begin, uint32_t end, function_view&& task, + paropt opt) +{ + parallel_for_impl(begin, end, std::move(task), opt); } +void +parallel_for(int64_t begin, int64_t end, function_view&& task, + paropt opt) +{ + parallel_for_impl(begin, end, std::move(task), opt); +} + + +void +parallel_for(uint64_t begin, uint64_t end, function_view&& task, + paropt opt) +{ + parallel_for_impl(begin, end, std::move(task), opt); +} + +OIIO_NAMESPACE_END + + +OIIO_NAMESPACE_3_1_BEGIN template inline void @@ -854,4 +901,4 @@ parallel_for_2D(int64_t xbegin, int64_t xend, int64_t ybegin, int64_t yend, } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/timer.cpp b/src/libutil/timer.cpp index 9dbec63f3c..7692c10e95 100644 --- a/src/libutil/timer.cpp +++ b/src/libutil/timer.cpp @@ -12,7 +12,7 @@ #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN double Timer::seconds_per_tick; Timer::ticks_t Timer::ticks_per_second; @@ -68,4 +68,4 @@ Timer::now(void) const } #endif -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/typedesc.cpp b/src/libutil/typedesc.cpp index 53e2bf07bd..1c58346a7e 100644 --- a/src/libutil/typedesc.cpp +++ b/src/libutil/typedesc.cpp @@ -16,7 +16,7 @@ #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN TypeDesc::TypeDesc(string_view typestring) : basetype(UNKNOWN) @@ -360,6 +360,57 @@ TypeDesc::fromstring(string_view typestring) +bool +TypeDesc::operator<(const TypeDesc& x) const noexcept +{ + if (basetype != x.basetype) + return basetype < x.basetype; + if (aggregate != x.aggregate) + return aggregate < x.aggregate; + if (arraylen != x.arraylen) + return arraylen < x.arraylen; + if (vecsemantics != x.vecsemantics) + return vecsemantics < x.vecsemantics; + return false; // they are equal +} + + + +TypeDesc::BASETYPE +TypeDesc::basetype_merge(TypeDesc at, TypeDesc bt) +{ + BASETYPE a = (BASETYPE)at.basetype; + BASETYPE b = (BASETYPE)bt.basetype; + + // Same type already? done. + if (a == b) + return a; + if (a == UNKNOWN) + return b; + if (b == UNKNOWN) + return a; + // Canonicalize so a's size (in bytes) is >= b's size in bytes. This + // unclutters remaining cases. + if (TypeDesc(a).size() < TypeDesc(b).size()) + std::swap(a, b); + // Double or float trump anything else + if (a == DOUBLE || a == FLOAT) + return a; + if (a == UINT32 && (b == UINT16 || b == UINT8)) + return a; + if (a == INT32 && (b == INT16 || b == UINT16 || b == INT8 || b == UINT8)) + return a; + if ((a == UINT16 || a == HALF) && b == UINT8) + return a; + if ((a == INT16 || a == HALF) && (b == INT8 || b == UINT8)) + return a; + // Out of common cases. For all remaining edge cases, punt and say that + // we prefer float. + return FLOAT; +} + + + tostring_formatting::tostring_formatting( const char* int_fmt, const char* float_fmt, const char* string_fmt, const char* ptr_fmt, const char* aggregate_begin, const char* aggregate_end, @@ -801,7 +852,7 @@ convert_type(TypeDesc srctype, const void* src, TypeDesc dsttype, void* dst, if (srctype == TypeUstringhash) (*(ustring*)dst) = ustring::from_hash(*(const ustring::hash_t*)src); else - (*(ustring*)dst) = ustring(tostring(srctype, src)); + (*(ustring*)dst) = ustring(OIIO::tostring(srctype, src)); return true; } @@ -858,55 +909,4 @@ convert_type(TypeDesc srctype, const void* src, TypeDesc dsttype, void* dst, return false; } - - -bool -TypeDesc::operator<(const TypeDesc& x) const noexcept -{ - if (basetype != x.basetype) - return basetype < x.basetype; - if (aggregate != x.aggregate) - return aggregate < x.aggregate; - if (arraylen != x.arraylen) - return arraylen < x.arraylen; - if (vecsemantics != x.vecsemantics) - return vecsemantics < x.vecsemantics; - return false; // they are equal -} - - - -TypeDesc::BASETYPE -TypeDesc::basetype_merge(TypeDesc at, TypeDesc bt) -{ - BASETYPE a = (BASETYPE)at.basetype; - BASETYPE b = (BASETYPE)bt.basetype; - - // Same type already? done. - if (a == b) - return a; - if (a == UNKNOWN) - return b; - if (b == UNKNOWN) - return a; - // Canonicalize so a's size (in bytes) is >= b's size in bytes. This - // unclutters remaining cases. - if (TypeDesc(a).size() < TypeDesc(b).size()) - std::swap(a, b); - // Double or float trump anything else - if (a == DOUBLE || a == FLOAT) - return a; - if (a == UINT32 && (b == UINT16 || b == UINT8)) - return a; - if (a == INT32 && (b == INT16 || b == UINT16 || b == INT8 || b == UINT8)) - return a; - if ((a == UINT16 || a == HALF) && b == UINT8) - return a; - if ((a == INT16 || a == HALF) && (b == INT8 || b == UINT8)) - return a; - // Out of common cases. For all remaining edge cases, punt and say that - // we prefer float. - return FLOAT; -} - -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/ustring.cpp b/src/libutil/ustring.cpp index 211c944e67..3da39ccf65 100644 --- a/src/libutil/ustring.cpp +++ b/src/libutil/ustring.cpp @@ -12,7 +12,7 @@ #include #include -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN // Use rw spin locks typedef spin_rw_mutex ustring_mutex_t; @@ -662,4 +662,4 @@ ustring::memory() return table.get_memory_usage(); } -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/xxhash.cpp b/src/libutil/xxhash.cpp index c0afcfefbb..e988bab4c5 100644 --- a/src/libutil/xxhash.cpp +++ b/src/libutil/xxhash.cpp @@ -140,7 +140,7 @@ typedef unsigned long long U64; #endif -OIIO_NAMESPACE_BEGIN +OIIO_NAMESPACE_3_1_BEGIN namespace xxhash { @@ -952,4 +952,4 @@ unsigned long long XXH64_digest (const XXH64_state_t* state_in) } // namespace xxhash -OIIO_NAMESPACE_END +OIIO_NAMESPACE_3_1_END diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index bf75e18870..c0ae72f9a2 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -3424,7 +3424,7 @@ ImageBufAlgox::cryptomatte_colors(ImageBuf& dst, const ImageBuf& src, span channelset, ROI roi, int nthreads) { - // pvt::LoggedTimer logtime("IBA::cryptomatte_colors"); + // OIIO::pvt::LoggedTimer logtime("IBA::cryptomatte_colors"); if (!roi.defined()) roi = get_roi(src.spec()); roi.chend = std::min(roi.chend, src.nchannels()); @@ -7535,7 +7535,7 @@ handle_sequence(Oiiotool& ot, int argc, const char** argv) OIIO::print("Running {} frames in parallel with {} threads\n", nfilenames, parallel_frame_threads); ot.begin_parallel_frame_loop(parallel_frame_threads); - parallel_for( + OIIO::parallel_for( uint64_t(0), uint64_t(nfilenames), [&](uint64_t i) { one_sequence_iteration(ot, i, frame_numbers[0][i], From 143cea53b327d5c67cf794b289b8856a8d9a416c Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 14 Sep 2025 06:54:02 +0200 Subject: [PATCH 005/508] feat(heif): Read and write of CICP and bit depth 10 and 12 (#4880) * Read and write 10 and 12 bit images * Read and write CICP This makes it possible to write HDR images that are much smaller than PNGs, in a file format that is supported in all modern web browsers. By setting the matrix coefficients, the library will perform RGB to YUV conversion on write. The YUV to RBG conversion was already happening on read automatically. A future improvement would be a better default choice of matrix coefficients for writing. When no CICP is specified or when CICP is read from PNG, then no conversion to YUV will be performed and compression will not work as well. This is the same behavior as before. When there is support for conversion between CICP and display interop ID, a good default choice for matrix coefficients could be made as part of that. Tests for CICP and 10 bit were added. --------- Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/builtinplugins.rst | 6 ++ src/heif.imageio/heifinput.cpp | 88 ++++++++++++++++-- src/heif.imageio/heifoutput.cpp | 72 ++++++++++++-- testsuite/heif/ref/out-libheif1.12-orient.txt | 28 ++++++ testsuite/heif/ref/out-libheif1.4.txt | 23 +++++ testsuite/heif/ref/out-libheif1.5.txt | 23 +++++ testsuite/heif/ref/out-libheif1.9-alt2.txt | 28 ++++++ .../heif/ref/out-libheif1.9-with-av1-alt2.txt | 23 +++++ .../heif/ref/out-libheif1.9-with-av1.txt | 23 +++++ testsuite/heif/ref/out-libheif1.9.txt | 28 ++++++ testsuite/heif/ref/test-10bit.avif | Bin 0 -> 820 bytes testsuite/heif/run.py | 6 +- 12 files changed, 330 insertions(+), 18 deletions(-) create mode 100644 testsuite/heif/ref/test-10bit.avif diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 81d72c911a..1e419e5696 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -688,6 +688,12 @@ preferred except when legacy file access is required. - string - Color space (see Section :ref:`sec-metadata-color`). We currently assume that any RGBE files encountered are linear with sRGB primaries. + * - ``CICP`` + - int[4] + - Coding-independent code points to describe the color profile. + * - ``oiio:BitsPerSample`` + - int + - Bits per sample in the file: 8, 10 or 12. * - ``heif:Orientation`` - int - If the configuration option ``heif:reorient`` is nonzero and diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp index be92acf9f4..4ba8e2fb61 100644 --- a/src/heif.imageio/heifinput.cpp +++ b/src/heif.imageio/heifinput.cpp @@ -3,7 +3,9 @@ // https://github.com/AcademySoftwareFoundation/OpenImageIO #include +#include #include +#include #include #include @@ -36,7 +38,11 @@ class HeifInput final : public ImageInput { const char* format_name(void) const override { return "heif"; } int supports(string_view feature) const override { - return feature == "exif"; + return feature == "exif" +#if LIBHEIF_HAVE_VERSION(1, 9, 0) + || feature == "cicp" +#endif + ; } bool valid_file(const std::string& filename) const override; bool open(const std::string& name, ImageSpec& newspec) override; @@ -53,6 +59,7 @@ class HeifInput final : public ImageInput { std::string m_filename; int m_subimage = -1; int m_num_subimages = 0; + int m_bitdepth = 0; int m_has_alpha = false; bool m_associated_alpha = true; bool m_keep_unassociated_alpha = false; @@ -203,11 +210,30 @@ HeifInput::seek_subimage(int subimage, int miplevel) return false; } - auto id = (subimage == 0) ? m_primary_id : m_item_ids[subimage - 1]; - m_ihandle = m_ctx->get_image_handle(id); + auto id = (subimage == 0) ? m_primary_id : m_item_ids[subimage - 1]; + m_ihandle = m_ctx->get_image_handle(id); + + m_bitdepth = m_ihandle.get_luma_bits_per_pixel(); + if (m_bitdepth < 0) { + errorfmt("Image has undefined bit depth"); + m_ctx.reset(); + return false; + } else if (!(m_bitdepth == 8 || m_bitdepth == 10 || m_bitdepth == 12)) { + errorfmt("Image has unsupported bit depth {}", m_bitdepth); + m_ctx.reset(); + return false; + } + m_has_alpha = m_ihandle.has_alpha_channel(); - auto chroma = m_has_alpha ? heif_chroma_interleaved_RGBA - : heif_chroma_interleaved_RGB; + auto chroma = m_has_alpha ? (m_bitdepth > 8) + ? littleendian() + ? heif_chroma_interleaved_RRGGBBAA_LE + : heif_chroma_interleaved_RRGGBBAA_BE + : heif_chroma_interleaved_RGBA + : (m_bitdepth > 8) ? littleendian() + ? heif_chroma_interleaved_RRGGBB_LE + : heif_chroma_interleaved_RRGGBB_BE + : heif_chroma_interleaved_RGB; #if 0 try { m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma); @@ -238,13 +264,40 @@ HeifInput::seek_subimage(int subimage, int miplevel) } #endif - int bits = m_himage.get_bits_per_pixel(heif_channel_interleaved); - m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved), - m_himage.get_height(heif_channel_interleaved), bits / 8, - TypeUInt8); + m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved), + m_himage.get_height(heif_channel_interleaved), + m_has_alpha ? 4 : 3, + (m_bitdepth > 8) ? TypeUInt16 : TypeUInt8); + if (m_bitdepth > 8) { + m_spec.attribute("oiio:BitsPerSample", m_bitdepth); + } m_spec.set_colorspace("srgb_rec709_scene"); +#if LIBHEIF_HAVE_VERSION(1, 9, 0) + // Read CICP. Have to use the C API to get it from the image handle, + // the one on the decoded image is not what was written in the file. + enum heif_color_profile_type profile_type + = heif_image_handle_get_color_profile_type( + m_ihandle.get_raw_image_handle()); + if (profile_type == heif_color_profile_type_nclx) { + heif_color_profile_nclx* nclx = nullptr; + const heif_error err = heif_image_handle_get_nclx_color_profile( + m_ihandle.get_raw_image_handle(), &nclx); + + if (nclx) { + if (err.code == heif_error_Ok) { + const int cicp[4] = { int(nclx->color_primaries), + int(nclx->transfer_characteristics), + int(nclx->matrix_coefficients), + int(nclx->full_range_flag ? 1 : 0) }; + m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); + } + heif_nclx_color_profile_free(nclx); + } + } +#endif + #if LIBHEIF_HAVE_VERSION(1, 12, 0) // Libheif >= 1.12 added API call to find out if the image is associated // alpha (i.e. colors are premultiplied). @@ -402,7 +455,22 @@ HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, return false; } hdata += (y - m_spec.y) * ystride; - memcpy(data, hdata, m_spec.width * m_spec.pixel_bytes()); + if (m_bitdepth == 10 || m_bitdepth == 12) { + const size_t num_values = m_spec.width * m_spec.nchannels; + const uint16_t* hdata16 = reinterpret_cast(hdata); + uint16_t* data16 = static_cast(data); + if (m_bitdepth == 10) { + for (size_t i = 0; i < num_values; ++i) { + data16[i] = bit_range_convert<10, 16>(hdata16[i]); + } + } else { + for (size_t i = 0; i < num_values; ++i) { + data16[i] = bit_range_convert<12, 16>(hdata16[i]); + } + } + } else { + memcpy(data, hdata, m_spec.width * m_spec.pixel_bytes()); + } return true; } diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 2d45f50920..6ed1dbb439 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -4,7 +4,9 @@ #include +#include #include +#include #include #include @@ -26,7 +28,11 @@ class HeifOutput final : public ImageOutput { const char* format_name(void) const override { return "heif"; } int supports(string_view feature) const override { - return feature == "alpha" || feature == "exif" || feature == "tiles"; + return feature == "alpha" || feature == "exif" || feature == "tiles" +#if LIBHEIF_HAVE_VERSION(1, 9, 0) + || feature == "cicp" +#endif + ; } bool open(const std::string& name, const ImageSpec& spec, OpenMode mode) override; @@ -45,6 +51,7 @@ class HeifOutput final : public ImageOutput { heif::Encoder m_encoder { heif_compression_HEVC }; std::vector scratch; std::vector m_tilebuffer; + int m_bitdepth = 0; }; @@ -104,19 +111,33 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, m_filename = name; - m_spec.set_format(TypeUInt8); // Only uint8 for now + m_bitdepth = m_spec.format.size() > TypeUInt8.size() ? 10 : 8; + m_bitdepth = m_spec.get_int_attribute("oiio:BitsPerSample", m_bitdepth); + if (m_bitdepth == 10 || m_bitdepth == 12) { + m_spec.set_format(TypeUInt16); + } else if (m_bitdepth == 8) { + m_spec.set_format(TypeUInt8); + } else { + errorfmt("Unsupported bit depth {}", m_bitdepth); + return false; + } try { m_ctx.reset(new heif::Context); m_himage = heif::Image(); static heif_chroma chromas[/*nchannels*/] = { heif_chroma_undefined, heif_chroma_monochrome, - heif_chroma_undefined, heif_chroma_interleaved_RGB, - heif_chroma_interleaved_RGBA }; + heif_chroma_undefined, + (m_bitdepth == 8) ? heif_chroma_interleaved_RGB + : littleendian() ? heif_chroma_interleaved_RRGGBB_LE + : heif_chroma_interleaved_RRGGBB_BE, + (m_bitdepth == 8) ? heif_chroma_interleaved_RGBA + : littleendian() ? heif_chroma_interleaved_RRGGBBAA_LE + : heif_chroma_interleaved_RRGGBBAA_BE }; m_himage.create(newspec.width, newspec.height, heif_colorspace_RGB, chromas[m_spec.nchannels]); m_himage.add_plane(heif_channel_interleaved, newspec.width, - newspec.height, 8 * m_spec.nchannels /*bit depth*/); + newspec.height, m_bitdepth); m_encoder = heif::Encoder(heif_compression_HEVC); auto compqual = m_spec.decode_compression_metadata("", 75); @@ -161,7 +182,22 @@ HeifOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, &hystride); #endif hdata += hystride * (y - m_spec.y); - memcpy(hdata, data, hystride); + if (m_bitdepth == 10 || m_bitdepth == 12) { + const uint16_t* data16 = static_cast(data); + uint16_t* hdata16 = reinterpret_cast(hdata); + const size_t num_values = m_spec.width * m_spec.nchannels; + if (m_bitdepth == 10) { + for (size_t i = 0; i < num_values; ++i) { + hdata16[i] = bit_range_convert<16, 10>(data16[i]); + } + } else { + for (size_t i = 0; i < num_values; ++i) { + hdata16[i] = bit_range_convert<16, 12>(data16[i]); + } + } + } else { + memcpy(hdata, data, hystride); + } return true; } @@ -207,8 +243,30 @@ HeifOutput::close() } else if (compqual.first == "none") { m_encoder.set_lossless(true); } + heif::Context::EncodingOptions options; +#if LIBHEIF_HAVE_VERSION(1, 9, 0) + // Write CICP. we can only set output_nclx_profile with the C API. + std::unique_ptr + nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free); + const ParamValue* p = m_spec.find_attribute("CICP", + TypeDesc(TypeDesc::INT, 4)); + if (p) { + const int* cicp = static_cast(p->data()); + nclx->color_primaries = heif_color_primaries(cicp[0]); + nclx->transfer_characteristics = heif_transfer_characteristics( + cicp[1]); + nclx->matrix_coefficients = heif_matrix_coefficients(cicp[2]); + nclx->full_range_flag = cicp[3]; + options.output_nclx_profile = nclx.get(); + // Chroma subsampling is incompatible with RGB. + if (nclx->matrix_coefficients == heif_matrix_coefficients_RGB_GBR) { + m_encoder.set_string_parameter("chroma", "444"); + } + } +#endif encode_exif(m_spec, exifblob, endian::big); - m_ihandle = m_ctx->encode_image(m_himage, m_encoder); + m_ihandle = m_ctx->encode_image(m_himage, m_encoder, options); std::vector head { 'E', 'x', 'i', 'f', 0, 0 }; exifblob.insert(exifblob.begin(), head.begin(), head.end()); try { diff --git a/testsuite/heif/ref/out-libheif1.12-orient.txt b/testsuite/heif/ref/out-libheif1.12-orient.txt index a3102659c6..e9ef2777bb 100644 --- a/testsuite/heif/ref/out-libheif1.12-orient.txt +++ b/testsuite/heif/ref/out-libheif1.12-orient.txt @@ -39,6 +39,34 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif Exif:SubsecTimeOriginal: "006" Exif:WhiteBalance: 0 (auto) oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.4.txt b/testsuite/heif/ref/out-libheif1.4.txt index ba85f95394..48384b5e0d 100644 --- a/testsuite/heif/ref/out-libheif1.4.txt +++ b/testsuite/heif/ref/out-libheif1.4.txt @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 channel list: R, G, B oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.5.txt b/testsuite/heif/ref/out-libheif1.5.txt index 8fb8e93fdf..8a371fca47 100644 --- a/testsuite/heif/ref/out-libheif1.5.txt +++ b/testsuite/heif/ref/out-libheif1.5.txt @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 channel list: R, G, B oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.9-alt2.txt b/testsuite/heif/ref/out-libheif1.9-alt2.txt index e24120d453..9d92f3b2ed 100644 --- a/testsuite/heif/ref/out-libheif1.9-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-alt2.txt @@ -39,6 +39,34 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif Exif:SubsecTimeOriginal: "006" Exif:WhiteBalance: 0 (auto) oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt index 2433705c6b..9d92f3b2ed 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 channel list: R, G, B oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1.txt b/testsuite/heif/ref/out-libheif1.9-with-av1.txt index 40d4b662df..5253081cdc 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1.txt @@ -44,6 +44,29 @@ ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 channel list: R, G, B oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.9.txt b/testsuite/heif/ref/out-libheif1.9.txt index 2ddc23c253..5253081cdc 100644 --- a/testsuite/heif/ref/out-libheif1.9.txt +++ b/testsuite/heif/ref/out-libheif1.9.txt @@ -39,6 +39,34 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif Exif:SubsecTimeOriginal: "006" Exif:WhiteBalance: 0 (auto) oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/test-10bit.avif b/testsuite/heif/ref/test-10bit.avif new file mode 100644 index 0000000000000000000000000000000000000000..9dab1f234699aa6fe1f676107597d5bec4509d92 GIT binary patch literal 820 zcmZ8fJ%|%Q6n?YGsTaZ_o(B<>+v^T7VP_MU&BDQ%KRhs8B{nweW|9o-&$v4YNh5b~ zs92pgwrRB0+QMEd5kXtI=1MyoMc>RC1!vfK@B7~OX7;@q0JuDqbKy_<5O73%NR=OP zA2E3mG3hHF$JTfdrnw6scp#Vl(yj?EBMk_yJsu{3LY-YGfMS5W%PqP!Ff{Z1FSCIv z97cJ`kZpv0&SU*!I=X>?<%t0P)S@cmABmCwNuu()v%I)hT%-aso^TN)Xv_YTwh!T! z+T|k81XB)<^UF{08COK1R4SqQqra;tEH;l36>lH4dWq%DIg+rqNM)Am4d5C*$^OWVa<8hcAV$X=~&RQ_Y0^L>Y zE6JjPFE2o8wNn1|WwEvRaA0p%V9h-_0{Z&?Y4h{o((20x;q#ks?)=!ieSH1n+g((z zDD@}1Q~~!Zke}@28ykU>36CZE)x?x6L$4;{5$p>V??wKI z?d_ZOnq9MM)__e-&uleXokr8?H0`Ea@4Ag1?R6ZRI Date: Sun, 14 Sep 2025 14:45:20 -0700 Subject: [PATCH 006/508] ci: better spread of libpng versions we test against (#4883) * The "oldest" didn't really test against the oldest we claim to support. * The "latest versions" didn't really test against the latest versions. * The "bleeding edge" didn't really test against libpng master. * The auto-build was unnecessarily a few releases behind. * Documentation touch-ups --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 10 ++++++++++ src/build-scripts/build_libpng.bash | 2 +- src/build-scripts/gh-installdeps.bash | 4 ++++ src/cmake/build_PNG.cmake | 2 +- src/doc/builtinplugins.rst | 5 +++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdeebf4cb1..87a02695e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,7 @@ jobs: opencolorio_ver: v2.3.0 pybind11_ver: v2.9.0 setenvs: export FREETYPE_VERSION=VER-2-12-0 + BUILD_PNG_VERSION=1.6.30 - desc: VP2022 clang13/C++17 py39 avx2 exr3.1 ocio2.3 nametag: linux-vfx2022.clang13 runner: ubuntu-latest @@ -75,6 +76,7 @@ jobs: simd: "avx2,f16c" fmt_ver: 9.1.0 setenvs: export FREETYPE_VERSION=VER-2-12-0 + BUILD_PNG_VERSION=1.6.30 - desc: oldest gcc9.3/C++17 py3.9 exr3.1 ocio2.3 # Oldest gcc and versions of the dependencies that we support. nametag: linux-oldest @@ -92,6 +94,7 @@ jobs: PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 PUGIXML_VERSION=v1.8 + BUILD_PNG_VERSION=1.6.0 depcmds: sudo rm -rf /usr/local/include/OpenEXR - desc: oldest clang10/C++17 py3.9 exr3.1 ocio2.3 # Oldest clang and versions of the dependencies that we support. @@ -112,6 +115,7 @@ jobs: PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 PUGIXML_VERSION=v1.8 + BUILD_PNG_VERSION=1.6.0 depcmds: sudo rm -rf /usr/local/include/OpenEXR - desc: hobbled gcc9.3/C++17 py3.9 exr-3.1 no-sse # Use the oldest supported versions of required dependencies, and @@ -137,6 +141,7 @@ jobs: USE_OPENCV=0 FREETYPE_VERSION=VER-2-10-0 PUGIXML_VERSION=v1.8 + BUILD_PNG_VERSION=1.6.0 depcmds: sudo rm -rf /usr/local/include/OpenEXR runs-on: ${{ matrix.runner }} @@ -372,6 +377,7 @@ jobs: setenvs: export SANITIZE=address,undefined OIIO_CMAKE_FLAGS="-DSANITIZE=address,undefined -DOIIO_HARDENING=3 -DUSE_PYTHON=0" CTEST_EXCLUSIONS="broken|png-damaged" + OpenImageIO_BUILD_LOCAL_DEPS=PNG # Test ABI stability. `abi_check` is the version or commit that we # believe is the current standard against which we don't want to @@ -425,6 +431,7 @@ jobs: python_ver: "3.12" simd: avx2,f16c setenvs: export LIBJPEGTURBO_VERSION=3.1.1 + LIBPNG_VERSION=v1.6.50 LIBRAW_VERSION=0.21.4 LIBTIFF_VERSION=v4.7.0 OPENJPEG_VERSION=v2.5.3 @@ -447,6 +454,7 @@ jobs: simd: avx2,f16c benchmark: 1 setenvs: export LIBJPEGTURBO_VERSION=main + LIBPNG_VERSION=master LIBRAW_VERSION=master LIBTIFF_VERSION=master OPENJPEG_VERSION=master @@ -523,6 +531,7 @@ jobs: pybind11_ver: v3.0.0 python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.1 + LIBPNG_VERSION=v1.6.50 LIBRAW_VERSION=0.21.4 LIBTIFF_VERSION=v4.7.0 OPENJPEG_VERSION=v2.5.3 @@ -543,6 +552,7 @@ jobs: pybind11_ver: v3.0.0 python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.0 + LIBPNG_VERSION=v1.6.50 LIBRAW_VERSION=0.21.4 LIBTIFF_VERSION=v4.7.0 OPENJPEG_VERSION=v2.5.3 diff --git a/src/build-scripts/build_libpng.bash b/src/build-scripts/build_libpng.bash index ea2f5da647..e947dd55bd 100755 --- a/src/build-scripts/build_libpng.bash +++ b/src/build-scripts/build_libpng.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of libpng to download if we don't have it yet LIBPNG_REPO=${LIBPNG_REPO:=https://github.com/pnggroup/libpng.git} -LIBPNG_VERSION=${LIBPNG_VERSION:=v1.6.47} +LIBPNG_VERSION=${LIBPNG_VERSION:=v1.6.50} # Where to put libpng repo source (default to the ext area) LIBPNG_SRC_DIR=${LIBPNG_SRC_DIR:=${PWD}/ext/libpng} diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index bc0ad63018..0416c0c276 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -208,6 +208,10 @@ if [[ "$FREETYPE_VERSION" != "" ]] ; then source src/build-scripts/build_Freetype.bash fi +if [[ "$LIBPNG_VERSION" != "" ]] ; then + source src/build-scripts/build_libpng.bash +fi + if [[ "$USE_ICC" != "" ]] ; then # We used gcc for the prior dependency builds, but use icc for OIIO itself echo "which icpc:" $(which icpc) diff --git a/src/cmake/build_PNG.cmake b/src/cmake/build_PNG.cmake index c0a1657d2d..338b60c5e5 100644 --- a/src/cmake/build_PNG.cmake +++ b/src/cmake/build_PNG.cmake @@ -6,7 +6,7 @@ # PNG by hand! ###################################################################### -set_cache (PNG_BUILD_VERSION 1.6.47 "PNG version for local builds") +set_cache (PNG_BUILD_VERSION 1.6.50 "PNG version for local builds") super_set (PNG_BUILD_GIT_REPOSITORY "https://github.com/pnggroup/libpng") super_set (PNG_BUILD_GIT_TAG "v${PNG_BUILD_VERSION}") super_set (PNG_BUILD_EXTRA_CMAKE_ARGS "") diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 1e419e5696..3d600591be 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1777,6 +1777,11 @@ files use the file extension :file:`.png`. * - ``oiio:ColorSpace`` - string - Color space (see Section :ref:`sec-metadata-color`). + * - ``CICP`` + - int[4] + - CICP color space information (see Section :ref:`sec-metadata-color`). + Note that this attribute is only supported if OIIO was built against + libPNG 1.6.45 or newer. * - ``ICCProfile`` - uint8[] - The ICC color profile. A variety of other ``ICCProfile:*`` attributes From f5a282fe4d77c0ee72ee6d8461afb6f8efed7aeb Mon Sep 17 00:00:00 2001 From: Zach Lewis Date: Mon, 15 Sep 2025 13:43:17 -0400 Subject: [PATCH 007/508] ci: disable macos-x86 wheels. (#4886) Temporary workaround to unblock publishing of other wheels for 3.1 release Signed-off-by: Zach Lewis Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/wheel.yml | 104 ++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 21ab865572..343fb97540 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -227,57 +227,57 @@ jobs: # macOS Wheels # --------------------------------------------------------------------------- - macos: - name: Build wheels on macOS - runs-on: macos-13 - if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' - strategy: - matrix: - include: - # ------------------------------------------------------------------- - # CPython 64 bits - # ------------------------------------------------------------------- - - build: CPython 3.9 64 bits - python: cp39-macosx_x86_64 - arch: x86_64 - - build: CPython 3.10 64 bits - python: cp310-macosx_x86_64 - arch: x86_64 - - build: CPython 3.11 64 bits - python: cp311-macosx_x86_64 - arch: x86_64 - - build: CPython 3.12 64 bits - python: cp312-macosx_x86_64 - arch: x86_64 - - build: CPython 3.13 64 bits - python: cp313-macosx_x86_64 - arch: x86_64 - - steps: - - name: Checkout repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Install Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 - with: - python-version: '3.9' - - - name: Build wheels - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 - env: - CIBW_BUILD: ${{ matrix.python }} - CIBW_ARCHS: ${{ matrix.arch }} - CMAKE_GENERATOR: "Unix Makefiles" - # TODO: Re-enable HEIF when we provide a build recipe that does - # not include GPL-licensed dynamic libraries. - USE_Libheif: 'OFF' - - - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - with: - name: cibw-wheels-${{ matrix.python }} - path: ./wheelhouse/*.whl + # macos: + # name: Build wheels on macOS + # runs-on: macos-13 + # if: | + # github.event_name != 'schedule' || + # github.repository == 'AcademySoftwareFoundation/OpenImageIO' + # strategy: + # matrix: + # include: + # # ------------------------------------------------------------------- + # # CPython 64 bits + # # ------------------------------------------------------------------- + # - build: CPython 3.9 64 bits + # python: cp39-macosx_x86_64 + # arch: x86_64 + # - build: CPython 3.10 64 bits + # python: cp310-macosx_x86_64 + # arch: x86_64 + # - build: CPython 3.11 64 bits + # python: cp311-macosx_x86_64 + # arch: x86_64 + # - build: CPython 3.12 64 bits + # python: cp312-macosx_x86_64 + # arch: x86_64 + # - build: CPython 3.13 64 bits + # python: cp313-macosx_x86_64 + # arch: x86_64 + + # steps: + # - name: Checkout repo + # uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # - name: Install Python + # uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + # with: + # python-version: '3.9' + + # - name: Build wheels + # uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + # env: + # CIBW_BUILD: ${{ matrix.python }} + # CIBW_ARCHS: ${{ matrix.arch }} + # CMAKE_GENERATOR: "Unix Makefiles" + # # TODO: Re-enable HEIF when we provide a build recipe that does + # # not include GPL-licensed dynamic libraries. + # USE_Libheif: 'OFF' + + # - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + # with: + # name: cibw-wheels-${{ matrix.python }} + # path: ./wheelhouse/*.whl # --------------------------------------------------------------------------- # macOS ARM Wheels @@ -389,7 +389,7 @@ jobs: upload_pypi: - needs: [sdist, linux, linux-arm, macos, macos-arm, windows] + needs: [sdist, linux, linux-arm, macos-arm, windows] runs-on: ubuntu-latest permissions: id-token: write From 6d6a35fd5fd595dd2dff3e35a788ca425dd7e5ae Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 15 Sep 2025 19:47:05 +0200 Subject: [PATCH 008/508] fix(ffmpeg): FFmpeg sets zero oiio:BitsPerSample (#4885) Many codecs don't provide `bits_per_raw_sample`. The bit depth of the luma channel is the closest equivalent to a single bit depth then, while the chroma channels may be subsampled. Tested on basically all the video files on my computer, they all give a non-zero bit depth that looks correct now. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/ffmpeg.imageio/ffmpeginput.cpp | 16 ++++++++++++++-- .../{out-ffmpeg-6.1.txt => out-ffmpeg6.1.txt} | 14 +++++++------- testsuite/ffmpeg/ref/out-ffmpeg8.0.txt | 14 +++++++------- 3 files changed, 28 insertions(+), 16 deletions(-) rename testsuite/ffmpeg/ref/{out-ffmpeg-6.1.txt => out-ffmpeg6.1.txt} (93%) diff --git a/src/ffmpeg.imageio/ffmpeginput.cpp b/src/ffmpeg.imageio/ffmpeginput.cpp index ae1be13b7d..257dace628 100644 --- a/src/ffmpeg.imageio/ffmpeginput.cpp +++ b/src/ffmpeg.imageio/ffmpeginput.cpp @@ -30,6 +30,7 @@ extern "C" { // ffmpeg is a C api #endif #include +#include } @@ -528,8 +529,19 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) m_spec.attribute("FramesPerSecond", TypeRational, &rat); m_spec.attribute("oiio:Movie", true); m_spec.attribute("oiio:subimages", int(m_frames)); - m_spec.attribute("oiio:BitsPerSample", - m_codec_context->bits_per_raw_sample); + if (m_codec_context->bits_per_raw_sample) { + m_spec.attribute("oiio:BitsPerSample", + m_codec_context->bits_per_raw_sample); + } else { + // If bits_per_raw_sample is not provided, the bit depth of the + // luma channel is the closest equivalent to a single bit depth. + const AVPixFmtDescriptor* pix_format_desc = av_pix_fmt_desc_get( + src_pix_format); + if (pix_format_desc && pix_format_desc->nb_components > 0) { + m_spec.attribute("oiio:BitsPerSample", + pix_format_desc->comp[0].depth); + } + } m_spec.attribute("ffmpeg:codec_name", m_codec_context->codec->long_name); /* The ffmpeg enums are documented to match CICP values, except the color range. */ const int cicp[4] diff --git a/testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt b/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt similarity index 93% rename from testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt rename to testsuite/ffmpeg/ref/out-ffmpeg6.1.txt index 82b00b1a10..1553a20790 100644 --- a/testsuite/ffmpeg/ref/out-ffmpeg-6.1.txt +++ b/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt @@ -10,7 +10,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 1: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -22,7 +22,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 2: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -34,7 +34,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 3: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -46,7 +46,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 4: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -58,7 +58,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 5: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -70,7 +70,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 6: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -82,6 +82,6 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 diff --git a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt index 988ef28210..082a0797f2 100644 --- a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt +++ b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt @@ -10,7 +10,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 1: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -22,7 +22,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 2: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -34,7 +34,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 3: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -46,7 +46,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 4: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -58,7 +58,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 5: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -70,7 +70,7 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 subimage 6: 192 x 108, 3 channel, uint8 FFmpeg movie @@ -82,6 +82,6 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie FramesPerSecond: 24/1 (24) SCENE: "Scene" ffmpeg:codec_name: "Google VP9" - oiio:BitsPerSample: 0 + oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 From 94cb4ed1c84c16e9a637a7fa86d862aea9f988e9 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 15 Sep 2025 10:50:01 -0700 Subject: [PATCH 009/508] admin: Adjust license notices of A2-only source (#4884) There are still a few historical authors who never signed on to the relicensing of OpenImageIO from BSD-3-Clause to Apache-2.0 two years ago, usually because we could find no current way to contact them. The few (< 25) files that still had their extant code were therefore marked as using both licenses. They they almost always are down to just a few lines in the whole file that are still original to those authors (and therefore BSD), so every once in a while, natural code churn and rewrites of what they touched remove the very last of these lines from a file, leaving that file entirely Apache-2.0. I noticed that this was the case for these three files, and so removed the reference to BSD license, which no longer applies. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/imagebuf.h | 2 +- src/libOpenImageIO/imagebuf.cpp | 2 +- src/webp.imageio/webpoutput.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/include/OpenImageIO/imagebuf.h b/src/include/OpenImageIO/imagebuf.h index ad55228101..1b9238419b 100644 --- a/src/include/OpenImageIO/imagebuf.h +++ b/src/include/OpenImageIO/imagebuf.h @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index e25c1f1fc2..8206ff3a8d 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 3e65ed30e8..5b743e2653 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO #include From f565f51ba15b88890749ff4aec6b3147cf50743b Mon Sep 17 00:00:00 2001 From: vvalderrv Date: Mon, 15 Sep 2025 14:23:39 -0500 Subject: [PATCH 010/508] fix: Switch to compile-commands (#4879) Fix SonarCloud analysis failures by switching the C/C++ configuration from the deprecated `sonar.cfamily.build-wrapper-output` property to the current `sonar.cfamily.compile-commands` property. **Changes** - Remove `sonar.cfamily.build-wrapper-output` from `sonar-project.properties`. - In `.github/workflows/build-steps.yml`, pass `--define sonar.cfamily.compile-commands="$BUILD_WRAPPER_OUT_DIR/compile_commands.json"`. **Root cause** The SonarCloud config still used the deprecated `sonar.cfamily.build-wrapper-output` property. The current CFamily analyzer expects a `compile_commands.json` (produced by build-wrapper). Using the old property caused scanner failures and blocked analysis. **Validation** Regenerated `bw_output/compile_commands.json`, ran `sonar-scanner`, and confirmed the scan completed and uploaded successfully. **Tests** CI-only change. Verified locally by running the scanner against a fresh build-wrapper capture. --------- Signed-off-by: Vanessa Valderrama Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 4 ++-- sonar-project.properties | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index cbc2133223..a35687a563 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -186,8 +186,8 @@ jobs: ls -l /__w/OpenImageIO/OpenImageIO/bw_output echo "BUILD_OUTPUT_DIR is " "${{ env.BUILD_WRAPPER_OUT_DIR }}" find . -name "*.gcov" -print - # sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" - time sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="$BUILD_WRAPPER_OUT_DIR" --define sonar.cfamily.gcov.reportsPath="_coverage" --define sonar.cfamily.threads="$PARALLEL" + # sonar-scanner --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" + time sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.compile-commands="$BUILD_WRAPPER_OUT_DIR/compile_commands.json" --define sonar.cfamily.gcov.reportsPath="_coverage" --define sonar.cfamily.threads="$PARALLEL" # Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options - name: Check ABI if: inputs.abi_check != '' diff --git a/sonar-project.properties b/sonar-project.properties index d988b8e397..e3a05e7aad 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -21,7 +21,6 @@ sonar.exclusions=src/doc/**,src/build-scripts/**,src/include/OpenImageIO/detail/ sonar.sourceEncoding=UTF-8 # C/C++ analyzer properties -sonar.cfamily.build-wrapper-output=/__w/OpenImageIO/OpenImageIO/bw_output sonar.cfamily.gcov.reportsPath=_coverage sonar.coverage.exclusions=src/iv/**,src/include/OpenImageIO/detail/pugixml/**,src/include/OpenImageIO/detail/fmt/**,src/libOpenImageIO/kissfft.hh From 15e3c0f58043d149eaf1f555bb745d708a381437 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 16 Sep 2025 15:08:26 -0700 Subject: [PATCH 011/508] ci: Fix testsuite/heif ref output (#4892) PR #4880 checked in some output reference that contained version numbers, which passed in main but failed when backported to 3.1. This repairs it and makes the output suitable for any version that contains the patch. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- testsuite/heif/ref/out-libheif1.12-orient.txt | 2 -- testsuite/heif/ref/out-libheif1.4.txt | 2 -- testsuite/heif/ref/out-libheif1.5.txt | 2 -- testsuite/heif/ref/out-libheif1.9-alt2.txt | 2 -- testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt | 2 -- testsuite/heif/ref/out-libheif1.9-with-av1.txt | 2 -- testsuite/heif/ref/out-libheif1.9.txt | 2 -- testsuite/heif/run.py | 2 +- 8 files changed, 1 insertion(+), 15 deletions(-) diff --git a/testsuite/heif/ref/out-libheif1.12-orient.txt b/testsuite/heif/ref/out-libheif1.12-orient.txt index e9ef2777bb..bb8a45b94e 100644 --- a/testsuite/heif/ref/out-libheif1.12-orient.txt +++ b/testsuite/heif/ref/out-libheif1.12-orient.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.4.txt b/testsuite/heif/ref/out-libheif1.4.txt index 48384b5e0d..9f53aa083c 100644 --- a/testsuite/heif/ref/out-libheif1.4.txt +++ b/testsuite/heif/ref/out-libheif1.4.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.5.txt b/testsuite/heif/ref/out-libheif1.5.txt index 8a371fca47..fb050fe42b 100644 --- a/testsuite/heif/ref/out-libheif1.5.txt +++ b/testsuite/heif/ref/out-libheif1.5.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-alt2.txt b/testsuite/heif/ref/out-libheif1.9-alt2.txt index 9d92f3b2ed..3fa6718e28 100644 --- a/testsuite/heif/ref/out-libheif1.9-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-alt2.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt index 9d92f3b2ed..3fa6718e28 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1.txt b/testsuite/heif/ref/out-libheif1.9-with-av1.txt index 5253081cdc..87614ec80c 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9.txt b/testsuite/heif/ref/out-libheif1.9.txt index 5253081cdc..87614ec80c 100644 --- a/testsuite/heif/ref/out-libheif1.9.txt +++ b/testsuite/heif/ref/out-libheif1.9.txt @@ -60,10 +60,8 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 channel list: R, G, B, A CICP: 9, 16, 9, 1 - Software: "OpenImageIO 3.2.0.0dev : A50DC799B2B4CA667217608C0F82302455E5D32A" Exif:ExifVersion: "0230" Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/run.py b/testsuite/heif/run.py index 8886f0de7d..9dc5a06f35 100755 --- a/testsuite/heif/run.py +++ b/testsuite/heif/run.py @@ -11,7 +11,7 @@ command += oiiotool (os.path.join(imagedir, "test-10bit.avif") + " -d uint10 --cicp \"9,16,9,1\" -o cicp_pq.avif" ) -command += info_command ("cicp_pq.avif") +command += info_command ("cicp_pq.avif", safematch=True) files = [ "greyhounds-looking-for-a-table.heic", "sewing-threads.heic" ] for f in files: From f455b165705bb5589a5fb105763a1c8aa627619a Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 17 Sep 2025 11:12:04 +1200 Subject: [PATCH 012/508] feat(raw): add thumbnail support to the raw input plugin (#4887) This adds thumbnail support to the raw input plugin. Fixes #4107 This supports 3 of the 4 formats of thumbnails Libraw provides: BMP, JPEG, JPEGXL(untested). H265 is not supported, I don't know of any camera using it, and expect the code being significantly different from the other formats, given that the H265 is a movie container. One thing I don't like about this implementation - I understand there is no way to query the number of thumbnails available in the file? We are expected to iterate, until `get_thumbnail()` returns false? If `get_thumbnail()` returns false not because there is no thumbnail for the requested index, but any other reason, we are not going to try reading past that index. ## Tests I have tested manually with a bunch of raw images having either JPEG or BMP thumbnails. I have not tested the JPEGXL branch, as I'm not aware of any cameras using that for thumbnails. The code is identical to the JPEG branch though, so I'm not expecting any issues with that. I couldn't see any unit tests around the thumbnail functionality at all. Should I add some? I assume we can't use `oiiotool` for this? --------- Signed-off-by: Anton Dukhovnikov Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/raw.imageio/rawinput.cpp | 130 ++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 5d8bae4f8f..8fa7c6bc71 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -10,7 +10,9 @@ #include #include +#include #include +#include #include #include #include @@ -61,7 +63,7 @@ class RawInput final : public ImageInput { const char* format_name(void) const override { return "raw"; } int supports(string_view feature) const override { - return (feature == "exif" + return (feature == "exif" || feature == "thumbnail" /* not yet? || feature == "iptc"*/); } bool open(const std::string& name, ImageSpec& newspec) override; @@ -70,6 +72,7 @@ class RawInput final : public ImageInput { bool close() override; bool read_native_scanline(int subimage, int miplevel, int y, int z, void* data) override; + bool get_thumbnail(ImageBuf& thumb, int subimage) override; private: bool m_process = true; @@ -1702,4 +1705,129 @@ RawInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, return true; } +template +void +_errorfmt(const RawInput* input, int subimage, const char* format, + const Args&... args) +{ + std::string fmt = "Failed to extract thumbnail at index " + + std::to_string(subimage) + ": " + format + "."; + input->errorfmt(fmt.c_str(), args...); +} + +bool +RawInput::get_thumbnail(ImageBuf& thumb, int subimage) +{ + if (m_processor == nullptr) { + _errorfmt(this, subimage, + "ImageInput hasn't been initialised properly"); + return false; + } + +#if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) + if (subimage > 0) { + // Older versions of Libraw supported a single thumbnail per image. + // No error here. + return false; + } + int errcode = m_processor->unpack_thumb(); + if (errcode != 0) { + if (errcode != LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE) + _errorfmt(this, subimage, "unpack_thumb error"); + return false; + } +#else + int errcode = m_processor->unpack_thumb_ex(subimage); + if (errcode != 0) { + if (errcode != LIBRAW_REQUEST_FOR_NONEXISTENT_THUMBNAIL) + _errorfmt(this, subimage, "unpack_thumb_ex error"); + return false; + } +#endif + + libraw_processed_image_t* mem_thumb = m_processor->dcraw_make_mem_thumb( + &errcode); + if (mem_thumb == nullptr) { + _errorfmt(this, subimage, "dcraw_make_mem_thumb error"); + return false; + } + + std::string image_type; + if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_JPEG) + image_type = "jpeg"; + else if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_BITMAP) + image_type = "bmp"; +#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 22, 0) + else if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_JPEGXL) + image_type = "jpegxl"; + else if (mem_thumb->type == LibRaw_image_formats::LIBRAW_IMAGE_H265) + image_type = "h265"; +#endif + + if (image_type == "h265") { + _errorfmt(this, subimage, "h265 thumbnails are not supported yet"); + return false; + } + + if (image_type.empty()) { + _errorfmt(this, subimage, "unknown image type {}", + static_cast(mem_thumb->type)); + return false; + } + + if (image_type == "bmp") { + size_t data_size = mem_thumb->width * mem_thumb->height + * mem_thumb->colors; + if (data_size != mem_thumb->data_size) + return false; + + ImageSpec image_spec(mem_thumb->width, mem_thumb->height, + mem_thumb->colors, TypeDesc::UCHAR); + thumb.reset(image_spec); + thumb.set_pixels(thumb.roi_full(), TypeDesc::UCHAR, mem_thumb->data); + } else { + auto image_input = OIIO::ImageInput::create(image_type, false); + if (image_input == nullptr) { + _errorfmt(this, subimage, "OIIO::ImageInput::create(\{}\") error", + image_type); + return false; + } + + Filesystem::IOMemReader proxy(mem_thumb->data, mem_thumb->data_size); + bool result = image_input->valid_file(&proxy); + if (!result) { + _errorfmt(this, subimage, + "the thumbnail is not a valid image of type \"{}\"", + image_type); + return false; + } + + ImageSpec temp_spec, image_spec; + Filesystem::IOProxy* pp = &proxy; + temp_spec.attribute("oiio:ioproxy", TypeDesc::PTR, &pp); + + result = image_input->open("", image_spec, temp_spec); + if (!result) { + _errorfmt( + this, subimage, + "failed to initialise an ImageInput object with the thumbnail data"); + return false; + } + + thumb.reset(image_spec); + result = image_input->read_image(0, 0, 0, image_spec.nchannels, + image_spec.format, + thumb.localpixels()); + if (!result) { + _errorfmt( + this, subimage, + "failed to initialise an ImageInput object of type \"{}\" with the thumbnail data", + image_type); + return false; + } + } + + return true; +} + OIIO_PLUGIN_NAMESPACE_END From 1be8710075536ec6c217f61c5fb5d9930ff90ace Mon Sep 17 00:00:00 2001 From: Zach Lewis Date: Tue, 16 Sep 2025 20:04:15 -0400 Subject: [PATCH 013/508] ci: Fix broken python wheel building (#4855) Signed-off-by: Zach Lewis Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/wheel.yml | 133 +++++++++++++++++++-------------- CMakeLists.txt | 2 + pyproject.toml | 4 +- src/cmake/build_yaml-cpp.cmake | 1 + 4 files changed, 81 insertions(+), 59 deletions(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 343fb97540..377f74d363 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -157,9 +157,16 @@ jobs: name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} path: | ./wheelhouse/*.whl + + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: stubs-${{ matrix.python }}-${{ matrix.manylinux }} + path: | ./wheelhouse/OpenImageIO/__init__.pyi - # if stub validation fails we want to upload the stubs for users to review - if: success() || failure() + # if stub validation fails we want to upload the stubs for users to review. + # keep the python build in sync with the version specified in tool.cibuildwheel.overrides + # section of pyproject.toml + if: always() && contains(matrix.python, 'cp311-manylinux') # --------------------------------------------------------------------------- # Linux ARM Wheels @@ -219,65 +226,78 @@ jobs: name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} path: | ./wheelhouse/*.whl + + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: stubs-${{ matrix.python }}-${{ matrix.manylinux }} + path: | ./wheelhouse/OpenImageIO/__init__.pyi - # if stub validation fails we want to upload the stubs for users to review - if: success() || failure() + # if stub validation fails we want to upload the stubs for users to review. + # keep the python build in sync with the version specified in tool.cibuildwheel.overrides + # section of pyproject.toml + if: always() && contains(matrix.python, 'cp311-manylinux') # --------------------------------------------------------------------------- # macOS Wheels # --------------------------------------------------------------------------- - # macos: - # name: Build wheels on macOS - # runs-on: macos-13 - # if: | - # github.event_name != 'schedule' || - # github.repository == 'AcademySoftwareFoundation/OpenImageIO' - # strategy: - # matrix: - # include: - # # ------------------------------------------------------------------- - # # CPython 64 bits - # # ------------------------------------------------------------------- - # - build: CPython 3.9 64 bits - # python: cp39-macosx_x86_64 - # arch: x86_64 - # - build: CPython 3.10 64 bits - # python: cp310-macosx_x86_64 - # arch: x86_64 - # - build: CPython 3.11 64 bits - # python: cp311-macosx_x86_64 - # arch: x86_64 - # - build: CPython 3.12 64 bits - # python: cp312-macosx_x86_64 - # arch: x86_64 - # - build: CPython 3.13 64 bits - # python: cp313-macosx_x86_64 - # arch: x86_64 - - # steps: - # - name: Checkout repo - # uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - # - name: Install Python - # uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 - # with: - # python-version: '3.9' - - # - name: Build wheels - # uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 - # env: - # CIBW_BUILD: ${{ matrix.python }} - # CIBW_ARCHS: ${{ matrix.arch }} - # CMAKE_GENERATOR: "Unix Makefiles" - # # TODO: Re-enable HEIF when we provide a build recipe that does - # # not include GPL-licensed dynamic libraries. - # USE_Libheif: 'OFF' - - # - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - # with: - # name: cibw-wheels-${{ matrix.python }} - # path: ./wheelhouse/*.whl + macos: + name: Build wheels on macOS + runs-on: macos-13 + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: + matrix: + include: + # ------------------------------------------------------------------- + # CPython 64 bits + # ------------------------------------------------------------------- + - build: CPython 3.9 64 bits + python: cp39-macosx_x86_64 + arch: x86_64 + - build: CPython 3.10 64 bits + python: cp310-macosx_x86_64 + arch: x86_64 + - build: CPython 3.11 64 bits + python: cp311-macosx_x86_64 + arch: x86_64 + - build: CPython 3.12 64 bits + python: cp312-macosx_x86_64 + arch: x86_64 + - build: CPython 3.13 64 bits + python: cp313-macosx_x86_64 + arch: x86_64 + + steps: + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install Python + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + with: + python-version: '3.9' + + - name: Remove brew OpenEXR/Imath + run: | + brew uninstall --ignore-dependencies openexr imath || true + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + MACOSX_DEPLOYMENT_TARGET: 10.15 + CMAKE_GENERATOR: "Unix Makefiles" + # TODO: Re-enable HEIF when we provide a build recipe that does + # not include GPL-licensed dynamic libraries. + USE_Libheif: 'OFF' + + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: cibw-wheels-${{ matrix.python }} + path: ./wheelhouse/*.whl + # --------------------------------------------------------------------------- # macOS ARM Wheels @@ -317,7 +337,6 @@ jobs: - name: Install Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 - # https://cibuildwheel.pypa.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64 with: python-version: '3.9' @@ -389,7 +408,7 @@ jobs: upload_pypi: - needs: [sdist, linux, linux-arm, macos-arm, windows] + needs: [sdist, linux, linux-arm, macos, macos-arm, windows] runs-on: ubuntu-latest permissions: id-token: write diff --git a/CMakeLists.txt b/CMakeLists.txt index ee4b9d413f..2ed1589cfc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,7 +191,9 @@ if (IGNORE_HOMEBREWED_DEPS) # Define the list of prefixes to ignore set (HOMEBREW_PREFIXES /opt/homebrew + /opt/homebrew/Cellar /usr/local + /usr/local/Cellar /usr/X11 /usr/X11R6 /opt/X11 diff --git a/pyproject.toml b/pyproject.toml index e38f7e924e..b243ba3ddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,12 +88,14 @@ if.platform-system = "darwin" if.platform-machine = "arm64" inherit.cmake.define = "append" cmake.define.CMAKE_OSX_ARCHITECTURES = "arm64" +cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET = "11" [[tool.scikit-build.overrides]] if.platform-system = "darwin" if.platform-machine = "x86_64" inherit.cmake.define = "append" cmake.define.CMAKE_OSX_ARCHITECTURES = "x86_64" +cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET = "10.15" [tool.cibuildwheel] build-verbosity = 1 @@ -109,8 +111,6 @@ test-command = "oiiotool --buildinfo" [tool.cibuildwheel.macos.environment] SKBUILD_CMAKE_ARGS = "-DLINKSTATIC=1; -DIGNORE_HOMEBREWED_DEPS=1" -# C++17 - std::filesystem is only available in macOS 10.15 and later; ARM compatibility introduced in 11. -MACOSX_DEPLOYMENT_TARGET = "11" # Optimize for size (not speed). SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" diff --git a/src/cmake/build_yaml-cpp.cmake b/src/cmake/build_yaml-cpp.cmake index 05cab6187f..1de3b876a8 100644 --- a/src/cmake/build_yaml-cpp.cmake +++ b/src/cmake/build_yaml-cpp.cmake @@ -25,6 +25,7 @@ build_dependency_with_cmake(yaml-cpp -D YAML_CPP_BUILD_CONTRIB=OFF -D YAML_BUILD_SHARED_LIBS=${yaml-cpp_BUILD_SHARED_LIBS} -D CMAKE_INSTALL_LIBDIR=lib + -D CMAKE_POLICY_VERSION_MINIMUM=3.5 ) set (yaml-cpp_ROOT ${yaml-cpp_LOCAL_INSTALL_DIR}) From a81b2884b0ce5f51cc485715a1bd7a56ba2e5e6a Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 17 Sep 2025 20:56:38 -0700 Subject: [PATCH 014/508] fix: gif output didn't handle FramesPerSecond attribute correctly (#4890) The version of open that takes an array of subimage specs didn't use them properly for finding the FramesPerSecond hint (or the ioproxy!). Fixes #3716 Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/gif.imageio/gifoutput.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gif.imageio/gifoutput.cpp b/src/gif.imageio/gifoutput.cpp index 49e320d0ab..aa74746c4e 100644 --- a/src/gif.imageio/gifoutput.cpp +++ b/src/gif.imageio/gifoutput.cpp @@ -155,10 +155,10 @@ GIFOutput::open(const std::string& name, int subimages, const ImageSpec* specs) m_subimage = 0; m_nsubimages = subimages; m_subimagespecs.assign(specs, specs + subimages); - float fps = m_spec.get_float_attribute("FramesPerSecond", 1.0f); + float fps = specs[0].get_float_attribute("FramesPerSecond", 1.0f); m_delay = (fps == 0.0f ? 0 : (int)(100.0f / fps)); - ioproxy_retrieve_from_config(m_spec); + ioproxy_retrieve_from_config(specs[0]); if (!ioproxy_use_or_open(name)) return false; From acd3002ccad4879f2adf96115fdfa1706b2b83de Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 18 Sep 2025 14:25:42 -0700 Subject: [PATCH 015/508] build: fix openjph target use (#4894) PR #4875, switched to using openjph's exported cmake config instead of our own FindOpenJPH.cmake, and in the process also renamed OpenJPH -> openjph to follow their convention. But I botched it, still using the old OPENJPH_LIBRARIES (etc.) variables instead of fully switching to the correct targets exported from their config. This PR minimally fixes that error, re-enabling use of openjph. Fixes #4893 Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/testing.cmake | 2 +- src/jpeg2000.imageio/CMakeLists.txt | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 113b993242..63e94c9ef8 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -278,7 +278,7 @@ macro (oiio_add_all_tests) FOUNDVAR OPENJPEG_FOUND IMAGEDIR oiio-images URL "Recent checkout of OpenImageIO-images") oiio_add_tests (htj2k - FOUNDVAR OPENJPH_FOUND + FOUNDVAR openjph_FOUND IMAGEDIR oiio-images URL "Recent checkout of OpenImageIO-images") oiio_add_tests (jpeg2000-j2kp4files FOUNDVAR OPENJPEG_FOUND diff --git a/src/jpeg2000.imageio/CMakeLists.txt b/src/jpeg2000.imageio/CMakeLists.txt index 910ae5811d..a989e48495 100644 --- a/src/jpeg2000.imageio/CMakeLists.txt +++ b/src/jpeg2000.imageio/CMakeLists.txt @@ -8,10 +8,7 @@ if (OPENJPEG_FOUND) set(_jpeg2000_libs ${OPENJPEG_LIBRARIES}) set(_jpeg2000_defs "USE_OPENJPEG") - if (OPENJPH_FOUND) - list(APPEND _jpeg2000_includes ${OPENJPH_INCLUDES}) - list(APPEND _jpeg2000_lib_dirs ${OPENJPH_LIBRARY_DIRS}) - list(APPEND _jpeg2000_libs ${OPENJPH_LIBRARIES}) + if (openjph_FOUND) list(APPEND _jpeg2000_defs "USE_OPENJPH") endif() @@ -19,6 +16,7 @@ if (OPENJPEG_FOUND) INCLUDE_DIRS ${_jpeg2000_includes} LINK_DIRECTORIES ${_jpeg2000_lib_dirs} LINK_LIBRARIES ${_jpeg2000_libs} + $ DEFINITIONS ${_jpeg2000_defs} ) else() From 29802f6c65a9f10f938414c5f943dc8c074a9080 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 19 Sep 2025 21:52:46 -0700 Subject: [PATCH 016/508] CHANGES Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CHANGES.md | 113 ++++++++++++++++++++++++++++++++++---------- CREDITS.md | 2 + docs/CHANGES-2.x.md | 8 ++++ 3 files changed, 98 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9e5d916081..38620323e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 * *ImageCache/TextureSystem*: * New global attribute queries via OIIO::getattribute(): * Miscellaneous API changes: + - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) ### 🚀 Performance improvements ### 🐛 Fixes and feature enhancements ### 🔧 Internals and developer goodies @@ -24,11 +25,31 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 --- --- -Release 3.1 (target: Sept 2025?) -- compared to 3.0 ---------------------------------------------------- +Release 3.1 (target: Oct 1 2025?) -- compared to 3.0 +---------------------------------------------------- - Beta 1: Aug 22, 2025 -- Anticipated release candidate: Sep 1, 2025 -- Anticipated supported release: Sep 15, 2025 +- Beta 2: Sep 19, 2025 +- Anticipated release candidate: Sep 24, 2025 +- Anticipated supported release: Oct 1, 2025 + +**Change highlights in beta 2** + - *oiiotool*: Allow easy splitting of subimages by name [#4874](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4874) (3.1.5.0) + - *ffmpeg*: Add ability to read CICP metadata [#4882](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4882) (by Brecht Van Lommel) (3.1.5.0) + - *ffmpeg*: FFmpeg incorrectly set zero oiio:BitsPerSample [#4885](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4885) (by Brecht Van Lommel) (3.1.5.0) + - *gif*: Gif output didn't handle FramesPerSecond attribute correctly [#4890](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4890) (3.1.5.0) + - *heic*: Read and write of CICP and support for bit depth 10 and 12 [#4880](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4880) (by Brecht Van Lommel) (3.1.5.0) + - *png*: CICP metadata support for PNG [#4746](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4746) (by Zach Lewis) (3.1.5.0) + - *raw*: Add thumbnail support to the raw input plugin [#4887](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4887) (by Anton Dukhovnikov) (3.1.5.0) + - *webp*: Support reading/writing the ICCProfile attribute [#4878](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4878) (by Jesse Yurkovich) (3.1.5.0) + - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.1.5.0) + - *deps(freetype)*: Test freetype 2.14 and document that it works [#4876](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4876) (3.1.5.0) + - *deps(ffmpeg)*: Ffmpeg 8 support [#4870](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4870) (3.1.5.0) + - *deps(openvdb)*: Look for boost headers for OpenVDBs older than 12 [#4873](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4873) (by Alex Fuller) (3.1.5.0) + - *deps(openexr)*: OpenEXR 3.4 supports two compression types for HTJ2K [#4871](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4871) (by Todica Ionut) (3.1.5.0) + - *deps(openexr)*: Several OpenEXR and OpenJPH build related fixes [#4875](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4875) (3.1.5.0) + - *deps(openjph)*: Fix openjph target use [#4894](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4894) (3.1.5.0) + - *ci*: Fix broken python wheel building [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) (3.1.5.0) + **NOTE:** We anticipate some additional changes to color management to be rolled out during the beta period. It will not include any breaks to API or @@ -46,8 +67,9 @@ ABI compatibility, but we do expect some behavior changes. - Support in Python for `ImageBuf._repr_png_` method allows use of OIIO inside [Jupyter Notebooks](https://jupyter.org/) to display computed images. - - Color management improvements to conform to Color Interchange Forum and - OpenEXR new conventions for naming and specifying color spaces. + - Color management improvements: Conform to Color Interchange Forum and + OpenEXR new conventions for naming and specifying color spaces. PNG, + HEIC, and ffmpeg/video files now support reading CICP metadata. ### New minimum dependencies and compatibility changes: * *Python*: 3.9 minimum (from 3.7) [#4830](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4830) (3.1.4.0) @@ -67,6 +89,7 @@ ABI compatibility, but we do expect some behavior changes. if they doesn't already exist [#4762](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4762) (by Dharshan Vishwanatha) (3.1.3.0) - `--eraseattrib` new modifier `:fromfile=1` reads from a file to get a list of patterns to specify the attributes to erase. [#4763](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4763) (by Lydia Zheng) (3.1.3.0) + - *oiiotool*: Allow easy splitting of subimages by name [#4874](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4874) (3.1.5.0) * *Command line utilities*: - *iv*: Implement files drag and drop into an iv window [#4774](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4774) (by Aleksandr Motsjonov) (3.1.3.0) - *iv*: Area probe [#4767](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4767) (by Danielle Imogu) (3.1.3.0) @@ -89,7 +112,7 @@ ABI compatibility, but we do expect some behavior changes. - Added queries for available font families and styles [#4523](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4523) (by peterhorvath111) (3.1.0.0/3.0.1.0) - *api*: Add global attribute `imageinput:strict` [#4560](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4560) (3.1.0.0) * Miscellaneous API changes: - - *api*: Make a 2-level namespace scheme [#4567](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4567) (by Larry Gritz) [#4603](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4603) (by Brecht Van Lommel) (3.1.1.0) + - *api*: Make a 2-level namespace scheme [#4567](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4567) (by Larry Gritz) [#4603](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4603) (by Brecht Van Lommel) (3.1.1.0) [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.1.5.0) - *api*: ImageSpec::scanline_bytes, tile_bytes, image_bytes [#4631](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4631) (3.1.1.0) - *python*: ImageBuf `_repr_png_` method added, which allows use of ImageBuf in [Jupyter Notebooks](https://jupyter.org/) as a displayable object. [#4753](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4753) (by Oktay Comu) (3.1.3.0) @@ -98,6 +121,7 @@ ABI compatibility, but we do expect some behavior changes. * Color management changes - *color mgmt*: Don't assume unlabeled OpenEXR files are lin_rec709 [#4840](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4840) (3.1.4.0) - *color mgmt*: Color space renaming to adhere to CIF conventions [#4860](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4860) (3.1.4.0) + - Ability to read CICP metadata from PNG [#4746](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4746) (by Zach Lewis) (3.1.5.0), HEIC [#4880](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4880) (by Brecht Van Lommel) (3.1.5.0), and video files/ffmpeg [#4882](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4882) (by Brecht Van Lommel) (3.1.5.0). ### 🚀 Performance improvements: @@ -137,12 +161,17 @@ ABI compatibility, but we do expect some behavior changes. - *exr*: Fill in OpenEXR lineOrder attribute when reading [#4628](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4628) (by vernalchen) (3.1.1.0) - *exr*: Did not properly allocate 'missingcolor' vector [#4751](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4751) (3.1.3.0) - *exr*: Not honoring 'missingcolor' for scanline files [#4757](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4757) (3.1.3.0) + - *ffmpeg*: FFmpeg incorrectly set zero oiio:BitsPerSample [#4885](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4885) (by Brecht Van Lommel) (3.1.5.0) + - *ffmpeg*: Add ability to read CICP metadata [#4882](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4882) (by Brecht Van Lommel) (3.1.5.0) + - *gif*: Gif output didn't handle FramesPerSecond attribute correctly [#4890](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4890) (3.1.5.0) + - *heic*: Read and write of CICP and support for bit depth 10 and 12 [#4880](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4880) (by Brecht Van Lommel) (3.1.5.0) - *ico*: More robust to corrupted ICO files [#4625](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4625) (3.1.1.0) - *iff*: Improved IFF support reading and writing z buffers [#4673](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4673) (by Mikael Sundell) (3.1.3.0) - *jpeg*: Support encoding/decoding arbitrary metadata as comments [#4430](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4430) (by Lukas Stockner) (3.1.0.0/3.0.1.0) - *jpeg-2000*: Write .j2c by adding HTJ2K Encoding using the OpenJPH library. [#4699](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4699) (by Sam Richards) (3.1.3.0) - *png*: Alpha premultiplication adjustment and attribute [#4585](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4585) (3.1.1.0) - *png*: Increase allowed width/height limit [#4655](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4655) (by Jesse Yurkovich) (3.1.1.0) + - *png*: CICP metadata support for PNG [#4746](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4746) (by Zach Lewis) (3.1.5.0) - *pnm*: Broken pgm having memory access error [#4559](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4559) (3.1.0.0) - *psd*: Perform endian byteswap on correct buffer area for PSD RLE [#4600](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4600) (by Jesse Yurkovich) (3.1.1.0) - *psd*: ICC profile reading improvements, especially for PSD [#4644](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4644) (3.1.1.0) @@ -150,11 +179,13 @@ ABI compatibility, but we do expect some behavior changes. - *raw*: Fix channel layout [#4516](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4516) (by Anton Dukhovnikov) (3.1.0.0/3.0.1.0) - *raw*: Add black level and BPS metadata [#4601](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4601) (by Anton Dukhovnikov) (3.1.1.0) - *raw*: Add `raw:ForceLoad` ImageInput configuration hint [#4704](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4704) (by Anton Dukhovnikov) (3.1.3.0) + - *raw*: Add thumbnail support to the raw input plugin [#4887](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4887) (by Anton Dukhovnikov) (3.1.5.0) - *rla*: More robust to corrupted RLA files that could overrun buffers [#4624](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4624) (3.1.1.0) - *sgi*: Fix valid_file to properly swap bytes on little-endian platforms [#4697](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4697) (by Jesse Yurkovich) (3.1.3.0) - *tiff*: The default value for bitspersample should be 1 [#4670](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4670) (by vernalchen) (3.1.1.0) - *webp*: Respect the `oiio:UnassociatedAlpha` attribute [#4770](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4770) (by Jesse Yurkovich) (3.1.3.0) - *webp*: Allow finer grained control over WEBP compression settings [#4772](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4772) (by Jesse Yurkovich) (3.1.3.0) + - *webp*: Support reading/writing the ICCProfile attribute [#4878](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4878) (by Jesse Yurkovich) (3.1.5.0) - *various formats*: Detect invalid ICC profile tags [#4557](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4557) [#4561](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4561) (3.1.0.0) - *various formats*: IPTC fields have length limits [#4568](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4568) (3.1.0.0) @@ -207,26 +238,32 @@ ABI compatibility, but we do expect some behavior changes. - *build/python*: Wheel upload_pypi step should only run from main repo [#4820](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4820) (3.1.3.0) - *build*: Fix typo related to finding ccache [#4833](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4833) (3.1.4.0) - *build*: C++23 support [#4844](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4844) (3.1.4.0) + - *build*: Clean up obsolete logic for old compilers [#4849](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4849) (3.1.5.0) * Dependency and platform support: - - *deps*: Support static OCIO self-builds [#4517](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4517) (by zachlewis) (3.1.0.0/3.0.1.0) - - *deps*: Add new ref output for libheif updates [#4525](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4525) (3.1.0.0/3.0.1.0) - - *build*: Add build recipe for PNG [#4423](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4423) (by zachlewis) (3.1.0.0/3.0.1.0) - - *deps*: Fix build_cmake.bash script for aarch64, bump its default version [#4581](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4581) (3.1.1.0) - - *deps*: Fix libraw definitions (again) [#4588](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4588) (3.1.1.0) - - *deps*: Detect libultrahdr version and enforce minimum of 1.3 [#4729](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4729) (3.1.3.0) - - *deps*: Fix fmt throwing behavior warnings [#4730](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4730) (3.1.3.0) - - *deps*: Fix failed test with old fmt [#4758](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4758) (3.1.3.0) - - *deps*: Fix new dcmtk 3.6.9 vs C++ warning [#4698](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4698) (3.1.3.0) - - *build*: PNG auto-build improvements [#4835](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4835) (3.1.4.0) - - *tests*: Update ref image for slightly changed freetype accents [#4765](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4765) (3.1.3.0) - - *deps/ffmpeg*: Replace deprecated and soon removed avcodec_close with avcodec_free_context [#4837](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4837) (by Vlad Erium) (3.1.4.0) - - *build/jpeg2000*: Update jpeg2000input.cpp to include cstdarg [#4836](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4836) (by Peter Kovář) (3.1.4.0) - - *deps*: Raise minimum supported Python from 3.7 to 3.9 [#4830](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4830) (3.1.4.0) - - *deps*: Use get_plane2 introduced by libheif 1.20.2 [#4851](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4851) (by toge) (3.1.4.0) + - *build(OCIO)*: Support static OCIO self-builds [#4517](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4517) (by zachlewis) (3.1.0.0/3.0.1.0) + - *build(PNG)*: Add build recipe for PNG [#4423](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4423) (by zachlewis) (3.1.0.0/3.0.1.0); PNG auto-build improvements [#4835](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4835) (3.1.4.0) + - *deps(cmake)*: Fix build_cmake.bash script for aarch64, bump its default version [#4581](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4581) (3.1.1.0) + - *deps(dcmtk)*: Fix new dcmtk 3.6.9 vs C++ warning [#4698](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4698) (3.1.3.0) + - *deps(ffmpeg)*: Ffmpeg 8 support [#4870](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4870) (3.1.5.0) + - *deps(ffmpeg)*: Replace deprecated and soon removed avcodec_close with avcodec_free_context [#4837](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4837) (by Vlad Erium) (3.1.4.0) + - *deps(fmt)*: Fix failed test with old fmt [#4758](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4758) (3.1.3.0) + - *deps(fmt)*: Fix fmt throwing behavior warnings [#4730](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4730) (3.1.3.0) + - *deps(freetype)*: Test freetype 2.14 and document that it works [#4876](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4876) (3.1.5.0) + - *deps(freetype)*: Update ref image for slightly changed freetype accents [#4765](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4765) (3.1.3.0) + - *deps(jpeg2000)*: Update jpeg2000input.cpp to include cstdarg [#4836](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4836) (by Peter Kovář) (3.1.4.0) + - *deps(libheif)*: Add new ref output for libheif updates [#4525](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4525) (3.1.0.0/3.0.1.0) + - *deps(libheif)*: Use get_plane2 introduced by libheif 1.20.2 [#4851](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4851) (by toge) (3.1.4.0) + - *deps(libraw)*: Fix libraw definitions (again) [#4588](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4588) (3.1.1.0) + - *deps(libultrahdr)*: Detect libultrahdr version and enforce minimum of 1.3 [#4729](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4729) (3.1.3.0) + - *deps(OCIO)*: Raise OpenColorIO minimum to 2.3 (from 2.2) [#4865](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4865) (3.1.4.0) + - *deps(openexr)*: OpenEXR 3.4 supports two compression types for HTJ2K [#4871](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4871) (by Todica Ionut) (3.1.5.0) + - *deps(openexr)*: Several OpenEXR and OpenJPH build related fixes [#4875](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4875) (3.1.5.0) + - *deps(openjph)*: Fix openjph target use [#4894](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4894) (3.1.5.0) + - *deps(openvdb)*: Look for boost headers for OpenVDBs older than 12 [#4873](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4873) (by Alex Fuller) (3.1.5.0) + - *deps(python)*: Raise minimum supported Python from 3.7 to 3.9 [#4830](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4830) (3.1.4.0) - *windows*: Include Windows version information on produced binaries [#4696](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4696) (by Jesse Yurkovich) (3.1.3.0) - - windows + ARM64*: Add arm_neon.h include on Windows ARM64 with clang-cl [#4691](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4691) (by Anthony Roberts) - - *build/windows*: Propagate CMAKE_MSVC_RUNTIME_LIBRARY [#4842](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4842) (3.1.4.0) - - *deps*: Raise OpenColorIO minimum to 2.3 (from 2.2) [#4865](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4865) (3.1.4.0) + - *windows*: Propagate CMAKE_MSVC_RUNTIME_LIBRARY [#4842](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4842) (3.1.4.0) + - *windows + ARM64*: Add arm_neon.h include on Windows ARM64 with clang-cl [#4691](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4691) (by Anthony Roberts) - *NetBSD*: Fix build on NetBSD [#4857](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4857) (by Thomas Klausner) (3.1.4.0) * Testing and Continuous integration (CI) systems: - *tests*: Improve Ptex testing [#4573](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4573) (3.1.1.0) @@ -274,6 +311,11 @@ ABI compatibility, but we do expect some behavior changes. - *ci*: Add a VFX Platform 2026 CI job [#4856](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4856) (3.1.4.0) - *ci*: Lock down to ci-oiio container with correct llvm components [#4859](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4859) (3.1.4.0) - *ci*: Bump webp and openexr for "latest versions" test [#4861](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4861) (3.1.4.0) + - *ci*: Switch to compile-commands for sonar [#4879](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4879) (by vvalderrv) (3.1.5.0) + - *ci*: Fix analysis workflow configuration [#4881](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4881) (3.1.5.0) + - *ci*: Better spread of libpng versions we test against [#4883](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4883) (3.1.5.0) + - *ci*: Fix broken python wheel building [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) (3.1.5.0) + - *ci*: Some more minor wheel workflow changes after the py 3.9 bump [#4867](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4867) (3.1.5.0) ### 📚 Notable documentation changes: - *docs*: Clarify 'copy_image' example [#4522](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4522) (3.1.0.0/3.0.1.0) @@ -297,12 +339,33 @@ ABI compatibility, but we do expect some behavior changes. - *admin*: Add ".vs" to .gitignore [#4645](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4645) (3.1.1.0) - *admin*: Set up .gitattributes file [#4648](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4648) (3.1.1.0) - *admin*: Update SECURITY to reflect that 2.5 only gets critical fixes now [#4829](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4829) + - *admin*: Adjust license notices of A2-only source [#4884](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4884) (3.1.5.0) --- --- +Release 3.0.10.1 (Sep 16, 2025) -- compared to 3.0.10.0 +------------------------------------------------------- + - *ci*: Fix broken python wheel building [#4886](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4886) [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) + - *deps*: Several fixes to build against OpenEXR 3.4 and OpenJPH build related fixes [#4875](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4875) + + +Release 3.0.10.0 (Sep 1, 2025) -- compared to 3.0.9.0 +------------------------------------------------------- + - *exr*: Support for OpenEXR 3.4's new compression types for HTJ2K [#4871](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4871) (by Todica Ionut) + - *deps*: Ffmpeg 8 support [#4870](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4870) + - *ci*: Add a VFX Platform 2026 CI job [#4856](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4856) + - *ci*: Bump webp and openexr for "latest versions" test [#4861](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4861) + + +Release 3.0.9.1 (Aug 7, 2025) -- compared to 3.0.9.0 +----------------------------------------------------- + - *deps*: C++23 support [#4844](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4844) + - *deps*: Adapt to libheif 1.20.2 [#4851](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4851) (by toge) + + Release 3.0.9.0 (Aug 1, 2025) -- compared to 3.0.8.1 ----------------------------------------------------- - *maketx*: Add flags to increase feature parity with txmake [#4841](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4841) (by Scott Milner) diff --git a/CREDITS.md b/CREDITS.md index 027948b69b..3629969eb3 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -11,6 +11,7 @@ lg@openimageio.org * Alejandro Conty * Alejandro Aguirre * Aleksandr Motsjonov +* Alex Fuller * Alex Guirre * Alex Hughes * Alex Schworer @@ -232,6 +233,7 @@ lg@openimageio.org * Todica Ionut * Tom Knowles * Troy James Sobotka +* Vanessa Valderrama * Vic P * Vinod Khare * Vishal Agrawal diff --git a/docs/CHANGES-2.x.md b/docs/CHANGES-2.x.md index 5ef5d65214..9949b71802 100644 --- a/docs/CHANGES-2.x.md +++ b/docs/CHANGES-2.x.md @@ -6,6 +6,14 @@ For the full release notes of all versions, see: +Release 2.5.19.1 (Sep 15 2025) -- compared to 2.5.19.0 +-------------------------------------------------------- +- *ffmpeg*: Replace deprecated and soon removed avcodec_close with avcodec_free_context [#4837](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4837) (by Vlad Erium) +- *heic*: Use get_plane2 introduced by libheif 1.20.2 [#4851](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4851) (by toge) +- *ci*: Various fixes to repair CI that had rusted away in the last few + months due to changes on the runners or to dependencies. + + Release 2.5.19.0 (July 5 2025) -- compared to 2.5.18.0 -------------------------------------------------------- - *build*: Fixes to build against libheif 1.20 [#4822](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4822) (by Rui Chen) From 2a7744e395c20cc1dbe16a10a473d26c68d0f808 Mon Sep 17 00:00:00 2001 From: vvalderrv Date: Wed, 24 Sep 2025 00:18:26 -0500 Subject: [PATCH 017/508] fix: sonar scan guard (#4902) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We recreated the SonarCloud project to clear the 404s. This commit fixes the workflow condition by comparing to the string '1', so the Sonar scan step runs and uploads results. Updates the GHA workflow guard so the Sonar scan actually executes: - Change `if: inputs.sonar == 1` → `if: inputs.sonar == '1'` because `inputs.sonar` is a string (from `workflow_call` inputs). No unit tests added (CI-only change). Validation: run the “Analysis” workflow and confirm SonarCloud receives results. Signed-off-by: Vanessa Valderrama Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index a35687a563..63f0810ca3 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -177,7 +177,7 @@ jobs: if: inputs.coverage == '1' run: src/build-scripts/ci-coverage.bash - name: Sonar-scanner - if: inputs.sonar == 1 + if: inputs.sonar == '1' env: GITHUB_TOKEN: ${{ secrets.PASSED_GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.PASSED_SONAR_TOKEN }} From 54d1d50dab0ba5b01a23f1520ec15c93fc09ab01 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 24 Sep 2025 15:41:25 -0700 Subject: [PATCH 018/508] ci: Add more exceptions to when we test docs building (#4899) These additional things need not trigger a CI test build of the online documentation: * Build scripts * Markdown docs that aren't part of the documentation we're generating (like the main README.md, INSTALL.md, etc.) Doing so just makes CI take longer and wastes resources. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/docs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d95dcdf9b8..ffc13b91c6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,6 +18,8 @@ on: - '**.cmake' - '**/CMakeLists.txt' - '**/run.py' + - 'src/build-scripts/**' + - './*.md' pull_request: paths-ignore: - '**/ci.yml' @@ -29,6 +31,8 @@ on: - '**.cmake' - '**/CMakeLists.txt' - '**/run.py' + - 'src/build-scripts/**' + - './*.md' schedule: # Full nightly build - cron: "0 8 * * *" From 5042e5617397e859b5682a42b94f51faccb18a78 Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Thu, 25 Sep 2025 13:52:04 -0700 Subject: [PATCH 019/508] fix(oiiotool): Use normalized path when creating wildcard path pattern (#4904) On Windows, using a wildcard input path containing `\` back slashes would lead to failures because it would be compared with file paths containing `/` forward slashes. Use the existing `Filesystem::generic_filepath` API (which calls `path::generic_string`) to get a normalized path containing consistent directory separators. An example of the existing failure: ``` .\oiiotool.exe -v t:\render\foo_#.png --resize 160x90 -o t:\smaller_#.png oiiotool WARNING : No frame number or views matched the wildcards oiiotool ERROR: read : File does not exist: "t:\render\foo_#.png" Full command line was: > oiiotool.exe -v t:\\render\\foo_#.png --resize 160x90 -o t:\\smaller_#.png ``` Workaround - without this PR you would have to use an input path containing `/`: ``` .\oiiotool.exe -v t:/render/foo_#.png --resize 160x90 -o t:\smaller_#.png Reading t:/render/foo_0001.png Writing t:\smaller_0001.png Reading t:/render/foo_0002.png Writing t:\smaller_0002.png ``` Signed-off-by: Jesse Yurkovich Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libutil/filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/filesystem.cpp b/src/libutil/filesystem.cpp index e19e35a40f..8c0fccadc8 100644 --- a/src/libutil/filesystem.cpp +++ b/src/libutil/filesystem.cpp @@ -911,7 +911,7 @@ Filesystem::parse_pattern(const char* pattern_, int framepadding_override, // std::cout << "Format: '" << fmt << "'\n"; - normalized_pattern = prefix + fmt + suffix; + normalized_pattern = Filesystem::generic_filepath(prefix + fmt + suffix); framespec = thesequence; return true; From c254a77a810a8a4ced86b6021b1ced3e0059a06d Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 25 Sep 2025 14:45:32 -0700 Subject: [PATCH 020/508] ci: require all dependencies, with explicit exceptions (#4898) We recently had a problem where our use of a dependency was broken, and it had gone unnoticed because none of our CI jobs actually had the dependency present and tested it. This can easily happen because so many of our dependencies are "optional" -- our build will just give a warning and silently disable the functionality that would have been supported by the missing dependency. We have long had build-time options `OpenImageIO_REQUIRED_DEPS`, to consider specific dependencies (or "all") required even if they would ordinarily be optional, and `OpenImageIO_OPTIONAL_DEPS`, to make exceptions. But we didn't use these in CI. So this PR makes sets `OpenImageIO_REQUIRED_DEPS=all` to make all dependencies ostensibly required, and then list all exceptions explicitly. This should make it much more difficult in the future to make a mistake where use of a dependency is completely untested in our CI without our being aware of it. And it gives us a visible "hit list" of untested or under-tested dependencies to slowly whittle down. Some changes that came long for the ride: * checked_find_package: explicitly disabled packages are treated as optional. * cuda_macros.cmake: Only look for CUDA on platforms that might conceivably have it (i.e., don't even look on Mac). * Separate linux-aswf from linux-ubuntu into separate job groups, because that makes it easier to have shared commonalities of which dependencies they test. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 10 ++++ .github/workflows/ci.yml | 80 ++++++++++++++++++++++++++- src/build-scripts/gh-installdeps.bash | 18 ++++-- src/cmake/cuda_macros.cmake | 4 +- src/cmake/dependency_utils.cmake | 22 ++++++-- 5 files changed, 118 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index 63f0810ca3..1df1ccaec3 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -68,6 +68,10 @@ on: type: string nametag: type: string + required_deps: + type: string + optional_deps: + type: string secrets: PASSED_GITHUB_TOKEN: required: false @@ -102,6 +106,12 @@ jobs: ABI_CHECK: ${{inputs.abi_check}} ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION: node16 ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + # For CI, sometimes we want to require all dependencies to be present, + # except for a select few listed explicitly. This ensures that we don't + # accidentally have a situation in which we think we are building + # against and testing an optional dependency, but in fact are not. + OpenImageIO_REQUIRED_DEPS: ${{inputs.required_deps}} + OpenImageIO_OPTIONAL_DEPS: ${{inputs.optional_deps}} steps: - name: Checkout repo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87a02695e9..b3bf9e44a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -239,9 +239,9 @@ jobs: # - # Linux Tests + # Linux Tests using ASWF-docker containers # - linux: + linux-aswf: if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml @@ -273,6 +273,10 @@ jobs: ctest_test_timeout: ${{ matrix.ctest_test_timeout }} coverage: ${{ matrix.coverage || 0 }} sonar: ${{ matrix.sonar || 0 }} + # Override required_deps to be 'all' and explicitly list as optional + # only the ones we are intentionally not testing for those jobs. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'DCMTK;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} strategy: fail-fast: false matrix: @@ -287,6 +291,7 @@ jobs: fmt_ver: 10.1.1 pybind11_ver: v2.10.0 setenvs: export PUGIXML_VERSION=v1.13 + optional_deps_append: 'LibRaw;Ptex;Qt6' - desc: VFX2023 icc/C++17 py3.10 exr3.1 ocio2.3 qt5.15 nametag: linux-vfx2023.icc runner: ubuntu-latest @@ -303,6 +308,7 @@ jobs: DISABLE_libuhdr=1 # For icc, use fp-model precise to eliminate needless LSB errors # that make test results differ from other platforms. + optional_deps_append: "LibRaw;Ptex;Qt6" - desc: VFX2023 icx/C++17 py3.10 exr3.1 ocio2.3 qt5.15 nametag: linux-vfx2023.icx runner: ubuntu-latest @@ -319,6 +325,7 @@ jobs: UHDR_CMAKE_CXX_COMPILER=g++ # Building libuhdr with icx results in test failures # so we force using gcc/g++. + optional_deps_append: "LibRaw;Ptex;Qt6" - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 nametag: linux-vfx2024 runner: ubuntu-latest @@ -330,6 +337,7 @@ jobs: pybind11_ver: v2.12.0 benchmark: 1 setenvs: export PUGIXML_VERSION=v1.14 + optional_deps_append: "LibRaw" - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 nametag: linux-vfx2024.clang runner: ubuntu-latest @@ -343,6 +351,7 @@ jobs: pybind11_ver: v2.12.0 benchmark: 1 setenvs: export PUGIXML_VERSION=v1.14 + optional_deps_append: "LibRaw" - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 nametag: linux-vfx2025 runner: ubuntu-latest @@ -354,6 +363,7 @@ jobs: pybind11_ver: v2.13.6 benchmark: 1 setenvs: export PUGIXML_VERSION=v1.15 + optional_deps_append: "openjph;Qt6" - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.4 nametag: linux-vfx2026 runner: ubuntu-latest @@ -364,6 +374,7 @@ jobs: pybind11_ver: v3.0.0 benchmark: 1 # setenvs: export + optional_deps_append: "Qt5;Qt6" - desc: Sanitizers nametag: sanitizer runner: ubuntu-latest @@ -378,6 +389,7 @@ jobs: OIIO_CMAKE_FLAGS="-DSANITIZE=address,undefined -DOIIO_HARDENING=3 -DUSE_PYTHON=0" CTEST_EXCLUSIONS="broken|png-damaged" OpenImageIO_BUILD_LOCAL_DEPS=PNG + optional_deps_append: "LibRaw" # Test ABI stability. `abi_check` is the version or commit that we # believe is the current standard against which we don't want to @@ -397,7 +409,52 @@ jobs: abi_check: 9bfcce725a3806a3f70c7e838d9d98d6d95c917a setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 + optional_deps_append: "openjph;Qt6" + + # + # Linux Tests using GHA Ubuntu runners directly + # + linux-ubuntu: + if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + name: "${{matrix.desc}}" + uses: ./.github/workflows/build-steps.yml + with: + nametag: ${{ matrix.nametag || 'unnamed!' }} + runner: ${{ matrix.runner || 'ubuntu-latest' }} + container: ${{ matrix.container }} + cc_compiler: ${{ matrix.cc_compiler }} + cxx_compiler: ${{ matrix.cxx_compiler }} + cxx_std: ${{ matrix.cxx_std || '17' }} + build_type: ${{ matrix.build_type || 'Release' }} + depcmds: ${{ matrix.depcmds }} + extra_artifacts: ${{ matrix.extra_artifacts }} + fmt_ver: ${{ matrix.fmt_ver }} + opencolorio_ver: ${{ matrix.opencolorio_ver }} + openexr_ver: ${{ matrix.openexr_ver }} + pybind11_ver: ${{ matrix.pybind11_ver }} + python_ver: ${{ matrix.python_ver }} + setenvs: ${{ matrix.setenvs }} + simd: ${{ matrix.simd }} + skip_build: ${{ matrix.skip_build }} + skip_tests: ${{ matrix.skip_tests }} + abi_check: ${{ matrix.abi_check }} + benchmark: ${{ matrix.benchmark }} + build_docs: ${{ matrix.build_docs }} + clang_format: ${{ matrix.clang_format }} + generator: ${{ matrix.generator }} + ctest_args: ${{ matrix.ctest_args }} + ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + coverage: ${{ matrix.coverage || 0 }} + sonar: ${{ matrix.sonar || 0 }} + # Override required_deps to be 'all' and explicitly list as optional + # only the ones we are intentionally not testing for those jobs. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;JXL;Nuke;OpenGL;openjph;OpenVDB;Ptex;pystring;Qt5;R3DSDK;' }}${{matrix.optional_deps_append}} + strategy: + fail-fast: false + matrix: + include: # Test formatting. This test entry doesn't build at all, it # just runs clang-format on everything, and passes if nothing is # misformatted. Upon failure, the build artifact will be the full @@ -440,6 +497,10 @@ jobs: WEBP_VERSION=v1.6.0 FREETYPE_VERSION=VER-2-14-0 USE_OPENVDB=0 + # Ensure we are testing all the deps we think we are. We would + # like this test to have minimal missing dependencies. + required_deps: all + optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' - desc: bleeding edge gcc14 C++23 py3.12 OCIO/libtiff/exr-main avx2 nametag: linux-bleeding-edge runner: ubuntu-24.04 @@ -467,6 +528,10 @@ jobs: FREETYPE_VERSION=master QT_VERSION=0 INSTALL_OPENCV=0 # The installed OpenVDB has a TLS conflict with Python 3.8 + # Ensure we are testing all the deps we think we are. We would + # like this test to have minimal missing dependencies. + required_deps: all + optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' - desc: all local builds gcc12 C++17 avx2 exr3.2 ocio2.3 nametag: linux-local-builds runner: ubuntu-22.04 @@ -482,7 +547,6 @@ jobs: PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 - - desc: clang15 C++17 avx2 exr3.1 ocio2.3 nametag: linux-clang15 runner: ubuntu-22.04 @@ -597,6 +661,11 @@ jobs: ctest_test_timeout: ${{ matrix.ctest_test_timeout || '800' }} coverage: ${{ matrix.coverage || 0 }} sonar: ${{ matrix.sonar || 0 }} + # We're able to use Homebrew to install ALMOST every dependency, so the + # only optional ones in our Mac CI tests are commercial things we can't + # test in GHA CI. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'Nuke;R3DSDK;' }}${{matrix.optional_deps_append}} strategy: fail-fast: false matrix: @@ -663,6 +732,11 @@ jobs: ctest_test_timeout: ${{ matrix.ctest_test_timeout }} coverage: ${{ matrix.coverage || 0 }} sonar: ${{ matrix.sonar || 0 }} + # Windows is a PITA, so we expect very few dependencies to be present or + # built. But we would like to add more dependencies and reduce this list + # of exceptions in the future. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} strategy: fail-fast: false matrix: diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index 0416c0c276..62d8509084 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -17,22 +17,28 @@ if [[ "$ASWF_ORG" != "" ]] ; then #ls /etc/yum.repos.d - if [[ "$ASWF_VFXPLATFORM_VERSION" == "2021" || "$ASWF_VFXPLATFORM_VERSION" == "2022" ]] ; then - # CentOS 7 based containers need the now-nonexistant centos repo to be + # time sudo dnf upgrade --refresh || true + time sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm -y || true + + if [[ "$ASWF_VFXPLATFORM_VERSION" == "2022" ]] ; then + # CentOS 7 based containers need the now-nonexistent centos repo to be # excluded or all the subsequent yum install commands will fail. yum-config-manager --disable centos-sclo-rh || true sed -i 's,^mirrorlist=,#,; s,^#baseurl=http://mirror\.centos\.org/centos/$releasever,baseurl=https://vault.centos.org/7.9.2009,' /etc/yum.repos.d/CentOS-Base.repo fi - sudo yum install -y giflib giflib-devel || true + time time sudo yum install -y giflib giflib-devel || true if [[ "${USE_OPENCV}" != "0" ]] ; then - sudo yum install -y opencv opencv-devel || true + time sudo yum install -y opencv opencv-devel || true fi if [[ "${USE_FFMPEG}" != "0" ]] ; then - sudo yum install -y ffmpeg ffmpeg-devel || true + time sudo dnf install -y ffmpeg ffmpeg-devel || true fi if [[ "${USE_FREETYPE:-1}" != "0" ]] ; then - sudo yum install -y freetype freetype-devel || true + time sudo yum install -y freetype freetype-devel || true + fi + if [[ "${USE_LIBRAW:-0}" != "0" ]] ; then + time sudo yum install -y LibRaw LibRaw-devel || true fi if [[ "${EXTRA_DEP_PACKAGES}" != "" ]] ; then time sudo yum install -y ${EXTRA_DEP_PACKAGES} || true diff --git a/src/cmake/cuda_macros.cmake b/src/cmake/cuda_macros.cmake index 29f6c300ce..aadfc45420 100644 --- a/src/cmake/cuda_macros.cmake +++ b/src/cmake/cuda_macros.cmake @@ -3,11 +3,11 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO -set_option (OIIO_USE_CUDA "Include Cuda support if found" OFF) +set_option (OIIO_USE_CUDA "Include CUDA support if found" OFF) set_cache (CUDA_TARGET_ARCH "sm_60" "CUDA GPU architecture (e.g. sm_60)") set_cache (CUDAToolkit_ROOT "" "Path to CUDA toolkit") -if (OIIO_USE_CUDA) +if (OIIO_USE_CUDA AND NOT APPLE) set (CUDA_PROPAGATE_HOST_FLAGS ON) set (CUDA_VERBOSE_BUILD ${VERBOSE}) checked_find_package(CUDAToolkit diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 02c764fd03..58892a6b43 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -76,7 +76,7 @@ function (dump_matching_variables pattern) endfunction () -# Helper: Print a report about missing dependencies and give insructions on +# Helper: Print a report about missing dependencies and give instructions on # how to turn on automatic local dependency building. function (print_package_notfound_report) message (STATUS) @@ -286,7 +286,7 @@ macro (checked_find_package pkgname) # cmake_parse_arguments(_pkg # prefix # noValueKeywords: - "REQUIRED;CONFIG;PREFER_CONFIG;DEBUG;NO_RECORD_NOTFOUND;NO_FP_RANGE_CHECK" + "REQUIRED;OPTIONAL;CONFIG;PREFER_CONFIG;DEBUG;NO_RECORD_NOTFOUND;NO_FP_RANGE_CHECK" # singleValueKeywords: "ENABLE;ISDEPOF;VERSION_MIN;VERSION_MAX;RECOMMEND_MIN;RECOMMEND_MIN_REASON;BUILD_LOCAL" # multiValueKeywords: @@ -303,17 +303,24 @@ macro (checked_find_package pkgname) set (${pkgname}_FIND_QUIETLY true) set (${pkgname_upper}_FIND_QUIETLY true) endif () - if ("${pkgname}" IN_LIST ${PROJECT_NAME}_REQUIRED_DEPS OR "ALL" IN_LIST ${PROJECT_NAME}_REQUIRED_DEPS) + if ("${pkgname}" IN_LIST ${PROJECT_NAME}_REQUIRED_DEPS + OR "ALL" IN_LIST ${PROJECT_NAME}_REQUIRED_DEPS + OR "all" IN_LIST ${PROJECT_NAME}_REQUIRED_DEPS) set (_pkg_REQUIRED 1) endif () - if ("${pkgname}" IN_LIST ${PROJECT_NAME}_OPTIONAL_DEPS OR "ALL" IN_LIST ${PROJECT_NAME}_OPTIONAL_DEPS) + if ("${pkgname}" IN_LIST ${PROJECT_NAME}_OPTIONAL_DEPS + OR "ALL" IN_LIST ${PROJECT_NAME}_OPTIONAL_DEPS + OR "all" IN_LIST ${PROJECT_NAME}_OPTIONAL_DEPS) set (_pkg_REQUIRED 0) + set (_pkg_OPTIONAL 1) endif () # string (TOLOWER "${_pkg_BUILD_LOCAL}" _pkg_BUILD_LOCAL) if ("${pkgname}" IN_LIST ${PROJECT_NAME}_BUILD_LOCAL_DEPS + OR ${PROJECT_NAME}_BUILD_LOCAL_DEPS STREQUAL "ALL" OR ${PROJECT_NAME}_BUILD_LOCAL_DEPS STREQUAL "all") set (_pkg_BUILD_LOCAL "always") elseif ("${pkgname}" IN_LIST ${PROJECT_NAME}_BUILD_MISSING_DEPS + OR ${PROJECT_NAME}_BUILD_MISSING_DEPS STREQUAL "ALL" OR ${PROJECT_NAME}_BUILD_MISSING_DEPS STREQUAL "all") set_if_not (_pkg_BUILD_LOCAL "missing") endif () @@ -341,6 +348,11 @@ macro (checked_find_package pkgname) set (_quietskip true) endif () endif () + if (NOT _enable) + set (_pkg_OPTIONAL 1) + set (_pkg_REQUIRED 0) + message(STATUS "Forcing optional of disabled ${pkgname}") + endif () set (_config_status "") unset (_${pkgname}_version_range) if (_pkg_BUILD_LOCAL AND NOT _pkg_NO_FP_RANGE_CHECK) @@ -361,7 +373,7 @@ macro (checked_find_package pkgname) # set (${pkgname}_FOUND FALSE) set (${pkgname}_LOCAL_BUILD FALSE) - if (_enable OR _pkg_REQUIRED) + if (_enable OR (_pkg_REQUIRED AND NOT _pkg_OPTIONAL)) # Unless instructed not to, try to find the package externally # installed. if (${pkgname}_FOUND OR ${pkgname_upper}_FOUND OR _pkg_BUILD_LOCAL STREQUAL "always") From afd0f9b12ea89ff2bd20a982f3139b8656db97ad Mon Sep 17 00:00:00 2001 From: Connie Chang Date: Fri, 26 Sep 2025 18:28:25 -0700 Subject: [PATCH 021/508] docs: Add type hints to Python docs (#4908) Closes https://github.com/AcademySoftwareFoundation/OpenImageIO/issues/4752 Add Python type hints into documentation, in places where example code defines a function. That ended up being just `src/doc/pythonbindings.rst`. I also updated `testsuite/runtest.py` and the functions in `testsuite/docs-examples-python/src/docs-examples-*.py`. While I was in `pythonbindings.rst`, I noticed a couple places with `py::method` (note the double colon). These lines didn't render in the docs, so I removed the extra colon to match other usage. I locally built the docs to make sure html looks good. I ran the tests with `ctest` and the tests ran, so I'm taking that as a sign that `runtest.py` still works. Signed-off-by: Connie Chang Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/pythonbindings.rst | 16 ++-- .../src/docs-examples-imagebuf.py | 2 +- .../src/docs-examples-imagebufalgo.py | 82 +++++++++---------- .../src/docs-examples-imagecache.py | 2 +- .../src/docs-examples-imageinput.py | 12 +-- .../src/docs-examples-imageioapi.py | 2 +- .../src/docs-examples-imageoutput.py | 8 +- .../src/docs-examples-texturesys.py | 2 +- .../src/docs-examples-writingplugins.py | 2 +- testsuite/runtest.py | 40 ++++----- 10 files changed, 84 insertions(+), 84 deletions(-) diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index 1565a65860..35a73ff00b 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -72,7 +72,7 @@ described in detail in Section :ref:`sec-typedesc`, is replicated for Python. These names are also exported to the `OpenImageIO` namespace. -.. py::method:: TypeDesc.TypeDesc(typename='unknown') +.. py:method:: TypeDesc.TypeDesc(typename='unknown') Construct a `TypeDesc` object the easy way: from a string description. If the type name is omitted, it will default to`UNKNOWN`. @@ -97,7 +97,7 @@ described in detail in Section :ref:`sec-typedesc`, is replicated for Python. -.. py::method:: TypeDesc.TypeDesc(basetype=oiio.UNKNOWN, aggregate=oiio.SCALAR, vecsemantics=NOSEMANTICS, arraylen=0) +.. py:method:: TypeDesc.TypeDesc(basetype=oiio.UNKNOWN, aggregate=oiio.SCALAR, vecsemantics=NOSEMANTICS, arraylen=0) Construct a `TypeDesc` object the hard way: from individual enum tokens describing the base type, aggregate class, semantic hints, and array length. @@ -751,7 +751,7 @@ function that opens a file and prints all the relevant header information: from OpenImageIO import ImageInput # Print the contents of an ImageSpec - def print_imagespec (spec, subimage=0, mip=0) : + def print_imagespec (spec: ImageSpec, subimage: int=0, mip: int=0) : if spec.depth <= 1 : print (" resolution %dx%d%+d%+d" % (spec.width, spec.height, spec.x, spec.y)) else : @@ -787,7 +787,7 @@ function that opens a file and prints all the relevant header information: print (" ", i.name, "=", i.value) - def poor_mans_iinfo (filename) : + def poor_mans_iinfo (filename: str) : input = ImageInput.open (filename) if not input : print ('Could not open "' + filename + '"') @@ -1243,7 +1243,7 @@ Example: Reading pixel values from a file to find min/max #!/usr/bin/env python import OpenImageIO as oiio - def find_min_max (filename) : + def find_min_max (filename: str) : input = ImageInput.open (filename) if not input : print ('Could not open "' + filename + '"') @@ -4041,8 +4041,8 @@ following boilerplate: # Create an ImageBuf holding a n image of constant color, given the # resolution, data format (defaulting to UINT8), fill value, and image # origin. - def make_constimage (xres, yres, chans=3, format=oiio.UINT8, value=(0,0,0), - xoffset=0, yoffset=0) : + def make_constimage (xres: int, yres: int, chans: int=3, format: TypeDesc=oiio.UINT8, value: tuple[int, int, int]=(0,0,0), + xoffset: int=0, yoffset: int=0) -> ImageBuf: spec = ImageSpec (xres,yres,chans,format) spec.x = xoffset spec.y = yoffset @@ -4062,7 +4062,7 @@ what to do with it next. # Save an ImageBuf to a given file name, with optional forced image format # and error handling. - def write_image (image, filename, format=oiio.UNKNOWN) : + def write_image (image: ImageBuf, filename: str, format: TypeDesc=oiio.UNKNOWN) : if not image.has_error : image.write (filename, format) if image.has_error : diff --git a/testsuite/docs-examples-python/src/docs-examples-imagebuf.py b/testsuite/docs-examples-python/src/docs-examples-imagebuf.py index 0d66a44cf0..5604536f39 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagebuf.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagebuf.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # diff --git a/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py b/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py index 04c81c260e..0bd717c9ac 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py @@ -20,7 +20,7 @@ import numpy as np -def example1(): +def example1() -> None: print("example1") # # Example code fragment from the docs goes here. @@ -40,7 +40,7 @@ def example1(): # Section: ImageBufAlgo common principles -def example_output_error1(): +def example_output_error1() -> None: print("example_output_error1") fg = ImageBuf() bg = ImageBuf() @@ -53,7 +53,7 @@ def example_output_error1(): # END-imagebufalgo-output-error1 -def example_output_error2(): +def example_output_error2() -> None: print("example_output_error2") fg = ImageBuf() bg = ImageBuf() @@ -69,7 +69,7 @@ def example_output_error2(): # Section: Pattern Generation -def example_zero(): +def example_zero() -> None: print("example_zero") A = ImageBuf("grid.exr") B = ImageBuf("grid.exr") @@ -98,7 +98,7 @@ def example_zero(): C.write("zero4.exr", "half") -def example_fill(): +def example_fill() -> None: print("example_fill") # BEGIN-imagebufalgo-fill # Create a new 640x480 RGB image, with a top-to-bottom gradient @@ -115,7 +115,7 @@ def example_fill(): A.write("fill.exr", "half") -def example_checker(): +def example_checker() -> None: print("example_checker") # BEGIN-imagebufalgo-checker # Create a new 640x480 RGB image, fill it with a two-toned gray @@ -130,7 +130,7 @@ def example_checker(): A.write("checker.exr", "half") -def example_noise1(): +def example_noise1() -> None: print("example_noise1") # BEGIN-imagebufalgo-noise1 # Create a new 256x256 field of grayscale white noise on [0,1) @@ -156,7 +156,7 @@ def example_noise1(): D.write("noise4.exr", "half") -def example_noise2(): +def example_noise2() -> None: print("example_noise2") # BEGIN-imagebufalgo-noise2 A = ImageBufAlgo.bluenoise_image() @@ -165,7 +165,7 @@ def example_noise2(): A.write("blue-noise.exr", "half") -def example_point(): +def example_point() -> None: print("example_point") # BEGIN-imagebufalgo-point A = ImageBuf(ImageSpec(640, 480, 4, "float")) @@ -176,7 +176,7 @@ def example_point(): A.write("point.exr", "half") -def example_lines(): +def example_lines() -> None: print("example_lines") # BEGIN-imagebufalgo-lines A = ImageBuf(ImageSpec(640, 480, 4, "float")) @@ -188,7 +188,7 @@ def example_lines(): A.write("lines.exr", "half") -def example_box(): +def example_box() -> None: print("example_box") # BEGIN-imagebufalgo-box A = ImageBuf(ImageSpec(640, 480, 4, "float")) @@ -201,7 +201,7 @@ def example_box(): A.write("box.exr", "half") -def example_text1(): +def example_text1() -> None: print("example_text1") ImgA = ImageBufAlgo.zero(ROI(0, 640, 0, 480, 0, 1, 0, 3)) ImgB = ImageBufAlgo.zero(ROI(0, 640, 0, 480, 0, 1, 0, 3)) @@ -222,7 +222,7 @@ def example_text1(): ImgB.write("text2.exr", "half") -def example_text2(): +def example_text2() -> None: print("example_text2") # BEGIN-imagebufalgo-text2 # Render text centered in the image, using text_size to find out @@ -239,7 +239,7 @@ def example_text2(): # Section: Image transformation and data movement -def example_channels(): +def example_channels() -> None: print("example_channels") RGBA = ImageBuf("grid.exr") BRGA = ImageBuf() @@ -270,7 +270,7 @@ def example_channels(): BRGA.write("channels-brga.exr", "half") -def example_channel_append(): +def example_channel_append() -> None: print("example_channel_append") Z = ImageBuf(ImageSpec(640, 480, 1, "float")) @@ -282,7 +282,7 @@ def example_channel_append(): RGBAZ.write("channel-append.exr", "half") -def example_copy(): +def example_copy() -> None: print("example_copy") # BEGIN-imagebufalgo-copy # Set B to be a copy of A, but converted to float @@ -293,7 +293,7 @@ def example_copy(): B.write("copy.exr", "half") -def example_crop(): +def example_crop() -> None: print("example_crop") # BEGIN-imagebufalgo-crop # Set B to be a 200x100 region of A starting at (50,50), trimming @@ -305,7 +305,7 @@ def example_crop(): B.write("crop.exr", "half") -def example_cut(): +def example_cut() -> None: print("example_cut") # BEGIN-imagebufalgo-cut # Set B to be a 200x100 region of A starting at (50,50), but @@ -317,7 +317,7 @@ def example_cut(): B.write("cut.exr", "half") -def example_paste(): +def example_paste() -> None: print("example_paste") # BEGIN-imagebufalgo-paste # Paste Fg on top of Bg, offset by (100,100) @@ -329,7 +329,7 @@ def example_paste(): Bg.write("paste.exr", "half") -def example_rotate_n(): +def example_rotate_n() -> None: print("example_rotate_n") # BEGIN-imagebufalgo-rotate-n A = ImageBuf("grid.exr") @@ -343,7 +343,7 @@ def example_rotate_n(): R270.write("rotate-270.exr", "half") -def example_flip_flop_transpose(): +def example_flip_flop_transpose() -> None: print("example_flip_flop_transpose") # BEGIN-imagebufalgo-flip-flop-transpose A = ImageBuf("grid.exr") @@ -357,7 +357,7 @@ def example_flip_flop_transpose(): B3.write("transpose.exr", "half") -def example_reorient(): +def example_reorient() -> None: print("example_reorient") tmp = ImageBuf("grid.exr") tmp.specmod().attribute("Orientation", 8) @@ -371,7 +371,7 @@ def example_reorient(): A.write("reorient.exr", "half") -def example_circular_shift(): +def example_circular_shift() -> None: print("example_circular_shift") # BEGIN-imagebufalgo-cshift A = ImageBuf("grid.exr") @@ -380,7 +380,7 @@ def example_circular_shift(): B.write("cshift.exr", "half") -def example_rotate(): +def example_rotate() -> None: print("example_rotate") # BEGIN-imagebufalgo-rotate-angle Src = ImageBuf("grid.exr") @@ -389,7 +389,7 @@ def example_rotate(): Dst.write("rotate-45.tif", "uint8") -def example_resize(): +def example_resize() -> None: print("example_resize") # BEGIN-imagebufalgo-resize # Resize the image to 640x480, using the default filter @@ -400,7 +400,7 @@ def example_resize(): Dst.write("resize.tif", "uint8") -def example_resample(): +def example_resample() -> None: print("example_resample") # BEGIN-imagebufalgo-resample @@ -412,7 +412,7 @@ def example_resample(): Dst.write("resample.exr", "half") -def example_fit(): +def example_fit() -> None: print("example_fit") # BEGIN-imagebufalgo-fit # Resize to fit into a max of 640x480, preserving the aspect ratio @@ -423,7 +423,7 @@ def example_fit(): Dst.write("fit.tif", "uint8") -def example_warp(): +def example_warp() -> None: print("example_warp") # BEGIN-imagebufalgo-warp M = (0.7071068, 0.7071068, 0, @@ -434,7 +434,7 @@ def example_warp(): # END-imagebufalgo-warp Dst.write("warp.exr", "half") -def example_demosaic(): +def example_demosaic() -> None: print("example_demosaic") # BEGIN-imagebufalgo-demosaic Src = ImageBuf("bayer.png") @@ -445,7 +445,7 @@ def example_demosaic(): Dst.write("demosaic.png") # Section: Image Arithmetic -def example_add(): +def example_add() -> None: print("example_add") # BEGIN-imagebufalgo-add # Add images A and B @@ -459,7 +459,7 @@ def example_add(): Sum.write("add.exr", "half") Sum_cspan.write("add_cspan.exr", "half") -def example_sub(): +def example_sub() -> None: print("example_sub") # BEGIN-imagebufalgo-sub A = ImageBuf("A.exr") @@ -468,7 +468,7 @@ def example_sub(): # END-imagebufalgo-sub Diff.write("sub.exr", "half") -def example_absdiff(): +def example_absdiff() -> None: print("example_absdiff") # BEGIN-imagebufalgo-absdiff A = ImageBuf("A.exr") @@ -477,7 +477,7 @@ def example_absdiff(): # END-imagebufalgo-absdiff Diff.write("absdiff.exr", "half") -def example_abs(): +def example_abs() -> None: print("example_abs") # BEGIN-imagebufalgo-absolute A = ImageBuf("grid.exr") @@ -485,7 +485,7 @@ def example_abs(): # END-imagebufalgo-absolute Abs.write("abs.exr", "half") -def example_scale(): +def example_scale() -> None: print("example_scale") # BEGIN-imagebufalgo-scale # Pixel-by-pixel multiplication of all channels of one image A by the single channel of the other image @@ -495,7 +495,7 @@ def example_scale(): #END-imagebufalgo-scale Product.write("scale.exr", "half") -def example_mul(): +def example_mul() -> None: print("example_mul") # BEGIN-imagebufalgo-mul # Pixel-by-pixel, channel-by-channel multiplication of A and B @@ -508,7 +508,7 @@ def example_mul(): # END-imagebufalgo-mul Product.write("mul.exr", "half") -def example_div(): +def example_div() -> None: print("example_div") # BEGIN-imagebufalgo-div # Pixel-by-pixel, channel-by-channel division of A by B @@ -531,7 +531,7 @@ def example_div(): # Section: Image enhancement / restoration -def example_fixNonFinite(): +def example_fixNonFinite() -> None: print("example_fixNonFinite") # BEGIN-imagebufalgo-fixNonFinite Src = ImageBuf("with_nans.tif") @@ -541,7 +541,7 @@ def example_fixNonFinite(): # fixing the nans seems nondeterministic - so not writing out the image # Src.write("with_nans_fixed.tif") -def example_fillholes_pushpull(): +def example_fillholes_pushpull() -> None: print("example_fillholes_pushpull") # BEGIN-imagebufalgo-fillholes_pushpull Src = ImageBuf("checker_with_alpha.exr") @@ -550,7 +550,7 @@ def example_fillholes_pushpull(): Filled.write("checker_with_alpha_filled.exr") -def example_median_filter(): +def example_median_filter() -> None: print("example_median_filter") # BEGIN-imagebufalgo-median_filter Noisy = ImageBuf("tahoe.tif") @@ -559,7 +559,7 @@ def example_median_filter(): Clean.write("tahoe_median_filter.tif") -def example_unsharp_mask(): +def example_unsharp_mask() -> None: print("example_unsharp_mask") # BEGIN-imagebufalgo-unsharp_mask Blurry = ImageBuf("tahoe.tif") @@ -575,7 +575,7 @@ def example_unsharp_mask(): # Section: Import / export -def example_make_texture(): +def example_make_texture() -> None: print("example_make_texture") # BEGIN-imagebufalgo-make-texture Input = ImageBuf("grid.exr") diff --git a/testsuite/docs-examples-python/src/docs-examples-imagecache.py b/testsuite/docs-examples-python/src/docs-examples-imagecache.py index 387c5ac556..868b019d43 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imagecache.py +++ b/testsuite/docs-examples-python/src/docs-examples-imagecache.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # diff --git a/testsuite/docs-examples-python/src/docs-examples-imageinput.py b/testsuite/docs-examples-python/src/docs-examples-imageinput.py index 5b63abb62b..05d8f015a0 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imageinput.py +++ b/testsuite/docs-examples-python/src/docs-examples-imageinput.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # @@ -37,7 +37,7 @@ def example1() : # BEGIN-imageinput-simple import OpenImageIO as oiio -def simple_read(): +def simple_read() -> None: filename = "tahoe.tif" inp = oiio.ImageInput.open(filename) @@ -51,7 +51,7 @@ def simple_read(): # END-imageinput-simple -def scanlines_read() : +def scanlines_read() -> None: filename = "scanlines.tif" # BEGIN-imageinput-scanlines @@ -66,7 +66,7 @@ def scanlines_read() : inp.close () # END-imageinput-scanlines -def tiles_read() : +def tiles_read() -> None: filename = "tiled.tif" # BEGIN-imageinput-tiles @@ -86,7 +86,7 @@ def tiles_read() : # END-imageinput-tiles -def unassociated_alpha(): +def unassociated_alpha() -> None: filename = "unpremult.tif" # BEGIN-imageinput-unassociatedalpha @@ -104,7 +104,7 @@ def unassociated_alpha(): print("pixels holds associated alpha") # END-imageinput-unassociatedalpha -def error_checking(): +def error_checking() -> None: # BEGIN-imageinput-errorchecking import OpenImageIO as oiio import numpy as np diff --git a/testsuite/docs-examples-python/src/docs-examples-imageioapi.py b/testsuite/docs-examples-python/src/docs-examples-imageioapi.py index 156543457f..45525728ea 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imageioapi.py +++ b/testsuite/docs-examples-python/src/docs-examples-imageioapi.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # diff --git a/testsuite/docs-examples-python/src/docs-examples-imageoutput.py b/testsuite/docs-examples-python/src/docs-examples-imageoutput.py index e76d8716cb..980ed3259d 100644 --- a/testsuite/docs-examples-python/src/docs-examples-imageoutput.py +++ b/testsuite/docs-examples-python/src/docs-examples-imageoutput.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # @@ -40,7 +40,7 @@ def example1() : import OpenImageIO as oiio import numpy as np -def simple_write() : +def simple_write() -> None: filename = "simple.tif" xres = 320 yres = 240 @@ -56,7 +56,7 @@ def simple_write() : # END-imageoutput-simple -def scanlines_write() : +def scanlines_write() -> None: filename = "scanlines.tif" xres = 320 yres = 240 @@ -77,7 +77,7 @@ def scanlines_write() : # END-imageoutput-scanlines -def tiles_write() : +def tiles_write() -> None: filename = "tiles.tif" xres = 320 yres = 240 diff --git a/testsuite/docs-examples-python/src/docs-examples-texturesys.py b/testsuite/docs-examples-python/src/docs-examples-texturesys.py index e7890387f3..f94b5060f7 100644 --- a/testsuite/docs-examples-python/src/docs-examples-texturesys.py +++ b/testsuite/docs-examples-python/src/docs-examples-texturesys.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # diff --git a/testsuite/docs-examples-python/src/docs-examples-writingplugins.py b/testsuite/docs-examples-python/src/docs-examples-writingplugins.py index 9b99544be1..b37d989a97 100644 --- a/testsuite/docs-examples-python/src/docs-examples-writingplugins.py +++ b/testsuite/docs-examples-python/src/docs-examples-writingplugins.py @@ -18,7 +18,7 @@ import OpenImageIO as oiio import numpy as np -def example1() : +def example1() -> None: # # Example code fragment from the docs goes here. # diff --git a/testsuite/runtest.py b/testsuite/runtest.py index ff988fccbe..5efced462f 100755 --- a/testsuite/runtest.py +++ b/testsuite/runtest.py @@ -48,7 +48,7 @@ redirect = " >> out.txt " wrapper_cmd = "" -def make_relpath (path, start=os.curdir): +def make_relpath (path: str, start: str=os.curdir) -> str: "Wrapper around os.path.relpath which always uses '/' as the separator." p = os.path.relpath (path, start) return p if platform.system() != 'Windows' else p.replace ('\\', '/') @@ -82,7 +82,7 @@ def make_relpath (path, start=os.curdir): test_source_dir = os.getenv('OIIO_TESTSUITE_SRC', os.path.join(OIIO_TESTSUITE_ROOT, mytest)) -def oiio_app (app): +def oiio_app (app: str) -> str: if (platform.system () != 'Windows' or options.devenv_config == ""): cmd = os.path.join(OIIO_BUILD_ROOT, "bin", app) + " " else: @@ -145,7 +145,7 @@ def oiio_app (app): # if not os.path.exists("../common") : # shutil.copytree ("../../testsuite/common", "..") else : - def newsymlink(src, dst): + def newsymlink(src: str, dst: str): print("newsymlink", src, dst) # os.path.exists returns False for broken symlinks, so remove if thats the case if os.path.islink(dst): @@ -176,7 +176,7 @@ def newsymlink(src, dst): # a non-zero value and writes the differences to "diff_file". # Based on the command-line interface to difflib example from the Python # documentation -def text_diff (fromfile, tofile, diff_file=None): +def text_diff (fromfile: str, tofile: str, diff_file: str=None) -> int: import time try: fromdate = time.ctime (os.stat (fromfile).st_mtime) @@ -208,7 +208,7 @@ def text_diff (fromfile, tofile, diff_file=None): return 1 -def run_app(app, silent=False, concat=True): +def run_app(app: str, silent: bool=False, concat: bool=True) -> str: command = app if not silent: command += redirect @@ -220,9 +220,9 @@ def run_app(app, silent=False, concat=True): # Construct a command that will print info for an image, appending output to # the file "out.txt". If 'safematch' is nonzero, it will exclude printing # of fields that tend to change from run to run or release to release. -def info_command (file, extraargs="", safematch=False, hash=True, - verbose=True, silent=False, concat=True, failureok=False, - info_program="oiiotool") : +def info_command (file: str, extraargs: str="", safematch: bool=False, hash: bool=True, + verbose: bool=True, silent: bool=False, concat: bool=True, failureok: bool=False, + info_program: str="oiiotool") -> str: args = "" if info_program == "oiiotool" : args += " --info" @@ -247,7 +247,7 @@ def info_command (file, extraargs="", safematch=False, hash=True, # the file "out.txt". We allow a small number of pixels to have up to # 1 LSB (8 bit) error, it's very hard to make different platforms and # compilers always match to every last floating point bit. -def diff_command (fileA, fileB, extraargs="", silent=False, concat=True) : +def diff_command (fileA: str, fileB: str, extraargs: str="", silent: bool=False, concat: bool=True) -> str : command = (oiio_app("idiff") + "-a" + " -fail " + str(failthresh) + " -failpercent " + str(failpercent) @@ -266,9 +266,9 @@ def diff_command (fileA, fileB, extraargs="", silent=False, concat=True) : # Construct a command that will create a texture, appending console # output to the file "out.txt". -def maketx_command (infile, outfile, extraargs="", - showinfo=False, showinfo_extra="", - silent=False, concat=True) : +def maketx_command (infile: str, outfile: str, extraargs: str="", + showinfo: bool=False, showinfo_extra: str="", + silent: str=False, concat: str=True) -> str : command = (oiio_app("maketx") + " " + make_relpath(infile,tmpdir) + " " + extraargs @@ -289,9 +289,9 @@ def maketx_command (infile, outfile, extraargs="", # correctly). If testwrite is nonzero, also iconvert the file to make a # copy (tests writing that format), and then idiff to make sure it # matches the original. -def rw_command (dir, filename, testwrite=True, use_oiiotool=False, extraargs="", - preargs="", idiffextraargs="", output_filename="", - safematch=False, printinfo=True) : +def rw_command (dir: str, filename: str, testwrite: bool=True, use_oiiotool: bool=False, extraargs: str="", + preargs: str="", idiffextraargs: str="", output_filename: str="", + safematch: bool=False, printinfo: bool=True) -> str: fn = make_relpath (dir + "/" + filename, tmpdir) if printinfo : cmd = info_command (fn, safematch=safematch) @@ -317,7 +317,7 @@ def rw_command (dir, filename, testwrite=True, use_oiiotool=False, extraargs="", # Construct a command that will testtex -def testtex_command (file, extraargs="", silent=False, concat=True) : +def testtex_command (file: str, extraargs: str="", silent: bool=False, concat: bool=True) -> str: cmd = oiio_app("testtex") + " " + file + " " + extraargs + " " if not silent : cmd += redirect @@ -327,7 +327,7 @@ def testtex_command (file, extraargs="", silent=False, concat=True) : # Construct a command that will run iconvert and append its output to out.txt -def iconvert (args, silent=False, concat=True, failureok=False) : +def iconvert (args: str, silent: bool=False, concat: bool=True, failureok: bool=False) -> str: cmd = (oiio_app("iconvert") + " " + args) if not silent : cmd += redirect @@ -339,7 +339,7 @@ def iconvert (args, silent=False, concat=True, failureok=False) : # Construct a command that will run oiiotool and append its output to out.txt -def oiiotool (args, silent=False, concat=True, failureok=False) : +def oiiotool (args: str, silent: bool=False, concat: bool=True, failureok: bool=False) -> str: cmd = (oiio_app("oiiotool") + " " + args) if not silent : cmd += redirect @@ -356,7 +356,7 @@ def oiiotool (args, silent=False, concat=True, failureok=False) : # the identical name, and if that fails, it will look for alternatives of # the form "basename-*.ext" (or ANY match in the ref directory, if anymatch # is True). -def checkref (name, refdirlist) : +def checkref (name: str, refdirlist: list[str]) -> tuple[bool, str]: # Break the output into prefix+extension (prefix, extension) = os.path.splitext(name) ok = 0 @@ -398,7 +398,7 @@ def checkref (name, refdirlist) : # in 'ref/'. If all outputs match their reference copies, return 0 # to pass. If any outputs do not match their references return 1 to # fail. -def runtest (command, outputs, failureok=0) : +def runtest (command: str, outputs: list[str], failureok: int=0) -> int : err = 0 # print ("working dir = " + tmpdir) os.chdir (srcdir) From e844ea4022f6ef6bc1b619430356d2fc7c432515 Mon Sep 17 00:00:00 2001 From: Carine Touraille Date: Fri, 26 Sep 2025 22:15:53 -0400 Subject: [PATCH 022/508] fix(oiiotool): Ignore empty subimage(s) when calculating non-zero region (#4909) Fixes #4799 When trimming an image that contains an empty subimage, the resulting ROI unexpectedly included the top-left pixel even if zero in all subimages. To avoid this, empty ROIs are ignored when computing the overall non-zero region. If all subimages are empty, the first subimage ROI is used and doctored to be 1 pixel. Two tests have been added to the oiiotool testsuite: - 1 to check the trimming of an empty image - 1 to check the trimming of a non-empty image with an empty subimage Signed-off-by: Carine Touraille Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/oiiotool/oiiotool.cpp | 17 ++++++++++------- testsuite/oiiotool/ref/out.txt | 4 ++++ testsuite/oiiotool/ref/trimempty.tif | Bin 0 -> 306 bytes testsuite/oiiotool/ref/trimemptysubimages.tif | Bin 0 -> 11433 bytes testsuite/oiiotool/run.py | 13 ++++++++++++- 5 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 testsuite/oiiotool/ref/trimempty.tif create mode 100644 testsuite/oiiotool/ref/trimemptysubimages.tif diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index c0ae72f9a2..b28a04f152 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -4077,14 +4077,17 @@ nonzero_region_all_subimages(ImageRecRef A) ROI nonzero_region; for (int s = 0; s < A->subimages(); ++s) { ROI roi = ImageBufAlgo::nonzero_region((*A)(s)); - if (roi.npixels() == 0) { - // Special case -- all zero; but doctor to make it 1 zero pixel - roi = (*A)(s).roi(); - roi.xend = roi.xbegin + 1; - roi.yend = roi.ybegin + 1; - roi.zend = roi.zbegin + 1; + if (roi.npixels() != 0) { + nonzero_region = roi_union(nonzero_region, roi); } - nonzero_region = roi_union(nonzero_region, roi); + } + if (!nonzero_region.defined()) { + // Special case -- all zero + // fallback to 1st subimage ROI, but doctor to make it 1 zero pixel + nonzero_region = (*A)(0).roi(); + nonzero_region.xend = nonzero_region.xbegin + 1; + nonzero_region.yend = nonzero_region.ybegin + 1; + nonzero_region.zend = nonzero_region.zbegin + 1; } return nonzero_region; } diff --git a/testsuite/oiiotool/ref/out.txt b/testsuite/oiiotool/ref/out.txt index 646c659e8d..0bd3534c6d 100644 --- a/testsuite/oiiotool/ref/out.txt +++ b/testsuite/oiiotool/ref/out.txt @@ -68,8 +68,12 @@ Comparing "autotrim.tif" and "ref/autotrim.tif" PASS Comparing "trim.tif" and "ref/trim.tif" PASS +Comparing "trimempty.tif" and "ref/trimempty.tif" +PASS Comparing "trimsubimages.tif" and "ref/trimsubimages.tif" PASS +Comparing "trimemptysubimages.tif" and "ref/trimemptysubimages.tif" +PASS Comparing "add.exr" and "ref/add.exr" PASS Comparing "cadd1.exr" and "ref/cadd1.exr" diff --git a/testsuite/oiiotool/ref/trimempty.tif b/testsuite/oiiotool/ref/trimempty.tif new file mode 100644 index 0000000000000000000000000000000000000000..6fb90bc2881dcb0cdedadd1b9b95c3c3078fba29 GIT binary patch literal 306 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 literal 0 HcmV?d00001 diff --git a/testsuite/oiiotool/run.py b/testsuite/oiiotool/run.py index f0d0c3c037..e54b4db546 100755 --- a/testsuite/oiiotool/run.py +++ b/testsuite/oiiotool/run.py @@ -105,11 +105,21 @@ command += oiiotool ("--create 320x240 3 -fill:color=.1,.5,.1 120x80+50+70 " + " -rotate 30 -trim -origin +0+0 -fullpixels -d uint8 -o trim.tif") +# test --trim on empty image +command += oiiotool ("--create 320x240 3 " + + " -trim -origin +0+0 -fullpixels -d uint8 -o trimempty.tif") + # test --trim, tricky case of multiple subimages command += oiiotool ( "-a --create 320x240 3 -fill:color=.1,.5,.1 120x80+50+70 -rotate 30 " + "--create 320x240 3 -fill:color=.5,.5,.1 100x10+70+70 -rotate 140 " + "--siappend -trim -origin +0+0 -fullpixels -d uint8 -o trimsubimages.tif") +# test --trim, tricky case of multiple subimages including one empty one +command += oiiotool ( "-a --create 320x240 3 -fill:color=.1,.5,.1 120x80+50+70 -rotate 30 " + + "--create 320x240 3 -fill:color=.5,.5,.1 100x10+70+70 -rotate 140 " + + "--create 320x240 3 " + + "--siappendall -trim -origin +0+0 -fullpixels -d uint8 -o trimemptysubimages.tif") + # test hole filling command += oiiotool ("ref/hole.tif --fillholes -o tahoe-filled.tif") # test hole filling for a cropped image @@ -262,7 +272,8 @@ outputs = [ "filled.tif", "autotrim.tif", - "trim.tif", "trimsubimages.tif", + "trim.tif", "trimempty.tif", + "trimsubimages.tif", "trimemptysubimages.tif", "add.exr", "cadd1.exr", "cadd2.exr", "sub.exr", "subc.exr", "mul.exr", "cmul1.exr", "cmul2.exr", From 176494563f26058332b2541e4715fa1008edefae Mon Sep 17 00:00:00 2001 From: omcaif <103504667+omcaif@users.noreply.github.com> Date: Sat, 27 Sep 2025 04:33:25 +0000 Subject: [PATCH 023/508] build: fix some build issues encountered on a musl libc system (#4903) Two small fixes to compile on musl libc systems. The first commit is about the use of `__WORDSIZE`, which is not a standard define and is defined another header in musl libc. I decided to fix this by following what was done in `libdnf` in [this commit](https://github.com/rpm-software-management/libdnf/pull/1159/commits/82a07dbe6e53fccfe3a8f8e0d951618e0aa72999). However, there are other ways, such as including `bits/reg.h` or `bits/user.h`, which define `__WORDSIZE` in musl libc, or using `__LP64__` to check for 64 bit instead (inspired by [Alpine Linux's patch](https://gitlab.alpinelinux.org/alpine/aports/-/blob/0f71e9504b59049e0ec1abb486ba455ab2650c83/community/openimageio/0001-fix-compile-error.patch)): ```c #if defined(__GNUC__) && (__WORDSIZE == 64 || (defined(__LP64__) && __LP64__)) && !(defined(__APPLE__) && defined(__MACH__)) || defined(__NetBSD__) ``` The second commit is about the use of the `feenableexcept` function. This function is only defined by glibc. The original code assumed all Linux systems use glibc (checking `__linux__` define), so I just changed it to check for glibc directly (by checking the `__GLIBC__` define). I have only tested it on my musl libc system. I think just compiling it on different systems is enough of a test in this case... --------- Signed-off-by: omcaif <103504667+omcaif@users.noreply.github.com> Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/typedesc.h | 2 +- src/libOpenImageIO/imageinout_test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index 04bf3f9a8c..33cf913560 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -409,7 +409,7 @@ template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; -#if defined(__GNUC__) && __WORDSIZE == 64 && !(defined(__APPLE__) && defined(__MACH__)) || defined(__NetBSD__) +#if defined(__GNUC__) && (ULONG_MAX == 0xffffffffffffffff) && !(defined(__APPLE__) && defined(__MACH__)) || defined(__NetBSD__) // Some platforms consider int64_t and long long to be different types, even // though they are actually the same size. static_assert(!std::is_same_v); diff --git a/src/libOpenImageIO/imageinout_test.cpp b/src/libOpenImageIO/imageinout_test.cpp index a33602647b..bcf4b072b8 100644 --- a/src/libOpenImageIO/imageinout_test.cpp +++ b/src/libOpenImageIO/imageinout_test.cpp @@ -537,7 +537,7 @@ main(int argc, char* argv[]) getargs(argc, argv); if (enable_fpe) { -#if defined(__linux__) +#if defined(__GLIBC__) fprintf(stderr, "Enable floating point exceptions.\n"); feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); #else From 72effefaad6bab27a483a94d99c92244accb3dfa Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 28 Sep 2025 23:21:01 -0700 Subject: [PATCH 024/508] build: Update autobuild defaults for some dependencies (#4910) Raise the version we "autobuild" for some packages: * OpenJPEG 2.5.2 -> 2.5.4 * pybind11 3.0.0 -> 3.0.1 * libtiff 4.6.0 -> 4.7.1 * webp 1.4.0 -> 1.6.0 * libjpeg-turbo 3.0.4 -> 3.1.2 And some minor debugging and other aids along the way: * After a build, on linux, do a `ldd` to verify what libraries are linked dynamically for the logs. * Enhance build_dependency_with_cmake macro to allow optional commands: GIT_COMMIT, GIT_SHALLOW, QUIET Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 39 +++++++++++---------- INSTALL.md | 12 +++---- pyproject.toml | 2 ++ src/build-scripts/build_OpenJPEG.bash | 2 +- src/build-scripts/build_libjpeg-turbo.bash | 2 +- src/build-scripts/build_libraw.bash | 2 +- src/build-scripts/build_libtiff.bash | 4 ++- src/build-scripts/build_pugixml.bash | 2 +- src/build-scripts/build_pybind11.bash | 2 +- src/build-scripts/build_webp.bash | 2 +- src/build-scripts/ci-test.bash | 4 +++ src/cmake/build_TIFF.cmake | 6 ++-- src/cmake/build_WebP.cmake | 2 +- src/cmake/build_libjpeg-turbo.cmake | 2 +- src/cmake/build_pybind11.cmake | 2 +- src/cmake/dependency_utils.cmake | 40 ++++++++++++++++------ 16 files changed, 76 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3bf9e44a5..8444a8c475 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,7 @@ jobs: pybind11_ver: v2.9.0 setenvs: export FREETYPE_VERSION=VER-2-12-0 BUILD_PNG_VERSION=1.6.30 + WebP_BUILD_VERSION=1.5.0 - desc: VP2022 clang13/C++17 py39 avx2 exr3.1 ocio2.3 nametag: linux-vfx2022.clang13 runner: ubuntu-latest @@ -359,7 +360,7 @@ jobs: cxx_std: 17 python_ver: "3.11" simd: "avx2,f16c" - fmt_ver: 11.1.4 + fmt_ver: 11.2.0 pybind11_ver: v2.13.6 benchmark: 1 setenvs: export PUGIXML_VERSION=v1.15 @@ -481,17 +482,17 @@ jobs: cc_compiler: gcc-13 cxx_compiler: g++-13 cxx_std: 20 - fmt_ver: 11.2.0 + fmt_ver: 12.0.0 opencolorio_ver: v2.4.2 openexr_ver: v3.4.0 - pybind11_ver: v3.0.0 + pybind11_ver: v3.0.1 python_ver: "3.12" simd: avx2,f16c - setenvs: export LIBJPEGTURBO_VERSION=3.1.1 + setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 LIBRAW_VERSION=0.21.4 - LIBTIFF_VERSION=v4.7.0 - OPENJPEG_VERSION=v2.5.3 + LIBTIFF_VERSION=v4.7.1 + OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 @@ -541,9 +542,9 @@ jobs: python_ver: "3.10" simd: avx2,f16c setenvs: export OpenImageIO_BUILD_LOCAL_DEPS=all - LIBJPEGTURBO_VERSION=3.0.4 - LIBRAW_VERSION=0.21.3 - OPENJPEG_VERSION=v2.4.0 + OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON + LIBRAW_VERSION=0.21.4 + OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 @@ -589,16 +590,16 @@ jobs: cc_compiler: gcc-14 cxx_compiler: g++-14 cxx_std: 20 - fmt_ver: 11.2.0 + fmt_ver: 12.0.0 opencolorio_ver: v2.4.2 openexr_ver: v3.4.0 - pybind11_ver: v3.0.0 + pybind11_ver: v3.0.1 python_ver: "3.12" - setenvs: export LIBJPEGTURBO_VERSION=3.1.1 + setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 LIBRAW_VERSION=0.21.4 - LIBTIFF_VERSION=v4.7.0 - OPENJPEG_VERSION=v2.5.3 + LIBTIFF_VERSION=v4.7.1 + OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 @@ -610,16 +611,16 @@ jobs: cc_compiler: clang-18 cxx_compiler: clang++-18 cxx_std: 20 - fmt_ver: 11.2.0 + fmt_ver: 12.0.0 opencolorio_ver: v2.4.2 openexr_ver: v3.4.0 - pybind11_ver: v3.0.0 + pybind11_ver: v3.0.1 python_ver: "3.12" - setenvs: export LIBJPEGTURBO_VERSION=3.1.0 + setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 LIBRAW_VERSION=0.21.4 - LIBTIFF_VERSION=v4.7.0 - OPENJPEG_VERSION=v2.5.3 + LIBTIFF_VERSION=v4.7.1 + OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 diff --git a/INSTALL.md b/INSTALL.md index dbb89247ae..761839b24b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -27,7 +27,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * libjpeg >= 8 (tested through jpeg9e), or libjpeg-turbo >= 2.1 (tested through 3.1) * zlib >= 1.2.7 (tested through 1.3.1) - * [fmtlib](https://github.com/fmtlib/fmt) >= 7.0 (tested through 11.2 and master). + * [fmtlib](https://github.com/fmtlib/fmt) >= 7.0 (tested through 12.0 and master). If not found at build time, this will be automatically downloaded unless the build sets `-DBUILD_MISSING_FMT=OFF`. * [Robin-map](https://github.com/Tessil/robin-map) (unknown minimum, tested @@ -51,7 +51,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for a wide variety of video formats: * ffmpeg >= 4.0 (tested through 8.0) * If you want support for jpeg 2000 images: - * OpenJpeg >= 2.0 (tested through 2.5.3; we recommend 2.4 or higher + * OpenJpeg >= 2.0 (tested through 2.5.4; we recommend 2.4 or higher for multithreading support) * If you want support for OpenVDB files: * OpenVDB >= 9.0 (tested through 12.1). @@ -59,12 +59,12 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * TBB >= 2018 (tested through 2021 and OneTBB) * If you want support for converting to and from OpenCV data structures, or for capturing images from a camera: - * OpenCV 4.x (tested through 4.11) + * OpenCV 4.x (tested through 4.12) * If you want support for GIF images: * giflib >= 5.0 (tested through 5.2.2) * If you want support for HEIF/HEIC or AVIF images: * libheif >= 1.11 (1.16 required for correct orientation support, - tested through 1.19.8) + tested through 1.20.2) * libheif must be built with an AV1 encoder/decoder for AVIF support. * If you want support for DICOM medical image files: * DCMTK >= 3.6.1 (tested through 3.6.9) @@ -78,10 +78,8 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * libultrahdr >= 1.3 (tested through 1.4) * If you want support for JPEG XL images: * libjxl >= 0.10.1 (tested through 0.11.1) - * If you want support for "Ultra HDR" inside JPEG images: - * libuhdr >= 1.3 (tested through 1.4) * If you want support for j2c files: - * OpenJPH >= 0.21 (tested through 0.22) + * OpenJPH >= 0.21.2 (tested through 0.23) * We use PugiXML for XML parsing. There is a version embedded in the OIIO tree, but if you want to use an external, system-installed version (as may be required by some software distributions with policies against diff --git a/pyproject.toml b/pyproject.toml index b243ba3ddc..2c10db63ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,6 +119,8 @@ SKBUILD_CMAKE_ARGS = "-DLINKSTATIC=1" # Suppress warnings that cause linux cibuildwheel build to fail CXXFLAGS = "-Wno-error=stringop-overflow -Wno-pragmas" SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" +# FIXME: Getting build problems when using WebP 1.6.0, so hold it back +WebP_BUILD_VERSION = "1.5.0" [tool.cibuildwheel.windows.environment] SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" diff --git a/src/build-scripts/build_OpenJPEG.bash b/src/build-scripts/build_OpenJPEG.bash index 3a5dff39eb..f2bc23f70f 100755 --- a/src/build-scripts/build_OpenJPEG.bash +++ b/src/build-scripts/build_OpenJPEG.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of OpenJPEG to download if we don't have it yet OPENJPEG_REPO=${OPENJPEG_REPO:=https://github.com/uclouvain/openjpeg.git} -OPENJPEG_VERSION=${OPENJPEG_VERSION:=v2.5.2} +OPENJPEG_VERSION=${OPENJPEG_VERSION:=v2.5.4} # Where to put OpenJPEG repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libjpeg-turbo.bash b/src/build-scripts/build_libjpeg-turbo.bash index bfa829db58..a15ad1897f 100755 --- a/src/build-scripts/build_libjpeg-turbo.bash +++ b/src/build-scripts/build_libjpeg-turbo.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of libjpeg-turbo to download if we don't have it yet LIBJPEGTURBO_REPO=${LIBJPEGTURBO_REPO:=https://github.com/libjpeg-turbo/libjpeg-turbo.git} -LIBJPEGTURBO_VERSION=${LIBJPEGTURBO_VERSION:=3.0.4} +LIBJPEGTURBO_VERSION=${LIBJPEGTURBO_VERSION:=3.1.2} # Where to put libjpeg-turbo repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libraw.bash b/src/build-scripts/build_libraw.bash index 1e6ea024e7..b5114d3aab 100755 --- a/src/build-scripts/build_libraw.bash +++ b/src/build-scripts/build_libraw.bash @@ -11,7 +11,7 @@ set -ex # Which LibRaw to retrieve, how to build it LIBRAW_REPO=${LIBRAW_REPO:=https://github.com/LibRaw/LibRaw.git} -LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.3} +LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.4} # Where to install the final results LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libtiff.bash b/src/build-scripts/build_libtiff.bash index 763fb4bc75..ff0718df5e 100755 --- a/src/build-scripts/build_libtiff.bash +++ b/src/build-scripts/build_libtiff.bash @@ -13,7 +13,7 @@ LIBTIFF_REPO=${LIBTIFF_REPO:=https://gitlab.com/libtiff/libtiff.git} LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} LIBTIFF_BUILD_DIR=${LIBTIFF_BUILD_DIR:=${LOCAL_DEPS_DIR}/libtiff} LIBTIFF_INSTALL_DIR=${LIBTIFF_INSTALL_DIR:=${PWD}/ext/dist} -LIBTIFF_VERSION=${LIBTIFF_VERSION:=v4.6.0} +LIBTIFF_VERSION=${LIBTIFF_VERSION:=v4.7.1} LIBTIFF_BUILD_TYPE=${LIBTIFF_BUILD_TYPE:=Release} if [[ `uname` == "Linux" ]] ; then LIBTIFF_CXX_FLAGS=${LIBTIFF_CXX_FLAGS:="-O3 -Wno-unused-function -Wno-deprecated-declarations -Wno-cast-qual -Wno-write-strings"} @@ -42,6 +42,8 @@ if [[ -z $DEP_DOWNLOAD_ONLY ]]; then -DCMAKE_INSTALL_PREFIX=${LIBTIFF_INSTALL_DIR} \ -DCMAKE_CXX_FLAGS="${LIBTIFF_CXX_FLAGS}" \ -DBUILD_SHARED_LIBS=${LIBTIFF_BUILD_SHARED_LIBS:-ON} \ + -Dtiff-tools=${LIBTIFF_BUILD_TESTS:-OFF} \ + -Dtiff-contrib=${LIBTIFF_BUILD_TESTS:-OFF} \ -Dtiff-tests=${LIBTIFF_BUILD_TESTS:-OFF} \ -Dtiff-docs=${LIBTIFF_BUILD_TESTS:-OFF} \ -Dlibdeflate=ON \ diff --git a/src/build-scripts/build_pugixml.bash b/src/build-scripts/build_pugixml.bash index c44500ac25..ff9572c314 100755 --- a/src/build-scripts/build_pugixml.bash +++ b/src/build-scripts/build_pugixml.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of pugixml to download if we don't have it yet PUGIXML_REPO=${PUGIXML_REPO:=https://github.com/zeux/pugixml.git} -PUGIXML_VERSION=${PUGIXML_VERSION:=v1.11.4} +PUGIXML_VERSION=${PUGIXML_VERSION:=v1.15} # Where to put pugixml repo source (default to the ext area) PUGIXML_SRC_DIR=${PUGIXML_SRC_DIR:=${PWD}/ext/pugixml} diff --git a/src/build-scripts/build_pybind11.bash b/src/build-scripts/build_pybind11.bash index 41151444ad..7c7d886e84 100755 --- a/src/build-scripts/build_pybind11.bash +++ b/src/build-scripts/build_pybind11.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of pybind11 to download if we don't have it yet PYBIND11_REPO=${PYBIND11_REPO:=https://github.com/pybind/pybind11.git} -PYBIND11_VERSION=${PYBIND11_VERSION:=v3.0.0} +PYBIND11_VERSION=${PYBIND11_VERSION:=v3.0.1} # Where to put pybind11 repo source (default to the ext area) PYBIND11_SRC_DIR=${PYBIND11_SRC_DIR:=${PWD}/ext/pybind11} diff --git a/src/build-scripts/build_webp.bash b/src/build-scripts/build_webp.bash index 8af015f683..35cc9779e0 100755 --- a/src/build-scripts/build_webp.bash +++ b/src/build-scripts/build_webp.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of webp to download if we don't have it yet WEBP_REPO=${WEBP_REPO:=https://github.com/webmproject/libwebp.git} -WEBP_VERSION=${WEBP_VERSION:=v1.4.0} +WEBP_VERSION=${WEBP_VERSION:=v1.6.0} # Where to put webp repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/ci-test.bash b/src/build-scripts/ci-test.bash index 9d7e86712f..89f3f5138b 100755 --- a/src/build-scripts/ci-test.bash +++ b/src/build-scripts/ci-test.bash @@ -34,6 +34,10 @@ $OpenImageIO_ROOT/bin/oiiotool --unittest --list-formats --threads 0 \ echo ; echo "Try unknown command:" $OpenImageIO_ROOT/bin/oiiotool -q --unknown || true +if [[ `uname -s` == "Linux" ]] ; then + echo ; echo "ldd oiiotool:" + ldd $OpenImageIO_ROOT/bin/oiiotool +fi # # Full test suite diff --git a/src/cmake/build_TIFF.cmake b/src/cmake/build_TIFF.cmake index 93c3507fa0..45cd525298 100644 --- a/src/cmake/build_TIFF.cmake +++ b/src/cmake/build_TIFF.cmake @@ -6,11 +6,11 @@ # TIFF by hand! ###################################################################### -set_cache (TIFF_BUILD_VERSION 4.6.0 "TIFF version for local builds") +set_cache (TIFF_BUILD_VERSION 4.7.1 "TIFF version for local builds") set (TIFF_GIT_REPOSITORY "https://gitlab.com/libtiff/libtiff.git") -set (TIFF_GIT_TAG "v${TIFF_BUILD_VERSION}") +set_cache (TIFF_GIT_TAG "v${TIFF_BUILD_VERSION}" "Git branch or tag") set_cache (TIFF_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} - DOC "Should a local TIFF build, if necessary, build shared libraries" ADVANCED) + "Should a local TIFF build, if necessary, build shared libraries" ADVANCED) # We need libdeflate to build libtiff checked_find_package (libdeflate REQUIRED diff --git a/src/cmake/build_WebP.cmake b/src/cmake/build_WebP.cmake index a86f99bd69..98b0f94406 100644 --- a/src/cmake/build_WebP.cmake +++ b/src/cmake/build_WebP.cmake @@ -6,7 +6,7 @@ # WebP by hand! ###################################################################### -set_cache (WebP_BUILD_VERSION 1.4.0 "WebP version for local builds") +set_cache (WebP_BUILD_VERSION 1.6.0 "WebP version for local builds") set (WebP_GIT_REPOSITORY "https://github.com/webmproject/libwebp.git") set (WebP_GIT_TAG "v${WebP_BUILD_VERSION}") diff --git a/src/cmake/build_libjpeg-turbo.cmake b/src/cmake/build_libjpeg-turbo.cmake index 4b2fa31de8..0bfcd6ca7c 100644 --- a/src/cmake/build_libjpeg-turbo.cmake +++ b/src/cmake/build_libjpeg-turbo.cmake @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/Academ SoftwareFoundation/OpenImageIO -set_cache (libjpeg-turbo_BUILD_VERSION 3.0.4 "libjpeg-turbo version for local builds") +set_cache (libjpeg-turbo_BUILD_VERSION 3.1.2 "libjpeg-turbo version for local builds") set (libjpeg-turbo_GIT_REPOSITORY "https://github.com/libjpeg-turbo/libjpeg-turbo") set (libjpeg-turbo_GIT_TAG "${libjpeg-turbo_BUILD_VERSION}") set_cache (libjpeg-turbo_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} diff --git a/src/cmake/build_pybind11.cmake b/src/cmake/build_pybind11.cmake index 83a0aebb8a..e7a6f3e282 100644 --- a/src/cmake/build_pybind11.cmake +++ b/src/cmake/build_pybind11.cmake @@ -6,7 +6,7 @@ # pybind11 by hand! ###################################################################### -set_cache (pybind11_BUILD_VERSION 3.0.0 "pybind11 version for local builds") +set_cache (pybind11_BUILD_VERSION 3.0.1 "pybind11 version for local builds") set (pybind11_GIT_REPOSITORY "https://github.com/pybind/pybind11") set (pybind11_GIT_TAG "v${pybind11_BUILD_VERSION}") set_cache (pybind11_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 58892a6b43..3a7fe15fa3 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -587,7 +587,7 @@ macro (build_dependency_with_cmake pkgname) # noValueKeywords: "NOINSTALL" # singleValueKeywords: - "GIT_REPOSITORY;GIT_TAG;VERSION;SOURCE_SUBDIR" + "GIT_REPOSITORY;GIT_TAG;GIT_COMMIT;VERSION;SOURCE_SUBDIR;GIT_SHALLOW;QUIET" # multiValueKeywords: "CMAKE_ARGS" # argsToParse: @@ -600,22 +600,42 @@ macro (build_dependency_with_cmake pkgname) set (${pkgname}_LOCAL_INSTALL_DIR "${${PROJECT_NAME}_LOCAL_DEPS_ROOT}/dist") message (STATUS "Downloading local ${_pkg_GIT_REPOSITORY}") - set (_pkg_quiet OUTPUT_QUIET) + unset (${pkgname}_GIT_CLONE_ARGS) + unset (_pkg_exec_quiet) + if (_pkg_GIT_SHALLOW OR "${_pkg_GIT_SHALLOW}" STREQUAL "") + list (APPEND ${pkgname}_GIT_CLONE_ARGS --depth 1) + endif () + if (_pkg_QUIET OR "${_pkg_QUIET}" STREQUAL "") + list (APPEND ${pkgname}_GIT_CLONE_ARGS -q) + set (_pkg_exec_quiet OUTPUT_QUIET) + endif () # Clone the repo if we don't already have it find_package (Git REQUIRED) if (NOT IS_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR}) + message (STATUS "COMMAND ${GIT_EXECUTABLE} clone ${_pkg_GIT_REPOSITORY} " + "-b ${_pkg_GIT_TAG} " + "${${pkgname}_LOCAL_SOURCE_DIR} " + "${${pkgname}_GIT_CLONE_ARGS} " + "${_pkg_exec_quiet}") execute_process(COMMAND ${GIT_EXECUTABLE} clone ${_pkg_GIT_REPOSITORY} - -b ${_pkg_GIT_TAG} --depth 1 -q + -b ${_pkg_GIT_TAG} ${${pkgname}_LOCAL_SOURCE_DIR} - ${_pkg_quiet}) + ${${pkgname}_GIT_CLONE_ARGS} + ${_pkg_exec_quiet}) if (NOT IS_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR}) message (FATAL_ERROR "Could not download ${_pkg_GIT_REPOSITORY}") endif () endif () - execute_process(COMMAND ${GIT_EXECUTABLE} checkout ${_pkg_GIT_TAG} - WORKING_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR} - ${_pkg_quiet}) + if ("${_pkg_GIT_COMMIT}" STREQUAL "") + execute_process(COMMAND ${GIT_EXECUTABLE} checkout ${_pkg_GIT_TAG} + WORKING_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR} + ${_pkg_exec_quiet}) + else () + execute_process(COMMAND ${GIT_EXECUTABLE} checkout ${_pkg_GIT_COMMIT} + WORKING_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR} + ${_pkg_exec_quiet}) + endif () # Configure the package if (${PROJECT_NAME}_DEPENDENCY_BUILD_VERBOSE) @@ -657,14 +677,14 @@ macro (build_dependency_with_cmake pkgname) ${_pkg_cmake_verbose} # Build args passed by caller ${_pkg_CMAKE_ARGS} - ${pkg_quiet} + ${_pkg_exec_quiet} ) # Build the package execute_process (COMMAND ${CMAKE_COMMAND} --build ${${pkgname}_LOCAL_BUILD_DIR} --config ${${PROJECT_NAME}_DEPENDENCY_BUILD_TYPE} - ${pkg_quiet} + ${_pkg_exec_quiet} ) # Install the project, unless instructed not to do so @@ -673,7 +693,7 @@ macro (build_dependency_with_cmake pkgname) --build ${${pkgname}_LOCAL_BUILD_DIR} --config ${${PROJECT_NAME}_DEPENDENCY_BUILD_TYPE} --target install - ${pkg_quiet} + ${_pkg_exec_quiet} ) set (${pkgname}_ROOT ${${pkgname}_LOCAL_INSTALL_DIR}) list (APPEND CMAKE_PREFIX_PATH ${${pkgname}_LOCAL_INSTALL_DIR}) From 15768ff198bec322d45019d9ee81fa9d13941a67 Mon Sep 17 00:00:00 2001 From: shanesmith-dwa Date: Tue, 30 Sep 2025 12:32:48 -0700 Subject: [PATCH 025/508] feat(jpeg-xl): ICC read and write for JPEG-XL files (issue 4649) (#4905) This PR addresses [4649](https://github.com/AcademySoftwareFoundation/OpenImageIO/issues/4649) implementing ICC read and write support for JPEG-XL. Fixes #4969 The jxl testsuite directory was added with tests. --------- Signed-off-by: Shane Smith Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/testing.cmake | 2 ++ src/jpegxl.imageio/jxlinput.cpp | 19 +++++++++++++++++++ src/jpegxl.imageio/jxloutput.cpp | 15 +++++++++++++++ testsuite/jxl/ref/out.txt | 21 +++++++++++++++++++++ testsuite/jxl/ref/test-jxl.icc | Bin 0 -> 560 bytes testsuite/jxl/run.py | 16 ++++++++++++++++ 6 files changed, 73 insertions(+) create mode 100644 testsuite/jxl/ref/out.txt create mode 100644 testsuite/jxl/ref/test-jxl.icc create mode 100755 testsuite/jxl/run.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 63e94c9ef8..58670a5886 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -284,6 +284,8 @@ macro (oiio_add_all_tests) FOUNDVAR OPENJPEG_FOUND IMAGEDIR j2kp4files_v1_5 URL http://www.itu.int/net/ITU-T/sigdb/speimage/ImageForm-s.aspx?val=10100803) + oiio_add_tests (jxl + FOUNDVAR JXL_FOUND) set (all_openexr_tests openexr-suite openexr-multires openexr-chroma openexr-decreasingy openexr-v2 openexr-window perchannel oiiotool-deep) diff --git a/src/jpegxl.imageio/jxlinput.cpp b/src/jpegxl.imageio/jxlinput.cpp index 2be2efaf4c..9ed3b1c45b 100644 --- a/src/jpegxl.imageio/jxlinput.cpp +++ b/src/jpegxl.imageio/jxlinput.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -53,6 +54,7 @@ class JxlInput final : public ImageInput { std::string m_filename; int m_next_scanline; // Which scanline is the next to read? uint32_t m_channels; + JxlColorEncoding m_color_encoding; JxlDecoderPtr m_decoder; JxlResizableParallelRunnerPtr m_runner; std::unique_ptr m_config; // Saved copy of configuration spec @@ -346,6 +348,23 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) m_spec = ImageSpec(info.xsize, info.ysize, m_channels, m_data_type); + if (m_icc_profile.size() && m_icc_profile.data()) { + m_spec.attribute("ICCProfile", + TypeDesc(TypeDesc::UINT8, m_icc_profile.size()), + m_icc_profile.data()); + std::string errormsg; + + bool ok = decode_icc_profile(cspan(m_icc_profile.data(), + m_icc_profile.size()), + m_spec, errormsg); + + if (!ok && OIIO::get_int_attribute("imageinput:strict")) { + errorfmt("Possible corrupt file, could not decode ICC profile: {}\n", + errormsg); + return false; + } + } + newspec = m_spec; return true; } diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index f1b47a0d3b..c5802e3b27 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -538,6 +538,21 @@ JxlOutput::save_image(const void* data) return 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"); + } + } + } // No more image frames nor metadata boxes to add DBG std::cout << "calling JxlEncoderCloseInput()\n"; diff --git a/testsuite/jxl/ref/out.txt b/testsuite/jxl/ref/out.txt new file mode 100644 index 0000000000..040910d3c9 --- /dev/null +++ b/testsuite/jxl/ref/out.txt @@ -0,0 +1,21 @@ +Reading tahoe-icc.jxl +tahoe-icc.jxl : 128 x 96, 3 channel, uint8 jpegxl + SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C + channel list: R, G, B + ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1094992453 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "Copyright 1999 Adobe Systems Incorporated" + ICCProfile:creation_date: "1999:06:03 00:00:00" + ICCProfile:creator_signature: "41444245" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "6e6f6e65" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "Adobe RGB (1998)" + ICCProfile:profile_size: 560 + ICCProfile:profile_version: "2.1.0" + ICCProfile:rendering_intent: "Perceptual" diff --git a/testsuite/jxl/ref/test-jxl.icc b/testsuite/jxl/ref/test-jxl.icc new file mode 100644 index 0000000000000000000000000000000000000000..fe2cab55e60201fc39020cceb18e1cd340e45d9c GIT binary patch literal 560 zcmZQzU@~xYadKr6U|`72D=7+ccT$Lmj8b4f&%nmO%m4<7$;AbZ0RcWBPF{XqDnt~S z{C16j5yZc&3o;8?h6pxSazRlEP~9IOHcCk?PG(?WGyt-*%S#G?;*4{EY>}jFFna@t zT@(`J3=}^CWb>s%*jGU8BnbNnh+PEq1W?Tvkot5mn~4L&PJ*yyKRDfvmM3c;1dC8@c^3Z8k%`9%f!MTsS;DL}Ol_knaV2tpKsLQDgw z(Lxg}N<{(`4-n%%2ZF Date: Wed, 1 Oct 2025 12:51:44 -0700 Subject: [PATCH 026/508] deps: Support for OpenColorIO 2.5 (#4916) A test needed updating because the set of color spaces in the built-in config are different than for 2.4. Fixes #4915 Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 4 +- INSTALL.md | 2 +- .../python-colorconfig/ref/out-ocio25.txt | 44 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 testsuite/python-colorconfig/ref/out-ocio25.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8444a8c475..e1ca5ea850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -483,7 +483,7 @@ jobs: cxx_compiler: g++-13 cxx_std: 20 fmt_ver: 12.0.0 - opencolorio_ver: v2.4.2 + opencolorio_ver: v2.5.0 openexr_ver: v3.4.0 pybind11_ver: v3.0.1 python_ver: "3.12" @@ -591,7 +591,7 @@ jobs: cxx_compiler: g++-14 cxx_std: 20 fmt_ver: 12.0.0 - opencolorio_ver: v2.4.2 + opencolorio_ver: v2.5.0 openexr_ver: v3.4.0 pybind11_ver: v3.0.1 python_ver: "3.12" diff --git a/INSTALL.md b/INSTALL.md index 761839b24b..c907ff5795 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -23,7 +23,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * Imath >= 3.1 (tested through 3.2 and main) * OpenEXR >= 3.1 (tested through 3.4 and main) * libTIFF >= 4.0 (tested through 4.7 and master) - * *OpenColorIO >= 2.3* (tested through 2.4 and main) + * *OpenColorIO >= 2.3* (tested through 2.5 and main) * libjpeg >= 8 (tested through jpeg9e), or libjpeg-turbo >= 2.1 (tested through 3.1) * zlib >= 1.2.7 (tested through 1.3.1) diff --git a/testsuite/python-colorconfig/ref/out-ocio25.txt b/testsuite/python-colorconfig/ref/out-ocio25.txt new file mode 100644 index 0000000000..07569ff915 --- /dev/null +++ b/testsuite/python-colorconfig/ref/out-ocio25.txt @@ -0,0 +1,44 @@ +getNumColorSpaces = 25 +getColorSpaceNames = ['sRGB - Display', 'Gamma 2.2 Rec.709 - Display', 'Display P3 - Display', 'Display P3 HDR - Display', 'P3-D65 - Display', 'Rec.1886 Rec.709 - Display', 'Rec.2100-PQ - Display', 'ST2084-P3-D65 - Display', 'ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'sRGB Encoded Rec.709 (sRGB)', 'Gamma 1.8 Encoded Rec.709', 'Gamma 2.2 Encoded Rec.709', 'Gamma 2.4 Encoded Rec.709', 'sRGB Encoded P3-D65', 'Gamma 2.2 Encoded AdobeRGB', 'sRGB Encoded AP1', 'Gamma 2.2 Encoded AP1', 'Linear AdobeRGB', 'Linear P3-D65', 'Linear Rec.2020', 'Linear Rec.709 (sRGB)', 'Raw'] +Index of 'lin_srgb' = 23 +Index of 'unknown' = -1 +Name of color space 2 = Display P3 - Display +getNumLooks = 1 +getLookNames = ['ACES 1.3 Reference Gamut Compression'] +getNumDisplays = 8 +getDisplayNames = ['sRGB - Display', 'Display P3 - Display', 'Display P3 HDR - Display', 'Gamma 2.2 Rec.709 - Display', 'P3-D65 - Display', 'Rec.1886 Rec.709 - Display', 'Rec.2100-PQ - Display', 'ST2084-P3-D65 - Display'] +getDefaultDisplayName = sRGB - Display +getNumViews = 4 +getViewNames = ['ACES 2.0 - SDR 100 nits (Rec.709)', 'Un-tone-mapped', 'Video (colorimetric)', 'Raw'] +getDefaultViewName = ACES 2.0 - SDR 100 nits (Rec.709) +getNumRoles = 9 +getRoles = ['aces_interchange', 'cie_xyz_d65_interchange', 'color_picking', 'color_timing', 'compositing_log', 'data', 'matte_paint', 'scene_linear', 'texture_paint'] +aliases of 'scene_linear' are ['ACES - ACEScg', 'lin_ap1', 'lin_ap1_scene'] +resolve('foo'): foo +resolve('linear'): Linear Rec.709 (sRGB) +resolve('scene_linear'): ACEScg +resolve('lin_srgb'): Linear Rec.709 (sRGB) +resolve('srgb'): sRGB Encoded Rec.709 (sRGB) +resolve('ACEScg'): ACEScg +equivalent('lin_srgb', 'srgb'): False +equivalent('scene_linear', 'srgb'): False +equivalent('linear', 'lin_srgb'): False +equivalent('scene_linear', 'lin_srgb'): False +equivalent('ACEScg', 'scene_linear'): True +equivalent('lnf', 'scene_linear'): False + +Loaded test OCIO config: oiio_test_v0.9.2.ocio +Parsed color space for filepath 'foo_lin_ap1.exr': ACEScg +Default color space: lin_rec709 +Default display: sRGB (~2.22) - Display +Default view for sRGB (~2.22) - Display (from lin_rec709): ACES 1.0 - SDR Video +Default view for sRGB (~2.22) - Display (from 'srgb_tx'): Colorimetry +Color space name from DisplayView transform referencing Shared View: sRGB (~2.22) - Display +Test buffer -- initial values: [[[0.1 0.5 0.9]]] (ACEScg) +ociodisplay #1 (apply default display/view): [[[-2.123 0.671 0.8037]]] (sRGB (~2.22) - Display) +ociodisplay #2 (apply default display/view again): [[[-2.123 0.671 0.8037]]] (sRGB (~2.22) - Display) +ociodisplay #3 (inverse look): [[[-2.252 0.6714 0.8037]]] (sRGB (~2.22) - Display) +ociodisplay #4 (forwards look): [[[-2.123 0.671 0.8037]]] (sRGB (~2.22) - Display) +ociodisplay #5 (inverse look + forwards look): [[[-2.123 0.671 0.8037]]] (sRGB (~2.22) - Display) + +Done. From 85cb6d64cb933ee259750d5e348f531de9f11f6f Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 2 Oct 2025 18:42:56 -0700 Subject: [PATCH 027/508] docs: CHANGES and other misc docs changes from the 3.1 release (#4918) Forward-porting some docs touch-ups that were part of the 3.1 release staging. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 4 +- CHANGES.md | 84 ++++++++++++++++++++++++---------------- CONTRIBUTING.md | 32 ++++++--------- CREDITS.md | 3 ++ README.md | 7 +++- SECURITY.md | 5 +-- docs/dev/RELEASING.md | 10 ++--- 7 files changed, 81 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1ca5ea850..e57351446c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -406,8 +406,8 @@ jobs: pybind11_ver: v3.0.0 simd: "avx2,f16c" skip_tests: 1 - # abi_check: v3.1.3.0 - abi_check: 9bfcce725a3806a3f70c7e838d9d98d6d95c917a + # abi_check: v3.1.6.0 + abi_check: d4c8024633dba8bb3c01d22b65ce9bc7a1ae215e setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 optional_deps_append: "openjph;Qt6" diff --git a/CHANGES.md b/CHANGES.md index 38620323e5..8d1b9cf96b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) ### 🚀 Performance improvements ### 🐛 Fixes and feature enhancements + - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.2.0.0) ### 🔧 Internals and developer goodies ### 🏗 Build/test/CI and platform ports * OIIO's CMake build system and scripts: @@ -20,45 +21,23 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 * Testing and Continuous integration (CI) systems: ### 📚 Notable documentation changes ### 🏢 Project Administration +### 🤝 Contributors --- --- -Release 3.1 (target: Oct 1 2025?) -- compared to 3.0 ----------------------------------------------------- +Release 3.1 (Oct 2 2025) -- compared to 3.0.x +----------------------------------------------------- - Beta 1: Aug 22, 2025 - Beta 2: Sep 19, 2025 -- Anticipated release candidate: Sep 24, 2025 -- Anticipated supported release: Oct 1, 2025 - -**Change highlights in beta 2** - - *oiiotool*: Allow easy splitting of subimages by name [#4874](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4874) (3.1.5.0) - - *ffmpeg*: Add ability to read CICP metadata [#4882](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4882) (by Brecht Van Lommel) (3.1.5.0) - - *ffmpeg*: FFmpeg incorrectly set zero oiio:BitsPerSample [#4885](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4885) (by Brecht Van Lommel) (3.1.5.0) - - *gif*: Gif output didn't handle FramesPerSecond attribute correctly [#4890](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4890) (3.1.5.0) - - *heic*: Read and write of CICP and support for bit depth 10 and 12 [#4880](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4880) (by Brecht Van Lommel) (3.1.5.0) - - *png*: CICP metadata support for PNG [#4746](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4746) (by Zach Lewis) (3.1.5.0) - - *raw*: Add thumbnail support to the raw input plugin [#4887](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4887) (by Anton Dukhovnikov) (3.1.5.0) - - *webp*: Support reading/writing the ICCProfile attribute [#4878](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4878) (by Jesse Yurkovich) (3.1.5.0) - - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.1.5.0) - - *deps(freetype)*: Test freetype 2.14 and document that it works [#4876](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4876) (3.1.5.0) - - *deps(ffmpeg)*: Ffmpeg 8 support [#4870](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4870) (3.1.5.0) - - *deps(openvdb)*: Look for boost headers for OpenVDBs older than 12 [#4873](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4873) (by Alex Fuller) (3.1.5.0) - - *deps(openexr)*: OpenEXR 3.4 supports two compression types for HTJ2K [#4871](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4871) (by Todica Ionut) (3.1.5.0) - - *deps(openexr)*: Several OpenEXR and OpenJPH build related fixes [#4875](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4875) (3.1.5.0) - - *deps(openjph)*: Fix openjph target use [#4894](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4894) (3.1.5.0) - - *ci*: Fix broken python wheel building [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) (3.1.5.0) - - -**NOTE:** We anticipate some additional changes to color management to be -rolled out during the beta period. It will not include any breaks to API or -ABI compatibility, but we do expect some behavior changes. +- Release candidate 1: Sep 27, 2025 +- Full release, v3.1.6.1: Oct 2, 2025 **Executive Summary / Highlights:** - New image file support: Ultra HDR (HDR images in JPEG containers). - oiiotool new commands: `--layersplit`, `--pastemeta`, `--demosaic`, - `create-dir` and new expression expansion tokens: `IS_CONSTANT`, + `--create-dir` and new expression expansion tokens: `IS_CONSTANT`, `IS_BLACK`, `SUBIMAGES`. - New IBA image processing functions: `scale()`, `demosaic()`. - New 2-level namespace scheme that we hope will make it possible in the @@ -134,22 +113,23 @@ ABI compatibility, but we do expect some behavior changes. ### 🐛 Fixes and feature enhancements: - *oiiotool*: Better handling of wildcards that match no files [#4627](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4627) (3.1.1.0) - - *oiotool*: Invalid loop bound when appending mipmap textures using oiiotool [#4671](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4671) (by Basile Fraboni) (3.1.1.0) + - *oiiotool*: Invalid loop bound when appending mipmap textures using oiiotool [#4671](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4671) (by Basile Fraboni) (3.1.1.0) - *oiiotool*: -i:native=1, fix --native behavior, fix convert datatype [#4708](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4708) (3.1.3.0) - *oiiotool*: Fixes to --missingfile behavior [#4803](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4803) (3.1.3.0) - *oiiotool*: Allow thread control for --parallel-frames [#4818](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4818) (3.1.3.0) + - *oiiotool*: Use normalized path when creating wildcard path pattern [#4904](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4904) (by Jesse Yurkovich) (3.1.6.0) + - *oiiotool*: Ignore empty subimage(s) when calculating non-zero region [#4909](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4909) (by Carine Touraille) (3.1.6.0) - *color mgmt*: Support OCIO Viewing Rules, other OCIO-related improvements [#4780](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4780) (by zachlewis) (3.1.3.0) - *iv*: Fix crash on .DS_Store; fix uppercase extensions [#4764](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4764) (by Anton Dukhovnikov) (3.1.3.0) - *iv*: Do not resize on open and other zoom fixes [#4766](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4766) (by Aleksandr Motsjonov) (3.1.3.0) - *iv*: Bug fix for iv window losing focus on mac on startup [#4773](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4773) (by Aleksandr Motsjonov) (3.1.3.0) - *iv*: Use screen pixel ratio to render sharp text in pixel view tool [#4768](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4768) (by Aleksandr Motsjonov) (3.1.3.0) - *IBA*: IBA:demosaic add white balancing [#4499](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4499) (by Anton Dukhovnikov) (3.1.0.0/3.0.1.0) - - *IBA*: IBA:demosaic add white balancing [#4499](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4499) (by Anton Dukhovnikov) (3.1.0.0/3.0.1.0) - *IBA*: IBA::demosaic - fix roi channels [#4602](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4602) (by Anton Dukhovnikov) (3.1.1.0) - *IBA*: Add 'auto' value for all options of `IBA::demosaic()` [#4786](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4786) (by Anton Dukhovnikov) (3.1.3.0) - *ImageBuf*: IB::pixeltype() did not always return the right value [#4614](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4614) (3.1.1.0) - *ImageBuf*: Fix bug in ImageBuf construction from ptr + neg strides [#4630](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4630) (3.1.1.0) - - *ImageBuf*: Better errors for nonexistant subimages/mips [#4801](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4801) (3.1.3.0) + - *ImageBuf*: Better errors for nonexistent subimages/mips [#4801](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4801) (3.1.3.0) - *ImageInput*: Incorrect IOProxy logic related to valid_file [#4839](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4839) (3.1.4.0) - *python*: Disable loading Python DLLs from PATH by default on Windows [#4590](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4590) (by zachlewis) (3.1.1.0) - *python*: Fix handle leak [#4685](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4685) (3.1.3.0) @@ -197,7 +177,7 @@ ABI compatibility, but we do expect some behavior changes. - *int*: Switch to spans for some exif manipulation, fixing warnings [#4689](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4689) (3.1.1.0) - *int*: Rearrange initialize_cuda() for better err check, no warn [#4726](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4726) (3.1.3.0) - *int*: Experimental default_init_allocator and default_init_vector [#4677](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4677) (3.1.3.0) - - *int*: Address some nickpick sonar warnings about TileID initialization [#4722](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4722) (3.1.3.0) + - *int*: Address some nitpick sonar warnings about TileID initialization [#4722](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4722) (3.1.3.0) - *int*: Address safety warnings in pvt::append_tiff_dir_entry [#4737](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4737) (3.1.3.0) - *int*: ImageInput/ImageOutput did not set per-file threads correctly [#4750](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4750) (3.1.3.0) - *int/iv*: Add raw string syntax modifier for VSCode and Cursor to understand its glsl [#4796](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4796) (by Aleksandr Motsjonov) (3.1.3.0) @@ -239,6 +219,7 @@ ABI compatibility, but we do expect some behavior changes. - *build*: Fix typo related to finding ccache [#4833](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4833) (3.1.4.0) - *build*: C++23 support [#4844](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4844) (3.1.4.0) - *build*: Clean up obsolete logic for old compilers [#4849](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4849) (3.1.5.0) + - *build*: Update autobuild defaults for some dependencies [#4910](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4910) (3.1.6.1) * Dependency and platform support: - *build(OCIO)*: Support static OCIO self-builds [#4517](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4517) (by zachlewis) (3.1.0.0/3.0.1.0) - *build(PNG)*: Add build recipe for PNG [#4423](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4423) (by zachlewis) (3.1.0.0/3.0.1.0); PNG auto-build improvements [#4835](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4835) (3.1.4.0) @@ -261,10 +242,12 @@ ABI compatibility, but we do expect some behavior changes. - *deps(openjph)*: Fix openjph target use [#4894](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4894) (3.1.5.0) - *deps(openvdb)*: Look for boost headers for OpenVDBs older than 12 [#4873](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4873) (by Alex Fuller) (3.1.5.0) - *deps(python)*: Raise minimum supported Python from 3.7 to 3.9 [#4830](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4830) (3.1.4.0) + - *deps(opencolorio)*: Support for OpenColorIO 2.5 [#4916](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4916) (3.1.6.1) - *windows*: Include Windows version information on produced binaries [#4696](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4696) (by Jesse Yurkovich) (3.1.3.0) - *windows*: Propagate CMAKE_MSVC_RUNTIME_LIBRARY [#4842](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4842) (3.1.4.0) - *windows + ARM64*: Add arm_neon.h include on Windows ARM64 with clang-cl [#4691](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4691) (by Anthony Roberts) - *NetBSD*: Fix build on NetBSD [#4857](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4857) (by Thomas Klausner) (3.1.4.0) + - *build*: Fix some build issues encountered on a musl libc system [#4903](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4903) (by omcaif) (3.1.6.0) * Testing and Continuous integration (CI) systems: - *tests*: Improve Ptex testing [#4573](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4573) (3.1.1.0) - *tests*: Better testing coverage of null image reader/writer [#4578](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4578) (3.1.1.0) @@ -311,11 +294,14 @@ ABI compatibility, but we do expect some behavior changes. - *ci*: Add a VFX Platform 2026 CI job [#4856](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4856) (3.1.4.0) - *ci*: Lock down to ci-oiio container with correct llvm components [#4859](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4859) (3.1.4.0) - *ci*: Bump webp and openexr for "latest versions" test [#4861](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4861) (3.1.4.0) - - *ci*: Switch to compile-commands for sonar [#4879](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4879) (by vvalderrv) (3.1.5.0) + - *ci*: Try to fix Sonar workflow by switching to compile-commands for sonar [#4879](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4879) (by vvalderrv) (3.1.5.0) - *ci*: Fix analysis workflow configuration [#4881](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4881) (3.1.5.0) - *ci*: Better spread of libpng versions we test against [#4883](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4883) (3.1.5.0) - *ci*: Fix broken python wheel building [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) (3.1.5.0) - *ci*: Some more minor wheel workflow changes after the py 3.9 bump [#4867](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4867) (3.1.5.0) + - *ci*: More Sonar scan workflow fixes [#4902](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4902) (by vvalderrv) (3.1.6.0) + - *ci*: Add more exceptions to when we test docs building [#4899](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4899) (3.1.6.0) + - *ci*: Require all dependencies, with explicit exceptions [#4898](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4898) (3.1.6.0) ### 📚 Notable documentation changes: - *docs*: Clarify 'copy_image' example [#4522](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4522) (3.1.0.0/3.0.1.0) @@ -330,6 +316,7 @@ ABI compatibility, but we do expect some behavior changes. - *docs*: Online docs improvements, mostly formatting [#4736](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4736) (3.1.3.0) - *docs*: Update Windows build instructions to rely on deps auto-build [#4769](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4769) (3.1.3.0) - *docs*: Correct docs and type of "resident_memory_used_MB" attribute [#4824](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4824) (3.1.4.0) + - *docs/python*: Add type hints to Python docs and tests [#4908](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4908) (by Connie Chang) (3.1.6.0) ### 🏢 Project Administration - *admin*: Code review guidelines and tips [#4532](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4532) (3.1.0.0/3.0.1.0) @@ -341,11 +328,42 @@ ABI compatibility, but we do expect some behavior changes. - *admin*: Update SECURITY to reflect that 2.5 only gets critical fixes now [#4829](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4829) - *admin*: Adjust license notices of A2-only source [#4884](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4884) (3.1.5.0) +### :handshake: Contributors + +During the course of development of 3.1 (since splitting from the 3.0 branch), +OpenImageIO has had 40 unique contributors, of which 15 (indicated by an +asterisk) had not previously contributed to the project. + +| | | | +| ----------------------- | ------------------- | -------------------- | +| Aleksandr Motsjonov (*) | Alex Fuller (*) | Anthony Roberts (*) | +| Anton Dukhovnikov | Aras Pranckevičius | Basile Fraboni | +| Brecht Van Lommel | Campbell Barton (*) | Carine Touraille (*) | +| Chad Dombrova | Connie Chang (*) | Danielle Imogu (*) | +| Dharshan Vishwanatha | Don Olmstead (*) | Jesse Yurkovich | +| Joachim Reichel | Jonathan Brown | kaarrot | +| Larry Gritz | LI JI (*) | Loïc Vital | +| Lukas Stockner | Lydia Zheng | Mikael Sundell | +| Oktay Comu (*) | omcaif (*) | Peter Kovář | +| Peter Horvath | pfranz | Rui Chen (*) | +| Sam Richards | Scott Milner (*) | Scott Wilson | +| Thomas Klausner (*) | Todica Ionut | toge | +| Vanessa Valderrama (*) | Vernal Chen | Vlad (Kuzmin) Erium | +| Zach Lewis | | | --- --- +Release 3.0.11.0 (Oct 1, 2025) -- compared to 3.0.10.1 +------------------------------------------------------- + - *oiiotool*: Allow easy splitting output of subimages by name [#4874](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4874) + - *webp*: Support reading/writing the ICCProfile attribute for WepP files[#4878](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4878) (by Jesse Yurkovich) + - *gif*: GIF output didn't handle FramesPerSecond attribute correctly [#4890](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4890) + - *deps*: Test freetype 2.14 and document that it works [#4876](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4876) + - *deps*: Look for boost headers for OpenVDBs older than 12 [#4873](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4873) (by Alex Fuller) + + Release 3.0.10.1 (Sep 16, 2025) -- compared to 3.0.10.0 ------------------------------------------------------- - *ci*: Fix broken python wheel building [#4886](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4886) [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3c9d673dc..1e0cfbdf23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,37 +1,29 @@ Contributing to OpenImageIO =========================== -> NOTE: This is the proposed post-ASWF-move version of CONTRIBUTING. After the -> project is legally transferred and moved to the new repo, this file will -> replace the one at the project root. -> -> TO DO: -> -> - [X] Update the mail list sign-up page after the mail list moves. -> - [ ] Update the repo URL -> - [ ] Double check the security and info email addresses. -> - -Code contributions to OpenImageIO are always welcome, and [nearly 200 +Code contributions to OpenImageIO are always welcome, and [nearly 250 people](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/CREDITS.md) have done so over the years. Please review this document to get a briefing on our process. +General Tips for Open Source Development +---------------------------------------- + +* GitHub's [Open Source Guides](https://opensource.guide/) + - Especially the guide on [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) + + Mail List and Slack ------------------- -Contributors should be reading the oiio-dev mail list: - * [oiio-dev](https://lists.aswf.io/g/oiio-dev) For developers of the OpenImageIO code itself, or users who are really interested in the OIIO internals. This is where we mostly discuss the code (including bug reports), but are also happy to answer user questions about -use or working of OIIO. - -You can sign up for the mail list on your own using the link above. +use or working of OIIO. You can sign up for the mail list on your own using the link above. -The [ASWF Slack](https://slack.aswf.io/) has an `openimageio` channel. Sign up +* [ASWF Slack](https://slack.aswf.io/) has an `openimageio` channel. Sign up for the Slack on your own, then under "channels", select "browse channels" and you should see the openimageio channel (among those of the other projects and working groups). @@ -55,7 +47,7 @@ enhancements: https://github.com/AcademySoftwareFoundation/OpenImageIO/issues **If you are merely asking a question ("how do I...")**, please do not file an issue, but instead ask the question on the [oiio-dev mailing -list](https://lists.aswf.io/g/oiio-dev). +list](https://lists.aswf.io/g/oiio-dev) or on the Slack channel. If you are submitting a bug report, please be sure to note which version of OIIO you are using, on what platform (OS/version, which compiler you used, @@ -215,7 +207,7 @@ under `fix:`, because that appears first in the list). It is also encouraged, when it makes sense to do so, to put a subcategory in parenthesis after the prefix, like `fix(exr):` or `feat(IBA):`. It's ok to use obvious abbreviations for major classes or subsections: IB=ImageBuf, -IBA=ImageBufAlgo, IC=ImageCace, TS=TextureSystem, etc. If there is no clear +IBA=ImageBufAlgo, IC=ImageCache, TS=TextureSystem, etc. If there is no clear single format or class that is the man focus of the patch, then you can omit the subcategory. diff --git a/CREDITS.md b/CREDITS.md index 3629969eb3..6d01f85972 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -48,6 +48,7 @@ lg@openimageio.org * Brian Hall * Brice Gros * Campbell Barton +* Carine Touraille * Carl Rand * Cassian Andrei * Chad Dombrova @@ -61,6 +62,7 @@ lg@openimageio.org * Christoph Willing * Clément Champetier * Cliff Stein +* Connie Chang * Curtis Black * D-Spirits * Dalai Felinto @@ -180,6 +182,7 @@ lg@openimageio.org * Nuno Cardoso * Oktay Comu * Ole Gulbrandsen +* omcaif * Ott Tinn * Pascal Lecocq * Patrick Hodoul diff --git a/README.md b/README.md index 72c2644e7e..0b6728634b 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ security-related issues [security@openimageio.org](security@openimageio.org). 🔧 Contributing and developer documentation ------------------------------------------- -OpenImageIO welcomes code contributions, and [nearly 200 people](CREDITS.md) +OpenImageIO welcomes code contributions, and [nearly 250 people](CREDITS.md) have done so over the years. We take code contributions via the usual GitHub pull request (PR) mechanism. @@ -155,6 +155,11 @@ pull request (PR) mechanism. building the documentation locally, which may be helpful if you are editing the documentation in nontrivial ways and want to preview the appearance. * Other developer documentation is in the [docs/dev](docs/dev) directory. +* You may also have luck learning a bit about the organization and + architecture of the project by reading the [DeepWiki Analysis of + OpenImageIO](https://deepwiki.com/AcademySoftwareFoundation/OpenImageIO). + But take it with a grain of salt -- like any LLM-generated summary, there + may be inaccuracies lurking. ☎️ Communications channels and additional resources diff --git a/SECURITY.md b/SECURITY.md index 1fc108aa7a..60c1f1bcb3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,9 +9,8 @@ security vulnerabilities. | ----------------- | ---------------------------------------------------- | | main | :white_check_mark: :construction: ALL fixes immediately, but this is a branch under development with a frequently unstable ABI and occasionally unstable API. | | 3.1.x | :white_check_mark: All fixes that can be backported without breaking ABI compatibility. New tagged releases monthly. | -| 3.0.x | :white_check_mark: All fixes that can be backported without breaking ABI compatibility. New tagged releases monthly. | -| 2.5.x | :warning: Bug fixes backported only if critical or upon request (and if we are able to cleanly backport). New tagged releases only occasionally. | -| < 2.5.x | :x: No longer receiving patches of any kind. | +| 3.0.x | :warning: Important fixes that can be easily backported without breaking ABI compatibility. New tagged releases as needed, and becoming less frequent over time. | +| <= 2.5.x | :x: No longer receiving patches of any kind. | ## Reporting a Vulnerability diff --git a/docs/dev/RELEASING.md b/docs/dev/RELEASING.md index 3d2233a348..e0d88fded0 100644 --- a/docs/dev/RELEASING.md +++ b/docs/dev/RELEASING.md @@ -325,7 +325,7 @@ The following are the steps for making the release: > The API is now frozen -- we promise that subsequent 3.1.x releases > (which should happen monthly) will not break back-compatibility of API, > ABI, or linkage, compared to this release. Please note that this release - > is *not* ABI or link compatible with 2.5 or older releases. + > is *not* ABI or link compatible with 3.0 or older releases. > > Release notes for 3.1 outlining all the changes since last year's > release can be found at *LINK TO THE GITHUB RELEASE PAGE.* @@ -347,15 +347,15 @@ The following are the steps for making the release: > releases to the 3.1 family roughly monthly, which will contain bug fixes > and non-breaking enhancements. > - > The older 2.5 series of releases is now considered obsolete. We will - > continue for now to make 2.4 patch releases, but over time, these will + > The older 3.0 series of releases is now considered obsolete. We will + > continue for now to make 3.0 patch releases, but over time, these will > become less frequent and be reserved for only the most critical bug > fixes. > - > The "main" branch is now progressing toward an eventual 3.1 release next + > The "main" branch is now progressing toward an eventual 3.2 release next > fall. As usual, you are welcome to use main for real work, but we do > not make any compatibility guarantees and don't guarantee continuing API - > compatibility in main. + > compatibility within main. > > (Paste the full set of 3.1 changes here, just copy the appropriate > part of CHANGES.md) From 104bcd9b50e260c5a359a49e198877ab6b57b339 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 3 Oct 2025 20:50:09 -0700 Subject: [PATCH 028/508] fix(api): Restore definition of OIIO_NAMESPACE_USING macro (#4920) Version 3.1 removed this definition, thinking it was safe because we didn't use it anywhere, didn't document it, and what it expands to (`using namespace OIIO`) isn't any longer than the macro name, so is pointless to use. But downstream projects did use it. Oops. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/oiioversion.h.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/include/OpenImageIO/oiioversion.h.in b/src/include/OpenImageIO/oiioversion.h.in index 7307df5160..3580e1934b 100644 --- a/src/include/OpenImageIO/oiioversion.h.in +++ b/src/include/OpenImageIO/oiioversion.h.in @@ -144,6 +144,7 @@ namespace OIIO = @PROJ_NAMESPACE@; // Macros to declare things in the current version's inline namespace. #define OIIO_NAMESPACE_BEGIN namespace @PROJ_NAMESPACE@ { inline namespace @PROJ_VERSION_NAMESPACE@ { #define OIIO_NAMESPACE_END } } +#define OIIO_NAMESPACE_USING using namespace OIIO; #define OIIO_CURRENT_NAMESPACE @PROJ_NAMESPACE@::@PROJ_VERSION_NAMESPACE@ #include From b4c9bbffd3cd3c86ad3623f707359bfa57afe97a Mon Sep 17 00:00:00 2001 From: Valery Angelique <71804886+vangeliq@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:57:01 -0700 Subject: [PATCH 029/508] build(gif): Add GIF library auto-build (#4921) Fixes #4387 Added build_GIF.cmake module for GIF library dependency management. Since GIFLIB does not provide a CMakeLists.txt, I created a custom one that gets added to the cloned repository. The flow is similar to other libraries with cmake support, so I added a conditional statement in the build_dependency_with_cmake macro that, if a cmakelists.txt path is provided, will add/replace existing CMakeLists.txt files on the cloned repository. Signed-off-by: Valery Angelique <71804886+vangeliq@users.noreply.github.com> Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/build_GIF.cmake | 39 ++++ src/cmake/build_GIF_CMakeLists.txt | 280 +++++++++++++++++++++++++++++ src/cmake/dependency_utils.cmake | 16 +- 3 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 src/cmake/build_GIF.cmake create mode 100644 src/cmake/build_GIF_CMakeLists.txt diff --git a/src/cmake/build_GIF.cmake b/src/cmake/build_GIF.cmake new file mode 100644 index 0000000000..1afe91050c --- /dev/null +++ b/src/cmake/build_GIF.cmake @@ -0,0 +1,39 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +###################################################################### +# GIF/GIFLIB +# The original library does not have a CMake build system, so we +# provide our own CMakeLists.txt template to build it. +# See build_GIF_CMakeLists.txt for details. +###################################################################### + +set_cache (GIF_BUILD_VERSION "5.2.1" "GIFLIB version for local builds") +super_set (GIF_BUILD_GIT_REPOSITORY "https://git.code.sf.net/p/giflib/code") +super_set (GIF_BUILD_GIT_TAG "${GIF_BUILD_VERSION}") +set_cache (GIF_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} + DOC "Should execute a local GIFLIB build; if necessary, build shared libraries" ADVANCED) + +string (MAKE_C_IDENTIFIER ${GIF_BUILD_VERSION} GIF_VERSION_IDENT) + +set (GIF_CMAKELISTS_TEMPLATE_PATH "${CMAKE_CURRENT_LIST_DIR}/build_GIF_CMakeLists.txt") +build_dependency_with_cmake(GIF + VERSION ${GIF_BUILD_VERSION} + GIT_REPOSITORY ${GIF_BUILD_GIT_REPOSITORY} + GIT_TAG ${GIF_BUILD_GIT_TAG} + CMAKE_ARGS + -D BUILD_SHARED_LIBS=${GIF_BUILD_SHARED_LIBS} +) +unset(GIF_CMAKELISTS_TEMPLATE_PATH) + + +# Set some things up that we'll need for a subsequent find_package to work +set (GIF_ROOT ${GIF_LOCAL_INSTALL_DIR}) + +# Signal to caller that we need to find again at the installed location +find_package (GIF ${GIF_BUILD_VERSION} EXACT CONFIG REQUIRED) + +if (GIF_BUILD_SHARED_LIBS) + install_local_dependency_libs (GIF GIF) +endif () \ No newline at end of file diff --git a/src/cmake/build_GIF_CMakeLists.txt b/src/cmake/build_GIF_CMakeLists.txt new file mode 100644 index 0000000000..e9070afc72 --- /dev/null +++ b/src/cmake/build_GIF_CMakeLists.txt @@ -0,0 +1,280 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +###################################################################### +# CMakeLists.txt for GIF/GIFLIB + +# GIFLib repository doesn't have a cmakelists.txt included. +# So when we clone the repository on build_GIF.cmake, we also want to +# add this file as a "CMakeLists.txt" into the repository. This way +# we can run it the same way with other libraries, following the same +# logic as the build_with_cmake_macro + +# Windows compatibility: Source code includes unistd.h, which is not available +# on Windows systems. We patch source files to use appropriate Windows +# alternatives and use these patched files (in GIF-build) instead of +# the originals (GIF) +###################################################################### + +cmake_minimum_required (VERSION @CMAKE_MINIMUM_REQUIRED_VERSION@) +project(GIF VERSION @GIF_BUILD_VERSION@ LANGUAGES C) + +# Options +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) +option(BUILD_UTILS "Build utility programs" OFF) + + +# Set MSVC specific flags and runtime +if(MSVC) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") + + # Let runtime library be set by CMAKE_MSVC_RUNTIME_LIBRARY from command line + if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) + if(BUILD_SHARED_LIBS) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + else() + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() + endif() + + # Ensure proper DLL export/import + if(BUILD_SHARED_LIBS) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + endif() +endif() + +# replace #includes unistd to use windows equivalent +if(WIN32) + file(GLOB_RECURSE ORIGINAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/*.h" "${CMAKE_CURRENT_SOURCE_DIR}/*.c") + + foreach(HEADER ${ORIGINAL_HEADERS}) + # Compute output path in build dir + file(RELATIVE_PATH REL_PATH "${CMAKE_CURRENT_SOURCE_DIR}" "${HEADER}") + set(OUT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${REL_PATH}") + + # Make sure directory exists + get_filename_component(OUT_DIR ${OUT_HEADER} DIRECTORY) + file(MAKE_DIRECTORY ${OUT_DIR}) + + # Read header + file(READ ${HEADER} CONTENTS) + + # Add #ifdef guards to unistd.h + if(CONTENTS MATCHES "#include ") + string(REPLACE "#include " "#ifdef _WIN32\n#include \n#else\n#include \n#endif" CONTENTS "${CONTENTS}") + endif() + + # Write patched header + file(WRITE ${OUT_HEADER} "${CONTENTS}") + + # Add patched directory to include path + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + endforeach() +endif() + +# Set output directories +if(MSVC) + # For MSVC, we want Debug and Release in separate directories + foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/lib/${OUTPUTCONFIG}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/lib/${OUTPUTCONFIG}) + endforeach() +else() + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +endif() + +# Core library source files +if (WIN32) + # Use patched files from build directory + set(GIF_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/dgif_lib.c + ${CMAKE_CURRENT_BINARY_DIR}/egif_lib.c + ${CMAKE_CURRENT_BINARY_DIR}/gif_err.c + ${CMAKE_CURRENT_BINARY_DIR}/gif_hash.c + ${CMAKE_CURRENT_BINARY_DIR}/gifalloc.c + ${CMAKE_CURRENT_BINARY_DIR}/openbsd-reallocarray.c + ${CMAKE_CURRENT_BINARY_DIR}/quantize.c + ) +else() + # Use original files on non-Windows platforms +set(GIF_SOURCES + dgif_lib.c + egif_lib.c + gif_err.c + gif_hash.c + gifalloc.c + openbsd-reallocarray.c + quantize.c +) +endif() + + +# Define the GIF library +if(BUILD_SHARED_LIBS) + add_library(GIF SHARED ${GIF_SOURCES}) + + if (MSVC) + # Add export definitions for Windows DLL + target_compile_definitions(GIF + PRIVATE -DGIF_EXPORTS + PUBLIC -DGIF_DLL + ) + endif() +else() + add_library(GIF STATIC ${GIF_SOURCES}) +endif() + +# Create an alias target since we want to refer to it as GIF::GIF +add_library(GIF::GIF ALIAS GIF) + +# Set library properties +set_target_properties(GIF PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + OUTPUT_NAME "GIF" + # Ensure PIC just in case we need to link it into a shared library + POSITION_INDEPENDENT_CODE ON +) + +# Add include path for GIFLIB +if(WIN32) + target_include_directories(GIF + PUBLIC + $ + ) +else() + target_include_directories(GIF + PUBLIC + $ + ) +endif() + +# Utility programs common source files +if(WIN32) + # Use patched files from build directory + set(UTILS_COMMON_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/getarg.c + ${CMAKE_CURRENT_BINARY_DIR}/qprintf.c + ${CMAKE_CURRENT_BINARY_DIR}/gif_font.c + ) +else() + # Use original files on non-Windows platforms + set(UTILS_COMMON_SOURCES + getarg.c + qprintf.c + gif_font.c + ) +endif() + +# Define utility programs +set(UTILS + gif2rgb + gifbuild + gifbg + gifclrmp + gifcolor + gifecho + giffilter + giffix + gifhisto + gifinto + gifsponge + giftext + giftool #note: this requires getopt.c, which is not available on Windows + gifwedge +) + +# Build utilities if enabled +if(BUILD_UTILS) + foreach(UTIL ${UTILS}) + add_executable(${UTIL} ${UTIL}.c ${UTILS_COMMON_SOURCES}) + target_link_libraries(${UTIL} PRIVATE GIF m) + + # Set utility output properties + set_target_properties(${UTIL} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}" + PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + ) + endforeach() +endif() + +# Installation +include(GNUInstallDirs) + +install(TARGETS GIF + EXPORT GIFTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +# Install headers +if(WIN32) + # Install patched headers from build directory + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/gif_lib.h + ${CMAKE_CURRENT_BINARY_DIR}/gif_hash.h + ${CMAKE_CURRENT_BINARY_DIR}/gif_lib_private.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) +else() + # Install original headers on non-Windows platforms + install(FILES + gif_lib.h + gif_hash.h + gif_lib_private.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) +endif() + +# Install utilities +if(BUILD_UTILS) + install(TARGETS ${UTILS} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endif() + +# Export targets +install(EXPORT GIFTargets + FILE GIFTargets.cmake + NAMESPACE GIF:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/GIF +) + +# Create and install config files +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/GIFConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +# since we don't have a GIFConfig.cmake file in the source tree, we want to create +# one and install it +set(GIF_CONFIG_IN " +@PACKAGE_INIT@ +include(\${CMAKE_CURRENT_LIST_DIR}/GIFTargets.cmake) +check_required_components(GIF) +") +file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/GIFConfig.cmake.in" "${GIF_CONFIG_IN}") + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/GIFConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/GIFConfig.cmake" + INSTALL_DESTINATION lib/cmake/GIF +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/GIFConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/GIFConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/GIF +) \ No newline at end of file diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 3a7fe15fa3..e1c084bc3e 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -594,6 +594,10 @@ macro (build_dependency_with_cmake pkgname) ${ARGN}) message (STATUS "Building local ${pkgname} ${_pkg_VERSION} from ${_pkg_GIT_REPOSITORY}") + + if(DEFINED ${pkgname}_CMAKELISTS_TEMPLATE_PATH AND ${pkgname}_CMAKELISTS_TEMPLATE_PATH) + message (STATUS "cmakelist template provided on: ${${pkgname}_CMAKELISTS_TEMPLATE_PATH}") + endif() set (${pkgname}_LOCAL_SOURCE_DIR "${${PROJECT_NAME}_LOCAL_DEPS_ROOT}/${pkgname}") set (${pkgname}_LOCAL_BUILD_DIR "${${PROJECT_NAME}_LOCAL_DEPS_ROOT}/${pkgname}-build") @@ -651,6 +655,16 @@ macro (build_dependency_with_cmake pkgname) ) endif () + + # if a CMakeLists.txt path is specified, add it to the repository. This will replace existing ones + # this should be set before calling the macro + if(DEFINED ${pkgname}_CMAKELISTS_TEMPLATE_PATH AND NOT "${${pkgname}_CMAKELISTS_TEMPLATE_PATH}" STREQUAL "") + message(STATUS "Adding custom CMakeLists.txt for ${pkgname}") + configure_file("${${pkgname}_CMAKELISTS_TEMPLATE_PATH}" + "${${pkgname}_LOCAL_SOURCE_DIR}/${_pkg_SOURCE_SUBDIR}/CMakeLists.txt" + @ONLY) + endif() + # Make sure to inherit CMAKE_IGNORE_PATH set(_pkg_CMAKE_ARGS ${_pkg_CMAKE_ARGS} ${_pkg_CMAKE_ARGS}) if (CMAKE_IGNORE_PATH) @@ -734,4 +748,4 @@ macro (alias_library_if_not_exists newalias realtarget) if (NOT TARGET ${newalias} AND TARGET ${realtarget}) add_library(${newalias} ALIAS ${realtarget}) endif () -endmacro () +endmacro () \ No newline at end of file From 395c0ff62ac7048cc75999499d2a36d41b78ddb9 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 10 Oct 2025 18:51:18 -0700 Subject: [PATCH 030/508] ci: For python wheel generation, use ccache (#4924) Building the python wheels takes a long time! I really hate waiting for that when testing PRs, there must be a way to speed it up. * For the wheel workflow, add cache actions to save and restore the CCACHE_DIR. Hey, it's no easy feat to figure out what the path should be to that directory, especially on Linux where the wheels are built in a container, so the paths inside the container (where the wheel is built and the C++ compilation happens) don't match the paths outside the container (where the cache restore and save actions execute). * I had a heck of a time on Linux trying to get a pre-built ccache installed and had to resort to writing a bash script to build ccache itself from scratch, which is much more expensive than I'd like, but we'll have to come back to fix that separately. * Changed our "auto-build" utility build_dependency_with_cmake to print the amount of time it takes to build each dependency. * When auto-building, pass along CMAKE_CXX_COMPILER_LAUNCHER so that the dependenencies also for sure use ccache. * Use CMAKE_BUILD_PARALLEL_LEVEL on the wheel run to use all the cores and compile in parallel. (We did that on the regular CI but I think not for the wheel building.) * Fixes to the logic in our compiler.cmake where it tries to use ccache even if the magic CMAKE_CXX_COMPILER_LAUNCHER isn't set -- I have come to believe we were doing it wrong before, it was having no effect, and all along we only got ccache working on CI because we *also* set the env variable. * For CI, set CCACHE_COMPRESSION=1 to make the caches take less space against the precious limit of how much cache we can use total on GHA. So, the result of all this: **Previous times (typical), and first wheel run for any git branch** | platform | total | compile OIIO + deps | | ----------- | ----- | ------------------- | | Linux Intel | 10:35 | 500s | | Linux ARM | 7:20 | 294s | | Mac Intel | 20:18 | 1146s | | Mac ARM | 7:19 | 388s | | Windows | 14:00 | 759s | **With ccache active, 2nd or more wheel run for a git branch** | platform | total | compile OIIO + deps | | ----------- | ----- | ------------------- | | Linux Intel | 3:33 | 98s | | Linux ARM | 3:34 | 83s | | Mac Intel | 5:01 | 212s | | Mac ARM | 2:30 | 95s | | Windows | N/A | not using ccache | The "compile OIIO + deps" column is the isolated time to build OIIO and also any auto-building of dependencies from source that we do. But it does not include any other setup, such as 40-60s of container setup on Linux, 20-40s setting up Python on Mac, or -- ick! -- 45-60 seconds to build cmake itself from scratch. So this is considerably better, speeding up the full wheel workflow by 2-4x on all platforms but Windows. Remaining room for improvement (possibly in subsequent PRs, hopefully not necessarily by me): * Is there a way to use ccache on Windows? I'm not sure if there is, when using MSVS. * Find a way to install pre-built binaries on Linux rather than building ccache from scratch, which would save almost a whole minute per job. * Organize each platform to build all of its wheels (i.e. all of the python versions we are building for on that platform) in a single job rather than as completely independent jobs, allowing (a) the fixed per-job overhead such as container initialization, and installing python, and building of certain dependencies to happen ONCE per platform instead of separately for each wheel, and (b) the magic of ccache to speed up the builds *across* those wheels, since only a tiny amount of OIIO source codes depend on python at all. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/wheel.yml | 189 ++++++++++++++++++++++------ pyproject.toml | 3 + src/build-scripts/build_ccache.bash | 77 ++++++++++++ src/cmake/compiler.cmake | 39 ++++-- src/cmake/dependency_utils.cmake | 14 ++- 5 files changed, 269 insertions(+), 53 deletions(-) create mode 100755 src/build-scripts/build_ccache.bash diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 377f74d363..f464007721 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -39,6 +39,12 @@ on: workflow_dispatch: # This allows manual triggering of the workflow from the web +# Allow subsequent pushes to the same PR or REF to cancel any previous jobs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + + jobs: # Linux jobs run in Docker containers (manylinux), so the latest OS version # is OK. macOS and Windows jobs need to be locked to specific virtual @@ -141,6 +147,14 @@ jobs: with: python-version: '3.9' + - name: ccache-restore + id: ccache-restore + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.manylinux}}-${{matrix.python}} + restore-keys: wheel-${{runner.os}}-${{matrix.manylinux}}-${{matrix.python}} + - name: Build wheels # Note: the version of cibuildwheel should be kept in sync with src/python/stubs/CMakeLists.txt uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 @@ -148,13 +162,35 @@ jobs: # pass GITHUB_ACTIONS through to the build container so that custom # processes can tell they are running in CI. CIBW_ENVIRONMENT_PASS_LINUX: GITHUB_ACTIONS + CIBW_BEFORE_ALL: "source src/build-scripts/build_ccache.bash && pwd && ext/dist/bin/ccache --max-size=200M && ext/dist/bin/ccache -sv && export CMAKE_C_COMPILER_LAUNCHER=/project/ext/dist/bin/ccache CMAKE_CXX_COMPILER_LAUNCHER=/project/ext/dist/bin/ccache" + CIBW_BEFORE_TEST: "ext/dist/bin/ccache -s" CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} + CIBW_ENVIRONMENT: > + CCACHE_DIR=/host//home/runner/.ccache + CCACHE_COMPRESSION=yes + CCACHE_PREBUILT=0 + CMAKE_BUILD_PARALLEL_LEVEL=4 + CTEST_PARALLEL_LEVEL=4 + SKBUILD_CMAKE_ARGS="-DLINKSTATIC=1" + SKBUILD_CMAKE_BUILD_TYPE="MinSizeRel" + SKBUILD_BUILD_DIR=/project/build + CXXFLAGS="-Wno-error=stringop-overflow -Wno-pragmas" + WebP_BUILD_VERSION="1.5.0" + # FIXME: Getting build problems when using WebP 1.6.0, so hold it back + # CMAKE_GENERATOR = "Ninja" + + - name: ccache-save + id: ccache-save + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.manylinux}}-${{matrix.python}} - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} + name: cibw-wheels-${{matrix.manylinux}}-${{ matrix.python }}-${{ matrix.manylinux }} path: | ./wheelhouse/*.whl @@ -173,12 +209,12 @@ jobs: # --------------------------------------------------------------------------- linux-arm: - name: Build wheels on Linux ARM - runs-on: ubuntu-24.04-arm - if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' - strategy: + name: Build wheels on Linux ARM + runs-on: ubuntu-24.04-arm + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: matrix: include: # ------------------------------------------------------------------- @@ -205,37 +241,66 @@ jobs: python: cp313-manylinux_aarch64 arch: aarch64 - steps: - - name: Checkout repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + steps: + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Install Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 - with: - python-version: '3.9' - - - name: Build wheels - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 - env: - CIBW_BUILD: ${{ matrix.python }} - CIBW_ARCHS: ${{ matrix.arch }} - CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux }} - - - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - with: - name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} - path: | - ./wheelhouse/*.whl + - name: Install Python + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + with: + python-version: '3.9' - - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - with: - name: stubs-${{ matrix.python }}-${{ matrix.manylinux }} - path: | - ./wheelhouse/OpenImageIO/__init__.pyi - # if stub validation fails we want to upload the stubs for users to review. - # keep the python build in sync with the version specified in tool.cibuildwheel.overrides - # section of pyproject.toml - if: always() && contains(matrix.python, 'cp311-manylinux') + - name: ccache-restore + id: ccache-restore + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.python}} + restore-keys: wheel-${{runner.os}}-${{matrix.python}} + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_ENVIRONMENT_PASS_LINUX: GITHUB_ACTIONS + CIBW_BEFORE_ALL: "source src/build-scripts/build_ccache.bash && pwd && /project/ext/dist/bin/ccache --max-size=200M && /project/ext/dist/bin/ccache -sv && export CMAKE_C_COMPILER_LAUNCHER=/project/ext/dist/bin/ccache CMAKE_CXX_COMPILER_LAUNCHER=/project/ext/dist/bin/ccache" + CIBW_BEFORE_TEST: "ext/dist/bin/ccache -s" + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux }} + CIBW_ENVIRONMENT: > + CCACHE_DIR=/host//home/runner/.ccache + CCACHE_COMPRESSION=yes + CCACHE_PREBUILT=0 + CMAKE_BUILD_PARALLEL_LEVEL=6 + CTEST_PARALLEL_LEVEL=6 + SKBUILD_CMAKE_ARGS="-DLINKSTATIC=1" + SKBUILD_CMAKE_BUILD_TYPE="MinSizeRel" + SKBUILD_BUILD_DIR=/project/build + CXXFLAGS="-Wno-error=stringop-overflow -Wno-pragmas" + WebP_BUILD_VERSION="1.5.0" + + - name: ccache-save + id: ccache-save + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.python}} + + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} + path: | + ./wheelhouse/*.whl + + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: stubs-${{ matrix.python }}-${{ matrix.manylinux }} + path: | + ./wheelhouse/OpenImageIO/__init__.pyi + # if stub validation fails we want to upload the stubs for users to review. + # keep the python build in sync with the version specified in tool.cibuildwheel.overrides + # section of pyproject.toml + if: always() && contains(matrix.python, 'cp311-manylinux') # --------------------------------------------------------------------------- # macOS Wheels @@ -278,6 +343,18 @@ jobs: with: python-version: '3.9' + - name: ccache-restore + id: ccache-restore + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.python}} + restore-keys: wheel-${{runner.os}}-${{matrix.python}} + + - name: Install build tools + run: | + brew install ninja ccache || true + - name: Remove brew OpenEXR/Imath run: | brew uninstall --ignore-dependencies openexr imath || true @@ -292,6 +369,18 @@ jobs: # TODO: Re-enable HEIF when we provide a build recipe that does # not include GPL-licensed dynamic libraries. USE_Libheif: 'OFF' + CMAKE_BUILD_PARALLEL_LEVEL: 6 + CTEST_PARALLEL_LEVEL: 6 + SKBUILD_BUILD_DIR: "/Users/runner/work/OpenImageIO/OpenImageIO/build" + CCACHE_DIR: /Users/runner/.ccache + CCACHE_COMPRESSION: yes + + - name: ccache-save + id: ccache-save + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.python}} - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: @@ -340,13 +429,36 @@ jobs: with: python-version: '3.9' + - name: ccache-restore + id: ccache-restore + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.python}} + restore-keys: wheel-${{runner.os}}-${{matrix.python}} + - name: Install build tools + run: | + brew install ninja ccache || true + - name: Build wheels uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} CMAKE_GENERATOR: "Unix Makefiles" + CMAKE_BUILD_PARALLEL_LEVEL: 6 + CTEST_PARALLEL_LEVEL: 6 + SKBUILD_BUILD_DIR: "/Users/runner/work/OpenImageIO/OpenImageIO/build" + CCACHE_DIR: /Users/runner/.ccache + CCACHE_COMPRESSION: yes + + - name: ccache-save + id: ccache-save + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.ccache + key: wheel-${{runner.os}}-${{matrix.python}} - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: @@ -400,6 +512,11 @@ jobs: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} CMAKE_POLICY_VERSION_MINIMUM: 3.5 + CMAKE_BUILD_PARALLEL_LEVEL: 4 + CTEST_PARALLEL_LEVEL: 4 + SKBUILD_BUILD_DIR: "$HOME/OpenImageIO/OpenImageIO/build" + CCACHE_DIR: ~/.ccache + CCACHE_COMPRESSION: yes - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: diff --git a/pyproject.toml b/pyproject.toml index 2c10db63ef..164eb706d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,6 @@ +# See docs at https://cibuildwheel.pypa.io/en/stable/options +# for description of all the options in this file. + [project] name = "OpenImageIO" # The build backend ascertains the version from the CMakeLists.txt file. diff --git a/src/build-scripts/build_ccache.bash b/src/build-scripts/build_ccache.bash new file mode 100755 index 0000000000..0290c599bb --- /dev/null +++ b/src/build-scripts/build_ccache.bash @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# Utility script to download or build ccacheccache +# +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +# Exit the whole script if any command fails. +set -ex + +echo "Building ccache" +uname +ARCH=`uname -m` +echo "HOME=$HOME" +echo "PWD=$PWD" +echo "ARCH=$ARCH" + +CCACHE_PREBULT=${CCACHE_PREBULT:=1} + +# Repo and branch/tag/commit of ccache to download if we don't have it yet +CCACHE_REPO=${CCACHE_REPO:=https://github.com/ccache/ccache} +CCACHE_VERSION=${CCACHE_VERSION:=4.12} +CCACHE_TAG=${CCACHE_TAG:=v${CCACHE_VERSION}} +LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} +# Where to put ccache repo source (default to the ext area) +CCACHE_SRC_DIR=${CCACHE_SRC_DIR:=${LOCAL_DEPS_DIR}/ccache} +# Temp build area (default to a build/ subdir under source) +CCACHE_BUILD_DIR=${CCACHE_BUILD_DIR:=${CCACHE_SRC_DIR}/build} +# Install area for ccache (default to ext/dist) +CCACHE_INSTALL_DIR=${CCACHE_INSTALL_DIR:=${PWD}/ext/dist} +CCACHE_CONFIG_OPTS=${CCACHE_CONFIG_OPTS:=} + + +# if [[ `uname` == "Linux" && `uname -m` == "x86_64" ]] ; then +if [[ `uname` == "Linux" ]] ; then + mkdir -p ${CCACHE_SRC_DIR} + pushd ${CCACHE_SRC_DIR} + + if [[ "$CCACHE_PREBUILT" != "0" ]] ; then + # + # Try to download -- had trouble with this on runners + # + CCACHE_DESCRIPTOR="ccache-${CCACHE_VERSION}-linux-x86_64" + curl --location "${CCACHE_REPO}/releases/download/${CCACHE_TAG}/${CCACHE_DESCRIPTOR}.tar.xz" -o ccache.tar.xz + tar xJvf ccache.tar.xz + mkdir -p ${CCACHE_INSTALL_DIR}/bin + cp ${CCACHE_SRC_DIR}/${CCACHE_DESCRIPTOR}/ccache ${CCACHE_INSTALL_DIR}/bin + else + # Clone ccache project from GitHub and build + if [[ ! -e ${CCACHE_SRC_DIR}/.git ]] ; then + echo "git clone ${CCACHE_REPO} ${CCACHE_SRC_DIR}" + git clone ${CCACHE_REPO} ${CCACHE_SRC_DIR} + fi + + echo "git checkout ${CCACHE_TAG} --force" + git checkout ${CCACHE_TAG} --force + + if [[ -z $DEP_DOWNLOAD_ONLY ]]; then + time cmake -S . -B ${CCACHE_BUILD_DIR} -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${CCACHE_INSTALL_DIR} \ + -DENABLE_TESTING=OFF -DENABLE_DOCUMENTATION=OFF \ + ${CCACHE_CONFIG_OPTS} + time cmake --build ${CCACHE_BUILD_DIR} --config Release --target install + fi + fi + + popd + ls ${CCACHE_INSTALL_DIR} + ls ${CCACHE_INSTALL_DIR}/bin + echo "CCACHE_INSTALL_DIR=$CCACHE_INSTALL_DIR" + echo "CCACHE_DIR=$CCACHE_DIR" + mkdir -p $CCACHE_DIR + ls $CCACHE_DIR || true + export PATH=${CCACHE_INSTALL_DIR}/bin:$PATH + ${CCACHE_INSTALL_DIR}/bin/ccache -sv +fi diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index 6dfad31395..1dc5995bac 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -243,18 +243,31 @@ endif () # logic here makes it work even if the user is unaware of ccache. If it's # not found on the system, it will simply be silently not used. option (USE_CCACHE "Use ccache if found" ON) -find_program (CCACHE_EXE ccache) -if (CCACHE_EXE AND USE_CCACHE) - if (CMAKE_COMPILER_IS_CLANG AND USE_QT AND (NOT DEFINED ENV{CCACHE_CPP2})) - message (STATUS "Ignoring ccache because clang + Qt + env CCACHE_CPP2 is not set") - else () - if (NOT ${CXX_COMPILER_LAUNCHER} MATCHES "ccache") - set (CXX_COMPILER_LAUNCHER ${CCACHE_EXE} ${CXX_COMPILER_LAUNCHER}) - endif () - if (NOT ${C_COMPILER_LAUNCHER} MATCHES "ccache") - set (C_COMPILER_LAUNCHER ${CCACHE_EXE} ${C_COMPILER_LAUNCHER}) +if (USE_CCACHE) + find_program (CCACHE_EXE ccache + PATHS "${PROJECT_SOURCE_DIR}/ext/dist/" + "${PROJECT_SOURCE_DIR}/ext/dist/bin") + if (CCACHE_EXE) + if (CMAKE_COMPILER_IS_CLANG AND USE_QT AND (NOT DEFINED ENV{CCACHE_CPP2})) + message (STATUS "Ignoring ccache because clang + Qt + env CCACHE_CPP2 is not set") + else () + message (STATUS "CMAKE_CXX_COMPILER_LAUNCHER: ${CMAKE_CXX_COMPILER_LAUNCHER}") + + if (NOT CMAKE_CXX_COMPILER_LAUNCHER MATCHES "ccache") + set (CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_EXE}) + message (STATUS "first if CMAKE_CXX_COMPILER_LAUNCHER: ${CMAKE_CXX_COMPILER_LAUNCHER}") + else () + message (STATUS "first else CMAKE_CXX_COMPILER_LAUNCHER: '${CMAKE_CXX_COMPILER_LAUNCHER}'") + endif () + if (NOT CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache") + set (CMAKE_C_COMPILER_LAUNCHER ${CCACHE_EXE}) + endif () + message (STATUS "ccache enabled: ${CCACHE_EXE}") + message (STATUS "CCACHE_DIR env: $ENV{CCACHE_DIR}") + message (STATUS "CMAKE_CXX_COMPILER_LAUNCHER: ${CMAKE_CXX_COMPILER_LAUNCHER}") endif () - message (STATUS "ccache enabled: ${CCACHE_EXE}") + else () + message (STATUS "ccache not found") endif () endif () @@ -267,8 +280,8 @@ endif () # set `-j 1` or CMAKE_BUILD_PARALLEL_LEVEL to 1. option (TIME_COMMANDS "Time each compile and link command" OFF) if (TIME_COMMANDS) - set (CXX_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E time ${CXX_COMPILER_LAUNCHER}) - set (C_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E time ${C_COMPILER_LAUNCHER}) + set (CMAKE_CXX_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E time ${CMAKE_CXX_COMPILER_LAUNCHER}) + set (CMAKE_C_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E time ${CMAKE_C_COMPILER_LAUNCHER}) endif () diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index e1c084bc3e..a7bde64b72 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -110,7 +110,7 @@ function (print_package_notfound_report) list (REMOVE_DUPLICATES CFP_ALL_BUILD_DEPS_NOTFOUND) foreach (_pkg IN LISTS CFP_ALL_BUILD_DEPS_NOTFOUND) if (_pkg IN_LIST CFP_LOCALLY_BUILT_DEPS) - message (STATUS " ${_pkg} ${_${_pkg}_version_range} ${${_pkg}_NOT_FOUND_EXPLANATION} ${ColorMagenta}(${${_pkg}_VERSION} BUILT LOCALLY)${ColorReset}") + message (STATUS " ${_pkg} ${_${_pkg}_version_range} ${${_pkg}_NOT_FOUND_EXPLANATION} ${ColorMagenta}(${${_pkg}_VERSION} BUILT LOCALLY in ${${_pkg}_build_elapsed_time}s)${ColorReset}") else () message (STATUS " ${_pkg} ${_${_pkg}_version_range} ${${_pkg}_NOT_FOUND_EXPLANATION}") endif () @@ -594,9 +594,10 @@ macro (build_dependency_with_cmake pkgname) ${ARGN}) message (STATUS "Building local ${pkgname} ${_pkg_VERSION} from ${_pkg_GIT_REPOSITORY}") - + string (TIMESTAMP ${pkgname}_build_start_time "%s") + if(DEFINED ${pkgname}_CMAKELISTS_TEMPLATE_PATH AND ${pkgname}_CMAKELISTS_TEMPLATE_PATH) - message (STATUS "cmakelist template provided on: ${${pkgname}_CMAKELISTS_TEMPLATE_PATH}") + message (STATUS "cmakelist template provided on: ${${pkgname}_CMAKELISTS_TEMPLATE_PATH}") endif() set (${pkgname}_LOCAL_SOURCE_DIR "${${PROJECT_NAME}_LOCAL_DEPS_ROOT}/${pkgname}") @@ -685,6 +686,8 @@ macro (build_dependency_with_cmake pkgname) -DCMAKE_INSTALL_PREFIX=${${pkgname}_LOCAL_INSTALL_DIR} # Same build type as us -DCMAKE_BUILD_TYPE=${${PROJECT_NAME}_DEPENDENCY_BUILD_TYPE} + -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER} + -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} # Shhhh -DCMAKE_MESSAGE_INDENT=" " -DCMAKE_COMPILE_WARNING_AS_ERROR=OFF @@ -712,6 +715,9 @@ macro (build_dependency_with_cmake pkgname) set (${pkgname}_ROOT ${${pkgname}_LOCAL_INSTALL_DIR}) list (APPEND CMAKE_PREFIX_PATH ${${pkgname}_LOCAL_INSTALL_DIR}) endif () + string (TIMESTAMP ${pkgname}_build_end_time "%s") + math (EXPR ${pkgname}_build_elapsed_time "${${pkgname}_build_end_time} - ${${pkgname}_build_start_time}") + # message (STATUS "Build time of ${${pkgname}_build_elapsed_time}s") endmacro () @@ -748,4 +754,4 @@ macro (alias_library_if_not_exists newalias realtarget) if (NOT TARGET ${newalias} AND TARGET ${realtarget}) add_library(${newalias} ALIAS ${realtarget}) endif () -endmacro () \ No newline at end of file +endmacro () From 1b2c33ff32516da1c929e5d510e9d45354a9d5c3 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 11 Oct 2025 11:42:40 -0700 Subject: [PATCH 031/508] fix(win): Address Windows crashes from issue 4641 (#4914) Fixes #4641 Has something to do with mixing Windows compiler versions, there's a subtle ABI compatibility issue that this sidesteps. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/compiler.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index 1dc5995bac..d31d5229f9 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -204,6 +204,8 @@ if (MSVC) add_compile_definitions (_CRT_NONSTDC_NO_WARNINGS) add_compile_definitions (_SCL_SECURE_NO_WARNINGS) add_compile_definitions (JAS_WIN_MSVC_BUILD) + # https://github.com/AcademySoftwareFoundation/OpenImageIO/issues/4641#issuecomment-2725013661 + add_compile_definitions (_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) endif (MSVC) if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" From 9c9e0736bb82fe146a48a081614952be261d7ef8 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 12 Oct 2025 09:19:28 -0700 Subject: [PATCH 032/508] fix(api/docs): Fix IBA::set_pixels declaration and docs (#4926) Two places where the docs and header for ImageBuf::set_pixels did not match. In one case, the docs were right, the declaration was supposed to have a parameter that was a `const image_span&` but the `&` was left out. Now, if this was an actual linkable function, we wouldn't be able to fix it until a compatibility-breaking boundary. But because it's a template and inline, I believe it is actually safe to fix it in situ without breaking ABI compatibility (and it seems to pass our ABI check in CI). In the second case, the header was correct but the docs used the wrong declaration (leaving out a `const`) and made the docs fail to pull in the right explanation. Fixes #4923 Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/imagebuf.rst | 2 +- src/include/OpenImageIO/imagebuf.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/imagebuf.rst b/src/doc/imagebuf.rst index b0f975ac6b..dfbe6c3321 100644 --- a/src/doc/imagebuf.rst +++ b/src/doc/imagebuf.rst @@ -182,7 +182,7 @@ Getting and setting pixel values .. doxygenfunction:: OIIO::ImageBuf::get_pixels(ROI, const image_span&) const .. doxygenfunction:: OIIO::ImageBuf::get_pixels(ROI, TypeDesc, const image_span&) const .. doxygenfunction:: OIIO::ImageBuf::set_pixels(ROI, const image_span&) -.. doxygenfunction:: OIIO::ImageBuf::set_pixels(ROI, TypeDesc, const image_span&) +.. doxygenfunction:: OIIO::ImageBuf::set_pixels(ROI, TypeDesc, const image_span&) | diff --git a/src/include/OpenImageIO/imagebuf.h b/src/include/OpenImageIO/imagebuf.h index 1b9238419b..adc6709d48 100644 --- a/src/include/OpenImageIO/imagebuf.h +++ b/src/include/OpenImageIO/imagebuf.h @@ -1094,7 +1094,7 @@ class OIIO_API ImageBuf { /// Return true if the operation could be completed, /// otherwise return false. /// - template bool set_pixels(ROI roi, const image_span buffer) + template bool set_pixels(ROI roi, const image_span& buffer) { return set_pixels(roi, TypeDescFromC::value(), as_image_span_bytes(buffer)); From 9a419a0e3f887707aa763d6f707363be884f68a8 Mon Sep 17 00:00:00 2001 From: Danny Greenstein Date: Tue, 14 Oct 2025 17:19:28 -0700 Subject: [PATCH 033/508] deps: cmake script to auto download and install the openjpeg dependency (#4911) sub task of : https://github.com/AcademySoftwareFoundation/OpenImageIO/issues/4387 for sep 2025 dev days. Adding a autobuild cmake file for OpenJPEG. Includes some changes to recognize and use static OpenJPEG if available. --------- Signed-off-by: grdanny Signed-off-by: Larry Gritz Co-authored-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 1 - src/build-scripts/build_OpenJPEG.bash | 2 ++ src/cmake/build_OpenJPEG.cmake | 33 +++++++++++++++++++++++++++ src/cmake/externalpackages.cmake | 3 ++- src/cmake/modules/FindOpenJPEG.cmake | 9 ++++++++ src/cmake/set_utils.cmake | 10 ++++++++ src/jpeg2000.imageio/CMakeLists.txt | 17 +++++++++++--- 7 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 src/cmake/build_OpenJPEG.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e57351446c..a2eda36bd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -544,7 +544,6 @@ jobs: setenvs: export OpenImageIO_BUILD_LOCAL_DEPS=all OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON LIBRAW_VERSION=0.21.4 - OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 diff --git a/src/build-scripts/build_OpenJPEG.bash b/src/build-scripts/build_OpenJPEG.bash index f2bc23f70f..531e48da0a 100755 --- a/src/build-scripts/build_OpenJPEG.bash +++ b/src/build-scripts/build_OpenJPEG.bash @@ -45,6 +45,8 @@ if [[ -z $DEP_DOWNLOAD_ONLY ]]; then -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${OPENJPEG_INSTALL_DIR} \ -DBUILD_CODEC=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DBUILD_SHARED_LIBS=${OPENJPEG_BUILD_SHARED_LIBS:-ON} \ ${OPENJPEG_CONFIG_OPTS} time cmake --build ${OPENJPEG_BUILD_DIR} --config Release --target install fi diff --git a/src/cmake/build_OpenJPEG.cmake b/src/cmake/build_OpenJPEG.cmake new file mode 100644 index 0000000000..4371b79791 --- /dev/null +++ b/src/cmake/build_OpenJPEG.cmake @@ -0,0 +1,33 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/Academ SoftwareFoundation/OpenImageIO + +set_cache (OpenJPEG_BUILD_VERSION 2.5.4 "OpenJPEG version for local builds") +set (OpenJPEG_GIT_REPOSITORY "https://github.com/uclouvain/openjpeg.git") +set (OpenJPEG_GIT_TAG "v${OpenJPEG_BUILD_VERSION}") +set_cache (OpenJPEG_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} + DOC "Should a local OpenJPEG build, if necessary, build shared libraries" ADVANCED) + + +string (MAKE_C_IDENTIFIER ${OpenJPEG_BUILD_VERSION} OpenJPEG_VERSION_IDENT) + +build_dependency_with_cmake(OpenJPEG + VERSION ${OpenJPEG_BUILD_VERSION} + GIT_REPOSITORY ${OpenJPEG_GIT_REPOSITORY} + GIT_TAG ${OpenJPEG_GIT_TAG} + CMAKE_ARGS + -D BUILD_CODEC=OFF + -D CMAKE_POSITION_INDEPENDENT_CODE=ON + ) +# Set some things up that we'll need for a subsequent find_package to work +set (OpenJPEG_ROOT ${OpenJPEG_LOCAL_INSTALL_DIR}) + + +# Signal to caller that we need to find again at the installed location +set (OpenJPEG_REFIND TRUE) +set (OpenJPEG_REFIND_ARGS CONFIG) +set_invert (OpenJPEG_LINKSTATIC ${OpenJPEG_BUILD_SHARED_LIBS}) + +if (OpenJPEG_BUILD_SHARED_LIBS) + install_local_dependency_libs (OpenJPEG openjp2) +endif () diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index fc9cfc48dc..12467ae6b6 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -172,7 +172,8 @@ checked_find_package (LibRaw checked_find_package (OpenJPEG VERSION_MIN 2.0 RECOMMEND_MIN 2.2 - RECOMMEND_MIN_REASON "for multithreading support") + RECOMMEND_MIN_REASON "for multithreading support" + PREFER_CONFIG) # Note: Recent OpenJPEG versions have exported cmake configs, but we don't # find them reliable at all, so we stick to our FindOpenJPEG.cmake module. diff --git a/src/cmake/modules/FindOpenJPEG.cmake b/src/cmake/modules/FindOpenJPEG.cmake index 3c89609089..53cbebe901 100644 --- a/src/cmake/modules/FindOpenJPEG.cmake +++ b/src/cmake/modules/FindOpenJPEG.cmake @@ -174,6 +174,15 @@ if (OPENJPEG_FOUND) foreach (tmplib ${OpenJpeg_libvars}) list (APPEND OPENJPEG_LIBRARIES ${${tmplib}}) endforeach () + + if (NOT TARGET openjp2) + add_library(openjp2 UNKNOWN IMPORTED) + set_target_properties(openjp2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OPENJPEG_INCLUDES}") + set_property(TARGET openjp2 APPEND PROPERTY + IMPORTED_LOCATION "${OPENJPEG_LIBRARIES}") + endif() + if (NOT OpenJpeg_FIND_QUIETLY) FIND_PACKAGE_MESSAGE (OPENJPEG "Found OpenJpeg: v${OPENJPEG_VERSION} ${OPENJPEG_LIBRARIES}" diff --git a/src/cmake/set_utils.cmake b/src/cmake/set_utils.cmake index 982c243a6e..24b39dd65b 100644 --- a/src/cmake/set_utils.cmake +++ b/src/cmake/set_utils.cmake @@ -20,6 +20,16 @@ macro (set_replace_if_nonempty var replacement) endmacro () +# Set a variable to the inverse of whether `value` was true or false. +macro (set_invert var value) + if (${value}) + set (${var} FALSE ${ARGN}) + else () + set (${var} TRUE ${ARGN}) + endif () +endmacro () + + # Set a cmake variable `var` from an environment variable, if it is not # already defined (or if the FORCE flag is used). By default, the env var is diff --git a/src/jpeg2000.imageio/CMakeLists.txt b/src/jpeg2000.imageio/CMakeLists.txt index a989e48495..c8bdf53907 100644 --- a/src/jpeg2000.imageio/CMakeLists.txt +++ b/src/jpeg2000.imageio/CMakeLists.txt @@ -3,9 +3,19 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO if (OPENJPEG_FOUND) - set(_jpeg2000_includes ${OPENJPEG_INCLUDES}) - set(_jpeg2000_lib_dirs ${OPENJPEG_LIBRARY_DIRS}) - set(_jpeg2000_libs ${OPENJPEG_LIBRARIES}) + if (TARGET openjp2_static AND (NOT TARGET openjp2 OR + LINKSTATIC OR OpenJPEG_LINKSTATIC)) + # Use static OpenJPEG library for LINKSTATIC situations, or if + # no dynamic library is available. + set (OPENJPEG_TARGET openjp2_static) + elseif (TARGET openjp2) + set (OPENJPEG_TARGET openjp2) + else () + set(_jpeg2000_includes ${OPENJPEG_INCLUDES}) + set(_jpeg2000_lib_dirs ${OPENJPEG_LIBRARY_DIRS}) + set(_jpeg2000_libs ${OPENJPEG_LIBRARIES}) + endif () + message (VERBOSE "OPENJPEG_TARGET=${OPENJPEG_TARGET}") set(_jpeg2000_defs "USE_OPENJPEG") if (openjph_FOUND) @@ -16,6 +26,7 @@ if (OPENJPEG_FOUND) INCLUDE_DIRS ${_jpeg2000_includes} LINK_DIRECTORIES ${_jpeg2000_lib_dirs} LINK_LIBRARIES ${_jpeg2000_libs} + ${OPENJPEG_TARGET} $ DEFINITIONS ${_jpeg2000_defs} ) From ef5a6b703345d20bd00c7c5e3bb1a526b8a232e3 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 14 Oct 2025 23:07:12 -0700 Subject: [PATCH 034/508] build: allow auto-build of just requried packages (#4927) The CMake (or env) variable OpenImageIO_BUILD_MISSING_DEPS enumerates dependencies to try to locally auto-build if not found. If set to "all", it will build them all. This patch also allows you to set it to "required", which will try to auto-build any missing dependences that are required, but not bother with non-required missing packages. It also works to use required and also have list items naming additional optional dependencies, such as -DOpenImageIO_BUILD_MISSING_DEPS="required;WebP" build all required dependecies, and also WebP (which is optional), if they are no found already installed. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/dependency_utils.cmake | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index a7bde64b72..552c7f46a5 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -11,7 +11,7 @@ set_option (${PROJECT_NAME}_ALWAYS_PREFER_CONFIG "Prefer a dependency's exported config file if it's available" OFF) set_cache (${PROJECT_NAME}_BUILD_MISSING_DEPS "" - "Try to download and build any of these missing dependencies (or 'all')") + "Try to download and build any of these missing dependencies (or 'all' or 'required')") set_cache (${PROJECT_NAME}_BUILD_LOCAL_DEPS "" "Force local builds of these dependencies if possible (or 'all')") @@ -123,6 +123,8 @@ function (print_package_notfound_report) message (STATUS " ${_pkg}") endforeach () message (STATUS "${ColorBoldWhite}To build them automatically if not found, build again with option:${ColorReset}") + message (STATUS " ${ColorBoldGreen}-D${PROJECT_NAME}_BUILD_MISSING_DEPS=required${ColorReset}") + message (STATUS " or") message (STATUS " ${ColorBoldGreen}-D${PROJECT_NAME}_BUILD_MISSING_DEPS=all${ColorReset}") endif () message (STATUS) @@ -321,7 +323,9 @@ macro (checked_find_package pkgname) set (_pkg_BUILD_LOCAL "always") elseif ("${pkgname}" IN_LIST ${PROJECT_NAME}_BUILD_MISSING_DEPS OR ${PROJECT_NAME}_BUILD_MISSING_DEPS STREQUAL "ALL" - OR ${PROJECT_NAME}_BUILD_MISSING_DEPS STREQUAL "all") + OR ${PROJECT_NAME}_BUILD_MISSING_DEPS STREQUAL "all" + OR ("required" IN_LIST ${PROJECT_NAME}_BUILD_MISSING_DEPS + AND _pkg_REQUIRED)) set_if_not (_pkg_BUILD_LOCAL "missing") endif () set (${pkgname}_local_build_script "${PROJECT_SOURCE_DIR}/src/cmake/build_${pkgname}.cmake") @@ -611,7 +615,7 @@ macro (build_dependency_with_cmake pkgname) list (APPEND ${pkgname}_GIT_CLONE_ARGS --depth 1) endif () if (_pkg_QUIET OR "${_pkg_QUIET}" STREQUAL "") - list (APPEND ${pkgname}_GIT_CLONE_ARGS -q) + list (APPEND ${pkgname}_GIT_CLONE_ARGS -q ERROR_VARIABLE ${pkgname}_clone_errors) set (_pkg_exec_quiet OUTPUT_QUIET) endif () From 839f79091a54cfcc2b89f72398bb079dad8e9fa3 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 16 Oct 2025 22:31:34 -0700 Subject: [PATCH 035/508] ci: drop deprecated macos-13 (intel) platform, add macos-15-intel (#4930) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2eda36bd9..d5eb565440 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -670,13 +670,13 @@ jobs: fail-fast: false matrix: include: - - desc: MacOS-13-Intel aclang14/C++17/py3.11 - runner: macos-13 - nametag: macos13-py311 + - desc: MacOS-15-Intel aclang17/C++17/py3.13 + runner: macos-15-intel + nametag: MacOS-15-Intel cc_compiler: clang cxx_compiler: clang++ cxx_std: 17 - python_ver: "3.11" + python_ver: "3.13" simd: sse4.2,avx2 ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 From 8a78fb0cd54d90dd1c981923eda15079731ccbc7 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 18 Oct 2025 20:14:40 -0700 Subject: [PATCH 036/508] fix(iff): Handle non-zero origin, protect against buffer overflows (#4925) IffInput::read_native_tile was simply incorrect for images with nonzero data window origin. Fix! Also switch the forumulation to use spancpy rather than memcpy, to be a little more careful that we are't overwriting the presumed sizes of the buffers. And while I'm in there, I realized we can hold the mutex for less time. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/iff.imageio/iffinput.cpp | 40 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/iff.imageio/iffinput.cpp b/src/iff.imageio/iffinput.cpp index faffca25a2..e812d2b509 100644 --- a/src/iff.imageio/iffinput.cpp +++ b/src/iff.imageio/iffinput.cpp @@ -505,17 +505,29 @@ bool IffInput::read_native_tile(int subimage, int miplevel, int x, int y, int /*z*/, void* data) { - lock_guard lock(*this); if (!seek_subimage(subimage, miplevel)) return false; - if (m_buf.empty()) { - if (!readimg()) { - return false; + { + lock_guard lock(*this); + + if (m_buf.empty()) { + if (!readimg()) + return false; } } - // tile size + // Offset vs the image data origin + x -= m_spec.x; + y -= m_spec.y; + + if (x < 0 || x >= m_spec.width || y < 0 || y >= m_spec.height) { + errorfmt("Tile coordinate is not within the valid pixel data window"); + return false; + } + + // tile size that we're reading -- consider if the tile overlaps the image + // boundary. int w = m_header.width; int tw = std::min(x + static_cast(m_header.tile_width), static_cast(m_header.width)) @@ -525,16 +537,14 @@ IffInput::read_native_tile(int subimage, int miplevel, int x, int y, int /*z*/, - y; // tile data - int oy = 0; - for (int iy = y; iy < y + th; iy++) { - // in - uint8_t* in_p = m_buf.data() + (iy * w + x) * m_header.pixel_bytes(); - // out - uint8_t* out_p = reinterpret_cast(data) - + (oy * m_header.tile_width) * m_header.pixel_bytes(); - // copy - memcpy(out_p, in_p, tw * m_header.pixel_bytes()); - oy++; + span dataspan(static_cast(data), + m_header.tile_width * m_header.tile_height + * m_header.pixel_bytes()); + cspan bufspan(m_buf); + for (int oy = 0, iy = y; oy < th; ++oy, ++iy) { + spancpy(dataspan, (oy * m_header.tile_width) * m_header.pixel_bytes(), + bufspan, (iy * w + x) * m_header.pixel_bytes(), + tw * m_header.pixel_bytes()); } return true; } From 8d221df5696b9948fb52c17903dc414d86908a99 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 18 Oct 2025 20:15:05 -0700 Subject: [PATCH 037/508] feat(openexr): Support for idManifest and deepImageState (#4877) Support for retrieving and setting the idManifest for deep image files with object ID channels. This is an OpenEXR feature where the image header can contian metadata that maps all the object ID channel integer codes to strings of the object names. We report this in the ImageSpec as the metadata "openexr:compressedIDManifest", whose type is an array of uint8 (byte) values. It is encoded the same way it appears in the exr file itself: the first 8 bytes are a little endian uint64_t giving the *uncompressed* size of a serialized OpenEXR IDManifest object, and then starting at byte 8, the zip-compressed seriailized IDManifest. Therefore, the compressed block size is the size of the metadata blob, minus 8 bytes for the length. It is up to the caller to use the OpenEXR APIs to turn this compressed serialized IDManifest into a fully expanded IDManifest, if that's what they want to do. It works for output, too, so it should copy the manifests correctly between files, even if the app doesn't know what to do with the binary data it contains. Also added support for deepImageState, which we previously had ignored. We report this as the metadata "openexr:deepImageState", a string, which if present has one of the values "messy", "sorted", "non_overlapping", or "tidy". Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/testing.cmake | 4 ++ src/doc/builtinplugins.rst | 13 +++++ src/openexr.imageio/exrinput.cpp | 40 ++++++++++++++ src/openexr.imageio/exrinput_c.cpp | 37 ++++++++++++- src/openexr.imageio/exroutput.cpp | 51 ++++++++++++++---- testsuite/openexr-idmanifest/ref/out.txt | 28 ++++++++++ testsuite/openexr-idmanifest/run.py | 13 +++++ testsuite/openexr-idmanifest/src/manifest.exr | Bin 0 -> 21081 bytes 8 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 testsuite/openexr-idmanifest/ref/out.txt create mode 100755 testsuite/openexr-idmanifest/run.py create mode 100644 testsuite/openexr-idmanifest/src/manifest.exr diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 58670a5886..b523429753 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -297,6 +297,10 @@ macro (oiio_add_all_tests) # properly supported all compression types (DWA in particular). list (APPEND all_openexr_tests openexr-compression) endif () + if (OpenEXR_VERSION VERSION_GREATER_EQUAL 3.3) + # OpenEXR 3.3 is when IDManifest was introduced + list (APPEND all_openexr_tests openexr-idmanifest) + endif () # Run all OpenEXR tests without core library oiio_add_tests (${all_openexr_tests} openexr-luminance-chroma ENVIRONMENT OPENIMAGEIO_OPTIONS=openexr:core=0 diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 3d600591be..6658a4dcac 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1599,6 +1599,19 @@ The official OpenEXR site is http://www.openexr.com/. - If nonzero, indicates whether the image is a luminance-chroma image. Upon reading, the subsampled Y/BY/RY(/A) channels of luminance-chroma images are automatically converted to RGB(A) channels. + * - ``openexr::deepImageState`` + - string + - If present in a deep file, reveals the deep image state, one of: + ``"messy"``, ``"sorted"``, ``"non_overlapping"``, or ``"tidy"``. + See the OpenEXR documentation for explanations. This metadata was + added in OpenImageIO 3.1. + * - ``openexr::compressedIDManifest`` + - uint8[] + - A byte array whose first 8 bytes are the uncompressed size of the + manifest, as a little-endian uint64. Then beginning at byte 8, + the remainder is the zip-compressed serialized manifest. + This metadata was added in OpenImageIO 3.1, and is only supported when + OIIO is built against OpenEXR 3.1 or newer. * - *other* - - All other attributes will be added to the ImageSpec by their name and diff --git a/src/openexr.imageio/exrinput.cpp b/src/openexr.imageio/exrinput.cpp index 09913c0319..4166642cb9 100644 --- a/src/openexr.imageio/exrinput.cpp +++ b/src/openexr.imageio/exrinput.cpp @@ -36,6 +36,7 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter") #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter") #include #include #include +#include #include #include #include @@ -642,6 +644,44 @@ OpenEXRInput::PartInfo::parse_header(OpenEXRInput* in, default: break; } spec.attribute("openexr:lineOrder", lineOrder); + } else if (type == "deepImageState") { + auto attr = header->findTypedAttribute( + name); + if (attr) { + const char* val = "messy"; + switch (attr->value()) { + case Imf::DIS_MESSY: val = "messy"; break; + case Imf::DIS_SORTED: val = "sorted"; break; + case Imf::DIS_NON_OVERLAPPING: val = "non_overlapping"; break; + case Imf::DIS_TIDY: val = "tidy"; break; + default: break; + } + spec.attribute("openexr:deepImageState", val); + } + } else if (type == "idmanifest") { + auto attr = header->findTypedAttribute( + name); + if (attr) { + // print("CompressedIDManifest size {}\n", + // attr->value()._compressedDataSize); + size_t csize(attr->value()._compressedDataSize); + // NOTE: The blob of bytes we're making consists of: + // Bytes 0-7: little endian uint64 giving the *uncompressed* + // size that will be needed for the serialized IDM. + // Bytes 8-(csize-1): the zip-compressed serialized IDManifest. + size_t blobsize = csize + 8; + std::unique_ptr blob(new std::byte[blobsize]); + uint64_t usize = attr->value()._uncompressedDataSize; + if constexpr (bigendian()) + usize = byteswap(usize); + memcpy(blob.get(), &usize, sizeof(usize)); + memcpy(blob.get() + 8, attr->value()._data, csize); + spec.attribute("openexr:compressedIDManifest", + TypeDesc(TypeDesc::UINT8, blobsize), blob.get()); + } else { + // print("idManifest found but not retrieved?\n"); + } + } else { #if 0 print(std::cerr, " unknown attribute '{}' name '{}'\n", diff --git a/src/openexr.imageio/exrinput_c.cpp b/src/openexr.imageio/exrinput_c.cpp index 44609173bf..2500dedd8d 100644 --- a/src/openexr.imageio/exrinput_c.cpp +++ b/src/openexr.imageio/exrinput_c.cpp @@ -270,6 +270,7 @@ static std::map cexr_tag_to_oiio_std { { "chunkCount", "openexr:chunkCount" }, { "maxSamplesPerPixel", "openexr:maxSamplesPerPixel" }, { "dwaCompressionLevel", "openexr:dwaCompressionLevel" }, + { "idManifest", "openexr:compressedIDManifest" }, // Ones to skip because we handle specially or consider them irrelevant { "channels", "" }, { "compression", "" }, @@ -742,8 +743,42 @@ OpenEXRCoreInput::PartInfo::parse_header(OpenEXRCoreInput* in, break; } +#if OPENEXR_CODED_VERSION >= 30300 + case EXR_ATTR_DEEP_IMAGE_STATE: { + const char* val = "messy"; + switch (attr->uc) { + case EXR_DIS_MESSY: val = "messy"; break; + case EXR_DIS_SORTED: val = "sorted"; break; + case EXR_DIS_NON_OVERLAPPING: val = "non_overlapping"; break; + case EXR_DIS_TIDY: val = "tidy"; break; + default: break; + } + spec.attribute("openexr:deepImageState", val); + break; + } +#endif + +#if OPENEXR_CODED_VERSION >= 30400 + case EXR_ATTR_BYTES: { + spec.attribute(oname, TypeDesc(TypeDesc::UINT8, attr->bytes->size), + make_span(attr->bytes->data, attr->bytes->size)); + break; + } +#endif + + case EXR_ATTR_OPAQUE: { + if (Strutil::iequals(oname, "idManifest")) + oname = "openexr:compressedIDManifeset"; // our name for this + spec.attribute(oname, TypeDesc(TypeDesc::UINT8, attr->opaque->size), + attr->opaque->packed_data); + // NOTE: The blob of bytes we're making consists of: + // Bytes 0-7: little endian uint64 giving the *uncompressed* + // size that will be needed for the serialized IDM. + // Bytes 8-(size-1): the zip-compressed serialized IDManifest. + break; + } + case EXR_ATTR_PREVIEW: - case EXR_ATTR_OPAQUE: case EXR_ATTR_ENVMAP: case EXR_ATTR_COMPRESSION: case EXR_ATTR_CHLIST: diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index 3e7e967f45..92f1f0ed3d 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -33,10 +33,12 @@ OIIO_GCC_PRAGMA(GCC diagnostic ignored "-Wunused-parameter") #include // JUST to get symbols to figure out version! #include #include +#include #include #include #include #include +#include #include #include #include @@ -937,7 +939,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, // Special cases if (Strutil::iequals(xname, "Compression") && type == TypeString) { - const char* str = *(char**)data; + const char* str = *(const char**)data; header.compression() = Imf::ZIP_COMPRESSION; // Default if (str) { if (Strutil::iequals(str, "none")) @@ -974,7 +976,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, } if (Strutil::iequals(xname, "openexr:lineOrder") && type == TypeString) { - const char* str = *(char**)data; + const char* str = *(const char**)data; header.lineOrder() = Imf::INCREASING_Y; // Default if (str) { if (Strutil::iequals(str, "randomY") @@ -987,6 +989,36 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, return true; } + if (Strutil::iequals(xname, "openexr:deepImageState") + && type == TypeString) { + const char* str = *(const char**)data; + Imf::DeepImageState val = Imf::DeepImageState::DIS_MESSY; + if (!strcmp(str, "sorted")) + val = Imf::DeepImageState::DIS_SORTED; + else if (!strcmp(str, "non_overlapping")) + val = Imf::DeepImageState::DIS_NON_OVERLAPPING; + else if (!strcmp(str, "tidy")) + val = Imf::DeepImageState::DIS_TIDY; + header.insert(xname.c_str(), Imf::DeepImageStateAttribute(val)); + return true; + } + + if (Strutil::iequals(xname, "openexr:compressedIDManifest") + && type.basetype == TypeDesc::UINT8 && type.arraylen > 8) { + const unsigned char* bdata = (const unsigned char*)data; + uint64_t usize = 0; + memcpy(&usize, bdata, sizeof(usize)); + if constexpr (bigendian()) + usize = byteswap(usize); + Imf::CompressedIDManifest idm; + idm._compressedDataSize = static_cast(type.size() - 8); + idm._uncompressedDataSize = usize; + idm._data = const_cast(bdata + 8); + header.insert("idManifest", Imf::IDManifestAttribute(idm)); + idm._data = nullptr; + return true; + } + // Special handling of any remaining "oiio:*" metadata. if (Strutil::istarts_with(xname, "oiio:")) { if (Strutil::iequals(xname, "oiio:ConstantColor") @@ -1052,27 +1084,28 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, if (type.aggregate == TypeDesc::SCALAR) { if (type == TypeDesc::INT || type == TypeDesc::UINT) { header.insert(xname.c_str(), - Imf::IntAttribute(*(int*)data)); + Imf::IntAttribute(*(const int*)data)); return true; } if (type == TypeDesc::INT16) { header.insert(xname.c_str(), - Imf::IntAttribute(*(short*)data)); + Imf::IntAttribute(*(const short*)data)); return true; } if (type == TypeDesc::UINT16) { header.insert(xname.c_str(), - Imf::IntAttribute(*(unsigned short*)data)); + Imf::IntAttribute( + *(const unsigned short*)data)); return true; } if (type == TypeDesc::FLOAT) { header.insert(xname.c_str(), - Imf::FloatAttribute(*(float*)data)); + Imf::FloatAttribute(*(const float*)data)); return true; } if (type == TypeDesc::HALF) { - header.insert(xname.c_str(), - Imf::FloatAttribute((float)*(half*)data)); + header.insert(xname.c_str(), Imf::FloatAttribute((float)*( + const half*)data)); return true; } if (type == TypeString && !((const ustring*)data)->empty()) { @@ -1083,7 +1116,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, } if (type == TypeDesc::DOUBLE) { header.insert(xname.c_str(), - Imf::DoubleAttribute(*(double*)data)); + Imf::DoubleAttribute(*(const double*)data)); return true; } } diff --git a/testsuite/openexr-idmanifest/ref/out.txt b/testsuite/openexr-idmanifest/ref/out.txt new file mode 100644 index 0000000000..18e2415bba --- /dev/null +++ b/testsuite/openexr-idmanifest/ref/out.txt @@ -0,0 +1,28 @@ +Reading src/manifest.exr +src/manifest.exr : 16 x 16, 8 channel, deep half/half/half/half/half/uint/uint/uint openexr + SHA-1: 526B00A9E52F6EF667E02168CF20772957ACA451 + channel list: R (half), G (half), B (half), A (half), Z (half), materialid (uint), modelid (uint), particleid (uint) + compression: "zips" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + version: 1 + oiio:subimages: 1 + openexr:chunkCount: 16 + openexr:compressedIDManifest: 55, 1, 0, 0, 0, 0, 0, 0, 120, 94, 99, 0, 2, 110, 32, 230, ... [250 x uint8] + openexr:deepImageState: "tidy" + openexr:lineOrder: "increasingY" +Reading manifest.exr +manifest.exr : 16 x 16, 8 channel, deep half/half/half/half/half/uint/uint/uint openexr + SHA-1: 526B00A9E52F6EF667E02168CF20772957ACA451 + channel list: R (half), G (half), B (half), A (half), Z (half), materialid (uint), modelid (uint), particleid (uint) + compression: "zips" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + version: 1 + oiio:subimages: 1 + openexr:chunkCount: 16 + openexr:compressedIDManifest: 55, 1, 0, 0, 0, 0, 0, 0, 120, 94, 99, 0, 2, 110, 32, 230, ... [250 x uint8] + openexr:deepImageState: "tidy" + openexr:lineOrder: "increasingY" diff --git a/testsuite/openexr-idmanifest/run.py b/testsuite/openexr-idmanifest/run.py new file mode 100755 index 0000000000..3ec6b75e70 --- /dev/null +++ b/testsuite/openexr-idmanifest/run.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + + +# Multi-part, not deep +command += info_command ("src/manifest.exr", safematch = True) +command += oiiotool ("src/manifest.exr -o manifest.exr") +command += info_command ("./manifest.exr", safematch = True) + +outputs += [ "out.txt" ] diff --git a/testsuite/openexr-idmanifest/src/manifest.exr b/testsuite/openexr-idmanifest/src/manifest.exr new file mode 100644 index 0000000000000000000000000000000000000000..650d52f3776560c2751480fa0248d6b4b4603768 GIT binary patch literal 21081 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 SaJHRP88bw Date: Sat, 18 Oct 2025 20:22:33 -0700 Subject: [PATCH 038/508] ci: Try to avoid ffmpeg install failures (#4936) A small portion of time -- maybe 10%, just enough for many CI workflow runs to have a single job fail -- our attempt to `dnf install ffmpeg` fails. I thought maybe it was an intermittent, transient failure. But looping to try multiple times with delays in between does not help. A job that fails once either succeeds initially or fails over and over. I'm now leaning toward the belief that rather than a transient failures, there are just a subset of GHA runners that will always fail, and the semi-random nature of our job failures is just random luck of the draw in runner selection. Anyway, so I'm not going to merge the try-in-a-loop approach. Instead, this PR just makes ffmpeg an optional dependency, so the failures to get the dependency won't fail the OIIO build and therefore the CI. It seems like most of the time we really do get the dependency, but I'm out of better ideas at the moment. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5eb565440..c9e5d4e361 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -277,7 +277,7 @@ jobs: # Override required_deps to be 'all' and explicitly list as optional # only the ones we are intentionally not testing for those jobs. required_deps: ${{ matrix.required_deps || 'all' }} - optional_deps: ${{ matrix.optional_deps || 'DCMTK;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} + optional_deps: ${{ matrix.optional_deps || 'DCMTK;FFmpeg;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} strategy: fail-fast: false matrix: From c68725e8b9865180284fdc8d94ff9b064a47efe9 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 19 Oct 2025 05:23:35 +0200 Subject: [PATCH 039/508] fix(ffmpeg): 10 bit video has wrong green channel (#4935) Due to apparent limitations or bugs in swscale, there is a problem reading the 16 bit green channel that is not 4 byte aligned like red and blue. Reading each channel into its own plane solves the issue. This also addresses an existing comment about 32 bit float being read as 16 bit. This needed the same mechanism of reading separate planes, so the same code is reused to implement that. Resolves #4901 I added a 10 bit test file encoded with VP9 and Matroska, same as the existing 8 bit test file to maximize compatibility. I verified this now converts correctly to a HDR PNG file. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/ffmpeg.imageio/ffmpeginput.cpp | 97 ++++++++++++++++++------ testsuite/ffmpeg/ref/out-ffmpeg6.1.txt | 23 ++++++ testsuite/ffmpeg/ref/out-ffmpeg8.0.txt | 23 ++++++ testsuite/ffmpeg/ref/vp9_rec2100_pq.mkv | Bin 0 -> 5803 bytes testsuite/ffmpeg/run.py | 2 +- 5 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 testsuite/ffmpeg/ref/vp9_rec2100_pq.mkv diff --git a/src/ffmpeg.imageio/ffmpeginput.cpp b/src/ffmpeg.imageio/ffmpeginput.cpp index 257dace628..8eec06b08f 100644 --- a/src/ffmpeg.imageio/ffmpeginput.cpp +++ b/src/ffmpeg.imageio/ffmpeginput.cpp @@ -398,8 +398,10 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) case AV_PIX_FMT_GBRAP12LE: case AV_PIX_FMT_P016LE: case AV_PIX_FMT_P016BE: - datatype = TypeUInt16; - m_dst_pix_format = AV_PIX_FMT_RGB48; + datatype = TypeUInt16; + // Must use planar format because swscale does not handle + // interleaved correctly for 16 bit. + m_dst_pix_format = AV_PIX_FMT_GBRP16; break; // Grayscale 8 bit case AV_PIX_FMT_GRAY8: @@ -456,29 +458,27 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) case AV_PIX_FMT_YUVA444P16BE: case AV_PIX_FMT_YUVA444P16LE: case AV_PIX_FMT_GBRAP16: - nchannels = 4; - datatype = TypeUInt16; - m_dst_pix_format = AV_PIX_FMT_RGBA64; + nchannels = 4; + datatype = TypeUInt16; + // Must use planar format because swscale does not handle + // interleaved correctly for 16 bit. + m_dst_pix_format = AV_PIX_FMT_GBRAP16; break; // RGB float case AV_PIX_FMT_GBRPF32BE: case AV_PIX_FMT_GBRPF32LE: - nchannels = 3; - datatype = TypeFloat; - m_dst_pix_format = AV_PIX_FMT_RGB48; // ? AV_PIX_FMT_GBRPF32 - // FIXME: They don't have a type for RGB float, only GBR float. - // Yuck. Punt for now and save as uint16 RGB. If people care, we - // can return and ask for GBR float and swap order. + nchannels = 3; + datatype = TypeFloat; + // Must use planar format as there is no interleaved 32 bit float. + m_dst_pix_format = AV_PIX_FMT_GBRPF32; break; // RGBA float case AV_PIX_FMT_GBRAPF32BE: case AV_PIX_FMT_GBRAPF32LE: - nchannels = 4; - datatype = TypeFloat; - m_dst_pix_format = AV_PIX_FMT_RGBA64; // ? AV_PIX_FMT_GBRAPF32 - // FIXME: They don't have a type for RGBA float, only GBRA float. - // Yuck. Punt for now and save as uint16 RGB. If people care, we - // can return and ask for GBRA float and swap order. + nchannels = 4; + datatype = TypeFloat; + // Must use planar format as there is no interleaved 32 bit float. + m_dst_pix_format = AV_PIX_FMT_GBRAPF32; break; // Everything else is regular 8 bit RGB @@ -571,7 +571,31 @@ FFmpegInput::seek_subimage(int subimage, int miplevel) return true; } +template +static bool +read_planar_scanline(void* data, int y, int width, AVFrame* rgb_frame) +{ + static_assert(nchannels == 3 || nchannels == 4); + + const T* in_planes[nchannels]; + const int swizzle[4] = { 1, 2, 0, 3 }; // GBR to RGB + for (int channel = 0; channel < nchannels; ++channel) { + if (rgb_frame->data[channel] == nullptr) { + return false; + } + in_planes[swizzle[channel]] = reinterpret_cast( + rgb_frame->data[channel] + y * rgb_frame->linesize[channel]); + } + T* out = static_cast(data); + for (int x = 0; x < width; ++x) { + for (int channel = 0; channel < nchannels; ++channel) { + out[channel] = in_planes[channel][x]; + } + out += nchannels; + } + return true; +} bool FFmpegInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, @@ -583,14 +607,39 @@ FFmpegInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, if (!m_read_frame) { read_frame(m_subimage); } - if (m_rgb_frame->data[0]) { - memcpy(data, m_rgb_frame->data[0] + y * m_rgb_frame->linesize[0], - m_stride); - return true; - } else { - errorfmt("Error reading frame"); - return false; + if (m_spec.format == TypeUInt8 || m_spec.nchannels == 1) { + if (m_rgb_frame->data[0]) { + memcpy(data, m_rgb_frame->data[0] + y * m_rgb_frame->linesize[0], + m_stride); + return true; + } + } else if (m_spec.format == TypeUInt16) { + if (m_spec.nchannels == 3) { + if (read_planar_scanline(data, y, m_spec.width, + m_rgb_frame)) { + return true; + } + } else if (m_spec.nchannels == 4) { + if (read_planar_scanline(data, y, m_spec.width, + m_rgb_frame)) { + return true; + } + } + } else if (m_spec.format == TypeFloat) { + if (m_spec.nchannels == 3) { + if (read_planar_scanline(data, y, m_spec.width, + m_rgb_frame)) { + return true; + } + } else if (m_spec.nchannels == 4) { + if (read_planar_scanline(data, y, m_spec.width, + m_rgb_frame)) { + return true; + } + } } + errorfmt("Error reading frame"); + return false; } diff --git a/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt b/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt index 1553a20790..0d037e3cc5 100644 --- a/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt +++ b/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt @@ -85,3 +85,26 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 +Reading ref/vp9_rec2100_pq.mkv +ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie + 2 subimages: 384x216 [u10,u10,u10] + subimage 0: 384 x 216, 3 channel, uint10 FFmpeg movie + SHA-1: 87BB7BBE57131F08F0336964256F2C190FE26A69 + channel list: R, G, B + CICP: 9, 16, 9, 1 + ENCODER: "Lavf62.3.100" + FramesPerSecond: 24/1 (24) + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 10 + oiio:Movie: 1 + oiio:subimages: 2 + subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie + SHA-1: 3820BE456CC3F4ADD3425E0C9397C83678A5BA8A + channel list: R, G, B + CICP: 9, 16, 9, 1 + ENCODER: "Lavf62.3.100" + FramesPerSecond: 24/1 (24) + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 10 + oiio:Movie: 1 + oiio:subimages: 2 diff --git a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt index 082a0797f2..a4b503edca 100644 --- a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt +++ b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt @@ -85,3 +85,26 @@ ref/vp9_display_p3.mkv : 192 x 108, 3 channel, uint8 FFmpeg movie oiio:BitsPerSample: 8 oiio:Movie: 1 oiio:subimages: 7 +Reading ref/vp9_rec2100_pq.mkv +ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie + 2 subimages: 384x216 [u10,u10,u10] + subimage 0: 384 x 216, 3 channel, uint10 FFmpeg movie + SHA-1: 87BB7BBE57131F08F0336964256F2C190FE26A69 + channel list: R, G, B + CICP: 9, 16, 9, 1 + ENCODER: "Lavf62.3.100" + FramesPerSecond: 24/1 (24) + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 10 + oiio:Movie: 1 + oiio:subimages: 2 + subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie + SHA-1: 3820BE456CC3F4ADD3425E0C9397C83678A5BA8A + channel list: R, G, B + CICP: 9, 16, 9, 1 + ENCODER: "Lavf62.3.100" + FramesPerSecond: 24/1 (24) + ffmpeg:codec_name: "Google VP9" + oiio:BitsPerSample: 10 + oiio:Movie: 1 + oiio:subimages: 2 diff --git a/testsuite/ffmpeg/ref/vp9_rec2100_pq.mkv b/testsuite/ffmpeg/ref/vp9_rec2100_pq.mkv new file mode 100644 index 0000000000000000000000000000000000000000..715ed80a4905207b0a9521b4c6bc47e5d21a4021 GIT binary patch literal 5803 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 Date: Mon, 20 Oct 2025 11:13:12 -0700 Subject: [PATCH 040/508] build: Make dependency report more clear about what was required (#4929) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/dependency_utils.cmake | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 552c7f46a5..25a1172555 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -76,6 +76,17 @@ function (dump_matching_variables pattern) endfunction () + +# Utility: if `condition` is true, append `addition` to variable `var` +macro (string_append_if var condition addition) + # message (STATUS "string_append_if ${var} ${condition}='${${condition}}' '${addition}'") + if (${condition}) + string (APPEND ${var} ${addition}) + endif () +endmacro() + + + # Helper: Print a report about missing dependencies and give instructions on # how to turn on automatic local dependency building. function (print_package_notfound_report) @@ -89,7 +100,9 @@ function (print_package_notfound_report) list (SORT CFP_EXTERNAL_BUILD_DEPS_FOUND CASE INSENSITIVE) list (REMOVE_DUPLICATES CFP_EXTERNAL_BUILD_DEPS_FOUND) foreach (_pkg IN LISTS CFP_EXTERNAL_BUILD_DEPS_FOUND) - message (STATUS " ${_pkg} ${${_pkg}_VERSION}") + set (_msg "${_pkg} ${${_pkg}_VERSION} ") + string_append_if (_msg ${_pkg}_REQUIRED " (REQUIRED)") + message (STATUS " ${_msg}") endforeach () endif () if (CFP_ALL_BUILD_DEPS_BADVERSION) @@ -97,11 +110,13 @@ function (print_package_notfound_report) list (SORT CFP_ALL_BUILD_DEPS_BADVERSION CASE INSENSITIVE) list (REMOVE_DUPLICATES CFP_ALL_BUILD_DEPS_BADVERSION) foreach (_pkg IN LISTS CFP_ALL_BUILD_DEPS_BADVERSION) + set (_msg "${_pkg}") + string_append_if (_msg ${_pkg}_REQUIRED " (REQUIRED)") + string_append_if (_msg ${_pkg}_NOT_FOUND_EXPLANATION " ${_pkg}_NOT_FOUND_EXPLANATION") if (_pkg IN_LIST CFP_LOCALLY_BUILT_DEPS) - message (STATUS " ${_pkg} ${${_pkg}_NOT_FOUND_EXPLANATION} ${ColorMagenta}(${${_pkg}_VERSION} BUILT LOCALLY)${ColorReset}") - else () - message (STATUS " ${_pkg} ${${_pkg}_NOT_FOUND_EXPLANATION}") + string (APPEND _msg " ${ColorMagenta}(${${_pkg}_VERSION} BUILT LOCALLY)${ColorReset} in ${${_pkg}_build_elapsed_time}s)${ColorReset}") endif () + message (STATUS " ${_msg}") endforeach () endif () if (CFP_ALL_BUILD_DEPS_NOTFOUND) @@ -109,11 +124,13 @@ function (print_package_notfound_report) list (SORT CFP_ALL_BUILD_DEPS_NOTFOUND CASE INSENSITIVE) list (REMOVE_DUPLICATES CFP_ALL_BUILD_DEPS_NOTFOUND) foreach (_pkg IN LISTS CFP_ALL_BUILD_DEPS_NOTFOUND) + set (_msg "${_pkg} ${_${_pkg}_version_range}") + string_append_if (_msg ${_pkg}_REQUIRED " (REQUIRED)") + string_append_if (_msg ${_pkg}_NOT_FOUND_EXPLANATION " ${_pkg}_NOT_FOUND_EXPLANATION") if (_pkg IN_LIST CFP_LOCALLY_BUILT_DEPS) - message (STATUS " ${_pkg} ${_${_pkg}_version_range} ${${_pkg}_NOT_FOUND_EXPLANATION} ${ColorMagenta}(${${_pkg}_VERSION} BUILT LOCALLY in ${${_pkg}_build_elapsed_time}s)${ColorReset}") - else () - message (STATUS " ${_pkg} ${_${_pkg}_version_range} ${${_pkg}_NOT_FOUND_EXPLANATION}") + string (APPEND _msg " ${ColorMagenta}(${${_pkg}_VERSION} BUILT LOCALLY in ${${_pkg}_build_elapsed_time}s)${ColorReset}") endif () + message (STATUS " ${_msg}") endforeach () endif () if (CFP_LOCALLY_BUILDABLE_DEPS_NOTFOUND OR CFP_LOCALLY_BUILDABLE_DEPS_BADVERSION) @@ -357,6 +374,7 @@ macro (checked_find_package pkgname) set (_pkg_REQUIRED 0) message(STATUS "Forcing optional of disabled ${pkgname}") endif () + set (${pkgname}_REQUIRED ${_pkg_REQUIRED}) set (_config_status "") unset (_${pkgname}_version_range) if (_pkg_BUILD_LOCAL AND NOT _pkg_NO_FP_RANGE_CHECK) From 73ee395842d282f1e44e5dd701f3ac072d34a52d Mon Sep 17 00:00:00 2001 From: Zach Lewis Date: Mon, 20 Oct 2025 14:36:58 -0400 Subject: [PATCH 041/508] ci(wheels): unbreak wheel release + other enhancements pt 1 (#4937) ## Description This PR fixes and improves a handful of wheels-related items: - Fix an issue where wheels produced with v3.1 releases were not getting uploaded to pypi - Update cibuildwheel to latest version (v3.2.1) - Remove the manylinux2014 builds, replace with manylinux_2_28 builds where applicable - Add CPython 3.14 wheels - Update macos-x86 runner to `macos-15-intel` - Update Python typestubs ## Notes - This PR ends support for pre-built wheels for glibc < 2.27 (CentOS-7 etc). However, the module can still be pip-installed locally, provided devtoolset-7+ (e.g., gcc-9+) is available. - Unless we arrive at a more elegant solution, we must remember to change the conditional in the "upload_pypi" step for every major OIIO release. --------- Signed-off-by: Zach Lewis Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/wheel.yml | 79 ++- pyproject.toml | 7 +- src/build-scripts/build_ccache.bash | 2 +- src/cmake/build_OpenColorIO.cmake | 8 +- src/cmake/build_minizip-ng.cmake | 2 +- src/python/stubs/CMakeLists.txt | 2 +- src/python/stubs/OpenImageIO/__init__.pyi | 714 +++++++++++----------- 7 files changed, 406 insertions(+), 408 deletions(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index f464007721..0dde3628b3 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -114,30 +114,10 @@ jobs: manylinux: manylinux_2_28 python: cp313-manylinux_x86_64 arch: x86_64 - # ------------------------------------------------------------------- - # CPython 64 bits manylinux2014 - # ------------------------------------------------------------------- - - build: CPython 3.9 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp39-manylinux_x86_64 - arch: x86_64 - - build: CPython 3.10 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp310-manylinux_x86_64 - arch: x86_64 - - build: CPython 3.11 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp311-manylinux_x86_64 - arch: x86_64 - - build: CPython 3.12 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp312-manylinux_x86_64 - arch: x86_64 - - build: CPython 3.13 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp313-manylinux_x86_64 + - build: CPython 3.14 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp314-manylinux_x86_64 arch: x86_64 - steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -157,7 +137,7 @@ jobs: - name: Build wheels # Note: the version of cibuildwheel should be kept in sync with src/python/stubs/CMakeLists.txt - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 env: # pass GITHUB_ACTIONS through to the build container so that custom # processes can tell they are running in CI. @@ -170,7 +150,7 @@ jobs: CIBW_ENVIRONMENT: > CCACHE_DIR=/host//home/runner/.ccache CCACHE_COMPRESSION=yes - CCACHE_PREBUILT=0 + CCACHE_PREBUILT=1 CMAKE_BUILD_PARALLEL_LEVEL=4 CTEST_PARALLEL_LEVEL=4 SKBUILD_CMAKE_ARGS="-DLINKSTATIC=1" @@ -220,26 +200,30 @@ jobs: # ------------------------------------------------------------------- # CPython ARM 64 bits manylinux2014 # ------------------------------------------------------------------- - - build: CPython 3.9 ARM 64 bits manylinux2014 - manylinux: manylinux2014 + - build: CPython 3.9 ARM 64 bits manylinux_2_28 + manylinux: manylinux_2_28 python: cp39-manylinux_aarch64 arch: aarch64 - - build: CPython 3.10 ARM 64 bits manylinux2014 - manylinux: manylinux2014 + - build: CPython 3.10 ARM 64 bits manylinux_2_28 + manylinux: manylinux_2_28 python: cp310-manylinux_aarch64 arch: aarch64 - - build: CPython 3.11 ARM 64 bits manylinux2014 - manylinux: manylinux2014 + - build: CPython 3.11 ARM 64 bits manylinux_2_28 + manylinux: manylinux_2_28 python: cp311-manylinux_aarch64 arch: aarch64 - - build: CPython 3.12 ARM 64 bits manylinux2014 - manylinux: manylinux2014 + - build: CPython 3.12 ARM 64 bits manylinux_2_28 + manylinux: manylinux_2_28 python: cp312-manylinux_aarch64 arch: aarch64 - - build: CPython 3.13 ARM 64 bits manylinux2014 - manylinux: manylinux2014 + - build: CPython 3.13 ARM 64 bits manylinux_2_28 + manylinux: manylinux_2_28 python: cp313-manylinux_aarch64 arch: aarch64 + - build: CPython 3.14 ARM 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp314-manylinux_aarch64 + arch: aarch64 steps: - name: Checkout repo @@ -259,7 +243,7 @@ jobs: restore-keys: wheel-${{runner.os}}-${{matrix.python}} - name: Build wheels - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 env: CIBW_ENVIRONMENT_PASS_LINUX: GITHUB_ACTIONS CIBW_BEFORE_ALL: "source src/build-scripts/build_ccache.bash && pwd && /project/ext/dist/bin/ccache --max-size=200M && /project/ext/dist/bin/ccache -sv && export CMAKE_C_COMPILER_LAUNCHER=/project/ext/dist/bin/ccache CMAKE_CXX_COMPILER_LAUNCHER=/project/ext/dist/bin/ccache" @@ -308,7 +292,7 @@ jobs: macos: name: Build wheels on macOS - runs-on: macos-13 + runs-on: macos-15-intel if: | github.event_name != 'schedule' || github.repository == 'AcademySoftwareFoundation/OpenImageIO' @@ -333,6 +317,9 @@ jobs: - build: CPython 3.13 64 bits python: cp313-macosx_x86_64 arch: x86_64 + - build: CPython 3.14 64 bits + python: cp314-macosx_x86_64 + arch: x86_64 steps: - name: Checkout repo @@ -355,12 +342,12 @@ jobs: run: | brew install ninja ccache || true - - name: Remove brew OpenEXR/Imath + - name: Brew uninstall problematic dependencies run: | - brew uninstall --ignore-dependencies openexr imath || true + brew uninstall -f --ignore-dependencies openexr imath expat cmake || true - name: Build wheels - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -419,6 +406,9 @@ jobs: - build: CPython 3.13 ARM 64 bits python: cp313-macosx_arm64 arch: arm64 + - build: CPython 3.14 ARM 64 bits + python: cp314-macosx_arm64 + arch: arm64 steps: - name: Checkout repo @@ -442,7 +432,7 @@ jobs: brew install ninja ccache || true - name: Build wheels - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -496,6 +486,9 @@ jobs: - build: CPython 3.13 64 bits python: cp313-win_amd64 arch: AMD64 + - build: CPython 3.14 64 bits + python: cp314-win_amd64 + arch: AMD64 steps: - name: Checkout repo @@ -507,7 +500,7 @@ jobs: python-version: '3.9' - name: Build wheels - uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -529,7 +522,7 @@ jobs: runs-on: ubuntu-latest permissions: id-token: write - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v3.0.') && github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags/v3.0.') || startsWith(github.event.ref, 'refs/tags/v3.1.')) && github.repository == 'AcademySoftwareFoundation/OpenImageIO' steps: - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 diff --git a/pyproject.toml b/pyproject.toml index 164eb706d2..29e7ede18d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "License :: OSI Approved :: Apache Software License", "Topic :: Multimedia :: Graphics", "Topic :: Multimedia :: Video", @@ -32,7 +33,7 @@ classifiers = [ ] requires-python = ">= 3.9" dependencies = [ - "numpy>=1.19", + "numpy>=2.0,<3", ] [project.urls] @@ -51,7 +52,7 @@ oiiotool = "OpenImageIO:_command_line" build-backend = "scikit_build_core.build" requires = [ "scikit-build-core>=0.10.6,<1", - "pybind11>=2.13,<3", + "pybind11>=2.13,<4", ] [tool.scikit-build] @@ -109,6 +110,8 @@ skip = [ # Building with musl seems to work, but the repair-wheel step seems to fail... # This may be a bug in repairwheel (or auditwheel)? "*musllinux*", + # Skip free-threading builds + "cp3??t-*", ] test-command = "oiiotool --buildinfo" diff --git a/src/build-scripts/build_ccache.bash b/src/build-scripts/build_ccache.bash index 0290c599bb..30420656ab 100755 --- a/src/build-scripts/build_ccache.bash +++ b/src/build-scripts/build_ccache.bash @@ -16,7 +16,7 @@ echo "HOME=$HOME" echo "PWD=$PWD" echo "ARCH=$ARCH" -CCACHE_PREBULT=${CCACHE_PREBULT:=1} +CCACHE_PREBUILT=${CCACHE_PREBUILT:=1} # Repo and branch/tag/commit of ccache to download if we don't have it yet CCACHE_REPO=${CCACHE_REPO:=https://github.com/ccache/ccache} diff --git a/src/cmake/build_OpenColorIO.cmake b/src/cmake/build_OpenColorIO.cmake index 136ec9d1ab..8502992696 100644 --- a/src/cmake/build_OpenColorIO.cmake +++ b/src/cmake/build_OpenColorIO.cmake @@ -9,7 +9,7 @@ set_cache (OpenColorIO_BUILD_VERSION 2.4.2 "OpenColorIO version for local builds") set (OpenColorIO_GIT_REPOSITORY "https://github.com/AcademySoftwareFoundation/OpenColorIO") set (OpenColorIO_GIT_TAG "v${OpenColorIO_BUILD_VERSION}") -set_cache (OpenColorIO_BUILD_SHARED_LIBS OFF #ON +set_cache (OpenColorIO_BUILD_SHARED_LIBS OFF DOC "Should a local OpenColorIO build, if necessary, build shared libraries" ADVANCED) # We would prefer to build a static OCIO, but haven't figured out how to make # it all work with the static dependencies, it just makes things complicated @@ -24,9 +24,9 @@ unset (OPENCOLORIO_VERSION_MINOR) unset (OpenColorIO_DIR) checked_find_package(pystring VERSION_MIN 1.1.3) -checked_find_package(expat REQUIRED VERSION_MIN 2.5) -checked_find_package(yaml-cpp REQUIRED VERSION_MIN 0.6.0) -checked_find_package(minizip-ng REQUIRED VERSION_MIN 3.0.0) +checked_find_package(expat REQUIRED VERSION_MIN 2.6) +checked_find_package(yaml-cpp REQUIRED VERSION_MIN 0.8.0) +checked_find_package(minizip-ng REQUIRED VERSION_MIN 4.0.10) string (MAKE_C_IDENTIFIER ${OpenColorIO_BUILD_VERSION} OpenColorIO_VERSION_IDENT) diff --git a/src/cmake/build_minizip-ng.cmake b/src/cmake/build_minizip-ng.cmake index c09e375fb6..f930853982 100644 --- a/src/cmake/build_minizip-ng.cmake +++ b/src/cmake/build_minizip-ng.cmake @@ -7,7 +7,7 @@ ###################################################################### -set_cache (minizip-ng_BUILD_VERSION 4.0.7 "minizip-ng version for local builds") +set_cache (minizip-ng_BUILD_VERSION 4.0.10 "minizip-ng version for local builds") set (minizip-ng_GIT_REPOSITORY "https://github.com/zlib-ng/minizip-ng") set (minizip-ng_GIT_TAG "${minizip-ng_BUILD_VERSION}") diff --git a/src/python/stubs/CMakeLists.txt b/src/python/stubs/CMakeLists.txt index bde2ed55c3..a9351eff2f 100644 --- a/src/python/stubs/CMakeLists.txt +++ b/src/python/stubs/CMakeLists.txt @@ -8,7 +8,7 @@ set (_stub_file "${CMAKE_SOURCE_DIR}/src/python/stubs/OpenImageIO/__init__.pyi") # the version of python that OpenImageIO is being built against. # Note: the version of cibuildwheel should be kept in sync with .github/workflows/wheel.yml add_custom_command (COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/python/stubs/generate_stubs_local.py - --repo-root ${CMAKE_SOURCE_DIR} --python-version="3.11" --cibuildwheel-version="2.21.1" + --repo-root ${CMAKE_SOURCE_DIR} --python-version="3.11" --cibuildwheel-version="3.2.1" --output-dir "${CMAKE_BINARY_DIR}/wheelhouse" OUTPUT "${CMAKE_BINARY_DIR}/wheelhouse/OpenImageIO/__init__.pyi" DEPENDS "${CMAKE_SOURCE_DIR}/src/python/stubs/generate_stubs.py" diff --git a/src/python/stubs/OpenImageIO/__init__.pyi b/src/python/stubs/OpenImageIO/__init__.pyi index 2b3ffbbe8d..200be7d774 100644 --- a/src/python/stubs/OpenImageIO/__init__.pyi +++ b/src/python/stubs/OpenImageIO/__init__.pyi @@ -2,10 +2,12 @@ # This file is auto-generated. DO NOT MODIFY! Run `make pystubs` to regenerate # +import collections.abc import numpy import typing +import typing_extensions from _typeshed import Incomplete -from typing import ClassVar, Iterator, overload +from typing import ClassVar, overload AutoStride: int BOX: VECSEMANTICS @@ -111,14 +113,14 @@ class AGGREGATE: VEC3: ClassVar[AGGREGATE] = ... VEC4: ClassVar[AGGREGATE] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -149,14 +151,14 @@ class BASETYPE: UNKNOWN: ClassVar[BASETYPE] = ... USHORT: ClassVar[BASETYPE] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -178,7 +180,7 @@ class ColorConfig: @overload def getColorSpaceFromFilepath(self, filepath: str, default_cs: str, cs_name_match: bool = ...) -> str: ... def getColorSpaceIndex(self, name: str) -> int: ... - def getColorSpaceNameByIndex(self, arg0: int, /) -> str: ... + def getColorSpaceNameByIndex(self, arg0: typing.SupportsInt, /) -> str: ... def getColorSpaceNameByRole(self, role: str) -> str: ... def getColorSpaceNames(self) -> list[str]: ... def getDefaultDisplayName(self) -> str: ... @@ -186,14 +188,14 @@ class ColorConfig: def getDefaultViewName(self, display: str = ...) -> str: ... @overload def getDefaultViewName(self, display: str = ..., *, input_color_space: str) -> str: ... - def getDisplayNameByIndex(self, arg0: int, /) -> str: ... + def getDisplayNameByIndex(self, arg0: typing.SupportsInt, /) -> str: ... def getDisplayNames(self) -> list[str]: ... def getDisplayViewColorSpaceName(self, display: str, view: str) -> str: ... def getDisplayViewLooks(self, display: str, view: str) -> str: ... - def getLookNameByIndex(self, arg0: int, /) -> str: ... + def getLookNameByIndex(self, arg0: typing.SupportsInt, /) -> str: ... def getLookNames(self) -> list[str]: ... def getNamedTransformAliases(self, arg0: str, /) -> list[str]: ... - def getNamedTransformNameByIndex(self, arg0: int, /) -> str: ... + def getNamedTransformNameByIndex(self, arg0: typing.SupportsInt, /) -> str: ... def getNamedTransformNames(self) -> list[str]: ... def getNumColorSpaces(self) -> int: ... def getNumDisplays(self) -> int: ... @@ -201,9 +203,9 @@ class ColorConfig: def getNumNamedTransforms(self) -> int: ... def getNumRoles(self) -> int: ... def getNumViews(self, display: str = ...) -> int: ... - def getRoleByIndex(self, arg0: int, /) -> str: ... + def getRoleByIndex(self, arg0: typing.SupportsInt, /) -> str: ... def getRoles(self) -> list[str]: ... - def getViewNameByIndex(self, display: str = ..., *, index: int) -> str: ... + def getViewNameByIndex(self, display: str = ..., *, index: typing.SupportsInt) -> str: ... def getViewNames(self, display: str = ...) -> list[str]: ... def geterror(self) -> str: ... def parseColorSpaceFromString(self, arg0: str, /) -> str: ... @@ -237,36 +239,36 @@ class CompareResults: class DeepData: def __init__(self) -> None: ... def allocated(self) -> bool: ... - def capacity(self, pixel: int) -> int: ... - def channelname(self, arg0: int, /) -> str: ... - def channelsize(self, arg0: int, /) -> int: ... - def channeltype(self, arg0: int, /) -> TypeDesc: ... + def capacity(self, pixel: typing.SupportsInt) -> int: ... + def channelname(self, arg0: typing.SupportsInt, /) -> str: ... + def channelsize(self, arg0: typing.SupportsInt, /) -> int: ... + def channeltype(self, arg0: typing.SupportsInt, /) -> TypeDesc: ... def clear(self) -> None: ... - def copy_deep_pixel(self, pixel: int, src: DeepData, srcpixel: int) -> bool: ... - def copy_deep_sample(self, pixel: int, sample: int, src: DeepData, srcpixel: int, srcsample: int) -> bool: ... - def deep_value(self, pixel: int, channel: int, sample: int) -> float: ... - def deep_value_uint(self, pixel: int, channel: int, sample: int) -> int: ... - def erase_samples(self, pixel: int, samplepos: int, nsamples: int = ...) -> None: ... + def copy_deep_pixel(self, pixel: typing.SupportsInt, src: DeepData, srcpixel: typing.SupportsInt) -> bool: ... + def copy_deep_sample(self, pixel: typing.SupportsInt, sample: typing.SupportsInt, src: DeepData, srcpixel: typing.SupportsInt, srcsample: typing.SupportsInt) -> bool: ... + def deep_value(self, pixel: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt) -> float: ... + def deep_value_uint(self, pixel: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt) -> int: ... + def erase_samples(self, pixel: typing.SupportsInt, samplepos: typing.SupportsInt, nsamples: typing.SupportsInt = ...) -> None: ... def free(self) -> None: ... @overload - def init(self, npixels: int, nchannels: int, channeltypes: object, channelnames: object) -> None: ... + def init(self, npixels: typing.SupportsInt, nchannels: typing.SupportsInt, channeltypes: object, channelnames: object) -> None: ... @overload def init(self, arg0: ImageSpec, /) -> None: ... def initialized(self) -> bool: ... - def insert_samples(self, pixel: int, samplepos: int, nsamples: int = ...) -> None: ... - def merge_deep_pixels(self, pixel: int, src: DeepData, srcpixel: int) -> None: ... - def merge_overlaps(self, pixel: int) -> None: ... - def occlusion_cull(self, pixel: int) -> None: ... - def opaque_z(self, pixel: int) -> float: ... + def insert_samples(self, pixel: typing.SupportsInt, samplepos: typing.SupportsInt, nsamples: typing.SupportsInt = ...) -> None: ... + def merge_deep_pixels(self, pixel: typing.SupportsInt, src: DeepData, srcpixel: typing.SupportsInt) -> None: ... + def merge_overlaps(self, pixel: typing.SupportsInt) -> None: ... + def occlusion_cull(self, pixel: typing.SupportsInt) -> None: ... + def opaque_z(self, pixel: typing.SupportsInt) -> float: ... def same_channeltypes(self, arg0: DeepData, /) -> bool: ... - def samples(self, pixel: int) -> int: ... + def samples(self, pixel: typing.SupportsInt) -> int: ... def samplesize(self) -> int: ... - def set_capacity(self, pixel: int, nsamples: int) -> None: ... - def set_deep_value(self, pixel: int, channel: int, sample: int, value: float) -> None: ... - def set_deep_value_uint(self, pixel: int, channel: int, sample: int, value: int) -> None: ... - def set_samples(self, pixel: int, nsamples: int) -> None: ... - def sort(self, pixel: int) -> None: ... - def split(self, pixel: int, depth: float) -> bool: ... + def set_capacity(self, pixel: typing.SupportsInt, nsamples: typing.SupportsInt) -> None: ... + def set_deep_value(self, pixel: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt, value: typing.SupportsFloat) -> None: ... + def set_deep_value_uint(self, pixel: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt, value: typing.SupportsInt) -> None: ... + def set_samples(self, pixel: typing.SupportsInt, nsamples: typing.SupportsInt) -> None: ... + def sort(self, pixel: typing.SupportsInt) -> None: ... + def split(self, pixel: typing.SupportsInt, depth: typing.SupportsFloat) -> bool: ... @property def AB_channel(self) -> int: ... @property @@ -292,15 +294,15 @@ class ImageBuf: @overload def __init__(self, arg0: str, /) -> None: ... @overload - def __init__(self, arg0: str, arg1: int, arg2: int, /) -> None: ... + def __init__(self, arg0: str, arg1: typing.SupportsInt, arg2: typing.SupportsInt, /) -> None: ... @overload def __init__(self, arg0: ImageSpec, /) -> None: ... @overload def __init__(self, arg0: ImageSpec, arg1: bool, /) -> None: ... @overload - def __init__(self, name: str, subimage: int, miplevel: int, config: ImageSpec) -> None: ... + def __init__(self, name: str, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, config: ImageSpec) -> None: ... @overload - def __init__(self, buffer: numpy.ndarray) -> None: ... + def __init__(self, buffer: typing_extensions.Buffer) -> None: ... def clear(self) -> None: ... def clear_thumbnail(self) -> None: ... @overload @@ -309,54 +311,54 @@ class ImageBuf: def copy(self, format: TypeDesc | BASETYPE | str = ...) -> ImageBuf: ... def copy_metadata(self, arg0: ImageBuf, /) -> None: ... def copy_pixels(self, arg0: ImageBuf, /) -> bool: ... - def deep_erase_samples(self, x: int, y: int, z: int = ..., *, samplepos: int, nsamples: int = ...) -> None: ... - def deep_insert_samples(self, x: int, y: int, z: int = ..., *, samplepos: int, nsamples: int = ...) -> None: ... - def deep_samples(self, x: int, y: int, z: int = ...) -> int: ... - def deep_value(self, x: int, y: int, z: int, channel: int, sample: int) -> float: ... - def deep_value_uint(self, x: int, y: int, z: int, channel: int, sample: int) -> int: ... + def deep_erase_samples(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ..., *, samplepos: typing.SupportsInt, nsamples: typing.SupportsInt = ...) -> None: ... + def deep_insert_samples(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ..., *, samplepos: typing.SupportsInt, nsamples: typing.SupportsInt = ...) -> None: ... + def deep_samples(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ...) -> int: ... + def deep_value(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt) -> float: ... + def deep_value_uint(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt) -> int: ... def deepdata(self) -> DeepData: ... def get_pixels(self, format: TypeDesc | BASETYPE | str = ..., roi: ROI = ...) -> numpy.ndarray | None: ... def get_thumbnail(self) -> ImageBuf: ... - def getchannel(self, x: int, y: int, z: int, c: int, wrap=...) -> float: ... + def getchannel(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, c: typing.SupportsInt, wrap=...) -> float: ... def geterror(self, clear: bool = ...) -> str: ... - def getpixel(self, x: int, y: int, z: int = ..., wrap: str = ...) -> tuple[float, ...]: ... - def init_spec(self, filename: str, subimage: int = ..., miplevel: int = ...) -> bool: ... - def interppixel(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... - def interppixel_NDC(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... - def interppixel_NDC_full(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... - def interppixel_bicubic(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... - def interppixel_bicubic_NDC(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... + def getpixel(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ..., wrap: str = ...) -> tuple[float, ...]: ... + def init_spec(self, filename: str, subimage: typing.SupportsInt = ..., miplevel: typing.SupportsInt = ...) -> bool: ... + def interppixel(self, x: typing.SupportsFloat, y: typing.SupportsFloat, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_NDC(self, x: typing.SupportsFloat, y: typing.SupportsFloat, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_NDC_full(self, x: typing.SupportsFloat, y: typing.SupportsFloat, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_bicubic(self, x: typing.SupportsFloat, y: typing.SupportsFloat, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_bicubic_NDC(self, x: typing.SupportsFloat, y: typing.SupportsFloat, wrap: str = ...) -> tuple[float, ...]: ... def make_writable(self, keep_cache_type: bool = ...) -> bool: ... def merge_metadata(self, src: ImageBuf, override: bool = ..., pattern: str = ...) -> None: ... def nativespec(self) -> ImageSpec: ... - def pixelindex(self, x: int, y: int, z: int, check_range: bool = ...) -> int: ... + def pixelindex(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, check_range: bool = ...) -> int: ... @overload - def read(self, subimage: int, miplevel: int, chbegin: int, chend: int, force: bool, convert: TypeDesc | BASETYPE | str) -> bool: ... + def read(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt, force: bool, convert: TypeDesc | BASETYPE | str) -> bool: ... @overload - def read(self, subimage: int = ..., miplevel: int = ..., force: bool = ..., convert: TypeDesc | BASETYPE | str = ...) -> bool: ... + def read(self, subimage: typing.SupportsInt = ..., miplevel: typing.SupportsInt = ..., force: bool = ..., convert: TypeDesc | BASETYPE | str = ...) -> bool: ... @overload - def reset(self, name: str, subimage: int = ..., miplevel: int = ...) -> None: ... + def reset(self, name: str, subimage: typing.SupportsInt = ..., miplevel: typing.SupportsInt = ...) -> None: ... @overload - def reset(self, name: str, subimage: int = ..., miplevel: int = ..., config: ImageSpec = ...) -> None: ... + def reset(self, name: str, subimage: typing.SupportsInt = ..., miplevel: typing.SupportsInt = ..., config: ImageSpec = ...) -> None: ... @overload def reset(self, spec: ImageSpec, zero: bool = ...) -> None: ... @overload - def reset(self, buffer: numpy.ndarray) -> None: ... - def set_deep_samples(self, x: int, y: int, z: int = ..., nsamples: int = ...) -> None: ... - def set_deep_value(self, x: int, y: int, z: int, channel: int, sample: int, value: float = ...) -> None: ... - def set_deep_value_uint(self, x: int, y: int, z: int, channel: int, sample: int, value: int = ...) -> None: ... - def set_full(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, /) -> None: ... - def set_origin(self, x: int, y: int, z: int = ...) -> None: ... - def set_pixels(self, roi: ROI, pixels: numpy.ndarray) -> bool: ... + def reset(self, buffer: typing_extensions.Buffer) -> None: ... + def set_deep_samples(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ..., nsamples: typing.SupportsInt = ...) -> None: ... + def set_deep_value(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt, value: typing.SupportsFloat = ...) -> None: ... + def set_deep_value_uint(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, channel: typing.SupportsInt, sample: typing.SupportsInt, value: typing.SupportsInt = ...) -> None: ... + def set_full(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt, arg3: typing.SupportsInt, arg4: typing.SupportsInt, arg5: typing.SupportsInt, /) -> None: ... + def set_origin(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ...) -> None: ... + def set_pixels(self, roi: ROI, pixels: typing_extensions.Buffer) -> bool: ... def set_thumbnail(self, thumb: ImageBuf) -> None: ... def set_write_format(self, arg0: object, /) -> None: ... - def set_write_tiles(self, width: int = ..., height: int = ..., depth: int = ...) -> None: ... + def set_write_tiles(self, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., depth: typing.SupportsInt = ...) -> None: ... @overload - def setpixel(self, x: int, y: int, z: int, pixel: object) -> None: ... + def setpixel(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, pixel: object) -> None: ... @overload - def setpixel(self, x: int, y: int, pixel: object) -> None: ... + def setpixel(self, x: typing.SupportsInt, y: typing.SupportsInt, pixel: object) -> None: ... @overload - def setpixel(self, i: int, pixel: object) -> None: ... + def setpixel(self, i: typing.SupportsInt, pixel: object) -> None: ... def spec(self) -> ImageSpec: ... def specmod(self) -> ImageSpec: ... def swap(self, arg0: ImageBuf, /) -> None: ... @@ -437,329 +439,329 @@ class ImageBufAlgo: def __init__(self, *args, **kwargs) -> None: ... @overload @staticmethod - def abs(dst: ImageBuf, A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def abs(dst: ImageBuf, A: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def abs(A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def abs(A: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def absdiff(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def absdiff(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def absdiff(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def absdiff(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def absdiff(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def absdiff(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def absdiff(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def absdiff(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def add(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def add(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def add(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def add(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def add(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def add(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def add(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def add(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod def bluenoise_image() -> ImageBuf: ... @overload @staticmethod - def channel_append(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def channel_append(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def channel_append(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def channel_append(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def channel_sum(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def channel_sum(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def channel_sum(dst: ImageBuf, src: ImageBuf, weight: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def channel_sum(dst: ImageBuf, src: ImageBuf, weight: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def channel_sum(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def channel_sum(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def channel_sum(src: ImageBuf, weight: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def channel_sum(src: ImageBuf, weight: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def channels(dst: ImageBuf, src: ImageBuf, channelorder: tuple, newchannelnames: tuple = ..., shuffle_channel_names: bool = ..., nthreads: int = ...) -> bool: ... + def channels(dst: ImageBuf, src: ImageBuf, channelorder: tuple, newchannelnames: tuple = ..., shuffle_channel_names: bool = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def channels(src: ImageBuf, channelorder: tuple, newchannelnames: tuple = ..., shuffle_channel_names: bool = ..., nthreads: int = ...) -> ImageBuf: ... + def channels(src: ImageBuf, channelorder: tuple, newchannelnames: tuple = ..., shuffle_channel_names: bool = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def checker(dst: ImageBuf, width: int, height: int, depth: int, color1: object, color2: object, xoffset: int = ..., yoffset: int = ..., zoffset: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def checker(dst: ImageBuf, width: typing.SupportsInt, height: typing.SupportsInt, depth: typing.SupportsInt, color1: object, color2: object, xoffset: typing.SupportsInt = ..., yoffset: typing.SupportsInt = ..., zoffset: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def checker(width: int, height: int, depth: int, color1: object, color2: object, xoffset: int = ..., yoffset: int = ..., zoffset: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def checker(width: typing.SupportsInt, height: typing.SupportsInt, depth: typing.SupportsInt, color1: object, color2: object, xoffset: typing.SupportsInt = ..., yoffset: typing.SupportsInt = ..., zoffset: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def circular_shift(dst: ImageBuf, src: ImageBuf, xshift: int, yshift: int, zshift: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def circular_shift(dst: ImageBuf, src: ImageBuf, xshift: typing.SupportsInt, yshift: typing.SupportsInt, zshift: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def circular_shift(src: ImageBuf, xshift: int, yshift: int, zshift: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def circular_shift(src: ImageBuf, xshift: typing.SupportsInt, yshift: typing.SupportsInt, zshift: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def clamp(dst: ImageBuf, src: ImageBuf, min: float | typing.Iterable[float], max: float | typing.Iterable[float], clampalpha01: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def clamp(dst: ImageBuf, src: ImageBuf, min: float | typing.Iterable[float], max: float | typing.Iterable[float], clampalpha01: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def clamp(src: ImageBuf, min: float | typing.Iterable[float], max: float | typing.Iterable[float], clampalpha01: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def clamp(src: ImageBuf, min: float | typing.Iterable[float], max: float | typing.Iterable[float], clampalpha01: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def color_map(dst: ImageBuf, src: ImageBuf, srcchannel: int, mapname: str, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def color_map(dst: ImageBuf, src: ImageBuf, srcchannel: typing.SupportsInt, mapname: str, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def color_map(dst: ImageBuf, src: ImageBuf, srcchannel: int, nknots: int, channels: int, knots: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def color_map(dst: ImageBuf, src: ImageBuf, srcchannel: typing.SupportsInt, nknots: typing.SupportsInt, channels: typing.SupportsInt, knots: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def color_map(src: ImageBuf, srcchannel: int, mapname: str, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def color_map(src: ImageBuf, srcchannel: typing.SupportsInt, mapname: str, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def color_map(src: ImageBuf, srcchannel: int, nknots: int, channels: int, knots: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def color_map(src: ImageBuf, srcchannel: typing.SupportsInt, nknots: typing.SupportsInt, channels: typing.SupportsInt, knots: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def color_range_check(src: ImageBuf, low: object, high: object, roi: ROI = ..., nthreads: int = ...) -> tuple[int, ...] | None: ... + def color_range_check(src: ImageBuf, low: object, high: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> tuple[int, ...] | None: ... @overload @staticmethod - def colorconvert(dst: ImageBuf, src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def colorconvert(dst: ImageBuf, src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def colorconvert(dst: ImageBuf, src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def colorconvert(dst: ImageBuf, src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def colorconvert(src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def colorconvert(src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def colorconvert(src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def colorconvert(src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def colormatrixtransform(dst: ImageBuf, src: ImageBuf, M: object, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def colormatrixtransform(dst: ImageBuf, src: ImageBuf, M: object, unpremult: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def colormatrixtransform(src: ImageBuf, M: object, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def colormatrixtransform(src: ImageBuf, M: object, unpremult: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def compare(A: ImageBuf, B: ImageBuf, failthresh: float, warnthresh: float, result: CompareResults, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def compare(A: ImageBuf, B: ImageBuf, failthresh: typing.SupportsFloat, warnthresh: typing.SupportsFloat, result: CompareResults, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def compare(A: ImageBuf, B: ImageBuf, failthresh: float, warnthresh: float, failrelative: float = ..., warnrelative: float = ..., roi: ROI = ..., nthreads: int = ...) -> CompareResults: ... + def compare(A: ImageBuf, B: ImageBuf, failthresh: typing.SupportsFloat, warnthresh: typing.SupportsFloat, failrelative: typing.SupportsFloat = ..., warnrelative: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> CompareResults: ... @overload @staticmethod - def compare(A: ImageBuf, B: ImageBuf, failthresh: float, warnthresh: float, roi: ROI = ..., nthreads: int = ...) -> CompareResults: ... + def compare(A: ImageBuf, B: ImageBuf, failthresh: typing.SupportsFloat, warnthresh: typing.SupportsFloat, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> CompareResults: ... @staticmethod - def compare_Yee(A: ImageBuf, B: ImageBuf, result: CompareResults, luminance: float = ..., fov: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def compare_Yee(A: ImageBuf, B: ImageBuf, result: CompareResults, luminance: typing.SupportsFloat = ..., fov: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def complex_to_polar(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def complex_to_polar(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def complex_to_polar(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def complex_to_polar(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def computePixelHashSHA1(src: ImageBuf, extrainfo: str = ..., roi: ROI = ..., blocksize: int = ..., nthreads: int = ...) -> str: ... + def computePixelHashSHA1(src: ImageBuf, extrainfo: str = ..., roi: ROI = ..., blocksize: typing.SupportsInt = ..., nthreads: typing.SupportsInt = ...) -> str: ... @overload @staticmethod - def computePixelStats(src: ImageBuf, stats: PixelStats, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def computePixelStats(src: ImageBuf, stats: PixelStats, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def computePixelStats(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> PixelStats: ... + def computePixelStats(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> PixelStats: ... @overload @staticmethod - def contrast_remap(dst: ImageBuf, src: ImageBuf, black: float | typing.Iterable[float] = ..., white: float | typing.Iterable[float] = ..., min: float | typing.Iterable[float] = ..., max: float | typing.Iterable[float] = ..., scontrast: float | typing.Iterable[float] = ..., sthresh: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def contrast_remap(dst: ImageBuf, src: ImageBuf, black: float | typing.Iterable[float] = ..., white: float | typing.Iterable[float] = ..., min: float | typing.Iterable[float] = ..., max: float | typing.Iterable[float] = ..., scontrast: float | typing.Iterable[float] = ..., sthresh: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def contrast_remap(src: ImageBuf, black: float | typing.Iterable[float] = ..., white: float | typing.Iterable[float] = ..., min: float | typing.Iterable[float] = ..., max: float | typing.Iterable[float] = ..., scontrast: float | typing.Iterable[float] = ..., sthresh: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def contrast_remap(src: ImageBuf, black: float | typing.Iterable[float] = ..., white: float | typing.Iterable[float] = ..., min: float | typing.Iterable[float] = ..., max: float | typing.Iterable[float] = ..., scontrast: float | typing.Iterable[float] = ..., sthresh: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def convolve(dst: ImageBuf, src: ImageBuf, kernel: ImageBuf, normalze: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def convolve(dst: ImageBuf, src: ImageBuf, kernel: ImageBuf, normalze: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def convolve(src: ImageBuf, kernel: ImageBuf, normalze: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def convolve(src: ImageBuf, kernel: ImageBuf, normalze: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def copy(dst: ImageBuf, src: ImageBuf, convert: TypeDesc | BASETYPE | str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def copy(dst: ImageBuf, src: ImageBuf, convert: TypeDesc | BASETYPE | str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def copy(src: ImageBuf, convert: TypeDesc | BASETYPE | str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def copy(src: ImageBuf, convert: TypeDesc | BASETYPE | str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def crop(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def crop(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def crop(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def crop(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def cut(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def cut(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def cut(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def cut(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def deep_holdout(dst: ImageBuf, src: ImageBuf, holdout: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def deep_holdout(dst: ImageBuf, src: ImageBuf, holdout: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def deep_holdout(src: ImageBuf, holdout: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def deep_holdout(src: ImageBuf, holdout: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def deep_merge(dst: ImageBuf, A: ImageBuf, B: ImageBuf, occlusion_cull: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def deep_merge(dst: ImageBuf, A: ImageBuf, B: ImageBuf, occlusion_cull: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def deep_merge(A: ImageBuf, B: ImageBuf, occlusion_cull: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def deep_merge(A: ImageBuf, B: ImageBuf, occlusion_cull: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def deepen(dst: ImageBuf, src: ImageBuf, zvalue: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def deepen(dst: ImageBuf, src: ImageBuf, zvalue: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def deepen(src: ImageBuf, zvalue: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def deepen(src: ImageBuf, zvalue: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def demosaic(dst: ImageBuf, src: ImageBuf, pattern: str = ..., algorithm: str = ..., layout: str = ..., white_balance_mode: str = ..., white_balance: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def demosaic(dst: ImageBuf, src: ImageBuf, pattern: str = ..., algorithm: str = ..., layout: str = ..., white_balance_mode: str = ..., white_balance: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def demosaic(src: ImageBuf, pattern: str = ..., algorithm: str = ..., layout: str = ..., white_balance_mode: str = ..., white_balance: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def demosaic(src: ImageBuf, pattern: str = ..., algorithm: str = ..., layout: str = ..., white_balance_mode: str = ..., white_balance: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def dilate(dst: ImageBuf, src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def dilate(dst: ImageBuf, src: ImageBuf, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def dilate(src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def dilate(src: ImageBuf, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def div(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def div(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def div(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def div(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def div(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def div(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def div(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def div(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def erode(dst: ImageBuf, src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def erode(dst: ImageBuf, src: ImageBuf, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def erode(src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def erode(src: ImageBuf, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fft(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fft(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fft(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fft(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fill(dst: ImageBuf, values: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fill(dst: ImageBuf, values: float | typing.Iterable[float], roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fill(dst: ImageBuf, top: float | typing.Iterable[float], bottom: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fill(dst: ImageBuf, top: float | typing.Iterable[float], bottom: float | typing.Iterable[float], roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fill(dst: ImageBuf, topleft: float | typing.Iterable[float], topright: float | typing.Iterable[float], bottomleft: float | typing.Iterable[float], bottomright: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fill(dst: ImageBuf, topleft: float | typing.Iterable[float], topright: float | typing.Iterable[float], bottomleft: float | typing.Iterable[float], bottomright: float | typing.Iterable[float], roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fill(values: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fill(values: float | typing.Iterable[float], roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fill(top: float | typing.Iterable[float], bottom: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fill(top: float | typing.Iterable[float], bottom: float | typing.Iterable[float], roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fill(topleft: float | typing.Iterable[float], topright: float | typing.Iterable[float], bottomleft: float | typing.Iterable[float], bottomright: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fill(topleft: float | typing.Iterable[float], topright: float | typing.Iterable[float], bottomleft: float | typing.Iterable[float], bottomright: float | typing.Iterable[float], roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fillholes_pushpull(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fillholes_pushpull(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fillholes_pushpull(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fillholes_pushpull(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fit(dst: ImageBuf, src: ImageBuf, filtername: str = ..., filterwidth: float = ..., fillmode: str = ..., exact: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fit(dst: ImageBuf, src: ImageBuf, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., fillmode: str = ..., exact: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fit(src: ImageBuf, filtername: str = ..., filterwidth: float = ..., fillmode: str = ..., exact: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fit(src: ImageBuf, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., fillmode: str = ..., exact: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def fixNonFinite(dst: ImageBuf, src: ImageBuf, mode: NonFiniteFixMode = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def fixNonFinite(dst: ImageBuf, src: ImageBuf, mode: NonFiniteFixMode = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def fixNonFinite(src: ImageBuf, mode: NonFiniteFixMode = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def fixNonFinite(src: ImageBuf, mode: NonFiniteFixMode = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def flatten(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def flatten(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def flatten(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def flatten(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def flip(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def flip(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def flip(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def flip(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def flop(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def flop(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def flop(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def flop(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def histogram(src: ImageBuf, channel: int = ..., bins: int = ..., min: float = ..., max: float = ..., ignore_empty: bool = ..., roi: ROI = ..., nthreads: int = ...) -> tuple[int, ...]: ... + def histogram(src: ImageBuf, channel: typing.SupportsInt = ..., bins: typing.SupportsInt = ..., min: typing.SupportsFloat = ..., max: typing.SupportsFloat = ..., ignore_empty: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> tuple[int, ...]: ... @overload @staticmethod - def ifft(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ifft(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ifft(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ifft(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def invert(dst: ImageBuf, A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def invert(dst: ImageBuf, A: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def invert(A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def invert(A: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def isConstantChannel(src: ImageBuf, channel: int, val: float, threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def isConstantChannel(src: ImageBuf, channel: typing.SupportsInt, val: typing.SupportsFloat, threshold: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @staticmethod - def isConstantColor(src: ImageBuf, threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> tuple[float, ...] | None: ... + def isConstantColor(src: ImageBuf, threshold: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> tuple[float, ...] | None: ... @staticmethod - def isMonochrome(src: ImageBuf, threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def isMonochrome(src: ImageBuf, threshold: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def laplacian(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def laplacian(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def laplacian(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def laplacian(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def mad(dst: ImageBuf, A: ImageBuf, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def mad(dst: ImageBuf, A: ImageBuf, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def mad(dst: ImageBuf, A: ImageBuf, B: object, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def mad(dst: ImageBuf, A: ImageBuf, B: object, C: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def mad(dst: ImageBuf, A: object, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def mad(dst: ImageBuf, A: object, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def mad(dst: ImageBuf, A: ImageBuf, B: object, C: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def mad(dst: ImageBuf, A: ImageBuf, B: object, C: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def mad(A: ImageBuf, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def mad(A: ImageBuf, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def mad(A: ImageBuf, B: object, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def mad(A: ImageBuf, B: object, C: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def mad(A: object, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def mad(A: object, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def mad(A: ImageBuf, B: object, C: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def mad(A: ImageBuf, B: object, C: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def make_kernel(dst: ImageBuf, name: str, width: float, height: float, depth: float = ..., normalize: bool = ...) -> bool: ... + def make_kernel(dst: ImageBuf, name: str, width: typing.SupportsFloat, height: typing.SupportsFloat, depth: typing.SupportsFloat = ..., normalize: bool = ...) -> bool: ... @overload @staticmethod - def make_kernel(name: str, width: float, height: float, depth: float = ..., normalize: bool = ...) -> ImageBuf: ... + def make_kernel(name: str, width: typing.SupportsFloat, height: typing.SupportsFloat, depth: typing.SupportsFloat = ..., normalize: bool = ...) -> ImageBuf: ... @overload @staticmethod def make_texture(mode: MakeTextureMode, filename: str, outputfilename: str, config: ImageSpec = ...) -> bool: ... @@ -768,323 +770,323 @@ class ImageBufAlgo: def make_texture(mode: MakeTextureMode, buf: ImageBuf, outputfilename: str, config: ImageSpec = ...) -> bool: ... @overload @staticmethod - def max(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def max(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def max(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def max(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def max(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def max(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def max(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def max(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def maxchan(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def maxchan(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def maxchan(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def maxchan(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def median_filter(dst: ImageBuf, src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def median_filter(dst: ImageBuf, src: ImageBuf, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def median_filter(src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def median_filter(src: ImageBuf, width: typing.SupportsInt = ..., height: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def min(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def min(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def min(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def min(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def min(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def min(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def min(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def min(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def minchan(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def minchan(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def minchan(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def minchan(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def mul(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def mul(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def mul(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def mul(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def mul(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def mul(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def mul(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def mul(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def noise(dst: ImageBuf, type: str = ..., A: float = ..., B: float = ..., mono: bool = ..., seed: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def noise(dst: ImageBuf, type: str = ..., A: typing.SupportsFloat = ..., B: typing.SupportsFloat = ..., mono: bool = ..., seed: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def noise(type: str = ..., A: float = ..., B: float = ..., mono: bool = ..., seed: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def noise(type: str = ..., A: typing.SupportsFloat = ..., B: typing.SupportsFloat = ..., mono: bool = ..., seed: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def nonzero_region(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ROI: ... + def nonzero_region(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ROI: ... @overload @staticmethod - def normalize(dst: ImageBuf, src: ImageBuf, inCenter: float = ..., outCenter: float = ..., scale: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def normalize(dst: ImageBuf, src: ImageBuf, inCenter: typing.SupportsFloat = ..., outCenter: typing.SupportsFloat = ..., scale: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def normalize(src: ImageBuf, inCenter: float = ..., outCenter: float = ..., scale: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def normalize(src: ImageBuf, inCenter: typing.SupportsFloat = ..., outCenter: typing.SupportsFloat = ..., scale: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str, colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str, colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociofiletransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociofiletransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociofiletransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociofiletransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociofiletransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociofiletransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociofiletransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociofiletransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociolook(dst: ImageBuf, src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociolook(dst: ImageBuf, src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociolook(dst: ImageBuf, src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ociolook(dst: ImageBuf, src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ociolook(src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociolook(src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ociolook(src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ociolook(src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ocionamedtransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ocionamedtransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ocionamedtransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def ocionamedtransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def ocionamedtransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ocionamedtransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def ocionamedtransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def ocionamedtransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def over(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def over(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def over(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def over(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def paste(dst: ImageBuf, xbegin: int, ybegin: int, zbegin: int, chbegin: int, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def paste(dst: ImageBuf, xbegin: typing.SupportsInt, ybegin: typing.SupportsInt, zbegin: typing.SupportsInt, chbegin: typing.SupportsInt, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def polar_to_complex(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def polar_to_complex(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def polar_to_complex(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def polar_to_complex(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def pow(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def pow(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def pow(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def pow(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def premult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def premult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def premult(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def premult(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rangecompress(dst: ImageBuf, src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rangecompress(dst: ImageBuf, src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rangecompress(src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rangecompress(src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rangeexpand(dst: ImageBuf, src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rangeexpand(dst: ImageBuf, src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rangeexpand(src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rangeexpand(src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def render_box(dst: ImageBuf, x1: int, y1: int, x2: int, y2: int, color: float | typing.Iterable[float] = ..., fill: bool = ...) -> bool: ... + def render_box(dst: ImageBuf, x1: typing.SupportsInt, y1: typing.SupportsInt, x2: typing.SupportsInt, y2: typing.SupportsInt, color: float | typing.Iterable[float] = ..., fill: bool = ...) -> bool: ... @staticmethod - def render_line(dst: ImageBuf, x1: int, y1: int, x2: int, y2: int, color: float | typing.Iterable[float] = ..., skip_first_point: bool = ...) -> bool: ... + def render_line(dst: ImageBuf, x1: typing.SupportsInt, y1: typing.SupportsInt, x2: typing.SupportsInt, y2: typing.SupportsInt, color: float | typing.Iterable[float] = ..., skip_first_point: bool = ...) -> bool: ... @staticmethod - def render_point(dst: ImageBuf, x: int, y: int, color: float | typing.Iterable[float] = ...) -> bool: ... + def render_point(dst: ImageBuf, x: typing.SupportsInt, y: typing.SupportsInt, color: float | typing.Iterable[float] = ...) -> bool: ... @staticmethod - def render_text(dst: ImageBuf, x: int, y: int, text: str, fontsize: int = ..., fontname: str = ..., textcolor: object = ..., alignx: str = ..., aligny: str = ..., shadow: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def render_text(dst: ImageBuf, x: typing.SupportsInt, y: typing.SupportsInt, text: str, fontsize: typing.SupportsInt = ..., fontname: str = ..., textcolor: object = ..., alignx: str = ..., aligny: str = ..., shadow: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def reorient(dst: ImageBuf, src: ImageBuf, nthreads: int = ...) -> bool: ... + def reorient(dst: ImageBuf, src: ImageBuf, nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def reorient(src: ImageBuf, nthreads: int = ...) -> ImageBuf: ... + def reorient(src: ImageBuf, nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def repremult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def repremult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def repremult(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def repremult(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def resample(dst: ImageBuf, src: ImageBuf, interpolate: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def resample(dst: ImageBuf, src: ImageBuf, interpolate: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def resample(src: ImageBuf, interpolate: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def resample(src: ImageBuf, interpolate: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def resize(dst: ImageBuf, src: ImageBuf, filtername: str = ..., filterwidth: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def resize(dst: ImageBuf, src: ImageBuf, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def resize(src: ImageBuf, filtername: str = ..., filterwidth: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def resize(src: ImageBuf, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rotate(dst: ImageBuf, src: ImageBuf, angle: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rotate(dst: ImageBuf, src: ImageBuf, angle: typing.SupportsFloat, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rotate(dst: ImageBuf, src: ImageBuf, angle: float, center_x: float, center_y: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rotate(dst: ImageBuf, src: ImageBuf, angle: typing.SupportsFloat, center_x: typing.SupportsFloat, center_y: typing.SupportsFloat, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rotate(src: ImageBuf, angle: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rotate(src: ImageBuf, angle: typing.SupportsFloat, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rotate(src: ImageBuf, angle: float, center_x: float, center_y: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rotate(src: ImageBuf, angle: typing.SupportsFloat, center_x: typing.SupportsFloat, center_y: typing.SupportsFloat, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rotate180(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rotate180(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rotate180(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rotate180(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rotate270(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rotate270(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rotate270(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rotate270(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def rotate90(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def rotate90(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def rotate90(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def rotate90(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def saturate(dst: ImageBuf, src: ImageBuf, scale: float = ..., firstchannel: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def saturate(dst: ImageBuf, src: ImageBuf, scale: typing.SupportsFloat = ..., firstchannel: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def saturate(src: ImageBuf, scale: float = ..., firstchannel: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def saturate(src: ImageBuf, scale: typing.SupportsFloat = ..., firstchannel: typing.SupportsInt = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def scale(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def scale(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def scale(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def scale(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def st_warp(dst: ImageBuf, src: ImageBuf, stbuf: ImageBuf, filtername: str = ..., filterwidth: float = ..., chan_s: int = ..., chan_t: int = ..., flip_s: bool = ..., flip_t: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def st_warp(dst: ImageBuf, src: ImageBuf, stbuf: ImageBuf, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., chan_s: typing.SupportsInt = ..., chan_t: typing.SupportsInt = ..., flip_s: bool = ..., flip_t: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def st_warp(src: ImageBuf, stbuf: ImageBuf, filtername: str = ..., filterwidth: float = ..., chan_s: int = ..., chan_t: int = ..., flip_s: bool = ..., flip_t: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def st_warp(src: ImageBuf, stbuf: ImageBuf, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., chan_s: typing.SupportsInt = ..., chan_t: typing.SupportsInt = ..., flip_s: bool = ..., flip_t: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def sub(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def sub(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def sub(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def sub(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def sub(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def sub(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def sub(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def sub(A: ImageBuf, B: object, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @staticmethod - def text_size(text: str, fontsize: int = ..., fontname: str = ...) -> ROI: ... + def text_size(text: str, fontsize: typing.SupportsInt = ..., fontname: str = ...) -> ROI: ... @overload @staticmethod - def transpose(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def transpose(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def transpose(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def transpose(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def unpremult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def unpremult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def unpremult(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def unpremult(src: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def unsharp_mask(dst: ImageBuf, src: ImageBuf, kernel: str = ..., width: float = ..., contrast: float = ..., threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def unsharp_mask(dst: ImageBuf, src: ImageBuf, kernel: str = ..., width: typing.SupportsFloat = ..., contrast: typing.SupportsFloat = ..., threshold: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def unsharp_mask(src: ImageBuf, kernel: str = ..., width: float = ..., contrast: float = ..., threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def unsharp_mask(src: ImageBuf, kernel: str = ..., width: typing.SupportsFloat = ..., contrast: typing.SupportsFloat = ..., threshold: typing.SupportsFloat = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def warp(dst: ImageBuf, src: ImageBuf, M: object, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., wrap: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def warp(dst: ImageBuf, src: ImageBuf, M: object, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., recompute_roi: bool = ..., wrap: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def warp(src: ImageBuf, M: object, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., wrap: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def warp(src: ImageBuf, M: object, filtername: str = ..., filterwidth: typing.SupportsFloat = ..., recompute_roi: bool = ..., wrap: str = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def zero(dst: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + def zero(dst: ImageBuf, roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def zero(roi: ROI, nthreads: int = ...) -> ImageBuf: ... + def zero(roi: ROI, nthreads: typing.SupportsInt = ...) -> ImageBuf: ... @overload @staticmethod - def zover(dst: ImageBuf, A: ImageBuf, B: ImageBuf, z_zeroisinf: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + def zover(dst: ImageBuf, A: ImageBuf, B: ImageBuf, z_zeroisinf: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> bool: ... @overload @staticmethod - def zover(A: ImageBuf, B: ImageBuf, z_zeroisinf: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + def zover(A: ImageBuf, B: ImageBuf, z_zeroisinf: bool = ..., roi: ROI = ..., nthreads: typing.SupportsInt = ...) -> ImageBuf: ... class ImageCache: def __init__(self, shared: bool = ...) -> None: ... @overload - def attribute(self, arg0: str, arg1: float, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsFloat, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: int, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsInt, /) -> None: ... @overload def attribute(self, arg0: str, arg1: str, /) -> None: ... @overload def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... @staticmethod def destroy(cache: ImageCache, teardown: bool = ...) -> None: ... - def get_cache_dimensions(self, filename: str, subimage: int = ..., miplevel: int = ...) -> ImageSpec: ... - def get_imagespec(self, filename: str, subimage: int = ...) -> ImageSpec: ... + def get_cache_dimensions(self, filename: str, subimage: typing.SupportsInt = ..., miplevel: typing.SupportsInt = ...) -> ImageSpec: ... + def get_imagespec(self, filename: str, subimage: typing.SupportsInt = ...) -> ImageSpec: ... @overload - def get_pixels(self, filename: str, subimage: int, miplevel: int, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int = ..., zend: int = ..., datatype: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def get_pixels(self, filename: str, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt = ..., zend: typing.SupportsInt = ..., datatype: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload - def get_pixels(self, filename: str, subimage: int, miplevel: int, roi: ROI, datatype: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def get_pixels(self, filename: str, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, roi: ROI, datatype: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... def getattribute(self, name: str, type: TypeDesc | BASETYPE | str = ...) -> typing.Any: ... def getattributetype(self, name: str) -> TypeDesc: ... def geterror(self, clear: bool = ...) -> str: ... - def getstats(self, level: int = ...) -> str: ... + def getstats(self, level: typing.SupportsInt = ...) -> str: ... def invalidate(self, filename: str, force: bool = ...) -> None: ... def invalidate_all(self, force: bool = ...) -> None: ... def resolve_filename(self, arg0: str, /) -> str: ... @@ -1108,30 +1110,30 @@ class ImageInput: @staticmethod def open(filename: str, config: ImageSpec) -> ImageInput: ... @overload - def read_image(self, subimage: int, miplevel: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_image(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload - def read_image(self, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_image(self, chbegin: typing.SupportsInt, chend: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload def read_image(self, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... - def read_native_deep_image(self, subimage: int = ..., miplevel: int = ...) -> DeepData: ... - def read_native_deep_scanlines(self, subimage: int, miplevel: int, ybegin: int, yend: int, z: int, chbegin: int, chend: int) -> DeepData: ... - def read_native_deep_tiles(self, subimage: int, miplevel: int, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, chbegin: int, chend: int) -> DeepData: ... - def read_scanline(self, y: int, z: int = ..., format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_native_deep_image(self, subimage: typing.SupportsInt = ..., miplevel: typing.SupportsInt = ...) -> DeepData: ... + def read_native_deep_scanlines(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, z: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt) -> DeepData: ... + def read_native_deep_tiles(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt, zend: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt) -> DeepData: ... + def read_scanline(self, y: typing.SupportsInt, z: typing.SupportsInt = ..., format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload - def read_scanlines(self, subimage: int, miplevel: int, ybegin: int, yend: int, z: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_scanlines(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, z: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload - def read_scanlines(self, ybegin: int, yend: int, z: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... - def read_tile(self, x: int, y: int, z: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_scanlines(self, ybegin: typing.SupportsInt, yend: typing.SupportsInt, z: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_tile(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload - def read_tiles(self, subimage: int, miplevel: int, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_tiles(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt, zend: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... @overload - def read_tiles(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... - def seek_subimage(self, arg0: int, arg1: int, /) -> bool: ... + def read_tiles(self, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt, zend: typing.SupportsInt, chbegin: typing.SupportsInt, chend: typing.SupportsInt, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def seek_subimage(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, /) -> bool: ... @overload def spec(self) -> ImageSpec: ... @overload - def spec(self, subimage: int, miplevel: int = ...) -> ImageSpec: ... - def spec_dimensions(self, subimage: int, miplevel: int = ...) -> ImageSpec: ... + def spec(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt = ...) -> ImageSpec: ... + def spec_dimensions(self, subimage: typing.SupportsInt, miplevel: typing.SupportsInt = ...) -> ImageSpec: ... def supports(self, arg0: str, /) -> int: ... def valid_file(self, arg0: str, /) -> bool: ... @property @@ -1148,20 +1150,20 @@ class ImageOutput: @overload def open(self, filename: str, spec: ImageSpec, mode: str = ...) -> bool: ... @overload - def open(self, filename: str, specs: list[ImageSpec]) -> bool: ... + def open(self, filename: str, specs: collections.abc.Sequence[ImageSpec]) -> bool: ... @overload def open(self, arg0: str, arg1: tuple, /) -> bool: ... def set_thumbnail(self, arg0, /) -> bool: ... def spec(self) -> ImageSpec: ... def supports(self, arg0: str, /) -> int: ... def write_deep_image(self, arg0: DeepData, /) -> bool: ... - def write_deep_scanlines(self, ybegin: int, yend: int, z: int, deepdata: DeepData) -> bool: ... - def write_deep_tiles(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, deepdata: DeepData) -> bool: ... - def write_image(self, arg0: numpy.ndarray, /) -> bool: ... - def write_scanline(self, y: int, z: int, pixels: numpy.ndarray) -> bool: ... - def write_scanlines(self, ybegin: int, yend: int, z: int, pixels: numpy.ndarray) -> bool: ... - def write_tile(self, x: int, y: int, z: int, pixels: numpy.ndarray) -> bool: ... - def write_tiles(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, pixels: numpy.ndarray) -> bool: ... + def write_deep_scanlines(self, ybegin: typing.SupportsInt, yend: typing.SupportsInt, z: typing.SupportsInt, deepdata: DeepData) -> bool: ... + def write_deep_tiles(self, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt, zend: typing.SupportsInt, deepdata: DeepData) -> bool: ... + def write_image(self, arg0: typing_extensions.Buffer, /) -> bool: ... + def write_scanline(self, y: typing.SupportsInt, z: typing.SupportsInt, pixels: typing_extensions.Buffer) -> bool: ... + def write_scanlines(self, ybegin: typing.SupportsInt, yend: typing.SupportsInt, z: typing.SupportsInt, pixels: typing_extensions.Buffer) -> bool: ... + def write_tile(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt, pixels: typing_extensions.Buffer) -> bool: ... + def write_tiles(self, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt, zend: typing.SupportsInt, pixels: typing_extensions.Buffer) -> bool: ... @property def has_error(self) -> bool: ... @@ -1194,7 +1196,7 @@ class ImageSpec: @overload def __init__(self) -> None: ... @overload - def __init__(self, arg0: int, arg1: int, arg2: int, arg3: TypeDesc | BASETYPE | str, /) -> None: ... + def __init__(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt, arg3: TypeDesc | BASETYPE | str, /) -> None: ... @overload def __init__(self, arg0, arg1: TypeDesc | BASETYPE | str, /) -> None: ... @overload @@ -1202,9 +1204,9 @@ class ImageSpec: @overload def __init__(self, arg0: ImageSpec, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: float, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsFloat, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: int, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsInt, /) -> None: ... @overload def attribute(self, arg0: str, arg1: str, /) -> None: ... @overload @@ -1212,9 +1214,9 @@ class ImageSpec: @overload def channel_bytes(self) -> int: ... @overload - def channel_bytes(self, channel: int, native: bool = ...) -> int: ... - def channel_name(self, arg0: int, /) -> str: ... - def channelformat(self, arg0: int, /) -> TypeDesc: ... + def channel_bytes(self, channel: typing.SupportsInt, native: bool = ...) -> int: ... + def channel_name(self, arg0: typing.SupportsInt, /) -> str: ... + def channelformat(self, arg0: typing.SupportsInt, /) -> TypeDesc: ... def channelindex(self, arg0: str, /) -> int: ... def copy(self) -> ImageSpec: ... def copy_dimensions(self, other: ImageSpec) -> None: ... @@ -1224,8 +1226,8 @@ class ImageSpec: def get(self, key: str, default: object = ...) -> typing.Any: ... def get_bytes_attribute(self, name: str, defaultval: str = ...) -> bytes: ... def get_channelformats(self) -> tuple[TypeDesc, ...]: ... - def get_float_attribute(self, name: str, defaultval: float = ...) -> float: ... - def get_int_attribute(self, name: str, defaultval: int = ...) -> int: ... + def get_float_attribute(self, name: str, defaultval: typing.SupportsFloat = ...) -> float: ... + def get_int_attribute(self, name: str, defaultval: typing.SupportsInt = ...) -> int: ... def get_string_attribute(self, name: str, defaultval: str = ...) -> str: ... def getattribute(self, name: str, type: TypeDesc | BASETYPE | str = ...) -> typing.Any: ... @overload @@ -1238,7 +1240,7 @@ class ImageSpec: @overload def pixel_bytes(self, native: bool = ...) -> int: ... @overload - def pixel_bytes(self, chbegin: int, chend: int, native: bool = ...) -> int: ... + def pixel_bytes(self, chbegin: typing.SupportsInt, chend: typing.SupportsInt, native: bool = ...) -> int: ... @overload def scanline_bytes(self, native: bool = ...) -> int: ... @overload @@ -1253,7 +1255,7 @@ class ImageSpec: def tile_bytes(self, arg0: TypeDesc | BASETYPE | str, /) -> int: ... def tile_pixels(self) -> int: ... def to_xml(self) -> str: ... - def valid_tile_range(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int = ..., zend: int = ...) -> bool: ... + def valid_tile_range(self, xbegin: typing.SupportsInt, xend: typing.SupportsInt, ybegin: typing.SupportsInt, yend: typing.SupportsInt, zbegin: typing.SupportsInt = ..., zend: typing.SupportsInt = ...) -> bool: ... def __contains__(self, arg0: str, /) -> bool: ... def __delitem__(self, arg0: str, /) -> None: ... def __getitem__(self, arg0: str, /) -> object: ... @@ -1270,14 +1272,14 @@ class Interp: PERPIECE: ClassVar[Interp] = ... VERTEX: ClassVar[Interp] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -1288,14 +1290,14 @@ class InterpMode: Closest: ClassVar[InterpMode] = ... SmartBicubic: ClassVar[InterpMode] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -1307,14 +1309,14 @@ class MakeTextureMode: MakeTxShadow: ClassVar[MakeTextureMode] = ... MakeTxTexture: ClassVar[MakeTextureMode] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -1326,14 +1328,14 @@ class MipMode: OneLevel: ClassVar[MipMode] = ... Trilinear: ClassVar[MipMode] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -1343,28 +1345,28 @@ class NonFiniteFixMode: NONFINITE_BOX3: ClassVar[NonFiniteFixMode] = ... NONFINITE_NONE: ClassVar[NonFiniteFixMode] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... class ParamValue: @overload - def __init__(self, arg0: str, arg1: int, /) -> None: ... + def __init__(self, arg0: str, arg1: typing.SupportsInt, /) -> None: ... @overload - def __init__(self, arg0: str, arg1: float, /) -> None: ... + def __init__(self, arg0: str, arg1: typing.SupportsFloat, /) -> None: ... @overload def __init__(self, arg0: str, arg1: str, /) -> None: ... @overload def __init__(self, name: str, type: TypeDesc | BASETYPE | str, value: object) -> None: ... @overload - def __init__(self, name: str, type: TypeDesc | BASETYPE | str, nvalues: int, interp: Interp, value: object) -> None: ... + def __init__(self, name: str, type: TypeDesc | BASETYPE | str, nvalues: typing.SupportsInt, interp: Interp, value: object) -> None: ... @property def name(self) -> str: ... @property @@ -1379,29 +1381,29 @@ class ParamValueList: def add_or_replace(self, value: ParamValue, casesensitive: bool = ...) -> None: ... def append(self, arg0: ParamValue, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: float, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsFloat, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: int, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsInt, /) -> None: ... @overload def attribute(self, arg0: str, arg1: str, /) -> None: ... @overload def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: int, arg3: object, /) -> None: ... + def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: typing.SupportsInt, arg3: object, /) -> None: ... def clear(self) -> None: ... def contains(self, name: str, type: TypeDesc | BASETYPE | str = ..., casesensitive: bool = ...) -> bool: ... def free(self) -> None: ... def merge(self, other: ParamValueList, override: bool = ...) -> None: ... def remove(self, name: str, type: TypeDesc | BASETYPE | str = ..., casesensitive: bool = ...) -> None: ... - def resize(self, arg0: int, /) -> None: ... + def resize(self, arg0: typing.SupportsInt, /) -> None: ... def sort(self, casesensitive: bool = ...) -> None: ... def __contains__(self, arg0: str, /) -> bool: ... def __delitem__(self, arg0: str, /) -> None: ... @overload - def __getitem__(self, arg0: int, /) -> ParamValue: ... + def __getitem__(self, arg0: typing.SupportsInt, /) -> ParamValue: ... @overload def __getitem__(self, arg0: str, /) -> object: ... - def __iter__(self) -> Iterator[ParamValue]: ... + def __iter__(self) -> collections.abc.Iterator[ParamValue]: ... def __len__(self) -> int: ... def __setitem__(self, arg0: str, arg1: object, /) -> None: ... @@ -1439,15 +1441,15 @@ class ROI: @overload def __init__(self) -> None: ... @overload - def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, /) -> None: ... + def __init__(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt, arg3: typing.SupportsInt, /) -> None: ... @overload - def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, /) -> None: ... + def __init__(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt, arg3: typing.SupportsInt, arg4: typing.SupportsInt, arg5: typing.SupportsInt, /) -> None: ... @overload - def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, arg6: int, arg7: int, /) -> None: ... + def __init__(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt, arg3: typing.SupportsInt, arg4: typing.SupportsInt, arg5: typing.SupportsInt, arg6: typing.SupportsInt, arg7: typing.SupportsInt, /) -> None: ... @overload def __init__(self, arg0: ROI, /) -> None: ... @overload - def contains(self, x: int, y: int, z: int = ..., ch: int = ...) -> bool: ... + def contains(self, x: typing.SupportsInt, y: typing.SupportsInt, z: typing.SupportsInt = ..., ch: typing.SupportsInt = ...) -> bool: ... @overload def contains(self, other: ROI) -> bool: ... def copy(self) -> ROI: ... @@ -1490,9 +1492,9 @@ class TextureOpt: class TextureSystem: def __init__(self, shared: bool = ...) -> None: ... @overload - def attribute(self, arg0: str, arg1: float, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsFloat, /) -> None: ... @overload - def attribute(self, arg0: str, arg1: int, /) -> None: ... + def attribute(self, arg0: str, arg1: typing.SupportsInt, /) -> None: ... @overload def attribute(self, arg0: str, arg1: str, /) -> None: ... @overload @@ -1501,22 +1503,22 @@ class TextureSystem: def close_all(self) -> None: ... @staticmethod def destroy(arg0: TextureSystem, /) -> None: ... - def environment(self, filename: str, options: TextureOpt, R, dRdx, dRdy, nchannels: int) -> tuple[float, ...]: ... + def environment(self, filename: str, options: TextureOpt, R, dRdx, dRdy, nchannels: typing.SupportsInt) -> tuple[float, ...]: ... def getattribute(self, name: str, type: TypeDesc | BASETYPE | str = ...) -> typing.Any: ... def getattributetype(self, name: str) -> TypeDesc: ... def geterror(self, clear: bool = ...) -> str: ... - def getstats(self, level: int = ..., icstats: bool = ...) -> str: ... + def getstats(self, level: typing.SupportsInt = ..., icstats: bool = ...) -> str: ... def has_error(self) -> bool: ... - def imagespec(self, filename: str, subimage: int = ...) -> ImageSpec | None: ... + def imagespec(self, filename: str, subimage: typing.SupportsInt = ...) -> ImageSpec | None: ... def invalidate(self, filename: str, force: bool = ...) -> None: ... def invalidate_all(self, force: bool = ...) -> None: ... def inventory_udim(self, filename: str) -> tuple: ... def is_udim(self, filename: str) -> bool: ... def reset_stats(self) -> None: ... def resolve_filename(self, filename: str) -> str: ... - def resolve_udim(self, filename: str, s: float, t: float) -> str: ... - def texture(self, filename: str, options: TextureOpt, s: float, t: float, dsdx: float, dtdx: float, dsdy: float, dtdy: float, nchannels: int) -> tuple[float, ...]: ... - def texture3d(self, filename: str, options: TextureOpt, P, dPdx, dPdy, dPdz, nchannels: int) -> tuple[float, ...]: ... + def resolve_udim(self, filename: str, s: typing.SupportsFloat, t: typing.SupportsFloat) -> str: ... + def texture(self, filename: str, options: TextureOpt, s: typing.SupportsFloat, t: typing.SupportsFloat, dsdx: typing.SupportsFloat, dtdx: typing.SupportsFloat, dsdy: typing.SupportsFloat, dtdy: typing.SupportsFloat, nchannels: typing.SupportsInt) -> tuple[float, ...]: ... + def texture3d(self, filename: str, options: TextureOpt, P, dPdx, dPdy, dPdz, nchannels: typing.SupportsInt) -> tuple[float, ...]: ... class TypeDesc: aggregate: AGGREGATE @@ -1534,7 +1536,7 @@ class TypeDesc: @overload def __init__(self, arg0: BASETYPE, arg1: AGGREGATE, arg2: VECSEMANTICS, /) -> None: ... @overload - def __init__(self, arg0: BASETYPE, arg1: AGGREGATE, arg2: VECSEMANTICS, arg3: int, /) -> None: ... + def __init__(self, arg0: BASETYPE, arg1: AGGREGATE, arg2: VECSEMANTICS, arg3: typing.SupportsInt, /) -> None: ... @overload def __init__(self, arg0: str, /) -> None: ... def basesize(self) -> int: ... @@ -1568,14 +1570,14 @@ class VECSEMANTICS: TIMECODE: ClassVar[VECSEMANTICS] = ... VECTOR: ClassVar[VECSEMANTICS] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @@ -1590,29 +1592,29 @@ class Wrap: PeriodicPow2: ClassVar[Wrap] = ... PeriodicSharedBorder: ClassVar[Wrap] = ... __entries: ClassVar[dict] = ... - def __init__(self, value: int) -> None: ... + def __init__(self, value: typing.SupportsInt) -> None: ... def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... def __index__(self) -> int: ... def __int__(self) -> int: ... def __ne__(self, other: object) -> bool: ... @property - def name(self) -> str: ... + def name(self): ... @property def value(self) -> int: ... @overload -def attribute(arg0: str, arg1: float, /) -> None: ... +def attribute(arg0: str, arg1: typing.SupportsFloat, /) -> None: ... @overload -def attribute(arg0: str, arg1: int, /) -> None: ... +def attribute(arg0: str, arg1: typing.SupportsInt, /) -> None: ... @overload def attribute(arg0: str, arg1: str, /) -> None: ... @overload 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_float_attribute(name: str, defaultval: float = ...) -> float: ... -def get_int_attribute(name: str, defaultval: int = ...) -> int: ... +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: ... def get_roi_full(arg0: ImageSpec, /) -> ROI: ... def get_string_attribute(name: str, defaultval: str = ...) -> str: ... @@ -1621,7 +1623,7 @@ def geterror(clear: bool = ...) -> str: ... def intersection(arg0: ROI, arg1: ROI, /) -> ROI: ... def is_imageio_format_name(name: str) -> bool: ... def set_colorspace(spec: ImageSpec, name: str) -> None: ... -def set_colorspace_rec709_gamma(arg0: ImageSpec, arg1: float, /) -> None: ... +def set_colorspace_rec709_gamma(arg0: ImageSpec, arg1: typing.SupportsFloat, /) -> None: ... def set_roi(arg0: ImageSpec, arg1: ROI, /) -> None: ... def set_roi_full(arg0: ImageSpec, arg1: ROI, /) -> None: ... def union(arg0: ROI, arg1: ROI, /) -> ROI: ... From f9ffe3a6312a8f7ca8616047bb3dc598ad4aa929 Mon Sep 17 00:00:00 2001 From: Oktay Comu Date: Sat, 25 Oct 2025 22:04:36 -0400 Subject: [PATCH 042/508] feat(openexr): ACES Container hint for exr outputs (#4907) Closes #4791 This PR introduces the `oiio:ACESContainer` hint for OpenEXR outputs. The hint can take one of the following values: `none` (default), `strict`, or `relaxed`. If not `none`, the spec will be checked to see if it is compliant with the ACES Container format defined in [ST 2065-4](https://pub.smpte.org/pub/st2065-4/st2065-4-2023.pdf). If it is, chromaticities will be set to the ACES AP0 ones, and the acesImageContainerFlag attribute will be set to 1. In `strict` mode, if the spec is non-compliant, the output will throw an error and avoid writing the image. While in `relaxed` mode, if the spec in non-compliant, only a warning will be printed and the attributes mentioned above will not be written to the spec. I've added several tests under `openexr-suite`. These use oiiotool to attempt writing several EXRs with the `oiio:ACESContainer` hint. --------- Signed-off-by: glowies Signed-off-by: Oktay Comu Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/builtinplugins.rst | 14 ++ src/openexr.imageio/exroutput.cpp | 212 ++++++++++++++++++++++++++++ testsuite/openexr-suite/ref/out.txt | 28 ++++ testsuite/openexr-suite/run.py | 33 +++++ 4 files changed, 287 insertions(+) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 6658a4dcac..4a8edd4359 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1656,6 +1656,19 @@ control aspects of the writing itself: * - Output Configuration Attribute - Type - Meaning + * - ``openexr:ACESContainerPolicy`` + - string + - One of `none` (default), `strict`, or `relaxed`. + If not `none`, the spec will be checked to see if it is compliant + with the ACES Container format defined in `ST 2065-4`_. If it is, + `chromaticities` will be set to the ACES AP0 ones, `colorInteropId` + will be set to 'lin_ap0_scene' and the `acesImageContainerFlag` + attribute will be set to 1. + In `strict` mode, if the spec is non-compliant, the output will + throw an error and avoid writing the image. + While in `relaxed` mode, if the spec is non-compliant, `chromaticities` + and `colorInteropId` will be set, but `acesImageContainerFlag` + will NOT. * - ``oiio:RawColor`` - int - If nonzero, writing images with non-RGB color models (such as YCbCr) @@ -1667,6 +1680,7 @@ control aspects of the writing itself: - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for example by writing to a memory buffer. +.. _ST 2065-4: https://pub.smpte.org/pub/st2065-4/ **Custom I/O Overrides** diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index 92f1f0ed3d..60d19514fc 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -292,6 +292,198 @@ set_exr_threads() +static constexpr float ACES_AP0_chromaticities[8] = { + 0.7347f, 0.2653f, // red + 0.0f, 1.0f, // green + 0.0001f, -0.077f, // blue + 0.32168f, 0.33767f // white +}; + +static const std::string ACES_AP0_colorInteropId = "lin_ap0_scene"; + + +bool +is_spec_aces_container_channels_only(const OIIO::ImageSpec& spec) +{ + // Note: this is constructing and comparing sets, so that channel order + // doesn't matter. + + // Allowed channel sets + static const std::vector> allowed_sets + = { { "B", "G", "R" }, + { "A", "B", "G", "R" }, + { "B", "G", "R", "left.B", "left.G", "left.R" }, + { "A", "B", "G", "R", "left.A", "left.B", "left.G", "left.R" } }; + + // Gather channel set from spec + std::set channels(spec.channelnames.begin(), + spec.channelnames.end()); + + // Compare to allowed sets (unordered) + for (const auto& allowed : allowed_sets) { + if (channels == allowed) { + return true; + } + } + + return false; +} + + + +bool +is_aces_container_attributes_non_empty(const OIIO::ImageSpec& spec, + std::string& non_compliant_attr) +{ + // attributes in this list should NOT be empty if they exist + static const std::string nonEmptyAttribs[] = { + "cameraFirmwareVersion", + "cameraIdentifier", + "cameraLabel", + "cameraMake", + "cameraModel", + "cameraSerialNumber", + "comments", + "creator", + "lensAttributes", + "lensFirmwareVersion", + "lensMake", + "lensModel", + "lensSerialNumber", + "owner", + "recorderFirmwareVersion", + "recorderMake", + "recorderModel", + "recorderSerialNumber", + "reelName", + "storageMediaSerialNumber", + }; + + for (const auto& label : nonEmptyAttribs) { + const ParamValue* found = spec.find_attribute(label, + OIIO::TypeDesc::STRING); + if (found + && (found->type() != TypeString || found->get_string(1).empty())) { + non_compliant_attr = label; + return false; + } + } + + return true; +} + + + +bool +is_aces_container_compliant(const OIIO::ImageSpec& spec, std::string& reason) +{ + if (!is_spec_aces_container_channels_only(spec)) { + reason + = "Spec channel names do not match those required for an ACES Container."; + return false; + } + + // Check data type + if (spec.format != OIIO::TypeDesc::HALF) { + reason + = "EXR data type is not 'HALF' as required for an ACES Container."; + return false; + } + + // Check compression + std::string compression = spec.get_string_attribute("compression", "zip"); + if (compression != "none") { + reason = "Compression is not 'none' as required for an ACES Container."; + return false; + } + + // Check non-empty attributes + std::string non_compliant_attr = ""; + if (!is_aces_container_attributes_non_empty(spec, non_compliant_attr)) { + reason = "Spec contains an empty string attribute ("; + reason += non_compliant_attr; + reason += ") that is required to be non-empty in an ACES Container."; + return false; + } + + // Check attributes with exact values if they exist + if (spec.get_string_attribute("oiio:ColorSpace", ACES_AP0_colorInteropId) + != ACES_AP0_colorInteropId + || spec.get_string_attribute("colorInteropId", ACES_AP0_colorInteropId) + != ACES_AP0_colorInteropId) { + reason + = "Color space is not lin_ap0_scene as required for an ACES Container."; + return false; + } + + if (spec.get_int_attribute("acesImageContainerFlag", 1) != 1) { + reason + = "acesImageContainerFlag is not set to '1' as required for an ACES Container."; + return false; + } + + // Check chromaticities + float chromaticities[8] = { 0., 0., 0., 0., 0., 0., 0., 0. }; + bool chroms_found + = spec.getattribute("chromaticities", + OIIO::TypeDesc(OIIO::TypeDesc::FLOAT, 8), + chromaticities); + bool chroms_equal = std::equal(std::begin(chromaticities), + std::end(chromaticities), + std::begin(ACES_AP0_chromaticities)); + + if (chroms_found && !chroms_equal) { + reason + = "Chromaticities are not set to AP0 chromaticities as required for an ACES Container."; + return false; + } + + return true; +} + + + +void +set_aces_container_attributes(OIIO::ImageSpec& spec) +{ + spec.attribute("chromaticities", OIIO::TypeDesc(OIIO::TypeDesc::FLOAT, 8), + ACES_AP0_chromaticities); + spec.attribute("colorInteropId", ACES_AP0_colorInteropId); + spec.attribute("acesImageContainerFlag", 1); +} + + + +bool +process_aces_container(OIIO::ImageSpec& spec, std::string policy, + int acesImageContainerFlag, + std::string& non_compliance_reason) +{ + bool treat_as_aces_container = policy == "strict" + || acesImageContainerFlag == 1; + bool is_compliant = is_aces_container_compliant(spec, + non_compliance_reason); + + if (treat_as_aces_container && !is_compliant) { + return false; + } + + set_aces_container_attributes(spec); + + if (policy == "relaxed" && !is_compliant) { + // When image is not compliant in relaxed mode, we should avoid + // setting the flag, and we should print a warning + + // TODO: When we have a way to report warnings, report one here + // to indicate that the given image spec is not compliant + spec.erase_attribute("acesImageContainerFlag"); + } + + return true; +} + + + OpenEXROutput::OpenEXROutput() { pvt::set_exr_threads(); @@ -814,6 +1006,26 @@ OpenEXROutput::spec_to_header(ImageSpec& spec, int subimage, Imf::LevelMode(m_levelmode), Imf::LevelRoundingMode(m_roundingmode))); + // Check ACES Container hint + int aces_container_flag = spec.get_int_attribute("acesImageContainerFlag", + 0); + std::string aces_container_policy + = spec.get_string_attribute("openexr:ACESContainerPolicy", "none"); + + if (aces_container_policy != "none" || aces_container_flag == 1) { + std::string non_compliance_reason = ""; + bool should_panic = !process_aces_container(spec, aces_container_policy, + aces_container_flag, + non_compliance_reason); + + if (should_panic) { + errorfmt( + "Cannot output non-compliant ACES Container in 'strict' mode. REASON: {}", + non_compliance_reason); + return false; + } + } + // Deal with all other params for (const auto& p : spec.extra_attribs) put_parameter(p.name().string(), p.type(), p.data(), header); diff --git a/testsuite/openexr-suite/ref/out.txt b/testsuite/openexr-suite/ref/out.txt index 92feb01244..3c83a696e1 100644 --- a/testsuite/openexr-suite/ref/out.txt +++ b/testsuite/openexr-suite/ref/out.txt @@ -341,3 +341,31 @@ negoverscan.exr : 64 x 64, 3 channel, half openexr screenWindowWidth: 1 oiio:subimages: 1 openexr:lineOrder: "increasingY" +acesImageContainerFlag for relaxed-out.exr is (1) +acesImageContainerFlag for fail.exr is () +acesImageContainerFlag for fail.exr is () +acesImageContainerFlag for fail.exr is () +Reading strict-out.exr +strict-out.exr : 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + acesImageContainerFlag: 1 + chromaticities: 0.7347, 0.2653, 0, 1, 0.0001, -0.077, 0.32168, 0.33767 + colorInteropId: "lin_ap0_scene" + compression: "none" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap0_scene" + oiio:subimages: 1 + openexr:ACESContainerPolicy: "strict" + openexr:lineOrder: "increasingY" +oiiotool ERROR: -o : Cannot output non-compliant ACES Container in 'strict' mode. REASON: Spec channel names do not match those required for an ACES Container. +Full command line was: +> oiiotool --create 4x4 3 -d half --compression none --ch left.R=R,G,B -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr +oiiotool ERROR: -o : Cannot output non-compliant ACES Container in 'strict' mode. REASON: Compression is not 'none' as required for an ACES Container. +Full command line was: +> oiiotool --create 4x4 3 -d half --compression zip -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr +oiiotool ERROR: -o : Cannot output non-compliant ACES Container in 'strict' mode. REASON: EXR data type is not 'HALF' as required for an ACES Container. +Full command line was: +> oiiotool --create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr diff --git a/testsuite/openexr-suite/run.py b/testsuite/openexr-suite/run.py index 30269dac29..105cd03ac2 100755 --- a/testsuite/openexr-suite/run.py +++ b/testsuite/openexr-suite/run.py @@ -50,3 +50,36 @@ # Check writing overscan and negative range command += oiiotool("--create 64x64-16-16 3 -d half -o negoverscan.exr") command += info_command("negoverscan.exr", safematch=True) + +# Check ACES Container output for relaxed mode +# +# Valid ACES Container +command += oiiotool("--create 4x4 3 -d half --compression none -sattrib openexr:ACESContainerPolicy relaxed -o relaxed-out.exr") +command += oiiotool("relaxed-out.exr --echo \"acesImageContainerFlag for {TOP.filename} is ({TOP[acesImageContainerFlag]})\"", failureok=True) # should give 1 + +# Invalid channel name set +command += oiiotool("--create 4x4 3 -d half --compression none --ch left.R=R,G,B -sattrib openexr:ACESContainerPolicy relaxed -o fail.exr") +command += oiiotool("fail.exr --echo \"acesImageContainerFlag for {TOP.filename} is ({TOP[acesImageContainerFlag]})\"", failureok=True) # should be empty + +# Invalid compression +command += oiiotool("--create 4x4 3 -d half --compression zip -sattrib openexr:ACESContainerPolicy relaxed -o fail.exr") +command += oiiotool("fail.exr --echo \"acesImageContainerFlag for {TOP.filename} is ({TOP[acesImageContainerFlag]})\"", failureok=True) # should be empty + +# Invalid data type +command += oiiotool("--create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy relaxed -o fail.exr") +command += oiiotool("fail.exr --echo \"acesImageContainerFlag for {TOP.filename} is ({TOP[acesImageContainerFlag]})\"", failureok=True) # should be empty + +# Check ACES Container output for strict mode +# +# Valid ACES Container +command += oiiotool("--create 4x4 3 -d half --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-out.exr") +command += info_command("strict-out.exr", safematch=True) + +# Invalid channel name set +command += oiiotool("--create 4x4 3 -d half --compression none --ch left.R=R,G,B -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr", failureok=True) + +# Invalid compression +command += oiiotool("--create 4x4 3 -d half --compression zip -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr", failureok=True) + +# Invalid data type +command += oiiotool("--create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr", failureok=True) From 6312a141f782c5c57544454cffe986afc2a5a85e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 26 Oct 2025 15:10:26 -0700 Subject: [PATCH 043/508] ci: simplify ci workflow by using build-steps for old aswf containers, too (#4932) Trick learned from the rawtoaces CI workflow --------- Signed-off-by: Larry Gritz Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 12 ++- .github/workflows/ci.yml | 134 +++++++++--------------------- 2 files changed, 52 insertions(+), 94 deletions(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index 1df1ccaec3..774f360b27 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -18,6 +18,9 @@ on: type: string container: type: string + container_volumes: + type: string + default: '[]' cc_compiler: type: string cxx_compiler: @@ -66,6 +69,9 @@ on: type: string sonar: type: string + old_node: + type: string + default: 0 nametag: type: string required_deps: @@ -88,7 +94,7 @@ jobs: runs-on: ${{ inputs.runner }} container: image: ${{ inputs.container }} - + volumes: ${{ fromJson( inputs.container_volumes ) }} env: CXX: ${{inputs.cxx_compiler}} CC: ${{inputs.cc_compiler}} @@ -114,6 +120,10 @@ jobs: OpenImageIO_OPTIONAL_DEPS: ${{inputs.optional_deps}} steps: + - name: install nodejs20glibc2.17 + if: inputs.old_node == '1' + run: | + curl --silent https://unofficial-builds.nodejs.org/download/release/v20.18.1/node-v20.18.1-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 -f - - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Build setup diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9e5d4e361..e023860bbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,41 @@ jobs: aswf-old: if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "(old) ${{matrix.desc}}" + uses: ./.github/workflows/build-steps.yml + with: + nametag: ${{ matrix.nametag || 'unnamed!' }} + runner: ${{ matrix.runner || 'ubuntu-latest' }} + container: ${{ matrix.container }} + container_volumes: '["/node20217:/node20217:rw,rshared", "/node20217:/__e/node20:ro,rshared]"]' + cc_compiler: ${{ matrix.cc_compiler }} + cxx_compiler: ${{ matrix.cxx_compiler }} + cxx_std: ${{ matrix.cxx_std || '17' }} + build_type: ${{ matrix.build_type || 'Release' }} + depcmds: ${{ matrix.depcmds }} + extra_artifacts: ${{ matrix.extra_artifacts }} + fmt_ver: ${{ matrix.fmt_ver }} + opencolorio_ver: ${{ matrix.opencolorio_ver }} + openexr_ver: ${{ matrix.openexr_ver }} + pybind11_ver: ${{ matrix.pybind11_ver }} + python_ver: ${{ matrix.python_ver }} + setenvs: ${{ matrix.setenvs }} + simd: ${{ matrix.simd }} + skip_build: ${{ matrix.skip_build }} + skip_tests: ${{ matrix.skip_tests }} + abi_check: ${{ matrix.abi_check }} + benchmark: ${{ matrix.benchmark }} + build_docs: ${{ matrix.build_docs }} + clang_format: ${{ matrix.clang_format }} + generator: ${{ matrix.generator }} + ctest_args: ${{ matrix.ctest_args }} + ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + coverage: ${{ matrix.coverage || 0 }} + sonar: ${{ matrix.sonar || 0 }} + old_node: ${{ matrix.old_node || 0 }} + # Override required_deps to be 'all' and explicitly list as optional + # only the ones we are intentionally not testing for those jobs. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'DCMTK;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} strategy: fail-fast: false matrix: @@ -62,6 +97,7 @@ jobs: setenvs: export FREETYPE_VERSION=VER-2-12-0 BUILD_PNG_VERSION=1.6.30 WebP_BUILD_VERSION=1.5.0 + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - desc: VP2022 clang13/C++17 py39 avx2 exr3.1 ocio2.3 nametag: linux-vfx2022.clang13 runner: ubuntu-latest @@ -78,6 +114,7 @@ jobs: fmt_ver: 9.1.0 setenvs: export FREETYPE_VERSION=VER-2-12-0 BUILD_PNG_VERSION=1.6.30 + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - desc: oldest gcc9.3/C++17 py3.9 exr3.1 ocio2.3 # Oldest gcc and versions of the dependencies that we support. nametag: linux-oldest @@ -97,6 +134,7 @@ jobs: PUGIXML_VERSION=v1.8 BUILD_PNG_VERSION=1.6.0 depcmds: sudo rm -rf /usr/local/include/OpenEXR + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - desc: oldest clang10/C++17 py3.9 exr3.1 ocio2.3 # Oldest clang and versions of the dependencies that we support. nametag: linux-oldest-clang @@ -118,6 +156,7 @@ jobs: PUGIXML_VERSION=v1.8 BUILD_PNG_VERSION=1.6.0 depcmds: sudo rm -rf /usr/local/include/OpenEXR + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - desc: hobbled gcc9.3/C++17 py3.9 exr-3.1 no-sse # Use the oldest supported versions of required dependencies, and # disable most optional dependencies and features (no SSE or @@ -144,99 +183,7 @@ jobs: PUGIXML_VERSION=v1.8 BUILD_PNG_VERSION=1.6.0 depcmds: sudo rm -rf /usr/local/include/OpenEXR - - runs-on: ${{ matrix.runner }} - container: - image: ${{ matrix.container }} - volumes: - - /node20217:/node20217:rw,rshared - - /node20217:/__e/node20:ro,rshared - env: - CXX: ${{matrix.cxx_compiler}} - CC: ${{matrix.cc_compiler}} - CMAKE_CXX_STANDARD: ${{matrix.cxx_std}} - USE_SIMD: ${{matrix.simd}} - FMT_VERSION: ${{matrix.fmt_ver}} - OPENCOLORIO_VERSION: ${{matrix.opencolorio_ver}} - OPENEXR_VERSION: ${{matrix.openexr_ver}} - PYBIND11_VERSION: ${{matrix.pybind11_ver}} - PYTHON_VERSION: ${{matrix.python_ver}} - ABI_CHECK: ${{matrix.abi_check}} - steps: - # Install nodejs 20 with glibc 2.17, to work around the face that the - # GHA runners are insisting on a node version that is too new for the - # glibc in the ASWF containers prior to 2023. - - name: install nodejs20glibc2.17 - if: matrix.old_node == '1' - run: | - curl --silent https://unofficial-builds.nodejs.org/download/release/v20.18.1/node-v20.18.1-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 -f - - # We would like to use harden-runner, but it flags too many false - # positives, every time we download a dependency. We should use it only - # on CI runs where we are producing artifacts that users might rely on. - # - name: Harden Runner - # uses: step-security/harden-runner@248ae51c2e8cc9622ecf50685c8bf7150c6e8813 # v1.4.3 - # with: - # egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - name: Checkout repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Build setup - run: | - ${{matrix.setenvs}} - src/build-scripts/ci-startup.bash - - name: Prepare ccache timestamp - id: ccache_cache_keys - shell: bash - run: echo "date=`date -u +'%Y-%m-%dT%H:%M:%SZ'`" >> $GITHUB_OUTPUT - - name: ccache-restore - id: ccache-restore - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: ${{ env.CCACHE_DIR }} - # path: ./ccache - key: ${{matrix.nametag}}-${{steps.ccache_cache_keys.outputs.date}} - restore-keys: ${{matrix.nametag}} - - name: Dependencies - run: | - ${{matrix.depcmds}} - src/build-scripts/gh-installdeps.bash - - name: Build - if: matrix.skip_build != '1' - run: src/build-scripts/ci-build.bash - - name: ccache-save - id: ccache-save - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 - with: - path: ${{ env.CCACHE_DIR }} - key: ${{matrix.nametag}}-${{steps.ccache_cache_keys.outputs.date}} - - name: Testsuite - if: matrix.skip_tests != '1' - run: src/build-scripts/ci-test.bash - - name: Benchmarks - if: matrix.benchmark == '1' - shell: bash - run: src/build-scripts/ci-benchmark.bash - - name: Build Docs - if: matrix.build_docs == '1' - run: | - cd src/doc - time make doxygen - time make sphinx - - name: Upload testsuite debugging artifacts - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - if: ${{ failure() || matrix.build_docs == '1'}} - with: - name: oiio-${{github.job}}-${{matrix.nametag}} - path: | - build/cmake-save - build/compat_reports - build/sphinx - build/benchmarks - build/testsuite/*/*.* - !build/testsuite/oiio-images - !build/testsuite/openexr-images - !build/testsuite/fits-images - !build/testsuite/j2kp4files_v1_5 - + required_deps: none # @@ -250,6 +197,7 @@ jobs: nametag: ${{ matrix.nametag || 'unnamed!' }} runner: ${{ matrix.runner || 'ubuntu-latest' }} container: ${{ matrix.container }} + container_volumes: ${{ matrix.container_volumes || '[]' }} cc_compiler: ${{ matrix.cc_compiler }} cxx_compiler: ${{ matrix.cxx_compiler }} cxx_std: ${{ matrix.cxx_std || '17' }} From 8118308648c4ab2d4b5ba6f68a77b10d22f710b9 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 27 Oct 2025 00:02:59 -0700 Subject: [PATCH 044/508] fix: uninitialized value revealed by clang-21 warning (#4940) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/iconvert/iconvert.cpp | 2 +- src/oiiotool/oiiotool.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iconvert/iconvert.cpp b/src/iconvert/iconvert.cpp index bcc8aef7ca..e794a8fbf1 100644 --- a/src/iconvert/iconvert.cpp +++ b/src/iconvert/iconvert.cpp @@ -152,7 +152,7 @@ DateTime_to_time_t(string_view datetime, time_t& timet) return false; // OIIO::print("{}:{}:{} {}:{}:{}\n", year, month, day, hour, min, sec); struct tm tmtime; - time_t now; + time_t now = time(nullptr); Sysutil::get_local_time(&now, &tmtime); // fill in defaults tmtime.tm_sec = sec; tmtime.tm_min = min; diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index b28a04f152..3c33cf2e50 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -1046,7 +1046,7 @@ DateTime_to_time_t(string_view datetime, time_t& timet) return false; // OIIO::print("{}:{}:{} {}:{}:{}\n", year, month, day, hour, min, sec); struct tm tmtime; - time_t now; + time_t now = time(nullptr); Sysutil::get_local_time(&now, &tmtime); // fill in defaults tmtime.tm_sec = sec; tmtime.tm_min = min; From dd546e82d50be2abd286bfd2c779d9ed2aa7fea3 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 28 Oct 2025 13:03:11 -0700 Subject: [PATCH 045/508] ci: We were not correctly setting fmt version from job options (#4939) Ugh, used the wrong option name. Also, it's sufficient to just do it with the env variable, don't need extra logic in ci-build.bash. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 2 +- src/build-scripts/ci-build.bash | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index 774f360b27..403d94e7b0 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -104,7 +104,7 @@ jobs: CTEST_ARGS: ${{inputs.ctest_args}} CTEST_TEST_TIMEOUT: ${{inputs.ctest_test_timeout}} USE_SIMD: ${{inputs.simd}} - FMT_VERSION: ${{inputs.fmt_ver}} + fmt_BUILD_VERSION: ${{inputs.fmt_ver}} OPENCOLORIO_VERSION: ${{inputs.opencolorio_ver}} OPENEXR_VERSION: ${{inputs.openexr_ver}} PYBIND11_VERSION: ${{inputs.pybind11_ver}} diff --git a/src/build-scripts/ci-build.bash b/src/build-scripts/ci-build.bash index 8afdb39f82..a4a439e998 100755 --- a/src/build-scripts/ci-build.bash +++ b/src/build-scripts/ci-build.bash @@ -18,10 +18,6 @@ if [[ "$USE_SIMD" != "" ]] ; then OIIO_CMAKE_FLAGS="$OIIO_CMAKE_FLAGS -DUSE_SIMD=$USE_SIMD" fi -if [[ -n "$FMT_VERSION" ]] ; then - OIIO_CMAKE_FLAGS="$OIIO_CMAKE_FLAGS -DBUILD_FMT_VERSION=$FMT_VERSION" -fi - if [[ -n "$CODECOV" ]] ; then OIIO_CMAKE_FLAGS="$OIIO_CMAKE_FLAGS -DCODECOV=${CODECOV}" fi From 8fcfbe58ca91798a0deb01e6c9b18d790bc28da8 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 30 Oct 2025 22:42:05 -0700 Subject: [PATCH 046/508] testing: Update ref images for heif (#4941) I think that in a recent checkin, we accidentally made some ref outputs for this test be identical, that were supposed to be for when libheif is built without avif support. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- testsuite/heif/ref/out-libheif1.9-alt2.txt | 26 ---------------------- testsuite/heif/ref/out-libheif1.9.txt | 26 ---------------------- 2 files changed, 52 deletions(-) diff --git a/testsuite/heif/ref/out-libheif1.9-alt2.txt b/testsuite/heif/ref/out-libheif1.9-alt2.txt index 3fa6718e28..e24120d453 100644 --- a/testsuite/heif/ref/out-libheif1.9-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-alt2.txt @@ -39,32 +39,6 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif Exif:SubsecTimeOriginal: "006" Exif:WhiteBalance: 0 (auto) oiio:ColorSpace: "srgb_rec709_scene" -Reading ref/Chimera-AV1-8bit-162.avif -ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif - SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 - channel list: R, G, B - oiio:ColorSpace: "srgb_rec709_scene" -Reading ref/test-10bit.avif -ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif - SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA - channel list: R, G, B, A - Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" - Exif:ExifVersion: "0230" - Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" - heif:UnassociatedAlpha: 1 - oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" -Reading cicp_pq.avif -cicp_pq.avif : 16 x 16, 4 channel, uint10 heif - SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 - channel list: R, G, B, A - CICP: 9, 16, 9, 1 - Exif:ExifVersion: "0230" - Exif:FlashPixVersion: "0100" - heif:UnassociatedAlpha: 1 - oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C diff --git a/testsuite/heif/ref/out-libheif1.9.txt b/testsuite/heif/ref/out-libheif1.9.txt index 87614ec80c..2ddc23c253 100644 --- a/testsuite/heif/ref/out-libheif1.9.txt +++ b/testsuite/heif/ref/out-libheif1.9.txt @@ -39,32 +39,6 @@ ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif Exif:SubsecTimeOriginal: "006" Exif:WhiteBalance: 0 (auto) oiio:ColorSpace: "srgb_rec709_scene" -Reading ref/Chimera-AV1-8bit-162.avif -ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif - SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 - channel list: R, G, B - oiio:ColorSpace: "srgb_rec709_scene" -Reading ref/test-10bit.avif -ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif - SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA - channel list: R, G, B, A - Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" - Exif:ExifVersion: "0230" - Exif:FlashPixVersion: "0100" - Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" - heif:UnassociatedAlpha: 1 - oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" -Reading cicp_pq.avif -cicp_pq.avif : 16 x 16, 4 channel, uint10 heif - SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 - channel list: R, G, B, A - CICP: 9, 16, 9, 1 - Exif:ExifVersion: "0230" - Exif:FlashPixVersion: "0100" - heif:UnassociatedAlpha: 1 - oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E From 9d24c73504083379aedc1155aa004a0b126f48f1 Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Fri, 31 Oct 2025 12:39:14 -0700 Subject: [PATCH 047/508] admin: Correct spelling in build_problem.md template (#4945) Small typo in our `build_problem.md` github template. Signed-off-by: Jesse Yurkovich Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/ISSUE_TEMPLATE/build_problem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/build_problem.md b/.github/ISSUE_TEMPLATE/build_problem.md index b1dfee9d50..8de9b6aa0d 100644 --- a/.github/ISSUE_TEMPLATE/build_problem.md +++ b/.github/ISSUE_TEMPLATE/build_problem.md @@ -7,7 +7,7 @@ assignees: '' --- -**PLEASE DO NOT REPORT BUILD TROUBLES AS GITHUB "ISSUES" UNLESS YOU ARE RALLY SURE IT'S A BUG** +**PLEASE DO NOT REPORT BUILD TROUBLES AS GITHUB "ISSUES" UNLESS YOU ARE REALLY SURE IT'S A BUG** The best way to get help with your build problems is to ask a question on the [oiio-dev developer mail list](https://lists.aswf.io/g/oiio-dev). From 5cc1719fc7d76a973d1e528047b5de1d19e1ee33 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 31 Oct 2025 12:39:42 -0700 Subject: [PATCH 048/508] docs: Update/correct explanation of "openexr:core" attribute, and typo fixes (#4943) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/imagebufalgo.h | 12 ++++++------ src/include/OpenImageIO/imageio.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index f70cbf72e6..4e9c80fcdc 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -307,7 +307,7 @@ bool OIIO_API render_line (ImageBuf &dst, int x1, int y1, int x2, int y2, /// as many values as `roi.chend-1`. The ROI can be used to limit the pixel /// area or channels that are modified, and default to the entirety of /// `dst`. If `fill` is `true`, the box will be completely filled in, -/// otherwise only its outlien will be drawn. +/// otherwise only its outline will be drawn. bool OIIO_API render_box (ImageBuf &dst, int x1, int y1, int x2, int y2, cspan color=1.0f, bool fill = false, ROI roi={}, int nthreads=0); @@ -542,7 +542,7 @@ bool OIIO_API transpose (ImageBuf &dst, const ImageBuf &src, /// @} -/// Return (or store into `dst`) a copy of `src`, but with whatever seties +/// Return (or store into `dst`) a copy of `src`, but with whatever series /// of rotations, flips, or flops are necessary to transform the pixels into /// the configuration suggested by the "Orientation" metadata of the image /// (and the "Orientation" metadata is then set to 1, ordinary orientation). @@ -1246,7 +1246,7 @@ OIIO_API bool contrast_remap (ImageBuf &dst, const ImageBuf &src, /// `src` within the ROI, and in the process adjusts the color saturation of /// the three consecutive channels starting with `firstchannel` based on the /// `scale` parameter: 0.0 fully desaturates to a greyscale image of -/// percaptually equivalent luminance, 1.0 leaves the colors unchanged, +/// perceptually equivalent luminance, 1.0 leaves the colors unchanged, /// `scale` values inside this range interpolate between them, and `scale` > 1 /// would increase apparent color saturation. /// @@ -2117,14 +2117,14 @@ bool OIIO_API ocionamedtransform (ImageBuf &dst, const ImageBuf &src, /// `src` within the ROI, and in the process divides all color channels /// (those not alpha or z) by the alpha value, to "un-premultiply" them. /// This presumes that the image starts of as "associated alpha" a.k.a. -/// "premultipled," and you are converting to "unassociated alpha." For +/// "premultiplied," and you are converting to "unassociated alpha." For /// pixels with alpha == 0, the color values are not modified. /// /// The `premult` operation returns (or copies into `dst`) the pixels of /// `src` within the ROI, and in the process multiplies all color channels /// (those not alpha or z) by the alpha value, to "premultiply" them. This /// presumes that the image starts of as "unassociated alpha" a.k.a. -/// "non-premultipled" and converts it to "associated alpha / premultipled." +/// "non-premultiplied" and converts it to "associated alpha / premultiplied." /// /// The `repremult` operation is like `premult`, but preserves the color /// values of pixels whose alpha is 0. This is intended for cases where you @@ -2531,7 +2531,7 @@ bool OIIO_API deep_merge (ImageBuf &dst, const ImageBuf &A, /// Return the samples of deep image `src` that are closer than the opaque /// frontier of deep image holdout, returning true upon success and false /// for any failures. Samples of `src` that are farther than the first -/// opaque sample of holdout (for the corresponding pixel)will not be copied +/// opaque sample of holdout (for the corresponding pixel) will not be copied /// to `dst`. Image holdout is only used as the depth threshold; no sample /// values from holdout are themselves copied to `dst`. ImageBuf OIIO_API deep_holdout (const ImageBuf &src, const ImageBuf &holdout, diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index 1002d8cf0b..e6c1ab0552 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -3825,8 +3825,8 @@ OIIO_API std::string geterror(bool clear = true); /// /// - `int openexr:core` /// -/// When nonzero, use the new "OpenEXR core C library" when available, -/// for OpenEXR >= 3.1. This is experimental, and currently defaults to 0. +/// When nonzero, use the new "OpenEXR core C library" when available. +/// The default is 1 for OpenEXR >= 3.1.10, 0 for older OpenEXR releases. /// /// - `int jpeg:com_attributes` /// From 3c57ad3b80471b9b6bdb5b7731091235d629c9a3 Mon Sep 17 00:00:00 2001 From: Jonathan Brown Date: Fri, 31 Oct 2025 19:58:03 +0000 Subject: [PATCH 049/508] fix(JXL): Correctly set Quality for JPEG XL (#4933) Distance in JPEG XL isn't linear, causing previous quality settings to scale between lossless and visually lossless, instead of lossless and the lowest quality. Quality | libjxl Distance | Old OIIO Distance |--------|------|------| | 99 | 0.19 | 0.01 | | 90 | 1.00 | 0.01 | | 80 | 1.90 | 0.01 | | 70 | 2.80 | 0.01 | | 60 | 3.70 | 0.02 | | 50 | 4.60 | 0.02 | | 40 | 5.50 | 0.03 | | 30 | 6.40 | 0.03 | | 20 | 8.13 | 0.05 | | 10 | 13.27 | 0.10 | | 1 | 24.24 | 1.00 | | 0 | 25.00 | Error | Thankfully libjxl has a built in function to convert Quality to Distance, so I've swapped it in. Unable to test it myself, but it's a simple fix that I ran by another libjxl dev too. --------- Signed-off-by: Jonathan Brown Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/jpegxl.imageio/jxloutput.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index c5802e3b27..de82467fa7 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -228,9 +228,9 @@ JxlOutput::open(const std::string& name, const ImageSpec& newspec, lossless = true; } else { m_basic_info.uses_original_profile = JXL_FALSE; - JxlEncoderSetFrameDistance( - m_frame_settings, - 1.0f / static_cast(compqual.second)); + const float distance = JxlEncoderDistanceFromQuality( + compqual.second); + JxlEncoderSetFrameDistance(m_frame_settings, distance); JxlEncoderSetFrameLossless(m_frame_settings, JXL_FALSE); } } else { // default to lossless From 14b4b658f4c5dc81b8fe6a28150e6a30c9469854 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 1 Nov 2025 17:09:54 -0700 Subject: [PATCH 050/508] CHANGES updates (#4947) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CHANGES.md | 80 ++++++++++++++++++++++++++++++++++++++++-------------- CREDITS.md | 3 ++ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8d1b9cf96b..6d328cfc94 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,20 +13,60 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) ### 🚀 Performance improvements ### 🐛 Fixes and feature enhancements - - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.2.0.0) + - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.1.7.0, 3.2.0.0) + - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.1.7.0, 3.2.0.0) + - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.2.0.0) + - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.1.7.0, 3.2.0.0) + - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.1.7.0, 3.2.0.0) + - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.1.7.0, 3.2.0.0) ### 🔧 Internals and developer goodies ### 🏗 Build/test/CI and platform ports * OIIO's CMake build system and scripts: + - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.1.7.0, 3.2.0.0) + - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.1.7.0, 3.2.0.0) * Dependency and platform support: + - *build/deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.1.7.0, 3.2.0.0) * Testing and Continuous integration (CI) systems: + - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.1.7.0, 3.2.0.0) + - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.1.7.0, 3.2.0.0) + - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.1.7.0, 3.2.0.0) ### 📚 Notable documentation changes + - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.1.7.0, 3.2.0.0) ### 🏢 Project Administration ### 🤝 Contributors - --- --- + + +Release 3.1.7.0 (Nov 1, 2025) -- compared to 3.1.7.0 +---------------------------------------------------- + - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.1.7.0) + - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.1.7.0) + - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.1.7.0) + - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.1.7.0) + - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.1.7.0) + - *api/docs*: Fix IBA::set_pixels declaration and docs [#4926](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4926) (3.1.7.0) + - *win*: Address Windows crashes from issue 4641 [#4914](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4914) (3.1.7.0) + - *fix*: Uninitialized value revealed by clang-21 warning [#4940](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4940) (3.1.7.0) + - *build/deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.1.7.0) + - *build*: Allow auto-build of just required packages [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.1.7.0) + - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.1.7.0) + - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.1.7.0) + - *ci*: Drop deprecated macos-13 (intel) platform, add macos-15-intel [#4930](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4930) (3.1.7.0) + - *ci*: Try to avoid ffmpeg install failures [#4936](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4936) (3.1.7.0) + - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.1.7.0) + - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.1.7.0) + - *tests*: Update ref images for heif [#4941](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4941) (3.1.7.0) + - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.1.7.0) + + +Release 3.1.6.2 (Oct 3, 2025) -- compared to 3.1.6.1 +---------------------------------------------------- + - *oiioversion.h*: Restore definition of `OIIO_NAMESPACE_USING` macro [#4920](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4920) + + Release 3.1 (Oct 2 2025) -- compared to 3.0.x ----------------------------------------------------- - Beta 1: Aug 22, 2025 @@ -197,13 +237,13 @@ Release 3.1 (Oct 2 2025) -- compared to 3.0.x ### 🏗 Build/test/CI and platform ports: * CMake build system and scripts: + - *build*: Python wheel building workflow [#4428](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4428) (by zachlewis) (3.1.1.0), [#4633](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4633) (3.1.1.0), [#4820](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4820) (3.1.3.0), [#4617](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4617) (3.1.1.0), [#4668](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4668) (3.1.1.0), [#4675](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4675) (3.1.1.0), [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) (3.1.5.0), [#4867](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4867) (3.1.5.0) + - *build*: C++23 support [#4844](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4844) (3.1.4.0) - *build*: Add hardening options [#4538](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4538) (3.1.0.0/3.0.1.0) - *build*: Use target_compile_options (fixes a LibRaw build issue) [#4556](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4556) (by Don Olmstead) (3.1.0.0) - - *build*: Python wheels workflow and build backend [#4428](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4428) (by zachlewis) (3.1.1.0) - *build*: Recent change broke when using non-Apple clang on Apple [#4596](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4596) (3.1.1.0) - *build*: Fix recently broken rpath setting [#4618](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4618) (3.1.1.0) - *build*: Improve OpenJpeg version detection. [#4665](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4665) (by jreichel-nvidia) (3.1.1.0) - - *build*: Fix recently broken rpath to restore python wheel building [#4633](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4633) (3.1.1.0) - *build*: Ensure python-based builds use maj.min.patch SO versioning [#4634](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4634) (by zachlewis) (3.1.1.0) - *build*: Better disabling of work when USE_PYTHON=0 [#4657](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4657) (3.1.1.0) - *build*: Bump auto-build libdeflate to 1.23 to avoid AVX512 not available errors [#4679](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4679) (by LI JI) (3.1.1.0) @@ -215,9 +255,7 @@ Release 3.1 (Oct 2 2025) -- compared to 3.0.x - *build*: Address compiler warnings [#4724](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4724) (3.1.3.0) - *build*: Fix furo requirement after recently breaking docs [#4743](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4743) (3.1.3.0) - *build*: Clean up Windows compilation warnings [#4706](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4706) (3.1.3.0) - - *build/python*: Wheel upload_pypi step should only run from main repo [#4820](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4820) (3.1.3.0) - *build*: Fix typo related to finding ccache [#4833](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4833) (3.1.4.0) - - *build*: C++23 support [#4844](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4844) (3.1.4.0) - *build*: Clean up obsolete logic for old compilers [#4849](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4849) (3.1.5.0) - *build*: Update autobuild defaults for some dependencies [#4910](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4910) (3.1.6.1) * Dependency and platform support: @@ -262,23 +300,17 @@ Release 3.1 (Oct 2 2025) -- compared to 3.0.x - *ci*: Test and document support for WebP 1.5 and fmt 11.1 [#4574](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4574) (3.1.1.0) - *ci*: Only pass build-steps the secrets it needs [#4576](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4576) (3.1.1.0) - *ci*: Fix Windows 2019 CI -- make python version match the runner [#4592](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4592) (3.1.1.0) - - *ci*: Raise 'latest' tests to use new fmt 11.1.2 [#4593](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4593) (3.1.1.0) - - *ci*: Adjust some pugixml versions [#4594](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4594) (3.1.1.0) + - *ci*: CI testing of new dependency releases: fmt 11.1.2 [#4593](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4593), various [#4683](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4683) (3.1.1.0), pugixml versions [#4594](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4594) (3.1.1.0), pybind11 3.0.0 [#4828](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4828) (3.1.4.0), webp and openexr [#4861](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4861) (3.1.4.0) - *ci*: Allow special branch names to prune CI jobs [#4604](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4604) (3.1.1.0) - - *ci*: Unbreak the scorecard workflow [#4605](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4605) (3.1.1.0) + - *ci*: Unbreak broken CI (mostly broken by random GH runner changes): scorecard workflow [#4605](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4605) (3.1.1.0) - *ci*: Switch to using ARM-native linux runners [#4616](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4616) (by zachlewis) (3.1.1.0) - - *ci*: Run wheel workflow on certain pushes [#4617](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4617) (3.1.1.0) - *ci*: Add `numpy` as a runtime requirement [#4638](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4638) (by zachlewis) (3.1.1.0) - *ci*: Move away from soon-to-be-deprecated ubuntu-20.04 GHA runner [#4636](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4636) (3.1.1.0) - *ci*: For docs workflow, lock down versions and speed up [#4646](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4646) (3.1.1.0) - *ci*: Improved clang-format CI task [#4647](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4647) (3.1.1.0) - *ci*: Add numpy as a runtime requirement [#4654](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4654) (by zachlewis) (3.1.1.0) - - *ci*: Fix wheel building on Mac [#4668](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4668) (3.1.1.0) - *ci*: Update libPNG address and version for ci & autobuild [#4659](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4659) (3.1.1.0) - - *ci*: Step naming adustments [#4658](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4658) (3.1.1.0) - - *ci*: Fix wheel building on Mac ARM (continuation of #4668) [#4675](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4675) (3.1.1.0) - - *ci*: Bump dependencies for "latest versions" CI test [#4683](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4683) (3.1.1.0) - - *ci*: Update ABI standard after PR 4669 [#4687](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4687) (3.1.1.0) + - *ci*: Step naming adjustments [#4658](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4658) (3.1.1.0) - *ci*: Save time by not checking out entire project history [#4731](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4731) (3.1.3.0) - *ci*: New variants for VFXP 2025, Windows 2025 [#4744](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4744) (3.1.3.0) - *ci*: Bump abi_check standard after ImageOutput changes [#4747](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4747) (3.1.3.0) @@ -286,19 +318,14 @@ Release 3.1 (Oct 2 2025) -- compared to 3.0.x - *ci*: Update ref output to compensate for GitHub windows drive changes [#4761](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4761) (3.1.3.0) - *ci*: Pkg config libdir fix [#4775](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4775) (by Scott Wilson) (3.1.3.0) - *ci*: Add facility for benchmarking as part of CI [#4745](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4745) (3.1.3.0) - - *ci*: Adjust ABI standard commit after ABI-breaking change of PR 4748 [#4778](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4778) (3.1.3.0) - *ci*: Add Linux ARM test [#4749](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4749) (3.1.3.0) - *ci*: Update linux arm clang reference output [#4782](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4782) (3.1.3.0) - - *ci*: Bump 'latest releases' tests to use pybind11 3.0.0 [#4828](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4828) (3.1.4.0) - *ci*: For python stub generation, lock pybind11 to pre-3.0 [#4831](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4831) (3.1.4.0) - *ci*: Add a VFX Platform 2026 CI job [#4856](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4856) (3.1.4.0) - *ci*: Lock down to ci-oiio container with correct llvm components [#4859](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4859) (3.1.4.0) - - *ci*: Bump webp and openexr for "latest versions" test [#4861](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4861) (3.1.4.0) - *ci*: Try to fix Sonar workflow by switching to compile-commands for sonar [#4879](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4879) (by vvalderrv) (3.1.5.0) - *ci*: Fix analysis workflow configuration [#4881](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4881) (3.1.5.0) - *ci*: Better spread of libpng versions we test against [#4883](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4883) (3.1.5.0) - - *ci*: Fix broken python wheel building [#4855](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4855) (by Zach Lewis) (3.1.5.0) - - *ci*: Some more minor wheel workflow changes after the py 3.9 bump [#4867](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4867) (3.1.5.0) - *ci*: More Sonar scan workflow fixes [#4902](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4902) (by vvalderrv) (3.1.6.0) - *ci*: Add more exceptions to when we test docs building [#4899](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4899) (3.1.6.0) - *ci*: Require all dependencies, with explicit exceptions [#4898](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4898) (3.1.6.0) @@ -355,6 +382,18 @@ asterisk) had not previously contributed to the project. --- --- + +Release 3.0.12.0 (Nov 1, 2025) -- compared to 3.0.11.0 +------------------------------------------------------- + - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) + - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) + - *win*: Address Windows crashes from issue 4641 [#4914](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4914) + - *fix*: Uninitialized value revealed by clang-21 warning [#4940](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4940) + - *ci*: For python wheel generation, use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) + - *ci*: Drop deprecated macos-13 (intel) platform, add macos-15-intel [#4930](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4930) + - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) + + Release 3.0.11.0 (Oct 1, 2025) -- compared to 3.0.10.1 ------------------------------------------------------- - *oiiotool*: Allow easy splitting output of subimages by name [#4874](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4874) @@ -362,6 +401,7 @@ Release 3.0.11.0 (Oct 1, 2025) -- compared to 3.0.10.1 - *gif*: GIF output didn't handle FramesPerSecond attribute correctly [#4890](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4890) - *deps*: Test freetype 2.14 and document that it works [#4876](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4876) - *deps*: Look for boost headers for OpenVDBs older than 12 [#4873](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4873) (by Alex Fuller) + - *deps*: Support for OpenColorIO 2.5 [#4916](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4916) Release 3.0.10.1 (Sep 16, 2025) -- compared to 3.0.10.0 diff --git a/CREDITS.md b/CREDITS.md index 6d01f85972..2fec42512a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -125,6 +125,7 @@ lg@openimageio.org * John Burnett * John Fea * John Haddon +* Jonathan Brown * Jonathan Hearn * Jonathan Scruggs * Joris Nijs @@ -221,6 +222,7 @@ lg@openimageio.org * Sergey Sharybin * Sergio Rojas * Shane Ambler +* Shane Smith * Simon Boorer * Solomon Boulos * SRHMorris @@ -236,6 +238,7 @@ lg@openimageio.org * Todica Ionut * Tom Knowles * Troy James Sobotka +* Valery Angelique * Vanessa Valderrama * Vic P * Vinod Khare From c0d1742d2728894ded8b1aa5acfc3c58311fd01d Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 7 Nov 2025 10:00:48 -0800 Subject: [PATCH 051/508] fix(imagebuf): fix set_pixels bug, didn't consider roi = All (#4949) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/imagebuf.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index 8206ff3a8d..8a5ed32e0a 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -2769,6 +2769,9 @@ bool ImageBuf::set_pixels(ROI roi, TypeDesc format, const void* data, stride_t xstride, stride_t ystride, stride_t zstride) { + if (!roi.defined()) + roi = this->roi(); + roi.chend = std::min(roi.chend, nchannels()); image_span s(reinterpret_cast(data), roi.nchannels(), roi.width(), roi.height(), roi.depth(), format.size(), xstride, ystride, From b635f59e1864517aa876154ddfd00f2eba96557e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 10 Nov 2025 23:23:30 -0800 Subject: [PATCH 052/508] fix: type warning in assertion in jpeg2000output.cpp (#4952) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/jpeg2000.imageio/jpeg2000output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jpeg2000.imageio/jpeg2000output.cpp b/src/jpeg2000.imageio/jpeg2000output.cpp index 869f663e6d..1c35491fb0 100644 --- a/src/jpeg2000.imageio/jpeg2000output.cpp +++ b/src/jpeg2000.imageio/jpeg2000output.cpp @@ -831,7 +831,7 @@ Jpeg2000Output::write_jph_scanline(int y, int /*z*/, const void* data) ojph::ui32 next_comp = 0; ojph::line_buf* cur_line = m_jph_stream->exchange(NULL, next_comp); for (int c = 0; c < m_spec.nchannels; ++c) { - assert(c == next_comp); + OIIO_ASSERT(ojph::ui32(c) == next_comp); for (int i = 0, j = c; i < m_spec.width; i++) { unsigned int val = scanline[j]; j += m_spec.nchannels; From 5dfb92ac7cdd6cecec396eaa655c8bd0ae91b140 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 11 Nov 2025 14:02:58 -0800 Subject: [PATCH 053/508] test: image_span_test reduce benchmark load for debug and CI renders (#4951) Almost all the other unit tests do this, just adding it to image_span_test: automatic reduction in test load for CI and debug renders by doing fewer time trials. This cuts down on runtime for CI, but stil exercises the test code. Command line arguments allow you to override, in cases where you need really good timing fidelity even for a debug run. Also conform simd_test to the usual standard `-iters` used elsewhere instead of being the one place that uses `-iterations`. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/image_span_test.cpp | 38 +++++++++++++++++++++++++- src/libutil/simd_test.cpp | 4 ++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/libOpenImageIO/image_span_test.cpp b/src/libOpenImageIO/image_span_test.cpp index ad7e357245..87aeb3dfb4 100644 --- a/src/libOpenImageIO/image_span_test.cpp +++ b/src/libOpenImageIO/image_span_test.cpp @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -18,6 +19,9 @@ using namespace OIIO; +static int ntrials = 5; + + template void @@ -195,6 +199,7 @@ test_image_span_copy_image() // Benchmark old (ptr) versus new (span) copy_image functions Benchmarker bench; + bench.trials(ntrials); bench.units(Benchmarker::Unit::us); bench(Strutil::format(" copy_image image_span {}", label), @@ -266,6 +271,7 @@ test_image_span_contiguize() // Benchmark old (ptr) versus new (span) contiguize functions Benchmarker bench; + bench.trials(ntrials); bench.units(Benchmarker::Unit::us); bench(Strutil::format(" contiguize image_span {}", label), [&]() { @@ -332,6 +338,7 @@ test_image_span_convert_image() // Benchmark old (ptr) versus new (span) contiguize functions Benchmarker bench; + bench.trials(ntrials); bench.units(Benchmarker::Unit::ms); bench(Strutil::format(" convert_image image_span {}", label), @@ -418,6 +425,7 @@ benchmark_image_span_passing() image_span ispan(sbuf.data(), nchans, xres, yres, 1); Benchmarker bench; + bench.trials(ntrials); bench.units(Benchmarker::Unit::us); float sum = 0.0f; @@ -442,6 +450,7 @@ benchmark_image_span_passing() // Do it all again for a SMALL image bench.units(Benchmarker::Unit::ns); + bench.trials(ntrials); int small = 16; image_span smispan(sbuf.data(), nchans, small, small, 1); bench(" pass by value (small)", @@ -544,9 +553,36 @@ test_image_span_within_span() +static void +getargs(int argc, char* argv[]) +{ + ArgParse ap; + ap.intro( + "image_span_test -- unit test and benchmarks for OpenImageIO/image_span.h\n" OIIO_INTRO_STRING) + .usage("image_span_test [options]"); + + // ap.arg("--iterations %d", &iterations) + // .help(Strutil::fmt::format("Number of iterations (default: {})", + // iterations)); + ap.arg("--trials %d", &ntrials).help("Number of trials"); + + ap.parse_args(argc, (const char**)argv); +} + + + int -main(int /*argc*/, char* /*argv*/[]) +main(int argc, char* argv[]) { +#if !defined(NDEBUG) || defined(OIIO_CI) || defined(OIIO_CODE_COVERAGE) + // For the sake of test time, reduce the default iterations for DEBUG, + // CI, and code coverage builds. Explicit use of --trials + // will override this, since it comes before the getargs() call. + ntrials = 1; +#endif + + getargs(argc, argv); + test_image_span(); test_image_span(); test_image_span(); diff --git a/src/libutil/simd_test.cpp b/src/libutil/simd_test.cpp index b34fb6bbd6..31c1b70792 100644 --- a/src/libutil/simd_test.cpp +++ b/src/libutil/simd_test.cpp @@ -43,8 +43,10 @@ getargs(int argc, char* argv[]) OIIO_INTRO_STRING) .usage("simd_test [options]"); - ap.arg("--iterations %d", &iterations) + ap.arg("--iters %d", &iterations) .help(Strutil::fmt::format("Number of iterations (default: {})", iterations)); + ap.arg("--iterations %d", &iterations) + .hidden(); // obsolete synonym for --iters ap.arg("--trials %d", &ntrials) .help("Number of trials"); From 6567599fe40880b005460e435ad4a60607b3a303 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 12 Nov 2025 13:20:27 -0800 Subject: [PATCH 054/508] ci: fix broken ci, debug and static cases, bump some latest (#4954) Two Ubuntu-based cases (debug build and static build) started failing this past week, unrelated to us. I think the runners changed yet again, it seems to be having particular trouble because those tests were trying to use gcc-9. But there's no particular need for that anyway, it just reflects how long ago we last modified those tests. So this spurred a grab bag of CI fixes: * Move the debug and static linkage tests off of raw ubuntu runners and to using the ASWF docker VFX Platform 2025 containers. This makes them more modern and also more stable. * Turns out the "static" test hadn't been working properly lately -- it was accidentally building dynamic libs, not static at all! Fix that, but then it uncovered some problems that are out of scope to fix in this PR. I will look into it separately, but in the mean time I have commented out the static build test to suppress the failures. * Bump a few of the "latest" dependencies to the most recent. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 65 ++++++++++++++++++++-------------------- src/cmake/compiler.cmake | 2 +- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e023860bbe..e3777baf74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -284,7 +284,6 @@ jobs: simd: "avx2,f16c" fmt_ver: 10.1.1 pybind11_ver: v2.12.0 - benchmark: 1 setenvs: export PUGIXML_VERSION=v1.14 optional_deps_append: "LibRaw" - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 @@ -298,7 +297,6 @@ jobs: simd: "avx2,f16c" fmt_ver: 10.1.1 pybind11_ver: v2.12.0 - benchmark: 1 setenvs: export PUGIXML_VERSION=v1.14 optional_deps_append: "LibRaw" - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 @@ -313,6 +311,31 @@ jobs: benchmark: 1 setenvs: export PUGIXML_VERSION=v1.15 optional_deps_append: "openjph;Qt6" + - desc: VFX2025 Debug gcc11/C++17 py3.11 exr3.3 ocio2.4 + nametag: linux-vfx2025-debug + runner: ubuntu-latest + container: aswf/ci-oiio:2025 + cxx_std: 17 + build_type: Debug + python_ver: "3.11" + simd: "avx2,f16c" + fmt_ver: 11.2.0 + pybind11_ver: v2.13.6 + setenvs: export PUGIXML_VERSION=v1.15 + optional_deps_append: "openjph;Qt6" + # - desc: VFX2025 Static gcc11/C++17 py3.11 exr3.3 ocio2.4 + # nametag: linux-vfx2025-static + # runner: ubuntu-latest + # container: aswf/ci-oiio:2025 + # cxx_std: 17 + # python_ver: "3.11" + # simd: "avx2,f16c" + # fmt_ver: 11.2.0 + # pybind11_ver: v2.13.6 + # benchmark: 1 + # setenvs: export PUGIXML_VERSION=v1.15 + # BUILD_SHARED_LIBS=OFF + # optional_deps_append: "openjph;Qt6" - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.4 nametag: linux-vfx2026 runner: ubuntu-latest @@ -430,9 +453,9 @@ jobs: cc_compiler: gcc-13 cxx_compiler: g++-13 cxx_std: 20 - fmt_ver: 12.0.0 + fmt_ver: 12.1.0 opencolorio_ver: v2.5.0 - openexr_ver: v3.4.0 + openexr_ver: v3.4.3 pybind11_ver: v3.0.1 python_ver: "3.12" simd: avx2,f16c @@ -507,39 +530,15 @@ jobs: pybind11_ver: v2.12.0 python_ver: "3.10" simd: avx2,f16c - - desc: debug gcc9/C++17, sse4.2, exr3.1 - nametag: linux-gcc9-cpp17-debug - runner: ubuntu-22.04 - cxx_compiler: g++-9 - cxx_std: 17 - build_type: Debug - python_ver: "3.10" - simd: sse4.2 - openexr_ver: v3.1.13 - pybind11_ver: v2.7.0 - ctest_test_timeout: 1200 - setenvs: export PUGIXML_VERSION=v1.9 - - desc: static libs gcc9 C++17 exr3.1 - nametag: linux-static - runner: ubuntu-22.04 - cxx_compiler: g++-9 - cxx_std: 17 - openexr_ver: v3.1.13 - python_ver: "3.10" - pybind11_ver: v2.7.0 - setenvs: export BUILD_SHARED_LIBS=OFF - depcmds: | - sudo rm -rf /usr/local/include/OpenEXR - sudo rm -rf /usr/local/lib64/cmake/{IlmBase,OpenEXR} - desc: Linux ARM latest releases gcc14 C++20 py3.12 exr3.4 ocio2.4 nametag: linux-arm-latest-releases runner: ubuntu-24.04-arm cc_compiler: gcc-14 cxx_compiler: g++-14 cxx_std: 20 - fmt_ver: 12.0.0 + fmt_ver: 12.1.0 opencolorio_ver: v2.5.0 - openexr_ver: v3.4.0 + openexr_ver: v3.4.3 pybind11_ver: v3.0.1 python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 @@ -558,9 +557,9 @@ jobs: cc_compiler: clang-18 cxx_compiler: clang++-18 cxx_std: 20 - fmt_ver: 12.0.0 - opencolorio_ver: v2.4.2 - openexr_ver: v3.4.0 + fmt_ver: 12.1.0 + opencolorio_ver: v2.5.0 + openexr_ver: v3.4.3 pybind11_ver: v3.0.1 python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index d31d5229f9..da4e76da48 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -655,7 +655,7 @@ message(VERBOSE "Setting SOVERSION to: ${SOVERSION}") # BUILD_SHARED_LIBS, if turned off, will disable building of .so/.dll # dynamic libraries and instead only build static libraries. # -option (BUILD_SHARED_LIBS "Build shared libraries (set to OFF to build static libs)" ON) +set_option (BUILD_SHARED_LIBS "Build shared libraries (set to OFF to build static libs)" ON) if (NOT BUILD_SHARED_LIBS) add_compile_definitions (${PROJ_NAME}_STATIC_DEFINE=1) endif () From 6884e275a1c47fca93d65b4facf414bf9ad1d16a Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 12 Nov 2025 13:36:49 -0800 Subject: [PATCH 055/508] fix(exr): improve attribute translation rules (#4946) In exr output, we were doing attribute name substitutions using case-insensitive comparisions, whereas on the exr input side, we were using case-sensitive comparisons. This was leading to some strange behavior where certain attributes were not properly "round tripping" when reading and writing exr files, particularly when it involved attribute names that differed only in case from certain reserved/standard names. Also be a little more flexible in adjusting exr attributes that have the wrong type, by being able to convert int or float to a value that's required to be a string. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/openexr.imageio/exroutput.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index 60d19514fc..fcd9a23d45 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -1140,8 +1140,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, TypeDesc exrtype = TypeUnknown; for (const auto& e : exr_meta_translation) { - if (Strutil::iequals(xname, e.oiioname) - || (e.exrname && Strutil::iequals(xname, e.exrname))) { + if (xname == e.oiioname || (e.exrname && xname == e.exrname)) { xname = std::string(e.exrname ? e.exrname : ""); exrtype = e.exrtype; // std::cerr << "exr put '" << name << "' -> '" << xname << "'\n"; @@ -1267,6 +1266,7 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, // OpenEXR expects, and we can make a good guess about how to translate. float tmpfloat; int tmpint; + ustring tmpstring; if (exrtype == TypeFloat && type == TypeInt) { tmpfloat = float(*(const int*)data); data = &tmpfloat; @@ -1278,10 +1278,19 @@ OpenEXROutput::put_parameter(const std::string& name, TypeDesc type, } else if (exrtype == TypeMatrix && type == TypeDesc(TypeDesc::FLOAT, 16)) { // Automatically translate float[16] to Matrix when expected type = TypeMatrix; + } else if (exrtype == TypeString && type == TypeFloat) { + tmpstring = ustring::fmtformat("{}", *(const float*)data); + data = &tmpstring; + type = TypeString; + } else if (exrtype == TypeString && type == TypeInt) { + tmpstring = ustring::fmtformat("{}", *(const int*)data); + data = &tmpstring; + type = TypeString; } // Now if we still don't match a specific type OpenEXR is looking for, - // skip it. + // skip it. If we ever support warnings for ImageOutput, we should make + // this a warning. if (exrtype != TypeDesc() && !exrtype.equivalent(type)) { OIIO::debugfmt( "OpenEXR output metadata \"{}\" type mismatch: expected {}, got {}\n", From a4c3961319ad07a7a9bb631445cab2b6202d50a9 Mon Sep 17 00:00:00 2001 From: LI JI <146397706+lji-ilm@users.noreply.github.com> Date: Fri, 14 Nov 2025 19:27:17 -0800 Subject: [PATCH 056/508] build: Disable LERC in libTIFF local build script (#4957) Recently, when building OIIO using `OpenImageIO_BUILD_LOCAL_DEPS=all` cmake option, I noticed a reference to `lerc` that fails during the linker stage, when compiling the simd tests: ``` [ 71%] Building CXX object src/libutil/CMakeFiles/simd_test.dir/simd_test.cpp.o [ 71%] Linking CXX executable ../../bin/simd_test /usr/bin/ld: ../../lib/libOpenImageIO.so.3.1.7: undefined reference to `lerc_decode' /usr/bin/ld: ../../lib/libOpenImageIO.so.3.1.7: undefined reference to `lerc_encodeForVersion' /usr/bin/ld: ../../lib/libOpenImageIO.so.3.1.7: undefined reference to `lerc_getBlobInfo' collect2: error: ld returned 1 exit status gmake[2]: *** [src/libutil/CMakeFiles/simd_test.dir/build.make:100: bin/simd_test] Error 1 gmake[1]: *** [CMakeFiles/Makefile2:2158: src/libutil/CMakeFiles/simd_test.dir/all] Error 2 gmake: *** [Makefile:166: all] Error 2 ``` `lerc` is an image compression library from ESRI: https://github.com/Esri/lerc A deeper dive of this reference to lerc shows that it is referred in `libtiff`. `libtiff`, however, did not seem to correctly export this dependency to OIIO when being built locally. If someone was building OIIO with a system-installed `libtiff`, this error is unlikely to happen. By disabling `lerc` in `libTiff`, the local build scripts ( `OpenImageIO_BUILD_LOCAL_DEPS=all` ) runs without a problem. Tested build OIIO with `OpenImageIO_BUILD_LOCAL_DEPS=all` with this patch and was successful in finishing the build. Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/build_TIFF.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmake/build_TIFF.cmake b/src/cmake/build_TIFF.cmake index 45cd525298..72a04632cf 100644 --- a/src/cmake/build_TIFF.cmake +++ b/src/cmake/build_TIFF.cmake @@ -44,6 +44,7 @@ build_dependency_with_cmake(TIFF -D lzma=OFF -D zstd=OFF -D jbig=OFF + -D lerc=OFF ${MORE_TIFF_CMAKE_ARGS} ) From 4afee0b4505db1788db360f4dc10b1d79a72cc7e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 17 Nov 2025 10:24:03 -0800 Subject: [PATCH 057/508] ci: unbreak icc/icx CI (#4958) A few days ago, the icc and icx CI tests started failing, saying they were out of disk space while installing dependencies (specifically, the intel compilers themselves). Now, the VFX Platform 2023 asf docker image I'm using hasn't changed, and neither has the years-old Intel rpms with the compilers. So maybe the runners themselves just changed again, in a way that adds some pre-installs and leaving us not quite enough disk space for everything else we need? Asking for a few less things to get installed seems to free up just enough space. And for the icx one, I also swtiched to the 2025 container, which let me use a slightly newer icx version. Also: Don't try to install giflib needlessly, which speeds up the setup time. And remove some dead code in my dependency setup script. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 17 +++++----- src/build-scripts/gh-installdeps.bash | 48 ++++++++++++--------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3777baf74..0359a3dd09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -251,30 +251,29 @@ jobs: fmt_ver: 7.1.3 # icc MUST use this older FMT version pybind11_ver: v2.9.0 - setenvs: export USE_ICC=1 USE_OPENVDB=0 + setenvs: export USE_ICC=1 USE_OPENVDB=0 USE_OPENCV=0 OIIO_EXTRA_CPP_ARGS="-fp-model=precise" FREETYPE_VERSION=VER-2-13-0 DISABLE_libuhdr=1 # For icc, use fp-model precise to eliminate needless LSB errors # that make test results differ from other platforms. optional_deps_append: "LibRaw;Ptex;Qt6" - - desc: VFX2023 icx/C++17 py3.10 exr3.1 ocio2.3 qt5.15 + - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 nametag: linux-vfx2023.icx runner: ubuntu-latest - container: aswf/ci-osl:2023 + container: aswf/ci-oiio:2025 cc_compiler: icx cxx_compiler: icpx - opencolorio_ver: v2.3.0 - python_ver: "3.10" - pybind11_ver: v2.10.0 + fmt_ver: 11.2.0 + python_ver: "3.11" + pybind11_ver: v2.13.6 simd: "avx2,f16c" benchmark: 1 - setenvs: export USE_OPENVDB=0 + setenvs: export USE_OPENVDB=0 USE_OPENCV=0 UHDR_CMAKE_C_COMPILER=gcc UHDR_CMAKE_CXX_COMPILER=g++ # Building libuhdr with icx results in test failures - # so we force using gcc/g++. - optional_deps_append: "LibRaw;Ptex;Qt6" + optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 nametag: linux-vfx2024 runner: ubuntu-latest diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index 62d8509084..7ce0c8e4f8 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -7,6 +7,7 @@ set -ex +df -h # @@ -17,8 +18,22 @@ if [[ "$ASWF_ORG" != "" ]] ; then #ls /etc/yum.repos.d + # This will show how much space is taken by each installed package, sorted + # by size in KB. + # rpm -qa --queryformat '%10{size} - %-25{name} \t %{version}\n' | sort -n + + # I would like this to free space by removing packages we don't need. + # BUT IT DOESN'T, because uninstalling a package just ends is visibility + # to the runtime, it doesn't remove it from the static container image + # that's taking up the disk space. So this is pointless. But leaving it + # here to remind myself not to waste time trying it again. + # time sudo yum remove -y nsight-compute-2022.3.0 libcublas-devel-11-8 libcublas-11-8 libcusparse-devel-11-8 libnpp-devel-11-8 libnpp-11-8 libcurand-devel-11-8 libcurand-11-8 || true + # time sudo yum remove -y nsight-compute-2024.3.1 libcublas-devel-12-6 libcublas-12-6 libcusparse-devel-12-6 libnpp-devel-12-6 libnpp-12-6 libcurand-devel-12-6 libcurand-12-6 || true + # time sudo dnf upgrade --refresh || true - time sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm -y || true + if [[ "${DO_RPMFUSION_REPO:-0}" != "0" ]] ; then + time sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm -y || true + fi if [[ "$ASWF_VFXPLATFORM_VERSION" == "2022" ]] ; then # CentOS 7 based containers need the now-nonexistent centos repo to be @@ -27,7 +42,7 @@ if [[ "$ASWF_ORG" != "" ]] ; then sed -i 's,^mirrorlist=,#,; s,^#baseurl=http://mirror\.centos\.org/centos/$releasever,baseurl=https://vault.centos.org/7.9.2009,' /etc/yum.repos.d/CentOS-Base.repo fi - time time sudo yum install -y giflib giflib-devel || true + # time time sudo yum install -y giflib giflib-devel || true if [[ "${USE_OPENCV}" != "0" ]] ; then time sudo yum install -y opencv opencv-devel || true fi @@ -47,25 +62,6 @@ if [[ "$ASWF_ORG" != "" ]] ; then time pip3 install ${PIP_INSTALLS} || true fi - if [[ "${CONAN_LLVM_VERSION}" != "" ]] ; then - mkdir conan - pushd conan - # Simple way to conan install just one package: - # conan install clang/${CONAN_LLVM_VERSION}@aswftesting/ci_common1 -g deploy -g virtualenv - # But the below method can accommodate multiple requirements: - echo "[imports]" >> conanfile.txt - echo "., * -> ." >> conanfile.txt - echo "[requires]" >> conanfile.txt - echo "clang/${CONAN_LLVM_VERSION}@aswftesting/ci_common1" >> conanfile.txt - time conan install . - echo "--ls--" - ls -R . - export PATH=$PWD/bin:$PATH - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH - export LLVM_ROOT=$PWD - popd - fi - if [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" ]] ; then # Lock down icc to 2022.1 because newer versions hosted on the Intel # repo require a glibc too new for the ASWF CentOS7-based containers @@ -74,12 +70,10 @@ if [[ "$ASWF_ORG" != "" ]] ; then sudo /usr/bin/yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2022.1.0.x86_64 set +e; source /opt/intel/oneapi/setvars.sh --config oneapi_2022.1.0.cfg; set -e elif [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" || "$CXX" == "icpx" || "$CC" == "icx" || "$USE_ICX" != "" ]] ; then - # Lock down icx to 2023.1 because newer versions hosted on the Intel - # repo require a libstd++ too new for the ASWF containers we run CI on - # because their default install of gcc 9 based toolchain. sudo cp src/build-scripts/oneAPI.repo /etc/yum.repos.d - sudo yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.1.0.x86_64 - # sudo yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic + sudo yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic + # If we needed to lock down to a particular version, we could: + # sudo yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.1.0.x86_64 set +e; source /opt/intel/oneapi/setvars.sh; set -e echo "Verifying installation of Intel(r) oneAPI DPC++/C++ Compiler:" icpx --version @@ -225,5 +219,7 @@ if [[ "$USE_ICC" != "" ]] ; then export CC=icc fi +df -h + # Save the env for use by other stages src/build-scripts/save-env.bash From bac8603bd9c1895f9e598465cc27c5dc1b981146 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 17 Nov 2025 10:24:43 -0800 Subject: [PATCH 058/508] admin: Update some license notices (#4955) Result of our annual license scan. * Add license notices some places we had missed (mostly documentation). * Double check some of the code files that are still marked as a mix of Apache 2.0 + BSD, see if there are any remaining lines that are from authors that hadn't relicensed their contributions -- there were a few that had none of those old lines left, so they can be changed to advertise that they are purely Apache 2.0. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CHANGES.md | 4 ++++ CODE_OF_CONDUCT.md | 4 ++++ CREDITS.md | 4 ++++ INSTALL.md | 4 ++++ RELICENSING.md | 3 +++ conanfile.txt | 4 ++++ docs/CHANGES-0.x.md | 4 ++++ docs/CHANGES-1.x.md | 4 ++++ docs/QuickStart.md | 4 ++++ docs/dev/Architecture.md | 4 ++++ src/include/OpenImageIO/argparse.h | 2 +- src/include/OpenImageIO/detail/detail-README.txt | 4 ++++ src/include/OpenImageIO/vecparam.h | 5 ----- src/include/OpenImageIO/version.h | 5 +++++ src/libutil/strutil.cpp | 2 ++ src/oiiotool/expressions.cpp | 2 +- src/python/stubs/CMakeLists.txt | 4 ++++ src/python/stubs/generate_stubs.py | 4 ++++ src/rla.imageio/CMakeLists.txt | 2 +- src/sgi.imageio/CMakeLists.txt | 2 +- 20 files changed, 62 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6d328cfc94..79d3eb436e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1152,3 +1152,7 @@ For older release notes, see: * [CHANGES-2.x](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/docs/CHANGES-2.x.md). * [CHANGES-1.x](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/docs/CHANGES-1.x.md). * [CHANGES-0.x](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/docs/CHANGES-0.x.md). + + + + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4677a3c658..6f62a36272 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -5,3 +5,7 @@ you can read in full [here](https://lfprojects.org/policies/code-of-conduct). To report incidents or to appeal reports of incidents, send email to the Manager of LF Projects at manager@lfprojects.org. + + + + diff --git a/CREDITS.md b/CREDITS.md index 2fec42512a..f3044fd4bf 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -255,3 +255,7 @@ lg@openimageio.org * Zach Lewis * Ziad Khouri * zomgrolf + + + + diff --git a/INSTALL.md b/INSTALL.md index c907ff5795..864499adb6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -449,3 +449,7 @@ You do not need any of these packages in order to build or use OpenImageIO. But if you are going to contribute to OpenImageIO development, you probably want them, since it is required for executing OpenImageIO's test suite (when you run "make test"). + + + + diff --git a/RELICENSING.md b/RELICENSING.md index c6390ca009..8ad2a470dd 100644 --- a/RELICENSING.md +++ b/RELICENSING.md @@ -1,3 +1,6 @@ + + + New code entering the OpenImageIO repository from July 1 2023 onward is subject to the [Apache 2.0 license](LICENSE.md). diff --git a/conanfile.txt b/conanfile.txt index 00c30ae476..06629018ea 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,3 +1,7 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + [requires] zlib/1.2.11 libtiff/4.0.9 diff --git a/docs/CHANGES-0.x.md b/docs/CHANGES-0.x.md index 0473d88c82..7889859e27 100644 --- a/docs/CHANGES-0.x.md +++ b/docs/CHANGES-0.x.md @@ -679,3 +679,7 @@ Initial developer release 0.1 (1 Sep 2008): * maketx * API docs for ImageInput, ImageOutput, writing plugins * Linux and OSX + + + + diff --git a/docs/CHANGES-1.x.md b/docs/CHANGES-1.x.md index dfd9dba5d3..0879304be8 100644 --- a/docs/CHANGES-1.x.md +++ b/docs/CHANGES-1.x.md @@ -4039,3 +4039,7 @@ Developer goodies: For older release notes, see: * [CHANGES-0.x](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/docs/CHANGES-0.x.md). + + + + diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 78941f36fc..574578bbf3 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -92,3 +92,7 @@ output.write_image(pixels) This is just a simple example to give you a flavor of the different major interfaces. For more advanced usage, you may want to explore the [documentation](https://docs.openimageio.org). + + + + diff --git a/docs/dev/Architecture.md b/docs/dev/Architecture.md index de1cb602d4..5e52bf4143 100644 --- a/docs/dev/Architecture.md +++ b/docs/dev/Architecture.md @@ -197,3 +197,7 @@ image- and file-oriented classes described above. The most important ones are: sequence point to the same character memory. In addition to saving memory, this makes string assignment and equality comparison as inexpensive as integer operations. + + + + diff --git a/src/include/OpenImageIO/argparse.h b/src/include/OpenImageIO/argparse.h index 4b598fdf73..c75be7d78d 100644 --- a/src/include/OpenImageIO/argparse.h +++ b/src/include/OpenImageIO/argparse.h @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO diff --git a/src/include/OpenImageIO/detail/detail-README.txt b/src/include/OpenImageIO/detail/detail-README.txt index 6748be639f..57823f17da 100644 --- a/src/include/OpenImageIO/detail/detail-README.txt +++ b/src/include/OpenImageIO/detail/detail-README.txt @@ -9,3 +9,7 @@ anything in those headers or in the pvt or detail namespaces. Therefore, anything in this directory may be changed arbitrarily as part of a minor version release (but not part of a patch version release, since changes here may well change the ABI/linkage). + + + + diff --git a/src/include/OpenImageIO/vecparam.h b/src/include/OpenImageIO/vecparam.h index bd47d78851..80f83ca8c0 100644 --- a/src/include/OpenImageIO/vecparam.h +++ b/src/include/OpenImageIO/vecparam.h @@ -13,11 +13,6 @@ OIIO_NAMESPACE_3_1_BEGIN -// NOTE: These interoperable type templates were copied from the -// [Imath project](http://github.com/AcademySoftwareFoundation/imath), -// licensed under the same BSD-3-clause license as OpenImageIO. - - /// @{ /// @name Detecting interoperable linear algebra types. /// diff --git a/src/include/OpenImageIO/version.h b/src/include/OpenImageIO/version.h index 89579fcbf3..294f23b1bc 100644 --- a/src/include/OpenImageIO/version.h +++ b/src/include/OpenImageIO/version.h @@ -1,3 +1,8 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + + // DEPRECATED header OpenImageIO/version.h // For back compatibility, just include the new name, oiioversion.h. diff --git a/src/libutil/strutil.cpp b/src/libutil/strutil.cpp index 224bafaf68..17060728d7 100644 --- a/src/libutil/strutil.cpp +++ b/src/libutil/strutil.cpp @@ -1515,6 +1515,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// SPDX-License-Identifier: MIT #define UTF8_ACCEPT 0 #define UTF8_REJECT 12 @@ -1592,6 +1593,7 @@ Strutil::utf8_to_unicode(string_view str, std::vector& uvec) René Nyffenegger rene.nyffenegger@adp-gmbh.ch */ +// SPDX-License-Identifier: Zlib std::string Strutil::base64_encode(string_view str) { diff --git a/src/oiiotool/expressions.cpp b/src/oiiotool/expressions.cpp index f303756a09..fbb8568666 100644 --- a/src/oiiotool/expressions.cpp +++ b/src/oiiotool/expressions.cpp @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO diff --git a/src/python/stubs/CMakeLists.txt b/src/python/stubs/CMakeLists.txt index a9351eff2f..00bb732172 100644 --- a/src/python/stubs/CMakeLists.txt +++ b/src/python/stubs/CMakeLists.txt @@ -1,3 +1,7 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + # Setup the pystub target, which is not built by default. diff --git a/src/python/stubs/generate_stubs.py b/src/python/stubs/generate_stubs.py index 37826c1b52..336acd99f4 100644 --- a/src/python/stubs/generate_stubs.py +++ b/src/python/stubs/generate_stubs.py @@ -1,3 +1,7 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + """ Script to generate pyi stubs by patching and calling mypy's stubgen tool. diff --git a/src/rla.imageio/CMakeLists.txt b/src/rla.imageio/CMakeLists.txt index 332ad0c922..9c5c2b2bdd 100644 --- a/src/rla.imageio/CMakeLists.txt +++ b/src/rla.imageio/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright Contributors to the OpenImageIO project. -# SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO add_oiio_plugin (rlainput.cpp rlaoutput.cpp) diff --git a/src/sgi.imageio/CMakeLists.txt b/src/sgi.imageio/CMakeLists.txt index 9f5931a37b..ddcb838540 100644 --- a/src/sgi.imageio/CMakeLists.txt +++ b/src/sgi.imageio/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright Contributors to the OpenImageIO project. -# SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO add_oiio_plugin (sgiinput.cpp sgioutput.cpp) From 02a3c550e8fa923902150bcbca3fe4a02a2c95e8 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 26 Nov 2025 18:21:41 -0800 Subject: [PATCH 059/508] fix(jpeg): Fix wrong pointers/crashing when decodng CMYK jpeg files (#4963) Was just wrong. Presumably broken recently. In the process, switched internal cmyk->rgb conversion to be span based. Fixes #4962 --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/testing.cmake | 2 +- src/jpeg.imageio/jpeginput.cpp | 38 ++++++++++++++----------- testsuite/jpeg/ref/out.txt | 9 ++++++ testsuite/jpeg/ref/rgb-from-YCbCrK.tif | Bin 0 -> 4230 bytes testsuite/jpeg/run.py | 12 ++++++++ testsuite/jpeg/src/YCbCrK.jpg | Bin 0 -> 2707 bytes 6 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 testsuite/jpeg/ref/out.txt create mode 100644 testsuite/jpeg/ref/rgb-from-YCbCrK.tif create mode 100644 testsuite/jpeg/run.py create mode 100644 testsuite/jpeg/src/YCbCrK.jpg diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index b523429753..8bd975efbc 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -149,7 +149,7 @@ macro (oiio_add_all_tests) oiiotool-xform diff dither dup-channels - jpeg-corrupt jpeg-metadata + jpeg jpeg-corrupt jpeg-metadata maketx oiiotool-maketx misnamed-file missingcolor diff --git a/src/jpeg.imageio/jpeginput.cpp b/src/jpeg.imageio/jpeginput.cpp index 092072aa5d..47795f5379 100644 --- a/src/jpeg.imageio/jpeginput.cpp +++ b/src/jpeg.imageio/jpeginput.cpp @@ -532,22 +532,24 @@ JpgInput::read_uhdr(Filesystem::IOProxy* ioproxy) +template static void -cmyk_to_rgb(int n, const unsigned char* cmyk, size_t cmyk_stride, - unsigned char* rgb, size_t rgb_stride) +cmyk_to_rgb(cspan cmyk, span rgb) { - for (; n; --n, cmyk += cmyk_stride, rgb += rgb_stride) { + size_t n = cmyk.size() / 4; + OIIO_ASSERT(rgb.size() == n * 3); + for (size_t i = 0; i < n; ++i) { // JPEG seems to store CMYK as 1-x - float C = convert_type(cmyk[0]); - float M = convert_type(cmyk[1]); - float Y = convert_type(cmyk[2]); - float K = convert_type(cmyk[3]); - float R = C * K; - float G = M * K; - float B = Y * K; - rgb[0] = convert_type(R); - rgb[1] = convert_type(G); - rgb[2] = convert_type(B); + float C = convert_type(cmyk[4 * i + 0]); + float M = convert_type(cmyk[4 * i + 1]); + float Y = convert_type(cmyk[4 * i + 2]); + float K = convert_type(cmyk[4 * i + 3]); + float R = C * K; + float G = M * K; + float B = Y * K; + rgb[3 * i + 0] = convert_type(R); + rgb[3 * i + 1] = convert_type(G); + rgb[3 * i + 2] = convert_type(B); } } @@ -682,10 +684,12 @@ JpgInput::read_native_scanlines(int subimage, int miplevel, int ybegin, } m_next_scanline = yend; - if (m_cmyk) - cmyk_to_rgb(m_spec.width * nscanlines, - reinterpret_cast(readdata), 4, - reinterpret_cast(data.data()), 3); + if (m_cmyk) { + for (int i = 0; i < nscanlines; ++i) + cmyk_to_rgb(make_cspan(readdata[i], m_spec.width * 4), + span_cast(data).subspan( + m_spec.width * 3 * i, m_spec.width * 3)); + } return true; } diff --git a/testsuite/jpeg/ref/out.txt b/testsuite/jpeg/ref/out.txt new file mode 100644 index 0000000000..442e464670 --- /dev/null +++ b/testsuite/jpeg/ref/out.txt @@ -0,0 +1,9 @@ +Reading src/YCbCrK.jpg +src/YCbCrK.jpg : 52 x 52, 3 channel, uint8 jpeg + SHA-1: 888C1DD026F9C9A613A94BE5053292AC7010E79C + channel list: R, G, B + jpeg:ColorSpace: "YCbCrK" + jpeg:subsampling: "4:4:4" + oiio:ColorSpace: "srgb_rec709_scene" +Comparing "rgb-from-YCbCrK.tif" and "ref/rgb-from-YCbCrK.tif" +PASS diff --git a/testsuite/jpeg/ref/rgb-from-YCbCrK.tif b/testsuite/jpeg/ref/rgb-from-YCbCrK.tif new file mode 100644 index 0000000000000000000000000000000000000000..d0187fc89c9e42da60326d66a575a9349d3f5ae7 GIT binary patch literal 4230 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 C`!ZSp literal 0 HcmV?d00001 diff --git a/testsuite/jpeg/run.py b/testsuite/jpeg/run.py new file mode 100644 index 0000000000..cb8edc5067 --- /dev/null +++ b/testsuite/jpeg/run.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + + +# Test ability to read a JPEG YCbCrK encoded file +command += info_command ("src/YCbCrK.jpg", safematch=True) +command += oiiotool ("src/YCbCrK.jpg -o rgb-from-YCbCrK.tif") + +outputs = [ "rgb-from-YCbCrK.tif", "out.txt" ] diff --git a/testsuite/jpeg/src/YCbCrK.jpg b/testsuite/jpeg/src/YCbCrK.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc94a72372b4dff0a952b130394da815e5dbbb6f GIT binary patch literal 2707 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 Date: Fri, 28 Nov 2025 07:07:36 +0100 Subject: [PATCH 060/508] fix(exr): ACES container writes colorInteropId instead of colorInteropID (#4966) The EXR reading code was correct. These attribute names are case sensitive in OpenEXR. Test output was updated. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/builtinplugins.rst | 4 ++-- src/openexr.imageio/exroutput.cpp | 12 ++++++------ testsuite/openexr-suite/ref/out.txt | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 4a8edd4359..f6eea37efd 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1661,13 +1661,13 @@ control aspects of the writing itself: - One of `none` (default), `strict`, or `relaxed`. If not `none`, the spec will be checked to see if it is compliant with the ACES Container format defined in `ST 2065-4`_. If it is, - `chromaticities` will be set to the ACES AP0 ones, `colorInteropId` + `chromaticities` will be set to the ACES AP0 ones, `colorInteropID` will be set to 'lin_ap0_scene' and the `acesImageContainerFlag` attribute will be set to 1. In `strict` mode, if the spec is non-compliant, the output will throw an error and avoid writing the image. While in `relaxed` mode, if the spec is non-compliant, `chromaticities` - and `colorInteropId` will be set, but `acesImageContainerFlag` + and `colorInteropID` will be set, but `acesImageContainerFlag` will NOT. * - ``oiio:RawColor`` - int diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index fcd9a23d45..11b350ff38 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -299,7 +299,7 @@ static constexpr float ACES_AP0_chromaticities[8] = { 0.32168f, 0.33767f // white }; -static const std::string ACES_AP0_colorInteropId = "lin_ap0_scene"; +static const std::string ACES_AP0_colorInteropID = "lin_ap0_scene"; bool @@ -407,10 +407,10 @@ is_aces_container_compliant(const OIIO::ImageSpec& spec, std::string& reason) } // Check attributes with exact values if they exist - if (spec.get_string_attribute("oiio:ColorSpace", ACES_AP0_colorInteropId) - != ACES_AP0_colorInteropId - || spec.get_string_attribute("colorInteropId", ACES_AP0_colorInteropId) - != ACES_AP0_colorInteropId) { + if (spec.get_string_attribute("oiio:ColorSpace", ACES_AP0_colorInteropID) + != ACES_AP0_colorInteropID + || spec.get_string_attribute("colorInteropID", ACES_AP0_colorInteropID) + != ACES_AP0_colorInteropID) { reason = "Color space is not lin_ap0_scene as required for an ACES Container."; return false; @@ -448,7 +448,7 @@ set_aces_container_attributes(OIIO::ImageSpec& spec) { spec.attribute("chromaticities", OIIO::TypeDesc(OIIO::TypeDesc::FLOAT, 8), ACES_AP0_chromaticities); - spec.attribute("colorInteropId", ACES_AP0_colorInteropId); + spec.attribute("colorInteropID", ACES_AP0_colorInteropID); spec.attribute("acesImageContainerFlag", 1); } diff --git a/testsuite/openexr-suite/ref/out.txt b/testsuite/openexr-suite/ref/out.txt index 3c83a696e1..a6affcc883 100644 --- a/testsuite/openexr-suite/ref/out.txt +++ b/testsuite/openexr-suite/ref/out.txt @@ -351,7 +351,7 @@ strict-out.exr : 4 x 4, 3 channel, half openexr channel list: R, G, B acesImageContainerFlag: 1 chromaticities: 0.7347, 0.2653, 0, 1, 0.0001, -0.077, 0.32168, 0.33767 - colorInteropId: "lin_ap0_scene" + colorInteropID: "lin_ap0_scene" compression: "none" PixelAspectRatio: 1 screenWindowCenter: 0, 0 From a6baa75a73f20d813a3561587b8dc94cb9b8c3fb Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 29 Nov 2025 23:46:16 -0800 Subject: [PATCH 061/508] ci: Emergency fix change deprecated sonarqube action (#4969) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 6 +++--- .github/workflows/wheel.yml | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index 403d94e7b0..49b876d1ad 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -137,7 +137,7 @@ jobs: run: echo "date=`date -u +'%Y-%m-%dT%H:%M:%SZ'`" >> $GITHUB_OUTPUT - name: ccache-restore id: ccache-restore - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ env.CCACHE_DIR }} # path: ./ccache @@ -156,7 +156,7 @@ jobs: fi - name: Install sonar-scanner and build-wrapper if: inputs.sonar == '1' - uses: sonarsource/sonarcloud-github-c-cpp@e4882e1621ad2fb48dddfa48287411bed34789b1 # v2.0.2 + uses: sonarsource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 - name: Build if: inputs.skip_build != '1' shell: bash @@ -177,7 +177,7 @@ jobs: popd - name: ccache-save id: ccache-save - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ env.CCACHE_DIR }} key: ${{inputs.nametag}}-${{steps.ccache_cache_keys.outputs.date}} diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 0dde3628b3..84f6145a0d 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -129,7 +129,7 @@ jobs: - name: ccache-restore id: ccache-restore - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.manylinux}}-${{matrix.python}} @@ -163,7 +163,7 @@ jobs: - name: ccache-save id: ccache-save - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.manylinux}}-${{matrix.python}} @@ -236,7 +236,7 @@ jobs: - name: ccache-restore id: ccache-restore - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.python}} @@ -265,7 +265,7 @@ jobs: - name: ccache-save id: ccache-save - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.python}} @@ -332,7 +332,7 @@ jobs: - name: ccache-restore id: ccache-restore - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.python}} @@ -364,7 +364,7 @@ jobs: - name: ccache-save id: ccache-save - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.python}} @@ -421,7 +421,7 @@ jobs: - name: ccache-restore id: ccache-restore - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.python}} @@ -445,7 +445,7 @@ jobs: - name: ccache-save id: ccache-save - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccache key: wheel-${{runner.os}}-${{matrix.python}} From 152098f29618bd03244d1f18c4e95bb33bb187be Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 30 Nov 2025 10:28:40 -0800 Subject: [PATCH 062/508] ci: Try python 3.13 to fix Mac breakage on CI (#4970) I think something changed with Homebrew on the runners, this worked until last week. Emergency fix, will merge immediately if CI finally passes. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0359a3dd09..8652caf897 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -627,13 +627,13 @@ jobs: ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 benchmark: 1 - - desc: MacOS-14-ARM aclang15/C++20/py3.12 + - desc: MacOS-14-ARM aclang15/C++20/py3.13 runner: macos-14 - nametag: macos14-arm-py312 + nametag: macos14-arm-py313 cc_compiler: clang cxx_compiler: clang++ cxx_std: 20 - python_ver: "3.12" + python_ver: "3.13" - desc: MacOS-15-ARM aclang16/C++20/py3.13 runner: macos-15 nametag: macos15-arm-py313 From 35c6b68e4908066fc8ef3fe766cbaff363cb7e95 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 6 Dec 2025 11:42:19 -0800 Subject: [PATCH 063/508] ci: Address tight disk space on GHA runners (#4974) - Mount the host's root under /host/root so it's visible in the containers. - Remove stuff we don't need to free lots of disk space. - While we're at it, arrange for the commands (copied from OSL) that allow the containers to see a CPU, when available. This is freeing 20+ GB! --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/build-steps.yml | 4 ++++ src/build-scripts/gh-installdeps.bash | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index 49b876d1ad..7dccbeb624 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -95,6 +95,10 @@ jobs: container: image: ${{ inputs.container }} volumes: ${{ fromJson( inputs.container_volumes ) }} + options: -v /:/host/root ${{ (contains(inputs.runner, 'gpu') && '-e NVIDIA_DRIVER_CAPABILITIES=compute,graphics,utility --gpus all') || '-e A=x' }} + # Extra options: + # - Ensure the GPU runners have OptiX is visible in the container. + # - Mount the native filesystem under /host/root env: CXX: ${{inputs.cxx_compiler}} CC: ${{inputs.cc_compiler}} diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index 7ce0c8e4f8..fb1d2b8213 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -7,7 +7,14 @@ set -ex -df -h + +# Make extra space on the runners +df -h . +time rm -rf /usr/local/lib/android /host/root/usr/local/lib/android & +sleep 3 +# rather than block, delete in background, but give it a few secs to start +# clearing things out before moving on. +# Other candidates, if we need it: /usr/share/dotnet /usr/local/.ghcup # @@ -219,7 +226,8 @@ if [[ "$USE_ICC" != "" ]] ; then export CC=icc fi -df -h +df -h . +df -h /host/root || true # Save the env for use by other stages src/build-scripts/save-env.bash From 43aa730663ad53410f2dfde80ae4da212ed58729 Mon Sep 17 00:00:00 2001 From: Pavan Madduri Date: Mon, 8 Dec 2025 03:07:27 -0600 Subject: [PATCH 064/508] fix(webp): Allow out-of-order scanlines when writing webp (#4973) Move write_complete_data() to close() for proper scanline order support The WebP output plugin incorrectly assumed that scanlines would be written in sequential order, triggering the final write when y == height - 1. This violated the write_scanline API contract which allows scanlines to be written in any order (the plugin even advertises 'random_access' support). Changes: - Remove premature write_complete_data() call from write_scanline() - Move final encoding/writing to close() where it belongs - Add m_image_complete flag to track write state - Move buffer cleanup to after write_complete_data() in close() Fixes the issue where writing scanlines out of order would result in incomplete or corrupted WebP files. Signed-off-by: pmady Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/webp.imageio/webpoutput.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 5b743e2653..2c2acb85cc 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -238,11 +238,6 @@ WebpOutput::write_scanline(int y, int z, TypeDesc format, const void* data, std::vector scratch; data = to_native_scanline(format, data, xstride, scratch, m_dither, y, z); memcpy(&m_uncompressed_image[y * m_scanline_size], data, m_scanline_size); - - /* If this was the final scanline, we are done. */ - if (y == m_spec.height - 1) { - return write_complete_data(); - } return true; } @@ -271,9 +266,15 @@ WebpOutput::close() OIIO_DASSERT(m_uncompressed_image.size()); ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, m_spec.format, &m_uncompressed_image[0]); - std::vector().swap(m_uncompressed_image); } + // Write the complete image data on close, not during write_scanline. + // This allows scanlines to be written in any order. + if (ok && m_uncompressed_image.size()) { + ok = write_complete_data(); + } + + std::vector().swap(m_uncompressed_image); WebPPictureFree(&m_webp_picture); init(); return ok; From 5e90b3c2c2390fb520bf3085d8b36efa58092dc6 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 12 Dec 2025 09:03:23 +0100 Subject: [PATCH 065/508] feat: Auto convert between oiio:ColorSpace and CICP attributes in I/O (#4964) When reading image files with CICP metadata, automatically set the corresponding "oiio:ColorSpace". When writing files that support CICP and no other colorspace metadata can represent "oiio:ColorSpace", automatically write CICP metadata. Setting "oiio:ColorSpace" on read prefers scene referred over display referred color spaces, changing existing behavior as little as possible. The alternative would have been to interpret the presence of CICP metadata as an indication that the image is likely display referred, which might be reasonable too. Either way it's a guess. There is no automatic mapping from `g22_rec709_display` to CICP currently to keep behavior unchanged, as this is often not what you want and further discussion is needed to decided on the right behavior. Also add new ColorConfig `get_cicp` and `get_color_interop_id` API functions to share logic between file formats. ## Tests Tests were updated, the auto detected color space name appears in the output. Commands like these should also do the right thing automatically. ``` oiiotool in.exr --ociodisplay "Rec.2100-PQ - Display" "ACES 2.0 - HDR 1000 nits (P3 D65)" -o out.avif oiiotool out.avif --autocc -o out.exr ``` --------- Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/pythonbindings.rst | 67 +++++++ src/ffmpeg.imageio/ffmpeginput.cpp | 6 + src/heif.imageio/heifinput.cpp | 6 + src/heif.imageio/heifoutput.cpp | 12 +- src/include/OpenImageIO/color.h | 18 ++ src/libOpenImageIO/color_ocio.cpp | 167 ++++++++++++++++++ src/png.imageio/png_pvt.h | 38 ++-- src/python/py_colorconfig.cpp | 19 ++ src/python/stubs/OpenImageIO/__init__.pyi | 5 + testsuite/ffmpeg/ref/out-ffmpeg6.1.txt | 2 + testsuite/ffmpeg/ref/out-ffmpeg8.0.txt | 2 + testsuite/heif/ref/out-libheif1.12-orient.txt | 12 +- testsuite/heif/ref/out-libheif1.4.txt | 12 +- testsuite/heif/ref/out-libheif1.5.txt | 12 +- .../heif/ref/out-libheif1.9-with-av1-alt2.txt | 12 +- .../heif/ref/out-libheif1.9-with-av1.txt | 12 +- testsuite/heif/run.py | 5 + .../python-colorconfig/ref/out-ocio23.txt | 5 + .../python-colorconfig/ref/out-ocio24.txt | 5 + .../python-colorconfig/ref/out-ocio25.txt | 5 + testsuite/python-colorconfig/ref/out.txt | 5 + .../src/test_colorconfig.py | 5 + 22 files changed, 412 insertions(+), 20 deletions(-) diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index 35a73ff00b..d07735eed2 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -3888,6 +3888,73 @@ sections) work with deep inputs:: | +.. _sec-pythoncolorconfig: + + +ColorConfig +=========== + +The `ColorConfig` class that represents the set of color transformations that +are allowed. + +If OpenColorIO is enabled at build time, this configuration is loaded at +runtime, allowing the user to have complete control of all color transformation +math. See the +`OpenColorIO documentation `_ for details. + +If OpenColorIO is not enabled at build time, a generic color configuration +is provided for minimal color support. + +.. + TODO: The documentation for this class is incomplete. + +.. py:method:: get_cicp (colorspace) + + Find CICP code corresponding to the colorspace. + Return a sequence of 4 ints, or None if not found. + + Example: + + .. code-block:: python + + colorconfig = oiio.ColorConfig() + cicp = colorconfig.get_cicp("pq_rec2020_display") + if cicp: + primaries, transfer, matrix, color_range = cicp + + This function was added in OpenImageIO 3.1. + + +.. py:method:: get_color_interop_id (colorspace) + + Find color interop ID for the given colorspace. + Returns empty string if not found. + + Example: + + .. code-block:: python + + colorconfig = oiio.ColorConfig() + interop_id = colorconfig.get_color_interop_id("Rec.2100-PQ - Display") + + This function was added in OpenImageIO 3.1. + + +.. py:method:: get_color_interop_id (cicp) + + Find color interop ID corresponding to the CICP code. + Returns empty string if not found. + + Example: + + .. code-block:: python + + colorconfig = oiio.ColorConfig() + interop_id = colorconfig.get_color_interop_id([9, 16, 9, 1]) + + This function was added in OpenImageIO 3.1. + + .. _sec-pythonmiscapi: Miscellaneous Utilities diff --git a/src/ffmpeg.imageio/ffmpeginput.cpp b/src/ffmpeg.imageio/ffmpeginput.cpp index 8eec06b08f..26227dd61c 100644 --- a/src/ffmpeg.imageio/ffmpeginput.cpp +++ b/src/ffmpeg.imageio/ffmpeginput.cpp @@ -71,6 +71,7 @@ receive_frame(AVCodecContext* avctx, AVFrame* picture, AVPacket* avpkt) +#include #include #include #include @@ -549,6 +550,11 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec) m_codec_context->colorspace, m_codec_context->color_range == AVCOL_RANGE_MPEG ? 0 : 1 }; m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); + const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); + string_view interop_id = colorconfig.get_color_interop_id(cicp); + if (!interop_id.empty()) + m_spec.attribute("oiio:ColorSpace", interop_id); + m_nsubimages = m_frames; spec = m_spec; m_filename = name; diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp index 4ba8e2fb61..349bcdb1d4 100644 --- a/src/heif.imageio/heifinput.cpp +++ b/src/heif.imageio/heifinput.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO +#include #include #include #include @@ -292,6 +293,11 @@ HeifInput::seek_subimage(int subimage, int miplevel) int(nclx->matrix_coefficients), int(nclx->full_range_flag ? 1 : 0) }; m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); + const ColorConfig& colorconfig( + ColorConfig::default_colorconfig()); + string_view interop_id = colorconfig.get_color_interop_id(cicp); + if (!interop_id.empty()) + m_spec.attribute("oiio:ColorSpace", interop_id); } heif_nclx_color_profile_free(nclx); } diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 6ed1dbb439..9998ab5a5f 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -3,6 +3,7 @@ // https://github.com/AcademySoftwareFoundation/OpenImageIO +#include #include #include #include @@ -249,10 +250,13 @@ HeifOutput::close() std::unique_ptr nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free); - const ParamValue* p = m_spec.find_attribute("CICP", - TypeDesc(TypeDesc::INT, 4)); - if (p) { - const int* cicp = static_cast(p->data()); + 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); + if (!cicp.empty()) { nclx->color_primaries = heif_color_primaries(cicp[0]); nclx->transfer_characteristics = heif_transfer_characteristics( cicp[1]); diff --git a/src/include/OpenImageIO/color.h b/src/include/OpenImageIO/color.h index 46676efe76..99eba56396 100644 --- a/src/include/OpenImageIO/color.h +++ b/src/include/OpenImageIO/color.h @@ -402,6 +402,24 @@ class OIIO_API ColorConfig { bool equivalent(string_view color_space, string_view other_color_space) const; + /// Find CICP code corresponding to the colorspace. + /// Return a cspan of 4 ints, or an empty span if not found. + /// + /// @version 3.1 + cspan get_cicp(string_view colorspace) const; + + /// Find color interop ID for the given colorspace. + /// Returns empty string if not found. + /// + /// @version 3.1 + string_view get_color_interop_id(string_view colorspace) const; + + /// Find color interop ID corresponding to the CICP code. + /// Returns empty string if not found. + /// + /// @version 3.1 + string_view get_color_interop_id(const int cicp[4]) const; + /// Return a filename or other identifier for the config we're using. std::string configname() const; diff --git a/src/libOpenImageIO/color_ocio.cpp b/src/libOpenImageIO/color_ocio.cpp index 26f45c8d2b..a365cd9b48 100644 --- a/src/libOpenImageIO/color_ocio.cpp +++ b/src/libOpenImageIO/color_ocio.cpp @@ -1997,6 +1997,173 @@ ColorConfig::parseColorSpaceFromString(string_view str) const } +////////////////////////////////////////////////////////////////////////// +// +// Color Interop ID + +namespace { +enum class CICPPrimaries : int { + Rec709 = 1, + Rec2020 = 9, + XYZD65 = 10, + P3D65 = 12, +}; + +enum class CICPTransfer : int { + BT709 = 1, + Gamma22 = 4, + Linear = 8, + sRGB = 13, + PQ = 16, + Gamma26 = 17, + HLG = 18, +}; + +enum class CICPMatrix : int { + RGB = 0, + BT709 = 1, + Unspecified = 2, + Rec2020_NCL = 9, + Rec2020_CL = 10, +}; + +enum class CICPRange : int { + Narrow = 0, + Full = 1, +}; + +struct ColorInteropID { + constexpr ColorInteropID(const char* interop_id) + : interop_id(interop_id) + , cicp({ 0, 0, 0, 0 }) + , has_cicp(false) + { + } + + constexpr ColorInteropID(const char* interop_id, CICPPrimaries primaries, + CICPTransfer transfer, CICPMatrix matrix) + : interop_id(interop_id) + , cicp({ int(primaries), int(transfer), int(matrix), + int(CICPRange::Full) }) + , has_cicp(true) + { + } + + const char* interop_id; + std::array cicp; + bool has_cicp; +}; + +// Mapping between color interop ID and CICP, based on Color Interop Forum +// recommendations. +constexpr ColorInteropID color_interop_ids[] = { + // Scene referred interop IDs first so they are the default in automatic + // conversion from CICP to interop ID. Some are not display color spaces + // at all, but can be represented by CICP anyway. + { "lin_ap1_scene" }, + { "lin_ap0_scene" }, + { "lin_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Linear, + CICPMatrix::BT709 }, + { "lin_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::Linear, + CICPMatrix::BT709 }, + { "lin_rec2020_scene", CICPPrimaries::Rec2020, CICPTransfer::Linear, + CICPMatrix::Rec2020_CL }, + { "lin_adobergb_scene" }, + { "lin_ciexyzd65_scene", CICPPrimaries::XYZD65, CICPTransfer::Linear, + CICPMatrix::Unspecified }, + { "srgb_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::sRGB, + CICPMatrix::BT709 }, + { "g22_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Gamma22, + CICPMatrix::BT709 }, + { "g18_rec709_scene" }, + { "srgb_ap1_scene" }, + { "g22_ap1_scene" }, + { "srgb_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::sRGB, + CICPMatrix::BT709 }, + { "g22_adobergb_scene" }, + { "data" }, + { "unknown" }, + + // Display referred interop IDs. + { "srgb_rec709_display", CICPPrimaries::Rec709, CICPTransfer::sRGB, + CICPMatrix::BT709 }, + { "g24_rec709_display", CICPPrimaries::Rec709, CICPTransfer::BT709, + CICPMatrix::BT709 }, + { "srgb_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::sRGB, + CICPMatrix::BT709 }, + { "srgbe_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::sRGB, + CICPMatrix::BT709 }, + { "pq_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::PQ, + CICPMatrix::Rec2020_NCL }, + { "pq_rec2020_display", CICPPrimaries::Rec2020, CICPTransfer::PQ, + CICPMatrix::Rec2020_NCL }, + { "hlg_rec2020_display", CICPPrimaries::Rec2020, CICPTransfer::HLG, + CICPMatrix::Rec2020_NCL }, + // No CICP mapping to keep previous behavior unchanged, as Gamma 2.2 + // display is more likely meant to be written as sRGB. On read the + // scene referred interop ID will be used. + { "g22_rec709_display", + /* CICPPrimaries::Rec709, CICPTransfer::Gamma22, CICPMatrix::BT709 */ }, + // No CICP code for Adobe RGB primaries. + { "g22_adobergb_display" }, + { "g26_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::Gamma26, + CICPMatrix::BT709 }, + { "g26_xyzd65_display", CICPPrimaries::XYZD65, CICPTransfer::Gamma26, + CICPMatrix::Unspecified }, + { "pq_xyzd65_display", CICPPrimaries::XYZD65, CICPTransfer::PQ, + CICPMatrix::Unspecified }, +}; +} // namespace + +string_view +ColorConfig::get_color_interop_id(string_view colorspace) const +{ + if (colorspace.empty()) + return ""; +#if OCIO_VERSION_HEX >= MAKE_OCIO_VERSION_HEX(2, 5, 0) + if (getImpl()->config_ && !disable_ocio) { + OCIO::ConstColorSpaceRcPtr c = getImpl()->config_->getColorSpace( + std::string(resolve(colorspace)).c_str()); + const char* interop_id = (c) ? c->getInteropID() : nullptr; + if (interop_id) { + return interop_id; + } + } +#endif + for (const ColorInteropID& interop : color_interop_ids) { + if (equivalent(colorspace, interop.interop_id)) { + return interop.interop_id; + } + } + return ""; +} + +string_view +ColorConfig::get_color_interop_id(const int cicp[4]) const +{ + for (const ColorInteropID& interop : color_interop_ids) { + if (interop.has_cicp && interop.cicp[0] == cicp[0] + && interop.cicp[1] == cicp[1]) { + return interop.interop_id; + } + } + return ""; +} + +cspan +ColorConfig::get_cicp(string_view colorspace) const +{ + string_view interop_id = get_color_interop_id(colorspace); + if (!interop_id.empty()) { + for (const ColorInteropID& interop : color_interop_ids) { + if (interop.has_cicp && interop_id == interop.interop_id) { + return interop.cicp; + } + } + } + return cspan(); +} + ////////////////////////////////////////////////////////////////////////// // diff --git a/src/png.imageio/png_pvt.h b/src/png.imageio/png_pvt.h index a6a7c06609..3c103eb836 100644 --- a/src/png.imageio/png_pvt.h +++ b/src/png.imageio/png_pvt.h @@ -330,8 +330,12 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type, { png_byte pri = 0, trc = 0, mtx = 0, vfr = 0; if (png_get_cICP(sp, ip, &pri, &trc, &mtx, &vfr)) { - int cicp[4] = { pri, trc, mtx, vfr }; + const int cicp[4] = { pri, trc, mtx, vfr }; spec.attribute(CICP_ATTR, TypeDesc(TypeDesc::INT, 4), cicp); + const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); + string_view interop_id = colorconfig.get_color_interop_id(cicp); + if (!interop_id.empty()) + spec.attribute("oiio:ColorSpace", interop_id); } } #endif @@ -608,7 +612,8 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, string_view colorspace = spec.get_string_attribute("oiio:ColorSpace", "srgb_rec709_scene"); const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); - srgb = false; + OIIO_MAYBE_UNUSED bool wrote_colorspace = false; + srgb = false; if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) { srgb = true; gamma = 1.0f; @@ -628,7 +633,8 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, 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; + srgb = false; + wrote_colorspace = true; } else if (Strutil::istarts_with(colorspace, "Gamma")) { // Back compatible, this is DEPRECATED(3.1) Strutil::parse_word(colorspace); @@ -638,24 +644,28 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, 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; + 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; + 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; + srgb = false; + wrote_colorspace = true; } else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) { 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; + srgb = true; + wrote_colorspace = true; } // Write ICC profile, if we have anything @@ -667,8 +677,10 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec, return "Could not set PNG iCCP chunk"; unsigned char* icc_profile = (unsigned char*)icc_profile_parameter->data(); - if (icc_profile && length) + if (icc_profile && length) { png_set_iCCP(sp, ip, "Embedded Profile", 0, icc_profile, length); + wrote_colorspace = true; + } } if (false && !spec.find_attribute("DateTime")) { @@ -724,13 +736,17 @@ 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)); - if (p) { - const int* int_vals = static_cast(p->data()); + cspan cicp = (p) ? p->as_cspan() + : (!wrote_colorspace) ? colorconfig.get_cicp(colorspace) + : cspan(); + if (!cicp.empty()) { png_byte vals[4]; for (int i = 0; i < 4; ++i) - vals[i] = static_cast(int_vals[i]); + vals[i] = static_cast(cicp[i]); if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp) return "Could not set PNG cICP chunk"; // libpng will only write the chunk if the third byte is 0 diff --git a/src/python/py_colorconfig.cpp b/src/python/py_colorconfig.cpp index 129d089502..35f8e304e8 100644 --- a/src/python/py_colorconfig.cpp +++ b/src/python/py_colorconfig.cpp @@ -4,6 +4,7 @@ #include "py_oiio.h" #include +#include #include namespace PyOpenImageIO { @@ -160,6 +161,24 @@ declare_colorconfig(py::module& m) return self.equivalent(color_space, other_color_space); }, "color_space"_a, "other_color_space"_a) + .def("get_color_interop_id", + [](const ColorConfig& self, const std::string& colorspace) { + return std::string(self.get_color_interop_id(colorspace)); + }) + .def("get_color_interop_id", + [](const ColorConfig& self, const std::array cicp) { + return std::string(self.get_color_interop_id(cicp.data())); + }) + .def("get_cicp", + [](const ColorConfig& self, const std::string& colorspace) + -> std::optional> { + cspan cicp = self.get_cicp(colorspace); + if (!cicp.empty()) { + return std::array( + { cicp[0], cicp[1], cicp[2], cicp[3] }); + } + return std::nullopt; + }) .def("configname", &ColorConfig::configname) .def_static("default_colorconfig", []() -> const ColorConfig& { return ColorConfig::default_colorconfig(); diff --git a/src/python/stubs/OpenImageIO/__init__.pyi b/src/python/stubs/OpenImageIO/__init__.pyi index 200be7d774..88681897d6 100644 --- a/src/python/stubs/OpenImageIO/__init__.pyi +++ b/src/python/stubs/OpenImageIO/__init__.pyi @@ -207,6 +207,11 @@ class ColorConfig: def getRoles(self) -> list[str]: ... def getViewNameByIndex(self, display: str = ..., *, index: typing.SupportsInt) -> str: ... def getViewNames(self, display: str = ...) -> list[str]: ... + def get_cicp(self, *args, **kwargs): ... + @overload + def get_color_interop_id(self, arg0: str, /) -> str: ... + @overload + def get_color_interop_id(self, arg0, /) -> str: ... def geterror(self) -> str: ... def parseColorSpaceFromString(self, arg0: str, /) -> str: ... def resolve(self, name: str) -> str: ... diff --git a/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt b/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt index 0d037e3cc5..1f1258b343 100644 --- a/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt +++ b/testsuite/ffmpeg/ref/out-ffmpeg6.1.txt @@ -96,6 +96,7 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie FramesPerSecond: 24/1 (24) ffmpeg:codec_name: "Google VP9" oiio:BitsPerSample: 10 + oiio:ColorSpace: "pq_rec2020_display" oiio:Movie: 1 oiio:subimages: 2 subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie @@ -106,5 +107,6 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie FramesPerSecond: 24/1 (24) ffmpeg:codec_name: "Google VP9" oiio:BitsPerSample: 10 + oiio:ColorSpace: "pq_rec2020_display" oiio:Movie: 1 oiio:subimages: 2 diff --git a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt index a4b503edca..28084a22e3 100644 --- a/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt +++ b/testsuite/ffmpeg/ref/out-ffmpeg8.0.txt @@ -96,6 +96,7 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie FramesPerSecond: 24/1 (24) ffmpeg:codec_name: "Google VP9" oiio:BitsPerSample: 10 + oiio:ColorSpace: "pq_rec2020_display" oiio:Movie: 1 oiio:subimages: 2 subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie @@ -106,5 +107,6 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie FramesPerSecond: 24/1 (24) ffmpeg:codec_name: "Google VP9" oiio:BitsPerSample: 10 + oiio:ColorSpace: "pq_rec2020_display" oiio:Movie: 1 oiio:subimages: 2 diff --git a/testsuite/heif/ref/out-libheif1.12-orient.txt b/testsuite/heif/ref/out-libheif1.12-orient.txt index 78b7d9fd6f..875e3ac54e 100644 --- a/testsuite/heif/ref/out-libheif1.12-orient.txt +++ b/testsuite/heif/ref/out-libheif1.12-orient.txt @@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif Exif:FlashPixVersion: "0100" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.4.txt b/testsuite/heif/ref/out-libheif1.4.txt index bd0ff14603..9d8304f14a 100644 --- a/testsuite/heif/ref/out-libheif1.4.txt +++ b/testsuite/heif/ref/out-libheif1.4.txt @@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif Exif:FlashPixVersion: "0100" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.5.txt b/testsuite/heif/ref/out-libheif1.5.txt index 39398f71cd..9dfd4ff23d 100644 --- a/testsuite/heif/ref/out-libheif1.5.txt +++ b/testsuite/heif/ref/out-libheif1.5.txt @@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif Exif:FlashPixVersion: "0100" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt index 6be6dc26e0..c938a6fe73 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt @@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif Exif:FlashPixVersion: "0100" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1.txt b/testsuite/heif/ref/out-libheif1.9-with-av1.txt index e1f848ea83..f6d7ca55a5 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1.txt @@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif Exif:FlashPixVersion: "0100" heif:UnassociatedAlpha: 1 oiio:BitsPerSample: 10 - oiio:ColorSpace: "srgb_rec709_scene" + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic ../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E diff --git a/testsuite/heif/run.py b/testsuite/heif/run.py index 9dc5a06f35..0a3250e374 100755 --- a/testsuite/heif/run.py +++ b/testsuite/heif/run.py @@ -13,6 +13,11 @@ " -d uint10 --cicp \"9,16,9,1\" -o cicp_pq.avif" ) command += info_command ("cicp_pq.avif", safematch=True) + +command += oiiotool (os.path.join(imagedir, "test-10bit.avif") + + " -d uint10 --attrib oiio:ColorSpace hlg_rec2020_display -o colorspace_hlg.avif" ) +command += info_command ("colorspace_hlg.avif", safematch=True) + files = [ "greyhounds-looking-for-a-table.heic", "sewing-threads.heic" ] for f in files: command = command + info_command (os.path.join(OIIO_TESTSUITE_IMAGEDIR, f)) diff --git a/testsuite/python-colorconfig/ref/out-ocio23.txt b/testsuite/python-colorconfig/ref/out-ocio23.txt index 883ef051d4..1badffcb7f 100644 --- a/testsuite/python-colorconfig/ref/out-ocio23.txt +++ b/testsuite/python-colorconfig/ref/out-ocio23.txt @@ -26,6 +26,11 @@ equivalent('linear', 'lin_srgb'): False equivalent('scene_linear', 'lin_srgb'): False equivalent('ACEScg', 'scene_linear'): True equivalent('lnf', 'scene_linear'): False +get_color_interop_id('ACEScg') = lin_ap1_scene +get_color_interop_id('lin_srgb') = lin_rec709_scene +get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene +get_cicp('pq_rec2020_display') = [9, 16, 9, 1] +get_cicp('unknown_interop_id') = None Loaded test OCIO config: oiio_test_v0.9.2.ocio Parsed color space for filepath 'foo_lin_ap1.exr': ACEScg diff --git a/testsuite/python-colorconfig/ref/out-ocio24.txt b/testsuite/python-colorconfig/ref/out-ocio24.txt index 1bf39d0cb3..2882859127 100644 --- a/testsuite/python-colorconfig/ref/out-ocio24.txt +++ b/testsuite/python-colorconfig/ref/out-ocio24.txt @@ -26,6 +26,11 @@ equivalent('linear', 'lin_srgb'): False equivalent('scene_linear', 'lin_srgb'): False equivalent('ACEScg', 'scene_linear'): True equivalent('lnf', 'scene_linear'): False +get_color_interop_id('ACEScg') = lin_ap1_scene +get_color_interop_id('lin_srgb') = lin_rec709_scene +get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene +get_cicp('pq_rec2020_display') = [9, 16, 9, 1] +get_cicp('unknown_interop_id') = None Loaded test OCIO config: oiio_test_v0.9.2.ocio Parsed color space for filepath 'foo_lin_ap1.exr': ACEScg diff --git a/testsuite/python-colorconfig/ref/out-ocio25.txt b/testsuite/python-colorconfig/ref/out-ocio25.txt index 07569ff915..b12d8ce08d 100644 --- a/testsuite/python-colorconfig/ref/out-ocio25.txt +++ b/testsuite/python-colorconfig/ref/out-ocio25.txt @@ -26,6 +26,11 @@ equivalent('linear', 'lin_srgb'): False equivalent('scene_linear', 'lin_srgb'): False equivalent('ACEScg', 'scene_linear'): True equivalent('lnf', 'scene_linear'): False +get_color_interop_id('ACEScg') = lin_ap1_scene +get_color_interop_id('lin_srgb') = lin_rec709_scene +get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene +get_cicp('pq_rec2020_display') = [9, 16, 9, 1] +get_cicp('unknown_interop_id') = None Loaded test OCIO config: oiio_test_v0.9.2.ocio Parsed color space for filepath 'foo_lin_ap1.exr': ACEScg diff --git a/testsuite/python-colorconfig/ref/out.txt b/testsuite/python-colorconfig/ref/out.txt index 9dee6146d9..cb9a3dddfc 100644 --- a/testsuite/python-colorconfig/ref/out.txt +++ b/testsuite/python-colorconfig/ref/out.txt @@ -26,6 +26,11 @@ equivalent('linear', 'lin_srgb'): False equivalent('scene_linear', 'lin_srgb'): False equivalent('ACEScg', 'scene_linear'): True equivalent('lnf', 'scene_linear'): False +get_color_interop_id('ACEScg') = lin_ap1_scene +get_color_interop_id('lin_srgb') = lin_rec709_scene +get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene +get_cicp('pq_rec2020_display') = [9, 16, 9, 1] +get_cicp('unknown_interop_id') = None Loaded test OCIO config: oiio_test_v0.9.2.ocio Parsed color space for filepath 'foo_lin_ap1.exr': ACEScg diff --git a/testsuite/python-colorconfig/src/test_colorconfig.py b/testsuite/python-colorconfig/src/test_colorconfig.py index 597223e785..766e6a037b 100755 --- a/testsuite/python-colorconfig/src/test_colorconfig.py +++ b/testsuite/python-colorconfig/src/test_colorconfig.py @@ -48,6 +48,11 @@ print ("equivalent('scene_linear', 'lin_srgb'):", config.equivalent("scene_linear", "lin_srgb")) print ("equivalent('ACEScg', 'scene_linear'):", config.equivalent("ACEScg", "scene_linear")) print ("equivalent('lnf', 'scene_linear'):", config.equivalent("lnf", "scene_linear")) + print ("get_color_interop_id('ACEScg') = ", config.get_color_interop_id("ACEScg")) + print ("get_color_interop_id('lin_srgb') = ", config.get_color_interop_id("lin_srgb")) + print ("get_color_interop_id([1, 13, 1, 1]) = ", config.get_color_interop_id([1, 13, 1, 1])) + print ("get_cicp('pq_rec2020_display') = ", config.get_cicp("pq_rec2020_display")) + print ("get_cicp('unknown_interop_id') = ", config.get_cicp("unknown_interop_id")) print ("") config = oiio.ColorConfig(str(TEST_CONFIG_PATH)) From 1ae4fd8ce35bc986565932ab3300e364778e0ba4 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 21 Nov 2025 22:31:56 -0800 Subject: [PATCH 066/508] fix: Fix some legacy 'Linear' color references (#4959) Convert to modern oiio color space nomenclature. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/dpx.imageio/dpxinput.cpp | 4 ++-- src/dpx.imageio/dpxoutput.cpp | 15 ++++++++++----- .../python-imagebufalgo/src/test_imagebufalgo.py | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/dpx.imageio/dpxinput.cpp b/src/dpx.imageio/dpxinput.cpp index 5dfadd919f..6675e95ffe 100644 --- a/src/dpx.imageio/dpxinput.cpp +++ b/src/dpx.imageio/dpxinput.cpp @@ -312,9 +312,9 @@ DPXInput::seek_subimage(int subimage, int miplevel) // image linearity switch (m_dpx.header.Transfer(subimage)) { - case dpx::kLinear: m_spec.set_colorspace("Linear"); break; + case dpx::kLinear: m_spec.set_colorspace("lin_rec709_scene"); break; case dpx::kLogarithmic: m_spec.set_colorspace("KodakLog"); break; - case dpx::kITUR709: m_spec.set_colorspace("Rec709"); break; + case dpx::kITUR709: m_spec.set_colorspace("srgb_rec709_scene"); break; case dpx::kUserDefined: if (!std::isnan(m_dpx.header.Gamma()) && m_dpx.header.Gamma() != 0) { set_colorspace_rec709_gamma(m_spec, float(m_dpx.header.Gamma())); diff --git a/src/dpx.imageio/dpxoutput.cpp b/src/dpx.imageio/dpxoutput.cpp index 5667c83249..4db1103a3c 100644 --- a/src/dpx.imageio/dpxoutput.cpp +++ b/src/dpx.imageio/dpxoutput.cpp @@ -12,6 +12,7 @@ #include "libdpx/DPX.h" #include "libdpx/DPXColorConverter.h" +#include #include #include #include @@ -435,14 +436,18 @@ 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 (Strutil::iequals(colorspace, "Linear")) + if (colorconfig.equivalent(colorspace, "lin_rec709_scene")) m_transfer = dpx::kLinear; - else if (Strutil::istarts_with(colorspace, "Gamma")) - m_transfer = dpx::kUserDefined; - else if (Strutil::iequals(colorspace, "Rec709")) + else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) m_transfer = dpx::kITUR709; - else if (Strutil::iequals(colorspace, "KodakLog")) + 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")) + m_transfer = dpx::kUserDefined; + else if (colorconfig.equivalent(colorspace, "KodakLog")) m_transfer = dpx::kLogarithmic; else { std::string dpxtransfer = spec_s.get_string_attribute("dpx:Transfer", diff --git a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py index 17e943e7af..9dbb0648c1 100755 --- a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py +++ b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py @@ -374,9 +374,9 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image b.setpixel(0, 1, (.5,.5,.5,1)) b.setpixel(1, 1, (1,1,1,1)) dumpimg (b, msg="linear src=") - r = test_iba (ImageBufAlgo.colorconvert, b, "Linear", "sRGB") + r = test_iba (ImageBufAlgo.colorconvert, b, "lin_rec709", "sRGB") dumpimg (r, msg="to srgb =") - r = ImageBufAlgo.colorconvert(r, "sRGB", "Linear") + r = ImageBufAlgo.colorconvert(r, "sRGB", "lin_rec709") dumpimg (r, msg="back to linear =") # Just to test, make a matrix that halves red, doubles green, # adds 0.1 to blue. From ab4fd7a951755a3223e21eb601529d8841c4cfd8 Mon Sep 17 00:00:00 2001 From: JacksonSun-adsk Date: Fri, 12 Dec 2025 18:11:52 -0500 Subject: [PATCH 067/508] Speedup to detect the existence of files on Windows (#4977) On Windows, `Filesystem::exists` forward the call to `std::filesystem::exists`, and finally calls `_std_fs_get_stats` . `_std_fs_get_stats` is slow if the argument is a netwok path. Signed-off-by: Jackson Sun Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libutil/filesystem.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libutil/filesystem.cpp b/src/libutil/filesystem.cpp index 8c0fccadc8..504cbd8480 100644 --- a/src/libutil/filesystem.cpp +++ b/src/libutil/filesystem.cpp @@ -330,8 +330,15 @@ Filesystem::path_is_absolute(string_view path, bool dot_is_absolute) bool Filesystem::exists(string_view path) noexcept { +#ifdef _WIN32 + // filesystem::exists is slow on Windows for network paths, so use the + // WinAPI directly + return INVALID_FILE_ATTRIBUTES + != GetFileAttributesW(Strutil::utf8_to_utf16wstring(path).c_str()); +#else error_code ec; return filesystem::exists(u8path(path), ec); +#endif } From dcd06c7ac054619048fa085cf7ae4a986a54abde Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 24 Nov 2025 12:51:28 -0800 Subject: [PATCH 068/508] fix(exif): Support EXIF 3.0 tags (#4961) We were supporting Exif 2.3, but in 2024, Exif 3.0 was published and it adds some more tags for us to recognize. The only part I am not sure about here is that the ExifVersion we output is still marked as 2.3, not 3.0, because I'm worried that if we mark it as 3.0, older software reading the images will reject it. Let's see what happens. If downstream apps care, maybe we can bump to 3.0, or even bump to 3.0 only if any of the 3.0 tags are used (that is more involved). Fixes #4960 --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/stdmetadata.rst | 53 +++++++++++++++++++ src/include/OpenImageIO/tiffutils.h | 19 +++++++ src/libOpenImageIO/exif.cpp | 18 ++++++- src/libOpenImageIO/exif.h | 3 ++ src/libOpenImageIO/xmp.cpp | 2 +- testsuite/heif/ref/out-libheif1.12-orient.txt | 1 + testsuite/heif/ref/out-libheif1.4.txt | 1 + testsuite/heif/ref/out-libheif1.5.txt | 1 + testsuite/heif/ref/out-libheif1.9-alt2.txt | 1 + .../heif/ref/out-libheif1.9-with-av1-alt2.txt | 1 + .../heif/ref/out-libheif1.9-with-av1.txt | 1 + testsuite/heif/ref/out-libheif1.9.txt | 1 + 12 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/doc/stdmetadata.rst b/src/doc/stdmetadata.rst index 646d348355..70a3fd6e2f 100644 --- a/src/doc/stdmetadata.rst +++ b/src/doc/stdmetadata.rst @@ -760,6 +760,59 @@ A sum of: A unique identifier for the image, as 16 ASCII hexadecimal digits representing a 128-bit number. +.. option:: "Exif:ImageTitle" : string + + Title of the image. + +.. option:: "Exif:Photographer" : string + + The name of the photographer. This may be different from the "Artist" + field. + +.. option:: "Exif:ImageEdtor" : string + + The name of the main person who edited the image. + +.. option:: "Exif:CameraFirmware" : string + + The name and version of the firmware of the camera that captured the image. + +.. option:: "Exif:RAWDevelopingSoftware" : string + + The name and version of the software that developed the RAW image. + +.. option:: "Exif:ImageEditingSoftware" : string + + The name and version of any image editing software that was used to + process or edit the image. + +.. option:: "Exif:MetadataEditingSoftware" : string + + The name and version of any image editing software that was used to + edit the image metadata but not the pixels. + +.. option:: "Exif:CompositeImage" : int + + Indicates whether the recorded image is a composite image (generated + from capturing multiple, tentatively recorded, source images). + + === ============================================================== + 0 unknown + 1 non-composite image + 2 general composite image + 3 composite image captured when shooting + === ============================================================== + +.. option:: "Exif:SourceImageNumberOfCompositeImage" : ushort[2] + + If a composite image, the first value is the number of source images + captured (at least 2) and the second value is the number of those + source images ultimately used in the comopsite image. + +.. option:: "Exif:SourceImageExposureTimesOfCompositeImage" + + This is currently unsupported by OpenImageIO. + GPS Exif metadata diff --git a/src/include/OpenImageIO/tiffutils.h b/src/include/OpenImageIO/tiffutils.h index c631665b5d..1fbf5c7746 100644 --- a/src/include/OpenImageIO/tiffutils.h +++ b/src/include/OpenImageIO/tiffutils.h @@ -45,6 +45,14 @@ struct TIFFDirEntry { OIIO_NAMESPACE_BEGIN +// Exif spec extends TIFFDataType beyond what TIFF or litiff define in +// TIFFDataType. +enum TIFFDataType_Exif3_Extensions { + EXIF_UTF8_TYPE = 129 +}; + + + // Define EXIF constants enum TIFFTAG { EXIF_EXPOSURETIME = 33434, @@ -128,6 +136,17 @@ enum TIFFTAG { EXIF_LENSMODEL = 42036, EXIF_LENSSERIALNUMBER = 42037, EXIF_GAMMA = 42240, + // Exif 3.0 additions follow + EXIF_IMAGETITLE = 42038, + EXIF_PHOTOGRAPHER = 42039, + EXIF_IMAGEEDTOR = 42040, + EXIF_CAMERAFIRMWARE = 42041, + EXIF_RAWDEVELOPINGSOFTWARE = 42042, + EXIF_IMAGEEDITINGSOFTWARE = 42043, + EXIF_METADATAEDITINGSOFTWARE = 42044, + EXIF_COMPOSITEIMAGE = 42080, + EXIF_SOURCEIMAGENUMBEROFCOMPOSITEIMAGE = 42081, + EXIF_SOURCEIMAGEEXPOSURETIMESOFCOMPOSITEIMAGE = 42082, }; OIIO_NAMESPACE_END diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index ccf63979a5..ab95d881ef 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -260,6 +260,7 @@ print_dir_entry(std::ostream& out, const TagMap& tagmap, switch (dir.tdir_type) { case TIFF_ASCII: + case EXIF_TIFF_UTF8: OIIO::print(out, "'{}'", string_view(mydata, dir.tdir_count)); break; case TIFF_RATIONAL: { @@ -522,7 +523,18 @@ static const TagInfo exif_tag_table[] = { { EXIF_LENSMAKE, "Exif:LensMake", TIFF_ASCII, 0 }, { EXIF_LENSMODEL, "Exif:LensModel", TIFF_ASCII, 0 }, { EXIF_LENSSERIALNUMBER, "Exif:LensSerialNumber", TIFF_ASCII, 0 }, - { EXIF_GAMMA, "Exif:Gamma", TIFF_RATIONAL, 0 } + { EXIF_GAMMA, "Exif:Gamma", TIFF_RATIONAL, 0 }, + // Exif 3.0 additions follow + { EXIF_IMAGETITLE, "Exif:ImageTitle", TIFF_ASCII, 0 }, + { EXIF_PHOTOGRAPHER, "Exif:Photographer", TIFF_ASCII, 0 }, + { EXIF_IMAGEEDTOR, "Exif:ImageEdtor", TIFF_ASCII, 0 }, + { EXIF_CAMERAFIRMWARE, "Exif:CameraFirmware", TIFF_ASCII, 0 }, + { EXIF_RAWDEVELOPINGSOFTWARE, "Exif:RAWDevelopingSoftware", TIFF_ASCII, 0 }, + { EXIF_IMAGEEDITINGSOFTWARE, "Exif:ImageEditingSoftware", TIFF_ASCII, 0 }, + { EXIF_METADATAEDITINGSOFTWARE, "Exif:MetadataEditingSoftware", TIFF_ASCII, 0 }, + { EXIF_COMPOSITEIMAGE, "Exif:CompositeImage", TIFF_SHORT, 1 }, + { EXIF_SOURCEIMAGENUMBEROFCOMPOSITEIMAGE, "Exif:SourceImageNumberOfCompositeImage", TIFF_SHORT, 2 }, + { EXIF_SOURCEIMAGEEXPOSURETIMESOFCOMPOSITEIMAGE, "Exif:SourceImageExposureTimesOfCompositeImage", TIFF_NOTYPE, 0 }, // clang-format on }; @@ -742,7 +754,7 @@ add_exif_item_to_spec(ImageSpec& spec, const char* name, spec.attribute(name, TypeDesc(TypeDesc::FLOAT, int(count)), f); return; } - if (dirp->tdir_type == TIFF_ASCII) { + if (dirp->tdir_type == TIFF_ASCII || dirp->tdir_type == EXIF_UTF8_TYPE) { size_t len = tiff_data_size(*dirp); cspan dspan = pvt::dataspan(*dirp, buf, offset_adjustment, len); @@ -1393,6 +1405,8 @@ encode_exif(const ImageSpec& spec, std::vector& blob, // Add some required Exif tags that wouldn't be in the spec append_tiff_dir_entry(exifdirs, blob, EXIF_EXIFVERSION, TIFF_UNDEFINED, 4, as_bytes("0230", 4), tiffstart, 0, endianreq); + // NOTE: We are still saying we write Exif 2.3 even though we support + // the additional 3.0 tags. append_tiff_dir_entry(exifdirs, blob, EXIF_FLASHPIXVERSION, TIFF_UNDEFINED, 4, as_bytes("0100", 4), tiffstart, 0, endianreq); diff --git a/src/libOpenImageIO/exif.h b/src/libOpenImageIO/exif.h index b1cc943d98..2789fa5b59 100644 --- a/src/libOpenImageIO/exif.h +++ b/src/libOpenImageIO/exif.h @@ -22,6 +22,9 @@ #define DEBUG_EXIF_UNHANDLED 0 +// Exif Documentation: +// - https://archive.org/details/exif-specs-3.0-dc-008-translation-2023-e + OIIO_NAMESPACE_BEGIN namespace pvt { diff --git a/src/libOpenImageIO/xmp.cpp b/src/libOpenImageIO/xmp.cpp index 88e9f3fe42..bc1a777a5e 100644 --- a/src/libOpenImageIO/xmp.cpp +++ b/src/libOpenImageIO/xmp.cpp @@ -317,7 +317,7 @@ add_attrib(ImageSpec& spec, string_view xmlname, string_view xmlvalue, && count == 1) { oiiotype = TypeDesc::FLOAT; special = Rational; - } else if (tifftype == TIFF_ASCII) + } else if (tifftype == TIFF_ASCII || tifftype == EXIF_UTF8_TYPE) oiiotype = TypeDesc::STRING; else if (tifftype == TIFF_BYTE && count == 1) oiiotype = TypeDesc::INT; diff --git a/testsuite/heif/ref/out-libheif1.12-orient.txt b/testsuite/heif/ref/out-libheif1.12-orient.txt index bb8a45b94e..78b7d9fd6f 100644 --- a/testsuite/heif/ref/out-libheif1.12-orient.txt +++ b/testsuite/heif/ref/out-libheif1.12-orient.txt @@ -82,6 +82,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 diff --git a/testsuite/heif/ref/out-libheif1.4.txt b/testsuite/heif/ref/out-libheif1.4.txt index 9f53aa083c..bd0ff14603 100644 --- a/testsuite/heif/ref/out-libheif1.4.txt +++ b/testsuite/heif/ref/out-libheif1.4.txt @@ -82,6 +82,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 diff --git a/testsuite/heif/ref/out-libheif1.5.txt b/testsuite/heif/ref/out-libheif1.5.txt index fb050fe42b..39398f71cd 100644 --- a/testsuite/heif/ref/out-libheif1.5.txt +++ b/testsuite/heif/ref/out-libheif1.5.txt @@ -82,6 +82,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 diff --git a/testsuite/heif/ref/out-libheif1.9-alt2.txt b/testsuite/heif/ref/out-libheif1.9-alt2.txt index e24120d453..f6448d4836 100644 --- a/testsuite/heif/ref/out-libheif1.9-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-alt2.txt @@ -56,6 +56,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt index 3fa6718e28..6be6dc26e0 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt @@ -82,6 +82,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1.txt b/testsuite/heif/ref/out-libheif1.9-with-av1.txt index 87614ec80c..e1f848ea83 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1.txt @@ -82,6 +82,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 diff --git a/testsuite/heif/ref/out-libheif1.9.txt b/testsuite/heif/ref/out-libheif1.9.txt index 2ddc23c253..2778c33493 100644 --- a/testsuite/heif/ref/out-libheif1.9.txt +++ b/testsuite/heif/ref/out-libheif1.9.txt @@ -56,6 +56,7 @@ Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic Exif:ApertureValue: 2.52607 (f/2.4) Exif:BrightnessValue: 2.7506 Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 Exif:DateTimeDigitized: "2023:09:28 09:44:03" Exif:DateTimeOriginal: "2023:09:28 09:44:03" Exif:DigitalZoomRatio: 1.3057 From fd3bd31ed026ffec0176c8db046cb6517d9fb442 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 14 Dec 2025 19:57:36 +0900 Subject: [PATCH 069/508] ci: optimize install_homebrew_deps by coalescing installs (#4975) There seems to be a couple seconds overhead for each "brew install". Refactor install_homebrew_deps.bash to first assemble the list of all packages to install, and then do a single `brew install` for the whole list. Also remove mention of packages that are pre-installed on the Mac GHA runners or that we don't need. In the process, by using the `OIIO_BREW_INSTALL_PACKAGES` variable and only setting it if empty, we now have the ability to fully override the list of packages that brew will install on a per-job basis. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/build-scripts/install_homebrew_deps.bash | 57 ++++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/build-scripts/install_homebrew_deps.bash b/src/build-scripts/install_homebrew_deps.bash index 18c16b6831..62a433e614 100755 --- a/src/build-scripts/install_homebrew_deps.bash +++ b/src/build-scripts/install_homebrew_deps.bash @@ -19,6 +19,7 @@ if [[ `which brew` == "" ]] ; then exit 1 fi +set -ex if [[ "${DO_BREW_UPDATE:=0}" != "0" ]] ; then brew update >/dev/null @@ -27,42 +28,38 @@ echo "" echo "Before my brew installs:" brew list --versions -# All cases except for clang-format target, we need the dependencies. -brew install --display-times -q gcc ccache cmake ninja || true -brew link --overwrite gcc -brew install --display-times -q python@${PYTHON_VERSION} || true -brew unlink python@3.8 || true -brew unlink python@3.9 || true -brew unlink python@3.10 || true -brew link --overwrite --force python@${PYTHON_VERSION} || true -#brew upgrade --display-times -q cmake || true -#brew install --display-times -q libtiff -brew install --display-times -q imath openexr opencolorio -#brew install --display-times -q libpng giflib webp -brew install --display-times -q jpeg-turbo openjpeg libultrahdr -brew install --display-times -q freetype libraw dcmtk pybind11 numpy || true -brew install --display-times -q ffmpeg libheif ptex || true -brew install --display-times -q tbb || true -brew install --display-times -q openvdb || true -brew install --display-times -q robin-map || true -if [[ "${USE_OPENCV}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then - brew install --display-times -q opencv || true -fi -if [[ "${USE_QT:=1}" != "0" ]] && [[ "${INSTALL_QT:=1}" != "0" ]] ; then - brew install --display-times -q qt${QT_VERSION} -fi -if [[ "${USE_LLVM:=0}" != "0" ]] || [[ "${LLVMBREWVER}" != "" ]]; then - brew install --display-times -q llvm${LLVMBREWVER} - export PATH=/usr/local/opt/llvm/bin:$PATH +if [[ "$OIIO_BREW_INSTALL_PACKAGES" == "" ]] ; then + OIIO_BREW_INSTALL_PACKAGES=" \ + ccache \ + dcmtk \ + ffmpeg \ + imath \ + libheif \ + libraw \ + libultrahdr \ + numpy \ + opencolorio \ + openexr \ + openjpeg \ + openvdb \ + ptex \ + pybind11 \ + robin-map \ + tbb \ + " + if [[ "${USE_OPENCV}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then + OIIO_BREW_INSTALL_PACKAGES+=" opencv" + fi + if [[ "${USE_QT:=1}" != "0" ]] && [[ "${INSTALL_QT:=1}" != "0" ]] ; then + OIIO_BREW_INSTALL_PACKAGES+=" qt${QT_VERSION}" + fi fi +brew install --display-times -q $OIIO_BREW_INSTALL_PACKAGES $OIIO_BREW_EXTRA_INSTALL_PACKAGES || true echo "" echo "After brew installs:" brew list --versions -# Needed on some systems -pip${PYTHON_VERSION} install numpy - # Set up paths. These will only affect the caller if this script is # run with 'source' rather than in a separate shell. export PATH=/usr/local/opt/qt5/bin:$PATH From 2e35c12584db389571435799cce1d69e2b580fe5 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 16 Dec 2025 04:32:57 +0900 Subject: [PATCH 070/508] ci: build_Ptex.bash should build Ptex using C++17 (#4978) If we don't specify anything, it seems that Ptex will think it's using C++98, which it is in fact not capable of doing. Meanwhile, since OIIO's own minimum requirement is C++17, it certainly meand that any compiler we're using can support 17 as a minimum when building Ptex as well. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 2 +- INSTALL.md | 2 +- src/build-scripts/build_Ptex.bash | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8652caf897..08aac9d4e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -463,7 +463,7 @@ jobs: LIBRAW_VERSION=0.21.4 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 - PTEX_VERSION=v2.4.3 + PTEX_VERSION=v2.5.0 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 FREETYPE_VERSION=VER-2-14-0 diff --git a/INSTALL.md b/INSTALL.md index 864499adb6..68d69aa67e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -71,7 +71,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for WebP images: * WebP >= 1.1 (tested through 1.6) * If you want support for Ptex: - * Ptex >= 2.3.1 (probably works for older; tested through 2.4.3) + * Ptex >= 2.3.1 (probably works for older; tested through 2.5) * If you want to be able to do font rendering into images: * Freetype >= 2.10.0 (tested through 2.14) * If you want to be able to read "ultra-HDR" embedded in JPEG files: diff --git a/src/build-scripts/build_Ptex.bash b/src/build-scripts/build_Ptex.bash index 4e5c30cc35..c6ce317ea3 100755 --- a/src/build-scripts/build_Ptex.bash +++ b/src/build-scripts/build_Ptex.bash @@ -33,6 +33,7 @@ git checkout ${PTEX_VERSION} --force time cmake -S . -B ${PTEX_BUILD_DIR} -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${PTEX_INSTALL_DIR} \ + -DCMAKE_CXX_STANDARD=17 \ ${PTEX_CONFIG_OPTS} time cmake --build ${PTEX_BUILD_DIR} --config Release --target install From 94e8fedcf9c0124548b9c23a3addad0bd6bc5f2e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 17 Dec 2025 14:46:05 +0900 Subject: [PATCH 071/508] ci: unbreak CI by adjusting Ubuntu installs (#4981) The default Ubuntu images keep shifing around. Last week, some packages that used to be installed disappeared. Need to adjust what we install in gh-installdeps.bash, and slightly adjust the optional deps in ci.yml to allow some things that seem to be only on some runners to be missing. Also bump the clang test to clang18 on an ubuntu-24 runner (from clang 15 on ubuntu 22). Gets us back to all-passing CI after a week of all sorts of failures in tests that used to work. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 15 ++++++++------- src/build-scripts/gh-installdeps.bash | 13 ++++++++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08aac9d4e6..eeba96601d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -502,7 +502,7 @@ jobs: # Ensure we are testing all the deps we think we are. We would # like this test to have minimal missing dependencies. required_deps: all - optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' + optional_deps: 'CUDAToolkit;DCMTK;JXL;libuhdr;Nuke;OpenCV;OpenGL;openjph;R3DSDK;' - desc: all local builds gcc12 C++17 avx2 exr3.2 ocio2.3 nametag: linux-local-builds runner: ubuntu-22.04 @@ -517,18 +517,19 @@ jobs: PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 - - desc: clang15 C++17 avx2 exr3.1 ocio2.3 - nametag: linux-clang15 - runner: ubuntu-22.04 - cxx_compiler: clang++-15 - cc_compiler: clang-15 + - desc: clang18 C++17 avx2 exr3.1 ocio2.3 + nametag: linux-clang18 + runner: ubuntu-24.04 + cxx_compiler: clang++ + cc_compiler: clang cxx_std: 17 fmt_ver: 10.1.1 opencolorio_ver: v2.3.0 openexr_ver: v3.1.13 pybind11_ver: v2.12.0 - python_ver: "3.10" + python_ver: "3.12" simd: avx2,f16c + setenvs: export USE_OPENVDB=0 - desc: Linux ARM latest releases gcc14 C++20 py3.12 exr3.4 ocio2.4 nametag: linux-arm-latest-releases runner: ubuntu-24.04-arm diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index fb1d2b8213..0dabcf61e0 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -96,14 +96,17 @@ else if [[ "${SKIP_SYSTEM_DEPS_INSTALL}" != "1" ]] ; then time sudo apt-get -q install -y --fix-missing \ git cmake ninja-build ccache g++ \ - libilmbase-dev libopenexr-dev \ - libtiff-dev libgif-dev libpng-dev \ + libtiff-dev libgif-dev libpng-dev libjpeg-dev \ libraw-dev libwebp-dev \ libavcodec-dev libavformat-dev libswscale-dev libavutil-dev \ dcmtk libopenvdb-dev \ libfreetype6-dev \ libopencolorio-dev \ - libtbb-dev || true + libtbb-dev \ + libdeflate-dev bzip2 + # Iffy ones get the "|| true" treatment so failure is ok + time sudo apt-get -q install -y --fix-missing \ + libjxl-dev || true fi if [[ "${USE_OPENCV}" != "0" ]] && [[ "${INSTALL_OPENCV}" != "0" ]] ; then sudo apt-get -q install -y --fix-missing libopencv-dev || true @@ -133,6 +136,10 @@ else libheif-plugin-x265 libheif-dev || true fi + if [[ "${USE_FFMPEG}" != "0" ]] ; then + time sudo apt-get -q install -y ffmpeg || true + fi + export CMAKE_PREFIX_PATH=/usr/lib/x86_64-linux-gnu:$CMAKE_PREFIX_PATH if [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" || "$USE_ICX" != "" ]] ; then From b38d509d861e50e35a094f0fe69797ac6a858fcc Mon Sep 17 00:00:00 2001 From: Pavan Madduri Date: Wed, 17 Dec 2025 05:38:44 -0600 Subject: [PATCH 072/508] fix(IBA): IBA::compare_Yee() accessed the wrong channel (#4976) The GaussianPyramid::value() method used by IBA::compare_Yee() was incorrectly accessing channel 1 instead of channel 0. This fix corrects the channel access to properly retrieve the luminance value from the single-channel pyramid levels. One oddity this led to is that `idiff -p` would appear to PASS for some clearly different images if they had different data windows. Update reference outputs to reflect the corrected perceptual diff results (Max error changes from 1 to 10 for the test images). Fixes #4948 Signed-off-by: pmady Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/imagebufalgo_yee.cpp | 4 ++-- testsuite/diff/ref/out-fmt6.txt | 6 +++--- testsuite/diff/ref/out.txt | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libOpenImageIO/imagebufalgo_yee.cpp b/src/libOpenImageIO/imagebufalgo_yee.cpp index 7ee875ebe5..25763dfdc8 100644 --- a/src/libOpenImageIO/imagebufalgo_yee.cpp +++ b/src/libOpenImageIO/imagebufalgo_yee.cpp @@ -52,7 +52,7 @@ class GaussianPyramid { if (lev >= PYRAMID_MAX_LEVELS) return 0.0f; else - return level[lev].getchannel(x, y, 0, 1); + return level[lev].getchannel(x, y, 0, 0); } #if 0 /* unused */ @@ -65,7 +65,7 @@ class GaussianPyramid { float operator()(int x, int y, int lev) const { OIIO_DASSERT(lev < PYRAMID_MAX_LEVELS); - return level[lev].getchannel(x, y, 0, 1); + return level[lev].getchannel(x, y, 0, 0); } #endif diff --git a/testsuite/diff/ref/out-fmt6.txt b/testsuite/diff/ref/out-fmt6.txt index e488da6520..1ddc33371e 100644 --- a/testsuite/diff/ref/out-fmt6.txt +++ b/testsuite/diff/ref/out-fmt6.txt @@ -16,7 +16,7 @@ Computing diff of "img1.exr" vs "img2.exr" 121 pixels (2.95%) over 1e-06 FAILURE Computing perceptual diff of "img1.exr" vs "img2.exr" - Max error = 1.0 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 + Max error = 10.0 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 121 pixels (2.95%) failed the perceptual test FAILURE Computing perceptual diff of "img1.exr" vs "img1.exr" @@ -26,7 +26,7 @@ Comparing "img1.exr" and "img2.exr" Mean error = 0 RMS error = 0 Peak SNR = 0 - Max error = 1 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 + Max error = 10 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 0 pixels (0%) over 0.008 121 pixels (2.95%) over 0.004 121 pixels (2.9541%) failed the perceptual test @@ -38,7 +38,7 @@ Comparing "img1.exr" and "img2.exr" Mean error = 0 RMS error = 0 Peak SNR = 0 - Max error = 1 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 + Max error = 10 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 0 pixels (0%) over 0.008 121 pixels (2.95%) over 1.0 121 pixels (2.9541%) failed the perceptual test diff --git a/testsuite/diff/ref/out.txt b/testsuite/diff/ref/out.txt index f5462e60c7..2000567703 100644 --- a/testsuite/diff/ref/out.txt +++ b/testsuite/diff/ref/out.txt @@ -16,7 +16,7 @@ Computing diff of "img1.exr" vs "img2.exr" 121 pixels (2.95%) over 1e-06 FAILURE Computing perceptual diff of "img1.exr" vs "img2.exr" - Max error = 1 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 + Max error = 10 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 121 pixels (2.95%) failed the perceptual test FAILURE Computing perceptual diff of "img1.exr" vs "img1.exr" @@ -26,7 +26,7 @@ Comparing "img1.exr" and "img2.exr" Mean error = 0 RMS error = 0 Peak SNR = 0 - Max error = 1 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 + Max error = 10 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 0 pixels (0%) over 0.008 121 pixels (2.95%) over 0.004 121 pixels (2.9541%) failed the perceptual test @@ -38,7 +38,7 @@ Comparing "img1.exr" and "img2.exr" Mean error = 0 RMS error = 0 Peak SNR = 0 - Max error = 1 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 + Max error = 10 @ (5, 17, R) values are 0.1, 0.1, 0.1 vs 0.1, 0.6, 0.1 0 pixels (0%) over 0.008 121 pixels (2.95%) over 1 121 pixels (2.9541%) failed the perceptual test From 0807b51818cf23c25100bb4e32dbbaa1ebc50370 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 19 Dec 2025 12:08:35 -0800 Subject: [PATCH 073/508] fix(png): We were not correctly suppressing hint metadata (#4983) Each file writer that is capable of writing arbitrary metadata is supposed to tace care to suppress hints meant for other writers, or for OIIO in general, not interpret them as literal metadata to write to the file. PNG was not doing so, and ended up writing hints as literal metadata. This brings it in line with our behavior when writing OpenEXR and other formats. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/png.imageio/png_pvt.h | 18 ++++++++++++++++++ testsuite/png-damaged/ref/out.txt | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/png.imageio/png_pvt.h b/src/png.imageio/png_pvt.h index 3c103eb836..f7b6bbbb62 100644 --- a/src/png.imageio/png_pvt.h +++ b/src/png.imageio/png_pvt.h @@ -568,12 +568,30 @@ put_parameter(png_structp& sp, png_infop& ip, const std::string& _name, return true; } #endif + + // Before handling general named metadata, suppress format-specific + // metadata hints meant for other formats that are not meant to be literal + // metadata written to the file. This includes anything with a namespace + // prefix of "oiio:" or the name of any other file format. + auto colonpos = name.find(':'); + if (colonpos != std::string::npos) { + std::string prefix = Strutil::lower(name.substr(0, colonpos)); + if (prefix != "png" && is_imageio_format_name(prefix)) + return false; + if (prefix == "oiio") + return false; + } + if (type == TypeDesc::STRING) { + // We can save arbitrary string metadata in multiple png text entries. + // Is that ok? Should we also do it for other types by converting to + // string? png_text t; t.compression = PNG_TEXT_COMPRESSION_NONE; t.key = (char*)ustring(name).c_str(); t.text = *(char**)data; // Already uniquified text.push_back(t); + return true; } return false; diff --git a/testsuite/png-damaged/ref/out.txt b/testsuite/png-damaged/ref/out.txt index 0c2636e3d3..4998469ba9 100644 --- a/testsuite/png-damaged/ref/out.txt +++ b/testsuite/png-damaged/ref/out.txt @@ -4,10 +4,10 @@ iconvert ERROR copying "../oiio-images/png/broken/invalid_gray_alpha_sbit.png" t PNG read error: IDAT: Read error: hit end of file in png reader libpng error: No IDATs written into file Comparing "../oiio-images/png/broken/invalid_gray_alpha_sbit.png" and "invalid_gray_alpha_sbit.png" -libpng error: tEXt: Read error: hit end of file in png reader -libpng error: tEXt: Read error: hit end of file in png reader +libpng error: oFFs: Read error: hit end of file in png reader +libpng error: oFFs: Read error: hit end of file in png reader idiff ERROR: Could not read invalid_gray_alpha_sbit.png: Invalid image file "invalid_gray_alpha_sbit.png": Read error: hit end of file in png reader -PNG read error: tEXt: Read error: hit end of file in png reader +PNG read error: oFFs: Read error: hit end of file in png reader Invalid image file "invalid_gray_alpha_sbit.png": Read error: hit end of file in png reader -PNG read error: tEXt: Read error: hit end of file in png reader +PNG read error: oFFs: Read error: hit end of file in png reader From 91b8d7fc9e3ccca70ab2278f3a5a133166aefe30 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 19 Dec 2025 12:09:03 -0800 Subject: [PATCH 074/508] admin: Minor rewording in the issue and PR templates (#4982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Periodic revisiting of the phrasing in the hopes of improving PR and Issue quality / actionability. Not that I think they are bad now, just always looking to guide people to improvements. Will it work? 🤷 Let's try and see if we think PRs improve. If not, we can revise again later. Also make the issue title prefixes less yelly and shorter (makes it easier to see the actual title on phone or other limited width display of messages). Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/ISSUE_TEMPLATE/bug_report.md | 5 ++- .github/ISSUE_TEMPLATE/build_problem.md | 5 ++- .github/ISSUE_TEMPLATE/feature_request.md | 5 ++- .github/ISSUE_TEMPLATE/question.md | 6 +-- .github/PULL_REQUEST_TEMPLATE.md | 48 +++++++++++++---------- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c1810d405f..3307c930f7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,12 +1,15 @@ --- name: Bug report about: I think I have identified a legit bug and can describe it. -title: "[BUG]" +title: "bug:" labels: '' assignees: '' --- + + + **Describe the bug** A clear and concise description of what the bug is. What happened, and diff --git a/.github/ISSUE_TEMPLATE/build_problem.md b/.github/ISSUE_TEMPLATE/build_problem.md index 8de9b6aa0d..179a1fb242 100644 --- a/.github/ISSUE_TEMPLATE/build_problem.md +++ b/.github/ISSUE_TEMPLATE/build_problem.md @@ -1,7 +1,7 @@ --- name: Build problems about: I'm having trouble building OIIO. Help! -title: "[BUILD]" +title: "build:" labels: '' assignees: '' @@ -10,7 +10,8 @@ assignees: '' **PLEASE DO NOT REPORT BUILD TROUBLES AS GITHUB "ISSUES" UNLESS YOU ARE REALLY SURE IT'S A BUG** The best way to get help with your build problems is to ask a question on the -[oiio-dev developer mail list](https://lists.aswf.io/g/oiio-dev). +[oiio-dev developer mail list](https://lists.aswf.io/g/oiio-dev) or on the +[ASWF Slack](https://slack.aswf.io) `#openimageio` channel. When you email about this, please attach one or both of the following: 1. The full verbose build log, which you can create like this: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9ec5c4a787..a563507875 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,12 +1,15 @@ --- name: Feature request about: I have a concrete idea about how to improve OpenImageIO. -title: "[FEATURE REQUEST]" +title: "feat:" labels: '' assignees: '' --- + + + **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 51a27aac18..7f389cbac2 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,7 +1,7 @@ --- name: Question about: How do I... I need help with... -title: "[HELP]" +title: "help:" labels: '' assignees: '' @@ -18,8 +18,8 @@ But if you are just asking a question: * Am I doing something wrong? * I can't build OpenImageIO -For anything of this nature, the best way to get help using OpenImageIO is -to ask a question on the [oiio-dev developer mail list](https://lists.aswf.io/g/oiio-dev). +For anything of this nature, the best way to get help using OpenImageIO is to +ask a question on the [oiio-dev developer mail list](https://lists.aswf.io/g/oiio-dev) or the [ASWF Slack](https://slack.aswf.io) `#openimageio` channel. The [documentation](https://docs.openimageio.org) is pretty comprehensive, so please check there first; you may find the answer diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 960bc557b3..536ef4915c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,34 +1,42 @@ - - - - +YOU MAY DELETE ALL OF THIS IF YOU ALREADY HAVE A DESCRIPTIVE COMMIT MESSAGE! +This is just a template and set of reminders about what constitutes a good PR. +But please look over the checklist at the bottom. -## Description +If THIS TEXT is still in your PR description, we'll know you didn't read the +instructions! + + + + +### Description -## Tests +### Tests -## Checklist: +### Checklist: -- [ ] I have read the [contribution guidelines](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/CONTRIBUTING.md). -- [ ] I have updated the documentation, if applicable. (Check if there is no - need to update the documentation, for example if this is a bug fix that - doesn't change the API.) -- [ ] I have ensured that the change is tested somewhere in the testsuite - (adding new test cases if necessary). -- [ ] If I added or modified a C++ API call, I have also amended the - corresponding Python bindings (and if altering ImageBufAlgo functions, also - exposed the new functionality as oiiotool options). -- [ ] My code follows the prevailing code style of this project. If I haven't - already run clang-format before submitting, I definitely will look at the CI - test that runs clang-format and fix anything that it highlights as being - nonconforming. +- [ ] **I have read the guidelines** on [contributions](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/CONTRIBUTING.md) and [code review procedures](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/docs/dev/CodeReview.md). +- [ ] **I have updated the documentation** if my PR adds features or changes + behavior. +- [ ] **I am sure that this PR's changes are tested somewhere in the + testsuite**. +- [ ] **I have run and passed the testsuite in CI** *before* submitting the + PR, by pushing the changes to my fork and seeing that the automated CI + passed there. (Exceptions: If most tests pass and you can't figure out why + the remaining ones fail, it's ok to submit the PR and ask for help. Or if + any failures seem entirely unrelated to your change; sometimes things break + on the GitHub runners.) +- [ ] **My code follows the prevailing code style of this project** and I + fixed any problems reported by the clang-format CI test. +- [ ] If I added or modified a public C++ API call, I have also amended the + corresponding Python bindings. If altering ImageBufAlgo functions, I also + exposed the new functionality as oiiotool options. From 341eefef43119f0b94474355a41671844e4b8e89 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 19 Dec 2025 21:37:06 +0100 Subject: [PATCH 075/508] feat(exr): Write OpenEXR colorInteropID metadata based on oiio:ColorSpace (#4967) If the colorspace exists and has an interop ID in an OCIO 2.5 config, use that. Otherwise check if the colorspace is equivalent to a known color interop ID. Tests were added. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/openexr.imageio/exroutput.cpp | 10 +++++++++ testsuite/openexr-suite/ref/out.txt | 34 +++++++++++++++++++++++++++++ testsuite/openexr-suite/run.py | 8 +++++++ 3 files changed, 52 insertions(+) diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index 11b350ff38..d164ee5690 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -1026,6 +1027,15 @@ OpenEXROutput::spec_to_header(ImageSpec& spec, int subimage, } } + // Set color interop ID from colorspace + if (spec.get_string_attribute("colorInteropID").empty()) { + 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); + if (!interop_id.empty()) + spec.attribute("colorInteropID", interop_id); + } + // Deal with all other params for (const auto& p : spec.extra_attribs) put_parameter(p.name().string(), p.type(), p.data(), header); diff --git a/testsuite/openexr-suite/ref/out.txt b/testsuite/openexr-suite/ref/out.txt index a6affcc883..ca7553d0e3 100644 --- a/testsuite/openexr-suite/ref/out.txt +++ b/testsuite/openexr-suite/ref/out.txt @@ -369,3 +369,37 @@ Full command line was: oiiotool ERROR: -o : Cannot output non-compliant ACES Container in 'strict' mode. REASON: EXR data type is not 'HALF' as required for an ACES Container. Full command line was: > oiiotool --create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr +Reading color_interop_id_scene_linear.exr +color_interop_id_scene_linear.exr : 4 x 4, 3 channel, float openexr + SHA-1: D7699308C38CD04EEB732577A82D31D04E05A339 + channel list: R, G, B + colorInteropID: "lin_ap1_scene" + compression: "zip" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimages: 1 + openexr:lineOrder: "increasingY" +Reading color_interop_id_linear_adobergb.exr +color_interop_id_linear_adobergb.exr : 4 x 4, 3 channel, float openexr + SHA-1: D7699308C38CD04EEB732577A82D31D04E05A339 + channel list: R, G, B + colorInteropID: "lin_adobergb_scene" + compression: "zip" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_adobergb_scene" + oiio:subimages: 1 + openexr:lineOrder: "increasingY" +Reading color_interop_id_unknown.exr +color_interop_id_unknown.exr : 4 x 4, 3 channel, float openexr + SHA-1: D7699308C38CD04EEB732577A82D31D04E05A339 + channel list: R, G, B + compression: "zip" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:subimages: 1 + openexr:lineOrder: "increasingY" diff --git a/testsuite/openexr-suite/run.py b/testsuite/openexr-suite/run.py index 105cd03ac2..8462ca8bf2 100755 --- a/testsuite/openexr-suite/run.py +++ b/testsuite/openexr-suite/run.py @@ -83,3 +83,11 @@ # Invalid data type command += oiiotool("--create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr", failureok=True) + +# Check color interop ID output +command += oiiotool("--create 4x4 3 --attrib oiio:ColorSpace scene_linear -o color_interop_id_scene_linear.exr") +command += info_command("color_interop_id_scene_linear.exr", safematch=True) +command += oiiotool("--create 4x4 3 --attrib oiio:ColorSpace lin_adobergb_scene -o color_interop_id_linear_adobergb.exr") +command += info_command("color_interop_id_linear_adobergb.exr", safematch=True) +command += oiiotool("--create 4x4 3 --attrib oiio:ColorSpace unknown_interop_id -o color_interop_id_unknown.exr") +command += info_command("color_interop_id_unknown.exr", safematch=True) From 645cfeb074d21d075b4ccd2d8b742a95300b3314 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 22 Dec 2025 19:24:15 +0100 Subject: [PATCH 076/508] feat(JXL): CICP read and write support for JPEG XL (#4968) The JPEG XL color encoding metadata only supports a subset of CICP. So for example `srgb_p3d65_display` and `pq_rec2020_display` are supported, but `g26_xyzd65_display` is not. Custom primaries, custom white point and arbitrary gamma could be used to support more, but I didn't implement that. Tests for read and write were added. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/jpegxl.imageio/jxlinput.cpp | 30 ++++++++++++++++- src/jpegxl.imageio/jxloutput.cpp | 56 ++++++++++++++++++++++++++++++++ testsuite/jxl/ref/out.txt | 23 +++++++++++++ testsuite/jxl/run.py | 3 ++ 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/jpegxl.imageio/jxlinput.cpp b/src/jpegxl.imageio/jxlinput.cpp index 9ed3b1c45b..b0f90cf4f1 100644 --- a/src/jpegxl.imageio/jxlinput.cpp +++ b/src/jpegxl.imageio/jxlinput.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -54,7 +55,6 @@ class JxlInput final : public ImageInput { std::string m_filename; int m_next_scanline; // Which scanline is the next to read? uint32_t m_channels; - JxlColorEncoding m_color_encoding; JxlDecoderPtr m_decoder; JxlResizableParallelRunnerPtr m_runner; std::unique_ptr m_config; // Saved copy of configuration spec @@ -224,6 +224,8 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) JxlDataType jxl_data_type; TypeDesc m_data_type; uint32_t bits = 0; + JxlColorEncoding color_encoding {}; + bool have_color_encoding = false; for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder.get()); @@ -300,6 +302,16 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) errorfmt("JxlDecoderGetColorAsICCProfile failed\n"); return false; } + + // Get the color encoding of the pixel data + // This will return JXL_DEC_ERR for a valid file without color + // encoding information, so don't report an error. + if (JXL_DEC_SUCCESS + == JxlDecoderGetColorAsEncodedProfile( + m_decoder.get(), JXL_COLOR_PROFILE_TARGET_DATA, + &color_encoding)) { + have_color_encoding = true; + } } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { DBG std::cout << "JXL_DEC_NEED_IMAGE_OUT_BUFFER\n"; @@ -348,6 +360,7 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) m_spec = ImageSpec(info.xsize, info.ysize, m_channels, m_data_type); + // Read ICC profile if (m_icc_profile.size() && m_icc_profile.data()) { m_spec.attribute("ICCProfile", TypeDesc(TypeDesc::UINT8, m_icc_profile.size()), @@ -365,6 +378,21 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) } } + // Read CICP from color encoding. Custom primaries, custom white point and + // arbitrary gamma not supported currently. + if (have_color_encoding && color_encoding.primaries != JXL_PRIMARIES_CUSTOM + && color_encoding.white_point != JXL_WHITE_POINT_CUSTOM + && color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_GAMMA) { + const int cicp[4] = { color_encoding.primaries, + color_encoding.transfer_function, 0 /* RGB */, + 1 /* Full range */ }; + m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); + const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); + string_view interop_id = colorconfig.get_color_interop_id(cicp); + if (!interop_id.empty()) + m_spec.attribute("oiio:ColorSpace", interop_id); + } + newspec = m_spec; return true; } diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index de82467fa7..1a4a64e858 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -538,6 +539,8 @@ JxlOutput::save_image(const void* data) return false; } + bool wrote_colorspace = false; + // Write the ICC profile, if available const ParamValue* icc_profile_parameter = m_spec.find_attribute( "ICCProfile"); @@ -551,6 +554,59 @@ JxlOutput::save_image(const void* data) length)) { errorfmt("JxlEncoderSetICCProfile failed\n"); } + 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(); + 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 + // support more CICP codes. + JxlColorEncoding color_encoding {}; + color_encoding.primaries = JxlPrimaries(cicp[0]); + color_encoding.transfer_function = JxlTransferFunction(cicp[1]); + color_encoding.color_space = JXL_COLOR_SPACE_RGB; + + bool supported_primaries = false; + bool supported_transfer = false; + + switch (color_encoding.primaries) { + case JXL_PRIMARIES_SRGB: + case JXL_PRIMARIES_2100: + case JXL_PRIMARIES_P3: + supported_primaries = true; + color_encoding.white_point = JXL_WHITE_POINT_D65; + break; + case JXL_PRIMARIES_CUSTOM: // Not an actual CICP code in JXL + break; + } + + switch (color_encoding.transfer_function) { + case JXL_TRANSFER_FUNCTION_709: + case JXL_TRANSFER_FUNCTION_UNKNOWN: + case JXL_TRANSFER_FUNCTION_LINEAR: + case JXL_TRANSFER_FUNCTION_SRGB: + case JXL_TRANSFER_FUNCTION_PQ: + case JXL_TRANSFER_FUNCTION_DCI: + case JXL_TRANSFER_FUNCTION_HLG: supported_transfer = true; break; + case JXL_TRANSFER_FUNCTION_GAMMA: // Not an actual CICP code + break; + } + + if (supported_primaries && supported_transfer) { + if (JXL_ENC_SUCCESS + != JxlEncoderSetColorEncoding(m_encoder.get(), + &color_encoding)) { + errorfmt("JxlEncoderSetColorEncoding failed\n"); + } } } diff --git a/testsuite/jxl/ref/out.txt b/testsuite/jxl/ref/out.txt index 040910d3c9..83b97fbba6 100644 --- a/testsuite/jxl/ref/out.txt +++ b/testsuite/jxl/ref/out.txt @@ -19,3 +19,26 @@ tahoe-icc.jxl : 128 x 96, 3 channel, uint8 jpegxl ICCProfile:profile_size: 560 ICCProfile:profile_version: "2.1.0" ICCProfile:rendering_intent: "Perceptual" +Reading tahoe-cicp-pq.jxl +tahoe-cicp-pq.jxl : 128 x 96, 3 channel, uint8 jpegxl + SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C + channel list: R, G, B + CICP: 9, 16, 0, 1 + ICCProfile: 0, 0, 16, 248, 106, 120, 108, 32, 4, 64, 0, 0, 109, 110, 116, 114, ... [4344 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1786276896 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "CC0" + ICCProfile:creation_date: "2019:12:01 00:00:00" + ICCProfile:creator_signature: "6a786c20" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "0" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "CIELAB" + ICCProfile:profile_description: "RGB_D65_202_Per_PeQ" + ICCProfile:profile_size: 4344 + ICCProfile:profile_version: "4.4.0" + ICCProfile:rendering_intent: "Perceptual" + oiio:ColorSpace: "pq_rec2020_display" diff --git a/testsuite/jxl/run.py b/testsuite/jxl/run.py index d112da67b1..78b7a19eba 100755 --- a/testsuite/jxl/run.py +++ b/testsuite/jxl/run.py @@ -10,6 +10,9 @@ command += info_command ("tahoe-icc.jxl", safematch=True) command += oiiotool ("tahoe-icc.jxl --iccwrite test-jxl.icc") +command += oiiotool ("../common/tahoe-tiny.tif --cicp \"9,16,9,1\" -o tahoe-cicp-pq.jxl") +command += info_command ("tahoe-cicp-pq.jxl", safematch=True) + outputs = [ "test-jxl.icc", "out.txt" From 74222f17738c5903b98436df6220d6ee8a8d5715 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 29 Dec 2025 17:36:38 -0800 Subject: [PATCH 077/508] docs: fix missing docs for `OIIO:attribute()` and `OIIO::getattribute()` (#4987) Rearrangements in 3.1 dropped the list of recognized attributes from the visible online docs and failed to document the span varieties. We fix and also reword a lot of the descriptions for clarity and uniformity. The previous organization was that there were several varieties of attribute(). In the header, the first one had the overall long explanation, including the list of all the recognized attributes. The other ones had short explanations of how they differed. In the docs, each one was referenced explicitly, pulling in its attendant bit of documentation. What really happened is that in the header, I made the new span-based version the "flagship" one with the full explanation, but I neglected to reference it in the docs, so the long description disappeared. I could have fixed by just adding refs to the new functions to the docs, as I originally meant to. But while I was there, I took the opportunity to surround the whole collection with a group marker, and then include the lot of them with a single reference to the group, rather than need to refer to each function variant individually. And while I was at it, I also reworded (and hopefully improved) some of those explanations. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/imageioapi.rst | 45 +------ src/include/OpenImageIO/imageio.h | 188 +++++++++++++++++++++--------- 2 files changed, 135 insertions(+), 98 deletions(-) diff --git a/src/doc/imageioapi.rst b/src/doc/imageioapi.rst index d2d6b192b4..bf1c22dd07 100644 --- a/src/doc/imageioapi.rst +++ b/src/doc/imageioapi.rst @@ -215,49 +215,12 @@ These helper functions are not part of any other OpenImageIO class, they just exist in the OpenImageIO namespace as general utilities. (See :ref:`sec-pythonmiscapi` for the corresponding Python bindings.) -.. doxygenfunction:: OIIO::attribute(string_view, TypeDesc, const void *) - -.. cpp:function:: bool OIIO::attribute(string_view name, int val) - bool OIIO::attribute(string_view name, float val) - bool OIIO::attribute(string_view name, string_view val) - - Shortcuts for setting an attribute to a single int, float, or string. - - -.. doxygenfunction:: OIIO::getattribute(string_view, TypeDesc, void *) - - -.. cpp:function:: bool getattribute (string_view name, int &val) - bool getattribute (string_view name, float &val) - bool getattribute (string_view name, char **val) - bool getattribute (string_view name, std::string& val) - - Specialized versions of `getattribute()` in which the data type is - implied by the type of the argument (for single int, float, or string). - Two string versions exist: one that retrieves it as a `std::string` and - another that retrieves it as a `char *`. In all cases, the return value - is `true` if the attribute is found and the requested data type - conversion was legal. - - EXAMPLES:: - - int threads; - OIIO::getattribute ("threads", &threads); - std::string path; - OIIO::getattribute ("plugin_searchpath", path); - -.. cpp:function:: int get_int_attribute (string_view name, int defaultvalue=0) - float get_float_attribute (string_view name, float defaultvalue=0) - string_view get_string_attribute (string_view name, string_view defaultvalue="") - - Specialized versions of `getattribute()` for common types, in which the - data is returned directly, and a supplied default value is returned if - the attribute was not found. +.. doxygengroup:: OIIO_attribute +.. - EXAMPLES:: - int threads = OIIO::get_int_attribute ("threads", 0); - string_view path = OIIO::get_string_attribute ("plugin_searchpath"); +.. doxygengroup:: OIIO_getattribute +.. diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index e6c1ab0552..4b9a05304b 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -3697,18 +3697,28 @@ OIIO_API bool has_error(); /// error messages. OIIO_API std::string geterror(bool clear = true); -/// `OIIO::attribute()` sets a global attribute (i.e., a property or -/// option) of OpenImageIO. The `name` designates the name of the attribute, -/// `type` describes the type of data, and `value` is a pointer to memory -/// containing the new value for the attribute. +/// @defgroup OIIO_attribute (global OIIO::attribute()) +/// @{ /// -/// If the name is known, valid attribute that matches the type specified, -/// the attribute will be set to the new value and `attribute()` will return -/// `true`. If `name` is not recognized, or if the types do not match -/// (e.g., `type` is `TypeFloat` but the named attribute is a string), the -/// attribute will not be modified, and `attribute()` will return `false`. +/// `OIIO::attribute()` sets a global attribute (i.e., a property or option) +/// of OpenImageIO. The `name` designates the name of the attribute, `value` +/// is the value to use for the attribute, and for some varieties of the call, +/// `type` is a TypeDesc describing the data type. /// -/// The following are the recognized attributes: +/// Most varieties of the call will return `true` if `name` is a known +/// attribute and its expected type is compatible with the type specified. If +/// `name` is not recognized, or if the types do not match (e.g., `type` is +/// `TypeFloat` but the named attribute is supposed to be a string), the +/// internal attribute will not be modified, and `attribute()` will return +/// `false`. +/// +/// In all cases, is up to the caller to ensure that `value` is or refers to +/// the right kind and size of storage for the given type. +/// +/// Note that all attributes set by this call may also be retrieved by +/// `OIIO::getattribute()`. +/// +/// RECOGNIZED ATTRIBUTES /// /// - `string options` /// @@ -3927,7 +3937,25 @@ OIIO_API std::string geterror(bool clear = true); /// enable globally in an environment where security is a higher priority /// than being tolerant of partially broken image files. /// -/// @version 3.1 +/// EXAMPLES: +/// ``` +/// // Setting single simple values simply: +/// bool ok = OIIO::getattribute("threads", 1); // implied: int +/// ok = OIIO::attribute("plugin_searchpath", "/foo/bar:/baz"); // implied: string +/// +/// // Setting a more complex value using a span, with explicit type +/// float missing[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; +/// ok = OIIO::attribute("missingcolor", TypeDesc("float[4]"), make_span(missing)); +/// ``` +/// +/// The different varieties of `OIIO::attribute()` call follow: + +/// Set the attribute's value from a span (which may be a single value). The +/// total size of `value` must match the `type` (if not, an assertion will be +/// thrown for debug builds of OIIO, an error will be printed for release +/// builds). +/// +/// @version 3.1+ template inline bool attribute(string_view name, TypeDesc type, span value) { @@ -3936,19 +3964,18 @@ inline bool attribute(string_view name, TypeDesc type, span value) return attribute(name, type, OIIO::as_bytes(value)); } -/// A version of `OIIO::attribute()` that takes its value from a span of -/// untyped bytes. The total size of `value` must match the `type` (if not, an -/// assertion will be thrown for debug builds of OIIO, an error will be -/// printed for release builds). +/// Set the attribute's value from a span of untyped bytes. The total size of +/// `value` must match the `type` (if not, an assertion will be thrown for +/// debug builds of OIIO, an error will be printed for release builds). /// -/// @version 3.1 +/// @version 3.1+ OIIO_API bool attribute(string_view name, TypeDesc type, cspan value); -/// A version of `OIIO::attribute()` where the `value` is only a pointer -/// specifying the beginning of the memory where the value should be copied -/// from. This is "unsafe" in the sense that there is no assurance that it -/// points to a sufficient amount of memory, so the span-based versions of -/// `attribute()` are preferred. +/// Set the named attribute to the contents of memory pointed to by `value`, +/// with the `type` implying the total size to be copied. This is "unsafe" in +/// the sense that there is no assurance that it points to a sufficient amount +/// of memory or value type, so the span-based versions of `attribute()` are +/// preferred. /// /// This was added in version 2.1. OIIO_API bool attribute(string_view name, TypeDesc type, const void* value); @@ -3967,12 +3994,23 @@ inline bool attribute(string_view name, string_view value) { const char *s = valstr.c_str(); return attribute(name, TypeString, &s); } +/// @} + -/// Get the named global attribute of OpenImageIO, store it in `value`. -/// Return `true` if found and it was compatible with the type specified, -/// otherwise return `false` and do not modify the contents of `value`. It -/// is up to the caller to ensure that `val` points to the right kind and -/// size of storage for the given type. +/// @defgroup OIIO_getattribute (global OIIO::getattribute()) +/// @{ +/// +/// `OIIO::getattribute()` retrieves a named global attribute of OpenImageIO, +/// and stores it in `value`. These are the retrieval side of the symmetric +/// set of `OIIO::attribute()` calls. +/// +/// Most varieties of the call will return `true` if the named attribute was +/// found and it was compatible with the type specified, otherwise return +/// `false` and do not modify the contents of `value`. In all cases, it is up +/// to the caller to ensure that `val` points to the right kind and size of +/// storage for the given type. +/// +/// RECOGNIZED ATTRIBUTES /// /// In addition to being able to retrieve all the attributes that are /// documented as settable by the `OIIO::attribute()` call, `getattribute()` @@ -4104,8 +4142,32 @@ inline bool attribute(string_view name, string_view value) { /// IBA::resize 20 0.24s (avg 12.18ms) /// IBA::zero 8 0.66ms (avg 0.08ms) /// +/// EXAMPLES: +/// ``` +/// // Retrieving a single simple value with success/failure return: +/// int threads; +/// bool ok = OIIO::getattribute("threads", threads); +/// std::string path; +/// ok = OIIO::getattribute("plugin_searchpath", path); /// -/// @version 3.1 +/// // Directly returning a single simple value, with default to use +/// // if the attribute is not found: +/// int threads = OIIO::get_int_attribute("threads", 0); +/// string_view path = OIIO::get_string_attribute("plugin_searchpath"); +/// +/// // Returning into a span, with explicit type +/// float missing[4]; +/// ok = OIIO::getattribute("missingcolor", TypeDesc("float[4]"), +/// make_span(missing)); +/// ``` +/// +/// The different varieties of `OIIO::getattribute()` call follow: + +/// Store the named attribute's current value into a writable span. The total +/// size of `value` must match the `type` (if not, an assertion will be thrown +/// for debug OIIO builds, an error will be printed for release builds). +/// +/// @version 3.1+ template inline bool getattribute(string_view name, TypeDesc type, span value) { @@ -4114,37 +4176,37 @@ inline bool getattribute(string_view name, TypeDesc type, span value) return OIIO::v3_1::getattribute(name, type, OIIO::as_writable_bytes(value)); } -/// A version of `getattribute()` that stores the value in a span of -/// untyped bytes. The total size of `value` must match the `type` (if -/// not, an assertion will be thrown for debug OIIO builds, an error will -/// be printed for release builds). +/// Store the value in a span of untyped bytes. The total size of `value` must +/// match the `type` (if not, an assertion will be thrown for debug OIIO +/// builds, an error will be printed for release builds). /// -/// @version 3.1 +/// @version 3.1+ OIIO_API bool getattribute(string_view name, TypeDesc type, span value); -/// A version of `OIIO::getattribute()` where the `value` is only a pointer -/// specifying the beginning of the memory where the value should be copied. -/// This is "unsafe" in the sense that there is no assurance that it points to -/// a sufficient amount of memory, so the span-based versions of `attribute()` -/// are preferred. +/// Store the value into memory pointed to by `val`. This is "unsafe" in the +/// sense that there is no assurance that it points to a sufficient amount of +/// memory or will be interpreted as the correct type, so the span-based +/// versions of `attribute()` are preferred. OIIO_API bool getattribute(string_view name, TypeDesc type, void* val); -/// Shortcut getattribute() for retrieving a single integer. The value is -/// placed in `value`, and the function returns `true` if the attribute was -/// found and was legally convertible to an int. +/// Retrieve a single-integer attribute. The value is placed in `value`, and +/// the function returns `true` if the attribute was found and was legally +/// convertible to an int. inline bool getattribute (string_view name, int &value) { return getattribute (name, TypeInt, &value); } -/// Shortcut getattribute() for retrieving a single float. The value is placed -/// in `value`, and the function returns `true` if the attribute was found and -/// was legally convertible to a float. + +/// Retrieve a single-float attribute. The value is placed in `value`, and the +/// function returns `true` if the attribute was found and was legally +/// convertible to a float. inline bool getattribute (string_view name, float &value) { return getattribute (name, TypeFloat, &value); } -/// Shortcut getattribute() for retrieving a single string as a `std::string`. -/// The value is placed in `value`, and the function returns `true` if the -/// attribute was found. + +/// Retrieve a single-string attribute, placed as a `std::string` into +/// `value`, and the function returns `true` if the attribute was found and +/// was legally convertible to an string. inline bool getattribute (string_view name, std::string &value) { ustring s; bool ok = getattribute (name, TypeString, &s); @@ -4152,32 +4214,44 @@ inline bool getattribute (string_view name, std::string &value) { value = s.string(); return ok; } -/// Shortcut getattribute() for retrieving a single string as a `char*`. -inline bool getattribute (string_view name, char **val) { - return getattribute (name, TypeString, val); + +/// Retrieve a single-string attribute, placed as a `const char*` into +/// `*value`, and the function returns `true` if the attribute was found and +/// was legally convertible to an string. Note that the `const char*` +/// retrieved is really the characters belonging to a `ustring`, and so is +/// owned by OIIO and should not be freed by the calling code. +inline bool getattribute (string_view name, char **value) { + return getattribute (name, TypeString, value); } -/// Shortcut getattribute() for retrieving a single integer, with a supplied -/// default value that will be returned if the attribute is not found or -/// could not legally be converted to an int. + +/// Retrieve a single-integer attribute, with a supplied default value that +/// will be returned if the attribute is not found or could not legally be +/// converted to an int. inline int get_int_attribute (string_view name, int defaultval=0) { int val; return getattribute (name, TypeInt, &val) ? val : defaultval; } -/// Shortcut getattribute() for retrieving a single float, with a supplied -/// default value that will be returned if the attribute is not found or -/// could not legally be converted to a float. + +/// Retrieve a single-float attribute, with a supplied default value that +/// will be returned if the attribute is not found or could not legally be +/// converted to a float. inline float get_float_attribute (string_view name, float defaultval=0) { float val; return getattribute (name, TypeFloat, &val) ? val : defaultval; } -/// Shortcut getattribute() for retrieving a single string, with a supplied -/// default value that will be returned if the attribute is not found. + +/// Retrieve a single-string attribute, with a supplied default value that +/// will be returned if the attribute is not found or could not legally be +/// converted to an int. default value that will be returned if the attribute +/// is not found. inline string_view get_string_attribute (string_view name, string_view defaultval = string_view()) { ustring val; return getattribute (name, TypeString, &val) ? string_view(val) : defaultval; } +/// @} + /// Set the metadata of the `spec` to presume that color space is `name` (or /// to assume nothing about the color space if `name` is empty). The core From 6fcf4d25e233607c04a99d3a8355867d2a0416d5 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 30 Dec 2025 11:27:23 -0800 Subject: [PATCH 078/508] deps: Test against libraw 0.21.5 (#4988) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 8 ++++---- INSTALL.md | 2 +- src/build-scripts/build_libraw.bash | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeba96601d..31d7e647ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -460,7 +460,7 @@ jobs: simd: avx2,f16c setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.21.5 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.5.0 @@ -513,7 +513,7 @@ jobs: simd: avx2,f16c setenvs: export OpenImageIO_BUILD_LOCAL_DEPS=all OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.21.5 PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 @@ -543,7 +543,7 @@ jobs: python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.21.5 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 @@ -564,7 +564,7 @@ jobs: python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.21.5 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 diff --git a/INSTALL.md b/INSTALL.md index 68d69aa67e..becea32e95 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -47,7 +47,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for PNG files: * libPNG >= 1.6.0 (tested though 1.6.50) * If you want support for camera "RAW" formats: - * LibRaw >= 0.20 (tested though 0.21.4 and master) + * LibRaw >= 0.20 (tested though 0.21.5 and master) * If you want support for a wide variety of video formats: * ffmpeg >= 4.0 (tested through 8.0) * If you want support for jpeg 2000 images: diff --git a/src/build-scripts/build_libraw.bash b/src/build-scripts/build_libraw.bash index b5114d3aab..2b4b3bef92 100755 --- a/src/build-scripts/build_libraw.bash +++ b/src/build-scripts/build_libraw.bash @@ -11,7 +11,7 @@ set -ex # Which LibRaw to retrieve, how to build it LIBRAW_REPO=${LIBRAW_REPO:=https://github.com/LibRaw/LibRaw.git} -LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.4} +LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.5} # Where to install the final results LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} From 6cb2aefb3c02459557570e32ed8d70ca8ece3dd9 Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Tue, 30 Dec 2025 23:45:23 -0800 Subject: [PATCH 079/508] feat(sgi): Implement RLE encoding support for output (#4990) Implement RLE compression support for the SGI output plugin. Reading RLE encoded images was already supported, but writing was never done up until this point. The existing sgi test seems sufficient to catch issues and it covers input/output of both 1 byte-per-pixel and 2 byte-per-pixel files. The documentation for the image plugins are sometimes not very clear about which attributes are relevant for input vs. output. There's usually 3 sections: Attributes, Attributes for Input, and Attributes for Output. Before this PR, SGI mentioned the "compression" attribute in the "general" Attributes section (rather than say just the Input section), which caused a bit of grief as the only way to discover that RLE was not implemented for Output was to glance at the file size of the resulting file... I had assumed that compression was supported for output too but discovered that it was not. Now that this PR implements the attribute for output I've left the documentation as-is in the "general" Attributes section since it applies to both read/writing now. But I'm open for suggestions here. Signed-off-by: Jesse Yurkovich Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/sgi.imageio/sgioutput.cpp | 249 +++++++++++++++++++++++++++++++--- 1 file changed, 228 insertions(+), 21 deletions(-) diff --git a/src/sgi.imageio/sgioutput.cpp b/src/sgi.imageio/sgioutput.cpp index 2d3c9f358d..0eb0928cd3 100644 --- a/src/sgi.imageio/sgioutput.cpp +++ b/src/sgi.imageio/sgioutput.cpp @@ -26,12 +26,19 @@ class SgiOutput final : public ImageOutput { std::string m_filename; std::vector m_scratch; unsigned int m_dither; - std::vector m_tilebuffer; + bool m_want_rle; + std::vector m_uncompressed_image; void init() { ioproxy_clear(); } bool create_and_write_header(); + bool write_scanline_raw(int y, const unsigned char* data); + bool write_scanline_rle(int y, const unsigned char* data, int64_t& offset, + std::vector& start_table, + std::vector& length_table); + bool write_buffered_pixels(); + /// Helper - write, with error detection template bool fwrite(const T* buf, size_t itemsize = sizeof(T), size_t nitems = 1) @@ -85,10 +92,12 @@ SgiOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) ? m_spec.get_int_attribute("oiio:dither", 0) : 0; + m_want_rle = m_spec.get_string_attribute("compression") == "rle"; + // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); + // it by buffering the whole image. RLE is treated similarly. + if (m_want_rle || (m_spec.tile_width && m_spec.tile_height)) + m_uncompressed_image.resize(m_spec.image_bytes()); return create_and_write_header(); } @@ -102,32 +111,57 @@ SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data, y = m_spec.height - y - 1; data = to_native_scanline(format, data, xstride, m_scratch, m_dither, y, z); + // If we are writing RLE data, just copy into the uncompressed buffer + if (m_want_rle) { + const auto scaneline_size = m_spec.scanline_bytes(); + memcpy(&m_uncompressed_image[y * scaneline_size], data, scaneline_size); + + return true; + } + + return write_scanline_raw(y, (const unsigned char*)data); +} + + + +bool +SgiOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, + stride_t xstride, stride_t ystride, stride_t zstride) +{ + // Emulate tiles by buffering the whole image + return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, + zstride, &m_uncompressed_image[0]); +} + + + +bool +SgiOutput::write_scanline_raw(int y, const unsigned char* data) +{ // In SGI format all channels are saved to file separately: first, all // channel 1 scanlines are saved, then all channel2 scanlines are saved // and so on. - // - // Note that since SGI images are pretty archaic and most probably - // people won't be too picky about full flexibility writing them, we - // content ourselves with only writing uncompressed data, and don't - // attempt to write with RLE encoding. size_t bpc = m_spec.format.size(); // bytes per channel std::unique_ptr channeldata( new unsigned char[m_spec.width * bpc]); for (int64_t c = 0; c < m_spec.nchannels; ++c) { - unsigned char* cdata = (unsigned char*)data + c * bpc; + const unsigned char* cdata = data + c * bpc; for (int64_t x = 0; x < m_spec.width; ++x) { channeldata[x * bpc] = cdata[0]; if (bpc == 2) channeldata[x * bpc + 1] = cdata[1]; cdata += m_spec.nchannels * bpc; // advance to next pixel } + if (bpc == 2 && littleendian()) swap_endian((unsigned short*)&channeldata[0], m_spec.width); + ptrdiff_t scanline_offset = sgi_pvt::SGI_HEADER_LEN + ptrdiff_t(c * m_spec.height + y) * m_spec.width * bpc; + ioseek(scanline_offset); if (!iowrite(&channeldata[0], 1, m_spec.width * bpc)) { return false; @@ -139,13 +173,179 @@ SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data, +static bool +data_equals(const unsigned char* data, int bpc, imagesize_t off1, + imagesize_t off2) +{ + if (bpc == 1) { + return data[off1] == data[off2]; + } else { + return data[off1] == data[off2] && data[off1 + 1] == data[off2 + 1]; + } +} + + + +static void +data_set(unsigned char* data, int bpc, imagesize_t off, + const unsigned char* val) +{ + if (bpc == 1) { + data[off] = val[0]; + } else { + data[off] = val[1]; + data[off + 1] = val[0]; + } +} + + + +static void +data_set(unsigned char* data, int bpc, imagesize_t off, const short val) +{ + if (bpc == 1) { + data[off] = static_cast(val); + } else { + data[off] = static_cast(val >> 8); + data[off + 1] = static_cast(val & 0xFF); + } +} + + + bool -SgiOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) +SgiOutput::write_scanline_rle(int y, const unsigned char* data, int64_t& offset, + std::vector& offset_table, + std::vector& length_table) { - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); + const size_t bpc = m_spec.format.size(); // bytes per channel + const size_t xstride = m_spec.nchannels * bpc; + const imagesize_t scanline_bytes = m_spec.scanline_bytes(); + + // Account for the worst case length when every pixel is different + m_scratch.resize(bpc * (m_spec.width + (m_spec.width / 127 + 2))); + + for (int64_t c = 0; c < m_spec.nchannels; ++c) { + const unsigned char* cdata = data + c * bpc; + + imagesize_t out = 0; + imagesize_t pos = 0; + while (pos < scanline_bytes) { + imagesize_t start = pos; + // Find the first run meeting a minimum length of 3 + imagesize_t ahead_1 = pos + xstride; + imagesize_t ahead_2 = pos + xstride * 2; + while (ahead_2 < scanline_bytes + && (!data_equals(cdata, bpc, ahead_1, ahead_2) + || !data_equals(cdata, bpc, pos, ahead_1))) { + pos += xstride; + ahead_1 += xstride; + ahead_2 += xstride; + } + if (ahead_2 >= scanline_bytes) { + // No more runs, just dump the rest as literals + pos = scanline_bytes; + } + int count = int((pos - start) / xstride); + while (count) { + int todo = (count > 127) ? 127 : count; + count -= todo; + data_set(m_scratch.data(), bpc, out, 0x80 | todo); + out += bpc; + while (todo) { + data_set(m_scratch.data(), bpc, out, cdata + start); + out += bpc; + start += xstride; + todo -= 1; + } + } + start = pos; + if (start >= scanline_bytes) + break; + pos += xstride; + while (pos < scanline_bytes + && data_equals(cdata, bpc, start, pos)) { + pos += xstride; + } + count = int((pos - start) / xstride); + while (count) { + int curr_run = (count > 127) ? 127 : count; + count -= curr_run; + data_set(m_scratch.data(), bpc, out, curr_run); + out += bpc; + data_set(m_scratch.data(), bpc, out, cdata + start); + out += bpc; + } + } + data_set(m_scratch.data(), bpc, out, short(0)); + out += bpc; + + // Fill in details about the scanline + const int table_index = c * m_spec.height + y; + offset_table[table_index] = static_cast(offset); + length_table[table_index] = static_cast(out); + + // Write the compressed data + if (!iowrite(&m_scratch[0], 1, out)) + return false; + offset += out; + } + + return true; +} + + + +bool +SgiOutput::write_buffered_pixels() +{ + OIIO_ASSERT(m_uncompressed_image.size()); + + const auto scanline_bytes = m_spec.scanline_bytes(); + if (m_want_rle) { + // Prepare RLE tables + const int64_t table_size = m_spec.height * m_spec.nchannels; + const int64_t table_size_bytes = table_size * sizeof(int); + std::vector offset_table; + std::vector length_table; + offset_table.resize(table_size); + length_table.resize(table_size); + + // Skip over the tables and start at the data area + int64_t offset = sgi_pvt::SGI_HEADER_LEN + 2 * table_size_bytes; + ioseek(offset); + + // Write RLE compressed data + for (int y = 0; y < m_spec.height; ++y) { + const unsigned char* scanline_data + = &m_uncompressed_image[y * scanline_bytes]; + if (!write_scanline_rle(y, scanline_data, offset, offset_table, + length_table)) + return false; + } + + // Write the tables now that they're filled in with offsets/lengths + ioseek(sgi_pvt::SGI_HEADER_LEN); + if (littleendian()) { + swap_endian(&offset_table[0], table_size); + swap_endian(&length_table[0], table_size); + } + if (!iowrite(&offset_table[0], 1, table_size_bytes)) + return false; + if (!iowrite(&length_table[0], 1, table_size_bytes)) + return false; + + } else { + // Write raw data + for (int y = 0; y < m_spec.height; ++y) { + unsigned char* scanline_data + = &m_uncompressed_image[y * scanline_bytes]; + if (!write_scanline_raw(y, scanline_data)) + return false; + } + } + + return true; } @@ -160,15 +360,22 @@ SgiOutput::close() bool ok = true; if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); + // We've been emulating tiles; now dump as scanlines. + OIIO_ASSERT(m_uncompressed_image.size()); ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - m_tilebuffer.clear(); - m_tilebuffer.shrink_to_fit(); + m_spec.format, &m_uncompressed_image[0]); } + // If we want RLE encoding or we were tiled, output all the processed scanlines now. + if (ok && (m_want_rle || m_spec.tile_width)) { + ok &= write_buffered_pixels(); + } + + m_uncompressed_image.clear(); + m_uncompressed_image.shrink_to_fit(); + init(); + return ok; } @@ -179,7 +386,7 @@ SgiOutput::create_and_write_header() { sgi_pvt::SgiHeader sgi_header; sgi_header.magic = sgi_pvt::SGI_MAGIC; - sgi_header.storage = sgi_pvt::VERBATIM; + sgi_header.storage = m_want_rle ? sgi_pvt::RLE : sgi_pvt::VERBATIM; sgi_header.bpc = m_spec.format.size(); if (m_spec.height == 1 && m_spec.nchannels == 1) From d005830cce192d3e19992c45428afdee64853f62 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 1 Jan 2026 11:03:27 -0800 Subject: [PATCH 080/508] docs: Update CHANGES (#4991) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CHANGES.md | 106 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 79d3eb436e..f1ed5b64c0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,29 +10,54 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 * *ImageCache/TextureSystem*: * New global attribute queries via OIIO::getattribute(): * Miscellaneous API changes: - - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) + - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) +* Color management improvements: + - Fix some legacy 'Linear' color references [#4959](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4959) (3.2.0.0) + - Auto convert between oiio:ColorSpace and CICP attributes in I/O [#4964](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4964) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) + - *openexr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) + - *jpeg-xl*: CICP read and write support for JPEG-XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) (3.2.0.0, 3.1.9.0) + - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.0.14.0, 3.2.0.0) ### 🚀 Performance improvements ### 🐛 Fixes and feature enhancements - - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.1.7.0, 3.2.0.0) - - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.1.7.0, 3.2.0.0) + - *IBA*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) (3.2.0.0) + - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) (3.2.0.0) + - *imagebuf*: Fix set_pixels bug, didn't consider roi = All [#4949](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4949) (3.2.0.0) + - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.2.0.0, 3.1.7.0) + - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.2.0.0, 3.1.7.0) + - *jpeg*: Fix wrong pointers/crashing when decoding CMYK jpeg files [#4963](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4963) (3.2.0.0) + - *jpeg-2000*: Type warning in assertion in jpeg2000output.cpp [#4952](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4952) (3.2.0.0) - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.2.0.0) - - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.1.7.0, 3.2.0.0) - - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.1.7.0, 3.2.0.0) - - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.1.7.0, 3.2.0.0) + - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.2.0.0, 3.1.7.0) + - *jpeg-xl*: CICP read and write support for JPEG XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) (3.2.0.0, 3.1.9.0) + - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.2.0.0, 3.1.7.0) + - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.2.0.0, 3.1.7.0) + - *openexr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) + - *openexr*: Improve attribute translation rules [#4946](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4946) (3.2.0.0) + - *openexr*: ACES container writes colorInteropId instead of colorInteropID [#4966](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4966) (by Brecht Van Lommel) (3.2.0.0) + - *png*: We were not correctly suppressing hint metadata [#4983](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4983) (3.2.0.0) + - *sgi*: Implement RLE encoding support for output [#4990](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4990) (by Jesse Yurkovich) (3.2.0.0) + - *webp*: Allow out-of-order scanlines when writing webp [#4973](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4973) (by Pavan Madduri) (3.2.0.0) ### 🔧 Internals and developer goodies + - *filesystem.h*: Speedup to detect the existence of files on Windows [#4977](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4977) (by JacksonSun-adsk) (3.2.0.0) ### 🏗 Build/test/CI and platform ports * OIIO's CMake build system and scripts: - - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.1.7.0, 3.2.0.0) - - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.1.7.0, 3.2.0.0) + - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.2.0.0, 3.1.7.0) + - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.2.0.0, 3.1.7.0) * Dependency and platform support: - - *build/deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.1.7.0, 3.2.0.0) + - *deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.2.0.0, 3.1.7.0) + - *deps*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) (3.2.0.0, 3.1.8.0) + - *deps*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) (3.2.0.0, 3.1.9.0) * Testing and Continuous integration (CI) systems: - - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.1.7.0, 3.2.0.0) - - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.1.7.0, 3.2.0.0) - - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.1.7.0, 3.2.0.0) + - *tests*: Image_span_test reduce benchmark load for debug and CI renders [#4951](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4951) (3.2.0.0, 3.1.8.0) + - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.2.0.0, 3.1.7.0) + - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.2.0.0, 3.1.7.0) + - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.2.0.0, 3.1.7.0) + - *ci*: Emergency fix change deprecated sonarqube action [#4969](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4969) (3.2.0.0) + - *ci*: Try python 3.13 to fix Mac breakage on CI [#4970](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4970) (3.2.0.0) ### 📚 Notable documentation changes - - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.1.7.0, 3.2.0.0) + - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.2.0.0, 3.1.7.0) ### 🏢 Project Administration + - *admin*: Minor rewording in the issue and PR templates [#4982](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4982) (3.2.0.0) ### 🤝 Contributors --- @@ -40,7 +65,43 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 -Release 3.1.7.0 (Nov 1, 2025) -- compared to 3.1.7.0 +Release 3.1.9.0 (Jan 1, 2026) -- compared to 3.1.8.0 +---------------------------------------------------- + - Color management improvements: + - Auto convert between oiio:ColorSpace and CICP attributes in I/O [#4964](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4964) (by Brecht Van Lommel) + - *exr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) + - *jpeg-xl*: CICP read and write support for JPEG-XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) + - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) + - *png*: We were not correctly suppressing hint metadata [#4983](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4983) + - *sgi*: Implement RLE encoding support for output [#4990](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4990) (by Jesse Yurkovich) + - *webp*: Allow out-of-order scanlines when writing webp [#4973](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4973) (by Pavan Madduri) + - *fix/IBA*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) + - *perf/filesystem.h*: Speedup to detect the existence of files on Windows [#4977](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4977) (by JacksonSun-adsk) + - *ci*: Address tight disk space on GHA runners [#4974](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4974) + - *ci*: Optimize install_homebrew_deps by coalescing installs [#4975](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4975) + - *ci*: Build_Ptex.bash should build Ptex using C++17 [#4978](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4978) + - *ci*: Unbreak CI by adjusting Ubuntu installs [#4981](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4981) + - *ci*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) + - *docs*: Fix missing docs for `OIIO:attribute()` and `OIIO::getattribute()` [#4987](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4987) + + +Release 3.1.8.0 (Dec 1, 2025) -- compared to 3.1.7.0 +---------------------------------------------------- + - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) + - *jpeg*: Fix wrong pointers/crashing when decoding CMYK jpeg files [#4963](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4963) + - *openexr*: Improve attribute translation rules [#4946](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4946) + - *openexr*: ACES container writes colorInteropId instead of colorInteropID [#4966](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4966) (by Brecht Van Lommel) + - *color mgmt*: Fix some legacy 'Linear' color references [#4959](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4959) + - *imagebuf*: Fix `ImageBuf::set_pixels()` bug, didn't consider roi = All [#4949](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4949) + - *tests*: Image_span_test reduce benchmark load for debug and CI renders [#4951](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4951) + - *build*: Type warning in assertion in jpeg2000output.cpp [#4952](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4952) + - *build*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) + - *ci*: Fix broken ci, debug and static cases, bump some latest [#4954](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4954) + - *ci*: Unbreak icc/icx CI [#4958](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4958) + - *admin*: Update some license notices [#4955](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4955) + + +Release 3.1.7.0 (Nov 1, 2025) -- compared to 3.1.6.1 ---------------------------------------------------- - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.1.7.0) - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.1.7.0) @@ -67,7 +128,7 @@ Release 3.1.6.2 (Oct 3, 2025) -- compared to 3.1.6.1 - *oiioversion.h*: Restore definition of `OIIO_NAMESPACE_USING` macro [#4920](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4920) -Release 3.1 (Oct 2 2025) -- compared to 3.0.x +Release 3.1 (Oct 2, 2025) -- compared to 3.0.x ----------------------------------------------------- - Beta 1: Aug 22, 2025 - Beta 2: Sep 19, 2025 @@ -383,6 +444,21 @@ asterisk) had not previously contributed to the project. --- +Release 3.0.14.0 (Jan 1, 2026) -- compared to 3.0.13.0 +------------------------------------------------------- + - *fix(IBA)*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) + - *ci*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) + - *ci*: Address tight disk space on GHA runners [#4974](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4974) + + +Release 3.0.13.0 (Dec 1, 2025) -- compared to 3.0.12.0 +------------------------------------------------------- + - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) + - *build*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) + - *ci*: Fix broken ci, debug and static cases, bump some latest [#4954](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4954) + - *ci*: Unbreak icc/icx CI [#4958](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4958) + + Release 3.0.12.0 (Nov 1, 2025) -- compared to 3.0.11.0 ------------------------------------------------------- - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) From a68813a3a726b6ca37ab9ccaac89e675138e264d Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 5 Jan 2026 10:31:46 -0800 Subject: [PATCH 081/508] deps: libheif 1.21 support (#4992) Starting with 1.21, libheif seems to change behavior: When no CICP metadata is present, libheif now returns 2,2,2 (all unspecified) on read. OIIO convention, though, is to not set the attribute if valid CICP data is not in the file. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- INSTALL.md | 2 +- src/heif.imageio/heifinput.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index becea32e95..1bcc407d68 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -64,7 +64,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * giflib >= 5.0 (tested through 5.2.2) * If you want support for HEIF/HEIC or AVIF images: * libheif >= 1.11 (1.16 required for correct orientation support, - tested through 1.20.2) + tested through 1.21.1) * libheif must be built with an AV1 encoder/decoder for AVIF support. * If you want support for DICOM medical image files: * DCMTK >= 3.6.1 (tested through 3.6.9) diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp index 349bcdb1d4..9e0230a86c 100644 --- a/src/heif.imageio/heifinput.cpp +++ b/src/heif.imageio/heifinput.cpp @@ -287,7 +287,14 @@ HeifInput::seek_subimage(int subimage, int miplevel) m_ihandle.get_raw_image_handle(), &nclx); if (nclx) { - if (err.code == heif_error_Ok) { + // When CICP metadata is not present in the file, libheif returns + // unspecified since v1.21. Ignore it then. + if (err.code == heif_error_Ok + && !(nclx->color_primaries == heif_color_primaries_unspecified + && nclx->transfer_characteristics + == heif_transfer_characteristic_unspecified + && nclx->matrix_coefficients + == heif_matrix_coefficients_unspecified)) { const int cicp[4] = { int(nclx->color_primaries), int(nclx->transfer_characteristics), int(nclx->matrix_coefficients), From 69d4f8e746b20664ab6a64e59a061fdf6bb3c4db Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 8 Jan 2026 11:47:37 -0800 Subject: [PATCH 082/508] perf: IBA::resample improvements to speed up 20x or more (#4993) For IBA::resample() when bilinear interpolation is used, almost all of the expense was due to its relying on ImageBuf::interppixel which is simple but constructs a new ImageBuf::ConstIterator EVERY TIME, which is very expensive. Reimplement in a way that reuses a single iterator. This speeds up IBA::resample by 20x or more typicaly. Also refactor resample to pull the handling of deep images into a separate helper function and out of the main inner loop. And add some benchmarking. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/imagebufalgo_test.cpp | 36 +++++ src/libOpenImageIO/imagebufalgo_xform.cpp | 155 ++++++++++++++++------ 2 files changed, 151 insertions(+), 40 deletions(-) diff --git a/src/libOpenImageIO/imagebufalgo_test.cpp b/src/libOpenImageIO/imagebufalgo_test.cpp index 940e2a8ff5..cdc545c9c8 100644 --- a/src/libOpenImageIO/imagebufalgo_test.cpp +++ b/src/libOpenImageIO/imagebufalgo_test.cpp @@ -641,6 +641,41 @@ test_zover() +// Test ImageBuf::resample +void +test_resample() +{ + std::cout << "test resample\n"; + + // Timing + Benchmarker bench; + bench.units(Benchmarker::Unit::ms); + + ImageSpec spec_hd_rgba_f(1920, 1080, 4, TypeFloat); + ImageSpec spec_hd_rgba_u8(1920, 1080, 4, TypeUInt8); + ImageBuf buf_hd_rgba_f(spec_hd_rgba_f); + ImageBuf buf_hd_rgba_u8(spec_hd_rgba_u8); + float red_rgba[] = { 1.0, 0.0, 0.0, 1.0 }; + ImageBufAlgo::fill(buf_hd_rgba_f, red_rgba); + ImageBufAlgo::fill(buf_hd_rgba_u8, red_rgba); + ImageBuf smallf(ImageSpec(1024, 512, 4, TypeFloat)); + ImageBuf smallu8(ImageSpec(1024, 512, 4, TypeUInt8)); + bench(" IBA::resample HD->1024x512 rgba f->f interp ", + [&]() { ImageBufAlgo::resample(smallf, buf_hd_rgba_f, true); }); + bench(" IBA::resample HD->1024x512 rgba f->u8 interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_f, true); }); + bench(" IBA::resample HD->1024x512 rgba u8->u8 interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_u8, true); }); + bench(" IBA::resample HD->1024x512 rgba f->f no interp ", + [&]() { ImageBufAlgo::resample(smallf, buf_hd_rgba_f, false); }); + bench(" IBA::resample HD->1024x512 rgba f->u8 no interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_f, false); }); + bench(" IBA::resample HD->1024x512 rgba u8->u8 no interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_u8, false); }); +} + + + // Tests ImageBufAlgo::compare void test_compare() @@ -1581,6 +1616,7 @@ main(int argc, char** argv) test_over(TypeFloat); test_over(TypeHalf); test_zover(); + test_resample(); test_compare(); test_isConstantColor(); test_isConstantChannel(); diff --git a/src/libOpenImageIO/imagebufalgo_xform.cpp b/src/libOpenImageIO/imagebufalgo_xform.cpp index 0abbb1ace8..beb7d1a700 100644 --- a/src/libOpenImageIO/imagebufalgo_xform.cpp +++ b/src/libOpenImageIO/imagebufalgo_xform.cpp @@ -1070,6 +1070,35 @@ ImageBufAlgo::fit(const ImageBuf& src, KWArgs options, ROI roi, int nthreads) +// This operates just like the internals of ImageBuf::interppixel(), but +// reuses the provided iterator to avoid the overhead of constructing a new +// one each time. This speeds it up by 20x! The iterator `it` must already be +// associated with `img`, but it need not be positioned correctly. +template +static bool +interppixel(const ImageBuf& img, ImageBuf::ConstIterator& it, float x, + float y, span pixel, ImageBuf::WrapMode wrap) +{ + int n = std::min(int(pixel.size()), img.spec().nchannels); + float* localpixel = OIIO_ALLOCA(float, n * 4); + float* p[4] = { localpixel, localpixel + n, localpixel + 2 * n, + localpixel + 3 * n }; + x -= 0.5f; + y -= 0.5f; + int xtexel, ytexel; + float xfrac, yfrac; + xfrac = floorfrac(x, &xtexel); + yfrac = floorfrac(y, &ytexel); + it.rerange(xtexel, xtexel + 2, ytexel, ytexel + 2, 0, 1, wrap); + for (int i = 0; i < 4; ++i, ++it) + for (int c = 0; c < n; ++c) + p[i][c] = it[c]; //NOSONAR + bilerp(p[0], p[1], p[2], p[3], xfrac, yfrac, n, pixel.data()); + return true; +} + + + template static bool resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, @@ -1080,7 +1109,6 @@ resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, const ImageSpec& srcspec(src.spec()); const ImageSpec& dstspec(dst.spec()); int nchannels = src.nchannels(); - bool deep = src.deep(); // Local copies of the source image window, converted to float float srcfx = srcspec.full_x; @@ -1109,25 +1137,10 @@ resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, float s = (x - dstfx + 0.5f) * dstpixelwidth; float src_xf = srcfx + s * srcfw; int src_x = ifloor(src_xf); - if (deep) { - srcpel.pos(src_x, src_y, 0); - int nsamps = srcpel.deep_samples(); - OIIO_DASSERT(nsamps == out.deep_samples()); - if (!nsamps || nsamps != out.deep_samples()) - continue; - for (int c = 0; c < nchannels; ++c) { - if (dstspec.channelformat(c) == TypeDesc::UINT32) - for (int samp = 0; samp < nsamps; ++samp) - out.set_deep_value( - c, samp, srcpel.deep_value_uint(c, samp)); - else - for (int samp = 0; samp < nsamps; ++samp) - out.set_deep_value(c, samp, - srcpel.deep_value(c, samp)); - } - } else if (interpolate) { + if (interpolate) { // Non-deep image, bilinearly interpolate - src.interppixel(src_xf, src_yf, pel, ImageBuf::WrapClamp); + interppixel(src, srcpel, src_xf, src_yf, pel, + ImageBuf::WrapClamp); for (int c = roi.chbegin; c < roi.chend; ++c) out[c] = pel[c]; } else { @@ -1144,6 +1157,88 @@ resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, +static bool +resample_deep(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, + int nthreads) +{ + // If it's deep, figure out the sample allocations first, because + // it's not thread-safe to do that simultaneously with copying the + // values. + const ImageSpec& srcspec(src.spec()); + const ImageSpec& dstspec(dst.spec()); + float srcfx = srcspec.full_x; + float srcfy = srcspec.full_y; + float srcfw = srcspec.full_width; + float srcfh = srcspec.full_height; + float dstpixelwidth = 1.0f / dstspec.full_width; + float dstpixelheight = 1.0f / dstspec.full_height; + ImageBuf::ConstIterator srcpel(src, roi); + ImageBuf::Iterator dstpel(dst, roi); + for (; !dstpel.done(); ++dstpel, ++srcpel) { + float s = (dstpel.x() - dstspec.full_x + 0.5f) * dstpixelwidth; + float t = (dstpel.y() - dstspec.full_y + 0.5f) * dstpixelheight; + int src_y = ifloor(srcfy + t * srcfh); + int src_x = ifloor(srcfx + s * srcfw); + srcpel.pos(src_x, src_y, 0); + dstpel.set_deep_samples(srcpel.deep_samples()); + } + + OIIO_ASSERT(src.deep() == dst.deep()); + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + const ImageSpec& srcspec(src.spec()); + const ImageSpec& dstspec(dst.spec()); + int nchannels = src.nchannels(); + + // Local copies of the source image window, converted to float + float srcfx = srcspec.full_x; + float srcfy = srcspec.full_y; + float srcfw = srcspec.full_width; + float srcfh = srcspec.full_height; + + float dstfx = dstspec.full_x; + float dstfy = dstspec.full_y; + float dstfw = dstspec.full_width; + float dstfh = dstspec.full_height; + float dstpixelwidth = 1.0f / dstfw; + float dstpixelheight = 1.0f / dstfh; + + ImageBuf::Iterator out(dst, roi); + ImageBuf::ConstIterator srcpel(src); + for (int y = roi.ybegin; y < roi.yend; ++y) { + // s,t are NDC space + float t = (y - dstfy + 0.5f) * dstpixelheight; + // src_xf, src_xf are image space float coordinates + float src_yf = srcfy + t * srcfh; + // src_x, src_y are image space integer coordinates of the floor + int src_y = ifloor(src_yf); + for (int x = roi.xbegin; x < roi.xend; ++x, ++out) { + float s = (x - dstfx + 0.5f) * dstpixelwidth; + float src_xf = srcfx + s * srcfw; + int src_x = ifloor(src_xf); + srcpel.pos(src_x, src_y, 0); + int nsamps = srcpel.deep_samples(); + OIIO_DASSERT(nsamps == out.deep_samples()); + if (!nsamps || nsamps != out.deep_samples()) + continue; + for (int c = 0; c < nchannels; ++c) { + if (dstspec.channelformat(c) == TypeDesc::UINT32) + for (int samp = 0; samp < nsamps; ++samp) + out.set_deep_value(c, samp, + srcpel.deep_value_uint(c, samp)); + else + for (int samp = 0; samp < nsamps; ++samp) + out.set_deep_value(c, samp, + srcpel.deep_value(c, samp)); + } + } + } + }); + + return true; +} + + + bool ImageBufAlgo::resample(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, int nthreads) @@ -1155,27 +1250,7 @@ ImageBufAlgo::resample(ImageBuf& dst, const ImageBuf& src, bool interpolate, return false; if (dst.deep()) { - // If it's deep, figure out the sample allocations first, because - // it's not thread-safe to do that simultaneously with copying the - // values. - const ImageSpec& srcspec(src.spec()); - const ImageSpec& dstspec(dst.spec()); - float srcfx = srcspec.full_x; - float srcfy = srcspec.full_y; - float srcfw = srcspec.full_width; - float srcfh = srcspec.full_height; - float dstpixelwidth = 1.0f / dstspec.full_width; - float dstpixelheight = 1.0f / dstspec.full_height; - ImageBuf::ConstIterator srcpel(src, roi); - ImageBuf::Iterator dstpel(dst, roi); - for (; !dstpel.done(); ++dstpel, ++srcpel) { - float s = (dstpel.x() - dstspec.full_x + 0.5f) * dstpixelwidth; - float t = (dstpel.y() - dstspec.full_y + 0.5f) * dstpixelheight; - int src_y = ifloor(srcfy + t * srcfh); - int src_x = ifloor(srcfx + s * srcfw); - srcpel.pos(src_x, src_y, 0); - dstpel.set_deep_samples(srcpel.deep_samples()); - } + return resample_deep(dst, src, interpolate, roi, nthreads); } bool ok; From e0cb720d1bcd8e4fed6b456c073505a31ec0736c Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 9 Jan 2026 12:33:27 -0800 Subject: [PATCH 083/508] ci/deps: Freetype adjustments (#4999) * CI test vs the latest freetype 2.14.1 * Bump the version of freetype that we auto-build to the latest (from 2.13.2) * Simplify BZip2 finding logic, switch to using targets Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 8 ++++---- src/build-scripts/build_Freetype.bash | 2 +- src/cmake/build_Freetype.cmake | 6 +++--- src/cmake/externalpackages.cmake | 6 ++---- src/ffmpeg.imageio/CMakeLists.txt | 2 +- src/libOpenImageIO/CMakeLists.txt | 2 +- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31d7e647ea..232ce657ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -466,7 +466,7 @@ jobs: PTEX_VERSION=v2.5.0 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-0 + FREETYPE_VERSION=VER-2-14-1 USE_OPENVDB=0 # Ensure we are testing all the deps we think we are. We would # like this test to have minimal missing dependencies. @@ -549,7 +549,7 @@ jobs: PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-0 + FREETYPE_VERSION=VER-2-14-1 USE_OPENVDB=0 - desc: Linux ARM latest releases clang18 C++20 py3.12 exr3.4 ocio2.4 nametag: linux-arm-latest-releases-clang @@ -570,7 +570,7 @@ jobs: PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-0 + FREETYPE_VERSION=VER-2-14-1 USE_OPENVDB=0 @@ -683,7 +683,7 @@ jobs: # built. But we would like to add more dependencies and reduce this list # of exceptions in the future. required_deps: ${{ matrix.required_deps || 'all' }} - optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} + optional_deps: ${{ matrix.optional_deps || 'BZip2;CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} strategy: fail-fast: false matrix: diff --git a/src/build-scripts/build_Freetype.bash b/src/build-scripts/build_Freetype.bash index 41c74f5c20..cd27c00ddd 100755 --- a/src/build-scripts/build_Freetype.bash +++ b/src/build-scripts/build_Freetype.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of Freetype to download if we don't have it yet FREETYPE_REPO=${FREETYPE_REPO:=https://github.com/freetype/freetype.git} -FREETYPE_VERSION=${FREETYPE_VERSION:=VER-2-13-3} +FREETYPE_VERSION=${FREETYPE_VERSION:=VER-2-14-1} # Where to put Freetype repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/cmake/build_Freetype.cmake b/src/cmake/build_Freetype.cmake index cd9becae46..a38e17151a 100644 --- a/src/cmake/build_Freetype.cmake +++ b/src/cmake/build_Freetype.cmake @@ -6,10 +6,10 @@ # Freetype by hand! ###################################################################### -set_cache (Freetype_BUILD_VERSION 2.13.2 "Freetype version for local builds") +set_cache (Freetype_BUILD_VERSION 2.14.1 "Freetype version for local builds") set (Freetype_GIT_REPOSITORY "https://github.com/freetype/freetype") -set (Freetype_GIT_TAG "VER-2-13-2") -set_cache (Freetype_BUILD_SHARED_LIBS OFF +set (Freetype_GIT_TAG "VER-2-14-1") +set_cache (Freetype_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Freetype build, if necessary, build shared libraries" ADVANCED) # We would prefer to build a static Freetype, but haven't figured out how to make # it all work with the static dependencies, it just makes things complicated diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 12467ae6b6..7c57764a04 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -200,11 +200,9 @@ checked_find_package (R3DSDK NO_RECORD_NOTFOUND) # RED camera set (NUKE_VERSION "7.0" CACHE STRING "Nuke version to target") checked_find_package (Nuke NO_RECORD_NOTFOUND) -if (FFmpeg_FOUND OR FREETYPE_FOUND) +if ((FFmpeg_FOUND OR FREETYPE_FOUND OR TARGET Freetype::Freetype) + AND NOT TARGET BZip2::BZip2) checked_find_package (BZip2) # Used by ffmpeg and freetype - if (NOT BZIP2_FOUND) - set (BZIP2_LIBRARIES "") # TODO: why does it break without this? - endif () endif() diff --git a/src/ffmpeg.imageio/CMakeLists.txt b/src/ffmpeg.imageio/CMakeLists.txt index c84ef3c90a..ac76c21473 100644 --- a/src/ffmpeg.imageio/CMakeLists.txt +++ b/src/ffmpeg.imageio/CMakeLists.txt @@ -28,7 +28,7 @@ if (FFmpeg_FOUND) add_oiio_plugin (ffmpeginput.cpp INCLUDE_DIRS ${FFMPEG_INCLUDES} LINK_LIBRARIES ${FFMPEG_LIBRARIES} - ${BZIP2_LIBRARIES} + $ DEFINITIONS "USE_FFMPEG" "-DOIIO_FFMPEG_VERSION=\"${FFMPEG_VERSION}\"") else() diff --git a/src/libOpenImageIO/CMakeLists.txt b/src/libOpenImageIO/CMakeLists.txt index f2459b2d32..e6a117f3f7 100644 --- a/src/libOpenImageIO/CMakeLists.txt +++ b/src/libOpenImageIO/CMakeLists.txt @@ -163,7 +163,7 @@ target_link_libraries (OpenImageIO $ $ $ - ${BZIP2_LIBRARIES} + $ ZLIB::ZLIB ${CMAKE_DL_LIBS} ) From 1d029789fa9b589e9335517e95a087b284dcbc02 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 9 Jan 2026 12:33:43 -0800 Subject: [PATCH 084/508] ci: Speed up macos15 intel variant by not installing Qt (#4998) The Intel MacOS 15 CI testing is getting dicier... lots of times, Homebrew doesn't have cached versions of updated packages, so it tries to build from source, which takes forever. The big culprit today is Qt. So, basically, just on this one CI job variant, don't ask it to install Qt. If it's there, it's there. If not, just skip it. It's tested plenty in other variants. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 232ce657ef..06800c60b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -626,7 +626,7 @@ jobs: python_ver: "3.13" simd: sse4.2,avx2 ctest_test_timeout: 1200 - setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 + setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 benchmark: 1 - desc: MacOS-14-ARM aclang15/C++20/py3.13 runner: macos-14 From 84c0aa7b622b77aae1c4b8006542d41a1c7d92a4 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 9 Jan 2026 14:49:05 -0800 Subject: [PATCH 085/508] ci: don't run non-wheel workflows when only pyproject.toml changes (#4997) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/analysis.yml | 1 + .github/workflows/ci.yml | 1 + .github/workflows/docs.yml | 2 ++ 3 files changed, 4 insertions(+) diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index bdaa0f8c81..db23ee6f55 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -26,6 +26,7 @@ on: - '!**/scorecard.yml' - '!**/wheel.yml' - '!**.properties' + - '!pyproject.toml' - '!docs/**' # Run analysis on PRs only if the branch name indicates that the purpose of # the PR is related to the Sonar analysis. We don't run on every PR because diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06800c60b4..54edda4ee3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ on: - '!**/scorecard.yml' - '!**/wheel.yml' - '!**.properties' + - '!pyproject.toml' - '!docs/**' pull_request: paths: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ffc13b91c6..4eb8445841 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,6 +20,7 @@ on: - '**/run.py' - 'src/build-scripts/**' - './*.md' + - 'pyproject.toml' pull_request: paths-ignore: - '**/ci.yml' @@ -33,6 +34,7 @@ on: - '**/run.py' - 'src/build-scripts/**' - './*.md' + - 'pyproject.toml' schedule: # Full nightly build - cron: "0 8 * * *" From 874059a3a623f0c2132f16e888493ba940f1b29b Mon Sep 17 00:00:00 2001 From: Brad Smith Date: Fri, 9 Jan 2026 19:55:11 -0500 Subject: [PATCH 086/508] fix(build): Fix building on OpenBSD (#5001) Fixes #5000 Signed-off-by: Brad Smith Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/typedesc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index 33cf913560..5c38ae6209 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -409,7 +409,7 @@ template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; -#if defined(__GNUC__) && (ULONG_MAX == 0xffffffffffffffff) && !(defined(__APPLE__) && defined(__MACH__)) || defined(__NetBSD__) +#if defined(__GNUC__) && (ULONG_MAX == 0xffffffffffffffff) && !(defined(__APPLE__) && defined(__MACH__)) && !defined(__OpenBSD__) || defined(__NetBSD__) // Some platforms consider int64_t and long long to be different types, even // though they are actually the same size. static_assert(!std::is_same_v); From 30e18cce52c5e19bb65144a68546078c5a449178 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 10 Jan 2026 09:46:21 -0800 Subject: [PATCH 087/508] admin: Refine PR template to give more visual separation (#4995) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/PULL_REQUEST_TEMPLATE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 536ef4915c..386b60c172 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,8 @@ + + + +------ :scissors: ------------------------------------------------------------------- + YOU MAY DELETE ALL OF THIS IF YOU ALREADY HAVE A DESCRIPTIVE COMMIT MESSAGE! This is just a template and set of reminders about what constitutes a good PR. From 415525de49d30f0bb7628d6685baf183ef7e7bb5 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 13 Jan 2026 12:13:24 -0800 Subject: [PATCH 088/508] build: Fix HARDENING build options (#4996) Due to typos in the option name in compiler.cmake (HARDENING vs OIIO_HARDENING, oops), I think we were never really setting the intended compiler flags. Fix that all up, and repair the other problems that it revealed -- some compiler version and option combinations weren't happy with each other, etc. One notable change is in encode_iptc_iim_one_tag: I think I made it safer by taking an early out for 0-sized data, and also it needed a warning suppression when certain gcc hardening levels were used. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 3 +- src/cmake/compiler.cmake | 62 +++++++++++++++++++++++++------------ src/libOpenImageIO/iptc.cpp | 22 +++++++------ 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54edda4ee3..3a11a36779 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -494,7 +494,7 @@ jobs: PTEX_VERSION=main PUGIXML_VERSION=master WEBP_VERSION=main - OIIO_CMAKE_FLAGS="-DOIIO_HARDENING=2" + OIIO_HARDENING=2 EXTRA_DEP_PACKAGES="python3.12-dev python3-numpy" USE_OPENVDB=0 FREETYPE_VERSION=master @@ -518,6 +518,7 @@ jobs: PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 + OIIO_HARDENING=3 - desc: clang18 C++17 avx2 exr3.1 ocio2.3 nametag: linux-clang18 runner: ubuntu-24.04 diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index da4e76da48..8dffb97d36 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -485,30 +485,34 @@ endif () # recommended default for optimized, shipping code. # 2 : enable features that trade off performance for security, recommended # for debugging or deploying in security-sensitive environments. -# 3 : enable features that have a significant performance impact, only -# recommended for debugging. +# 3 : enable features that have a significant performance impact, to maximize +# finding bugs without regard to performance. Only recommended for +# debugging. # # Some documentation: # https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html # https://www.gnu.org/software/libc/manual/html_node/Source-Fortification.html # https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html # https://libcxx.llvm.org/Hardening.html -# +# https://www.productive-cpp.com/hardening-cpp-programs-stack-protector/ +# https://medium.com/@simontoth/daily-bit-e-of-c-hardened-mode-of-standard-library-implementations-18be2422c372 +# https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html + if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set (${PROJ_NAME}_HARDENING_DEFAULT 3) + set (${PROJ_NAME}_HARDENING_DEFAULT 2) else () set (${PROJ_NAME}_HARDENING_DEFAULT 1) endif () set_cache (${PROJ_NAME}_HARDENING ${${PROJ_NAME}_HARDENING_DEFAULT} "Turn on security hardening features 0, 1, 2, 3") # Implementation: -if (HARDENING GREATER_EQUAL 1) +add_compile_definitions (${PROJ_NAME}_HARDENING_DEFAULT=${${PROJ_NAME}_HARDENING}) +if (${PROJ_NAME}_HARDENING GREATER_EQUAL 1) + # Enable PIE and pie to build as position-independent executables and + # libraries, needed for address space randomization used by some kernels. + set (CMAKE_POSITION_INDEPENDENT_CODE ON) # Features that should not detectably affect performance if (COMPILER_IS_GCC_OR_ANY_CLANG) - # Enable PIE and pie to build as position-independent executables, - # needed for address space randomiztion used by some kernels. - add_compile_options (-fPIE -pie) - add_link_options (-fPIE -pie) # Protect against stack overwrites. Is allegedly not a performance # tradeoff. add_compile_options (-fstack-protector-strong) @@ -516,21 +520,39 @@ if (HARDENING GREATER_EQUAL 1) endif () # Defining _FORTIFY_SOURCE provides buffer overflow checks in modern gcc & # clang with some compiler-assisted deduction of buffer lengths) for the - # many C functions such as memcpy, strcpy, sprintf, etc. See: - add_compile_definitions (_FORTIFY_SOURCE=${${PROJ_NAME}_HARDENING}) + # many C functions such as memcpy, strcpy, sprintf, etc. But it requires + # optimization, so we don't do it for debug builds. + if ((CMAKE_COMPILER_IS_CLANG OR (GCC_VERSION VERSION_GREATER_EQUAL 14)) + AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions (_FORTIFY_SOURCE=${${PROJ_NAME}_HARDENING}) + endif () +endif () +if (${PROJ_NAME}_HARDENING EQUAL 1) # Setting _LIBCPP_HARDENING_MODE enables various hardening features in # clang/llvm's libc++ 18.0 and later. - add_compiler_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) -endif () -if (HARDENING GREATER_EQUAL 2) + if (OIIO_CLANG_VERSION VERSION_GREATER_EQUAL 18.0 OR OIIO_APPLE_CLANG_VERSION VERSION_GREATER_EQUAL 18.0) + add_compile_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) + endif () +elseif (${PROJ_NAME}_HARDENING EQUAL 2) # Features that might impact performance measurably - add_compile_definitions (_GLIBCXX_ASSERTIONS) - add_compiler_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE) -endif () -if (HARDENING GREATER_EQUAL 3) + if (GCC_VERSION VERSION_GREATER_EQUAL 14) + # I've had trouble turning this on in older gcc + add_compile_definitions (_GLIBCXX_ASSERTIONS) + endif () + if (OIIO_CLANG_VERSION VERSION_GREATER_EQUAL 18.0 OR OIIO_APPLE_CLANG_VERSION VERSION_GREATER_EQUAL 18.0) + add_compile_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE) + endif () +elseif (${PROJ_NAME}_HARDENING EQUAL 3) # Debugging features that might impact performance significantly - add_compile_definitions (_GLIBCXX_DEBUG) - add_compiler_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) + if (GCC_VERSION VERSION_GREATER_EQUAL 14) + # I've had trouble turning this on in older gcc + add_compile_definitions (_GLIBCXX_ASSERTIONS) + # N.B. _GLIBCXX_DEBUG changes ABI, so don't do this: + # add_compile_definitions (_GLIBCXX_DEBUG) + endif () + if (OIIO_CLANG_VERSION VERSION_GREATER_EQUAL 18.0 OR OIIO_APPLE_CLANG_VERSION VERSION_GREATER_EQUAL 18.0) + add_compile_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) + endif () endif () diff --git a/src/libOpenImageIO/iptc.cpp b/src/libOpenImageIO/iptc.cpp index b8ee573059..eed82eeb45 100644 --- a/src/libOpenImageIO/iptc.cpp +++ b/src/libOpenImageIO/iptc.cpp @@ -181,17 +181,21 @@ decode_iptc_iim(const void* iptc, int length, ImageSpec& spec) static void encode_iptc_iim_one_tag(int tag, string_view data, std::vector& iptc) { - OIIO_DASSERT(data != nullptr); + if (data.size() == 0) + return; + data = data.substr(0, 0xffff); // Truncate to prevent 16 bit overflow + size_t tagsize = data.size(); iptc.push_back((char)0x1c); iptc.push_back((char)0x02); iptc.push_back((char)tag); - if (data.size()) { - int tagsize = std::min(int(data.size()), - 0xffff - 1); // Prevent 16 bit overflow - iptc.push_back((char)(tagsize >> 8)); - iptc.push_back((char)(tagsize & 0xff)); - iptc.insert(iptc.end(), data.data(), data.data() + tagsize); - } + iptc.push_back((char)(tagsize >> 8)); + iptc.push_back((char)(tagsize & 0xff)); + OIIO_PRAGMA_WARNING_PUSH + OIIO_GCC_ONLY_PRAGMA(GCC diagnostic ignored "-Wstringop-overflow") + // Suppress what I'm sure is a false positive warning when + // _GLIBCXX_ASSERTIONS is enabled. + iptc.insert(iptc.end(), data.begin(), data.end()); + OIIO_PRAGMA_WARNING_POP } @@ -208,7 +212,7 @@ encode_iptc_iim(const ImageSpec& spec, std::vector& iptc) std::string allvals = p->get_string(0); std::vector tokens; Strutil::split(allvals, tokens, ";"); - for (auto& token : tokens) { + for (auto token : tokens) { token = Strutil::strip(token); if (token.size()) { if (iimtag[i].maxlen && iimtag[i].maxlen < token.size()) From eb1f5b9aa5ac385043fe61e0b65c6fa3129afd61 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 14 Jan 2026 14:16:30 -0800 Subject: [PATCH 089/508] test: Add new ref image for jpeg test (#5007) Needed for some systems after the changes of #4963. Ever so slightly different LSB somewhere makes the hashes not match, but it's a correct visual result. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- testsuite/jpeg/ref/out-jpeg9.4.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 testsuite/jpeg/ref/out-jpeg9.4.txt diff --git a/testsuite/jpeg/ref/out-jpeg9.4.txt b/testsuite/jpeg/ref/out-jpeg9.4.txt new file mode 100644 index 0000000000..bc8035159e --- /dev/null +++ b/testsuite/jpeg/ref/out-jpeg9.4.txt @@ -0,0 +1,9 @@ +Reading src/YCbCrK.jpg +src/YCbCrK.jpg : 52 x 52, 3 channel, uint8 jpeg + SHA-1: B54FAE77E27EFCEACF27BA796A48DCE6DF262F26 + channel list: R, G, B + jpeg:ColorSpace: "YCbCrK" + jpeg:subsampling: "4:4:4" + oiio:ColorSpace: "srgb_rec709_scene" +Comparing "rgb-from-YCbCrK.tif" and "ref/rgb-from-YCbCrK.tif" +PASS From 68977baec516643d33744b5a03726dda93a327ed Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 15 Jan 2026 10:54:47 -0800 Subject: [PATCH 090/508] docs: Remove outdated/wrong description in INSTALL.md (#5008) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- INSTALL.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 1bcc407d68..c822a01052 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -28,12 +28,10 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. through 3.1) * zlib >= 1.2.7 (tested through 1.3.1) * [fmtlib](https://github.com/fmtlib/fmt) >= 7.0 (tested through 12.0 and master). - If not found at build time, this will be automatically downloaded unless - the build sets `-DBUILD_MISSING_FMT=OFF`. + If not found at build time, this will be automatically downloaded and built. * [Robin-map](https://github.com/Tessil/robin-map) (unknown minimum, tested through 1.4, which is the recommended version). If not found at build time, - this will be automatically downloaded unless the build sets - `-DBUILD_MISSING_FMT=OFF`. + this will be automatically downloaded and built. ### Optional dependencies -- features may be disabled if not found * If you are building the `iv` viewer (which will be disabled if any of From 5cb070e015805782dc6e03cf83042abc5c00a608 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 17 Jan 2026 08:41:09 -0800 Subject: [PATCH 091/508] ci: Windows runners switched which python version they had (#5010) Yay, I love how these things just break with no notice. Fixes CI that broke a couple days agi. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 4 ++-- src/build-scripts/gh-win-installdeps.bash | 4 ++++ src/cmake/testing.cmake | 1 + testsuite/runtest.py | 4 +--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a11a36779..4406149c73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -695,13 +695,13 @@ jobs: nametag: windows-2022 vsver: 2022 generator: "Visual Studio 17 2022" - python_ver: "3.9" + python_ver: "3.12" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - desc: Windows-2025 VS2022 runner: windows-2025 nametag: windows-2025 vsver: 2022 generator: "Visual Studio 17 2022" - python_ver: "3.9" + python_ver: "3.12" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 benchmark: 1 diff --git a/src/build-scripts/gh-win-installdeps.bash b/src/build-scripts/gh-win-installdeps.bash index d4f0283853..48ad421924 100755 --- a/src/build-scripts/gh-win-installdeps.bash +++ b/src/build-scripts/gh-win-installdeps.bash @@ -32,6 +32,10 @@ elif [[ "$PYTHON_VERSION" == "3.9" ]] ; then export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.9.13/x64" export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.9.13/x64/python3.exe" export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages +elif [[ "$PYTHON_VERSION" == "3.12" ]] ; then + export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.12.10/x64" + export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.12.10/x64/python3.exe" + export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages fi pip install numpy diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 8bd975efbc..72a0cd8843 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -110,6 +110,7 @@ macro (oiio_add_tests) "OIIO_TESTSUITE_ROOT=${_testsuite}" "OIIO_TESTSUITE_SRC=${_testsrcdir}" "OIIO_TESTSUITE_CUR=${_testdir}" + "Python_EXECUTABLE=${Python3_EXECUTABLE}" ${_ats_ENVIRONMENT}) if (NOT ${_ats_testdir} STREQUAL "") set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT diff --git a/testsuite/runtest.py b/testsuite/runtest.py index 5efced462f..052b68434a 100755 --- a/testsuite/runtest.py +++ b/testsuite/runtest.py @@ -162,9 +162,7 @@ def newsymlink(src: str, dst: str): if os.getenv("Python_EXECUTABLE") : pythonbin = os.getenv("Python_EXECUTABLE") else : - pythonbin = 'python' - if os.getenv("PYTHON_VERSION") : - pythonbin += os.getenv("PYTHON_VERSION") + pythonbin = sys.executable #print ("pythonbin = ", pythonbin) From 1186ee0d4de1234ca41796a3a24bdf56ac3d7840 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 17 Jan 2026 11:10:01 -0800 Subject: [PATCH 092/508] ci: test against libraw 0.22 for 'latest' test variants (#5009) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 8 ++++---- INSTALL.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4406149c73..bb8b86f022 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -461,7 +461,7 @@ jobs: simd: avx2,f16c setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.5 + LIBRAW_VERSION=0.22.0 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.5.0 @@ -514,7 +514,7 @@ jobs: simd: avx2,f16c setenvs: export OpenImageIO_BUILD_LOCAL_DEPS=all OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON - LIBRAW_VERSION=0.21.5 + LIBRAW_VERSION=0.22.0 PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 @@ -545,7 +545,7 @@ jobs: python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.5 + LIBRAW_VERSION=0.22.0 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 @@ -566,7 +566,7 @@ jobs: python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.5 + LIBRAW_VERSION=0.22.0 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 diff --git a/INSTALL.md b/INSTALL.md index c822a01052..ee0d73eee8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -45,7 +45,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for PNG files: * libPNG >= 1.6.0 (tested though 1.6.50) * If you want support for camera "RAW" formats: - * LibRaw >= 0.20 (tested though 0.21.5 and master) + * LibRaw >= 0.20 (tested though 0.22.0 and master) * If you want support for a wide variety of video formats: * ffmpeg >= 4.0 (tested through 8.0) * If you want support for jpeg 2000 images: From 70cb418b35993bfe04dba79c5b0b01cb61e039f7 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 18 Jan 2026 09:24:10 -0800 Subject: [PATCH 093/508] fix: several bug fixes related to internal use of image_span (#5004) * ImageBuf internal buffer span lacked correct chansize. The internal `m_bufspan` is an `image_span`, and as such, it needs to remember the size of the original data type. Otherwise, there's a cascade of potential errors when it thinks that the individual values are byte sized. * In both ImageInput and ImageOutput, several sanity checks of image_span size versus expectations were incorrect. They were only checking if the total byte sizes matched expectations, but they are allowed to disagree when you consider type conversions (in which case, it's the total number of values that need to match, not the total byte sizes. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/imagebuf.cpp | 9 +-- src/libOpenImageIO/imageinput.cpp | 89 +++++++++++++++--------------- src/libOpenImageIO/imageoutput.cpp | 67 +++++++++++++--------- 3 files changed, 90 insertions(+), 75 deletions(-) diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index 8a5ed32e0a..403a22c545 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -150,10 +150,11 @@ class ImageBufImpl { stride_t ystride = AutoStride, stride_t zstride = AutoStride) { - m_bufspan = image_span(reinterpret_cast(data), - m_spec.nchannels, m_spec.width, m_spec.height, - m_spec.depth, m_spec.format.size(), xstride, - ystride, zstride); + auto formatsize = m_spec.format.size(); + m_bufspan = image_span(reinterpret_cast(data), + m_spec.nchannels, m_spec.width, m_spec.height, + m_spec.depth, formatsize, xstride, ystride, + zstride, formatsize); } bool init_spec(string_view filename, int subimage, int miplevel, diff --git a/src/libOpenImageIO/imageinput.cpp b/src/libOpenImageIO/imageinput.cpp index 03af1297c5..3ff8f1a255 100644 --- a/src/libOpenImageIO/imageinput.cpp +++ b/src/libOpenImageIO/imageinput.cpp @@ -211,6 +211,40 @@ ImageInput::spec_dimensions(int subimage, int miplevel) +// Utility: Make sure the provided data span is the right size for the +// image described by spec and datatype. If they don't match, issue an +// error and return false. +static bool +check_span_size(ImageInput* in, string_view caller, const ImageSpec& spec, + TypeDesc datatype, imagesize_t npixels, int chbegin, int chend, + const image_span& data) +{ + // One of two things must be correct: Either format is Unknown and the + // total byte size needs to match the "native" size, or the format is + // concrete and the number of value must match (it's ok if the size + // doesn't match, since a data type conversion will occur). + if (datatype.is_unknown()) { // Unknown assumes native chan types + size_t sz = npixels * spec.pixel_bytes(chbegin, chend, true); + if (sz != data.size_bytes()) { + in->errorfmt( + "{}: image_span size is incorrect ({} bytes vs {} needed)", + caller, data.size_bytes(), sz); + return false; + } + } else { // single concrete type + size_t nvals = npixels * size_t(chend - chbegin); + if (nvals != data.nvalues()) { + in->errorfmt( + "{}: image_span size is incorrect ({} values vs {} needed)", + caller, data.nvalues(), nvals); + return false; + } + } + return true; +} + + + bool ImageInput::read_scanline(int y, int z, TypeDesc format, void* data, stride_t xstride) @@ -300,16 +334,10 @@ ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend, chend); return false; } - size_t isize = (format == TypeUnknown - ? spec.pixel_bytes(chbegin, chend, true /*native*/) - : format.size() * (chend - chbegin)) - * size_t(spec.width); - if (isize != data.size_bytes()) { - errorfmt( - "read_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", - isize, data.size_bytes()); + if (!check_span_size(this, "read_scanlines", m_spec, format, + m_spec.width * size_t(yend - ybegin), chbegin, chend, + data)) return false; - } // Default implementation (for now): call the old pointer+stride return read_scanlines(subimage, miplevel, ybegin, yend, 0, chbegin, chend, @@ -656,16 +684,11 @@ ImageInput::read_tiles(int subimage, int miplevel, int xbegin, int xend, errorfmt("read_tiles: invalid channel range [{},{})", chbegin, chend); return false; } - size_t isize = (format == TypeUnknown - ? spec.pixel_bytes(chbegin, chend, true /*native*/) - : format.size() * (chend - chbegin)) - * size_t(xend - xbegin) * size_t(yend - ybegin) - * size_t(zend - zbegin); - if (isize != data.size_bytes()) { - errorfmt("read_tiles: Buffer size is incorrect ({} bytes vs {} needed)", - isize, data.size_bytes()); + if (!check_span_size(this, "read_tiles", m_spec, format, + size_t(xend - xbegin) * size_t(yend - ybegin) + * size_t(zend - zbegin), + chbegin, chend, data)) return false; - } // Default implementation (for now): call the old pointer+stride return read_tiles(subimage, miplevel, ybegin, yend, xbegin, xend, zbegin, @@ -1164,24 +1187,6 @@ bool ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, TypeDesc format, const image_span& data) { -#if 0 - ImageSpec spec = spec_dimensions(subimage, miplevel); - if (chend < 0 || chend > spec.nchannels) - chend = spec.nchannels; - size_t isize = (format == TypeUnknown - ? spec.pixel_bytes(chbegin, chend, true /*native*/) - : format.size() * (chend - chbegin)) - * spec.image_pixels(); - if (isize != data.size_bytes()) { - errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); - return false; - } - - // Default implementation (for now): call the old pointer+stride - return read_image(subimage, miplevel, chbegin, chend, format, data.data(), - data.xstride(), data.ystride(), data.zstride()); -#else OIIO::pvt::LoggedTimer logtime("II::read_image"); ImageSpec spec; int rps = 0; @@ -1210,16 +1215,9 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, errorfmt("read_image: invalid channel range [{},{})", chbegin, chend); return false; } - int nchans = chend - chbegin; - bool native = (format == TypeUnknown); - size_t pixel_bytes = native ? spec.pixel_bytes(chbegin, chend, native) - : (format.size() * nchans); - size_t isize = pixel_bytes * spec.image_pixels(); - if (isize != data.size_bytes()) { - errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)", - isize, data.size_bytes()); + if (!check_span_size(this, "read_image", m_spec, format, + spec.image_pixels(), chbegin, chend, data)) return false; - } bool ok = true; if (spec.tile_width) { // Tiled image -- rely on read_tiles @@ -1259,7 +1257,6 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, } } return ok; -#endif } diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 1b9edaede5..216c2a18f9 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -102,6 +102,40 @@ ImageOutput::~ImageOutput() +// Utility: Make sure the provided data span is the right size for the +// image described by spec and datatype. If they don't match, issue an +// error and return false. +static bool +check_span_size(ImageOutput* out, string_view caller, const ImageSpec& spec, + TypeDesc datatype, imagesize_t npixels, + const image_span& data) +{ + // One of two things must be correct: Either format is Unknown and the + // total byte size needs to match the "native" size, or the format is + // concrete and the number of value must match (it's ok if the size + // doesn't match, since a data type conversion will occur). + if (datatype.is_unknown()) { // Unknown assumes native chan types + size_t sz = npixels * spec.pixel_bytes(true); + if (sz != data.size_bytes()) { + out->errorfmt( + "{}: image_span size is incorrect ({} bytes vs {} needed)", + caller, data.size_bytes(), sz); + return false; + } + } else { // single concrete type + size_t nvals = npixels * size_t(spec.nchannels); + if (nvals != data.nvalues()) { + out->errorfmt( + "{}: image_span size is incorrect ({} values vs {} needed)", + caller, data.nvalues(), nvals); + return false; + } + } + return true; +} + + + bool ImageOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc /*format*/, const void* /*data*/, stride_t /*xstride*/) @@ -120,13 +154,9 @@ ImageOutput::write_scanline(int y, TypeDesc format, errorfmt("write_scanlines: Invalid scanline index {}", y); return false; } - size_t sz = m_spec.scanline_bytes(format); - if (sz != data.size_bytes()) { - errorfmt( - "write_scanline: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_scanline", m_spec, format, m_spec.width, + data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_scanline(y, 0, format, data.data(), data.xstride()); @@ -164,13 +194,9 @@ ImageOutput::write_scanlines(int ybegin, int yend, TypeDesc format, errorfmt("write_scanlines: Invalid scanline range {}-{}", ybegin, yend); return false; } - size_t sz = m_spec.scanline_bytes(format) * size_t(yend - ybegin); - if (sz != data.size_bytes()) { - errorfmt( - "write_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_scanlines", m_spec, format, + m_spec.width * size_t(yend - ybegin), data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_scanlines(ybegin, yend, 0, format, data.data(), data.xstride(), @@ -194,15 +220,9 @@ bool ImageOutput::write_tile(int x, int y, int z, TypeDesc format, const image_span& data) { - size_t sz = format == TypeUnknown - ? m_spec.pixel_bytes(true /*native*/) - : m_spec.tile_pixels() * size_t(m_spec.nchannels) - * format.size(); - if (sz != data.size_bytes()) { - errorfmt("write_tile: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_tile", m_spec, format, + m_spec.tile_pixels(), data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_tile(x, y, z, format, data.data(), data.xstride(), @@ -691,12 +711,9 @@ bool ImageOutput::write_image(TypeDesc format, const image_span& data) { - size_t sz = m_spec.image_bytes(/*native=*/format == TypeUnknown); - if (sz != data.size_bytes()) { - errorfmt("write_image: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_image", m_spec, format, + m_spec.image_pixels(), data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_image(format, data.data(), data.xstride(), data.ystride(), From 254a528d263a6fef89cb5b897f0f529efaba66e0 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 20 Jan 2026 17:29:48 -0800 Subject: [PATCH 094/508] build: Use libheif exported config if available (#5012) Needed to change the Libheif::Libheif target we set up ages ago to the "heif" name that the package's exported config uses. Also, need to give the warning about static libraries occur any time we are using static libheif, even if we didn't set LIBSTATIC to try to force all static libraries. --------- Signed-off-by: Larry Gritz Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/externalpackages.cmake | 1 + src/cmake/modules/FindLibheif.cmake | 10 ++++---- src/heif.imageio/CMakeLists.txt | 38 +++++++++++++---------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 7c57764a04..73a07172f6 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -163,6 +163,7 @@ checked_find_package (GIF VERSION_MIN 5.0) # For HEIF/HEIC/AVIF formats checked_find_package (Libheif VERSION_MIN 1.11 + PREFER_CONFIG RECOMMEND_MIN 1.16 RECOMMEND_MIN_REASON "for orientation support") diff --git a/src/cmake/modules/FindLibheif.cmake b/src/cmake/modules/FindLibheif.cmake index 5d061af6ea..61c93feac7 100644 --- a/src/cmake/modules/FindLibheif.cmake +++ b/src/cmake/modules/FindLibheif.cmake @@ -27,7 +27,7 @@ find_library (LIBHEIF_LIBRARY heif HINTS ${LIBHEIF_LIBRARY_PATH} ENV LIBHEIF_LIBRARY_PATH - DOC "The directory where libheif libraries reside") + DOC "The found libheif library") if (LIBHEIF_INCLUDE_DIR) file(STRINGS "${LIBHEIF_INCLUDE_DIR}/libheif/heif_version.h" TMP REGEX "^#define LIBHEIF_VERSION[ \t].*$") @@ -44,11 +44,11 @@ if (Libheif_FOUND) set(LIBHEIF_INCLUDES "${LIBHEIF_INCLUDE_DIR}") set(LIBHEIF_LIBRARIES "${LIBHEIF_LIBRARY}") - if (NOT TARGET Libheif::Libheif) - add_library(Libheif::Libheif UNKNOWN IMPORTED) - set_target_properties(Libheif::Libheif PROPERTIES + if (NOT TARGET heif) + add_library(heif UNKNOWN IMPORTED) + set_target_properties(heif PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBHEIF_INCLUDES}") - set_property(TARGET Libheif::Libheif APPEND PROPERTY + set_property(TARGET heif APPEND PROPERTY IMPORTED_LOCATION "${LIBHEIF_LIBRARIES}") endif () endif() diff --git a/src/heif.imageio/CMakeLists.txt b/src/heif.imageio/CMakeLists.txt index 25606a1391..c79b544035 100644 --- a/src/heif.imageio/CMakeLists.txt +++ b/src/heif.imageio/CMakeLists.txt @@ -3,31 +3,27 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO if (Libheif_FOUND) - if (LINKSTATIC) - set (_static_suffixes .lib .a) - set (_static_libraries_found 0) - - foreach (_libeheif_library IN LISTS LIBHEIF_LIBRARIES) - get_filename_component (_ext ${_libeheif_library} LAST_EXT) - list (FIND _static_suffixes ${_ext} _index) - if (${_index} GREATER -1) - MATH (EXPR _static_libraries_found "${static_libraries_found}+1") - endif() - endforeach() - - if (${_static_libraries_found} GREATER 0) - message (STATUS "${ColorYellow}") - message (STATUS "You are linking OpenImageIO against a static version of libheif, which is LGPL") - message (STATUS "licensed. If you intend to redistribute this build of OpenImageIO, we recommend") - message (STATUS "that you review the libheif license terms, or you may wish to switch to using a") - message (STATUS "dynamically-linked libheif.") - message ("${ColorReset}") + # Some extra care is needed if the libheif we found was static + set (_static_suffixes .lib .a) + set (_static_libraries_found 0) + foreach (_libeheif_library IN LISTS LIBHEIF_LIBRARIES) + get_filename_component (_ext ${_libeheif_library} LAST_EXT) + list (FIND _static_suffixes ${_ext} _index) + if (${_index} GREATER -1) + MATH (EXPR _static_libraries_found "${_static_libraries_found}+1") endif() + endforeach() + if (${_static_libraries_found} GREATER 0) + message (STATUS "${ColorYellow}") + message (STATUS "You are linking OpenImageIO against a static version of libheif, which is LGPL") + message (STATUS "licensed. If you intend to redistribute this build of OpenImageIO, we recommend") + message (STATUS "that you review the libheif license terms, or you may wish to switch to using a") + message (STATUS "dynamically-linked libheif.") + message ("${ColorReset}") endif() add_oiio_plugin (heifinput.cpp heifoutput.cpp - INCLUDE_DIRS ${LIBHEIF_INCLUDES} - LINK_LIBRARIES ${LIBHEIF_LIBRARIES} + LINK_LIBRARIES heif DEFINITIONS "USE_HEIF=1") else () message (WARNING "heif plugin will not be built") From 0e0e3d053cb36604d4f8cbb9eec1c75c40f98eb8 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 22 Jan 2026 17:19:22 -0800 Subject: [PATCH 095/508] build: fully disable tests when their required dependencies are missing (#5005) We previously merely *marked* tests as "broken" in this case, and depended on ctest being launched with `-E broken` to ensure those were skipped. Not everybody did. Let's just truly skip them. Fixes #4979 Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- Makefile | 2 +- src/cmake/testing.cmake | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 830e39a208..115a56eb68 100644 --- a/Makefile +++ b/Makefile @@ -293,7 +293,7 @@ test: build @ ${CMAKE} -E cmake_echo_color --switch=$(COLOR) --cyan "Running tests ${TEST_FLAGS}..." @ ( cd ${build_dir} ; \ PYTHONPATH=${PWD}/${build_dir}/lib/python/site-packages \ - ctest -E broken ${TEST_FLAGS} \ + ctest ${TEST_FLAGS} \ ) @ ( if [[ "${CODECOV}" == "1" ]] ; then \ cd ${build_dir} ; \ diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 72a0cd8843..bfa588ca7b 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -47,7 +47,7 @@ set(OIIO_TESTSUITE_IMAGEDIR "${PROJECT_BINARY_DIR}/testsuite" CACHE PATH # # The optional SUFFIX is appended to the test name. # -# The optinonal ENVIRONMENT is a list of environment variables to set for the +# The optional ENVIRONMENT is a list of environment variables to set for the # test. # macro (oiio_add_tests) @@ -56,9 +56,12 @@ macro (oiio_add_tests) set (_ats_testdir "${OIIO_TESTSUITE_IMAGEDIR}/${_ats_IMAGEDIR}") # If there was a FOUNDVAR param specified and that variable name is # not defined, mark the test as broken. + set (_test_disabled FALSE) + set (_test_notfound FALSE) foreach (_var ${_ats_FOUNDVAR}) if (NOT ${_var}) set (_ats_LABEL "broken") + set (_test_notfound TRUE) endif () endforeach () set (_test_disabled 0) @@ -66,7 +69,7 @@ macro (oiio_add_tests) if ((NOT "${${_var}}" STREQUAL "" AND NOT "${${_var}}") OR (NOT "$ENV{${_var}}" STREQUAL "" AND NOT "$ENV{${_var}}")) set (_ats_LABEL "broken") - set (_test_disabled 1) + set (_test_disabled TRUE) endif () endforeach () # For OCIO 2.2+, have the testsuite use the default built-in config @@ -74,6 +77,8 @@ macro (oiio_add_tests) "OIIO_TESTSUITE_OCIOCONFIG=ocio://default") if (_test_disabled) message (STATUS "Skipping test(s) ${_ats_UNPARSED_ARGUMENTS} because of disabled ${_ats_ENABLEVAR}") + elseif (_test_notfound) + message (STATUS "Skipping test(s) ${_ats_UNPARSED_ARGUMENTS} because of missing dependency from ${_ats_FOUNDVAR}") elseif (_ats_IMAGEDIR AND NOT EXISTS ${_ats_testdir}) # If the directory containing reference data (images) for the test # isn't found, point the user at the URL. From abbe13e27b5d499e49e66fae2cb70ed8e9870702 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 23 Jan 2026 20:37:58 +0100 Subject: [PATCH 096/508] fix(heif): Can not output AVIF when libheif has no HEVC support (#5013) Initializing the encoder to `heif_compression_HEVC` by default throws an exception when libheif was built without HEVC support, which prevents it from being used entirely even if there is AVIF support. So delay that initialization until we are sure which encoding we want. Not really any practical way to automatically test this. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/heif.imageio/heifoutput.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 9998ab5a5f..309bc53ac5 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -49,7 +49,9 @@ class HeifOutput final : public ImageOutput { std::unique_ptr m_ctx; heif::ImageHandle m_ihandle; heif::Image m_himage; - heif::Encoder m_encoder { heif_compression_HEVC }; + // Undefined until we know the specific requested encoder, because an + // exception is thrown if libheif is built without support for it. + heif::Encoder m_encoder { heif_compression_undefined }; std::vector scratch; std::vector m_tilebuffer; int m_bitdepth = 0; @@ -140,12 +142,13 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, m_himage.add_plane(heif_channel_interleaved, newspec.width, newspec.height, m_bitdepth); - m_encoder = heif::Encoder(heif_compression_HEVC); auto compqual = m_spec.decode_compression_metadata("", 75); auto extension = Filesystem::extension(m_filename); if (compqual.first == "avif" || (extension == ".avif" && compqual.first == "")) { m_encoder = heif::Encoder(heif_compression_AV1); + } else { + m_encoder = heif::Encoder(heif_compression_HEVC); } } catch (const heif::Error& err) { std::string e = err.get_message(); From 04485afe8c3db71f462d29919c1c95c8a223ba7b Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Tue, 27 Jan 2026 20:24:47 -0800 Subject: [PATCH 097/508] fix(webp): Use correct resolution limits for WebpOutput::open (#5016) The WebP format is very limited in the maximum resolution it supports. We need to change the values we allow to be much smaller. Signed-off-by: Jesse Yurkovich Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/webp.imageio/webpoutput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 2c2acb85cc..784575963e 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -73,7 +73,7 @@ WebpImageWriter(const uint8_t* img_data, size_t data_size, bool WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) { - if (!check_open(mode, spec, { 0, 1 << 20, 0, 1 << 20, 0, 1, 0, 4 }, + if (!check_open(mode, spec, { 0, 16383, 0, 16383, 0, 1, 0, 4 }, uint64_t(OpenChecks::Disallow1or2Channel))) return false; From fd1c7ae501f6cbc45bf19d4a60fbfa143a985328 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 28 Jan 2026 04:09:23 -0800 Subject: [PATCH 098/508] api: IBA::make_texture now honors "maketx:threads" hint (#5014) Nearly all IBA functions take an optional parameter controlling the threading. But make_texture() did not, so there was no way to control its thread usage (it would always use the default number of threads). Without changing the call signature or ABI, this patch merely makes make_texture honor any "maketx:threads" hint passed in the config ImageSpec that it already takes to convey all sorts of controls over the texture creation process. Then this value is passed to any IBA functions, use of parallel_image, and anything else in the implementation of make_texture that would end up using the thread pool. Fixes #4254 --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/imagebufalgo.h | 1 + src/libOpenImageIO/maketexture.cpp | 112 ++++++++++++++----------- 2 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index 4e9c80fcdc..4bce5c1b6e 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -2247,6 +2247,7 @@ enum MakeTextureMode { /// the coordinates for normal maps. ("") /// - `maketx:verbose` (int) : How much detail should go to outstream (0). /// - `maketx:runstats` (int) : If nonzero, print run stats to outstream (0). +/// - `maketx:threads` (int) : Number of threads to use (0 = auto). /// - `maketx:resize` (int) : If nonzero, resize to power of 2. (0) /// - `maketx:keepaspect` (int): If nonzero, save aspect ratio to metadata. (0) /// - `maketx:nomipmap` (int) : If nonzero, only output the top MIP level (0). diff --git a/src/libOpenImageIO/maketexture.cpp b/src/libOpenImageIO/maketexture.cpp index ca38206523..533cf7dc61 100644 --- a/src/libOpenImageIO/maketexture.cpp +++ b/src/libOpenImageIO/maketexture.cpp @@ -510,12 +510,11 @@ bump_to_bumpslopes(ImageBuf& dst, const ImageBuf& src, return false; } is_height = false; - } else if (Strutil::iequals( - bumpformat, - "auto")) { // guess input bump format by analyzing channel count and component + } else if (Strutil::iequals(bumpformat, "auto")) { + // guess input bump format by analyzing channel count and component if (src.spec().nchannels > 2 - && !ImageBufAlgo::isMonochrome(src)) // maybe it's a normal map? - is_height = false; + && !ImageBufAlgo::isMonochrome(src, 0.0f, ROI(), nthreads)) + is_height = false; // maybe it's a normal map? else is_height = true; } else { @@ -705,6 +704,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, ImageSpec outspec = outspec_template; outspec.set_format(outputdatatype); + int nthreads = configspec.get_int_attribute("maketx:threads"); + // Going from float to half is prone to generating Inf values if we had // any floats that were out of the range that half can represent. Nobody // wants Inf in textures; better to clamp. @@ -783,7 +784,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, if (clamp_half) { std::shared_ptr tmp(new ImageBuf); - ImageBufAlgo::clamp(*tmp, *img, -HALF_MAX, HALF_MAX, true); + ImageBufAlgo::clamp(*tmp, *img, -HALF_MAX, HALF_MAX, true, ROI(), + nthreads); std::swap(tmp, img); } if (!img->write(out)) { @@ -831,7 +833,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, std::shared_ptr t(new ImageBuf(smallspec)); ImageBufAlgo::channels(*t, *small, outspec.nchannels, cspan(), cspan(), - cspan(), true); + cspan(), true, + nthreads); std::swap(t, small); } smallspec.tile_width = outspec.tile_width; @@ -863,7 +866,7 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, // and pixel windows match. Don't worry, the texture // engine doesn't care what the upper MIP levels have // for the window sizes, it uses level 0 to determine - // the relatinship between texture 0-1 space (display + // the relationship between texture 0-1 space (display // window) and the pixels. smallspec.x = 0; smallspec.y = 0; @@ -875,12 +878,11 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, if (filtername == "box" && !orig_was_overscan && sharpen <= 0.0f) { - ImageBufAlgo::parallel_image(get_roi(small->spec()), - std::bind(resize_block, - std::ref(*small), - std::cref(*img), _1, - envlatlmode, - allow_shift)); + ImageBufAlgo::parallel_image( + get_roi(small->spec()), paropt(nthreads), [&](ROI roi) { + resize_block(*small, *img, roi, envlatlmode, + allow_shift); + }); } else { Filter2D* filter = setup_filter(small->spec(), img->spec(), filtername); @@ -901,38 +903,44 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, OIIO::print(outstream, "\n"); } if (do_highlight_compensation) - ImageBufAlgo::rangecompress(*img, *img); + ImageBufAlgo::rangecompress(*img, *img, false, ROI(), + nthreads); if (sharpen > 0.0f && sharpen_first) { std::shared_ptr sharp(new ImageBuf); bool uok = ImageBufAlgo::unsharp_mask(*sharp, *img, - sharpenfilt, 3.0, - sharpen, 0.0f); + sharpenfilt, 3.0f, + sharpen, 0.0f, + ROI(), nthreads); if (!uok) errorfmt("{}", sharp->geterror()); std::swap(img, sharp); } ImageBufAlgo::resize(*small, *img, - { make_pv("filterptr", filter) }); + { make_pv("filterptr", filter) }, + ROI(), nthreads); if (sharpen > 0.0f && !sharpen_first) { std::shared_ptr sharp(new ImageBuf); bool uok = ImageBufAlgo::unsharp_mask(*sharp, *small, - sharpenfilt, 3.0, - sharpen, 0.0f); + sharpenfilt, 3.0f, + sharpen, 0.0f, + ROI(), nthreads); if (!uok) errorfmt("{}", sharp->geterror()); std::swap(small, sharp); } if (do_highlight_compensation) { - ImageBufAlgo::rangeexpand(*small, *small); + ImageBufAlgo::rangeexpand(*small, *small, false, ROI(), + nthreads); ImageBufAlgo::clamp(*small, *small, 0.0f, std::numeric_limits::max(), - true); + true, ROI(), nthreads); } Filter2D::destroy(filter); } } if (clamp_half) - ImageBufAlgo::clamp(*small, *small, -HALF_MAX, HALF_MAX, true); + ImageBufAlgo::clamp(*small, *small, -HALF_MAX, HALF_MAX, true, + ROI(), nthreads); double this_miptime = miptimer(); stat_miptime += this_miptime; @@ -1093,6 +1101,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, if (!configspec.tile_depth) configspec.tile_depth = 1; + int nthreads = configspec.get_int_attribute("maketx:threads"); + bool ignore_unassoc = configspec.get_int_attribute("maketx:ignore_unassoc"); ImageSpec inconfig; if (ignore_unassoc) @@ -1257,8 +1267,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, bool ok = true; OIIO_DISPATCH_COMMON_TYPES(ok, "lightprobe_to_envlatl", lightprobe_to_envlatl, src->spec().format, - *latlong, *src, true); - // lightprobe_to_envlatl(*latlong, *src, true); + *latlong, *src, true, ROI(), nthreads); // Carry on with the lat-long environment map from here on out mode = ImageBufAlgo::MakeTxEnvLatl; src = latlong; @@ -1269,7 +1278,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, if (Strutil::iequals(configspec.get_string_attribute("maketx:bumprange", "auto"), "auto")) - src_pixel_stats = ImageBufAlgo::computePixelStats(*src); + src_pixel_stats = ImageBufAlgo::computePixelStats(*src, ROI(), + nthreads); ImageSpec newspec = src->spec(); newspec.tile_width = newspec.tile_height = 0; @@ -1286,7 +1296,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, bool ok; OIIO_DISPATCH_COMMON_TYPES(ok, "bump_to_bumpslopes", bump_to_bumpslopes, src->spec().format, *bumpslopes, *src, - configspec, src_pixel_stats, outstream); + configspec, src_pixel_stats, outstream, + ROI(), nthreads); // bump_to_bumpslopes(*bumpslopes, *src); mode = ImageBufAlgo::MakeTxTexture; src = bumpslopes; @@ -1330,7 +1341,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, std::vector hist; for (int i = 0; i < channels; i++) { - hist = ImageBufAlgo::histogram(*src, i, bins, 0.0f, 1.0f); + hist = ImageBufAlgo::histogram(*src, i, bins, 0.0f, 1.0f, false, + ROI(), nthreads); // Turn the histogram into a non-normalized CDF for (uint64_t j = 1; j < bins; j++) { @@ -1389,7 +1401,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, bool compute_stats = (constant_color_detect || opaque_detect || compute_average_color || monochrome_detect); if (compute_stats) { - pixel_stats = ImageBufAlgo::computePixelStats(*src); + pixel_stats = ImageBufAlgo::computePixelStats(*src, ROI(), nthreads); } double stat_pixelstatstime = alltime.lap(); STATUS("pixelstats", stat_pixelstatstime); @@ -1420,7 +1432,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, newspec.full_height = newspec.height; newspec.full_depth = newspec.depth; src->reset(newspec); - ImageBufAlgo::fill(*src, constantColor); + ImageBufAlgo::fill(*src, constantColor, ROI(), nthreads); if (verbose) { outstream << " Constant color image detected. "; outstream << "Creating " << newspec.width << "x" @@ -1441,7 +1453,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, std::shared_ptr newsrc(new ImageBuf(src->spec())); ImageBufAlgo::channels(*newsrc, *src, src->nchannels() - 1, cspan(), cspan(), - cspan(), true); + cspan(), true, nthreads); std::swap(src, newsrc); // N.B. the old src will delete } @@ -1455,14 +1467,14 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, && src->spec().alpha_channel < 0 && pixel_stats.avg[0] == pixel_stats.avg[1] && pixel_stats.avg[0] == pixel_stats.avg[2] - && ImageBufAlgo::isMonochrome(*src)) { + && ImageBufAlgo::isMonochrome(*src, 0.0f, ROI(), nthreads)) { if (verbose) OIIO::print( outstream, " Monochrome image detected. Converting to single channel texture.\n"); std::shared_ptr newsrc(new ImageBuf(src->spec())); ImageBufAlgo::channels(*newsrc, *src, 1, cspan(), cspan(), - cspan(), true); + cspan(), true, nthreads); newsrc->specmod().default_channel_names(); std::swap(src, newsrc); } @@ -1475,7 +1487,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, << std::endl; std::shared_ptr newsrc(new ImageBuf(src->spec())); ImageBufAlgo::channels(*newsrc, *src, nchannels, cspan(), - cspan(), cspan(), true); + cspan(), cspan(), true, + nthreads); std::swap(src, newsrc); } @@ -1663,7 +1676,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, && (srcspec.format.basetype == TypeDesc::FLOAT || srcspec.format.basetype == TypeDesc::HALF || srcspec.format.basetype == TypeDesc::DOUBLE) - && !ImageBufAlgo::fixNonFinite(*src, *src, fixmode, &pixelsFixed)) { + && !ImageBufAlgo::fixNonFinite(*src, *src, fixmode, &pixelsFixed, ROI(), + nthreads)) { errorfmt("Error fixing nans/infs."); return false; } @@ -1677,7 +1691,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, || srcspec.format.basetype == TypeDesc::HALF || srcspec.format.basetype == TypeDesc::DOUBLE)) { int found_nonfinite = 0; - ImageBufAlgo::parallel_image(get_roi(srcspec), + ImageBufAlgo::parallel_image(get_roi(srcspec), nthreads, std::bind(check_nan_block, std::ref(*src), _1, std::ref(found_nonfinite))); if (found_nonfinite) { @@ -1736,7 +1750,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, outstream << " Unpremulting image..." << std::endl; if (!ImageBufAlgo::colorconvert(*ccSrc, *src, processor.get(), - unpremult)) { + unpremult, ROI(), nthreads)) { errorfmt("Error applying color conversion to image."); return false; } @@ -1838,10 +1852,12 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, toplevel.reset(new ImageBuf(dstspec)); if ((resize_filter == "box" || resize_filter == "triangle") && !orig_was_overscan) { - ImageBufAlgo::parallel_image( - get_roi(dstspec), - std::bind(resize_block, std::ref(*toplevel), std::cref(*src), - _1, envlatlmode, allow_shift != 0)); + ImageBufAlgo::parallel_image(get_roi(dstspec), nthreads, + [&](ROI roi) { + resize_block(*toplevel, *src, roi, + envlatlmode, + allow_shift != 0); + }); } else { Filter2D* filter = setup_filter(toplevel->spec(), src->spec(), resize_filter); @@ -1850,7 +1866,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, return false; } ImageBufAlgo::resize(*toplevel, *src, - { make_pv("filterptr", filter) }); + { make_pv("filterptr", filter) }, ROI(), + nthreads); Filter2D::destroy(filter); } } @@ -1906,12 +1923,11 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, addlHashData << "keepaspect=1 "; const int sha1_blocksize = 256; - std::string hash_digest - = configspec.get_int_attribute("maketx:hash", 1) - ? ImageBufAlgo::computePixelHashSHA1(*toplevel, - addlHashData.str(), - ROI::All(), sha1_blocksize) - : ""; + std::string hash_digest = configspec.get_int_attribute("maketx:hash", 1) + ? ImageBufAlgo::computePixelHashSHA1( + *toplevel, addlHashData.str(), ROI::All(), + sha1_blocksize, nthreads) + : ""; if (hash_digest.length()) { if (out->supports("arbitrary_metadata")) { dstspec.attribute("oiio:SHA-1", hash_digest); From 3eb90a053a8d91eb8030627b18d5582152b2402f Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 28 Jan 2026 07:54:22 -0800 Subject: [PATCH 099/508] api(ImageBuf): IB::localpixels_as_[writable_]byte_image_span (#5011) Utilities to ask the IB for the local pixels as an untyped span of bytes. This is a "safe" alternative to `localpixels()`, which just returned a single pointer, instead returning an image_span that understands the sizes and strides of the buffer. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/imagebuf.h | 10 ++++++++++ src/libOpenImageIO/imagebuf.cpp | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/include/OpenImageIO/imagebuf.h b/src/include/OpenImageIO/imagebuf.h index adc6709d48..eb0227688d 100644 --- a/src/include/OpenImageIO/imagebuf.h +++ b/src/include/OpenImageIO/imagebuf.h @@ -1369,6 +1369,16 @@ class OIIO_API ImageBuf { void* localpixels(); const void* localpixels() const; + /// Return an `image_span` giving the extent and layout + /// of "local" pixel memory, if they are fully in RAM and not backed by an + /// ImageCache, or an empty span otherwise. + image_span localpixels_as_byte_image_span() const; + + /// Return an `image_span` giving the extent and layout of + /// "local" pixel memory, if they are fully in RAM and not backed by an + /// ImageCache, and it is a writable IB, or an empty span otherwise. + image_span localpixels_as_writable_byte_image_span(); + /// Pixel-to-pixel stride within the localpixels memory. stride_t pixel_stride() const; /// Scanline-to-scanline stride within the localpixels memory. diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index 403a22c545..fdf14fe447 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -2140,6 +2140,22 @@ ImageBuf::localpixels() +image_span +ImageBuf::localpixels_as_byte_image_span() const +{ + return m_impl->m_bufspan; +} + + + +image_span +ImageBuf::localpixels_as_writable_byte_image_span() +{ + return m_impl->m_readonly ? image_span() : m_impl->m_bufspan; +} + + + const void* ImageBuf::localpixels() const { From ebce661de4e85efbb9f04e0cf0ab2c89780f1648 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 28 Jan 2026 19:13:00 +0100 Subject: [PATCH 100/508] fix(heif): Error saving multiple images with different bit depths (#5018) This variable should not have been static. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/heif.imageio/heifoutput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 309bc53ac5..315f6b1a60 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -128,7 +128,7 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, try { m_ctx.reset(new heif::Context); m_himage = heif::Image(); - static heif_chroma chromas[/*nchannels*/] + const heif_chroma chromas[/*nchannels*/] = { heif_chroma_undefined, heif_chroma_monochrome, heif_chroma_undefined, (m_bitdepth == 8) ? heif_chroma_interleaved_RGB From 80f9afecf5dd88192834c6addb8ede0ee7a41c28 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 28 Jan 2026 19:17:32 +0100 Subject: [PATCH 101/508] feat(heif): Add IOProxy for input and output (#5017) Add IOProxy support similar to other file formats. MyHeifWriter was renamed for consistency with other code. All input and output now goes through the proxy, so this is covered by existing tests. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/builtinplugins.rst | 8 ++++++ src/heif.imageio/heifinput.cpp | 50 ++++++++++++++++++++++++++++----- src/heif.imageio/heifoutput.cpp | 37 +++++++++++------------- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index f6eea37efd..05c66fe352 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -806,6 +806,10 @@ attributes are supported: having Orientation 1). If zero, then libheif will not reorient the image and the Orientation metadata will be set to reflect the camera orientation. + * - ``oiio:ioproxy`` + - ptr + - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for + example by reading from memory rather than the file system. **Configuration settings for HEIF output** @@ -824,6 +828,10 @@ control aspects of the writing itself: - If supplied, can be ``"heic"`` or ``"avif"``, but may optionally have a quality value appended, like ``"heic:90"``. Quality can be 1-100, with 100 meaning lossless. The default is 75. + * - ``oiio:ioproxy`` + - ptr + - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for + example by writing to memory rather than the file system. diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp index 9e0230a86c..f4b78aae65 100644 --- a/src/heif.imageio/heifinput.cpp +++ b/src/heif.imageio/heifinput.cpp @@ -32,6 +32,35 @@ OIIO_PLUGIN_NAMESPACE_BEGIN + +class HeifReader final : public heif::Context::Reader { +public: + HeifReader(Filesystem::IOProxy* ioproxy) + : m_ioproxy(ioproxy) + { + m_ioproxy->seek(0); + } + int64_t get_position() const override { return m_ioproxy->tell(); } + int read(void* data, size_t size) override + { + return m_ioproxy->read(data, size) == size ? 0 : -1; + } + int seek(int64_t position) override + { + return m_ioproxy->seek(position) ? 0 : -1; + } + heif_reader_grow_status wait_for_file_size(int64_t target_size) override + { + return target_size <= int64_t(m_ioproxy->size()) + ? heif_reader_grow_status_size_reached + : heif_reader_grow_status_size_beyond_eof; + } + +private: + Filesystem::IOProxy* m_ioproxy; +}; + + class HeifInput final : public ImageInput { public: HeifInput() {} @@ -39,13 +68,13 @@ class HeifInput final : public ImageInput { const char* format_name(void) const override { return "heif"; } int supports(string_view feature) const override { - return feature == "exif" + return feature == "exif" || feature == "ioproxy" #if LIBHEIF_HAVE_VERSION(1, 9, 0) || feature == "cicp" #endif ; } - bool valid_file(const std::string& filename) const override; + bool valid_file(Filesystem::IOProxy* ioproxy) const override; bool open(const std::string& name, ImageSpec& newspec) override; bool open(const std::string& name, ImageSpec& newspec, const ImageSpec& config) override; @@ -67,6 +96,7 @@ class HeifInput final : public ImageInput { bool m_do_associate = false; bool m_reorient = true; std::unique_ptr m_ctx; + std::unique_ptr m_reader; heif_item_id m_primary_id; // id of primary image std::vector m_item_ids; // ids of all other images heif::ImageHandle m_ihandle; @@ -74,7 +104,6 @@ class HeifInput final : public ImageInput { }; - void oiio_heif_init() { @@ -111,10 +140,12 @@ OIIO_PLUGIN_EXPORTS_END bool -HeifInput::valid_file(const std::string& filename) const +HeifInput::valid_file(Filesystem::IOProxy* ioproxy) const { + if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Mode::Read) + return false; uint8_t magic[12]; - if (Filesystem::read_bytes(filename, magic, sizeof(magic)) != sizeof(magic)) + if (ioproxy->pread(magic, sizeof(magic), 0) != sizeof(magic)) return false; heif_filetype_result filetype_check = heif_check_filetype(magic, sizeof(magic)); @@ -141,7 +172,12 @@ HeifInput::open(const std::string& name, ImageSpec& newspec, m_filename = name; m_subimage = -1; + ioproxy_retrieve_from_config(config); + if (!ioproxy_use_or_open(name)) + return false; + m_ctx.reset(new heif::Context); + m_reader.reset(new HeifReader(ioproxy())); m_himage = heif::Image(); m_ihandle = heif::ImageHandle(); @@ -150,8 +186,7 @@ HeifInput::open(const std::string& name, ImageSpec& newspec, m_reorient = config.get_int_attribute("oiio:reorient", 1); try { - m_ctx->read_from_file(name); - // FIXME: should someday be read_from_reader to give full flexibility + m_ctx->read_from_reader(*m_reader); m_item_ids = m_ctx->get_list_of_top_level_image_IDs(); m_primary_id = m_ctx->get_primary_image_ID(); @@ -187,6 +222,7 @@ HeifInput::close() m_himage = heif::Image(); m_ihandle = heif::ImageHandle(); m_ctx.reset(); + m_reader.reset(); m_subimage = -1; m_num_subimages = 0; m_associated_alpha = true; diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 315f6b1a60..8cfa40afd7 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -29,7 +29,8 @@ class HeifOutput final : public ImageOutput { const char* format_name(void) const override { return "heif"; } int supports(string_view feature) const override { - return feature == "alpha" || feature == "exif" || feature == "tiles" + return feature == "alpha" || feature == "exif" || feature == "ioproxy" + || feature == "tiles" #if LIBHEIF_HAVE_VERSION(1, 9, 0) || feature == "cicp" #endif @@ -58,12 +59,9 @@ class HeifOutput final : public ImageOutput { }; - -namespace { - -class MyHeifWriter final : public heif::Context::Writer { +class HeifWriter final : public heif::Context::Writer { public: - MyHeifWriter(Filesystem::IOProxy* ioproxy) + HeifWriter(Filesystem::IOProxy* ioproxy) : m_ioproxy(ioproxy) { } @@ -84,9 +82,6 @@ class MyHeifWriter final : public heif::Context::Writer { Filesystem::IOProxy* m_ioproxy = nullptr; }; -} // namespace - - OIIO_PLUGIN_EXPORTS_BEGIN @@ -114,6 +109,11 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, m_filename = name; + ioproxy_retrieve_from_config(m_spec); + if (!ioproxy_use_or_open(name)) { + return false; + } + m_bitdepth = m_spec.format.size() > TypeUInt8.size() ? 10 : 8; m_bitdepth = m_spec.get_int_attribute("oiio:BitsPerSample", m_bitdepth); if (m_bitdepth == 10 || m_bitdepth == 12) { @@ -221,7 +221,9 @@ HeifOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, bool HeifOutput::close() { - if (!m_ctx) { // already closed + if (!m_ctx || !ioproxy_opened()) { // already closed + m_ctx.reset(); + ioproxy_clear(); return true; } @@ -286,25 +288,20 @@ HeifOutput::close() #endif } m_ctx->set_primary_image(m_ihandle); - Filesystem::IOFile ioproxy(m_filename, Filesystem::IOProxy::Write); - if (ioproxy.mode() != Filesystem::IOProxy::Write) { - errorfmt("Could not open \"{}\"", m_filename); - ok = false; - } else { - MyHeifWriter writer(&ioproxy); - m_ctx->write(writer); - } + HeifWriter writer(ioproxy()); + m_ctx->write(writer); } catch (const heif::Error& err) { std::string e = err.get_message(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); - return false; + ok = false; } catch (const std::exception& err) { std::string e = err.what(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); - return false; + ok = false; } m_ctx.reset(); + ioproxy_clear(); return ok; } From 9e85353570fd6e0c1a62a6fd4efa7e423c4d57cc Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 29 Jan 2026 23:56:15 +0100 Subject: [PATCH 102/508] fix(webp): Missing oiio:UnassociatedAlpha on input (#5020) Like other file formats, the returned ImageSpec should indicate if the image contains unassociated alpha. This was an oversight in #4770. Test added. Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/webp.imageio/webpinput.cpp | 5 ++++- testsuite/webp/ref/out-webp1.1.txt | 6 ++++++ testsuite/webp/run.py | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/webp.imageio/webpinput.cpp b/src/webp.imageio/webpinput.cpp index 09cc08c4cd..bbe35d9822 100644 --- a/src/webp.imageio/webpinput.cpp +++ b/src/webp.imageio/webpinput.cpp @@ -214,8 +214,11 @@ WebpInput::open(const std::string& name, ImageSpec& spec, // Make space for the decoded image m_decoded_image.reset(new uint8_t[m_spec.image_bytes()]); - if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) + if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) { m_keep_unassociated_alpha = true; + if (m_spec.alpha_channel != -1) + m_spec.attribute("oiio:UnassociatedAlpha", 1); + } seek_subimage(0, 0); spec = m_spec; diff --git a/testsuite/webp/ref/out-webp1.1.txt b/testsuite/webp/ref/out-webp1.1.txt index f7e0d095de..92ec6d9f0f 100644 --- a/testsuite/webp/ref/out-webp1.1.txt +++ b/testsuite/webp/ref/out-webp1.1.txt @@ -18,3 +18,9 @@ Reading ../oiio-images/webp/4.webp SHA-1: 8F42E3DCCE6FE15146BA06C440C15B7831F60572 channel list: R, G, B oiio:ColorSpace: "srgb_rec709_scene" +Reading rgba.webp +rgba.webp : 64 x 64, 4 channel, uint8 webp + SHA-1: 897256B6709E1A4DA9DABA92B6BDE39CCFCCD8C1 + channel list: R, G, B, A + oiio:ColorSpace: "srgb_rec709_scene" + oiio:UnassociatedAlpha: 1 diff --git a/testsuite/webp/run.py b/testsuite/webp/run.py index a0a5c53510..3e61dd5d81 100755 --- a/testsuite/webp/run.py +++ b/testsuite/webp/run.py @@ -11,3 +11,6 @@ # a lossy format and is not stable under the round trip # command += rw_command (OIIO_TESTSUITE_IMAGEDIR, f, # extraargs='-attrib compression lossless') + +command += oiiotool ("--create 64x64 4 -o rgba.webp") +command += info_command ("rgba.webp", "--iconfig oiio:UnassociatedAlpha 1", safematch=True) From 7bb12a2703ee285dffe982385393efece753cd63 Mon Sep 17 00:00:00 2001 From: Zach Lewis Date: Fri, 30 Jan 2026 13:43:49 -0500 Subject: [PATCH 103/508] build(ocio): bump build ver to 2.5.1 (#5022) Makes OpenColorIO 2.5.1 the default version when building OCIO locally. This will cause the released python wheels to build against the currently-latest version of OCIO. Signed-off-by: Zach Lewis Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/build_OpenColorIO.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmake/build_OpenColorIO.cmake b/src/cmake/build_OpenColorIO.cmake index 8502992696..ea68c003c6 100644 --- a/src/cmake/build_OpenColorIO.cmake +++ b/src/cmake/build_OpenColorIO.cmake @@ -6,7 +6,7 @@ # OpenColorIO by hand! ###################################################################### -set_cache (OpenColorIO_BUILD_VERSION 2.4.2 "OpenColorIO version for local builds") +set_cache (OpenColorIO_BUILD_VERSION 2.5.1 "OpenColorIO version for local builds") set (OpenColorIO_GIT_REPOSITORY "https://github.com/AcademySoftwareFoundation/OpenColorIO") set (OpenColorIO_GIT_TAG "v${OpenColorIO_BUILD_VERSION}") set_cache (OpenColorIO_BUILD_SHARED_LIBS OFF From 3104d31401cdf43463064356f31b39305b586455 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 31 Jan 2026 13:41:10 -0800 Subject: [PATCH 104/508] ci: lock bleeding edge to pybind11 latest version (#5024) There's something in pybind11 master at the moment that is crashing in its destructors. It's been causing our "bleeding edge" test to fail for over a week now. I'm tired of our test failing, so I'm locking down to the last known working version. Will check back periodically and return to testing against pybind11 master after they have fixed it. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb8b86f022..a227cdbd7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -482,7 +482,7 @@ jobs: fmt_ver: master opencolorio_ver: main openexr_ver: main - pybind11_ver: master + pybind11_ver: v3.0.1 python_ver: "3.12" simd: avx2,f16c benchmark: 1 From 162242b00ce873992fe6021394a325cc92607ae6 Mon Sep 17 00:00:00 2001 From: Valery Angelique <71804886+vangeliq@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:52:00 -0800 Subject: [PATCH 105/508] feat(iv): flip, rotate and save image (#5003) Fixes #4715 - Added save functionality - Added flipping and rotation capabilities via metadata. - Hotkeys: Ctrl-Shift-R and L to rotate the image left and right. Note that the actual pixel data remains unchanged, so only image formats that support the rotation metadata will reflect the changes. I thought that it made sense for the flip and rotate to be under the tool section, similar to where the macbook Preview app places it. Keyboard shortcuts also follow the shortcuts that Preview uses. Tests Used a .jpeg photo to test: - saving - making changes with/without saving and re-opening the image - flipping and rotation from different initial rotation/mirrored states Otherwise there doesn't seem to be a test suite for iv? Signed-off-by: valery <71804886+vangeliq@users.noreply.github.com> Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/iv/imageviewer.cpp | 100 +++++++++++++++++++++++++++++++++++++++++ src/iv/imageviewer.h | 15 +++++++ 2 files changed, 115 insertions(+) diff --git a/src/iv/imageviewer.cpp b/src/iv/imageviewer.cpp index 12db1aa772..f4a4924a5e 100644 --- a/src/iv/imageviewer.cpp +++ b/src/iv/imageviewer.cpp @@ -520,6 +520,22 @@ ImageViewer::createActions() closeupAvgPixelsBox->setToolTip(closeupAvgPixelsTooltip); closeupAvgPixelsLabel->setToolTip(closeupAvgPixelsTooltip); + + rotateLeftAct = new QAction(tr("&Rotate Left"), this); + rotateLeftAct->setShortcut(tr("Ctrl+Shift+L")); + connect(rotateLeftAct, SIGNAL(triggered()), this, SLOT(rotateLeft())); + + rotateRightAct = new QAction(tr("&Rotate Right"), this); + rotateRightAct->setShortcut(tr("Ctrl+Shift+R")); + connect(rotateRightAct, SIGNAL(triggered()), this, SLOT(rotateRight())); + + flipHorizontalAct = new QAction(tr("&Flip Horizontal"), this); + connect(flipHorizontalAct, SIGNAL(triggered()), this, + SLOT(flipHorizontal())); + + flipVerticalAct = new QAction(tr("&Flip Vertical"), this); + connect(flipVerticalAct, SIGNAL(triggered()), this, SLOT(flipVertical())); + // Connect signals to ensure closeupAvgPixelsBox value is always <= closeupPixelsBox value connect(closeupPixelsBox, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -780,6 +796,11 @@ ImageViewer::createMenus() toolsMenu->addAction(toggleAreaSampleAct); toolsMenu->addMenu(slideMenu); toolsMenu->addMenu(sortMenu); + toolsMenu->addSeparator(); + toolsMenu->addAction(rotateLeftAct); + toolsMenu->addAction(rotateRightAct); + toolsMenu->addAction(flipHorizontalAct); + toolsMenu->addAction(flipVerticalAct); // Menus, toolbars, & status // Annotate @@ -2455,3 +2476,82 @@ ImageViewer::areaSampleMode() const { return m_areaSampleMode; } + + +void +ImageViewer::rotateLeft() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 8, 5, 6, 7, 4, 1, 2, 3 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} + + +void +ImageViewer::rotateRight() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 6, 7, 8, 5, 2, 3, 4, 1 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} + + +void +ImageViewer::flipHorizontal() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 2, 1, 4, 3, 6, 5, 8, 7 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} + + +void +ImageViewer::flipVertical() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 4, 3, 2, 1, 8, 7, 6, 5 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} \ No newline at end of file diff --git a/src/iv/imageviewer.h b/src/iv/imageviewer.h index 3a255647bb..763aaf0dd7 100644 --- a/src/iv/imageviewer.h +++ b/src/iv/imageviewer.h @@ -219,6 +219,14 @@ class ImageViewer final : public QMainWindow { return img ? &img->spec() : NULL; } + /// Return a modifiable ref to the current image spec, or NULL if there is no + /// current image. + ImageSpec* curspecmod(void) const + { + IvImage* img = cur(); + return img ? &img->specmod() : NULL; + } + bool pixelviewOn(void) const { return showPixelviewWindowAct && showPixelviewWindowAct->isChecked(); @@ -334,6 +342,11 @@ private slots: void editPreferences(); ///< Edit viewer preferences void toggleAreaSample(); ///< Use area probe + void rotateLeft(); + void rotateRight(); + void flipHorizontal(); + void flipVertical(); + void useOCIOAction(bool checked); void ocioColorSpaceAction(); void ocioDisplayViewAction(); @@ -404,6 +417,8 @@ private slots: QAction* showPixelviewWindowAct; QAction* toggleAreaSampleAct; QAction* toggleWindowGuidesAct; + QAction *rotateLeftAct, *rotateRightAct, *flipHorizontalAct, + *flipVerticalAct; QMenu *fileMenu, *editMenu, /**imageMenu,*/ *viewMenu, *toolsMenu, *helpMenu; QMenu* openRecentMenu; From 10c52b2be32b66645f4b4d3e0ca5a8a4b76c3f13 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 1 Feb 2026 12:31:31 -0800 Subject: [PATCH 106/508] CHANGES udpates (#5028) Reflecting this month's releases and other things that recently went into main. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CHANGES.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CREDITS.md | 3 +++ 2 files changed, 66 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f1ed5b64c0..df7e647fea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,8 +6,11 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 * *New image file format support:* * *oiiotool new features and major improvements*: * *Command line utilities*: + - *iv*: Flip, rotate and save image [#5003](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5003) (by Valery Angelique) (3.2.0.0) * *ImageBuf/ImageBufAlgo*: + - *ImageBuf*: IB::localpixels_as_[writable_]byte_image_span [#5011](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5011) (3.2.0.0, 3.1.10.0) * *ImageCache/TextureSystem*: + - *api/TS*: `IBA::make_texture()` now honors "maketx:threads" hint [#5014](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5014) (3.2.0.0, 3.1.10.0) * New global attribute queries via OIIO::getattribute(): * Miscellaneous API changes: - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) @@ -17,12 +20,20 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *openexr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) - *jpeg-xl*: CICP read and write support for JPEG-XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) (3.2.0.0, 3.1.9.0) - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.0.14.0, 3.2.0.0) +* Other notable new feature: + - *heif*: Add IOProxy support for both input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + ### 🚀 Performance improvements + - *perf*: `ImageBufAlgo::resample` and `oiiotool --resample` improvements to speed up 20x or more [#4993](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4993) (3.2.0.0, 3.1.10.0) + ### 🐛 Fixes and feature enhancements - *IBA*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) (3.2.0.0) - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) (3.2.0.0) - *imagebuf*: Fix set_pixels bug, didn't consider roi = All [#4949](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4949) (3.2.0.0) - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.2.0.0, 3.1.7.0) + - *heif*: Add IOProxy support for both input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + - *heif*: Fix: Could not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + - *heif*: Fix error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.2.0.0, 3.1.7.0) - *jpeg*: Fix wrong pointers/crashing when decoding CMYK jpeg files [#4963](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4963) (3.2.0.0) - *jpeg-2000*: Type warning in assertion in jpeg2000output.cpp [#4952](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4952) (3.2.0.0) @@ -37,27 +48,45 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *png*: We were not correctly suppressing hint metadata [#4983](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4983) (3.2.0.0) - *sgi*: Implement RLE encoding support for output [#4990](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4990) (by Jesse Yurkovich) (3.2.0.0) - *webp*: Allow out-of-order scanlines when writing webp [#4973](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4973) (by Pavan Madduri) (3.2.0.0) + - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) (3.2.0.0, 3.1.10.0) + - *webp*: Fix missing oiio:UnassociatedAlpha on input [#5020](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5020) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) ### 🔧 Internals and developer goodies + - *fix*: Several bug fixes related to internal use of image_span [#5004](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5004) (3.2.0.0, 3.1.10.0) - *filesystem.h*: Speedup to detect the existence of files on Windows [#4977](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4977) (by JacksonSun-adsk) (3.2.0.0) ### 🏗 Build/test/CI and platform ports * OIIO's CMake build system and scripts: - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.2.0.0, 3.1.7.0) - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.2.0.0, 3.1.7.0) + - *build*: Fix HARDENING build options [#4996](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4996) (3.2.0.0) + - *build*: Fully disable tests when their required dependencies are missing [#5005](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5005) (3.2.0.0, 3.1.10.0) * Dependency and platform support: - *deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.2.0.0, 3.1.7.0) - *deps*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) (3.2.0.0, 3.1.8.0) - *deps*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) (3.2.0.0, 3.1.9.0) + - *build/platforms*: Fix building on OpenBSD [#5001](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5001) (by Brad Smith) (3.2.0.0, 3.1.10.0) + - *build/deps*: Bump OCIO auto-build ver to 2.5.1 [#5022](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5022) (by Zach Lewis) (3.2.0.0, 3.1.10.0) + - *build/deps*: Use libheif exported config if available [#5012](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5012) (3.2.0.0, 3.1.10.0) + - *build/deps*: Libheif 1.21 support [#4992](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4992) (3.2.0.0, 3.1.10.0) * Testing and Continuous integration (CI) systems: - *tests*: Image_span_test reduce benchmark load for debug and CI renders [#4951](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4951) (3.2.0.0, 3.1.8.0) + - *tests*: Add new ref image for jpeg test [#5007](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5007) (3.2.0.0, 3.1.10.0) - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.2.0.0, 3.1.7.0) - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.2.0.0, 3.1.7.0) - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.2.0.0, 3.1.7.0) - *ci*: Emergency fix change deprecated sonarqube action [#4969](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4969) (3.2.0.0) - *ci*: Try python 3.13 to fix Mac breakage on CI [#4970](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4970) (3.2.0.0) + - *ci*: Freetype adjustments [#4999](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4999) (3.2.0.0) + - *ci*: Speed up macos15 intel variant by not installing Qt [#4998](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4998) (3.2.0.0, 3.1.10.0) + - *ci*: Don't run non-wheel workflows when only pyproject.toml changes [#4997](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4997) (3.2.0.0, 3.1.10.0) + - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) (3.2.0.0, 3.1.10.0) + - *ci*: Test against libraw 0.22 for 'latest' test variants [#5009](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5009) (3.2.0.0, 3.1.10.0) + - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) (3.2.0.0, 3.1.10.0) ### 📚 Notable documentation changes - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.2.0.0, 3.1.7.0) + - *docs*: Remove outdated/wrong description in INSTALL.md [#5008](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5008) (3.2.0.0) ### 🏢 Project Administration - *admin*: Minor rewording in the issue and PR templates [#4982](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4982) (3.2.0.0) + - *admin*: Refine PR template to give more visual separation [#4995](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4995) (3.2.0.0) ### 🤝 Contributors --- @@ -65,6 +94,30 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 +Release 3.1.10.0 (Feb 1, 2026) -- compared to 3.1.9.0 +----------------------------------------------------- + - *perf*: `IBA::resample()` and `oiiotool --resample` improvements to speed up 20x or more [#4993](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4993) + - *ImageBuf*: IB::localpixels_as_[writable_]byte_image_span [#5011](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5011) + - ImageBufAlgo*: IBA::make_texture now honors "maketx:threads" hint [#5014](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5014) + - *heif*: Add IOProxy for input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) + - *heif*: Can not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) + - *heif*: Error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) + - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) + - *webp*: Missing oiio:UnassociatedAlpha on input [#5020](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5020) (by Brecht Van Lommel) + - *fix*: Several bug fixes related to internal use of image_span [#5004](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5004) + - *build*: Fix building on OpenBSD [#5001](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5001) (by Brad Smith) + - *deps*: Libheif 1.21 support [#4992](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4992) + - *deps*: Bump OCIO auto-build ver to 2.5.1 [#5022](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5022) (by Zach Lewis) + - *deps*: Use libheif exported config if available [#5012](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5012) + - *tests*: Add new ref image for jpeg test [#5007](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5007) + - *tests*: Fully disable tests when their required dependencies are missing [#5005](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5005) + - *ci*: Speed up macos15 intel variant by not installing Qt [#4998](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4998) + - *ci*: Don't run non-wheel workflows when only pyproject.toml changes [#4997](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4997) + - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) + - *ci*: Test against libraw 0.22 for 'latest' test variants [#5009](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5009) + - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) + + Release 3.1.9.0 (Jan 1, 2026) -- compared to 3.1.8.0 ---------------------------------------------------- - Color management improvements: @@ -444,6 +497,16 @@ asterisk) had not previously contributed to the project. --- +Release 3.0.15.0 (Feb 1, 2026) -- compared to 3.0.14.0 +------------------------------------------------------- + - *heif*: Can not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) + - *heif*: Error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) + - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) + - *ci*: Speed up macos15 intel variant by not installing Qt [#4998](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4998) + - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) + - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) + + Release 3.0.14.0 (Jan 1, 2026) -- compared to 3.0.13.0 ------------------------------------------------------- - *fix(IBA)*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) diff --git a/CREDITS.md b/CREDITS.md index f3044fd4bf..ed1bb4c15b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -110,6 +110,7 @@ lg@openimageio.org * Imarz * Irena Damsky * Ismael Cortes +* Jackson Sun * Jan Hettenkofer * Jan Honsbrok * Jens Lindgren @@ -191,6 +192,7 @@ lg@openimageio.org * Paul Franz * Paul Melis * Paul Molodowitch +* Pavan Madduri * Pavel Karneliuk * Pete Larabell * Peter Horvath @@ -223,6 +225,7 @@ lg@openimageio.org * Sergio Rojas * Shane Ambler * Shane Smith +* Shashvat K. Singh * Simon Boorer * Solomon Boulos * SRHMorris From 536d4d61ab20740db502a76bba241cbab6996eb1 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 1 Feb 2026 12:33:10 -0800 Subject: [PATCH 107/508] testing: Adjust test comparision thresholds for Mac ARM (#5026) Even though we have CI testing on Mac with ARM CPU that were passing, after getting a new laptop, I saw some test failures that were due to just a few pixels on a few tests needing a higher comparision threshold. Results are correct, just different due to the math. I guess this machine (CPU? build flags? specific compiler or library versions?) is ever so slightly different than the CI Macs, so I caught a few more instances that needed to be adjusted. I tried to increase the thresholds as little as possible to fix the problem. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- testsuite/texture-crop/run.py | 1 + testsuite/texture-interp-bilinear/run.py | 1 + testsuite/texture-skinny/run.py | 1 + testsuite/texture-uint8/run.py | 1 + 4 files changed, 4 insertions(+) diff --git a/testsuite/texture-crop/run.py b/testsuite/texture-crop/run.py index 9973dc6afc..5623052c2e 100755 --- a/testsuite/texture-crop/run.py +++ b/testsuite/texture-crop/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.02 command += oiiotool("../common/grid.tif --crop 512x512+200+100 -o grid-crop.tif") command += maketx_command ("grid-crop.tif", "grid-crop.tx") diff --git a/testsuite/texture-interp-bilinear/run.py b/testsuite/texture-interp-bilinear/run.py index 0617c8319f..ee9dbed8b1 100755 --- a/testsuite/texture-interp-bilinear/run.py +++ b/testsuite/texture-interp-bilinear/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.02 command = testtex_command ("../common/textures/grid.tx", extraargs = "-interpmode 1 -d uint8 -o out.tif") diff --git a/testsuite/texture-skinny/run.py b/testsuite/texture-skinny/run.py index 8d2f16c7d7..c61eb79d98 100755 --- a/testsuite/texture-skinny/run.py +++ b/testsuite/texture-skinny/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.013 command = testtex_command ("src/vertgrid.tx", " --scalest 4 1 ") outputs = [ "out.exr" ] diff --git a/testsuite/texture-uint8/run.py b/testsuite/texture-uint8/run.py index efe91e14c1..af786ad492 100755 --- a/testsuite/texture-uint8/run.py +++ b/testsuite/texture-uint8/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.021 command = testtex_command ("../common/textures/grid.tx") outputs = [ "out.exr" ] From 2f7d9c115e5f687d49952bb4f7b82a719d38ef18 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 2 Feb 2026 12:30:05 -0800 Subject: [PATCH 108/508] fix: conform certain attrib names "exif:*" to our "Exif:*" convention (#5025) I think it was basically harmless, since we do all the metadata name comparisons using case-insensitive comparisons. But we use "Exif:" as our prefix for Exif data throughout OIIO by convention, and there was this tiny handful of places where we said "exif:". Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/jpeg.imageio/jpegoutput.cpp | 6 +++--- src/libOpenImageIO/xmp.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jpeg.imageio/jpegoutput.cpp b/src/jpeg.imageio/jpegoutput.cpp index f6058deb25..dc7a7af3fe 100644 --- a/src/jpeg.imageio/jpegoutput.cpp +++ b/src/jpeg.imageio/jpegoutput.cpp @@ -370,9 +370,9 @@ void JpgOutput::resmeta_to_density() { // Clear cruft from Exif that might confuse us - m_spec.erase_attribute("exif:XResolution"); - m_spec.erase_attribute("exif:YResolution"); - m_spec.erase_attribute("exif:ResolutionUnit"); + m_spec.erase_attribute("Exif:XResolution"); + m_spec.erase_attribute("Exif:YResolution"); + m_spec.erase_attribute("Exif:ResolutionUnit"); string_view resunit = m_spec.get_string_attribute("ResolutionUnit"); if (Strutil::iequals(resunit, "none")) diff --git a/src/libOpenImageIO/xmp.cpp b/src/libOpenImageIO/xmp.cpp index bc1a777a5e..f919afbfd7 100644 --- a/src/libOpenImageIO/xmp.cpp +++ b/src/libOpenImageIO/xmp.cpp @@ -98,8 +98,8 @@ static XMPtag xmptag[] = { { "tiff:Software", "Software", TypeDesc::STRING, TiffRedundant }, { "exif:ColorSpace", "Exif:ColorSpace", TypeDesc::INT, ExifRedundant }, - { "exif:PixelXDimension", "", TypeDesc::INT, ExifRedundant|TiffRedundant}, - { "exif:PixelYDimension", "", TypeDesc::INT, ExifRedundant|TiffRedundant }, + { "exif:PixelXDimension", "Exif:PixelXDimension", TypeDesc::INT, ExifRedundant|TiffRedundant}, + { "exif:PixelYDimension", "Exif:PixelYDimension", TypeDesc::INT, ExifRedundant|TiffRedundant }, { "exifEX:PhotographicSensitivity", "Exif:ISOSpeedRatings", TypeDesc::INT, ExifRedundant }, { "xmp:CreateDate", "DateTime", TypeDesc::STRING, DateConversion|TiffRedundant }, From 0d8bd8d4300402c5105350e0b72d342b0ecb1962 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 2 Feb 2026 13:42:10 -0800 Subject: [PATCH 109/508] fix(win): `oiiotool --buildinfo` misreported platform on MSVS (#5027) Need to test some MSVS-specific macros to determine what architecture to report. And especially, if it doesn't know the processor architecture, it still should be *appending* that to the platform, not replacing it! This caused MSVS-compiled OIIO on Windows to report "unknown arch?" Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/imageio.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index 909f8529d4..c23fb7a331 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -257,14 +257,15 @@ oiio_build_platform() platform = "UnknownOS"; #endif platform += "/"; -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(_M_AMD64) platform += "x86_64"; -#elif defined(__i386__) +#elif defined(__i386__) || defined(_M_IX86) platform += "i386"; -#elif defined(_M_ARM64) || defined(__aarch64__) || defined(__aarch64) +#elif defined(_M_ARM64) || defined(__aarch64__) || defined(__aarch64) \ + || defined(__ARM_ARCH) platform += "ARM"; #else - platform = "unknown arch?"; + platform += "unknown arch?"; #endif return platform; } From 4896a2e77e0e15dc2619ee5d924d1640e374a5d3 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 5 Feb 2026 19:08:29 -0800 Subject: [PATCH 110/508] testing: Add testsuite/heif ref output for libheif 1.21 + avif support (#5031) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .../heif/ref/out-libheif1.21-with-av1.txt | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 testsuite/heif/ref/out-libheif1.21-with-av1.txt diff --git a/testsuite/heif/ref/out-libheif1.21-with-av1.txt b/testsuite/heif/ref/out-libheif1.21-with-av1.txt new file mode 100644 index 0000000000..b22dcca2c5 --- /dev/null +++ b/testsuite/heif/ref/out-libheif1.21-with-av1.txt @@ -0,0 +1,164 @@ +Reading ref/IMG_7702_small.heic +ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif + SHA-1: 2380C124F8338910013FEA75C9C64C23567A3156 + channel list: R, G, B + DateTime: "2019:01:21 16:10:54" + ExposureTime: 0.030303 + FNumber: 1.8 + Make: "Apple" + Model: "iPhone 7" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "12.1.2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69599 (f/1.8) + Exif:BrightnessValue: 3.99501 + Exif:ColorSpace: 65535 + Exif:DateTimeDigitized: "2019:01:21 16:10:54" + Exif:DateTimeOriginal: "2019:01:21 16:10:54" + Exif:ExifVersion: "0221" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 24 (no flash, auto flash) + Exif:FlashPixVersion: "0100" + Exif:FocalLength: 3.99 (3.99 mm) + Exif:FocalLengthIn35mmFilm: 28 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 7 back camera 3.99mm f/1.8" + Exif:LensSpecification: 3.99, 3.99, 1.8, 1.8 + Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 20 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SceneCaptureType: 0 (standard) + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 5.03599 (1/32 s) + Exif:SubsecTimeDigitized: "006" + Exif:SubsecTimeOriginal: "006" + Exif:WhiteBalance: 0 (auto) + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic +../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif + SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C + channel list: R, G, B + DateTime: "2023:09:28 09:44:03" + ExposureTime: 0.0135135 + FNumber: 2.4 + Make: "Apple" + Model: "iPhone 12 Pro" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "16.7" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 2.52607 (f/2.4) + Exif:BrightnessValue: 2.7506 + Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 + Exif:DateTimeDigitized: "2023:09:28 09:44:03" + Exif:DateTimeOriginal: "2023:09:28 09:44:03" + Exif:DigitalZoomRatio: 1.3057 + Exif:ExifVersion: "0232" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 16 (no flash, flash suppression) + Exif:FocalLength: 1.54 (1.54 mm) + Exif:FocalLengthIn35mmFilm: 17 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 12 Pro back triple camera 1.54mm f/2.4" + Exif:LensSpecification: 1.54, 6, 1.6, 2.4 + Exif:MeteringMode: 5 (pattern) + Exif:OffsetTime: "+02:00" + Exif:OffsetTimeDigitized: "+02:00" + Exif:OffsetTimeOriginal: "+02:00" + Exif:PhotographicSensitivity: 320 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 6.20983 (1/74 s) + Exif:SubsecTimeDigitized: "886" + Exif:SubsecTimeOriginal: "886" + Exif:WhiteBalance: 0 (auto) + GPS:Altitude: 3.24105 (3.24105 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:DateStamp: "2023:09:28" + GPS:DestBearing: 90.2729 + GPS:DestBearingRef: "T" (true north) + GPS:HPositioningError: 5.1893 + GPS:ImgDirection: 90.2729 + GPS:ImgDirectionRef: "T" (true north) + GPS:Latitude: 41, 50, 58.43 + GPS:LatitudeRef: "N" + GPS:Longitude: 3, 7, 31.98 + GPS:LongitudeRef: "E" + GPS:Speed: 0.171966 + GPS:SpeedRef: "K" (km/hour) + oiio:ColorSpace: "srgb_rec709_scene" + oiio:OriginalOrientation: 6 +Reading ../oiio-images/heif/sewing-threads.heic +../oiio-images/heif/sewing-threads.heic : 4000 x 3000, 3 channel, uint8 heif + SHA-1: 44551A0A8AADD2C71B504681F2BAE3F7863EF9B9 + channel list: R, G, B + DateTime: "2023:12:12 18:39:16" + ExposureTime: 0.04 + FNumber: 1.8 + Make: "samsung" + Model: "SM-A326B" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "A326BXXS8CWK2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69 (f/1.8) + Exif:BrightnessValue: 1.19 + Exif:ColorSpace: 1 + Exif:DateTimeDigitized: "2023:12:12 18:39:16" + Exif:DateTimeOriginal: "2023:12:12 18:39:16" + Exif:DigitalZoomRatio: 1 + Exif:ExifVersion: "0220" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 0 (no flash) + Exif:FocalLength: 4.6 (4.6 mm) + Exif:FocalLengthIn35mmFilm: 25 + Exif:MaxApertureValue: 1.69 (f/1.8) + Exif:MeteringMode: 2 (center-weighted average) + Exif:OffsetTime: "+01:00" + Exif:OffsetTimeOriginal: "+01:00" + Exif:PhotographicSensitivity: 500 + Exif:PixelXDimension: 4000 + Exif:PixelYDimension: 3000 + Exif:SceneCaptureType: 0 (standard) + Exif:ShutterSpeedValue: 0.04 (1/1 s) + Exif:SubsecTime: "576" + Exif:SubsecTimeDigitized: "576" + Exif:SubsecTimeOriginal: "576" + Exif:WhiteBalance: 0 (auto) + Exif:YCbCrPositioning: 1 + GPS:Altitude: 292 (292 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:Latitude: 41, 43, 33.821 + GPS:LatitudeRef: "N" + GPS:Longitude: 1, 49, 34.0187 + GPS:LongitudeRef: "E" + oiio:ColorSpace: "srgb_rec709_scene" From ab2f61ed8f8003f56250f22dad25c761e22be78e Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Fri, 6 Feb 2026 13:55:43 -0800 Subject: [PATCH 111/508] cleanup: remove left over tile emulation code for various formats (#5029) Since the OpenImageIO 2.5 series, when calls to `check_open` were added, any format that did not declare support for "tiles" would immediately fail to open. But many of the formats which attempted to emulate tiles, by buffering the contents and writing it all as scanlines at the end, were not updated. All of the tile emulation code for these formats is effectively dead-code and untested. Remove the tile emulation code from these formats. An example of what the failure currently looks like: ```python >>> out = oiio.ImageOutput.create("test.png") >>> spec = oiio.ImageSpec(64, 64, 3, 'uint8') >>> spec.tile_width = 64 >>> out.open("test.png", spec) False >>> out.geterror() 'png does not support tiled images' ``` No tests were impacted. Signed-off-by: Jesse Yurkovich Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/bmp.imageio/bmpoutput.cpp | 37 +------------------------------ src/dpx.imageio/dpxoutput.cpp | 36 +----------------------------- src/hdr.imageio/hdroutput.cpp | 31 +------------------------- src/ico.imageio/icooutput.cpp | 30 +------------------------ src/jpeg.imageio/jpegoutput.cpp | 33 ++------------------------- src/png.imageio/pngoutput.cpp | 30 +------------------------ src/pnm.imageio/pnmoutput.cpp | 29 +----------------------- src/rla.imageio/rlaoutput.cpp | 30 +------------------------ src/targa.imageio/targaoutput.cpp | 31 +------------------------- src/zfile.imageio/zfile.cpp | 36 +----------------------------- 10 files changed, 11 insertions(+), 312 deletions(-) diff --git a/src/bmp.imageio/bmpoutput.cpp b/src/bmp.imageio/bmpoutput.cpp index c6e772799c..e60ae820ff 100644 --- a/src/bmp.imageio/bmpoutput.cpp +++ b/src/bmp.imageio/bmpoutput.cpp @@ -27,9 +27,6 @@ class BmpOutput final : public ImageOutput { bool close(void) override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: int64_t m_padded_scanline_size; @@ -38,7 +35,6 @@ class BmpOutput final : public ImageOutput { bmp_pvt::DibInformationHeader m_dib_header; int64_t m_image_start; unsigned int m_dither; - std::vector m_tilebuffer; std::vector m_scratch; std::vector m_buf; // more tmp space for write_scanline @@ -110,11 +106,6 @@ BmpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) m_image_start = iotell(); - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -157,23 +148,6 @@ BmpOutput::write_scanline(int y, int z, TypeDesc format, const void* data, } - -bool -BmpOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - if (!ioproxy_opened()) { - errorfmt("write_tile called but file is not open."); - return false; - } - - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, m_tilebuffer.data()); -} - - - bool BmpOutput::close(void) { @@ -182,17 +156,8 @@ BmpOutput::close(void) return true; } - bool ok = true; - if (m_spec.tile_width && m_tilebuffer.size()) { - // Handle tile emulation -- output the buffered pixels - OIIO_DASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, m_tilebuffer.data()); - std::vector().swap(m_tilebuffer); - } - init(); - return ok; + return true; } diff --git a/src/dpx.imageio/dpxoutput.cpp b/src/dpx.imageio/dpxoutput.cpp index 4db1103a3c..719a38da1a 100644 --- a/src/dpx.imageio/dpxoutput.cpp +++ b/src/dpx.imageio/dpxoutput.cpp @@ -46,9 +46,6 @@ class DPXOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: OutStream* m_stream = nullptr; @@ -69,7 +66,6 @@ class DPXOutput final : public ImageOutput { std::vector m_subimage_specs; bool m_write_pending; // subimage buffer needs to be written unsigned int m_dither; - std::vector m_tilebuffer; // Initialize private members to pre-opened state void init(void) @@ -417,11 +413,6 @@ DPXOutput::open(const std::string& name, const ImageSpec& userspec, ? spec0.get_int_attribute("oiio:dither", 0) : 0; - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (spec0.tile_width && spec0.tile_height) - m_tilebuffer.resize(spec0.image_bytes()); - return prep_subimage(m_subimage, true); } @@ -593,16 +584,7 @@ DPXOutput::close() return true; } - bool ok = true; - const ImageSpec& spec_s(m_subimage_specs[m_subimage]); - if (spec_s.tile_width && m_tilebuffer.size()) { - // Handle tile emulation -- output the buffered pixels - ok &= write_scanlines(spec_s.y, spec_s.y + spec_s.height, 0, - spec_s.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - - ok &= write_buffer(); + bool ok = write_buffer(); m_dpx.Finish(); init(); // Reset to initial state return ok; @@ -644,22 +626,6 @@ DPXOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -DPXOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - if (!is_opened()) { - errorfmt("write_tile called but file is not open."); - return false; - } - - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - dpx::Characteristic DPXOutput::get_characteristic_from_string(const std::string& str) { diff --git a/src/hdr.imageio/hdroutput.cpp b/src/hdr.imageio/hdroutput.cpp index 5feb97042a..e682a20079 100644 --- a/src/hdr.imageio/hdroutput.cpp +++ b/src/hdr.imageio/hdroutput.cpp @@ -26,14 +26,10 @@ class HdrOutput final : public ImageOutput { OpenMode mode) override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; bool close() override; private: std::vector scratch; - std::vector m_tilebuffer; void init(void) { ioproxy_clear(); } @@ -226,11 +222,6 @@ HdrOutput::open(const std::string& name, const ImageSpec& newspec, if (!iowritefmt("-Y {} +X {}\n", m_spec.height, m_spec.width)) return false; - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -246,17 +237,6 @@ HdrOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc format, -bool -HdrOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - bool HdrOutput::close() { @@ -265,18 +245,9 @@ HdrOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // We've been emulating tiles; now dump as scanlines. - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - init(); - return ok; + return true; } OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ico.imageio/icooutput.cpp b/src/ico.imageio/icooutput.cpp index 18e5fdb5a3..b6f3eed9de 100644 --- a/src/ico.imageio/icooutput.cpp +++ b/src/ico.imageio/icooutput.cpp @@ -32,9 +32,6 @@ class ICOOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; ///< Stash the filename @@ -47,7 +44,6 @@ class ICOOutput final : public ImageOutput { int m_and_slb; ///< AND mask scanline length in bytes int m_bpp; ///< Bits per pixel unsigned int m_dither; - std::vector m_tilebuffer; png_structp m_png; ///< PNG read structure pointer png_infop m_info; ///< PNG image info structure pointer @@ -361,11 +357,6 @@ ICOOutput::open(const std::string& name, const ImageSpec& userspec, fseek(m_file, m_offset + sizeof(bmi), SEEK_SET); } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -392,15 +383,6 @@ ICOOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - if (m_png) { PNG_pvt::write_end(m_png, m_info); if (m_png || m_info) @@ -411,7 +393,7 @@ ICOOutput::close() fclose(m_file); m_file = NULL; init(); // re-initialize - return ok; + return true; } @@ -516,14 +498,4 @@ ICOOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -ICOOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/jpeg.imageio/jpegoutput.cpp b/src/jpeg.imageio/jpegoutput.cpp index dc7a7af3fe..68156aad0d 100644 --- a/src/jpeg.imageio/jpegoutput.cpp +++ b/src/jpeg.imageio/jpegoutput.cpp @@ -40,9 +40,6 @@ class JpgOutput final : public ImageOutput { OpenMode mode = Create) override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; bool close() override; bool copy_image(ImageInput* in) override; @@ -55,7 +52,7 @@ class JpgOutput final : public ImageOutput { struct jpeg_error_mgr c_jerr; jvirt_barray_ptr* m_copy_coeffs; struct jpeg_decompress_struct* m_copy_decompressor; - std::vector m_tilebuffer; + // m_outbuffer/m_outsize are used for jpeg-to-memory unsigned char* m_outbuffer = nullptr; #if OIIO_JPEG_LIB_VERSION >= 94 @@ -356,11 +353,6 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec, m_dither = m_spec.get_int_attribute("oiio:dither", 0); - // If user asked for tiles -- which JPEG doesn't support, emulate it by - // buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -517,17 +509,6 @@ JpgOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -JpgOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - bool JpgOutput::close() { @@ -536,16 +517,6 @@ JpgOutput::close() return true; } - bool ok = true; - - if (m_spec.tile_width) { - // We've been emulating tiles; now dump as scanlines. - OIIO_DASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); // free it - } - if (m_next_scanline < spec().height && m_copy_coeffs == NULL) { // But if we've only written some scanlines, write the rest to avoid // errors @@ -578,7 +549,7 @@ JpgOutput::close() } init(); - return ok; + return true; } diff --git a/src/png.imageio/pngoutput.cpp b/src/png.imageio/pngoutput.cpp index 00e11947c9..7d06eca25f 100644 --- a/src/png.imageio/pngoutput.cpp +++ b/src/png.imageio/pngoutput.cpp @@ -38,9 +38,6 @@ class PNGOutput final : public ImageOutput { bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, const void* data, stride_t xstride = AutoStride, stride_t ystride = AutoStride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; ///< Stash the filename @@ -55,7 +52,6 @@ class PNGOutput final : public ImageOutput { float m_gamma = 1.0f; ///< Gamma to use for alpha conversion std::vector m_scratch; std::vector m_pngtext; - std::vector m_tilebuffer; bool m_err = false; // Initialize private members to pre-opened state @@ -240,11 +236,6 @@ PNGOutput::open(const std::string& name, const ImageSpec& userspec, m_convert_alpha = m_spec.alpha_channel != -1 && !m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0); - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -258,15 +249,6 @@ PNGOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - if (m_png) { PNG_pvt::write_end(m_png, m_info); if (m_png || m_info) @@ -276,7 +258,7 @@ PNGOutput::close() } init(); // re-initialize - return ok; + return true; } @@ -457,14 +439,4 @@ PNGOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, -bool -PNGOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/pnm.imageio/pnmoutput.cpp b/src/pnm.imageio/pnmoutput.cpp index 54596a3afa..1bb94b9677 100644 --- a/src/pnm.imageio/pnmoutput.cpp +++ b/src/pnm.imageio/pnmoutput.cpp @@ -30,9 +30,6 @@ class PNMOutput final : public ImageOutput { bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, const void* data, stride_t xstride = AutoStride, stride_t ystride = AutoStride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; // Stash the filename @@ -42,7 +39,6 @@ class PNMOutput final : public ImageOutput { unsigned int m_dither; std::vector m_scratch; - std::vector m_tilebuffer; void init(void) { ioproxy_clear(); } @@ -310,10 +306,6 @@ PNMOutput::open(const std::string& name, const ImageSpec& userspec, ok &= iowritefmt("{}\n", scale); } } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); return ok; } @@ -326,17 +318,8 @@ PNMOutput::close() if (!ioproxy_opened()) // already closed return true; - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_DASSERT(m_tilebuffer.size()); - ok &= ImageOutput::write_scanlines(m_spec.y, m_spec.y + m_spec.height, - 0, m_spec.format, &m_tilebuffer[0]); - m_tilebuffer.shrink_to_fit(); - } - init(); - return ok; + return true; } @@ -422,14 +405,4 @@ PNMOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, -bool -PNMOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/rla.imageio/rlaoutput.cpp b/src/rla.imageio/rlaoutput.cpp index fec2736d51..6afcad128f 100644 --- a/src/rla.imageio/rlaoutput.cpp +++ b/src/rla.imageio/rlaoutput.cpp @@ -35,16 +35,12 @@ class RLAOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::vector m_scratch; RLAHeader m_rla; ///< Wavefront RLA header std::vector m_sot; ///< Scanline offset table std::vector m_rle; ///< Run record buffer for RLE - std::vector m_tilebuffer; unsigned int m_dither; // Initialize private members to pre-opened state @@ -340,11 +336,6 @@ RLAOutput::open(const std::string& name, const ImageSpec& userspec, m_sot.resize(m_spec.height, (int32_t)0); write(&m_sot[0], m_sot.size()); - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -382,22 +373,13 @@ RLAOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_DASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - // Now that all scanlines have been output, return to write the // correct scanline offset table to file and close the stream. ioseek(sizeof(RLAHeader)); write(m_sot.data(), m_sot.size()); init(); // re-initialize - return ok; + return true; } @@ -545,14 +527,4 @@ RLAOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -RLAOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/targa.imageio/targaoutput.cpp b/src/targa.imageio/targaoutput.cpp index b580da9907..ef75e2c0bb 100644 --- a/src/targa.imageio/targaoutput.cpp +++ b/src/targa.imageio/targaoutput.cpp @@ -37,9 +37,6 @@ class TGAOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; bool set_thumbnail(const ImageBuf& thumb) override; private: @@ -50,7 +47,6 @@ class TGAOutput final : public ImageOutput { std::vector m_scratch; int m_idlen; ///< Length of the TGA ID block unsigned int m_dither; - std::vector m_tilebuffer; ImageBuf m_thumb; // Initialize private members to pre-opened state @@ -239,11 +235,6 @@ TGAOutput::open(const std::string& name, const ImageSpec& userspec, } } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -421,16 +412,7 @@ TGAOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - m_tilebuffer.shrink_to_fit(); - } - - ok &= write_tga20_data_fields(); + bool ok = write_tga20_data_fields(); init(); // re-initialize return ok; @@ -681,17 +663,6 @@ TGAOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -TGAOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - bool TGAOutput::set_thumbnail(const ImageBuf& thumb) { diff --git a/src/zfile.imageio/zfile.cpp b/src/zfile.imageio/zfile.cpp index 65b3f4fc54..d2e326aa4a 100644 --- a/src/zfile.imageio/zfile.cpp +++ b/src/zfile.imageio/zfile.cpp @@ -92,16 +92,12 @@ class ZfileOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; ///< Stash the filename FILE* m_file; ///< Open image handle for not compressed gzFile m_gz; ///< Handle for compressed files std::vector m_scratch; - std::vector m_tilebuffer; bool opened() const { return m_file || m_gz; } @@ -112,7 +108,6 @@ class ZfileOutput final : public ImageOutput { m_gz = 0; m_filename.clear(); m_scratch.clear(); - m_tilebuffer.clear(); } }; @@ -306,11 +301,6 @@ ZfileOutput::open(const std::string& name, const ImageSpec& userspec, return false; } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image.this form - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -324,15 +314,6 @@ ZfileOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width && m_tilebuffer.size()) { - // We've been emulating tiles; now dump as scanlines. - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, m_tilebuffer.data()); - m_tilebuffer.clear(); - m_tilebuffer.shrink_to_fit(); - } - if (m_gz) { gzclose(m_gz); m_gz = 0; @@ -343,7 +324,7 @@ ZfileOutput::close() } init(); // re-initialize - return ok; + return true; } @@ -382,19 +363,4 @@ ZfileOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, -bool -ZfileOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - if (!opened()) { - errorfmt("File not open"); - return false; - } - // Emulate tiles by buffering the whole image - OIIO_ASSERT(m_tilebuffer.data()); - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, m_tilebuffer.data()); -} - - OIIO_PLUGIN_NAMESPACE_END From 500cdb3a64b6e255491fcfb4d74962b00c11eb40 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 6 Feb 2026 13:57:03 -0800 Subject: [PATCH 112/508] api: OIIO_CONTRACT_ASSERT and other hardening improvements (#5006) Review: we have long had two assertion macros: OIIO_ASSERT which aborts upon failure in Debug builds and prints but continues in Release builds, and OIIO_DASSERT which aborts in Debug builds and is completely inactive for Relase builds. Inspired by C++26 contracts, and increasingly available "hardening modes" in major compilers (especially with the LLVM/clang project's libc++), I'm introducing some new verification helpers. New macro `OIIO_CONTRACT_ASSERT` more closely mimics C++26 contract_assert in many ways, and perhaps will simply wrap C++ contract_assert when C++26 is on our menu. Important ways that OIIO_CONTRACT_ASSERT differs from OIIO_ASSERT and OIIO_DASSERT: * Keeping in line with C++ contracts, there are 4 possible responses to a failed contract assertion: Ignore, Observe (print only), Enforce (print and abort) and Quick-Enforce (just abort). * Also define hardening levels: None, Fast, Extensive, and Debug, mimicking the levels of libc++. The idea is that maybe there will be some CONTRACT_ASSERT checks you only want to do for certain hardening levels. * By default, the contract failure response is Enforce, unless it's both a release build and the hardening level is set to None (in which case the response will be Ignore). But it's also overrideable optionally on a per-translation-unit basis by setting OIIO_ASSERTION_RESPONSE_DEFAULT before any OIIO headers are included (though obviously that only applies to inline functions or templates, not to any already-compiled code in the library). * Macros for explicit hardening levels: OIIO_HARDENING_ASSERT_FAST(), EXTENSIVE(), and DEBUG(), which call CONTRACT_ASSERT only when the hardening level is what's required or stricter. I also changed the bounds checking in operator[] of string_view, span, and image_span to use the contract assertions. Note that this adds a tiny bit of overhead, since the default is "enforce" for release builds (previously, using OIIO_DASSERT, it did no checks for release builds). But the benchmarks seem to idicate that the perf difference is barely measurable. I added some benchmarking that proves that the bounds check adds a minute overhead to an element access for a trivial `span`, maybe even indescernable. Here are benchmarks comparing raw pointer access, std::array access, span access with the new checks, span access carefully bypassing the tests. Linux workstation, gcc-11, on my work computer: pointer operator[]: 647.8 ns (+/- 0.1ns) std::array operator[]: 647.8 ns (+/- 0.1ns) span operator[] : 657.6 ns (+/- 0.5ns) span unsafe indexing: 648.2 ns (+/- 0.2ns) span range : 648.1 ns (+/- 0.1ns) These are the most stable tests I have, with the least trial-to-trial variation, and show about a 1.5% speed hit on the bounds-checked span access itself, which I think will be truly un-measurable in the context of being interleaved with any other operations that you do with the data you pull from the span. Mac Intel, Apple Clang 17, on my (old) personal laptop: (much more variable timing, probably from MacOS scheduler quirks) pointer operator[]: 929.2 ns (+/- 6.7ns) std::array operator[]: 913.1 ns (+/- 20.6ns) span operator[] : 905.8 ns (+/- 13.3ns) span unsafe indexing: 913.9 ns (+/- 16.6ns) span range : 916.4 ns (+/- 20.3ns) You can see that here there is no obvious penalty, in fact it appears a little faster, but all within the timing uncertainty of the multiple trials, so statistically it's hard to discern any penalty. And a couple more for good measure from our CI, but note that because these are uncontrolled machines somewhere on the GitHub cloud, the timings might not be as reliable: Windows, MSVS 2022: pointer operator[]: 3716.3 ns (+/- 6.3ns) std::array operator[]: 3715.5 ns (+/- 3.4ns) span operator[] : 3715.6 ns (+/- 2.6ns) span unsafe indexing: 3712.1 ns (+/- 0.7ns) span range : 3714.2 ns (+/- 2.9ns) Linux, gcc-14, C++20: pointer operator[]: 1130.9 ns (+/- 0.2ns), 884.2 k/s std::array operator[]: 1132.0 ns (+/- 0.4ns), 883.4 k/s span operator[] : 1133.7 ns (+/- 0.4ns), 882.1 k/s span unsafe indexing: 1134.2 ns (+/- 1.6ns), 881.7 k/s span range : 1133.9 ns (+/- 0.7ns), 881.9 k/s MacOS ARM: pointer operator[]: 3456.6 ns (+/- 7.5ns) std::array operator[]: 3466.8 ns (+/- 12.2ns) span operator[] : 3610.9 ns (+/- 11.0ns) span unsafe indexing: 3607.4 ns (+/- 4.9ns) span range : 3612.4 ns (+/- 12.2ns) Windows with MSVS and Linux with newer g++ don't appear to show any penalty, and the bracketing of trial times indicates that maybe it's consistent enough to be meaningful? I can't think of anything I'm doing wrong here that would throw off the timing or disable the range checking on these tests. For MacOS ARM, the span looks like it has about a 4% penalty versus raw pointers? But OTOH, span bounds-checked vs non-checked vs range-for are all the same, so maybe the speed vs raw pointer is something else entirely? Also please note that a preferred way to avoid these extra bounds checks entirely is to change an index-oriented loop like span s; for (size_t i = 0; i < s.size(); ++i) foo(s[i]); // maybe bounds check on each iteration? to a range based loop: span s; for (auto& v : s) foo(v); which should be inherently safe and require no in-loop checks at all. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/build-scripts/ci-benchmark.bash | 7 +- src/cmake/compiler.cmake | 6 +- src/include/OpenImageIO/dassert.h | 152 ++++++++++++++++++++++++++ src/include/OpenImageIO/hash.h | 7 +- src/include/OpenImageIO/image_span.h | 26 +++-- src/include/OpenImageIO/span.h | 14 ++- src/include/OpenImageIO/string_view.h | 22 ++-- src/libutil/errorhandler.cpp | 11 ++ src/libutil/span_test.cpp | 91 ++++++++++++++- src/libutil/strutil.cpp | 2 +- 10 files changed, 308 insertions(+), 30 deletions(-) diff --git a/src/build-scripts/ci-benchmark.bash b/src/build-scripts/ci-benchmark.bash index 7d5a5f0a33..d3b3551b1b 100755 --- a/src/build-scripts/ci-benchmark.bash +++ b/src/build-scripts/ci-benchmark.bash @@ -13,13 +13,14 @@ ls build ls $BUILD_BIN_DIR mkdir -p build/benchmarks -for t in image_span_test ; do +for t in image_span_test span_test ; do echo echo echo "$t" echo "========================================================" - ${BUILD_BIN_DIR}/$t > build/benchmarks/$t.out - cat build/benchmarks/$t.out + OpenImageIO_CI=0 ${BUILD_BIN_DIR}/$t | tee build/benchmarks/$t.out + # Note: set OpenImageIO_CI=0 to avoid CI-specific automatic reduction of + # the number of trials and iterations. echo "========================================================" echo "========================================================" echo diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index 8dffb97d36..6936ac51b9 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -499,12 +499,12 @@ endif () # https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set (${PROJ_NAME}_HARDENING_DEFAULT 2) + set (${PROJ_NAME}_HARDENING_DEFAULT 2) # Extensive else () - set (${PROJ_NAME}_HARDENING_DEFAULT 1) + set (${PROJ_NAME}_HARDENING_DEFAULT 1) # Fast endif () set_cache (${PROJ_NAME}_HARDENING ${${PROJ_NAME}_HARDENING_DEFAULT} - "Turn on security hardening features 0, 1, 2, 3") + "Turn on security hardening features 0=none, 1=fast, 2=extensive, 3=debug") # Implementation: add_compile_definitions (${PROJ_NAME}_HARDENING_DEFAULT=${${PROJ_NAME}_HARDENING}) if (${PROJ_NAME}_HARDENING GREATER_EQUAL 1) diff --git a/src/include/OpenImageIO/dassert.h b/src/include/OpenImageIO/dassert.h index db7cf65976..db3e8992b9 100644 --- a/src/include/OpenImageIO/dassert.h +++ b/src/include/OpenImageIO/dassert.h @@ -9,9 +9,161 @@ #include #include +#include #include + +// General resources about security and hardening for C++: +// +// https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html +// https://www.gnu.org/software/libc/manual/html_node/Source-Fortification.html +// https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html +// https://libcxx.llvm.org/Hardening.html +// https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html +// https://stackoverflow.com/questions/13544512/what-is-the-most-hardened-set-of-options-for-gcc-compiling-c-c +// https://medium.com/@simontoth/daily-bit-e-of-c-hardened-mode-of-standard-library-implementations-18be2422c372 +// https://en.cppreference.com/w/cpp/contract +// https://en.cppreference.com/w/cpp/language/contracts + + + +// Define hardening levels for OIIO: which checks should we do? +// NONE - YOLO mode, no extra checks (not recommended) +// FAST - Minimal checks that have low performance impact +// EXTENSIVE - More thorough checks, may impact performance +// DEBUG - Maximum checks, for debugging purposes +#define OIIO_HARDENING_NONE 0 +#define OIIO_HARDENING_FAST 1 +#define OIIO_HARDENING_EXTENSIVE 2 +#define OIIO_HARDENING_DEBUG 3 + +// OIIO_HARDENING_DEFAULT defines the default hardening level we actually use. +// By default, we use FAST for release builds and DEBUG for debug builds. But +// it can be overridden: +// - For OIIO internals, at OIIO build time with the `OIIO_HARDENING` CMake +// variable. +// - For other projects using OIIO's headers, any translation unit may +// override this by defining OIIO_HARDENING_DEFAULT before including any +// OIIO headers. But note that this only affects calls to inline functions +// or templates defined in the headers. Non-inline functions compiled into +// the OIIO library itself will have been compiled with whatever hardening +// level was selected when the library was built. +#ifndef OIIO_HARDENING_DEFAULT +# ifdef NDEBUG +# define OIIO_HARDENING_DEFAULT OIIO_HARDENING_FAST +# else +# define OIIO_HARDENING_DEFAULT OIIO_HARDENING_DEBUG +# endif +#endif + + +// Choices for what to do when a contract assertion fails. +// This mimics the C++26 standard's std::contract behavior. +#define OIIO_ASSERTION_RESPONSE_IGNORE 0 +#define OIIO_ASSERTION_RESPONSE_OBSERVE 1 +#define OIIO_ASSERTION_RESPONSE_ENFORCE 2 +#define OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE 3 + +// OIIO_ASSERTION_RESPONSE_DEFAULT defines the default response to failed +// contract assertions. By default, we enforce them, UNLESS we are a release +// mode build that has set the hardening mode to NONE. But any translation +// unit (including clients of OIIO) may override this by defining +// OIIO_ASSERTION_RESPONSE_DEFAULT before including any OIIO headers. But note +// that this only affects calls to inline functions or templates defined in +// the headers. Non-inline functions compiled into the OIIO library itself +// will have been compiled with whatever response was selected when the +// library was built. +#ifndef OIIO_ASSERTION_RESPONSE_DEFAULT +# if OIIO_HARDENING_DEFAULT == OIIO_HARDENING_NONE && defined(NDEBUG) +# define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_IGNORE +# else +# define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_ENFORCE +# endif +#endif + + + +// `OIIO_CONTRACT_ASSERT(condition)` checks if the condition is met, and if +// not, calls the contract violation handler with the appropriate response. +// `OIIO_CONTRACT_ASSERT_MSG(condition, msg)` is the same, but allows a +// different message to be passed to the handler. +#if OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_IGNORE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) (void)0 +#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ + (OIIO_LIKELY(condition) ? ((void)0) : (std::abort(), (void)0)) +#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_OBSERVE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ + (OIIO_LIKELY(condition) ? ((void)0) \ + : (OIIO::contract_violation_handler( \ + __FILE__ ":" OIIO_STRINGIZE(__LINE__), \ + OIIO_PRETTY_FUNCTION, message), \ + (void)0)) +#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_ENFORCE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ + (OIIO_LIKELY(condition) ? ((void)0) \ + : (OIIO::contract_violation_handler( \ + __FILE__ ":" OIIO_STRINGIZE(__LINE__), \ + OIIO_PRETTY_FUNCTION, message), \ + std::abort(), (void)0)) +#else +# error "Unknown OIIO_ASSERTION_RESPONSE_DEFAULT" +#endif + +#define OIIO_CONTRACT_ASSERT(condition) \ + OIIO_CONTRACT_ASSERT_MSG(condition, #condition) + +// Macros to use to select whether or not to do a contract check, based on the +// hardening level: +// - OIIO_HARDENING_ASSERT_FAST : only checks contract for >= FAST hardening. +// - OIIO_HARDENING_ASSERT_EXTENSIVE : only checks contract for >= EXTENSIVE. +// - OIIO_HARDENING_ASSERT_DEBUG : only checks contract for DEBUG hardening. +#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_FAST +# define OIIO_HARDENING_ASSERT_FAST_MSG(condition, message) \ + OIIO_CONTRACT_ASSERT_MSG(condition, message) +#else +# define OIIO_HARDENING_ASSERT_FAST_MSG(...) (void)0 +#endif + +#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_EXTENSIVE +# define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, message) \ + OIIO_CONTRACT_ASSERT_MSG(condition, message) +#else +# define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(...) (void)0 +#endif + +#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_DEBUG +# define OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, message) \ + OIIO_CONTRACT_ASSERT_MSG(condition, message) +#else +# define OIIO_HARDENING_ASSERT_DEBUG_MSG(...) (void)0 +#endif + +#define OIIO_HARDENING_ASSERT_NONE(condition) \ + OIIO_HARDENING_ASSERT_NONE_MSG(condition, #condition) +#define OIIO_HARDENING_ASSERT_FAST(condition) \ + OIIO_HARDENING_ASSERT_FAST_MSG(condition, #condition) +#define OIIO_HARDENING_ASSERT_EXTENSIVE(condition) \ + OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, #condition) +#define OIIO_HARDENING_ASSERT_DEBUG(condition) \ + OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, #condition) + + +OIIO_NAMESPACE_3_1_BEGIN +// Internal contract assertion handler +OIIO_UTIL_API void +contract_violation_handler(const char* location, const char* function, + const char* msg = ""); +OIIO_NAMESPACE_3_1_END + +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::contract_violation_handler; +#endif +OIIO_NAMESPACE_END + + /// OIIO_ABORT_IF_DEBUG is a call to abort() for debug builds, but does /// nothing for release builds. #ifndef NDEBUG diff --git a/src/include/OpenImageIO/hash.h b/src/include/OpenImageIO/hash.h index 8424869a97..9005040b86 100644 --- a/src/include/OpenImageIO/hash.h +++ b/src/include/OpenImageIO/hash.h @@ -247,8 +247,11 @@ strhash (string_view s) size_t len = s.length(); if (! len) return 0; unsigned int h = 0; - for (size_t i = 0; i < len; ++i) { - h += (unsigned char)(s[i]); + for (auto c : s) { + // Note: by using range for here, instead of looping over indices and + // calling operator[] to get each char, we avoid the bounds checking + // that operator[] does. + h += (unsigned char)(c); h += h << 10; h ^= h >> 6; } diff --git a/src/include/OpenImageIO/image_span.h b/src/include/OpenImageIO/image_span.h index 2684c15504..0ee7d2dcaf 100644 --- a/src/include/OpenImageIO/image_span.h +++ b/src/include/OpenImageIO/image_span.h @@ -271,18 +271,30 @@ template class image_span { /// Return a pointer to the value at channel c, pixel (x,y,z). inline T* getptr(int c, int x, int y, int z = 0) const { - // Bounds check in debug mode - OIIO_DASSERT(unsigned(c) < unsigned(nchannels()) - && unsigned(x) < unsigned(width()) - && unsigned(y) < unsigned(height()) - && unsigned(z) < unsigned(depth())); if constexpr (Rank == 2) { OIIO_DASSERT(y == 0 && z == 0); + OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels()) + && unsigned(x) < unsigned(width())); + return (T*)((char*)data() + c * chanstride()); } else if constexpr (Rank == 3) { OIIO_DASSERT(z == 0); + OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels()) + && unsigned(x) < unsigned(width()) + && unsigned(y) < unsigned(height())); + return (T*)((char*)data() + c * chanstride() + x * xstride() + + y * ystride()); + } else { + // Rank == 4 + OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels()) + && unsigned(x) < unsigned(width()) + && unsigned(y) < unsigned(height()) + && unsigned(z) < unsigned(depth())); + return (T*)((char*)data() + c * chanstride() + x * xstride() + + y * ystride() + z * zstride()); } - return (T*)((char*)data() + c * chanstride() + x * xstride() - + y * ystride() + z * zstride()); +#ifdef __INTEL_COMPILER + return nullptr; // should never get here, but icc is confused +#endif } /// Return a pointer to the value at channel 0, pixel (x,y,z). diff --git a/src/include/OpenImageIO/span.h b/src/include/OpenImageIO/span.h index f1c49dafdc..b184809a75 100644 --- a/src/include/OpenImageIO/span.h +++ b/src/include/OpenImageIO/span.h @@ -207,28 +207,28 @@ class span { /// optimized builds, there is no bounds check. Note: this is different /// from C++ std::span, which never bounds checks `operator[]`. constexpr reference operator[] (size_type idx) const { - OIIO_DASSERT(idx < m_size && "OIIO::span::operator[] range check"); + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[idx]; } constexpr reference operator() (size_type idx) const { - OIIO_DASSERT(idx < m_size && "OIIO::span::operator() range check"); + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[idx]; } /// Bounds-checked access, throws an assertion if out of range. reference at (size_type idx) const { if (idx >= size()) - throw (std::out_of_range ("OpenImageIO::span::at")); + throw (std::out_of_range ("OIIO::span::at")); return m_data[idx]; } /// The first element of the span. constexpr reference front() const noexcept { - OIIO_DASSERT(m_size >= 1); + OIIO_CONTRACT_ASSERT(m_size >= 1); return m_data[0]; } /// The last element of the span. constexpr reference back() const noexcept { - OIIO_DASSERT(m_size >= 1); + OIIO_CONTRACT_ASSERT(m_size >= 1); return m_data[size() - 1]; } @@ -374,14 +374,16 @@ class span_strided { constexpr stride_type stride() const noexcept { return m_stride; } constexpr reference operator[] (size_type idx) const { + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[m_stride*idx]; } constexpr reference operator() (size_type idx) const { + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[m_stride*idx]; } reference at (size_type idx) const { if (idx >= size()) - throw (std::out_of_range ("OpenImageIO::span_strided::at")); + throw (std::out_of_range ("OIIO::span_strided::at")); return m_data[m_stride*idx]; } constexpr reference front() const noexcept { return m_data[0]; } diff --git a/src/include/OpenImageIO/string_view.h b/src/include/OpenImageIO/string_view.h index b959bb9d5d..d07854ceb6 100644 --- a/src/include/OpenImageIO/string_view.h +++ b/src/include/OpenImageIO/string_view.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -205,11 +206,12 @@ class basic_string_view { /// Is the view empty, containing no characters? constexpr bool empty() const noexcept { return m_len == 0; } - /// Element access of an individual character. For debug build, does - /// bounds check with assertion. For optimized builds, there is no bounds - /// check. Note: this is different from C++ std::span, which never bounds - /// checks `operator[]`. - constexpr const_reference operator[](size_type pos) const { return m_chars[pos]; } + /// Element access of an individual character. For debug or hardened + /// builds, does bounds check with assertion. + constexpr const_reference operator[](size_type pos) const { + OIIO_CONTRACT_ASSERT(pos < m_len); + return m_chars[pos]; + } /// Element access with bounds checking and exception if out of bounds. constexpr const_reference at(size_t pos) const { @@ -218,9 +220,15 @@ class basic_string_view { return m_chars[pos]; } /// The first character of the view. - constexpr const_reference front() const { return m_chars[0]; } + constexpr const_reference front() const { + OIIO_CONTRACT_ASSERT(m_len >= 1); + return m_chars[0]; + } /// The last character of the view. - constexpr const_reference back() const { return m_chars[m_len - 1]; } + constexpr const_reference back() const { + OIIO_CONTRACT_ASSERT(m_len >= 1); + return m_chars[m_len - 1]; + } /// Return the underlying data pointer to the first character. constexpr const_pointer data() const noexcept { return m_chars; } diff --git a/src/libutil/errorhandler.cpp b/src/libutil/errorhandler.cpp index d0d1a2c385..417ed2aca8 100644 --- a/src/libutil/errorhandler.cpp +++ b/src/libutil/errorhandler.cpp @@ -58,4 +58,15 @@ ErrorHandler::operator()(int errcode, const std::string& msg) fflush(stderr); } + + +void +contract_violation_handler(const char* location, const char* function, + const char* msg) +{ + Strutil::print(stderr, "{} ({}): Contract assertion failed: {}\n", location, + function, msg ? msg : ""); + fflush(stderr); +} + OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/span_test.cpp b/src/libutil/span_test.cpp index 041f59644b..2e030214e4 100644 --- a/src/libutil/span_test.cpp +++ b/src/libutil/span_test.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include #include @@ -14,6 +16,34 @@ using namespace OIIO; +static int iterations = 100000; +static int ntrials = 5; + +// Intentionally not static so the compiler can't optimize away its value +int Nlen_unknown = 0; + + + +static void +getargs(int argc, char* argv[]) +{ + ArgParse ap; + ap.intro( + "span_test -- unit test and spans for OpenImageIO/span.h\n" OIIO_INTRO_STRING) + .usage("span_test [options]"); + + ap.arg("--iters %d", &iterations) + .help(Strutil::fmt::format("Number of iterations (default: {})", + iterations)); + ap.arg("--trials %d", &ntrials).help("Number of trials"); + + // Fake option to hide from compiler how big it will be + ap.arg("--unknown %d", &Nlen_unknown).hidden(); + + ap.parse_args(argc, (const char**)argv); +} + + void test_span() @@ -457,9 +487,67 @@ test_spanzero() +void +benchmark_span() +{ + Benchmarker bench; + bench.iterations(iterations).trials(ntrials); + const size_t N = 1000; + // bench.work(N); + std::array fstdarr; + std::fill(fstdarr.begin(), fstdarr.end(), 1.0f); + size_t Nlen = Nlen_unknown ? size_t(Nlen_unknown) : N; + bench("pointer operator[]", [&]() { + float* fptr(fstdarr.data()); + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fptr[i]); + }); + bench("std::array operator[]", [&]() { + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fstdarr[i]); + }); + bench("span operator[]", [&]() { + span fspan(fstdarr); + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fspan[i]); + }); + bench("span unsafe indexing", [&]() { + span fspan(fstdarr); + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fspan.data()[i]); + }); + bench("span range", [&]() { + span fspan(fstdarr); + float t = 0.0f; + for (auto x : fspan) + DoNotOptimize(t += x); + }); +} + + + int -main(int /*argc*/, char* /*argv*/[]) +main(int argc, char* argv[]) { + // For the sake of test time, reduce the default number of benchmarking + // trials and iterations for DEBUG, CI, and code coverage builds. Explicit + // use of --iters or --trials will override this, since it comes before + // the getargs() call. + if (Strutil::eval_as_bool(Sysutil::getenv("OpenImageIO_CI")) +#if !defined(NDEBUG) || defined(OIIO_CODE_COVERAGE) + || true +#endif + ) { + iterations /= 10; + ntrials = 1; + } + + getargs(argc, argv); + test_span(); test_span_mutable(); test_span_initlist(); @@ -475,6 +563,7 @@ main(int /*argc*/, char* /*argv*/[]) test_spancpy(); test_spanset(); test_spanzero(); + benchmark_span(); return unit_test_failures; } diff --git a/src/libutil/strutil.cpp b/src/libutil/strutil.cpp index 17060728d7..dc053df65e 100644 --- a/src/libutil/strutil.cpp +++ b/src/libutil/strutil.cpp @@ -152,7 +152,7 @@ c_str(string_view str) // in C++17 string_view. So maybe we'll find ourselves relying on it a // lot less, and therefore the performance hit of doing it foolproof // won't be as onerous. - if (str[str.size()] == 0) // 0-terminated + if (str.data()[str.size()] == 0) // 0-terminated return str.data(); // Rare case: may not be 0-terminated. Bite the bullet and construct a From 97d60cc08ed1a5fa9c8537ec4e50ed0f6431ac08 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 6 Feb 2026 22:10:51 -0800 Subject: [PATCH 113/508] ci: Don't install OpenCV on Mac Intel job variant (#5032) Mac Intel is getting long in the tooth, and quite often the Homebrew packages for Intel are found to be uncached and will try to build from source. When it's OpenCV, that's disastrous for our CI build times, it can get stalled for hours building all of OpenCV and its dependencies. So disable it for that one build variant. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 4 +++- src/build-scripts/install_homebrew_deps.bash | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a227cdbd7b..83cf2b0f3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -628,7 +628,9 @@ jobs: python_ver: "3.13" simd: sse4.2,avx2 ctest_test_timeout: 1200 - setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 + setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 + INSTALL_QT=0 INSTALL_OPENCV=0 + optional_deps_append: 'OpenCV;Qt5;Qt6' benchmark: 1 - desc: MacOS-14-ARM aclang15/C++20/py3.13 runner: macos-14 diff --git a/src/build-scripts/install_homebrew_deps.bash b/src/build-scripts/install_homebrew_deps.bash index 62a433e614..e8cfe2bc59 100755 --- a/src/build-scripts/install_homebrew_deps.bash +++ b/src/build-scripts/install_homebrew_deps.bash @@ -47,7 +47,7 @@ if [[ "$OIIO_BREW_INSTALL_PACKAGES" == "" ]] ; then robin-map \ tbb \ " - if [[ "${USE_OPENCV}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then + if [[ "${USE_OPENCV:=}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then OIIO_BREW_INSTALL_PACKAGES+=" opencv" fi if [[ "${USE_QT:=1}" != "0" ]] && [[ "${INSTALL_QT:=1}" != "0" ]] ; then From 833427f80e3373d1c824f04eca5f4aa784736573 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 7 Feb 2026 03:04:06 -0800 Subject: [PATCH 114/508] fix(bmp): detect corrupt files where palette doesn't match bpp (#5030) Extra protections for corrupted BMP files that claim to be palette images, but have a BPP that doesn't support palette images. Also an extra guard around accessing the palette array if it is empty. Add an extra test case for this kind of corruption. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/bmp.imageio/bmpinput.cpp | 12 ++++++++++-- testsuite/bmp/ref/out.txt | 3 +++ testsuite/bmp/run.py | 1 + testsuite/bmp/src/palette32bit-corrupt.bmp | Bin 0 -> 67 bytes 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 testsuite/bmp/src/palette32bit-corrupt.bmp diff --git a/src/bmp.imageio/bmpinput.cpp b/src/bmp.imageio/bmpinput.cpp index a39d0b2061..ba43d4fc5f 100644 --- a/src/bmp.imageio/bmpinput.cpp +++ b/src/bmp.imageio/bmpinput.cpp @@ -259,6 +259,13 @@ BmpInput::open(const std::string& name, ImageSpec& newspec, case WINDOWS_V5: m_spec.attribute("bmp:version", 5); break; } + if (m_dib_header.cpalete && !m_colortable.size()) { + errorfmt( + "BMP error: bad BPP ({}) for palette image -- presumed corrupt file", + m_dib_header.bpp); + return false; + } + // Default presumption is that a BMP file is meant to look reasonable on a // display, so assume it's sRGB. This is not really correct -- see the // comments below. @@ -391,8 +398,9 @@ BmpInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, size_t scanline_bytes = m_spec.scanline_bytes(); uint8_t* mscanline = (uint8_t*)data; - if (m_dib_header.compression == RLE4_COMPRESSION - || m_dib_header.compression == RLE8_COMPRESSION) { + if ((m_dib_header.compression == RLE4_COMPRESSION + || m_dib_header.compression == RLE8_COMPRESSION) + && m_colortable.size()) { for (int x = 0; x < m_spec.width; ++x) { int p = m_uncompressed[(m_spec.height - 1 - y) * m_spec.width + x]; auto& c = colortable(p); diff --git a/testsuite/bmp/ref/out.txt b/testsuite/bmp/ref/out.txt index 13fc651063..2f7323ea0c 100644 --- a/testsuite/bmp/ref/out.txt +++ b/testsuite/bmp/ref/out.txt @@ -298,3 +298,6 @@ oiiotool ERROR: read : "src/bad-y.bmp": BMP might be corrupted, it is referencin BMP error reading rle-compressed image Full command line was: > oiiotool --info -v -a --hash src/bad-y.bmp +oiiotool ERROR: read : "src/palette32bit-corrupt.bmp": BMP error: bad BPP (32) for palette image -- presumed corrupt file +Full command line was: +> oiiotool --info -v -a --hash src/palette32bit-corrupt.bmp diff --git a/testsuite/bmp/run.py b/testsuite/bmp/run.py index 0e53937bdf..5f49b64682 100755 --- a/testsuite/bmp/run.py +++ b/testsuite/bmp/run.py @@ -33,3 +33,4 @@ # See if we handle these corrupt files with useful error messages command += info_command ("src/decodecolormap-corrupt.bmp") command += info_command ("src/bad-y.bmp") +command += info_command ("src/palette32bit-corrupt.bmp") diff --git a/testsuite/bmp/src/palette32bit-corrupt.bmp b/testsuite/bmp/src/palette32bit-corrupt.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1f140906b4df76395903521430ce90e38010e5f8 GIT binary patch literal 67 zcmZ?rm0@Q912Yx|1`Qyq9*7-)n2|vNh#453F#$<_AcKJ+9mEHMYu7;1Obq`S7}$3H F2LQ9{2f_dV literal 0 HcmV?d00001 From 7e1c195e1f0f82af2bb9152f0adaacf1454156c4 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 10 Feb 2026 17:14:57 -0800 Subject: [PATCH 115/508] fix(tiff): Fix TIFF output crash for multi-count Exif metadata (#5035) Fixes #5023 This was crashing when writing TIFF information that was supposed to be arrays of more than one rational, but in fact was provided as a single value, it was reading past the end of a memory array. I noticed that this whole region needs a cleanup, this is not the only problem. But a full overhaul seems too risky to backport, so my strategy is as follows: * THIS fix first, which I will backport right away to 3.0 and 3.1. * I will then submit a separate PR (already implemented and tested) that is a much more complete fix and overhaul of this portion of the code (and other places). That will get merged into main when approved. * After the second PR is merged, I'll hold it in main for a while to test its safety, and then decide if it seems ok to backport to 3.1 (but definitely not 3.0). Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/tiff.imageio/tiffoutput.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index efac72345b..77657329de 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -1141,16 +1141,16 @@ TIFFOutput::write_exif_data() if (tifftype == TIFF_ASCII) { ok = TIFFSetField(m_tif, tag, *(char**)p.data()); } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) - && p.type() == TypeDesc::SHORT) { + && p.type() == TypeDesc::SHORT && count == 1) { ok = TIFFSetField(m_tif, tag, (int)*(short*)p.data()); } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) - && p.type() == TypeDesc::INT) { + && p.type() == TypeDesc::INT && count == 1) { ok = TIFFSetField(m_tif, tag, *(int*)p.data()); } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && p.type() == TypeDesc::FLOAT) { + && p.type() == TypeDesc::FLOAT && count == 1) { ok = TIFFSetField(m_tif, tag, *(float*)p.data()); } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && p.type() == TypeDesc::DOUBLE) { + && p.type() == TypeDesc::DOUBLE && count == 1) { ok = TIFFSetField(m_tif, tag, *(double*)p.data()); } if (!ok) { From 04d04e0d5617a2f05baee447ebf7643c945d3756 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Tue, 10 Feb 2026 21:06:41 -0800 Subject: [PATCH 116/508] build: Raise fmt auto-build version to 12.1, handle Windows flags (#5039) Bump the version of 'fmt' library that we download and build (if not found) from 10.2 to 12.1. Some other touch-ups in build_fmt.cmake. Also, we have seen that recent fmt versions will fail to compile on MSVC unless using the `/utf-8` compiler flag, so ensure that is used and also passed on to other clients of libOpenImageIO_Util (which expose templates using those headers). Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/build_fmt.cmake | 6 ++---- src/libutil/CMakeLists.txt | 6 ++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cmake/build_fmt.cmake b/src/cmake/build_fmt.cmake index 95a497c70a..2b41502e55 100644 --- a/src/cmake/build_fmt.cmake +++ b/src/cmake/build_fmt.cmake @@ -6,7 +6,7 @@ # fmt by hand! ###################################################################### -set_cache (fmt_BUILD_VERSION 10.2.1 "fmt version for local builds") +set_cache (fmt_BUILD_VERSION 12.1.0 "fmt version for local builds") set (fmt_GIT_REPOSITORY "https://github.com/fmtlib/fmt") set (fmt_GIT_TAG "${fmt_BUILD_VERSION}") # Note: fmt doesn't put "v" in front of version for its git tags @@ -22,8 +22,6 @@ build_dependency_with_cmake(fmt -D FMT_TEST=OFF ) -# Set some things up that we'll need for a subsequent find_package to work -set (fmt_ROOT ${fmt_INSTALL_DIR}) - # Signal to caller that we need to find again at the installed location set (fmt_REFIND TRUE) +set (fmt_VERSION ${fmt_BUILD_VERSION}) diff --git a/src/libutil/CMakeLists.txt b/src/libutil/CMakeLists.txt index 526aa6f023..2a159e449b 100644 --- a/src/libutil/CMakeLists.txt +++ b/src/libutil/CMakeLists.txt @@ -51,6 +51,12 @@ function (setup_oiio_util_library targetname) target_link_options(${targetname} PRIVATE ${${PROJECT_NAME}_link_options}) + if (MSVC AND fmt_VERSION VERSION_GREATER_EQUAL 11.0) + # For MSVC, Unicode support requires compiling with /utf-8, and fmt + # needs this. This line adapted from fmt's CMakeLists.txt file. + target_compile_options(${targetname} PUBLIC $<$,$>:/utf-8>) + endif () + target_include_directories (${targetname} PUBLIC $ From 83f268c6541bc2f49e55a51534ed665328523816 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 11 Feb 2026 16:04:03 -0800 Subject: [PATCH 117/508] fix(tiff): Improve TIFF robustness for non-matching tag/metadata types (#5036) This is a more comprehensive fix for issues discovered in PR #5035. The original problem reported in Issue #5023 was a crash when writing TIFF information that was supposed to be arrays of more than one rational, it was reading past the end of a memory array. #5035 is a minimal, immediate fix to address the crashes. But in the process, I saw a number of ways in which we were dropping metadata on the floor when the types didn't exactly match, but that we *could* handle with automatic conversion. The new cases that we handle with this PR are: * Exif RESOLUTIONUNIT tag is a short, but by convention we store it by the name as a string in OIIO metadata, so we need to convert back to a code (we did so for the main TIFF metadata, but not for Exif in TIFF). * Handle Exif "version" and "flashpixversion" metadata which have unusual encoding in TIFF files (they are 4-character strings, but must be stored in a TIFF tag of type BYTES, not as the usual type ASCII that most strings use. * Handle things that TIFF insists are ASCII but that come to us as metadata that's strings. Easy -- our `ParamValue.get_string()` automatically converts ther things like ints or floats into string representation. * Much more flexibility in automatically converting among the signed and unsigned, 16 and 32 bit, integer types when the metadata in our ImageSpec is integer but not the specific type of integer that TIFF/Exif thinks it should be. This doesn't appear to change the results of anything in our testsuite, but it's possible that some non-TIFF-to-TIFF image conversions that contain Exif data may now do certain type conversions properly instead of just silently dropping the metadata that had non-matching (but reasonably valid) types. Additionally, to do this nicely, I ended up adding a new TypeURational alias in typedesc.h (similar to TypeRational, but the case where both numerator and denominator are unsigned ints). And also fixed a random comment typo I noticed in tiffinput.cpp. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/typedesc.h | 2 + src/libutil/typedesc.cpp | 8 + src/tiff.imageio/tiffinput.cpp | 2 +- src/tiff.imageio/tiffoutput.cpp | 225 ++++++++++++++++++----------- 4 files changed, 152 insertions(+), 85 deletions(-) diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index 5c38ae6209..b546dcd0e0 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -385,6 +385,7 @@ inline constexpr TypeDesc TypeHalf (TypeDesc::HALF); inline constexpr TypeDesc TypeTimeCode (TypeDesc::UINT, TypeDesc::SCALAR, TypeDesc::TIMECODE, 2); inline constexpr TypeDesc TypeKeyCode (TypeDesc::INT, TypeDesc::SCALAR, TypeDesc::KEYCODE, 7); inline constexpr TypeDesc TypeRational(TypeDesc::INT, TypeDesc::VEC2, TypeDesc::RATIONAL); +inline constexpr TypeDesc TypeURational(TypeDesc::UINT, TypeDesc::VEC2, TypeDesc::RATIONAL); inline constexpr TypeDesc TypePointer(TypeDesc::PTR); inline constexpr TypeDesc TypeUstringhash(TypeDesc::USTRINGHASH); @@ -648,6 +649,7 @@ using v3_1::TypeHalf; using v3_1::TypeTimeCode; using v3_1::TypeKeyCode; using v3_1::TypeRational; +using v3_1::TypeURational; using v3_1::TypePointer; using v3_1::TypeUstringhash; #endif diff --git a/src/libutil/typedesc.cpp b/src/libutil/typedesc.cpp index 1c58346a7e..99dc09fed7 100644 --- a/src/libutil/typedesc.cpp +++ b/src/libutil/typedesc.cpp @@ -325,6 +325,8 @@ TypeDesc::fromstring(string_view typestring) t = OIIO::TypeTimeCode; else if (type == "rational") t = OIIO::TypeRational; + else if (type == "urational") + t = OIIO::TypeURational; else if (type == "box2i") t = OIIO::TypeBox2i; else if (type == "box3i") @@ -890,6 +892,12 @@ convert_type(TypeDesc srctype, const void* src, TypeDesc dsttype, void* dst, ((float*)dst)[0] = den ? float(num) / float(den) : 0.0f; return true; } + if (dsttype == TypeFloat && srctype == TypeURational) { + auto num = ((const uint32_t*)src)[0]; + auto den = ((const uint32_t*)src)[1]; + ((float*)dst)[0] = den ? float(num) / float(den) : 0.0f; + return true; + } if (dsttype == TypeFloat && srctype == TypeString) { // Only succeed for a string if it exactly holds something that // exactly parses to a float value. diff --git a/src/tiff.imageio/tiffinput.cpp b/src/tiff.imageio/tiffinput.cpp index ac360d2c32..06d1394424 100644 --- a/src/tiff.imageio/tiffinput.cpp +++ b/src/tiff.imageio/tiffinput.cpp @@ -671,7 +671,7 @@ static std::pair tiff_input_compressions[] = { { COMPRESSION_NEXT, "next" }, // NeXT 2-bit RLE { COMPRESSION_CCITTRLEW, "ccittrle2" }, // #1 w/ word alignment { COMPRESSION_PACKBITS, "packbits" }, // Macintosh RLE - { COMPRESSION_THUNDERSCAN, "thunderscan" }, // ThundeScan RLE + { COMPRESSION_THUNDERSCAN, "thunderscan" }, // ThunderScan RLE { COMPRESSION_IT8CTPAD, "IT8CTPAD" }, // IT8 CT w/ patting { COMPRESSION_IT8LW, "IT8LW" }, // IT8 linework RLE { COMPRESSION_IT8MP, "IT8MP" }, // IT8 monochrome picture diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index 77657329de..5852d7d810 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -149,8 +149,7 @@ class TIFFOutput final : public ImageOutput { void fix_bitdepth(void* data, int nvals); // Add a parameter to the output - bool put_parameter(const std::string& name, TypeDesc type, - const void* data); + bool put_parameter(const ParamValue& metadata); bool write_exif_data(); // Make our best guess about whether the spec is describing data that @@ -921,10 +920,8 @@ TIFFOutput::open(const std::string& name, const ImageSpec& userspec, } // Deal with all other params - for (size_t p = 0; p < m_spec.extra_attribs.size(); ++p) - put_parameter(m_spec.extra_attribs[p].name().string(), - m_spec.extra_attribs[p].type(), - m_spec.extra_attribs[p].data()); + for (const auto& p : m_spec.extra_attribs) + put_parameter(p); if (m_spec.get_int_attribute("tiff:write_iptc")) { // Enable IPTC block writing only if "tiff_write_iptc" hint is explicitly @@ -956,120 +953,125 @@ TIFFOutput::open(const std::string& name, const ImageSpec& userspec, +inline int +resunit_to_code(string_view s) +{ + if (Strutil::iequals(s, "none")) + return RESUNIT_NONE; + else if (Strutil::iequals(s, "in") || Strutil::iequals(s, "inch")) + return RESUNIT_INCH; + else if (Strutil::iequals(s, "cm")) + return RESUNIT_CENTIMETER; + return 0; +} + + + bool -TIFFOutput::put_parameter(const std::string& name, TypeDesc type, - const void* data) +TIFFOutput::put_parameter(const ParamValue& param) { - if (!data || (type == TypeString && *(char**)data == nullptr)) { + ustring name = param.uname(); + TypeDesc type = param.type(); + if (!param.data() + || (type == TypeString && *(char**)param.data() == nullptr)) { // we got a null pointer, don't set the field return false; } - if (Strutil::iequals(name, "Artist") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_ARTIST, *(char**)data); + if (Strutil::iequals(name, "Artist")) { + TIFFSetField(m_tif, TIFFTAG_ARTIST, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "Copyright") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_COPYRIGHT, *(char**)data); + if (Strutil::iequals(name, "Copyright")) { + TIFFSetField(m_tif, TIFFTAG_COPYRIGHT, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "DateTime") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_DATETIME, *(char**)data); + if (Strutil::iequals(name, "DateTime") && type == TypeString) { + TIFFSetField(m_tif, TIFFTAG_DATETIME, param.get_string().c_str()); return true; } - if ((Strutil::iequals(name, "name") - || Strutil::iequals(name, "DocumentName")) - && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_DOCUMENTNAME, *(char**)data); + if (Strutil::iequals(name, "name") + || Strutil::iequals(name, "DocumentName")) { + TIFFSetField(m_tif, TIFFTAG_DOCUMENTNAME, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "fovcot") && type == TypeDesc::FLOAT) { - double d = *(float*)data; - TIFFSetField(m_tif, TIFFTAG_PIXAR_FOVCOT, d); + if (Strutil::iequals(name, "fovcot") && type == TypeFloat) { + TIFFSetField(m_tif, TIFFTAG_PIXAR_FOVCOT, param.get_float()); return true; } - if ((Strutil::iequals(name, "host") - || Strutil::iequals(name, "HostComputer")) - && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_HOSTCOMPUTER, *(char**)data); + if (Strutil::iequals(name, "host") + || Strutil::iequals(name, "HostComputer")) { + TIFFSetField(m_tif, TIFFTAG_HOSTCOMPUTER, param.get_string().c_str()); return true; } if ((Strutil::iequals(name, "description") || Strutil::iequals(name, "ImageDescription")) - && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_IMAGEDESCRIPTION, *(char**)data); + && type == TypeString) { + TIFFSetField(m_tif, TIFFTAG_IMAGEDESCRIPTION, + param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "tiff:Predictor") && type == TypeDesc::INT) { - m_predictor = *(int*)data; + if (Strutil::iequals(name, "tiff:Predictor")) { + m_predictor = param.get_int(); TIFFSetField(m_tif, TIFFTAG_PREDICTOR, m_predictor); return true; } - if (Strutil::iequals(name, "ResolutionUnit") && type == TypeDesc::STRING) { - const char* s = *(char**)data; - bool ok = true; - if (Strutil::iequals(s, "none")) - TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); - else if (Strutil::iequals(s, "in") || Strutil::iequals(s, "inch")) - TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); - else if (Strutil::iequals(s, "cm")) - TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER); - else - ok = false; - return ok; + if (Strutil::iequals(name, "ResolutionUnit") && type == TypeString) { + if (int r = resunit_to_code(param.get_string())) { + TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, r); + return true; + } + return false; } if (Strutil::iequals(name, "tiff:RowsPerStrip") && !m_spec.tile_width /* don't set rps for tiled files */ && m_planarconfig == PLANARCONFIG_CONTIG /* only for contig */) { - if (type == TypeDesc::INT) { - m_rowsperstrip = *(int*)data; - } else if (type == TypeDesc::STRING) { - // Back-compatibility with Entropy and PRMan - m_rowsperstrip = Strutil::stoi(*(char**)data); - } else { + int rps = param.get_int(); + if (rps <= 0) return false; - } - m_rowsperstrip = clamp(m_rowsperstrip, 1, m_spec.height); + m_rowsperstrip = clamp(rps, 1, m_spec.height); TIFFSetField(m_tif, TIFFTAG_ROWSPERSTRIP, m_rowsperstrip); return true; } - if (Strutil::iequals(name, "Make") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_MAKE, *(char**)data); + if (Strutil::iequals(name, "Make")) { + TIFFSetField(m_tif, TIFFTAG_MAKE, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "Model") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_MODEL, *(char**)data); + if (Strutil::iequals(name, "Model")) { + TIFFSetField(m_tif, TIFFTAG_MODEL, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "Software") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_SOFTWARE, *(char**)data); + if (Strutil::iequals(name, "Software")) { + TIFFSetField(m_tif, TIFFTAG_SOFTWARE, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "tiff:SubFileType") && type == TypeDesc::INT) { - TIFFSetField(m_tif, TIFFTAG_SUBFILETYPE, *(int*)data); + if (Strutil::iequals(name, "tiff:SubFileType")) { + TIFFSetField(m_tif, TIFFTAG_SUBFILETYPE, param.get_int()); return true; } - if (Strutil::iequals(name, "textureformat") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_TEXTUREFORMAT, *(char**)data); + if (Strutil::iequals(name, "textureformat")) { + TIFFSetField(m_tif, TIFFTAG_PIXAR_TEXTUREFORMAT, + param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "wrapmodes") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_WRAPMODES, *(char**)data); + if (Strutil::iequals(name, "wrapmodes")) { + TIFFSetField(m_tif, TIFFTAG_PIXAR_WRAPMODES, + param.get_string().c_str()); return true; } if (Strutil::iequals(name, "worldtocamera") && type == TypeMatrix) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA, data); + TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA, param.data()); return true; } if (Strutil::iequals(name, "worldtoscreen") && type == TypeMatrix) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN, data); + TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN, param.data()); return true; } - if (Strutil::iequals(name, "XResolution") && type == TypeDesc::FLOAT) { - TIFFSetField(m_tif, TIFFTAG_XRESOLUTION, *(float*)data); + if (Strutil::iequals(name, "XResolution")) { + TIFFSetField(m_tif, TIFFTAG_XRESOLUTION, param.get_float()); return true; } - if (Strutil::iequals(name, "YResolution") && type == TypeDesc::FLOAT) { - TIFFSetField(m_tif, TIFFTAG_YRESOLUTION, *(float*)data); + if (Strutil::iequals(name, "YResolution")) { + TIFFSetField(m_tif, TIFFTAG_YRESOLUTION, param.get_float()); return true; } return false; @@ -1134,27 +1136,82 @@ TIFFOutput::write_exif_data() int tag, tifftype, count; if (exif_tag_lookup(p.name(), tag, tifftype, count) && tifftype != TIFF_NOTYPE) { + bool ok = false; + bool handled = false; + // Some special cases first if (tag == EXIF_SECURITYCLASSIFICATION || tag == EXIF_IMAGEHISTORY - || tag == EXIF_PHOTOGRAPHICSENSITIVITY) + || tag == EXIF_PHOTOGRAPHICSENSITIVITY) { continue; // libtiff doesn't understand these - bool ok = false; - if (tifftype == TIFF_ASCII) { - ok = TIFFSetField(m_tif, tag, *(char**)p.data()); - } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) - && p.type() == TypeDesc::SHORT && count == 1) { - ok = TIFFSetField(m_tif, tag, (int)*(short*)p.data()); - } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) - && p.type() == TypeDesc::INT && count == 1) { - ok = TIFFSetField(m_tif, tag, *(int*)p.data()); - } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && p.type() == TypeDesc::FLOAT && count == 1) { - ok = TIFFSetField(m_tif, tag, *(float*)p.data()); + } + if (tag == TIFFTAG_RESOLUTIONUNIT && p.type() == TypeString) { + // OIIO stores resolution unit as a string, but libtiff wants + // it as a short code, so we have to convert. + if (int r = resunit_to_code(p.get_string())) { + ok = TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, r); + } + handled = true; + } else if (tag == EXIF_EXIFVERSION || tag == EXIF_FLASHPIXVERSION) { + if (p.type() == TypeString) { + // These tags are a 4-byte array of chars, but we + // allow users to set it as a string. Convert it if needed. + std::string version = p.get_string(); + if (version.size() >= 4) { + ok = TIFFSetField(m_tif, tag, version.c_str()); + } + handled = true; + } else if (p.type() == TypeInt) { + std::string s = Strutil::fmt::format("{:04}", p.get_int()); + if (s.size() == 4) + ok = TIFFSetField(m_tif, tag, s.c_str()); + handled = true; + } + } + // General cases... + else if (tifftype == TIFF_ASCII) { + ok = TIFFSetField(m_tif, tag, p.get_string().c_str()); + handled = true; + } else if (tifftype == TIFF_SHORT || tifftype == TIFF_SSHORT + || tifftype == TIFF_LONG || tifftype == TIFF_SLONG) { + if ((p.type() == TypeInt16 || p.type() == TypeInt32 + || p.type() == TypeUInt16 || p.type() == TypeUInt32) + && count == 1) { + // Passing our kinda-int as TIFF kinda-int + ok = TIFFSetField(m_tif, tag, p.get_int()); + handled = true; + } else if (p.type() == TypeString && count == 1) { + // Passing our string as TIFF kinda-int -- convert as long + // as the string looks like an int. + std::string s = p.get_string(); + if (Strutil::string_is_int(s)) { + int val = Strutil::stoi(s); + ok = TIFFSetField(m_tif, tag, val); + handled = true; + } + } } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && p.type() == TypeDesc::DOUBLE && count == 1) { - ok = TIFFSetField(m_tif, tag, *(double*)p.data()); + && (p.type() == TypeFloat || p.type() == TypeDesc::DOUBLE + || p.type() == TypeUInt16 || p.type() == TypeUInt32 + || p.type() == TypeInt16 || p.type() == TypeInt32 + || p.type() == TypeRational + || p.type() == TypeURational) + && count == 1) { + // If the tag is a rational, there are a number of types we + // can force into that form by converting to and then passing + // a float. + ok = TIFFSetField(m_tif, tag, p.get_float()); + handled = true; + } + if (!handled) { +# ifndef NDEBUG + print("Unhandled EXIF {} ({}) / tag {} tifftype {} count {}\n", + p.name(), p.type(), tag, tifftype, count); +# endif } + // NOTE: We are not handling arrays of values, just scalars. if (!ok) { - // std::cout << "Unhandled EXIF " << p.name() << " " << p.type() << "\n"; + // print( + // "Error handling EXIF {} ({}) / tag {} tifftype {} count {}\n", + // p.name(), p.type(), tag, tifftype, count); } } } From b37c5cb8d4fbba5da9b2a2b5bb88f05ab719c3df Mon Sep 17 00:00:00 2001 From: Lumina Wang <143051772+adskWangl@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:51:37 -0500 Subject: [PATCH 118/508] fix: gamma precision (#5038) This is a PR proposing to keep the gamma precision. In some cases, we need more precise gamma values, while the existing rounding operation loses most of the precision. This change will continue to use rounded values to calculate and store color space information, but retain the original value in the "Gamma" parameter. In addition, it can also tidy up existing code. I've verified with png/exif.png & python-colorconfig tests. No regression is introduced. Signed-off-by: Lumina Wang Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/hdr.imageio/hdrinput.cpp | 5 ----- src/libOpenImageIO/color_ocio.cpp | 18 +++++++++++------- src/png.imageio/png_pvt.h | 5 ----- src/rla.imageio/rlainput.cpp | 5 ----- src/targa.imageio/targainput.cpp | 5 ----- 5 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/hdr.imageio/hdrinput.cpp b/src/hdr.imageio/hdrinput.cpp index 850899d89d..9627b9b3ed 100644 --- a/src/hdr.imageio/hdrinput.cpp +++ b/src/hdr.imageio/hdrinput.cpp @@ -304,12 +304,7 @@ HdrInput::RGBE_ReadHeader() found_FORMAT_line = true; /* LG says no: break; // format found so break out of loop */ } else if (Strutil::parse_values(line, "GAMMA=", span(tempf))) { - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. float g = float(1.0 / tempf); - g = roundf(100.0 * g) / 100.0f; set_colorspace_rec709_gamma(m_spec, g); } else if (Strutil::parse_values(line, "EXPOSURE=", span(tempf))) { diff --git a/src/libOpenImageIO/color_ocio.cpp b/src/libOpenImageIO/color_ocio.cpp index a365cd9b48..b458673840 100644 --- a/src/libOpenImageIO/color_ocio.cpp +++ b/src/libOpenImageIO/color_ocio.cpp @@ -2803,21 +2803,25 @@ ColorConfig::set_colorspace(ImageSpec& spec, string_view colorspace) const void ColorConfig::set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const { - gamma = std::round(gamma * 100.0f) / 100.0f; - if (fabsf(gamma - 1.0f) <= 0.01f) { + // Round gamma to the nearest hundredth to prevent stupid precision choices + // and make it easier for apps to make decisions based on known gamma values. + float g_rounded = std::round(gamma * 100.0f) / 100.0f; + if (fabsf(g_rounded - 1.0f) <= 0.01f) { set_colorspace(spec, "lin_rec709_scene"); - } else if (fabsf(gamma - 1.8f) <= 0.01f) { + } else if (fabsf(g_rounded - 1.8f) <= 0.01f) { set_colorspace(spec, "g18_rec709_scene"); spec.attribute("oiio:Gamma", 1.8f); - } else if (fabsf(gamma - 2.2f) <= 0.01f) { + } else if (fabsf(g_rounded - 2.2f) <= 0.01f) { set_colorspace(spec, "g22_rec709_scene"); spec.attribute("oiio:Gamma", 2.2f); - } else if (fabsf(gamma - 2.4f) <= 0.01f) { + } else if (fabsf(g_rounded - 2.4f) <= 0.01f) { set_colorspace(spec, "g24_rec709_scene"); spec.attribute("oiio:Gamma", 2.4f); } else { - set_colorspace(spec, Strutil::fmt::format("g{}_rec709_scene", - std::lround(gamma * 10.0f))); + set_colorspace(spec, + Strutil::fmt::format("g{}_rec709_scene", + std::lround(g_rounded * 10.0f))); + // Preserve the original gamma value for use in color conversions. spec.attribute("oiio:Gamma", gamma); } } diff --git a/src/png.imageio/png_pvt.h b/src/png.imageio/png_pvt.h index f7b6bbbb62..3631910192 100644 --- a/src/png.imageio/png_pvt.h +++ b/src/png.imageio/png_pvt.h @@ -226,12 +226,7 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type, if (png_get_sRGB(sp, ip, &srgb_intent)) { spec.attribute("oiio:ColorSpace", "srgb_rec709_scene"); } else if (png_get_gAMA(sp, ip, &gamma) && gamma > 0.0) { - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. float g = float(1.0 / gamma); - g = roundf(100.0f * g) / 100.0f; set_colorspace_rec709_gamma(spec, g); } else { // If there's no info at all, assume sRGB. diff --git a/src/rla.imageio/rlainput.cpp b/src/rla.imageio/rlainput.cpp index bd1215dacc..4befaffe2b 100644 --- a/src/rla.imageio/rlainput.cpp +++ b/src/rla.imageio/rlainput.cpp @@ -397,11 +397,6 @@ RLAInput::seek_subimage(int subimage, int miplevel) float gamma = Strutil::from_string(m_rla.Gamma); if (gamma > 0.f) { - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. - gamma = roundf(100.0 * gamma) / 100.0f; set_colorspace_rec709_gamma(m_spec, gamma); } diff --git a/src/targa.imageio/targainput.cpp b/src/targa.imageio/targainput.cpp index 984f772ffa..45453965ea 100644 --- a/src/targa.imageio/targainput.cpp +++ b/src/targa.imageio/targainput.cpp @@ -435,11 +435,6 @@ TGAInput::read_tga2_header() if (bigendian()) swap_endian(&buf.s[0], 2); float gamma = (float)buf.s[0] / (float)buf.s[1]; - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. - gamma = roundf(100.0 * gamma) / 100.0f; set_colorspace_rec709_gamma(m_spec, gamma); } From f86cb5ec1b4508396a28a6774d0fffc91ac5c38d Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 12 Feb 2026 21:29:24 -0800 Subject: [PATCH 119/508] ci: Turn off nightly workflows for user forks (#5042) Also switch to a better idiom for detecting if we're a fork. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/analysis.yml | 2 +- .github/workflows/ci.yml | 11 +++++------ .github/workflows/docs.yml | 2 +- .github/workflows/scorecard.yml | 3 +-- .github/workflows/wheel.yml | 20 +++++++------------- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index db23ee6f55..aaf69defe6 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -52,7 +52,7 @@ jobs: name: "SonarCloud Analysis" # Exclude runs on forks, since only the main org has the SonarCloud # account credentials. - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event.repository.fork == false uses: ./.github/workflows/build-steps.yml # Must let the called steps workflow inherit necessary secrets secrets: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83cf2b0f3e..8947ea5682 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ on: schedule: # Full nightly build - cron: "0 8 * * *" - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' workflow_dispatch: # This allows manual triggering of the workflow from the web @@ -42,7 +41,7 @@ concurrency: jobs: aswf-old: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "(old) ${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -191,7 +190,7 @@ jobs: # Linux Tests using ASWF-docker containers # linux-aswf: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -388,7 +387,7 @@ jobs: # Linux Tests using GHA Ubuntu runners directly # linux-ubuntu: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -580,7 +579,7 @@ jobs: # MacOS Tests # macos: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'linux-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'linux-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -653,7 +652,7 @@ jobs: # Windows Tests # windows: - if: ${{ ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4eb8445841..9d3e3c0da4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -38,7 +38,6 @@ on: schedule: # Full nightly build - cron: "0 8 * * *" - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' workflow_dispatch: # This allows manual triggering of the workflow from the web @@ -53,6 +52,7 @@ concurrency: jobs: docs: name: "Docs / ${{matrix.desc}}" + if: ${{ github.event_name != 'schedule' || github.event.repository.fork == false }} uses: ./.github/workflows/build-steps.yml with: nametag: ${{ matrix.nametag || 'unnamed!' }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 41f18cd349..18c775e87d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -10,7 +10,6 @@ on: push: # Run on pushes to main, but only the official repo, not forks branches: [ "main" ] - if: github.event.pull_request.head.repo.full_name == github.repository pull_request: # Only run on individual PRs if the workflows changed paths: @@ -28,7 +27,7 @@ concurrency: jobs: analysis: name: Scorecards analysis - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event.repository.fork == false runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 84f6145a0d..70eaa59e32 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -59,8 +59,7 @@ jobs: name: Build SDist runs-on: ubuntu-latest if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false steps: @@ -86,8 +85,7 @@ jobs: name: Build wheels on Linux runs-on: ubuntu-latest if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + ${{ github.event_name != 'schedule' || github.event.repository.fork == false }} strategy: matrix: include: @@ -192,8 +190,7 @@ jobs: name: Build wheels on Linux ARM runs-on: ubuntu-24.04-arm if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -294,8 +291,7 @@ jobs: name: Build wheels on macOS runs-on: macos-15-intel if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -383,8 +379,7 @@ jobs: name: Build wheels on macOS ARM runs-on: macos-14 if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -463,8 +458,7 @@ jobs: name: Build wheels on Windows runs-on: windows-2022 if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -522,7 +516,7 @@ jobs: runs-on: ubuntu-latest permissions: id-token: write - if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags/v3.0.') || startsWith(github.event.ref, 'refs/tags/v3.1.')) && github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags/v3.0.') || startsWith(github.event.ref, 'refs/tags/v3.1.')) && github.event.repository.fork == false steps: - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 From 533d39a647cb440084d2212e8317ed28a66fa453 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 13 Feb 2026 21:38:19 +0100 Subject: [PATCH 120/508] feat(heif): Monochrome channel read and write support, fix crash (#5043) Implement support for reading and writing monochrome. Reading requires libheif 1.17+ for heif_image_handle_get_preferred_decoding_colorspace. Previously writing a single channel image would cause an exception due to wrong parameters, but close() would continue writing the image and crash. Destroy m_ctx on exception to prevent that for other potential errors. Test added for monochrome read and write. --------- Signed-off-by: Brecht Van Lommel Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- INSTALL.md | 4 +- src/cmake/externalpackages.cmake | 2 +- src/doc/builtinplugins.rst | 7 + src/heif.imageio/heifinput.cpp | 61 ++++-- src/heif.imageio/heifoutput.cpp | 21 +- testsuite/heif/ref/out-libheif1.12-orient.txt | 14 ++ .../heif/ref/out-libheif1.21-with-av1.txt | 12 ++ testsuite/heif/ref/out-libheif1.21.txt | 196 ++++++++++++++++++ testsuite/heif/ref/out-libheif1.4.txt | 14 ++ testsuite/heif/ref/out-libheif1.5.txt | 14 ++ testsuite/heif/ref/out-libheif1.9-alt2.txt | 14 ++ .../heif/ref/out-libheif1.9-with-av1-alt2.txt | 14 ++ .../heif/ref/out-libheif1.9-with-av1.txt | 14 ++ testsuite/heif/ref/out-libheif1.9.txt | 14 ++ testsuite/heif/run.py | 6 + 15 files changed, 381 insertions(+), 26 deletions(-) create mode 100644 testsuite/heif/ref/out-libheif1.21.txt diff --git a/INSTALL.md b/INSTALL.md index ee0d73eee8..a52378c5ba 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -61,8 +61,8 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for GIF images: * giflib >= 5.0 (tested through 5.2.2) * If you want support for HEIF/HEIC or AVIF images: - * libheif >= 1.11 (1.16 required for correct orientation support, - tested through 1.21.1) + * libheif >= 1.11 (1.16 required for correct orientation support and + 1.17 required for monochrome HEIC support; tested through 1.21.1) * libheif must be built with an AV1 encoder/decoder for AVIF support. * If you want support for DICOM medical image files: * DCMTK >= 3.6.1 (tested through 3.6.9) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 73a07172f6..bcf0ca921c 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -165,7 +165,7 @@ checked_find_package (GIF VERSION_MIN 5.0) checked_find_package (Libheif VERSION_MIN 1.11 PREFER_CONFIG RECOMMEND_MIN 1.16 - RECOMMEND_MIN_REASON "for orientation support") + RECOMMEND_MIN_REASON "1.16 for orientation support, 1.17 for monochrome support") checked_find_package (LibRaw VERSION_MIN 0.20.0 diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 05c66fe352..92c8a5f6bf 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -837,6 +837,13 @@ control aspects of the writing itself: | +**Additional notes and limitations** + +* The underlying libheif dependency must be 1.16 or newer to support the + "oiio:reorient" configuration option and the "heif:Orientation" metadata. +* The underlying libheif dependency must be 1.17 or newer to support + monochrome HEIC images. + .. _sec-bundledplugins-ico: ICO diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp index f4b78aae65..a49350a215 100644 --- a/src/heif.imageio/heifinput.cpp +++ b/src/heif.imageio/heifinput.cpp @@ -262,15 +262,40 @@ HeifInput::seek_subimage(int subimage, int miplevel) } m_has_alpha = m_ihandle.has_alpha_channel(); - auto chroma = m_has_alpha ? (m_bitdepth > 8) - ? littleendian() - ? heif_chroma_interleaved_RRGGBBAA_LE - : heif_chroma_interleaved_RRGGBBAA_BE - : heif_chroma_interleaved_RGBA - : (m_bitdepth > 8) ? littleendian() - ? heif_chroma_interleaved_RRGGBB_LE - : heif_chroma_interleaved_RRGGBB_BE - : heif_chroma_interleaved_RGB; + + bool is_monochrome = false; + +#if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 17, 0, 0) + heif_colorspace preferred_colorspace = heif_colorspace_undefined; + heif_chroma preferred_chroma = heif_chroma_undefined; + + if (heif_image_handle_get_preferred_decoding_colorspace( + m_ihandle.get_raw_image_handle(), &preferred_colorspace, + &preferred_chroma) + .code + == heif_error_Ok) { + is_monochrome = preferred_colorspace == heif_colorspace_monochrome; + } +#endif + + const heif_chroma chroma + = (is_monochrome) ? heif_chroma_monochrome + : m_has_alpha ? (m_bitdepth > 8) + ? littleendian() + ? heif_chroma_interleaved_RRGGBBAA_LE + : heif_chroma_interleaved_RRGGBBAA_BE + : heif_chroma_interleaved_RGBA + : (m_bitdepth > 8) ? littleendian() + ? heif_chroma_interleaved_RRGGBB_LE + : heif_chroma_interleaved_RRGGBB_BE + : heif_chroma_interleaved_RGB; + const heif_colorspace colorspace = is_monochrome + ? heif_colorspace_monochrome + : heif_colorspace_RGB; + const heif_channel channel = is_monochrome ? heif_channel_Y + : heif_channel_interleaved; + const int nchannels = is_monochrome ? 1 : m_has_alpha ? 4 : 3; + #if 0 try { m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma); @@ -290,8 +315,8 @@ HeifInput::seek_subimage(int subimage, int miplevel) // print("Got decoding options version {}\n", options->version); struct heif_image* img_tmp = nullptr; struct heif_error herr = heif_decode_image(m_ihandle.get_raw_image_handle(), - &img_tmp, heif_colorspace_RGB, - chroma, options.get()); + &img_tmp, colorspace, chroma, + options.get()); if (img_tmp) m_himage = heif::Image(img_tmp); if (herr.code != heif_error_Ok || !img_tmp) { @@ -301,9 +326,8 @@ HeifInput::seek_subimage(int subimage, int miplevel) } #endif - m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved), - m_himage.get_height(heif_channel_interleaved), - m_has_alpha ? 4 : 3, + m_spec = ImageSpec(m_himage.get_width(channel), + m_himage.get_height(channel), nchannels, (m_bitdepth > 8) ? TypeUInt16 : TypeUInt8); if (m_bitdepth > 8) { @@ -492,12 +516,13 @@ HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, #else int ystride = 0; #endif + const heif_channel channel = m_spec.nchannels == 1 + ? heif_channel_Y + : heif_channel_interleaved; #if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 20, 2, 0) - const uint8_t* hdata = m_himage.get_plane2(heif_channel_interleaved, - &ystride); + const uint8_t* hdata = m_himage.get_plane2(channel, &ystride); #else - const uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, - &ystride); + const uint8_t* hdata = m_himage.get_plane(channel, &ystride); #endif if (!hdata) { errorfmt("Unknown read error"); diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 8cfa40afd7..2a2fcf2744 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -137,10 +137,16 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, (m_bitdepth == 8) ? heif_chroma_interleaved_RGBA : littleendian() ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE }; - m_himage.create(newspec.width, newspec.height, heif_colorspace_RGB, + const heif_colorspace colorspace = (m_spec.nchannels == 1) + ? heif_colorspace_monochrome + : heif_colorspace_RGB; + const heif_channel channel = (m_spec.nchannels == 1) + ? heif_channel_Y + : heif_channel_interleaved; + + m_himage.create(newspec.width, newspec.height, colorspace, chromas[m_spec.nchannels]); - m_himage.add_plane(heif_channel_interleaved, newspec.width, - newspec.height, m_bitdepth); + m_himage.add_plane(channel, newspec.width, newspec.height, m_bitdepth); auto compqual = m_spec.decode_compression_metadata("", 75); auto extension = Filesystem::extension(m_filename); @@ -153,10 +159,12 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, } catch (const heif::Error& err) { std::string e = err.get_message(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); + m_ctx.reset(); return false; } catch (const std::exception& err) { std::string e = err.what(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); + m_ctx.reset(); return false; } @@ -180,10 +188,13 @@ HeifOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, #else int hystride = 0; #endif + const heif_channel hchannel = (m_spec.nchannels == 1) + ? heif_channel_Y + : heif_channel_interleaved; #if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 20, 2, 0) - uint8_t* hdata = m_himage.get_plane2(heif_channel_interleaved, &hystride); + uint8_t* hdata = m_himage.get_plane2(hchannel, &hystride); #else - uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, &hystride); + uint8_t* hdata = m_himage.get_plane(hchannel, &hystride); #endif hdata += hystride * (y - m_spec.y); if (m_bitdepth == 10 || m_bitdepth == 12) { diff --git a/testsuite/heif/ref/out-libheif1.12-orient.txt b/testsuite/heif/ref/out-libheif1.12-orient.txt index 875e3ac54e..2765da7eda 100644 --- a/testsuite/heif/ref/out-libheif1.12-orient.txt +++ b/testsuite/heif/ref/out-libheif1.12-orient.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.21-with-av1.txt b/testsuite/heif/ref/out-libheif1.21-with-av1.txt index b22dcca2c5..7843a32b31 100644 --- a/testsuite/heif/ref/out-libheif1.21-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.21-with-av1.txt @@ -162,3 +162,15 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.21.txt b/testsuite/heif/ref/out-libheif1.21.txt new file mode 100644 index 0000000000..5a89c9e2ae --- /dev/null +++ b/testsuite/heif/ref/out-libheif1.21.txt @@ -0,0 +1,196 @@ +Reading ref/IMG_7702_small.heic +ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif + SHA-1: 2380C124F8338910013FEA75C9C64C23567A3156 + channel list: R, G, B + DateTime: "2019:01:21 16:10:54" + ExposureTime: 0.030303 + FNumber: 1.8 + Make: "Apple" + Model: "iPhone 7" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "12.1.2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69599 (f/1.8) + Exif:BrightnessValue: 3.99501 + Exif:ColorSpace: 65535 + Exif:DateTimeDigitized: "2019:01:21 16:10:54" + Exif:DateTimeOriginal: "2019:01:21 16:10:54" + Exif:ExifVersion: "0221" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 24 (no flash, auto flash) + Exif:FlashPixVersion: "0100" + Exif:FocalLength: 3.99 (3.99 mm) + Exif:FocalLengthIn35mmFilm: 28 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 7 back camera 3.99mm f/1.8" + Exif:LensSpecification: 3.99, 3.99, 1.8, 1.8 + Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 20 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SceneCaptureType: 0 (standard) + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 5.03599 (1/32 s) + Exif:SubsecTimeDigitized: "006" + Exif:SubsecTimeOriginal: "006" + Exif:WhiteBalance: 0 (auto) + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" +Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic +../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif + SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C + channel list: R, G, B + DateTime: "2023:09:28 09:44:03" + ExposureTime: 0.0135135 + FNumber: 2.4 + Make: "Apple" + Model: "iPhone 12 Pro" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "16.7" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 2.52607 (f/2.4) + Exif:BrightnessValue: 2.7506 + Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 + Exif:DateTimeDigitized: "2023:09:28 09:44:03" + Exif:DateTimeOriginal: "2023:09:28 09:44:03" + Exif:DigitalZoomRatio: 1.3057 + Exif:ExifVersion: "0232" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 16 (no flash, flash suppression) + Exif:FocalLength: 1.54 (1.54 mm) + Exif:FocalLengthIn35mmFilm: 17 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 12 Pro back triple camera 1.54mm f/2.4" + Exif:LensSpecification: 1.54, 6, 1.6, 2.4 + Exif:MeteringMode: 5 (pattern) + Exif:OffsetTime: "+02:00" + Exif:OffsetTimeDigitized: "+02:00" + Exif:OffsetTimeOriginal: "+02:00" + Exif:PhotographicSensitivity: 320 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 6.20983 (1/74 s) + Exif:SubsecTimeDigitized: "886" + Exif:SubsecTimeOriginal: "886" + Exif:WhiteBalance: 0 (auto) + GPS:Altitude: 3.24105 (3.24105 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:DateStamp: "2023:09:28" + GPS:DestBearing: 90.2729 + GPS:DestBearingRef: "T" (true north) + GPS:HPositioningError: 5.1893 + GPS:ImgDirection: 90.2729 + GPS:ImgDirectionRef: "T" (true north) + GPS:Latitude: 41, 50, 58.43 + GPS:LatitudeRef: "N" + GPS:Longitude: 3, 7, 31.98 + GPS:LongitudeRef: "E" + GPS:Speed: 0.171966 + GPS:SpeedRef: "K" (km/hour) + oiio:ColorSpace: "srgb_rec709_scene" + oiio:OriginalOrientation: 6 +Reading ../oiio-images/heif/sewing-threads.heic +../oiio-images/heif/sewing-threads.heic : 4000 x 3000, 3 channel, uint8 heif + SHA-1: 44551A0A8AADD2C71B504681F2BAE3F7863EF9B9 + channel list: R, G, B + DateTime: "2023:12:12 18:39:16" + ExposureTime: 0.04 + FNumber: 1.8 + Make: "samsung" + Model: "SM-A326B" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "A326BXXS8CWK2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69 (f/1.8) + Exif:BrightnessValue: 1.19 + Exif:ColorSpace: 1 + Exif:DateTimeDigitized: "2023:12:12 18:39:16" + Exif:DateTimeOriginal: "2023:12:12 18:39:16" + Exif:DigitalZoomRatio: 1 + Exif:ExifVersion: "0220" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 0 (no flash) + Exif:FocalLength: 4.6 (4.6 mm) + Exif:FocalLengthIn35mmFilm: 25 + Exif:MaxApertureValue: 1.69 (f/1.8) + Exif:MeteringMode: 2 (center-weighted average) + Exif:OffsetTime: "+01:00" + Exif:OffsetTimeOriginal: "+01:00" + Exif:PhotographicSensitivity: 500 + Exif:PixelXDimension: 4000 + Exif:PixelYDimension: 3000 + Exif:SceneCaptureType: 0 (standard) + Exif:ShutterSpeedValue: 0.04 (1/1 s) + Exif:SubsecTime: "576" + Exif:SubsecTimeDigitized: "576" + Exif:SubsecTimeOriginal: "576" + Exif:WhiteBalance: 0 (auto) + Exif:YCbCrPositioning: 1 + GPS:Altitude: 292 (292 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:Latitude: 41, 43, 33.821 + GPS:LatitudeRef: "N" + GPS:Longitude: 1, 49, 34.0187 + GPS:LongitudeRef: "E" + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.4.txt b/testsuite/heif/ref/out-libheif1.4.txt index 9d8304f14a..457e6045f2 100644 --- a/testsuite/heif/ref/out-libheif1.4.txt +++ b/testsuite/heif/ref/out-libheif1.4.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.5.txt b/testsuite/heif/ref/out-libheif1.5.txt index 9dfd4ff23d..b3dbaf12a3 100644 --- a/testsuite/heif/ref/out-libheif1.5.txt +++ b/testsuite/heif/ref/out-libheif1.5.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-alt2.txt b/testsuite/heif/ref/out-libheif1.9-alt2.txt index f6448d4836..a36c6b8a63 100644 --- a/testsuite/heif/ref/out-libheif1.9-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-alt2.txt @@ -146,3 +146,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt index c938a6fe73..36ca82c6f4 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1.txt b/testsuite/heif/ref/out-libheif1.9-with-av1.txt index f6d7ca55a5..bf56e7c97b 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9.txt b/testsuite/heif/ref/out-libheif1.9.txt index 2778c33493..0772246c63 100644 --- a/testsuite/heif/ref/out-libheif1.9.txt +++ b/testsuite/heif/ref/out-libheif1.9.txt @@ -146,3 +146,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/run.py b/testsuite/heif/run.py index 0a3250e374..5d5945d870 100755 --- a/testsuite/heif/run.py +++ b/testsuite/heif/run.py @@ -22,5 +22,11 @@ for f in files: command = command + info_command (os.path.join(OIIO_TESTSUITE_IMAGEDIR, f)) +command += oiiotool("--pattern checker:color1=1:color2=0 64x64 1 -o mono-8bit.avif") +command += info_command("mono-8bit.avif", safematch=True) + +command += oiiotool("--pattern checker:color1=1:color2=0 64x64 1 -d uint10 -o mono-10bit.avif") +command += info_command("mono-10bit.avif", safematch=True) + # avif conversion is expected to fail if libheif is built without AV1 support failureok = 1 From ca4ba2797f0153b32d8da3f28dfdc844f54a499e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 14 Feb 2026 04:59:46 -0800 Subject: [PATCH 121/508] fix(tiff): Correctly read TIFF EXIF fields for ExifVersion and FlashPixVersion (#5045) This allows us to correctly read the ExifVersion and FlashPixVersion metadata in an EXIF block of a TIFF file. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/libOpenImageIO/exif.cpp | 10 +++-- src/tiff.imageio/tiffinput.cpp | 45 ++++++++++++++++++--- src/tiff.imageio/tiffoutput.cpp | 10 +++-- testsuite/tiff-suite/ref/out-alt.txt | 4 ++ testsuite/tiff-suite/ref/out-alt2.txt | 4 ++ testsuite/tiff-suite/ref/out-jpeg9b.txt | 4 ++ testsuite/tiff-suite/ref/out-jpeg9d-alt.txt | 4 ++ testsuite/tiff-suite/ref/out.txt | 4 ++ 8 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index ab95d881ef..c418dec1a0 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -260,13 +260,15 @@ print_dir_entry(std::ostream& out, const TagMap& tagmap, switch (dir.tdir_type) { case TIFF_ASCII: +# ifdef EXIF_TIFF_UTF8 case EXIF_TIFF_UTF8: +# endif OIIO::print(out, "'{}'", string_view(mydata, dir.tdir_count)); break; case TIFF_RATIONAL: { const unsigned int* u = (unsigned int*)mydata; for (size_t i = 0; i < dir.tdir_count; ++i) - OIIO::print(out, "{}/{} = {} ", u[2 * i], << u[2 * i + 1], + OIIO::print(out, "{}/{} = {} ", u[2 * i], u[2 * i + 1], (double)u[2 * i] / (double)u[2 * i + 1]); } break; case TIFF_SRATIONAL: { @@ -449,7 +451,7 @@ static const TagInfo exif_tag_table[] = { { EXIF_SPECTRALSENSITIVITY,"Exif:SpectralSensitivity", TIFF_ASCII, 0 }, { EXIF_ISOSPEEDRATINGS, "Exif:ISOSpeedRatings", TIFF_SHORT, 1 }, { EXIF_OECF, "Exif:OECF", TIFF_NOTYPE, 1 }, // skip it - { EXIF_EXIFVERSION, "Exif:ExifVersion", TIFF_UNDEFINED, 1, version4char_handler }, // skip it + { EXIF_EXIFVERSION, "Exif:ExifVersion", TIFF_UNDEFINED, 1, version4char_handler }, { EXIF_DATETIMEORIGINAL, "Exif:DateTimeOriginal", TIFF_ASCII, 0 }, { EXIF_DATETIMEDIGITIZED,"Exif:DateTimeDigitized", TIFF_ASCII, 0 }, { EXIF_OFFSETTIME,"Exif:OffsetTime", TIFF_ASCII, 0 }, @@ -475,7 +477,7 @@ static const TagInfo exif_tag_table[] = { { EXIF_SUBSECTIME, "Exif:SubsecTime", TIFF_ASCII, 0 }, { EXIF_SUBSECTIMEORIGINAL,"Exif:SubsecTimeOriginal", TIFF_ASCII, 0 }, { EXIF_SUBSECTIMEDIGITIZED,"Exif:SubsecTimeDigitized", TIFF_ASCII, 0 }, - { EXIF_FLASHPIXVERSION, "Exif:FlashPixVersion", TIFF_UNDEFINED, 1, version4char_handler }, // skip "Exif:FlashPixVesion", TIFF_NOTYPE, 1 }, + { EXIF_FLASHPIXVERSION, "Exif:FlashPixVersion", TIFF_UNDEFINED, 1, version4char_handler }, { EXIF_COLORSPACE, "Exif:ColorSpace", TIFF_SHORT, 1 }, { EXIF_PIXELXDIMENSION, "Exif:PixelXDimension", TIFF_LONG, 1 }, { EXIF_PIXELYDIMENSION, "Exif:PixelYDimension", TIFF_LONG, 1 }, @@ -1202,7 +1204,7 @@ decode_exif(cspan exif, ImageSpec& spec) #if DEBUG_EXIF_READ std::cerr << "Exif dump:\n"; - for (size_t i = 0; i < std::min(200L, exif.size()); ++i) { + for (size_t i = 0; i < std::min(200UL, exif.size()); ++i) { if ((i % 16) == 0) std::cerr << "[" << i << "] "; if (exif[i] >= ' ') diff --git a/src/tiff.imageio/tiffinput.cpp b/src/tiff.imageio/tiffinput.cpp index 06d1394424..8233de9284 100644 --- a/src/tiff.imageio/tiffinput.cpp +++ b/src/tiff.imageio/tiffinput.cpp @@ -280,7 +280,8 @@ class TIFFInput final : public ImageInput { OIIO_NODISCARD bool safe_tiffgetfield(string_view name OIIO_MAYBE_UNUSED, int tag, - TypeDesc expected, void* dest) + TypeDesc expected, void* dest, + const uint32_t* count = nullptr) { TypeDesc type = tiffgetfieldtype(tag); // Caller expects a specific type and the tag doesn't match? Punt. @@ -295,6 +296,11 @@ class TIFFInput final : public ImageInput { int readcount = TIFFFieldReadCount(field); if (!passcount && readcount > 0) { return TIFFGetField(m_tif, tag, dest); + } else if (passcount && readcount <= 0) { + uint32_t mycount = 0; + if (!count) + count = &mycount; + return TIFFGetField(m_tif, tag, count, dest); } // OIIO::debugfmt(" stgf {} tag {} {} datatype {} passcount {} readcount {}\n", // name, tag, type, int(TIFFFieldDataType(field)), passcount, readcount); @@ -396,21 +402,48 @@ class TIFFInput final : public ImageInput { // add it in the obvious way to m_spec under the name 'oiioname'. void find_tag(int tifftag, TIFFDataType tifftype, string_view oiioname) { + if (tifftype == TIFF_NOTYPE) + return; // NOTYPE is a signal that should skip it auto info = find_field(tifftag, tifftype); if (!info) { // Something has gone wrong, libtiff doesn't think the field type // is the same as we do. return; } - if (tifftype == TIFF_ASCII) + tifftype = TIFFFieldDataType(info); + int count = TIFFFieldReadCount(info); + if (tifftype == TIFF_ASCII) { get_string_attribute(oiioname, tifftag); - else if (tifftype == TIFF_SHORT) + return; + } else if (tifftype == TIFF_SHORT) { get_short_attribute(oiioname, tifftag); - else if (tifftype == TIFF_LONG) + return; + } else if (tifftype == TIFF_LONG) { get_int_attribute(oiioname, tifftag); - else if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL - || tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) + return; + } else if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL + || tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) { get_float_attribute(oiioname, tifftag); + return; + } + // special cases follow + if (tifftype == TIFF_UNDEFINED) { + if ((tifftag == EXIF_EXIFVERSION || tifftag == EXIF_FLASHPIXVERSION) + && count == 4) { + char* ptr = nullptr; + if (safe_tiffgetfield(oiioname, tifftag, TypeUnknown, &ptr) + && ptr && ptr[0]) { + std::string str(ptr, 4); + m_spec.attribute(oiioname, str); + } + return; + } + } +#if 0 + print("Unhandled TIFF tag {} type {} count {} pass {} for {}\n", + tifftag, int(tifftype), count, TIFFFieldPassCount(info), + oiioname); +#endif } // If we're at scanline y, where does the next strip start? diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index 5852d7d810..eb0291f1f1 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -1202,16 +1202,18 @@ TIFFOutput::write_exif_data() handled = true; } if (!handled) { -# ifndef NDEBUG +# if 0 print("Unhandled EXIF {} ({}) / tag {} tifftype {} count {}\n", p.name(), p.type(), tag, tifftype, count); # endif } // NOTE: We are not handling arrays of values, just scalars. if (!ok) { - // print( - // "Error handling EXIF {} ({}) / tag {} tifftype {} count {}\n", - // p.name(), p.type(), tag, tifftype, count); +# if 0 + print( + "Error handling EXIF {} ({}) / tag {} tifftype {} count {}\n", + p.name(), p.type(), tag, tifftype, count); +# endif } } } diff --git a/testsuite/tiff-suite/ref/out-alt.txt b/testsuite/tiff-suite/ref/out-alt.txt index 20a7ce2efb..5a65b8c5b9 100644 --- a/testsuite/tiff-suite/ref/out-alt.txt +++ b/testsuite/tiff-suite/ref/out-alt.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out-alt2.txt b/testsuite/tiff-suite/ref/out-alt2.txt index 3a14b41bc5..ad3878cc23 100644 --- a/testsuite/tiff-suite/ref/out-alt2.txt +++ b/testsuite/tiff-suite/ref/out-alt2.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out-jpeg9b.txt b/testsuite/tiff-suite/ref/out-jpeg9b.txt index 354a70c52e..494a935621 100644 --- a/testsuite/tiff-suite/ref/out-jpeg9b.txt +++ b/testsuite/tiff-suite/ref/out-jpeg9b.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt b/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt index fb5da67d19..423c4a2218 100644 --- a/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt +++ b/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out.txt b/testsuite/tiff-suite/ref/out.txt index 30a318664a..a28ac71677 100644 --- a/testsuite/tiff-suite/ref/out.txt +++ b/testsuite/tiff-suite/ref/out.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) From 1467ce8eced3cf58cabbaa4a5c65a84f9842ff47 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 19 Feb 2026 10:28:06 -0800 Subject: [PATCH 122/508] fix(oiiotool): Fix expression BOTTOM when there are exactly two images (#5046) Fixes #5044 Oops, the logic was a little mixed up when there were exactly two images. One reason that this was a special case is that conceptually, there is just a stack, but the implementation is that there is a separate variable for the top item, and then the actual stack is all the other items. Also add more thorough testing of TOP/BOTTOM, including what happens for 2, 1, and also 0 items on the image stack (errors in that last case). Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/oiiotool/expressions.cpp | 2 +- testsuite/oiiotool-control/ref/out.txt | 16 +++++++++++++++- testsuite/oiiotool-control/run.py | 19 ++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/oiiotool/expressions.cpp b/src/oiiotool/expressions.cpp index fbb8568666..edc68fa88f 100644 --- a/src/oiiotool/expressions.cpp +++ b/src/oiiotool/expressions.cpp @@ -156,7 +156,7 @@ Oiiotool::express_parse_atom(const string_view expr, string_view& s, if (Strutil::parse_prefix(s, "TOP")) { img = curimg; } else if (Strutil::parse_prefix(s, "BOTTOM")) { - img = (image_stack.size() <= 1) ? curimg : image_stack[0]; + img = image_stack.empty() ? curimg : image_stack[0]; } else if (Strutil::parse_prefix(s, "IMG[")) { std::string until_bracket = Strutil::parse_until(s, "]"); if (until_bracket.empty() || !Strutil::parse_char(s, ']')) { diff --git a/testsuite/oiiotool-control/ref/out.txt b/testsuite/oiiotool-control/ref/out.txt index 999e3d598b..c8fed296cf 100644 --- a/testsuite/oiiotool-control/ref/out.txt +++ b/testsuite/oiiotool-control/ref/out.txt @@ -1,5 +1,19 @@ -Stack holds [0] = d.tif, [1] = c.tif, [2] = b.tif +Stack holds [0] = d.tif, [1] = c.tif, [2] = b.tif , [3] = a.tif TOP = d.tif, BOTTOM = a.tif +Stack holds [0] = b.tif, [1] = a.tif +TOP = b.tif, BOTTOM = a.tif +Stack holds [0] = a.tif +TOP = a.tif, BOTTOM = a.tif +Stack is empty +oiiotool ERROR: expression : not a valid image at char 4 of 'TOP.filename' +Full command line was: +> oiiotool --echo "Stack is empty" --echo "TOP = {TOP.filename}" +TOP = TOP.filename +Stack is empty +oiiotool ERROR: expression : not a valid image at char 7 of 'BOTTOM.filename' +Full command line was: +> oiiotool --echo "Stack is empty" --echo "BOTTOM = {BOTTOM.filename}" +BOTTOM = BOTTOM.filename Stack bottom to top: a.tif b.tif diff --git a/testsuite/oiiotool-control/run.py b/testsuite/oiiotool-control/run.py index 51c9a03f1b..c165df5fed 100755 --- a/testsuite/oiiotool-control/run.py +++ b/testsuite/oiiotool-control/run.py @@ -23,9 +23,26 @@ # Test TOP, BOTTOM, IMG[] # TOP should be c.tif, BOTTOM should be a.tif command += oiiotool ("a.tif b.tif c.tif d.tif " + - "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}, [2] = {IMG[2].filename}\" " + + "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}, [2] = {IMG[2].filename} , [3] = {IMG[3].filename}\" " + + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " + ) +# Regression test (Issue #5044): make sure BOTTOM works correctly for 0-2 images +command += oiiotool ("a.tif b.tif " + + "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}\" " + + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " + ) +command += oiiotool ("a.tif " + + "--echo \"Stack holds [0] = {IMG[0].filename}\" " + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " ) +# Empty -- should get an error about TOP and BOTTOM not being available +command += oiiotool ("--echo \"Stack is empty\" " + + "--echo \"TOP = {TOP.filename}\" " + ) +command += oiiotool ("--echo \"Stack is empty\" " + + "--echo \"BOTTOM = {BOTTOM.filename}\" " + ) + # Test --pop, --popbottom, --stackreverse, --stackclear, --stackextract command += oiiotool ( "a.tif b.tif c.tif d.tif " From 36f9d9dbf6246c1869fd6d89df7969b05a01aecb Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 19 Feb 2026 22:47:59 -0800 Subject: [PATCH 123/508] build: self-builder logic fixes for deep vs shallow clones (#5034) * cmake utility build_dependency_with_cmake was unconditionally doing a shallow clone and using `clone -b`, but that only works if it's got a branch or tag name, not if it has a commit hash. So change the logic so it does a shallow clone only if GIT_TAG is specified but GIT_COMMIT is not. * pybind11 self-builder is modified to allow a git commit override. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/build_pybind11.cmake | 7 +++++-- src/cmake/dependency_utils.cmake | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/cmake/build_pybind11.cmake b/src/cmake/build_pybind11.cmake index e7a6f3e282..b6c827f764 100644 --- a/src/cmake/build_pybind11.cmake +++ b/src/cmake/build_pybind11.cmake @@ -8,9 +8,12 @@ set_cache (pybind11_BUILD_VERSION 3.0.1 "pybind11 version for local builds") set (pybind11_GIT_REPOSITORY "https://github.com/pybind/pybind11") -set (pybind11_GIT_TAG "v${pybind11_BUILD_VERSION}") +set_cache (pybind11_GIT_TAG "v${pybind11_BUILD_VERSION}" + "pybind11 git tag to checkout") +set_cache (pybind11_GIT_COMMIT "" + "pybind11 specific commit to checkout (overrides tag if set)") set_cache (pybind11_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} - DOC "Should a local pybind11 build, if necessary, build shared libraries" ADVANCED) + "Should a local pybind11 build, if necessary, build shared libraries" ADVANCED) string (MAKE_C_IDENTIFIER ${pybind11_BUILD_VERSION} pybind11_VERSION_IDENT) diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 25a1172555..c6468663c1 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -609,7 +609,7 @@ macro (build_dependency_with_cmake pkgname) # noValueKeywords: "NOINSTALL" # singleValueKeywords: - "GIT_REPOSITORY;GIT_TAG;GIT_COMMIT;VERSION;SOURCE_SUBDIR;GIT_SHALLOW;QUIET" + "GIT_REPOSITORY;GIT_TAG;GIT_COMMIT;VERSION;SOURCE_SUBDIR;QUIET" # multiValueKeywords: "CMAKE_ARGS" # argsToParse: @@ -629,8 +629,10 @@ macro (build_dependency_with_cmake pkgname) unset (${pkgname}_GIT_CLONE_ARGS) unset (_pkg_exec_quiet) - if (_pkg_GIT_SHALLOW OR "${_pkg_GIT_SHALLOW}" STREQUAL "") - list (APPEND ${pkgname}_GIT_CLONE_ARGS --depth 1) + if (NOT "${pkg_GIT_TAG}" STREQUAL "" AND "${_pkg_GIT_COMMIT}" STREQUAL "") + # If a tag was specified, but not a specific commit, do a shallow + # clone. + list (APPEND ${pkgname}_GIT_CLONE_ARGS -b ${pkg_GIT_TAG} --depth 1) endif () if (_pkg_QUIET OR "${_pkg_QUIET}" STREQUAL "") list (APPEND ${pkgname}_GIT_CLONE_ARGS -q ERROR_VARIABLE ${pkgname}_clone_errors) @@ -641,12 +643,10 @@ macro (build_dependency_with_cmake pkgname) find_package (Git REQUIRED) if (NOT IS_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR}) message (STATUS "COMMAND ${GIT_EXECUTABLE} clone ${_pkg_GIT_REPOSITORY} " - "-b ${_pkg_GIT_TAG} " "${${pkgname}_LOCAL_SOURCE_DIR} " "${${pkgname}_GIT_CLONE_ARGS} " "${_pkg_exec_quiet}") execute_process(COMMAND ${GIT_EXECUTABLE} clone ${_pkg_GIT_REPOSITORY} - -b ${_pkg_GIT_TAG} ${${pkgname}_LOCAL_SOURCE_DIR} ${${pkgname}_GIT_CLONE_ARGS} ${_pkg_exec_quiet}) From 86627521f9a07d2a009a58d39163845ff947b359 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 21 Feb 2026 14:06:07 -0800 Subject: [PATCH 124/508] test: imageinout_test: add benchmark of read and write speed vs tile size (#5037) For various tile sizes (and scanline), benchmark how long it takes to read and write a 4k x 2k image. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 3 + src/libOpenImageIO/imageinout_test.cpp | 96 +++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8947ea5682..00d8d1548e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -316,6 +316,7 @@ jobs: container: aswf/ci-oiio:2025 cxx_std: 17 build_type: Debug + ctest_test_timeout: "240" python_ver: "3.11" simd: "avx2,f16c" fmt_ver: 11.2.0 @@ -697,6 +698,7 @@ jobs: vsver: 2022 generator: "Visual Studio 17 2022" python_ver: "3.12" + ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - desc: Windows-2025 VS2022 runner: windows-2025 @@ -704,5 +706,6 @@ jobs: vsver: 2022 generator: "Visual Studio 17 2022" python_ver: "3.12" + ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 benchmark: 1 diff --git a/src/libOpenImageIO/imageinout_test.cpp b/src/libOpenImageIO/imageinout_test.cpp index bcf4b072b8..98cf25cc49 100644 --- a/src/libOpenImageIO/imageinout_test.cpp +++ b/src/libOpenImageIO/imageinout_test.cpp @@ -101,8 +101,8 @@ make_test_image(string_view formatname) static bool checked_write(ImageOutput* out, string_view filename, const ImageSpec& spec, - TypeDesc type, const void* data, bool do_asserts = true, - std::string* errmsg = nullptr, + TypeDesc type, image_span data, + bool do_asserts = true, std::string* errmsg = nullptr, Filesystem::IOProxy* ioproxy = nullptr) { if (errmsg) @@ -117,7 +117,7 @@ checked_write(ImageOutput* out, string_view filename, const ImageSpec& spec, if (errmsg) *errmsg = OIIO::geterror(); else - std::cout << " " << OIIO::geterror() << "\n"; + print(" {}\n", OIIO::geterror()); return false; } @@ -131,19 +131,27 @@ checked_write(ImageOutput* out, string_view filename, const ImageSpec& spec, static bool checked_read(ImageInput* in, string_view filename, - std::vector& data, bool already_opened = false, - bool do_asserts = true, std::string* errmsg = nullptr) + std::vector& data, TypeDesc datatype = TypeFloat, + bool already_opened = false, bool do_asserts = true, + std::string* errmsg = nullptr) { if (errmsg) *errmsg = ""; + std::unique_ptr in_local; + if (!in) { + in_local = ImageInput::create(filename); + in = in_local.get(); + already_opened = false; + } + OIIO_CHECK_ASSERT(in && "Failed to create input"); if (!already_opened) { ImageSpec spec; CHECKED(in, open(filename, spec)); } data.resize(in->spec().image_pixels() * in->spec().nchannels - * sizeof(float)); + * datatype.size()); CHECKED(in, - read_image(0, 0, 0, in->spec().nchannels, TypeFloat, data.data())); + read_image(0, 0, 0, in->spec().nchannels, datatype, data.data())); CHECKED(in, close()); return true; } @@ -164,7 +172,8 @@ test_write_proxy(string_view formatname, string_view extension, // Use ImageOutput.write_image interface to write to outproxy Filesystem::IOVecOutput outproxy; ok = checked_write(nullptr, disk_filename, buf.spec(), buf.spec().format, - buf.localpixels(), true, nullptr, &outproxy); + buf.localpixels_as_writable_byte_image_span(), true, + nullptr, &outproxy); // Use ImageBuf.write interface to write to outproxybuf Filesystem::IOVecOutput outproxybuf; @@ -281,7 +290,7 @@ test_read_proxy(string_view formatname, string_view extension, OIIO_CHECK_ASSERT(in && "Failed to open input with proxy"); if (in) { std::vector readpixels; - ok &= checked_read(in.get(), memname, readpixels, true); + ok &= checked_read(in.get(), memname, readpixels, TypeFloat, true); OIIO_ASSERT(readpixels.size() == nvalues * sizeof(float)); ok &= test_pixel_match({ (const float*)readpixels.data(), nvalues }, { (const float*)buf.localpixels(), nvalues }, @@ -331,7 +340,8 @@ test_write_unwritable(string_view extension, const ImageBuf& buf) if (badout) { std::string errmsg; ok = checked_write(badout.get(), bad_filename, buf.spec(), - buf.spec().format, buf.localpixels(), + buf.spec().format, + buf.localpixels_as_byte_image_span(), /*do_asserts=*/false, &errmsg); if (!ok) std::cout << term.ansi("green", "OK") << " (" @@ -391,7 +401,7 @@ test_all_formats() std::cout << " Writing " << filename << " ... "; ok = checked_write(out.get(), filename, buf.spec(), buf.spec().format, - orig_pixels); + buf.localpixels_as_writable_byte_image_span()); if (ok) std::cout << term.ansi("green", "OK\n"); @@ -531,6 +541,68 @@ test_read_tricky_sizes() +void +benchmark_tile_sizes(string_view extension, TypeDesc datatype, + int tilestart = 4) +{ + const int test_res = 4096; + std::vector tile_sizes; + for (int ts = tilestart; ts <= test_res / 2; ts *= 2) + tile_sizes.push_back(ts); + ImageSpec test_image_spec(4096, 2048, 4, datatype); + ImageBuf buf(test_image_spec); + static float colors[4][4] = { { 0.1f, 0.1f, 0.1f, 1.0f }, + { 1.0f, 0.0f, 0.0f, 1.0f }, + { 0.0f, 1.0f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } }; + // ImageBufAlgo::fill(buf, make_cspan(colors[1], 4)); + ImageBufAlgo::fill(buf, colors[0], colors[1], colors[2], colors[3]); + buf.write(Strutil::format("test.{}", extension)); + + Benchmarker bench; + bench.units(Benchmarker::Unit::ms); + bench.iterations(1); + bench.trials(5); + print("\nBenchmarking write/read for {} under different tile sizes\n", + extension); + + // Write a scanline file + auto scanline_filename = Strutil::format("test_scanline.{}", extension); + bench(Strutil::format(" write {} scanline ", extension), [&]() { + checked_write(nullptr, scanline_filename, test_image_spec, datatype, + buf.localpixels_as_byte_image_span()); + }); + + // Write tiled files of different sizes + for (auto ts : tile_sizes) { + test_image_spec.tile_width = ts; + test_image_spec.tile_height = ts; + test_image_spec.tile_depth = ts ? 1 : 0; + auto filename = Strutil::format("test_tile_{:04}.{}", ts, extension); + bench(Strutil::format(" write {} tile {}", extension, ts), [&]() { + checked_write(nullptr, filename, test_image_spec, datatype, + buf.localpixels_as_byte_image_span()); + }); + } + + // read the scanline file (and delete it when we're done) + std::vector readbuffer(test_image_spec.image_bytes()); + bench(Strutil::format(" read {} scanline ", extension), [&]() { + checked_read(nullptr, scanline_filename, readbuffer, datatype); + }); + Filesystem::remove(scanline_filename); + + // read the tiled files of different sizes (and delete when done) + for (auto ts : tile_sizes) { + auto filename = Strutil::format("test_tile_{:04}.{}", ts, extension); + bench(Strutil::format(" read {} tile {}", extension, ts), + [&]() { checked_read(nullptr, filename, readbuffer, datatype); }); + Filesystem::remove(filename); + } +} + + + int main(int argc, char* argv[]) { @@ -549,6 +621,8 @@ main(int argc, char* argv[]) test_all_formats(); test_read_tricky_sizes(); + benchmark_tile_sizes("exr", TypeHalf, 4); + benchmark_tile_sizes("tif", TypeUInt16, 16); return unit_test_failures; } From fcec802dd4f445d15ae0c84c60d26ed97e487755 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 21 Feb 2026 14:06:32 -0800 Subject: [PATCH 125/508] build: Remove support for deprecated Intel icc compiler (#5040) Intel icc is deprecated and hasn't had a release for a few years. It's holding us back, both by making us work around an ever growing number of icc bugs and limitation that will never be fixed, as well as not allowing us to upgrade minimum versions of certain dependencies, because icc can't correctly compile newer versions (as an example, it cannot use a 'fmt' library newer than the oldest we support, 7.0). So it's time to thank icc for its service and put it on the ice floe for the polar bears to eat. This is of course in main (future 3.2), and will not be backported to release branches, since we never stop support of a dependency or toolchain of existing releases. People requiring icc for whatever reason may keep using OIIO 3.1 or older. We will continue to support and test icx, the fully supported Intel LLVM-based compiler. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 49 +- CHANGES.md | 1 + INSTALL.md | 4 +- src/build-scripts/gh-installdeps.bash | 9 +- src/include/OpenImageIO/benchmark.h | 2 +- src/include/OpenImageIO/bit.h | 38 +- src/include/OpenImageIO/image_span.h | 3 - src/include/OpenImageIO/platform.h | 20 +- src/include/OpenImageIO/simd.h | 60 -- src/libOpenImageIO/formatspec.cpp | 1 - src/libOpenImageIO/imageio.cpp | 4 +- src/libutil/sysutil.cpp | 2 - testsuite/tiff-depths/ref/out-icc.txt | 876 -------------------------- 13 files changed, 30 insertions(+), 1039 deletions(-) delete mode 100644 testsuite/tiff-depths/ref/out-icc.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00d8d1548e..bc24a1179b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,39 +241,6 @@ jobs: pybind11_ver: v2.10.0 setenvs: export PUGIXML_VERSION=v1.13 optional_deps_append: 'LibRaw;Ptex;Qt6' - - desc: VFX2023 icc/C++17 py3.10 exr3.1 ocio2.3 qt5.15 - nametag: linux-vfx2023.icc - runner: ubuntu-latest - container: aswf/ci-osl:2023 - opencolorio_ver: v2.3.0 - python_ver: "3.10" - # simd: "avx2,f16c" - fmt_ver: 7.1.3 - # icc MUST use this older FMT version - pybind11_ver: v2.9.0 - setenvs: export USE_ICC=1 USE_OPENVDB=0 USE_OPENCV=0 - OIIO_EXTRA_CPP_ARGS="-fp-model=precise" - FREETYPE_VERSION=VER-2-13-0 - DISABLE_libuhdr=1 - # For icc, use fp-model precise to eliminate needless LSB errors - # that make test results differ from other platforms. - optional_deps_append: "LibRaw;Ptex;Qt6" - - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 - nametag: linux-vfx2023.icx - runner: ubuntu-latest - container: aswf/ci-oiio:2025 - cc_compiler: icx - cxx_compiler: icpx - fmt_ver: 11.2.0 - python_ver: "3.11" - pybind11_ver: v2.13.6 - simd: "avx2,f16c" - benchmark: 1 - setenvs: export USE_OPENVDB=0 USE_OPENCV=0 - UHDR_CMAKE_C_COMPILER=gcc - UHDR_CMAKE_CXX_COMPILER=g++ - # Building libuhdr with icx results in test failures - optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 nametag: linux-vfx2024 runner: ubuntu-latest @@ -336,6 +303,22 @@ jobs: # setenvs: export PUGIXML_VERSION=v1.15 # BUILD_SHARED_LIBS=OFF # optional_deps_append: "openjph;Qt6" + - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 + nametag: linux-vfx2025.icx + runner: ubuntu-latest + container: aswf/ci-oiio:2025 + cc_compiler: icx + cxx_compiler: icpx + fmt_ver: 11.2.0 + python_ver: "3.11" + pybind11_ver: v2.13.6 + simd: "avx2,f16c" + benchmark: 1 + setenvs: export USE_OPENVDB=0 USE_OPENCV=0 + UHDR_CMAKE_C_COMPILER=gcc + UHDR_CMAKE_CXX_COMPILER=g++ + # Building libuhdr with icx results in test failures + optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.4 nametag: linux-vfx2026 runner: ubuntu-latest diff --git a/CHANGES.md b/CHANGES.md index df7e647fea..8344cea2e9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 --------------------------------------------------- ### New minimum dependencies and compatibility changes: + - The deprecated icc compiler is no longer supported. (3.2.0.0) ### ⛰️ New features and public API changes: * *New image file format support:* * *oiiotool new features and major improvements*: diff --git a/INSTALL.md b/INSTALL.md index a52378c5ba..8e96afc2b1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -17,8 +17,8 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * C++17 or higher (also builds with C++20 and C++23) * The default build mode is C++17. This can be controlled by via the CMake configuration flag: `-DCMAKE_CXX_STANDARD=20`, etc. - * Compilers: gcc 9.3 - 14.2, **clang 10** - 20, MSVS 2017 - 2022 (v19.14 - and up), Intel icc 19+, Intel OneAPI C++ compiler 2022+. + * Compilers: gcc 9.3 - 14.2, clang 10 - 20, MSVS 2017 - 2022 (v19.14 + and up), Intel OneAPI C++ compiler 2022+. * CMake >= 3.18.2 (tested through 4.1) * Imath >= 3.1 (tested through 3.2 and main) * OpenEXR >= 3.1 (tested through 3.4 and main) diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index 0dabcf61e0..5ce3e0afee 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -69,14 +69,7 @@ if [[ "$ASWF_ORG" != "" ]] ; then time pip3 install ${PIP_INSTALLS} || true fi - if [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" ]] ; then - # Lock down icc to 2022.1 because newer versions hosted on the Intel - # repo require a glibc too new for the ASWF CentOS7-based containers - # we run CI on. - sudo cp src/build-scripts/oneAPI.repo /etc/yum.repos.d - sudo /usr/bin/yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2022.1.0.x86_64 - set +e; source /opt/intel/oneapi/setvars.sh --config oneapi_2022.1.0.cfg; set -e - elif [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" || "$CXX" == "icpx" || "$CC" == "icx" || "$USE_ICX" != "" ]] ; then + if [[ "$CXX" == "icpx" || "$CC" == "icx" || "$USE_ICX" != "" ]] ; then sudo cp src/build-scripts/oneAPI.repo /etc/yum.repos.d sudo yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic # If we needed to lock down to a particular version, we could: diff --git a/src/include/OpenImageIO/benchmark.h b/src/include/OpenImageIO/benchmark.h index bfcb8b8235..47727a7513 100644 --- a/src/include/OpenImageIO/benchmark.h +++ b/src/include/OpenImageIO/benchmark.h @@ -15,7 +15,7 @@ #include -#if (((OIIO_GNUC_VERSION && NDEBUG) || OIIO_CLANG_VERSION >= 30500 || OIIO_APPLE_CLANG_VERSION >= 70000 || defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER)) \ +#if (((OIIO_GNUC_VERSION && NDEBUG) || OIIO_CLANG_VERSION >= 30500 || OIIO_APPLE_CLANG_VERSION >= 70000 || defined(__INTEL_LLVM_COMPILER)) \ && (defined(__x86_64__) || defined(__i386__))) \ || defined(_MSC_VER) #define OIIO_DONOTOPT_FORECINLINE OIIO_FORCEINLINE diff --git a/src/include/OpenImageIO/bit.h b/src/include/OpenImageIO/bit.h index 08526f3b85..520f35ba93 100644 --- a/src/include/OpenImageIO/bit.h +++ b/src/include/OpenImageIO/bit.h @@ -37,40 +37,6 @@ bitcast(const From& from) noexcept return result; } -#if defined(__INTEL_COMPILER) -// For Intel icc, using the memcpy implementation above will cause a loop with -// a bitcast to fail to vectorize, but using the intrinsics below will allow -// it to vectorize. For icx, as well as gcc and clang, the same optimal code -// is generated (even in a vectorized loop) for memcpy. We can probably remove -// these intrinsics once we drop support for icc. -template<> -OIIO_NODISCARD OIIO_FORCEINLINE uint32_t -bitcast(const float& val) noexcept -{ - return static_cast(_castf32_u32(val)); -} - -template<> -OIIO_NODISCARD OIIO_FORCEINLINE int32_t -bitcast(const float& val) noexcept -{ - return static_cast(_castf32_u32(val)); -} - -template<> -OIIO_NODISCARD OIIO_FORCEINLINE float -bitcast(const uint32_t& val) noexcept -{ - return _castu32_f32(val); -} - -template<> -OIIO_NODISCARD OIIO_FORCEINLINE float -bitcast(const int32_t& val) noexcept -{ - return _castu32_f32(val); -} -#endif OIIO_NODISCARD OIIO_FORCEINLINE OIIO_HOSTDEVICE int @@ -112,9 +78,7 @@ byteswap(T n) -#if (OIIO_GNUC_VERSION || OIIO_ANY_CLANG \ - || OIIO_INTEL_CLASSIC_COMPILER_VERSION) \ - && !defined(__CUDACC__) +#if (OIIO_GNUC_VERSION || OIIO_ANY_CLANG) && !defined(__CUDACC__) // CPU gcc and compatible can use these intrinsics, 8-15x faster template<> diff --git a/src/include/OpenImageIO/image_span.h b/src/include/OpenImageIO/image_span.h index 0ee7d2dcaf..3cea3215ee 100644 --- a/src/include/OpenImageIO/image_span.h +++ b/src/include/OpenImageIO/image_span.h @@ -292,9 +292,6 @@ template class image_span { return (T*)((char*)data() + c * chanstride() + x * xstride() + y * ystride() + z * zstride()); } -#ifdef __INTEL_COMPILER - return nullptr; // should never get here, but icc is confused -#endif } /// Return a pointer to the value at channel 0, pixel (x,y,z). diff --git a/src/include/OpenImageIO/platform.h b/src/include/OpenImageIO/platform.h index e4760e5545..1c52a217ba 100644 --- a/src/include/OpenImageIO/platform.h +++ b/src/include/OpenImageIO/platform.h @@ -340,8 +340,6 @@ # define OIIO_ALIGN(size) __attribute__((aligned(size))) #elif defined(_MSC_VER) # define OIIO_ALIGN(size) __declspec(align(size)) -#elif defined(__INTEL_COMPILER) -# define OIIO_ALIGN(size) __declspec(align((size))) #else # define OIIO_ALIGN(size) alignas(size) #endif @@ -365,7 +363,7 @@ // if (OIIO_UNLIKELY(x)) ... // if you think x will rarely be true // Caveat: Programmers are notoriously bad at guessing this, so it // should be used only with thorough benchmarking. -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) +#if defined(__GNUC__) || defined(__clang__) # define OIIO_LIKELY(x) (__builtin_expect(bool(x), true)) # define OIIO_UNLIKELY(x) (__builtin_expect(bool(x), false)) #else @@ -382,7 +380,7 @@ # define OIIO_FORCEINLINE __inline__ #elif defined(__GNUC__) || defined(__clang__) || __has_attribute(always_inline) # define OIIO_FORCEINLINE inline __attribute__((always_inline)) -#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) +#elif defined(_MSC_VER) # define OIIO_FORCEINLINE __forceinline #else # define OIIO_FORCEINLINE inline @@ -394,7 +392,7 @@ // optimizations by knowing that calling the function cannot possibly alter // any other memory. This declaration goes after the function declaration: // int blah (int arg) OIIO_PURE_FUNC; -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) || __has_attribute(pure) +#if defined(__GNUC__) || defined(__clang__) || __has_attribute(pure) # define OIIO_PURE_FUNC __attribute__((pure)) #elif defined(_MSC_VER) # define OIIO_PURE_FUNC /* seems not supported by MSVS */ @@ -408,7 +406,7 @@ // no side effects. This is even more strict than 'pure', and allows even // more optimizations (such as eliminating multiple calls to the function // that have the exact same argument values). -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) || __has_attribute(const) +#if defined(__GNUC__) || defined(__clang__) || __has_attribute(const) # define OIIO_CONST_FUNC __attribute__((const)) #elif defined(_MSC_VER) # define OIIO_CONST_FUNC /* seems not supported by MSVS */ @@ -425,7 +423,7 @@ // OIIO_RESTRICT is a parameter attribute that indicates a promise that the // parameter definitely will not alias any other parameters in such a way // that creates a data dependency. Use with caution! -#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) || defined(__INTEL_COMPILER) +#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) # define OIIO_RESTRICT __restrict #else # define OIIO_RESTRICT @@ -469,7 +467,7 @@ // false positives that you can't easily get rid of. // This should work for any clang >= 3.3 and gcc >= 4.8, which are // guaranteed by our minimum requirements. -#if defined(__clang__) || (OIIO_GNUC_VERSION > 90000 && !defined(__INTEL_COMPILER)) \ +#if defined(__clang__) || OIIO_GNUC_VERSION > 90000 \ || __has_attribute(no_sanitize_address) # define OIIO_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) #else @@ -480,8 +478,7 @@ // OIIO_NO_SANITIZE_UNDEFINED can be used to mark a function that you don't // want undefined behavior sanitizer to catch. Only use this if you know there // are false positives that you can't easily get rid of. -#if defined(__clang__) || (OIIO_GNUC_VERSION > 90000 && !defined(__INTEL_COMPILER)) \ - || __has_attribute(no_sanitize) +#if defined(__clang__) || OIIO_GNUC_VERSION > 90000 || __has_attribute(no_sanitize) # define OIIO_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined"))) #else # define OIIO_NO_SANITIZE_UNDEFINED @@ -623,10 +620,7 @@ template inline T* aligned_new(Args&&... args) { static_assert(alignof(T) > alignof(void*), "Type doesn't seem to be over-aligned, aligned_new is not required"); void* ptr = aligned_malloc(sizeof(T), alignof(T)); - OIIO_PRAGMA_WARNING_PUSH - OIIO_INTEL_PRAGMA(warning disable 873) return ptr ? new (ptr) T(std::forward(args)...) : nullptr; - OIIO_PRAGMA_WARNING_POP } template diff --git a/src/include/OpenImageIO/simd.h b/src/include/OpenImageIO/simd.h index 9bb0b97093..37d8476729 100644 --- a/src/include/OpenImageIO/simd.h +++ b/src/include/OpenImageIO/simd.h @@ -1020,12 +1020,7 @@ class vint4 { vint4& operator=(int a) { load(a); return *this; } /// Assignment from another vint4 -#if !defined(__INTEL_COMPILER) vint4& operator=(const vint4& other) = default; -#else - // For explanation of the necessity of this, see implementation comment. - vint4& operator=(const vint4& other); -#endif /// Component access (get) int operator[] (int i) const; @@ -1314,12 +1309,7 @@ class vint8 { vint8& operator=(int a) { load(a); return *this; } /// Assignment from another vint8 -#if !defined(__INTEL_COMPILER) vint8& operator=(const vint8& other) = default; -#else - // For explanation of the necessity of this, see implementation comment. - vint8& operator=(const vint8& other); -#endif /// Component access (get) int operator[] (int i) const; @@ -1614,12 +1604,7 @@ class vint16 { vint16& operator=(int a) { load(a); return *this; } /// Assignment from another vint16 -#if !defined(__INTEL_COMPILER) vint16& operator=(const vint16& other) = default; -#else - // For explanation of the necessity of this, see implementation comment. - vint16& operator=(const vint16& other); -#endif /// Component access (get) int operator[] (int i) const; @@ -4106,18 +4091,6 @@ OIIO_FORCEINLINE bool none (const vbool16& v) { return reduce_or(v) == false; } ////////////////////////////////////////////////////////////////////// // vint4 implementation -#if defined(__INTEL_COMPILER) -// For reasons we don't understand, all sorts of failures crop up only on icc -// if we make this =default. Although we still support icc for now, it's a -// discontinued compiler, so we special-case it here rather than spend a lot -// of time investigating what might be broken (and would of course never be -// fixed if it's a compiler bug). -OIIO_FORCEINLINE vint4& vint4::operator=(const vint4& other) { - m_simd = other.m_simd; - return *this; -} -#endif - OIIO_FORCEINLINE int vint4::operator[] (int i) const { OIIO_DASSERT(i struct fmt::formatter : OIIO::pvt::array_formatter {}; -// Allow C++ metaprogramming to understand that the simd types are trivially -// copyable (i.e. memcpy to copy simd types is fine). -#if defined(__INTEL_COMPILER) -// Necessary because we have to define the vint types copy constructors on icc -template<> struct std::is_trivially_copyable : std::true_type {}; -template<> struct std::is_trivially_copyable : std::true_type {}; -template<> struct std::is_trivially_copyable : std::true_type {}; -#endif - #undef SIMD_DO #undef SIMD_CONSTRUCT diff --git a/src/libOpenImageIO/formatspec.cpp b/src/libOpenImageIO/formatspec.cpp index 81374b9624..efd9eff586 100644 --- a/src/libOpenImageIO/formatspec.cpp +++ b/src/libOpenImageIO/formatspec.cpp @@ -41,7 +41,6 @@ inline void get_default_quantize_(long long& quant_min, long long& quant_max) noexcept { OIIO_PRAGMA_WARNING_PUSH - OIIO_INTEL_PRAGMA(warning disable 173) if (std::numeric_limits::is_integer) { quant_min = (long long)std::numeric_limits::min(); quant_max = (long long)std::numeric_limits::max(); diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index c23fb7a331..0d33f64885 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -220,9 +220,7 @@ oiio_build_compiler() using Strutil::fmt::format; std::string comp; -#if OIIO_INTEL_CLASSIC_COMPILER_VERSION - comp = format("Intel icc {}", OIIO_INTEL_CLASSIC_COMPILER_VERSION); -#elif OIIO_INTEL_LLVM_COMPILER +#if OIIO_INTEL_LLVM_COMPILER comp = format("Intel icx {}.{}", __clang_major__, __clang_minor__); #elif OIIO_APPLE_CLANG_VERSION comp = format("Apple clang {}.{}", __clang_major__, __clang_minor__); diff --git a/src/libutil/sysutil.cpp b/src/libutil/sysutil.cpp index 03e9127473..95762cd59d 100644 --- a/src/libutil/sysutil.cpp +++ b/src/libutil/sysutil.cpp @@ -73,8 +73,6 @@ # define HAVE_STACKTRACE 1 #endif -OIIO_INTEL_PRAGMA(warning disable 2196) - OIIO_NAMESPACE_3_1_BEGIN diff --git a/testsuite/tiff-depths/ref/out-icc.txt b/testsuite/tiff-depths/ref/out-icc.txt deleted file mode 100644 index ae0e53bea4..0000000000 --- a/testsuite/tiff-depths/ref/out-icc.txt +++ /dev/null @@ -1,876 +0,0 @@ -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-02.tif -../oiio-images/libtiffpic/depth/flower-minisblack-02.tif : 73 x 43, 1 channel, uint2 tiff - SHA-1: F6BD9D10FB0DD8E9AC62DEBBB743A78FC48D3C9B - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 2 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 431 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-02.tif" and "flower-minisblack-02.tif" -PASS -flower-minisblack-02.tif : 73 x 43, 1 channel, uint2 tiff - SHA-1: F6BD9D10FB0DD8E9AC62DEBBB743A78FC48D3C9B -../oiio-images/libtiffpic/depth/flower-minisblack-02.tif : 73 x 43, 1 channel, uint2 tiff - SHA-1: F6BD9D10FB0DD8E9AC62DEBBB743A78FC48D3C9B -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-04.tif -../oiio-images/libtiffpic/depth/flower-minisblack-04.tif : 73 x 43, 1 channel, uint4 tiff - SHA-1: 8C0CF14B3B585F4B1F249C681BEDEA4CB63E3EDD - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 4 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 221 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-04.tif" and "flower-minisblack-04.tif" -PASS -flower-minisblack-04.tif : 73 x 43, 1 channel, uint4 tiff - SHA-1: 8C0CF14B3B585F4B1F249C681BEDEA4CB63E3EDD -../oiio-images/libtiffpic/depth/flower-minisblack-04.tif : 73 x 43, 1 channel, uint4 tiff - SHA-1: 8C0CF14B3B585F4B1F249C681BEDEA4CB63E3EDD -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-06.tif -../oiio-images/libtiffpic/depth/flower-minisblack-06.tif : 73 x 43, 1 channel, uint6 tiff - SHA-1: AE809BFEF36E3E0047343655231200A916D83492 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-06.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 6 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 148 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-06.tif" and "flower-minisblack-06.tif" -PASS -flower-minisblack-06.tif : 73 x 43, 1 channel, uint6 tiff - SHA-1: AE809BFEF36E3E0047343655231200A916D83492 -../oiio-images/libtiffpic/depth/flower-minisblack-06.tif : 73 x 43, 1 channel, uint6 tiff - SHA-1: AE809BFEF36E3E0047343655231200A916D83492 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-08.tif -../oiio-images/libtiffpic/depth/flower-minisblack-08.tif : 73 x 43, 1 channel, uint8 tiff - SHA-1: 1A909C8E70CC479D8A35BAA9BFEDDCBF4BF46FDC - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-08.tif" and "flower-minisblack-08.tif" -PASS -flower-minisblack-08.tif : 73 x 43, 1 channel, uint8 tiff - SHA-1: 1A909C8E70CC479D8A35BAA9BFEDDCBF4BF46FDC -../oiio-images/libtiffpic/depth/flower-minisblack-08.tif : 73 x 43, 1 channel, uint8 tiff - SHA-1: 1A909C8E70CC479D8A35BAA9BFEDDCBF4BF46FDC -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-10.tif -../oiio-images/libtiffpic/depth/flower-minisblack-10.tif : 73 x 43, 1 channel, uint10 tiff - SHA-1: E9240FEF19CC8EF5EBBF2EE4A10EDF25E51C67B4 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-10.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 10 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 89 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-10.tif" and "flower-minisblack-10.tif" -PASS -flower-minisblack-10.tif : 73 x 43, 1 channel, uint10 tiff - SHA-1: E9240FEF19CC8EF5EBBF2EE4A10EDF25E51C67B4 -../oiio-images/libtiffpic/depth/flower-minisblack-10.tif : 73 x 43, 1 channel, uint10 tiff - SHA-1: E9240FEF19CC8EF5EBBF2EE4A10EDF25E51C67B4 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-12.tif -../oiio-images/libtiffpic/depth/flower-minisblack-12.tif : 73 x 43, 1 channel, uint12 tiff - SHA-1: AAE977957ED6AAC647967192A74E4AD55FF75811 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-12.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 12 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 74 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-12.tif" and "flower-minisblack-12.tif" -PASS -flower-minisblack-12.tif : 73 x 43, 1 channel, uint12 tiff - SHA-1: AAE977957ED6AAC647967192A74E4AD55FF75811 -../oiio-images/libtiffpic/depth/flower-minisblack-12.tif : 73 x 43, 1 channel, uint12 tiff - SHA-1: AAE977957ED6AAC647967192A74E4AD55FF75811 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-14.tif -../oiio-images/libtiffpic/depth/flower-minisblack-14.tif : 73 x 43, 1 channel, uint14 tiff - SHA-1: C1B9CA21C227EF11626EF0C58BD49769EEF48363 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-14.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 14 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 64 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-14.tif" and "flower-minisblack-14.tif" -PASS -flower-minisblack-14.tif : 73 x 43, 1 channel, uint14 tiff - SHA-1: C1B9CA21C227EF11626EF0C58BD49769EEF48363 -../oiio-images/libtiffpic/depth/flower-minisblack-14.tif : 73 x 43, 1 channel, uint14 tiff - SHA-1: C1B9CA21C227EF11626EF0C58BD49769EEF48363 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-16.tif -../oiio-images/libtiffpic/depth/flower-minisblack-16.tif : 73 x 43, 1 channel, uint16 tiff - SHA-1: 7EBB74E46C869CA0D6D091183732214B6A75173A - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-16.tif" and "flower-minisblack-16.tif" -PASS -flower-minisblack-16.tif : 73 x 43, 1 channel, uint16 tiff - SHA-1: 7EBB74E46C869CA0D6D091183732214B6A75173A -../oiio-images/libtiffpic/depth/flower-minisblack-16.tif : 73 x 43, 1 channel, uint16 tiff - SHA-1: 7EBB74E46C869CA0D6D091183732214B6A75173A -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-24.tif -../oiio-images/libtiffpic/depth/flower-minisblack-24.tif : 73 x 43, 1 channel, uint24 tiff - SHA-1: BBFA6633ECF3FF686DB36F6DD00F8A359D2B1DAF - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-24.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q8 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 24 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 37 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-24.tif" and "flower-minisblack-24.tif" -PASS -flower-minisblack-24.tif : 73 x 43, 1 channel, uint24 tiff - SHA-1: BBFA6633ECF3FF686DB36F6DD00F8A359D2B1DAF -../oiio-images/libtiffpic/depth/flower-minisblack-24.tif : 73 x 43, 1 channel, uint24 tiff - SHA-1: BBFA6633ECF3FF686DB36F6DD00F8A359D2B1DAF -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-32.tif -../oiio-images/libtiffpic/depth/flower-minisblack-32.tif : 73 x 43, 1 channel, uint tiff - SHA-1: C98FB1125C7210E380E3F86DFCAEFF49A16742E0 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-32.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 32 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 28 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-32.tif" and "flower-minisblack-32.tif" -PASS -flower-minisblack-32.tif : 73 x 43, 1 channel, uint tiff - SHA-1: C98FB1125C7210E380E3F86DFCAEFF49A16742E0 -../oiio-images/libtiffpic/depth/flower-minisblack-32.tif : 73 x 43, 1 channel, uint tiff - SHA-1: C98FB1125C7210E380E3F86DFCAEFF49A16742E0 -Reading ../oiio-images/libtiffpic/depth/flower-palette-02.tif -../oiio-images/libtiffpic/depth/flower-palette-02.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 52B3033465AA01129BAE149FF96CBB49877DAB7C - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:BitsPerSample: 2 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 431 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-02.tif" and "flower-palette-02.tif" -PASS -flower-palette-02.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 52B3033465AA01129BAE149FF96CBB49877DAB7C -../oiio-images/libtiffpic/depth/flower-palette-02.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 52B3033465AA01129BAE149FF96CBB49877DAB7C -Reading ../oiio-images/libtiffpic/depth/flower-palette-04.tif -../oiio-images/libtiffpic/depth/flower-palette-04.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: C6E40A3D134F1A29E153FE15459D8DE657CB7F9C - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:BitsPerSample: 4 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 221 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-04.tif" and "flower-palette-04.tif" -PASS -flower-palette-04.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: C6E40A3D134F1A29E153FE15459D8DE657CB7F9C -../oiio-images/libtiffpic/depth/flower-palette-04.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: C6E40A3D134F1A29E153FE15459D8DE657CB7F9C -Reading ../oiio-images/libtiffpic/depth/flower-palette-08.tif -../oiio-images/libtiffpic/depth/flower-palette-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 0ADA355BABFE9866F3D88AF7CA3AAC69D7DC036D - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-08.tif" and "flower-palette-08.tif" -PASS -flower-palette-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 0ADA355BABFE9866F3D88AF7CA3AAC69D7DC036D -../oiio-images/libtiffpic/depth/flower-palette-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 0ADA355BABFE9866F3D88AF7CA3AAC69D7DC036D -Reading ../oiio-images/libtiffpic/depth/flower-palette-16.tif -../oiio-images/libtiffpic/depth/flower-palette-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 543285C6812105A1DA3B8ADA691D5DA3AE89B10D - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-16.tif" and "flower-palette-16.tif" -PASS -flower-palette-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 543285C6812105A1DA3B8ADA691D5DA3AE89B10D -../oiio-images/libtiffpic/depth/flower-palette-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 543285C6812105A1DA3B8ADA691D5DA3AE89B10D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 2 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 148 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif" and "flower-rgb-contig-02.tif" -PASS -flower-rgb-contig-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 4 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 74 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif" and "flower-rgb-contig-04.tif" -PASS -flower-rgb-contig-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 37 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif" and "flower-rgb-contig-08.tif" -PASS -flower-rgb-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-10.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 10 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 29 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif" and "flower-rgb-contig-10.tif" -PASS -flower-rgb-contig-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-12.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 12 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 24 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif" and "flower-rgb-contig-12.tif" -PASS -flower-rgb-contig-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-14.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 14 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 21 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif" and "flower-rgb-contig-14.tif" -PASS -flower-rgb-contig-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 18 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif" and "flower-rgb-contig-16.tif" -PASS -flower-rgb-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-24.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q8 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 24 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 12 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif" and "flower-rgb-contig-24.tif" -PASS -flower-rgb-contig-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-32.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 32 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 9 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif" and "flower-rgb-contig-32.tif" -PASS -flower-rgb-contig-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 2 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 431 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif" and "flower-rgb-planar-02.tif" -PASS -flower-rgb-planar-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 4 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 221 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif" and "flower-rgb-planar-04.tif" -PASS -flower-rgb-planar-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif" and "flower-rgb-planar-08.tif" -PASS -flower-rgb-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-10.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 10 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 89 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif" and "flower-rgb-planar-10.tif" -PASS -flower-rgb-planar-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-12.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 12 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 74 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif" and "flower-rgb-planar-12.tif" -PASS -flower-rgb-planar-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-14.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 14 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 64 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif" and "flower-rgb-planar-14.tif" -PASS -flower-rgb-planar-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif" and "flower-rgb-planar-16.tif" -PASS -flower-rgb-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-24.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q8 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 24 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 37 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif" and "flower-rgb-planar-24.tif" -PASS -flower-rgb-planar-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-32.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 32 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 28 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif" and "flower-rgb-planar-32.tif" -PASS -flower-rgb-planar-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -Reading ../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif -../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-contig-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 28 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif" and "flower-separated-contig-08.tif" -PASS -flower-separated-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -Reading ../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif -../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-contig-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 14 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif" and "flower-separated-contig-16.tif" -PASS -flower-separated-contig-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: E55335D12E9A20EFB0A5EAE80F1801DF5A9BEE12 -../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 -Reading ../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif -../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-planar-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif" and "flower-separated-planar-08.tif" -PASS -flower-separated-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -Reading ../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif -../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-planar-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif" and "flower-separated-planar-16.tif" -PASS -flower-separated-planar-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: E55335D12E9A20EFB0A5EAE80F1801DF5A9BEE12 -../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 -Comparing "cmyk_as_cmyk.tif" and "ref/cmyk_as_cmyk.tif" -PASS From b3471fbae9320c89f6ad6fdc4fe18fdd027bfbec Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 21 Feb 2026 16:31:45 -0800 Subject: [PATCH 126/508] build(deps): Raise minimum fmt library version to 9.0 (#5041) The previous minimum, 7.0, dated from mid-2020. We are raising now (in main / future 3.2 only) to 9.0, which dates from mid-2022, so we're still supporting several versions and/or years back. Because this changes minimum dependency versions, it will NOT be backported to release branches (3.1 or earlier). I had to remove the CI test variant for icc, because ancient icc can't correctly build newer versions of fmt, it seems. There is a separate PR to simply drop icc from our list of supported compilers. If anybody wants to argue for pulling the minimum up even farther (say, to fmt 10.0, released in 2023, so still supporting 3 years back), which would simplify even more places, I would consider it. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 8 ++++---- CHANGES.md | 2 ++ INSTALL.md | 6 +++--- src/cmake/externalpackages.cmake | 4 ++-- src/include/OpenImageIO/detail/fmt.h | 21 +++++---------------- src/include/OpenImageIO/strutil.h | 23 +---------------------- src/include/OpenImageIO/typedesc.h | 2 +- src/include/OpenImageIO/ustring.h | 6 ++---- src/libutil/typedesc_test.cpp | 5 +---- src/libutil/ustring_test.cpp | 4 +--- 10 files changed, 22 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc24a1179b..94a95efbf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: cxx_std: 17 python_ver: 3.9 simd: "avx2,f16c" - fmt_ver: 8.1.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 pybind11_ver: v2.9.0 setenvs: export FREETYPE_VERSION=VER-2-12-0 @@ -123,7 +123,7 @@ jobs: vfxyear: 2022 old_node: 1 cxx_std: 17 - fmt_ver: 7.0.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 openexr_ver: v3.1.0 pybind11_ver: v2.7.0 @@ -145,7 +145,7 @@ jobs: cc_compiler: clang cxx_compiler: clang++ cxx_std: 17 - fmt_ver: 7.0.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 openexr_ver: v3.1.0 pybind11_ver: v2.7.0 @@ -167,7 +167,7 @@ jobs: vfxyear: 2022 old_node: 1 cxx_std: 17 - fmt_ver: 7.0.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 openexr_ver: v3.1.0 pybind11_ver: v2.7.0 diff --git a/CHANGES.md b/CHANGES.md index 8344cea2e9..01302429ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 ### New minimum dependencies and compatibility changes: - The deprecated icc compiler is no longer supported. (3.2.0.0) + - **fmt**: Minimum required version is now 9.0 (was 7.0). + ### ⛰️ New features and public API changes: * *New image file format support:* * *oiiotool new features and major improvements*: diff --git a/INSTALL.md b/INSTALL.md index 8e96afc2b1..8d662290f1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -23,11 +23,11 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * Imath >= 3.1 (tested through 3.2 and main) * OpenEXR >= 3.1 (tested through 3.4 and main) * libTIFF >= 4.0 (tested through 4.7 and master) - * *OpenColorIO >= 2.3* (tested through 2.5 and main) + * OpenColorIO >= 2.3 (tested through 2.5 and main) * libjpeg >= 8 (tested through jpeg9e), or libjpeg-turbo >= 2.1 (tested through 3.1) * zlib >= 1.2.7 (tested through 1.3.1) - * [fmtlib](https://github.com/fmtlib/fmt) >= 7.0 (tested through 12.0 and master). + * **[fmtlib](https://github.com/fmtlib/fmt) >= 9.0** (tested through 12.1 and master). If not found at build time, this will be automatically downloaded and built. * [Robin-map](https://github.com/Tessil/robin-map) (unknown minimum, tested through 1.4, which is the recommended version). If not found at build time, @@ -39,7 +39,7 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * Qt5 >= 5.6 (tested through 5.15) or Qt6 (tested through 6.9) * OpenGL * If you are building the Python bindings or running the testsuite: - * **Python >= 3.9** (tested through 3.13). + * Python >= 3.9 (tested through 3.13). * pybind11 >= 2.7 (tested through 3.0) * NumPy (tested through 2.2.4) * If you want support for PNG files: diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index bcf0ca921c..581231c4b4 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -231,9 +231,9 @@ checked_find_package (Robinmap REQUIRED ) # fmtlib -option (OIIO_INTERNALIZE_FMT "Copy fmt headers into /include/OpenImageIO/detail/fmt" ON) +set_option (OIIO_INTERNALIZE_FMT "Copy fmt headers into /include/OpenImageIO/detail/fmt" ON) checked_find_package (fmt REQUIRED - VERSION_MIN 7.0 + VERSION_MIN 9.0 BUILD_LOCAL missing ) get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES) diff --git a/src/include/OpenImageIO/detail/fmt.h b/src/include/OpenImageIO/detail/fmt.h index b68ea14e02..3558152ae7 100644 --- a/src/include/OpenImageIO/detail/fmt.h +++ b/src/include/OpenImageIO/detail/fmt.h @@ -70,12 +70,9 @@ OIIO_PRAGMA_WARNING_PUSH OIIO_PRAGMA_WARNING_POP -// At some point a method signature changed -#if FMT_VERSION >= 90000 -# define OIIO_FMT_CUSTOM_FORMATTER_CONST const -#else -# define OIIO_FMT_CUSTOM_FORMATTER_CONST -#endif +// DEPRECATED(3.2): This definition is obsolete and should be removed at the +// next ABI compatibility boundary. +#define OIIO_FMT_CUSTOM_FORMATTER_CONST const OIIO_NAMESPACE_3_1_BEGIN @@ -132,18 +129,14 @@ template - auto format(const T& v, FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const T& v, FormatContext& ctx) const { std::string vspec = elem_fmt.size() ? fmt::format("{{:{}}}", elem_fmt) : std::string("{}"); for (size_t i = 0; i < size_t(v.size()); ++i) { if (i) fmt::format_to(ctx.out(), "{}", sep == ',' ? ", " : " "); -#if FMT_VERSION >= 80000 fmt::format_to(ctx.out(), fmt::runtime(vspec), v[i]); -#else - fmt::format_to(ctx.out(), vspec, v[i]); -#endif } return ctx.out(); } @@ -177,19 +170,15 @@ template struct array_formatter : format_parser_with_separator { // inherits parse() from format_parser_with_separator template - auto format(const T& v, FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const T& v, FormatContext& ctx) const { std::string vspec = elem_fmt.size() ? fmt::format("{{:{}}}", elem_fmt) : std::string("{}"); for (int i = 0; i < Size; ++i) { if (i) fmt::format_to(ctx.out(), "{}", sep == ',' ? ", " : " "); -#if FMT_VERSION >= 80000 fmt::format_to(ctx.out(), fmt::runtime(vspec), ((const Elem*)&v)[i]); -#else - fmt::format_to(ctx.out(), vspec, ((const Elem*)&v)[i]); -#endif } return ctx.out(); } diff --git a/src/include/OpenImageIO/strutil.h b/src/include/OpenImageIO/strutil.h index c446c49689..f06518d78f 100644 --- a/src/include/OpenImageIO/strutil.h +++ b/src/include/OpenImageIO/strutil.h @@ -204,7 +204,6 @@ namespace sync { /// Output is fully thread-safe (the outputs are "atomic" to the file or /// stream), and if the stream is buffered, it is flushed after the output). -#if FMT_VERSION >= 70000 template inline void print (FILE *file, const Str& fmt, Args&&... args) { @@ -223,26 +222,6 @@ inline void print (std::ostream &file, const Str& fmt, Args&&... args) sync_output (file, ::fmt::vformat(fmt, ::fmt::make_format_args(args...))); } -#else - -template -inline void print (FILE *file, const char* fmt, Args&&... args) -{ - sync_output (file, ::fmt::format(fmt, std::forward(args)...)); -} - -template -inline void print (const char* fmt, Args&&... args) -{ - print(stdout, fmt, std::forward(args)...); -} - -template -inline void print (std::ostream &file, const char* fmt, Args&&... args) -{ - sync_output (file, ::fmt::format(fmt, std::forward(args)...)); -} -#endif } // namespace sync @@ -275,7 +254,7 @@ void print (FILE *file, const char* fmt, const Args&... args); template void print (std::ostream &file, const char* fmt, const Args&... args); -#elif FMT_VERSION >= 70000 && !OIIO_PRINT_IS_SYNCHRONIZED +#elif !OIIO_PRINT_IS_SYNCHRONIZED using ::fmt::print; #else using sync::print; diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index b546dcd0e0..94eee60633 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -681,7 +681,7 @@ struct fmt::formatter { } template - auto format(const OIIO::TypeDesc& t, FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const OIIO::TypeDesc& t, FormatContext& ctx) const { // C++14: auto format(const OIIO::TypeDesc& p, FormatContext& ctx) const { // ctx.out() is an output iterator to write to. diff --git a/src/include/OpenImageIO/ustring.h b/src/include/OpenImageIO/ustring.h index aba90ffdf5..b3e172dea3 100644 --- a/src/include/OpenImageIO/ustring.h +++ b/src/include/OpenImageIO/ustring.h @@ -1146,8 +1146,7 @@ FMT_BEGIN_NAMESPACE template<> struct formatter : formatter { template - auto format(const OIIO::ustring& u, - FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const OIIO::ustring& u, FormatContext& ctx) const { return formatter::format({ u.data(), u.size() }, ctx); @@ -1157,8 +1156,7 @@ template<> struct formatter : formatter { template<> struct formatter : formatter { template - auto format(const OIIO::ustringhash& h, - FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const OIIO::ustringhash& h, FormatContext& ctx) const { OIIO::ustring u(h); return formatter::format({ u.data(), u.size() }, diff --git a/src/libutil/typedesc_test.cpp b/src/libutil/typedesc_test.cpp index b7c2c57ae8..cb6f7b2b3d 100644 --- a/src/libutil/typedesc_test.cpp +++ b/src/libutil/typedesc_test.cpp @@ -69,10 +69,7 @@ test_type(string_view textrep, TypeDesc constructed, tostring_formatting fm(tostring_formatting::STDFORMAT); fm.aggregate_sep = ", "; fm.array_sep = ", "; -#if FMT_VERSION < 70100 - fm.float_fmt = "{:g}"; -#endif - std::string s = tostring(constructed, &value, fm); + std::string s = tostring(constructed, &value, fm); if (valuerep.size()) { OIIO_CHECK_EQUAL(s, valuerep); Strutil::print(" {}\n", s); diff --git a/src/libutil/ustring_test.cpp b/src/libutil/ustring_test.cpp index 6e0a883401..3b4ff692e4 100644 --- a/src/libutil/ustring_test.cpp +++ b/src/libutil/ustring_test.cpp @@ -17,9 +17,7 @@ #include #include -#if FMT_VERSION >= 90000 -# include -#endif +#include using namespace OIIO; From 61dedd80473a6c018130f1ca980a4940d2d30786 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 23 Feb 2026 12:32:49 -0800 Subject: [PATCH 127/508] ci: temporarily disable python stub checking (#5061) The CI stub generation has been broken for a few days, failing CI every time. The checked-in stub files seem fine. Just turn off this check until we can figure out why it is broken. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- pyproject.toml | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 29e7ede18d..04f176cbd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,23 +131,28 @@ WebP_BUILD_VERSION = "1.5.0" [tool.cibuildwheel.windows.environment] SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" -[[tool.cibuildwheel.overrides]] -# Trigger the build & validation of the python stubs for certain platforms. -# The test command acts as a kind of "post-build" callback where it's possible -# for the stub-generator to import the freshly-built wheel. -# There are two entry-points which are designed to call generate_stubs.py through -# this test command: -# - `make pystubs` is called during local development to generate the -# stubs and copy them into the git repo to be committed and reviewed. -# - in CI, the cibuildwheel action is used to validate that the stubs match what -# has been committed to the repo. -test-requires = "mypy~=1.15.0 stubgenlib~=0.1.0" -# Note: the python version here must be kept in sync with src/python/stubs/CMakeLists.txt -select = "cp311-manylinux_*64" -inherit.test-command = "append" -test-command = [ - "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/OpenImageIO/__init__.pyi'", -] +# Temporarily disabled test below. The CI stub generation seems broken, and +# is failing CI every time. Just turn off this check until we can figure out +# why it is broken. +# ---- +# [[tool.cibuildwheel.overrides]] +# # Trigger the build & validation of the python stubs for certain platforms. +# # The test command acts as a kind of "post-build" callback where it's possible +# # for the stub-generator to import the freshly-built wheel. +# # There are two entry-points which are designed to call generate_stubs.py through +# # this test command: +# # - `make pystubs` is called during local development to generate the +# # stubs and copy them into the git repo to be committed and reviewed. +# # - in CI, the cibuildwheel action is used to validate that the stubs match what +# # has been committed to the repo. +# test-requires = "mypy~=1.15.0 stubgenlib~=0.1.0" +# # Note: the python version here must be kept in sync with src/python/stubs/CMakeLists.txt +# select = "cp311-manylinux_*64" +# inherit.test-command = "append" +# test-command = [ +# "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/OpenImageIO/__init__.pyi'", +# ] +# ---- [tool.mypy] files = [ From 245c2da78a08ecbb28ce9501d51ecea4ed932302 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 23 Feb 2026 20:31:49 -0800 Subject: [PATCH 128/508] fix: address fmath.h warning with ispow2 (#5033) I was seeing warnings with instantiation of the ispow2 function template for unsigned type, where the `x >= 0` clause is meaningless. Use a constexpr if to eliminate that pointless test for unsigned types. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/fmath.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/include/OpenImageIO/fmath.h b/src/include/OpenImageIO/fmath.h index bb73ff14ea..ea31eb43ce 100644 --- a/src/include/OpenImageIO/fmath.h +++ b/src/include/OpenImageIO/fmath.h @@ -144,7 +144,11 @@ ispow2(T x) noexcept // Numerous references for this bit trick are on the web. The // principle is that x is a power of 2 <=> x == 1< x-1 is // all 1 bits for bits < b. - return (x & (x - 1)) == 0 && (x >= 0); + if constexpr (std::is_signed::value) { + return (x & (x - 1)) == 0 && (x >= 0); + } else { + return (x & (x - 1)) == 0; + } } From 03b4185f8864707d13701ace6952819f8d3c1004 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 27 Feb 2026 17:08:17 -0800 Subject: [PATCH 129/508] fix(oiiotool): make sure `oiiotool --compression` does expression substitution (#5055) `oiiotool --compression XXX` just used the argument as a literal, and wasn't able to use expression substitution for the value. That made it behave differently than the supposedly synonymous `--attrib compression XXX` in the case that the argument was an expression rather than a literal value. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/oiiotool/oiiotool.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 77a844043f..9a1360ea24 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -1163,6 +1163,15 @@ set_dataformat(Oiiotool& ot, cspan argv) +// --compression +static void +set_compression(Oiiotool& ot, cspan argv) +{ + ot.output_compression = ot.express(argv[1]); +} + + + // --if static void control_if(Oiiotool& ot, cspan argv) @@ -6758,8 +6767,9 @@ Oiiotool::getargs(int argc, char* argv[]) .OTACTION(output_tiles); ap.arg("--force-tiles", &ot.output_force_tiles) .hidden(); // undocumented - ap.arg("--compression %s:NAME", &ot.output_compression) - .help("Set the compression method (in the form \"name\" or \"name:quality\")"); + ap.arg("--compression %s:NAME") + .help("Set the compression method (in the form \"name\" or \"name:quality\")") + .OTACTION(set_compression); ap.arg("--quality %d:QUALITY", &ot.output_quality) .hidden(); // DEPRECATED(2.1) ap.arg("--dither", &ot.output_dither) From 43efa15a20f131e410701e3378b577363f4ee929 Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Mon, 23 Feb 2026 20:36:56 -0800 Subject: [PATCH 130/508] jxl: Extending JXL CICP support to include P3 / color primaries 12 (#5054) I tested out the JPEG XL CICP support and noticed that color primaries 12 was not supported. This pull request is looking to extend P3 support for color primaries 12. Note: color primaries 11 uses the DCI white point and color primaries 12 uses the D65 white point. The JxlPrimaries enum only covers P3 primaries as value 11 and not 12. See, https://github.com/libjxl/libjxl/blob/main/lib/include/jxl/color_encoding.h#L55-L75 Further code is therefore required to account for this on read and write. Tests for read and write of color primaries 11 and 12 were added. Signed-off-by: Shane Smith Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/jpegxl.imageio/jxlinput.cpp | 12 ++++++--- src/jpegxl.imageio/jxloutput.cpp | 18 +++++++++---- testsuite/jxl/ref/out.txt | 45 ++++++++++++++++++++++++++++++++ testsuite/jxl/run.py | 6 +++++ 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/jpegxl.imageio/jxlinput.cpp b/src/jpegxl.imageio/jxlinput.cpp index b0f90cf4f1..88f2ccea06 100644 --- a/src/jpegxl.imageio/jxlinput.cpp +++ b/src/jpegxl.imageio/jxlinput.cpp @@ -383,9 +383,15 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) if (have_color_encoding && color_encoding.primaries != JXL_PRIMARIES_CUSTOM && color_encoding.white_point != JXL_WHITE_POINT_CUSTOM && color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_GAMMA) { - const int cicp[4] = { color_encoding.primaries, - color_encoding.transfer_function, 0 /* RGB */, - 1 /* Full range */ }; + int color_primaries = color_encoding.primaries; + // JxlPrimaries enum only covers P3 primaries as value 11 and not 12 + // but CICP has separate code values based on white point. + if (color_primaries == JXL_PRIMARIES_P3 + && color_encoding.white_point == JXL_WHITE_POINT_D65) { + color_primaries = 12; + } + const int cicp[4] = { color_primaries, color_encoding.transfer_function, + 0 /* RGB */, 1 /* Full range */ }; m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); string_view interop_id = colorconfig.get_color_interop_id(cicp); diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index 1a4a64e858..37fff33817 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -571,7 +571,18 @@ JxlOutput::save_image(const void* data) // primaries and white point are not currently used but could help // support more CICP codes. JxlColorEncoding color_encoding {}; - color_encoding.primaries = JxlPrimaries(cicp[0]); + color_encoding.primaries = JxlPrimaries(cicp[0]); + // CICP primaries 11 and 12 both represent P3, but with different white points. + if (cicp[0] == 11) { + color_encoding.white_point = JXL_WHITE_POINT_DCI; + } + // JxlPrimaries enum only covers P3 primaries as value 11 and not 12. + else if (cicp[0] == 12) { + color_encoding.primaries = JXL_PRIMARIES_P3; + color_encoding.white_point = JXL_WHITE_POINT_D65; + } else { + color_encoding.white_point = JXL_WHITE_POINT_D65; + } color_encoding.transfer_function = JxlTransferFunction(cicp[1]); color_encoding.color_space = JXL_COLOR_SPACE_RGB; @@ -581,10 +592,7 @@ JxlOutput::save_image(const void* data) switch (color_encoding.primaries) { case JXL_PRIMARIES_SRGB: case JXL_PRIMARIES_2100: - case JXL_PRIMARIES_P3: - supported_primaries = true; - color_encoding.white_point = JXL_WHITE_POINT_D65; - break; + case JXL_PRIMARIES_P3: supported_primaries = true; break; case JXL_PRIMARIES_CUSTOM: // Not an actual CICP code in JXL break; } diff --git a/testsuite/jxl/ref/out.txt b/testsuite/jxl/ref/out.txt index 83b97fbba6..0ed56fe485 100644 --- a/testsuite/jxl/ref/out.txt +++ b/testsuite/jxl/ref/out.txt @@ -42,3 +42,48 @@ tahoe-cicp-pq.jxl : 128 x 96, 3 channel, uint8 jpegxl ICCProfile:profile_version: "4.4.0" ICCProfile:rendering_intent: "Perceptual" oiio:ColorSpace: "pq_rec2020_display" +Reading tahoe-cicp-dcip3.jxl +tahoe-cicp-dcip3.jxl : 128 x 96, 3 channel, uint8 jpegxl + SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C + channel list: R, G, B + CICP: 11, 17, 0, 1 + ICCProfile: 0, 0, 2, 24, 106, 120, 108, 32, 4, 64, 0, 0, 109, 110, 116, 114, ... [536 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1786276896 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "CC0" + ICCProfile:creation_date: "2019:12:01 00:00:00" + ICCProfile:creator_signature: "6a786c20" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "0" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "RGB_DCI_DCI_Per_DCI" + ICCProfile:profile_size: 536 + ICCProfile:profile_version: "4.4.0" + ICCProfile:rendering_intent: "Perceptual" +Reading tahoe-cicp-displayp3.jxl +tahoe-cicp-displayp3.jxl : 128 x 96, 3 channel, uint8 jpegxl + SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C + channel list: R, G, B + CICP: 12, 13, 0, 1 + ICCProfile: 0, 0, 2, 4, 106, 120, 108, 32, 4, 64, 0, 0, 109, 110, 116, 114, ... [516 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1786276896 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "CC0" + ICCProfile:creation_date: "2019:12:01 00:00:00" + ICCProfile:creator_signature: "6a786c20" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "0" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "DisplayP3" + ICCProfile:profile_size: 516 + ICCProfile:profile_version: "4.4.0" + ICCProfile:rendering_intent: "Perceptual" + oiio:ColorSpace: "srgb_p3d65_scene" diff --git a/testsuite/jxl/run.py b/testsuite/jxl/run.py index 78b7a19eba..a3299e02a9 100755 --- a/testsuite/jxl/run.py +++ b/testsuite/jxl/run.py @@ -13,6 +13,12 @@ command += oiiotool ("../common/tahoe-tiny.tif --cicp \"9,16,9,1\" -o tahoe-cicp-pq.jxl") command += info_command ("tahoe-cicp-pq.jxl", safematch=True) +command += oiiotool ("../common/tahoe-tiny.tif --cicp \"11,17,0,1\" -o tahoe-cicp-dcip3.jxl") +command += info_command ("tahoe-cicp-dcip3.jxl", safematch=True) + +command += oiiotool ("../common/tahoe-tiny.tif --cicp \"12,13,0,1\" -o tahoe-cicp-displayp3.jxl") +command += info_command ("tahoe-cicp-displayp3.jxl", safematch=True) + outputs = [ "test-jxl.icc", "out.txt" From 24d36fc3156d6b0b939faa89d1b5f9f765dfca59 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 27 Feb 2026 17:09:17 -0800 Subject: [PATCH 131/508] ci: add MacOS 26 (ARM) to the CI lineup (#5059) Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94a95efbf7..2f0f26344c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -630,6 +630,14 @@ jobs: cxx_std: 20 python_ver: "3.13" benchmark: 1 + - desc: MacOS-26-ARM aclang16/C++20/py3.13 + runner: macos-26 + nametag: macos26-arm-py313 + cc_compiler: clang + cxx_compiler: clang++ + cxx_std: 20 + python_ver: "3.14" + benchmark: 1 # From 0cdbc53ad74f1e0538bedf9eea431e11e416ad01 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 26 Feb 2026 05:06:15 -0800 Subject: [PATCH 132/508] build: use quote to avoid error if variable is empty (#5053) Problem identified by @irieger Ingmar Rieger, solves a problem that pops up when Conan build provides the targets. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/heif.imageio/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/heif.imageio/CMakeLists.txt b/src/heif.imageio/CMakeLists.txt index c79b544035..aaf8faf365 100644 --- a/src/heif.imageio/CMakeLists.txt +++ b/src/heif.imageio/CMakeLists.txt @@ -8,7 +8,7 @@ if (Libheif_FOUND) set (_static_libraries_found 0) foreach (_libeheif_library IN LISTS LIBHEIF_LIBRARIES) get_filename_component (_ext ${_libeheif_library} LAST_EXT) - list (FIND _static_suffixes ${_ext} _index) + list (FIND _static_suffixes "${_ext}" _index) if (${_index} GREATER -1) MATH (EXPR _static_libraries_found "${_static_libraries_found}+1") endif() From aaa94c9bba4dd34bcd0ca9449ba9e0a71d659922 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 28 Feb 2026 14:06:51 -0800 Subject: [PATCH 133/508] Update CHANGES and stage for tagging 3.2.0.0-dev preview Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CHANGES.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++- CREDITS.md | 1 + 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 01302429ba..2765f6437e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,7 +9,7 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 * *New image file format support:* * *oiiotool new features and major improvements*: * *Command line utilities*: - - *iv*: Flip, rotate and save image [#5003](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5003) (by Valery Angelique) (3.2.0.0) + - *iv*: Flip, rotate and save image [#5003](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5003) (by Valery Angelique) (3.2.0.0, 3.1.11.0) * *ImageBuf/ImageBufAlgo*: - *ImageBuf*: IB::localpixels_as_[writable_]byte_image_span [#5011](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5011) (3.2.0.0, 3.1.10.0) * *ImageCache/TextureSystem*: @@ -31,18 +31,26 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 ### 🐛 Fixes and feature enhancements - *IBA*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) (3.2.0.0) + - *windows*: `oiiotool --buildinfo` misreported platform on MSVS [#5027](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5027) (3.2.0.0, 3.1.11.0) + - *fix*: Gamma precision improvements [#5038](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5038) (by Lumina Wang) (3.2.0.0, 3.1.11.0) + - *oiiotool*: Fix expression BOTTOM when there are exactly two images [#5046](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5046) (3.2.0.0, 3.1.11.0) + - *oiiotool*: `-d SUBIMAGENAME.*` didn't work properly [#5048](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5048) (3.2.0.0, 3.1.11.0) + - *oiiotool*: Make sure `oiiotool --compression` does expression substitution [#5055](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5055) (3.2.0.0, 3.1.11.0) + - *bmp*: Detect corrupt files where palette doesn't match bpp [#5030](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5030) (3.2.0.0, 3.1.11.0) - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) (3.2.0.0) - *imagebuf*: Fix set_pixels bug, didn't consider roi = All [#4949](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4949) (3.2.0.0) - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.2.0.0, 3.1.7.0) - *heif*: Add IOProxy support for both input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) - *heif*: Fix: Could not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) - *heif*: Fix error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + - *heif*: Monochrome channel read and write support, fix crash [#5043](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5043) (by Brecht Van Lommel) (3.2.0.0, 3.1.11.0) - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.2.0.0, 3.1.7.0) - *jpeg*: Fix wrong pointers/crashing when decoding CMYK jpeg files [#4963](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4963) (3.2.0.0) - *jpeg-2000*: Type warning in assertion in jpeg2000output.cpp [#4952](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4952) (3.2.0.0) - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.2.0.0) - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.2.0.0, 3.1.7.0) - *jpeg-xl*: CICP read and write support for JPEG XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) (3.2.0.0, 3.1.9.0) + - *jpeg-xl*: Extend JXL CICP support to include P3 / color primaries 12 [#5054](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5054) (by Shane Smith) (3.2.0.0, 3.1.11.0) - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.2.0.0, 3.1.7.0) - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.2.0.0, 3.1.7.0) - *openexr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) @@ -50,18 +58,32 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *openexr*: ACES container writes colorInteropId instead of colorInteropID [#4966](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4966) (by Brecht Van Lommel) (3.2.0.0) - *png*: We were not correctly suppressing hint metadata [#4983](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4983) (3.2.0.0) - *sgi*: Implement RLE encoding support for output [#4990](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4990) (by Jesse Yurkovich) (3.2.0.0) + - *tiff*: Fix TIFF output crash for multi-count Exif metadata [#5035](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5035) (3.2.0.0, 3.1.11.0) + - *tiff*: Improve TIFF robustness for non-matching tag/metadata types [#5036](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5036) (3.2.0.0, 3.1.11.0) + - *tiff*: Correctly read TIFF EXIF fields for ExifVersion and FlashPixVersion [#5045](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5045) (3.2.0.0, 3.1.11.0) - *webp*: Allow out-of-order scanlines when writing webp [#4973](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4973) (by Pavan Madduri) (3.2.0.0) - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) (3.2.0.0, 3.1.10.0) - *webp*: Fix missing oiio:UnassociatedAlpha on input [#5020](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5020) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + ### 🔧 Internals and developer goodies - *fix*: Several bug fixes related to internal use of image_span [#5004](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5004) (3.2.0.0, 3.1.10.0) + - *int*: Conform certain attrib names "exif:*" to our "Exif:*" convention [#5025](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5025) (3.2.0.0) + - *int*: Remove left over tile emulation code for various formats that really only support scanline I/O. This hasn't worked properly for a long time, so we aren't really taking away any functionality that anybody could have been using. [#5029](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5029) (by Jesse Yurkovich) (3.2.0.0) + - *dassert.h*: OIIO_CONTRACT_ASSERT and other hardening improvements [#5006](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5006) (3.2.0.0) - *filesystem.h*: Speedup to detect the existence of files on Windows [#4977](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4977) (by JacksonSun-adsk) (3.2.0.0) + - *fmath.h*: Address fmath.h warning with ispow2 [#5033](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5033) (3.2.0.0) + - *typedesc.h*: New TypeURational type definition is like TypeRational, but with unsigned components. [#5036](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5036) [#5057](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5057) (3.2.0.0, 3.1.11.0) + ### 🏗 Build/test/CI and platform ports * OIIO's CMake build system and scripts: - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.2.0.0, 3.1.7.0) - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.2.0.0, 3.1.7.0) - *build*: Fix HARDENING build options [#4996](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4996) (3.2.0.0) - *build*: Fully disable tests when their required dependencies are missing [#5005](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5005) (3.2.0.0, 3.1.10.0) + - *build*: Raise fmt auto-build version to 12.1, handle Windows flags [#5039](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5039) (3.2.0.0, 3.1.11.0) + - *build*: Self-builder logic fixes for deep vs shallow clones [#5034](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5034) (3.2.0.0, 3.1.11.0) + - *build*: Remove support for deprecated Intel icc compiler [#5040](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5040) (3.2.0.0) + - *build*: Use quote to avoid error if variable is empty [#5053](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5053) (3.2.0.0, 3.1.11.0) * Dependency and platform support: - *deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.2.0.0, 3.1.7.0) - *deps*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) (3.2.0.0, 3.1.8.0) @@ -70,9 +92,13 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *build/deps*: Bump OCIO auto-build ver to 2.5.1 [#5022](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5022) (by Zach Lewis) (3.2.0.0, 3.1.10.0) - *build/deps*: Use libheif exported config if available [#5012](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5012) (3.2.0.0, 3.1.10.0) - *build/deps*: Libheif 1.21 support [#4992](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4992) (3.2.0.0, 3.1.10.0) + - *deps*: Raise minimum fmt library version to 9.0 [#5041](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5041) (3.2.0.0) * Testing and Continuous integration (CI) systems: - *tests*: Image_span_test reduce benchmark load for debug and CI renders [#4951](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4951) (3.2.0.0, 3.1.8.0) - *tests*: Add new ref image for jpeg test [#5007](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5007) (3.2.0.0, 3.1.10.0) + - *tests*: Adjust test comparision thresholds for Mac ARM [#5026](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5026) (3.2.0.0, 3.1.11.0) + - *tests*: Add testsuite/heif ref output for libheif 1.21 + avif support [#5031](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5031) (3.2.0.0, 3.1.11.0) + - *tests*: Imageinout_test: add benchmark of read and write speed vs tile size [#5037](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5037) (3.2.0.0, 3.1.11.0) - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.2.0.0, 3.1.7.0) - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.2.0.0, 3.1.7.0) - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.2.0.0, 3.1.7.0) @@ -84,12 +110,19 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) (3.2.0.0, 3.1.10.0) - *ci*: Test against libraw 0.22 for 'latest' test variants [#5009](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5009) (3.2.0.0, 3.1.10.0) - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) (3.2.0.0, 3.1.10.0) + - *ci*: Don't install OpenCV on Mac Intel job variant [#5032](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5032) (3.2.0.0, 3.1.11.0) + - *ci*: Turn off nightly workflows for user forks [#5042](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5042) (3.2.0.0) + - *ci*: Temporarily disable python stub checking [#5061](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5061) (3.2.0.0, 3.1.11.0) + - *ci*: Add MacOS 26 (ARM) to the CI lineup [#5059](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5059) (3.2.0.0, 3.1.11.0) + ### 📚 Notable documentation changes - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.2.0.0, 3.1.7.0) - *docs*: Remove outdated/wrong description in INSTALL.md [#5008](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5008) (3.2.0.0) + ### 🏢 Project Administration - *admin*: Minor rewording in the issue and PR templates [#4982](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4982) (3.2.0.0) - *admin*: Refine PR template to give more visual separation [#4995](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4995) (3.2.0.0) + ### 🤝 Contributors --- @@ -97,6 +130,32 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 +Release 3.1.11.0 (Mar 1, 2026) -- compared to 3.1.10.0 +------------------------------------------------------ + - *oiiotool*: Fix expression BOTTOM when there are exactly two images [#5046](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5046) + - *oiiotool*: `-d SUBIMAGENAME.*` didn't work properly [#5048](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5048) + - *oiiotool*: Make sure `oiiotool --compression` does expression substitution [#5055](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5055) + - *iv*: Ability to flip, rotate and save image [#5003](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5003) (by Valery Angelique) + - *fix*: Gamma precision [#5038](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5038) (by Lumina Wang) + - *bmp*: Detect corrupt files where palette doesn't match bpp [#5030](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5030) + - *heif*: Monochrome channel read and write support, fix crash [#5043](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5043) (by Brecht Van Lommel) + - *jpeg-xl / color mgmt*: Extending JXL CICP support to include P3 / color primaries 12 [#5054](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5054) (by Shane Smith) + - *tiff*: Fix TIFF output crash for multi-count Exif metadata [#5035](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5035) + - *tiff*: Improve TIFF robustness for non-matching tag/metadata types [#5036](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5036) + - *tiff*: Correctly read TIFF EXIF fields for ExifVersion and FlashPixVersion [#5045](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5045) + - *typedesc.h*: New TypeURational type definition is like TypeRational, but with unsigned components. [#5036](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5036) [#5057](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5057) + - *win*: `oiiotool --buildinfo` misreported platform on MSVS [#5027](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5027) + - *build*: Raise fmt auto-build version to 12.1, handle Windows flags [#5039](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5039) + - *build*: Self-builder logic fixes for deep vs shallow clones [#5034](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5034) + - *build*: Use quote to avoid error if variable is empty [#5053](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5053) + - *tests*: Add testsuite/heif ref output for libheif 1.21 + avif support [#5031](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5031) + - *tests*: Adjust test comparision thresholds for Mac ARM [#5026](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5026) + - *tests*: Imageinout_test: add benchmark of read and write speed vs tile size [#5037](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5037) + - *ci*: Don't install OpenCV on Mac Intel job variant [#5032](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5032) + - *ci*: Temporarily disable python stub checking [#5061](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5061) + - *ci*: Add MacOS 26 (ARM) to the CI lineup [#5059](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5059) + + Release 3.1.10.0 (Feb 1, 2026) -- compared to 3.1.9.0 ----------------------------------------------------- - *perf*: `IBA::resample()` and `oiiotool --resample` improvements to speed up 20x or more [#4993](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4993) @@ -500,6 +559,21 @@ asterisk) had not previously contributed to the project. --- +Release 3.0.16.0 (Mar 1, 2026) -- compared to 3.0.15.0 +------------------------------------------------------- + - *oiiotool*: Fix expression BOTTOM when there are exactly two images [#5046](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5046) + - *bmp*: Detect corrupt files where palette doesn't match bpp [#5030](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5030) + - *tiff*: Fix TIFF output crash for multi-count Exif metadata [#5035](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5035) + - *windows*: `oiiotool --buildinfo` misreported platform on MSVS [#5027](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5027) + - *build*: Raise fmt auto-build version to 12.1, handle Windows flags [#5039](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5039) + - *ci*: Optimize install_homebrew_deps by coalescing installs [#4975](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4975) + - *ci*: Don't install OpenCV on Mac Intel job variant [#5032](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5032) + - *ci*: Temporarily disable python stub checking [#5061](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5061) + - *ci*: Add MacOS 26 (ARM) to the CI lineup [#5059](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5059) + - *tests*: Add testsuite/heif ref output for libheif 1.21 + avif support [#5031](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5031) + - *tests*: Adjust test comparision thresholds for Mac ARM [#5026](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5026) + + Release 3.0.15.0 (Feb 1, 2026) -- compared to 3.0.14.0 ------------------------------------------------------- - *heif*: Can not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) diff --git a/CREDITS.md b/CREDITS.md index ed1bb4c15b..e453cd386e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -150,6 +150,7 @@ lg@openimageio.org * Lukas Schrangl * Lukasz Maliszewski * Luke Emrose +* Lumina Wang * Lydia Zheng * M Joonas Pihlaja * Malcolm Humphreys From 3a5ce2aa0dca206d2dc295eb6daaa86cb465a0ca Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 26 Feb 2026 05:22:46 -0800 Subject: [PATCH 134/508] fix(oiiotool): `-d SUBIMAGENAME.*` didn't work properly (#5048) The docs described a syntax where you could use `-d` to change the data type of all channels in one subimage of a multi-subimage image. But it didn't work, as revealed in #5047. Fixes #5047 --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/oiiotool/oiiotool.cpp | 28 ++++++++++++++++++---------- testsuite/oiiotool-copy/ref/out.txt | 15 +++++++++++++++ testsuite/oiiotool-copy/run.py | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 3c33cf2e50..77a844043f 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -819,20 +819,26 @@ get_value_override(string_view localoption, string_view defaultval) static void set_output_dataformat(ImageSpec& spec, TypeDesc format, std::map& channelformats, - int bitdepth) + int bitdepth, int subimage_index, int nsubimages) { if (format != TypeUnknown) spec.format = format; spec.channelformats.resize(spec.nchannels, spec.format); if (!channelformats.empty()) { std::string subimagename = spec["oiio:subimagename"]; + // If this is one of several subimages and it doesn't have a name, let + // it be referred to be "subimage0N". + if (nsubimages > 1 && subimagename.size() == 0) + subimagename = Strutil::format("subimage{:02}", subimage_index); + auto subwildcard = Strutil::format("{}.*", subimagename); for (int c = 0; c < spec.nchannels; ++c) { std::string chname = spec.channel_name(c); - auto subchname = Strutil::fmt::format("{}.{}", subimagename, - chname); + auto subchname = Strutil::format("{}.{}", subimagename, chname); TypeDesc chtype = spec.channelformat(c); if (subimagename.size() && channelformats[subchname] != "") chtype = TypeDesc(channelformats[subchname]); + else if (subimagename.size() && channelformats[subwildcard] != "") + chtype = TypeDesc(channelformats[subwildcard]); else if (channelformats[chname] != "") chtype = TypeDesc(channelformats[chname]); if (chtype != TypeUnknown) @@ -862,6 +868,7 @@ set_output_dataformat(ImageSpec& spec, TypeDesc format, static void adjust_output_options(string_view filename, ImageSpec& spec, const ImageSpec* nativespec, const Oiiotool& ot, + int subimage_index, int nsubimages, bool format_supports_tiles, const ParamValueList& fileoptions, bool was_direct_read = false) @@ -944,7 +951,8 @@ adjust_output_options(string_view filename, ImageSpec& spec, // Set the types in the spec set_output_dataformat(spec, requested_output_dataformat, - requested_channelformats, requested_output_bits); + requested_channelformats, requested_output_bits, + subimage_index, nsubimages); // Tiling strategy: @@ -5850,8 +5858,8 @@ output_file(Oiiotool& ot, cspan argv) bool ok = true; if (do_tex || do_latlong || do_bumpslopes) { ImageSpec configspec; - adjust_output_options(filename, configspec, nullptr, ot, supports_tiles, - fileoptions); + adjust_output_options(filename, configspec, nullptr, ot, 0, 1, + supports_tiles, fileoptions); prep_texture_config(ot, configspec, fileoptions); ImageBufAlgo::MakeTextureMode mode = ImageBufAlgo::MakeTxTexture; if (do_shad) @@ -5878,10 +5886,10 @@ output_file(Oiiotool& ot, cspan argv) } else { // Non-texture case std::vector subimagespecs(ir->subimages()); - for (int s = 0; s < ir->subimages(); ++s) { + for (int s = 0, send = ir->subimages(); s < send; ++s) { ImageSpec spec = *ir->spec(s, 0); - adjust_output_options(filename, spec, ir->nativespec(s), ot, - supports_tiles, fileoptions, + adjust_output_options(filename, spec, ir->nativespec(s), ot, s, + send, supports_tiles, fileoptions, (*ir)[s].was_direct_read()); // If it's not tiled and MIP-mapped, remove any "textureformat" if (!spec.tile_pixels() || ir->miplevels(s) <= 1) @@ -5923,7 +5931,7 @@ output_file(Oiiotool& ot, cspan argv) for (int m = 0, mend = ir->miplevels(s); m < mend && ok; ++m) { ImageSpec spec = *ir->spec(s, m); adjust_output_options(filename, spec, ir->nativespec(s, m), ot, - supports_tiles, fileoptions, + s, send, supports_tiles, fileoptions, (*ir)[s].was_direct_read()); if (s > 0 || m > 0) { // already opened first subimage/level if (!out->open(tmpfilename, spec, mode)) { diff --git a/testsuite/oiiotool-copy/ref/out.txt b/testsuite/oiiotool-copy/ref/out.txt index 8acc4f4723..667e7a8dad 100644 --- a/testsuite/oiiotool-copy/ref/out.txt +++ b/testsuite/oiiotool-copy/ref/out.txt @@ -28,6 +28,21 @@ rgbahalf-zfloat.exr : 38 x 38, 5 channel, half/half/half/half/float openexr screenWindowWidth: 1 oiio:subimages: 1 openexr:lineOrder: "increasingY" +Reading sub-formats.exr + 3 subimages: 64x64 [h,h,h], 64x64 [h,f,h], 64x64 [h,h,h,h] + oiio:subimages: 3 + oiio:subimages: 3 + oiio:subimages: 3 +Reading sub-formats2.exr + 3 subimages: 64x64 [h,h,h], 64x64 [f,f,f], 64x64 [h,h,h,h] + oiio:subimages: 3 + oiio:subimages: 3 + oiio:subimages: 3 +Reading sub-formats3.exr + 3 subimages: 64x64 [h,h,h], 64x64 [f,f,f], 64x64 [h,h,h] + oiio:subimages: 3 + oiio:subimages: 3 + oiio:subimages: 3 explicit -d uint save result: uint8.tif : 128 x 128, 3 channel, uint8 tiff tile size: 16 x 16 diff --git a/testsuite/oiiotool-copy/run.py b/testsuite/oiiotool-copy/run.py index 7338557707..9107b17ed5 100755 --- a/testsuite/oiiotool-copy/run.py +++ b/testsuite/oiiotool-copy/run.py @@ -36,6 +36,25 @@ command += oiiotool ("src/rgbaz.exr -d half -d Z=float -o rgbahalf-zfloat.exr") command += info_command ("rgbahalf-zfloat.exr", safematch=1) +# test -d SUBIMAGE.NAME=fmt to change data format of one subimage +command += oiiotool ("--create 64x64 3 --attrib oiio:subimagename subimageA " + + "--create 64x64 3 --attrib oiio:subimagename subimageB " + + "--create 64x64 4 --attrib oiio:subimagename subimageC " + + "--siappendall -d half -d subimageB.G=float -o sub-formats.exr") +command += info_command ("sub-formats.exr", extraargs="--metamatch subimages") +# test -d SUBIMAGE.*=fmt to change data format of one channel of one subimage +command += oiiotool ("--create 64x64 3 --attrib oiio:subimagename subimageA " + + "--create 64x64 3 --attrib oiio:subimagename subimageB " + + "--create 64x64 4 --attrib oiio:subimagename subimageC " + + "--siappendall -d half -d subimageB.*=float -o sub-formats2.exr") +command += info_command ("sub-formats2.exr", extraargs="--metamatch subimages") +# test -d SUBIMAGE.*=fmt to change data format of one channel of one subimage, +# when no subimages are named (they wil be upon output) but it will still +# recognize "subimage{:02}". +command += oiiotool ("--create 64x64 3 --dup --dup " + + "--siappendall -d half -d subimage01.*=float -o sub-formats3.exr") +command += info_command ("sub-formats3.exr", extraargs="--metamatch subimages") + # Some tests to verify that we are transferring data formats properly. # From f0c6eb5c1b216e8cd520888b89fb77fba5d47df0 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 28 Feb 2026 15:14:58 -0800 Subject: [PATCH 135/508] Bump version after developer preview tag Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed1589cfc..9f39c20197 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required (VERSION 3.18.2...4.0) -set (OpenImageIO_VERSION "3.2.0.0") +set (OpenImageIO_VERSION "3.2.0.1") set (OpenImageIO_VERSION_OVERRIDE "" CACHE STRING "Version override (use with caution)!") mark_as_advanced (OpenImageIO_VERSION_OVERRIDE) From d834be10ac7b846f5763b5041ac2e15b3ec0556f Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Thu, 26 Feb 2026 05:54:30 -0800 Subject: [PATCH 136/508] fix: Fixups for new TypeURational after PR #5036 (#5057) PR #5036 incidentally added a new TypeURational alias, but I neglected to make the corresponding change on the Python bindings, to update some of the docs, or to fully test it. This PR completes those items. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/imageioapi.rst | 2 +- src/doc/pythonbindings.rst | 3 +- src/libutil/typedesc_test.cpp | 5 ++ src/python/py_typedesc.cpp | 71 ++++++++++--------- src/python/stubs/OpenImageIO/__init__.pyi | 1 + testsuite/python-typedesc/ref/out.txt | 2 + .../python-typedesc/src/test_typedesc.py | 5 +- 7 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/doc/imageioapi.rst b/src/doc/imageioapi.rst index bf1c22dd07..ebceb8148f 100644 --- a/src/doc/imageioapi.rst +++ b/src/doc/imageioapi.rst @@ -51,7 +51,7 @@ in the outer OpenImageIO scope: TypeFloat2 TypeVector2 TypeVector2i TypeVector3i TypeFloat4 TypeString TypeTimeCode TypeKeyCode TypeBox2 TypeBox2i TypeBox3 TypeBox3i - TypeRational TypePointer TypeUstringhash + TypeRational TypeURational TypePointer TypeUstringhash The only types commonly used to store *pixel values* in image files are scalars of ``UINT8``, ``UINT16``, `float`, and ``half``. diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index d07735eed2..470e4d619a 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -128,7 +128,8 @@ described in detail in Section :ref:`sec-typedesc`, is replicated for Python. TypeFloat2 TypeVector2 TypeFloat4 TypeVector2i TypeVector3i TypeMatrix TypeMatrix33 - TypeTimeCode TypeKeyCode TypeRational TypePointer + TypeTimeCode TypeKeyCode TypeRational TypeURational + TypePointer Pre-constructed `TypeDesc` objects for some common types, available in the outer OpenImageIO scope. diff --git a/src/libutil/typedesc_test.cpp b/src/libutil/typedesc_test.cpp index cb6f7b2b3d..d8dfa7b324 100644 --- a/src/libutil/typedesc_test.cpp +++ b/src/libutil/typedesc_test.cpp @@ -202,6 +202,11 @@ main(int /*argc*/, char* /*argv*/[]) TypeDesc(TypeDesc::INT, TypeDesc::VEC2, TypeDesc::RATIONAL), TypeRational, i2, "1/2"); + uint32_t ui2[2] = { 1, 2 }; + test_type("urational", + TypeDesc(TypeDesc::UINT32, TypeDesc::VEC2, + TypeDesc::RATIONAL), + TypeURational, ui2, "1/2"); test_type("box2", TypeDesc(TypeDesc::FLOAT, TypeDesc::VEC2, TypeDesc::BOX, 2), diff --git a/src/python/py_typedesc.cpp b/src/python/py_typedesc.cpp index 21c1d635d1..522ba70021 100644 --- a/src/python/py_typedesc.cpp +++ b/src/python/py_typedesc.cpp @@ -141,41 +141,42 @@ declare_typedesc(py::module& m) py::implicitly_convertible(); // Global constants of common TypeDescs - m.attr("TypeUnknown") = TypeUnknown; - m.attr("TypeFloat") = TypeFloat; - m.attr("TypeColor") = TypeColor; - m.attr("TypePoint") = TypePoint; - m.attr("TypeVector") = TypeVector; - m.attr("TypeNormal") = TypeNormal; - m.attr("TypeString") = TypeString; - m.attr("TypeInt") = TypeInt; - m.attr("TypeUInt") = TypeUInt; - m.attr("TypeInt64") = TypeInt64; - m.attr("TypeUInt64") = TypeUInt64; - m.attr("TypeInt32") = TypeInt32; - m.attr("TypeUInt32") = TypeUInt32; - m.attr("TypeInt16") = TypeInt16; - m.attr("TypeUInt16") = TypeUInt16; - m.attr("TypeInt8") = TypeInt8; - m.attr("TypeUInt8") = TypeUInt8; - m.attr("TypeHalf") = TypeHalf; - m.attr("TypeMatrix") = TypeMatrix; - m.attr("TypeMatrix33") = TypeMatrix33; - m.attr("TypeMatrix44") = TypeMatrix44; - m.attr("TypeTimeCode") = TypeTimeCode; - m.attr("TypeKeyCode") = TypeKeyCode; - m.attr("TypeFloat2") = TypeFloat2; - m.attr("TypeVector2") = TypeVector2; - m.attr("TypeFloat4") = TypeFloat4; - m.attr("TypeVector4") = TypeVector4; - m.attr("TypeVector2i") = TypeVector2i; - m.attr("TypeVector3i") = TypeVector3i; - m.attr("TypeBox2") = TypeBox2; - m.attr("TypeBox3") = TypeBox3; - m.attr("TypeBox2i") = TypeBox2i; - m.attr("TypeBox3i") = TypeBox3i; - m.attr("TypeRational") = TypeRational; - m.attr("TypePointer") = TypePointer; + m.attr("TypeUnknown") = TypeUnknown; + m.attr("TypeFloat") = TypeFloat; + m.attr("TypeColor") = TypeColor; + m.attr("TypePoint") = TypePoint; + m.attr("TypeVector") = TypeVector; + m.attr("TypeNormal") = TypeNormal; + m.attr("TypeString") = TypeString; + m.attr("TypeInt") = TypeInt; + m.attr("TypeUInt") = TypeUInt; + m.attr("TypeInt64") = TypeInt64; + m.attr("TypeUInt64") = TypeUInt64; + m.attr("TypeInt32") = TypeInt32; + m.attr("TypeUInt32") = TypeUInt32; + m.attr("TypeInt16") = TypeInt16; + m.attr("TypeUInt16") = TypeUInt16; + m.attr("TypeInt8") = TypeInt8; + m.attr("TypeUInt8") = TypeUInt8; + m.attr("TypeHalf") = TypeHalf; + m.attr("TypeMatrix") = TypeMatrix; + m.attr("TypeMatrix33") = TypeMatrix33; + m.attr("TypeMatrix44") = TypeMatrix44; + m.attr("TypeTimeCode") = TypeTimeCode; + m.attr("TypeKeyCode") = TypeKeyCode; + m.attr("TypeFloat2") = TypeFloat2; + m.attr("TypeVector2") = TypeVector2; + m.attr("TypeFloat4") = TypeFloat4; + m.attr("TypeVector4") = TypeVector4; + m.attr("TypeVector2i") = TypeVector2i; + m.attr("TypeVector3i") = TypeVector3i; + m.attr("TypeBox2") = TypeBox2; + m.attr("TypeBox3") = TypeBox3; + m.attr("TypeBox2i") = TypeBox2i; + m.attr("TypeBox3i") = TypeBox3i; + m.attr("TypeRational") = TypeRational; + m.attr("TypeURational") = TypeURational; + m.attr("TypePointer") = TypePointer; } } // namespace PyOpenImageIO diff --git a/src/python/stubs/OpenImageIO/__init__.pyi b/src/python/stubs/OpenImageIO/__init__.pyi index 88681897d6..416e77e307 100644 --- a/src/python/stubs/OpenImageIO/__init__.pyi +++ b/src/python/stubs/OpenImageIO/__init__.pyi @@ -69,6 +69,7 @@ TypeNormal: TypeDesc TypePoint: TypeDesc TypePointer: TypeDesc TypeRational: TypeDesc +TypeURational: TypeDesc TypeString: TypeDesc TypeTimeCode: TypeDesc TypeUInt: TypeDesc diff --git a/testsuite/python-typedesc/ref/out.txt b/testsuite/python-typedesc/ref/out.txt index fdebc0829d..723eb9ba33 100644 --- a/testsuite/python-typedesc/ref/out.txt +++ b/testsuite/python-typedesc/ref/out.txt @@ -217,6 +217,8 @@ type 'TypeHalf' c_str "half" type 'TypeRational' c_str "rational2i" +type 'TypeURational' + c_str "rational2ui" type 'TypeUInt' c_str "uint" diff --git a/testsuite/python-typedesc/src/test_typedesc.py b/testsuite/python-typedesc/src/test_typedesc.py index ead07f9401..c408b7acb9 100755 --- a/testsuite/python-typedesc/src/test_typedesc.py +++ b/testsuite/python-typedesc/src/test_typedesc.py @@ -171,8 +171,9 @@ def breakdown_test(t: oiio.TypeDesc, name="", verbose=True): breakdown_test (oiio.TypeVector2i, "TypeVector2i", verbose=False) breakdown_test (oiio.TypeVector3i, "TypeVector3i", verbose=False) breakdown_test (oiio.TypeHalf, "TypeHalf", verbose=False) - breakdown_test (oiio.TypeRational, "TypeRational", verbose=False) - breakdown_test (oiio.TypeUInt, "TypeUInt", verbose=False) + breakdown_test (oiio.TypeRational, "TypeRational", verbose=False) + breakdown_test (oiio.TypeURational, "TypeURational", verbose=False) + breakdown_test (oiio.TypeUInt, "TypeUInt", verbose=False) print ("") print ("Done.") From 791c1d582c1bf100300ff3a3be2d2b1af9f63bc6 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 1 Mar 2026 14:13:28 -0800 Subject: [PATCH 137/508] ci: Add CI test for MSVS 2026 (#5060) And some minor fixes discovered along the way. --------- Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 10 ++++++++-- src/build-scripts/build_opencolorio.bash | 4 ++-- src/build-scripts/gh-win-installdeps.bash | 17 ++++++++++++----- src/cmake/build_expat.cmake | 2 +- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f0f26344c..ffdf961b9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -686,7 +686,6 @@ jobs: - desc: Windows-2022 VS2022 runner: windows-2022 nametag: windows-2022 - vsver: 2022 generator: "Visual Studio 17 2022" python_ver: "3.12" ctest_test_timeout: "240" @@ -694,9 +693,16 @@ jobs: - desc: Windows-2025 VS2022 runner: windows-2025 nametag: windows-2025 - vsver: 2022 generator: "Visual Studio 17 2022" python_ver: "3.12" ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 benchmark: 1 + - desc: Windows-2025 VS2026 + runner: windows-2025-vs2026 + nametag: windows-2025-vs2026 + generator: "Visual Studio 18 2026" + python_ver: "3.12" + ctest_test_timeout: "240" + setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + benchmark: 1 diff --git a/src/build-scripts/build_opencolorio.bash b/src/build-scripts/build_opencolorio.bash index 0dddc2971e..5fd2758674 100755 --- a/src/build-scripts/build_opencolorio.bash +++ b/src/build-scripts/build_opencolorio.bash @@ -19,8 +19,8 @@ OPENCOLORIO_SOURCE_DIR=${OPENCOLORIO_SOURCE_DIR:=${LOCAL_DEPS_DIR}/OpenColorIO} OPENCOLORIO_BUILD_DIR=${OPENCOLORIO_BUILD_DIR:=${LOCAL_DEPS_DIR}/OpenColorIO-build} OPENCOLORIO_INSTALL_DIR=${OPENCOLORIO_INSTALL_DIR:=${LOCAL_DEPS_DIR}/dist} OPENCOLORIO_BUILD_TYPE=${OPENCOLORIO_BUILD_TYPE:=Release} -OPENCOLORIO_CXX=${OPENCOLORIO_CXX:=g++} -OPENCOLORIO_CXX_FLAGS=${OPENCOLORIO_CXX_FLAGS:="-Wno-unused-function -Wno-deprecated-declarations -Wno-cast-qual -Wno-write-strings"} +OPENCOLORIO_CXX=${OPENCOLORIO_CXX:=${CXX}} +OPENCOLORIO_CXX_FLAGS=${OPENCOLORIO_CXX_FLAGS:=} # Just need libs: OPENCOLORIO_BUILDOPTS="-DOCIO_BUILD_APPS=OFF -DOCIO_BUILD_NUKE=OFF \ -DOCIO_BUILD_DOCS=OFF -DOCIO_BUILD_TESTS=OFF \ diff --git a/src/build-scripts/gh-win-installdeps.bash b/src/build-scripts/gh-win-installdeps.bash index 48ad421924..4e4f5bb29c 100755 --- a/src/build-scripts/gh-win-installdeps.bash +++ b/src/build-scripts/gh-win-installdeps.bash @@ -24,11 +24,7 @@ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$DEP_DIR/lib:$VCPKG_INSTALLATION_ROOT/i #ls -l "C:/Program Files (x86)/Microsoft Visual Studio" && true -if [[ "$PYTHON_VERSION" == "3.7" ]] ; then - export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.7.9/x64" - export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.7.9/x64/python.exe" - export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages -elif [[ "$PYTHON_VERSION" == "3.9" ]] ; then +if [[ "$PYTHON_VERSION" == "3.9" ]] ; then export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.9.13/x64" export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.9.13/x64/python3.exe" export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages @@ -36,6 +32,10 @@ elif [[ "$PYTHON_VERSION" == "3.12" ]] ; then export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.12.10/x64" export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.12.10/x64/python3.exe" export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages +elif [[ "$PYTHON_VERSION" == "3.14" ]] ; then + export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.14.3/x64" + export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.14.3/x64/python3.exe" + export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages fi pip install numpy @@ -77,6 +77,13 @@ if [[ "$OPENEXR_VERSION" != "" ]] ; then # the above line is admittedly sketchy fi +echo "OPENCOLORIO_VERSION='${OPENCOLORIO_VERSION}'" +if [[ "$OPENCOLORIO_VERSION" != "" ]] ; then + OPENCOLORIO_INSTALL_DIR=$DEP_DIR + source src/build-scripts/build_opencolorio.bash + export PATH="$OPENCOLORIO_INSTALL_DIR/bin:$OPENCOLORIO_INSTALL_DIR/lib:$PATH" +fi + cp $DEP_DIR/lib/*.lib $DEP_DIR/bin || true cp $DEP_DIR/bin/*.dll $DEP_DIR/lib || true echo "DEP_DIR $DEP_DIR :" diff --git a/src/cmake/build_expat.cmake b/src/cmake/build_expat.cmake index 73c369e23f..820452e613 100644 --- a/src/cmake/build_expat.cmake +++ b/src/cmake/build_expat.cmake @@ -47,7 +47,7 @@ set (expat_DIR ${expat_ROOT}/lib/cmake/expat-${expat_VERSION}) if (WIN32) # Set the expat_LIBRARY variable to the full path to ${EXPAT_LIBRARIES}. # For some reason, find_package(expat) behaves differently on Windows - find_package (EXPAT ${expat_BUILD_VERSION} EXACT REQUIRED) + find_package (expat ${expat_BUILD_VERSION} EXACT REQUIRED) set_cache(expat_LIBRARY ${EXPAT_LIBRARIES} "Full path to the expat library") message(STATUS "expat_LIBRARY = ${expat_LIBRARY}") endif () From 60f2c3734ee4b69cbfa253ae73aa2e699bfe38cc Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 4 Mar 2026 10:07:38 -0800 Subject: [PATCH 138/508] feat(tiff): Support GPS fields, and other metadata enhancements (#5050) Most visible change: The TIFF reader and writer now correctly handle GPS tags. Fixes #5049 But along the way, and to get it completely right, a fairly extensive refactoring of our TIFF tag handling was needed. The libtiff behavior around different tags is extremely convoluted, and there were a bunch of cases we either didn't handle correctly, or were dropping on the floor. So there are also some other non-GPS tags that we missed all along but now read properly. Note that the support for separate IFDs for GPS tags is only supported via the API in libtiff 4.2 and newer, so this is disabled when bulding against older libtiff versions. (Our support of old libtiff versions extends extraordinarily far back in time.) --------- Signed-off-by: Larry Gritz Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/externalpackages.cmake | 4 +- src/include/OpenImageIO/tiffutils.h | 7 + src/libOpenImageIO/exif.cpp | 19 +- src/tiff.imageio/tiffinput.cpp | 204 +++++++++++++++---- src/tiff.imageio/tiffoutput.cpp | 177 ++++++++++------ testsuite/misnamed-file/ref/out.txt | 1 + testsuite/tiff-misc/ref/out-libtiff403-b.txt | 14 ++ testsuite/tiff-misc/ref/out-libtiff403-c.txt | 14 ++ testsuite/tiff-misc/ref/out-libtiff403.txt | 14 ++ testsuite/tiff-misc/ref/out-libtiff410.txt | 14 ++ testsuite/tiff-misc/ref/out-libtiff430.txt | 29 +++ testsuite/tiff-misc/ref/out-libtiff470.txt | 58 ++++++ testsuite/tiff-misc/ref/out.txt | 14 ++ testsuite/tiff-misc/run.py | 4 + testsuite/tiff-misc/src/gps.tif | Bin 0 -> 1058 bytes testsuite/tiff-suite/ref/out-alt.txt | 11 + testsuite/tiff-suite/ref/out-alt2.txt | 11 + testsuite/tiff-suite/ref/out-jpeg9b.txt | 11 + testsuite/tiff-suite/ref/out-jpeg9d-alt.txt | 11 + testsuite/tiff-suite/ref/out.txt | 11 + 20 files changed, 522 insertions(+), 106 deletions(-) create mode 100644 testsuite/tiff-misc/ref/out-libtiff470.txt create mode 100644 testsuite/tiff-misc/src/gps.tif diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 581231c4b4..cb366b2a55 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -91,7 +91,9 @@ checked_find_package (libuhdr VERSION_MIN 1.3) checked_find_package (TIFF REQUIRED - VERSION_MIN 4.0) + VERSION_MIN 4.0 + RECOMMEND_MIN 4.2 + RECOMMEND_MIN_REASON "4.2 for GPS support") alias_library_if_not_exists (TIFF::TIFF TIFF::tiff) # JPEG XL diff --git a/src/include/OpenImageIO/tiffutils.h b/src/include/OpenImageIO/tiffutils.h index 1fbf5c7746..2db1719b4e 100644 --- a/src/include/OpenImageIO/tiffutils.h +++ b/src/include/OpenImageIO/tiffutils.h @@ -196,6 +196,12 @@ OIIO_API void encode_exif (const ImageSpec &spec, std::vector &blob, OIIO_API bool exif_tag_lookup (string_view name, int &tag, int &tifftype, int &count); +/// Helper: For the given OIIO metadata attribute name, look up the GPS tag +/// ID, TIFFDataType (expressed as an int), and count. Return true and fill +/// in the fields if found, return false if not found. +OIIO_API bool gps_tag_lookup (string_view name, int &tag, + int &tifftype, int &count); + /// Add metadata to spec based on raw IPTC (International Press /// Telecommunications Council) metadata in the form of an IIM /// (Information Interchange Model). Return true if all is ok, false if @@ -286,6 +292,7 @@ using v3_1::decode_xmp; using v3_1::encode_iptc_iim; using v3_1::encode_xmp; using v3_1::exif_tag_lookup; +using v3_1::gps_tag_lookup; using v3_1::tag_lookup; using v3_1::tag_table; using v3_1::TagInfo; diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index c418dec1a0..1a038e17b7 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -426,7 +426,7 @@ static const TagInfo exif_tag_table[] = { { TIFFTAG_PHOTOMETRIC, "Exif:Photometric", TIFF_NOTYPE, 1 }, { TIFFTAG_SAMPLESPERPIXEL, "Exif:SamplesPerPixel", TIFF_NOTYPE, 1 }, { TIFFTAG_PLANARCONFIG, "Exif:PlanarConfig", TIFF_NOTYPE, 1 }, - { TIFFTAG_YCBCRSUBSAMPLING, "Exif:YCbCrSubsampling",TIFF_SHORT, 1 }, + { TIFFTAG_YCBCRSUBSAMPLING, "Exif:YCbCrSubsampling",TIFF_SHORT, 2 }, { TIFFTAG_YCBCRPOSITIONING, "Exif:YCbCrPositioning",TIFF_SHORT, 1 }, // TIFF tags we may come across { TIFFTAG_ORIENTATION, "Orientation", TIFF_SHORT, 1 }, @@ -525,7 +525,7 @@ static const TagInfo exif_tag_table[] = { { EXIF_LENSMAKE, "Exif:LensMake", TIFF_ASCII, 0 }, { EXIF_LENSMODEL, "Exif:LensModel", TIFF_ASCII, 0 }, { EXIF_LENSSERIALNUMBER, "Exif:LensSerialNumber", TIFF_ASCII, 0 }, - { EXIF_GAMMA, "Exif:Gamma", TIFF_RATIONAL, 0 }, + { EXIF_GAMMA, "Exif:Gamma", TIFF_RATIONAL, 1 }, // Exif 3.0 additions follow { EXIF_IMAGETITLE, "Exif:ImageTitle", TIFF_ASCII, 0 }, { EXIF_PHOTOGRAPHER, "Exif:Photographer", TIFF_ASCII, 0 }, @@ -1559,4 +1559,19 @@ exif_tag_lookup(string_view name, int& tag, int& tifftype, int& count) return true; } + + +bool +gps_tag_lookup(string_view name, int& tag, int& tifftype, int& count) +{ + const TagInfo* e = gps_tagmap_ref().find(name); + if (!e) + return false; // not found + + tag = e->tifftag; + tifftype = e->tifftype; + count = e->tiffcount; + return true; +} + OIIO_NAMESPACE_3_1_END diff --git a/src/tiff.imageio/tiffinput.cpp b/src/tiff.imageio/tiffinput.cpp index 8233de9284..7eff5bae25 100644 --- a/src/tiff.imageio/tiffinput.cpp +++ b/src/tiff.imageio/tiffinput.cpp @@ -265,25 +265,37 @@ class TIFFInput final : public ImageInput { } OIIO_NODISCARD - TypeDesc tiffgetfieldtype(int tag) + TypeDesc tiffgetfieldtype(int tag, OIIO_MAYBE_UNUSED string_view name = "", + int* readcount_ = nullptr, + int* passcount_ = nullptr, + TIFFDataType* tiffdatatype_ = nullptr) { auto field = find_field(tag); if (!field) return TypeUnknown; - TIFFDataType tiffdatatype = TIFFFieldDataType(field); - int passcount = TIFFFieldPassCount(field); - int readcount = TIFFFieldReadCount(field); - if (!passcount && readcount > 0) - return tiff_datatype_to_typedesc(tiffdatatype, readcount); - return TypeUnknown; + auto tiffdatatype = TIFFFieldDataType(field); + int readcount = TIFFFieldReadCount(field); + int passcount = TIFFFieldPassCount(field); + TypeDesc type = tiff_datatype_to_typedesc(tiffdatatype, + std::max(1, readcount)); + if (readcount == TIFF_SPP) + type.arraylen = m_spec.nchannels; + if (readcount_) + *readcount_ = readcount; + if (passcount_) + *passcount_ = passcount; + if (tiffdatatype_) + *tiffdatatype_ = tiffdatatype; + return type; } OIIO_NODISCARD bool safe_tiffgetfield(string_view name OIIO_MAYBE_UNUSED, int tag, TypeDesc expected, void* dest, - const uint32_t* count = nullptr) + uint32_t* count = nullptr) { - TypeDesc type = tiffgetfieldtype(tag); + int readcount = 0, passcount = 0; + TypeDesc type = tiffgetfieldtype(tag, name, &readcount, &passcount); // Caller expects a specific type and the tag doesn't match? Punt. if (expected != TypeUnknown && !equivalent(expected, type)) return false; @@ -292,8 +304,8 @@ class TIFFInput final : public ImageInput { return false; // TIFFDataType tiffdatatype = TIFFFieldDataType(field); - int passcount = TIFFFieldPassCount(field); - int readcount = TIFFFieldReadCount(field); + passcount = TIFFFieldPassCount(field); + readcount = TIFFFieldReadCount(field); if (!passcount && readcount > 0) { return TIFFGetField(m_tif, tag, dest); } else if (passcount && readcount <= 0) { @@ -370,31 +382,120 @@ class TIFFInput final : public ImageInput { m_spec.attribute(name, TypeMatrix, f); } - // Get a float tiff tag field and put it into extra_params - void get_float_attribute(string_view name, int tag) - { - float f[16]; - if (safe_tiffgetfield(name, tag, TypeUnknown, f)) - m_spec.attribute(name, f[0]); - } - - // Get an int tiff tag field and put it into extra_params - void get_int_attribute(string_view name, int tag) - { - int i = 0; - if (safe_tiffgetfield(name, tag, TypeUnknown, &i)) - m_spec.attribute(name, i); - } - - // Get an int tiff tag field and put it into extra_params - void get_short_attribute(string_view name, int tag) + // Get a tiff tag field whose C type will match T, and put it into + // extra_params. If it must be a very specific TypeDesc, it can be + // supplied as expected_type. The hard part is that libtiff is horribly + // complicated because some tags have variable lengths, and there are even + // several different parameter passing conventions to the incredibly + // unsafe TIFFGetField function. + template + void get_attribute_from_tag(string_view name, int tag, + TypeDesc expected_type = TypeUnknown) { - // Make room for two shorts, in case the tag is not the type we - // expect, and libtiff writes a long instead. - unsigned short s[2] = { 0, 0 }; - if (safe_tiffgetfield(name, tag, TypeUInt16, &s)) { - int i = s[0]; - m_spec.attribute(name, i); + int readcount = 0, passcount = 0; + TIFFDataType tiffdatatype = TIFF_NOTYPE; + TypeDesc oiiotype = tiffgetfieldtype(tag, name, &readcount, &passcount, + &tiffdatatype); + if (oiiotype == TypeUnknown) + return; + OIIO_ASSERT((readcount > 0 && passcount == 0) + || (readcount == 0 && passcount == 0) + || (readcount < 0 && passcount > 0)); + oiiotype.basetype = BaseTypeFromC_v; + if (expected_type != TypeUnknown) { + // If a specific OIIO type is demanded, skip if what we got + // doesn't really match in base type and total number of elements. + // But allow a ushort tag to make a int attribute. + if (oiiotype.basetype != expected_type.basetype) + return; + if (oiiotype.basevalues() != expected_type.basevalues()) + return; + oiiotype = expected_type; + } else { + oiiotype.aggregate = TypeDesc::SCALAR; + oiiotype.vecsemantics = TypeDesc::NOSEMANTICS; + } + if (readcount > 0 && passcount == 0) { + // We know how many are passed, so we pass TIFFGetField a pointer + // to where we want the (already sized) data to go. + OIIO_ASSERT(size_t(readcount) == oiiotype.basevalues()); + if constexpr (std::is_same_v) { + // Special case: we save a single tiff ushort as an int. + if (tiffdatatype == TIFF_SHORT && readcount == 1) { + T val; + if (TIFFGetField(m_tif, tag, &val)) + m_spec.attribute(name, int(val)); + return; + } + // Fun, there are some quirky special cases in libtiff where + // certain uint16[2] fields are retrieved with two pointers! + if (tiffdatatype == TIFF_SHORT && readcount == 2 + && (tag == TIFFTAG_PAGENUMBER + || tag == TIFFTAG_HALFTONEHINTS + || tag == TIFFTAG_DOTRANGE + || tag == TIFFTAG_YCBCRSUBSAMPLING)) { + T vals[2] = { 0, 0 }; + if (TIFFGetField(m_tif, tag, &(vals[0]), &(vals[1]))) { + constexpr TypeDesc TypeUInt16_2(TypeDesc::UINT16, 2); + m_spec.attribute(name, TypeUInt16_2, vals); + } + return; + } + } + if (readcount > 1) { + // If there are multiple values, we pass a T** and it puts the + // address of the real data in our pointer. There are very few + // TIFF tags like this. + const T* ptr = nullptr; + if (TIFFGetField(m_tif, tag, &ptr) && ptr) + m_spec.attribute(name, oiiotype, ptr); + return; + } else { + // If there is just one value, we pass a pointer to our data, + // and libtiff fills it in. + T val; + if (TIFFGetField(m_tif, tag, &val)) + m_spec.attribute(name, oiiotype, &val); + } + return; + } else if (readcount == TIFF_VARIABLE) { + // Must pass a uin16_t to find out how many, and we get a data + // pointer instead of providing a pointer. + uint16_t count = 0; + const T* vals = nullptr; + if (TIFFGetField(m_tif, tag, &count, &vals) && vals && count) { + oiiotype.unarray(); + oiiotype.arraylen = count; + m_spec.attribute(name, oiiotype, make_span(vals, count)); + } + return; + } else if (readcount == TIFF_VARIABLE2) { + // Must pass a uin32t_t to find out how many, and we get a data + // pointer instead of providing a pointer. + uint32_t count = 0; + const T* vals = nullptr; + if (TIFFGetField(m_tif, tag, &count, &vals) && vals && count) { + if (count > 1) + oiiotype.arraylen = count; + else + oiiotype.unarray(); + m_spec.attribute(name, oiiotype, make_span(vals, count)); + } + return; + } else if (readcount == TIFF_SPP) { + // The number of values is equal to the number of channels. + uint32_t count = static_cast(m_spec.nchannels); + const T* vals = nullptr; + if (TIFFGetField(m_tif, tag, &vals) && vals) { + if (count > 1) + oiiotype.arraylen = count; + else + oiiotype.unarray(); + m_spec.attribute(name, oiiotype, make_span(vals, count)); + } + return; + } else { + // print("UNHANDLED CASE!\n"); } } @@ -416,14 +517,14 @@ class TIFFInput final : public ImageInput { get_string_attribute(oiioname, tifftag); return; } else if (tifftype == TIFF_SHORT) { - get_short_attribute(oiioname, tifftag); + get_attribute_from_tag(oiioname, tifftag); return; } else if (tifftype == TIFF_LONG) { - get_int_attribute(oiioname, tifftag); + get_attribute_from_tag(oiioname, tifftag); return; } else if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL || tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) { - get_float_attribute(oiioname, tifftag); + get_attribute_from_tag(oiioname, tifftag); return; } // special cases follow @@ -1295,11 +1396,13 @@ TIFFInput::readspec(bool read_meta) if (xdensity && ydensity) m_spec.attribute("PixelAspectRatio", ydensity / xdensity); - get_matrix_attribute("worldtocamera", TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA); - get_matrix_attribute("worldtoscreen", TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN); - get_int_attribute("tiff:subfiletype", TIFFTAG_SUBFILETYPE); - // FIXME -- should subfiletype be "conventionized" and used for all - // plugins uniformly? + get_attribute_from_tag("worldtocamera", + TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA, + TypeMatrix); + get_attribute_from_tag("worldtoscreen", + TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN, + TypeMatrix); + get_attribute_from_tag("tiff:subfiletype", TIFFTAG_SUBFILETYPE); // Special names for shadow maps char* s = NULL; @@ -1357,6 +1460,21 @@ TIFFInput::readspec(bool read_meta) TIFFSetDirectory(m_tif, m_actual_subimage); } +#if OIIO_TIFFLIB_VERSION >= 40200 + // Search for an GPS IFD in the TIFF file, and if found, rummage + // around for GPS fields. + toff_t gpsoffset = 0; + if (TIFFGetField(m_tif, TIFFTAG_GPSIFD, &gpsoffset)) { + if (TIFFReadEXIFDirectory(m_tif, gpsoffset)) { + for (const auto& tag : tag_table("GPS")) + find_tag(tag.tifftag, tag.tifftype, tag.name); + } + // TIFFReadEXIFDirectory seems to do something to the internal state + // that requires a TIFFSetDirectory to set things straight again. + TIFFSetDirectory(m_tif, m_actual_subimage); + } +#endif + // Search for IPTC metadata in IIM form -- but older versions of // libtiff botch the size, so ignore it for very old libtiff. int iptcsize = 0; diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index eb0291f1f1..8356d312a1 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -152,6 +152,11 @@ class TIFFOutput final : public ImageOutput { bool put_parameter(const ParamValue& metadata); bool write_exif_data(); + // Write the tags of the given tag set to a custom TIFF directory. + // Return the offset of the new directory, or 0 if it could not be + // done. + uint64_t write_extra_tag_directory(string_view tag_set_name); + // Make our best guess about whether the spec is describing data that // is in true CMYK values. bool source_is_cmyk(const ImageSpec& spec); @@ -1096,19 +1101,26 @@ TIFFOutput::write_exif_data() // First, see if we have any Exif data at all bool any_exif = false; - for (size_t i = 0, e = m_spec.extra_attribs.size(); i < e; ++i) { - const ParamValue& p(m_spec.extra_attribs[i]); + bool any_gps = false; + for (const auto& p : m_spec.extra_attribs) { int tag, tifftype, count; - if (exif_tag_lookup(p.name(), tag, tifftype, count) + if (!any_exif && exif_tag_lookup(p.name(), tag, tifftype, count) && tifftype != TIFF_NOTYPE) { if (tag == EXIF_SECURITYCLASSIFICATION || tag == EXIF_IMAGEHISTORY || tag == EXIF_PHOTOGRAPHICSENSITIVITY) continue; // libtiff doesn't understand these any_exif = true; - break; } +# if OIIO_TIFFLIB_VERSION >= 40200 + if (!any_gps && gps_tag_lookup(p.name(), tag, tifftype, count) + && tifftype != TIFF_NOTYPE) { + any_gps = true; + } +# endif + if (any_exif && any_gps) + break; // If we've found both kinds, we're done } - if (!any_exif) + if (!any_exif && !any_gps) return true; # if ENABLE_JPEG_COMPRESSION @@ -1125,16 +1137,59 @@ TIFFOutput::write_exif_data() return false; } - // Create an Exif directory - if (TIFFCreateEXIFDirectory(m_tif) != 0) { - errorfmt("failed TIFFCreateEXIFDirectory()"); - return false; + uint64_t exif_dir_offset = 0; + if (any_exif) { + // Create an Exif directory + if (TIFFCreateEXIFDirectory(m_tif) != 0) { + errorfmt("failed TIFFCreateEXIFDirectory()"); + return false; + } + exif_dir_offset = write_extra_tag_directory("Exif"); + } +# if OIIO_TIFFLIB_VERSION >= 40200 + uint64_t gps_dir_offset = 0; + if (any_gps) { + // Create a GPS directory + if (TIFFCreateGPSDirectory(m_tif) != 0) { + errorfmt("failed TIFFCreateGPSDirectory()"); + return false; + } + gps_dir_offset = write_extra_tag_directory("GPS"); + } +# endif + + // Go back to the first directory, and add the EXIFIFD pointer. + // std::cout << "diffdir = " << tiffdir << "\n"; + TIFFSetDirectory(m_tif, 0); + if (exif_dir_offset) + TIFFSetField(m_tif, TIFFTAG_EXIFIFD, exif_dir_offset); +# if OIIO_TIFFLIB_VERSION >= 40200 + if (gps_dir_offset) + TIFFSetField(m_tif, TIFFTAG_GPSIFD, gps_dir_offset); +# endif +#endif + + return true; // all is ok +} + + + +uint64_t +TIFFOutput::write_extra_tag_directory(string_view tag_set_name) +{ + using TagLookupFunc = bool (*)(string_view, int&, int&, int&); + TagLookupFunc lookup_func = nullptr; + if (tag_set_name == "GPS") { + lookup_func = gps_tag_lookup; + } else { + lookup_func = exif_tag_lookup; } + OIIO_ASSERT(lookup_func); for (size_t i = 0, e = m_spec.extra_attribs.size(); i < e; ++i) { const ParamValue& p(m_spec.extra_attribs[i]); int tag, tifftype, count; - if (exif_tag_lookup(p.name(), tag, tifftype, count) + if (lookup_func(p.name(), tag, tifftype, count) && tifftype != TIFF_NOTYPE) { bool ok = false; bool handled = false; @@ -1170,71 +1225,73 @@ TIFFOutput::write_exif_data() else if (tifftype == TIFF_ASCII) { ok = TIFFSetField(m_tif, tag, p.get_string().c_str()); handled = true; - } else if (tifftype == TIFF_SHORT || tifftype == TIFF_SSHORT - || tifftype == TIFF_LONG || tifftype == TIFF_SLONG) { - if ((p.type() == TypeInt16 || p.type() == TypeInt32 - || p.type() == TypeUInt16 || p.type() == TypeUInt32) - && count == 1) { - // Passing our kinda-int as TIFF kinda-int - ok = TIFFSetField(m_tif, tag, p.get_int()); - handled = true; - } else if (p.type() == TypeString && count == 1) { - // Passing our string as TIFF kinda-int -- convert as long - // as the string looks like an int. - std::string s = p.get_string(); - if (Strutil::string_is_int(s)) { - int val = Strutil::stoi(s); - ok = TIFFSetField(m_tif, tag, val); - handled = true; - } + } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_SSHORT + || tifftype == TIFF_LONG || tifftype == TIFF_SLONG) + && ((p.type().elementtype() == TypeInt16 + || p.type().elementtype() == TypeUInt16 + || p.type().elementtype() == TypeInt32 + || p.type().elementtype() == TypeUInt32))) { + // If the tag is an integer type, there are a number of types + // we can force into that form by converting to and then + // passing ints. + if (count == 1) { + ok = TIFFSetField(m_tif, tag, p.get_int()); + } else { + auto vals = OIIO_ALLOCA_SPAN(int, count); + for (int i = 0; i < count; ++i) + vals[i] = p.get_int_indexed(i); + ok = TIFFSetField(m_tif, tag, vals.data()); } - } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && (p.type() == TypeFloat || p.type() == TypeDesc::DOUBLE - || p.type() == TypeUInt16 || p.type() == TypeUInt32 - || p.type() == TypeInt16 || p.type() == TypeInt32 - || p.type() == TypeRational - || p.type() == TypeURational) - && count == 1) { - // If the tag is a rational, there are a number of types we - // can force into that form by converting to and then passing - // a float. - ok = TIFFSetField(m_tif, tag, p.get_float()); handled = true; + } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL + || tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) + && (p.type().is_floating_point() + || p.type().elementtype() == TypeUInt16 + || p.type().elementtype() == TypeUInt32 + || p.type().elementtype() == TypeInt16 + || p.type().elementtype() == TypeInt32 + || p.type().elementtype() == TypeRational + || p.type().elementtype() == TypeURational)) { + // If the tag is a rational or floats, there are a number of types + // we can force into that form by converting to and then passing + // floats. But actually, libtiff uses C conventions, so we actually + // pass doubles. + if (count == 1) { + ok = TIFFSetField(m_tif, tag, p.get_float()); + handled = true; + } else if (count > 1) { + auto vals = OIIO_ALLOCA_SPAN(double, count); + for (int i = 0; i < count; ++i) + vals[i] = p.get_float_indexed(i); + ok = TIFFSetField(m_tif, tag, vals.data()); + handled = true; + } } if (!handled) { -# if 0 - print("Unhandled EXIF {} ({}) / tag {} tifftype {} count {}\n", - p.name(), p.type(), tag, tifftype, count); -# endif +#if 0 + print("Unhandled {} {} ({}) / tag {} tifftype {} count {}\n", + tag_set_name, p.name(), p.type(), tag, tifftype, count); +#endif } - // NOTE: We are not handling arrays of values, just scalars. if (!ok) { -# if 0 +#if 0 print( - "Error handling EXIF {} ({}) / tag {} tifftype {} count {}\n", - p.name(), p.type(), tag, tifftype, count); -# endif + "Error handling {} {} ({}) / tag {} tifftype {} count {}\n", + tag_set_name, p.name(), p.type(), tag, tifftype, count); +#endif } } } - // Now write the directory of Exif data -# ifndef TIFF_GCC_DEPRECATED - uint64 dir_offset = 0; // old type -# else + // Now write the directory of this data uint64_t dir_offset = 0; -# endif if (!TIFFWriteCustomDirectory(m_tif, &dir_offset)) { - errorfmt("failed TIFFWriteCustomDirectory() of the Exif data"); - return false; + errorfmt("failed TIFFWriteCustomDirectory() of the {} data", + tag_set_name); + return 0; } - // Go back to the first directory, and add the EXIFIFD pointer. - // std::cout << "diffdir = " << tiffdir << "\n"; - TIFFSetDirectory(m_tif, 0); - TIFFSetField(m_tif, TIFFTAG_EXIFIFD, dir_offset); -#endif - return true; // all is ok + return dir_offset; } diff --git a/testsuite/misnamed-file/ref/out.txt b/testsuite/misnamed-file/ref/out.txt index f2b6d7530f..653f0d4987 100644 --- a/testsuite/misnamed-file/ref/out.txt +++ b/testsuite/misnamed-file/ref/out.txt @@ -13,6 +13,7 @@ misnamed.exr : 1000 x 1000, 4 channel, uint8 tiff YResolution: 72 oiio:BitsPerSample: 8 tiff:Compression: 8 + tiff:PageNumber: 0, 1 tiff:PhotometricInterpretation: 2 tiff:PlanarConfiguration: 1 tiff:RowsPerStrip: 8 diff --git a/testsuite/tiff-misc/ref/out-libtiff403-b.txt b/testsuite/tiff-misc/ref/out-libtiff403-b.txt index 897920e992..92a85bf3a6 100644 --- a/testsuite/tiff-misc/ref/out-libtiff403-b.txt +++ b/testsuite/tiff-misc/ref/out-libtiff403-b.txt @@ -25,5 +25,19 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : TIFFReadRawTile failed reading tile x=1088,y=72,z=0: Read error at row 4294967295, col 4294967295; got 114 bytes, expected 127 +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/ref/out-libtiff403-c.txt b/testsuite/tiff-misc/ref/out-libtiff403-c.txt index 86eb7625ed..4e7339e302 100644 --- a/testsuite/tiff-misc/ref/out-libtiff403-c.txt +++ b/testsuite/tiff-misc/ref/out-libtiff403-c.txt @@ -25,5 +25,19 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/ref/out-libtiff403.txt b/testsuite/tiff-misc/ref/out-libtiff403.txt index cf9932e6ab..abd0cdec72 100644 --- a/testsuite/tiff-misc/ref/out-libtiff403.txt +++ b/testsuite/tiff-misc/ref/out-libtiff403.txt @@ -25,5 +25,19 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : Decoding error at scanline 0, incorrect header check +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/ref/out-libtiff410.txt b/testsuite/tiff-misc/ref/out-libtiff410.txt index 477c237db6..fbd1f43e41 100644 --- a/testsuite/tiff-misc/ref/out-libtiff410.txt +++ b/testsuite/tiff-misc/ref/out-libtiff410.txt @@ -25,5 +25,19 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : Decoding error at scanline 0, incorrect header check +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/ref/out-libtiff430.txt b/testsuite/tiff-misc/ref/out-libtiff430.txt index 5da95516dd..41efe19647 100644 --- a/testsuite/tiff-misc/ref/out-libtiff430.txt +++ b/testsuite/tiff-misc/ref/out-libtiff430.txt @@ -25,5 +25,34 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : TIFFReadRawTile failed reading tile x=1088,y=72,z=0: Read error at row 4294967295, col 4294967295; got 114 bytes, expected 127 +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + GPS:Altitude: 2377 (2377 m) + GPS:DestBearing: 0 + GPS:DestBearingRef: "T" (true north) + GPS:DestDistance: 0 + GPS:DestDistanceRef: "K" (km) + GPS:DestLatitude: 0, 0, 0 + GPS:DestLatitudeRef: "N" + GPS:DestLongitude: 0, 0, 0 + GPS:DestLongitudeRef: "E" + GPS:Latitude: 40, 2, 22.686 + GPS:LatitudeRef: "N" + GPS:Longitude: 105, 15, 10.152 + GPS:LongitudeRef: "W" + GPS:Track: 146.254 + GPS:TrackRef: "T" (true north) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/ref/out-libtiff470.txt b/testsuite/tiff-misc/ref/out-libtiff470.txt new file mode 100644 index 0000000000..42c5582610 --- /dev/null +++ b/testsuite/tiff-misc/ref/out-libtiff470.txt @@ -0,0 +1,58 @@ +Reading src/separate.tif +src/separate.tif : 128 x 128, 3 channel, uint8 tiff + SHA-1: 486088DECAE711C444FDCAB009C378F7783AD9C5 + channel list: R, G, B + compression: "zip" + DateTime: "2020:10:25 15:32:04" + Orientation: 1 (normal) + planarconfig: "separate" + Software: "OpenImageIO 2.3.0dev : oiiotool --pattern fill:topleft=0,0,0:topright=1,0,0:bottomleft=0,1,0:bottomright=1,1,1 128x128 3 --planarconfig separate -scanline -attrib tiff:rowsperstrip 17 -d uint8 -o separate.tif" + oiio:BitsPerSample: 8 + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 2 + tiff:RowsPerStrip: 7 +Comparing "src/separate.tif" and "separate.tif" +PASS +oiiotool ERROR: read : No support for data format of "src/corrupt1.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info -v src/corrupt1.tif +oiiotool ERROR: read : File does not exist: "src/crash-1633.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1633.tif +oiiotool ERROR: read : File does not exist: "src/crash-1643.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr +iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : + +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + GPS:Altitude: 2377 (2377 m) + GPS:DestBearing: 0 + GPS:DestBearingRef: "T" (true north) + GPS:DestDistance: 0 + GPS:DestDistanceRef: "K" (km) + GPS:DestLatitude: 0, 0, 0 + GPS:DestLatitudeRef: "N" + GPS:DestLongitude: 0, 0, 0 + GPS:DestLongitudeRef: "E" + GPS:Latitude: 40, 2, 22.686 + GPS:LatitudeRef: "N" + GPS:Longitude: 105, 15, 10.152 + GPS:LongitudeRef: "W" + GPS:Track: 146.254 + GPS:TrackRef: "T" (true north) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 +Comparing "check1.tif" and "ref/check1.tif" +PASS diff --git a/testsuite/tiff-misc/ref/out.txt b/testsuite/tiff-misc/ref/out.txt index 9c6ee4260d..e52c8fcc9e 100644 --- a/testsuite/tiff-misc/ref/out.txt +++ b/testsuite/tiff-misc/ref/out.txt @@ -25,5 +25,19 @@ Full command line was: > oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 Comparing "check1.tif" and "ref/check1.tif" PASS diff --git a/testsuite/tiff-misc/run.py b/testsuite/tiff-misc/run.py index a2803b591c..d77b2714e2 100755 --- a/testsuite/tiff-misc/run.py +++ b/testsuite/tiff-misc/run.py @@ -25,4 +25,8 @@ command += oiiotool ("--oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr", failureok = True) command += iconvert ("src/crash-1709.tif crash-1709.exr", failureok=True) +# Test reading and writing GPS tags +command += oiiotool ("src/gps.tif -o gps.tif") +command += info_command ("gps.tif", safematch=True, hash=False) + outputs = [ "check1.tif", "out.txt" ] diff --git a/testsuite/tiff-misc/src/gps.tif b/testsuite/tiff-misc/src/gps.tif new file mode 100644 index 0000000000000000000000000000000000000000..0fdd3da2beb702ed82cc19190967485ad57e77c0 GIT binary patch literal 1058 zcmb_bJxc>Y5S_ck7z+)4Bz|CVjUXc7Zq0`|5Y*%j5786~f}LO#3&l#UM6l3G#KKC% zLah7&Rsk)AxC2|gk* zoHGN=^&a4Caeocsx(T@EFJ-EnZ7SQ!8CJ$Nv$i=z<@L&%x4N)g@yfSl&1Z(klq$K!Og8ISrsKLf*H!tkQn5r^CvVqUGp@Wj`zWs_8o4{7 z_oXgeTkjkCJ=7_Q*aLJJJ8JOfe9qmT1v3ZqE!4NNg1*zi7mi3EPxt3Gjgw9d{v4Jt z6rLu}W)2<|ej7v#m@|)oTO?){=Y;U5;M;}gnjMmR0X`|b13o2u6?|u?PM6eq0^cqC zB;tF7e+JLi59dDk$opn>9=}mqku%ii^N&+JQ=Pn~;Jj;iL_45hoX7T?y%lNo)qMp? x{iNAXpWv|(P?INDsw;$k3d44U`|6SoB{eAy$J^@E)U*iA( literal 0 HcmV?d00001 diff --git a/testsuite/tiff-suite/ref/out-alt.txt b/testsuite/tiff-suite/ref/out-alt.txt index 5a65b8c5b9..7009cf37fa 100644 --- a/testsuite/tiff-suite/ref/out-alt.txt +++ b/testsuite/tiff-suite/ref/out-alt.txt @@ -77,11 +77,14 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 Exif:FocalPlaneYResolution: 847 + Exif:ISOSpeedRatings: 125 Exif:MaxApertureValue: 3.53 (f/3.4) Exif:MeteringMode: 1 (average) + Exif:PhotographicSensitivity: 125 Exif:SensingMethod: 2 (1-chip color area) Exif:ShutterSpeedValue: 6.5 (1/90 s) Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 oiio:ColorSpace: "srgb_rec709_scene" tiff:ColorSpace: "YCbCr" @@ -100,6 +103,7 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif XResolution: 72 YResolution: 72 Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 1 @@ -122,6 +126,7 @@ Reading ../oiio-images/libtiffpic/fax2d.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/fax2d.tif" and "fax2d.tif" @@ -140,6 +145,7 @@ Reading ../oiio-images/libtiffpic/g3test.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/g3test.tif" and "g3test.tif" @@ -247,9 +253,11 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) + Exif:ISOSpeedRatings: 200 Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 200 Exif:Saturation: 0 (normal) Exif:SceneCaptureType: 0 (standard) Exif:Sharpness: 2 (hard) @@ -316,6 +324,7 @@ Reading ../oiio-images/libtiffpic/ycbcr-cat.tif Orientation: 1 (normal) planarconfig: "contig" Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 5 @@ -336,6 +345,7 @@ Reading ../oiio-images/libtiffpic/smallliz.tif XResolution: 100 YResolution: 100 Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 @@ -354,6 +364,7 @@ Reading ../oiio-images/libtiffpic/zackthecat.tif ResolutionUnit: "in" XResolution: 75 YResolution: 75 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 diff --git a/testsuite/tiff-suite/ref/out-alt2.txt b/testsuite/tiff-suite/ref/out-alt2.txt index ad3878cc23..cb4091f3b6 100644 --- a/testsuite/tiff-suite/ref/out-alt2.txt +++ b/testsuite/tiff-suite/ref/out-alt2.txt @@ -77,11 +77,14 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 Exif:FocalPlaneYResolution: 847 + Exif:ISOSpeedRatings: 125 Exif:MaxApertureValue: 3.53 (f/3.4) Exif:MeteringMode: 1 (average) + Exif:PhotographicSensitivity: 125 Exif:SensingMethod: 2 (1-chip color area) Exif:ShutterSpeedValue: 6.5 (1/90 s) Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 oiio:ColorSpace: "srgb_rec709_scene" tiff:ColorSpace: "YCbCr" @@ -100,6 +103,7 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif XResolution: 72 YResolution: 72 Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 1 @@ -122,6 +126,7 @@ Reading ../oiio-images/libtiffpic/fax2d.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/fax2d.tif" and "fax2d.tif" @@ -140,6 +145,7 @@ Reading ../oiio-images/libtiffpic/g3test.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/g3test.tif" and "g3test.tif" @@ -247,9 +253,11 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) + Exif:ISOSpeedRatings: 200 Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 200 Exif:Saturation: 0 (normal) Exif:SceneCaptureType: 0 (standard) Exif:Sharpness: 2 (hard) @@ -316,6 +324,7 @@ Reading ../oiio-images/libtiffpic/ycbcr-cat.tif Orientation: 1 (normal) planarconfig: "contig" Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 5 @@ -336,6 +345,7 @@ Reading ../oiio-images/libtiffpic/smallliz.tif XResolution: 100 YResolution: 100 Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 @@ -354,6 +364,7 @@ Reading ../oiio-images/libtiffpic/zackthecat.tif ResolutionUnit: "in" XResolution: 75 YResolution: 75 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 diff --git a/testsuite/tiff-suite/ref/out-jpeg9b.txt b/testsuite/tiff-suite/ref/out-jpeg9b.txt index 494a935621..93f136dc8a 100644 --- a/testsuite/tiff-suite/ref/out-jpeg9b.txt +++ b/testsuite/tiff-suite/ref/out-jpeg9b.txt @@ -77,11 +77,14 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 Exif:FocalPlaneYResolution: 847 + Exif:ISOSpeedRatings: 125 Exif:MaxApertureValue: 3.53 (f/3.4) Exif:MeteringMode: 1 (average) + Exif:PhotographicSensitivity: 125 Exif:SensingMethod: 2 (1-chip color area) Exif:ShutterSpeedValue: 6.5 (1/90 s) Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 oiio:ColorSpace: "srgb_rec709_scene" tiff:ColorSpace: "YCbCr" @@ -100,6 +103,7 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif XResolution: 72 YResolution: 72 Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 1 @@ -122,6 +126,7 @@ Reading ../oiio-images/libtiffpic/fax2d.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/fax2d.tif" and "fax2d.tif" @@ -140,6 +145,7 @@ Reading ../oiio-images/libtiffpic/g3test.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/g3test.tif" and "g3test.tif" @@ -247,9 +253,11 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) + Exif:ISOSpeedRatings: 200 Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 200 Exif:Saturation: 0 (normal) Exif:SceneCaptureType: 0 (standard) Exif:Sharpness: 2 (hard) @@ -316,6 +324,7 @@ Reading ../oiio-images/libtiffpic/ycbcr-cat.tif Orientation: 1 (normal) planarconfig: "contig" Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 5 @@ -336,6 +345,7 @@ Reading ../oiio-images/libtiffpic/smallliz.tif XResolution: 100 YResolution: 100 Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 @@ -354,6 +364,7 @@ Reading ../oiio-images/libtiffpic/zackthecat.tif ResolutionUnit: "in" XResolution: 75 YResolution: 75 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 diff --git a/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt b/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt index 423c4a2218..cf54f5b29e 100644 --- a/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt +++ b/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt @@ -77,11 +77,14 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 Exif:FocalPlaneYResolution: 847 + Exif:ISOSpeedRatings: 125 Exif:MaxApertureValue: 3.53 (f/3.4) Exif:MeteringMode: 1 (average) + Exif:PhotographicSensitivity: 125 Exif:SensingMethod: 2 (1-chip color area) Exif:ShutterSpeedValue: 6.5 (1/90 s) Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 oiio:ColorSpace: "srgb_rec709_scene" tiff:ColorSpace: "YCbCr" @@ -100,6 +103,7 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif XResolution: 72 YResolution: 72 Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 1 @@ -122,6 +126,7 @@ Reading ../oiio-images/libtiffpic/fax2d.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/fax2d.tif" and "fax2d.tif" @@ -140,6 +145,7 @@ Reading ../oiio-images/libtiffpic/g3test.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/g3test.tif" and "g3test.tif" @@ -247,9 +253,11 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) + Exif:ISOSpeedRatings: 200 Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 200 Exif:Saturation: 0 (normal) Exif:SceneCaptureType: 0 (standard) Exif:Sharpness: 2 (hard) @@ -316,6 +324,7 @@ Reading ../oiio-images/libtiffpic/ycbcr-cat.tif Orientation: 1 (normal) planarconfig: "contig" Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 5 @@ -336,6 +345,7 @@ Reading ../oiio-images/libtiffpic/smallliz.tif XResolution: 100 YResolution: 100 Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 @@ -354,6 +364,7 @@ Reading ../oiio-images/libtiffpic/zackthecat.tif ResolutionUnit: "in" XResolution: 75 YResolution: 75 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 diff --git a/testsuite/tiff-suite/ref/out.txt b/testsuite/tiff-suite/ref/out.txt index a28ac71677..9e1dfeb612 100644 --- a/testsuite/tiff-suite/ref/out.txt +++ b/testsuite/tiff-suite/ref/out.txt @@ -77,11 +77,14 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 Exif:FocalPlaneYResolution: 847 + Exif:ISOSpeedRatings: 125 Exif:MaxApertureValue: 3.53 (f/3.4) Exif:MeteringMode: 1 (average) + Exif:PhotographicSensitivity: 125 Exif:SensingMethod: 2 (1-chip color area) Exif:ShutterSpeedValue: 6.5 (1/90 s) Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 oiio:ColorSpace: "srgb_rec709_scene" tiff:ColorSpace: "YCbCr" @@ -100,6 +103,7 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif XResolution: 72 YResolution: 72 Exif:YCbCrPositioning: 2 + Exif:YCbCrSubsampling: 2, 1 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 1 @@ -122,6 +126,7 @@ Reading ../oiio-images/libtiffpic/fax2d.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/fax2d.tif" and "fax2d.tif" @@ -140,6 +145,7 @@ Reading ../oiio-images/libtiffpic/g3test.tif YResolution: 98 oiio:BitsPerSample: 1 tiff:Compression: 3 + tiff:PageNumber: 1, 1 tiff:PhotometricInterpretation: 0 tiff:PlanarConfiguration: 1 Comparing "../oiio-images/libtiffpic/g3test.tif" and "g3test.tif" @@ -247,9 +253,11 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) + Exif:ISOSpeedRatings: 200 Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 200 Exif:Saturation: 0 (normal) Exif:SceneCaptureType: 0 (standard) Exif:Sharpness: 2 (hard) @@ -316,6 +324,7 @@ Reading ../oiio-images/libtiffpic/ycbcr-cat.tif Orientation: 1 (normal) planarconfig: "contig" Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 5 @@ -336,6 +345,7 @@ Reading ../oiio-images/libtiffpic/smallliz.tif XResolution: 100 YResolution: 100 Exif:YCbCrPositioning: 1 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 @@ -354,6 +364,7 @@ Reading ../oiio-images/libtiffpic/zackthecat.tif ResolutionUnit: "in" XResolution: 75 YResolution: 75 + Exif:YCbCrSubsampling: 2, 2 oiio:BitsPerSample: 8 tiff:ColorSpace: "YCbCr" tiff:Compression: 6 From e748f8159fd92f2a0e154a735a6b7f37d404f5fd Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Fri, 6 Mar 2026 20:11:17 -0800 Subject: [PATCH 139/508] ci: On Mac Intel CI variant, don't insall openvdb, for speed (#5065) Lately, some Mac Intel CI runs have taken forever or failed because of timeout. It appears to be because some packages are maybe not cached (bottled) for Intel anymore? It only happens sometimes, so maybe it fails when the package version has just been bumped, but eventually somebody published a pre-built bottle and then it's ok? openvdb and its dependencies seem to be a frequent culprit. Let's just skip them for this one soon-to-be-obosolete variant. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- .github/workflows/ci.yml | 6 +++--- src/build-scripts/install_homebrew_deps.bash | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffdf961b9e..beeac9b1e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -608,12 +608,12 @@ jobs: cc_compiler: clang cxx_compiler: clang++ cxx_std: 17 - python_ver: "3.13" + python_ver: "3.14" simd: sse4.2,avx2 ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 - INSTALL_QT=0 INSTALL_OPENCV=0 - optional_deps_append: 'OpenCV;Qt5;Qt6' + INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 + optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' benchmark: 1 - desc: MacOS-14-ARM aclang15/C++20/py3.13 runner: macos-14 diff --git a/src/build-scripts/install_homebrew_deps.bash b/src/build-scripts/install_homebrew_deps.bash index e8cfe2bc59..b26e35a93d 100755 --- a/src/build-scripts/install_homebrew_deps.bash +++ b/src/build-scripts/install_homebrew_deps.bash @@ -32,6 +32,7 @@ if [[ "$OIIO_BREW_INSTALL_PACKAGES" == "" ]] ; then OIIO_BREW_INSTALL_PACKAGES=" \ ccache \ dcmtk \ + expat \ ffmpeg \ imath \ libheif \ @@ -41,7 +42,6 @@ if [[ "$OIIO_BREW_INSTALL_PACKAGES" == "" ]] ; then opencolorio \ openexr \ openjpeg \ - openvdb \ ptex \ pybind11 \ robin-map \ @@ -50,6 +50,9 @@ if [[ "$OIIO_BREW_INSTALL_PACKAGES" == "" ]] ; then if [[ "${USE_OPENCV:=}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then OIIO_BREW_INSTALL_PACKAGES+=" opencv" fi + if [[ "${USE_OPENVDB:=1}" != "0" ]] && [[ "${INSTALL_OPENVDB:=1}" != "0" ]] ; then + OIIO_BREW_INSTALL_PACKAGES+=" openvdb" + fi if [[ "${USE_QT:=1}" != "0" ]] && [[ "${INSTALL_QT:=1}" != "0" ]] ; then OIIO_BREW_INSTALL_PACKAGES+=" qt${QT_VERSION}" fi @@ -62,9 +65,9 @@ brew list --versions # Set up paths. These will only affect the caller if this script is # run with 'source' rather than in a separate shell. -export PATH=/usr/local/opt/qt5/bin:$PATH -export PATH=/usr/local/opt/python/libexec/bin:$PATH -export PYTHONPATH=/usr/local/lib/python${PYTHON_VERSION}/site-packages:$PYTHONPATH +export PATH=${HOMEBREW_PREFIX}/opt/qt5/bin:$PATH +export PATH=${HOMEBREW_PREFIX}/opt/python/libexec/bin:$PATH +export PYTHONPATH=${HOMEBREW_PREFIX}/lib/python${PYTHON_VERSION}/site-packages:$PYTHONPATH # Save the env for use by other stages src/build-scripts/save-env.bash From 1cb93e84bd6e30fdc3be653e6dd652f4ec6b28ee Mon Sep 17 00:00:00 2001 From: Aamir Raza Date: Fri, 6 Mar 2026 23:31:30 -0500 Subject: [PATCH 140/508] docs: Update description for dwaCompressionLevel (#5074) Clarify in the documentation that `openexr:dwaCompressionLevel` is a read-only attribute and cannot be used to set the compression level on write. Not sure if this is an obvious change or not, but it wasn't immediately clear if the attribute is meant to be writable. Signed-off-by: Aamir Raza Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/doc/builtinplugins.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 92c8a5f6bf..7b5c882ad4 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1608,7 +1608,9 @@ The official OpenEXR site is http://www.openexr.com/. - the MIPmap rounding mode of the file. * - ``openexr:dwaCompressionLevel`` - float - - compression level for dwaa or dwab compression (default: 45.0). + - compression level for dwaa or dwab compression (default: 45.0). + Reflects the level used when reading an existing file. To set + the compression level, use the ``compression`` attribute. * - ``openexr::luminancechroma`` - int - If nonzero, indicates whether the image is a luminance-chroma image. From 4f0082ce46083b76377630199f8cbcde31d5813a Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 7 Mar 2026 00:57:14 -0800 Subject: [PATCH 141/508] test: Add new ref output for tiff-misc (#5075) I don't know why, maybe some slightly different version of libtiff rolled out to the GHA runners, but despite no changes to our TIFF handling in the recent past, just today we started intermittently getting different output for this test on some of the Linux runners. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- testsuite/tiff-misc/ref/out-libtiff409.txt | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 testsuite/tiff-misc/ref/out-libtiff409.txt diff --git a/testsuite/tiff-misc/ref/out-libtiff409.txt b/testsuite/tiff-misc/ref/out-libtiff409.txt new file mode 100644 index 0000000000..5149930b37 --- /dev/null +++ b/testsuite/tiff-misc/ref/out-libtiff409.txt @@ -0,0 +1,43 @@ +Reading src/separate.tif +src/separate.tif : 128 x 128, 3 channel, uint8 tiff + SHA-1: 486088DECAE711C444FDCAB009C378F7783AD9C5 + channel list: R, G, B + compression: "zip" + DateTime: "2020:10:25 15:32:04" + Orientation: 1 (normal) + planarconfig: "separate" + Software: "OpenImageIO 2.3.0dev : oiiotool --pattern fill:topleft=0,0,0:topright=1,0,0:bottomleft=0,1,0:bottomright=1,1,1 128x128 3 --planarconfig separate -scanline -attrib tiff:rowsperstrip 17 -d uint8 -o separate.tif" + oiio:BitsPerSample: 8 + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 2 + tiff:RowsPerStrip: 7 +Comparing "src/separate.tif" and "separate.tif" +PASS +oiiotool ERROR: read : No support for data format of "src/corrupt1.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info -v src/corrupt1.tif +oiiotool ERROR: read : File does not exist: "src/crash-1633.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info -v src/crash-1633.tif +oiiotool ERROR: read : File does not exist: "src/crash-1643.tif" +Full command line was: +> oiiotool --oiioattrib try_all_readers 0 --info src/crash-1643.tif -o out.exr +iconvert ERROR copying "src/crash-1709.tif" to "crash-1709.exr" : + TIFFReadRawTile failed reading tile x=1088,y=72,z=0: Read error at row 4294967295, col 4294967295; got 114 bytes, expected 127 +Reading gps.tif +gps.tif : 64 x 64, 3 channel, uint8 tiff + channel list: R, G, B + compression: "zip" + Orientation: 1 (normal) + planarconfig: "contig" + Exif:ColorSpace: 1 + Exif:SubjectDistance: 0 (0 m) + oiio:BitsPerSample: 8 + oiio:ColorSpace: "srgb_rec709_scene" + tiff:Compression: 8 + tiff:PhotometricInterpretation: 2 + tiff:PlanarConfiguration: 1 + tiff:RowsPerStrip: 32 +Comparing "check1.tif" and "ref/check1.tif" +PASS From c64154785434dfb2af2de508cbf6f66f8e66a473 Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sat, 7 Mar 2026 00:58:13 -0800 Subject: [PATCH 142/508] docs: fix formatting examples for version macros (#5073) The comments that explain how to use the OIIO_MAKE_VERSION and related macros actually were using formatting that would have been rejected by our own clang-format settings. Change those examples so that they use the correct formatting that woule be accepted by the project. Signed-off-by: Larry Gritz Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/include/OpenImageIO/oiioversion.h.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/include/OpenImageIO/oiioversion.h.in b/src/include/OpenImageIO/oiioversion.h.in index 3580e1934b..374a5fa00c 100644 --- a/src/include/OpenImageIO/oiioversion.h.in +++ b/src/include/OpenImageIO/oiioversion.h.in @@ -55,7 +55,7 @@ // Construct a single integer version number from major, minor, patch. // Example of its use: // -// #if OIIO_VERSION >= OIIO_MAKE_VERSION(2,3,0) +// #if OIIO_VERSION >= OIIO_MAKE_VERSION(2, 3, 0) // ... use a feature introduced in version 2.3.0 ... // #endif // @@ -96,7 +96,7 @@ // were deprecated as of that version may be hidden from view of software // including the OIIO headers. For example, if a downstream project says // -// #define OIIO_DISABLE_DEPRECATED OIIO_MAKE_VERSION(2,2,0) +// #define OIIO_DISABLE_DEPRECATED OIIO_MAKE_VERSION(2, 2, 0) // // before including any OpenImageIO header, then we will do our best to make // it a compile-time error if they try to use anything that was deprecated @@ -108,7 +108,7 @@ // To clarify, if version 2.2 is the first to deprecate a certain definition, // then it is considered good practice to guard it like this: // -// #if OIIO_DISABLE_DEPRECATED < OIIO_MAKE_VERSION(2,2,0) +// #if OIIO_DISABLE_DEPRECATED < OIIO_MAKE_VERSION(2, 2, 0) // ... deprecated definition here ... // #endif // From d661533577b7aabc9c8f9028967f7b715268cc05 Mon Sep 17 00:00:00 2001 From: Pascal Lecocq Date: Sat, 7 Mar 2026 10:57:28 -0800 Subject: [PATCH 143/508] fix(texture): fix texture overblur with st-blur parameters (#5071) Fixes #5069 This PR fixes the texture blur overstimate reported in #5069 We provide the following changes: * Use the mathematically correct Pythagorean form to properly adjust the ellispse footprint with st-blur parameters. * Add a new legacy_texture_blur attribute in TextureSystem to opt into the fix without breaking existing renders. * Add a new --fix-texture-blur option in testtex to enable the fix. * Add a new texture-blurfix test. Note: This PR has been partially edited using the Claude coding assistant. --------- Signed-off-by: Pascal Lecocq Signed-off-by: Vlad (Kuzmin) Erium Signed-off-by: Vlad --- src/cmake/testing.cmake | 1 + src/libtexture/texture_pvt.h | 7 +++ src/libtexture/texturesys.cpp | 44 ++++++++++++++---- src/testtex/testtex.cpp | 7 +++ .../texture-blurfix/ref/checker-0.00.tif | Bin 0 -> 116180 bytes .../texture-blurfix/ref/checker-0.02.tif | Bin 0 -> 181633 bytes .../texture-blurfix/ref/checker-0.05.tif | Bin 0 -> 150925 bytes .../texture-blurfix/ref/checker-0.10.tif | Bin 0 -> 107969 bytes .../texture-blurfix/ref/checker-0.20.tif | Bin 0 -> 52892 bytes .../texture-blurfix/ref/checker-lgb-0.00.tif | Bin 0 -> 116180 bytes .../texture-blurfix/ref/checker-lgb-0.02.tif | Bin 0 -> 172308 bytes .../texture-blurfix/ref/checker-lgb-0.05.tif | Bin 0 -> 133834 bytes .../texture-blurfix/ref/checker-lgb-0.10.tif | Bin 0 -> 76233 bytes .../texture-blurfix/ref/checker-lgb-0.20.tif | Bin 0 -> 9981 bytes testsuite/texture-blurfix/run.py | 31 ++++++++++++ 15 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 testsuite/texture-blurfix/ref/checker-0.00.tif create mode 100644 testsuite/texture-blurfix/ref/checker-0.02.tif create mode 100644 testsuite/texture-blurfix/ref/checker-0.05.tif create mode 100644 testsuite/texture-blurfix/ref/checker-0.10.tif create mode 100644 testsuite/texture-blurfix/ref/checker-0.20.tif create mode 100644 testsuite/texture-blurfix/ref/checker-lgb-0.00.tif create mode 100644 testsuite/texture-blurfix/ref/checker-lgb-0.02.tif create mode 100644 testsuite/texture-blurfix/ref/checker-lgb-0.05.tif create mode 100644 testsuite/texture-blurfix/ref/checker-lgb-0.10.tif create mode 100644 testsuite/texture-blurfix/ref/checker-lgb-0.20.tif create mode 100644 testsuite/texture-blurfix/run.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index bfa588ca7b..4dd2360343 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -186,6 +186,7 @@ macro (oiio_add_all_tests) texture-uint8 texture-width0blur texture-wrapfill + texture-blurfix texture-fat texture-skinny texture-stats texture-threadtimes diff --git a/src/libtexture/texture_pvt.h b/src/libtexture/texture_pvt.h index c1de13890a..aa7f78a52e 100644 --- a/src/libtexture/texture_pvt.h +++ b/src/libtexture/texture_pvt.h @@ -467,6 +467,13 @@ class TextureSystemImpl { int m_max_tile_channels; ///< narrow tile ID channel range when ///< the file has more channels int m_stochastic; +#if OIIO_VERSION_GREATER_EQUAL(3, 2, 0) + bool m_legacy_texture_blur = false; ///< Use legacy texture blur behavior? + // Opt OUT of the fix for 3.2 and beyond +#else + bool m_legacy_texture_blur = true; ///< Use legacy texture blur behavior? + // Opt IN to get the fix for 3.1 +#endif static EightBitConverter uchar2float; enum StochasticStrategyBits { diff --git a/src/libtexture/texturesys.cpp b/src/libtexture/texturesys.cpp index 249c47b7aa..8c49f3c2fe 100644 --- a/src/libtexture/texturesys.cpp +++ b/src/libtexture/texturesys.cpp @@ -746,10 +746,11 @@ void TextureSystemImpl::init() { m_Mw2c.makeIdentity(); - m_gray_to_rgb = false; - m_flip_t = false; - m_max_tile_channels = 6; - m_stochastic = StochasticStrategy_None; + m_gray_to_rgb = false; + m_flip_t = false; + m_max_tile_channels = 6; + m_stochastic = StochasticStrategy_None; + m_legacy_texture_blur = false; hq_filter.reset(Filter1D::create("b-spline", 4)); m_statslevel = 0; @@ -907,6 +908,10 @@ TextureSystemImpl::attribute(string_view name, TypeDesc type, const void* val) unit_test_texture_blur = *(const float*)val; return true; } + if (name == "legacy_texture_blur" && type == TypeInt) { + m_legacy_texture_blur = (*(const int*)val != 0); + return true; + } // Maybe it's meant for the cache? return m_imagecache->attribute(name, type, val); @@ -926,6 +931,7 @@ TextureSystemImpl::getattributetype(string_view name) const { "flip_t", TypeInt }, { "max_tile_channels", TypeInt }, { "stochastic", TypeInt }, + { "legacy_texture_blur", TypeInt }, }; // clang-format on @@ -975,6 +981,10 @@ TextureSystemImpl::getattribute(string_view name, TypeDesc type, *(int*)val = m_stochastic; return true; } + if (name == "legacy_texture_blur" && type == TypeInt) { + *(int*)val = m_legacy_texture_blur; + return true; + } // If not one of these, maybe it's an attribute meant for the image cache? return m_imagecache->getattribute(name, type, val); @@ -1890,7 +1900,7 @@ adjust_width(float& dsdx, float& dtdx, float& dsdy, float& dtdy, float swidth, // back to this and solve it better. inline void adjust_blur(float& majorlength, float& minorlength, float& theta, float sblur, - float tblur) + float tblur, bool legacy_textblur = false) { if (sblur + tblur != 0.0f /* avoid the work when blur is zero */) { // Carefully add blur to the right derivative components in the @@ -1901,8 +1911,22 @@ adjust_blur(float& majorlength, float& minorlength, float& theta, float sblur, fast_sincos(theta, &sintheta, &costheta); sintheta = fabsf(sintheta); costheta = fabsf(costheta); - majorlength += sblur * costheta + tblur * sintheta; - minorlength += sblur * sintheta + tblur * costheta; + + if (legacy_textblur) { + // The legacy blur code just adds the blur to the major and minor + // axes, which is a crude approximation that is wrong at some angles + // but is very simple and fast. I'm leaving it here as an option + // for now, in case the new code causes any unforeseen problems. + majorlength += sblur * costheta + tblur * sintheta; + minorlength += sblur * sintheta + tblur * costheta; + } else { + const float sintheta2 = sintheta * sintheta; + const float costheta2 = costheta * costheta; + const float sblur2 = sblur * sblur; + const float tblur2 = tblur * tblur; + majorlength += sqrtf(sblur2 * costheta2 + tblur2 * sintheta2); + minorlength += sqrtf(sblur2 * sintheta2 + tblur2 * costheta2); + } #if 1 if (minorlength > majorlength) { // Wildly uneven sblur and tblur values might swap which axis is @@ -2273,7 +2297,8 @@ TextureSystemImpl::texture_lookup(TextureFile& texturefile, // or bicubic texture probes, and therefore runtime! ellipse_axes(dsdx, dtdx, dsdy, dtdy, majorlength, minorlength, theta); - adjust_blur(majorlength, minorlength, theta, options.sblur, options.tblur); + adjust_blur(majorlength, minorlength, theta, options.sblur, options.tblur, + m_legacy_texture_blur); float aspect, trueaspect; aspect = anisotropic_aspect(majorlength, minorlength, options, trueaspect); @@ -3388,7 +3413,8 @@ TextureSystemImpl::visualize_ellipse(const std::string& name, float dsdx, ellipse_axes(dsdx, dtdx, dsdy, dtdy, majorlength, minorlength, theta, ABCF); std::cout << " ellipse major " << majorlength << ", minor " << minorlength << ", theta " << theta << "\n"; - adjust_blur(majorlength, minorlength, theta, sblur, tblur); + adjust_blur(majorlength, minorlength, theta, sblur, tblur, + m_legacy_texture_blur); std::cout << " post " << sblur << ' ' << tblur << " blur: major " << majorlength << ", minor " << minorlength << "\n\n"; diff --git a/src/testtex/testtex.cpp b/src/testtex/testtex.cpp index 82ee772292..7ca01efff1 100644 --- a/src/testtex/testtex.cpp +++ b/src/testtex/testtex.cpp @@ -86,6 +86,7 @@ static bool test_derivs = false; static bool test_statquery = false; static bool invalidate_before_iter = true; static bool close_before_iter = false; +static bool legacy_texture_blur = false; static bool runstats = false; static bool udim_tests = false; static bool do_gettextureinfo = true; @@ -200,6 +201,8 @@ getargs(int argc, const char* argv[]) .help("Do not warp the image->texture mapping"); ap.arg("--tube", &tube) .help("Make a tube projection"); + ap.arg("--legacy-texture-blur", &legacy_texture_blur) + .help("Use mathematically correct texture blur instead of legacy overblur"); ap.arg("--ctr", &test_construction) .help("Test TextureOpt construction time"); ap.arg("--gettexels", &test_gettexels) @@ -1831,6 +1834,10 @@ main(int argc, const char* argv[]) texsys->attribute("gray_to_rgb", gray_to_rgb); texsys->attribute("flip_t", flip_t); texsys->attribute("stochastic", stochastic); + if (legacy_texture_blur) + texsys->attribute("legacy_texture_blur", 1); + else + texsys->attribute("legacy_texture_blur", 0); texcolortransform_id = std::max(0, texsys->get_colortransform_id(ustring(texcolorspace), ustring("scene_linear"))); diff --git a/testsuite/texture-blurfix/ref/checker-0.00.tif b/testsuite/texture-blurfix/ref/checker-0.00.tif new file mode 100644 index 0000000000000000000000000000000000000000..6c4e7553a912a3e0c57d9397df2d1a2f13e0462d GIT binary patch literal 116180 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 $`TQ7%rak8GW?g9b3)cuud5d; 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°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 T-b{?JD2b-}2x@k%<-h*{Ux}^| literal 0 HcmV?d00001 diff --git a/testsuite/texture-blurfix/ref/checker-0.02.tif b/testsuite/texture-blurfix/ref/checker-0.02.tif new file mode 100644 index 0000000000000000000000000000000000000000..ddeee1f47828ee82db98a6b547853bb212ea9675 GIT binary patch literal 181633 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

5T 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 literal 0 HcmV?d00001 diff --git a/testsuite/texture-blurfix/ref/checker-0.05.tif b/testsuite/texture-blurfix/ref/checker-0.05.tif new file mode 100644 index 0000000000000000000000000000000000000000..d8005db17d1a4df6de42dde9e4e0625fd587e9b5 GIT binary patch literal 150925 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%0Axs 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 literal 0 HcmV?d00001 diff --git a/testsuite/texture-blurfix/ref/checker-0.10.tif b/testsuite/texture-blurfix/ref/checker-0.10.tif new file mode 100644 index 0000000000000000000000000000000000000000..93770370b52b522053a9ba6ab4194ecdaebbad13 GIT binary patch literal 107969 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 literal 0 HcmV?d00001 diff --git a/testsuite/texture-blurfix/ref/checker-0.20.tif b/testsuite/texture-blurfix/ref/checker-0.20.tif new file mode 100644 index 0000000000000000000000000000000000000000..06c6f6039065136a6fd745d575706315eae7df59 GIT binary patch literal 52892 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

P-EDyqSd$5-4)# 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�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�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

rw-%xoN>7TEEJd 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 literal 0 HcmV?d00001 diff --git a/testsuite/texture-blurfix/ref/checker-lgb-0.05.tif b/testsuite/texture-blurfix/ref/checker-lgb-0.05.tif new file mode 100644 index 0000000000000000000000000000000000000000..b18385ea24b0cfe728822a9717c58e8f553425c0 GIT binary patch literal 133834 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