Files
jurjen.ladeniusandClaude Opus 5 db7de024de Art: SVG -> 48x48 portrait converter, and it works
Magnific's SVG export is flat-shaded vector (~550 solid-fill paths, 6
gradients), which converts to pixel art cleanly. tools/svg2portrait.py
rasterises at 16x the target via qlmanage, then takes the most common
colour per 16x16 block — a true pixelate with no blending — and maps to
the 11-colour portrait palette.

Measured on Aki: 19 colours after pixelate, 10 after palette, visually
near-identical. So the portrait palette is sufficient for this art.

Contrast with the bitmap route this replaces: a 48x48 PNG resized in
Photoshop had 1330 unique colours in 2304 pixels, because bicubic
resampling turns flat regions into gradients. Quantising that speckled
badly across the blazer and background.

Also adds tools/pixtool.py — dependency-free PNG decode/encode (no Pillow
on this machine) plus the palette table shared with kissaten.c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:46:12 +02:00

11 KiB
Raw Permalink Blame History

Kissaten Yūgure — Art brief

Pipeline — export SVG, not PNG

Generate at high resolution, export as SVG, drop it in art/, and run:

python3 tools/svg2portrait.py art/aki-neutral.svg

That writes art/aki-neutral-48.png (the real asset) and art/aki-neutral-48-x6.png (a 6× preview for judging).

Export SVG, never a resized bitmap. This was learned the hard way. A 48×48 PNG produced by resizing in Photoshop came back with 1330 unique colours in 2304 pixels — bicubic resampling turns every flat region into a gradient, and reducing that to 11 colours speckles horribly, worst on large flat areas like the blazer and background.

The SVG from Magnific is flat-shaded vector: ~550 paths, solid fills, a handful of gradients. The converter rasterises it at 768×768 (16× the target) via qlmanage, then takes the most common colour in each 16×16 block — a true pixelate with no blending at all. Measured on Aki: 19 colours after the pixelate, 10 after the palette reduction, and the two are nearly indistinguishable. The 11-colour portrait palette is sufficient for this art — that was the open question and it's settled.

If you must work from bitmaps, the same logic applies: resize only by exact integer factors with nearest-neighbour, never bicubic.


1. The palette — read this before generating anything

Everything in the game shares one 16-colour palette. It is already in kissaten.c as g_Palette and is what the current build renders with.

  • art/palette.png — 16×1 px, one pixel per entry. Machine-readable; use it wherever PixelLab accepts a palette image.
  • art/palette_ref.png — 512×64 swatch strip, same order, for eyeballing.
# Hex Role Notes
0 #000000 transparent Never paint with this. It is the transparent index.
1 #242424 outline near-black; outlines, dark hair
2 #492424 dark wood deep shadow, dark brown hair
3 #924924 mid wood wall panels, doors, deep skin shadow
4 #B66D49 light wood counter top, skin shadow
5 #FFB649 lamp glow warm light
6 #FFDB92 cream glassware, skin highlight
7 #6D4949 muted wall upper wall, muted cloth
8 #492492 dusk deep sky only
9 #9249B6 dusk violet sky only
10 #DB6D6D dusk rose sky only
11 #FF9249 horizon amber sky only
12 #6D2400 coffee coffee, dark red-brown cloth
13 #496DB6 coat blue uniforms, coats
14 #FFB692 skin skin base
15 #FFFFDB warm white highlights, cup rims, eye whites

The one non-negotiable rule

Slots 811 are sky. Slots 47 are wood and warm interior. Nothing else may use 811.

This is what makes seasons and time-of-day free: the background bitmap is authored once, and each season/hour is a 32-byte palette swap. If a character's jacket is painted in slot 9, that jacket changes colour every time the sun moves. Getting this wrong is not a cosmetic issue — it silently destroys the whole seasonal system, and you don't find out until M7.

Skin, specifically

There is only one literal "skin" entry, which is too thin for a face. Use these three as a ramp — they're hue-compatible:

  • highlight #FFDB92 (6) → base #FFB692 (14) → shadow #B66D49 (4)
  • deepest shadow / hair: #924924 (3), #492424 (2), #242424 (1)

2. What I need back

Asset Size Count
Character portrait 48×48 3 per character (neutral / happy / troubled)
Shop background 256×212 1
Customer sprite 16×16 2 layers per character (fill + outline)

PNG, generated natively at the target size — do not downscale a larger image. Downscaling pixel art produces half-pixels and anti-aliased edges that cannot be quantised cleanly.

No anti-aliasing, no gradients, no dithering unless it's deliberate hard checkerboard. If PixelLab can be constrained to the 16 colours, do that; if not, generate close and I'll quantise — but the sky/wood slot rule above still has to hold visually or the quantiser can't fix it.

48×48 is very small — roughly 6 pixels of eye. Faces need a strong silhouette and simplified features. Detail that reads at 200×200 will turn to mud. If a generation looks great zoomed out and illegible at 1:1, it's wrong.


3. Prompts — Aki (needed now, for M3)

Hoshino Aki, 17, studying for Tokyo entrance exams, cream soda drinker. Character notes in SCRIPT.md §1. Written as a girl there; that's one word to change if you'd rather — the dialogue beats themselves use no gendered pronouns.

Portrait palette block — paste into every portrait prompt

Portraits never show sky, so the four sky slots are simply absent from the list. That is safer than asking the generator to avoid them.

Use only these 11 colours and no others:
#242424 #492424 #924924 #B66D49 #FFB649 #FFDB92 #6D4949 #6D2400
#496DB6 #FFB692 #FFFFDB
Skin: #FFDB92 highlight, #FFB692 base, #B66D49 shadow, #924924 deep shadow.
Hair and outlines: #242424 or #492424.
Eye whites and highlights: #FFFFDB.
Fill the entire background behind the head with flat #492424.
Do not use pure black #000000 anywhere.

Common style stem — prepend to all three, then append the palette block:

48x48 pixel art portrait, 1987 Japanese anime style as drawn for MSX2 or PC-88 games, head and shoulders, three-quarter view, hard-edged pixels, no anti-aliasing, flat cel shading, simple readable silhouette, flat single colour background

Aki — neutral

...17 year old Japanese high school student, straight dark bob-length hair, tired but polite expression, calm eyes, slight shadows under the eyes, looking slightly off to one side

Aki — happy

...17 year old Japanese high school student, straight dark bob-length hair, small genuine smile, eyes softened and slightly narrowed, faint blush, looking toward the viewer

Aki — troubled

...17 year old Japanese high school student, straight dark bob-length hair, eyes cast downward, faint frown, tense mouth, weary


4. Prompts — the rest of the cast (M8, not urgent)

Same style stem and the same portrait palette block from §3, swapping the clothing line. Note the clothing colours available are only #496DB6 (blue), #6D4949 (muted brown-grey), #6D2400 (dark red-brown), #492424 and #242424 — so give each character a distinct one or they'll blur together in the dialogue box.

Ōta-san — retired stationmaster, ~70, black coffee, punctual.

elderly Japanese man in his seventies, close-cropped grey hair, deeply lined face, upright bearing, plain dark jacket buttoned to the collar

  • neutral: composed, formal · happy: faint dignified smile, eyes crinkled
  • troubled: jaw set, looking past the viewer

Fujimoto — manga artist, ~30, exhausted, funny about it.

Japanese manga artist in their thirties, messy shoulder-length hair, heavy shadows under the eyes, round glasses, rumpled shirt with sleeves pushed up

  • neutral: wired, sleepless · happy: crooked grin, animated
  • troubled: hollow stare, hand near face

Nakajima-san — widow, ~65, orders two coffees.

Japanese woman in her mid-sixties, silver hair pinned back neatly, soft composed face, high-collared blouse under a plain cardigan, small pearl earrings

  • neutral: serene, self-possessed · happy: warm gentle smile
  • troubled: distant, looking at something not in the room

5. Prompt — the shop background (M6, longest lead time)

256×212. This one carries the game's whole look and is the hardest to redo, so it's worth several attempts.

Composition is fixed by the code — see DESIGN.md §4 and DrawScene():

  • Top 16px is the status bar (drawn over) — keep it empty/dark.
  • y=16148 is the shop scene.
  • Bottom 64px is the dialogue window (drawn over) — keep it empty/dark.
  • Window on the left (~x1688), showing dusk sky in slots 811 only.
  • Door on the right (~x208244).
  • Counter running horizontally at y≈98, its top edge level.
  • Hanging lamp above the counter, centre.
  • Siphon coffee rig sits on the counter left of centre — I can keep drawing this procedurally if it's easier to leave out.

256x212 pixel art interior of a small 1987 Japanese kissaten coffee shop at dusk, viewed straight on, static single-screen game background, wooden counter running across the lower third, tall window on the left showing a violet and amber dusk sky, wooden door on the right, hanging lamp with warm glow above the counter, wood panelling, shelves with glassware, cosy and quiet, 1987 MSX2 game art, hard-edged pixels, no anti-aliasing, flat cel shading

Background palette block — this one is split by region

Unlike the portraits, the background needs all 15 colours — but which colours may appear is decided by where you are in the image. This is the constraint that makes seasons free, and it is the easiest one to get wrong.

Use only these 15 colours and no others.

For the sky visible through the window, use ONLY these four:
#492492 #9249B6 #DB6D6D #FF9249

For everything else — walls, floor, counter, door, window frame, lamp,
shelves, glassware, every object in the room — use ONLY these eleven:
#242424 #492424 #924924 #B66D49 #FFB649 #FFDB92 #6D4949 #6D2400
#496DB6 #FFB692 #FFFFDB

The four sky colours must appear nowhere except inside the window opening.
The window frame, mullions and sill are wood and use the interior colours.
Do not use pure black #000000 anywhere.
Leave the top 16 rows and the bottom 64 rows flat #242424 — the game draws
its own status bar and dialogue box over them.

Why this one rule matters more than the rest of this document: each season and hour of day is a 32-byte palette swap on the same bitmap. Repaint slots 811 and the sky goes from dusk to noon to rain for free. But if a chair leg or a coffee cup is painted in #9249B6, that chair leg changes colour every time the sun moves — and you won't find out until M7, long after the art is finished.


6. Sprites (M6)

16×16, two layers per customer: a fill layer and an outline layer that is the same silhouette dilated by one pixel, drawn behind. Sprite mode 2, so each layer is a single colour — these are shapes, not shaded art. Current placeholders are in kissaten.c as g_SprCustFill / g_SprCustLine.

Palette does not apply here in the usual sense. These are hardware sprites, not bitmap art: each layer is one flat colour chosen at runtime by the code, so the image only needs to define shape. Draw them as pure black-on-white silhouettes and I'll assign colours — currently #496DB6 for the fill layer and #242424 for the outline, per customer.

Worth doing by hand rather than generating; a 16×16 single-colour silhouette is faster to draw than to prompt for.