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>
gtkmm sets both the C and the C++ global locale from the environment, so
an unguarded stream writes "0,35" on a German desktop and groups 1920 as
"1.920" — neither of which the reader, or any other JSON reader, can take
back. Both the writer and the number parser now work in the classic
locale: the writer imbues its stream, and the parser uses from_chars
rather than stod, JSON having a point for a decimal mark whatever the
user's mark is.
The seed is read from the digits it was written as rather than from the
double they parsed into: a 64-bit seed has more bits than a double has
mantissa, and a seed that comes back off by a few is a project that no
longer renders its own picture.
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>
A project is the whole setup as a small text file: the source paths, the
effect chain with its params, the canvas and the seed. Reading and writing
are driven by the effects' ParamSpecs rather than by whatever sits in a
stage's values, so a new effect is saveable without touching this code, a
key an effect does not declare is never written or read back, and an old
project keeps the defaults for params that did not exist when it was
saved. Paths under the project's own folder are written relative, so a
folder of images and their project can be moved whole.
src/core/project.cpp carries its own small JSON reader instead of a
dependency, and names the line when a hand-edited file is wrong.
The interface gets a header-bar menu: New, Open, Save, Save As, with
Ctrl+N/O/S. The title shows the project name. Opening a project replaces
the sources, discarding a decode still in flight rather than mixing it
into the new project.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>