Search code examples
openimageio

OpenImageIO: Open image to the display window resolution instead of the data resolution


I am working with OpenImage Denoiser, which loads EXR files using OpenImageIO.

Images are loaded like so:

  std::shared_ptr<ImageBuffer> loadImageOIIO(const std::string& filename, int channels)
  {
    auto in = OIIO::ImageInput::open(filename);
    if (!in)
      throw std::runtime_error("cannot open image file: " + filename);

    const OIIO::ImageSpec& spec = in->spec();
    if (channels == 0)
      channels = spec.nchannels;
    else if (spec.nchannels < channels)
      throw std::runtime_error("not enough image channels");
    auto image = std::make_shared<ImageBuffer>(spec.width, spec.height, channels);
    if (!in->read_image(0, 0, 0, channels, OIIO::TypeDesc::FLOAT, image->getData()))
      throw std::runtime_error("failed to read image data");
    in->close();

#if OIIO_VERSION < 10903
    OIIO::ImageInput::destroy(in);
#endif
    return image;
  }

However, this crops the image to the bounding box of the data window. Because my image has 0 values, this image is smaller than the actual input image.

How can I get an ImageBuffer with the full resolution of the Display window?


Solution

  • The above lines are allocating a width x height x channels image, but the problem is that from OIIO's point of view, spec.width and height describe the data window. So you want to start with

    auto image = std::make_shared<ImageBuffer>(spec.full_width, spec.full_height, channels);
    

    That allocates a buffer for the "full" (display) window.

    Then you want to call read_image using some of the additional parameters that you've left as defaults, the ones that let you give explicit strides. Without the strides, it assumes that you're reading into a buffer that is contiguous and sized just right for the data window. Supplying the strides will write the pixels offset into the correct positions with the right spacing, and also you want to offset correctly into the buffer. I think you want this:

    size_t pixelsize = channels * sizeof(float);
    size_t scanlinesize = pixelsize * spec.full_width;
    if (!in->read_image(/*subimage*/ 0, /*miplevel*/ 0, /*chbegin*/ 0, /*chend*/ channels,
                        OIIO::TypeDesc::FLOAT,
                        image->getData() + spec.x * pixelsize + spec.y * scanlinesize,
                        /*xstride*/ pixelsize, /*ystride*/ scanlinesize)
    

    Don't forget to zero out your buffer first, because now the read_image call won't write over all buffer values, only the ones that have data pixels!

    Now, even this is not quite fully general. I simplified by assuming that your display window (what OIIO calls "full") has its origin as (0,0) and would be the same as the data window for an image that had no black pixels -- that is, data and full differ only because of the "cropping" to the nonzero region. But actually, it is possible (and frequent, in production) to have data windows which are larger than the display window, meaning that you are computing "overscan" regions (including having the data window possibly extend into negative pixel indices). If this might be the case, and you need to cover all possible bases, then you need to maybe be allocating an image that is the union of overlap between the display and data windows, and adjust the code above to handle this properly.