Echter Fix: PCC-Decoder nutzt jetzt ImageMagick statt eigenem RLE-Code
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.
@@ -535,15 +535,86 @@ Projekt gesichert unter `tools/wip_bob_sprite_research/` (noch nicht
|
|||||||
aufgeraeumt/nicht final, bewusst als "work in progress" separiert von den
|
aufgeraeumt/nicht final, bewusst als "work in progress" separiert von den
|
||||||
fertigen Tools `split_pre.py`/`pcc_to_png.py`).
|
fertigen Tools `split_pre.py`/`pcc_to_png.py`).
|
||||||
|
|
||||||
## Naechste Schritte (Prioritaet, Stand 09:30)
|
## KORREKTUR 2026-07-22 (nachmittags) — der "kontinuierliche" Fix oben war FALSCH
|
||||||
1. **BOB/SRB-Sprite-Format weiter reversen** — siehe `tools/wip_bob_sprite_research/`
|
|
||||||
fuer den aktuellen (unfertigen) Exploration-Stand. Naechster konkreter
|
Stefan hat sich das gepushte Repo angeschaut und den Bug SOFORT wieder
|
||||||
Schritt: `PCKELL.DAT`-Offset-Tabelle sauber mit den 32 `SRB.LST`-Namen
|
gesehen (Frosch links statt rechts, Kellogg's-Logo verschoben) — die
|
||||||
abgleichen, danach `A.BOB` (kleinstes Beispiel) testdecodieren.
|
"pixel-perfect verifiziert"-Behauptung im Abschnitt direkt darueber war
|
||||||
2. Rauser-Intro-Sequenz (3 Frames) zu einer zusammengesetzten Vorschau
|
**selbst ein Messfehler**, genau wie der davor schon korrigierte. Beide
|
||||||
|
eigenen Hand-RLE-Decoder (Row-Reset UND kontinuierlich) haben KARTE.PCC/
|
||||||
|
KELLOGGS.PCC nachweislich sichtbar falsch decodiert (Bildinhalt dupliziert/
|
||||||
|
verschoben) — mein visueller Vorher-Vergleich war schlicht nicht sorgfaeltig
|
||||||
|
genug, ich habe der eigenen Automatik zu frueh vertraut.
|
||||||
|
|
||||||
|
**Stefans entscheidender Hinweis:** https://github.com/movAX13h/tony-and-friends-in-kelloggs-land
|
||||||
|
— ein bereits existierendes Reverse-Engineering-Projekt fuer GENAU dieses
|
||||||
|
Spiel (dort "Tony and Friends in Kellogg's Land" genannt, gleiche
|
||||||
|
PCKELL.DAT/PCKELL.SET-Assets). Enthaelt fertigen, funktionierenden C#-Code
|
||||||
|
fuer PCX/BOB/MAP/ARE/ICO/DAT — Quelle: `/tmp/tony-ref` (geklont, kein
|
||||||
|
LICENSE-File im Repo, daher NICHT 1:1-Code uebernommen, sondern nur als
|
||||||
|
Format-Referenz genutzt und in eigenem Python-Code neu umgesetzt).
|
||||||
|
|
||||||
|
Deren `Formats/PCXFile.cs` bestaetigt: `.PCC` ist stinknormales PCX v5,
|
||||||
|
8bpp, RLE, **mit Row-Reset** (pro Zeile wird ein frischer RLE-Reader
|
||||||
|
erzeugt, ueberschuessige Pixel eines Runs am Zeilenende werden verworfen —
|
||||||
|
Standard-PCX-Verhalten, keine Sonderregel). Das war also urspruenglich
|
||||||
|
(Bugfix 1, vormittags) schon RICHTIG vermutet — der eigentliche Bug lag
|
||||||
|
tiefer in der eigenen Implementierung und wurde nie gefunden.
|
||||||
|
|
||||||
|
**Der eigentliche Fix:** Decoder komplett ausgetauscht — `tools/pcc_to_png.py`
|
||||||
|
nutzt jetzt NICHT mehr eigenes RLE-Decoding, sondern `convert pcx:datei.PCC
|
||||||
|
out.png` (ImageMagick's ausgereifter PCX-Decoder, der die Datei einfach
|
||||||
|
per Standard-Format-Erkennung liest). Kein Workaround, sondern die richtige
|
||||||
|
Loesung: unser Format-Verstaendnis war im Kern korrekt, nur die
|
||||||
|
Handimplementierung hatte einen Bug den wir trotz zweier Anlaeufe nicht
|
||||||
|
gefunden haben — ein reifer, jahrzehntealter PCX-Decoder macht das
|
||||||
|
zuverlaessig richtig.
|
||||||
|
|
||||||
|
**Neu verifiziert (diesmal wirklich sorgfaeltig, Bild angeschaut statt nur
|
||||||
|
Byte-Statistik):** `png_out/KELLOGGS.png` und `png_out/KARTE.png` sehen jetzt
|
||||||
|
visuell identisch zu den echten DOSBox-Screenshots aus
|
||||||
|
(`aria_kellogg_titlescreen.png`/`aria_kellogg_worldmap.png`) — Vogelkopf
|
||||||
|
oben links, Drache+Schloss oben rechts, Frosch im Teich, Tiger unten
|
||||||
|
rechts, Coco unten links, alles an der richtigen Stelle, kein Doppel-Bild,
|
||||||
|
keine Kerbe. `FACTOR5.PCC`s "Geister-Doppellogo" ist ueber ImageMagick
|
||||||
|
ebenfalls sauber decodiert und bestaetigt sich damit endgueltig als
|
||||||
|
Absicht (Reflexions-Design), nicht als Bug. Alle 77 PCC-Dateien mit dem
|
||||||
|
neuen Decoder neu erzeugt und liegen in `png_out/`.
|
||||||
|
|
||||||
|
**Nebenfund:** `TONY.PCC`, `SMACKS.PCC`, `TOUCAN.PCC`, `COCO.PCC` (und
|
||||||
|
vermutlich weitere Charakter-Namen) decodieren zu winzigen 1x1-Pixel-Bildern
|
||||||
|
— das ist KEIN Extraktions-Bug. Laut `Form1.cs` der Referenz dienen manche
|
||||||
|
`.PCC`-Dateien nur als **Palette-Lieferant** fuer die zugehoerige `.ICO`/
|
||||||
|
`.BOB`-Sprite-Datei (ICO/BOB haben selbst keine eingebettete Palette,
|
||||||
|
im Gegensatz zu vollen PCC-Bildschirmen). Wichtig fuers spaetere BOB/ICO-
|
||||||
|
Decoding: Palette kommt dann von der PCC-Datei mit gleichem Basisnamen
|
||||||
|
(z.B. `TONY.BOB` -> Palette aus `TONY.PCC`), nicht aus der Sprite-Datei
|
||||||
|
selbst.
|
||||||
|
|
||||||
|
## Naechste Schritte (Prioritaet, Stand nachmittags 22.07.)
|
||||||
|
1. **BOB/SRB-Sprite-Format mit Hilfe der Referenz reversen.** Die Referenz
|
||||||
|
(`/tmp/tony-ref/Unpacker/Formats/BOBFile.cs`, `BOBPainter.cs`) hat das
|
||||||
|
schon geloest — README beschreibt BOB als "data and x86 code to display;
|
||||||
|
somewhat similar to BOB format of Turrican II". Naechster Schritt: deren
|
||||||
|
`BOBFile.cs` genau lesen und die Logik in eigenes Python uebersetzen
|
||||||
|
(nicht 1:1 kopieren, kein LICENSE im Repo), dann gegen `A.BOB`
|
||||||
|
(kleinstes Beispiel) verifizieren. Das sollte den bisherigen
|
||||||
|
`wip_bob_sprite_research/`-Blindflug ueberfluessig machen.
|
||||||
|
2. Genauso `MAPFile.cs`/`AREFile.cs`/`ICOFile.cs` fuer Level-Maps/Areas/
|
||||||
|
Tilesets nutzen statt weiter blind zu raten — auch hier: Format-Wissen
|
||||||
|
uebernehmen, Code selbst neu schreiben.
|
||||||
|
3. Rauser-Intro-Sequenz (3 Frames) zu einer zusammengesetzten Vorschau
|
||||||
rendern/dokumentieren, sobald Zeit ist — niedrige Prioritaet, Assets sind
|
rendern/dokumentieren, sobald Zeit ist — niedrige Prioritaet, Assets sind
|
||||||
schon da.
|
schon da.
|
||||||
3. MAP/ARE-Levelformat reversen.
|
4. Python/pygame-Projektstruktur aufsetzen (PCC-Loader ist jetzt wirklich
|
||||||
4. Python/pygame-Projektstruktur aufsetzen (PCC-Loader ist fertig, muss nur
|
fertig und verifiziert: `convert pcx:datei.PCC out.png` bzw. fuer den
|
||||||
noch von PPM/ImageMagick-Zwischenschritt auf direktes Pillow-basiertes
|
finalen Port direkt Pillow's PCX-Reader nutzen — Pillow unterstuetzt
|
||||||
PNG/Surface-Schreiben umgestellt werden fuer den finalen Port).
|
PCX nativ, damit brauchen wir zur Laufzeit kein externes ImageMagick
|
||||||
|
mehr).
|
||||||
|
|
||||||
|
**Lektion fuer mich selbst (ARIA):** zweimal hintereinander eine "pixel-
|
||||||
|
perfect verifiziert"-Behauptung aufgestellt, die beim tatsaechlichen
|
||||||
|
Hinschauen (Bild anzeigen, nicht nur Bytes vergleichen) klar falsch war.
|
||||||
|
Naechstes Mal: bei jeder Verifikations-Behauptung IMMER das decodierte Bild
|
||||||
|
selbst anschauen (Read-Tool, multimodal), nicht nur Byte-/Pixel-Statistiken
|
||||||
|
vertrauen, bevor ich "geloest"/"verifiziert" sage.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 371 B |
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 590 B |
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 598 B |
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 587 B |
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 571 B |
|
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 483 B |
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 458 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 447 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 527 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 395 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 319 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 352 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 351 B |
|
Before Width: | Height: | Size: 761 B After Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 381 B |
|
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 353 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 353 B |
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 455 B |
|
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 420 B After Width: | Height: | Size: 445 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 419 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 434 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 431 B |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 422 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 835 B After Width: | Height: | Size: 900 B |
|
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 883 B |
|
Before Width: | Height: | Size: 804 B After Width: | Height: | Size: 852 B |
|
Before Width: | Height: | Size: 804 B After Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 775 B After Width: | Height: | Size: 849 B |
|
Before Width: | Height: | Size: 777 B After Width: | Height: | Size: 806 B |
|
Before Width: | Height: | Size: 734 B After Width: | Height: | Size: 800 B |
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 734 B |
|
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 322 B |
@@ -1,109 +1,75 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Finaler PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA).
|
"""PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA) -- v3, ECHTER FIX.
|
||||||
|
|
||||||
Format (verifiziert gegen MENU.PCC/RAUSER1-3/FACTOR5, visuell + byte-exakt,
|
Vorgeschichte (fuer die naechste Session / falls hier nochmal jemand ran will):
|
||||||
UND jetzt zusaetzlich Pixel-fuer-Pixel gegen echte DOSBox-Screenshots von
|
Wir hatten ZWEI eigene Hand-RLE-Decoder gebaut (v1: pro-Zeile-Reset/Trim,
|
||||||
KARTE.PCC und KELLOGGS.PCC verglichen -- siehe Bugfix 2 unten):
|
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.
|
||||||
|
|
||||||
- Byte 0-15: echter PCX-Header-Anfang (Manufacturer=0x0A, Version=5,
|
Fund via Stefans Hinweis auf https://github.com/movAX13h/tony-and-friends-in-kelloggs-land:
|
||||||
Encoding=1/RLE, BPP=8, dann Xmin/Ymin/Xmax/Ymax als LE16 bei Offset 4-11).
|
Deren PCXFile.cs bestaetigt .PCC = stinknormales PCX v5, 8bpp, RLE, EIGENE
|
||||||
WICHTIG (Bugfix 1, 2026-07-22): Width/Height MUESSEN aus Xmax-Xmin+1 /
|
256-Farb-Palette am Dateiende -- KEIN Custom-Format, KEINE Sonderregeln.
|
||||||
Ymax-Ymin+1 berechnet werden. Die 2 LE16-Werte bei Offset 12-15 sehen fuer
|
Das Game selbst listet es im README so: "PCC | Image | PCX version 5,
|
||||||
Vollbild-Screens (320x200) zufaellig identisch aus und wurden erst dafuer
|
encoded, 8 bit per px".
|
||||||
gehalten -- sind aber tatsaechlich NICHT die Bilddimensionen (vermutlich
|
|
||||||
ein DPI/Reserved-Feld wie im echten 128-Byte-PCX-Header), sondern ein
|
|
||||||
Konstantwert der bei kleinen Sprites/Logos (z.B. RAUSER1.PCC: 182x46,
|
|
||||||
FAC0.PCC: 16x16) komplett falsch war und zu kaputten/leeren Bildern fuehrte.
|
|
||||||
|
|
||||||
- Byte 16 .. (len-769): RLE-komprimierte Pixel-Indexdaten.
|
ECHTER FIX: statt eines eigenen RLE-Decoders nutzen wir ImageMagick's
|
||||||
WICHTIG (Bugfix 2, 2026-07-22): Die RLE-Runs werden NICHT pro Scanline
|
ausgereiften, extrem gut getesteten PCX-Decoder direkt (`convert pcx:datei.PCC
|
||||||
zurueckgesetzt/abgeschnitten. Frueher wurde nach `width` Pixeln pro Zeile
|
out.png`). Das ist kein Umgehen des Problems, sondern die richtige Antwort --
|
||||||
hart getrimmt und der Rest eines laufenden RLE-Runs verworfen ("row reset").
|
unser Format-Verstaendnis (Header, RLE-Tupel, Palette) war im Kern korrekt,
|
||||||
Das war FALSCH: dieses Format haelt sich nicht an die klassische PCX-Regel
|
aber die Handimplementierung hatte einen Bug den wir trotz zweier Anlaeufe
|
||||||
"ein Run ueberschreitet nie eine Scanline" -- Runs koennen frei ueber
|
nicht gefunden haben. ImageMagick beherrscht Standard-PCX seit Jahrzehnten
|
||||||
Zeilengrenzen hinweglaufen. Der Beweis: mit Row-Reset waren KARTE.PCC und
|
korrekt. Verifiziert: KARTE.PCC und KELLOGGS.PCC sehen damit jetzt WIRKLICH
|
||||||
KELLOGGS.PCC (die einzigen zwei echten 320x200-Vollbilder) sichtbar verwuerfelt
|
identisch zu den echten DOSBox-Screenshots aus (Vogelkopf oben links, Drache+
|
||||||
(Charaktere an falscher Position, "Kerbe" im Logo-Rahmen), obwohl der
|
Schloss oben rechts, Frosch im Teich, Tiger unten rechts, Coco unten links --
|
||||||
Byte-Konsum fast vollstaendig war -- der Fehler kostet nur ~0.3-0.5% der
|
alles an der richtigen Stelle).
|
||||||
Pixel, aber genau die falschen, wodurch ganze Bildbereiche sichtbar
|
|
||||||
verrutschen. Mit kontinuierlicher Dekodierung (einfach `width*height` Pixel
|
|
||||||
am Stueck aus dem RLE-Strom lesen, OHNE pro-Zeile zu trimmen) sind beide
|
|
||||||
Bilder jetzt Pixel-fuer-Pixel identisch zu echten DOSBox-Screenshots
|
|
||||||
(verifiziert per ImageMagick-Vergleich, siehe NOTES.md). Kleine Sprites
|
|
||||||
waren von diesem Bug kaum betroffen, weil sie selten/nie einen Run ueber
|
|
||||||
eine Zeilengrenze hinweg haben -- deshalb fiel es dort nicht auf.
|
|
||||||
RLE-Tupel: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) = Lauflaenge (&0x3F),
|
|
||||||
gefolgt von einem Wert-Byte. Sonst literaler Pixel.
|
|
||||||
|
|
||||||
- Letzte 769 Bytes: 0x0C-Marker + 768 Byte (256 x RGB) eingebettete Palette
|
Format-Doku (zur Referenz, nicht mehr fuers Decoding gebraucht):
|
||||||
(klassische PCX-v5-256-Farben-Erweiterung). PRO DATEI eigene Palette,
|
- Byte 0-15: PCX-Header-Anfang (Manufacturer=0x0A, Version=5, Encoding=1/RLE,
|
||||||
keine globale Palette noetig.
|
BPP=8, Xmin/Ymin/Xmax/Ymax LE16 bei Offset 4-11). Breite/Hoehe = Xmax-Xmin+1
|
||||||
|
/ Ymax-Ymin+1.
|
||||||
GELOEST (frueher "bekannter offener Bug", Stand vor 2026-07-22 Nachmittag):
|
- Byte 16..(len-769): RLE-Pixeldaten, klassisches PCX-RLE (Byte mit oberen
|
||||||
Das "Kerbe"/Notch-Artefakt neben dem Kellogg's-Schriftzug und die verrutschten
|
2 Bits gesetzt = Lauflaenge&0x3F + Wert-Byte, sonst literaler Pixel),
|
||||||
Charaktere auf KARTE.PCC waren beide der gleiche Bug (Row-Reset, s.o.), NICHT
|
PRO ZEILE auf die Bildbreite abgeschnitten (Standard-PCX-Regel: ein Run
|
||||||
ein horizontales Rollen und NICHT eine Palette-Fehlzuordnung. Mit der
|
ueberschreitet nie eine Scanline -- das war frueher unsere v1-Annahme
|
||||||
kontinuierlichen Dekodierung ist das Artefakt komplett weg.
|
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>
|
Nutzung: python3 pcc_to_png.py <input.PCC> <output.png>
|
||||||
Schreibt ein PPM und konvertiert via ImageMagick `convert`/`magick` zu PNG.
|
|
||||||
"""
|
"""
|
||||||
import sys, struct, subprocess, os, shutil
|
import sys, struct, subprocess, shutil
|
||||||
|
|
||||||
def decode_pcc(data):
|
def read_header(data):
|
||||||
manuf, version, encoding, bpp = data[0], data[1], data[2], data[3]
|
manuf, version, encoding, bpp = data[0], data[1], data[2], data[3]
|
||||||
xmin, ymin, xmax, ymax = struct.unpack('<HHHH', data[4:12])
|
xmin, ymin, xmax, ymax = struct.unpack('<HHHH', data[4:12])
|
||||||
width, height = xmax - xmin + 1, ymax - ymin + 1
|
width, height = xmax - xmin + 1, ymax - ymin + 1
|
||||||
pal_start = len(data) - 769
|
pal_start = len(data) - 769
|
||||||
marker = data[pal_start]
|
marker = data[pal_start] if pal_start >= 0 else None
|
||||||
pixel_region = data[16:pal_start]
|
return dict(manuf=manuf, version=version, encoding=encoding, bpp=bpp,
|
||||||
pal_bytes = data[pal_start+1:pal_start+1+768]
|
width=width, height=height, marker_ok=(marker == 0x0C))
|
||||||
palette = [(pal_bytes[i], pal_bytes[i+1], pal_bytes[i+2]) for i in range(0, 768, 3)]
|
|
||||||
|
|
||||||
total = width * height
|
def decode_with_imagemagick(inp, outp):
|
||||||
out = bytearray()
|
|
||||||
i = 0
|
|
||||||
n = len(pixel_region)
|
|
||||||
while len(out) < total and i < n:
|
|
||||||
b = pixel_region[i]; i += 1
|
|
||||||
if (b & 0xC0) == 0xC0:
|
|
||||||
count = b & 0x3F
|
|
||||||
if i >= n:
|
|
||||||
break
|
|
||||||
val = pixel_region[i]; i += 1
|
|
||||||
remaining = total - len(out)
|
|
||||||
out.extend([val] * min(count, remaining))
|
|
||||||
else:
|
|
||||||
out.append(b)
|
|
||||||
if len(out) < total:
|
|
||||||
out.extend([0] * (total - len(out)))
|
|
||||||
|
|
||||||
return dict(width=width, height=height, marker_ok=(marker == 0x0C),
|
|
||||||
pixels=bytes(out), palette=palette,
|
|
||||||
consumed=i, pixel_region_len=n)
|
|
||||||
|
|
||||||
def write_png(pixels, palette, width, height, out_png):
|
|
||||||
ppm = out_png + '.ppm'
|
|
||||||
with open(ppm, 'wb') as f:
|
|
||||||
f.write(f'P6\n{width} {height}\n255\n'.encode())
|
|
||||||
buf = bytearray()
|
|
||||||
for px in pixels[:width*height]:
|
|
||||||
r, g, b = palette[px]
|
|
||||||
buf.extend([r, g, b])
|
|
||||||
f.write(bytes(buf))
|
|
||||||
convert_bin = shutil.which('magick') or shutil.which('convert')
|
convert_bin = shutil.which('magick') or shutil.which('convert')
|
||||||
args = [convert_bin, ppm, out_png] if 'magick' not in (convert_bin or '') or convert_bin.endswith('convert') else [convert_bin, 'convert', ppm, out_png]
|
if not convert_bin:
|
||||||
subprocess.run(args, check=True)
|
raise RuntimeError("Weder 'magick' noch 'convert' (ImageMagick) gefunden.")
|
||||||
os.remove(ppm)
|
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():
|
def main():
|
||||||
inp, outp = sys.argv[1], sys.argv[2]
|
inp, outp = sys.argv[1], sys.argv[2]
|
||||||
with open(inp, 'rb') as f:
|
with open(inp, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
res = decode_pcc(data)
|
hdr = read_header(data)
|
||||||
print(f"{inp}: {res['width']}x{res['height']} marker_ok={res['marker_ok']} "
|
print(f"{inp}: {hdr['width']}x{hdr['height']} bpp={hdr['bpp']} "
|
||||||
f"consumed={res['consumed']}/{res['pixel_region_len']}")
|
f"marker_ok={hdr['marker_ok']}")
|
||||||
write_png(res['pixels'], res['palette'], res['width'], res['height'], outp)
|
decode_with_imagemagick(inp, outp)
|
||||||
print(f"-> {outp}")
|
print(f"-> {outp}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||