Describe the bug
ImageInput::read_image(subimage, miplevel, chbegin, chend, format, image_span) returns false whenever a tiled image's width or height is not an exact multiple of the tile size. No error is recorded, so geterror() returns an empty string. The pointer overload reads the same files correctly.
The destination buffer here is exactly width * height * nchannels values, and the docs describe data as "the memory extent of the data buffer" without mentioning any tile-alignment requirement, so as far as I can tell there is no buffer a caller could pass that would work. That makes tiled images with partial edge tiles unreadable through this overload, and most real tiled images have partial edge tiles.
I expected this overload to read the same images the pointer overload reads, given it is documented as "the 'safe' preferred alternative to the version of read_image that takes raw pointers" — and, on failure, to record an error saying why.
I have not tried to pinpoint the cause in the reading code, so the above is only what is observable from the outside. It is possible the intended contract is different from what I assumed, in which case the silent false with no error message is still the part worth looking at.
OpenImageIO version and dependencies
OIIO 3.2.0.2dev | Windows/x86_64
Build compiler: MSVS 1951 | C++17/199711
HW features enabled at build: sse2
Dependencies: BZip2 1.0.8, fmt 12.1.0, Freetype 2.13.3, GIF 5.2.2, Imath 3.2.2,
libdeflate 1.25, libjpeg-turbo 3.1.3, LibRaw 0.22.0, OpenColorIO 2.5.1,
OpenEXR 3.4.7, OpenJPEG 2.5.4, openjph 0.26.3, PNG 1.6.55, Robinmap 1.4.1,
TIFF 4.7.1, WebP 1.6.0, ZLIB 1.3.1
Built from main at 9b96c37 with the vcpkg toolchain. Also reproduced identically on the released 3.1.12.0 (vcpkg binary, same machine and compiler), so this is not new in 3.2.
To Reproduce
Self-contained repro, public OpenImageIO API only, covering just this behaviour:
https://github.com/KMean/oiio-bind/blob/12025630fddf902417d79951d0e5d766bd515498/contrib/span_tiled_read_repro.cpp
It writes tiled OpenEXR files with write_image, then reads each back three ways and exits non-zero if the overloads disagree. Output on 3.2.0.2dev:
32x32, 16x16 tiles (exact multiple)
read_image(image_span, explicit strides) : ok
read_image(image_span, default strides) : ok
read_image(pointer) : ok
16x16, 16x16 tiles (exact multiple)
read_image(image_span, explicit strides) : ok
read_image(image_span, default strides) : ok
read_image(pointer) : ok
40x32, 16x16 tiles (PARTIAL edge tiles)
read_image(image_span, explicit strides) : FAILED
read_image(image_span, default strides) : FAILED
read_image(pointer) : ok
32x24, 16x16 tiles (PARTIAL edge tiles)
read_image(image_span, explicit strides) : FAILED
read_image(image_span, default strides) : FAILED
read_image(pointer) : ok
40x24, 16x16 tiles (PARTIAL edge tiles)
read_image(image_span, explicit strides) : FAILED
read_image(image_span, default strides) : FAILED
read_image(pointer) : ok
Both image_span forms are covered, and they fail identically:
- the
image_span<std::byte> plus TypeDesc::FLOAT overload, with every stride spelled out;
- the typed
image_span<float> overload, letting OpenImageIO compute every stride itself:
const image_span<float> data(buffer.data(), nchannels, width, height);
in->read_image(0, 0, 0, nchannels, data);
So the result does not depend on my stride arithmetic. Images whose dimensions are an exact multiple of the tile size are unaffected, and the pointer overload reads all five files correctly.
Steps:
- Write a tiled EXR, e.g. 40x24 with 16x16 tiles, 3 channels,
float.
- Read it back into a
40*24*3 float buffer using either the image_span<std::byte> + TypeDesc::FLOAT overload or the typed image_span<float> overload.
- It returns
false and geterror() is empty.
- The same file read with
read_image(0, 0, 0, 3, TypeDesc::FLOAT, ptr) succeeds.
I could not reproduce this through oiiotool, since the failing path is the image_span overload rather than anything the tools call.
Additional context
Found while writing Rust bindings over the 3.1 API. Happy to test a patch or provide more detail.
Describe the bug
ImageInput::read_image(subimage, miplevel, chbegin, chend, format, image_span)returnsfalsewhenever a tiled image's width or height is not an exact multiple of the tile size. No error is recorded, sogeterror()returns an empty string. The pointer overload reads the same files correctly.The destination buffer here is exactly
width * height * nchannelsvalues, and the docs describedataas "the memory extent of the data buffer" without mentioning any tile-alignment requirement, so as far as I can tell there is no buffer a caller could pass that would work. That makes tiled images with partial edge tiles unreadable through this overload, and most real tiled images have partial edge tiles.I expected this overload to read the same images the pointer overload reads, given it is documented as "the 'safe' preferred alternative to the version of read_image that takes raw pointers" — and, on failure, to record an error saying why.
I have not tried to pinpoint the cause in the reading code, so the above is only what is observable from the outside. It is possible the intended contract is different from what I assumed, in which case the silent
falsewith no error message is still the part worth looking at.OpenImageIO version and dependencies
Built from
mainat 9b96c37 with the vcpkg toolchain. Also reproduced identically on the released 3.1.12.0 (vcpkg binary, same machine and compiler), so this is not new in 3.2.To Reproduce
Self-contained repro, public OpenImageIO API only, covering just this behaviour:
https://github.com/KMean/oiio-bind/blob/12025630fddf902417d79951d0e5d766bd515498/contrib/span_tiled_read_repro.cpp
It writes tiled OpenEXR files with
write_image, then reads each back three ways and exits non-zero if the overloads disagree. Output on 3.2.0.2dev:Both
image_spanforms are covered, and they fail identically:image_span<std::byte>plusTypeDesc::FLOAToverload, with every stride spelled out;image_span<float>overload, letting OpenImageIO compute every stride itself:So the result does not depend on my stride arithmetic. Images whose dimensions are an exact multiple of the tile size are unaffected, and the pointer overload reads all five files correctly.
Steps:
float.40*24*3float buffer using either theimage_span<std::byte>+TypeDesc::FLOAToverload or the typedimage_span<float>overload.falseandgeterror()is empty.read_image(0, 0, 0, 3, TypeDesc::FLOAT, ptr)succeeds.I could not reproduce this through
oiiotool, since the failing path is theimage_spanoverload rather than anything the tools call.Additional context
Found while writing Rust bindings over the 3.1 API. Happy to test a patch or provide more detail.