Zurueck zum Inhaltsverzeichnis
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;
Zurueck zum Inhaltsverzeichnis