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


Comments

  • Transferring bitmaps between PCs

    Posted by andreatrevisan on 03/26/2004 10:54am

    Your 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

    Reply
  • Why not AlphaBlend,

    Posted by Legacy on 06/27/2003 12:00am

    Originally posted by: Rio

    which is simple

    Reply
  • This is wrong: SplashScreen Misconcept

    Posted by Legacy on 06/09/2003 12:00am

    Originally 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


    • Ummm...

      Posted by dogbear on 03/31/2006 05:22am

      Whatever.

      Reply
    Reply
  • Great example, with a minor bug

    Posted by Legacy on 06/08/2003 12:00am

    Originally 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.

    Reply
  • I successfully compiled the project finally :-)

    Posted by Legacy on 06/07/2003 12:00am

    Originally posted by: dacid

    I am using vc6 which can't open the .vcproj file;
    but 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.
    ;)

    Reply
  • Appears demo project is incomplete

    Posted by Legacy on 06/06/2003 12:00am

    Originally 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
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds