A Splash Screen with Alpha Blending
Environment: compiled with .NET (should work with VC6), NT family OS (NT, 2000, XP)
Have you ever thought about creating a splash screen that melts with the background using an Alpha channel together with the splash bitmap? This three-class implementation could help you do this.
The concept at the base of the class is that the Splash screen is called in the CWinApp::InitIstance function before any initialization. What the class does is to create a POP_UP window without any border, with a NULL background brush. The WM_PAINT routine of the class effectively paints the splash screen. To melt the splash with the desktop, a capture of the desktop window is performed on startup, the splash is composed on the desktop using the Alpha channel, and the whole thing is painted by the Window.
Another important thing to notice is that the Window class is created inside a different thread created by CSplashClass. The purpose of this implementation is to catch WM_PAINT messages unavailable in the CWinApp initialization. During the InitInstance phase, the message loop has not been started yet.
This is the list of the used classes and their use:
| Class | Description |
| CSplashClass | A CObject-derived class that starts the new interface thread |
| CSplashThread | The class derived from CWinThread that holds the new thread |
| CSplashWnd | The window that performs the splash (derived from CWnd) |
We skip the detail of creating a new interface thread (you can learn this from the MFC documentation). This is basically what the CSplashWnd interface does:
- Loads the two resources bitmaps, one for the Splash screen and one for the Alpha Channel (the two images MUST BE 24 bits).
- Calculates the size of the desktop, the size of the Splash image, and the area that effectively needs to be captured to melt the splash screen.
- Loads the splash image bits into a BYTE array using the GetDIBits API. The byte array for the splash image is a class member. This is because successive calls to the WM_PAINT will read the byte array directly, without reconverting it for each paint. This is not done for the alpha channel and the screen capture because the image is melted with the background only the first time it is painted. Successive calls to WM_PAINT will paint the splash image without composing it with the background.
- The OnPaint function will call the compositing routine PerformSplash, the first time using transparency, the second time not.
- PerformSplash() gets a reference to the desktop DC using GetWindowDC(::GetDesktopWindow()).
- A compatible DC and a compatible bitmap are created to store the capture of the desktop.
- Using the Bitblt function, the content of the desktop DC is copied on the compatible DC.
- As done for the splash screen, we create two byte arrays, one for the captured desktop and one for the Alpha channel. Using the GetDIBits function, we get the content of the two device context inside those two arrays.
- After that, a for loop is performed for each BYTE of the three arrays. In each loop, the three colors (R,G,B) of each image are calculated and composited. Pay attention that in each loop, we get only the RED component of the Alpha channel to perform the masking (Alpha channel is a grey-scale image, so Red = Green = Blue).
- Each color component is composed using the Alpha channel formula: NewPixel = ((SplashPixelRed * dValue) + BackgroundPixelRed * (255 - dValue) ) / 255;.
- After the conversion, the new background byte array is copied back on the compatible DC using the SetDIBits function.
- The compatible DC is compied on the Window DC using the Bitblt function.
This is the source code of the CSplashWnd class:
// SplashWnd.cpp : implementation file // #include "stdafx.h" #include "Capturer.h" #include "SplashWnd.h" #define SPLASHRC IDB_SPLASH #define ALPHARC IDB_ALPHA // CSplashWnd IMPLEMENT_DYNAMIC(CSplashWnd, CWnd) CSplashWnd::CSplashWnd() { InitClass(); bFirstPaint = TRUE; strWndCls = AfxRegisterWndClass(CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW), NULL, NULL); CreateEx(0, strWndCls, "SPLASHWND", WS_POPUP, Xoffset, Yoffset, dSplashSizeX, dSplashSizeY, NULL, NULL); } CSplashWnd::~CSplashWnd() { ::InvalidateRect(::GetDesktopWindow(), CRect(Xoffset,Yoffset, Xoffset + dSplashSizeX, Yoffset + dSplashSizeY) , FALSE); ::GlobalFree(lpvBits); } BEGIN_MESSAGE_MAP(CSplashWnd, CWnd) ON_WM_PAINT() END_MESSAGE_MAP() // CSplashWnd message handlers void CSplashWnd::PerformSplash(bool bTransparency) { HWND hDesktop = ::GetDesktopWindow(); HDC hdcScreen = ::GetWindowDC(hDesktop); // Get the desktop DC and create a compatible DC to work on HDC hdcCompatible = CreateCompatibleDC(hdcScreen); // Create a compatible bitmap for hdcScreen; the size is taken // from the splash bitmap HBITMAP hbmScreen = CreateCompatibleBitmap(hdcScreen, DesktopSizeX, DesktopSizeY); // Select the bitmaps into the compatible DC HBITMAP hOldScreenBitmap = (HBITMAP)SelectObject(hdcCompatible, hbmScreen); // Capture the screen, only if we are drawing in transparency if (bTransparency) { BitBlt(hdcCompatible, 0,0, dSplashSizeX, dSplashSizeY, hdcScreen, Xoffset,Yoffset, SRCCOPY); } BITMAPINFOHEADER bi; ::ZeroMemory(&bi, sizeof(BITMAPINFOHEADER)); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = SplashInfo.bmWidth; bi.biHeight = SplashInfo.bmHeight; bi.biPlanes = 1; bi.biBitCount = 24; bi.biCompression = BI_RGB; // Get the BYTE arrays of the three images if (bTransparency) { int dResult = GetDIBits ( hdcCompatible, AlphaBitmap, 0, AlphaInfo.bmHeight, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS); LPVOID lpvBitsAlpha = ::GlobalAlloc(GMEM_FIXED,bi.biSizeImage); dResult = GetDIBits ( hdcCompatible, AlphaBitmap, 0, AlphaInfo.bmHeight, lpvBitsAlpha, (BITMAPINFO*)&bi, DIB_RGB_COLORS); dResult = GetDIBits ( hdcCompatible, hbmScreen, 0, SplashInfo.bmHeight, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS); LPVOID lpvBitsScreen = ::GlobalAlloc(GMEM_FIXED,bi.biSizeImage); dResult = GetDIBits ( hdcCompatible, hbmScreen, 0, SplashInfo.bmHeight, lpvBitsScreen, (BITMAPINFO*)&bi, DIB_RGB_COLORS); BYTE* pAlpha = (BYTE*)lpvBitsAlpha; BYTE* pSplash = (BYTE*)lpvBits; BYTE* pScreen = (BYTE*)lpvBitsScreen; BYTE* pResult = (BYTE*)lpvBitsScreen; // Mask the Splash on the screen capture using the // alpha channel long dBytesNum = SplashInfo.bmHeight * SplashInfo.bmWidth; for ( long nx = 0; nx < dBytesNum; nx ++) { BYTE dValue = *pAlpha ; pAlpha += (sizeof(BYTE) * 3); BYTE PixelRed = BYTE(*pSplash++); BYTE PixelGreen = BYTE(*pSplash++); BYTE PixelBlue = BYTE(*pSplash++); BYTE SPixelRed = BYTE(*pScreen++ ); BYTE SPixelGreen = BYTE(*pScreen++); BYTE SPixelBlue = BYTE(*pScreen++); *pResult++ = ((PixelRed * dValue) + SPixelRed * (255 - dValue) ) / 255; *pResult++ = ((PixelGreen * dValue) + SPixelGreen * (255 - dValue) ) / 255; *pResult++ = ((PixelBlue * dValue) + SPixelBlue * (255 - dValue) ) / 255; } // Copy the Screen Capture buffer back to the Screen bitmap SetDIBits ( hdcCompatible, hbmScreen, 0, SplashInfo.bmHeight, lpvBitsScreen, (BITMAPINFO*)&bi, DIB_RGB_COLORS); ::GlobalFree(lpvBitsAlpha); ::GlobalFree(lpvBitsScreen); } // If we don't have the transparency flag, copy the // entire splash else SetDIBits ( hdcCompatible, hbmScreen, 0, SplashInfo.bmHeight, lpvBits, (BITMAPINFO*)&bi, DIB_RGB_COLORS); // Copy the image on the Compatible HDC to the Window DC CDC* pWinDC = GetDC(); BitBlt(pWinDC->m_hDC,0,0,dSplashSizeX, dSplashSizeY, hdcCompatible, 0 ,0, SRCCOPY); ReleaseDC(pWinDC); SelectObject(hdcCompatible, hOldScreenBitmap); // Delete used DC DeleteObject(hbmScreen); DeleteDC(hdcCompatible); // Release the desktop DC ::ReleaseDC(hDesktop,hdcScreen); } void CSplashWnd::InitClass(void) { // Load the base bitmap to display as splash HWND hDesktop = ::GetDesktopWindow(); HDC hdcScreen = ::GetWindowDC(hDesktop); SplashBitmap.LoadBitmap(SPLASHRC); AlphaBitmap.LoadBitmap(ALPHARC); SplashBitmap.GetBitmap(&SplashInfo); AlphaBitmap.GetBitmap(&AlphaInfo); dSplashSizeX = SplashInfo.bmWidth; dSplashSizeY = SplashInfo.bmHeight; DesktopSizeX = GetDeviceCaps(hdcScreen,HORZRES); DesktopSizeY = GetDeviceCaps(hdcScreen,VERTRES); ::ReleaseDC(hDesktop,hdcScreen); // Calculate the upper-left corner position Xoffset = (DesktopSizeX - dSplashSizeX) / 2; Yoffset = (DesktopSizeY - dSplashSizeY) / 2; BITMAPINFOHEADER bi; ::ZeroMemory(&bi, sizeof(BITMAPINFOHEADER)); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = SplashInfo.bmWidth; bi.biHeight = SplashInfo.bmHeight; bi.biPlanes = 1; bi.biBitCount = 24; bi.biCompression = BI_RGB; int dResult = GetDIBits ( ::GetWindowDC(::GetDesktopWindow()), SplashBitmap, 0, SplashInfo.bmHeight, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS); lpvBits = ::GlobalAlloc(GMEM_FIXED,bi.biSizeImage); dResult = GetDIBits ( ::GetWindowDC(::GetDesktopWindow()), SplashBitmap, 0, SplashInfo.bmHeight, lpvBits, (BITMAPINFO*)&bi, DIB_RGB_COLORS); } void CSplashWnd::OnPaint() { BeginPaint(NULL); if (bFirstPaint) { PerformSplash(TRUE); bFirstPaint = FALSE; } else PerformSplash(FALSE); EndPaint(NULL); }
Downloads
Download demo project - 323 KbDownload source - 4 Kb

Comments
Transferring bitmaps between PCs
Posted by andreatrevisan on 03/26/2004 10:54amYour program is a very good and neat starting point about treatment of BYTE array obtained from bitmaps.I didn't know how to manage GetDIBits,SetDIBits,DDB and DIB.I only guessed I needed them.I started with GetDIBits,SetDIBits in your program and now I can transfer bitmaps from different devices. Thanks Mr. Trevisan Andrea
ReplyWhy not AlphaBlend,
Posted by Legacy on 06/27/2003 12:00amOriginally posted by: Rio
which is simple
Reply
This is wrong: SplashScreen Misconcept
Posted by Legacy on 06/09/2003 12:00amOriginally posted by: Vitaly
1. Splash screens are usually recommended for heavy applications whose start-up time is far from instantaneous.
2. It is recommended to reduce the use of layered windows in complex applications to the minimum, as layered windows consume too much CPU time to provide transparency.
What you are suggesting here is to load the CPU with displaying your layered splash screen whilst in fact it is the application itself who is critical to the system speed, and this is why we need a splash screen in the first place.
Totally, if you are using a splash screen because you want your customers to see something beautiful and descriptive while your application is loading don't use layered windows, as they will much contribute in the delay of application's load time, and may produce glitches of incorrect background drawing because heavy applications take much CPU time without letting any time for layered windows to update their background.
Regards,
Vitaly
P.S. Professional tooltips on http://www.Tooltips.NET
-
ReplyUmmm...
Posted by dogbear on 03/31/2006 05:22amWhatever.
ReplyGreat example, with a minor bug
Posted by Legacy on 06/08/2003 12:00amOriginally posted by: Leo
It is a great example, but there is just one problem. The effect will be destroyed if you change the background, such as minimize the window behind it. Maybe the redawing is not right.
Thanks for such a good example.
ReplyI successfully compiled the project finally :-)
Posted by Legacy on 06/07/2003 12:00amOriginally posted by: dacid
I am using vc6 which can't open the .vcproj file;
Replybut I guess from the file name and think it is a Dialog based app named Capturer;
haha!
so I created a app, then overwrite my files by the demo files, finally I compiled it by vc6 and it works very well!
Thank for your code.
But, please include your .dsw file next time you show your intellectual work.
;)
Appears demo project is incomplete
Posted by Legacy on 06/06/2003 12:00amOriginally posted by: Paul
Where is BlendSplash.h (referenced by CaptureDlg.h)?
Where is IDD_CAPTURE_DIALOG (CaptureDlg.cpp)?
... and so on....
They may not be necessary if CaptureDlg is not needed but then why did you include it? I downloaded the project and I am porting it back to VC6.
Reply