Esempio: bmp_two

Trasparenza

Dare alle bitmap l’apparenza di avere sezioni trasparenti è abbastanza semplice, bisogna aggiungere all’immagine che vogliamo far diventare trasparente, una seconda immagine in bianco e nero che fa da Maschera.

Per fare funzionare correttamente l’effetto bisogna soddisfare le seguenti condizioni: Prima di tutto, l’immagine colorata deve essere nera in tutte le aree che vogliamo visualizzare come trasparenti. E secondo, l’immagine che fa da Maschera deve essere bianca nelle aree che vogliamo trasparenti e nera nelle altre parti. Le due immagini colorate e bianco e nero sono visualizzate sulla sinistra nella foto di esempio in questa pagina.

Operazioni BitBlt

Come otteniamo la trasparenza? Prima effettuiamo un BitBlt() sull’immagine che fa da maschera usando l’operazione SRCAND come ultimo parametro, e poi sopra di questa effettuiamo un BitBlt() con l’immagine colorata usando l’operazione SRCPAINT. Il risultato sarà: le aree che vogliamo trasparenti nell’HDC di destinazione non cambiano, mentre il resto dell’immagine è disegnato come sempre.

    SelectObject(hdcMem, g_hbmMask);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);

    SelectObject(hdcMem, g_hbmBall);
    BitBlt(hdc, 0, bm.bmHeight, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCPAINT);

Abbastanza semplice, vero? Fortunatamente lo è, ma rimane una domanda… da dove proviene la maschera? Ci sono sostanzialmente due modi per procurarsi la maschera…

  • Farsela da soli, magari con lo stesso programmi con cui è stata fatta la bitmap colorata, e questa è una soluzione ragionevole se è stato usato un numero limitato di grafica all’interno del programma. In questo modo è sufficiente aggiungere il file di maschera alle risorse del programma e caricarlo con LoadBitmap().
  • Generarla mentre il programma gira, selezionando un colore nell’immagine originale per farlo diventare il colore “trasparente”, e creare la maschera che è bianca ovunque questo colore esista, e nera dalle altre parti.

Nel primo metodo non c’è niente di nuovo, per il secondo metodo c’è bisogno di usare un piccolo trucchetto con BitBlt(), e ora vediamo come.

Creazione della Maschera

Il modo migliore per farlo, potrebbe essere un loop per ogni pixel dell’immagine, controllare il valore e settarlo al pixel corrispondente della maschera (bianco o nero)… ma SetPixel() è un modo veramente lento per disegnare le immagini e non è nemmemo molto pratica.

Un metodo più efficiente è quello di usare BitBlt() per convertire l’immagine colorata in una in bianco e nero. Se usiamo BitBlt() (usando SRCCOPY) da un HDC che conserva un’immagine colorata in un HDC che conserva un’immagine bianco e nero, la funzione controllerà quale colore è settato come Colore di background nell’immagine colorata e metterà tutti questi pixel a Bianco, tutti i pixel che sono diversi dal colore di background saranno messi a Nero.

Questa operazione fa esattamente al caso nostro dato che vogliamo settare a bianco il colore di background che vogliamo trasparente. Da notare che questo trucco funziona solamente con le immagini monocrome (bianco e nero)… ossia con quelle che utilizzano 1 bit per pixel. Se si tenta questa operazione con un’immagine che ha sì, pixel bianco e neri ma che utilizza più di un bit per pixel (diciamo 16 o 24), non funzionerà

Ricordate la prima condizione per creare la trasparenza espressa sopra? Diceva che l’immagine deve essere nera in tutti i punto che vogliamo trasparenti. La bitmap usata nell’esempio rispetta già questa condizione, quindi non ha bisogno di nessuna operazione speciale, ma se si va a settare la trasparenza su un’immagine che ha un colore differente da far diventare trasparente (hot pink è una scelta comune), abbiamo bisogno di un secondo passaggio che consiste nell’utilizzare la maschera che abbiamo appena creato per alterare l’immagine originale, in modo che i punti che vogliamo trasparenti diventino di colore nero. Da notare che la presenza di altri pixel neri all’interno dell’immagine non compromette l’effetto finale dato che all’interno della maschera i punti neri nell’immagine colorata sono rappresentati da punti bianchi, quindi vengono visualizzati correttamente. Per effettuare questa operazione utilizziamo sempre BitBlt() dalla nuova maschera all’immagine colorata originale, usando l’operazione SRCINVERT, che setta tutte le aree che sono bianche nella maschera a nero nell’immagine colorata.

Questa operazione è leggermente più complessa delle altre, quindi in linea generale potrebbe essere utile avere una funzione che faccia tutto questo per noi, ed eccola qui:

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
    HDC hdcMem, hdcMem2;
    HBITMAP hbmMask;
    BITMAP bm;

    // Crea una maschera monocroma (1 bit).

    GetObject(hbmColour, sizeof(BITMAP), &bm);
    hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

    // Procura alcuni HDC compatibili con il driver video.

    hdcMem = CreateCompatibleDC(0);
    hdcMem2 = CreateCompatibleDC(0);

    SelectBitmap(hdcMem, hbmColour);
    SelectBitmap(hdcMem2, hbmMask);

    // Setta il colore di background dell'immagine colorata con il colore
    // che vogliamo visualizzare come trasparente.
    SetBkColor(hdcMem, crTransparent);

    // Copia i bit dall'immagine colorata all'immagine della maschera B+W... tutto
    // quello con il colore di background diventa bianco mentre il resto diventa nero...
    // Proprio come volevamo

    BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    // Prende la nuova maschera e la usa per modificare nell'immagine originale
    // il colore che vogliamo trasparente trasformandolo in nero in modo che
    // l'effetto trasparenza funzioni alla perfezione.
    BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);

    // Pulisce.

    DeleteDC(hdcMem);
    DeleteDC(hdcMem2);

    return hbmMask;
}

NOTA: Questa funzione chiama SelectObject() per selezionare temporaneamente la bitmap colorata che passata dentro l’HDC. Una bitmap non può essere selezionata in più di un HDC per volta, quindi bisogna essere sicuri che la bitmap non sia selezionata all’interno di qualche altro HDC altrimenti la chiamata alla funzione fallirà. Ora che abbiamo la nostra bella funzione possiamo andare a crearci una maschera per la foto originale non appena la andiamo a caricare:

    case WM_CREATE:
        g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
        if(g_hbmBall == NULL)
            MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);

        g_hbmMask = CreateBitmapMask(g_hbmBall, RGB(0, 0, 0));
        if(g_hbmMask == NULL)
            MessageBox(hwnd, "Could not create mask!", "Error", MB_OK | MB_ICONEXCLAMATION);
    break;

Il secondo parametro è ovviamente il colore dell’immagine originale che vogliamo far diventare trasparente, in questo caso il nero.

Come fa a funzionare tutto questo?

.. vi starete chiedendo. Beh si spera che la vostra esperienza con il C o con il C++ vi porti a comprendere le operazioni binarie come OR, XOR, AND, NOT e così via. Non rientra nelle finalità di questo tutorial la spiegazione dettagliata di tutte le operazioni effettuate a livello computazionale per ottenere il risultato desiderato, ad ogni modo si proverà a spiegare come queste operazioni sono state utilizzate in questo esempio. Capire le operazioni non è un passo critico per utilizzare queste funzioni, si può tranquillamente credere che funzionano per effettuare quello che si desidera.

SRCAND

La raster operation SRCAND, oppure ROP code per BitBlt() vuol dire combinare i bit usando l’operatore binario AND. E questo è: solo i bit che sono settati a 1 sia nell’origine che nella destinazione, sono settati anche nel risultato finale. Noi andiamo a fare proprio questo con la nostra maschera per settare a nero tutti i pixel che eventualmente hanno un colore nell’immagine colorata. L’immagine utilizzata come maschera ha il nero (che in binario corrisponde a 0) dove vogliamo il colore, e bianco (tutti 1) dove vogliamo la trasparenza. Qualsiasi valore combinato con 0 usando AND è 0, quindi tutti i pixel utilizzati nella maschera vengono settati a 0 nel risultato e finiscono per essere neri. Qualsiasi valore combinato con 1 utilizzando AND è lasciato invariato, quindi se in origine è 1 rimane 1 e se in origine è 0 rimane 0…quindi tutti i pixel che sono bianchi nella nostra maschera, rimangono invariati dopo la chiamata a BitBlt(). Il risultato è l’immagine in alto a destra nella foto dell’esempio.

SRCPAINT

SRCPAINT utilizza l’operatore binario OR, quindi se uno dei due o se entrambi i bit sono settati a 1, nel risultato saranno settati a 1. Utilizziamo questa operazione sull’immagine colorata. Quando la parte nera (trasparente) è combinata con i dati nella destinazione utilizzando OR, il risultato sarà che i dati rimarranno invariati perchè tutti i valori combinati con 0 non verranno modificati.

Comunque, il resto dell’immagine colorata non è nero, e se neanche la destinazione è nera avremo una combinazione dei colori della sorgente e della destinazione, il risultato si può vedere nella seconda palla della seconda riga della foto di esempio. Questa è la ragione principale per cui è stata usata la maschera per settare i pixel a prima a nero, in questo modo essi rimangono invariati in seguito quando viene utilizzato OR sull’immagine colorata.

SRCINVERT

Rappresenta l’operazione di XOR, utilizzata per impostare il colore di trasparenza nell’immagine originale a nero ( se ancora non lo era ). Combinando un pixel nero della maschera con con un pixel che non appartiene allo sfondo, lo lascia immutato, mentre combinando un pixel bianco della maschera (che abbiamo generato settando un colore particolare come “sfondo”) con un colore di sfondo della destinazione, lo cancella e lo imposta a nero.

Questo e’ un piccolo rompicapo di GDI che riguarda il trattamento delle immagini colorate con quelle monocrome, pensarci molto fa male alla testa ma serve veramente…

Esempio

Il codice di esempio nel progetto bmp_two associato a questa sezione contiene il codice dell’immagine di esempio all’inizio della pagina. Consiste nel disegnare la maschera e l’immagine colorata esattamente come sono usando SRCCOPY, dopodiché, vengono usate rispettivamente le operazioni SRCAND e SRCPAINT e infine il tutto viene combinato per ottenere il prodotto finale.