VGA-Kurs - Part #6 'T.C.P.s Beginner's Guide To VGA Coding', Part VI ist da! Heute wollen wir uns ber ein Thema unterhalten, mit dem viele Anf„nger Schwierigkeiten haben. Sie wollen ein Programm schreiben. Sie wissen auch, wie sie im Grafikmodus ein paar Punkte setzen k”nnen, aber das hilft ihnen nicht weiter, denn sie wollen in ihr Programm ein Bild einbauen. Entweder haben sie es selbst gezeichnet oder zeichnen lassen oder was auch immer. Auf jeden Fall wollen sie es unbedingt in ihrem Programm haben, und zwar m”glichst professionell, also nicht durch ein Hintertrchen, indem sie einen externen Viewer wie PicView dazupacken und an der entsprechenden Stelle im Programm mit der EXEC-Prozedur aufrufen. Also, wie mache ich das jetzt, ein Bild anzuzeigen? Tja, erst mal brauchen wir ein geeignetes Format, das wir leicht einlesen und anzeigen k”nnen. Das simpelste Format von allen, auch wenn es mehr oder weniger inoffiziell ist, ist das RAW-Format. Der Aufbau dieses Formats ist in einem Satz erkl„rt: Alle Daten des Bildes werden in einem Rutsch vom Bildschirmspeicher in eine Datei geschrieben. Das Anzeigen eines solchen Bildes ist ebenso einfach: Alle Daten werden aus der Datei nacheinander zurck in den Bildschirmspeicher geschrieben. Dies sieht nun in Pascal so aus: procedure SaveRAW(Name:string); var f : file; begin assign(f,Name); rewrite(f,1); { Datei erstellen } blockwrite(f,mem[$A000:0],64000); { Ab Adresse $A000:0 64000 Byte lesen und in die Datei schreiben } close(f); { Datei wieder schlieáen } end; procedure LoadRAW(Name:string); var f : file; begin assign(f,Name); reset(f,1); { Datei fr Lesezugriff vorbereiten } blockread(f,mem[$A000:0],64000); { 64000 Byte aus Datei lesen und in den Bildschirmspeicher schreiben } close(f); end; Da hier allerdings nur die Bildinformationen gespeichert werden, und nicht die notwendige Palette, mssen wir diese auch noch sichern: procedure GetPal(col:byte;var r,g,b:byte); begin port[$3C7] := Col; r := port[$3C9]; g := port[$3C9]; b := port[$3C9]; end; procedure SetPal(col:byte;r,g,b:byte); begin port[$3C8] := Col; port[$3C9] := r; port[$3C9] := g; port[$3C9] := b; end; procedure SavePal(Name:string); var f : file; n : byte; RGB : array[1..3] of byte; begin assign(f,Name); rewrite(f,1); for n := 0 to 255 do begin getpal(n,RGB[1],RGB[2],RGB[3]); blockwrite(f,RGB,3); end; close(f); end; procedure LoadPal(Name:string); var f : file; n : byte; RGB : array[1..3] of byte; begin assign(f,Name); reset(f,1); for n := 0 to 255 do begin blockread(f,RGB,3); setpal(n,RGB[1],RGB[2],RGB[3]); end; close(f); end; Man kann die Prozeduren auch kombinieren, indem man die Palettendaten an die RAW-Datei anh„ngt. So macht es das (unkomprimierte) TGA-Format auch. Allerdings speichert es die Palettendaten in der Reihenfolge Blau, Grn, Rot ab. Auáerdem ist jeder Farbwert mit 4 multipliziert. Jetzt stehen wir vor einem weiteren Problem: Da das RAW-Format kaum verbreitet ist, gibt es kein Konvertierprogramm (auáer Paintshop Pro 3.0, allerdings speichert es die RAWs aus unerfindlichen Grnden falschrum ab), mit dem man seine Bilder in RAWs konvertieren kann. Also mssen wir uns selbst darum kmmern. Beim folgenden Konvertierer habe ich mich fr PCX als Eingabeformat entschieden. Das GIF-Format ist zwar wesentlich mehr verbreitet, darf aber aus allseits bekannten Grnden hier nicht zur Anwendung kommen. program PCX2RAW; uses crt; type TPCXHeader = record { Header der PCX-Datei } Manuf,Version,Encode,BitsPerPixel : byte; X1,Y1,X2,Y2,Xres,Yres : integer; Palette : array[0..47] of byte; VideoMode,Planes : byte; BytesPerLine : integer; Reserved : array[0..59] of byte; end; PPCXPic = ^TPCXPic; TPCXPic = record Header : TPCXHeader; { Der Header } Palette : array[0..767] of byte; { Die Palette } Pixels : pointer; { Das Bild } end; var PCX_ : TPCXPic; I : integer; palf,rawf : file; PCX,PAL,RAW : string; procedure LoadPCX(FileName:string;var PCX:TPCXPic); { L„dt PCX-Datei } var F : file; Buf : array[0..1024] of byte; BufPtr,Off,Size : word; Code,Count : byte; begin assign(F,FileName); reset(F,1); blockread(F,PCX.Header,sizeof(PCX.Header)); { Header einlesen } with PCX.Header do { und auswerten } if (Manuf <> 10) or (Version <> 5) or (Encode <> 1) or (BitsPerPixel <> 8) or (Planes <> 1) or (BytesPerLine > 320) or (Y2 - Y1 > 199) then begin PCX.Pixels := nil; { Bild kann nicht dargestellt werden } exit; end; Size := PCX.Header.BytesPerLine * succ(PCX.Header.Y2 - PCX.Header.Y1); { Bildgr”áe ermitteln } getmem(PCX.Pixels,Size); if PCX.Pixels = nil then exit; BufPtr := sizeof(Buf); Off := 0; { Offset in der PCX-Datei } while Off < Size do begin if BufPtr >= sizeof(Buf) then begin blockread(F,Buf,sizeof(Buf)); { Daten lesen } BufPtr := 0; end; Code := Buf[BufPtr]; inc(BufPtr); if Code shr 6 = 3 then begin { Dekomprimierung } Count := Code and 63; if BufPtr >= sizeof(Buf) then begin blockread(F,Buf,sizeof(Buf)); BufPtr := 0; end; Code := Buf[BufPtr]; inc(BufPtr); fillchar(mem[Seg(PCX.Pixels^):ofs(PCX.Pixels^)+Off],Count,Code); inc(Off,Count); end else begin mem[seg(PCX.Pixels^):ofs(PCX.Pixels^)+Off] := Code; inc(Off); end; end; if BufPtr >= sizeof(Buf) then begin blockread(F,Buf,sizeof(Buf)); BufPtr := 0; end; Code := Buf[BufPtr]; inc(BufPtr); if Code = 12 then begin for Off := 0 to 767 do begin if BufPtr >= sizeof(Buf) then begin blockread(F,Buf,767-Off); BufPtr := 0; end; PCX.Palette[Off] := Buf[BufPtr]; inc(BufPtr); end; end; close(F); end; procedure FreePCX(var PCX:TPCXPic); begin if PCX.Pixels <> nil then freemem(PCX.Pixels,PCX.Header.BytesPerLine*succ(PCX.Header.Y2-PCX.Header.Y1)); end; begin if paramcount <> 2 then halt; PCX := paramstr(1); { Name der PCX-Datei } RAW := paramstr(2); { Name der RAW-Datei } PAL := RAW; { Name der PAL-Datei } delete(PAL,pos('.',PAL),4); { eventuelle RAW-Endung entfernen } PAL := PAL + '.pal'; { Endung '.PAL' anh„ngen } LoadPCX(PCX,PCX_); { PCX-Datei laden } if PCX_.Pixels = nil then begin { Fehler beim Laden } writeln(#13#10'Error reading PCX file: ',PCX); halt; end; asm mov ax,13h; int 10h end; { Modus 13h setzen } port[$3C8] := 0; { Palette setzen } for I := 0 to 767 do begin PCX_.Palette[I] := PCX_.Palette[I] shr 2; Port[$3C9] := PCX_.Palette[I]; end; with PCX_ do { Bild darstellen } for I := Header.Y1 to Header.Y2 do Move(mem[seg(PCX_.Pixels^):ofs(PCX_.Pixels^)+I*Header.BytesPerLine], mem[$A000:320*I],Header.X2 - Header.X1 + 1); assign(rawf,RAW); { Dateien vorbereiten } rewrite(rawf,1); assign(palf,PAL); rewrite(palf,1); with PCX_ do { RAW-File schreiben } for I := Header.Y1 to Header.Y2 do blockwrite(rawf,mem[$A000:320*I],Header.X2 - Header.X1 + 1); blockwrite(palf,PCX_.Palette,768); { PAL-File schreiben } readkey; close(rawf); close(palf); textmode(3); end. Ein wirklich langer Quelltext, aber leider ein Muá. Aufgerufen wird das Programm folgendermaáen: PCX2RAW [PCXFile] [RAWFile]. Es folgt nun die Aufschlsselung des PCX-Formats, wahrscheinlich nur fr etwas fortgeschrittenere Leser interessant, aber trotzdem: Am Anfang der PCX-Datei steht ein 128 Byte langer Header, in dem s„mtliche Informationen ber die Eigenschaften des Bildes stehen. Hier die Offsets: Offset Bytes Bedeutung -------------------------- 0 1 Identifikationsbyte. Enth„lt den Wert 10, wenn PCX-Datei. 1 1 Versionsnummer. Werte: 0 : Version 2.5 2 : Version 2.8 mit Palettendaten 3 : Version 2.8 ohne Palettendaten 5 : Version 3.0 2 1 Komprimierungsinfo. Werte: 0 : Bild nicht gepackt 1 : Bild gepackt 3 1 Bits pro Pixel (8 fr 256 Farben) 4 2 Linke obere X-Koord 6 2 Linke obere Y-Koord 8 2 Rechte untere X-Koord 10 2 Rechte untere Y-Koord 12 2 X Aufl”sung 14 2 Y Aufl”sung 16-65 59 Palette fr 16-Farben-Bilder 64 1 Videomodus 65 1 Anzahl der Farbebenen (Planes) 66 2 Bytes pro Zeile 68 2 Paletteninfo. Werte: 1 : Mono/Farbe 2 : Graustufen 70-127 57 Bisher unbenutzt Jetzt zum Kompressionsverfahren. Es funktioniert folgendermaáen: Sind in einem Datenbyte die zwei obersten Bits gesetzt, ist es ein Z„hlerbyte, d.h. die unteren 6 Bytes bilden einen Wert von 1 bis 63, der bestimmt, wie oft sich das darauffolgende Byte wiederholt. Ist dagegen nur eins bzw. gar keins der beiden Bits gesetzt, kann das Byte so wie es ist in den Bildschirmspeicher eingelesen werden. Das Verfahren nennt sich brigens Laufl„ngenkodierung, zu Englisch Run Length Encoding (RLE). Was aber, wenn man unkomprimiertes Byte hat, in dem die obersten Bits gesetzt sind, z.B. 255? Dann muá man wohl oder bel ein Z„hlerbyte erzeugen, das anzeigt, daá einmal der Wert 255 eingelesen werden muá, womit ein Byte verschwendet w„re. Ein Beispiel: Unkomprimiert: 00h 00h 00h 00h 01h 05h FFh Komprimiert: C4h 00h 01h 05h C1h FFh Hier werden 2 Bytes gespart, weil wir die 4 '00h' zu 'C4h 00h' zusammenfassen k”nnen. Allerdings geht auch ein Byte wieder verloren, da bei 'FFh' die oberen beiden Bits gesetzt und wir 'C1h FFh' schreiben mssen. Das maximale Kompressionsverh„ltnis betr„gt also 1 zu 32 (es k”nnen 64 Byte zu 2 Byte zusammengefaát werden). Allerdings kann es im ungnstigsten Fall auch zu einer Vergr”áerung des Umfangs der Daten kommen. Da wir jetzt aber das PCX-Format einlesen k”nnen, wozu brauchen wir dann noch das RAW-Format? Nun, es liegt wohl auf der Hand, daá ein einziges Blockread sehr viel einfacher und auch schneller ist, als der gesamte obige Code fr das Einlesen einer PCX-Grafik. Auáerdem ist es ein wenig professioneller, wenn man fr seine Programm ein Format benutzt, das nicht jeder mit einem Viewer einsehen oder die Bilder klauen kann. Trotzdem hat das RAW-Format einen gewichtigen Nachteil: Es ist zu groá. Dem kann man abhelfen, indem man sich einen Kompressionsalgorithmus schreibt, „hnlich wie oben beschrieben. Das ist nicht so schwer wie es sich anh”rt. Ich zum Beispiel benutze fr das DFI-Format (Diabolic Force Image) einen Algorithmus, der in weniger als 30 Zeilen Platz findet. Trotzdem packt er recht gut. Ein eigenes Format ist also nicht von Nachteil. Da das Kompressionsverfahren von PCX nicht geschtzt ist, k”nnten wir es dafr verwenden, aber wir wollen doch mal sehen, ob wir es nicht noch verbessern k”nnen. Die Laufl„ngenkodierung, wie sie oben beschrieben ist, stellt schon mal einen guten Anfang dar. Jedoch sollte man darber nachdenken, statt der oberen 2 Bits die obersten 3 Bits zur Markierung eines Z„hlerbytes zu benutzen. Die 3 Bits k”nnten dann folgendes bedeuten: 000xxxxx : X Null-Bytes (kommen sehr hafig vor) 001xxxxx : X mal das folgende Byte 010xxxxx : Die n„chsten X Bytes unver„ndert in den Speicher laden 011xxxxx : X mal die folgenden 2 Bytes 100xxxxx : Die 5 Restbits & die 8 Bits des n„chsten Bytes als Z„hler fr das bern„chste Byte (13 Bit = 1-8191) usw. Fr den Rest k”nnt ihr euch selbst was passendes einfallen lassen. Das obige Verfahren drfte ca. 5-10%, bei weniger komplexen Bildern bis zu 30% mehr Kompression einbringen. Ein anderes komplexes Verfahren, das ebenso viele Freunde wie Feinde haben drfte, ist das JPEG-Format. Es komprimiert die Bilddaten wie kein anderes Format, allerdings mit dem Nachteil, das Bildinformationen verloren gehen. Auáerdem ist die Kompression bzw. Dekompression so aufwendig und langsam, daá es sich kaum fr Spiele oder andere Programme eignen drfte. Wenn ihr also nicht gerade einen Viewer schreiben wollt, k”nnt ihr darauf verzichten. Ein weiterer Bereich, in dem die Kompressionsmethoden immer mehr verbessert werden, ist der der digitalen Videos. Hier konnte sich das MPEG-Verfahren, das mit JPEG verwandt ist, einigermaáen durchsetzen, jedoch sind hierfr teure Zusatzkarten erforderlich, wenn man nicht gerade einen ultraschnellen PC hat. Fr Animationen gibt es auáerdem das bekannte FLI-Format, bzw. dessen Weiterentwicklung, das FLC. Hier kommt eine Technik zum Einsatz, bei der nur die Daten gespeichert werden, die zur vorigen Frame (Einzelbild) unidentisch sind. Die erste Frame wird also komplett gespeichert (ebenfalls mit einer Art Laufl„ngenkodierung). Danach folgen die n„chsten Frames, wobei jede einen eigenen Header besitzt, in dem steht, wie groá und von welchem Typ sie ist. Falls sich die Palette der Frame ge„ndert hat, enth„lt der darauffolgende Datenteil als erstes eine gepackte Palette. Anschlieáend folgen die Daten, allerdings nur die, die unterschiedlich zur vorigen Frame sind. Sehr gute und ausfrliche Beschreibungen von Grafik- und Animations-, aber auch Sound-Formaten u.a. finden sich in der PC Games Programmer's Encyclopedia 1.0. Wer sich also jetzt vorgenommen hat, sein eigenes Bild- oder sogar Video-Format zu schreiben, dem sei gesagt: Es fhrt kein Weg am Assembler vorbei. Auáerdem ist ein Algorithmus nie ganz ausgereizt, es gibt immer Wege, ihn zu verbessern. Ich hoffe, die Ausfhrungen in diesem Teil haben euch geholfen, und euch nicht zu sehr verwirrt. Was im n„chsten Teil kommen wird, weiá ich noch nicht genau, laát euch einfach berraschen. [ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ] [ Distributed exclusively through PC-Heimwerker, Verlag Thomas Eberle. ] [ ] [ No part of this document may be reproduced, transmitted, ] [ transcribed, stored in a retrieval system, or translated into any ] [ human or computer language, in any form or by any means; electronic, ] [ mechanical, magnetic, optical, chemical, manual or otherwise, ] [ without the expressed written permission of the author. ] [ ] [ The information contained in this text is believed to be correct. ] [ The text is subject to change without notice and does not represent ] [ a commitment on the part of the author. ] [ The author does not make a warranty of any kind with regard to this ] [ material, including, but not limited to, the implied warranties of ] [ merchantability and fitness for a particular purpose. The author ] [ shall not be liable for errors contained herein or for incidental or ] [ consequential damages in connection with the furnishing, performance ] [ or use of this material. ]