Offer standard web and print canvas formats

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>
This commit is contained in:
Si
2026-09-18 22:34:17 +02:00
co-authored by Claude Opus 5
parent f4d8457828
commit d48a81c665
12 changed files with 494 additions and 24 deletions
+31
View File
@@ -5,6 +5,7 @@
#include <cstring>
#include <sstream>
#include "core/canvas.hpp"
#include "core/chain.hpp"
#include "core/effect.hpp"
#include "core/registry.hpp"
@@ -74,6 +75,20 @@ Options parseOptions(int argc, char** argv) {
"each side between " + std::to_string(kMinCanvasSide) + " and " +
std::to_string(kMaxCanvasSide));
}
} else if (arg == "--format") {
const std::string value = requireValue(argc, argv, i, arg);
if (canvasFormatByKey(value) == nullptr) {
throw EffectError("unknown canvas format '" + value + "', see --help");
}
options.format = value;
} else if (arg == "--dpi") {
const std::string value = requireValue(argc, argv, i, arg);
long dpi = 0;
if (!parseLong(value, dpi) || dpi < kMinDpi || dpi > kMaxDpi) {
throw EffectError("invalid dpi '" + value + "', expected " +
std::to_string(kMinDpi) + "-" + std::to_string(kMaxDpi));
}
options.dpi = static_cast<int>(dpi);
} else if (arg == "--seed") {
const std::string value = requireValue(argc, argv, i, arg);
long seed = 0;
@@ -140,6 +155,11 @@ Options parseOptions(int argc, char** argv) {
options.images.push_back(arg);
}
}
// Applied once everything is parsed, so --dpi may come before --format and
// so a format always wins over an -s given alongside it.
if (const auto* format = canvasFormatByKey(options.format)) {
canvasFormatSize(*format, options.dpi, options.width, options.height);
}
if (options.effects.empty()) options.effects.push_back(defaultEffectName());
return options;
}
@@ -153,10 +173,21 @@ std::string usage(const char* programName) {
<< " -o, --output FILE render headlessly to this file and exit\n"
<< " -s, --size WxH canvas size, each side " << kMinCanvasSide << "-"
<< kMaxCanvasSide << " (default: 1920x1080)\n"
<< " --format NAME canvas format, overrides --size (see below)\n"
<< " --dpi N resolution of a print format, " << kMinDpi << "-" << kMaxDpi
<< " (default: " << kDefaultDpi << ")\n"
<< " --seed N random seed (default: random)\n"
<< " --list-effects list available effects and exit\n"
<< " -h, --help show this help and exit\n";
out << "\nCanvas formats (--format):\n";
for (const auto& format : canvasFormats()) {
out << " " << format.key;
if (format.key.size() < 20) out << std::string(20 - format.key.size(), ' ');
out << " " << format.label << ", " << canvasFormatDetail(format)
<< (format.print ? " at --dpi" : "") << "\n";
}
for (const auto& effect : effects()) {
out << "\n" << effect->title() << " options:\n";
for (const auto& spec : effect->params()) {
+3
View File
@@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include "core/canvas.hpp"
#include "core/chain.hpp"
#include "core/params.hpp"
@@ -16,6 +17,8 @@ struct Options {
std::string output; // empty -> launch the interface
int width = 1920;
int height = 1080;
std::string format; // a named canvas format, empty for plain pixels
int dpi = kDefaultDpi; // only a print format is rendered at a resolution
std::optional<std::uint64_t> seed;
ParamValues params; // only the keys the user actually passed
bool listEffects = false;