Files
klarbild/CHANGELOG.md
T
till 210a84dcf8 feat: rename to Drucken, 86 image sizes, fix the distorted crop editor
The crop frame had width:100% together with max-height, so the height was
clamped while the width stayed - the frame lost the target aspect ratio and
object-fit:fill stretched the picture into it. Measured at 390px: frame 0.843
instead of 0.778, picture 8.3% too wide. The width now follows from the allowed
height and the ratio, and the image carries its own aspect-ratio instead of a
second percentage, so it cannot stretch at all.

Dragging was fine but felt dead: at fill zoom the crop sits at the stop in one
axis. The editor now states which way it can move, double-tap zooms, arrow keys
nudge, and the zoom slider shows its factor.

Sizes: 41 -> 86 in ten groups, including US inch sizes, instant-film picture
areas, cards, DIN A0-A7 and eleven aspect ratios as physical sizes.

The tab is called Drucken now; /druck and /passbilder redirect there.
2026-08-19 07:50:44 +00:00

192 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to Klarbild are documented here. Newest first.
## 2026-08-18 (4) — Renamed to "Drucken", 86 sizes, crop editor fixed
### Fixed
- **The crop editor distorted the picture.** `.crop-box` had `width:100%` *and*
`max-height`, so the height was clamped while the width stayed put — the frame no
longer had the target aspect ratio, and `object-fit:fill` stretched the image into it.
Measured on a 390 px viewport: frame 0.843 instead of 0.778, picture **8.3 % too wide**.
The frame width is now derived from the allowed height and the ratio, and the image
carries its own `aspect-ratio` instead of a second percentage — it can no longer stretch,
even if the frame is ever off.
- **Dragging felt broken.** At "fill" zoom the crop sits exactly at the stop in one axis,
so pulling that way does nothing — correct, but invisible. The editor now says which way
the picture can move, double-click/double-tap zooms a step, arrow keys nudge (shift for
bigger steps), and the zoom slider shows its factor.
### Changed
- **Renamed to "Drucken"** — the tab, page, title and Telegram button. `/druck` and
`/passbilder` both redirect permanently to `/drucken`.
- **86 image sizes in ten groups** (was 41): passport formats per country, cm small sizes,
classic photo sizes, **US inch sizes** (wallet to 24 × 36), **instant-film picture areas**
(Instax mini/Square/Wide, Polaroid), poster and frame sizes up to 100 × 140, squares,
**DIN A0A7 incl. A3+**, cards (business card, postcard, DIN lang, cheque card) and
aspect ratios as physical sizes (16:9, 9:16, 21:9, 2:1, 3:2, 2:3, 4:3, 3:4, 5:4, 4:5, 1:1).
- Paper list grew to 19: DIN A1, square photo paper, and the US inch cuts.
## 2026-08-18 (3) — Code review: fixes
### Fixed
- **EXIF orientation was ignored.** Phone photos carry their rotation as metadata only.
sharp cropped the unrotated raster, so a portrait shot came out of the printer sideways
and with the wrong framing. Orientation is now applied before any geometry, and
`/api/uploads` reports oriented dimensions so the crop editor agrees with the render.
- **Memory blow-up on "Rand lassen".** The padded image was materialised at source
resolution before the resize. A panorama into a narrow contain target built a ~960 MB
intermediate and then failed outright; it is one extract→resize→extend chain now
(sharp's own order) — 90 ms and a few MB.
- **`bleedMm` was unbounded** in `/api/print/single` (the sheet endpoint clamped it).
- **Delivery gallery could escape the target's base folder** — `posixpath.join` happily
resolves `../..`, and the folder is created before upload. Names are validated now.
- **Denial of service:** sheet requests are capped at 500 pieces and the packer has a
step budget, so a degenerate request can no longer block the single-threaded server.
- **Print presets:** delete only your own (admins all), config size and count limits,
and `by_name` honours `anonymous_generations`.
- **Telegram callbacks** now require an active pairing, like every other path.
- **Error responses** no longer leak storage paths or delivery hostnames.
- `allowRotate: undefined` meant "no rotation" in one place and "rotation allowed" in
two others — a picture that only fits rotated was reported as unplaceable.
- The many-formats shortcut dropped a format that only fits rotated.
- `unplaced` blamed the first format instead of the one actually missing.
- Corner marks could land inside the printed bleed; the offset is raised to clear it.
- `capacity()` silently capped at 200.
- Image keys could collide with a cell literally named `x::rot`.
- `labelMm` rounded away real decimals (11,25 cm became 11,3); `parseSizeMm` ignored the
order in `a:b`, so `3:4/15` and `4:3/15` produced the same portrait size.
- The footer was drawn over the artwork when the margin was small.
- The UI now warns when corner marks do not fit the margin, and when continuous guides
are combined with mixed sizes (they cannot run through).
### Changed
- Tests grew from 21 to 31 — every finding above has a regression test.
## 2026-08-18 (2) — Passbildfunktion: all sizes, fit rules, mobile
### Added
- **Renamed to "Passbilder"** — the tab, page and Telegram button now say what it is.
`/druck` permanently redirects to `/passbilder`.
- **Every size the AI pipeline knows** is now printable too: 9×13 … 60×90, DIN A6A2,
squares, plus poster/frame sizes up to 70×100 and the screen ratios (16:9 "The Frame",
9:16, 3:2, 4:3, 5:4) as physical measurements. 41 presets in seven groups.
- **Fit rule per image** — when the picture does not match the target ratio, choose
**Zuschneiden** (fill the format, crop the overflow — default) or **Rand lassen**
(keep the whole picture, pad with a border colour: white, black, paper or custom).
Nothing is ever distorted. Works in the crop editor, the sheet preview, the PDF,
`/api/print/*` (`fit`, `bg`) and the saved templates.
- **Mobile layout** — image cards become a row on phones, touch targets grow to ~40 px,
the crop editor turns into a full-width bottom sheet, no horizontal overflow.
Verified at 390 / 820 / 1440 px; all three produce the identical PDF.
### Fixed
- **Sheet cells could come out oversized.** sharp applies `extend` *after* `resize`, so
padding was added on top of the finished size — a 35×45 mm cell became 35×171 mm in
"Rand lassen" mode, and bleed near an image edge was off too. Crop and padding now run
in their own pass. Covered by `tests/printrender.test.ts`.
- **Orientation is no longer overridden.** A single portrait passport photo was laid down
sideways just because more would fit that way. Rotation now only happens when it
actually saves a sheet.
## 2026-08-18 — Print module: exact sizes, sheets & crop marks (no AI)
### Added
- **Print module (`/druck`)** — a lab-style, completely AI-free path: crop, scale and place
images at exact physical sizes. No model call, no cost, no queue.
- **Interactive crop editor** — pan and zoom on a fixed target aspect ratio (rule-of-thirds
overlay); the crop is stored relative (0..1) so it survives any source resolution.
- **Size presets & free input** — biometric passport 35 × 45 mm, 2 × 3 / 3 × 4 / 4 × 5 / 4,5 × 6 cm,
9 × 13 … 50 × 70 cm, squares, DIN. Free input understands `12x15` (cm), `35x45mm`, `5`
(= 5 × 5 cm) and `4:3/15` (ratio + long edge).
- **Sheet layout** — paper presets DIN A6A2 incl. **A3+ (329 × 483 mm)**, photo-paper cuts
(9 × 13 … 20 × 30), US Letter/Legal, plus any custom paper size. Portrait/landscape,
printer margin, centering, per-image copy count.
- **Automatic nesting** — identical sizes produce an exact grid; mixed sizes go through a
MaxRects packer that fixes one orientation per format and tries every combination,
so eight passport photos land in the free space next to the 13 × 18.
- **Crop marks** — `corner` (fine 0.25 pt marks outside the trim box, Photoshop/InDesign
convention, 4 mm long / 3 mm offset by default) or `grid` (continuous guides across the
sheet that never cross another image). Gap between images adapts to the chosen marks.
- **Bleed** (010 mm) — the image extends past the trim box, edge pixels are copied rather
than stretched; the gap is always ≥ 2 × bleed so neighbouring bleeds cannot overlap.
- **PDF output at 1:1** — the PDF page is exactly the sheet size (pdf-lib), with a footer
stating the sizes, dpi and the "print at 100 %, not fit-to-page" reminder.
- **Single export** — one image at an exact size as PNG/JPG with dpi metadata, plus a
resolution warning when the source cannot hold the requested dpi.
- **Sheet templates** — save the whole sheet setup (paper, marks, formats, counts) as a
reusable preset (`print_presets`, migration `013`).
- **All three entrances** — `/druck` in the web app, `📐 Druckbogen` in Telegram (fully
button-driven, returns an A4 PDF), and the MCP tools `exact_size` / `print_sheet`.
- **API** — `POST /api/print/single`, `GET /api/print/single?size=…` (mm → px calculator),
`POST /api/print/sheet`, `GET|POST|DELETE /api/print/presets`. Documented in `/llms.txt`.
- **Seeded sheet templates** — "Kita-Satz (A4)", "Schulsatz klein (A4)", "Passbildbogen 35×45 (A4)"
and "2 × 10×7,5 auf Fotopapier 10×15". Loading a multi-format template with a single image
clones that image into every format, so one click produces the whole set.
- **Margin profiles** — borderless (0 mm), standard (5 mm) and safe (10 mm) as one-click buttons.
- **Deliver the sheet** — the finished PDF can additionally be pushed to Picdrop, the NAS or any
extra delivery target (`deliver` in the sheet request).
- **Tests** — `tests/printlayout.test.ts` (`npm test`) covers size parsing, exact mm → px,
non-overlap, margins, bleed spacing, mark geometry and multi-sheet paging.
## 2026-07-23 — Compose, generate & second backup
### Added
- **Compose mode** — combine multiple images + a text description into one new image.
- **Generate mode** — create images from text only, no source needed.
- **The Frame "extend"** — outpaint more scene around the motif instead of only cropping.
- **Reuse** — take a finished result back into the studio as a new source to edit further.
- **Alternatives** — generate additional versions of the same image, grouped with a switcher.
- **Folders** — filter, rename and delete in the library; per-folder delivery gallery mapping.
- **Bulk actions** — download selected results as a ZIP, move several into a folder at once.
- **Thumbnails** — low-res previews for the library grid.
- **Models** — quality tiers incl. open-source (FLUX.2); the used model is shown per image.
- **Color tags** — Picdrop-style flags (red / orange / green / final) per image, filterable, and
written into the companion `.md` metadata.
- **Tap-to-save** — "Laden" opens the image full-bleed so on iPhone/iPad a long-press offers
"Save to Photos" (plus a classic file download).
- **Access control & privacy** — library visibility (own-only vs shared), optional anonymous
generations (creator hidden from non-admins), admins always see all.
- **Private sessions** — generate-and-forget: no library entry, no delivery/backup, no stored
prompt, sources purged, result auto-deleted shortly after. Enabled globally by admin or forced
per user.
- **NSFW gate** — models flagged NSFW are only selectable/visible when the admin enables it.
Operator responsibility; content involving minors is never permitted.
- **Single-image transform** — compose now accepts one image, so a single photo can be
transformed by prompt (e.g. "make it an oil painting") and put onto a format.
- **Multiple delivery targets** — besides the default Picdrop, add any number of FTP/SFTP
destinations; selectable in the studio ("Wohin?", incl. NAS); a preset or folder can be
hard-wired to a specific target + gallery.
- **Backup to any target** — mark any target as a backup destination (like the NAS); one-click
"back up all images" mirrors to all backup destinations.
- **Metadata sidecar** — optionally ship a companion `.md` (prompt, model, format …) next to
each image on Picdrop/NAS/FTP.
- **Prompt view** — every generated image shows its prompt in the library (with copy).
- **Preset manager** — save current settings as a preset, load and delete presets.
- **API token + MCP server** — programmatic access so an assistant can push images from a
local folder or iMessage into Klarbild (`mcp/klarbild-mcp.mjs`).
- **The Frame** preset now delivers to its own gallery ("TheFrame-Backgrounds").
- **Second backup** — mirror every result over SFTP/FTPS to a NAS (works over Tailscale too),
with a "NAS ✓" badge and a "mirror all existing images" action.
- **Storage management** — thumbnails, delete sources after processing, auto-retention.
- **Telegram** — fully button-driven (persistent keyboard + command menu): edit, compose,
"new image", reuse — no slash commands required.
- **Reset** — wipe library, jobs and folders cleanly (keeps users, settings, models, recipes),
guarded by a typed confirmation.
- **Help page**, in-app **changelog**, and a NAS **reachability diagnostic**.
### Fixed / hardened
- Real per-image cost cap (monthly budget) enforced by the worker.
- Idempotent job completion (no duplicate delivery/notification).
- Before/after comparison works for composed images; hidden for text-only results.
## 2026-07-22 — Foundation
### Added
- Astro 5 + React islands UI (mobile-first), PostgreSQL, fs/S3 storage abstraction.
- pg-boss job queue with in-process worker; sharp pipeline (exact cm/dpi print sizes,
sticker contour).
- OpenRouter image gateway; admin-managed models.
- Picdrop delivery over SFTP/FTPS; recipes; library with before/after.
- Telegram bot (grammY webhook); argon2 auth; AES-256-GCM secret encryption.