OpenImageIO ImageBuf Storage Modes: LOCALBUFFER vs APPBUFFER vs IMAGECACHE
ImageBuf provides three distinct storage modes—LOCALBUFFER, APPBUFFER, and IMAGECACHE—that determine memory ownership, mutability, and whether pixels are stored locally or fetched on-demand from an ImageCache.
The ImageBuf class in the AcademySoftwareFoundation/openimageio repository serves as the primary container for image data in OpenImageIO (OIIO). Understanding these ImageBuf storage modes is essential for optimizing memory usage and performance in image processing pipelines. The storage mode is defined by the IBStorage enum in src/include/OpenImageIO/imagebuf.h【1†L127-L145】 and controls how pixel data is allocated, accessed, and modified.
Understanding the Three ImageBuf Storage Modes
LOCALBUFFER: Owned Local Memory
LOCALBUFFER indicates that the ImageBuf instance owns its pixel memory. The buffer allocates storage internally via new_pixels when constructed from an ImageSpec or when a read operation forces full allocation【1†L135-L139】. The memory is released automatically when the ImageBuf destructor runs or when reset() is called.
This mode provides full read-write access to pixels through methods like setpixel() and iterator interfaces. It is the default choice for small-to-moderate images where you need direct, in-memory manipulation without the overhead of tile-based caching.
APPBUFFER: Wrapped Application Memory
APPBUFFER represents a view into memory allocated and managed by the calling application. The ImageBuf does not own this memory—it merely wraps a pointer or span provided during construction【1†L139-L144】. The caller retains full responsibility for ensuring the memory remains valid for the lifetime of the ImageBuf and for deallocating it afterward.
Mutability depends entirely on the span type passed to the constructor. A span<T> or image_span<T> with non-const T yields a writable buffer, while a read-only span produces a read-only ImageBuf. Use this mode when integrating with external libraries, GPU memory, or custom allocators where copying data would be prohibitively expensive.
IMAGECACHE: On-Demand Tile Access
IMAGECACHE delegates pixel storage to an ImageCache instance. The ImageBuf holds no local pixel data; instead, it fetches tiles on demand from the cache when pixels are accessed【1†L144-L150】. This mode is read-only by default and is ideal for very large images, texture workflows, or scenarios where multiple ImageBuf objects need to share cached tiles to minimize memory footprint.
To modify pixels in an IMAGECACHE buffer, you must explicitly call make_writable(), which reads the entire image into a locally allocated buffer and converts the storage mode to LOCALBUFFER【1†L334-L340】.
How Storage Modes Affect Mutability and Performance
The storage mode directly impacts whether you can modify pixel values and how memory is managed:
- LOCALBUFFER: Fully writable. Best performance for random access on small images, but consumes full RAM for the entire image.
- APPBUFFER: Writable only if the wrapped span is non-const. Zero-copy integration with external data, but requires careful lifetime management.
- IMAGECACHE: Read-only until
make_writable()is called. Memory-efficient for large images and shared access, but individual pixel access may trigger disk I/O for uncached tiles.
When reading files without an explicit ImageCache, ImageBuf automatically selects between LOCALBUFFER and IMAGECACHE based on image size and configuration options【1†L173-L177】. Small images load into local buffers, while large images default to cached access to prevent excessive memory consumption.
Practical Code Examples
The following examples demonstrate how to create ImageBuf objects with each storage mode using the OpenImageIO API:
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
using namespace OIIO;
// 1. LOCALBUFFER – Allocate and own the pixels
ImageSpec spec (256, 256, 3, TypeFloat);
ImageBuf local_buf (spec); // storage == LOCALBUFFER
local_buf.setpixel (10, 20, 0, {1.0f, 0.0f, 0.0f}); // writable
// 2. APPBUFFER – Wrap a pre-allocated array (no copy)
float raw_pixels[256*256*3];
ImageSpec spec2 (256, 256, 3, TypeFloat);
ImageBuf app_buf (spec2, span<float>(raw_pixels, 256*256*3)); // storage == APPBUFFER
// Caller must keep raw_pixels alive until app_buf is destroyed.
// 3. IMAGECACHE – Read lazily via the global cache
ImageBuf cache_buf ("huge_image.exr"); // storage will be IMAGECACHE for large files
if (cache_buf.storage() == ImageBuf::IMAGECACHE) {
// Pixels fetched on demand; buffer is read-only by default.
float val = cache_buf.getchannel (10000, 20000, 0, 0); // triggers tile load
}
// 4. Convert cached image to writable local buffer
if (cache_buf.storage() == ImageBuf::IMAGECACHE) {
cache_buf.make_writable(); // now storage == LOCALBUFFER
cache_buf.setpixel (0, 0, 0, {0.5f, 0.5f, 0.5f}); // writable
}
These examples reference the constructor overloads and reset() methods defined in src/include/OpenImageIO/imagebuf.h【1†L362-L380】 and implemented in src/libOpenImageIO/imagebuf.cpp.
Summary
- LOCALBUFFER provides owned, writable memory suitable for small-to-moderate images requiring direct pixel manipulation.
- APPBUFFER enables zero-copy integration with existing application memory, with mutability determined by the span type provided.
- IMAGECACHE offers memory-efficient, read-only access to large images via tile-based caching, convertible to local storage via
make_writable().
Choosing the appropriate ImageBuf storage mode depends on your image size, performance requirements, and whether you need to modify pixel data or share memory with external systems.
Frequently Asked Questions
When should I use LOCALBUFFER instead of IMAGECACHE?
Use LOCALBUFFER when working with small-to-moderate sized images that fit comfortably in RAM and require frequent random access or pixel modification. This mode provides the fastest access times since all pixels reside in contiguous memory. Use IMAGECACHE for very large images (such as high-resolution textures or deep EXR files) where loading the entire image would exhaust available memory, or when multiple ImageBuf objects need to share the same underlying tile data to minimize duplication.
Who is responsible for freeing memory in APPBUFFER mode?
In APPBUFFER mode, the application retains full ownership of the pixel memory. The ImageBuf merely wraps a pointer or span to existing data and does not allocate or deallocate the underlying buffer. You must ensure the memory remains valid for the entire lifetime of the ImageBuf instance and free it according to your application's allocation strategy after the ImageBuf is destroyed or reset.
How do I convert an IMAGECACHE buffer to a writable format?
Call the make_writable() method on the ImageBuf instance. This operation forces a full read of the image from the cache into a locally allocated buffer, converting the storage mode from IMAGECACHE to LOCALBUFFER. After this conversion, the buffer becomes fully writable using methods like setpixel() or iterators. Note that this operation increases memory usage proportional to the full image size and should be used only when modification is required.
Can I change the storage mode of an existing ImageBuf without reconstructing it?
Yes, you can transition from IMAGECACHE to LOCALBUFFER using make_writable() as described above. However, you cannot directly convert between LOCALBUFFER and APPBUFFER or vice versa without calling reset() with new parameters. The reset() method provides overloads that allow you to reinitialize the ImageBuf with different storage modes, effectively changing how pixel data is managed while reusing the object instance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →