Beide vorherigen Hand-RLE-Decoder (Row-Reset und kontinuierlich) waren trotz gegenteiliger Behauptung sichtbar kaputt (Bildinhalt verschoben/ dupliziert) -- Stefan hat das im gepushten Repo sofort wieder gesehen. Format-Referenz https://github.com/movAX13h/tony-and-friends-in-kelloggs-land bestaetigt .PCC = Standard-PCX v5. Fix: convert pcx:datei.PCC statt eigenem RLE-Parser. Verifiziert (visuell, nicht nur Byte-Stats) gegen echte DOSBox-Screenshots -- KARTE.PCC und KELLOGGS.PCC jetzt wirklich korrekt. Alle 77 PNGs neu erzeugt.
77 lines
3.7 KiB
Python
77 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA) -- v3, ECHTER FIX.
|
|
|
|
Vorgeschichte (fuer die naechste Session / falls hier nochmal jemand ran will):
|
|
Wir hatten ZWEI eigene Hand-RLE-Decoder gebaut (v1: pro-Zeile-Reset/Trim,
|
|
v2: "kontinuierlich" ohne Zeilen-Reset) -- BEIDE haben KARTE.PCC/KELLOGGS.PCC
|
|
sichtbar kaputt dekodiert (Charaktere verschoben, Bild dupliziert/gespiegelt).
|
|
Stefan hat das beim Draufschauen zurecht bemaengelt ("Frosch waere rechts,
|
|
nicht links" / Logo verschoben). v2 wurde faelschlich als "pixel-perfect
|
|
verifiziert" dokumentiert -- das war ein Verifikations-Fehler, nicht die
|
|
Wahrheit; visuell war es klar erkennbar kaputt.
|
|
|
|
Fund via Stefans Hinweis auf https://github.com/movAX13h/tony-and-friends-in-kelloggs-land:
|
|
Deren PCXFile.cs bestaetigt .PCC = stinknormales PCX v5, 8bpp, RLE, EIGENE
|
|
256-Farb-Palette am Dateiende -- KEIN Custom-Format, KEINE Sonderregeln.
|
|
Das Game selbst listet es im README so: "PCC | Image | PCX version 5,
|
|
encoded, 8 bit per px".
|
|
|
|
ECHTER FIX: statt eines eigenen RLE-Decoders nutzen wir ImageMagick's
|
|
ausgereiften, extrem gut getesteten PCX-Decoder direkt (`convert pcx:datei.PCC
|
|
out.png`). Das ist kein Umgehen des Problems, sondern die richtige Antwort --
|
|
unser Format-Verstaendnis (Header, RLE-Tupel, Palette) war im Kern korrekt,
|
|
aber die Handimplementierung hatte einen Bug den wir trotz zweier Anlaeufe
|
|
nicht gefunden haben. ImageMagick beherrscht Standard-PCX seit Jahrzehnten
|
|
korrekt. Verifiziert: KARTE.PCC und KELLOGGS.PCC sehen damit jetzt WIRKLICH
|
|
identisch zu den echten DOSBox-Screenshots aus (Vogelkopf oben links, Drache+
|
|
Schloss oben rechts, Frosch im Teich, Tiger unten rechts, Coco unten links --
|
|
alles an der richtigen Stelle).
|
|
|
|
Format-Doku (zur Referenz, nicht mehr fuers Decoding gebraucht):
|
|
- Byte 0-15: PCX-Header-Anfang (Manufacturer=0x0A, Version=5, Encoding=1/RLE,
|
|
BPP=8, Xmin/Ymin/Xmax/Ymax LE16 bei Offset 4-11). Breite/Hoehe = Xmax-Xmin+1
|
|
/ Ymax-Ymin+1.
|
|
- Byte 16..(len-769): RLE-Pixeldaten, klassisches PCX-RLE (Byte mit oberen
|
|
2 Bits gesetzt = Lauflaenge&0x3F + Wert-Byte, sonst literaler Pixel),
|
|
PRO ZEILE auf die Bildbreite abgeschnitten (Standard-PCX-Regel: ein Run
|
|
ueberschreitet nie eine Scanline -- das war frueher unsere v1-Annahme
|
|
und war tatsaechlich richtig, nur unsere Implementierung hatte woanders
|
|
einen Bug).
|
|
- Letzte 769 Byte: 0x0C-Marker + 768 Byte (256 x RGB) eigene Palette pro Datei.
|
|
|
|
Nutzung: python3 pcc_to_png.py <input.PCC> <output.png>
|
|
"""
|
|
import sys, struct, subprocess, shutil
|
|
|
|
def read_header(data):
|
|
manuf, version, encoding, bpp = data[0], data[1], data[2], data[3]
|
|
xmin, ymin, xmax, ymax = struct.unpack('<HHHH', data[4:12])
|
|
width, height = xmax - xmin + 1, ymax - ymin + 1
|
|
pal_start = len(data) - 769
|
|
marker = data[pal_start] if pal_start >= 0 else None
|
|
return dict(manuf=manuf, version=version, encoding=encoding, bpp=bpp,
|
|
width=width, height=height, marker_ok=(marker == 0x0C))
|
|
|
|
def decode_with_imagemagick(inp, outp):
|
|
convert_bin = shutil.which('magick') or shutil.which('convert')
|
|
if not convert_bin:
|
|
raise RuntimeError("Weder 'magick' noch 'convert' (ImageMagick) gefunden.")
|
|
if convert_bin.endswith('magick'):
|
|
args = [convert_bin, f'pcx:{inp}', outp]
|
|
else:
|
|
args = [convert_bin, f'pcx:{inp}', outp]
|
|
subprocess.run(args, check=True, capture_output=True)
|
|
|
|
def main():
|
|
inp, outp = sys.argv[1], sys.argv[2]
|
|
with open(inp, 'rb') as f:
|
|
data = f.read()
|
|
hdr = read_header(data)
|
|
print(f"{inp}: {hdr['width']}x{hdr['height']} bpp={hdr['bpp']} "
|
|
f"marker_ok={hdr['marker_ok']}")
|
|
decode_with_imagemagick(inp, outp)
|
|
print(f"-> {outp}")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|