Die Darstellung von Grafiken im Speicher mit FreeBASIC lässt sich in mehrere Kapitel untergliedern:
- Darstellung eines einzelnen Pixels
- Transparenz - Maskenfarben und Alpha-Wert
- Struktur eines Bildpuffers für die Drawing Primitives
Darstellung eines einzelnen Pixels
Die GFXLib verwendet immer eines von drei Pixelformaten: indizierte 1-Byte-Pixel, Direct Color 2-Bytes-Pixel (Hicolor) und Direct Color 4-Bytes-Pixel (Truecolor). Diese Formate werden in den entsprechenden Modi verwendet:
Farbtiefe (bpp) | Pixelelformat |
---|---|
1 | 1 Byte pro Pixel, indiziert, Index zwischen 0 und 1. |
2 | 1 Byte pro Pixel, indiziert, Index zwischen 0 und 3. |
4 | 1 Byte pro Pixel, indiziert, Index zwischen 0 und 15. |
8 | 1 Byte pro Pixel, indiziert, Index zwischen 0 und 255. |
15, 16 | 2 Bytes pro Pixel, Direct Color, Format &bRRRRRGGGGGGBBBBB. |
24, 32 | 4 Bytes pro Pixel, Direct Color, Format &bAAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB. |
Indiziert bedeutet hierbei, dass jeder Zahl eine Farbe zugeordnet ist; beispielsweise kann ein Pixel mit dem Farbattribut 4 rot sein. Welche Farbe welcher Zahl zugeordnet ist, hängt vom Bildschirmmodus ab; siehe dazu die Standard-Paletten. Diese können mit PALETTE noch im Programmverlauf bearbeitet werden. Bei Direct-Color-Modi stellt die Zahl bereits die Farbe dar. Jeweils ein bestimmter Teil der Zahl stellt den Rot- Grün- und Blau-Anteil der Farbe dar. Bei der Angabe des Formats in obiger Tabelle steht jeweils ein Zeichen für ein Bit der Farbinformation. R symbolisiert jeweils den Rot-, G den Grün- und B den Blauanteil. Die Stellen A, die bei 24 und 32bpp aufgeführt sind, können tatsächlich nur in 32bpp-Modi genutzt werden. Sie stellen die Alpha- oder Transparenz-Komponente dar. In 24bpp-Modi sind diese Stellen jeweils mit null besetzt, sie werden lediglich zur leichteren Verwaltbarkeit beibehalten. Wie Sie sehen, verwenden die 15 und der 16bpp-Modi dasselbe 16bpp-Format. Dasselbe kann man über die 24 und 32bpp-Modi sagen, beide benutzen das 32bpp-Format. Das interne Format ist plattformunabhängig. Der Bildschirmspeicher wird in einem der drei Formate gehalten. Denken Sie daran, wenn Sie per SCREENPTR darauf zugreifen; dasselbe gilt für Daten, die in GET- und PUT-Feldern gespeichert sind. Mehr dazu unter Struktur eines Bildpuffers für die Drawing Primitives. In Modi mit mehr als einem Byte pro Pixel werden die Bytes in umgekehrter Reihenfolge im Speicher abgelegt; das obere Byte des Gesamtwertes ist also das zweite im Speicher, das untere Byte wird zuerst abgelegt. Befindet sich an der angenommenen Adresse 0 also eine 32bit-Pixelinformation, so werden ihre Komponenten folgendermaßen zerlegt:
Byte | Komponente |
---|---|
0 | Blau |
1 | Grün |
2 | Rot |
3 | Alpha |
Transparenz - Maskenfarben und Alpha-Wert
Maskenfarbe
Die Maskenfarbe hängt von der aktuellen Farbtiefe ab; sie ist einer von diesen Werten:
Farbtiefe | Maskenfarbnummer |
---|---|
1, 2, 4, 8 | 0 |
15, 16 | &hF81F |
24, 32 | &hFF00FF |
Indizierte Modi benutzen immer den Index 0 als Transparenzmaske, während Direct Color-Modi immer Pink verwenden.
Alphawert
FreeBASIC unterstützt Transparenzeffekte nach der ALPHA-Methode. Dabei wird neben dem Farb-Tripplet (Rot, Grün, Blau) auch ein Transparenzgrad, der Alphawert angegeben. Ebenso wie die Werte des Farbtripplets liegt auch der Alpha-Wert zwischen 0 und 255; dabei entspricht Alpha=0 völliger Transparenz und Alpha=255 völliger Überdeckung. Für alle Werte dazwischen werden 'durchscheinende' Bilder erzeugt, d.h. ein Farbwert wird berechnet, der 'zwischen' überzeichnender und überzeichneter Farbe liegt. Dabei gibt der Alphawert an, welcher der beiden Werte den Farbeindruck dominiert. Berechnet wird die neue Farbe nach dieser Funktion:
Function custom_alpha(Byval src As Uinteger, Byval dst _
As Uinteger, Byval param As Any Ptr ) As Uinteger
Dim As Ubyte ptr color_src, color_dst
Dim As Ubyte r, g, b, a
color_src = Cast(Ubyte ptr, @src)
color_dst = Cast(Ubyte ptr, @dst)
If param <> 0 Then
a = *Cast(Ubyte ptr, param)
'ein alpha wert für alle pixel
Else
a = color_src[3]
'aus jedem Pixel den alpha wert lesen
End If
r=(color_src[2]*a+color_dst[2]*(255-a))\255
g=(color_src[1]*a+color_dst[1]*(255-a))\255
b=( color_src[0]*a+color_dst[0]*(255-a))\255
Return RGBA(r, g, b, 255)
End Function
(Diese Funktion ist so gehalten, dass sie mit PUT (Grafik) benutzt werden kann.)
- 'src' entspricht dabei der Farbinformation des überzeichnenden (neuen) Pixels.
- 'dst' entspricht dabei der Farbinformation des zu überzeichnenden (bereits auf der Zeichenfläche befindlichen) Pixels.
- 'param' ist dabei ein Pointer auf einen UBYTE-Wert zwischen 0 und 255, der den Alpha-Wert darstellt.
Struktur eines Bildpuffers für die Drawing Primitives
Ein Bildpuffer ist ein Speicherbereich, in dem größere Blocks von Pixeldaten - kurz Bildausschnitte - abgelegt werden können. Dies kann ein Array sein (Siehe DIM, REDIM), oder ein mit ALLOCATE, CALLOCATE oder (besonders zu empfehlen) IMAGECREATE reservierter Bereich. In FreeBASIC werden zwei verschiedene Formate für Bildpuffer verwendet; abhängig von der Version des Compilers und den Kommandozeilenoptionen verwendet FreeBASIC Version 1 oder Version 2 der verfügbaren Formate. Beide Formate sind (ab FreeBASIC v0.17) mit allen Drawing Primitives kompatibel; siehe PSET, IMAGECREATE, GET (Grafik).
Version 2 (aktuell)
Diese Version des Bildpuffers ist seit v0.17 verfügbar; das Programm kann ohne oder mit der Kommandozeilenoption -lang fb kompiliert werden. Ebenso wie in der weiter unten aufgeführten Version 1 lässt sich dieses Bildpuffer-Format in einen Header und die eigentlichen Pixeldaten zerlegen. Der Bildpuffer ist dabei so angelegt:
TYPE Image FIELD = 1
UNION
old AS _OLD_HEADER
type AS UINTEGER
END UNION
bpp AS INTEGER
width AS UINTEGER
height AS UINTEGER
pitch AS UINTEGER
_reserved(1 to 12) AS UBYTE
END TYPE
Wie Sie sehen, enthält dieser Header noch die Informationen des alten Formates. Dadurch ist eine schnelle und einfache Prüfung des Bildformates möglich, wodurch die Abwärtskompatibilität gewahrt wird. Zur Überprüfung der Pufferversion siehe später. Die einzelnen Elemente bedeuten dabei Folgendes:
- 'old' ist eine Speicherstruktur, die dem Header der Version 1 entspricht.
- 'type' ist ein INTEGER-Wert, der die Version des Headers angibt. Da es sich in einem UNION mit 'old' befindet, wird es diesen Record überschreiben. Soll die neue Header-Struktur benutzt werden, so hat 'type' den Wert 7. Diesen Wert konnte der alte Header nie erreichen, wodurch sicher gestellt ist, dass die Version eindeutig erkennbar ist.
- 'bpp' gibt die Farbtiefe in Bytes pro Pixel an. Dieser Wert ist entweder gleich 1 (für 1-8bit), 2 (für 15 und 16bit), oder gleich 4 (für 24 und 32bit)
- 'width' und 'height' geben die Breite und Höhe des Bildpuffers in Pixeln an.
- 'pitch' gibt die Anzahl der Bytes pro Zeile an. Der Wert berechnet sich auch für alle Farbtiefen (bpp = 1, 2 oder 4): pitch=(width*bpp+15) AND -16 Wie Sie sehen, wird also jede Bildzeile so aufbereitet, dass die Anzahl der Byte pro Zeile ein Vielfaches von 16 ist. Dies wirkt sich darin aus, dass in fast jedem Bildpuffer einige ungenutzte Bytes vorhanden sind; in Anbetracht der Größe des zur Verfügung stehenden Speichers fallen diese aber gar nicht ins Gewicht. Der Vorteil dieser Methode liegt darin, dass sich der Umgang mit den Bildpuffern optimieren lässt; die heutigen 32bit-Prozessoren bieten 128 Bit - Register (= 16 Byte) die damit optimal angewandt werden können. Einem minimal größeren Speicheraufwand steht also ein spürbar kleinerer Zeitaufwand bei Berechnungen gegenüber.
- '_reserved' ist eine 12 Bytes große Speicherstelle, die später möglicherweise für verschiedene OpenGL-Operationen verwendet werden kann; die Verwendung dieser Stellen steht im Moment noch nicht fest. Der User kann frei entscheiden, ob er diese Speicherstellen nutzen möchte, sollte dabei aber bedenken, dass sein Programm damit möglicherweise später nicht mehr kompatibel ist.
Wie bei 'pitch' bereits angesprochen, sind im neuen Datenformat die Pixel nicht mehr direkt aneinander gereiht, sondern Zeilenweise in Datenblocks zusammengefasst. Hinter jeder Zeile werden eine Reihe von Bytes reserviert, die nicht genutzt werden. Durch dieses 'Padding' können bestimmte Berechnungen schneller durchgeführt werden. Der Wert dieser Padding-Bytes ist abhängig von der Art der Erstellung des Puffers. Bei der Verwendung eines Arrays wird hier in den meisten Fällen null eingetragen, da mit DIM und REDIM initiierte Variablen i.d.R. mit null initialisiert werden. Eine Ausnahme sind Arrays, die durch = ANY initialisiert wurden; da hier der Inhalt des Speicherbereichs nicht gelöscht wird, und nur die tatsächlich genutzen Bytes im Laufe der Bildbearbeitung überschrieben werden, findet man hier immer den 'Datenmüll', der sich vor der Reservierung des Speicherberichs auf den entsprechenden Stellen angesammelt hat. Durch CALLOCATE wird immer ein null-initialisierter Speicherbereich reserviert. Da die Speicherbereiche, die mit IMAGECREATE reserviert wurden, werden vor Benutzung komplett mit einem Wert befüllt; dieser hängt von den Parametern, mit denen IMAGECREATE genutzt wird, und dem aktuellen Bildschirmmodus ab. Siehe IMAGECREATE für Details. Wie schon bei Arrays, die durch = ANY initialisiert wurden, findet man auch bei der Reservierung eines Speicherbereichs mittels ALLOCATE Datenmüll in den Padding-Bytes. Um nun bei gegebener Startadresse des Bildpuffers die Adresse eines einzelnen Pixels zu ermitteln, bedient man sich dieser Formel:
p = buffer+32+(y*((w*bpp+15) AND -16)+(x*bpp)
Dabei ist...
- 'p' ein Pointer, der auf den gewünschten Pixel zeigt. Benutzen Sie bevorzugt einen UBYTE PTR, damit bei der Berechnung des Formelausdrucks keine Fehler auftritt. Wird z.B. an einem INTEGER PTR die Operation +=1 ausgeführt, so erhöht sich sein Wert tatsächlich um vier, da ein INTEGER die Länge 4 BYTE hat.
- 'buffer' die Startadresse des Bildpuffers.
- 'x' und 'y' die Koordinaten des Pixels, relativ zum linken oberen Rand des Bildpuffers.
- 'w' die Breite des Bildes in Pixeln.
- 'bpp' die Farbtiefe des Bildpuffers in Bytes pro Pixel.
Steht 'pitch' aus dem Bildheader zur Verfügung, so kann der Ausdruck auch vereinfacht werden:
p = buffer + 32 + (y * pitch) + (x * bpp)
Der Speicherbedarf berechnet sich also nach dieser Formel:
size = 32 + ( h * (( w * bpp + 15 ) AND -16 )
Beispiel: Ein Bildpuffer der Größe 2x2 Pixel in einem 32bpp-Modus, der einen roten, grünen, gelben und blauen Pixel enthält:
Byte | Bedeutung | Wert |
---|---|---|
0 bis 3 | Version des Bildpuffers - neuer Header | 7 |
4 bis 7 | Farbtiefe in bpp | 4 |
8 bis 11 | Breite des Bildpuffers in Pixeln | 2 |
12 bis 15 | Höhe des Bildpuffers in Pixeln | 2 |
16 bis 19 | Bytes pro Zeile in diesem Puffer | 16 |
20 bis 31 | zwölf reservierte Bytes, die in zukünftigen OpenGL-Anwendungen eine Bestimmung finden könnten | 0 |
32 bis 35 | Farbe des ersten Pixels (links oben) im Format ARGB | &H00FF0000 |
36 bis 39 | Farbe des zweiten Pixels (rechts oben) im Format ARGB | &H0000FF00 |
40 bis 43 | Padding-Stelle - ohne Bedeutung | &HFFFF00FF |
44 bis 47 | Padding-Stelle - ohne Bedeutung | &HFFFF00FF |
48 bis 51 | Farbe des dritten Pixels (links unten) im Format ARGB | &H000000FF |
52 bis 55 | Farbe des vierten Pixels (rechts unten) im Format ARGB | &H00FF00FF |
56 bis 59 | Padding-Stelle - ohne Bedeutung | &HFFFF00FF |
60 bis 63 | Padding-Stelle - ohne Bedeutung | &HFFFF00FF |
Beispiel 1:
#Include "fbgfx.bi"
Using FB
ScreenRes 400, 300, 32
Dim buffer As Any Ptr
Dim header As Image Ptr
Dim Pixels As UInteger Ptr
Dim Colors As UByte Ptr
Dim i As UInteger
buffer = ImageCreate(2, 2)
header = buffer
Pixels = buffer + 32
Colors = buffer + 32
Pset buffer, (0, 0), &hFF0000
Pset buffer, (1, 0), &h00FF00
Pset buffer, (0, 1), &H0000FF
Pset buffer, (1, 1), &HFFFF00
With *header
PRINT "Header of the Buffer:"
PRINT "Version ID: "; .type
PRINT "Bytes per Pixel: "; .bpp
PRINT "Width: "; .width
PRINT "Height: "; .height
PRINT "Bytes per Row: "; .pitch
End With
Draw String (0, 100), "The graphic:"
Put (100, 100), buffer, PSET
Open Err For Output As #1
PRINT #1, "Pixel Data:"
PRINT #1, ""
For i = 0 To 31
PRINT #1, i, Hex(Colors[i], 2),
If (i + 1) Mod 4 = 0 Then ? #1, ""
If (i ) Mod 4 = 0 Then
PRINT #1, Hex(Pixels[i \ 4], 8)
Else
PRINT #1, ""
End If
Next
ImageDestroy buffer
Close #1
Sleep
Beispiel 2:
#Include "fbgfx.bi"
Using FB
ScreenRes 400, 300, 32
Dim As Any Ptr buffer
Dim As Image Ptr header
Dim As UInteger Ptr Pixel
Dim As UInteger x, y, pitch
buffer = ImageCreate(256, 256)
header = buffer
pitch = header->pitch
'Open Err For Output As #1
'Print #1, Using "Start Adress: ##########"; Cast(Integer, buffer)
For y = 0 To 255
For x = 0 To 255
Pixel = buffer + 32 + (y * pitch) + (x * 4)
*Pixel = RGB(x, y, 0)
Next
'Print #1, Using "Line ### starts at: ##########"; y; Cast(Integer, Pixel)
Next
Put (0, 0), buffer
'Close #1
Sleep
Erkennen des Puffertyps und Bearbeitung
Je nach gewählten Ausgangsbedingungen wird FreeBASIC bei der Erstellung neuer Datenpuffer immer eine bestimmte Version des Puffers benutzen:
- Bedingungen für Version 1:
freeBASIC-Version <= 0.16 oder
freeBASIC-Version >= 0.17 und Kommandozeilenoption -lang qb oder
freeBASIC-Version >= 0.17 und Kommandozeilenoption -lang deprecated - Bedingungen für Version 2:
freeBASIC-Version >= 0.17 und Kommandozeilenoption -lang fb
Dennoch ist es möglich, dass ein anderes Pufferformat behandelt werden soll, als das unter gegebenen Bedingungen von FreeBASIC gewählte, z.B. wenn mittels BLOAD Daten in den Speicher geladen werden, die ein älteres Programm erstellt hat. Die Drawing Primitives ab FreeBASIC v0.17 können - bei jeder gewählten Kommandozeilenoption - mit beiden Pufferformaten umgehen; sie erkennen automatisch, welche Struktur angewandt werden muss. Die Drawing Primitives von FreeBASIC-Versionen vor v0.17 können nur mit Version 1 des Pufferformats umgehen; der User muss selbstständig Daten des jüngeren Formats umwandeln. Bei direkten Speicherzugriffen ohne die FB-Eigenen Drawing Primitives kann anhand des ersten INTEGER-Stelle des Headers überprüft werden, ob es sich um das alte oder das neue Format handelt: Beim neuen Format beinhaltet diese immer den Wert 7; beim alten Format ist hier ein anderer Wert gespeichert, der sich aus Farbtiefe, Höhe und Breite des Bildpuffers zusammensetzt. Dieser Wert kann nie gleich sieben werden. Mit dem Headertyp ändert sich auch der Startpunkt der eigentlichen Pixeldaten. Diese Formel gibt unabhängig von Pointertyp und Pufferformat den richtigen Startpunkt aus:
start = buffer + IIF( PEEK(INTEGER, buffer) = 7, 32, 4 ) \ SIZEOF(buffer)
Beachten Sie hierbei, dass Sie separat prüfen müssen, ob die Zeilen gepaddet sind. Dies ist bei Version 2 IMMER der Fall, in Version 1 hingegen NIE.
Version 1 (veraltet)
Diese Version des Bildpuffers wurde bis FreeBASIC v0.16 benutzt; seit v0.17 ist er nur noch dann verfügbar, wenn die Kommandozeilenoption -lang deprecated oder -lang qb
eingesetzt wird. Er besteht aus dem Header und den eigentlichen Pixelinformationen. Der Header ist ein Bitfeld (siehe TYPE (UDTs) und Bitfelder), das Angaben über Höhe, Breite und Farbtiefe des Bildes enthält; an ihn schließen sich die eigentlichen Pixeldaten im oben genannten Format an. Der Header besteht insgesamt aus 32bit, also vier Bytes oder einer INTEGER-Stelle. Er ist folgendermaßen aufgebaut:
TYPE _OLD_HEADER FIELD = 1
bpp : 3 AS USHORT
width : 13 AS USHORT
height AS USHORT
END TYPE
Die einzelnen Elemente bedeuten dabei Folgendes:
- 'bpp' gibt die Farbtiefe in Bytes pro Pixel an. Er ist entweder gleich 1 (für 1-8bit), 2 (für 15 und 16bit), oder gleich 4 (für 24 und 32bit)
- 'width' gibt die Breite des Bildes in Pixeln an. Da 13 Bits verwendet werden, kann dieser Wert zwischen 1 und 8191 liegen; eine Breite von 0 ist (aus praktischen Gründen) ungültig.
- 'height' gibt die Höhe des Bildes in Pixeln an. Da hier eine volle USHORT-Stelle (16 Bit) verwendet wird, kann dieser Wert zwischen 1 und 65535 liegen; eine Höhe von 0 ist (aus praktischen Gründen) ungültig.
Dem Header schließen sich die eigentlichen Pixeldaten an; für jeden Pixel werden dabei so viele Bytes verwendet, wie in 'bpp' angegeben ist. Die Pixeldaten sind von links nach rechts und von oben nach unten geordnet. Die Größe eines Puffers nach diesem Format lässt sich also folgendermaßen berechnen:
size = 4 + (w * h * b)
Wobei 'w' die Breite, 'h' die Höhe und 'b' die Farbtiefe in Bytes pro Pixel ist.
Beispiel: Ein Bildpuffer der Größe 2x2 Pixel in einem 32bpp-Modus, der einen roten, grünen, gelben und blauen Pixel enthält:
Byte | Bedeutung | Wert |
---|---|---|
0 und 1 | (Breite SHL 3) OR bpp | 33 |
2 und 3 | Höhe | 1 |
4 bis 7 | Farbe des ersten Pixels (links oben) im Format ARGB | &H00FF0000 |
8 bis 11 | Farbe des zweiten Pixels (rechts oben) im Format ARGB | &H0000FF00 |
12 bis 15 | Farbe des dritten Pixels (links unten) im Format ARGB | &H00FFFF00 |
16 bis 19 | Farbe des vierten Pixels (rechts unten) im Format ARGB | &H000000FF |
Dies verdeutlicht auch dieses Programm:
ScreenRes 400, 300
Dim x As UByte Ptr
Dim y As UInteger Ptr
Dim i As Integer
x = ImageCreate(2, 2)
y = x
Pset x, (0, 0), &HFF0000
Pset x, (1, 0), &H00FF00
Pset x, (0, 1), &H0000FF
Pset x, (1, 1), &HFFFF00
For i = 0 To 19
Print i, x[i],
If ( i Mod 4) = 0 Then
Print Hex(y[i \ 4], 8)
Else
Print
End If
If ((i + 1) Mod 4) = 0 Then Print
Next
ImageDestroy x
Sleep