Bitmaps
und Speicher-DCs
 |
In dieser Lektion wird nur die Darstellung von Bitmaps aus
Dateien behandelt. Später im Kurs lernen Sie auch noch Bitmaps kennen die in
Ressourcen abgelegt sind. Außerdem wird auf Grund der am Markt vorhandenen Grafikkarten
davon ausgegangen, dass für die Anzeige mindestens die High-Color Darstellung
(65535 Farben) eingestellt ist. Bei niedriger Farbauflösung (<= 256 Farben)
sind zusätzliche Schritte notwendig um eine Bitmap soweit wie möglich farbgetreu
wiedergeben zu können da hier die Bitmap-Farben in einer Farbpalette abgelegt
sind. |
Sehen wir uns zuerst einmal an, was Bitmaps sind. Bitmaps sind Dateien in denen
Bilder in digitalisierter Form abgespeichert sind. Jedes Pixel (Punkt) der Bitmap
besteht aus einem (monochrome Bitmap) oder 4, 8, 16, 24 oder 32 Bits, je nach Farbtiefe
der Bitmap. Bitmap-Dateien haben meistens die Extension BMP oder auch DIP. WINDOWS
unterstützt direkt die Darstellung von Bitmaps.
 |
Wollen Sie andere Grafikdateien wie z.B. GIF- oder JPG-Dateien darstellen,
so müssen Sie die dafür notwendigen Funktionen selbst schreiben; WINDOWS unterstützt
diese Grafikformate nicht von Haus aus. |
Wenn wir in dieser Lektionen von Bitmaps sprechen, dann sind damit immer so genannte
device
independend bitmaps
(DIB) gemeint. Es gibt noch ein älteres
Format für Bitmaps, die device
dependend
bitmaps (DDB),
das aber fast nicht mehr im Einsatz ist. Einer der Hauptunterschiede zwischen dem
DIB- und DBB-Format besteht darin, dass bei DIBs die Farbinformationen mit in der
Datei abgespeichert ist. Dadurch ist jedes GDI-fähige Ausgabegerät in der Lage,
DIBs farbrichtig darzustellen.
 |
Fast alle BMP-Dateien sind DIB-Dateien. DIB-Dateien besitzen
in der ersten beiden Bytes die Kennung 'BM'. Sie können dies nachprüfen, indem
Sie im Editor des VC++ eine BMP-Datei in binärer Darstellung einmal öffnen. |
Kommen wir nun zur Darstellung von Bitmaps. Leider besitzt WINDOWS keine Funktion
wie z.B. DrawBitmap(...) um eine Bitmap darzustellen. Die Darstellung einer
Bitmap muss in mehreren Schritten erfolgen.
Zuerst muss die Bitmap natürlich geladen werden. Dazu stellt WINDOWS die API-Funktion
LoadImage(...) zur Verfügung. Die MFC enthält leider keine äquivalente
CDC-Methode. Da LoadImage(...) nicht nur zum Laden von Bitmaps
verwendet werden kann, müssen beim Laden einer Bitmap mehrere Parameter entsprechend
gesetzt sein. Der erste Parameter ist das Instanzen-Handle des Moduls, in dem die
Bitmap abgelegt ist. Wollen Sie die Bitmap aus einer Datei laden, so geben Sie hier
den Wert '0' an. Mit Hilfe dieses Parameter wäre es aber auch möglich, Bitmaps aus
DLLs zu laden. Danach folgt der Name der zu ladenden Bitmap. Den nächsten Parameter
für den zu ladenden Image-Type müssen Sie auf den den Wert IMAGE_BITMAP setzen.
Die dann folgenden Parameter für die Bestimmung der Auflösung sind mit 0 anzugeben.
Der letzte Parameter der Funktion bestimmt dann noch, wie das Image zu laden ist.
Für das Laden einer Bitmap aus einer Datei ist hier die Konstanten LR_LOADFROMFILE
anzugeben. Wollen Sie die Bitmap schwarz/weiß darstellen, können Sie zusätzlich
noch die Konstante LR_MONOCHROME mit angeben. Die weiteren Konstanten entnehmen
Sie bitte wieder der Online-Hilfe. Als Returnwert liefert die Funktion ein allgemeines
Handle. Sie müssen diese Handle in der Regel auf den gewünschten Typ konvertierten;
in unserem Fall auf den Typ HBITMAP.
Wird die geladene Bitmap nicht mehr benötigt, so sollte sie mit der API-Funktion
DeleteObject(...) aus dem Speicher entfernt werden. Als Parameter geben
Sie der Funktion das von LoadImage(...) erhaltene Handle mit.
 |
Beginn wir die Übung zu diesem Kapitel damit, eine beliebige
Bitmap zu laden. Erstellen Sie sich dazu zunächst eine Standard SDI-Anwendung.
Geben Sie dem Projekt den Namen DispBmp. Damit Sie später im Datei-Öffnen
Dialog auch nur BMP-Dateien angezeigt bekommen, klicken Sie bitte im Schritt
4 des Anwendungs-Assistenten den Button Weitere Optionen... an. Geben
Sie dann, wie im Bild unten dargestellt, im Feld Dateierweiterung die
Extension bmp ein. Die restlichen Schritte führen Sie wie gewohnt durch.

Nach dem der Anwendungsrahmen erstellt ist, fügen Sie der Klasse CDispBmpDoc
eine privat Membervariable m_hBitmap vom Typ HBITMAP hinzu.
Initialisieren Sie die Membervariable im Konstruktor der Klasse mit dem Wert
NULL.
Nun kann die Bitmap geladen werden. Versuchen Sie es einmal selbst. Sie wissen
doch hoffentlich noch, in welcher Methode Sie die Bitmap laden müssen? Die Methode
Serialize(...) des Dokuments erhält eine Referenz auf das mit der zu
ladenden Datei verknüpfte CArchive-Objekt.
 |
Wenn Sie genug geschwitzt haben kann ich Ihnen auch meine Lösung anzeigen. |
Lösung zum Laden einer Bitmap
CDispBmpDoc::~CDispBmpDoc()
{
// Falls noch Bitmap
geladen ist,
// diese auch wieder freigeben
if (m_hBitmap != NULL)
::DeleteObject(m_hBitmap);
}
....
void CDispBmpDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// ZU ERLEDIGEN: Hier Code zum Speichern
einfügen
}
else
{
// ZU ERLEDIGEN: Hier Code zum Laden
einfügen
// Falls eine Bitmap bereits geladen wurde
// dann Bitmap loeschen
if (m_hBitmap != NULL)
::DeleteObject(m_hBitmap);
// Neue Bitmap laden und auch Fehler abfangen
m_hBitmap = (HBITMAP)::LoadImage(0,ar.m_strFileName,
IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if (m_hBitmap==NULL)
AfxMessageBox("Kann
Bitmap nicht öffnen!",MB_OK);
}
} |
Haben Sie bei Ihrer Lösung im Destruktor auch die geladene Bitmap wieder
gelöscht?
Das eigentliche Laden der Bitmap sollte nicht besonders schwer gewesen
sein, wenn Sie den Namen der Bitmap im CArchive-Objekt gefunden haben.
Und denken Sie immer daran, eine bereits geladene Bitmap vor dem Laden einer
neuen Bitmap auch wieder zu löschen.
Ende der Lösung
|
Sie können das Programm nun auch einmal starten. Selbstverständlich wird
im Augenblick noch keine Bitmap dargestellt, den der Teil fehlt uns noch, aber
im Datei-Öffnen Dialog sollte als Standard-Dateityp nun BMP stehen.
Als nächstes wird die Dokumentenklasse noch um eine public-Methode
erweitert, die dem Ansichtsobjekt den Zugriff auf die geladene Bitmap ermöglicht.
Fügen Sie zur Dokumentenklasse noch die nachfolgende Methode hinzu:
HBITMAP& CDispBmpDoc::GetBitmap()
{
return m_hBitmap;
} |
|
Damit ist die Anpassung der Dokumentenklasse abgeschlossen und wir kommen zur
Darstellung der geladenen Bitmap. Wie alle anderen Ausgaben erfolgt auch die Darstellung
der Bitmap in der OnDraw(...) Methode des Ansichtsobjektes. Zuerst holen
wir uns in dieser Methode das Handle die geladene Bitmap.
 |
Erweitern Sie die Methode OnDraw(...) der Ansichtsklasse
CDispBmpView wie folgt:
void CDispBmpView::OnDraw(CDC*
pDC)
{
CDispBmpDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten
hinzufügen
BITMAP BitmapParam;
HBITMAP hBitmap;
// Handle auf geladene Bitmap
holen
hBitmap = pDoc->GetBitmap();
if (hBitmap == 0)
return;
// keine Bitmap geladen!
} |
Was es mit dem ebenfalls eingefügten Datum BitmapParam auf sich
erfahren Sie gleich noch.
|
Da WINDOWS, wie Sie bereits erfahren haben, leider keine Funktion DrawBitmap(...)
besitzt muss zur Darstellung der geladenen Bitmap ein 'kleiner' Umweg gegangen werden.
Zur Darstellung einer Bitmap benötigen Sie immer einen so genannten Speicher-DC.
Ein Speicher-DC erlaubt es anstelle auf einem Ausgabegerät, wie z.B. dem Bildschirm,
in den Speicher 'zu zeichnen'. Doch warum sollte man überhaupt in den Speicher zeichnen
und nicht gleich auf dem Bildschirm? Stellen Sie sich dazu einmal vor, Sie haben
eine relativ komplexe Ausgabe die aus vielen einzelnen Zeichenanweisungen besteht.
Muss nun der Inhalt des Fensters neu darstellt werden, so werden die einzelnen Zeichenanweisungen
der Reihe nach abgearbeitet und der Anwender sieht quasi den Aufbau des Fensters.
Anders dagegen wenn Sie im Speicher zeichnen. Da der Anwender das Zeichnen im Speicher
nicht auf dem Bildschirm sieht, können Sie dort den Fensterinhalt sozusagen versteckt
aufbauen. Erst wenn der gesamte 'Fensterinhalt' gezeichnet wurde, kopieren Sie diesen
auf den Bildschirm. Für den Anwender hat dies dann den Anschein, als ob das gesamte
Fenster auf einmal gezeichnet wird. Wie der Speicherinhalt ins Fensters der Anwendung
gelangt erfahren Sie gleich. Mehr zu dem Speicher-DC erfahren Sie auf den Tipps&Tricks
Seiten zu diesem Kapitel bei den Animationen; dort spielt er eine wichtige Rolle.
Doch sehen wir uns nun an wie ein Speicher-DC erstellt wird. Ein Speicher-DC
wird immer als Kopie eines bestehenden DC erstellt. Dazu wird zuerst wie gewohnt
ein neues CDC-Objekt erstellt und dann dessen Methode CreateCompatibleDC(...)
aufgerufen. Diese Methode erhält als Parameter ein Zeiger auf das CDC-Objekt,
dessen Einstellungen für den Speicher-DC verwendet sollen. Sie erinnern sich doch
noch: ein DC ist nicht etwa der Speicher in den gezeichnet wird, sondern er enthält
nur die zum Zeichnen verwendeten Einstellungen.
 |
Fügen wir die Erstellung des Speicher-DC in unser Beispiel
ein.
void CDispBmpView::OnDraw(CDC*
pDC)
{
....
if (hBitmap == 0)
return; // keine Bitmap geladen!
// Neuen DC im Speicher
erstellen
CDC *pMemDC = new CDC;
BOOL bRetCode = pMemDC->CreateCompatibleDC(pDC);
ASSERT(bRetCode);
} |
|
Der so erstellte Speicher-DC hat jedoch noch einen 'kleinen' Nachteil: er besteht
nur aus einem monochromen Pixel. Um den zum Zeichnen notwendigen Speicher
zu reservieren, müssen Sie zuerst eine entsprechende Speicher-Bitmap erstellen.
Da wir aber schon eine Bitmap in den Speicher geladen haben, brauchen wir nur diese
mit dem Speicher-DC verbinden. Und hier kommt nun wieder die MFC ins Spiel. Die
MFC-Klasse CBitmap, die erst später beim Laden von Bitmap-Ressourcen ausführlicher
behandelt wird, enthält die statische Methode FromHandle(...), die ein
WINDOWS Bitmap-Handle (HBITMAP) in ein CBitmap-Objekt konvertiert. Als
Parameter erhält die Methode das Handle auf die zu 'konvertierende' Bitmap und liefert
als Returnwert einen Zeiger auf das damit assoziierte CBitmap-Objekt.
 |
Dieses CBitmap-Objekt ist nur solange gültig, bis die Anwendung
das nächste Mal in die Idle-Verarbeitung eintritt. Gehen Sie unter keinen Umständen
davon aus, dass das CBitmap-Objekt zwischen zwei Nachrichten immer
noch gültig ist. |
Anschließend muss nur noch das CBitmap-Objekt über die CDC-Methode
SelectObject(...) des Speicher-DC selektiert werden. Durch den Aufruf
von SelectObject(...) wird der für die Bitmap reservierte Speicher mit
dem Speicher-DC verknüpft.
 |
Bauen wir die eben beschriebenen Schritte wieder ins Beispiel
ein.
void CDispBmpView::OnDraw(CDC*
pDC)
{
....
ASSERT(bRetCode);
// temp. MFC Bitmap-Objekt
fuer geladene Bitmap erstellen
CBitmap* pCBitmap = CBitmap::FromHandle(hBitmap);
// Groesse und Anzahl der
darstellbaren Farben des Speicher-DCs
// auf Bitmap-Aufloesung einstellen
pMemDC->SelectObject(pCBitmap);
....
} |
|
Und damit sind wir auch fast schon fertig. Um die im Speicher befindliche Bitmap
jetzt noch ins Fenster zu kopieren wird die CDC-Methode BitBlt(...)
aufgerufen. Die BitBlt(...) Methode (sprich: Bit-Blit) ist in der Lage,
einen beliebig großen, rechteckigen Bereich innerhalb eines DC oder auch zwischen
zwei DCs zu kopieren. BitBlt ist übrigens die Abkürzung für Bit-Block-Transfer.
Damit Sie die Arbeitsweise dieser Methode besser verstehen, ist nachfolgend ausnahmsweise
einmal das Prototyping der Methode wiedergeben:
BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC*
pSrcDC,
int xSrc,
int ySrc, DWORD dwRop ); |
Die Parameter x/y geben hierbei die linke obere Koordinate des Zielbereichs
an und nWidth/nHeight die Breite und Höhe des zu kopierenden Bereichs.
pSrcDC ist ein CDC-Zeiger auf das CDC-Objekt, aus dem kopiert
wird. xSrc und ySrc geben die linke obere Koordinate der Bitmap
im Quellbereich an. dwRop bestimmt wie der Kopiervorgang erfolgen soll.
Hierfür sind diverse Konstanten definiert. In den Tipps&Tricks zu diesem Kapitel
erfahren Sie mehr darüber. Um die Bitmap unverändert vom Quell- in den Zielbereich
zu kopieren geben Sie hier die Konstante SRCCOPY an.
Bis auf zwei Parameter könnten Sie alle Parameter der Methode bestimmen. Soll
z.B. die Bitmap links oben im Fenster dargestellt werden, so ist für x
und y der Wert '0' anzugeben. Die beiden folgenden Parameter nWidth
und nHeight können Sie im Augenblick (noch) nicht bestimmen, da Sie ja
die Größe der geladenen Bitmap nicht kennen. Da die Bitmap vom Speicher-DC ins Fenster
kopiert werden soll, ist für pSrcDC der vorhin erstellte Speicher-DC einzusetzen.
Für xSrc und ySrc ist ebenfalls der Wert '0' einzusetzen da ja
die komplette Bitmap kopiert werden soll. Für dwRop geben Sie, wie erwähnt,
SRCCOPY an.
Sehen wir uns zum Schluss noch an, wie Sie an die Kenngrößen einer Bitmap gelangen.
Um an die Kenngrößen einer mit einem CBitmap-Objekt verknüpften Bitmap
zu gelangen, ist die CBitmap-Methode GetBitmap(...) aufzurufen.
Als Parameter erhält die Methode eine Zeiger auf eine Struktur vom Typ BITMAP die
folgenden Aufbau besitzt:
typedef struct tagBITMAP
{
int bmType;
int bmWidth;
int bmHeight;
int bmWidthBytes;
BYTE bmPlanes;
BYTE bmBitsPixel;
LPVOID bmBits;
} BITMAP; |
Wir wollen uns hier nicht alle Elemente der Struktur ansehen. Worauf es uns ankommt
sind die Elemente bmWidth und bmHeight. Sie enthalten die uns
noch fehlende Breite und Höhe der Bitmap.
 |
Versuchen jetzt wieder selbst einmal, das Beispiel zu vervollständigen.
Das einzige was Sie 'nur' noch tun müssen, ist die geladene Bitmap mit der erwähnten
Methode BitBlt(...) aus dem Speicher ins Fenster zu kopieren.
 |
Wenn Sie genug geschwitzt haben kann ich Ihnen auch meine Lösung anzeigen. |
Lösung zum Anzeigen einer Bitmap
void CDispBmpView::OnDraw(CDC*
pDC)
{
....
// Groesse und Anzahl der darstellbaren Farben des Speicher-DCs
// auf Bitmap-Aufloesung einstellen
pMemDC->SelectObject(pCBitmap);
// Parameter der Bitmap auslesen
pCBitmap->GetBitmap(&BitmapParam);
// Bitmap-Daten nun
aus dem Speicher auf den Bildschirm kopieren
pDC->BitBlt(0,0,BitmapParam.bmWidth,BitmapParam.bmHeight,pMemDC,0,0,SRCCOPY);
delete pMemDC;
} |
Haben Sie vielleicht bei einem Ihrer Versuch folgende Anzeige im Debuggerfenster
erhalten:
Detected memory leaks!
Dumping objects ->
D:\MFCKurs\Work\DispBmp\DispBmpView.cpp(66) : {111} client block at
0x00771560, subtype 0, 16 bytes long.
a CDC object at $00771560, 16 bytes long
Object dump complete.
Thread 0xFFFAEDCB wurde mit Code 0 (0x0) beendet.
Das Programm "D:\MFCKURS\WORK\DispBmp\Debug\DispBmp.exe" wurde mit Code
0 (0x0) beendet. |
Dann haben Sie vergessen, den in der OnDraw(...) Methode erstellten
Speicher-DC auch wieder zu löschen! Sie können übrigens durch einen Doppelklick
auf die Zeile mit der Angabe der Zeilennummer im Source-Code zu Stelle gelangen,
an der der nicht mehr freigegebene Speicher reserviert wurde. In unserem
Fall sollten Sie also dann auf der Zeile landen, in der der Speicher-DC
erstellt wird.
Ende der Lösung
|
Sie können das Programm nun starten und dann beliebige Bitmaps laden. Aber
Achtung! Speicher Sie eine geladene Bitmap-Datei im Beispiel niemals ab, da
sie sonst danach leer ist.
Das fertig Beispiel finden Sie wieder unter 05GDI\DispBmp.
|
Damit beenden wir die Einführung in die Darstellung von Bitmap. Bei den Tipps&Tricks
zu diesem Kapitel können Sie sich dann noch einige zusätzlichen Anwendungen mit
Bitmaps ansehen. Ansonsten geht's weiter mit einem weiteren Grafikformat, dem WMF-Format.
|