Transparente Bitmap

Anstelle vieler Vorworte steigen wir gleich ein die Praxis ein und gehen ans Darstellen einer transparenten Bitmap. Zuerst muss die Bitmap natürlich wieder geladen werden. Wir tun dies im Beispiel im Konstruktor der Ansichtsklasse mit der bereits bekannten API-Funktion LoadImage(...). Und da alles, was irgendwie einmal erzeugt bzw. geladen wurde auch wieder freigegeben werden sollte, löschen wir die Bitmap entsprechend mittels DeleteObject(...) im Destruktor.

CTransBmpView::CTransBmpView()
{
    // ZU ERLEDIGEN: Hier Code zur Konstruktion einfügen,
    // Bitmap fuer transparent Darstellung laden
    m_hTransBmp = (HBITMAP)::LoadImage(NULL,"wizard.bmp",
                                        IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
    ASSERT(m_hTransBmp);
}

CTransBmpView::~CTransBmpView()
{
    // geladene Bitmap wieder freigeben
    ::DeleteObject(m_hTransBmp);
}

Damit haben wir zunächst ein Handle auf die darzustellende Bitmap. Den Rest der Aufgabe erledigen wir in den beiden CWnd-Methoden OnInitialUpdate(...) und OnDestroy(...). In der Methode OnInitialUpdate(...) erstellen wir zunächst (wie gewohnt) einen Speicher-DC, in den die geladene Bitmap selektiert wird.

void CTransBmpView::OnInitialUpdate()
{
    CView::OnInitialUpdate();
   
    // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
    BITMAP    BitmapParam; // Parameter der Bitmaps

    // DC des Views holen
    CDC *pViewDC = GetDC();

    // --- Speicher-DC fuer transparente Bitmap erstellen ---
    // zuerst temp. CBitmap-Objekt aus HBITMAP erstellen
    CBitmap *pCBmp = CBitmap::FromHandle(m_hTransBmp);
    // Bitmap-Groesse auslesen
    pCBmp->GetBitmap(&BitmapParam);
    m_nTransBmpWidth = BitmapParam.bmWidth;
    m_nTransBmpHeight = BitmapParam.bmHeight;
    // Speicher-DC erstellen
    m_pTransMemDC = new CDC;
    m_pTransMemDC->CreateCompatibleDC(pViewDC);
    // und geladene Bitmap darin auswaehlen
    m_pTransMemDC->SelectObject(pCBmp);
    ....
}

Soweit im Prinzip nichts Neues. Der Trick beim Darstellen einer transparenten Bitmap besteht nun darin, sich eine monochrome Bitmap-Maske zu erzeugen, bei der die transparent erscheinende Farbe durch die Farbe weiß ersetzt wird und die restlichen Farben der Bitmap durch die Farbe schwarz. Erstellen wir jetzt zuerst eine monochrome Bitmap mit den gleichen Abmessungen wie die Original-Bitmap. Anschließend wir ein zweiter Speicher-DC erstellt, in dem die erstellte monochrome Bitmap selektiert wird.

void CTransBmpView::OnInitialUpdate()
{
    ....
    // und geladene Bitmap darin auswaehlen
    m_pTransMemDC->SelectObject(pCBmp);
    // --- Speicher-DC fuer Bitmap-Maske erstellen ---
    // zuerst monochromes (s/w) CBitmap-Objekt in der gleichen
    // Groesse wie eingelesen Bitmap erstellen
    CBitmap    CMaskBmp;
    CMaskBmp.CreateBitmap( m_nTransBmpWidth, m_nTransBmpHeight, 1, 1, NULL );
    // Speicher-DC erstellen
    m_pMaskMemDC = new CDC;
    m_pMaskMemDC->CreateCompatibleDC(pViewDC);
    // und Masken-Bitmap darin auswaehlen
    m_pMaskMemDC->SelectObject(&CMaskBmp);
    ....
}

Diese monochrome Bitmap ist bis jetzt aber noch mit einem zufälligen Pixelmuster versehen. Würden Sie die Bitmap jetzt mittels BitBlt(..., SRCCOPY) ausgeben, so würden Sie etwa das nachfolgende Ergebnis erhalten. Beachten Sie bitte, dass die dargestellte 'Bitmap' nur einfarbig ist.

Nicht initialisierte s/w Bitmap

Damit die Transparenz der Bitmap später besser ersichtlich wird, wurde Fenster mit einem rot/gelb schraffiertem Hintergrund versehen.

Sollten Sie beim Ausprobieren eine andere Farbkombination als schwarz/weiß erhalten so liegt das daran, dass Sie noch eine andere Text- und Hintergrundfarbe eingestellt haben. Aber was passiert eigentlich, wenn Sie die soeben neu erzeugte Bitmap mittels BitBlt(...) darstellen? Sie kopieren letztendlich eine Bitmap aus einem monochromen (einfarbigen) DC in einen mehrfarbigen DC um. Da aber beim Kopieren die Anzahl der Farben erhöht werden muss, damit die Bitmap wieder richtig erscheint, werden alle '1' Pixel der Bitmap auf die aktuelle Textfarbe gesetzt und alle '0' Pixel auf die aktuelle Hintergrundfarbe.

Und nun kommt der eigentliche 'Trick'. Wir kopieren umgekehrt die geladene Bitmap aus dem mehrfarbigen DC in den vorhin erstellen monochromen DC um.

void CTransBmpView::OnInitialUpdate()
{
    ....
    // und Masken-Bitmap darin auswaehlen
    m_pMaskMemDC->SelectObject(&CMaskBmp);
    // --- S/W Bitmap erstellen ----
    // Farbe des ersten Pixels soll die transp. Farbe sein
    // zum Versuchen: Pixel-Position auch einmal auf 200,250 aendern
    COLORREF    BkgndColor = m_pTransMemDC->GetPixel(1,1);
    m_pTransMemDC->SetBkColor(BkgndColor);
    // geladene Bitmap in monochrome Bitmap umkopieren
    // -> alles ausser HG erscheint nun in schwarzer Farbe
    m_pMaskMemDC->BitBlt(0,0,m_nTransBmpWidth,m_nTransBmpHeight,
                         m_pTransMemDC,0,0, SRCCOPY );
    ....
}

Beim Umkopieren erfolgt eine Farbreduktion die nach folgenden Schema durchgeführt wird. Alle Pixel, die der aktuellen Hintergrundfarbe entsprechen, werden zu weißen Pixel und alle anderen Farben zu schwarzen Pixel. Damit ergibt sich dann folgende Bitmap im monochromen DC.

Die Masken-Bitmap

Und mit dieser s/w Maske werden nun alle weiteren Operationen vorgenommen. Zuerst entfernen wir aus der Original-Bitmap den Hintergrund. Dazu wird zunächst die Maske invertiert, d.h. der Hintergrund erscheint schwarz (RGB:0x00,0x00,0x00) und der Umriss der Bitmap weiß (RGB: 0xFF,0xFF,0xFF). Die so invertierte Maske wird mit der Original-Bitmap verundet. Bei dieser Verundung wird aus der Original-Bitmap der Hintergrund entfernt, d.h. er wird durch die Farbe schwarz ersetzt (0x00 AND 0x.. ergibt immer 0x00!).

void CTransBmpView::OnInitialUpdate()
{
    ....
    // geladene Bitmap in monochrome Bitmap umkopieren
    // -> alles ausser HG erscheint nun in schwarzer Farbe
    m_pMaskMemDC->BitBlt(0,0,m_nTransBmpWidth,m_nTransBmpHeight,
                         m_pTransMemDC,0,0, SRCCOPY );

    // Aus Orignal-Bitmap Hintergrund entfernen
    m_pTransMemDC->SetBkColor(RGB(255,255,255));
    // Negativ von S/W Bitmap, HG ist nun schwarz (RGB:0,0,0)
    m_pMaskMemDC->BitBlt(0,0,m_nTransBmpWidth,m_nTransBmpHeight,
                         m_pMaskMemDC,0,0, DSTINVERT );
    // geladene Bitmap mit negativ von S/W-Bitmap verunden
    // --> HG der geladenen Bitmap ist nun schwarz
    m_pTransMemDC->BitBlt(0,0,m_nTransBmpWidth,m_nTransBmpHeight,
                          m_pMaskMemDC,0,0, SRCAND );
    // Mask wieder restaurieren (HG ist weiss = 0xFFFFFF)
    m_pMaskMemDC->BitBlt(0,0,m_nTransBmpWidth,m_nTransBmpHeight,
                         m_pMaskMemDC,0,0, DSTINVERT );

    // DC des Views wieder freigeben
    ReleaseDC(pViewDC);
}

Somit ergibt sich jetzt folgende darzustellende Bitmap bei der die Hintergrundfarbe durch die Farbe schwarz ersetzt wurde:

Bitmap ohne Hintergrundfarbe

Nach der Verundung wird die Maske wieder hergestellt und zum Schluss noch der am Anfang erstellte ViewDC freigegeben.

Bevor wird jetzt auf die eigentliche Darstellung der Bitmap zu sprechen kommen, vervollständigen wir noch die Methode OnDestroy(...). Da wir in OnInitialUpdate(...) zwei Speicher-DC erstellt haben, sollten wir diese auch an geeigneter Stelle wieder freigeben.

void CTransBmpView::OnDestroy()
{
    CView::OnDestroy();
   
    // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
    // Speicher-DCs loeschen
    delete m_pTransMemDC;
    delete m_pMaskMemDC;
}

So, jetzt ist es endlich soweit, wir stellen die Bitmap transparent dar. Sehen wir uns zunächst nochmals an was wir bisher haben:

TBmp2.gif (2858 Byte) TBmp3.gif (11513 Byte)
Bitmap-Maske
Original-Bitmap mit
schwarzem Hintergrund

Was wir jetzt 'nur' noch tun müssen, sind diese beiden Bitmaps durch geeignete Kopier-Operationen ins Fenster zu bringen. Zuerst kopieren wir die Bitmap-Maske so ins Fenster, dass alle Pixel im Fenster, die nicht im schwarzen Bereich liegen, ihren Farbwert behalten und der Rest, das ist der Umriss der Bitmap, schwarz wird. Dies erreichen Sie durch den BitBlt(...,SRCAND) Befehl.

void CTransBmpView::OnDraw(CDC* pDC)
{
    CTransBmpDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    // ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten hinzufügen
    // Bitmap-Umrisse ausschneiden aus Hintergrund
    // Zum Versuchen: Textfarbe einmal auf einen anderen Wert setzen
    // Umrisse der Bitmap aus dem HG ausschneiden
    pDC->SetTextColor(RGB(0,0,0));
    pDC->SetBkColor(RGB(255,255,255));
    pDC->BitBlt(20,30,m_nTransBmpWidth,m_nTransBmpHeight,m_pMaskMemDC,0,0,SRCAND);
}

Die Verundung des Fensterinhalts mit der Bitmap-Maske bringt folgendes Ergebnis:

Ausgeschnitte Umrisse der Bitmap

Zum Schluss wird dann die Original-Bitmap, in der der Hintergrund durch die Farbe schwarz (RGB:0x00,0x00,0x00!) ersetzt wurde, ins Fenster verodert.

void CTransBmpView::OnDraw(CDC* pDC)
{
    ....
    pDC->BitBlt(20,30,m_nTransBmpWidth,m_nTransBmpHeight,m_pMaskMemDC,0,0,SRCAND);
    // Bitmap einkopieren
    // Zum Versuchen: auch einmal diesen Aufruf entfernen
    pDC->BitBlt(20,30,m_nTransBmpWidth,m_nTransBmpHeight,m_pTransMemDC,0,0,SRCPAINT);
}

Und als Ergebnis erhalten Sie ein Bitmap deren Hintergrund entfernt wurde.

TBmp5.gif (11728 Byte) TBmp6.gif (12027 Byte)
Original
Bitmap
Transparent
einkopierte Bitmap

Das fertige Beispiel finden Sie unter 05GDI\TransBmp.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470