How to Implement a Custom ImageInput Plugin for a New Image Format in OpenImageIO
To implement a custom ImageInput plugin for OpenImageIO, subclass the abstract ImageInput base class, implement virtual methods including valid_file(), open(), close(), and read_native_scanline(), and expose the plugin using the OIIO_PLUGIN_EXPORTS_BEGIN/END macros with a factory function.
OpenImageIO (OIIO) provides a plugin architecture that allows developers to add support for new image formats without modifying the core library. Each format is handled by a concrete subclass of ImageInput defined in src/include/OpenImageIO/imageio.h that implements format-specific reading logic. This guide walks through creating a custom ImageInput plugin based on the patterns used in the academysoftwarefoundation/openimageIO repository, specifically referencing the PNG implementation as a reference model.
Understand the ImageInput API
The ImageInput base class in src/include/OpenImageIO/imageio.h defines the interface that all image reader plugins must implement. According to the source code, the most critical virtual methods for a minimal implementation include:
virtual bool valid_file(Filesystem::IOProxy* ioproxy) const– Performs a lightweight check to verify the file matches your format's signature without fully opening it.virtual bool open(const std::string& name, ImageSpec& spec)– Initializes the decoder, validates the file, and populates the ImageSpec object with image dimensions, channels, and metadata.virtual bool close()– Releases all allocated resources and resets internal state.virtual bool read_native_scanline(int subimage, int miplevel, int y, int z, void* data)– Reads raw pixel data for a single scanline; alternative methods likeread_native_tile()exist for tiled formats.
See the declaration here: [imageio.h – ImageInput definition](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/src/include/OpenImageIO/imageio.h#L43).
Create the Plugin Skeleton
Create a new directory following OIIO's naming convention: src/myformat.imageio/. Add these files:
myformatinput.cpp– Contains theImageInputsubclass implementation and plugin registration macros.myformat_pvt.h(optional) – Hides third-party library dependencies behind a private namespace, similar topng_pvt.hin the PNG plugin.
Implement the Subclass
A minimal but functional subclass follows the pattern established in src/png.imageio/pnginput.cpp. The implementation requires a class declaration, constructor initialization, and the mandatory plugin export block.
Class Declaration and Constructor
Define your class with proper initialization and cleanup:
// src/myformat.imageio/myformatinput.cpp
#include "myformat_pvt.h"
OIIO_PLUGIN_NAMESPACE_BEGIN
class MyFormatInput final : public ImageInput {
public:
MyFormatInput() { init(); }
~MyFormatInput() override { close(); }
const char* format_name() const override { return "myformat"; }
int supports(string_view feature) const override {
return (feature == "ioproxy");
}
bool valid_file(Filesystem::IOProxy* ioproxy) const override;
bool open(const std::string& name, ImageSpec& spec) override;
bool close() override;
bool read_native_scanline(int subimage, int miplevel, int y, int z,
void* data) override;
private:
std::string m_filename;
MyLibDecoder* m_decoder = nullptr;
int m_subimage = -1;
ImageSpec m_spec;
void init() { m_subimage = -1; m_decoder = nullptr; }
};
The constructor calls a private init() method to zero member state, mirroring the pattern in PNGInput::init() found at line 61 of the PNG source.
Plugin Registration Macros
Expose the plugin to OIIO using the export macros after your class definition:
OIIO_PLUGIN_EXPORTS_BEGIN
OIIO_EXPORT ImageInput* myformat_input_imageio_create() {
return new MyFormatInput;
}
OIIO_EXPORT int myformat_imageio_version = OIIO_PLUGIN_VERSION;
OIIO_EXPORT const char* myformat_imageio_library_version() {
return MyLibVersion();
}
OIIO_EXPORT const char* myformat_input_extensions[] = { "myf", nullptr };
OIIO_PLUGIN_EXPORTS_END
OIIO_PLUGIN_NAMESPACE_END
The factory function myformat_input_imageio_create serves as the entry point OIIO looks for when loading plugins. This pattern appears in [pnginput.cpp at line 6](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/src/png.imageio/pnginput.cpp#L6).
Implementing valid_file()
Provide a fast header check to identify files without fully initializing the decoder:
bool MyFormatInput::valid_file(Filesystem::IOProxy* ioproxy) const {
if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Mode::Read)
return false;
unsigned char hdr[8];
size_t n = ioproxy->pread(hdr, sizeof(hdr), 0);
return n == sizeof(hdr) && MyFormat_pvt::check_header(hdr);
}
Implementing open()
Handle file initialization and ImageSpec population:
bool MyFormatInput::open(const std::string& name, ImageSpec& spec) {
m_filename = name;
m_subimage = 0;
// Acquire IOProxy (implements ioproxy support)
if (!ioproxy_use_or_open(name))
return false;
// Verify file header
unsigned char hdr[8];
if (ioproxy()->pread(hdr, sizeof(hdr), 0) != sizeof(hdr))
return false;
if (!MyFormat_pvt::check_header(hdr))
return false;
// Initialize decoder
std::string err;
if (!MyFormat_pvt::create_decoder(m_decoder, m_filename, err)) {
errorf("Could not create decoder: %s", err);
return false;
}
// Query image properties and fill spec
int width, height, channels;
MyFormat_pvt::get_image_info(m_decoder, width, height, channels);
m_spec = ImageSpec(width, height, channels, TypeDesc::UINT8);
// Add any format-specific attributes
m_spec.attribute("myformat:version", MyFormat_pvt::get_version(m_decoder));
spec = m_spec;
return true;
}
This follows the initialization pattern in PNGInput::open (lines 42-76), including error handling and IOProxy management.
Implementing close()
Release resources to prevent memory leaks:
bool MyFormatInput::close() {
if (m_decoder) {
MyFormat_pvt::destroy_decoder(m_decoder);
m_decoder = nullptr;
}
init();
return true;
}
This mirrors the cleanup logic in PNGInput::close at lines 39-44.
Implementing read_native_scanline()
Read raw pixel data for a specific scanline:
bool MyFormatInput::read_native_scanline(int subimage, int miplevel,
int y, int /*z*/, void* data) {
lock_guard lock(*this);
if (!seek_subimage(subimage, miplevel))
return false;
y -= m_spec.y; // Account for image origin offset
if (y < 0 || y >= m_spec.height)
return false;
return MyFormat_pvt::read_scanline(m_decoder, y, data);
}
For interlaced formats, you may need to buffer the entire image first, as demonstrated in the PNG implementation around lines 12-23.
Private Helper Headers
If your format relies on an external decoding library, isolate those dependencies in a private header to keep your main plugin clean. Create src/myformat.imageio/myformat_pvt.h:
#pragma once
#include <string>
struct MyLibDecoder; // Forward declaration
namespace MyFormat_pvt {
bool check_header(const unsigned char* header);
bool create_decoder(MyLibDecoder*& decoder, const std::string& filename,
std::string& error);
void get_image_info(MyLibDecoder* decoder, int& w, int& h, int& channels);
bool read_scanline(MyLibDecoder* decoder, int y, void* dst);
void destroy_decoder(MyLibDecoder* decoder);
const char* get_version(MyLibDecoder* decoder);
const char* MyLibVersion();
}
This approach mirrors png_pvt.h and png_pvt.cpp, which wrap libpng calls in the PNG_pvt namespace.
Build System Integration
Add your new source files to the OIIO CMake build system. The repository already contains patterns for each plugin directory. After compilation, the plugin installs to the plugins/ directory and becomes automatically discoverable by OIIO at runtime through the OIIO_PLUGIN_SEARCHPATH.
Using the New Plugin
Once compiled and installed, your plugin works transparently with OIIO's standard API:
#include <OpenImageIO/imageio.h>
#include <iostream>
#include <vector>
int main() {
// Plugin loads automatically if available on OIIO_PLUGIN_SEARCHPATH
auto in = OIIO::ImageInput::open("example.myf");
if (!in) {
std::cerr << "Failed to open: " << OIIO::geterror() << "\n";
return 1;
}
const OIIO::ImageSpec &spec = in->spec();
std::vector<unsigned char> pixels(spec.image_bytes());
if (!in->read_image(0, 0, 0, spec.nchannels,
OIIO::TypeDesc::UINT8, pixels.data())) {
std::cerr << "Read failed: " << OIIO::geterror() << "\n";
return 1;
}
in->close();
return 0;
}
Summary
- Subclass
ImageInputand implement the virtual interface (valid_file,open,close, and read methods) to handle your format's specific requirements. - Use the export macros
OIIO_PLUGIN_EXPORTS_BEGIN/ENDto expose the factory functionmyformat_input_imageio_create, version symbols, and file extensions array. - Follow the PNG pattern in
src/png.imageio/pnginput.cppfor initialization, error handling, and resource cleanup. - Isolate third-party dependencies in a private header like
myformat_pvt.hto maintain clean separation between OIIO's interface and external libraries. - Place your implementation in
src/myformat.imageio/and integrate it into the CMake build system for automatic plugin discovery.
Frequently Asked Questions
What is the minimum set of methods required for an ImageInput plugin?
You must implement format_name(), valid_file(), open(), close(), and at least one read method—typically read_native_scanline() for scanline-based formats or read_native_tile() for tiled formats. The supports() method is optional but recommended for advertising capabilities like IOProxy support.
How does OpenImageIO discover custom plugins at runtime?
OIIO searches directories listed in the OIIO_PLUGIN_SEARCHPATH environment variable (defaulting to the plugins/ subdirectory of the installation). It loads shared libraries and looks for exported factory functions matching the naming convention formatname_input_imageio_create, as defined in your OIIO_PLUGIN_EXPORTS block.
Can I implement tiled reading instead of scanline reading?
Yes. If your format is inherently tiled, override read_native_tile() instead of (or in addition to) read_native_scanline(). You may also override read_native_scanline() to provide convenience access by internally reading the containing tile and extracting the scanline, though this is less efficient than native scanline support.
How do I handle format-specific metadata in the ImageSpec?
During the open() method, populate the ImageSpec object passed by reference. Use spec.attribute() to add custom metadata with namespaced keys (e.g., "myformat:compression", "myformat:icc_profile"). These attributes become accessible to applications through the ImageSpec::extra_attribs vector or get_attribute() methods.
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 →