AcademySoftwareFoundation / AcademySoftwareFoundation/OpenImageIO
bug: ImageInput::read_image()/read_tiles() with an image_span transpose x and y on tiled files
- Dominant language
- C++
- Stars
- 2.4k
- Forks
- 698
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 47
Description
**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 9b96c377 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` plus `TypeDesc::FLOAT` overload, with every stride spelled out;
- the typed `image_span` overload, letting OpenImageIO compute every stride itself:
```cpp
const image_span 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:
1. Write a tiled EXR, e.g. 40x24 with 16x16 tiles, 3 channels, `float`.
2. Read it back into a `40*24*3` float buffer using either the `image_span` + `TypeDesc::FLOAT` overload or the typed `image_span` overload.
3. It returns `false` and `geterror()` is empty.
4. 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.
Contributor guide
Research direction
Start with the linked contrib/span_tiled_read_repro.cpp, then trace the ImageInput::read_image/read_tiles image_span overloads and compare them with the working pointer overload. Done means image_span reads tiled files with partial edge tiles and reports a useful error when it returns false; verify all cases in the repro.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- api, computer-graphics
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100