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.
-
enumerator UNINITIALIZED#
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 isIBStorage::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#
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()
orread()
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 beIMAGECACHE
) — 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.
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. ChoosingNo
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 optionalzero
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 notIMAGECACHE
.- 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 whenreset()
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 callread()
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:
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).You want the ImageBuf to read and contain only a subset of the channels in the file (conveyed by the
chmin
andchmax
parameters on the version ofread()
that accepts them).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 theconvert
parameter specifies a type that is not the native file type and also cannot be accommodated directly by the cache).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 thereset()
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 aLOCALPIXELS
storage buffer). Otherwise, it is up to the implementation whether to immediately read or have the image backed by an ImageCache (storageIMAGECACHE
, 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 toprogress_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, orfalse
if the read failed (in which case, you should be able to retrieve an error message viageterror()
).
-
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 andchend
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 toreset()
), then the spec will be automatically read the first time you make any other ImageBuf API call that requires it. The only reason to callread()
yourself is if you are changing the filename, subimage, or MIP level, or if you want to useforce=true
or a specificconvert
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, orfalse
if the read failed (in which case, you should be able to retrieve an error message viageterror()
).
-
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 emptyfileformat
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 toset_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 toprogress_callback(progress_callback_data, portion_done)
which allows you to implement some sort of progress meter.
- Returns:
true
upon success, orfalse
if the write failed (in which case, you should be able to retrieve an error message viageterror()
).
-
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 toset_write_format
orset_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 toprogress_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 openImageOutput*
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 openImageOutput*
). 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.
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), orIMAGECACHE
(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()
— 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
, ordepth
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 tospec().x
,spec().x+spec().width
,spec().y
,spec().y+spec().height
,spec().z
, andspec().z+spec().depth
, respectively.
-
int OIIO::ImageBuf::orientation() const#
Return the current
"Orientation"
metadata for the image, per the table insec-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.
-
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, theformat
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 thesrc
. If*this
is already initialized and hasAPPBUFFER
storage (“wrapping” an application buffer), this parameter is ignored.
- Returns:
true
upon success orfalse
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 insrc
will be set to 0, and pixel data insrc
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, thengetchannel()
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) whereasinterppixel_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 spanpixel[]
. 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. Ifroi
is set toROI::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 thebuffer
.- 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 positionsamplepos
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 positionsamplepos
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 samples
of channelc
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 samples
of channelc
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 channelc
of pixel(x,y,z)
to afloat
value (it is expected that channelc
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 channelc
of pixel(x,y,z)
to auint32_t
value (it is expected that channelc
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)
, channelc
, samples
. Returnnullptr
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 — these pointers may be invalidated by calls that adjust the number of samples in any pixel.
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 viageterror()
.
-
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 abool
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)
, channelch
, is stored in the image buffer. Use with extreme caution! Will returnnullptr
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 returnWrapDefault
.
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 notfloat
, 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, unlessoptions
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 functionop
on the corresponding pixel values ofsrc
. Theop
function should take twospan<float>
arguments, the first referencing a destination pixel, and the second being a reference to the corresponding source pixel. Theop
function should returntrue
if the operation was successful, orfalse
if there was an error.The
perpixel_op
function is thread-safe and will parallelize the operation across multiple threads ifnthreads
is not equal to 1 (following the usual ImageBufAlgonthreads
rules), and also takes care of all the pixel loops and conversions to and fromfloat
values.The
options
keyword/value list contains additional controls. It supports all hints described byIBAPrep()
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 ofImageBufAlgo::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 tofloat
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 thatConstIterator
may be used on aconst ImageBuf
and may not be used to alter the contents of the ImageBuf. For simplicity, the remainder of this section will only discuss theIterator
.
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. Thewrap
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 byroi
, and start it out pointing to pixel (roi.xbegin, roi.ybegin, roi.zbegin
). Thewrap
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). Thewrap
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 numbers
, 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 numbers
, at the current pixel. This only is useful if the ImageBuf has already had thedeep_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.