Display images as you type in C++

There was a question on Stackoverflow about a C++ project displaying an image as you type. Here is the original question:

I am writing a very simple program in C++ that listens to keyboard input, but what I want to output is much more difficult than I expected. For every key I press, I want an image (specific to the key) to appear on the screen. For example, let’s say if I press the “O” key, an image of Earth appears on my screen.

It sounded like a fun exercise to do in C++ using Windows API. So here is my quick answer, a simple solution to the problem.

I wanted to display three different images, an earth, a moon and a sun, when the user types E, M or S in the window. The images should use transparency, so I decided to use PNG files. Here is how the images look.


A good option for loading PNG files is using Windows Imaging Component. The point would be to add the PNG files to the project resources, then use WIC to load them and create an HBITMAP that would be used later to display the image. Bradley Grainger had just the right code on his blog. This is how it looks, except that I made some slight modifications to LoadImageFromResources (called LoadSplashImage on his blog).

IStream* CreateStreamOnResource(LPCTSTR lpName, LPCTSTR lpType)
{
   IStream * ipStream = NULL;

   HRSRC hrsrc = FindResource(NULL, lpName, lpType);
   if (hrsrc == NULL)
      goto Return;

   DWORD dwResourceSize = SizeofResource(NULL, hrsrc);
   HGLOBAL hglbImage = LoadResource(NULL, hrsrc);
   if (hglbImage == NULL)
      goto Return;

   LPVOID pvSourceResourceData = LockResource(hglbImage);
   if (pvSourceResourceData == NULL)
      goto Return;

   HGLOBAL hgblResourceData = GlobalAlloc(GMEM_MOVEABLE, dwResourceSize);
   if (hgblResourceData == NULL)
      goto Return;

   LPVOID pvResourceData = GlobalLock(hgblResourceData);

   if (pvResourceData == NULL)
      goto FreeData;

   CopyMemory(pvResourceData, pvSourceResourceData, dwResourceSize);

   GlobalUnlock(hgblResourceData);

   if (SUCCEEDED(CreateStreamOnHGlobal(hgblResourceData, TRUE, &ipStream)))
      goto Return;

FreeData:
   GlobalFree(hgblResourceData);

Return:
   return ipStream;
}

IWICBitmapSource* LoadBitmapFromStream(IStream * ipImageStream)
{
   IWICBitmapSource* ipBitmap = NULL;

   IWICBitmapDecoder* ipDecoder = NULL;
   if (FAILED(CoCreateInstance(CLSID_WICPngDecoder, NULL, CLSCTX_INPROC_SERVER, 
                               __uuidof(ipDecoder), reinterpret_cast<void**>(&ipDecoder))))
      goto Return;

   if (FAILED(ipDecoder->Initialize(ipImageStream, WICDecodeMetadataCacheOnLoad)))
      goto ReleaseDecoder;

   UINT nFrameCount = 0;
   if (FAILED(ipDecoder->GetFrameCount(&nFrameCount)) || nFrameCount != 1)
      goto ReleaseDecoder;

   IWICBitmapFrameDecode* ipFrame = NULL;
   if (FAILED(ipDecoder->GetFrame(0, &ipFrame)))
      goto ReleaseDecoder;

   WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, ipFrame, &ipBitmap);
   ipFrame->Release();

ReleaseDecoder:
   ipDecoder->Release();
Return:
   return ipBitmap;
}

HBITMAP CreateHBITMAP(IWICBitmapSource* ipBitmap)
{
   HBITMAP hbmp = NULL;

   UINT width = 0;
   UINT height = 0;
   if (FAILED(ipBitmap->GetSize(&width, &height)) || width == 0 || height == 0)
      goto Return;

   BITMAPINFO bminfo;

   ZeroMemory(&bminfo, sizeof(bminfo));
   bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   bminfo.bmiHeader.biWidth = width;
   bminfo.bmiHeader.biHeight = -((LONG) height);
   bminfo.bmiHeader.biPlanes = 1;
   bminfo.bmiHeader.biBitCount = 32;
   bminfo.bmiHeader.biCompression = BI_RGB;

   void * pvImageBits = NULL;
   HDC hdcScreen = GetDC(NULL);
   hbmp = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0);
   ReleaseDC(NULL, hdcScreen);
   if (hbmp == NULL)
      goto Return;

   const UINT cbStride = width * 4;
   const UINT cbImage = cbStride * height;
   if (FAILED(ipBitmap->CopyPixels(NULL, cbStride, cbImage, static_cast<BYTE*>(pvImageBits))))
   {
      DeleteObject(hbmp);
      hbmp = NULL;
   }

Return:
   return hbmp;
}

HBITMAP LoadImageFromResources(LPCTSTR lpName, LPCTSTR lpType)
{
   HBITMAP hbmpSplash = NULL;

   IStream* ipImageStream = CreateStreamOnResource(lpName, lpType);
   if (ipImageStream == NULL)
      goto Return;

   IWICBitmapSource* ipBitmap = LoadBitmapFromStream(ipImageStream);
   if (ipBitmap == NULL)
      goto ReleaseStream;

   hbmpSplash = CreateHBITMAP(ipBitmap);
   ipBitmap->Release();

ReleaseStream:
   ipImageStream->Release();

Return:
   return hbmpSplash;
}

However for this to work the following are necessary:

#include <Objidl.h>
#include <Wincodec.h>

#pragma comment (lib, "windowscodecs.lib")

Objidl.h is required for IStream, while Wincodec.h and windowscodecs.lib for WIC.

I have created a Win32 Project, called TypePictures. The wizard generates the backbone for the app, but I’ll not talk about that part. Besides adding the functions listed above, I also added the following:

HWND g_hPictureWnd = NULL;
HBITMAP g_hbmp;
UINT_PTR g_timer = 0;
const TCHAR c_szPictureClass[] = _T("myPictureWindow");

ATOM RegisterPictureWindowClass(HINSTANCE hInstance);
BOOL CreatePictureWindow(HINSTANCE hInstance, HWND hOwner);
BOOL DestroyPictureWindow();
BOOL SetPicture(HBITMAP hbmp);
VOID CALLBACK PictureTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

hPictureWnd is the handle to the window that displays the image, g_timer is a timer used for automatically closing the window after a desire interval, and g_hbmp is the handle to the current loaded image.

RegisterPictureWindowClass registers the window class for the picture window. It uses the c_szPictureClass defined earlier for the class name.

ATOM RegisterPictureWindowClass(HINSTANCE hInstance)
{
   WNDCLASSEX wcex = {0};

   wcex.cbSize = sizeof(WNDCLASSEX);

   wcex.lpfnWndProc	= DefWindowProc;
   wcex.cbClsExtra	= 0;
   wcex.cbWndExtra	= 0;
   wcex.hInstance	= hInstance;
   wcex.hIcon		= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TYPEPICTURES));
   wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
   wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
   wcex.lpszClassName	= c_szPictureClass;
   wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

   return RegisterClassEx(&wcex);
}

This will be called in the main function before the call to InitInstance.

RegisterPictureWindowClass(hInstance);

CreatePictureWindow is a function that creates the picture window. This will be a layered window that we’ll later update with UpdateLayeredWindow when displayined the picture.

BOOL CreatePictureWindow(HINSTANCE hInstance, HWND hOwner)
{
   if(g_hPictureWnd != NULL)
      return FALSE;

   RECT rcwin;
   GetWindowRect(hOwner, &rcwin);

   g_hPictureWnd = ::CreateWindowEx(
      WS_EX_LAYERED,
      c_szPictureClass, 
      NULL, 
      WS_POPUP | WS_VISIBLE,
      rcwin.right+1, rcwin.top, 128, 128, 
      hOwner, 
      NULL, 
      hInstance, 
      NULL);

   return g_hPictureWnd != NULL;
}

DestroyPictureWindow does what the name implies, it destroys the layered window that displays the picture.

BOOL DestroyPictureWindow()
{
   if(g_hPictureWnd == NULL)
      return FALSE;

   BOOL ret = DestroyWindow(g_hPictureWnd);
   g_hPictureWnd = NULL;

   return ret;
}

SetPicture is a key function that displays a bitmap in the layered window. It takes the bitmap that was loaded from an PNG with LoadImageFromResources and displays it in the layered window. As mentioned earlier it calls UpdateLayeredWindow to update the window.

BOOL SetPicture(HBITMAP hbmp)
{
   if(g_hPictureWnd == NULL)
      return FALSE;

   BITMAP bm;
   GetObject(hbmp, sizeof(bm), &bm);
   SIZE sizebmp = {bm.bmWidth, bm.bmHeight};
   
   HDC hdcScreen = GetDC(g_hPictureWnd);
   HDC hdcMem = CreateCompatibleDC(hdcScreen);
   HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, hbmp);

   BLENDFUNCTION blend = { 0 };
   blend.BlendOp = AC_SRC_OVER;
   blend.SourceConstantAlpha = 255;
   blend.AlphaFormat = AC_SRC_ALPHA;

   RECT rcwin;
   GetWindowRect(g_hPictureWnd, &rcwin);

   POINT ptSrc = {0};
   POINT ptDest = {rcwin.left, rcwin.top};

   UpdateLayeredWindow(
      g_hPictureWnd, 
      hdcScreen, 
      &ptDest, 
      &sizebmp,
      hdcMem, 
      &ptSrc, 
      RGB(0, 0, 0), 
      &blend, 
      ULW_ALPHA);

   SelectObject(hdcMem, hbmpOld);
   DeleteDC(hdcMem);
   ReleaseDC(g_hPictureWnd, hdcScreen);

   return TRUE;
}

PictureTimerProc is the timer procedure we use to destroy the window after a specified interval since the last typed key elapsed.

VOID CALLBACK PictureTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
   if(idEvent == g_timer)
   {
      ::KillTimer(hwnd, idEvent);
      g_timer = 0;

      DestroyPictureWindow();
   }
}

Make sure you call CoInitialize() to initialize the COM library for the main thread before the message loop begins in function main. Also make sure to call CoUninitialize() before the program ends.

WndProc is the function where the messages sent to the main window are processed. We’ll handle WM_CHAR, and when E, M or S are pressed we load the appropriate image, create the layered window for the picture if not already created, and set the picture in the window. This is how the code looks:

   case WM_CHAR:
      {
         HBITMAP hbmp = NULL;
         switch((char)wParam)
         {
         case 'E': case 'e':
            hbmp = LoadImageFromResources(MAKEINTRESOURCE(IDB_PNG1), _T("PNG"));
            break;
         case 'M': case 'm':
            hbmp = LoadImageFromResources(MAKEINTRESOURCE(IDB_PNG2), _T("PNG"));
            break;
         case 'S': case 's':
            hbmp = LoadImageFromResources(MAKEINTRESOURCE(IDB_PNG3), _T("PNG"));
            break;
         default:
            break;
         }

         if(hbmp != NULL)
         {
            if(g_hbmp != NULL)
               DeleteObject(g_hbmp);
            g_hbmp = hbmp;

            if(g_hPictureWnd == NULL)
            {
               CreatePictureWindow(hInst, hWnd);
            }

            if(g_hPictureWnd != NULL)
            {
               if(SetPicture(hbmp))
               {
                  g_timer = SetTimer(hWnd, 1, 2000, PictureTimerProc);
                  SetFocus(hWnd);
               }
            }
         }
      }
      break;

And basically that is all. Just run the application and type E, M and S. You’ll see the images changing and 2 seconds after the last key is pressed the image is automatically hidden (as the layered window is destroyed).

You can download the source code from here: TypePictures (2942 downloads ) .

2 Replies to “Display images as you type in C++”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.