# OpenImageIO ImageBuf Storage Modes: LOCALBUFFER vs APPBUFFER vs IMAGECACHE

> Understand OpenImageIO ImageBuf storage modes: LOCALBUFFER APPBUFFER and IMAGECACHE. Learn about memory ownership mutability and pixel fetching to optimize your image processing workflows.

- Repository: [Academy Software Foundation/openimageio](https://github.com/academysoftwarefoundation/openimageio)
- Tags: deep-dive
- Published: 2026-02-23

---

**`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`](https://github.com/academysoftwarefoundation/openimageio/blob/main/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:

```cpp
#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`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/include/OpenImageIO/imagebuf.h)【1†L362-L380】 and implemented in [`src/libOpenImageIO/imagebuf.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/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.