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 Kb
Download source - 4 Kb