TGA Bilder laden: Unterschied zwischen den Versionen
Waran (Diskussion | Beiträge) |
Waran (Diskussion | Beiträge) |
||
Zeile 1: | Zeile 1: | ||
− | + | = TGA Dateien Laden = | |
+ | |||
+ | == TGA Dateien - Warum? == | ||
+ | |||
+ | In diesem Tutorial (mein erstes) geht es um das manuelle Laden von Truevision | ||
+ | Bilddateien mit der allseits bekannten Endung ".tga". | ||
+ | |||
+ | Dieses Dateiformat ist für den Anfang aus verschiedenen Gründen auf voller | ||
+ | Linie empfehlenswert. Es ist sehr einfach aufgebaut und die Pixeldaten liegen | ||
+ | auch schon in einer von neueren OpenGL-Implementierungen direkt benutzbaren | ||
+ | Ordnung vor. | ||
+ | |||
+ | == Landkarte und Kompass - oder besser GPS :D == | ||
+ | |||
+ | Zuerst stellen wir uns die Frage, wie eine TGA-Datei intern aufgebaut ist. | ||
+ | Da es sich um eine Binärdatei handelt, lässt sich die Bedeutung der Daten nicht | ||
+ | erraten - wir sind also auf fremde Dokumentation angewiesen. | ||
+ | Eine ganze Sammlung solcher "Datei-Spickzettel" findet man auf der Internetseite | ||
+ | http://www.wotsit.org/. | ||
+ | |||
+ | Nachdem wir uns das entsprechende Dokument von dort besorgt und kritisch | ||
+ | überflogen haben, fällt wohl erstmal ins Auge, dass es verschiedene Typen von | ||
+ | TGA-Dateien zu geben scheint. | ||
+ | |||
+ | Wir beschäftigen uns hier nur mit Typ 2 (24/32-Bit TGA ohne Kompression), da es | ||
+ | das gebräuchlichste Format ist. | ||
+ | |||
+ | == Ein Wort zur Kompression == | ||
+ | |||
+ | Komprimierte TGA-Dateien lassen wir außen vor, | ||
+ | da sie leider einige Nachteile mit sich bringen: Es ist schwieriger die Datei | ||
+ | einzulesen, das Dekodieren benötigt wertvolle Rechenzeit und die | ||
+ | RLE-Kompression, diein den TGA-Dateien zum Einsatz kommt, ist vergleichsweise | ||
+ | schlecht. | ||
+ | |||
+ | Als Alternative zu komprimierten TGA-Dateien bietet sich Microsoft's DDS-Format | ||
+ | an, da es eine Komprimierung unterstützt, die direkt von der Grafikkarte gelesen | ||
+ | werden kann - d.h. die Dateien werden kleiner, zusätzlich spart man Bandbreite | ||
+ | und erhöht so effektiv die Lade- und Rendergeschwindigkeit. | ||
+ | |||
+ | Alternativ lassen sich "normale" TGA's auch manuell mit einer effektiveren | ||
+ | Methode als RLE packen (Stichwort: Virtuelles Dateisystem). | ||
+ | Da dieses Tutorial aber eher für Anfänger gedacht ist gehe ich nun nicht weiter | ||
+ | darauf ein :-) | ||
+ | |||
+ | == Definition eines Datentyps == | ||
+ | |||
+ | Welche Daten aus der TGA-Datei sind nun wirklich für unsere Zwecke interessant? | ||
+ | |||
+ | |||
+ | Image Type Code - zum überprüfen, ob die Datei das richtige Format hat. | ||
+ | Width of Image | ||
+ | Height of Image | ||
+ | Image Pixel Size - anhand dieser können wir u.a. ermitteln, ob ein Alphakanal | ||
+ | vorhanden ist. | ||
+ | Identification Field - die eigentlichen Pixeldaten. | ||
+ | |||
+ | |||
+ | Damit haben wir alles benötigte zusammen und können diese Informationen schon | ||
+ | mal in einen Verbundtyp packen (Die Datentypen entnehmen wir der "Length"-Spalte | ||
+ | in unserem Dokument von wotsit): | ||
+ | |||
+ | <pascal> | ||
+ | type | ||
+ | TTGAFile = packed record | ||
+ | iType: byte; // should be 2 | ||
+ | w, h: word; // Width, Height | ||
+ | bpp: byte; // Byte per Pixel | ||
+ | data: ^byte; // Pixels, dynamic length | ||
+ | end; | ||
+ | </pascal> | ||
+ | |||
+ | |||
+ | == Die Datei laden == | ||
+ | |||
+ | Die Schritte, die nun erforderlich sind, um diese Struktur mit Leben zu Füllen | ||
+ | sind denkbar einfach. Zuerst wird die Datei binär geöffnet; der Dateizeiger mit | ||
+ | Seek positioniert (siehe "Offset"-Spalte in unserem Dokument) und mit Blockread | ||
+ | gelesen. Die Felder w, h und bpp liegen in der Datei nebeneinander; deshalb | ||
+ | können sie mit nur einer einzigen (anstatt 3) Seek/Blockread-Kombination | ||
+ | eingelesen werden. Da wir mit "Bit pro Pixel" wenig anfangen können teilen wir | ||
+ | bpp anschließend durch 8 und erhalten "Bytes pro Pixel". | ||
+ | |||
+ | Hinweis: Für den, der es an dieser Stelle lieber objektorientiert mag, bieten | ||
+ | sich natürlich auch Streams an. Allerdings ist dies für diesen | ||
+ | konkreten Fall nicht von Belang. | ||
+ | |||
+ | <pascal> | ||
+ | function LoadTGA(const filename: string): TTGAFile; | ||
+ | var f: file; bytes: longword; | ||
+ | begin | ||
+ | assign(f, filename); reset(f, 1); | ||
+ | |||
+ | // type | ||
+ | seek(f, 2); blockread(f, result.iType, 1); | ||
+ | |||
+ | // w, h, bpp | ||
+ | seek(f, 12); blockread(f, result.w, 5); | ||
+ | result.bpp := result.bpp div 8; | ||
+ | |||
+ | // data | ||
+ | bytes := result.w * result.h * result.bpp; | ||
+ | getmem(result.data, bytes); | ||
+ | seek(f, 18); blockread(f, result.data^, bytes); | ||
+ | |||
+ | close(f); | ||
+ | end; | ||
+ | </pascal> | ||
+ | |||
+ | Diese Funktion lädt alle benötigten Daten aus der Datei; reserviert auf Basis | ||
+ | dieser Informationen genug Speicher für die Pixeldaten und liest sie dann ein. | ||
+ | Der Pointer kann anschließend direkt in OpenGL verwendet werden, wenn die | ||
+ | Implementierung die Formate GL_BGR und GL_BGRA unterstützt (je nach dem, ob | ||
+ | "bpp" den Wert 3 oder 4 erhalten hat). | ||
+ | |||
+ | == Datei rein - Textur raus == | ||
+ | |||
+ | Die eigentliche Erzeugung des Texturobjektes gleicht dem "SDL-Weg". Nur wird | ||
+ | hier unser eigenes Record anstatt einer SDL-Surface verwendet. Nicht vergessen | ||
+ | den dynamisch reservierten Speicher für die Pixeldaten nach der Verwendung mit | ||
+ | <pascal> | ||
+ | FreeMem(TGARecord.data); | ||
+ | </pascal> | ||
+ | wieder freizugeben! | ||
+ | |||
+ | <pascal> | ||
+ | procedure TGATexture(const filename: string; var TexID: longword); | ||
+ | var | ||
+ | tex: TTGAFile; | ||
+ | glFormat: longword; | ||
+ | begin | ||
+ | tex := LoadTGA(filename); | ||
+ | if tex.iType = 2 then | ||
+ | begin | ||
+ | glGenTextures(1, @TexID); | ||
+ | glBindTexture(GL_TEXTURE_2D, TexID); | ||
+ | |||
+ | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||
+ | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||
+ | |||
+ | if tex.bpp = 3 then glFormat := GL_BGR | ||
+ | else glFormat := GL_BGRA; | ||
+ | |||
+ | glTexImage2D(GL_TEXTURE_2D, 0, tex.bpp, tex.w, tex.h, | ||
+ | 0, glFormat, GL_UNSIGNED_BYTE, tex.data); | ||
+ | |||
+ | FreeMem(tex.data); | ||
+ | end; | ||
+ | end; | ||
+ | </pascal> | ||
+ | |||
+ | Ich hoffe dieses Tutorial war hilfreich (Ich will Feedback!) und wünsche euch | ||
+ | viel Spaß und Erfolg bei euren zukünftigen Projekten. | ||
+ | |||
+ | Gruß Waran. |
Version vom 5. August 2007, 21:39 Uhr
Inhaltsverzeichnis
TGA Dateien Laden
TGA Dateien - Warum?
In diesem Tutorial (mein erstes) geht es um das manuelle Laden von Truevision Bilddateien mit der allseits bekannten Endung ".tga".
Dieses Dateiformat ist für den Anfang aus verschiedenen Gründen auf voller Linie empfehlenswert. Es ist sehr einfach aufgebaut und die Pixeldaten liegen auch schon in einer von neueren OpenGL-Implementierungen direkt benutzbaren Ordnung vor.
Landkarte und Kompass - oder besser GPS :D
Zuerst stellen wir uns die Frage, wie eine TGA-Datei intern aufgebaut ist. Da es sich um eine Binärdatei handelt, lässt sich die Bedeutung der Daten nicht erraten - wir sind also auf fremde Dokumentation angewiesen. Eine ganze Sammlung solcher "Datei-Spickzettel" findet man auf der Internetseite http://www.wotsit.org/.
Nachdem wir uns das entsprechende Dokument von dort besorgt und kritisch überflogen haben, fällt wohl erstmal ins Auge, dass es verschiedene Typen von TGA-Dateien zu geben scheint.
Wir beschäftigen uns hier nur mit Typ 2 (24/32-Bit TGA ohne Kompression), da es das gebräuchlichste Format ist.
Ein Wort zur Kompression
Komprimierte TGA-Dateien lassen wir außen vor, da sie leider einige Nachteile mit sich bringen: Es ist schwieriger die Datei einzulesen, das Dekodieren benötigt wertvolle Rechenzeit und die RLE-Kompression, diein den TGA-Dateien zum Einsatz kommt, ist vergleichsweise schlecht.
Als Alternative zu komprimierten TGA-Dateien bietet sich Microsoft's DDS-Format an, da es eine Komprimierung unterstützt, die direkt von der Grafikkarte gelesen werden kann - d.h. die Dateien werden kleiner, zusätzlich spart man Bandbreite und erhöht so effektiv die Lade- und Rendergeschwindigkeit.
Alternativ lassen sich "normale" TGA's auch manuell mit einer effektiveren Methode als RLE packen (Stichwort: Virtuelles Dateisystem). Da dieses Tutorial aber eher für Anfänger gedacht ist gehe ich nun nicht weiter darauf ein :-)
Definition eines Datentyps
Welche Daten aus der TGA-Datei sind nun wirklich für unsere Zwecke interessant?
Image Type Code - zum überprüfen, ob die Datei das richtige Format hat. Width of Image Height of Image Image Pixel Size - anhand dieser können wir u.a. ermitteln, ob ein Alphakanal vorhanden ist. Identification Field - die eigentlichen Pixeldaten.
Damit haben wir alles benötigte zusammen und können diese Informationen schon
mal in einen Verbundtyp packen (Die Datentypen entnehmen wir der "Length"-Spalte
in unserem Dokument von wotsit):
type TTGAFile = packed record iType: byte; // should be 2 w, h: word; // Width, Height bpp: byte; // Byte per Pixel data: ^byte; // Pixels, dynamic length end;
Die Datei laden
Die Schritte, die nun erforderlich sind, um diese Struktur mit Leben zu Füllen sind denkbar einfach. Zuerst wird die Datei binär geöffnet; der Dateizeiger mit Seek positioniert (siehe "Offset"-Spalte in unserem Dokument) und mit Blockread gelesen. Die Felder w, h und bpp liegen in der Datei nebeneinander; deshalb können sie mit nur einer einzigen (anstatt 3) Seek/Blockread-Kombination eingelesen werden. Da wir mit "Bit pro Pixel" wenig anfangen können teilen wir bpp anschließend durch 8 und erhalten "Bytes pro Pixel".
Hinweis: Für den, der es an dieser Stelle lieber objektorientiert mag, bieten sich natürlich auch Streams an. Allerdings ist dies für diesen konkreten Fall nicht von Belang.
function LoadTGA(const filename: string): TTGAFile; var f: file; bytes: longword; begin assign(f, filename); reset(f, 1); // type seek(f, 2); blockread(f, result.iType, 1); // w, h, bpp seek(f, 12); blockread(f, result.w, 5); result.bpp := result.bpp div 8; // data bytes := result.w * result.h * result.bpp; getmem(result.data, bytes); seek(f, 18); blockread(f, result.data^, bytes); close(f); end;
Diese Funktion lädt alle benötigten Daten aus der Datei; reserviert auf Basis dieser Informationen genug Speicher für die Pixeldaten und liest sie dann ein. Der Pointer kann anschließend direkt in OpenGL verwendet werden, wenn die Implementierung die Formate GL_BGR und GL_BGRA unterstützt (je nach dem, ob "bpp" den Wert 3 oder 4 erhalten hat).
Datei rein - Textur raus
Die eigentliche Erzeugung des Texturobjektes gleicht dem "SDL-Weg". Nur wird hier unser eigenes Record anstatt einer SDL-Surface verwendet. Nicht vergessen den dynamisch reservierten Speicher für die Pixeldaten nach der Verwendung mit
FreeMem(TGARecord.data);
wieder freizugeben!
procedure TGATexture(const filename: string; var TexID: longword); var tex: TTGAFile; glFormat: longword; begin tex := LoadTGA(filename); if tex.iType = 2 then begin glGenTextures(1, @TexID); glBindTexture(GL_TEXTURE_2D, TexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if tex.bpp = 3 then glFormat := GL_BGR else glFormat := GL_BGRA; glTexImage2D(GL_TEXTURE_2D, 0, tex.bpp, tex.w, tex.h, 0, glFormat, GL_UNSIGNED_BYTE, tex.data); FreeMem(tex.data); end; end;
Ich hoffe dieses Tutorial war hilfreich (Ich will Feedback!) und wünsche euch viel Spaß und Erfolg bei euren zukünftigen Projekten.
Gruß Waran.