Skip to content

bug: ImageInput::read_image()/read_tiles() with an image_span transpose x and y on tiled files #5400

Description

@KMean

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:

  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<std::byte> + TypeDesc::FLOAT overload or the typed image_span<float> 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions