6675 Commits
Author SHA1 Message Date
Larry Gritz d3aa1658fc fix(psd): fixes against corrupt files with better validation (#5089)
1. Validate channel count against color mode to prevent heap buffer
   overflow.

2. Corrupted PSD files can declare a color mode (e.g. RGB, needing 3
   channels) with transparency (needing +1) but report fewer channels
   in the header.  This caused an out-of-bounds read in
   read_native_scanline when setup() built channel pointer arrays
   using mode_channel_count, which exceeded the actual
   m_image_data.channel_info size.

3. Additional buffer overflow protection, error checking and
propagation.

4. Avoid leak by properly freeing z_stream resources in decompress_zip.

Used Claude Code / Opus 4.6 to help narrow down and confirm the bugs 1
and 2.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-17 16:39:17 -07:00
Larry Gritz 7d16832211 fix(tiff): check for invalid bit depth for palette images (#5091)
Reading the TIFF 6.0 spec, on p. 23, it sure looks like palette images
can only be 4 or 8 bits per sample. But the libtiff set of test images
indeed has 2, 4, 8, and yes, 16 bps palette images among the examples.
So I think we should support up to 16, since we might encounter it in
the wild, but no higher.

We didn't previously check for this, though, and that could lead to
accepting invalid TIFF files, and then potentially have problems with
`(1 << m_bitspersample)` which appears several times. That of course is
safe if m_bitspersample can't be > 16.

Also check and proper error for a file that has a bitspersample that is
not one of the valid choices (1, 2, 4, 6, 8, 10, 12, 14, 16, 24, 32,
64), and proper errors for if the sampleformat was an unknown value or
was not a valid value for the number of bits per sample.

Note that this changes the reference output of some tests because the
new checks can alter the first error discovered in a corrupt file.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-17 16:05:46 -07:00
Larry Gritz 8abf94e4bd fix(bmp): use check_open to guard against corrupt resolutions (#5086)
Also change the x and y counters to uint64_t to prevent any possible
overflow as it's computing offsets.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 22:35:00 -07:00
Larry Gritz 95b339b3ef ci: Speed up slow benchmarks for debug and sanitizer CI tests (#5077)
Be even more aggressive for the runs that are both debug mode (including
sanitizers) and a CI run. Speeds up the slowest GHA test runs. Should
not affect ordinary builds.

This shaves about 8 minutes off the long "sanitizer" run, which is often
the last job to finish in our CI workflows.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 22:33:59 -07:00
Larry Gritz 5579f53eae build: Improve security by pinning auto-build dependencies by hash (#5076)
The files that match the pattern "src/cmake/build_<dependency>.cmake"
are all similar and each one is called by our build system if a
dependency is not found, to download and built it locally to be
incorporated into our build. One example is src/cmake/build_TIFF.cmake.

Within these files, the version to be retrieved is determined by a pair
of lines such as
```
set_cache (TIFF_BUILD_VERSION 4.7.1 "TIFF version for local builds")
set_cache (TIFF_GIT_TAG "v${TIFF_BUILD_VERSION}" "Git branch or tag")
```

The tag is passed to `build_dependency_with_cmake` with the `GIT_TAG`
argument. There is also an optional `GIT_COMMIT` argument, that we
usually do not use (previously, only needed if we wanted to build from a
commit that didn't have a version tag).

But it turns out that it's probably not safe to blindly retrieve a tag,
in case the dependency's repo has been hijacked and the tag reset to
point to a malicious commit.

So here is how to address this:

- `build_dependency_with_cmake` modifies its internal logic:
- If only the GIT_COMMIT is passed and GIT_TAG is blank, use the commit.
- If both GIT_COMMIT and GIT_TAG are passed, check out the tag but
verify that it is the commit specified, and if not, then consider it an
error and do not proceed with building the dependency.
- If only GIT_TAG is passed and not GIT_COMMIT, warn about the potential
safety problem.
- But as a special option for dangerous tasks, an override called
OpenImageIO_DEPENDENCY_BUILD_ALLOW_UNVERIFIED_TAGS allows an unverified
git tag to be checked out. This is how we make the "bleeding edge" test
still build against dependecy main/masters.

- For each of the `build_<dependency>.cmake` files, change it as
follows:
- Determine the commit hash of the specified version by checking the
repository.
- Add a line setting `<dependency>_GIT_COMMIT` to the corresponding
hash.
  - Pass both arguments to build_dependency_with_cmake.

This patch was made with the help of Claude Code using Opus 4.6. I
started the dialog with a prompt that was substantially similar to the
description above, detailing my proposed plan.

I first suggested that if only the TAG is present, to allow it as long
as it turned out to be an immutable git release tag, but it pointed out
that (a) this requires a build-time dependency on the `gh` command line
util, (b) not all our dependencies are on GitHub (such as libtiff on
GitLab), so we modified the plan to warn in that case rather than try to
determine if it's immutable. The rest proceeded more or less as
expected, with a few rounds of back and forth and corrections.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 22:33:21 -07:00
Larry Gritz f2e6f0c4b2 fix(tiff): fix buffer overrun and make better error reporting (#5082)
Certain CMYK files (maybe only corrupt ones?) hit an edge case that
caused us to read the original 4 channels but into space for only the 3
channels we ultimately report back. Fix that logic.

Also noticed several spots where we didn't correctly report back error
messages for newer versions of libtiff. This was some kind of oversight
from long ago.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 19:12:23 -07:00
Larry Gritz 7841ab5924 fix(jpeg): Improved safety and error reporting for jpeg and iptc (#5081)
* `decide_iptc_iim()` (semi-public) : change from taking a pointer &
length to a string_view, and internal safety checks.

* `jpeg_decode_iptc()` (non-public): change from taking an unlengthed
pointer (!) to a string_view, and internal safety checks.

* JpgInput::open(): a number of extra checks for memory bounds validity.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 14:47:10 -07:00
Larry Gritz 10f4e756c8 ImageInput::check_open fixes and new checks (#5087)
* fix check_open used m_spec instead of parameter spec!
  The function advertised that its doing checks on the parameter passed
  to it, but -- oops -- it was really doing it on this->m_spec. They
  were almost always (or always always?) the same, but still. Bug.
    
* Check zero-sized images separately from nonsensical negative sizes.
  By being togther, there was some confusion about whether "noimage"
  feature should negate the error (that feature should allow
  zero-sized images, but not negative dimensions).
    
* Add new checks for the full_width/etc to also be sensible.
    
* Some other minor refactoring of check_open.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 12:39:29 -07:00
Larry Gritz f672e8648d docs: keep TextureSystem docs in sync with ImageCache (#5085)
The docs for TexturesSystem::attribute() had a summary of ImageCache
options that were accepted and passed along. Over the years, a couple
had been added to IC without being reflected in the TS docs.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-15 00:35:12 -07:00
Larry Gritz ceff8075ba fix(tiff): wrong number of values passed to invert_photometric (#5083)
stripchans was multiplied in twice (it was already part of stripvals).
The error could overrun buffers.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-14 23:54:33 -07:00
Pascal Lecocq 92c6c6898c fix(texture): remove legacy_texture_blur override (#5080)
The m_legacy_texture_blur flag is set in TextureSystemImpl::Init, which
overrides the default initialisation depending on the OIIO version.

In other words, the texture blur fix was always enabled, regardless of
the OIIO version.

I also updated the help description for this flag in testtex.

This fixes minor shortcomings of #5071

---------

Signed-off-by: Pascal Lecocq <pascal.lecocq@gmail.com>
2026-03-11 16:57:25 -07:00
Larry Gritz dfdb5ef502 testing: New ref output for heif no-avif case (#5079)
Encountered at work: when using certain libheif dependency builds that
can read, but not write, avif files, we needed an additional reference
output.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-11 11:18:20 -07:00
Larry Gritz 62d5d9196f ci: For security, replace some workflow substitutions with safer env substitutions (#5070)
Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-10 17:11:10 -07:00
Larry Gritz bea3e7599d fix(oiiotool): better type understanding with -i:ch= and other cleanup (#5056)
Background: oiiotool is pretty good at remembering the data type of the
first file it reads in a command, and ensuring that the output preserves
the same types, despite the fact that internally, it tends to keep
everything in float. (As a further reminder: -d can override the
heuristics that make this work.)

But there was an exception: when `-i` was used with the optinal modifier
`-i:ch=...` to select a subset of channels or reorder channels upon
read, the underlying call to IBA::channels would cause it to not have
that information in the "nativespec" at the right time that it was
collected.

This PR primarily ensures that the `-i:ch=...` preserves the information
about which channels had which data types, despite the channel shuffle.

Some other bits and pieces along the way:

* A new `TypeDesc::basetype_merge()` variety that take a
`cspan<TypeDesc>`.
* A new utility `TypeDesc::all_types_equal(cspan<TypeDesc>)`
* Cleanup in IBA::channels() to more correctly compute the common type
of the channel subset that you actually specify.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-10 17:09:45 -07:00
Nathan Rusch 1d940693be fix(win): Embed manifest in OIIO .exes to enable long path handling (#5066)
Adds an embedded manifest to OIIO's generated executables on Windows,
which enables "long path" awareness.

This is done via a "resource" file, with the manifest resource ID
specified. From what I've read, this is more broadly compatible with
e.g. non-MSVC build toolchains like MinGW.

The manifest file was created by building OIIO from `main`, extracting
the manifest from the resulting `oiiotool` (using the `mt` tool), and
manually adding in the XML snippet from
https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#application-manifest-updates-to-declare-long-path-capability.

Fixes #5064 

Added a new Windows-only test to validate that `oiiotool` can correctly
read long image paths.

The one executable that I have not tested is `iv`. The only reason I'm
more curious about it than others is that it is a GUI executable, so I'm
not sure if its default

---------

Signed-off-by: Nathan Rusch <nrusch@users.noreply.github.com>
2026-03-07 13:23:02 -08:00
Larry Gritz 7c4b306ab8 fix(python): Address new pybind11 float/int auto-conversion behavior (#5058)
It's not in any pybind11 tagged release yet, but in its master, a change
has been introduced that causes it to allow functions with float
paramters to automatically match int arguments. This is fatal for us, as
we have set up several cases of methods that overload on int vs float,
and with this change, the float one gets called when an int is passed.
Pybind11 docs say that the `.noconvert()` modifier on the argument will
do the trick, but I'm finding that it doesn't help for overloaded
function names.

The solution is to find these cases, change our declaration to a generic
py::object, and then sort out what type was passed with py::isinstance.
It seems to mainly affect `attribute()` style calls.

In the process, minor changes to the pybind11 auto-builder allow it to
take a git commit hash directly. Also, I took the opportunity to remove
a few reference outputs that are no longer needed because they were
python2-specific.

Meanwhile, just in case anybody had been paying attention, the problem
in pybind11 master that had caused us to temporarily stop CI testing
against master seems to have been resolved. So re-enable the bleeding
edge test to use pybind11 master (which will fail if not for this PR
here), and bump the "latest version" tests to the latest pybind11 3.0.2.

I diagnosed the problem myself, wrote the solution for one class and
verified that it solved the issues with that class. Then I used Claude
Code to make the analogous set of changes to the other classes that
needed them and I reviewed that it was correct.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-07 10:59:29 -08:00
Larry Gritz 8ab986ba81 build: Test building with clang 22.1, fix warnings uncovered (#5067)
Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-07 10:58:41 -08:00
Pascal Lecocq 30142ed8d3 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 <pascal.lecocq@gmail.com>
2026-03-07 10:57:28 -08:00
Larry Gritz 583ace5c55 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 <lg@larrygritz.com>
2026-03-07 00:58:13 -08:00
Larry Gritz f1b399b768 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 <lg@larrygritz.com>
2026-03-07 00:57:14 -08:00
Aamir Raza 0156e6e951 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 <aamir_raza@outlook.com>
2026-03-06 20:31:30 -08:00
Larry Gritz 18619b8f48 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 <lg@larrygritz.com>
2026-03-06 20:11:17 -08:00
Larry GritzandCopilot 33c9999adb 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 <lg@larrygritz.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 10:07:38 -08:00
Larry Gritz 43b8aa9cb6 ci: Add CI test for MSVS 2026 (#5060)
And some minor fixes discovered along the way.

---------

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-03-01 14:13:28 -08:00
Larry Gritz 5125c96687 Bump version after developer preview tag
Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-02-28 15:14:58 -08:00
Larry Gritz a0940a9f1e Update CHANGES and stage for tagging 3.2.0.0-dev preview
Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-02-28 14:28:26 -08:00
Larry Gritz fed1137c1a ci: add MacOS 26 (ARM) to the CI lineup (#5059)
Signed-off-by: Larry Gritz <lg@larrygritz.com>
2026-02-27 17:09:17 -08:00
Larry Gritz 97597da8ed 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 <lg@larrygritz.com>
2026-02-27 17:08:17 -08:00
Larry Gritz 3cefa62f9c 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 <lg@larrygritz.com>
2026-02-26 05:54:30 -08:00
Larry Gritz 21dbd07a7c 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 <lg@larrygritz.com>
2026-02-26 05:22:46 -08:00
Larry Gritz 3092c42b52 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 <lg@larrygritz.com>
2026-02-26 05:06:15 -08:00
Shane Smith 3ec0b8a9d9 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 <shane.smith@dreamworks.com>
2026-02-23 20:36:56 -08:00
Larry Gritz 0ceac85217 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 <lg@larrygritz.com>
2026-02-23 20:31:49 -08:00
Larry Gritz e94a27004e 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 <lg@larrygritz.com>
2026-02-23 12:32:49 -08:00
Larry Gritz da49cd8a12 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 <lg@larrygritz.com>
2026-02-21 16:31:45 -08:00
Larry Gritz d2f45dbbdd 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 <lg@larrygritz.com>
2026-02-21 14:06:32 -08:00
Larry Gritz 944ffd50f7 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 <lg@larrygritz.com>
2026-02-21 14:06:07 -08:00
Larry Gritz 14dbd96dbb 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 <lg@larrygritz.com>
2026-02-19 22:47:59 -08:00
Larry Gritz 2486b66af8 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 <lg@larrygritz.com>
2026-02-19 10:28:06 -08:00
Larry Gritz 0457774292 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 <lg@larrygritz.com>
2026-02-14 04:59:46 -08:00
Brecht Van Lommel bf7bc9c6ef 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 <brecht@blender.org>
2026-02-13 12:38:19 -08:00
Larry Gritz 758082a9c7 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 <lg@larrygritz.com>
2026-02-12 21:29:24 -08:00
Lumina Wang 15034bb670 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 <lumina.wang@autodesk.com>
2026-02-12 11:51:37 -08:00
Larry Gritz 8a39d36304 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 <lg@larrygritz.com>
2026-02-11 16:04:03 -08:00
Larry Gritz 27b6799b52 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 <lg@larrygritz.com>
2026-02-10 21:06:41 -08:00
Larry Gritz 5e005e68a0 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 <lg@larrygritz.com>
2026-02-10 17:14:57 -08:00
Larry Gritz ee3370d004 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 <lg@larrygritz.com>
2026-02-07 03:04:06 -08:00
Larry Gritz 1188706bac 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 <lg@larrygritz.com>
2026-02-06 22:10:51 -08:00
Larry Gritz 81c54c46b5 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<float>`, 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 <lg@larrygritz.com>
2026-02-06 13:57:03 -08:00
Jesse Yurkovich ae20fc7894 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 <jesse.y@gmail.com>
2026-02-06 13:55:43 -08:00