Magic Noir: Farbige Bilder monochrom dargestellt

Bild 1: Das Original in 16,7 Millionen Farben

Besitzer moderner Grafikkarten und passender Monitore sind in der Regel aus dem Schneider: TrueColor-Bilder erscheinen auf ihrer Mattscheibe in der Farbenpracht, in der Gott (oder ein anderer DTP-Künstler) sie geschaffen hat. Was aber macht der bemitleidenswerte Zeitgenosse, dessen Grafiksystem monochrom ist.

Als der ATARI ST das Licht der Welt erblickte, wurden lediglich drei Auflösungen unterstützt: zwei, vier und sechzehn Farben. Eine fotorealistische Darstellung war mit dieser Hard- und Software nicht möglich. Betrachtet man den heutigen Stand der Technik, wird man sehen, daß im professionellen Bereich hauptsächlich mit zwei Auflösungen gearbeitet wird: 256 und 16,7 Millionen Farben (TrueColor). Obwohl diese Möglichkeiten für gehobenere Ansprüche (DTP) geradezu ideal sind, wird sich der normale Anwender ein solches Grafiksystem nicht immer leisten können. Da jedoch fast jeder Anwender einen monochromen Monitor besitzt, sollte man bedenken, daß man mit nur zwei Farben auch eine brauchbare Darstellung erzeugen kann. Wie man ein Bild mit 256 oder 16,7 Millionen (z.B. im TIFF- oder ESM-Format) nun möglichst realistisch in monochromer Auflösung darstellt, beschreibt der folgende Grundlagenartikel.

Als Beispiel für die unten beschriebene Methode wurde die in Bild 1 abgebildete Blume (16,7 Millionen Farbabstufungen) gewählt.

Die einfache Methode

Will man ein Bild konvertieren, muß man dies pixelweise machen, d.h. jeder Bildpunkt (Pixel) eines Quellbildes muß auf einen Bildpunkt des Zielbildes abgebildet werden. Das Problem liegt aber darin, daß ein Pixel in monochromer Auflösung entweder schwarz oder weiß ist; Abstufungen, die im Farbbild vorhanden sind, können hier nicht direkt berücksichtigt werden.

Die einfachste Möglichkeit, ein farbiges Bild in monochrome Darstellung zu konvertieren, besteht darin, alle Quell-Pixel, die einen bestimmten Farbwert überschreiten, auf ein schwarzes, alle anderen auf ein weißes Ziel-Pixel abzubilden. Diese „naive“ Methode erzeugt jedoch wenig überzeugende Bilder; wie unsere ursprünglich bunte Blume nach dieser Konvertiermethode aussieht, zeigt Bild 2. Man erkennt die Blume zwar noch, von ihren Nuancen ist jedoch nichts erhalten geblieben.

Graustufen

Um eine realistischere Darstellung zu erhalten, muß man auf Graustufen zurückgreifen. Bild 3 zeigt eine Übersicht über 256 Raster, die aus jeweils 16x16 Pixeln bestehen. Jedem Raster ist ein Wert zugewiesen; je höher der Wert, desto mehr Punkte sind gesetzt, d.h. desto dunkler erscheint das Raster. Im ersten Raster sind keine Punkte gesetzt, im letzten alle. Wer sich die einzelnen Muster genauer anguckt, wird feststellen, daß von einem zum nächsten Muster ein zusätzlicher Punkt gesetzt ist (Ausnahme: Raster 0 nach Raster 1).

Da das GEM nur wenige dieser Muster zur Verfügung stellt (z.B. Muster 127 oder Muster 255), benötigen wir einen Algorithmus, der diese erzeugt.

Im Listing 1 ist ein solcher Algorithmus dargestellt. Die in Pure C geschriebene Routine CalcGridTable berechnet dabei alle 256 Raster und legt sie im Feld grid[256] ab; diese Daten werden später zur Konvertierung des Farbbildes benötigt. Ein einzelnes Raster wird durch die Struktur GRID_STRUCT beschrieben, die aus 16 UWORDs besteht; ein UWORD dieser Struktur nimmt dabei die 16 Pixel einer Zeile des Rasters auf, d.h. jedes Bit der Variablen repräsentiert ein Pixel.

Bild 2: Nach der Konvertierung in eine monochrome Darstellung
Bild 3: Übersicht über 256 Graustufenraster
Bild 4: Die Blume nach der Graustufenkonvertierung

Konvertieren des Bildes

Was fangen wir aber jetzt mit den Graustufen an? Das Vorhandensein dieser Graustufen ändert schließlich nichts an der Tatsache, daß ein Pixel nur schwarz oder weiß sein kann.

Schauen wir uns den Konvertieralgorithmus (Listing 2) an, der ebenfalls in Pure C verfaßt ist. Um mit der Funktion ConvertPic arbeiten zu können, muß lediglich noch die Routine GetSourceColor bereitgestellt werden. Mit dieser Funktion wird der Farbwert des Quell-Pixels an der Position x/y ermittelt. Der Farbwert wird nach folgender Definition erwartet: er muß zwischen 0 und 255 liegen, wobei Farbwert 0 weiß und Farbwert 255 schwarz bedeutet. Sollte es sich bei dem Quellbild um ein TrueColor-Bild handeln, muß die Funktion die Farbwerte herunterrechnen (z.B. um 16 Bit shiften); bei einem Bild mit 256 Farben oder Graustufen muß der Farbwert natürlich nicht mehr verändert werden.

Der Konvertieralgorithmus besteht aus zwei verschachtelten Schleifen, die jeden Bildpunkt des Quellbildes erfassen und Pixel für Pixel das Zielbild errechnen. Um den Algorithmus zu verstehen, bediene ich mich eines Beispieles: An der Position x=40 und y=122 hat das Quellbild den Farbwert 110. Jetzt betrachte ich das Raster mit der Nummer 110 (siehe Bild 3). Eigentlich würde ich jetzt prüfen, ob in diesem Raster an der Stelle x=40 und y=122 ein Punkt gesetzt ist oder nicht; da ein Raster aber nur aus 16x16 Pixeln besteht, betrachte ich im Raster die Stelle x=8 (= 40 % 16) und y=10 (= 122 % 16). Falls dort ein Punkt gesetzt ist, setze ich auch im Zielbild an der Stelle x=40 und y=122 einen Punkt, ansonsten bleibt dieser Punkt weiß. Im Algorithmus wird das Setzen eines Pixels im Zielbild mit der Funktion SetDestPoint erreicht, ansonsten wird ClearDestPoint aufgerufen; diese Funktionen sind jedoch nicht im Listing enthalten, da sie für das Verständnis des Algorithmus’ unwichtig sind.

Lösen wir uns von dem Beispiel: Ich ermittle an der Stelle x/y den Farbwert color. Ist jetzt an der Stelle x % 16 und y % 16 ein Punkt im Raster mit der Nummer color gesetzt, setze ich auch im Zielbild das Pixel an der Stelle x/y auf Schwarz, ansonsten auf Weiß.

Was mit unserem ursprünglich bunten Bild geschehen ist, nachdem jedes Pixel des Quellbildes auf ein Pixel im Zielbild abgebildet wurde, sieht man in Bild 4. Im Vergleich zur „naiven“ Variante schneidet das mit 256 Graustufen dargestellte Bild wesentlich besser ab. Und sollten Sie den Vorteil dieser Darstellungsart noch immer nicht erkennen, können Sie ja mal versuchen, einen Rasterverlauf mit den beiden Methoden zu konvertieren ...

Schlußworte

Der vorliegende Algorithmus ist zwar sicherlich sehr übersichtlich, in seiner aktuellen Form aber viel zu langsam. Wenn man bedenkt, daß einige Bilder aus über 500000 Pixeln bestehen, wird schnell ersichtlich, daß man für derartige Anwendungen eine optimiertem Fassung entwickeln muß, auch wenn dies zu Lasten der Übersichtlichkeit geht.

Literatur:

[ 1 ] Algorithmen zur Grafik und Bildverarbeitung, Theo Pavlidis, Heise Verlag


/*******************************************/ /* Sprache : PureC */ /* Modul : CONVERT.C */ /* Aufgabe : Konvertieralgorithmus für */ /* farbige Bilder ins monochrome */ /* Format. */ /* (c) 1992 MAXON Computer */ /*******************************************/ # include <portab.h> /*******************************************/ /* Aufgabe : Konvertieren */ /* Parameter : width, height : Breite und */ /* Höhe des Bildes in Pixeln */ /A******************************************/ VOID ConvertPic( WORD width, WORD height ) { WORD color, x, y; UWORD bit_list[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, (UWORD)32768L }; for( x=0; x<width; x++ ) for( y=0; y<height; y++ ) { /* Farbwert des Quellpixels ermitteln (0..255) */ color = GetSourceColor( x, y ); /* Pixel setzen oder nicht ?? */ if( grid[color].word[y % 16] & bit_list[x % 16] ) SetDestPoint( x, y ); else ClearDestPoint( x, y ); } }

/*******************************************/ /* Sprache : PureC */ /* Modul : GRID.C */ /* Aufgabe : Erzeugen von Rastern */ /* (c) 1992 MAXON Computer */ /*******************************************/ # include <portab.h> # include <string.h> /* Rasterdefinition */ typedef struct { UWORD word[16]; } GRID_STRUCT; /* Punkte */ typedef struct { WORD g_x, g_y; } GPOINT; GRID_STRUCT curr_grid = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, grid[256]; /*******************************************/ /* Aufgabe : Berechnet 256 verschiedene */ /* Raster und legt sie im Feld */ /* 'grid' ab */ /*******************************************/ VOID CalcGridTable( VOID ) { GPOINT start_pos[16] = { { 1, 1 }, { 3, 3 }, { 3, 1 }, { 1, 3 }, { 2, 2 }, { 4, 4 }, { 4, 2 }, { 2, 4 }, { 2, 1 }, { 4, 3 }, { 4, 1 }, { 2, 3 }, { 1, 2 }, { 3, 4 }, { 3, 2 }, { 1, 4 } }; UWORD bit_list[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, (UWORD)32768L }; WORD i, j, x, y, grid_index = 0; for( i=0; i<=15; i++ ) for( j=0; j<=15; j++ ) { x = (start_pos[j].g_x - 1) * 4 + start_pos[i].g_x; y = (start_pos[j].g_y - 1) * 4 + start_pos[i].g_y; /* Das 0.Raster */ if( !i && !j ) memcpy( &grid[grid_index++], &curr_grid, sizeof( GRID_STRDCT ) ); curr_grid.word[y - 1] |= bit_list[x - 1]; /* Das n.Raster */ if( i || j ) memcpy( &grid[grid_index++], &curr_grid, sizeof( GRID_STRUCT ) ); } }

Markus Hövener
Links

Copyright-Bestimmungen: siehe Über diese Seite