The window used to hand the user a Scanlines stage before they had asked for
anything, which also meant the first thing on screen was "Add source images
to start." — an instruction about an effect nobody chose. It now opens on "No
effects yet" and waits: the chain is the subject of the interface, so the
first stage is the user's to pick.
The default effect was injected by the option parser, which would have put
Scanlines straight back on every launch. It moves to renderHeadless, where a
chain really is mandatory — a headless run must render something, while the
interface has no such need. `-o out.png` with no -e is unchanged.
The sources section follows the same question the render gate already asks,
chainNeedsSources(): only the first stage is ever handed the sources, so a
chain led by blur or text makes the whole section beside the point and it is
hidden rather than left there to be filled in vain. Today that shows it for
scanlines and tiles, but no effect is named in the GUI — a new one declaring
needsSources() gets this for free. rebuildChainRows() is the one funnel every
chain change goes through, so that is where the visibility is set.
New in the project menu now starts empty too, matching what the window opens
with.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The canvas was two spin buttons, so "an Instagram story" or "A4 at 300 dpi"
was a number the user had to work out. src/core/canvas.cpp now holds one
table of standard sizes both front-ends read: a web format is a fixed pixel
size, a print format a sheet in millimetres that becomes pixels at a dpi.
Adding a format is a line in canvasFormats() — the dropdown, --format and
--help all walk the table.
The pixels stay the truth everywhere; renderChain never hears about formats.
A format is the presentation of a canvas, carried alongside the pixels in
Options and Project and empty for a custom size, and where one is named it
decides the pixels — so a project saved as A4 reopens as A4 rather than as
whatever it happened to measure.
In the interface a format fills the width and height in and greys them out
rather than hiding them: the size a sheet works out to is worth seeing. A
print format also shows a DPI row, and Custom hands the spins back keeping
the last size, which makes starting from a format and then tweaking it the
ordinary path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Writes a line or several over its input in a chosen family, weight, size
and colour, optionally on a box filled with a colour or with a picture
from disk. Like the blur and the overlay it needs no sources of its own,
so it can lead a chain; given sources it fills the canvas from a randomly
chosen one first.
Two param types rather than effect-specific branches in the front-ends:
Text, free-form text the user types, and Color, a colour written
"#rrggbb". Both travel as strings, so the interface gets an entry and a
colour button, the command line gets a flag (a colour that does not parse
is refused there rather than drawn as black), and a project keeps them
across a round trip.
The glyphs come from Pango, which draws through Cairo without a display,
so the core stays headless — but they are the one thing here that is not
byte-identical across machines: a machine without the chosen family
renders the substitute its fontconfig picks. Everything the effect is
handed, the rng included, is used exactly as every other effect uses it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lays a single picture from disk over its input: fitted, filled, stretched,
left at its own size or tiled, centred or pinned to the top left corner,
nudged by an offset and mixed in by its opacity.
The file it needs is a new kind of param, Path, so the interface shows a
chooser and the CLI takes a file name, both without knowing an overlay
exists. Projects write it relative to their own folder, as they do a
source, and Image::load can now hand back the alpha channel instead of
flattening it onto white, so a cut-out PNG keeps its cut-out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A canvas side was cast to int with no range check, so a number that did
not fit came back as one that did: `-s 4294968296x600` rendered a
perfectly plausible 1000x600, and a hand-edited project file could ask
for a width the reader turned into something else (or, for a double out
of range, into undefined behaviour).
Both front-ends now check the side before the cast, against one shared
bound in chain.hpp — the 1..20000 the GUI spin buttons already offered,
so the help text, the CLI error, the project reader and the interface
all move together.
The tests gain a `canvas size` section covering the round trip at both
ends of the range and the refusals on either side of it, through the
project reader and through `-s`; the test binary picks up the CLI parser
to do it, which stays GUI-free.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
renderChain runs a list of stages in order: stage 0 works on the loaded
sources, every later one on the output of the stage before it, so blur
after scanlines softens the scanlines rather than the photographs. The
effects themselves are untouched — none of them knows whether its input
came from disk. All stages share one Rng and run in sequence, so a seed
still reproduces the cascade byte for byte.
The sidebar grows an "Effect chain" list: add a stage from a menu built
from the registry, reorder it, remove it, and edit the selected stage's
params underneath. Each stage keeps its own values. The CLI takes the
same chain from repeated -e, with every flag going to the stage whose
effect declares it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Port of the Python prototype to C++20 with a CMake build:
- effector_core: GUI-free static library (effects, image IO via gdk-pixbuf,
declarative param specs, registry, portable seeded RNG, thread helper)
- scanlines effect: plans strips from the seeded RNG, then copies pixels in
parallel with memcpy and doubling horizontal tiling
- gtkmm 4 interface: native file dialogs, widgets generated from the param
specs, coalesced background renders, preview as a GDK memory texture
- CLI: flags generated from the effect registry, headless rendering with no
display required
- ctest suite covering the RNG, registry, effect geometry and image round trip
The Python prototype is kept under python-reference/.