How to draw a PNG in GDI+ and maintain transparency/alpha channel - c++

Basically, the images with alpha draw correctly when draw directly on top of a solid colored surface, however when it hits a region where the window is transparent, the PNG with alpha values start having a white background.
Hopefully my code will help you guys decipher what is going on. All help is much appreciated.
GDI+ Drawing
Image indicator(L"resources\\images\\floating_icon [a] 1.png", FALSE);
g.DrawImage(&indicator, 0, 0);
TextureBrush myTextureBrush(&indicator);
g.FillRectangle(&myTextureBrush, WindowPadding, 50, WindowDim.right - WindowPadding * 2, WindowPadding);
Window Properties
hWnd = CreateWindowEx(0, szWindowClass, szTitle, WS_POPUP | WS_OVERLAPPED,
0, 0, WindowDim.right, WindowDim.bottom, NULL, NULL, hInstance, NULL);
SetLayeredWindowAttributes(hWnd, RGB(255, 255, 255), 0,
LWA_COLORKEY);

If your underlying bitmap is pure white (255,255,255) then when you draw an alpha image on top of that, you'll end up with a color that's not pure white (unless the pixel is completely transparent) - each pixel of the image is blended with the underlying color of the background.
The problem with this is that your color key is only going to make pure white pixels transparent, and anything else will be solid.
The solution would be to switch to using per-pixel alpha for your layered window.

Related

Drawing on a transparent CWnd DC

I'm attempting to create a transparent CWnd on top of an MFC dialog so that I can bitblt bitmaps to the dialog dc and then separately draw shapes to the transparent DC that sits directly on top of the dialog. The issue is, I can't seem to get the CWnd to be transparent. Instead, whenever I blit a black bitmap to the transparent CWnd, it flickers with the dialog... as if one were to blit the dialog dc and then immediately after blit a black bitmap to the same DC.
For instance, on the dialog I create a CWnd object that will be the transparent overlay:
pDrawingWnd = new CWnd();
pDrawingWnd->Create(NULL, L"", WS_VISIBLE, CRect(0, 0, IMAGEX, IMAGEY), this, 0);
pDrawingWnd->ModifyStyleEx(0, WS_EX_LAYERED | WS_EX_TRANSPARENT);
pDrawingWnd->SetLayeredWindowAttributes(RGB(0,0,0), 0, LWA_COLORKEY);
Then, in a timer loop, I draw a rectangle to the CWnd DC:
CDC mDC;
pDC = pDrawingWnd->GetDC();
mDC.CreateCompatibleDC(pDC);
if (drawingBmp)
delete drawingBmp;
drawingBmp = new CBitmap();
drawingBmp->CreateCompatibleBitmap(pDC, IMAGEX, IMAGEY);
CBitmap* pOldBmp2 = mDC.SelectObject(drawingBmp);
mDC.FrameRect(CRect(0,0,100,100), &brush);
pDC->BitBlt(0, 0, IMAGEX, IMAGEY, &mDC, 0, 0, SRCCOPY);
mDC.SelectObject(pOldBmp2);
ReleaseDC(pDC);
So instead of drawing a green rectangle on top of the dialog, it just flickers displaying the black bitmap with the green rectangle. Normally I would draw the rectangle directly to the dialog dc, however when I have a slower update rate, let's say 10 times per second.. it updates the size of the rectangle slowly. So I would rather the drawing of the rectangle be independent of the blit rate on the dialog. What's the correct way to draw on top of another DC?
Thanks for any help!
You need to create layered window with per pixel alpha.

Transparency in C++

I'm trying to create transparency in my application.
For instance the window of my app is square and i want to make it round, by hiding parts of the window.
My code looks something like this:
HDC hdcMask = nullptr;
HBITMAP hBMP = (HBITMAP)LoadImageW(nullptr, L"C:\\mask.bmp", IMAGE_BITMAP, 150, 160, LR_LOADFROMFILE);
SelectObject(hdcMask, hBMP);
HWND hWnd = GetActiveWindow();
HDC hdcWindow = GetDC(hWnd);
TransparentBlt(hdcWindow, 0, 0, 150, 160, hdcWindow, 0, 0, 150, 160, RGB(0,0,0));
where mask.bmp is a bitmap where white is what i want to be transparent and black is what I want to be visible.
After applying this code, nothings happens. What am I doing wrong ? Is there another method to obtain the desired result?
Note: I need this code to work on Windows XP OS or later.
There are several ways of making a window transparent and/or translucent.
SetWindowRgn will make parts of a window transparent.
SetLayeredWindowAttributes can make parts of a window transparent, and can also apply translucency to the whole of the rest of the window.
UpdateLayeredWindow can give individual windows different amounts of translucency.
Why don't you use a different format than bmp where you can also include alpha data?
You can use a PixelFormat32bppPARGB Bitmap, use Bitmap::LockBits an Bitmap::Unlockbits
to keep the file's format and avoid having the alpha setting overwritten.

Using BitBlt to mask background of bitmap C++

I am making a small game with bitmaps representing objects and I am trying to mask the background using a bitmap mask. I looked it up on google and made two bitmaps. One of the ship and one as a mask. For the mask, I colored everything I wanted visible black and the background white. (If this should have been opposite, please let me know). I load the bitmaps as so:
HBITMAP bmpShip = (HBITMAP)LoadImage(NULL,
"C:\\Users\\Owner\\Desktop\\Asteroids\\Starship.bmp",
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE);
HBITMAP hShipMask = (HBITMAP)LoadImage(NULL,
"C:\\Users\\Owner\\Desktop\\Asteroids\\StarshipMask.bmp",
IMAGE_BITMAP,
0,
0,
LR_LOADFROMFILE);
Now how can I use the BitBlt raster operations to display a bitmap without the background on the screen at a specific location. Without masking the background, this is what I have been doing:
Ship = CreateWindowEx(0,
"STATIC","",
SS_CENTERIMAGE | SS_REALSIZEIMAGE | SS_BITMAP | WS_CHILD | WS_VISIBLE,
10,
shipPos,
294,
86,
Background,
(HMENU)-1,
NULL, NULL);
SendMessage(Ship, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)bmpShip);
UpdateWindow(hWnd);
//And moving the window with MoveWindow()
Can anyone please tell me how I can mask the background and then move the window to a new location
Edit: By the way, Background is just another bitmap loaded in the same way as the others which the ship is displayed on top of.

Freehand Drawing on Entire Screen

I'm trying to make a simple little tool that would allow a user to switch from normal operation to a mode where all application messages are disabled and they can use the mouse to do some freehand drawing, then switch modes again to keep their drawing on the screen while they do whatever other normal stuff they want. This could, if I decide, evolve into a nice thing you could use to use a decorated screen by saving the decorations you do and loading them later.
When I started this (which was over half a year ago, soon after discovering the Windows API) I just did global mouse tracking and painted a circle wherever it was to a GetDC(NULL) hdc. The problems, of course, were that it would disappear when anything under it updated and would still have the mouse messages put through, so if I held down the button on the desktop, for example, it would put resizing rectangle things throughout the paintings.
Today, after finally having some spare time since the last major work on this most of that 6 months ago, I decided to remake it and see if I could achieve what I wanted. I made a transparent, topmost, WS_CHILD, layered, maximized window (basically the screen doesn't change, but there's a window on top of everything letting messages through). Next, I made it so that when it was in painting mode, it would set the alpha value to 1 and let the user paint. The thing I didn't realize until I did it was that since the alpha value of the window was 1, none of the painting would be visible.
Next, I tried using GetDC(NULL), but remembered that gets erased when something updates.
Now I just thought of using bitmaps and dcs to repeatedly store the screen into a dc, paint on another dc, and then copy it back to the one with the stored screen with transparency for the parts that aren't drawn on, and copy that back to the screen, but I'm losing a bit of heart. Here's my source code for that (the mask function is taken from this tutorial). Please tell me if some of this is unnecessary. I've used bitmaps for double buffering sure, but I'm not all that sure on where I need them.
//Global mask since it takes longer to make
HBITMAP mask;
//Window Procedure Start
HDC screenDC; //hdc for entire screen
screenDC = GetDC (NULL); //get DC for screen
HDC memDC = CreateCompatibleDC (screenDC); //create DC for holding the screen+paint
HBITMAP bm = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for memDC
HDC paintDC = CreateCompatibleDC (screenDC); //create DC to paint on
HBITMAP paintBM = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for paintDC
SelectObject (memDC, bm); //select bitmap into memDC
SelectObject (paintDC, paintBM); //select painting bitmap into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), screenDC, 0, 0, SRCCOPY); //copy screen to memDC
SetBkColor (paintDC, RGB(0,0,0)); //set background of paintDC to black so it's all transparent to start
//WM_CREATE
mask = CreateBitmapMask (bm, RGB(0,0,0)); //create black mask (paint colours are limited 1-255 now)
//painting is done into paintDC
//at end of Window Procedure
SelectObject (paintDC, mask); //select mask into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCAND); //this in combination with the next should make it bitblt with all of the black taken out I thought
SelectObject (paintDC, paintBM); //select bitmaps into DCs
SelectObject (memDC, bm);
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCPAINT); //second part of transparent bitblt
BitBlt (screenDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCCOPY); //copy memDC back to screen
DeleteObject (paintBM); //delete stuff
DeleteObject (mask);
DeleteDC (memDC);
DeleteDC (paintDC);
ReleaseDC (hwnd, screenDC);
//CreateBitmapMask() (taken directly from http://www.winprog.org/tutorial/transparency.html
HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
HDC hdcMem, hdcMem2;
HBITMAP hbmMask;
BITMAP bm;
// Create monochrome (1 bit) mask bitmap.
GetObject(hbmColour, sizeof(BITMAP), &bm);
hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
// Get some HDCs that are compatible with the display driver
hdcMem = CreateCompatibleDC(0);
hdcMem2 = CreateCompatibleDC(0);
SelectObject(hdcMem, hbmColour);
SelectObject(hdcMem2, hbmMask);
// Set the background colour of the colour image to the colour
// you want to be transparent.
SetBkColor(hdcMem, crTransparent);
// Copy the bits from the colour image to the B+W mask... everything
// with the background colour ends up white while everythig else ends up
// black...Just what we wanted.
BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
// Take our new mask and use it to turn the transparent colour in our
// original colour image to black so the transparency effect will
// work right.
BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);
// Clean up.
DeleteDC(hdcMem);
DeleteDC(hdcMem2);
return hbmMask;
}
I know that code may very well be horrible. I'm taking all suggestions into account, it's just that I'm not too clear with this subject, and don't get everything that's happening, which makes it hard to fix. This code runs for me by pretty much putting a fullscreen black rectangle every so often.
My main question is: Is there any way I can paint onto the screen without it getting erased when windows underneath update? The only real thing I can think of now would be to store all of the locations of the tiny line segments the user draws and keep redrawing them on top of the screen. At first glance it seems very inefficient and wasteful of memory.
Also, I was pretty sure while writing this that I didn't need any code examples for the theoretical stuff before the supplied code segments. Most of it is gone now, but this really is more of a theory issue.
EDIT:
I just found out about the TransparentBlt function which seemed perfect for the situation, so I tried using that instead of the SRCPAINT and SRCAND BitBlts and it produced the same result: a black rectangle covering the screen, sometimes having parts disappear when my mouse moves over things.
Simplest way, perhaps:
When in non-drawing mode, use SetLayeredWindowAttributes to set a 'transparency key' color for the transparent window. Make the window's alpha fully opaque, but fill the window (FillRect or similar) with that key color, and it will all appear transparent. Then anything you draw in the non-key color will appear as solid, on top of all the windows beneath the transparent layered window.
To go into drawing mode, one approach is to create a new window with a captured bitmap of the desktop immediately under your transparent layer. Or avoid the bitmap, and make it slightly non-transparent and all a solid color - eg so it looks like the desktop is "greyed out". The key thing is that this window is not completely transparent, so it will be able to receive mouse input that you can then use to draw on the actual transparent layer.
I think you would be best of by creating a snapshot of the screen and save that in a bitmap (in the form of a memory DC) BEFORE you show a window which displays the contents of the memory DC in fullscreen. That way you actually fetch the messages caused by clicks etc on your own window and process them as usual.
Capture screen contents
Create window (full screen) and use captured contents
Do some drawing
Save the content (as bmp or anything fancy)
Close the window and return to regular desktop
Good idea?

Drawing a system-like cursor, top-most, anywhere

I need to draw a system-like cursor that I simply can control the position of.
In other words, I need to draw a transparent image that looks just like the system cursor and I need it to be rendered on top of all other windows.
I've tried multiple approaches, but they all seem to have some downside.
I've figured out that I can load the cursor image by using LoadImage() and passing the resource OCR_NORMAL and casting it into a HBITMAP.
HICON NormalCursor = (HICON)LoadImage(NULL, MAKEINTRESOURCE(OCR_NORMAL), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
Then getting the "desktop" HDC
hDC = GetDC(NULL);
Then I can try to draw it using DrawIconEx()
DrawIconEx(hDC, (int)x, 0, NormalCursor, 0, 0, NULL, NULL, DI_DEFAULTSIZE | DI_NORMAL);
The DI_NORMAL flag is supposed to combine the DI_IMAGE & DI_MASK flags giving me a transparent image/icon/cursor, but this is my result on the desktop:
Not to mention that if it moves it creates trails.
By making a transparent window using SetLayeredWindowAttributes like this:
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 50, LWA_COLORKEY);
And having the background color of my window to be black, I can remove the background from the window. But due to doing alpha based on a color I get ugly black pixels around my cursor icon.
Can I make the background of a window transparent in some other way than using a color mask?
How do I draw a transparent cursor on top of all windows properly?
I would recommend that you do make your own window, and do something like what's described at http://www.codeproject.com/KB/GDI-plus/CsTranspTutorial3.aspx . It's in C#, but most of it is just win32 calls. It does a nice job of variable transparency, too, not just 0%/100%.
Isn't the outline of the cursor black? Is the problem just that you're making the outline transparent too? Why don't you just change the transparency color (and the background color of the window) to anything other than black or white?