QuickPaint

Writing plugins

QuickPaint plugins are dynamic libraries (.so / .dll / .dylib) that export a single C function:

c
const QpPluginInfo* qp_plugin_entry(void);

The returned descriptor and everything it points to must stay valid for the lifetime of the process. All types are defined as #[repr(C)] in the qp-plugin-api crate, so plugins can also be written in C, C++, Zig and other languages.

Where plugins go

At startup QuickPaint loads the libraries in these folders:

  1. The plugins/ folder next to the executable
  2. The plugins/ folder in the user settings folder (Linux: ~/.config/quickpaint/plugins, Windows: %APPDATA%\EarthQuick\QuickPaint\config\plugins)

Loaded plugins and load errors are listed in Help > Plugins.

Writing an effect in Rust

toml
[lib]
crate-type = ["cdylib"]

[dependencies]
qp-plugin-api = { path = "../../crates/qp-plugin-api" }
rust
use qp_plugin_api::sdk::{self, Render};
use qp_plugin_api::*;

extern "C" fn invert_red(args: *const QpRenderArgs) -> i32 {
    let mut r = unsafe { Render::from_raw(args) };
    let amount = r.int(0) as f32 / 100.0;          // value by property order
    r.map(|_x, _y, c| QpColor { r: c.r + ((255 - 2 * c.r as i32) as f32 * amount) as u8, ..c });
    0 // success
}

export_plugin!(|| {
    sdk::plugin("My Plugin", "Me", "1.0.0", "Description",
        vec![sdk::effect("me.invertred", "Invert Red", "My Menu", menu::EFFECTS,
            vec![sdk::int("amount", "Amount", 0, 100, 100)], invert_red)],
        vec![])
});

Property types

QuickPaint builds the settings dialog, with live preview, from the properties you declare.

SDK functionControlValue field
intslideri
floatsliderf
booleancheck boxi (0/1)
choiceradio buttons / drop-down listi (index)
colorcolor picker (primary / secondary / fixed default)color
angleangle dialf (degrees)
point2D position picker, -1..1 around the centerf, f2
seed"Reseed" buttoni

The render contract

  • src and dst are straight (not premultiplied) RGBA, with a row stride of width * 4 bytes.
  • dst starts as a copy of src; you only need to write the roi_* rectangle.
  • The app applies the selection mask afterwards (pixels outside the selection are restored).
  • In long loops check Render::cancelled() and report progress with Render::progress().
  • Render runs on a background thread; mind thread safety if you use global state.

File type plugins

QpFileTypeInfo gives the list of extensions ("raw;bin") and the load, save and free_image functions. The QpImage that load fills is handed back to your free_image once the app is done with it. The sample plugin that ships with QuickPaint adds a .qraw file type this way.

ABI version

QpPluginInfo::abi_version must be QP_ABI_VERSION. Plugins with another version are not loaded and are listed as errors.