2

I have a window created with the WS_EX_LAYERED window style. I am currently drawing onto a memory bitmap using GDI+, and using UpdateLayeredWindow to update the graphical content of my layered window.

Here's a snippet of my code:

void Redraw(HWND hWnd, int width, int height) {
    static bool floppy = true;

    floppy = !floppy;

    HDC hScreenDC = GetDC(HWND_DESKTOP);
    HDC hMemDC = CreateCompatibleDC(hScreenDC);
    HBITMAP hBmp = CreateCompatibleBitmap(hScreenDC, width, height);
    HGDIOBJ hObj = SelectObject(hMemDC, hBmp);

    Graphics gfx(hMemDC);

    SolidBrush b(Color(254, (floppy ? 255 : 0), (floppy ? 0 : 255), 0));
    gfx.FillRectangle(&b, Rect(0, 0, width, height));

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

    POINT src = { 0, 0 };

    SIZE size;
    size.cx = width;
    size.cy = height;

    Assert(UpdateLayeredWindow(
        hWnd,
        hScreenDC,
        NULL,
        &size,
        hMemDC,
        &src,
        RGB(0, 0, 0),
        &blend,
        ULW_ALPHA
    ));

    SelectObject(hMemDC, hObj);
    DeleteObject(hBmp);
    DeleteDC(hMemDC);
    ReleaseDC(HWND_DESKTOP, hScreenDC);
}

When creating my SolidBrush, I specified the value of 254 for the alpha component. This results in a 99.6% opaque fill, which is not what I want.

When I specify 255 as the alpha component, there appears to be no fill; my window becomes completely transparent. This is an issue because I wish to draw shapes that are 100% opaque, but I also wish to draw some that aren't.

Community
  • 1
  • 1
Spooky
  • 2,966
  • 8
  • 27
  • 41
  • Just a comment to this old post, you are mixing GDI and GDI+ too much, GDI should only be needed to BitBlt the bitmap to screen. – Evan Carslake Mar 29 '16 at 16:24

2 Answers2

3

There seems to be some qwerks with FillRectangle. This becomes apparent when we observe that using FillEllipse with a SolidBrush whose alpha component is 255, results in the shape being rendered perfectly (opaque).

Here are two work-arounds that I came up with, which each solve the issue for me:

  • Call FillRectangle twice

    SolidBrush b(Color(254, 255, 0, 0));
    gfx.FillRectangle(&b, Rect(0, 0, width, height));
    gfx.FillRectangle(&b, Rect(0, 0, width, height));
    

    Since the same area is being filled twice, they will blend and create RGB(255, 0, 0) regardless of the content behind the window (it's now 100% opaque). I do not prefer this method, as it requires every rectangle to be drawn twice.

  • Use FillPolygon instead

    Just as with FillEllipse, FillPolygon doesn't seem to have the colour issue, unless you call it like so:

    SolidBrush b(Color(255, 255, 0, 0));
    Point points[4];
    points[0] = Point(0, 0);
    points[1] = Point(width, 0);
    points[2] = Point(width, height);
    points[4] = Point(0, height);
    gfx.FillPolygon(&b, points, 4); //don't copy and paste - this won't work
    

    The above code will result in a 100% transparent window. I am guessing that this is either due to some form of optimisation that passes the call to FillRectangle instead. Or - most likely - there is some problem with FillPolygon, which is called by FillRectangle. However, if you add an extra Point to the array, you can get around it:

    SolidBrush b(Color(255, 255, 0, 0));
    Point points[5];
    points[0] = Point(0, 0);
    points[1] = Point(0, 0); //<-
    points[2] = Point(width, 0);
    points[3] = Point(width, height);
    points[4] = Point(0, height);
    gfx.FillPolygon(&b, points, 5);
    

    The above code will indeed draw a 100% opaque shape, which fixes my problem.

Spooky
  • 2,966
  • 8
  • 27
  • 41
1

UpdateLayeredWindow() requires a bitmap with pre-multiplied alpha:

Note that the APIs use premultiplied alpha, which means that the red, green and blue channel values in the bitmap must be premultiplied with the alpha channel value. For example, if the alpha channel value is x, the red, green and blue channels must be multiplied by x and divided by 0xff prior to the call.

You can use Bitmap::ConvertFormat() to convert a bitmap to pre-multiplied (the format is PixelFormat32bppPARGB).

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • This appears to have removed all (real) transparency. Using a `SolidBrush` of ARGB: [ 128, 255, 0, 0 ], the drawn colour is actually RGB: [ 128, 0, 0 ]. Also, congratulations on 10k. – Spooky Oct 07 '13 at 00:25
  • Hmm, to be honest I'm not sure why that would be. I haven't ever tried to use GDI+ in this way myself. I found http://stackoverflow.com/questions/14315869/drawing-a-partially-transparent-gdi-bitmap-to-a-borderless-window-using-updatel?rq=1 which seems like a similar question to yours and might be of help? And thanks :) – Jonathan Potter Oct 07 '13 at 00:43
  • Thanks for the question link, but the answer to it was in fact the one that gave me my initial direction, and although I have moved some code around, I am doing the same things that he's doing. This allows me to draw transparent and semi-transparent pixels (as the OP was aiming to do), but not 100% opaque pixels as well. The closest I've managed to get is through using an alpha component of 254, which - when the window is over a white window - results in RGB(255, 1, 1) being drawn. I have found that one workaround is to call FillRectangle twice, but that's not very nice... – Spooky Oct 07 '13 at 01:26