WIP Gameengine

  • [WIP]

    Moin, ich bastel grad an einer kleinen Spielengine - mal schauen ob es ein Projekt wird was ich auch mal fertigstelle :D


    Inhaltsverzeichnis

    - Grundsätzliche Definitionen (Anforderungen an die Engine und deren Bestandteile): weiter unten in Diesem Beitrag.

    - Dateiformate

    - Hilfsprogramme

    - System

    - Sound

    - Grafik

    - usw.



    Die Engine:


    Die Anforderungen an die Engine sind wie folgt:

    - für Adventures geeignet (also nicht unbedingt tauglich für Actionspiele)

    - 640x480 Pixel in 8 Bit Farbtiefe

    - Animationen

    - PCM Sound per Creative ADPCM bis 11kHz (im Grunde genommen *.Voc Files - allerdings entferne ich vorher die Blockheader, keine Lust mich zur Laufzeit damit herumzuschlagen), bzw. Wav bis 22khz in 8 Bit - diese Formate können eben von allen Soundblasterkompatiblen abgespielt werden.

    - Spielablaufsteuerung mittels kompilierten Skripten

    - Maus- und Tastatursteuerung

    - Savegames - am besten mit Aufwärtskompatibilität damit alte Savegames auch in neueren Spielversionen funktionieren (können)


    Technisches:


    Sound:


    Kurz und knapp, Soundblaster 2 Kompatibilität ist angestrebt, d.h.

    - Soundwiedergabe per DMA und Soundkarteninterrupt

    - Creative ADPCM mit bis zu 11kHz in Mono, bzw. 8Bit PCM mit bis zu 22kHz in Mono



    Grafik:


    Wir haben 256 Farben bei 640x480. 256 Farben sind nicht viel, aber glücklicherweise ist der 8Bit Mode indexed - das heißt: Malen nach Zahlen!

    Statt der Farbe wird eine Farbnummer gespeichert, diese wiederum zeigt auf eine Stelle in unserer Palette in der dann die tatsächliche Farbe zu finden ist - und diese Palette können wir relativ frei definieren, damit können wir die Palette so wählen dass sie nur Farben enthält die wir auch nutzen.

    Also auch wenn nur 256 Farben zur Verfügung stehen, können wir wenigstens dafür sorgen dass es genau die Farben sind die wir auch nutzen.


    Nachteil:

    Diese 256 Farben gelten nicht für jedes einzelne Bild/jeden einzelnen Sprite, sondern für den gesamten Bildschirm. Wenn wir also mehrere Bilder gleichzeitig anzeigen müssen sich diese Bilder die 256 Farben teilen.


    Noch eine kleine Anmerkung: 24/32 Bit Bitmaps verwenden eine Farbtiefe von 8 Bit pro Farbkanal, der Standard VGA DAC ist aber 6 Bittig, d.h. 2 Bit Pro Farbkanal können nicht dargestellt werden.

    Ich muss also die Bitmaps gewissermaßen auf 18 Bit Farbtiefe reduzieren - das geht allerdings einfach mit einem shr 2.


    Die Unterteilung des Farbraumes:


    Sichtbare Bildelemente Ingame:

    - User Interface

    - Hintergrund

    - bis zu 4 Charaktere

    - 1 Portrait


    Sichtbare Bildelemente Zwischenszene:

    - User Interface

    - Szene

    - 1 Portrait


    Farben:

    016 User Interface

    024 Portrait

    024 Charakter

    120 Hintergrund (256 - (User Interface (16)+4*Charakter(24)+Portrait(24)) )

    216 Szene (256 - (User Interface (16)+Portrait(24))


    Bei den Farben gibt es noch eine Besonderheit: die UI ist immer vorhanden und beinhaltet Transparenz - Bilder die Farben der UI nutzen bekommen Quasi mehr Farben.

    Bleistift: in der UI (16 Farben) ist die Farbe Schwarz enthalten, in dem Hintergrundbild (120 Farben) ist ebenfalls die Farbe schwarz enthalten - d.h. der Hintergrund kann das Schwarz der UI nutzen statt ein eigenes schwarz zu definieren und kann daher 121 Farben nutzen - das wird bei der Bilddekodierung etwas mehr Aufwand, aber verbessert die Farbwiedergabe.


    Bildgrößen:


    Diese Engine entsteht weil ich ein Spiel portiere (ob ich es veröffentliche hängt davon ab, ob ich es fertigstelle und der eigentliche Entwickler es erlaubt) und daher muss ich mit vorhandenem Material arbeiten und die Engine ist letztlich genau für dieses Spiel angepassr, daher auch folgende Bildgrößen:

    - Hintergrund/Szene 640x360

    - Charakter 256x360

    - Portrait 93x108

    - GUI 640x120


    Skriptengine/Compiler:


    Das Compilierte Skript: (WIP)


    Das hier ist noch stark in überarbeitung....


    - Die darstellbaren Zeichen werden genullt, d.h. A=0,B=1 usw. - das macht den Interpreter einfacher

    - Befehle sind Zeichen >=$80 - d.h. Zeichen ab Zeichen 128 aufwärts markieren Befehle - das macht letzlich 128 verschiedene Befehle, sollte reichen.

    - Variablen/Objekte werden nicht mit Klarnamen gespeichert, sondern über eine 16Bit ID - das macht die Kompilierung der Skripte komplizierter (und ich brauche ab der ersten kompilierung eine Ressourcendatei um sicherzustellen dass immer dieselben IDs vergeben werden - sonst funktionieren die Savegames nicht Versionsübergreifend), aber den Interpreten deutlich einfacher


    Befehle:


    - IF (Ausdruck) Befehle ELSE Befehle ENDIF- Dieser Befehl muss gefolgt werden von einem Ausdrucksbefehl


    - Ausdrucksbefehle:

    -- Equal (Variable, Konstante)

    -- Equal(Variable,Variable)

    -- Equal(Operation,Konstante)

    -- Equal(Operation,Variable)

    -- Equal(Operation,Operation)

    -- Dasselbe nochmal mit >,<,>=,<=

    --- Der Befehlsumfang ließe sich mit der Verwendung von 4 reservierten Tempvariablen (pro Seite des Ausdrucks brauche ich 2 Tempvariablen) reduzieren auf Ausdruck(Variable, Konstante) + Ausdruck(Variable,Variable)

    --- vermutlich werde ich den reduzierten Ansatz wählen - scheint einfacher, sinnvoller und belastet das Befehlsbudget weniger


    - Operationsbefehle: (reduzierter Ansatz (Tempvariable))

    -- shl (Variable,Konstante) (max 15)

    -- shl (Variable,Variable) (max 15)

    -- shr (Variable,Konstante) (max 15)

    -- shr (Variable,Variable) (max 15)

    -- not (Variable)

    -- mul (Variable, Konstante)

    -- mul (Variable, Variable)

    -- div (Variable,Konstante)

    -- div (Variable,Variable)

    -- add (Variable,Konstante)

    -- add(Variable,Variable)

    -- sub (Variable,Konstante)

    -- sub (Variable,Variable)

    -- and (Variable,Konstante)

    -- and (Variable,Variable)

    -- or (Variable,Konstante)

    -- or (Variable,Variable)

    -- xor (Variable,Konstante)

    -- xor (Variable,Variable)


    Steuerbefehle

    - Jump (LocationID) - Springt zu einer anderen Stelle im Skript

    - Set (Variable, Operation)

    - LoadPicture (Position, ID, PaletteID, Variant)

    - LoadCharakter(ID, PaletteID, Variant, Preffered Slot) - ist dafür da weil es nur 4 Charakterslots gibt und es Situationen gibt in denen mehrere Charaktere auftauchen die gerne an derselben Position wären)

    - LoadAnim (Position, ID, PaletteID, TimerDivider-1,Loop)

    - Write (Position, Speed, String)

    - PlaySound(Name/ID,Loop)

    - StopSound

    - LoadGame

    - SaveGame

    - Quit

    - LoadClickableMap(Position, Handler) - wie machen wir das mit UI/Game? Sind 2 verschiedene Handler... - Daisychain?


    Das User Interface


    Für die Mausbedienung werde ich mich einer ClickAble-Map bedienen, vereinfacht gesagt ist dies eine Art Bild bei dem der Farbwert angibt was geklickt wurde.


    Bei jedem Klick lasse ich mir vom Maustreiber die Cursorposition mitliefern, bestehend aus X und Y - damit hole ich mir einfach direkt von der Clickable Map den Rückgabewert und erspare mir jede Menge Vergleichsarbeit.


    Rückgabewert:=Clickablemap[ (Cursorpos_Y*640)+Cursorpos_X ];


    Ist eben deutlich schneller als:


    If (Cursorpos_x >120) and (Cursorpos_x<160) and (Cursorpos_y >20) and (Cursorpos_y<90) then Aktion 1

    Else If (Cursorpos_x >100) and (Cursorpos_x<130) and (Cursorpos_y >200) and (Cursorpos_y<210) then Aktion 2

    Else If....


    Ich werde jedoch die Auflösung der Clickable Map vierteln - damit hat diese nur noch 19200 bytes und passt somit in ein 64kb Segment und 4x4 Pixel sind als kleinstes klickbares Objekt auch angemessen.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

    10 Mal editiert, zuletzt von Dosenware ()

  • Dateiformate:


    Hier geht es erstmal Gundsätzlich um den Aufbau der verschiedenen Dateiformate die ich einsetze.


    Bildressourcendatei


    Dies ist eigentlich ein Sammelbecken an Bilddateien, damit erspare ich mir Overhead und damit kostbaren Festplattenspeicher.

    Festplattenspeicher ist eingeteilt in Cluster, diese sind üblicherweise 4-16kb groß und stellen die kleinste addressierbare Einheit im Dateisystem dar.


    Das heißt: jede Datei - und sei sie auch nur ein byte groß - belegt mindestens einen Cluster.

    Es macht also keinen Unterschied ob eine Datei aus nur einem byte, oder aus 16000 bytes besteht - beide belegen 1 Cluster und damit 16kb (bei 16kb Clustern) - wenn ich also mit mehreren tausend Dateien zu tun habe, kann da schon eine Menge Verschnitt anfallen.

    Um dies zu umgehen packe ich die kleinen Dateien zusammen in eine große Datei:

    Nur damit ihr wisst was das Ausmacht: die unkomprimierten 24Bit BMPs für die Hintergründe sind je 901kb groß, bei 1321 Dateien macht das 1,13Gb - belegt werden aber 1,28Gb, ich habe da also allein 150Mb Verschnitt allein durch das Dateisystem und das Verhältnis von Dateigröße zu Verschnitt wird tendenziell immer schlechter je kleiner die Dateien sind)


    Header der Ressourcendatei:

    - Liste der Dateipositionen, longint, terminiert mit $FFFF FFFF - Die BildID entspricht einfach der Reihenfolge der Dateipositionen (der erste Eintrag in der Liste ist also ID=0, der 2te Eintag ist ID=1, usw. - entspricht im Vollausbau mit 65536 Einträgen einer Tabellengröße von 256kb)


    Header der Bild/Animationsdateien:

    - Breite, word

    - Höhe, word

    - Anzahl der Farben, byte

    - Anzahl der Paletten, byte

    - Anzahl der Varianten, word - Bit 15: 1=Sequenziell (Animation), 0=Absolut (Variante)

    - Variant3+ Offset, longint - dies ist eine Liste ähnlich der im Header (ohne terminierung), der Offset ist relativ zum Start der Bilddatei und nur vorhanden wenn es mehr als 2 Varianten gibt

    - Palettedata, eine Liste von Longints (aka 4 Bytes) die Länge der Liste entspricht der "Anzahl der Farben" - es gibt "Anzahl der Paletten" viele Listen


    Ein Wort zu den Paletten:

    Tag/Nacht sehen häufig gleich aus, nur die Farben Unterscheiden sich - statt nun umständlich das komplette Bild, inklusive seiner Palette erneut zu speichern - kann ich auch einfach nur die Palette speichern, dann lade ich EIN Bild und schalte nur die Palette um wie z.b. hier zu sehen: Vesa und die Tücken 16 Bit Protected Mode


    Ein Wort zu Varianten:

    Varianten sind Bilder die sich nur in wenigen Details vom Ursprungsbild unterscheiden, z.b. Tür zu- > Tür Auf oder auch Licht im Fenster an -> Licht im Fenster aus.

    Für die Varianten werden nur die Unterschiede zum Ursprungsbild gespeichert, der Rest wird auf Transparent/NAC (Not a Color) gesetzt.


    Bilddaten:


    Wie man im vorherigem Post sieht nutzt kein Bild alles 256 Farben, damit können wir - im zusammenhang mit der niedrigen Farbzahl - eine einfache Kompession umsetzen:


    Der Wert $FF entspricht dem Befehl Repeat - gefolgt von der Anzahl und der Farbe:

    $FF $08 $A1 entspricht also folgenden Bilddaten: $A1 $A1 $A1 $A1 $A1 $A1 $A1 $A1

    Ich kann damit im Extremfall 256 bytes auf 3 bytes reduzieren - dafür benötige ich aber einfarbige Flächen - dithering ist in dem Fall eher schlecht (und sieht ohnehin Kacke aus)

    Ich konnte damit (bzw. einem Vorgänger der allerdings nur 128 Farben unterstützte - dort war Bit 7 ($80) der Trigger, die unteren 7 Bit gaben die Anzahl an)) ein Bild von 226kb auf 36 kb reduzieren, als Gif sinds 33kb, als png 26kb - für die Einfachheit und damit der Schnelligkeit des Verfahrens garnicht mal schlecht.

    Evtl. werde ich die alte und neue Variante implementieren, die alte ist etwas effizienter, die neue kann dafür auch die Szenen (216 Farben).

    Für die Paletten muss ich mir jedoch noch etwas überlegen aktuell besteht jeder eintrag aus 4 bytes, davon werden 3 genutzt. Die Konvertierung von 3 auf 4 Bytes kostet wieder Zeit, kann aber bis zu 216 bytes einsparen.


    NAC/Transparenz: ist einfach der Wert $FE - die Farbe wird dann einfach nicht dargestellt d.h. der Pixelzähler wird einfach erhöht.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

    9 Mal editiert, zuletzt von Dosenware ()

  • Hilfsprogramme


    Als erstes geht es mal mit dem aktuellen Roadblock los:


    Die Bildbearbeitung/Konvertierung



    Seht ihr das Krieseln an den Kanten? (in Falschfarben gut sichtbar) Das sind alles verschenkte Farben - ich habe das Bild schon kräfig nachbearbeitet um die Farbzahl zu reduzieren, ohne dass es mistig ausschaut (Farbbänder, Dithering) - aber das Ergebnis könnte besser sein.

    Doch dazu muss ich die überschüssigen Farben loswerden um sie an anderer Stelle nutzen zu können - z.b. für einigermaßen brauchbare Farbverläufe.


    Ebenso muss ich die Bilder für multiple Paletten und Varianten/Animationen vorbereiten und dazu brauche ich ein Tool, also hier Kurz die Anforderungen.


    - Bei Laden eines Bildes automatisch Konvertierung auf 18 Bit, dann sind schoneinmal die nicht darstellbaren Farben raus (bei 24/32 Bit Farbtiefe werden 8Bit pro Farbkanal genutzt, bei VGA/Vesa sind 6 Bit pro Kanal normal)

    - Zählen der Farben und aufsteigend sortiert anzeigen, sowie anzeigen einiger Bildinformationen

    - Mausbedienung + Hotkeys

    - Highlightfunktion -> selektierte Farbe wird negiert/durch Spezialfarbe ausgetauscht um sichtbar zu machen wo sie verwendet wird

    - ersetzen von Farben durch Pick and Place (sowohl im Bild, als auch in der Palette)

    - Undofunktion - d.h. zwischenspeichern

    - anzeige 2er Bilder simultan

    - Blättern durch Bilder für Animationen/Varianten

    - Laden von Bildgruppen und deren Farbpaletten

    - Laden von Farbpaletten

    - umarangieren von Farbpaletten

    - bei Laden von Varianten/Animationen automatisch Differenzbilder erzeugen. (Transparenz als $FE $FE $FE speichern, diese Farbe ist eh nicht darstellbar)

    - beim Laden von Bildern(Bildgruppen) Arbeitskopie als BMP erzeugen, die alle geladenen Bilder enthält - so habe ich nur eine offene Datei, statt bis zu 60

    - Speichern im eigenen Bildformat


    Die Probleme beginnen schon in Dosbox - 1280x960/1024 werden nicht in 24/32 Bit unterstützt, bei dieser Farbtiefe sind maximal 800x600 Pixel möglich, was die gleichzeitige Anzeige 2er 640x480 Bilder erschwert.


    Ein erstes Konzept sieht so aus:


    Die bunten Kästchen sind die Paletteneinträge - die leeren weißen Kästchen daneben sind Platzhalter, dort soll später eine 4 stellige Hexadezimale Zahl stehen die die Zahl der Pixel angibt die jene Farbe verwenden.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

    4 Mal editiert, zuletzt von Dosenware ()

  • Entwicklung Hilfsprogramm: Bildbearbeitung/Konvertierung


    Zählen ist schwer:

    Was erstmal doof klingt, hat mich jetzt eine ganze Weile beschäftigt.


    Ich zähle 32bittige Farbwerte in Bildern die maximal 640x360 Pixel groß sind - 640x360 ist mehr als der Datentyp word aufnehmen kann, also brauche ich Longint - theoretisch sind wir also bei 4Milliarden x 4 Milliarden Werten... 16 Gb... das ist viel, glücklicherweise lässt sich das reduzieren:

    - große Zählwerte interessieren mich nicht, ich will die wenig genutzten Farben loswerden (einzelne Pixel mit leicht anderer Farbe sieht man eh nicht) - d.h. als Zählwert reicht mir word (65535 max)

    - Im 32 Bit Farbwert ist ain Alphakanal mit enthalten - den nutze ich nicht d.h. wir landen bei 24 Bit - da der VGA-DAC nur mit 6 Bit Pro Kanal arbeitet, kann ich die unteren 2 Bit jedes Farbkanals nullen und arbeite faktisch nur mit 18Bit =262144 Farbwerte

    - viel wichtiger ist allerdings: 640x360=230400 Pixel - damit also auch nur maximal 230400 Farbwerte


    Am Ende landen wir bei 230400 Farbwerten (32Bit) + 230400 Zählwerten (16 Bit) = 1.382.400 bytes - 1,35 Mbyte - immernoch viel, aber handlebar.


    Und nun kommt noch die Segmentierung des Speichers hinzu - jede Datenstruktur in Pascal kann maximal 65535 bytes einnehmen, also muss ich meine 1,35Mbyte in handhabbare Häppchen zerlegen.


    Nächstes Thema: Geschwindigkeit.

    Für jeden einzelnen Pixel meine Zählliste (mit maximal 230400 Einträgen) zu durchsuchen frisst wahnsinnig viel Zeit, also mache ich mir zunutze dass benachbarte Pixel wahrscheinlich dieselbe Farbe haben und vergleiche die Farbwerte der Pixel miteinander während ich sie Zähle - in die Zählliste wird das ganze erst dann eingetragen, wenn ich auf ein Pixel mit anderer Farbe stoße.


    Mit 8000 cycles (soll wohl einem 386er mit 33 MHz entsprechen) braucht das Konstrukt immernoch etwas über 18 Minuten - ist aber schonmal deutlich schneller als die erste Version, die wirklich jeden Pixel mit der Liste verglich, die 28,5 Minuten brauchte - obwohl die Ursprungsversion nur mit einer 4096 Einträge langen Liste arbeitete...


    Das Testbild hat übrigens 6185 verschiedene Farben (nach Reduktion auf 6Bit/Kanal) - wovon 2006 Farben nur von einem einzelnen Pixel genutzt werden und 4705 Farben werden von 1-9 Pixeln genutzt - klar dass ich die loswerden will - die fressen nur den besser sichtbaren Bereichen die Farben weg.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Pixel ersetzen:


    Ich arbeite gerade daran automatisch Pixel ersetzen zu lassen, genauer gesagt will ich Farben, die nur 9mal - oder weniger - im Bild vorkommen automatisch durch andere, sehr ähnliche - aber häufiger verwendete - ersetzen lassen.

    Hier sieht man ganz gut welche einzelnen Farbpixel ersetzt werden - die Farbvergleichsoperation muss ich allerdings noch korrigieren.



    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • nein, das Gegenteil. Ich brauche eine Farbreduktion auf 120/216 (Hintergrund/Szene) Farben - leider sieht das mit der Farbreduktion über die normale Bildbearbeitung schrecklich aus, drum versuche ich das jetzt selbst und will erstmal die "unsichtbaren" Farben loswerden.


    Das Bild hat übrigens nach der Konvertierung zu 6Bit/Farbkanal (VGA Dac) ~6500 Farben, wovon etwa 5000 nur auf 1-9 Pixeln vorkommen - wie du siehst: fast alle sind an den Kanten.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Dithering ist Mist - sieht Kacke aus, als hätte das Bild Masern. Dithering kannst du bei hochauflösenden Bildern machen, da störts nicht.

    Die verwendeten Bilder haben üblicherweise nicht viele Farben - vielleicht mal einen Farbverlauf, aber sonst viele einfarbige Flächen, die meisten Farben tauchen eben an den Kanten auf.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Ich werde das ganze wohl Umstellen auf einen Vergleich mit den Umgebungspixeln:

    -Wenn die 8 angrenzenden Pixel eine andere Farbe als das aktuelle Pixel haben, wird die Farbe des Pixels ersetzt - sofern die Farbabweichung zu einem der benachbarten Pixeln 1 1 1 ist (das niederste Bit in jedem Farbkanal)


    Abweichungen der Art 1 1 0, 1 -1 1 und ähnlich sind nicht zulässig weil sich sonst die Farbe ändern würde...

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Mal ein kleiner Bildervergleich:

    Original (auf 640x360 resized, 8Bit pro Farbkanal) - 27551 Farben



    Auf VGA Umgestellt (6 Bit pro Farbkanal ) - 6185 Farben


    Mit obiger Methode (siehe das Bild mit falsch gefärbten Kanten) bearbeitet - 1895 Farben


    Mal schauen was rauskommt wenn ich jedes Pixel mit seinen 8 Nachbarn vergleiche (siehe Posting eins weiter oben) - die Farbzahl dürfte dann jedenfalls nochmals massiv sinken.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Ich habe mal Spaßeshalber den Algorithmus unbeschränkt (oben sucht er nur nach alternativen für Farben die weniger als 10 Mal vorkommen) durchlaufen lassen: - 498 Farben



    PS. da sind übrigens immer noch 68 Farben drin, die nur als einzelne Pixel vorkommen...

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Hmm...


    Bugs:

    - habe versucht das Ganze auf dem Epson Kassenpc zum laufen zu bekommen - erstmal hatte ich einen Bug in der SB Unit, wenn ich vorher nicht eine Datei abgespielt habe hat SB_Done zum Absturz geführt.

    - INT 30 AX=300h (http://www.ctyme.com/intr/rb-5831.htm) führt zum Absturz, die Pascalfunktion INTR scheint zu funktionieren - mal schauen was passiert wenn ich Speicheradressen übergeben muss...


    BTW. Ich überlege auf 16Bit Farben hochzugehen

    - ist einfacher zu programmieren, weil deutlich weniger Verwaltungsaufwand

    - es ist schwierig eine brauchbare Qualität mit 256 Farben hinzubekommen (läuft letztlich auf sehr viel Handarbeit hinaus), speziell da in dem Spiel noch Bilder in einem anderen Artstyle auftauchen den ich bei 8Bit definitiv dithern müsste

    - die obigen Reduktionsroutinen werde ich in modifizierter Form dennoch anwenden einfach um die Bilder besser komprimieren zu können

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Hmm, Eine Idee was die Gelb markierten Stellen vom BMP Header bedeuten?


    Die Farbtiefe steht eigentlich in 1C, aber wenn ich die Gelb markierten Stellen nicht auf diese Werte ändere ist das Bild angeblich 32/24 bittig, wird aber dennoch geometrisch richtig, aber eben mit Falschfarben dargestellt.


    Die Bilddaten beginnen an Offset $46 und ich habe jetzt auf Anhieb keinen Header gefunden der in dieses Schema passt



    PS. Die 32/16 Konvertierung sieht so aus:



    Pic.P_arr[i2]^[i3]:=

    =((bmp32^[i3][2] and $F8)shl 8 ) (Rot)

    +((bmp32^[i3][1] and $FC)shl 3) (Grün)

    + (bmp32^[i3][0] shr 3); (Blau)


    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Eine mögliche Lösung:


    F8 00= 1111 1000 0000 0000

    07 E0= 0000 0111 1110 0000

    00 1F= 0000 0000 0001 1111


    Das ist das Pixelformat -> wie werden die Bilddaten in die Farbkanäle zerlegt...

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Bei der Bildvorbereitung weitergemacht - jetzt in 16 Bit.


    Funktionsweise:


    Schritt 1: 32Bit Bild auf 16Bit reduzieren.

    Die Daten sind im Format RGBA - wobei der Alphakanal nicht genutzt wird, die Umsetzung auf 16 Bit geschieht mit der Funktion

    Pixel16:=((R and $F8) shl 8 ) + ((G and $FC) shl 3) + (B shr 3)



    Schritt 2: Farben Zählen, sortieren nach Häufigkeit und "unsichtbare" Pixel herausfiltern

    Wenn dort einzelstehende Pixel sind - d.h. die haben keine benachbarten Pixel gleicher Farbe) - die von der Umgebung nur um eine Helligkeitsstufe abweichen, sind diese praktisch unsichtbar.

    D.h. es fällt nicht auf wenn ich deren Farbe leicht anpasse (siehe auch: WIP Gameengine) - damit spare ich Farben, was für eine Bildkompression recht hilfreich ist.

    Immerhin sprechen wir hier von über 500MB an Bildmaterial (auf 640x360 16Bit reduziert) - BIS JETZT.

    Ist etwas viel für die Zielsysteme - das muss also kleiner werden.


    Die Reduktionsfunktion ist noch nicht vollständig - aktuell wird nicht der oben beschriebene Ansatz verwendet, stattdessen wird die Farbanpassung stur auf alle Farben angewendet die im Bild weniger als 10 mal vorkommen - ist so schon lahm genug...



    Schritt 3: Kompression
    Die Kompression funktioniert tatsächlich mit indizierten Farben, das Bild wird 8 Bittig gespeichert - mit kleinen Tricks:


    1. Ich nutze nicht alle 256 Möglichkeiten der 8 Bitzahlen für Farben, stattdessen sind aktuell die oberen 3 "Farben" für Befehle reserviert.


    2. Ich passe die Palette zur Laufzeit an - taucht im Bild eine Farbe auf die nicht in der aktuellen Palette ist, schmeiße ich eine wenig verwendete Farbe aus der Palette und setze dafür eine neue Farbe ein.

    Dafür gibt es den Befehl: Replace Palette entry ($FE) - in den Bilddaten sieht das dann so aus:


    $FE XX WWWW -- $FE ist der Befehl, XX der Paletteneintrag, WWWW die neue Farbe


    3. Der Befehl Repeat: Die obere Reduktionsfunktion ziehlt darauf ab möglichst große einfarbige Flächen hinzubekommen, ohne das die Bildqualität nennenswert darunter leidet - Denn: einfarbige Flächen sind super - einfach die Farbe speichern und wie oft sie sich wiederholt: fertig.

    In den Bilddaten schaut das dann so aus:


    $FF XX WWWW -- $FF ist der Befehl, XX die Farbe, WWWW die Anzahl an Wiederholungem - im Extremfall bekomme ich damit 128kb auf 4 Bytes zusammengequetscht.


    4. Noch nicht implementiert, aber für später angedacht: Repeat not a Pixel -> Transparenz, diese Pixel werden einfach Übersprungen.

    Von den Bildern gibt es mehrere Varianten mit nur kleinen Unterschieden, sowie Animationen. Indem ich ein Referenzbild speichere und in den Varianten nur die Unterschiede zum Referenzbild (oder bei Animationen: die Unterschiede zum Vorgängerbild), kann ich wahnsinnig viel Speicher sparen.


    5. Noch nicht implementiert aber als nächstes Ziel: Mehrere Paletten.

    Nützlich für Tag/Nachtvarianten, die sich nicht im Bild, sondern nur in den Farben unterscheiden - man speichert einfach 2 Paletten, aber die Struktur nur einmal.


    Was das Bedeutet: dieses Bild verwendet aktuell 1877 Farben - wenn ich noch die Nachtvariante darin Unterbringen will sind das im Idealfall nur 3754 zusätzliche bytes die gespeichert werden müssen - realistischer sind aber 10kb, immernoch recht klein.


    Bei der Kompression gibt es noch Baustellen:


    Der Pixel für Pixel Vergleich in der Farbreduktion fehlt noch.


    In der Kompression ist die "Replace Palette Entry" Funktion noch nicht optimal umgesetzt

    Aktuell wird einfach der Eintrag, der am wenigsten im Bild genutzt wird, ersetzt - was dazu führt dass evtl. eine Farbe durch eine andere ersetzt wird, aber direkt für den nächsten Pixel gebraucht wird - ich habe also noch unnötig viele Ersetzungen für die Palette.

    Ich werde diese Funktion überarbeiten so dass - sofern der Zähler für einen Eintrag nicht auf 0 gesunken ist (ich zähle wie oft jede Farbe im Bild vorkommt - während der Kompression werden die Zähler entsprechend verringert) - ich im Bild weitergehe und die Palettennutzung protokolliere bis nur noch ein Eintrag überbleibt der noch nicht genutzt wurde, dieser wird dann ersetzt.

    Auf diese weise reduziere ich die Nutzung des "Replace Palette Entry" auf ein Minimum.


    Und allgemein muss ich den QT überarbeiten... aktuell ist er... naja, er verwirrt den Compiler derart, dass dieser etwas produziert was funktioniert und es ist lahm wie Hölle - macht aber nicht wirklich was, Hauptsache die dekompression ist Flott.


    Das obige Bild ist übrigens 451kb als BMP, 134kb im vorläufigen nicht optimierten Format und 65kb als PNG


    QT: BMPPREP2.7z


    EDIT: vorher hatte ich den falschen QT angehongen

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

    2 Mal editiert, zuletzt von Dosenware ()

  • Das Bildformat:


    Header:

    Offset 0 - Width, Height: word; - gibt die Größe des Bildes an

    Offset 4 - Palettes:byte; - gibt die Anzahl an Paletten an (0=1)

    Offset 5 - Palettesize:byte; - Die Größe der Startpalette, max 252;

    Offset 6 - Variants:byte; - Anzahl der Varianten

    Offset 7 - Varianttype/Transparency:byte;

    Offset 7 - Bit 0 gibt den Variantentyp an, 0=Das Ursprungsbild wird als Referenz genutzt, 1=Die Vorgängervariante wird als Referenz genutzt (nützlich für Animationen)

    Offset 7 - Bit 7 Tranzparenz Aus/An

    Offset 8 - Transparenzfarbe:word; - wird evtl. mal zur Extraktion genutzt, Transparente Pixel werden dann durch diese Farbe ersetzt - kennen manche evtl. noch von früher wo ganz gerne mal Pink für Transparenz/Overlay genutzt wurde

    Offset A bis A+Palettesize*2 - Die Startpalette, sind mehrere Paletten definiert folgen die entsprechenden Startpaletten

    Nach den Paletten kommen dann die eigentlichen Bilddaten


    Und ich Merke gerade: da fehlt noch etwas im Header:

    Variant2+Offset:longint; die Offsetliste für die Varianten - die wird nur dann vorhanden sein wenn der Variantentyp 0 ist, bei Variantentyp 1 muss ich eh alles nach und nach dekodieren.

    Dieser Wert wird wohl an Offset A wandern.


    Hier mal die Hexwerte der Picdatei aus der obiges Bild entpackt wurde:


    80 02 = 280Hex= 640

    68 01 = 168Hex= 360

    00 = 1 Palette

    FC = 252 - Die Startpalette hat 252 Einträge

    00 = 0 Varianten

    00 = Varianttyp 0, keine Transparenz

    00 00 = Transparenzsfarbe= 0000


    gefolgt von der Startpalette

    bei Offset 204h gehts dann mit den eigentlichen Bilddaten los

    00 01 02 - die ersten 3 Pixel (weil BMP unten links)

    FF 29 00 03 - wiederhole 41 (0029hex) mal die Farbe 03

    usw...


  • Da die vorherige Version der Bildcodierung teilweise nur deshalb "funktioniert" hat weil ich zwischen en- und decodieren den Speicher nicht gelöscht habe - und das ganze extremst unübersichtlich wurde - habe ich mich daran gemnacht dies neu zu schreiben.


    Änderungen:


    Shortrepeat

    Es gibt nun einen weiteren Befehl: shortrepeat

    Es hat sich gezeigt: die meisten Repeats nutzen weniger als 256 Pixel, also sparen wir etwas:.

    Syntax: $FD AA BB

    AA=Index und BB=Repeats sind bytes


    Replace Palette Entrys

    Der Befehl "Replace Palette Entry" bekam ein "s" angehängt und ändert mehrere Paletteneinträge auf einmal

    Syntax: $FC AA BBBB AA BBBB.... $FF

    AA=Index (byte), BBBB neue Farbe (word), das wiederholt sich bis als Index $FF gelesen wird.


    Im Encoder wird dies realisiert indem beim einlesen eines Pixels - dessen Farbe noch nicht in der Farbtpalette ist - im wesentlichen die Palette neu generiert wird, d.h. die nächsten Pixel werden eingelesen bis wieder eine ganze Palette voll ist (252 Farben).

    Bei der Neugenerierung werden die Einträge der (alten) Palette markiert die noch genutzt werden und anschließend die ungenutzten Einträge durch die neuen Farben getauscht.

    (im Angehängten QT bei u_pic.pas - {neue Palette generieren} bis {terminierung mit $FF})


    Bugs


    Leider durfte ich auch einen Überläufer jagen, ich habe die halbe Woche lang diesen Bug gejagt - und was war?

    Ich habe beim Einlesen die eingelesenen bytes gezäht - blöd nur dass die Longintvariable stattdessen die Pixel gespeichert hatte... und ich ins Minus gelaufen bin - so hat er nur das halbe Bild geladen und ich habe mich gewundert warum nichts funktionierte und noch lustiger: sich die Farbe des Pixels änderte:


    (siehe die erste Zahl (131638 - das Pixel) und die Zahl nach der 566 - das ist die Farbe)



    Nachdem das geklärt war, war es noch etwas windig weil ich die Pixel auch bei Befehlen weitergezählt habe (die Fehlstellen kann man auch sehen), aber das war auch schnell geklärt:


    aktuell ist im neuen Encoder nur der Palettenbefehl integriert, allein die Palettenfunktion verringert die Dateigröße von 460.870 bytes auf 259.421 bytes - mit den Repeatfunktionen und der (verbesserten) Farbreduzierung dürfte ich auf ~100kb runterkommen


    Und hier noch etwas Dateiaufbau:


    - Am Anfang ist der Header (Auflösung, Palettenanzahl/Größe, Variantenanzahl/Typ/Transparenz, Transparensfarbe)

    - der Markierte Bereich ist die Startpalette

    - danach geht es mit den Bilddaten los - und da sieht man auch wozu der Repeatbefehl da ist, viele Wiederholungen.


    Und im 2ten Bild sieht man den Befehl "Replace Palette Entrys" in Aktion (Markierter Bereich)


  • Den QT vom Encoder kommentiert und einen kleinen Fehler entfernt der zu Platzverschwendug führte.


    Nur mit der Palettenfunktion bin ich nun runter auf 255.224bytes (259.421 bytes in der obigen Variante, 460870 bytes als BMP)


    PS. an die Mods: Bitte, im Programmierenbereich könnten mehr als nur 10000 Zeichen wirklich nicht schaden.

    Von allen Dingen auf Erden ist die Intelligenz am gerechtesten verteilt: Jeder glaubt, er hätte genug davon.

  • Ok, Repeat eingebaut:


    Ergebnis: 104.388 bytes (255.224 bytes ohne Repeats und 460.870bytes als BMP)


    Hier noch der Unterschied in der Datei: in Zeile 200 gehören die 0F 94 noch zur Startpalette, danach geht es mit den Bilddaten los, Bild 1 ist ohne Repeat, Bild 2 ist mit Repeat (FD/FE)


    FD 03 29 Bedeutet: $29 (+1) mal die Farbe $03


    Die Kompression kann man auch sehr schön sehen. Aufgrund der Art wie die Startpalette generiert wird, sind die Farbnummern am Anfang im wesentlichen Aufsteigend - schaut einfach mal wo die Farbnummer $37 in beiden Bildern das erste mal auftaucht.



    PS.


    kleiner Vergleich:

    als PNG hat die 16Bit Version 99kb mit Paint.net, 84kb mit Gimp (Kompressionsstufe 9), 65kb mit Irfan View (Kompressionsstufe 9) - allerdings stimmen bei Irfan die Farben nicht richtig

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!