erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC          Stand: 06.01.2006

Zurueck zum Inhaltsverzeichnis

zurück zum vorherigen Kapitel
 
 

Kapitel 4 - Bitmaps und Icons

4.1 Bitmaps als Ressource einbinden, laden und darstellen

Ein grafisch orientiertes Betriebssystem wie MS Windows nutzt Bilder/Grafiken als optische Schnittstelle zum Benutzer. Die typische Speicherform einer Bildinformation in MS Windows ist die sogenannte Bitmap (Dateiendung bmp). Diese repräsentiert eine Rechteckfläche, die aus Pixel besteht. Jedes Pixel trägt eine Koordinaten- und, falls es sich nicht um eine Monochrom-Darstellung handelt, eine Farbinformation. Wir üben die Einbindung einer Bitmap in eine Anwendung sofort an einem praktischen Beispiel:

Erstellen Sie bitte eine dialogfeldbasierende Anwendung mit Namen "DialogBitmap". Akzeptieren Sie alle Standardeinstellungen und entfernen Sie im Dialogfeld die beiden Schaltflächen und das Textfeld. Nun werden Sie ein Bitmap erzeugen und in das Projekt als Ressource einbinden: Führen Sie in der Ressourcenansicht einen Rechtsklick auf den Knoten "DialogBitmap Ressourcen" durch, und wählen Sie "Einfügen".
Es öffnet sich folgendes Fenster:

Abb. 4.1: Das Fenster "Ressource einfügen"

Entscheiden Sie sich hier für "Bitmap Neu". Sie können nun ein Bitmap erstellen:

Abb. 4.2: Die Bitmap-Ressource IDB_BITMAP1 wird erstellt

Sie erstellen gerade das Bitmap mit dem Namen IDB_BITMAP1. Belassen Sie das leere Bitmap auf seiner vorgegebenen Größe (48 * 48). Betätigen Sie sich einfach spielerisch kreativ, indem Sie mit einigen Pinselstrichen unter Einsatz mehrerer Farben ein individuelles Bitmap erzeugen. Nach dem Speichern finden Sie folgenden Eintrag im Ressourcenskript:

IDB_BITMAP1 BITMAP DISCARDABLE "res\\bitmap1.bmp"

Im Unterverzeichnis .../DialogBitmap/res befindet sich die Datei bitmap1.bmp. Das Bitmap verfügt über 48 * 48 = 2304 Pixel mit 16 verschiedenen Farben. Nachdem wir eine Bitmap-Ressource in das Projekt eingebunden haben, wollen wir dieses in unserem Dialogfenster darstellen. Hierzu müssen wir folgende Schritte unternehmen:
 

1. Fügen Sie in die Dialog-Klasse die Member-Variable Bild1 vom Typ CBitmap als "Privat" ein:


 

2. Verwenden Sie die Member-Funktion LoadBitmap(...) der MFC-Klasse CBitmap in OnInitDialog():

BOOL CDialogBitmapDlg::OnInitDialog()
{
...
// ZU ERLEDIGEN: Hier zusätzliche Initialisierung einfügen

Bild1.LoadBitmap( IDB_BITMAP1 );

return TRUE; // Geben Sie TRUE zurück, außer ein Steuerelement soll den Fokus erhalten
}
 

3. Räumen Sie die Member-Funktion OnPaint() auf und fügen Sie folgendes ein:

void CDialogBitmapDlg::OnPaint()
{
  CClientDC dc( this );
  BITMAP bm;
  Bild1.GetObject( sizeof( bm ), &bm );
  CDC SpeicherDC;
  SpeicherDC.CreateCompatibleDC( &dc );
  SpeicherDC.SelectObject( &Bild1 );
  CRect Rect;
  GetClientRect( &Rect );
  dc.SetStretchBltMode( HALFTONE );
  dc.StretchBlt( 0, 0, Rect.right, Rect.bottom, &SpeicherDC,
                 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY );
  CDialog::OnPaint();
}
 

4. Nach dem Kompilieren / Starten zeigt sich jetzt das Dialogfenster mit dem Bitmap als Hintergrund. In Abb. 4.3 erkennen Sie die grobe Pixelstruktur von nur 48 * 48:

Abb. 4.3: Das Bitmap wird im Dialogfenster als Hintergrundbild dargestellt
 

Wir untersuchen nun schrittweise den eingegebenen Code in OnPaint():

CClientDC dc( this );

Die MFC-Klasse CClientDC erzeugt einen Gerätekontext für das aktuelle Fenster, den wir zur Bildschirmausgabe einsetzen.
 

BITMAP bm;

Die Struktur vom Typ BITMAP definiert Typ, Höhe, Breite, Farbformat und Bit-Werte eines Bitmaps und besitzt folgende Elemente:
 
bmType:  Bitmap-Typ. Bei logischen Bitmaps ist dieses Element 0.
bmWidth:  Breite des Bitmap in Pixel.
bmHeight: Höhe des Bitmap in Pixel (=Rasterzeile).
bmWidthBytes:  Anzahl Bytes in jeder Bitmap-Rasterzeile.
bmPlanes:  Anzahl Farbebenen im Bitmap.
bmBitsPixel: Anzahl Farb-Bits in jeder Farbeebene pro Pixel.
bmBits:  Zeiger auf die Adresse der Bit-Werte (1-Byte-Array) des Bitmap.

Für die Bilddarstellung benötigen wir momentan nur Höhe und Breite dieser Struktur.

Bild1.GetObject( sizeof( bm ), &bm );

Hier kommt die Funktion
int CGdiObject::GetObject ( int nCount, LPVOID lpObject )
zum Einsatz.

Bedeutung der Funktions-Parameter:
nCount:      Anzahl Bytes, die in den lpObject-Puffer kopiert werden.
lpObject:   Zeiger auf einen Puffer, der die Daten aufnimmt.

Diese MFC-Funktion füllt einen Puffer (hier: die BITMAP-Struktur bm) mit Daten eines spezifizierten Grafik-Objekts (hier: CBitmap Bild1). Nachfolgende Übersicht zeigt die Beziehung zwischen Grafik-Objekten und erzeugten Datenstrukturen:
 
Objekt: Datenpuffer-Typ:
CPen LOGPEN
CBrush LOGBRUSH
CFont LOGFONT
CBitmap BITMAP
CPalette WORD
CRgn (nicht unterstützt)

Wenn das Grafik-Objekt ein CBitmap Objekt ist, liefert GetObject nur Breite, Höhe und Farbformat-Information des Bitmaps.
 

CDC SpeicherDC;
SpeicherDC.CreateCompatibleDC( &dc );

Die MFC-Klasse CDC umhüllt die Funktion eines Windows-Geräte-Kontexts (device context),
der die Bildschirm- oder Druckerausgabe gewährleistet.

Die MFC-Funktion virtual BOOL CreateCompatibleDC(CDC*pDC ) erzeugt einen Speicher-Geräte-Kontext.
Dieser ist "kompatibel" mit dem durch den Zeiger pDC (gleichbedeutend mit der Speicheradresse des Geräte-Kontexts)
angegebenen Geräte-Kontext, hier also mit unserem CClientDC dc. Ein mit dieser Funktion erstellter Speicher-Geräte-Kontext ist ein Speicherbereich, der die Bildschirmausgabe erzeugen kann.
 

SpeicherDC.SelectObject( &Bild1 );

Die MFC-Funktion CDC::SelectObject tritt hier in folgender Variante auf:

CBitmap* SelectObject( CBitmap* pBitmap );

Hiermit wird das CBitmap-Objekt Bild1 in den Speicher-Geräte-Kontext SpeicherDC kopiert.
Die Funktion erwartet als Parameter die Adresse von Bild1 also &Bild1.
 

CRect Rect;
GetClientRect(&Rect);

In diesen zwei Zeilen besorgen wir uns ein Rechteck, das dem Bildschirmbereich unseres aktuellen Fensters entspricht.
 

dc.SetStretchBltMode( HALFTONE );
dc.StretchBlt( 0, 0, Rect.right, Rect.bottom, &SpeicherDC,
               0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY );

Die MFC-Funktion CDC::StretchBlt kopiert ein Bitmap aus einem Quell-Rechteck in ein Ziel-Rechteck.
Die Quelle ist hier der Speicher-Geräte-Kontext, das Ziel der für die Ausgabe zuständige Geräte-Kontext CClientDC dc.
Hierbei wird das Bitmap den Abmessungen des Ziel-Rechtecks durch Dehnung oder Streckung angepaßt.
Die allgemeine Syntax dieser Funktion lautet:

BOOL StretchBlt ( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );

Die vor dieser Operation durchgeführte Einstellung des "StretchBltMode" erfolgt mittels der MFC-Funktion
int CDC::SetStretchBltMode( int nStretchMode ).
Dieser Modus regelt insbesondere die Art des Datenverlustes bei der Kompression von Bitmaps.
Die besten Ergebnisse liefert HALFTONE. Für unser einfaches Beispiel ist dies nicht von Bedeutung.
Dennoch soll diese Funktion hier vorgestellt werden.

Folgende Möglichkeiten für nStretchMode sollen hier vorgestellt werden:

BLACKONWHITE, WHITEONBLACK, COLORONCOLOR, HALFTONE

Der BLACKONWHITE und WHITEONBLACK Modus wird in Monochrom-Bitmaps eingesetzt, um die Vordergrund-Pixel zu erhalten.
Der COLORONCOLOR (STRETCH_DELETESCANS) Modus wird zur Farberhaltung in Farb-Bitmaps verwendet.
Der HALFTONE Modus erzeugt die beste Qualität. Hierfür wird jedoch auch der höchste Rechenaufwand benötigt.

Neben StretchBlt(...) gibt es auch die einfache Version zur Darstellung eines Bitmaps ohne Streckung / Stauchung:

BOOL BitBlt (int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc,intySrc, DWORD dwRop);

Der gesamte Prozeß vom Einbinden eines Bitmap-Bildes über das Laden bis zur Bildschirmausgabe ist für einen Einsteiger zu Beginn schwierig nachzuvollziehen. Sie müssen die einzelnen Befehle auch nicht auswendig beherrschen, da Sie diese bei Bedarf als Einheit durch Kopieren übernehmen können. Hierbei müssen Sie die entsprechenden Bezeichnungen für die Variablen anpassen.
 

4.2 Transparente Bitmaps erzeugen

Spätestens, wenn Sie versuchen, mit Vorder- und Hintergrund-Bitmaps einen bewegten Ablauf oder gar ein kleines Spiel zu programmieren, kommen Sie an den Punkt, wo Sie sich fragen, wie man eigentlich Vordergrund-Bitmaps so darstellt, daß eine Farbe Ihrer Wahl transparent erscheint.

Dies ist bezüglich des Verständnisses des Programmablaufs kein leichtes Thema. Die praktische Realisierung bleibt jedoch überschaubar, da Sie den gesamten Programmcode wie oben als Block bzw. als separate Funktion einsetzen können. Das folgende mehrstufige Vorgehen ist für die Maskierung einer Farbe (hier: schwarz) notwendig:

// Vorbereitungen:
// IDB_BITMAP2 erstellen // Ressource
// CBitmap Bild2; // in der Header-Datei
// Bild2.LoadBitmap(IDB_BITMAP2); // z.B. in OnInitDialog()
// Transparente Bitmap-Darstellung: Maskiert wird in diesem Beispiel RGB( 0, 0, 0 )

int X = 100;
int Y = 100;
CClientDC dc( this );
BITMAP bm; //BITMAP-Struktur bm deklarieren

//evtl. auch als Member-Variable

Bild2.GetObject( sizeof( bm ), &bm ); //Größe und Adresse von CBitmap Bild2 --> BITMAP bm
CDC SpeicherDC;
SpeicherDC.CreateCompatibleDC( &dc ); //SpeicherDC initialisieren
SpeicherDC.SelectObject( & Bild2 ); //Bild2 --> Speicher
CDC MaskDC;
MaskDC.CreateCompatibleDC( &dc ); //MaskDC initialisieren
CBitmap MaskBitmap;
MaskBitmap.CreateBitmap( bm.bmWidth, bm.bmHeight, 1, 1, NULL );
MaskDC.SelectObject( &MaskBitmap );
SpeicherDC.SetBkColor( RGB( 0, 0, 0 ) ); //Diese Farbe wird transparent dargestellt
MaskDC.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &SpeicherDC, 0, 0, SRCCOPY );
CDC OrDC;
OrDC.CreateCompatibleDC( &dc );
CBitmap OrBitmap;
OrBitmap.CreateCompatibleBitmap( &SpeicherDC, bm.bmWidth, bm.bmHeight );
OrDC.SelectObject( &OrBitmap );
OrDC.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &SpeicherDC, 0, 0, SRCCOPY );
OrDC.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &MaskDC, 0, 0, 0x220326 );

/*
Was bedeutet 0x220326 ? Dies steht für folgende ternäre Rasteroperation:
dest = (NOT src) AND dest

Bezüglich Details siehe:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/pantdraw_6n77.asp
http://msdn2.microsoft.com/en-us/library/ms534885.aspx

*/

CDC TempDC;
TempDC.CreateCompatibleDC( &dc );
CBitmap TempBitmap;
TempBitmap.CreateCompatibleBitmap( &SpeicherDC, bm.bmWidth, bm.bmHeight );
TempDC.SelectObject( &TempBitmap );
TempDC.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &dc, X, Y, SRCCOPY );
TempDC.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &MaskDC, 0, 0, SRCAND );
TempDC.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &OrDC, 0, 0, SRCPAINT );
dc.BitBlt( X, Y, bm.bmWidth, bm.bmHeight, &TempDC, 0, 0, SRCCOPY );

Zunächst gibt es in der Funktion BitBlt(...) oder StretchBlt(...) den Parameter dwRop (raster-operation codes),
der die Bitmap-Ausgabe entscheidend beeinflußt. Hier einige Möglichkeiten:

SRCCOPY   Kopiert die Quell-Bitmap in die Ziel-Bitmap.
SRCAND    Kombiniert die Pixel von Quell-Bitmap und Ziel-Bitmap mit dem Booleschen AND operator.
SRCPAINT  Kombiniert die Pixel von Quell-Bitmap und Ziel-Bitmap mit dem Booleschen OR operator.
SRCINVERT Kombiniert die Pixel von Quell-Bitmap und Ziel-Bitmap mit dem Booleschen XOR operator.

Eine Reihe weiterer Werte finden Sie in der Hilfe (Suchbegriff: CDC::StretchBlt). In unserem Beispiel im vorhergehenden Abschnitt verwendeten wir SRCCOPY. Dieser Parameter stellt sicher, daß das Quell-Bitmap (soweit möglich) farblich unverändert an das Ziel kopiert wird. Andere Werte verknüpfen Quell- und Ziel-Bitmap unter Anwendung bit-logischer Verknüpfungen (NOT, AND, OR, XOR).
 

In der MFC-Bibliothek liest sich dies übrigens wie folgt:

/* Ternary raster operations */
#define SRCCOPY      (DWORD) 0x00CC0020 /* dest = source */
#define SRCPAINT     (DWORD) 0x00EE0086 /* dest = source OR dest */
#define SRCAND       (DWORD) 0x008800C6 /* dest = source AND dest */
#define SRCINVERT    (DWORD) 0x00660046 /* dest = source XOR dest */
#define SRCERASE     (DWORD) 0x00440328 /* dest = source AND (NOT dest ) */
#define NOTSRCCOPY   (DWORD) 0x00330008 /* dest = (NOT source) */
#define NOTSRCERASE  (DWORD) 0x001100A6 /* dest = (NOT src) AND (NOT dest) */
#define MERGECOPY    (DWORD) 0x00C000CA /* dest = (source AND pattern) */
#define MERGEPAINT   (DWORD) 0x00BB0226 /* dest = (NOT source) OR dest */
#define PATCOPY      (DWORD) 0x00F00021 /* dest = pattern */
#define PATPAINT     (DWORD) 0x00FB0A09 /* dest = DPSnoo */
#define PATINVERT    (DWORD) 0x005A0049 /* dest = pattern XOR dest */
#define DSTINVERT    (DWORD) 0x00550009 /* dest = (NOT dest) */
#define BLACKNESS    (DWORD) 0x00000042 /* dest = BLACK */
#define WHITENESS    (DWORD) 0x00FF0062 /* dest = WHITE */
 

4.3 Monochrome Bitmaps im Programm erzeugen und darstellen

Nachdem Sie gesehen haben, wie man Bitmaps im Ressourcen-Editor erzeugt und darstellt, werden wir nachfolgend ein monochromes Bitmap direkt im Programm erzeugen. Auf diese Weise begreift man den Zusammenhang zwischen Bild und zugrunde liegenden Bitmap-Daten, da man eigene praktische Versuche anstellen kann.

Wir erstellen nun eine einfache SDI-Anwendung mit dem Projektnamen 'Monochrom_Bitmap'.
In Schritt 1 wählen Sie SDI und Doc/View. Alles andere akzeptieren Sie bitte unverändert.

Wir wollen nun ein Bild darstellen, dessen Daten wir im Programm selbst festlegen ohne Laden von Ressource oder von extern.

Hierzu erzeugen wir zunächst ein Objekt der MFC-Klasse CBitmap:

Rechtsklick in der Klassenansicht auf CMonochrom_BitmapView und "Member-Variable hinzufügen..." wählen.
Als Variablentyp geben Sie CBitmap und als Name Bild ein. Der Zugriff ist private:

class CMonochrom_BitmapView : public CView
{
...
...
// Implementierung
 private:
  CBitmap Bild;
...

Als nächstes benötigen wir ein Array, das die Daten für das Bitmap aufnimmt.
Rechtsklick in der Klassenansicht auf CMonochrom_BitmapView und "Member-Variable hinzufügen..." wählen.
Als Variablentyp geben Sie BYTE (identisch mit unsigned char) und als Name A[10] ein. Der Zugriff ist private:

// Implementierung
 private:
  BYTE A[10];
  CBitmap Bild;

Damit haben wir die zwei wesentlichen Dinge erzeugt:
Ein BYTE-Array mit zehn Elementen, in dem wir Bits (1 Byte = 8 Bit) auf 0 (schwarz) oder 1 (weiß)  setzen können und ein Objekt der Klasse CBitmap.

Die konkrete Ausgestaltung und Verknüpfung dieser beiden Objekte führen wir einfach im Konstruktor durch.
Dieser sieht bisher folgendermaßen aus:

CMonochrom_BitmapView::CMonochrom_BitmapView()
{
 // ZU ERLEDIGEN: Hier Code zur Konstruktion einfügen,

}

Wir definieren unser Array, indem wir alle Elemente auf 0 setzen, und verbinden es anschließend mit Bild über die Funktion CreateBitmap(...):

CMonochrom_BitmapView::CMonochrom_BitmapView()
{
 for(int i=0; i<=9; i++) A[i]=0; // Alle Punkte auf schwarz setzen
 Bild.CreateBitmap(16, 5, 1, 1, A);
}

Hierbei haben wir einige Festlegungen getroffen:
Breite: 16,
Höhe: 5,
Farbebenen: 1,
Bits pro Pixel: 1

Damit haben wir ein monochromes Bitmap-Array der Breite 16 und der Höhe 5 erzeugt.
Als Parameter lpBits haben wir der Funktion die Adresse des Arrays übergeben.

Das werden wir nun testen. Wie geht das? Ganz einfach.
In der Ausgabe-Funktion OnDraw(...) zeigen wir unser CBitmap-Objekt an:

void CMonochrom_BitmapView::OnDraw(CDC* pDC)
{
 CMonochrom_BitmapDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);

 BITMAP bm;
 Bild.GetObject( sizeof( bm ), &bm );
 CDC SpeicherDC;
 SpeicherDC.CreateCompatibleDC( pDC );
 SpeicherDC.SelectObject( &Bild );
 pDC->BitBlt( 0, 0, 16, 5, &SpeicherDC, 0, 0, SRCCOPY);
}

Wenn alles richtig eingegeben wurde, zeigt sich nun links oben in der Client Area ein kleines schwarzes Rechteck
mit der Pixel-Dimension 16 x 5 = 80. Das entspricht den 10 BYTES (= 80 bits) in unserem Array A[0] ... A[9].

Damit wir bei weiteren Manipulationen die Veränderungen richtig sehen können, vergrößern wir die Darstellung.
Hierfür verwenden wir StretchBlt(...). Der Zoom-Faktor wurde auf 40 ( 16 => 640, 5 =>200 ) eingestellt.

void CMonochrom_BitmapView::OnDraw(CDC* pDC)
{
 CMonochrom_BitmapDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);

 BITMAP bm;
 Bild.GetObject( sizeof( bm ), &bm );
 CDC SpeicherDC;
 SpeicherDC.CreateCompatibleDC( pDC );
 SpeicherDC.SelectObject( &Bild );
 //pDC->BitBlt  ( 0, 0,  16,   5, &SpeicherDC, 0, 0,                          SRCCOPY);
 pDC->StretchBlt( 0, 0, 640, 200, &SpeicherDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY );

  // Zum Vergleich:
  // BitBlt(     int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc,                                DWORD dwRop );
  // StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );
}

Jetzt nimmt unser schwarzes Rechteck eine Fläche von 640 x 200 Pixel ein.
Die Auflösung, die wir beeinflussen werden, ist jedoch nach wie vor 16 x 5.

Damit die Zuordnung der 80 bits im Array A[...] zum Bild klar wird, ergänzen Sie bitte folgendes im Konstruktor:

CMonochrom_BitmapView::CMonochrom_BitmapView()
{
 for(int i=0; i<=9; i++) A[i]=0; // Alle Punkte auf schwarz setzen

 A[0] = 128+64+      8+4+2+1; A[1] = 128+64+32+16+8+4+2+1;
 A[2] = 128+   32+16+  4+2+1; A[3] = 128+   32+16+8+  2+1;
 A[4] = 128+   32+16+8+4+2+1; A[5] =           16+      1; // C++
 A[6] = 128+   32+16+  4+2+1; A[7] = 128+   32+16+8+  2+1;
 A[8] = 128+64+      8+4+2+1; A[9] = 128+64+32+16+8+4+2+1;

 Bild.CreateBitmap(16, 5, 1, 1, A);
}

Wenn Sie alles korrekt übernommen haben (am besten Copy & Paste), dann erhalten Sie ein "grob-pixeliges" C++.

An diesem Beispiel können Sie gut erkennen, wie die Array-Daten dem Bitmap zugeordnet sind.
Die Reihenfolge geht von links nach rechts und Reihe für Reihe von oben nach unten.
Die 16 Punkte der ersten Reihe entsprechen A[0] und A[1]. Das hochwertigste bit ist links, das niederwertigste bit rechts:
128+64+8+4+2+1 entspricht der Binärzahl 11001111. Die Ziffer 0 steht hier für schwarz und die Ziffer 1 für weiß.

Experimentieren Sie nun ausgiebig mit den Bitmap-Dimensionen und den Parametern von BitBlt(...) und StretchBlt(...).
Verschieben Sie das Bitmap auf der Ausgabefläche, z.B. in die Mitte des Bildes. Geben Sie eigene Daten im Array ein.
Das Array können Sie natürlich vergrößern, um feinere Auflösungen zu erzielen.
Nur durch Ausprobieren können Sie den Zusammenhang vollständig erfassen.
Bit für Bit! Pixel für Pixel!

Übrigens kann man das Ganze auch ohne Einbinden einer BITMAP-Struktur erledigen.
Dann muß man in StretchBlt(...) direkt 16 als Breite und 5 als Höhe des Source-Bitmaps angeben:

void CMonochrom_BitmapView::OnDraw(CDC* pDC)
{
 CMonochrom_BitmapDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);

 CDC SpeicherDC;
 SpeicherDC.CreateCompatibleDC( pDC );
 SpeicherDC.SelectObject( &Bild );
 pDC->StretchBlt( 0, 0, 640, 200, &SpeicherDC, 0, 0, 16, 5, SRCCOPY );
}

Beachten Sie hier die zentrale Rolle von SpeicherDC:
 
.CreateCompatibleDC( pDC ) <--- SpeicherDC ---> .SelectObject( &Bild )
                                  |
                                  |
                                  |
                                  V
               pDC->StretchBlt( ... &SpeicherDC...)

Bisher haben wir uns auf Schwarz-Weiß-Bilder beschränkt, um den direkten Zusammenhang zwischen dem Bit-Zustand 0 oder 1 zu einem Punkt herzustellen. Um zumindest ein grundlegendes Verständnis für farbige Bitmaps zu vermitteln, bauen wir unser Programm etwas um:

Wir ändern unser Array von BYTE auf DWORD ab, um 32 bit in einer Zahl darstellen zu können, und vergrößern unser Array:

    // Implementierung
      private:
      DWORD A[40];
      CBitmap Bild;
 

Den Konstruktor räumen wir aus:

    CMonochrom_BitmapView::CMonochrom_BitmapView() { }
 

Mit dem Klassenassistenten fügen wir eine Funktion für WM_MOUSEMOVE ein.
Dort füllen wir das Array abhängig von den Mauskoordinaten mit Werten.
Die Zahl der Bitcounts setzen wir auf 16. Das bedeutet, dass wir 16 bit zur Festlegung der Farbe eines Punktes verwenden:

    void CMonochrom_BitmapView::OnMouseMove(UINT nFlags, CPoint point)
    {
     for(int i=0; i<=39; i++) A[i]= i * point.x * point.y;

     Bild.CreateBitmap( 16, 5, 1, 16, A );

     Invalidate();
     UpdateWindow();

     CView::OnMouseMove(nFlags, point);
    }
 

Stellen Sie Ihre Grafikkarte auf 16 bit (High Color) um. Dann sollten Sie farbige Punkte sehen.

Falls die Einstellung auf 16 bit nicht möglich ist, stellen Sie Ihre Grafikkarte auf 8 bit, 24 bit oder 32 bit um und verwenden jeweils entsprechend folgende Funktionen:

Bild.CreateBitmap(16, 5, 1,  8, A);
Bild.CreateBitmap(16, 5, 1, 24, A);
Bild.CreateBitmap(16, 5, 1, 32, A);

Zum Verständnis zeigen wir hier die Parameter der Funktion CBitmap::CreateBitmap(...):

BOOL CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitcount, const void* lpBits );

nWidth:    Breite des Bitmap in Pixel.
nHeight:   Höhe des Bitmap in Pixel.
nPlanes:   Anzahl der Farbebenen des Bitmaps.
nBitcount: Anzahl der Farbbits je Pixel.
lpBits:    Adresse des Bitmap-Arrays.

Hiermit erzeugt man ein sogenanntes DDB (device-dependant bitmap).
Für ein farbiges Bitmap sollte nPlanes auf 1 und nBitcount auf einen Wert größer 1, z.B. 8, 16, 24 oder 32, gesetzt werden.
Sind diese beiden Parameter gleich 1, erhält man ein monochromes Bitmap.

Wer sich intensiver mit Farbbitmaps auseinandersetzen will, der kann folgendes unternehmen:

Auf Basis eines BYTE-Arrays kann man die Daten zur Darstellung eines Pixels in Abhängigkeit der Bits pro Pixel wie folgt in ein Bild umsetzen:
 
bits 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
16















24























32























0 0 0 0 0 0 0 0

Um in einem Programm die aktuell mittels Grafikkarte eingestellten Bits pro Pixel abzufragen, kann man z.B. nach Anlegen eines kompatiblen SpeicherDC diese Zahl mittels folgender Member-Funktion als Rückgabewert abfragen:

int CDC::GetDeviceCaps( BITSPIXEL );

Wichtig: Die Anordnung der Farbinformationen ist  >> Blau-Grün-Rot <<  nicht RGB!

Bei 24 Bit oder 32 Bit pro Pixel werden für jede Farbe 8 Bit bereit gestellt. Bei 16 Bit pro Pixel ist die Anordnung typischerweise 5-6-5.

Um das Thema an einem praktischen Beispiel zu demonstrieren, werden wir folgendes anstreben:
Ein SDI-Beispiel soll in der linken oberen Ecke 3 Farbpunkte mit den Farben blau, grün und rot anzeigen. Als Varianten sollen 16-, 24- und 32-Bit-Farbdarstellungen berücksichtigt werden. Also beginnen wir:

Erstellen Sie eine einfache SDI-Anwendung mit dem Projektnamen 'Farb_Bitmap'.
In Schritt 1 wählen Sie SDI und Doc/View. Alles andere akzeptieren Sie bitte unverändert.

Wir erzeugen zunächst ein Objekt der MFC-Klasse CBitmap:

Rechtsklick in der Klassenansicht auf CFarb_BitmapView und "Member-Variable hinzufügen..." wählen.
Als Variablentyp geben Sie CBitmap und als Name Bild ein. Der Zugriff ist private:

// Implementierung
...
  private:
  CBitmap Bild;
...

Wir benötigen nun ein BYTE-Array, das die Daten für die 3 Punkte des Bitmap aufnimmt. Pro Punkt wollen wir maximal 32 Bit, also 4 Byte, vorsehen. Daher benötigen wir ein BYTE-Array für 12 Elemente. Rechtsklick in der Klassenansicht auf CFarb_BitmapView und "Member-Variable hinzufügen..." wählen. Als Variablentyp geben Sie BYTE (identisch mit unsigned char) und als Name A[12] ein.
Der Zugriff ist private:

// Implementierung
...
 private:
 BYTE A[12];
 CBitmap Bild;
...

Damit haben wir wieder die zwei wesentlichen Dinge erzeugt:
Ein BYTE-Array, in dem wir Bytes auf 0 - 255 setzen können und ein Objekt der Klasse CBitmap.

Die konkrete Ausgestaltung und Verknüpfung dieser beiden Objekte führen wir einfach im Konstruktor durch.
Dieser sieht bisher folgendermaßen aus:

CFarb_BitmapView::CFarb_BitmapView()
{
 // ZU ERLEDIGEN: Hier Code zur Konstruktion einfügen,

}

Wir definieren unser Array, indem wir alle Elemente auf 0 setzen. Die Verbindung mit der Member-Variable Bild über die Funktion CreateBitmap(...) kann hier noch nicht erfolgen, da wir zuerst die Zahl der Bits pro Pixel erfahren müssen. Dies erledigen wir in OnDraw(...):

CFarb_BitmapView::CFarb_BitmapView()
{
 for(int i=0; i<=11; i++) A[i]=0;
}

void CFarb_BitmapView::OnDraw(CDC* pDC)
{
 CFarb_BitmapDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);

 CDC SpeicherDC;
 SpeicherDC.CreateCompatibleDC( pDC );
 int bpp = SpeicherDC.GetDeviceCaps( BITSPIXEL );
 Bild.CreateBitmap(3,1,1,bpp,A);
 SpeicherDC.SelectObject( &Bild );
 pDC->StretchBlt( 0, 0, 60, 20, &SpeicherDC, 0, 0, 3, 1, SRCCOPY );
}

Wenn Sie jetzt kompilieren und starten, erhalten Sie drei schwarze Rechtecke (je 20 x 20) links oben.

Jetzt ist die Frage, wie wir Farbe ins Spiel bringen. Das erledigen wir über unser BYTE-Array.
Zunächst nehmen wir den einfacheren Fall der 24- oder 32-bit-Farbdarstellung:

void CFarb_BitmapView::OnDraw(CDC* pDC)
{
 CFarb_BitmapDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);

 CDC SpeicherDC;
 SpeicherDC.CreateCompatibleDC( pDC );
 int bpp = SpeicherDC.GetDeviceCaps( BITSPIXEL );

 switch(bpp)
 {
 case 24:
  A[0]  = 255;
  A[4]  = 255;
  A[8]  = 255;
 break;

 case 32:
  A[0]  = 255;
  A[5]  = 255;
  A[10] = 255;
 break;
 }

 Bild.CreateBitmap(3,1,1,bpp,A);
 SpeicherDC.SelectObject( &Bild );
 pDC->StretchBlt( 0, 0, 60, 20, &SpeicherDC, 0, 0, 3, 1, SRCCOPY );
}

Das sollte auch bei Ihnen funktionieren.  Zur Veranschaulichung noch einmal die Tabelle von oben:
 
bits 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
16















24























32























0 0 0 0 0 0 0 0

Die Geschichte mit der 16-Bit-Darstellung (5-6-5) ist etwas komplizierter, da wir hier einzelne Bits gezielt (siehe Abbildung oben) setzen müssen:

void CFarb_BitmapView::OnDraw(CDC* pDC)
{
 CFarb_BitmapDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);

 CDC SpeicherDC;
 SpeicherDC.CreateCompatibleDC( pDC );
 int bpp = SpeicherDC.GetDeviceCaps( BITSPIXEL );

 switch(bpp)
 {
 case 16:                //Pkt.1: A[0] und A[1]
  A[0]  = 16+8+4+2+1;     //Blau          Bit 0-4
  A[2]  = 128+64+32;      //Grün (Teil 1) Bit 5-7
  A[3]  = 4+2+1;         //Grün (Teil 2) Bit 0-2
  A[5]  = 128+64+32+16+8; //Rot           Bit 3-7
 break;

 case 24:                //Pkt.1: A[0], A[1] und A[2]
  A[0]  = 255;
  A[4]  = 255;
  A[8]  = 255;
 break;

 case 32:                //Pkt.1: A[0], A[1], A[2] und A[3]
  A[0]  = 255;
  A[5]  = 255;
  A[10] = 255;
 break;
 }

 Bild.CreateBitmap(3,1,1,bpp,A);
 SpeicherDC.SelectObject( &Bild );
 pDC->StretchBlt( 0, 0, 60, 20, &SpeicherDC, 0, 0, 3, 1, SRCCOPY ); //Zoom-Faktor 20
}

Testen Sie mit diesem kleinen Experimentierprogramm ausgiebig.
Nur auf diese Weise verstehen Sie die Zusammenhänge in praktischer Form.

Erweitern Sie das Array und die Bilddarstellung auf mehr als 3 Punkte. Verändern Sie Farben und Zoom-Faktor.
Wichtig ist, dass Sie die elementaren Zusammenhänge zwischen Array und CBitmap und die zentrale Rolle von SpeicherDC klar erkennen.
Entscheidend ist auch, dass Sie die erkennen, dass das Bitmap "device dependant" ist, eben ein DDB.

Für 8-Bit-Farbdarstellung (256 Farben), können Sie für Experimente z.B. folgendes ergänzen:

case 8:
  A[0]  = 140;
  A[1]  = 120;
  A[2]  = 100;
break;
 
 
 

Weiter zum Nächsten Kapitel

Zurueck zum Inhaltsverzeichnis