How to show a PNG image with 25% opacity using GDI+? (MFC) - c++

I am trying to output a PNG image by using GDI+, MFC. I want to output it with 25% opacity. Below is the way to output a PNG image on x=10, y=10:
CDC *pDC =GetDC();
Graphics graphics(pDC->m_hDC);
Image image(L"test1.png", FALSE);
graphics.DrawImage(&image, 10, 10);
But I don't know how to make it translucent. Any idea?

To draw the image with alpha blending, declare Gdiplus::ImageAttributes and Gdiplus::ColorMatrix with required alpha channel:
float alpha = 0.25f;
Gdiplus::ColorMatrix matrix =
{
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, alpha, 0,
0, 0, 0, 0, 1
};
Gdiplus::ImageAttributes attrib;
attrib.SetColorMatrix(&matrix);
graphics.DrawImage(&image,
Gdiplus::Rect(10, 10, image.GetWidth(), image.GetHeight()),
0, 0, image.GetWidth(), image.GetHeight(), Gdiplus::UnitPixel, &attrib);
See also: Using a Color Matrix to Transform a Single Color
Note that GetDC() is usually not used in MFC. If you do use it, be sure to call ReleaseDC(pDC) when pDC is no longer needed. Or simply use CClientDC dc(this) which has automatic cleanup. If painting is done in OnPaint then use CPaintDC which also has automatic cleanup:
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
Gdiplus::Graphics graphics(dc);
...
}

Related

C++ BitBlt displaying checkerboard and skewed colors

I'm attempting to draw to an off-screen device context / bitmap and move the image to the main hdc using bitblt. Here's the result I'm currently seeing:
The blue, yellow, and green bars on the left are being drawn directly to the window's hdc. The strange-looking ones on the right were drawn to the back buffer and copied over as a single frame. They should be identical, but clearly that's not the case.
Here's the code I'm using, reduced to a minimal example:
COLORREF color_yellow = RGB (224, 224, 0);
COLORREF color_green = RGB (0, 192, 0);
COLORREF color_blue = RGB (0, 0, 192);
HBRUSH brush_yellow = CreateSolidBrush (color_yellow);
HBRUSH brush_green = CreateSolidBrush (color_green);
HBRUSH brush_blue = CreateSolidBrush (color_blue);
HDC hdc = GetDC (Window);
HDC hdc_buffer = CreateCompatibleDC (hdc);
HBITMAP bitmap_buffer = CreateCompatibleBitmap (hdc_buffer, blit.screen_width, blit.screen_height);
SelectObject (hdc_buffer, bitmap_buffer);
draw_rectangle (hdc, 0, 0, 100, 30, brush_blue);
draw_rectangle (hdc, 0, 30, 100, 60, brush_yellow);
draw_rectangle (hdc, 0, 60, 100, 90, brush_green);
draw_rectangle (hdc_buffer, 0, 0, 100, 30, brush_blue);
draw_rectangle (hdc_buffer, 0, 30, 100, 60, brush_yellow);
draw_rectangle (hdc_buffer, 0, 60, 100, 90, brush_green);
BitBlt (hdc, 120, 0, 100, 90, hdc_buffer, 0, 0, SRCCOPY);
void draw_rectangle (HDC hdc, int left, int top, int right, int bottom, HBRUSH brush)
{
RECT rect;
SetRect (&rect, left, top, right, bottom);
FillRect (hdc, &rect, brush);
}
I'm creating a new hdc (compatible with the window's), creating a compatible bitmap, selecting it, drawing the rectangles, and bit blitting over with SRCCOPY. All of this looks right to me.
I'm sure there's some small thing I'm not doing, but I can't find it.
This is explained in documentation for CreateCompatibleBitmap:
Note: When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the HDC that was used to create the memory device context
Therefore, change
CreateCompatibleBitmap(hdc_buffer, width, height);//monochrome
to
CreateCompatibleBitmap(hdc, width, height);//colored bitmap

When I use DirectWrite to draw text on a GDI hdc, how to set a transparent back ground?

Now I am working on a legacy product which use GDI to draw text in screen. Now I try to use DirectWrite to draw text for better appearance and accurancy of font. I am very curious that has anyone done this before?
I meet a problem that when I use DirectWrite to draw text on a GDI hdc, the background color is always white, I need a transparent background, is it possible? it seems that the SetBkMode is useless
The sample code is as below,
SetBkMode(hdc, TRANSPARENT); //hDC is the target GDI dc
SIZE size = {};
HDC memoryHdc = NULL;
memoryHdc = g_pBitmapRenderTarget->GetMemoryDC();
SetBkMode(memoryHdc, TRANSPARENT);
hr = g_pBitmapRenderTarget->GetSize(&size);
Rectangle(memoryHdc, 0, 0, size1.cx , size1.cy );
if (SUCCEEDED(hr)) {
hr = g_pTextLayout->Draw(NULL, g_pGdiTextRenderer, 0, 0);
}
BitBlt(hdc, x, y, width + 1, height + 1, memoryHdc, 0, 0, SRCCOPY | NOMIRRORBITMAP);
Default (Stock) brush for freshly created GDI device context is white solid brush, which is why you have white rectangle in output. See GetStockObject
GDI doesn't work with transparent images, BitBlt will replace all pixels inside destination rectangle in target DC. You have to copy contents of target DC destination rectangle in memory DC, then draw text and copy result back to achieve desired effect.
SetBkMode(hdc, TRANSPARENT); //hDC is the target GDI dc
SIZE size = {};
HDC memoryHdc = g_pBitmapRenderTarget->GetMemoryDC();
BitBlt(memoryHdc, 0, 0, width+1, height+1, hdc, x, y, SRCCOPY);
if (SUCCEEDED(hr)) {
hr = g_pTextLayout->Draw(NULL, g_pGdiTextRenderer, 0, 0);
}
BitBlt(hdc, x, y, width + 1, height + 1, memoryHdc, 0, 0, SRCCOPY | NOMIRRORBITMAP);
Make sure you use smallest possible update region, since moving large chunks of bitmaps in memory will surely dwindle performance.
If application uses back buffer to draw windows, memory DC of IDWriteBitmapRenderTarget could be used instead of allocating another one - in that case you solve problem of transparent text background automatically.

Use a sprite C++ MFC

I need to display an image from a sprite using MFC. A search on google led me to this link which led me to this code :
//This code is in the OnPaint function
//img is a CImage, declared as a class member
img.Load(_T("icon-sprite.png"));
HDC imgDc = img.GetDC();
int height = 24;
int width = 24;
//Sprite Icon is a CStatic
CDC* spriteDc = spriteIcon.GetDC();
HDC spriteHdc = spriteDc->GetSafeHdc();
CClientDC pDC(this);
HDC hdcWindow = pDC->GetSafeHdc();
//img.StretchBlt(imgDc, 0, 0, 600, 203, SRCCOPY);
//img.BitBlt(imgDc, width, height, 600, 203, 0, 0, SRCAND);
//img.BitBlt(imgDc, width, height, 640, 480, 0, 0, SRCPAINT);
StretchBlt(imgDc, 0, 0, 600, 203,
imgDc, 0, 0, 200, 203, SRCCOPY);
BitBlt(imgDc, width, height, 600, 203,
imgDc, 0, 0, SRCAND);
BitBlt(imgDc, width, height, 640, 480,
imgDc, 0, 0, SRCPAINT);
spriteIcon.SetBitmap((HBITMAP)img);
With this code spriteIcon only display a rectangle of the size of the sprite image.
What did I do wrong ?
Most probably CImage is local and going out of scope, thus the image (and HBITMAP) is invalid. You have two options:
Declare CImage such that it would exist even after this function returns.
Use CImage::Detach which returns a HBITMAP and relinquishes its ownership with handle (i.e. won't delete on destructor).
Please ensure that given image exists and is loaded properly.
Also, you don't need to new CClientDC, you can have it on stack.

Converting ICON to BITMAP -- side-effect

How do I make it so that this code strictly does the conversion from a Windows ICON to a CBitmap?
The code is incorrectly displaying the new bitmap on screen. :(
This code was acquired from 'someone' on the web. And though it achieves it's goal of converting the ICON, it also displays the icon on screen (upper left hand corner) which it should not be doing.
D'oh!
void CUIHelper::ConvertIconToBitmap2(CBitmap& bmpObj, HICON hIcon)
{
CClientDC clientDC(NULL);
CDC memDC;
memDC.CreateCompatibleDC(&clientDC);
ASSERT(hIcon);
ICONINFO info;
VERIFY(GetIconInfo(hIcon, &info));
BITMAP bmp;
GetObject(info.hbmColor, sizeof(bmp), &bmp);
HBITMAP hBitmap = (HBITMAP)CopyImage(info.hbmColor, IMAGE_BITMAP, 0, 0, 0);
ASSERT(hBitmap);
ASSERT(memDC.GetSafeHdc());
HBITMAP hOldBmp = (HBITMAP)memDC.SelectObject(hBitmap);
clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(hOldBmp);
VERIFY( bmpObj.Attach(hBitmap) );
DeleteObject(info.hbmColor);
DeleteObject(info.hbmMask);
}
I am dumb when it comes to GDI.
clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);
should be
memDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);

Slight undesired transparency from FillRectangle

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.
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.
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).