Repository navigation
Releases: ShadobaDev/PlateMaker
Release list
0.5.1
Packaging only — no source, API, or behaviour change; code built against 0.5.0 is unaffected. Windows now
also ships an MSVC build of the dev and CLI packages alongside the existing MinGW ones. Prompted by a
Microsoft Defender ML false positive (Trojan:Win32/Wacatac.B!ml) that flags MinGW-compiled, unsigned
binaries (including libplatemaker.dll itself); an MSVC build of the same sources is not flagged.
Changed
- Windows releases now publish both MinGW and MSVC packages. The release CI builds Windows twice
(dist-mingw-release+dist-msvc-release), so a consumer built with either toolchain can pull a
matching, ABI-compatible dev package (…-windows-msvc-…vs…-windows-mingw-…— the package config's
guard rejects a cross-toolchain link, and only checks MSVC-vs-MinGW, so the toolchains must stay in step).
The MSVC job is pinned to the VS 2022 runner because that is the compiler Qt 6.11 supports, so the
shipped MSVC lib links into the Qt GUI. Adist-msvc-debugpreset was added so MSVC has the same
debug/release dist coverage as MinGW.
Full Changelog: 0.5.0...0.5.1
0.5.0
Defines a render output contract (SPECIFICATION §7.0) that keeps a consumer from racing the
pipeline's writes — the root cause of the GUI's intermittent unable to open for write render failures —
and folds in the diagnostic logger and the multi-source slice fix from the interim commits. Breaking only
because ProcessingPipeline::run() gained a trailing parameter: source-compatible via its default, but
the mangled symbol changes, so the GUI pins the version in lockstep.
Changed
- Build requirement: libvips ≥ 8.15 on Linux.
ImageIO::save()now usesVIPS_FOREIGN_KEEP_ICC
(added in libvips 8.15) to drop source EXIF/XMP/IPTC while keeping the ICC profile. Windows is unaffected
(the bundled web-zip is pinned to 8.18); on Linux the build consumes the system libvips, so the CMake
pkg_check_modulesnow requiresvips-cpp>=8.15and fails at configure with a clear version error on
anything older. Distros that ship <8.15 (e.g. Ubuntu 22.04 → 8.12) need a newer libvips or distro
(Ubuntu 24.04+ ships 8.15, matching CI). ProcessingPipeline::run()gains an optional trailingthumbnailCacheDir. When non-empty, the
pipeline warms aThumbnailCacherooted there from each slice's in-RAM pixels — before
onSliceSavedfires — so a consumer'sgetOrGenerate(outputPath)is a cache hit and never re-reads a
freshly-written output during a run. Empty (default) → no thumbnails; the CLI and headless callers pay
nothing. Defaulted, so existing source compiles unchanged.
Added
- Component-gated diagnostic logger (
Infrastructure::Log). A runtimeuint64bitmask, one bit per
component (ProcessingPipeline / Scaler / ScaledStrip / …), all-off by default, with a swappable sink
(stderr by default) and no severity levels;PLATEMAKER_LOG(component, msg)builds the message only
when the component is enabled (setEnabledComponents/enable/disable/isEnabled). The logger
is wired into Scaler, ScaledStrip and ProcessingPipeline (each logs its geometry). The CLI gains a
--trace=0xMASKshadow argument to flip components on. - Atomic output publish +
OutputLockedError.ImageIO::save()now encodes to a temp sibling and
renames it over the destination (whole-or-nothing; a reader never sees a partial slice). If another
process holds the destination, it throwsOutputLockedError, which the pipeline surfaces as the new
ProcessingErrorCode::OutputLocked— the library does not poll/retry; the consumer owns that
policy. Fixes intermittentunable to open for write … Invalid argumentrender aborts on Windows. ThumbnailCache::generate(sourceFilePath, const Core::PixelBuffer&)— an in-RAM overload that
shrinks a caller-supplied image (no file read) and files it under the source path's digest, sharing the
atomic shrink+write core with the file-readinggenerate. Lets the pipeline pre-warm the cache from
pixels it already holds. Thumbnail writes are now atomic too.
Fixed
- Multi-source slices no longer get a black band.
ScaledStrip::buildSlice()foldsvips_join
(vertical) over the same-width parts instead ofvips_arrayjoin, which sized every grid cell to the
tallest part and black-filled the rest — so any slice combining sources of unequal contributed height
(short camera photos, a page boundary falling mid-slice) came out padded toN × maxHeight. Asserts
built.height == the requested slice heightas a post-condition, so it can never silently regress. - Mixed RGB/RGBA (and grayscale/CMYK) inputs no longer abort a render. A folder combining 3-band
(RGB) and 4-band (RGBA) sources — e.g. camera photos next to a stray screenshot — killed the whole job
the first time a slice straddled the boundary, becausevips_joinrequires equal band counts.
ScaledStrip::sliceAll()now normalises the strip up front by promoting to the widest band layout
(any non-RGB colourspace → sRGB, then a fully-opaque alpha added where missing) — promote-only, so the
user's pixels are never composited onto a background. A uniform strip is untouched. Alpha is dropped
only later, at save, and only for JPEG, which cannot carry it (flattened over white); PNG/WebP keep
the alpha end-to-end. - EXIF
Orientationis now honoured on load (both pipelines). Camera JPEGs store landscape pixels
plus an Orientation tag; the pipeline ignored it, so a portrait photo rendered on its side,180°
shots rendered upside‑down, and outputs inherited the source tag so viewers re‑rotated them.
Scaler::scale(filePath)andImageIO::load()nowvips_autoroton load (idempotent for the
untagged/1case — a no‑op for Procreate exports),headerGeometry()reports the display dimensions
(transposed for Orientation 5–8) so canvas‑profile matching agrees with the rendered pixels, and
ImageIO::save()drops source EXIF/XMP/IPTC (keeping the ICC colour profile) so a rendered slice
carries no stray camera metadata or orientation. (headerDim()was renamedheaderGeometry().) - Input thumbnail previews now follow EXIF orientation too — even for photos already in the cache.
ThumbnailCache::generatewas switched to auto-rotate (droppedno_rotate) alongside the load fix
above, but the on-disk cache is keyed on the source path and invalidated by mtime only: an
old-dated camera JPEG always has an older mtime than its cached thumbnail, so a preview written by the
earlier (sideways) code was judged fresh forever and re-served after the fix. The thumbnail filename now
carries a cache-generation version token (<digest>.vN.png,kThumbnailCacheVersion); bumping it on
any generation-logic change orphans the stale thumbnails so they regenerate. The mtime check is kept and
still catches in-place content changes (re-renderedoutput_00Nslices) — the token is additive. - Adding a canvas profile no longer marks a whole project out of date when the profile matches no
page. Canvas profiles match purely by canvas W×H, but a page's dimensions were never recorded, so
detectCanvasConfigChange()could only fall back to a blanket "the effective profile list changed →
every page is suspect" — creating one profile turned an entire chapter's tiles amber and raised a scary
"state cannot be confirmed" prompt even when nothing it matched existed in the project. Each
InputFilenow records the display W×H the render resolved against (added additively to the
workspace JSON,0= unknown), anddetectCanvasConfigChange()re-matches every page against the
profiles now in effect — flagging only pages whose applied profile actually changed (a new profile that
now matches a previously-unmatched page, a removed/reordered one, or an in-place margin edit). The match
is scoped to the project's assigned profiles only (mirroringCanvasProfileMatcher's subA), so an
unlinked workspace profile of the same size never desyncs a project. A legacy page with no recorded
size keeps the honest coarse fallback until its first re-render records the size. The single W×H match
rule is nowModels::canvasSizeMatches(), shared by the matcher and the staleness re-match. - Internal:
log.hppinclude guard renamed (…LOGGING…→…LOG…) to match the header path.
Full Changelog: 0.4.1...0.5.0
0.4.1
Added
-
Windows version metadata on
libplatemaker.dllandplatemaker-cli.exe. Both now carry a
VERSIONINFOresource (CompanyName, ProductName, File/ProductVersion from the project version,
description, copyright), so Explorer's Details tab and tools like Process Explorer show proper
identity instead of blanks. Generated fromPROJECT_VERSIONvialib/cmake/version.rc.inand
cli/version.rc.in(MinGW/windres,WIN32-only). The CLI resource declares its GPL-3.0-or-later
licence, the DLL its LGPL-3.0-or-later. -
Embedded version marker (all platforms).
libplatemaker.so/.dllandplatemaker-clinow carry
a single@(#)platemaker <version> (component)string literal, so the version is discoverable
without running the binary on platforms that have no WindowsVERSIONINFOresource (Linux/macOS):
what libplatemaker.so.<ver>orstrings … | grep '@(#)'. This is the portable companion to the
Windows metadata above (and to the shared library'sSOVERSION/versioned filename). Generated from
PROJECT_VERSIONvia a build-tree-onlylib/cmake/ident.hpp.in(never installed, not public API);
the symbol is kept from being stripped with__attribute__((used))on GCC/Clang.
Full Changelog: 0.4.0...0.4.1
0.4.0
Changed
- Breaking — the processing error channel is now typed.
ProcessingOutcome::errorMessage(a free
string) is replaced bystd::optional<Models::ProcessingError> error, and
ProjectItem::applyProcessingResults()now returnsstd::vector<Models::ProcessingError>
(previouslyvoid). Consumers readoutcome.error->messageinstead ofoutcome.errorMessage, and
should surface the vector returned byapplyProcessingResults().outcome.failed/
outcome.cancelledare unchanged (failed == error.has_value()). Core::InputResultgainserrorCode/errorCategory— the per-input skip reason
(SkippedError) now carries a stable machine tag alongside the existingdetailstring, so a
consumer can group/localise skips without parsing English. The file and message are still
inputPath/detail(no duplication).
Added
Models::ProcessingError— one typed error vocabulary (code+category+message+fileslice) shared wherever a failure is consumed as a result:ProcessingOutcome::error, the
applyProcessingResults()return, and theInputResulttag.ProcessingErrorCategory
(Load/ProfileMatch/Slice/Encode/Io) andProcessingErrorCodecover the pipeline's
fatal sites and the post-render hash failure. Deliberately no liveonErrorcallback: for a fatal
error the run returns immediately (readoutcome.error), and per-input skips already flow through
onInput;onLog(Error, …)remains the human transcript.
FileStatus::Error— a new, sticky input status for a page that was rendered but whose content
hash could not be computed afterwards.WorkspaceEditor::duplicateProject(source, newName)— seeds a new project from an existing one,
copying its input files and profile links (canvasProfileIds+outputProfileId) only. The
output directory, the output slice list and all render state are deliberately dropped, so the copy
starts withPendinginputs and renders into its own folder. Mints a fresh workspace-unique project
uid (the source keeps its own) and fresh project-local input uids — no identifier collides. Enables
the GUI's "New from this…" action for the multi-publisher workflow.- Complete third-party notices for the bundled DLL graph. The Windows packages now ship
credits/THIRD-PARTY-NOTICES.txt,credits/licenses/(18 canonical/upstream licence texts) and a
credits/sbom.spdx.jsoncovering all 32 components (the 3 direct deps + the ~29 libvips runtime /
compiler-runtime DLLs — glib, libpng, libjpeg/mozjpeg, freetype, harfbuzz, cairo, pango, webp, …),
instead of only libvips + nlohmann/json. Generated at configure time (cmake/gen_credits.cmake) from
the web-build's authoritativeversions.json+ a curatedcmake/third_party.json; aThird-party coverageCI workflow fails if a bundled DLL is unmapped. The committedsbom/snapshot (GitHub
dependency graph) was refreshed to the full graph. (Legal note:third_party.jsonSPDX ids are
curated best-effort and pending a final review; libimagequant is disclosed as GPL-3.0-or-later.) - Safety net for unforeseen faults.
ProcessingPipeline::run()now wraps its whole body so any
exception that escapes the inline handling (a bug, out-of-memory, a non-std::exceptionthrow) becomes
a typedUnexpected/Internalfailure onoutcome.errorinstead of unwinding out ofrun()and
terminating the caller's worker thread; the CLI gained an equivalent top-level guard that prints a
reportable diagnostic. NewProcessingErrorCode::Unexpected+ProcessingErrorCategory::Internal.
(This handles C++ exceptions only — a hardware fault such as a segfault or null dereference is an OS
signal / SEH, not a C++ exception, and is out of scope; catching those needs a dedicated crash handler.)
The CLI also installsstd::set_terminateto print the in-flight exception on theterminatepaths
(uncaught exception /noexceptviolation / pure-virtual call) instead of aborting silently.
Fixed
- An unreadable-after-render input no longer loops forever, silently. When
computeFileSha256()
returned""(indistinguishable for "absent" and "locked / denied / offline"),
applyProcessingResults()left a successfully-rendered input atPending;sanitize()re-confirmed
Pending, so every subsequent render redid all the work and overwrote the output indefinitely with
nothing reported. Such an input is now set toFileStatus::Error(sticky, non-forcing) and returned
as a typedInputHashFailedfailure; it recovers toProcessedonce the file can be read again.
CLI
project duplicate --workspace F --name SRC --new-name COPY [--output DIR]— seeds a new project
fromSRC(input files + profile links only; no outputs / output dir / render state), the CLI face of
WorkspaceEditor::duplicateProject(). Refuses a missing source or an already-taken name; without
--outputit notes that the copy needs an output directory before rendering.processgained structured, TTY-aware output — a per-input tally (with the typed skip reason),
a strip-assembly marker, an in-place progress bar (plain lines when piped), timing, and dedicated
error sections that renderProcessingErrorfields (category / slice / file) plus the post-render
"unverified input(s)" list.--jsonis unchanged apart from a newunverifiedInputsarray. Symbols
only, no colour, so redirected output stays clean.
What's Changed
- chore(deps): bump advanced-security/spdx-dependency-submission-action from 0.1.1 to 0.2.0 by @dependabot[bot] in #1
- chore(deps): bump actions/checkout from 4 to 7 by @dependabot[bot] in #2
- chore(deps): update pytest requirement from >=7.0 to >=9.1.1 in /tests/cli-tests by @dependabot[bot] in #3
- chore(deps): bump softprops/action-gh-release from 2 to 3 by @dependabot[bot] in #8
- chore(deps): bump actions/attest-build-provenance from 1 to 4 by @dependabot[bot] in #7
- chore(deps): bump actions/upload-artifact from 4 to 7 by @dependabot[bot] in #6
- chore(deps): bump actions/checkout from 4 to 7 by @dependabot[bot] in #5
- chore(deps): bump actions/download-artifact from 4 to 8 by @dependabot[bot] in #4
New Contributors
- @dependabot[bot] made their first contribution in #1
Full Changelog: 0.3.1...0.4.0
0.3.1
Added
-
ProjectEditorandWorkspaceEditornow expose component snapshot/restore for undo/redo (or any
save-and-revert-a-single-part need), so a consumer never has to serialise or parse workspace JSON
itself — the editors, already the validated-mutation authorities, own it:ProjectEditor::snapshot()/restore(str)— a compact serialisation of one project (inputs,
outputs, profile links, output-profile selection, output dir, render baselines).restorereinstalls
the content in place, rebuilds the lookup tables, and applies the private profile-link fields through
the friend path; the projectnameis preserved (it belongs to the workspace scope).WorkspaceEditor::snapshotMeta()/restoreMeta(str)— workspace-level metadata only: the canvas
and output palettes plus the project(uid, name)roster, without project contents.restoreMeta
reinstalls the palettes through the validated setters (ids preserved, presets stripped,templateInfo
restored exactly) and restores project names by uid, leaving inputs/outputs untouched.
The two are scoped so a workspace-level restore can never resurrect project content reverted on a
project's own timeline. (Internally, the Models JSON codec moved fromworkspace_serializer.cppto a
sharedmodel_jsonunit both the serializer and the editors use;WorkspaceSerializer's public API
is unchanged.) -
Five new platform output presets. Alongside "Webtoon Standard", the catalogue now ships presets
for Tapas, NAMICOMI, GlobalComix, Popjoy and ComicFury/indie-web. Design rule: a preset must always
produce an uploadable file for its platform — slice heights are kept conservative (≈1.4–3.3 MP) and
compressed formats preferred; users who want to push size/quality do it in a custom profile.- Tapas —
940 × 1504, JPEG. 940px width is required, with a 2 MB per-file limit; the height
keeps Webtoon's1:1.6ratio (940 × 1.6 = 1504) and a q90 JPEG stays well under 2 MB. - NAMICOMI —
1200 × 1600, PNG. The platform allows a 250 MB per-chapter budget, so lossless
is free and its creators prefer it — the one non-lossy preset. - GlobalComix (HD) —
1280 × 2560, WebP. The platform promotes HD and fully supports WebP,
which keeps a tall HD slice uploadable (smaller than the equivalent JPEG). - Popjoy —
1000 × 2000, JPEG. Targets high-DPI phone screens; 2 MP q90 stays well under a
typical per-file limit. - ComicFury / Indie —
950 × 1500, JPEG. A layout-safe width for classic web CMS templates.
All resolve from the compile-time catalogue like "Webtoon Standard" (stable ids
op-preset-tapas,
op-preset-namicomi,op-preset-globalcomix,op-preset-popjoy,op-preset-comicfury; never
persisted); consumers that listoutputProfilePresets()— the CLI and the GUI — pick them up
automatically. - Tapas —
Packaging
- The MinGW build no longer depends on MSYS2's libvips (or on an MSVC build). Both Windows
toolchains now fetch the same pinned prebuilt "web" libvips viaFetchContent(each into its own
build tree, so the branches are independent). MinGW previously used MSYS2'smingw-w64-x86_64-libvips
through pkg-config — the full build (~92 DLLs / ~34 MB, plusvips-modules"unable to load …"
warnings). The slim web build cuts the bundled third-party DLLs to ~40 (~25 MB with the MinGW
runtime) and drops the module warnings. MinGW links the Clibvipsonly (the web zip's C++
runtime is LLVM libc++, ABI-incompatible with libstdc++, but Platemaker uses only the vips C API,
whose ABI is stable across runtimes) — linking directly against the DLL, so no.lib/.def/dlltool
step is needed. Building on MinGW now requires only the MSYS2 toolchain (gcc,ninja); no
mingw-w64-x86_64-libvipspackage and noPKG_CONFIG_PATH.
Full Changelog: 0.3.0...0.3.1
0.3.0
Breaking (pre-1.0 shifted scale: a breaking change bumps the minor). The output-profile preset
model changed and some symbols were removed. Workspace files stay readable and are migrated on load;
a consumer that used the removed preset internals must adapt.
Removed
Models::isOutputProfilePresetId()andModels::k_outputPresetPrefix— preset-ness is no longer
derived from an id prefix. UseModels::outputPresetDefById(id)(returns a definition pointer, or
nullptr) instead.WorkspaceSerializer's adopt/fork/presence preset handling (enforceOutputProfilePresets), the
"a preset is always present in the workspace" invariant, andWorkspaceRepairReport's
preset-collision fields. Presets are no longer written into a workspace.
Changed (breaking)
- Unmatched input pages are now rendered implicitly instead of being dropped. When a project has
canvas profiles and an input'sW×Hmatches none of them, the pipeline used to skip the page (it
never joined the strip, only a summary count noted it — and because slice numbering is continuous,
the missing page left no visible gap). It is now scaled totargetWidthwith no margin crop (the
same path a project with no profiles uses) and reported viaonInputasAppendedWithoutProfile, or
AppendedProfileNotLinked(with a Warning log naming the ids) when a same-size profile exists in
the workspace but is not linked to the project. Such a page is markedFileStatus::Processedwith an
emptycanvasProfileId.ProcessingOutcome::skippedPagesnow lists only missing / load-error inputs.
Two newInputStatusvalues are added;SkippedNoProfile/SkippedProfileNotLinkedare retained
but unemitted (reserved for a future opt-in "drop unmatched pages" mode). This deviates from the old
SPECIFICATION §7.5.1 steps 4a/4b (now amended): determinism is preserved by visibility (the input
is flagged) rather than by omission, and quick-start renders and late-added profile-less frames no
longer lose pages. See §7.5.1. - Presets are code-defined and never persisted. A preset is a baked-in template — a compile-time
catalogue (Models::k_outputPresetDefs/OutputPresetDef) materialised on demand — never
serialised intooutputProfiles. Preset-ness is provenance, tested zero-copy by
Models::outputPresetDefById(id); there is no "is a preset" field and no reserved id prefix.
Immutability is the library's own guarantee: the serializer refuses to write a preset-id profile, and
load()migrates older files (a persisted copy of a preset is dropped and its references relinked to
the canonical id; a diverged preset-id profile is given a fresh id; a user profile that merely
resembles a preset is untouched). Customising a preset means duplicating it. A future change to a
preset's content therefore no longer desyncs stored workspaces. Models::resolveOutputProfile(workspace, id)— new shared resolver returning
std::optional<OutputProfile>, unioning the workspace's own profiles with the preset catalogue
(presets take precedence). Replaces per-consumer resolution.Models::outputProfilePresets()now returnsconst std::vector<OutputProfile>&(built once) and
Models::outputProfilePresetById()materialises from the compile-time catalogue.ProcessingPipeline::run()groups its callbacks into aCore::ProcessingCallbacksstruct. The
three loose trailing callback parameters (onProgress,onLog,onSliceSaved) are replaced by a
singleconst ProcessingCallbacks& callbacks = {}(beforeonlySlices); each field is an optional
std::function, so a caller wires only what it needs. The per-sliceonSliceSavednow reports a
SliceSaved{sliceIndex, name, fullPath}(was(name, fullPath)) — the absolute 0-based slice index
lets a consumer address the output by position.ProcessingProgressandProcessingLogLevelmove
fromprocessing_pipeline.hppto the newprocessing_callbacks.hpp(a transitive include, so existing
users are unaffected).cancelstays a separate parameter.run()is also nowstatic— the
pipeline is stateless, so callProcessingPipeline::run(...)without constructing an instance.Models::Workspace's profile palettes are now encapsulated.canvasProfilesand
outputProfilesare no longer public vectors: they are read throughws.canvasProfiles()/
ws.outputProfiles()(const accessors) and mutated only through the new
Infrastructure::WorkspaceEditor. Direct writes (ws.canvasProfiles.assign(...),push_back, …) no
longer compile — a consumer that edited the vectors by hand must go through the editor. Reading is
unchanged apart from the(). This closes a real hole: an edit made in a running session is now put
through the same invariant checks (unique ids, no persisted presets,templateInfopreserved) that
a loaded file is, instead of only being validated on the next open.ProjectItem's profile-link fields are now encapsulated too.canvasProfileIdsand
outputProfileIdare no longer public: they are read throughpi.canvasProfileIds()/
pi.outputProfileId()(const accessors) and written only throughProjectItem::addCanvasProfile()
(the dimension guard) andWorkspaceEditor::setProjectOutputProfile()/removeCanvasProfileFromProject().
This closes the last bypass: a rawpi.outputProfileId = "garbage"(skipping the id validation) or a
rawpi.canvasProfileIds.push_back()(skipping the "one profile per canvas W×H" guard,
SPECIFICATION.md §7.5.2) no longer compiles.from_json(ProjectItem)is intentionally partial (like
Workspace);load()installs the links through the friend path. OtherProjectItemfields
(name/uid/inputDirectory/outputSignature/canvasProfileIdsAtRender) stay public — they carry
no cross-cutting invariant.uuid→uidacrossInputFile/OutputFile/ProjectItem, and the ids are now genuinely
unique. The field and its JSON key are renamed (these are short local ids likefile-<hex>/
out-N/proj-<hex>, never RFC 4122 UUIDs — the name over-promised). No back-compat: the old
"uuid"key is not read, so a workspace written by an older build loads with empty file/project ids
that are minted fresh on load (and the old key is dropped on the next save). Input ids are no longer
derived from the list position ("file-" + index), which could hand two files the same id across
re-scans;ProjectItem::ensureUniqueFileUids()andWorkspaceSerializer::load()mint via
Infrastructure::makeUniqueIdand de-duplicate. The GUI's project id (previously a timestamp — also
collision-prone) now goes throughmakeUniqueIdtoo.
Added
Infrastructure::ProjectEditor(platemaker/infrastructure/project_editor/project_editor.hpp)
— the authority for a single project's content, twin ofWorkspaceEditor. Owns input ordering:
setInputOrder(orderedUids)(validates a permutation, rewrites eachInputFile::order) and
moveInput(uid, ±1). It never physically reorders the stored input vector — only theorder
field — so a reorder does not churn the project structure.- The render now follows
InputFile::order, not the stored-vector order. New
ProjectItem::inputsInOrder()returns the inputs sorted byorder; the CLI passes it to
ProcessingPipeline::run(the pipeline itself is unchanged — it still renders the sequence it is
handed, with no knowledge oforder). Previously the strip was built in raw vector order, so a
reorder that only touchedordernever reached the output. - Input-composition staleness axis —
ProjectItem::inputOrderAtRender(the input uid sequence, in
order, captured at render, next tocanvasProfileIdsAtRender) and
ProjectItem::detectInputCompositionChange().sanitize()marks every outputDesynchronizedwhen
the current order/composition differs from the baseline: reordering / adding / removing an input
shifts the continuous strip so every downstream slice changes while each file stays byte-identical —
no hash could notice.WorkspaceSerializer::load()backfills the baseline of a project rendered
before this field existed from the outputs'sourceMapprovenance, so an old reorder is still caught
without a spurious re-render. WorkspaceEditor::addProject(name)— creates a project with a workspace-uniqueproj-uid and
appends it. Minting the project uid is now the lib's job: the CLI (and GUI) no longer hand-roll it
withmakeUniqueId(the last identifier a consumer still generated itself).Models::outputPresetDefById(),OutputPresetDef,k_outputPresetDefs— the compile-time preset
catalogue (single source of truth) and its zero-copy membership test.Core::ProcessingCallbacksand its event payloads (core/processing_callbacks/processing_callbacks.hpp)
— the pipeline now reports, in addition to progress/log/slice-saved:onInput(InputResult)once per
input (Appended;AppendedWithoutProfile/AppendedProfileNotLinkedfor an implicitly-rendered page,
the latter carrying the matching-but-unlinked workspace profile ids; orSkippedMissing/SkippedError.
TheSkippedNoProfile/SkippedProfileNotLinkedvalues remain in the enum but are no longer emitted —
reserved for a future opt-in skip mode);onSlicingStarted(SlicingStarted)at the phase-1→phase-2
boundary with the expected slice count;
andonSliceSkipped(SliceSkipped)for a clean slice a partial re-render leaves untouched. All are
optional. Callbacks fire synchronously on the calling thread; the library remains thread- and
GUI-agnostic.Models::FileStatus::Skipped— a new lifecycle value for an input the last render did not
inc...
0.2.1
Additive API only: nothing was removed or changed, so code written against 0.2.0 still
compiles. The reverse does not hold — code using anything added below needs 0.2.1.
Workspace files stay compatible in both directions, but load() behaves differently: it
now repairs colliding identifiers and appends any missing output preset, so a workspace can
come back with one more profile than was saved. Nothing is lost or overwritten.
Added
Infrastructure::makeId()and themakeUnique*Id()helpers
(platemaker/infrastructure/id_generator/id_generator.hpp) — random 128-bit identifiers
that are checked against the ids already in use, so a collision is impossible rather than
merely unlikely.- Output profile presets (
platemaker/models/output_profile.hpp) —
webtoonStandardPreset(), theoutputProfilePresets()lookup table,
isOutputProfilePresetId()andoutputProfilePresetById(). Preset identifiers are the
same in every workspace, so a preset stays recognisable across files and across app
updates WorkspaceSerializer::load()overload taking aWorkspaceRepairReport— reports
identifier collisions the load had to repair, so a GUI can explain them to the user
Fixed
-
Profiles could share an identifier, making one of them unreachable — ids were a
millisecond timestamp, so every profile created in one pass of the "manage profiles"
dialog got the same one. The visible symptom was a canvas profile that could not be
assigned to a project: it counted as already assigned and vanished from the assign
list. Existing workspaces are repaired on load — the first profile keeps the id, later
duplicates get a new one, and project assignments are preserved -
Identifiers derived from profile names are gone (
"cp-" + name) — a second identity
scheme that was not unique either, since two profiles with the same name collided.
Profiles saved without an id now get a random one, and the legacy references that relied
on the derived form are relinked on load -
Paths containing non-ASCII characters did not work on Windows — one cause behind two
symptoms that looked unrelated:- inputs stayed Pending after a successful render, so every render redid all the work
and overwrote the existing output. Hashing an input opened the file through a narrow
string, which Windows reads in the ANSI code page while the library's paths are UTF-8,
so the hash silently came back empty and the status was never updated - a workspace could be saved but not reopened.
save()opened through a
std::filesystem::pathandload()through astd::string, so the two disagreed about
the same path. Easy to mistake for a Google Drive limitation, because Drive creates a
localised folder name (Polish "Mój dysk") — the same file under an ASCII path was fine
Every filesystem boundary now converts explicitly (
utf8ToPath()/pathToUtf8()in
platemaker/infrastructure/file/path_utf8.hpp), so behaviour no longer depends on the
toolchain or on the machine's code page. Rendering itself was never affected: libvips
takes UTF-8 and lets GLib convert it - inputs stayed Pending after a successful render, so every render redid all the work
-
CLI could create two projects with the same identifier — project ids came from a
second-resolution timestamp, so a single command creating two projects collided -
The "Webtoon Standard" profile was defined twice, once in the CLI and once in the GUI,
agreeing only because both happened to match the struct's field defaults. Both now seed
from the library's preset table, so a workspace created either way carries the same
preset with the same id -
Presets are kept consistent on load — a profile that is the preset takes the canonical
id, one that carries a preset id but has been edited is given an id of its own (its
settings untouched), and any missing preset is added back. The passes compose: an edited
profile is separated out, which frees the canonical id, and the genuine preset returns
beside it
0.2.0
Breaking API changes; consumers must be rebuilt. Workspace files stay compatible in both
directions — the new fields are additive, so older builds still open newer workspaces.
Added
- Canvas profile change tracking — each page records the profile it was rendered with
(canvasRenderFingerprint()), so a later edit to that profile is detectable ProjectItem::detectCanvasConfigChange()— reports which pages a profile edit
invalidated, or that the profile list itself changedProjectItem::effectiveCanvasProfileIds()— the profiles a project actually renders
with, mirroringCanvasProfileMatcher's ruleProcessingOutcome::appliedProfiles— which canvas profile the pipeline applied to
each input
Changed
- Breaking —
ProjectItem::sanitize()now takes the workspace canvas profiles, and
flags config-stale files asDesynchronizedalongside the existing on-disk checks - Breaking —
ProjectItem::applyProcessingResults()takes the applied profiles and the
workspace profile list, so the canvas baseline is recorded with the render - Breaking —
ScaledStrip::sliceAll()streams each slice to a callback instead of
returning them all; non-const and single-use - Breaking —
Slicerremoved — it wrapped two ints and forwarded toScaledStrip,
whichProcessingPipelinealready drove directly
Fixed
ScaledStripmemory contract — sources are released as slicing advances, as the class
always documented but never did. Peak memory is now flat instead of growing with page
count (24-page chapter: 534 MB → 213 MB). Output is byte-for-byte identical- Canvas profile edits were ignored — changing margins or canvas size left a project
reporting itself up to date while its rendered output no longer matched - Windows CLI hang on exit — an intermittent deadlock in the loader's DLL teardown
(LdrShutdownProcess), reproducible under both MinGW and MSVC. The CLI now flushes its
output and terminates instead of unwinding the dependency graph
0.1.1
Added
- Processing pipeline — core image scaling, slicing, and saving in a single pass
- Partial re-rendering — only tiles whose configuration changed are reprocessed (tracked via output signature)
- Incremental processing — SHA-256 content checks skip unchanged source images
- Per-format output options —
PngOptionsandWebpOptions; output profile drives format selection and compression settings CanvasProfileMatcher— automatically resolves the best canvas profile based on image dimensions- Per-project profile assignment — each project carries its own canvas and output profile
- Canvas profile UUIDs — stable identifiers; workspace serializer is backwards-compatible with 0.1.0 workspaces
- CLI
--no-profile— bypass canvas profile matching and process with explicit parameters - CLI template generation — configurable background colour
- CLI integration tests (pytest)
- Linux
install.sh— bundled in the CLI and dev tarballs; detects package manager and prompts forlibvipsif missing - Linux portable RPATH —
$ORIGIN-relative; binaries run from the build tree and from installed tarballs withoutLD_LIBRARY_PATH - Windows DLL pruning (MinGW) — closure computed from actual PE imports; only required DLLs are bundled
- SPDX license identifiers in all source files
Changed
- Windows (MSVC): libvips download switched from
alltowebvariant — removes unused Python/Tcl bindings, smaller bundle - CMake minimum version raised to 3.25 (required for
cmake --workflow)
Fixed
ProjectItem: missing move constructor and assignment operator (affectedcanvasProfileIdsandoutputProfileId)OutputProfile: missing profile ID is relinked to an available profile on load instead of silently failing
0.1.0
Portable linux version, keep both platemaker and libplatemaker.so in the same directory. No manual yet - rough option explanation in file cli/main.cpp