<meta name="generator" content="StarOffice/OpenOffice.org XSLT (http://xml.openoffice.org/sx2ml)" /><meta name="created" content="2007-08-09T16:18:54" /><meta name="changedby" content="Hartwig Thomas" /><meta name="changed" content="2007-08-09T17:26:21" /><base href="." /><style type="text/css"> @page { size: 21.001cm 29.7cm; margin-top: 2cm; margin-bottom: 1cm; margin-left: 2cm; margin-right: 2cm } table { border-collapse:collapse; border-spacing:0; empty-cells:show } td, th { vertical-align:top; } h1, h2, h3, h4, h5, h6 { clear:both } ol, ul { padding:0; } * { margin:0; } *.Caption { font-family:'Courier New'; font-size:12pt; margin-top:0.212cm; margin-bottom:0.212cm; font-style:italic; } *.Code { font-family:'Courier New'; font-size:8pt; } *.Header { font-family:'Courier New'; font-size:11pt; } *.Heading { font-family:Arial; font-size:14pt; margin-top:0.423cm; margin-bottom:0.212cm; } *.Heading1 { font-family:'Courier New'; font-size:11pt; margin-top:0.423cm; margin-bottom:0.212cm; font-weight:bold; } *.Heading2 { font-family:'Courier New'; font-size:11pt; margin-top:0.423cm; margin-bottom:0.212cm; font-style:italic; font-weight:bold; } *.Illustration { font-family:'Courier New'; font-size:11pt; margin-top:0.212cm; margin-bottom:0.212cm; font-style:italic; } *.Index { font-family:'Courier New'; font-size:11pt; } *.Ingress { font-family:'Courier New'; font-size:11pt; font-style:italic; } *.List { font-family:'Courier New'; font-size:11pt; margin-top:0cm; margin-bottom:0.212cm; } *.P1 { font-family:'Courier New'; font-size:11pt; text-align:center ! important; } *.P2 { font-family:'Courier New'; font-size:11pt; text-align:center ! important; } *.P3 { font-family:'Courier New'; font-size:11pt; font-style:italic; text-decoration:underline; } *.PreformattedText { font-family:'Times New Roman'; font-size:10pt; margin-top:0cm; margin-bottom:0cm; } *.Spitzmarke { font-family:'Courier New'; font-size:11pt; font-style:italic; text-decoration:underline; } *.Standard { font-family:'Courier New'; font-size:11pt; } *.Textbody { font-family:'Courier New'; font-size:11pt; margin-top:0cm; margin-bottom:0.212cm; } *.Emphasis { font-family:'Courier New'; font-size:11pt; font-style:italic; } *.T1 { vertical-align:sub; } *.T2 { font-style:normal; } </style></head><body dir="ltr"><p class="P3">GDI-Programmierung </p><p class="Standard"> </p><p class="Standard"> </p><p class="Standard"> </p><h1 class="Heading1"><a name="Raster-Operation" />Raster-Operation</h1><p class="Standard"> </p><p class="Standard"> </p><p class="Standard"> </p><p class="Ingress">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. </p><p class="Ingress"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="16_Farben_sind_zuwenig" />16 Farben sind zuwenig</h2><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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] </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="Schachbrett" />Schachbrett</h2><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="Farben" />Farben</h2><p class="Standard"> </p><p class="Standard">Um sich mit den Mischmöglichkeiten vertraut zu machen, muss man als erstes die 16er-Palette des VGA-Treibers kennenlernen. Einige Experimente mit <span class="Emphasis">GetPaletteEntries</span> und <span class="Emphasis">GetSystemPaletteEntries</span> führen auf die Tabelle, die in Abb. 1 in Form eines static-Arrays <span class="Emphasis">ColorBase</span> von <span class="Emphasis">RGBQUAD</span>-Einträgen abgebildet ist.</p><p class="Standard"> </p><p class="Code">static RGBQUAD ColorBase[] = </p><p class="Code">   { {0x00, 0x00, 0x00, 0x00},</p><p class="Code">     {0x00, 0x00, 0x80, 0x00},</p><p class="Code">     {0x00, 0x80, 0x00, 0x00},</p><p class="Code">     {0x00, 0x80, 0x80, 0x00},</p><p class="Code">     {0x80, 0x00, 0x00, 0x00},</p><p class="Code">     {0x80, 0x00, 0x80, 0x00},</p><p class="Code">     {0x80, 0x80, 0x00, 0x00},</p><p class="Code">     {0x80, 0x80, 0x80, 0x00},</p><p class="Code">     {0xC0, 0xC0, 0xC0, 0x00},</p><p class="Code">     {0x00, 0x00, 0xFF, 0x00},</p><p class="Code">     {0x00, 0xFF, 0x00, 0x00},</p><p class="Code">     {0x00, 0xFF, 0xFF, 0x00},</p><p class="Code">     {0xFF, 0x00, 0x00, 0x00},</p><p class="Code">     {0xFF, 0x00, 0xFF, 0x00},</p><p class="Code">     {0xFF, 0xFF, 0x00, 0x00},</p><p class="Code">     {0xFF, 0xFF, 0xFF, 0x00}</p><p class="Code">   };</p><p class="Standard"> </p><p class="Illustration">Abb. 1: Die 16 reinen VGA-Farben </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">Wir gehen davon aus, dass die <span class="Emphasis">RGBQUAD</span>-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.</p><p class="Standard"> </p><p class="Standard">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 = (A<span class="T1">r</span>,A<span class="T1">g</span>,A<span class="T1">b</span>) und B = (B<span class="T1">r</span>,B<span class="T1">g</span>,B<span class="T1">b</span>) entsteht, erhalten wir das arithmetische Mittel C = (C<span class="T1">r</span>,C<span class="T1">g</span>,C<span class="T1">b</span>), durch Mittelwertbildung in jeder Komponente:</p><p class="Standard"> </p><p class="Standard">C<span class="T1">r</span> = (A<span class="T1">r</span> + B<span class="T1">r</span>)/2</p><p class="Standard">C<span class="T1">g</span> = (A<span class="T1">g</span> + B<span class="T1">g</span>)/2</p><p class="Standard">C<span class="T1">b</span> = (A<span class="T1">b</span> + B<span class="T1">b</span>)/2</p><p class="Standard"> </p><p class="Standard"> </p><p class="Standard">Bei der Bestimmung der durch Rastern darstellbaren Farben stellen wir fest, dass das Fehlen von gemischten 0xFF- und 0x80-Einträgen in der <span class="Emphasis">ColorBase</span> 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 <span class="Emphasis">ColorBase</span>-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.</p><p class="Standard"> </p><p class="Code">          Blau = 0x00              Blau = 0x40              Blau = 0x80</p><p class="Code"> Grün:</p><p class="Code"> </p><p class="Code"> 0xFF █────┬────▒────┬────█    ┌────┬────┬────┬────┐    ▒────┬────▒────┬────▒</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0xC0 ▒────▒────▒────▒────┤    ▒────▒────▒────▒────┤    ▒────▒────▒────▒────┤</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0x80 █────▒────█────▒────▒    ▒────▒────▒────▒────┤    █────▒────█────▒────▒</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0x40 ▒────▒────▒────▒────┤    ▒────▒────▒────▒────┤    ▒────▒────▒────▒────┤</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0x00 █────▒────█────▒────█    ▒────▒────▒────▒────┘    █────▒────█────▒────▒</p><p class="Code">Rot: 0x00 0x40 0x80 0xC0 0xFF 0x00 0x40 0x80 0xC0 0xFF 0x00 0x40 0x80 0xC0 0xFF </p><p class="Code"> </p><p class="Code"> </p><p class="Code">          Blau = 0xC0              Blau = 0xFF</p><p class="Code"> Grün:</p><p class="Code"> </p><p class="Code"> 0xFF ┌────┬────┬────┬────┐    █────┬────▒────┬────█</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0xC0 ▒────▒────▒────█────┤    ▒────▒────▒────▒────┤</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0x80 ▒────▒────▒────▒────┤    ▒────▒────▒────▒────▒</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0x40 ▒────▒────▒────▒────┤    ▒────▒────▒────▒────┤</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code">      │    │    │    │    │    │    │    │    │    │</p><p class="Code"> 0x00 ▒────▒────▒────▒────┘    █────▒────▒────▒────█</p><p class="Code">Rot: 0x00 0x40 0x80 0xC0 0xFF 0x00 0x40 0x80 0xC0 0xFF </p><p class="Standard"> </p><p class="Illustration">Abb. 2: VGA-Farben (█) und Mischfarben (▒) im Farbwürfel </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="Mischen" />Mischen</h2><p class="Standard"> </p><p class="Standard">Es ist dann (nur) noch eine Fleissarbeit, für jeden der 125 Mischfarbenpunkte dieses Farbwürfels die zwei Grundfarben aus der <span class="Emphasis">ColorBase</span> zu bestimmen, welche gemischt die betreffende Farbe ergeben. Im Array <span class="Emphasis">MixTable</span> in Abb. 3 ist diese Fleissarbeit geleistet. Da 41 der möglichen Kombinationen ungültig sind, verbleiben 84 verwendbare Farbkombinationen.</p><p class="Standard"> </p><p class="Code">static int MixTable[][2] = </p><p class="Code">{ </p><p class="Code"> { 0,  0 }, { 0,  1 }, { 1,  1 }, { 1,  9 }, { 9,  9 },</p><p class="Code"> { 0,  2 }, { 1,  2 }, { 1,  3 }, { 3,  9 }, {-1, -1 },</p><p class="Code"> { 2,  2 }, { 2,  3 }, { 3,  3 }, { 1, 11 }, { 9, 11 },</p><p class="Code"> { 2, 10 }, { 3, 10 }, { 2, 11 }, { 3, 11 }, {-1, -1 },</p><p class="Code"> {10, 10 }, {-1, -1 }, {10, 11 }, {-1, -1 }, {11, 11 },</p><p class="Code"> </p><p class="Code"> { 0,  4 }, { 1,  4 }, { 1,  5 }, { 9,  5 }, {-1, -1 },</p><p class="Code"> { 2,  4 }, { 2,  5 }, { 3,  5 }, { 7,  9 }, {-1, -1 },</p><p class="Code"> { 2,  6 }, { 3,  6 }, { 3,  7 }, { 5, 11 }, { 0,  0 },</p><p class="Code"> { 6, 10 }, { 7, 10 }, { 6, 11 }, { 7, 11 }, {-1, -1 },</p><p class="Code"> {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },</p><p class="Code"> </p><p class="Code"> { 4,  4 }, { 4,  5 }, { 5,  5 }, { 1, 13 }, { 9, 13 },</p><p class="Code"> { 4,  6 }, { 5,  6 }, { 5,  7 }, { 3, 13 }, {-1, -1 },</p><p class="Code"> { 6,  6 }, { 6,  7 }, { 7,  7 }, { 1, 15 }, {13, 11 },</p><p class="Code"> { 2, 14 }, { 3, 14 }, { 2, 14 }, { 3, 15 }, {-1, -1 },</p><p class="Code"> {10, 14 }, {-1, -1 }, {11, 14 }, {-1, -1 }, {11, 15 },</p><p class="Code"> </p><p class="Code"> { 4, 12 }, { 5, 12 }, { 4, 13 }, { 5, 13 }, {-1, -1 },</p><p class="Code"> { 6, 12 }, { 7, 12 }, { 6, 13 }, { 7, 13 }, {-1, -1 },</p><p class="Code"> { 4, 14 }, { 5, 14 }, { 4, 15 }, { 5, 15 }, {-1, -1 },</p><p class="Code"> { 6, 14 }, { 7, 14 }, { 6, 15 }, { 8,  8 }, {-1, -1 },</p><p class="Code"> {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },</p><p class="Code"> </p><p class="Code"> {12, 12 }, {-1, -1 }, {12, 13 }, {-1, -1 }, {13, 13 },</p><p class="Code"> {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },</p><p class="Code"> {12, 14 }, {-1, -1 }, {14, 13 }, {-1, -1 }, {13, 15 },</p><p class="Code"> {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 }, {-1, -1 },</p><p class="Code"> {14, 14 }, {-1, -1 }, {14, 15 }, {-1, -1 }, {15, 15 }</p><p class="Code">}; </p><p class="Standard"> </p><p class="Illustration">Abb. 3: Die Mischtabelle (-1 steht für ungültige Kombinationen) </p><p class="Standard"> </p><p class="Standard">Um nun bei gegebener Realfarbe auf den geeignetsten Eintrag in der Mischtabelle zu kommen, wendet man mit Vorteil die Funktion <span class="Emphasis">ColorIndex</span> 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).</p><p class="Standard"> </p><p class="Code">BYTE ColorIndex(RGBQUAD rgb) </p><p class="Code">{ </p><p class="Code">  RGBQUAD rgbTemp;</p><p class="Code"> </p><p class="Code">  rgbTemp = rgb;</p><p class="Code">  // reduce it to the nearest multiple of 0x40 in each color ...</p><p class="Code">  rgbTemp.rgbRed   = ((int)rgb.rgbRed   + 0x20) >> 6;</p><p class="Code">  rgbTemp.rgbGreen = ((int)rgb.rgbGreen + 0x20) >> 6;</p><p class="Code">  rgbTemp.rgbBlue  = ((int)rgb.rgbBlue  + 0x20) >> 6;</p><p class="Code"> </p><p class="Code">  if ((rgbTemp.rgbRed == 4) ||</p><p class="Code">      (rgbTemp.rgbGreen == 4) ||</p><p class="Code">      (rgbTemp.rgbBlue == 4))</p><p class="Code">  {</p><p class="Code">    // ... or to the nearest multiple of 0x80 in each color</p><p class="Code">    rgbTemp.rgbRed   = ((rgb.rgbRed   + 0x40) >> 7) << 1;</p><p class="Code">    rgbTemp.rgbGreen = ((rgb.rgbGreen + 0x40) >> 7) << 1;</p><p class="Code">    rgbTemp.rgbBlue  = ((rgb.rgbBlue  + 0x40) >> 7) << 1;</p><p class="Code">  }</p><p class="Code"> </p><p class="Code">  return rgbTemp.rgbRed +</p><p class="Code">         5*rgbTemp.rgbGreen +</p><p class="Code">         25*rgbTemp.rgbBlue;</p><p class="Code">} // ColorIndex </p><p class="Standard"> </p><p class="Illustration">Abb. 4: ColorIndex berechnet die nächste Mischfarbe für eine beliebige Farbe. </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="Rastern" />Rastern</h2><p class="Standard"> </p><p class="Standard">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 (<span class="Emphasis">BitBlt</span>,</p><p class="Standard"><span class="Emphasis">StretchBlt</span>, <span class="Emphasis">StretchDIBits</span>) 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.</p><p class="Standard"> </p><p class="Standard">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 <span class="Emphasis">StretchDIBits</span> aus einer</p><p class="Standard">geräteunabhängigen eine geräteabhängige Bitmap zu erzeugen. Wir sehen uns gezwungen, die Funktion <span class="Emphasis">StretchDIBits</span> zu verwenden, obwohl wir gar nicht „stretchen” wollen, weil im API rätselhafterweise bei den <span class="Emphasis">SetDIB...</span>-Funktionen der Parameter <span class="Emphasis">dwRop</span> fehlt, der bei der entsprechenden <span class="Emphasis">Blt</span>-Funktion zur Verfügung steht.</p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Code">Boolesche        Hex        Boolesche        Symbolische</p><p class="Code">Funktion        ROP        Funktion in        Bezeichnung</p><p class="Code">in Hex                polnischer Notation</p><p class="Code">- - - - - - - -   - - - - - - - -   - - - - - - - - - - -   - - - - - -</p><p class="Code">00        00000042        0        BLACKNESS</p><p class="Code">01        00010289        DPSoon        -</p><p class="Code">02        00020C89        DPSona        -</p><p class="Code">03        000300AA        PSon        -</p><p class="Code">04        00040C88        SDPona        -</p><p class="Code">. </p><p class="Code">. </p><p class="Code">. </p><p class="Code">FB        00FB0A09        DPSnoo        PATPAINT</p><p class="Code">FC        00FC008A        PSo        -</p><p class="Code">FD        00FD0A0A        PSDnoo        -</p><p class="Code">FE        00FE02A9        DPSoo        -</p><p class="Code">FF        00FF0062        1        -</p><p class="Standard"> </p><p class="Illustration">Abb. 5: Tabelle der ternären Rasteroperationen aus dem SDK (Ausschnitt) </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Code">Funktionsnummer:        AC</p><p class="Code">Kode:        00AC0744</p><p class="Code">Linke Hälfte:        00AC = Funktionsnummer</p><p class="Code">Rechte Hälfte:        0744 = 00 00 01 11 01 00 0100</p><p class="Code">                                 │  │  │   │   └──  SP...</p><p class="Code">                                 │  │  │   └──────  ungerade Anzahl Operationen</p><p class="Code">                                 │  │  └──────────  xor</p><p class="Code">                                 │  └─────────────  and</p><p class="Code">                                 └────────────────  xor</p><p class="Code">Polnische Notation:        SPDSxax</p><p class="Standard"> </p><p class="Illustration">Abb. 6: Linke und rechte Hälfte des ROP-Kodes AC </p><p class="Standard"> </p><p class="Standard">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 <span class="Emphasis">and</span>, <span class="Emphasis">or</span>, <span class="Emphasis">xor</span> und <span class="Emphasis">not</span> 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.</p><p class="Standard"> </p><p class="Standard">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 <span class="Emphasis">StretchDIBits</span> relevante rechte Hälfte des <span class="Emphasis">dwRop</span>-Parameters.</p><p class="Standard"> </p><p class="Standard">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 <span class="Emphasis">StretchDIBits</span>-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”.</p><p class="Standard"> </p><p class="Code">Pattern        Source        Destination        Resultat</p><p class="Code">- - - -        - - -        - - - - - -        - - - -</p><p class="Code">        0                0                0                0</p><p class="Code">        0                0                1                1</p><p class="Code">        0                1                0                0</p><p class="Code">        0                1                1                1</p><p class="Code">        1                0                0                0</p><p class="Code">        1                0                1                0</p><p class="Code">        1                1                0                1</p><p class="Code">        1                1                1                1</p><p class="Standard"> </p><p class="Illustration">Abb. 7: Erste ROP: 11001010 = CA </p><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">Bei der zweiten Rasteroperation ist einfach die Rolle von Pattern 0 und Pattern 1 vertauscht. Das ergibt die Funktionsnummer AC. </p><p class="Standard"> </p><p class="Code">Pattern        Source        Destination        Resultat</p><p class="Code">- - - -        - - -        - - - - - -        - - - -</p><p class="Code">        0                0                0                0</p><p class="Code">        0                0                1                0</p><p class="Code">        0                1                0                1</p><p class="Code">        0                1                1                1</p><p class="Code">        1                0                0                0</p><p class="Code">        1                0                1                1</p><p class="Code">        1                1                0                0</p><p class="Code">        1                1                1                1</p><p class="Standard"> </p><p class="Illustration">Abb. 8: Zweite ROP: 10101100 = AC </p><p class="Standard"> </p><p class="Standard">In unserem Programm schlägt sich dieser ganze Exkurs ins Reich der booleschen Funktionen bloss in zwei Definitionen nieder: </p><p class="Standard"> </p><p class="Code">#define RASTER_COPY  0x00CA0749L // Rop for copying with raster</p><p class="Code">#define RASTER_MERGE 0x00AC0744L // Rop for merging with raster </p><p class="Standard"> </p><p class="Illustration">Abb. 9: Raster-ROPs </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="Stanzen" />Stanzen</h2><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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 <span class="Emphasis">TextColor</span> und die <span class="Emphasis">BkColor</span> auf schwarz und weiss zu setzen. In anderen Anwendungen als der vorliegenden</p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard">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 <span class="Emphasis">CheckerBits</span>. 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.</p><p class="Standard"> </p><p class="Code">static WORD CheckerBits[]   =</p><p class="Code">   {0xAAAA, 0x5555, 0xAAAA, 0x5555,</p><p class="Code">    0xAAAA, 0x5555, 0xAAAA, 0x5555};</p><p class="Standard"> </p><p class="Illustration">Abb. 10: Schachbrett-Bits für monochrome 8*8 Bitmap </p><p class="Standard"> </p><p class="Standard">Das ganze Verfahren findet man in der Funktion <span class="Emphasis">MakeCheckerBrush</span> 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.</p><p class="Standard"> </p><p class="Code">HBRUSH MakeCheckerBrush() </p><p class="Code">{ </p><p class="Code">  int     w;</p><p class="Code">  int     i;               // holds number of colors</p><p class="Code">  HBITMAP hBit1,hSrcMap;   // monochrome bitmap</p><p class="Code">  HBITMAP hBit4,hDestMap;  // 4-plane bitmap for 4-plane brush</p><p class="Code">  HDC     hDC;             // screen DC used as reference</p><p class="Code">  HDC     hSrcDC;          // DC for monochrome pattern</p><p class="Code">  HDC     hDestDC;         // DC for 4-plane pattern</p><p class="Code">  HBRUSH  hCheckerBrush;</p><p class="Code"> </p><p class="Code">  hDC = GetDC(NULL);</p><p class="Code">  // get number of colors that the physical screen can display</p><p class="Code">  i = GetDeviceCaps(hDC,PLANES);</p><p class="Code">  i *= GetDeviceCaps(hDC,BITSPIXEL);</p><p class="Code">  i = 1 << i;</p><p class="Code">  w = GetDeviceCaps(hDC,RASTERCAPS);</p><p class="Code">  if (w & RC_PALETTE)</p><p class="Code">    i = GetDeviceCaps(hDC,SIZEPALETTE);</p><p class="Code"> </p><p class="Code">  if ((i > 0) && (i <= 16))</p><p class="Code">  {</p><p class="Code">    hBit1 = CreateBitmap(8,8,1,1,(LPSTR)CheckerBits);</p><p class="Code">    hSrcDC = CreateCompatibleDC(hDC);</p><p class="Code">    hSrcMap = SelectObject(hSrcDC,hBit1);</p><p class="Code">    hBit4 = CreateCompatibleBitmap(hDC,8,8);</p><p class="Code">    hDestDC = CreateCompatibleDC(hDC);</p><p class="Code">    hDestMap = SelectObject(hDestDC,hBit4);</p><p class="Code">    SetBkMode(hDestDC,OPAQUE);</p><p class="Code">    SetBkColor(hDestDC, 0x00FFFFFFL); // white</p><p class="Code">    SetTextColor(hDestDC, 0x00000000L); // black</p><p class="Code">    BitBlt(hDestDC,0,0,8,8,hSrcDC,0,0,SRCCOPY);</p><p class="Code">    hCheckerBrush = CreatePatternBrush(hBit4);</p><p class="Code">    SelectObject(hSrcDC,hSrcMap);</p><p class="Code">    DeleteDC(hSrcDC);</p><p class="Code">    SelectObject(hDestDC,hDestMap);</p><p class="Code">    DeleteDC(hDestDC);</p><p class="Code">    DeleteObject(hBit1);</p><p class="Code">    DeleteObject(hBit4);</p><p class="Code">  }</p><p class="Code">  else</p><p class="Code">    hCheckerBrush = NULL;</p><p class="Code">  // done with screen DC</p><p class="Code">  ReleaseDC(NULL,hDC);</p><p class="Code">  // return brush</p><p class="Code">  return hCheckerBrush;</p><p class="Code">} // MakeCheckerBrush </p><p class="Standard"> </p><p class="Illustration">Abb. 11: MakeCheckerBrush erzeugt das Stanzwerkzeug. </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="All_together_now" />All together now</h2><p class="Standard"> </p><p class="Standard">Um nun in den Genuss all dieser Vorarbeiten zu kommen, konstruieren wir die anfangs erwähnte Überdefinition der Funktion <span class="Emphasis">DDBFromDIB</span> 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 <span class="Emphasis">DDBFromDIB</span> 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 <span class="Emphasis">hpBits</span> der auf die Bits der DIB zeigt. Ausserdem besitzt dieses DDB-Objekt die Funktion <span class="Emphasis">FlushDDB</span>, welche eine allfällige vorherige DDB sauber abräumt.</p><p class="Standard"> </p><p class="Standard">Die neue Funktion <span class="Emphasis">DDBFromDIB</span> greift auf die alte zurück, wenn entweder kein Rastern erwünscht ist (!bMix) oder der Bildschirm mehr als 16 Farben hat (!hBrush4).</p><p class="Standard"> </p><p class="Standard">Falls nun die zu konvertierende DIB eine Farbpalette von bis zu 256 Einträgen besitzt, verwenden wir zweimal <span class="Emphasis">StretchDIBits</span> mit den beiden oben eruierten Rasteroperationen, um die beiden Mischfarbenteile in die DDB zu bringen. Wir unterschieben der Funktion <span class="Emphasis">StretchDIBits</span> dabei jedesmal eine mittels der Funktion <span class="Emphasis">ColorIndex</span> errechnete Farbpalette, die beim ersten <span class="Emphasis">StretchDIBits</span><span class="Emphasis"><span class="T2"> </span></span>jeweils der ersten Mischfarbe und beim zweiten <span class="Emphasis">StretchDIBits</span> der zweiten Mischfarbe für die darzustellende Farbe entspricht.</p><p class="Standard"> </p><p class="Code">BOOL TMix::DDBFromDIB() </p><p class="Code">{ </p><p class="Code">  WORD        i;</p><p class="Code">  HDC         hDC;</p><p class="Code">  HDC         hMemDC;</p><p class="Code">  HBITMAP     hMemMap;</p><p class="Code">  HBRUSH      hMemBrush;</p><p class="Code">  PBITMAPINFO pbi1;</p><p class="Code">  PBITMAPINFO pbi2;</p><p class="Code"> </p><p class="Code">  if ((!hBrush4) || (!bMix))</p><p class="Code">    return TDDB::DDBFromDIB();</p><p class="Code"> </p><p class="Code">  if (!pbi)</p><p class="Code">    return FALSE;</p><p class="Code"> </p><p class="Code">  if ((pbi->bmiHeader.biClrUsed > 256) || (pbi->bmiHeader.biClrUsed <= 0))</p><p class="Code">    return TDDB::DDBFromDIB();</p><p class="Code"> </p><p class="Code">  // Flush DDB</p><p class="Code">  FlushDDB();</p><p class="Code"> </p><p class="Code">  // lock the DIB bits</p><p class="Code">  hpBits = (HPBYTE)GlobalLock(hBits);</p><p class="Code">  if (!hpBits)</p><p class="Code">    return FALSE;</p><p class="Code"> </p><p class="Code">  // get bitmap from DIB</p><p class="Code">  hDC = GetDC(NULL); // is needed in order to use RealizePalette</p><p class="Code">  if (hDC)</p><p class="Code">  {</p><p class="Code">    // here we make the two bitmap infos</p><p class="Code">    i = pbi->bmiHeader.biSize + PaletteSize();</p><p class="Code">    // allocate them</p><p class="Code">    pbi1 = (PBITMAPINFO)malloc(i);</p><p class="Code">    pbi2 = (PBITMAPINFO)malloc(i);</p><p class="Code">    // copy the headers</p><p class="Code">    pbi1->bmiHeader = pbi->bmiHeader;</p><p class="Code">    pbi2->bmiHeader = pbi->bmiHeader;</p><p class="Code">    // loop over colors</p><p class="Code">    for (i = 0; i < pbi->bmiHeader.biClrUsed; i++)</p><p class="Code">    {</p><p class="Code">      pbi1->bmiColors[i] = ColorBase[MixTable[ColorIndex(pbi->bmiColors[i])][0]];</p><p class="Code">      pbi2->bmiColors[i] = ColorBase[MixTable[ColorIndex(pbi->bmiColors[i])][1]];</p><p class="Code">    }</p><p class="Code">    // make a memory DC for the DDB</p><p class="Code">    hMemDC = CreateCompatibleDC(hDC);</p><p class="Code">    // create the DDB</p><p class="Code">    hDDB = CreateCompatibleBitmap(hDC,(int)pbi->bmiHeader.biWidth,(int)pbi->bmiHeader.biHeight);</p><p class="Code">    // select bitmap into DC</p><p class="Code">    hMemMap = SelectObject(hMemDC,hDDB);</p><p class="Code">    // select brush into DC</p><p class="Code">    hMemBrush = SelectObject(hMemDC,hBrush4);</p><p class="Code">    // use StretchDIBits because of raster operations</p><p class="Code">    i = StretchDIBits(hMemDC, 0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,</p><p class="Code">                              0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,</p><p class="Code">                              (LPSTR)hpBits,(LPBITMAPINFO)pbi1,DIB_RGB_COLORS,RASTER_COPY);</p><p class="Code">    if (i != (int)pbi->bmiHeader.biHeight)</p><p class="Code">      return FALSE;</p><p class="Code">    i = StretchDIBits(hMemDC, 0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,</p><p class="Code">                              0, 0, (int)pbi->bmiHeader.biWidth, (int)pbi->bmiHeader.biHeight,</p><p class="Code">                              (LPSTR)hpBits,(LPBITMAPINFO)pbi2,DIB_RGB_COLORS,RASTER_MERGE);</p><p class="Code">    if (i != (int)pbi->bmiHeader.biHeight)</p><p class="Code">      return FALSE;</p><p class="Code">    // free the bitmap infos</p><p class="Code">    free(pbi1);</p><p class="Code">    free(pbi2);</p><p class="Code">    // delete memory DC</p><p class="Code">    SelectObject(hMemDC,hMemMap);</p><p class="Code">    SelectObject(hMemDC,hMemBrush);</p><p class="Code">    DeleteDC(hMemDC);</p><p class="Code">  }</p><p class="Code">  ReleaseDC(NULL,hDC);</p><p class="Code">  UnlockBits();</p><p class="Code">  return (hDDB != NULL);</p><p class="Code">} // DDBFromDIB </p><p class="Standard"> </p><p class="Illustration">Abb. 12: 84-Farben-Konversion </p><p class="Standard"> </p><p class="Standard"> </p><h2 class="Heading2"><a name="84_Farben_sind_besser" />84 Farben sind besser</h2><p class="Standard"> </p><p class="Standard">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. </p><p class="Standard"> </p><p class="Standard"> </p><p class="Standard">14.5.93 Hartwig Thomas </p><p class="Standard"> </p><p class="Standard"> </p><p class="Standard">[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. </p><p class="Standard"> </p><p class="Standard"><a href="VUSYSTEM.EXE">VUSYSTEM.EXE</a>    enthält ENTERVU.EXE und einige Bilder</p><p class="Standard"><a href="VUSOURCE.EXE">VUSOURCE.EXE</a>    enthält den gesamten Quellkode (BC++ 3.1) von EnterVU]</p><p class="Standard"> </p></body></html>