HBITMAP unscaledBitmap(void *i, intptr_t dx, intptr_t dy) { BITMAPINFO bi; VOID *ppvBits; HBITMAP bitmap; ZeroMemory(&bi, sizeof (BITMAPINFO)); bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); bi.bmiHeader.biWidth = (LONG) dx; bi.bmiHeader.biHeight = -((LONG) dy); // negative height to force top-down drawing; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biSizeImage = (DWORD) (dx * dy * 4); bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &ppvBits, 0, 0); if (bitmap == NULL) xpanic("error creating HBITMAP for unscaled ImageList image copy", GetLastError()); dotoARGB(i, (void *) ppvBits); return bitmap; }
static void paintArea(HWND hwnd, void *data) { RECT xrect; PAINTSTRUCT ps; HDC hdc; HDC rdc; HBITMAP rbitmap, prevrbitmap; RECT rrect; BITMAPINFO bi; VOID *ppvBits; HBITMAP ibitmap; HDC idc; HBITMAP previbitmap; BLENDFUNCTION blendfunc; void *i; intptr_t dx, dy; int hscroll, vscroll; // FALSE here indicates don't send WM_ERASEBKGND if (GetUpdateRect(hwnd, &xrect, FALSE) == 0) return; // no update rect; do nothing getScrollPos(hwnd, &hscroll, &vscroll); hdc = BeginPaint(hwnd, &ps); if (hdc == NULL) xpanic("error beginning Area repaint", GetLastError()); // very big thanks to Ninjifox for suggesting this technique and helping me go through it // first let's create the destination image, which we fill with the windows background color // this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx rdc = CreateCompatibleDC(hdc); if (rdc == NULL) xpanic("error creating off-screen rendering DC", GetLastError()); // the bitmap has to be compatible with the window // if we create a bitmap compatible with the DC we just created, it'll be monochrome // thanks to David Heffernan in http://stackoverflow.com/questions/23033636/winapi-gdi-fillrectcolor-btnface-fills-with-strange-grid-like-brush-on-window rbitmap = CreateCompatibleBitmap(hdc, xrect.right - xrect.left, xrect.bottom - xrect.top); if (rbitmap == NULL) xpanic("error creating off-screen rendering bitmap", GetLastError()); prevrbitmap = (HBITMAP) SelectObject(rdc, rbitmap); if (prevrbitmap == NULL) xpanic("error connecting off-screen rendering bitmap to off-screen rendering DC", GetLastError()); rrect.left = 0; rrect.right = xrect.right - xrect.left; rrect.top = 0; rrect.bottom = xrect.bottom - xrect.top; if (FillRect(rdc, &rrect, areaBackgroundBrush) == 0) xpanic("error filling off-screen rendering bitmap with the system background color", GetLastError()); i = doPaint(&xrect, hscroll, vscroll, data, &dx, &dy); if (i == NULL) // cliprect empty goto nobitmap; // we need to blit the background no matter what // now we need to shove realbits into a bitmap // technically bitmaps don't know about alpha; they just ignore the alpha byte // AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx ZeroMemory(&bi, sizeof (BITMAPINFO)); bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); bi.bmiHeader.biWidth = (LONG) dx; bi.bmiHeader.biHeight = -((LONG) dy); // negative height to force top-down drawing bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biSizeImage = (DWORD) (dx * dy * 4); // this is all we need, but because this confused me at first, I will say the two pixels-per-meter fields are unused (see http://blogs.msdn.com/b/oldnewthing/archive/2013/05/15/10418646.aspx and page 581 of Charles Petzold's Programming Windows, Fifth Edition) // now for the trouble: CreateDIBSection() allocates the memory for us... ibitmap = CreateDIBSection(NULL, // Ninjifox does this, so do some wine tests (http://source.winehq.org/source/dlls/gdi32/tests/bitmap.c#L725, thanks vpovirk in irc.freenode.net/#winehackers) and even Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2006/11/16/1086835.aspx), so. &bi, DIB_RGB_COLORS, &ppvBits, 0, 0); if (ibitmap == NULL) xpanic("error creating HBITMAP for image returned by AreaHandler.Paint()", GetLastError()); // now we have to do TWO MORE things before we can finally do alpha blending // first, we need to load the bitmap memory, because Windows makes it for us // the pixels are arranged in RGBA order, but GDI requires BGRA // this turns out to be just ARGB in little endian; let's convert into this memory dotoARGB(i, (void *) ppvBits, FALSE); // FALSE = not NRGBA // the second thing is... make a device context for the bitmap :| // Ninjifox just makes another compatible DC; we'll do the same idc = CreateCompatibleDC(hdc); if (idc == NULL) xpanic("error creating HDC for image returned by AreaHandler.Paint()", GetLastError()); previbitmap = (HBITMAP) SelectObject(idc, ibitmap); if (previbitmap == NULL) xpanic("error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC", GetLastError()); // AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111 blendfunc.BlendOp = AC_SRC_OVER; blendfunc.BlendFlags = 0; blendfunc.SourceConstantAlpha = 255; // only use per-pixel alphas blendfunc.AlphaFormat = AC_SRC_ALPHA; // premultiplied if (AlphaBlend(rdc, 0, 0, (int) dx, (int) dy, // destination idc, 0, 0, (int) dx, (int)dy, // source blendfunc) == FALSE) xpanic("error alpha-blending image returned by AreaHandler.Paint() onto background", GetLastError()); // clean up after idc/ibitmap here because of the goto nobitmap if (SelectObject(idc, previbitmap) != ibitmap) xpanic("error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP", GetLastError()); if (DeleteObject(ibitmap) == 0) xpanic("error deleting HBITMAP for image returned by AreaHandler.Paint()", GetLastError()); if (DeleteDC(idc) == 0) xpanic("error deleting HDC for image returned by AreaHandler.Paint()", GetLastError()); nobitmap: // and finally we can just blit that into the window if (BitBlt(hdc, xrect.left, xrect.top, xrect.right - xrect.left, xrect.bottom - xrect.top, rdc, 0, 0, // from the rdc's origin SRCCOPY) == 0) xpanic("error blitting Area image to Area", GetLastError()); // now to clean up if (SelectObject(rdc, prevrbitmap) != rbitmap) xpanic("error reverting HDC for off-screen rendering to original HBITMAP", GetLastError()); if (DeleteObject(rbitmap) == 0) xpanic("error deleting HBITMAP for off-screen rendering", GetLastError()); if (DeleteDC(rdc) == 0) xpanic("error deleting HDC for off-screen rendering", GetLastError()); EndPaint(hwnd, &ps); }