# How to Configure OpenImageIO to Use IOProxy for Custom File I/O Handlers

> Configure OpenImageIO to use IOProxy for custom file I/O. Subclass Filesystem::IOProxy, verify support, and attach your proxy for tailored file handling in your projects.

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

---

**To configure OpenImageIO for custom file I/O handlers, subclass `Filesystem::IOProxy`, verify that your target format supports the capability via `supports("ioproxy")`, and attach the proxy using `set_ioproxy()` or the `"oiio:ioproxy"` configuration attribute before opening the file.**

OpenImageIO (OIIO) abstracts file system access through the `Filesystem::IOProxy` class, allowing developers to redirect image I/O to memory buffers, network streams, or virtual file systems. When you configure OpenImageIO to use IOProxy for custom file I/O handlers, you bypass the default `FILE*` based operations and gain full control over byte-level read, write, and seek behaviors.

## Understanding the IOProxy Architecture

### The IOProxy Base Class

The abstract interface is defined in [`src/include/OpenImageIO/filesystem.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/include/OpenImageIO/filesystem.h) (lines 16–27). An `IOProxy` subclass must implement the core virtual methods that OIIO plugins invoke:

- `read(void* buf, size_t size)` – Read bytes into a buffer
- `write(const void* buf, size_t size)` – Write bytes from a buffer
- `seek(int64_t offset)` – Reposition the read/write pointer
- `tell()` – Return the current position
- `size()` – Return the total size of the stream (optional but recommended)

### Built-In Implementations

OIIO provides three concrete implementations in [`filesystem.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/filesystem.h) that you can use directly or subclass:

- **`IOFile`** – Wraps a standard C `FILE*` handle for disk-based I/O (the default behavior)
- **`IOMemReader`** – Reads from an existing memory buffer (`cspan<unsigned char>`), defined around lines 57–70
- **`IOVecOutput`** – Writes to a `std::vector<unsigned char>` that grows dynamically

## Checking Format Support for IOProxy

Not all image formats support custom I/O proxies. Before attaching a proxy, you must verify that the specific `ImageInput` or `ImageOutput` plugin advertises the `"ioproxy"` capability.

```cpp
auto in = OIIO::ImageInput::create("image.exr");
if (!in || !in->supports("ioproxy")) {
    // This format cannot use custom proxies; it will only accept file paths
    return;
}

```

The `supports()` method is declared in [`src/include/OpenImageIO/imageio.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/include/OpenImageIO/imageio.h) and implemented per-format in individual plugins (for example, OpenEXR checks this in [`src/openexr.imageio/exr_pvt.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/openexr.imageio/exr_pvt.h)).

## Three Methods to Attach a Custom IOProxy

OIIO offers three equivalent mechanisms to inject a custom proxy into the I/O pipeline. All require that the proxy be attached **before** calling `open()`.

### Method 1: Direct set_ioproxy() Call

Explicitly assign the proxy to an already-created plugin instance. This is the most straightforward approach when you have direct access to the `ImageInput` or `ImageOutput` object.

```cpp
OIIO::Filesystem::IOMemReader proxy(buffer, buffer_size);
auto in = OIIO::ImageInput::create("dummy.exr");
in->set_ioproxy(&proxy);
in->open("dummy.exr", spec);

```

The implementation in [`src/libOpenImageIO/imageinput.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/libOpenImageIO/imageinput.cpp) (lines 37–42) validates the capability and stores the pointer in `m_impl->m_io`.

### Method 2: Factory Creation with ImageSpec Attribute

Pass the proxy through the `ImageSpec` configuration when using the factory method. This is useful when the plugin creation and I/O setup are decoupled.

```cpp
OIIO::Filesystem::IOMemReader proxy(data, size);
OIIO::ImageSpec config;
config.attribute("oiio:ioproxy", OIIO::TypeDesc::PTR, &proxy);

auto in = OIIO::ImageInput::create("image.exr", false, nullptr, &config);
in->open("image.exr", spec, config);

```

The factory logic in [`src/libOpenImageIO/imageioplugin.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/libOpenImageIO/imageioplugin.cpp) (lines 45–50) automatically extracts this attribute and invokes `set_ioproxy` on the newly instantiated plugin.

### Method 3: ImageOutput Configuration

The same pattern applies to writing images. Use `ImageOutput::set_ioproxy` or the attribute method before `open()`.

```cpp
auto out = OIIO::ImageOutput::create("output.tiff");
if (out->supports("ioproxy")) {
    out->set_ioproxy(&my_custom_proxy);
}
out->open("output.tiff", spec);

```

The setter implementation resides in [`src/libOpenImageIO/imageoutput.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/libOpenImageIO/imageoutput.cpp) (lines 9–14).

## Implementing a Custom IOProxy Subclass

To integrate non-standard storage (network streams, encrypted archives, etc.), derive from `Filesystem::IOProxy` and implement the virtual interface. The following example implements a write-only proxy that streams data into a `std::vector`.

```cpp
#include <OpenImageIO/filesystem.h>
#include <vector>

class VecWriter : public OIIO::Filesystem::IOProxy {
public:
    VecWriter() : IOProxy("", Write), m_pos(0) {}

    size_t write(const void* buf, size_t size) override {
        const auto* p = static_cast<const unsigned char*>(buf);
        m_buf.insert(m_buf.end(), p, p + size);
        m_pos += size;
        return size;
    }

    size_t read(void*, size_t) override { return 0; } // Not supported in write mode
    bool seek(int64_t offset) override { m_pos = offset; return true; }
    size_t size() const override { return m_buf.size(); }
    const std::vector<unsigned char>& data() const { return m_buf; }

private:
    std::vector<unsigned char> m_buf;
    int64_t m_pos;
};

```

This pattern allows you to capture image output in memory for further processing or transmission without ever touching the local filesystem.

## Complete Working Examples

### In-Memory Reading with IOMemReader

Use the built-in `IOMemReader` to read an image from a buffer retrieved from a database or network call.

```cpp
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/imageio.h>
#include <vector>

int main() {
    // Buffer loaded from external source
    std::vector<unsigned char> img_buffer = load_from_database();
    
    // Wrap buffer in proxy
    OIIO::Filesystem::IOMemReader proxy(img_buffer.data(), img_buffer.size());
    
    // Create input and verify proxy support
    auto in = OIIO::ImageInput::create("image.exr");
    if (!in || !in->supports("ioproxy")) {
        return 1;
    }
    
    // Attach proxy and open
    in->set_ioproxy(&proxy);
    OIIO::ImageSpec spec;
    if (!in->open("image.exr", spec)) {
        std::cerr << "Error: " << in->geterror() << "\n";
        return 1;
    }
    
    // Read pixels...
    in->close();
}

```

### Custom Vector Writer for Streaming Output

Combine the custom `VecWriter` class with `ImageOutput` to capture TIFF data in memory.

```cpp
#include <OpenImageIO/imageio.h>
#include <vector>

// (Assume VecWriter class defined as shown previously)

int main() {
    VecWriter proxy;
    
    auto out = OIIO::ImageOutput::create("output.tiff");
    if (!out || !out->supports("ioproxy")) {
        std::cerr << "Format does not support IOProxy\n";
        return 1;
    }
    
    out->set_ioproxy(&proxy);
    
    OIIO::ImageSpec spec(512, 512, 4, OIIO::TypeDesc::UINT8);
    out->open("output.tiff", spec);
    
    // Write image data...
    std::vector<unsigned char> pixels(512 * 512 * 4, 255);
    out->write_image(OIIO::TypeDesc::UINT8, pixels.data());
    
    out->close();
    
    // Access written data
    const auto& file_data = proxy.data();
    // Transmit file_data to network or store in database...
}

```

### Factory-Based Configuration via ImageSpec

For scenarios where you cannot directly call `set_ioproxy`, use the factory attribute approach.

```cpp
OIIO::Filesystem::IOMemReader proxy(buffer, size);
OIIO::ImageSpec config;
config.attribute("oiio:ioproxy", OIIO::TypeDesc::PTR, &proxy);

auto in = OIIO::ImageInput::create("image.exr", false, nullptr, &config);
if (!in) { /* error */ }

OIIO::ImageSpec spec;
if (!in->open("image.exr", spec, config)) {
    std::cerr << "Open failed: " << in->geterror() << "\n";
}

```

## Key Source Files and Implementation Details

The IOProxy system spans several core files in the OpenImageIO repository:

- **[`src/include/OpenImageIO/filesystem.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/include/OpenImageIO/filesystem.h)** – Defines the abstract `IOProxy` base class and built-in implementations (`IOFile`, `IOMemReader`, `IOVecOutput`). The virtual interface requires `read`, `write`, `seek`, `tell`, and `size` methods.

- **[`src/include/OpenImageIO/imageio.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/include/OpenImageIO/imageio.h)** – Declares the `ImageInput` and `ImageOutput` APIs, including the `set_ioproxy()` virtual methods and the `supports()` capability query.

- **[`src/libOpenImageIO/imageinput.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/libOpenImageIO/imageinput.cpp)** (lines 37–42) – Implements `ImageInput::set_ioproxy()`, which validates the `"ioproxy"` capability and stores the pointer in the internal implementation object.

- **[`src/libOpenImageIO/imageoutput.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/libOpenImageIO/imageoutput.cpp)** (lines 9–14) – Implements the corresponding `ImageOutput::set_ioproxy()` method for write operations.

- **[`src/libOpenImageIO/imageioplugin.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/libOpenImageIO/imageioplugin.cpp)** (lines 45–50) – Contains the factory logic that extracts the `"oiio:ioproxy"` attribute from an `ImageSpec` configuration and automatically forwards it to the newly created plugin instance.

- **[`src/openexr.imageio/exr_pvt.h`](https://github.com/academysoftwarefoundation/openimageio/blob/main/src/openexr.imageio/exr_pvt.h)** – Example of a format-specific implementation that utilizes the proxy for file validation and I/O operations.

## Summary

- **IOProxy abstraction**: OpenImageIO delegates all byte-level I/O to `Filesystem::IOProxy` subclasses, enabling custom storage backends without modifying format plugins.

- **Capability verification**: Always check `supports("ioproxy")` on your `ImageInput` or `ImageOutput` instance before attempting to attach a proxy; unsupported formats will fall back to standard file I/O.

- **Attachment methods**: You can configure the proxy via three equivalent mechanisms: direct `set_ioproxy()` calls, the `"oiio:ioproxy"` attribute in `ImageSpec` passed to factory methods, or configuration specs passed to `open()`.

- **Lifetime management**: The caller retains full ownership of the proxy object; OIIO stores only a pointer and never deletes the proxy. Ensure the proxy outlives the `ImageInput` or `ImageOutput` using it.

- **Thread safety**: Custom proxy implementations must ensure that `read`, `write`, and `seek` operations are thread-safe if the image plugin may invoke them from multiple threads.

## Frequently Asked Questions

### How do I know if a specific image format supports IOProxy?

Call the `supports()` method on the plugin instance before opening the file. For example: `if (in->supports("ioproxy")) { in->set_ioproxy(&proxy); }`. If the format returns false, it can only read from or write to actual filesystem paths using the standard `IOFile` implementation.

### Can I use IOProxy to read from a network stream or encrypted archive?

Yes. Subclass `OIIO::Filesystem::IOProxy` and implement the virtual methods (`read`, `write`, `seek`, `size`) to interface with your backend. For read-only network streams, implement `read` and `seek` (if supported by the protocol); for encrypted archives, decrypt data blocks in your `read` implementation before copying to the buffer.

### Who is responsible for deleting the IOProxy object?

The caller retains ownership. When you call `set_ioproxy()` or pass a proxy via the `"oiio:ioproxy"` attribute, OIIO stores the raw pointer internally (as seen in [`imageinput.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/imageinput.cpp) lines 37–42) but never invokes `delete`. You must ensure the proxy object remains valid for the entire duration of the `ImageInput` or `ImageOutput` lifecycle.

### What is the difference between using `set_ioproxy()` and the `"oiio:ioproxy"` attribute?

Both achieve the same result, but the timing differs. `set_ioproxy()` is called explicitly on an existing plugin instance before `open()`. The `"oiio:ioproxy"` attribute is passed within an `ImageSpec` to `ImageInput::create()` or `ImageOutput::create()`, allowing the factory (in [`imageioplugin.cpp`](https://github.com/academysoftwarefoundation/openimageio/blob/main/imageioplugin.cpp) lines 45–50) to automatically configure the proxy immediately after instantiation. Use the attribute approach when you want the factory to handle setup, or `set_ioproxy()` when you need to change proxies between operations.