Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing a window with a standard frame and transparent contents

Tags:

windows

winapi

For xtow, I want to draw a top-level window with the standard non-client area and the client area filled with a bitmap which has an alpha channel.

I now discover the way I have implemented this works on Windows 7, but doesn't render correctly on Windows 8.1, leaving behind images of the window contents when it is moved or maximized.

To investigate, I made a simple test program alpha-test, which

  • Uses DwmEnableBlurBehindWindow() to set a non-intersecting blur region, so that alpha values in the window are honoured, without blur.
  • Uses BitBlt() to copy a bitmap with alpha into it.
//
// g++ alpha-test.cc -o alpha-test -mwindows -lgdiplus -ldwmapi
//

#define  _WIN32_WINNT 0x0600

#include <assert.h>
#include <stdio.h>
#include <windows.h>
#include <gdiplus.h>
#include <dwmapi.h>

int width = 360;
int height = 360;
HBITMAP hBitmap;

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
    {
    case WM_PAINT:
      {
        PAINTSTRUCT ps;
        HDC hdcUpdate = BeginPaint(hWnd, &ps);

        RECT rc;
        GetClientRect(hWnd, &rc);
        HBRUSH hbrush = CreateSolidBrush(RGB(0,0,0));
        FillRect(hdcUpdate, &rc, hbrush);
        DeleteObject(hbrush);

        HDC hdcMem = CreateCompatibleDC(hdcUpdate);
        HBITMAP hbmpold = (HBITMAP)SelectObject(hdcMem, hBitmap);

        if (!BitBlt(hdcUpdate, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY))
          {
            printf("BitBlt failed: 0x%08x\n", (int)GetLastError());
          }

        SelectObject(hdcMem, hbmpold);
        DeleteDC(hdcMem);

        EndPaint(hWnd, &ps);
      }
      return 0;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
    }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
  ULONG_PTR gdiplusToken;
  Gdiplus::GdiplusStartupInput gdiplusStartupInput;
  GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

  LPCTSTR szWindowClass = "TransparentClass";

  // Register class
  WNDCLASSEX wcex = {0};
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style          = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC;
  wcex.lpfnWndProc    = WndProc;
  wcex.cbClsExtra     = 0;
  wcex.cbWndExtra     = 0;
  wcex.hInstance      = hInstance;
  wcex.hIcon          = NULL;
  wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
  wcex.lpszClassName  = szWindowClass;
  wcex.hIconSm        = NULL;
  wcex.hbrBackground  = (HBRUSH)CreateSolidBrush(0x00000000);
  RegisterClassEx(&wcex);

  // Create window
  HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW,
                             szWindowClass,
                             "Transparent Window",
                             WS_OVERLAPPED | WS_SYSMENU,
                             CW_USEDEFAULT, CW_USEDEFAULT, width, height,
                             NULL, NULL, hInstance, NULL);

  Gdiplus::Bitmap *m_pImage = Gdiplus::Bitmap::FromFile(L"sample.png", FALSE);
  Gdiplus::Color bg(0,0,0,0);
  m_pImage->GetHBITMAP(bg, &hBitmap);
  assert(hBitmap);

  DWM_BLURBEHIND blurBehind = { 0 };
  blurBehind.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
  blurBehind.hRgnBlur = CreateRectRgn(0, 0, -1, -1);
  blurBehind.fEnable = TRUE;
  blurBehind.fTransitionOnMaximized = FALSE;
  DwmEnableBlurBehindWindow(hWnd, &blurBehind);
  DeleteObject(blurBehind.hRgnBlur);

  ShowWindow(hWnd, SW_SHOW);

  // Main message loop
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

  return (int)msg.wParam;
}

Is this really broken? How can I fix my code? Is this a Windows bug/limitation?

Is there another way to achieve my goal of drawing a bitmap with alpha into a window with a border?

Update: I did some tests using Direct2D and Direct3D to fill the client area with the bitmaps, but they mis-rendered in the same way.

like image 313
jturney Avatar asked Mar 13 '15 17:03

jturney


1 Answers

The DWM doesn't do blurring any more (this feature was deemed too power hungry and was removed in Windows 8), so I'd guess that it's not properly compositing the background area of your window any more - and therefore you aren't getting the "automatic" alpha effect it was giving you in Windows 7.

This is kind of an unusual way to draw transparent windows to be honest. Using UpdateLayeredWindow is the "official" way and would have the benefit of working on Windows 8 as well as Windows 7.

like image 90
Jonathan Potter Avatar answered Sep 22 '22 01:09

Jonathan Potter