API to read ImageInput into ImageBuf
In the comments of #4225, @zavinator suggested:
it would be great if the ImageInput class could integrate more seamlessly with ImageBuf, for instance by returning an ImageBuf instance for each subimage along with its associated metadata.
This is a great idea, and I can think of several possible forms for this to take:
- An ImageBuf constructor that takes an open ImageInput* where it reads from the "current" subimage of the input into the IB. (IB already has a write() method that writes to an open ImageOutput that you pass it.)
- An ImageInput::read_imagebuf() method a bit like read_image(), but returns (or reads into?) an ImageBuf rather than reading into a memory buffer.
- One of the above two, but as a fully standalone utility functions rather than being a method of either ImageBuf or ImageInput.
- Some kind of iterator that, given an ImageInput* and ImageBuf*, reads each successive subimage from the ImageInput into the IB. (Choice 1: it's just the iterator and you call next() to advance; choice 2: it's a total black box and the caller doesn't iterate, but rather, what you supply is a lambda that is called for each subimage?)
Or something else I haven't thought of?
Please feel free to continue the discussion in the comments to this issue.
All of these directions sound promising :)
Starting with an ImageInput::read_imagebuf() method seems most practical. An iterator-based approach could also be great for batch processing of subimages.
Some thoughts on what these choices might look like:
Choice 1: ImageBuf constructor from open ImageInput
auto inp = ImageInput::open("foo.exr");
ImageBuf buf(*inp, subimage, miplevel, datatype);
Choice 2: ImageInput method to read into an ImageBuf
auto inp = ImageInput::open("foo.exr");
ImageBuf buf = inp->read_imagebuf(subimage, miplevel, datatype);
or
auto inp = ImageInput::open("foo.exr");
ImageBuf buf;
bool ok = inp->read_imagebuf(buf, subimage, miplevel, datatype);
// A little more symmetric to ImageInput::read_image()
Choice 3: Standalone utility
auto inp = ImageInput::open("foo.exr");
ImageBuf buf;
bool ok = OIIO::read_into_imagebuf(buf, &inp);
Choice 4a: Iterator object
auto inp = ImageInput::open("foo.exr");
ImageBuf buf;
for (ReaderIterator it(inp, buf); !it.done; it.next()) {
// use buf()
}
Choice 4b: Iterating function + lambda
auto inp = ImageInput::open("foo.exr");
OIIO::iterate_ImageBuf_over_ImageInput(
[](const ImageBuf& buf, int subimage, int miplevel)
{
// use buf... subimage and miplevel say which subimage/mip it's pointing to
}
);
Another possibility:
ImageBuf buf = ...;
bool ok = buf.next_subimage();
To move the IB to load the next subimage of the same image file. Returns false if not possible or there are no remaining subimages. Only works for IB's that directly read from a file, not for an IB that is an altered copy or the returned result of an IBA function. Could do the same for MIP levels, so you could write a strictly IB-land loop like:
ImageBuf buf("foo.exr");
do {
do {
// buf is now at buf.subimage(), buf.miplevel()
} while (buf.next_miplevel());
} while (buf.next_subimage());
I'm interested in this in relation to reading raw files. Usually a raw file contains one raw image and a set of JPEG images of various sizes.
Typical use cases would be:
- read the main sub-image (the raw image)
- read the biggest available JPEG sub-image, to provide a quick preview.
- read the thumbnail sub-image closest to the requested size, for example, to show in an image gallery.
I assume this would be impossible to do without iterating on the caller side. Unless we pass some hints to the reader on what kind of image is required, essentially forcing the ImageInput to discard all but one/few sub-images?
Anton, we already have this method of ImageInput:
virtual bool get_thumbnail(ImageBuf& thumb, int subimage);
it just doesn't yet have an implementation in the RawInput class.
Looks solid to me:
bool ok = inp->read_imagebuf(buf, subimage, miplevel, datatype);
for (ReaderIterator it(inp, buf); !it.done; it.next()) {
// use buf()
}
// The question is how to handle mipmap (subimage?) selection - e.g., if someone wants to skip all mipmap levels.
// This one is interesting too
do {
do {
// buf is now at buf.subimage(), buf.miplevel()
} while (buf.next_miplevel());
} while (buf.next_subimage());