
                             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 Anfnger
Schwierigkeiten haben. Sie wollen ein Programm schreiben. Sie wissen auch, wie
sie im Grafikmodus ein paar Punkte setzen knnen, 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 mglichst
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 knnen. 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 erklrt: 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 schlieen }
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 anhngt. So macht es das (unkomprimierte) TGA-Format auch. Allerdings
speichert es die Palettendaten in der Reihenfolge Blau, Grn, Rot ab. Auerdem
ist jeder Farbwert mit 4 multipliziert.
Jetzt stehen wir vor einem weiteren Problem: Da das RAW-Format kaum verbreitet
ist, gibt es kein Konvertierprogramm (auer 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); { Ldt 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);
  { Bildgre 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' anhngen }
  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 folgendermaen: 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 smtliche
Informationen ber die Eigenschaften des Bildes stehen. Hier die Offsets:

Offset   Bytes   Bedeutung
--------------------------
   0       1     Identifikationsbyte. Enthlt 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 Auflsung
  14       2     Y Auflsung
 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 folgendermaen:
Sind in einem Datenbyte die zwei obersten Bits gesetzt, ist es ein Zhlerbyte,
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 Lauflngenkodierung, 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 Zhlerbyte erzeugen, das
anzeigt, da einmal der Wert 255 eingelesen werden mu, womit ein Byte
verschwendet wre. 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
knnen. Allerdings geht auch ein Byte wieder verloren, da bei 'FFh' die oberen
beiden Bits gesetzt und wir 'C1h FFh' schreiben mssen.
Das maximale Kompressionsverhltnis betrgt also 1 zu 32 (es knnen 64 Byte zu
2 Byte zusammengefat werden). Allerdings kann es im ungnstigsten Fall auch
zu einer Vergrerung des Umfangs der Daten kommen.

Da wir jetzt aber das PCX-Format einlesen knnen, 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. Auerdem 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 anhrt. 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, knnten wir es dafr verwenden, aber wir wollen
doch mal sehen, ob wir es nicht noch verbessern knnen.
Die Lauflngenkodierung, 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 Zhlerbytes zu benutzen.
Die 3 Bits knnten dann folgendes bedeuten:

000xxxxx : X Null-Bytes (kommen sehr hafig vor)
001xxxxx : X mal das folgende Byte
010xxxxx : Die nchsten X Bytes unverndert in den Speicher laden
011xxxxx : X mal die folgenden 2 Bytes
100xxxxx : Die 5 Restbits & die 8 Bits des nchsten Bytes als Zhler fr
           das bernchste Byte (13 Bit = 1-8191)
usw.

Fr den Rest knnt 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.
Auerdem 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, knnt 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, einigermaen durchsetzen, jedoch sind hierfr teure
Zusatzkarten erforderlich, wenn man nicht gerade einen ultraschnellen PC hat.
Fr Animationen gibt es auerdem 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
Lauflngenkodierung). Danach folgen die nchsten Frames, wobei jede einen
eigenen Header besitzt, in dem steht, wie gro und von welchem Typ sie ist.
Falls sich die Palette der Frame gendert hat, enthlt der darauffolgende
Datenteil als erstes eine gepackte Palette. Anschlieend 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.
Auerdem 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 nchsten Teil kommen wird, wei ich noch nicht genau, lat 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.                                             ]
