Skip to content
/ photo Public

A Qt-based desktop photo editor featuring a two-pane workflow and a powerful, asynchronous plugin architecture built on nexuslua.

License

Notifications You must be signed in to change notification settings

acrion/photo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

acrionphoto is a Qt desktop application that pairs a clean two-pane workflow with a powerful, asynchronous plugin system built on nexuslua. The app itself stays small and focused; the heavy lifting (I/O, processing, interactive tools, custom dialogs) lives in plugins. This architecture keeps the UI responsive while you watch results stream in.

After a long period of private development, acrionphoto is now released to the community. The plugin API is open - anyone can build plugins that integrate as if they were shipped with the app.

Development Status

  • The core app runs stably.
  • Installers for macOS, Windows, and Linux are not yet available.
  • Plugin registry integration (with acrion/nexuslua-plugins) is planned but not yet implemented.
  • The macOS build is currently incomplete due to recent refactoring.
  • The image-tools plugin build currently relies on pacman; see the Build section for platform notes.

The original motivation was to create a multi-platform successor to my older commercial astrophotography tool, Straton. Early work on acrionphoto dates to ~2015, and the project gradually evolved into a general-purpose tool, especially after the plugin concept was introduced. An astrophotography plugin for acrionphoto already exists and will be released soon (likely as open source).

nexuslua was designed to solve a common problem in IDEs: plugins that block the UI or behave unpredictably. It uses a concurrent messaging system to ensure responsiveness. The Lua-free parts of the messaging core live in the C++ library Cbeam.

acrionphoto's concept is similar to ImageJ but features a simpler, Lua-based plugin interface and built-in asynchrony via nexuslua. Plugins can also extend the UI in several ways, including with fully custom dialogs.


Key UI Concepts

Recent Files & Plugin-Configurable UI

Recent files, two-pane layout, plugin surface

  • Two synchronized panes are at the core of the workflow: Reference (left) and Working (right). This fixed layout makes before/after comparisons, A/B testing, and iterative workflows feel natural.
  • Plugin-configurable areas:
    • The top button row (in the screenshot: Open, Save right, Color Pick, Set Pixel) is populated by plugins.
    • The left navigation can host a plugin entry that exposes its main functions (see acrion image tools and acrion imago below).
  • Recent Files lists thumbnails for both panes, providing quick visual context.

Plugin Manager (Install, Uninstall, License)

Plugin Manager: versions, license buttons

  • The Plugin Manager lists installed plugins with their Installed Version, Online Version, and License status.
  • Depending on plugin metadata (from nexuslua_plugin.toml), license-related buttons appear (e.g., acrion imago shows actions to install or view a license file).
  • Install/Uninstall are fully implemented. Next steps include:
    • Integration with a public registry at acrion/nexuslua-plugins.
    • The manager shows only a subset of nexuslua plugins, filtering for those whose main.lua declares messages compatible with acrionphoto.

Note: acrionphoto is tightly integrated with the nexuslua ecosystem. The Plugin Manager filters and displays plugins that adhere to acrionphoto's message and parameter conventions (details below).


Example Plugin: acrion image tools

Example plugin panel with operations

This plugin serves as both a demo and an essential component: it provides image I/O and fundamental operations. While acrionphoto can run without it, you won't be able to open or save images.

Highlights:

  • Operations can be composed. The brightened Working image above was produced by applying Invert Image followed by Difference.
  • Some functions are Lua-only (e.g., right-left (wrap)) to demonstrate pure-Lua processing, while others call native libraries via import(...).
  • The plugin exposes interactive utilities (mouse test, pixel pick/set) to illustrate how to build coordinate-driven tools.

Custom Dialogs from Lua: acrion imago

Custom dialog generated from main.lua

The (unreleased) acrion imago plugin demonstrates fully custom dialogs defined declaratively in Lua. You don't write Qt UI code; acrionphoto reads your parameter descriptors and renders the dialog automatically:

  • Numbers become sliders with min/max ranges.
  • Enums become dropdowns with inline help text from values.
  • Booleans render as checkboxes or toggles.
  • Mark parameters internal=true to hide them from the UI while keeping them available to the algorithm.
  • Set per-message icons, display names, and descriptions.

The Lua snippet below defines the "Visualize distortion" dialog shown in the screenshot:

local ImageParameters <const> =
{
  imageBuffer = { type = "void*" },
  width       = { type = "long long" },
  height      = { type = "long long" },
  channels    = { type = "long long" },
  depth       = { type = "long long" },
}

local CommonParameters <const> = mergetables(ImageParameters,
{
    InterpolationModel               = { type = "enum", default = "Center And Borders", values = {["Center And Borders"]="", Continuous=""}},
    ConsiderEdges                    = { type = "boolean", default = true, values = {[false]="only consider corners, not edges (affects interpolation model 'Center And Borders')", [true]="consider the edges additional to the corners (affects interpolation model 'Center And Borders')"}},
    ConsiderCenter                   = { type = "boolean", default = false, values = {[false]="affects interpolation model 'Center And Borders'", [true]="affects interpolation model 'Center And Borders'"}},
    ConsiderTopLeftCorner            = { type = "boolean", default = true, values = {[false]="affects interpolation model 'Center And Borders'", [true]="affects interpolation model 'Center And Borders'"}},
    ConsiderTopRightCorner           = { type = "boolean", default = true, values = {[false]="affects interpolation model 'Center And Borders'", [true]="affects interpolation model 'Center And Borders'"}},
    ConsiderBottomLeftCorner         = { type = "boolean", default = true, values = {[false]="affects interpolation model 'Center And Borders'", [true]="affects interpolation model 'Center And Borders'"}},
    ConsiderBottomRightCorner        = { type = "boolean", default = true, values = {[false]="affects interpolation model 'Center And Borders'", [true]="affects interpolation model 'Center And Borders'"}}
})

addmessage("CallVisualize", {
  displayname="Visualize distortion",
  description="Analyze distortions in the left image and visualize them",
  icon="Visualize.svg",
  parameters=mergetables(CommonParameters, {
    referenceImageBuffer = { type = "void*" },
    StepSize             = { type = "long long", default = 20, minimum = 0, maximum = 20 }
  })
})

referenceImageBuffer (the left image data) is automatically supplied by acrionphoto. For the right image, use imageBuffer.

Commercial plugins can report their license state via a simple Lua function:

function IsLicensed()
  import("acrionimago", "CheckLicense", "bool()")
  import("acrionimago", "GetLicensee", "const char*()")
  return CheckLicense(), GetLicensee()
end

The same metadata drives both the left-hand tool panel and the runtime dialogs, keeping the UI and execution declarative and in sync.

acrion imago may be published as open source later or kept closed source. The Lua metadata is shared here to document the dialog-building mechanism.


View Pane: Compare, Overlay, Diff, Gamma

View pane controls: overlay, diff, gamma, zoom

  • Overlay Left (press and hold): Toggles the Reference image overlay on the Working pane for quick "blink" comparisons.
  • Overlay Diff (press and hold): Shows the pixel-wise difference between the two panes.
  • Swap Images: Swaps the reference and working images.
  • Zoom: Full (fit to view) and 1:1 (pixel-perfect).
  • Gamma: Brightens the view only without altering pixel data - invaluable for inspecting low-signal images, such as in astrophotography.

Build Interactive Apps Inside the Image Panes ⭐️

This is not a gimmick - it unlocks a new paradigm for building visual tools.

acrionphoto can forward mouse events from the image panes to plugins. In response, plugins can draw back asynchronously into the Working image. This simple loop is all you need to implement a complete interactive application inside acrionphoto's panes:

  • Create brush tools, spot repair tools, selection markers, measurement aids, ROI widgets, etc.
  • Render complex UIs directly into the image. A plugin could even use a third-party immediate-mode GUI (e.g., cvui) to draw controls.
  • The host application remains responsive because all plugin communication runs through nexuslua's asynchronous message queues.

How it works:

  • If your message's parameter list includes both x and y, acrionphoto treats it as coordinate-driven.
  • On mouse events, the host sends a message with { imageBuffer, width, height, ..., referenceImageBuffer, x, y, ... }.
  • Your plugin processes the event, updates the image buffer(s), and returns a result table. acrionphoto then refreshes the pane.
  • You can chain this with inter-plugin calls for modular, powerful toolsets.

A demo plugin showcasing this capability is planned. Contributions are welcome!


Architecture & Plugin Model

acrionphoto is a message-driven shell for image manipulation. All meaningful work happens in plugins hosted by nexuslua. A plugin is typically a folder containing a main.lua file that:

  • Declares messages via addmessage(name, { displayname, description, icon, parameters = {...} }).
  • Describes parameters with rich metadata:
    • Types: string, boolean, double, long long, enum, void* (for image buffers), and special I/O types loadpath and savepath.
    • Metadata: default, min/max ranges, and values (for enum documentation).
    • internal=true hides a parameter from the UI while keeping it available to the algorithm.
  • Calls native code with import("libname", "Function", "signature").
  • Sends messages to other plugins with send("Plugin Name", "MessageName", parameters).
  • Returns results as a Lua table (often including a new imageBuffer, brightness bounds, and status/error messages).

Under the hood:

  • nexuslua::agents hosts each plugin in its own threaded agent (Lua or C++).
  • nexuslua::AgentMessage describes messages, their parameters, and icons.
  • nexuslua::LuaTable (a typed, nested map) carries parameters and results.
  • [Cbeam] provides logging, cross-library memory management, and serialization.

How acrionphoto Discovers Plugin Capabilities

Since nexuslua plugins are general-purpose, acrionphoto relies on conventions to identify and integrate relevant plugins. For a plugin to be recognized, it must adhere to specific message and parameter patterns.

The core convention revolves around passing image data. Plugins should define and use a standard set of parameters for images:

local ImageParameters <const> =
{
    imageBuffer = { type = "void*"},
    width       = { type = "long long"},
    height      = { type = "long long"},
    channels    = { type = "long long"},
    depth       = { type = "long long"},
}
  • I/O Messages (Open/Save) A message is treated as an Open action if it declares a parameter named path of type loadpath. It's treated as a Save action if the path parameter has type savepath. An optional filter parameter (string) can define the file dialog filter (e.g., "*.fits;*.tif;*.png").

    Internally (src/PluginManager.cpp):

    • areIOparams(...) detects the path (loadpath/savepath) and optional filter.
    • IsInputMessage(...) and IsOutputMessage(...) classify messages based on this.
    • RunIoPluginMessage(...) injects the user-selected path and sends the message.
  • Image Plumbing Before sending a message, acrionphoto injects image context using PluginManager::AddImageParameters(left, right, parameters, coordinate):

    • The Working image's data (imageBuffer, width, height, etc.) becomes the default parameter table.
    • The Reference image buffer is exposed as referenceImageBuffer.
    • Mouse coordinates are added as x and y for interactive tools.
    • Your message's parameters are merged last (with collision checks).
  • Requesting Ad-Hoc Input If a message includes the requestUserInput parameter, the UI can broadcast user input back to it. This is ideal for interactive, multi-step, or long-running operations.

Inter-Plugin Communication

Plugins can call each other, allowing you to compose functionality and keep code modular. For example, your main.lua can define a message that internally calls another plugin:

addmessage("CallTestMessageInvert", {
    displayname="Test message",
    description="Sends an 'invert' message to the image tools plugin",
    parameters=mergetables(ImageParameters,
    {
        minBrightness  = { type = "double"},
        maxBrightness  = { type = "double"}
    })
})

This code adds a button to your plugin's UI that, when clicked, uses the acrion image tools plugin to invert the current image.

Asynchronous Processing Under the Hood

  • Each plugin runs in a separate agent thread.
  • The UI posts messages and returns immediately, preventing any blocking.
  • Image buffers use Cbeam's stable reference buffer:
    • Cross-library reference counting keeps pointers valid, even across different shared libraries (.dll, .so, .dylib).
    • A scoped delay_deallocation mechanism allows raw pointers to be used safely during Lua ↔ C++ calls.
  • Results stream back as Lua tables. The Working pane updates automatically, so you can watch processing happen in real time while the UI remains fully interactive.

Image Model & Bit Depths

acrionphoto itself is agnostic to pixel formats. All image data is managed by the shared library acrion image:

  • A single container holds the imageBuffer, width, height, channels, depth, and view hints (minBrightness, maxBrightness, gamma).
  • Supported per-channel bit depths include 8/16/32/64-bit unsigned integers and 64-bit floating point.
  • Plugins read from and write to the same container structure. This is how acrion image tools can provide I/O for FITS, TIFF, PNG, etc., without the core application needing to know about file formats.

Build

Please refer to https://github.com/acrion/nexuslua-build to build acrionphoto and its dependencies.

  • The image-tools plugin currently requires pacman for its dependencies:
    • On Linux, an Arch-based distro is the most straightforward path.
    • On Windows, the build uses MSYS2/ucrt64, where pacman is available.
    • The macOS build of image-tools is being updated to align with the latest refactoring for this public release.

Standalone build (without nexuslua-build)

You can build acrionphoto as a single repository:

# from repo root
cmake -B build src/
cmake --build build

Important: acrionphoto runs without plugins, but has no image I/O unless an image I/O plugin (e.g. acrion image tools) is installed. That means the UI starts and you can open About/Licences, but you cannot open/save images until a compatible plugin is present.

How to get image I/O today

  • Option 1 (easiest): build via the umbrella repo, which also builds the acrion image tools plugin:

    • https://github.com/acrion/nexuslua-build (--profile acrionphoto)

    • Afterwards, run the development install script once to link the plugin into your local nexuslua plugin folder:

      # from the plugin repo root
      ./install-development-plugin.sh
      # Example output:
      # Plugin name: 'acrion image tools'
      # Successfully created symbolic link: /home/<you>/.local/share/nexuslua/plugins/acrion image tools
  • Option 2 (soon): install plugins directly from the public registry (acrion/nexuslua-plugins). The manager already supports installation/update/uninstall; the registry hookup will deliver the metadata URLs.


Installation

There are no installers yet. Although the current build path for dependencies uses pacman, future platform-specific installers will not require it.


Run

  1. Launch the built application (it will include the acrion image tools plugin if you build via nexuslua-build).
  2. Open an image.
  3. Explore the tools. For example, in the acrion image tools panel, click Invert Image, then Difference. Afterward, switch to the View Pane and click & hold "Overlay Left" or "Overlay Diff" to visualize the modifications.

Write Your First Plugin

A minimal main.lua is all you need to get started.

-- 1) A simple "Open" message (identified as "Open" due to 'loadpath')
addmessage("OpenRight", {
  displayname = "Open (right)",
  description = "Load an image into the Working pane",
  icon        = "FileOpen.svg",
  parameters  = {
    path   = { type = "loadpath" }, -- This marks the message as an "Open" action
    filter = { type = "string", default = "*.fits;*.tif;*.png" }
  }
})

-- 2) A coordinate-driven operation (fires on clicks in the Working pane)
addmessage("SpotOp", {
  displayname = "Spot operation",
  description = "Process a small area around a clicked coordinate",
  icon        = "Start.svg",
  parameters  = {
    -- These are filled automatically by acrionphoto:
    imageBuffer          = { type = "void*" },
    width                = { type = "long long" },
    height               = { type = "long long" },
    channels             = { type = "long long" },
    depth                = { type = "long long" },
    referenceImageBuffer = { type = "void*" },

    -- These make the message coordinate-driven:
    x = { type = "long long" },
    y = { type = "long long" }
  }
})

Guidelines

  • Use void* for image buffers and always include width, height, channels, and depth.
  • Include referenceImageBuffer if your tool needs to read from the left image.
  • Add x and y parameters to receive mouse coordinates from the image panes.
  • Use import(...) to call native C/C++ code from shared libraries. A common signature is "table(table)", which takes a parameter table and returns a result table.
  • To expose I/O functionality, name a parameter path and set its type to loadpath (Open) or savepath (Save). You can also provide a filter.
  • Return a Lua table with a new imageBuffer and (optionally) minBrightness/maxBrightness to update the Working pane.

Project Layout

  • src/ - The main Qt application (MainWindow, panels, dialogs, dark theme).
    • MainWindow.*, ImageViewer*.*, ControlsPanel/*, Dialogs/* - Core UI components.
    • PluginManager.* - Handles plugin discovery, installation, and message dispatching (I/O detection, coordinate routing, image parameter injection).
    • Runtime/Resources/Help/{en,de,fr}/ - Built-in help files.
  • screenshots/ - Images embedded in this README.
  • LICENSE - Dual-license terms (AGPL/Commercial).

Community

acrionphoto, like nexuslua and Cbeam, is intended to be a community-driven project. Join the conversation on our Discord server, share ideas, or get help.

💬 Join the acrionphoto Discord Server

Here are some ways you can contribute:

  • Share ideas and open issues.
  • Contribute plugins (for processing, I/O, educational purposes, or interactive tools).
  • Help shape the Plugin Marketplace and its metadata standards.
  • Improve the documentation and add examples.

If you have built interesting Lua pipelines or C++/CUDA/OpenCL filters, we would love to see them wrapped as plugins!


License

acrionphoto is dual-licensed to support both open-source development and commercial use.

  1. AGPL v3 or later: For use in open-source projects that are compatible with the AGPL.
  2. Commercial License: For integration into proprietary applications or for cases where AGPLv3 terms cannot be met.

I would like to emphasize that offering a dual license does not restrict users of the normal open-source license (including commercial users). The dual licensing model is designed to support both open-source collaboration and commercial integration needs.