Boot-Sequenz vermessen + Intro-Player gebaut, echten PCC-Decoder-Regressionsbug gefixt
- Regression gefunden+gefixt: pcc_to_png.py war durch einen frueheren Commit versehentlich auf den kaputten Hand-RLE-Decoder zurueckgefallen (der ImageMagick-Fix ging verloren). Jetzt: kellogg_formats.load_pcc() nutzt Pillows PCX-Decoder direkt, kein eigener RLE-Code mehr im Projekt. Alle 77 PCC neu gerendert, AE=0 (pixelidentisch) zu den alten guten PNGs. - Boot-Sequenz per echter DOSBox-Screenshot-Serie (0.3s-Intervall) vermessen: Rauser-Karte -> Factor5-Logo (statisch, keine Animation) -> Kellogg's-Logo mit Palette-Fade des "praesentiert"-Texts (Indizes 34/42/43/44/45) -> Titelbild "Tony & Friends in Kellogg's Land" mit echtem Vorhang-Wipe-Effekt (320x480 aus 4 Quadranten KELL256A-D, obere Haelfte faehrt runter, untere hoch, treffen sich in der Mitte). Korrigiert eine fruehere Fehlannahme: der Wipe sitzt auf dem TITELBILD, nicht auf dem Kellogg's-Markenlogo. - tools/intro_sequence.py: erster spielbarer pygame-Meilenstein, baut die komplette Sequenz nach (Asset-Loading, Palette-Rendering, Timing, echter Palette-Fade, Wipe-Effekt). Headless getestet, visuell verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,47 @@ def get_pcx_palette(pcc_bytes):
|
||||
return [tuple(pal_bytes[i:i+3]) for i in range(0, 768, 3)]
|
||||
|
||||
|
||||
def load_pcc(pcc_bytes):
|
||||
'''Decode a .PCC (= plain PCX v5, 8bpp, RLE, own trailing 256-color
|
||||
palette) via Pillow's battle-tested PCX decoder instead of a hand-rolled
|
||||
RLE parser -- see git history 2026-07-22 (commits 5efba4b/62af15e/3410a53)
|
||||
for why: TWO from-scratch RLE implementations silently produced visibly
|
||||
wrong pixels (shifted/duplicated content) for full-screen 320x200 images
|
||||
(KARTE.PCC, KELLOGGS.PCC) despite being "verified" -- Pillow avoids that
|
||||
whole class of bug entirely.
|
||||
|
||||
Returns (width, height, indices, palette):
|
||||
indices -- flat list[int] of palette indices, len width*height, row-major
|
||||
palette -- list of 256 (r,g,b) tuples (this file's OWN embedded palette,
|
||||
not any global one)
|
||||
'''
|
||||
from PIL import Image
|
||||
import io
|
||||
img = Image.open(io.BytesIO(pcc_bytes))
|
||||
img.load()
|
||||
if img.mode != 'P':
|
||||
raise ValueError(f'expected palette (P) mode PCX, got {img.mode}')
|
||||
width, height = img.size
|
||||
indices = list(img.getdata())
|
||||
raw_pal = img.getpalette() # flat [r,g,b, r,g,b, ...], may be shorter than 768
|
||||
raw_pal = (raw_pal + [0] * 768)[:768]
|
||||
palette = [tuple(raw_pal[i:i+3]) for i in range(0, 768, 3)]
|
||||
return width, height, indices, palette
|
||||
|
||||
|
||||
def pcc_to_rgba(pcc_bytes, transparent_index=None):
|
||||
'''Convenience wrapper around load_pcc(): returns (width, height, rgba_bytes)
|
||||
ready for e.g. pygame.image.frombuffer(rgba, (w,h), 'RGBA') or PIL Image.frombytes.
|
||||
If transparent_index is given, that palette index becomes alpha=0.'''
|
||||
width, height, indices, palette = load_pcc(pcc_bytes)
|
||||
out = bytearray(width * height * 4)
|
||||
for i, idx in enumerate(indices):
|
||||
r, g, b = palette[idx & 0xFF]
|
||||
a = 0 if (transparent_index is not None and idx == transparent_index) else 255
|
||||
out[i*4:i*4+4] = bytes([r, g, b, a])
|
||||
return width, height, bytes(out)
|
||||
|
||||
|
||||
def get_bob_palette(entries, name):
|
||||
'''name = basename without extension, e.g. 'TONY' for TONY.BOB.
|
||||
Implements the palette-selection rules from Form1.cs (BOB case).'''
|
||||
|
||||
Reference in New Issue
Block a user