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 blac开发者_运维百科k 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?
精彩评论