ImageBuf: Image Buffers#

ImageBuf Introduction and Theory of Operation#

ImageBuf is a utility class that stores an entire image. It provides a nice API for reading, writing, and manipulating images as a single unit, without needing to worry about any of the details of storage or I/O.

All I/O involving ImageBuf (that is, calls to read or write) are implemented underneath in terms of ImageCache, ImageInput, and ImageOutput, and so support all of the image file formats supported by OIIO.

The ImageBuf class definition requires that you:

#include <OpenImageIO/imagebuf.h>
enum OIIO::ImageBuf::IBStorage#

An ImageBuf can store its pixels in one of several ways (each identified by an IBStorage enumerated value):

Values:

enumerator UNINITIALIZED#

An ImageBuf that doesn’t represent any image at all (either because it is newly constructed with the default constructor, or had an error during construction).

enumerator LOCALBUFFER#

“Local storage” is allocated to hold the image pixels internal to the ImageBuf. This memory will be freed when the ImageBuf is destroyed.

enumerator APPBUFFER#

The ImageBuf “wraps” pixel memory already allocated and owned by the calling application. The caller will continue to own that memory and be responsible for freeing it after the ImageBuf is destroyed.

enumerator IMAGECACHE#

The ImageBuf is “backed” by an ImageCache, which will automatically be used to retrieve pixels when requested, but the ImageBuf will not allocate separate storage for it. This brings all the advantages of the ImageCache, but can only be used for read-only ImageBuf’s that reference a stored image file.

Constructing, destructing, resetting an ImageBuf#

There are several ways to construct an ImageBuf. Each constructor has a corresponding reset method that takes the same arguments. Calling reset on an existing ImageBuf is equivalent to constructing a new ImageBuf from scratch (even if the ImageBuf, prior to reset, previously held an image).

Making an empty or uninitialized ImageBuf#

OIIO::ImageBuf::ImageBuf()#

Default constructor makes an empty/uninitialized ImageBuf. There isn’t much you can do with an uninitialized buffer until you call reset(). The storage type of a default-constructed ImageBuf is IBStorage::UNINITIALIZED.

inline void OIIO::ImageBuf::reset()#

Destroy any previous contents of the ImageBuf and re-initialize it to resemble a freshly constructed ImageBuf using the default constructor (holding no image, with storage IBStorage::UNINITIALIZED).

Constructing a readable ImageBuf#

explicit OIIO::ImageBuf::ImageBuf(string_view name, int subimage = 0, int miplevel = 0, std::shared_ptr<ImageCache> imagecache = {}, const ImageSpec *config = nullptr, Filesystem::IOProxy *ioproxy = nullptr)#

Construct a read-only ImageBuf that will be used to read the named file (at the given subimage and MIP-level, defaulting to the first in the file). But don’t read it yet! The image will actually be read lazily, only when other methods need to access the spec and/or pixels, or when an explicit call to init_spec() or read() is made, whichever comes first.

The implementation may end up either reading the entire image internally owned memory (if so, the storage will be LOCALBUFFER), or it may rely on being backed by an ImageCache (in this case, the storage will be IMAGECACHE) &#8212; depending on the image size and other factors.

Parameters:
  • name – The image to read.

  • subimage/miplevel – The subimage and MIP level to read (defaults to the first subimage of the file, highest-res MIP level).

  • imagecache – Optionally, an ImageCache to use, if possible, rather than reading the entire image file into memory.

  • config – Optionally, a pointer to an ImageSpec whose metadata contains configuration hints that set options related to the opening and reading of the file.

  • ioproxy – Optional pointer to an IOProxy to use when reading from the file. The caller retains ownership of the proxy, and must ensure that it remains valid for the lifetime of the ImageBuf.

void OIIO::ImageBuf::reset(string_view name, int subimage = 0, int miplevel = 0, std::shared_ptr<ImageCache> imagecache = {}, const ImageSpec *config = nullptr, Filesystem::IOProxy *ioproxy = nullptr)#

Destroy any previous contents of the ImageBuf and re-initialize it as if newly constructed with the same arguments, as a read-only representation of an existing image file.

Constructing a writable ImageBuf#

explicit OIIO::ImageBuf::ImageBuf(const ImageSpec &spec, InitializePixels zero = InitializePixels::Yes)#

Construct a writable ImageBuf with the given specification (including resolution, data type, metadata, etc.). The ImageBuf will allocate and own its own pixel memory and will free that memory automatically upon destruction, clear(), or reset(). Upon successful initialization, the storage will be reported as LOCALBUFFER.

Parameters:
  • spec – An ImageSpec describing the image and its metadata. If not enough information is given to know how much memory to allocate (width, height, depth, channels, and data format), the ImageBuf will remain in an UNINITIALIZED state and will have no local pixel storage.

  • zero – After a successful allocation of the local pixel storage, this parameter controls whether the pixels will be initialized to hold zero (black) values (InitializePixels::Yes) or if the pixel memory will remain uninitialized (InitializePixels::No) and thus may hold nonsensical values. Choosing No may save the time of writing to the pixel memory if you know for sure that you are about to overwrite it completely before you will need to read any pixel values.

void OIIO::ImageBuf::reset(const ImageSpec &spec, InitializePixels zero = InitializePixels::Yes)#

Destroy any previous contents of the ImageBuf and re-initialize it as if newly constructed with the same arguments, as a read/write image with locally allocated storage that can hold an image as described by spec. The optional zero parameter controls whether the pixel values are filled with black/empty, or are left uninitialized after being allocated.

Note that if the ImageSpec does not contain enough information to specify how much memory to allocate (width, height, channels, and data format), the ImageBuf will remain uninitialized (regardless of how zero is set).

bool OIIO::ImageBuf::make_writable(bool keep_cache_type = false)#

Make the ImageBuf be writable. That means that if it was previously backed by an ImageCache (storage was IMAGECACHE), it will force a full read so that the whole image is in local memory. This will invalidate any current iterators on the image. It has no effect if the image storage is not IMAGECACHE.

Parameters:

keep_cache_type – If true, preserve any ImageCache-forced data types (you might want to do this if it is critical that the apparent data type doesn’t change, for example if you are calling make_writable() from within a type-specialized function).

Returns:

Return true if it works (including if no read was necessary), false if something went horribly wrong.

Constructing an ImageBuf that “wraps” an application buffer#

OIIO::ImageBuf::ImageBuf(const ImageSpec &spec, void *buffer, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride)#
void OIIO::ImageBuf::reset(const ImageSpec &spec, void *buffer, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride)#

Unsafe reset of a “wrapped” buffer, mostly for backward compatibility. This version does not pass a span that explicitly delineates the memory bounds, but only passes a raw pointer and assumes that the caller has ensured that the buffer pointed to is big enough to accommodate accessing any valid pixel as describes by the spec and the strides. Use with caution!

Reading and Writing disk images#

bool OIIO::ImageBuf::read(int subimage = 0, int miplevel = 0, bool force = false, TypeDesc convert = TypeUnknown, ProgressCallback progress_callback = nullptr, void *progress_callback_data = nullptr)#

Read the particular subimage and MIP level of the image, if it has not already been read. It will clear and re-allocate memory if the previously allocated space was not appropriate for the size or data type of the image being read.

In general, calling read() should be unnecessary for most uses of ImageBuf. When an ImageBuf is created (or when reset() is called), usually the opening of the file and reading of the header is deferred until the spec is accessed or needed, and the reading of the pixel values is usually deferred until pixel values are needed, at which point these things happen automatically. That is, every ImageBuf method that needs pixel values will call read() itself if it has not previously been called.

There are a few situations where you want to call read() explicitly, after the ImageBuf is constructed but before any other methods have been called that would implicitly read the file:

  1. If you want to request that the internal buffer be a specific pixel data type that might differ from the pixel data type in the file itself (conveyed by the convert parameter).

  2. You want the ImageBuf to read and contain only a subset of the channels in the file (conveyed by the chmin and chmax parameters on the version of read() that accepts them).

  3. The ImageBuf has been set up to be backed by ImageCache, but you want to force it to read the whole file into memory now (conveyed by the force parameter, or if the convert parameter specifies a type that is not the native file type and also cannot be accommodated directly by the cache).

  4. For whatever reason, you want to force a full read of the pixels to occur at this point in program execution, rather than at some undetermined future time when you first need to access those pixels.

The read() function should not be used to change an already established subimage, MIP level, pixel data type, or channel range of a file that has already read its pixels. You should use the reset() method for that purpose.

Parameters:
  • subimage/miplevel – The subimage and MIP level to read.

  • force – If true, will force an immediate full read into ImageBuf-owned local pixel memory (yielding a LOCALPIXELS storage buffer). Otherwise, it is up to the implementation whether to immediately read or have the image backed by an ImageCache (storage IMAGECACHE, if the ImageBuf was originally constructed or reset with an ImageCache specified).

  • convert – If set to a specific type (not UNKNOWN), the ImageBuf memory will be allocated for that type specifically and converted upon read.

  • progress_callback/progress_callback_data – If progress_callback is non-NULL, the underlying read, if expensive, may make several calls to progress_callback(progress_callback_data, portion_done) which allows you to implement some sort of progress meter. Note that if the ImageBuf is backed by an ImageCache, the progress callback will never be called, since no actual file I/O will occur at this time (ImageCache will load tiles or scanlines on demand, as individual pixel values are needed).

Returns:

true upon success, or false if the read failed (in which case, you should be able to retrieve an error message via geterror()).

bool OIIO::ImageBuf::read(int subimage, int miplevel, int chbegin, int chend, bool force, TypeDesc convert, ProgressCallback progress_callback = nullptr, void *progress_callback_data = nullptr)#

Read the file, if possible only allocating and reading a subset of channels, [chbegin..chend-1]. This can be a performance and memory improvement for some image file formats, if you know that any use of the ImageBuf will only access a subset of channels from a many-channel file.

Additional parameters:

Parameters:

chbegin/chend – The subset (a range with “exclusive end”) of channels to read, if the implementation is able to read only a subset of channels and have a performance advantage by doing so. If chbegin is 0 and chend is either negative or greater than the number of channels in the file, all channels will be read. Please note that it is “advisory” and not guaranteed to be honored by the underlying implementation.

bool OIIO::ImageBuf::init_spec(string_view filename, int subimage, int miplevel)#

Read the ImageSpec for the given file, subimage, and MIP level into the ImageBuf, but will not read the pixels or allocate any local storage (until a subsequent call to read()). This is helpful if you have an ImageBuf and you need to know information about the image, but don’t want to do a full read yet, and maybe won’t need to do the full read, depending on what’s found in the spec.

Note that init_spec() is not strictly necessary. If you are happy with the filename, subimage and MIP level specified by the ImageBuf constructor (or the last call to reset()), then the spec will be automatically read the first time you make any other ImageBuf API call that requires it. The only reason to call read() yourself is if you are changing the filename, subimage, or MIP level, or if you want to use force=true or a specific convert value to force data format conversion.

Parameters:
  • filename – The filename to read from (should be the same as the filename used when the ImageBuf was constructed or reset.)

  • subimage/miplevel – The subimage and MIP level to read.

Returns:

true upon success, or false if the read failed (in which case, you should be able to retrieve an error message via geterror()).

bool OIIO::ImageBuf::write(string_view filename, TypeDesc dtype = TypeUnknown, string_view fileformat = string_view(), ProgressCallback progress_callback = nullptr, void *progress_callback_data = nullptr) const#

Write the image to the named file, converted to the specified pixel data type dtype (TypeUnknown signifies to use the data type of the buffer), and file format (an empty fileformat means to infer the type from the filename extension).

By default, it will always try to write a scanline-oriented file, unless the set_write_tiles() method has been used to override this.

Parameters:
  • filename – The filename to write to.

  • dtype – Optional override of the pixel data format to use in the file being written. The default (UNKNOWN) means to try writing the same data format that as pixels are stored within the ImageBuf memory (or whatever type was specified by a prior call to set_write_format()). In either case, if the file format does not support that data type, another will be automatically chosen that is supported by the file type and loses as little precision as possible.

  • fileformat – Optional override of the file format to write. The default (empty string) means to infer the file format from the extension of the filename (for example, “foo.tif” will write a TIFF file).

  • progress_callback/progress_callback_data – If progress_callback is non-NULL, the underlying write, if expensive, may make several calls to progress_callback(progress_callback_data, portion_done) which allows you to implement some sort of progress meter.

Returns:

true upon success, or false if the write failed (in which case, you should be able to retrieve an error message via geterror()).

bool OIIO::ImageBuf::write(ImageOutput *out, ProgressCallback progress_callback = nullptr, void *progress_callback_data = nullptr) const#

Write the pixels of the ImageBuf to an open ImageOutput. The ImageOutput must have already been opened with a spec that indicates a resolution identical to that of this ImageBuf (but it may have specified a different pixel data type, in which case data conversions will happen automatically). This method does NOT close the file when it’s done (and so may be called in a loop to write a multi-image file).

Note that since this uses an already-opened ImageOutput, which is too late to change how it was opened, it does not honor any prior calls to set_write_format or set_write_tiles.

The main application of this method is to allow an ImageBuf (which by design may hold only a single image) to be used for the output of one image of a multi-subimage and/or MIP-mapped image file.

Parameters:
  • out – A pointer to an already-opened ImageOutput to which the pixels of the ImageBuf will be written.

  • progress_callback/progress_callback_data – If progress_callback is non-NULL, the underlying write, if expensive, may make several calls to progress_callback(progress_callback_data, portion_done) which allows you to implement some sort of progress meter.

Returns:

true if all went ok, false if there were errors writing.

void OIIO::ImageBuf::set_write_format(TypeDesc format)#

Set the pixel data format that will be used for subsequent write() calls that do not themselves request a specific data type request.

Note that this does not affect the variety of write() that takes an open ImageOutput* as a parameter.

Parameters:

format – The data type to be used for all channels.

void OIIO::ImageBuf::set_write_format(cspan<TypeDesc> format)#

Set the per-channel pixel data format that will be used for subsequent write() calls that do not themselves request a specific data type request.

Parameters:

format – The type of each channel (in order). Any channel’s format specified as TypeUnknown will default to be whatever type is described in the ImageSpec of the buffer.

void OIIO::ImageBuf::set_write_tiles(int width = 0, int height = 0, int depth = 0)#

Override the tile sizing for subsequent calls to the write() method (the variety that does not take an open ImageOutput*). Setting all three dimensions to 0 indicates that the output should be a scanline-oriented file.

This lets you write a tiled file from an ImageBuf that may have been read originally from a scanline file, or change the dimensions of a tiled file, or to force the file written to be scanline even if it was originally read from a tiled file.

In all cases, if the file format ultimately written does not support tiling, or the tile dimensions requested, a suitable supported tiling choice will be made automatically.

void OIIO::ImageBuf::set_write_ioproxy(Filesystem::IOProxy *ioproxy)#

Supply an IOProxy to use for a subsequent call to write().

If a proxy is set but it later turns out that the file format selected does not support write proxies, then write() will fail with an error.

Getting and setting information about an ImageBuf#

bool OIIO::ImageBuf::initialized() const#

Returns true if the ImageBuf is initialized, false if not yet initialized.

IBStorage OIIO::ImageBuf::storage() const#

Which type of storage is being used for the pixels? Returns an enumerated type describing the type of storage currently employed by the ImageBuf: UNINITIALIZED (no storage), LOCALBUFFER (the ImageBuf has allocated and owns the pixel memory), APPBUFFER (the ImageBuf “wraps” memory owned by the calling application), or IMAGECACHE (the image is backed by an ImageCache).

const ImageSpec &OIIO::ImageBuf::spec() const#

Return a read-only (const) reference to the image spec that describes the buffer.

const ImageSpec &OIIO::ImageBuf::nativespec() const#

Return a read-only (const) reference to the “native” image spec (that describes the file, which may be slightly different than the spec of the ImageBuf, particularly if the IB is backed by an ImageCache that is imposing some particular data format or tile size).

This may differ from spec() &#8212; for example, if a data format conversion was requested, if the buffer is backed by an ImageCache which stores the pixels internally in a different data format than that of the file, or if the file had differing per-channel data formats (ImageBuf must contain a single data format for all channels).

ImageSpec &OIIO::ImageBuf::specmod()#

Return a writable reference to the ImageSpec that describes the buffer. It’s ok to modify most of the metadata, but if you modify the spec’s format, width, height, or depth fields, you get the pain you deserve, as the ImageBuf will no longer have correct knowledge of its pixel memory layout. USE WITH EXTREME CAUTION.

string_view OIIO::ImageBuf::name(void) const#

Returns the name of the buffer (name of the file, for an ImageBuf read from disk).

string_view OIIO::ImageBuf::file_format_name(void) const#

Return the name of the image file format of the file this ImageBuf refers to (for example "openexr"). Returns an empty string for an ImageBuf that was not constructed as a direct reference to a file.

int OIIO::ImageBuf::subimage() const#

Return the index of the subimage within the file that the ImageBuf refers to. This will always be 0 for an ImageBuf that was not constructed as a direct reference to a file, or if the file contained only one image.

int OIIO::ImageBuf::nsubimages() const#

Return the number of subimages in the file this ImageBuf refers to, if it can be determined efficiently. This will always be 1 for an ImageBuf that was not constructed as a direct reference to a file, or for an ImageBuf that refers to a file type that is not capable of containing multiple subimages.

Note that a return value of 0 indicates that the number of subimages cannot easily be known without reading the entire image file to discover the total. To compute this yourself, you would need check every subimage successively until you get an error.

int OIIO::ImageBuf::miplevel() const#

Return the index of the miplevel with a file’s subimage that the ImageBuf is currently holding. This will always be 0 for an ImageBuf that was not constructed as a direct reference to a file, or if the subimage within that file was not MIP-mapped.

int OIIO::ImageBuf::nmiplevels() const#

Return the number of MIP levels of the current subimage within the file this ImageBuf refers to. This will always be 1 for an ImageBuf that was not constructed as a direct reference to a file, or if this subimage within the file was not MIP-mapped.

int OIIO::ImageBuf::nchannels() const#

Return the number of color channels in the image. This is equivalent to spec().nchannels.

int xbegin() const#
int xend() const#
int ybegin() const#
int yend() const#
int zbegin() const#
int zend() const#

Returns the [begin,end) range of the pixel data window of the buffer. These are equivalent to spec().x, spec().x+spec().width, spec().y, spec().y+spec().height, spec().z, and spec().z+spec().depth, respectively.

int OIIO::ImageBuf::orientation() const#

Return the current "Orientation" metadata for the image, per the table in sec-metadata-orientation_

void OIIO::ImageBuf::set_orientation(int orient)#

Set the "Orientation" metadata for the image.

int oriented_width() const#
int oriented_height() const#
int oriented_x() const#
int oriented_y() const#
int oriented_full_width() const#
int oriented_full_height() const#
int oriented_full_x() const#
int oriented_full_y() const#

The oriented width, height, x, and y describe the pixel data window after taking the display orientation into consideration. The full versions the “full” (a.k.a. display) window after taking the display orientation into consideration.

ROI OIIO::ImageBuf::roi() const#

Return pixel data window for this ImageBuf as a ROI.

ROI OIIO::ImageBuf::roi_full() const#

Return full/display window for this ImageBuf as a ROI.

void OIIO::ImageBuf::set_origin(int x, int y, int z = 0)#

Alters the metadata of the spec in the ImageBuf to reset the “origin” of the pixel data window to be the specified coordinates. This does not affect the size of the pixel data window, only its position.

void OIIO::ImageBuf::set_full(int xbegin, int xend, int ybegin, int yend, int zbegin, int zend)#

Set the “full” (a.k.a. display) window to Alters the metadata of the spec in the ImageBuf to reset the “full” image size (a.k.a. “display window”) to

[xbegin,xend) x [ybegin,yend) x [zbegin,zend)

This does not affect the size of the pixel data window.

void OIIO::ImageBuf::set_roi_full(const ROI &newroi)#

Set full/display window for this ImageBuf to a ROI. Does NOT change the channels of the spec, regardless of newroi.

bool OIIO::ImageBuf::contains_roi(const ROI &roi) const#

Is the specified roi completely contained in the data window of this ImageBuf?

TypeDesc OIIO::ImageBuf::pixeltype() const#

The data type of the pixels stored in the buffer (equivalent to spec().format).

int OIIO::ImageBuf::threads() const#

Retrieve the current thread-spawning policy of this ImageBuf.

void OIIO::ImageBuf::threads(int n) const#

Set the threading policy for this ImageBuf, controlling the maximum amount of parallelizing thread “fan-out” that might occur during expensive operations. The default of 0 means that the global attribute("threads") value should be used (which itself defaults to using as many threads as cores).

The main reason to change this value is to set it to 1 to indicate that the calling thread should do all the work rather than spawning new threads. That is probably the desired behavior in situations where the calling application has already spawned multiple worker threads.

Copying ImageBuf’s and blocks of pixels#

const ImageBuf &OIIO::ImageBuf::operator=(const ImageBuf &src)#

Copy assignment.

const ImageBuf &OIIO::ImageBuf::operator=(ImageBuf &&src)#

Move assignment.

bool OIIO::ImageBuf::copy(const ImageBuf &src, TypeDesc format = TypeUnknown)#

Try to copy the pixels and metadata from src to *this (optionally with an explicit data format conversion).

If the previous state of *this was uninitialized, owning its own local pixel memory, or referring to a read-only image backed by ImageCache, then local pixel memory will be allocated to hold the new pixels and the call always succeeds unless the memory cannot be allocated. In this case, the format parameter may request a pixel data type that is different from that of the source buffer.

If *this previously referred to an app-owned memory buffer, the memory cannot be re-allocated, so the call will only succeed if the app-owned buffer is already the correct resolution and number of channels. The data type of the pixels will be converted automatically to the data type of the app buffer.

Parameters:
  • src – Another ImageBuf from which to copy the pixels and metadata.

  • format – Optionally request the pixel data type to be used. The default of TypeUnknown means to use whatever data type is used by the src. If *this is already initialized and has APPBUFFER storage (“wrapping” an application buffer), this parameter is ignored.

Returns:

true upon success or false upon error/failure.

ImageBuf OIIO::ImageBuf::copy(TypeDesc format) const#

Return a full copy of this ImageBuf (optionally with an explicit data format conversion).

void OIIO::ImageBuf::copy_metadata(const ImageBuf &src)#

Copy all the metadata from src to *this (except for pixel data resolution, channel types and names, and data format).

bool OIIO::ImageBuf::copy_pixels(const ImageBuf &src)#

Copy the pixel data from src to *this, automatically converting to the existing data format of *this. It only copies pixels in the overlap regions (and channels) of the two images; pixel data in *this that do exist in src will be set to 0, and pixel data in src that do not exist in *this will not be copied.

inline void OIIO::ImageBuf::swap(ImageBuf &other)#

Swap the entire contents with another ImageBuf.

Getting and setting pixel values#

Getting and setting individual pixels – slow

float OIIO::ImageBuf::getchannel(int x, int y, int z, int c, WrapMode wrap = WrapBlack) const#

Retrieve a single channel of one pixel.

Parameters:
  • x/y/z – The pixel coordinates.

  • c – The channel index to retrieve. If c is not in the valid channel range 0..nchannels-1, then getchannel() will return 0.

  • wrap – WrapMode that determines the behavior if the pixel coordinates are outside the data window: WrapBlack, WrapClamp, WrapPeriodic, WrapMirror.

Returns:

The data value, converted to a float.

inline void OIIO::ImageBuf::getpixel(int x, int y, int z, float *pixel, int maxchannels = 1000, WrapMode wrap = WrapBlack) const#

Unsafe version of getpixel using raw pointer. Avoid if possible.

void OIIO::ImageBuf::interppixel(float x, float y, span<float> pixel, WrapMode wrap = WrapBlack) const#

Sample the image plane at pixel coordinates (x,y), using linear interpolation between pixels, placing the result in pixel[0..n-1] where n is the smaller of the span’s size and the actual number of channels stored in the buffer.

Parameters:
  • x/y – The pixel coordinates. Note that pixel data values themselves are at the pixel centers, so pixel (i,j) is at image plane coordinate (i+0.5, j+0.5).

  • pixel – A span giving the location where results will be stored.

  • wrap – WrapMode that determines the behavior if the pixel coordinates are outside the data window: WrapBlack, WrapClamp, WrapPeriodic, WrapMirror.

void OIIO::ImageBuf::interppixel_bicubic(float x, float y, span<float> pixel, WrapMode wrap = WrapBlack) const#

Bicubic interpolation at pixel coordinates (x,y).

void OIIO::ImageBuf::interppixel_NDC(float s, float t, span<float> pixel, WrapMode wrap = WrapBlack) const#

Linearly interpolate at NDC coordinates (s,t), where (0,0) is the upper left corner of the display window, (1,1) the lower right corner of the display window.

Note

interppixel() uses pixel coordinates (ranging 0..resolution) whereas interppixel_NDC() uses NDC coordinates (ranging 0..1).

void OIIO::ImageBuf::interppixel_bicubic_NDC(float s, float t, span<float> pixel, WrapMode wrap = WrapBlack) const#

Bicubic interpolation at NDC space coordinates (s,t), where (0,0) is the upper left corner of the display (a.k.a. “full”) window, (1,1) the lower right corner of the display window.

void OIIO::ImageBuf::setpixel(int x, int y, int z, cspan<float> pixel)#

Set the pixel with coordinates (x,y,z) to have the values in span pixel[]. The number of channels copied is the minimum of the span length and the actual number of channels in the image.

inline void OIIO::ImageBuf::setpixel(int i, cspan<float> pixel)#

Set the i-th pixel value of the image (out of width*height*depth), from floating-point values in span pixel[]. The number of channels copied is the minimum of the span length and the actual number of channels in the image.


Getting and setting regions of pixels – fast

template<typename T>
inline bool OIIO::ImageBuf::get_pixels(ROI roi, span<T> buffer, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride) const#

Retrieve the rectangle of pixels spanning the ROI (including channels) at the current subimage and MIP-map level, storing the pixel values into the buffer.

Parameters:
  • roi – The region of interest to copy into. A default uninitialized ROI means the entire image.

  • buffer – A span delineating the extent of the safely accessible memory where the results should be stored.

  • xstride/ystride/zstride – The distance in bytes between successive pixels, scanlines, and image planes in the buffer (or AutoStride to indicate “contiguous” data in any of those dimensions).

Returns:

Return true if the operation could be completed, otherwise return false.

template<typename T>
inline bool OIIO::ImageBuf::get_pixels(ROI roi, span<T> buffer, T *buforigin, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride) const#

get_pixels() with an extra parameter:

Parameters:

buforigin – A pointer to the first pixel of the buffer. If null, it will be assumed to be the beginning of the buffer. This is useful if any negative strides are used to give an unusual layout of pixels within the buffer.

template<typename T>
inline bool OIIO::ImageBuf::set_pixels(ROI roi, span<T> buffer, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride)#

Copy the data into the given ROI of the ImageBuf. The data points to values specified by format, with layout detailed by the stride values (in bytes, with AutoStride indicating “contiguous” layout). It is up to the caller to ensure that data points to an area of memory big enough to account for the ROI. If roi is set to ROI::all(), the data buffer is assumed to have the same resolution as the ImageBuf itself. Return true if the operation could be completed, otherwise return false. Set the rectangle of pixels within the ROI to the values in the buffer.

Parameters:
  • roi – The region of interest to copy into. A default uninitialized ROI means the entire image.

  • buffer – A span delineating the extent of the safely accessible memory where the results should be copied from.

  • xstride/ystride/zstride – The distance in bytes between successive pixels, scanlines, and image planes in the buffer (or AutoStride to indicate “contiguous” data in any of those dimensions).

Returns:

Return true if the operation could be completed, otherwise return false.

template<typename T>
inline bool OIIO::ImageBuf::set_pixels(ROI roi, span<T> buffer, const T *buforigin, stride_t xstride = AutoStride, stride_t ystride = AutoStride, stride_t zstride = AutoStride)#

set_pixels() with an extra parameter:

Parameters:

buforigin – A pointer to the first pixel of the buffer. If null, it will be assumed to be the beginning of the buffer. This is useful if any negative strides are used to give an unusual layout of pixels within the buffer.

Deep data in an ImageBuf#

bool OIIO::ImageBuf::deep() const#

Does this ImageBuf store deep data? Returns true if the ImageBuf holds a “deep” image, false if the ImageBuf holds an ordinary pixel-based image.

int OIIO::ImageBuf::deep_samples(int x, int y, int z = 0) const#

Retrieve the number of deep data samples corresponding to pixel (x,y,z). Return 0 if not a deep image, or if the pixel is outside of the data window, or if the designated pixel has no deep samples.

void OIIO::ImageBuf::set_deep_samples(int x, int y, int z, int nsamples)#

Set the number of deep samples for pixel (x,y,z). If data has already been allocated, this is equivalent to inserting or erasing samples.

void OIIO::ImageBuf::deep_insert_samples(int x, int y, int z, int samplepos, int nsamples)#

Insert nsamples new samples, starting at position samplepos of pixel (x,y,z).

void OIIO::ImageBuf::deep_erase_samples(int x, int y, int z, int samplepos, int nsamples)#

Remove nsamples samples, starting at position samplepos of pixel (x,y,z).

float OIIO::ImageBuf::deep_value(int x, int y, int z, int c, int s) const#

Return the value (as a float) of sample s of channel c of pixel (x,y,z). Return 0 if not a deep image or if the pixel coordinates or channel number are out of range or if that pixel has no deep samples.

uint32_t OIIO::ImageBuf::deep_value_uint(int x, int y, int z, int c, int s) const#

Return the value (as a uint32_t) of sample s of channel c of pixel (x,y,z). Return 0 if not a deep image or if the pixel coordinates or channel number are out of range or if that pixel has no deep samples.

void OIIO::ImageBuf::set_deep_value(int x, int y, int z, int c, int s, float value)#

Set the value of sample s of channel c of pixel (x,y,z) to a float value (it is expected that channel c is a floating point type).

void OIIO::ImageBuf::set_deep_value(int x, int y, int z, int c, int s, uint32_t value)#

Set the value of sample s of channel c of pixel (x,y,z) to a uint32_t value (it is expected that channel c is an integer type).

const void *OIIO::ImageBuf::deep_pixel_ptr(int x, int y, int z, int c, int s = 0) const#

Return a pointer to the raw data of pixel (x,y,z), channel c, sample s. Return nullptr if the pixel coordinates or channel number are out of range, if the pixel/channel has no deep samples, or if the image is not deep. Use with caution &#8212; these pointers may be invalidated by calls that adjust the number of samples in any pixel.

DeepData &OIIO::ImageBuf::deepdata()#
const DeepData &OIIO::ImageBuf::deepdata() const#

Returns a reference to the underlying DeepData for a deep image.

Error Handling#

template<typename Str, typename ...Args>
inline void OIIO::ImageBuf::errorfmt(const Str &fmt, Args&&... args) const#

Error reporting for ImageBuf: call this with std::format style formatting specification. It is not necessary to have the error message contain a trailing newline.

bool OIIO::ImageBuf::has_error(void) const#

Returns true if the ImageBuf has had an error and has an error message ready to retrieve via geterror().

std::string OIIO::ImageBuf::geterror(bool clear = true) const#

Return the text of all pending error messages issued against this ImageBuf, and clear the pending error message unless clear is false. If no error message is pending, it will return an empty string.

Miscellaneous#

void *localpixels()#
const void *localpixels() const#

Return a raw pointer to the “local” pixel memory, if they are fully in RAM and not backed by an ImageCache, or nullptr otherwise. You can also test it like a bool to find out if pixels are local.

void *pixeladdr(int x, int y, int z = 0, int ch = 0)#
const void *pixeladdr(int x, int y, int z = 0, int ch = 0) const#

Return the address where pixel (x,y,z), channel ch, is stored in the image buffer. Use with extreme caution! Will return nullptr if the pixel values aren’t local in RAM.

int OIIO::ImageBuf::pixelindex(int x, int y, int z, bool check_range = false) const#

Return the index of pixel (x,y,z). If check_range is true, return -1 for an invalid coordinate that is not within the data window.

static WrapMode OIIO::ImageBuf::WrapMode_from_string(string_view name)#

Return the WrapMode corresponding to the name ("default", "black", "clamp", "periodic", "mirror"). For an unknown name, this will return WrapDefault.

void lock() const#
void unlock() const#

Manually lock or unlock the mutex that protects the ImageBuf from concurrent access by multiple threads. Use with caution – this should almost never be needed in ordinary user code.

Writing your own image processing functions#

In this section, we will discuss how to write functions that operate pixel by pixel on an ImageBuf. There are several different approaches to this, with different trade-offs in terms of speed, flexibility, and simplicity of implementation.

Simple pixel-by-pixel access with ImageBufAlgo::perpixel_op()#

Pros:

  • You only need to supply the inner loop body, the part that does the work for a single pixel.

  • You can assume that all pixel data are float values.

Cons/Limitations:

  • The operation must be one where each output pixel depends only on the corresponding pixel of the input images.

  • Currently, the operation must be unary (one input image to produce one output image), or binary (two input images, one output image). At this time, there are not options to operate on a single image in-place, or to have more than two input images, but this may be extended in the future.

  • Operating on float-based images is “full speed,” but if the input images are not float, the automatic conversions will add some expense. In practice, we find working on non-float images to be about half the speed of float images, but this may be acceptable in exchange for the simplicity of this approach, especially for operations where you expect inputs to be float typically.

ImageBuf OIIO::ImageBufAlgo::perpixel_op(const ImageBuf &src, function_view<bool(span<float>, cspan<float>)> op, KWArgs options = {})#

Simple image per-pixel unary operation: Given a source image src, return an image of the same dimensions (and same data type, unless options includes the “dst_float_pixels” hint turned on, which will result in a float pixel result image) where each pixel is the result of running the caller-supplied function op on the corresponding pixel values of src. The op function should take two span<float> arguments, the first referencing a destination pixel, and the second being a reference to the corresponding source pixel. The op function should return true if the operation was successful, or false if there was an error.

The perpixel_op function is thread-safe and will parallelize the operation across multiple threads if nthreads is not equal to 1 (following the usual ImageBufAlgo nthreads rules), and also takes care of all the pixel loops and conversions to and from float values.

The options keyword/value list contains additional controls. It supports all hints described by IBAPrep() as well as the following:

  • “nthreads” : int (default: 0)

    Controls the number of threads (0 signalling to use all available threads in the pool.

An example (using the binary op version) of how to implement a simple pixel-by-pixel add() operation that is the equivalent of ImageBufAlgo::add():

// Assume ImageBuf A, B are the inputs, ImageBuf R is the output
R = ImageBufAlgo::perpixel_op(A, B,
        [](span<float> r, cspan<float> a, cspan<float> b) {
            for (size_t c = 0, nc = size_t(r.size()); c < nc; ++c)
                r[c] = a[c] + b[c];
            return true;
        });

Caveats:

  • The operation must be one that can be applied independently to each pixel.

  • If the input image is not float-valued pixels, there may be some inefficiency due to the need to convert the pixels to float and back, since there is no type templating and thus no opportunity to supply a version of the operation that allows specialization to any other pixel data types

ImageBuf OIIO::ImageBufAlgo::perpixel_op(const ImageBuf &srcA, const ImageBuf &srcB, function_view<bool(span<float>, cspan<float>, cspan<float>)> op, KWArgs options = {})#

A version of perpixel_op that performs a binary operation, taking two source images and a 3-argument op function that receives a destination and two source pixels.

Examples:

// Assume ImageBuf A, B are the inputs, ImageBuf R is the output

/////////////////////////////////////////////////////////////////
// Approach 1: using a standalone function to add two images
bool my_add (span<float> r, cspan<float> a, cspan<float> b) {
    for (size_t c = 0, nc = size_t(r.size()); c < nc; ++c)
        r[c] = a[c] + b[c];
    return true;
}

R = ImageBufAlgo::perpixel_op(A, B, my_add);

/////////////////////////////////////////////////////////////////
// Approach 2: using a "functor" class to add two images
struct Adder {
    bool operator() (span<float> r, cspan<float> a, cspan<float> b) {
        for (size_t c = 0, nc = size_t(r.size()); c < nc; ++c)
            r[c] = a[c] + b[c];
        return true;
    }
};

Adder adder;
R = ImageBufAlgo::perpixel_op(A, B, adder);

/////////////////////////////////////////////////////////////////
// Approach 3: using a lambda to add two images
R = ImageBufAlgo::perpixel_op(A, B,
        [](span<float> r, cspan<float> a, cspan<float> b) {
            for (size_t c = 0, nc = size_t(r.size()); c < nc; ++c)
                r[c] = a[c] + b[c];
            return true;
        });

Iterators – the fast way of accessing individual pixels#

Sometimes you need to visit every pixel in an ImageBuf (or at least, every pixel in a large region). Using the getpixel() and setpixel() for this purpose is simple but very slow. But ImageBuf provides templated Iterator and ConstIterator types that are very inexpensive and hide all the details of local versus cached storage.

Note

ImageBuf::ConstIterator is identical to the Iterator, except that ConstIterator may be used on a const ImageBuf and may not be used to alter the contents of the ImageBuf. For simplicity, the remainder of this section will only discuss the Iterator.

An Iterator is associated with a particular ImageBuf. The Iterator has a current pixel coordinate that it is visiting, and an iteration range that describes a rectangular region of pixels that it will visits as it advances. It always starts at the upper left corner of the iteration region. We say that the iterator is done after it has visited every pixel in its iteration range. We say that a pixel coordinate exists if it is within the pixel data window of the ImageBuf. We say that a pixel coordinate is valid if it is within the iteration range of the iterator.

The Iterator<BUFT,USERT> is templated based on two types: BUFT the type of the data stored in the ImageBuf, and USERT type type of the data that you want to manipulate with your code. USERT defaults to float, since usually you will want to do all your pixel math with float. We will thus use Iterator<T> synonymously with Iterator<T,float>.

For the remainder of this section, we will assume that you have a float-based ImageBuf, for example, if it were set up like this:

ImageBuf buf ("myfile.exr");
buf.read (0, 0, true, TypeDesc::FLOAT);
template<>
Iterator<BUFT>(ImageBuf &buf, WrapMode wrap = WrapDefault)#

Initialize an iterator that will visit every pixel in the data window of buf, and start it out pointing to the upper left corner of the data window. The wrap describes what values will be retrieved if the iterator is positioned outside the data window of the buffer.

template<>
Iterator<BUFT>(ImageBuf &buf, const ROI &roi, WrapMode wrap = WrapDefault)#

Initialize an iterator that will visit every pixel of buf within the region described by roi, and start it out pointing to pixel (roi.xbegin, roi.ybegin, roi.zbegin). The wrap describes what values will be retrieved if the iterator is positioned outside the data window of the buffer.

template<>
Iterator<BUFT>(ImageBuf &buf, int x, int y, int z, WrapMode wrap = WrapDefault)#

Initialize an iterator that will visit every pixel in the data window of buf, and start it out pointing to pixel (x, y, z). The wrap describes what values will be retrieved if the iterator is positioned outside the data window of the buffer.

Iterator::operator++()#

The ++ operator advances the iterator to the next pixel in its iteration range. (Both prefix and postfix increment operator are supported.)

bool Iterator::done() const#

Returns true if the iterator has completed its visit of all pixels in its iteration range.

ROI Iterator::range() const#

Returns the iteration range of the iterator, expressed as an ROI.

int Iterator::x() const#
int Iterator::y() const#
int Iterator::z() const#

Returns the x, y, and z pixel coordinates, respectively, of the pixel that the iterator is currently visiting.

bool Iterator::valid() const#

Returns true if the iterator’s current pixel coordinates are within its iteration range.

bool Iterator::valid(int x, int y, int z = 0) const#

Returns true if pixel coordinate (x, y, z) are within the iterator’s iteration range (regardless of where the iterator itself is currently pointing).

bool Iterator::exists() const#

Returns true if the iterator’s current pixel coordinates are within the data window of the ImageBuf.

bool Iterator::exists(int x, int y, int z = 0) const#

Returns true if pixel coordinate (x, y, z) are within the pixel data window of the ImageBuf (regardless of where the iterator itself is currently pointing).

USERT &Iterator::operator[](int i)#

The value of channel i of the current pixel. (The wrap mode, set up when the iterator was constructed, determines what value is returned if the iterator points outside the pixel data window of its buffer.)

int Iterator::deep_samples() const#

For deep images only, retrieves the number of deep samples for the current pixel.

void Iterator::set_deep_samples()#

For deep images only (and non-const ImageBuf), set the number of deep samples for the current pixel. This only is useful if the ImageBuf has not yet had the deep_alloc() method called.

USERT Iterator::deep_value(int c, int s) const#
uint32_t Iterator::deep_value_int(int c, int s) const#

For deep images only, returns the value of channel c, sample number s, at the current pixel.

void Iterator::set_deep_value(int c, int s, float value)#
void Iterator::set_deep_value(int c, int s, uint32_t value)#

For deep images only (and non-cconst ImageBuf, sets the value of channel c, sample number s, at the current pixel. This only is useful if the ImageBuf has already had the deep_alloc() method called.

Example: Visiting all pixels to compute an average color#

void print_channel_averages (const std::string &filename)
{
    // Set up the ImageBuf and read the file
    ImageBuf buf (filename);
    bool ok = buf.read (0, 0, true, TypeDesc::FLOAT);  // Force a float buffer
    if (! ok)
        return;

    // Initialize a vector to contain the running total
    int nc = buf.nchannels();
    std::vector<float> total (n, 0.0f);

    // Iterate over all pixels of the image, summing channels separately
    for (ImageBuf::ConstIterator<float> it (buf);  ! it.done();  ++it)
        for (int c = 0;  c < nc;  ++c)
            total[c] += it[c];

    // Print the averages
    imagesize_t npixels = buf.spec().image_pixels();
    for (int c = 0;  c < nc;  ++c)
        std::cout << "Channel " << c << " avg = " (total[c] / npixels) << "\n";
}

Example: Set all pixels in a region to black#

bool make_black (ImageBuf &buf, ROI region)
{
    if (buf.spec().format != TypeDesc::FLOAT)
        return false;    // Assume it's a float buffer

    // Clamp the region's channel range to the channels in the image
    roi.chend = std::min (roi.chend, buf.nchannels);

    // Iterate over all pixels in the region...
    for (ImageBuf::Iterator<float> it (buf, region);  ! it.done();  ++it) {
        if (! it.exists())   // Make sure the iterator is pointing
            continue;        //   to a pixel in the data window
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            it[c] = 0.0f;  // clear the value
    }
    return true;
}

Dealing with buffer data types#

The previous section on iterators presented examples and discussion based on the assumption that the ImageBuf was guaranteed to store float data and that you wanted all math to also be done as float computations. Here we will explain how to deal with buffers and files that contain different data types.

Strategy 1: Only have float data in your ImageBuf#

When creating your own buffers, make sure they are float:

ImageSpec spec (640, 480, 3, TypeDesc::FLOAT); // <-- float buffer
ImageBuf buf ("mybuf", spec);

When using ImageCache-backed buffers, force the ImageCache to convert everything to float:

// Just do this once, to set up the cache:
ImageCache *cache = ImageCache::create (true /* shared cache */);
cache->attribute ("forcefloat", 1);
...
ImageBuf buf ("myfile.exr");   // Backed by the shared cache

Or force the read to convert to float in the buffer if it’s not a native type that would automatically stored as a float internally to the ImageCache:[1]

ImageBuf buf ("myfile.exr");   // Backed by the shared cache
buf.read (0, 0, false /* don't force read to local mem */,
          TypeDesc::FLOAT /* but do force conversion to float*/);

Or force a read into local memory unconditionally (rather than relying on the ImageCache), and convert to float:

ImageBuf buf ("myfile.exr");
buf.read (0, 0, true /*force read*/,
          TypeDesc::FLOAT /* force conversion */);

Strategy 2: Template your iterating functions based on buffer type#

Consider the following alternate version of the make_black function from Section Example: Set all pixels in a region to black

template<typename BUFT>
static bool make_black_impl (ImageBuf &buf, ROI region)
{
    // Clamp the region's channel range to the channels in the image
    roi.chend = std::min (roi.chend, buf.nchannels);

    // Iterate over all pixels in the region...
    for (ImageBuf::Iterator<BUFT> it (buf, region);  ! it.done();  ++it) {
        if (! it.exists())   // Make sure the iterator is pointing
            continue;        //   to a pixel in the data window
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            it[c] = 0.0f;  // clear the value
    }
    return true;
}

bool make_black (ImageBuf &buf, ROI region)
{
    if (buf.spec().format == TypeDesc::FLOAT)
        return make_black_impl<float> (buf, region);
    else if (buf.spec().format == TypeDesc::HALF)
        return make_black_impl<half> (buf, region);
    else if (buf.spec().format == TypeDesc::UINT8)
        return make_black_impl<unsigned char> (buf, region);
    else if (buf.spec().format == TypeDesc::UINT16)
        return make_black_impl<unsigned short> (buf, region);
    else {
        buf.error ("Unsupported pixel data format %s", buf.spec().format);
        return false;
    }
}

In this example, we make an implementation that is templated on the buffer type, and then a wrapper that calls the appropriate template specialization for each of 4 common types (and logs an error in the buffer for any other types it encounters).

In fact, imagebufalgo_util.h provides a macro to do this (and several variants, which will be discussed in more detail in the next chapter). You could rewrite the example even more simply:

#include <OpenImageIO/imagebufalgo_util.h>

template<typename BUFT>
static bool make_black_impl (ImageBuf &buf, ROI region)
{
    ... same as before ...
}

bool make_black (ImageBuf &buf, ROI region)
{
    bool ok;
    OIIO_DISPATCH_COMMON_TYPES (ok, "make_black", make_black_impl,
                                 buf.spec().format, buf, region);
    return ok;
}

This other type-dispatching helper macros will be discussed in more detail in Chapter ImageBufAlgo: Image Processing.