--- layout: default ---

## Plugin system

Photoflare supports a plugin system that allows third-party developers to extend the application with new image filters and file format exporters. Plugins are compiled as shared libraries (`.dll` on Windows, `.so` on Linux, `.dylib` on macOS) and loaded at startup from a `plugins/` folder alongside the Photoflare executable.

### Installing plugins

Copy the plugin shared library into the `plugins/` folder next to the Photoflare executable, then launch Photoflare. To reload plugins without restarting, open Edit → Plugins… and click Rescan.

### Plugin Manager dialog

Open Edit → Plugins… to view all loaded plugins. Filter plugins can also be applied from this dialog by selecting one and clicking Apply. Click Open Folder to open the plugins directory in your file manager.

## Developing a plugin

Plugins are Qt 6 C++ shared libraries that implement one of the plugin interfaces defined in the Photoflare SDK headers. Example plugins are provided in the `examples/plugins/` directory of the source repository.

### Requirements

- Qt 6.5 or later (same major version as the Photoflare build you are targeting)
- A C++17 compatible compiler (MSVC 2019+, GCC 10+, Clang 12+)
- The three SDK headers from `src/plugins/`: `IPhotoflarePlugin.h`, `AppContext.h`

### SDK headers

Copy or reference these two headers into your plugin project — no linking against Photoflare itself is required.

- IPhotoflarePlugin.h — defines `IPhotoflarePlugin`, `IFilterPlugin`, `IExporterPlugin`, and `PluginParam`
- AppContext.h — defines the `AppContext` API surface passed to `initialise()`

### Plugin types

Interface | What it does | Where it appears
--- | --- | ---
`IFilterPlugin` | Transforms a `QImage` and returns a new one | Plugin Filters menu & Plugin Manager dialog
`IExporterPlugin` | Writes a `QImage` to a file in a custom format | File → Save As format list

## Creating a filter plugin

A filter plugin receives the current canvas image, applies a transformation, and returns the result. The host runs `apply()` on a background thread automatically, so the UI stays responsive.

### Minimal example

    #include <QObject>
    #include <QtPlugin>
    #include <QImage>
    #include "IPhotoflarePlugin.h"
    #include "AppContext.h"

    class MyFilterPlugin : public QObject, public IFilterPlugin
    {
        Q_OBJECT
        Q_PLUGIN_METADATA(IID "org.photoflare.FilterPlugin/1.0" FILE "myplugin.json")
        Q_INTERFACES(IPhotoflarePlugin IFilterPlugin)
    public:
        QString pluginId()    const override { return "com.example.myplugin"; }
        QString displayName() const override { return "My Filter"; }
        QString version()     const override { return "1.0.0"; }
        QString author()      const override { return "Your Name"; }
        QString menuPath()    const override { return "Filters/My Plugins"; }

        void initialise(AppContext* ctx) override { m_ctx = ctx; }

        QList<PluginParam> parameters() const override
        {
            return {
                { "strength", "Strength", PluginParam::Float, 0.5, 0.0, 1.0, {} },
            };
        }

        QImage apply(const QImage& input, const QVariantMap& params) const override
        {
            const float strength = params.value("strength", 0.5f).toFloat();
            // ... transform the image ...
            return input; // replace with your result
        }

    private:
        AppContext* m_ctx = nullptr;
    };

    #include "MyFilterPlugin.moc"

### Parameter types

The `PluginParam` struct describes each parameter. The host auto-generates a dialog from your `parameters()` list.

Type | Widget shown | Value in QVariantMap
--- | --- | ---
`PluginParam::Float` | Slider (uses minValue / maxValue) | `toFloat()`
`PluginParam::Int` | Slider (integer steps) | `toInt()`
`PluginParam::Bool` | Check box | `toBool()`

### menuPath

The `menuPath()` return value determines the submenu structure under Plugin Filters. The first segment is always replaced with "Plugin Filters" by the host, so `"Filters/Noise"` and `"Plugin Filters/Noise"` both produce the same result: Plugin Filters → Noise → My Filter. Return a single word (e.g. `"Filters"`) to place the action directly under Plugin Filters with no submenu.

### Threading in apply()

`apply()` is always called on a background `QThread` — never on the main thread. Do not access any Qt UI objects inside `apply()`. For additional parallelism you can use `QtConcurrent::blockingMap` to distribute row-level work across CPU cores. See `examples/plugins/grain/GrainFilterPlugin.cpp` for a worked example.

## Creating an exporter plugin

An exporter plugin adds a new file format to File → Save As.

### Minimal example

    #include <QObject>
    #include <QtPlugin>
    #include <QImage>
    #include <QImageWriter>
    #include "IPhotoflarePlugin.h"
    #include "AppContext.h"

    class MyExporterPlugin : public QObject, public IExporterPlugin
    {
        Q_OBJECT
        Q_PLUGIN_METADATA(IID "org.photoflare.ExporterPlugin/1.0" FILE "myexporter.json")
        Q_INTERFACES(IPhotoflarePlugin IExporterPlugin)
    public:
        QString     pluginId()    const override { return "com.example.myexporter"; }
        QString     displayName() const override { return "My Format Exporter"; }
        QString     version()     const override { return "1.0.0"; }
        QString     author()      const override { return "Your Name"; }
        QString     formatName()  const override { return "My Format"; }
        QStringList supportedExtensions() const override { return { "myf" }; }

        void initialise(AppContext* ctx) override { m_ctx = ctx; }

        bool exportImage(const QImage&      image,
                         const QString&     filePath,
                         const QVariantMap& settings,
                         QString&           errorMessage) const override
        {
            // write image to filePath; return false and set errorMessage on failure
            if (!image.save(filePath)) {
                errorMessage = "Failed to save image.";
                return false;
            }
            return true;
        }

    private:
        AppContext* m_ctx = nullptr;
    };

    #include "MyExporterPlugin.moc"

### Optional settings widget

Override `settingsWidget(QWidget* parent)` to return a widget shown in the export dialog. Use `QObject::setObjectName()` on child widgets — their object names become the keys in the `settings` map passed to `exportImage()`. See `examples/plugins/webp_exporter/WebpExporterPlugin.cpp` for a worked example.

## JSON metadata file

Every plugin needs a small JSON file referenced in `Q_PLUGIN_METADATA`. The content is embedded into the compiled library at build time by the Qt MOC — the file does not need to be distributed alongside the DLL.

    {
        "id":          "com.example.myplugin",
        "name":        "My Plugin",
        "version":     "1.0.0",
        "description": "A short description."
    }

### qmake project file

    QT       += core gui widgets
    CONFIG   += plugin c++17
    CONFIG   -= app_bundle
    TEMPLATE  = lib

    TARGET = myplugin

    INCLUDEPATH += /path/to/photoflare/src/plugins

    SOURCES += MyPlugin.cpp
    OTHER_FILES += myplugin.json

    DESTDIR = $$OUT_PWD/plugins

## AppContext API reference

`AppContext*` is passed to your plugin via `initialise()` and remains valid for the lifetime of the plugin.

Method | Description
--- | ---
`QImage* currentImage()` | Returns a pointer to the currently open image, or `nullptr` if no image is open. Stable for the duration of a single UI operation.
`void markCanvasDirty()` | Commits any direct modifications to `*currentImage()` back to the canvas and triggers a repaint. Call after modifying the image directly.
`void pushUndoState(const QString& label)` | Snapshots the current image into the undo stack. Not required when using `IFilterPlugin::apply()` — the host handles it automatically.
`void registerMenuAction(const QString& path, QAction* action)` | Adds an action to the menu bar. Use `"/"` as a separator, e.g. `"Tools/My Plugin"`.
`void registerDockPanel(const QString& title, QWidget* panel)` | Registers a widget as a dockable side panel.
`void showStatusMessage(const QString& msg, int ms)` | Shows a transient message in the status bar.
`QSettings& pluginSettings(const QString& pluginId)` | Returns a `QSettings` instance scoped to your plugin ID for persistent settings storage.
`QWidget* mainWindow()` | Returns the main window pointer. Use only to parent dialogs.
`QString appVersion()` | Returns the Photoflare version string, e.g. `"2.1.0"`.

Return to table of contents >>