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, andPluginParam - AppContext.h — defines the
AppContextAPI surface passed toinitialise()
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". |