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

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 (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 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.

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 and implemented per-format in individual plugins (for example, OpenEXR checks this in 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.

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 (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.

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 (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().

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 (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.

#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.

#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.

#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.

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 – 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 – Declares the ImageInput and ImageOutput APIs, including the set_ioproxy() virtual methods and the supports() capability query.

  • 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 (lines 9–14) – Implements the corresponding ImageOutput::set_ioproxy() method for write operations.

  • 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 – 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 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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →