5. Transparent and Semi-transparent Bitmaps5.1. OverviewRectangular bitmaps are faster to display than other shapes and in order to use them to display non-rectangular objects (such as the glass beads) we must make part of them trasparent. We have a source image (the object image) and a destination image (the display screen). In most graphics systems it is possible to create a display by combining the source and the destination images by the following formula destination(x,y) = A*source(x,y) + (1-A)*destination(x,y) A is a number between 0 and 1, called usually the alpha value. If A is 0, we have complete transparency, if A is 1 we have complete opacity. 32 bit per pixel graphic systems provide an alpha channel, in addition to the three color channels. The value of A is expressed as an 8-bit integer, so the above formula must modified by dividing A by 255. In practice, the bitmap is modified by premultiplication with A and there are display functions that will render automatically according to the above formula. If we do not have a system with an alpha channel, we can still obtain transparency either complete or partial, but it takes more work. We show two different implementations (both using MFC) to produce the following effect. Anytime we press a mouse button a glass bead is displayed centered around the cursor position as shown in the image below.
In both cases we create the image of the glass bead and save it in an array m_bits that is filled by the following code that is executed during the initialization of the program.
// Listing A
int nc = 4; // three colors plus alpha channel
m_rad = 40;
m_bits = new BYTE[m_rad*m_rad*nc*4]; // (2*m_rad) sup 2 * nc
ASSERT(m_bits != NULL);
int i = 0;
double yd, dd, op, eps = 0.5;
for(int y=0; y<m_rad*2; y++) {
yd = (y-m_rad)*(y-m_rad);
for(int x=0; x<m_rad*2; x++) {
dd = (x-m_rad)*(x-m_rad) + yd;
dd = sqrt(dd);
// bead is transparent outside a circle of radius m_rad
if(dd > m_rad) for(int j=0; j<nc; j++, i++) m_bits[i] = 0;
else {
// set alpha to eps inside a small circle and make the
// bead gradually more opaque outside that circle
op = dd > m_rad/2 ? (2*dd/m_rad)*(1-eps) + 2*eps-1 : eps;
for(int j=0; j<nc; j++, i++)
m_bits[i] = (int)(255*op + 0.5);
}
}
}
This array can be used in two discuss as discussed next. 5.2. The Hard (but General) WayThe most general appraoch is to read the data from the display and combine them with the source bitmap (In the case the array m_bits) according to the formula of Section 3.1. The following is an implementation for MFC. The variable m_spot is declared as a CRect and when the mouse button is released the glass bead is erased by invoking InvalidateRect(&m_spot, FALSE); We break the listing of the code in pieces so that we can comment on it in detail.
// Listing B - part 1
// Prepare buffer for screen data
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC cdc(this);
m_spot.SetRect(point.x-m_rad, point.y-m_rad, point.x+m_rad, point.y+m_rad);
CBitmap b;
b.CreateCompatibleBitmap(&cdc, m_spot.Width(), m_spot.Height());
These four statements create a bitmap that can received data from the screen with the size of the rectangle m_spot. // Listing B - part 2 // Read screen data into the buffer CDC mdc; mdc.CreateCompatibleDC(&cdc); mdc.SelectObject(&b); mdc.BitBlt(0, 0, m_spot.Width(), m_spot.Height(), &cdc, point.x-rsz, point.y-rsz, SRCCOPY); The above statements do the actual copying of the screen on the bitmap b. The BitBlt operation uses as source cdc (i.e. the screen) and destination mdc that has been associated with the bitmap. // Listing B - part 3 // Copy the buffer into an array where you can compute // In some systems you may need to do that BITMAP binfo; b.GetBitmap(&binfo); int n = binfo.bmWidthBytes*binfo.bmHeight; int nc = binfo.bmBitsPixel/8; BYTE *bits = new BYTE[n]; ASSERT(bits != NULL); int k = b.GetBitmapBits(n, bits); ASSERT(k==n); Now we extract information about the dimensions of the bitmap and the number of colors nc. (The latter number is unknow until we run the program.) We use this information to allocate an array to copy the pixel values so that we can do arithmetic on them. The last two statements copy the data and we verify that we read as many bytes as we expected.
// Listing B - part 4
// Combine source (m_bits[]) and destination (bits[])
double blend;
int i = 0;
for(int j=0; j<k/nc; j++) {
blend = 1 - (double)m_bits[i+3]/255;
for(int k=0; k<nc-1; k++, i++)
bits[i] = m_bits[i] + (int)(blend*bits[i] + 0.5);
i++; // skip 4th byte
}
We do not multiple the values of the array m_bits[] by alpha because we have done that alreay when we created the array. // Listing B - part 5 // Put data back on the screen and clean up b.SetBitmapBits(k, bits); cdc.BitBlt(point.x-rsz, point.y-rsz, m_spot.Width(), m_spot.Height(), &mdc, 0, 0, SRCCOPY); delete[] bits; } Finally, we copy the array bits back onto the bitmap and the bitmap (pointed to by mdc) to the screen (pointed to by cdc.)
|