For a few days now, we've used the SetDIBitsToDevice function in conjunction with a precomputed BITMAPINFO to draw a DIB with an alternate color table without modifying the HBITMAP.

The SetDIBitsToDevice function operates like a BitBlt with raster operation SRCCOPY. If you want another raster operation, you can use StretchDIBits, which has a final raster operation parameter. Despite its name, you don't have to stretch with StretchDIBits. Just pass a source and destination of equal size, and you've performed a NOP stretch, but you get the benefit of the raster operation.

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
 if (g_pvBits) {
    StretchDIBits(pps->hdc, 0, 0,
                      g_bmiGray.bmiHeader.biWidth,
                      g_bmiGray.bmiHeader.biHeight, 0, 0,
                      g_bmiGray.bmiHeader.biWidth,
                      g_bmiGray.bmiHeader.biHeight,
                      g_pvBits,
                     (BITMAPINFO*)&g_bmiGray, DIB_RGB_COLORS,
                      NOTSRCCOPY);
 }
}

I changed the call from SetDIBitsToDevice to StretchDIBits, setting the source and destination rectangles to the same size (so no actual stretching occurs), and specifying a raster operation of NOTSRCCOPY so the result on screen is a negative grayscale.

Some people may object to performing a stretch operation and requesting no stretching, but that's perfectly fine. At least in this case, GDI is not stupid. If you ask it to perform a stretch operation but pass parameters that don't do any stretching, it will optimize this to a non-stretching operation. You don't need to hand-optimize it. Instead of writing

if (cxSrc == cxDst && cySrc == cyDst) {
 BitBlt(hdc, xDst, yDst, cxDst, cyDst,
        hdcSrc, xSrc, ySrc, dwRop);
} else {
 StretchBlt(hdc, xDst, yDst, cxDst, cyDst,
            hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwRop);
}

... just go straight to the StretchBlt:

StretchBlt(hdc, xDst, yDst, cxDst, cyDst,
           hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwRop);

The StretchBlt function will convert the operation to a BitBlt if cxSrc == cxDst and cySrc == cyDst. You don't have to hand-optimize it. The GDI folks hand-optimized it for you.

(In fact, for a long time, the SetDIBitsToDevice function simply called StretchDIBits, saying that the input and output rectangles were the same size, and StretchDIBits detected the absence of stretching and used an optimized code path. Consequently, "optimizating" the code by calling SetDIBitsToDevice was actually a pessimization.)

Back to StretchDIBits. So far, we've been drawing the entire bitmap at the upper left corner of the destination device context. The last remaining feature of BitBlt is the ability to draw a rectangular chunk of a source bitmap at a destination location, so let's do that. We'll draw the bottom right corner of the bitmap in the bottom right corner of the window, with negative colors, and just to show we can, we'll also stretch it.

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
 if (g_pvBits) {
  RECT rc;
  GetClientRect(hwnd, &rc);
  int cxChunk = g_bmiGray.bmiHeader.biWidth / 2;
  int cyChunk = g_bmiGray.bmiHeader.biHeight / 2;
  StretchDIBits(pps->hdc, rc.right - cxChunk * 2,
                rc.bottom - cxChunk * 2,
                cxChunk * 2, cyChunk * 2,
                g_bmiGray.bmiHeader.biWidth - cxChunk,
                g_bmiGray.bmiHeader.biHeight - cyChunk,
                cxChunk, cyChunk,
                g_pvBits, (BITMAPINFO*)&g_bmiGray,
                DIB_RGB_COLORS, NOTSRCCOPY);
 }
}

So far, we've been operating on DIB pixels that are held inside a DIB section. But there's no requirement that the bits passed to StretchDIBits come from an actual DIB section. We'll look at the total disembodiment of DIBs next time, as well as looking at some unexpected consequences of all our game-playing.