GDI-Programmierung
Die Mehrheit der Windows-Benutzer hat den VGA-Treiber installiert, dessen Farbpalette nicht einmal ganz für die 20 Systemfarben von Windows ausreicht. Um diesen VGA-Benutzern einen etwas erweiterten Farbgenuss zukommen zu lassen, muss man sich mit den rätselhaften ROPs (Raster OPerations) und dem GDI (Graphics Device Interface) auseinandersetzen.
Ausser einfachen Cartoons [Beispiel in ZIP: WERNER.BMP] kann man mit dem VGA-Treiber unter Windows keine brauchbaren Bilder hervorzaubern. Gegenüber dem EGA unter DOS ist sogar ein Rückschritt eingetreten: Dort konnte man die sechzehn Bildschirmfarben wenigstens beliebig aus zwei Millionen Farben auswählen. Unter Windows ist man auf hellere und dunklere Primärfarben reduziert.
Um nun die Benutzer dieser Hauptplattform für Windows doch einem höheren Bild- und Farbgenuss auszusetzen, muss man Tricks anwenden. Schon Windows selber benutzt mit komischen Mustern gerasterte „Farben” für die 4 Systemfarben, die in der 16er-Palette nicht vorkommen.
Die im Folgenden dargestellte Rastertechnik wurde erfolgreich eingesetzt für die bessere Annäherung an die reale Darstellung der 32 (aus 4096) Farben des CEPT-Standards für Bildschirmtext des Videotex-Decoders VTXWINX der Firma Furrer+Partner AG, Zürich. [Beispiele: VTXENTER.BMP, TESTBILD.BMP]
In diesem Artikel bauen wir dieselbe Technik in unser vom SDK-ShowDIB abgeleitetes EnterVU ein. Dieses Programm dient uns als Experimentierfeld für alle möglichen Bildverarbeitungsalgorithmen und enthält schon von früher her die bequemen Datei-Funktionen für verschiedene Bildformate sowie Druck und Bildschirm-Anzeige. Wir brauchen nur die Überdefinition der Funktion, die aus einer geräteunabhängigen Bitmap (device independent bitmap, DIB) eine geräteabhängige (device dependent bitmap, DDB) macht, in einem neuen Modul zu implementieren und schon ist die neue Funktionalität nahtlos ins Programm eingefügt. Die objekt-orientierte OWL (Object Windows Library) macht's möglich.
Die Grundidee für die Erzeugung von mehr als sechzehn Farben basiert auf der Idee der Mischung von zwei Farben, indem man immer abwechselnd nebeneinanderliegende Bildpunkte (picture elements, Pixels) in der einen bzw. in der anderen Farbe einfärbt. Der subjektive Eindruck der Farbe einer solchen schachbrettartig mit zwei Farben gefüllten Fläche entspricht dem Mittel der beiden Farben. Auf diese Art kann man also zusätzlich zu den sechzehn darstellbaren „reinen” Farben noch die Mischung aus je zwei Farben zur Darstellung verwenden.
Die resultierende Farbpalette ist zwar immer noch nicht mit der 256er-Palette eines Super-VGA-Treibers zu vergleichen, da sie einerseits weniger Einträge besitzt (etwa 84 Einträge, siehe unten), und andererseits – viel gravierender! – nicht ermöglicht, diese Paletteneinträge frei aus sechzehn Millionen Farben auszuwählen. Für die Darstellung von Bildschirmtext und für bescheidenere Schmuckgraphik kann man aber deutliche Fortschritte verzeichnen.
Um sich mit den Mischmöglichkeiten vertraut zu machen, muss man als erstes die 16er-Palette des VGA-Treibers kennenlernen. Einige Experimente mit GetPaletteEntries und GetSystemPaletteEntries führen auf die Tabelle, die in Abb. 1 in Form eines static-Arrays ColorBase von RGBQUAD-Einträgen abgebildet ist.
static RGBQUAD ColorBase[] =
{ {0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x80, 0x00},
{0x00, 0x80, 0x00, 0x00},
{0x00, 0x80, 0x80, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x80, 0x00, 0x80, 0x00},
{0x80, 0x80, 0x00, 0x00},
{0x80, 0x80, 0x80, 0x00},
{0xC0, 0xC0, 0xC0, 0x00},
{0x00, 0x00, 0xFF, 0x00},
{0x00, 0xFF, 0x00, 0x00},
{0x00, 0xFF, 0xFF, 0x00},
{0xFF, 0x00, 0x00, 0x00},
{0xFF, 0x00, 0xFF, 0x00},
{0xFF, 0xFF, 0x00, 0x00},
{0xFF, 0xFF, 0xFF, 0x00}
};
Abb. 1: Die 16 reinen VGA-Farben
Man könnte nun theoretisch jede dieser 16 Farben mit jeder anderen mischen und käme auf 256 Kombinationen. Da bei Vertauschen der beiden Mischfarben dieselbe Farbe entsteht, sind allerdings höchstens 136 verschiedene Farben erzielbar. Auch von diesen Kombinationen haben einige denselben Farbwert. Auch sind Mischungen von zu weit auseinanderliegenden Farben unschön.
Um eine Tabelle der darstellbaren Farben und ihrer Mischkomponenten zu erhalten, müssen wir uns zuerst über den Mischeffekt des Schachbrettrasters der Bildschirmpunkte klar werden.
Da die Bildpunkte nebeneinander stehen und nicht wie beim Farbdruck Farben aufeinander gedruckt werden, addiert sich der Effekt der beiden Farben. Wir haben es also mit einer additiven Farbmischung des RGB-Systems der Bildschirmtechnik zu tun, nicht mit der subtraktiven Mischung des CMYK-Systems der Drucktechnologie. Wie Erwin Schrödinger in den dreissiger Jahren nachgewiesen hat, kann man bei der additiven Farbmischung die Rot-, Grün- und Blau-Komponenten separat addieren. Diese Tatsache ist bekannt unter den Bezeichnungen Superpositionsprinzip, bzw. Linearität des Farbraums.
Wir gehen davon aus, dass die RGBQUAD-Repräsentation der Farben wirklich linear den Bildschirmfarben entspricht. Es ist Sache der Bildschirmtreiberhersteller, diese Annahme immer besser zu verwirklichen. Man kann diese Annahme übrigens durch Drehen der Helligkeits- und Kontrast-Knöpfe am Bildschirm stark verbessern oder verfälschen.
Bei der vorgeschlagenen Schachbrettmischung zweier Farben müssen wir dann noch berücksichtigen, dass jede Farbe nur die Hälfte der Fläche bedeckt und somit ihre wahrgenommene Intensität noch halbiert werden muss. Als Mischfarbe, die aus den beiden Farbkomponenten A = (Ar,Ag,Ab) und B = (Br,Bg,Bb) entsteht, erhalten wir das arithmetische Mittel C = (Cr,Cg,Cb), durch Mittelwertbildung in jeder Komponente:
Cr = (Ar + Br)/2
Cg = (Ag + Bg)/2
Cb = (Ab + Bb)/2
Bei der Bestimmung der durch Rastern darstellbaren Farben stellen wir fest, dass das Fehlen von gemischten 0xFF- und 0x80-Einträgen in der ColorBase den ärgerlichen Effekt hat, dass wir die am meisten gesättigten Farben an der Oberfläche des Farbwürfels am schlechtesten mischen können. Bei der Untersuchung der durch Mischung erzeugbaren Farben im Farbwürfel zeigt sich schnell, dass man sonst jede Zwischenfarbe erzeugen kann, wo alle Komponenten ein vielfaches von 0x40 sind. Auf die wenigen weiteren durch Mischung aus den ColorBase-Farben erzeugbaren Mischfarben, verzichten wir, da es sich nur um recht wenige und zum Teil unschöne (weit auseinanderliegende Komponenten) oder nur wenig von ihren Nachbarn differenzierte Farben handelt. Die resultierenden Algorithmen profitieren beträchtlich von dieser Beschränkung. Der Farbwürfel in Abb. 2 stellt alle neu durch Rasterung darstellbaren Farben übersichtlich dar.
Blau = 0x00 Blau = 0x40 Blau = 0x80
Grün:
0xFF █────┬────▒────┬────█ ┌────┬────┬────┬────┐ ▒────┬────▒────┬────▒
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
0xC0 ▒────▒────▒────▒────┤ ▒────▒────▒────▒────┤ ▒────▒────▒────▒────┤
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
0x80 █────▒────█────▒────▒ ▒────▒────▒────▒────┤ █────▒────█────▒────▒
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
0x40 ▒────▒────▒────▒────┤ ▒────▒────▒────▒────┤ ▒────▒────▒────▒────┤
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
0x00 █────▒────█────▒────█ ▒────▒────▒────▒────┘ █────▒────█────▒────▒
Rot: 0x00 0x40 0x80 0xC0 0xFF 0x00 0x40 0x80 0xC0 0xFF 0x00 0x40 0x80 0xC0 0xFF
Blau = 0xC0 Blau = 0xFF
Grün:
0xFF ┌────┬────┬────┬────┐ █────┬────▒────┬────█
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
0xC0 ▒────▒────▒────█────┤ ▒────▒────▒────▒────┤
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
0x80 ▒────▒────▒────▒────┤ ▒────▒────▒────▒────▒
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
0x40 ▒────▒────▒────▒────┤ ▒────▒────▒────▒────┤
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
0x00 ▒────▒────▒────▒────┘ █────▒────▒────▒────█
Rot: 0x00 0x40 0x80 0xC0 0xFF 0x00 0x40 0x80 0xC0 0xFF
Abb. 2: VGA-Farben (█) und Mischfarben (▒) im Farbwürfel
Es ist dann (nur) noch eine Fleissarbeit, für jeden der 125 Mischfarbenpunkte dieses Farbwürfels die zwei Grundfarben aus der ColorBase zu bestimmen, welche gemischt die betreffende Farbe ergeben. Im Array MixTable in Abb. 3 ist diese Fleissarbeit geleistet. Da 41 der möglichen Kombinationen ungültig sind, verbleiben 84 verwendbare Farbkombinationen.
static int MixTable[][2] =
{
{ 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 9 }, { 9, 9 },
{ 0, 2 }, { 1, 2 }, { 1, 3 }, { 3, 9 }, {-1, -1 },
{ 2, 2 }, { 2, 3 }, { 3, 3 }, { 1, 11 }, { 9, 11 },
{ 2, 10 }, { 3, 10 }, { 2, 11 }, { 3, 11 }, {-1, -1 },
{10, 10 }, {-1, -1 }, {10, 11 }, {-1, -1 }, {11, 11 },
{ 0, 4 }, { 1, 4 }, { 1, 5 }, { 9, 5 }, {-1, -1 },
{ 2, 4 }, { 2, 5 }, { 3, 5 }, { 7, 9 }, {-1, -1 },
{ 2, 6 }, { 3, 6 }, { 3, 7 }, { 5, 11 }, { 0, 0 },
{ 6, 10 }, { 7, 10 }, { 6, 11 }, { 7, 11 }, {-1, -1 },
{-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },
{ 4, 4 }, { 4, 5 }, { 5, 5 }, { 1, 13 }, { 9, 13 },
{ 4, 6 }, { 5, 6 }, { 5, 7 }, { 3, 13 }, {-1, -1 },
{ 6, 6 }, { 6, 7 }, { 7, 7 }, { 1, 15 }, {13, 11 },
{ 2, 14 }, { 3, 14 }, { 2, 14 }, { 3, 15 }, {-1, -1 },
{10, 14 }, {-1, -1 }, {11, 14 }, {-1, -1 }, {11, 15 },
{ 4, 12 }, { 5, 12 }, { 4, 13 }, { 5, 13 }, {-1, -1 },
{ 6, 12 }, { 7, 12 }, { 6, 13 }, { 7, 13 }, {-1, -1 },
{ 4, 14 }, { 5, 14 }, { 4, 15 }, { 5, 15 }, {-1, -1 },
{ 6, 14 }, { 7, 14 }, { 6, 15 }, { 8, 8 }, {-1, -1 },
{-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },
{12, 12 }, {-1, -1 }, {12, 13 }, {-1, -1 }, {13, 13 },
{-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },
{12, 14 }, {-1, -1 }, {14, 13 }, {-1, -1 }, {13, 15 },
{-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },
{14, 14 }, {-1, -1 }, {14, 15 }, {-1, -1 }, {15, 15 }
};
Abb. 3: Die Mischtabelle (-1 steht für ungültige Kombinationen)
Um nun bei gegebener Realfarbe auf den geeignetsten Eintrag in der Mischtabelle zu kommen, wendet man mit Vorteil die Funktion ColorIndex an. Diese rundet die gegebene Farbe zuerst auf die nächstgelegenen 0x40-Vielfachen und überprüft dann ob sich die resultierende Farbe an der Oberfläche des Farbwürfels befindet, wo wir nur Vielfache von 0x80 darstellen können (s. Abb. 4).
BYTE ColorIndex(RGBQUAD rgb)
{
RGBQUAD rgbTemp;
rgbTemp = rgb;
// reduce it to the nearest multiple of 0x40 in each color ...
rgbTemp.rgbRed = ((int)rgb.rgbRed + 0x20) >> 6;
rgbTemp.rgbGreen = ((int)rgb.rgbGreen + 0x20) >> 6;
rgbTemp.rgbBlue = ((int)rgb.rgbBlue + 0x20) >> 6;
if ((rgbTemp.rgbRed == 4) ||
(rgbTemp.rgbGreen == 4) ||
(rgbTemp.rgbBlue == 4))
{
// ... or to the nearest multiple of 0x80 in each color
rgbTemp.rgbRed = ((rgb.rgbRed + 0x40) >> 7) << 1;
rgbTemp.rgbGreen = ((rgb.rgbGreen + 0x40) >> 7) << 1;
rgbTemp.rgbBlue = ((rgb.rgbBlue + 0x40) >> 7) << 1;
}
return rgbTemp.rgbRed +
5*rgbTemp.rgbGreen +
25*rgbTemp.rgbBlue;
} // ColorIndex
Abb. 4: ColorIndex berechnet die nächste Mischfarbe für eine beliebige Farbe.
Als nächsten Schritt gilt es sich in die Eigenheiten des GDI zu vertiefen. Es geht um eine möglichst ökonomische Darstellung einer Bitmap mit Schachbrettrasterung. Es dürfte klar sein, dass das Setzen jedes einzelnen Pixels nicht in Frage kommt. Nun besitzt das Windows-Programmier-Interface eine Reihe von mächtigen Aufrufen, die ermöglichen, eine ganze Bitmap auf dem Bildschirm anzuzeigen. Die meisten dieser Aufrufe (BitBlt,
StretchBlt, StretchDIBits) enthalten als Parameter eine Rasteroperation, die angibt, wie bei diesem Anzeigevorgang die Bits (Pixels) der darzustellenden Bitmap (Quelle/Source) mit den Bits eines überlagerbaren Musters (Brush/Pattern) und den schon vorher am Bildschirm dargestellten Bits (Ziel/Destination) zu kombinieren sind.
In unserem Fall können wir davon ausgehen, dass die darzustellende Bitmap zusammen mit einer Palette im 1-Byte-Format vorliegt. In diesem Fall müsste es möglich sein, mit klugem Einsatz einer Schachbrett-Brush und richtig gewählten Rasteroperationen, mit nur zwei StretchDIBits aus einer
geräteunabhängigen eine geräteabhängige Bitmap zu erzeugen. Wir sehen uns gezwungen, die Funktion StretchDIBits zu verwenden, obwohl wir gar nicht „stretchen” wollen, weil im API rätselhafterweise bei den SetDIB...-Funktionen der Parameter dwRop fehlt, der bei der entsprechenden Blt-Funktion zur Verfügung steht.
Wenn man sich die Tabelle der ternären Rasteroperationen im Software Development Kit (SDK) anschaut, ist man von der Aufgabe erst einmal überfordert, die richtige Kombination von booleschen Verknüpfungen zu bestimmen, die in unserem speziellen Fall benötigt werden. Eine genauere Analyse der Liste vereinfacht die Sache jedoch beträchtlich und ermöglicht uns, die benötigten zwei Operationen ohne mühselige Suche oder wildes Raten einfach zu bestimmen.
Boolesche Hex Boolesche Symbolische
Funktion ROP Funktion in Bezeichnung
in Hex polnischer Notation
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
00 00000042 0 BLACKNESS
01 00010289 DPSoon -
02 00020C89 DPSona -
03 000300AA PSon -
04 00040C88 SDPona -
.
.
.
FB 00FB0A09 DPSnoo PATPAINT
FC 00FC008A PSo -
FD 00FD0A0A PSDnoo -
FE 00FE02A9 DPSoo -
FF 00FF0062 1 -
Abb. 5: Tabelle der ternären Rasteroperationen aus dem SDK (Ausschnitt)
Die Tabelle der ternären Rasteroperationen hat 256 Einträge. Das ROP-Doppelwort besteht aus einer linken Hälfte, welche einfach eine Wiederholung der booleschen Funktionsnummer „in Hex” ist, nach welcher die Liste sortiert ist, und aus einer rechten Hälfte, welche irgendwie die boolesche Funktion „in Reverse Polish” zu kodieren scheint.
Funktionsnummer: AC
Kode: 00AC0744
Linke Hälfte: 00AC = Funktionsnummer
Rechte Hälfte: 0744 = 00 00 01 11 01 00 0100
│ │ │ │ └── SP...
│ │ │ └────── ungerade Anzahl Operationen
│ │ └────────── xor
│ └───────────── and
└──────────────── xor
Polnische Notation: SPDSxax
Abb. 6: Linke und rechte Hälfte des ROP-Kodes AC
Es gibt eben zwei Arten, um eine boolesche Funktion mit drei booleschen Parametern zu beschreiben: Einerseits kann man einfach eine Tabelle der Funktionswerte für alle Kombinationen der Eingabeparameter anlegen, wie dies im Text im SDK-Handbuch auf der Seite vor der grossen Tabelle ausgeführt wird. Bei drei Parametern, die je den Wert 0 oder 1 annehmen können, enthält diese Tabelle acht Einträge mit einer 0 oder 1. Diese kann man bequem in ein Byte verpacken und als „Funktionsnummer” für die entsprechende Funktion verwenden. Andererseits kann man die Funktion als Kombination der elementaren booleschen Funktionen and, or, xor und not auf den verschiedenen Inputs ausdrücken. Diese Darstellung ist nicht eindeutig. Es gehört eine Portion mathematischer Erfahrung dazu, eine möglichst optimale Kombination für eine gegebene boolesche Funktion zu bestimmen.
Nun zeigt sich uns erst der unschätzbare Wert dieser Tabelle: Jemand hat sich die Mühe gemacht und für jede mögliche Funktionswertekombination für die drei Inputs (Source, Destination, Pattern) eine optimale Kombination von elementaren booleschen Operationen aufzulisten, die wahrscheinlich auch beim Windows-internen Ausführen der Rasteroperation in dieser Reihenfolge verwendet wird. Wir brauchen uns jedoch nur um die linke Hälfte zu kümmern und erhalten aus der Tabelle die für das StretchDIBits relevante rechte Hälfte des dwRop-Parameters.
Wie kommen wir nun auf den Funktionskode der beiden von uns benötigten ROPs? Wir stellen uns vor, dass die „Brush” (das „Pattern”) ein Schachbrettmuster definiert. Wir benötigen zwei StretchDIBits-Operationen zur Darstellung der farbgerasterten Bitmap. Beide Operationen verwenden die Schachbrett-Brush. Zuerst erstellen wir eine Farbpalette, die jeweils aus der ersten Mischfarbe für den betreffenden Farbeintrag besteht. Wir wünschen nun, dass überall dort, wo das Schachbrett weiss ist (Pattern = 1) die Bitmap (Source) abgebildet wird, wahrend überall dort, wo das Schachbrett schwarz ist, (Pattern = 0) der Farbwert des Bildschirms an dieser Stelle unverändert bleibt (d.h. die Farbe der Destination behält). Das äussert sich darin, dass in der Tabelle in Abb. 5 für die Zeilen mit Pattern 0 die Einträge von „Destination” ins Resultat kopiert wurden und für die anderen Zeilen die Einträge von „Source”.
Pattern Source Destination Resultat
- - - - - - - - - - - - - - - - -
0 0 0 0
0 0 1 1
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 0
1 1 0 1
1 1 1 1
Abb. 7: Erste ROP: 11001010 = CA
Wenn wir nun noch berücksichtigen, dass die Bits in der Resultatspalte von unten nach oben gelesen die Funktionsnummer CA des ROP-Eintrags ergeben, wissen wir dank der Tabelle im SDK, dass wir für unsere erste Rasteroperation den Code 0x00CA0749L benötigen.
Bei der zweiten Rasteroperation ist einfach die Rolle von Pattern 0 und Pattern 1 vertauscht. Das ergibt die Funktionsnummer AC.
Pattern Source Destination Resultat
- - - - - - - - - - - - - - - - -
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 0
1 1 1 1
Abb. 8: Zweite ROP: 10101100 = AC
In unserem Programm schlägt sich dieser ganze Exkurs ins Reich der booleschen Funktionen bloss in zwei Definitionen nieder:
#define RASTER_COPY 0x00CA0749L // Rop for copying with raster
#define RASTER_MERGE 0x00AC0744L // Rop for merging with raster
Abb. 9: Raster-ROPs
Ein bisschen haben wir oben bei der Bestimmung der Rasteroperationen gemogelt. Wir haben nämlich mit keinem Wort erwähnt, dass wir die besagte Rasteroperation für alle vier Bitplanes der VGA-Farbdarstellung benötigen. Das ist allerdings leicht zu bewerkstelligen, wenn wir die zu verwendende Brush als Stanzwerkzeug gestalten, das in allen vier Bitplanes eine 1 hat an den weissen Schachbrettpositionen und in allen vier Bitplanes eine 0 an den schwarzen.
Tatsächlich könnten wir uns im vorliegenden Fall darauf beschränken, nur eine monochrome Brush zu verwenden und es Windows zu überlassen, diese für den Zweck der Rasterung temporär in eine 4-plane Brush zu verwandeln. Dann wären wir aber gezwungen, jedesmal die TextColor und die BkColor auf schwarz und weiss zu setzen. In anderen Anwendungen als der vorliegenden
Bildanwendung könnten die zu rasternden Bilder monochrome Bitmaps mit verschiedenen Vordergrund- und Hintergrundfarben sein. Ausserdem ersparen wir Windows die jedesmalige Erzeugung einer 4-plane Brush, wenn wir diese ein für alle Mal selber herstellen.
Wir benützen dabei die Tatsache, dass man eine Bitmap in eine Brush verwandeln kann. Wir brauchen also nur eine Schachbrett 4-plane Bitmap herzustellen. Mit diesem Ziel initialisieren wir erst eine monochrome Bitmap mit den Schachbrettbits CheckerBits. Diese wird darauf (mit korrekt gewählter Vordergrund und Hintergrundfarbe) auf eine 4-plane Bitmap kopiert. Und diese schliesslich bildet das Rohmaterial für unsere Schachbrett-Brush.
static WORD CheckerBits[] =
{0xAAAA, 0x5555, 0xAAAA, 0x5555,
0xAAAA, 0x5555, 0xAAAA, 0x5555};
Abb. 10: Schachbrett-Bits für monochrome 8*8 Bitmap
Das ganze Verfahren findet man in der Funktion MakeCheckerBrush in Abb. 9 zusammengefasst, welche nur für den VGA-Treiber eine Brush herstellt, für Treiber mit 256-Farben oder mehr jedoch auf das Rastergemisch verzichtet.
HBRUSH MakeCheckerBrush()
{
int w;
int i; // holds number of colors
HBITMAP hBit1,hSrcMap; // monochrome bitmap
HBITMAP hBit4,hDestMap; // 4-plane bitmap for 4-plane brush
HDC hDC; // screen DC used as reference
HDC hSrcDC; // DC for monochrome pattern
HDC hDestDC; // DC for 4-plane pattern
HBRUSH hCheckerBrush;
hDC = GetDC(NULL);
// get number of colors that the physical screen can display
i = GetDeviceCaps(hDC,PLANES);
i *= GetDeviceCaps(hDC,BITSPIXEL);
i = 1 << i;
w = GetDeviceCaps(hDC,RASTERCAPS);
if (w & RC_PALETTE)
i = GetDeviceCaps(hDC,SIZEPALETTE);
if ((i > 0) && (i <= 16))
{
hBit1 = CreateBitmap(8,8,1,1,(LPSTR)CheckerBits);
hSrcDC = CreateCompatibleDC(hDC);
hSrcMap = SelectObject(hSrcDC,hBit1);
hBit4 = CreateCompatibleBitmap(hDC,8,8);
hDestDC = CreateCompatibleDC(hDC);
hDestMap = SelectObject(hDestDC,hBit4);
SetBkMode(hDestDC,OPAQUE);
SetBkColor(hDestDC, 0x00FFFFFFL); // white
SetTextColor(hDestDC, 0x00000000L); // black
BitBlt(hDestDC,0,0,8,8,hSrcDC,0,0,SRCCOPY);
hCheckerBrush = CreatePatternBrush(hBit4);
SelectObject(hSrcDC,hSrcMap);
DeleteDC(hSrcDC);
SelectObject(hDestDC,hDestMap);
DeleteDC(hDestDC);
DeleteObject(hBit1);
DeleteObject(hBit4);
}
else
hCheckerBrush = NULL;
// done with screen DC
ReleaseDC(NULL,hDC);
// return brush
return hCheckerBrush;
} // MakeCheckerBrush
Abb. 11: MakeCheckerBrush erzeugt das Stanzwerkzeug.
Um nun in den Genuss all dieser Vorarbeiten zu kommen, konstruieren wir die anfangs erwähnte Überdefinition der Funktion DDBFromDIB der EnterVU-Anwendung. Um diese objektorientierte Ableitung eines DDB-Objekts zu verstehen, genügt es, zu wissen, dass dieses Objekt schon weiss, wie man DIBs liest und schreibt und druckt und am Bildschirm darstellt und normalerweise mittels der virtuellen Funktion DDBFromDIB aus der geräteunabhängigen Bitmap (DIB) eine bildschirmkompatible Bitmap (DDB) herstellt, die dann vom DDB-Objekt für die Darstellung am Bildschirm verwendet wird als Reaktion auf WM_PAINT-Meldungen. Dieses DDB-Objekt besitzt als Memberdaten den Pointer pbi der auf die momentan aktive BITMAPINFO-Struktur zeigt und den Pointer hpBits der auf die Bits der DIB zeigt. Ausserdem besitzt dieses DDB-Objekt die Funktion FlushDDB, welche eine allfällige vorherige DDB sauber abräumt.
Die neue Funktion DDBFromDIB greift auf die alte zurück, wenn entweder kein Rastern erwünscht ist (!bMix) oder der Bildschirm mehr als 16 Farben hat (!hBrush4).
Falls nun die zu konvertierende DIB eine Farbpalette von bis zu 256 Einträgen besitzt, verwenden wir zweimal StretchDIBits mit den beiden oben eruierten Rasteroperationen, um die beiden Mischfarbenteile in die DDB zu bringen. Wir unterschieben der Funktion StretchDIBits dabei jedesmal eine mittels der Funktion ColorIndex errechnete Farbpalette, die beim ersten StretchDIBits jeweils der ersten Mischfarbe und beim zweiten StretchDIBits der zweiten Mischfarbe für die darzustellende Farbe entspricht.
BOOL TMix::DDBFromDIB()
{
WORD i;
HDC hDC;
HDC hMemDC;
HBITMAP hMemMap;
HBRUSH hMemBrush;
PBITMAPINFO pbi1;
PBITMAPINFO pbi2;
if ((!hBrush4) || (!bMix))
return TDDB::DDBFromDIB();
if (!pbi)
return FALSE;
if ((pbi->bmiHeader.biClrUsed > 256) || (pbi->bmiHeader.biClrUsed <= 0))
return TDDB::DDBFromDIB();
// Flush DDB
FlushDDB();
// lock the DIB bits
hpBits = (HPBYTE)GlobalLock(hBits);
if (!hpBits)
return FALSE;
// get bitmap from DIB
hDC = GetDC(NULL); // is needed in order to use RealizePalette
if (hDC)
{
// here we make the two bitmap infos
i = pbi->bmiHeader.biSize + PaletteSize();
// allocate them
pbi1 = (PBITMAPINFO)malloc(i);
pbi2 = (PBITMAPINFO)malloc(i);
// copy the headers
pbi1->bmiHeader = pbi->bmiHeader;
pbi2->bmiHeader = pbi->bmiHeader;
// loop over colors
for (i = 0; i < pbi->bmiHeader.biClrUsed; i++)
{
pbi1->bmiColors[i] = ColorBase[MixTable[ColorIndex(pbi->bmiColors[i])][0]];
pbi2->bmiColors[i] = ColorBase[MixTable[ColorIndex(pbi->bmiColors[i])][1]];
}
// make a memory DC for the DDB
hMemDC = CreateCompatibleDC(hDC);
// create the DDB
hDDB = CreateCompatibleBitmap(hDC,(int)pbi->bmiHeader.biWidth,(int)pbi->bmiHeader.biHeight);
// select bitmap into DC
hMemMap = SelectObject(hMemDC,hDDB);
// select brush into DC
hMemBrush = SelectObject(hMemDC,hBrush4);
// use StretchDIBits because of raster operations
i = StretchDIBits(hMemDC, 0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,
0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,
(LPSTR)hpBits,(LPBITMAPINFO)pbi1,DIB_RGB_COLORS,RASTER_COPY);
if (i != (int)pbi->bmiHeader.biHeight)
return FALSE;
i = StretchDIBits(hMemDC, 0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,
0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,
(LPSTR)hpBits,(LPBITMAPINFO)pbi2,DIB_RGB_COLORS,RASTER_MERGE);
if (i != (int)pbi->bmiHeader.biHeight)
return FALSE;
// free the bitmap infos
free(pbi1);
free(pbi2);
// delete memory DC
SelectObject(hMemDC,hMemMap);
SelectObject(hMemDC,hMemBrush);
DeleteDC(hMemDC);
}
ReleaseDC(NULL,hDC);
UnlockBits();
return (hDDB != NULL);
} // DDBFromDIB
Abb. 12: 84-Farben-Konversion
Wenn man sich nun verschiedene Farbbilder mit EnterVU gerastert und ungerastert betrachtet [Menüpunkt: Raster], so sieht man schnell, dass die resultierende Palette für eigentliche Realbilder immer noch zu schmalbrüstig ist. Mit geeigneter Vorbearbeitung im Photoshop dürfte aber doch einiges herauszuholen sein [BASEL.PY1, UNDER.PY1]. Für die bescheideneren Bildschirmtext-Bedürfnisse ergibt sich dagegen eine markante Verbesserung. Auch hier sind 84 Farben bei weitem noch nicht 4096 Farben [vergleiche TESTBILD.BMP gerastert, ungerastert und mit einem 256-Farben-Treiber], das Resultat ist aber doch schon sehr brauchbar. Die Übung hat uns ausserdem eine ausführliche Exkursion in sehr starke und wenig benützte Region des GDI ermöglicht.
14.5.93 Hartwig Thomas
[Die Verweise beziehen sich auf Inhalte der zugehörigen selbstextrahierenden ZIP-Dateien mit Bildern und Programmen. Dort findet man den gesamten Quellkode der Anwendung im Verzeichnis SOURCE, die EXE im Rootverzeichnis und die Bilder im Verzeichnis IMAGES.
VUSYSTEM.EXE enthält ENTERVU.EXE und einige Bilder
VUSOURCE.EXE enthält den gesamten Quellkode (BC++ 3.1) von EnterVU]