Skip to content
Bartłomiej Mucha edited this page Sep 6, 2026 · 2 revisions

libplatemaker — specification

The library's domain model, pipeline and contracts. This is the developer documentation: the end-user manual for the Platemaker application lives in the Platemaker-qt wiki.

Split out of a single 1225-line document once it stopped being readable end to end. The four foundations are the intended reading order; everything else is reference you consult.

Applies to: libplatemaker 0.6.0 (unreleased) and the platemaker CLI.

The four foundations

Page What it settles
1 Building a workspace What a workspace holds, how it is persisted and repaired, and where its files live.
2 Building a project What one chapter is, what state it carries between renders, and what makes it stale.
3 Rendering a project The pipeline from input page to output slice, and the contract with whoever calls it.
4 The preview workflow How a consumer shows a chapter — and authors edits against it — before any render exists.

Reference

Page What it is
Canvas profile matching Which profile a page renders with, and how a profile edit is noticed. The most intricate rule in the library.
Optional processing steps Colour correction and text/bubble overlays — the two seams, and why they sit where they do.
Components Class by class: what each one does and what it does not.
CLI reference Command grammar, exit codes, JSON output.
Platform & distribution Targets, toolchains, dependencies, packaging.
Roadmap & history Planned work, the staged build order, and what the Clip2l prototype taught.
Diagrams Nine draw.io diagrams and the conventions they follow.

Library components


Project Overview

Platemaker is a cross-platform desktop application (with CLI) for comic artists. It handles both pre-processing (generating canvas templates with margin guides) and post-processing (scaling and slicing finished artwork for distribution platforms).

The name draws from the printing trade: a platemaker crafts the printing plates from which pages are born. Platemaker does the same — it shapes canvases before drawing and prepares finished pages for publication.

Primary use case (v1): Processing comic pages for Webtoon-style vertical scroll format. The application treats an ordered set of source images as a single virtual strip, scales them to a target width, and slices the strip into fixed-height output panels.

Future scope (not in current requirements): Template generation and margin-aware processing for traditional comic formats — American (6.625" × 10.25"), European (various), manga (B4/A4). The architecture is designed to support multiple CanvasProfile presets without code changes.

The project is open-source (LGPL 3.0). Monetization via donations may be added in the future — LGPL 3.0 permits this as long as libplatemaker remains dynamically linked and unmodified by the end user.


Core Use Case — The Virtual Strip Model

The artist works on one or more canvases in Procreate and exports them as PNG files. These files arrive at Platemaker pre-sliced at arbitrary heights (e.g. 2560 px, 2000 px, 1800 px) — they are already "pieces of a strip" from the artist's perspective.

Platemaker treats the entire ordered file list as a single continuous virtual strip. Slice boundaries in the output are determined solely by the target slice height and are completely independent of source file boundaries.

Pipeline overview:

  1. Load an ordered list of image files (directory or explicit list).
  2. Allow reordering of pages (drag & drop or manual sort).
  3. Scale each file to the target width (Webtoon standard: 800 px), preserving aspect ratio.
  4. Concatenate scaled images conceptually into one virtual strip.
  5. Slice the strip at every N pixels (Webtoon standard: 1280 px).
  6. Export output slices as sequentially numbered PNG files.
  7. One tail slice at the end (shorter than sliceHeight) — handled per LastSlicePolicy.

Goal: minimise the number of output files. Webtoon does not respect upload order reliably, so fewer files = less manual reordering after upload.

Note: "Sequence mode" in the Clip2l prototype (--sequence / -s flag) corresponds to this pipeline. In Platemaker this is the only and default mode — there is no per-file-independent processing mode.

Canvas math example:

Input files (after scaling to 800 px wide):
  page_01.png  →  800 × 5120 px
  page_02.png  →  800 × 4000 px
  page_03.png  →  800 × 3600 px

Virtual strip total height:  12720 px
Slices at 1280 px:           9 full slices (11520 px) + tail (1200 px)
Output files:                output_001.png … output_010.png

output_003.png may contain the bottom N px of page_01 and top (1280-N) px of page_02.
Slice boundaries never need to align with source file boundaries.

Architecture

Layer Diagram

┌──────────────────────────────────────────────┐
│        Qt 6 GUI (separate repository)        │  Tabs/panels, preview, drag&drop
├──────────────────────────────────────────────┤
│        CLI binary  (`platemaker`)            │  Standalone tool; future web backend
├──────────────────────────────────────────────┤
│      Core Library  (`libplatemaker`)         │  Pure C++, zero Qt dependency
│  Scaler │ Strip │ Cropper │ TplGen  │ Match  │
├──────────────────────────────────────────────┤
│       Infrastructure / IO                    │  WorkspaceSerializer, ImageIO, Cache
├──────────────────────────────────────────────┤
│   libvips  │  nlohmann/json  │  std::        │  Third-party dependencies
└──────────────────────────────────────────────┘

Key Architectural Decisions

Decision Choice Rationale
Language C++ 17/20 Developer is expert; best performance for image processing
Image processing libvips Multi-threaded, lazy/streaming pipeline, Lanczos3 resampling, low RAM
JSON serialisation nlohmann/json Header-only, MIT licence, clean API
Build system CMake + CMakePresets.json Standard for C++ cross-platform; preset per platform
Package manager vcpkg (manifest mode) vcpkg.json in repo; reproducible on every machine
File integrity SHA-256 Per-input-file hashing for incremental processing
Library distribution LGPL 3.0 shared library End users can relink; GUI repo links .dll/.so via find_package

Critical Design Rules

libplatemaker must have zero Qt dependencies.

This keeps the Core testable without a display, usable from CLI without Qt, and portable to future backends (web server, Python bindings, etc.). Qt types (QImage, QPixmap, etc.) must only appear in the GUI layer.

The strip must never be fully materialised in RAM.

ScaledStrip processes files one at a time and keeps in memory only the minimum number of scaled images needed to complete the current output slice. For example: if a slice boundary falls mid-file, two files may be in memory simultaneously (tail of file N + head of file N+1). As soon as a file's contribution to all remaining slices is exhausted, it is released. This is critical for large projects where the total input may be several gigabytes — the file list and thumbnails are metadata only; raw pixel data is loaded on demand.

Every path in the API is UTF-8, and the conversion to std::filesystem is explicit.

Workspace JSON stores UTF-8 and the GUI hands over UTF-8, but what a narrow std::string means is not agreed upon across the standard library: on Windows std::ifstream passes the bytes to fopen(), which reads them in the ANSI code page, while libstdc++'s std::filesystem::path reads the same bytes as UTF-8 (MSVC reads them as ANSI in both places). A program mixing the two conventions is not portable and not even self-consistent.

Nothing in the library may construct a std::filesystem::path from a narrow string, or call path::string(). Use utf8ToPath() / pathToUtf8() from platemaker/infrastructure/file/path_utf8.hpp, which go through std::u8string and are therefore defined by C++20 rather than by the toolchain or the machine's code page. path::string() is additionally unsafe on MSVC: it throws on a character the ANSI page cannot represent.

This was not hypothetical. Under a path such as G:/Mój dysk/… — the folder name Google Drive creates on a Polish system — hashing an input silently returned nothing, so inputs stayed Pending after a successful render and every render redid all the work; and a workspace saved through an fs::path could never be reopened through a std::string. See tests/lib-unit-tests/test_path_encoding.cpp.

CLI as First-Class Citizen

CLI is not a wrapper added after the fact — it is a primary interface and the intended backend for a future web application. A web server can call platemaker process --json as a subprocess and parse its stdout. The layer hierarchy is:

libplatemaker  (shared library, pure C++)
  ├── platemaker      (CLI binary — links libplatemaker)
  └── platemaker-gui  (Qt GUI binary — separate repo, links libplatemaker .dll/.so)

Data Models

id / uid are local identifiers, not RFC 4122 UUIDs — random <prefix>-<hex> strings minted by Infrastructure::id_generator (profiles use id; projects/inputs/outputs use uid). The : uid type annotation below means exactly that.


See also: Home · Workspace · Project · Render · Preview · Canvas-Profiles · Processing-Steps · Components · CLI · Platform · Roadmap · Diagrams

Clone this wiki locally