C++ at Work

Layered Windows, Blending Images

Paul DiLascia

Code download available at:CatWork0512.exe(493 KB)

Q Is there a way that I can create a window that's 50 percent transparent and passes all mouse events through to the desktop or another app window beneath mine?

Q Is there a way that I can create a window that's 50 percent transparent and passes all mouse events through to the desktop or another app window beneath mine?

Scott Stringham

A Why yes, and it's fairly easy. All you have to do is create a "layered window." I wrote a little program called lwtest (layered window test) that shows how. You can download it from the MSDN®Magazine Web site. To create a layered window, you need the extended style WS_EX_LAYERED, and to make the window transparent to mouse clicks, you need WS_EX_TRANSPARENT. You can set both styles, after the window has been created, in your OnCreate handler. In MFC it looks like this:

int CMainFrame::OnCreate(...) { ... ModifyStyleEx(0, WS_EX_LAYERED|WS_EX_TRANSPARENT); }

A Why yes, and it's fairly easy. All you have to do is create a "layered window." I wrote a little program called lwtest (layered window test) that shows how. You can download it from the MSDN®Magazine Web site. To create a layered window, you need the extended style WS_EX_LAYERED, and to make the window transparent to mouse clicks, you need WS_EX_TRANSPARENT. You can set both styles, after the window has been created, in your OnCreate handler. In MFC it looks like this:

int CMainFrame::OnCreate(...) { ... ModifyStyleEx(0, WS_EX_LAYERED|WS_EX_TRANSPARENT); }

ModifyStyle and ModifyStyleEx are special MFC CWnd methods that do what their names suggest. If you're writing in C, you'd call GetWindowLong(GWL_EXSTYLE) to get the extended style, then tweak it and call SetWindowLong(GWL_EXSTYLE) to set it. This is, in effect, what ModifyStyle(Ex) does. Or, of course, you can use the styles when you create your window.

Once you've created a layered window, you can call SetLayeredWindowAttributes to set the transparency. One of the layered window attributes you can set is LWA_ALPHA, which is the transparency. The transparency is measured from zero (totally transparent) to 255 (opaque). For 50 percent transparency you'd call SetLayeredWindowAttributes, like so:

// in CMainFrame::OnCreate SetLayeredWindowAttributes(0, 255 * 0.50, LWA_ALPHA);

I've used the multiplication to highlight the general formula; you could just use 128 since that's half of 255 (rounded). You can also use a specific color as the transparent color. In that case, you'd use LWA_COLORKEY as the attribute, and specify a COLORREF in the first parameter. Windows® makes all pixels that have that color transparent. Note that the preceding snippet assumes you're calling from within a CWnd-derived object. In C, you'd use ::SetLayeredWindowAttributes, which takes the HWND as an additional first parameter.

You can use layered windows to do animations and other transition effects; for additional details, see the section entitled "Layered Windows" in the documentation.

Q I'm writing a slide show program that shows a sequence of JPEG images. I used your CPicture class from the March 2002 column to draw the images (see C++ Q&A: Do You Have a License for that GIF? PreSubclassWindow, EOF in MFC, and More). That part works fine. But now I'd like to add a feature that fades from one image to the next. I can do this in a Web page using transition effects. I'm wondering if there's any way to access this from code? Or is there some other way to fade one image into another?

Q I'm writing a slide show program that shows a sequence of JPEG images. I used your CPicture class from the March 2002 column to draw the images (see C++ Q&A: Do You Have a License for that GIF? PreSubclassWindow, EOF in MFC, and More). That part works fine. But now I'd like to add a feature that fades from one image to the next. I can do this in a Web page using transition effects. I'm wondering if there's any way to access this from code? Or is there some other way to fade one image into another?

Bob Kline

A Indeed you can access the Internet Explorer transition effects using COM. These effects—which include fade, wipe, box in, box out, bars, and all the others—are supported in DirectX®. The details are beyond the scope of this column, so I can only refer you to the documentation, which is in a section called "Using Transforms in C++" under Internet Development. You'll need some familiarity with COM as well as some basic knowledge of DirectX concepts like surfaces and transforms (DXSurface and DXTransform).

A Indeed you can access the Internet Explorer transition effects using COM. These effects—which include fade, wipe, box in, box out, bars, and all the others—are supported in DirectX®. The details are beyond the scope of this column, so I can only refer you to the documentation, which is in a section called "Using Transforms in C++" under Internet Development. You'll need some familiarity with COM as well as some basic knowledge of DirectX concepts like surfaces and transforms (DXSurface and DXTransform).

But if all you want is a simple blend from one image to the next, I can show you how to do it with the GDI+ function AlphaBlend, which the Redmondtonians were friendly enough to wrap in MFC as CDC::AlphaBlend. The alpha in AlphaBlend is a graphics term. Typically, bitmaps use 3 bytes to specify a pixel: 1 byte each for red, green, and blue color values. Since a 32-bit DWORD has 4 bytes, that leaves one extra byte by my count (4-3=1).The extra byte is often used as the "alpha channel," used to specify the pixel's transparency. The alpha value is used to merge pixels according to the following formula:

[R,G,B]blended = ?[R,G,B]image + (1-?? [R,G,B]background

When alpha is zero, you get the background (image is fully transparent); when alpha is 1, you get the image (fully opaque). In practice, using an 8-bit byte, the alpha value ranges from 0-255 instead of 0-1, where zero is transparent and 255 is opaque. You can use per-pixel alpha values, but most applications don't need it; most blending applications apply a constant alpha value to a whole object such as an image. For example, you might want a particular image to appear 25 percent transparent.

The AlphaBlend function is like the old familiar functions BitBlt and StretchBlt, but it does blending. Pronounced "blit," the term derives from the ancient PDP-10 BLT (block transfer) instruction used to transfer a large block of memory from one location to another. Figure 1 shows the AlphaBlend function in detail. The parameters are straightforward, but using AlphaBlend to do blending can be a chore because you can't just call it once, you have to call it repeatedly to gradually blend one image to the next, using a timer and different alpha values ranging from 0 to 255.

Figure 1 AlphaBlend Function and Parameters

// MFC wraps the GDI+ function ::AlphaBlend, which has the // destination HDC as the first parameter. // From afxwin.h: class CDC : public CObject { BOOL AlphaBlend(int xDest, // destination: x int yDest, // destination: y int nDestWidth, // destination: width int nDestHeight, // destination: height CDC* pSrcDC, // source device context int xSrc, // source: x int ySrc, // source: y int nSrcWidth, // source: width int nSrcHeight, // source: height BLENDFUNCTION blend); // see below ... }; // From wingdi.h: typedef struct _BLENDFUNCTION { BYTE BlendOp; // op code: must be AC_SRC_OVER BYTE BlendFlags; // flags must be zero BYTE SourceConstantAlpha; // alpha value, 0 to 255 BYTE AlphaFormat; // AC_SRC_ALPHA for per-pixel alpha; else, // 0 } BLENDFUNCTION,*PBLENDFUNCTION;

To show how AlphaBlend works in practice, I wrote a program called BlendView based on my image viewer from the March 2002 column. BlendView lets you view image files (BMP, JPG, GIF, or any other format supported by GDI+), but when you open a new image, the view gradually blends the new image with the old, as the sequence in Figure 2 shows.

Figure 2 Original Image

Figure 2** Original Image **

Figure 2 Blended Image

Figure 2** Blended Image **

Figure 2 Final Image

Figure 2** Final Image **

To blend one image with another, you need both images. Duh. But when the user opens a new document, the first thing MFC does is destroy the old one. So you need to make some provision to save the old image somewhere before MFC loads the new one. Since the blending effect is conceptually part of the view (it falls under the category of painting), I want the view to handle this. That is, I want the view to save the old image. But how does the view know when to save it? You have to tell it, of course. Fortunately, CDocument has a method you can use to notify your view(s) whenever something important happens.It's CDocument::UpdateAllViews. Here's the code:

// in Doc.cpp: BOOL CPictureDoc::OnOpenDocument(LPCTSTR lpszPathName) { UpdateAllViews(NULL, PREOPENDOC, this); return m_pict.Load(lpszPathName); }

PREOPENDOC is my own enum code defined in doc.h. When you call UpdateAllViews, you pass your own "hint code" (a 32-bit integer) along with a pointer to a "hint object," which can be any CObject-derived MFC class. In this case I pass the document itself. Note that I call UpdateAllViews just before loading the new image, while the old image is still valid. The view handles the notification by saving the image:

void CPictureView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint==CPictureDoc::PREOPENDOC) { SaveDocImage((CPictureDoc*)pHint); } }

The same OnUpdate function handles all document notifications, so you have to check which notification code was sent. In general, the idea behind the hint code and hint object is that the document provides information in the form of a hint that tells the view what part of the screen it needs to update. For CPictureView, if the hint code is PREOPENDOC, CPictureView calls a helper function SaveDocImage to save the current image. Figure 3 shows the code for SaveDocImage. It creates a bitmap and memory device context (DC) then renders the image into the memory device context, effectively copying it for blending after the document destroys the original image.

Figure 3 SaveDocImage

#include "StdAfx.h" #include "View.h" #include "resource.h" // total duration to perform alpha blend. const BLEND_DURATION = 3 * CLOCKS_PER_SEC; // 3 seconds //////////////////////////////////////////////////////////////// // CPictureView // IMPLEMENT_DYNCREATE(CPictureView, CScrollView) BEGIN_MESSAGE_MAP(CPictureView, CScrollView) ... ON_COMMAND(ID_VIEW_ROTATE, OnViewRotate) END_MESSAGE_MAP() CPictureView::CPictureView() { m_iHowScale = ID_VIEW_TOFIT; // default scale mode: to fit m_iStartTime = 0; // no blending m_szBlend = CSize(0,0); } CPictureView::~CPictureView() { DeleteTempBitmap(); } ////////////////// // OnInitialUpdate - called by Framework when view is first created or // the user opens a new document. This is the place to perform one-time // per document initialization. // void CPictureView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); // default calls OnUpdate SetScrollSizes(); // set scroll sizes from image } ////////////////// // OnUpdate - called by the Framework when you invoke // CDocument::UpdateAllViews. UpdateAllViews can pass a hint code and hint // object. CPictureDoc sends hint code CPictureDoc::PREOPENDOC just before // it's about to open a document. This gives the view a chance to save the // image (for blending) before the image file is closed. // void CPictureView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint==CPictureDoc::PREOPENDOC) { SaveDocImage((CPictureDoc*)pHint); // save document image m_iStartTime = clock(); // note start time } } ////////////////// // Helper to save the current document image. Called just before // opening new document. Create memory bitmap and render old // image into it. // void CPictureView::SaveDocImage(CPictureDoc* pDoc) { ASSERT(pDoc); CPicture* ppic = pDoc->GetPicture(); ASSERT(ppic); DeleteDocImage(); // delete old doc image CRect rc; GetImageRect(rc); CSize sz = rc.Size(); CClientDC dc(this); // my client DC m_dcOld.CreateCompatibleDC(&dc); // create memory DC m_bmOld.CreateCompatibleBitmap(&dc, sz.cx, sz.cy); // create bitmap m_dcOld.SelectObject(&m_bmOld); // select it into DC ppic->Render(&m_dcOld,rc); // draw picture on bitmap m_szOld = sz; // and save size } ////////////////// // Delete (old) document image. // void CPictureView::DeleteDocImage() { if (HaveDocImage()) { m_dcOld.SelectObject((CBitmap*)NULL); // de-select bitmap from DC m_bmOld.DeleteObject(); // delete bitmap m_dcOld.DeleteDC(); // delete DC } } ////////////////// // Draw the picture -- call CPicture to do it. // void CPictureView::OnDraw(CDC* pDC) { CPictureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CPicture* ppic = pDoc->GetPicture(); ASSERT(ppic); CRect rc; GetImageRect(rc); if (m_iStartTime) { CSize sz = rc.Size(); // if window size changed, create new blend bitmap. if (m_szBlend != sz) CreateTempBitmap(pDC, sz); ASSERT(m_dcBlend.m_hDC); // render current (NEW) image into temporary dc. ppic->Render(&m_dcBlend,rc); // Compute blend amount: goes from 255 to zero (backwards) because I // am starting with the new image (instead of old). Blend amount is // whatever fraction of time remains to do the blend. BLENDFUNCTION bf; memset(&bf,0,sizeof(bf)); bf.BlendOp = AC_SRC_OVER; int alpha = ((clock() - m_iStartTime) * 255) / BLEND_DURATION; alpha = max(255-alpha,0); // alpha = 1-alpha (backwards) bf.SourceConstantAlpha = alpha; // blend old image on top of new, in temporary DC m_dcBlend.AlphaBlend(0,0,sz.cx,sz.cy, &m_dcOld, 0,0,m_szOld.cx,m_szOld.cy, bf); // copy scratch bits to screen pDC->BitBlt(0, 0, sz.cx, sz.cy, &m_dcBlend, 0, 0, SRCCOPY); if (alpha>0) { // not done yet? Invalidate(FALSE); // do it again! } else { // otherwise: StopBlending(); // stop } } else if (*ppic) { ppic->Render(pDC,rc); // done blending: render as normal } } ////////////////// // Create temporary bitmap and memory DC used for bliting. // Recreated when the window size changes. // void CPictureView::CreateTempBitmap(CDC* pDC, CSize sz) { DeleteTempBitmap(); m_dcBlend.CreateCompatibleDC(pDC); m_bmBlend.CreateCompatibleBitmap(pDC, sz.cx, sz.cy); m_dcBlend.SelectObject(&m_bmBlend); m_szBlend = sz; } ///////////////// // Delete temporary bitmap and memory DC. // void CPictureView::DeleteTempBitmap() { if (m_dcBlend.m_hDC) { m_dcBlend.SelectObject((HBITMAP)NULL); m_dcBlend.DeleteDC(); m_bmBlend.DeleteObject(); } } ////////////////// // Stop blending: delete old doc image and reset start time. // void CPictureView::StopBlending() { DeleteDocImage(); // delete old doc image m_iStartTime = 0; // don't blend. }

Now, when the user opens a new document, the document notifies the view, and the OnUpdate handler saves the image in a bitmap. How to do the blending? This requires calling AlphaBlend repeatedly to blend the new image with the old. The most obvious way to do it is to set a timer. Say you want the blend to take three seconds. To perform the blend in 100 steps, you'd set a timer for 3000/100 = 30 ms. The problem is that it takes a relatively significant amount of time to actually do the AlphaBlend. Moreover, the amount of time it takes depends on the size of the image. Larger images take longer to blend. If you use the timer approach, you'll end up with a slide show that blends smaller images more quickly and larger images more slowly—probably not what you want.

The way to keep the blend-time constant is to fix the total duration—say, 3,000 ms—and then calculate the alpha value based on how much time has actually elapsed. For example, suppose the first iteration happens at time t+20 ms. Then you'd use an alpha value of 20*255/3000 = 1 (rounded down to the nearest integer). Then you immediately do another blend using whatever alpha value you calculate based on what time it is now. If half the time has elapsed, you'll end up with an alpha of .5 (128 as a byte). By calculating the alpha value from the actual elapsed time, you guarantee that the blend always finishes on time. The drawback is that larger images won't blend as smoothly because they'll end up with fewer iterations, as more time is consumed in AlphaBlend. Got it?

Implementing all this is more or less straightforward. Figure 3 and Figure 4 show the details. When CPictureView::OnUpdate gets the PREOPENDOC notification, after saving the old image, it sets a data member m_iStartTime to the current clock time. The clock time is the number of "clock ticks" since the process started. There are CLOCKS_PER_SEC (usually 1,000) ticks per second. When OnUpdate returns, control passes back to the document and MFC, which calls your view's OnInitialUpdate function, which calls OnUpdate, which invalidates the window. Eventually, Windows sends your view a WM_PAINT message, which MFC handles by calling the view's virtual OnDraw method. This is MFC 101: in a view, you do the painting in OnDraw, not OnPaint. CPictureView handles painting like so:

void CPictureView::OnDraw(CDC* pDC) { CPicture* ppic = // get current picture if (m_iStartTime) { // do blend } else { // render as normal ppic->Render(pDC,rc); } }

Figure 4 CPictureView

#include "Doc.h" ///////////////// // Picture view is a typical scroll view. // Modified to implement alpha blend when user opens a new image. // class CPictureView : public CScrollView { protected: // stuff used for blending: CBitmap m_bmOld; // old bitmap.. CDC m_dcOld; // ..device context CSize m_szOld; // ..and image size int m_iStartTime; // clock time for start of blending // scratch DC in which to blend CDC m_dcBlend; // device context CBitmap m_bmBlend; // bitmap CSize m_szBlend; // size ... // helpers BOOL HaveDocImage() { return m_dcOld.m_hDC!=NULL; } void SaveDocImage(CPictureDoc* pDoc); void DeleteDocImage(); void CreateTempBitmap(CDC* pDC, CSize sz); void DeleteTempBitmap(); void StopBlending(); virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint); virtual void OnDraw(CDC* pDC); virtual void OnInitialUpdate(); // command/message handlers afx_msg LRESULT OnDoBlend(WPARAM wp, LPARAM lp); ... };

I've omitted the details of blending to highlight how CPictureView uses m_iStartTime as a flag to determine whether or not to blend. Here are the basic steps required to perform a blend.

  1. Create a memory DC.
  2. Draw bitmap A into the memory DC.
  3. Calculate alpha value for blending.
  4. AlphaBlend bitmap B into the memory DC.
  5. Copy the result to the screen (BitBlt).

It's important to do the blending off-screen in a memory DC and then copy to the screen; otherwise the user will see the intermediate images flash by. Since AlphaBlend requires a device context, not a CPicture object, it's more convenient to draw the new image first (so I can call CPicture::Render) and then blend the old one on top of it. So the alpha value I use is the inverse (1-alpha) of the alpha value I'd use if I were starting with the old image. In other words, instead of starting with the old image and blending more and more of the new one on top, I start with the new image and blend less and less of the old one on top. Pretty clever, eh? The net effect is the same. Here are the critical lines that calculate the alpha value for AlphaBlend:

int alpha = ((clock() - m_iStartTime) * 255) / BLEND_DURATION; alpha = max(255-alpha,0);

If, after blending, the calculated alpha value is greater than zero, more blending needs to be done. So OnDraw calls Invalidate(FALSE) to invalidate the window without erasing the background. Windows sends another WM_PAINT message—but not until the current message finishes processing. By doing it this way (in effect posting the WM_PAINT), there's no blocking. The user can still use the app during blending. You can verify this by resizing the window during blending. CPictureView keeps on blending, using the new window size.

If the calculated alpha value is zero, blending is complete. Time to stop. In that case, OnDraw calls a helper function, StopBlending, which deletes the old image and sets m_iStartTime to zero as a cue for OnDraw to stop blending. Now when the view needs painting, OnDraw paints the normal way, by calling CPicture::Render to render the new image directly, without blending.

If you're using the Active Template Library (ATL) CImage class to hold your image (instead of CPicture, which I implemented long ago before CImage was available), you can use CImage::AlphaBlend, which comes in a few overloaded flavors, to do the blending.

If you're using the Microsoft® .NET Framework, you can do alpha blending with one of the Graphics.DrawImage overloads that takes an ImageAttributes structure. One of the methods in ImageAttributes is SetColorMatrix. The color matrix is a 5×5 matrix that defines the color mapping for red, green, blue, and alpha plus a fifth w channel that must be 1 on the diagonal and zero elsewhere (for math geeks, the fifth channel is used to perform nonlinear transformations). To perform a 50 percent blend, you'd use the identity matrix (all values 0, except 1's on the diagonal) and then set the alpha value (ColorMatrix.Matrix33) to .5f and use this to draw.

Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site.