Click here to Skip to main content
15,895,784 members
Articles / Desktop Programming / Win32

Guide to Win32 Memory DC

Rate me:
Please Sign up or sign in to vote.
4.98/5 (82 votes)
21 Jul 2011CPOL15 min read 192.6K   11.6K   164  
Guide to creating and using Memory Device Contexts (DC) in Win32.
/* MemDcUsage.cpp *************************************************************
Author      Paul Watt
Date:       7/3/2011 11:52:48 AM
Purpose:    Demonstration code for the usage or Win32 Memory DCs.
            Create an animated gradient sequence that is 1/4 of the screen width
            and scroll across the screen in 5 seconds at ~30 fps.
            
Copyright 2011 Paul Watt
******************************************************************************/
/* Includes ******************************************************************/
#include <stdafx.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <list>

#include "MemDcUsage.h"

/* Linker Directives *********************************************************/
#pragma comment( lib, "Msimg32" )   // Lib required for Gradient Fill

namespace // anonymous
{

/* Declarations **************************************************************/
struct RectInfo
{
  double x;
  double y;
  double cx;
  double cy;
  HBRUSH hBrush;
};

typedef std::list<RectInfo> RectList;
typedef RectList::iterator  RectListIter;

/* Forward Declarations ******************************************************/
void CreateAnimationClipRgns(const RECT&, const RECT&, HRGN&, HRGN&);
void DrawWiper(HDC hDC, const RECT &rc, COLORREF baseColor, COLORREF hiColor);
void DrawShapes(HDC, HRGN, double, double, RectList&, bool isEllipse);

void UpdateFpsDisplay();
void GetRectangleGroup(const UINT , const UINT , const UINT , RectList&);
void GetRandomRectangle(const UINT width, const UINT height, RectInfo &randomRect);
HBITMAP GetWiperAlphaBlendMask(HDC hDC, const RECT &rc, COLORREF, COLORREF);

/* Constants *****************************************************************/
const double   k_percentWidth       = 0.33;
const double   k_aniToWindowRatio   = 1.0 / (1 + (2 * k_percentWidth));
const int      k_rectCount          = 10;

const COLORREF k_white                  = RGB(0xFF, 0xFF, 0xFF);
const COLORREF k_gray                   = RGB(0xED, 0xED, 0xED);
const COLORREF k_black                  = RGB(0x00, 0x00, 0x00);
const COLORREF k_codeProjectDarkOrange  = RGB(0xFF, 0x99, 0x00);
const COLORREF k_codeProjectLightOrange = RGB(0xFF, 0xE2, 0xA8);
const COLORREF k_codeProjectDarkGreen   = RGB(0x48, 0x8E, 0x00);
const COLORREF k_codeProjectLightGreen  = RGB(0x76, 0xAB, 0x40);

const char     k_instructions[]     = " -  Hold Left Mouse Button down on window to pause screen updates.\n"
                                      " -  Drag with Left Mouse Button to move the Wiper right and left.\n"
                                      " -  Right Click to toggle painting mode.  Wiper color indicates mode:\n"
                                      "        Green:  Paint indirectly using a double buffer\n"
                                      "        Orange: Paint directly to the display";

/* Global Variable ***********************************************************/
bool        g_useBackBuffer = true;   // Indicates if the back buffer is used or not.
HDC         g_hMemDc      = NULL;     // Memory DC for the back buffer.
HBITMAP     g_hBackBuffer = NULL;     // Back buffer bitmap.

int         g_cur         = 0;        // Current offset from the beginning of the
                                      // animation.

ULONGLONG   g_lastTime    = 0;        // Time marker for use in FPS calculations.
UINT        g_frameCount  = 0;        // Current frame count for this second.
UINT        g_desiredRate = 75;       // Track the desired frame-rate.
UINT        g_desiredLen  = 5;        // The desired length in seconds of the animation.
UINT        g_steps       = g_desiredRate * g_desiredLen; // count of steps in the sequence

double      g_currentFps  = 0.0;      // Cache the last calculated FPS until
                                      // the next rate is calculated.

RectList    g_group1;                 // list of random rectangles 
RectList    g_group2;                 // to make the display a bit more complicated.
bool        g_isGroup1Active = true;  // Status for which group paints as ellipses.

HPEN        g_hPenBlack      = NULL;
HBRUSH      g_hBrGray        = NULL;
HBRUSH      g_hBrLightOrange = NULL;
HBRUSH      g_hBrDarkOrange  = NULL;
HBRUSH      g_hBrLightGreen  = NULL;
HBRUSH      g_hBrDarkGreen   = NULL;

HBITMAP     g_hWiperMask     = NULL;

}

// Namespace to contain functions implemented for the demonstration.
namespace article
{

/******************************************************************************
Date:       7/4/2011
Purpose:    Initialize any data required for the demonstration.
Parameters: width[in]: Every generated rectangle will be smaller and fit 
              within the boundary width.  
            height[in]: Every generated rectangle will be smaller and fit 
              within the boundary height.  
*******************************************************************************/
void Init(const UINT width, const UINT height)
{
  ::srand(::GetTickCount());

  g_hPenBlack       = ::CreatePen(PS_SOLID, 3, k_black);
  g_hBrGray         = ::CreateSolidBrush(k_gray);
  g_hBrLightOrange  = ::CreateSolidBrush(k_codeProjectLightOrange);
  g_hBrDarkOrange   = ::CreateSolidBrush(k_codeProjectDarkOrange);
  g_hBrLightGreen   = ::CreateSolidBrush(k_codeProjectLightGreen);
  g_hBrDarkOrange   = ::CreateSolidBrush(k_codeProjectDarkGreen);

  GetRectangleGroup(width, height, k_rectCount, g_group1);
  GetRectangleGroup(width, height, k_rectCount, g_group2);
}

/******************************************************************************
Date:       7/4/2011
Purpose:    Free resources from the demo app.
*******************************************************************************/
void Term()
{
  // Free STL Resources
  g_group1.clear();
  g_group2.clear();

  // Free GDI Resources.
  ::DeleteObject(g_hPenBlack);
  ::DeleteObject(g_hBrGray);
  ::DeleteObject(g_hBrLightOrange);
  ::DeleteObject(g_hBrDarkOrange);
  ::DeleteObject(g_hBrLightGreen);
  ::DeleteObject(g_hBrDarkGreen);

  g_hPenBlack      = NULL;
  g_hBrGray        = NULL;
  g_hBrLightOrange = NULL;
  g_hBrDarkOrange  = NULL;
  g_hBrLightGreen  = NULL;
  g_hBrDarkGreen   = NULL;

  FlushBackBuffer();
}

/******************************************************************************
Date:       7/4/2011
Purpose:    Calculates and returns the delay in between update frames.
*******************************************************************************/
UINT  GetFrameRateDelay()
{
  return static_cast<UINT>(1000.0 / g_desiredRate);
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Sets a flag that enables back-buffer painting with the memory DC.
Parameters: isEnable[in]: true will enable back buffer painting
                          false will disable back buffer painting and paint
                            directly to the DC passed in by the user.
*******************************************************************************/
void EnableBackBuffer(bool isEnable)
{
  if (g_useBackBuffer != isEnable)
  {
    g_useBackBuffer = isEnable;
    FlushBackBuffer();
  }
  
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Reports if the back buffer is being used.
*******************************************************************************/
bool IsBackBufferEnabled()
{
  return g_useBackBuffer;
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Clears any state stored in the back buffer, 
            including releasing allocated resources.
*******************************************************************************/
void FlushBackBuffer()
{
  if (g_hMemDc)
  {
    ::DeleteDC(g_hMemDc);
    g_hMemDc = NULL;
  }

  if (g_hBackBuffer)
  {
    ::DeleteObject(g_hBackBuffer);
    g_hBackBuffer = NULL;
  }

  if (g_hWiperMask)
  {
    ::DeleteObject(g_hWiperMask);
    g_hWiperMask = NULL;
  }
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Insures the target windows back buffer is initialized, and returns
            the handle to the caller.
Parameters: hWnd[in]: Handle to the window which back buffer painting will be used.
Return:     Initialized back buffer memory DC.
            This is a cached resource.
            A call to Term or FlushBackBuffer will release this resource.
******************************************************************************/
HDC GetBackBuffer(HWND hWnd)
{
  // !!! Create and return a back-buffer for double-buffer painting.
  // If the memory DC is already initialized, simply return.
  if (g_hMemDc)
  {
    return g_hMemDc;
  }
  
  // Initialize the memory DC and a back buffer bitmap to write on.
  RECT client;
  ::GetClientRect(hWnd, &client);

  int width = client.right - client.left;
  int height= client.bottom - client.top;

  // Get a DC from the target paint location to be sure a compatible 
  // drawing context is created.
  HDC hDc  = ::GetDC(hWnd);

  // Create the Memory DC to be compatible with the target window.
  g_hMemDc = ::CreateCompatibleDC(hDc);

  // It is important the BITMAP be created to be compatible as well.
  // This will insure the correct bit-color depth and other parameters
  // match the target DC.
  g_hBackBuffer = ::CreateCompatibleBitmap(hDc, width, height);

  // Select the bitmap into the memory DC.
  // Otherwise there will be no buffer for the the operations to write into.
  ::SelectObject(g_hMemDc, g_hBackBuffer);

  // Release handles to target window 
  ::ReleaseDC(hWnd, hDc);

  return g_hMemDc;
}

/******************************************************************************
Date:       7/4/2011
Purpose:    Adjusts the offset of the current position of the highlight animation.
Parameters: hWnd[in]: The handle to the window with the animation.
            offset[in]: Offset in pixels.  Value can be positive or negative.
              Any values that are beyond the width of the window will be ignored.
Return:     The amount of offset that is accepted and applied to the animation.
*******************************************************************************/
int AdjustAnimation(HWND hWnd, int offset)
{
  // Determine the number of pixels in each animation step in window coordinates.
  RECT client;
  ::GetClientRect(hWnd, &client);

  int width       = client.right - client.left;
  int hiWidth     = static_cast<int>(width * k_percentWidth);
  int aniRange    = width + hiWidth;
  int stepWidth   = aniRange / g_steps;

  // Calculate the number of steps to offset.
  int offsetCount = offset / stepWidth;

  g_cur += offsetCount;

  // Force an update of the display.
  ::InvalidateRect(hWnd, NULL, FALSE);
 
  return offsetCount * stepWidth;
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Update the animation to the next frame.
*******************************************************************************/
void StepAnimation(HWND hWnd)
{
  // Advance to the next step, 
  // when the animation reaches the end, start over.
  g_cur = ((g_cur + 1) % g_steps);

  // If the Wiper is starting back on the left side of the screen, swap
  // rectangle groups, and regenerate the second group.
  if (0 == g_cur)
  {
    std::swap(g_group1, g_group2);
    g_group2.clear();

    RECT client;
    ::GetClientRect(hWnd, &client);
    UINT width = client.right - client.left;
    UINT height= client.bottom - client.top;

    GetRectangleGroup(width, height, k_rectCount, g_group2);

    g_isGroup1Active = !g_isGroup1Active;
  }

  // Force an update of the display.
  ::InvalidateRect(hWnd, NULL, FALSE);
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Primary Demonstration function for Memory DC usage.
Parameters: hWnd
            hDC
*******************************************************************************/
void PaintAnimation(HWND hWnd, HDC hDC)
{
  RECT client;
  ::GetClientRect(hWnd, &client);

  // The total width of the animation will be: 
  //    100% of the window width + (2*animation width)  -> 150%
  // Calculate an appropriate offset 
  // based on the current position of the animation.
  double width    = client.right - client.left;
  double height   = client.bottom - client.top;
  double hiWidth  = width * k_percentWidth;
  double aniWidth = width + (2 * hiWidth); 

  // The range of the offset for the wiper is (-aniWidth Width to 1.0 Width)
  //    -: 
  //    H: Highlight
  //    X: Offscreen
  // 
  //    Start, No Visible Highlight:      H---X
  //    Highlight Visible, Ani 33%:       XH--X
  //    Highlight Visible, Ani 66%:       X-H-X
  //    Highlight Visible, Ani 100%:      X--HX
  //    End, Highlight no longer Visible: X---H
  //
  double  offsetBase  = -hiWidth;
  double  offsetRange = aniWidth - hiWidth;
  double  stepWidth   = offsetRange / g_steps;
  int     offsetX     = static_cast<int>((stepWidth * g_cur) + offsetBase); 

  RECT rc =
  {
    offsetX,
    client.top,
    static_cast<LONG>(offsetX + hiWidth),
    client.bottom
  };

  HDC hBufferDC = hDC;

  COLORREF foreColor = k_codeProjectDarkOrange;
  COLORREF aftColor  = k_codeProjectDarkGreen;
  if (g_useBackBuffer)
  {
    hBufferDC = GetBackBuffer(hWnd);
    std::swap(foreColor, aftColor);
  }

  // Clear the canvas back to solid white.
  ::FillRect(hBufferDC, &client, (HBRUSH)::GetStockObject(WHITE_BRUSH));

  // Create two clipping regions divided by the wiper, 
  // and paint each set of shapes on one side of the wiper.
  HRGN hLeftRgn  = NULL;
  HRGN hRightRgn = NULL;
  CreateAnimationClipRgns(client, rc, hLeftRgn, hRightRgn);

  //!!! Save DC / Restore DC Sample
  int ctx = ::SaveDC(hDC);

  // Draw the set of rectangles.
  ::SelectObject(hBufferDC, g_hPenBlack);
  ::SelectObject(hBufferDC, ::GetStockObject(WHITE_BRUSH));

  // Draw Right Side:
  DrawShapes(hBufferDC, hRightRgn, width, height, g_group1, g_isGroup1Active);
  // Draw Left Side:
  DrawShapes(hBufferDC, hLeftRgn, width, height, g_group2, !g_isGroup1Active);

  ::RestoreDC(hDC, ctx);

  ::DeleteObject(hRightRgn);
  ::DeleteObject(hLeftRgn);

  // Draw the highlight gradient.
  DrawWiper(hBufferDC, rc, foreColor, aftColor);

  // Perform status output.
  UpdateFpsDisplay();

  // Set up to draw text with a transparent background.
  ::SetBkMode(hBufferDC, TRANSPARENT);

  // Display input instructions.
  ::DrawTextA(hBufferDC, k_instructions, -1, &client, DT_LEFT | DT_TOP);

  // Display the frame rate.
  std::stringstream fpsText;
  fpsText.flags(std::ios_base::fixed);
  fpsText.precision(3);
  fpsText << "FrameRate: " << g_currentFps << " FPS";
  ::DrawTextA(hBufferDC, fpsText.str().c_str(), -1, &client, DT_RIGHT | DT_BOTTOM | DT_SINGLELINE | DT_EXPANDTABS);

  if (g_useBackBuffer)
  {
    //!!! This is the point where the Back-Buffer is drawn to the window canvas.
    // Blt to the actual window now.
    // BitBlt: Bit Block Transfer
    ::BitBlt( hDC,          // Destination DC
              client.left,  // Starting X offset on destination to paint
              client.top,   // Starting Y offset on destination to paint
              (int)width,   // Width of image to paint onto destination
              (int)height,  // Height of image to paint onto destination
              hBufferDC,    // Source DC
              0,            // Starting X off on the source to paint from
              0,            // Starting Y off on the source to paint from
              SRCCOPY);     // Raster Operation (ROP) code, Simple Copy
  }
}

} // namespace article

/* Anonymous Namespace Implementation ****************************************/
namespace // anonymous
{

/******************************************************************************
Date:       7/4/2011
Purpose:    Calculate the Frame rate that is currently displaying.
*******************************************************************************/
void UpdateFpsDisplay()
{
  g_frameCount++;

  ULONGLONG now = ::GetTickCount64();  
  if (now > g_lastTime + 1000)
  {
    double diff   = static_cast<double>(now - g_lastTime) 
                  / 1000.0;

    g_currentFps  = static_cast<double>(g_frameCount)
                  / diff;

    g_frameCount = 0;
    g_lastTime   = now;
  }
}

/******************************************************************************
Date:       7/9/2011
Purpose:    Creates the pair of clipping regions required for the animation.
Parameters: client[in]: The bounding rectangle for the entire animation.
            rc[in]: The bounding rectangle for the wiper that divides the screen.
            left[out]: A reference to the region handle to accept the left side.
              The caller is responsible to free this resource.
            right[out]: A reference to the region handle to accept the right side.
              The caller is responsible to free this resource.
*******************************************************************************/
void CreateAnimationClipRgns(const RECT &client, 
                             const RECT &rc, 
                             HRGN &hLeftRgn, 
                             HRGN &hRightRgn)
{
  //!!! Create two regions that are mutually exclusive over one larger region
  hLeftRgn  = ::CreateRectRgnIndirect(&client);

  // The region will be divided from the lower-left of the wiper,
  // to the upper-right of the wiper rectangle area.
  int segment = (rc.right - rc.left) / 3;
  int left    = rc.left  + segment;
  int right   = rc.right - segment;
  POINT rightAreaPts[4];
  rightAreaPts[0].x = left;
  rightAreaPts[0].y = rc.bottom;
  rightAreaPts[1].x = right;
  rightAreaPts[1].y = rc.top;
  rightAreaPts[2].x = client.right;
  rightAreaPts[2].y = client.top;
  rightAreaPts[3].x = client.right;
  rightAreaPts[3].y = client.bottom;

  hRightRgn = ::CreatePolygonRgn(rightAreaPts, 4, ALTERNATE);
  ::CombineRgn(hLeftRgn, hLeftRgn, hRightRgn, RGN_DIFF);
}

/******************************************************************************
Date:       7/4/2011
Purpose:    Generate a randomly placed and sized rectangle within the specified
            boundaries.
Parameters: width[in]: Every generated rectangle will be smaller and fit 
              within the boundary width.  
            height[in]: Every generated rectangle will be smaller and fit 
              within the boundary height.  
            count[in]: The number of rectangles to generate.
            randomRect[out]: The random rectangle output.
*******************************************************************************/
void GetRectangleGroup(const UINT width, 
                       const UINT height, 
                       const UINT count, 
                       RectList &list)
{
  // Generate randomly placed and sized rectangles.
  for (UINT i = 0; i < count; ++i)
  {
    RectInfo randInfo;
    GetRandomRectangle(width, height, randInfo);

    list.push_back(randInfo);
  }
}

/******************************************************************************
Date:       7/4/2011
Purpose:    Generate a randomly placed and sized rectangle within the specified
            boundaries.
Parameters: width[in]: Every generated rectangle will be smaller and fit 
              within the boundary width.  
            height[in]: Every generated rectangle will be smaller and fit 
              within the boundary height.  
            randomRect[out]: The random rectangle output.
*******************************************************************************/
void GetRandomRectangle(const UINT width, const UINT height, RectInfo &randomRect)
{
  // Calculate the Width and Height of the rectangle.
  double rectWidth  = ::rand() % (width / 2 - 1);
  double rectHeight = ::rand() % (height/ 2 - 1);
  
  // Calculate the placement.
  double range  = width - rectWidth;
  double domain = height- rectHeight;

  randomRect.x   = (::rand() % (int)(width  - 1)) / (double)width;
  randomRect.y   = (::rand() % (int)(height - 1)) / (double)height;
  randomRect.cx  = rectWidth / width;
  randomRect.cy  = rectHeight/ height;

  // Select a random color for the rectangle.
  int colorIdx = ::rand() % 5;
  switch(colorIdx)
  {
  case 0:
    randomRect.hBrush = g_hBrGray;
    break;
  case 1:
    randomRect.hBrush = g_hBrLightOrange;
    break;
  case 2:
    randomRect.hBrush = g_hBrDarkOrange;
    break;
  case 3:
    randomRect.hBrush = g_hBrLightGreen;
    break;
  case 4:
    randomRect.hBrush = g_hBrDarkGreen;
    break;
  default:
    randomRect.hBrush = (HBRUSH)::GetStockObject(WHITE_BRUSH);
    break;
  }
}

/******************************************************************************
Date:       7/6/2011
Purpose:    Draw a group of rectangles on the specified DC and possible clip rgn.
Parameters: hDC[in]:  Device context to paint upon.
            hClipRgn[in]: Clipping region, can be NULL.
            width[in]: Width of the area to paint in.
            height[in]: Height of the area to paint in.
            list[in]: List of rectangle info structures that contains the 
              information to draw each of the rectangles.
            isEllipse[in]: Indicates if ellipses should be drawn.  
              Otherwise rectangles will be used.
*******************************************************************************/
void DrawShapes(HDC hDC, 
                    HRGN hClipRgn, 
                    double width, 
                    double height, 
                    RectList &list,
                    bool isEllipse)
{
  int ctx = ::SaveDC(hDC);

  //!!! Set the clipping region, and draw
  ::SelectClipRgn(hDC, hClipRgn);
  RectListIter cur = list.begin();
  RectListIter end = list.end();
  for (; cur != end; ++cur)
  {
    ::SelectObject(hDC, cur->hBrush);
    if (isEllipse)
    {
      ::Ellipse(hDC, 
        (int)(cur->x * width), 
        (int)(cur->y * height), 
        (int)(cur->cx* width), 
        (int)(cur->cy* height));
    }
    else
    {
      ::Rectangle(hDC, 
        (int)(cur->x * width), 
        (int)(cur->y * height), 
        (int)(cur->cx* width), 
        (int)(cur->cy* height));
    }
  }

  ::RestoreDC(hDC, ctx);
}

/******************************************************************************
Date:       7/3/2011
Purpose:    Draw a gradient filled highlight in a specified rectangular area
            with a base and highlight color.
Parameters: hDC[in]:  
            rc[in]: 
            baseColor[in]:  
            hiColor[in]:  
******************************************************************************/
void DrawWiper(HDC hDC, const RECT &rc, COLORREF baseColor, COLORREF hiColor)
{ 
  int bmpWidth = rc.right - rc.left;
  int bmpHeight= rc.bottom - rc.top;

  HDC     hDCMem     = ::CreateCompatibleDC(hDC);
  HBITMAP hBlendMask = GetWiperAlphaBlendMask(hDC, rc, baseColor, hiColor);
  
  ::SelectObject(hDCMem, hBlendMask);

  //!!! Sample use of the blend function
  // Blend the two images, where:
  BLENDFUNCTION bFn;
  bFn.BlendOp = AC_SRC_OVER;
  bFn.BlendFlags = 0;
  bFn.SourceConstantAlpha = 0xFF; // The src constant blend will be fully opaque
  bFn.AlphaFormat = AC_SRC_ALPHA; // The alpha info is encode for each pixel.

  ::AlphaBlend( hDC, 
                rc.left, rc.top, 
                bmpWidth,
                bmpHeight,
                hDCMem,
                0,0,
                bmpWidth,
                bmpHeight,
                bFn);

  ::DeleteDC(hDCMem);
}

/* Gradient MACROS ***********************************************************/
//!!! MACROs to make formatting of color values for Gradient Fill more convenient.
// Convenience Macros to format the extract color parts into a 16-bit
// value which the Gradient fill structures require. GetXValue returns a byte.
#define GRADIENT_R_VAL(color) (GetRValue(color) << 8)
#define GRADIENT_G_VAL(color) (GetGValue(color) << 8)
#define GRADIENT_B_VAL(color) (GetBValue(color) << 8)


/******************************************************************************
Date:       7/8/2011
Purpose:    Functor to populate vertices of the wiper.
******************************************************************************/
template <int T>
struct VertexPopulator
{
  void operator()(TRIVERTEX v[T], 
    POINT pts[T],
    bool isOpaque[T], 
    COLORREF c1, 
    COLORREF c2)
  {
    for (int i = 0; i < T; ++i)
    {
      COLORREF  color = isOpaque[i] 
                      ? c1
                      : c2;
      USHORT    mask  = isOpaque[i] 
                      ? 0xFF00
                      : 0x0000;


      v[i].x      = pts[i].x;
      v[i].y      = pts[i].y;
      v[i].Red    = GRADIENT_R_VAL(color);
      v[i].Green  = GRADIENT_G_VAL(color);
      v[i].Blue   = GRADIENT_B_VAL(color);
      v[i].Alpha  = mask;
    }
  }
};

/******************************************************************************
Date:       7/8/2011
Purpose:    Use the gradientFill function to create a Alpha Blending mask
            for painting the transition wiper on the screen.
Parameters: hDC[in]: DC which the bitmap mask should be compatible with.
            rc[in]:  The bounding rectangle to create the wiper within.
Return:     A handle to a bitmap that contains the wiper alpha blend mask.
            The bitmap will be compatible with the DC that is passed in.
            This resource is cached for efficiency.
            A call to Term or FlushBackBuffer will Release the bitmap.
            NULL will be returned if the call fails.
******************************************************************************/
HBITMAP GetWiperAlphaBlendMask(HDC hDC, const RECT &rc, COLORREF color1, COLORREF color2)
{
  // If the Wiper mask already exists, return it now.
  if (g_hWiperMask)
  {
    return g_hWiperMask; 
  }

  VertexPopulator<8> populateVertices;

  // Adjust the rectangle to start from the origin (0,0).
  // This will simplify calculations, and reduce memory requirements 
  // for memory buffer that is created.
  RECT orgRect = rc;
  ::OffsetRect(&orgRect, -orgRect.left, 0);

  // Break the input rect into 3 parts horizontally to keep the highlight
  // gradient smooth, and within the specified boundaries.
  int width         = orgRect.right;    // rect is aligned at 0, right edge is width.
  int height        = orgRect.bottom;
  int segmentWidth  = width / 3;

  int left          = 0;
  int leftHilight   = left + segmentWidth;
  int rightHilight  = leftHilight + segmentWidth;
  int right         = left + width;   // which should also be: orgRect.right

  // There are 4 triangle surfaces to make a smooth transition from the background
  // color of white, to color A, switch to color B then transition back to white.
  // Vertices 1 and 4 are the same point with two different color definitions.
  // Vertices 2 and 7 are the same point with two different color definitions.
  //
  //    L 0-1  4-5
  //     /|/  /|/
  //    3-2  7-6 R
  //  
  //   L        The original left coordinate in the rectangle.
  //   R        The original right coordinate in the rectangle.
  //
  //  (0,1,6,2) or (0,4,6,7) or ...
  //            Creates the rectangle where the main highlight will be displayed.
  //  (4,5,6)   Creates the fade in transition.
  //  (0,2,3)   Creates the fade out transition.

  GRADIENT_TRIANGLE surface[4];
  surface[0].Vertex1 = 0;
  surface[0].Vertex2 = 1;
  surface[0].Vertex3 = 2;

  surface[1].Vertex1 = 0;
  surface[1].Vertex2 = 2;
  surface[1].Vertex3 = 3;

  surface[2].Vertex1 = 4;
  surface[2].Vertex2 = 5;
  surface[2].Vertex3 = 6;

  surface[3].Vertex1 = 4;
  surface[3].Vertex2 = 6;
  surface[3].Vertex3 = 7;

  // Interesting points, index mapping documented above.
  POINT pts[8] =
  {
    {leftHilight,   orgRect.top},
    {rightHilight,  orgRect.top},
    {leftHilight,   orgRect.bottom},
    {left,          orgRect.bottom},
    {rightHilight,  orgRect.top},
    {right,         orgRect.top},
    {rightHilight,  orgRect.bottom},
    {leftHilight,   orgRect.bottom}
  };

  // Mapping to indicate which vertices represent opaque points on the mask.
  bool maskOpaque[8] = {false, true, true, false, true, false, false, true};

  // Populate the vertex list for the Alpha mask.
  TRIVERTEX  mask[8];
  populateVertices(mask, pts, maskOpaque, color1, k_black);

  HDC hDCMask       = ::CreateCompatibleDC(hDC);
  HBITMAP hBmpMask  = ::CreateCompatibleBitmap(hDC, width, height);
  ::SelectObject(hDCMask, hBmpMask);

  ::BitBlt(hDCMask, 0, 0, width, height, hDC, orgRect.left, orgRect.top, BLACKNESS);
  ::GradientFill(hDCMask, mask, 8, &surface, 4, GRADIENT_FILL_TRIANGLE);

  // Delete the Memory DC, this will release its hold on the mask bitmap.
  ::DeleteDC(hDCMask);

  // Store the handle into the global location.
  g_hWiperMask = hBmpMask;

  return hBmpMask;
}



} // namespace anonymous

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
United States United States
I am a software architect and I have been developing software for nearly two decades. Over the years I have learned to value maintainable solutions first. This has allowed me to adapt my projects to meet the challenges that inevitably appear during development. I use the most beneficial short-term achievements to drive the software I develop towards a long-term vision.

C++ is my strongest language. However, I have also used x86 ASM, ARM ASM, C, C#, JAVA, Python, and JavaScript to solve programming problems. I have worked in a variety of industries throughout my career, which include:
• Manufacturing
• Consumer Products
• Virtualization
• Computer Infrastructure Management
• DoD Contracting

My experience spans these hardware types and operating systems:
• Desktop
o Windows (Full-stack: GUI, Application, Service, Kernel Driver)
o Linux (Application, Daemon)
• Mobile Devices
o Windows CE / Windows Phone
o Linux
• Embedded Devices
o VxWorks (RTOS)
o Greenhills Linux
o Embedded Windows XP

I am a Mentor and frequent contributor to CodeProject.com with tutorial articles that teach others about the inner workings of the Windows APIs.

I am the creator of an open source project on GitHub called Alchemy[^], which is an open-source compile-time data serialization library.

I maintain my own repository and blog at CodeOfTheDamned.com/[^], because code maintenance does not have to be a living hell.

Comments and Discussions