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

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?

Related

setting transparency for layered windows

I have a multi-level window, I need to display translucent text. The problem is that I can call SetLayeredWindowAttributes() only once, ie I need to make the background transparent first:
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_COLORKEY);
and then draw text and apply transparency to the entire window:
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
but each subsequent call to SetLayeredWindowAttributes() overrides the previous one.
I tried to display the text using AlphaBlend(), but since what I need to mix with lies in another window, the text just turned gray.

All-aero window have controls' colors blended - how to avoid it

I'm looking for a way to paint my custom controls into both the client and non-client area of a dialog. More or less the white area below
I used DwmExtendFrameIntoClientArea and I managed to get that effect by extending the client area on the entire window with
MARGINS mar = {-1, -1, -1, -1};
DwmExtendFrameIntoClientArea ( hWnd, &mar );
but now every control which I set with a transparent background
SetBkMode(hdc, TRANSPARENT);
have their colors blended with the aero stuff (the same problem you can see here).
Is there a way for the controls to retain their right color and avoid blending with the background?
It is because the window treated the black colour as the transparency key.
You just need to set another value:
SetWindowLong(hWnd,GWL_EXSTYLE,WS_EX_LAYERED);
// Choose a colour that you will not use in the program, eg RGB(200,201,202)
SetLayeredWindowAttributes(hWnd,RGB(200,201,202),0,LWA_COLORKEY);

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.

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?

How do I clear a Direct2D render target to fully transparent

I'm trying to draw semi-transparent rectangles on an invisible HWND. However, clearing the window with ID2D1HwndRenderTarget::Clear just makes the entire window black, so when I draw rectangles on top, they look semi-black.
If I don't Clear() and don't draw, then the window is invisible, as it should be. Clear() is the culprit here; however if I don't use it then painting messes up pretty badly.
Here's the code I'm using in my WindowProc:
case WM_PAINT:
// Begin drawing
pRenderTarget->BeginDraw();
pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
// Clear the window
pRenderTarget->Clear();
// Paint the panel and its children
D2DSurface()->StartPainting();
{
D2DSurface()->PaintTraverse(panel);
}
D2DSurface()->FinishPainting();
// Finish drawing
HRESULT hr = plat->pRenderTarget->EndDraw();
Thanks in advance!
Transparency is a problem. The only window that support per-pixel transparency are WS_EX_LAYERED windows. These windows were designed for uses like drag-drop icons, and that leads to problems in every other usage.
Using D2D with WS_EX_LAYERED windows requires that you use a DXGI render target. To EndDraw, you get a DC out of the render target then pass that into the layered window and tell the layered window to update itself. (Try this with a HWNDRenderTarget - it will probably crash the device driver).
I suspect your window appears transparent because it's not drawing at all.
Another gotcha you may run into... if the window is sufficiently transparent, mouse clicks will go through it to the underlying window.
Use ID2D1DCRenderTarget instead of ID2D1HwndRendTarget, then bind the device context (DC) of your window before drawing begins.
Of course, you will need to set the WS_EX_LAYERED for your window and then call SetLayeredWindowAttributes to set the transparent color:
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 0, LWA_COLORKEY);
Screenshot:
When creating your RenderTarget, you'll have to tell D2D that you want to use alpha (in premultiplied mode) in the pixel format:
HRESULT hr = mD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat( DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED ) ),
D2D1::HwndRenderTargetProperties( mWindow, size ),
&mRenderTarget );
After this, calling Clear() with an alpha value of zero works just fine.