Building Custom OpenFX Plugins: A Step-by-Step Tutorial
Overview
This tutorial walks through creating a simple custom OpenFX (OFX) plugin for image compositing applications that support the OpenFX API (e.g., Nuke, Natron, Fusion). It covers environment setup, plugin structure, parameter handling, pixel processing, building, and testing. Example code uses C++ and the OFX image effect API (C-style headers). Assume a 64-bit Linux or macOS development environment and a working C++ toolchain.
Prerequisites
- C++ compiler (g++ / clang++) and make or CMake
- OFX header files (from the OpenFX SDK) placed in an include path
- Host application supporting OFX for testing (Natron recommended for free testing)
- Basic knowledge of C++ and image processing concepts
Plugin Concept
We’ll implement a simple color-adjust plugin that multiplies RGBA channels by user-controlled factors (R, G, B, A). This demonstrates parameter handling, image fetch/store, and multi-thread-safe pixel processing.
Project structure
- src/
- ColorMult.cpp
- include/
- ofxImageEffect.h (from OFX SDK)
- build/
- CMakeLists.txt (or Makefile)
Key OFX concepts
- Plugin descriptor: registers plugin identity and supported contexts.
- Instance and parameter suite: create and manage parameters (double sliders).
- Render entry point: called by host to process frames/tiles/scanlines.
- Image and pixel data access: use host-provided image and property suites.
- Threading: implement per-pixel operations without global state.
Step 1 — Create plugin descriptor
Implement plugin entry point to describe the effect and parameters. Minimal essentials:
- Unique ID (e.g., “com.example.ColorMult”)
- Label, grouping, supported pixel depths (8-bit, 16-bit, float)
- Parameters: R, G, B, A (default 1.0, range 0.0–4.0)
Example (conceptual snippet):
cpp
// register plugin (pseudocode) void describe(OfxImageEffectHandle effect) { // set labels, contexts // define 4 param doubles with defaults }
Step 2 — Define parameters
Create four DoubleParam instances named “multR”, “multG”, “multB”, “multA”. Expose sliders with sensible UI hints:
- Default: 1.0
- Minimum: 0.0
- Maximum: 4.0
- Display steps and increment: 0.01
Step 3 — Implement render action
The host will call your render function with a render window (rectangle). Steps inside render:
- Fetch source image and destination image descriptors.
- Lock image data and retrieve pixel pointers and row strides.
- Get current parameter values for the frame/time.
- Loop over pixels within the render window and multiply channels.
Handle different pixel depths by separate conversion paths (uint8_t, uint16t, float). Example pseudocode:
cpp
for (y = y1; y < y2; ++y) for (x = x1; x < x2; ++x) { src = getPixel(srcImg, x, y); dst = multiplyChannels(src, multR, multG, multB, multA); setPixel(dstImg, x, y, dst); }
Step 4 — Handle pixel formats and alpha
- For 8-bit and 16-bit integer formats: convert to float, apply multipliers, clamp to valid range, convert back.
- For float formats: apply multipliers directly.
- Preserve or premultiply alpha depending on host expectations; document behavior. For simplicity, assume unpremultiplied input and write unpremultiplied output (or follow host conventions if required).
Step 5 — Performance and threading
- Avoid global mutable state.
- Process by scanlines or tiles as the host requests to enable parallel rendering.
- Use SIMD or multi-threading inside render only if you manage thread-safety and match host threading model. Often the host parallelizes calls; keep per-pixel loops simple and efficient.
- Minimize memory allocations inside render.
Step 6 — Build system (CMake example)
Create CMakeLists.txt that:
- Finds C++ compiler
- Adds include path for OFX headers
- Builds a shared library with correct naming/ABI for the host (e.g., .so on Linux, .dylib on macOS)
- Installs plugin into host plugin folder or a location the host scans
Minimal CMake snippet:
cmake
add_library(ColorMult SHARED src/ColorMult.cpp) target_include_directories(ColorMult PRIVATE include /path/to/ofx) set_target_properties(ColorMult PROPERTIES PREFIX ”” OUTPUT_NAME “ColorMult”)
Step 7 — Install and test
- Copy built plugin binary to host’s plugin directory (Natron: ~/.config/Natron/plugins or system plugin folder).
- Start host and look for “ColorMult” in effects menu.
- Test with a range of input formats and animated parameter values; verify correct render window handling by masking and region renders.
Debugging tips
- Log parameter values and render rectangles (hosts often provide a message/logging facility).
- Test with known input pixels (checkerboards, ramps).
- If the plugin crashes on load, verify exported entry points match OFX SDK expectations and that the binary’s symbol visibility is correct.
- Use host-provided validation if available.
Example full-source pointers
- Follow OFX sample plugins in the official OpenFX SDK for exact API calls and property suite usage.
- Implement separate helper functions: fetchParameters(frame), processTile(src, dst, rect), convertPixelFormats().
Next steps and extensions
- Add a UI for color space handling (sRGB vs linear).
- Support LUTs or curve-based color corrections.
- Implement GPU acceleration via OpenCL/CUDA or host-specific GPU APIs where supported.
This tutorial gives the essential workflow to build a basic, robust OFX plugin. For exact API calls and header details, reference the OpenFX SDK samples and the host application plugin guides.
Leave a Reply