I'm a little confused on how to double buffer this. I'm
not sure if I need to create another CreateCompatibleBitmap or CreateCompatibleDC and how to link it all.
This works as is but I don't think its double buffered right.
void __OnPaint(HWND hWnd, HDC _hdc = nullptr)
{
HDC hdc = _hdc;
PAINTSTRUCT paint;
RECT& rcClient = paint.rcPaint;
if (!_hdc)
hdc = BeginPaint(hWnd, &paint);
else
GetClientRect(hWnd, &rcClient);
if (hdc)
{
int width = rcClient.right - rcClient.left;
int height = rcClient.bottom - rcClient.top;
HDC hDCMem = CreateCompatibleDC(_hdc);
HBITMAP hBitmapMem = CreateCompatibleBitmap(hDCMem, width, height);
SelectObject(hDCMem, hBitmapMem);
Rectangle(hDCMem, 0, 0, width, height);
BLENDFUNCTION bfn;
bfn.BlendOp = AC_SRC_OVER;
bfn.BlendFlags = 0;
bfn.AlphaFormat = 0;
bfn.SourceConstantAlpha = 0x50;
AlphaBlend(hdc, 0, 0, width, height, hDCMem, 0, 0, width, height, bfn);
SetTextColor(hdc, RGB(255, 0, 0));
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, "Your text here", -1, &rcClient, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//BitBlt(hdc, 0, 0, width, height, hDCMem, 0, 0, SRCCOPY);
DeleteDC(hDCMem);
DeleteObject(hBitmapMem);
}
if (!_hdc)
EndPaint(hWnd, &paint);
}
Also i found i have another problem with this.
i move my window in WM_TIMER, i call my __onpaint, problem im having is that it does not redraw it has something todo with the alphaBlend, it keeps what ever was under the window at the time of 1st draw, since it worked before i was using that
double buffering is to do all your printing and drawing on a temporary bitmap, that should be stored somewhere. the drawings on that bitmap can happen outside of WM_PAINT event (eg: on Adding items or selection change).
then on WM_PAINT event, the only thing you have to do is to project that bitmap to the window via BitBlt function or similar functions.
the way you are using AlphaBlend is wrong. AlphaBlend is used to draw images that have an AlphaChanel over an existing image as an overlay.
Related
As the title says I'm unable to FillRect bitmap to be transparent. I know when creating the bitmap it is not monochrome as gray brush works fine but I have no way (that I'm aware of) to check if it is colored or grayscale. I also am aware that by default the bitmap is black hence why I'm trying to change it to transparent. I am also aware that I'm likely not cleaning up the dc's correctly however that is not the main issue. I'm trying to solve the black background by making it transparent.
#include<windows.h>
#include<iostream>
int main()
{
// Init DC
HWND Wnd = GetDesktopWindow();//GetConsoleWindow();
HDC ScreenDC = GetDC(Wnd);
// Init Rectangle
RECT ClientRect;
GetClientRect(Wnd, &ClientRect);
// Init Double Buffer
HDC MemDC = CreateCompatibleDC(ScreenDC);
HBITMAP MemBM = CreateCompatibleBitmap(ScreenDC, ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top);
HBITMAP OldBM = (HBITMAP)SelectObject(MemDC, MemBM);
// Create Brush and Pen
HPEN Pen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
HBRUSH ClearBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);
// Set Brush and Pen
SelectObject(MemDC, Pen);
SelectObject(MemDC, ClearBrush);
POINT p;
while(!GetAsyncKeyState(VK_RETURN))
{
// Clear and Draw
GetCursorPos(&p);
FillRect(MemDC, &ClientRect, ClearBrush);
Rectangle(MemDC, p.x, p.y, p.x+20, p.y+20);
BitBlt(ScreenDC, 0, 0, ClientRect.right - ClientRect.left, ClientRect.bottom + ClientRect.left, MemDC, 0, 0, SRCCOPY);
}
SelectObject(MemDC, OldBM);
DeleteObject(ClearBrush);
DeleteObject(Pen);
DeleteObject(OldBM);
DeleteObject(MemBM);
DeleteDC(MemDC);
ReleaseDC(Wnd, ScreenDC);
return 0;
}
I've tried many different ways of setting transparent background to no avail. The end result is a rectangle appearing over the mouse and following it across the screen however the background shouldn't be black I should be able to see other windows.
I'm trying to display text on the screen, on top of everything, not clickable, without having any window. The idea is to be able to show notifications. I'm somewhat close from what I want, but a really weird problem just showed. This is the code:
#include <Windows.h>
int main(void){
HDC hdc = ::GetDC(0);
RECT rect;
SetTextColor(hdc, RGB(0, 0, 255));
SetBkMode(hdc, TRANSPARENT);
SetBkColor(hdc, RGB(0, 0, 0));
auto hFont = CreateFont(40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, L"Verdana");
auto hTmp = (HFONT)SelectObject(hdc, hFont);
rect.left = 40;
rect.top = 10;
while (true){
DrawText(hdc, L"THIS IS A TEXT", -1, &rect, DT_SINGLELINE | DT_NOCLIP);
Sleep(1);
}
DeleteObject(SelectObject(hdc, hTmp));
::ReleaseDC(0, hdc);
return 0;
}
and this is what happens when I change the text settings from red to blue and size 80 to 40:
For some reason I can still see the old text, after rerunning the program, which tells me that I misunderstood something. Is there a better, cleaner way to do this?
EDIT: I checked windows notifications and that's NOT a solution. Imagine you are playing a full screen game, and want to know if an email arrives. Another essential thing is that it cannot be clickable, so that clicks by mistake don't minimize your game. How annoying is that skype popup that minimizes your application when you get a call?
You have bypassed all of the windows/client controls and so the system has no idea that this area needs to be cleared. You need to tell it manually, especially since you are not using the windows message notification mechanism.
Before drawing to it, you want to invalidate that part of the screen and tell windows to redraw it:
::InvalidateRect (0, &rect, false); // Redraw without erasing. If doesn't help, try true
::UpdateWindow (0);
while (true)...
I ended up running into a lot of problems, while trying to make this work. If someone ends up visiting this page looking for an answer to the same problems I ran into, I hope you have an easier time than I had. This is the code that worked for me:
#include <Windows.h>
INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR lpCmdLine, INT nCmdShow)
{
// Define and initialize variables
HDC hdc;
HDC hdcMem;
HBITMAP hbmMem;
HANDLE hOld;
RECT rect;
SIZE sz;
int win_width = 0;
int win_height = 0;
int font_size = 20;
int location_x = 40;
int location_y = 40;
int border = font_size / 4;
int duration = 10000; // In miliseconds. The notification will always stay up more time
wchar_t* font_face = L"Consolas";
wchar_t message[100];
// Save command-line arguments to message; They are showed by the notification
MultiByteToWideChar(0, 0,
lpCmdLine,
strlen(lpCmdLine),
message,
100
);
message[strlen(lpCmdLine)] = L'\0';
// Acquire screen
hdc = ::GetDC(0);
//Create necessary font
HFONT hFont = CreateFont(font_size, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, font_face);
HFONT hTmp = (HFONT)SelectObject(hdc, hFont);
// Calculate size of the text
GetTextExtentPoint32(hdc, message, wcslen(message), &sz);
win_width = sz.cx;
win_height = sz.cy;
rect = { 0, 0, sz.cx, sz.cx };
// Create an off-screen DC for double-buffering
hdcMem = CreateCompatibleDC(hdc);
hbmMem = CreateCompatibleBitmap(hdc, win_width + 2 * border, win_height + 2 * border);
// Configure off-screen DC
SetBkMode(hdcMem, OPAQUE);
SetTextColor(hdcMem, RGB(125, 125, 255));
SetBkColor(hdcMem, RGB(0, 0, 0));
SelectObject(hdcMem, hFont);
hOld = SelectObject(hdcMem, hbmMem);
// Draw loop
for (int i = 0; i < duration; i++)
{
// Draw into hdcMem
DrawText(hdcMem, message, -1, &rect, DT_SINGLELINE);
// Transfer the off-screen DC to the screen
BitBlt(hdc, location_x, location_y, win_width + 2 * border, win_height + 2 * border, hdcMem, -5, -5, SRCCOPY);
// Don't eat all the cpu!
Sleep(1);
}
// Delete notification right after time expires
::InvalidateRect(0, &rect, false);
::UpdateWindow(0);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
// Release created objects
DeleteObject(SelectObject(hdc, hTmp));
::ReleaseDC(0, hdc);
return 0;
}
It can still be improved, a lot. The only thing that appears is the rectangle with the notification.
The arguments that you pass to the program will be shown as a message. Everything related with hdcMem was implemented to avoid flickering. I can't yet change the background of the bigger rectangle.
So, I'm very new to WinAPI and I've succeeded in loading a sprite which I can move with the arrow keys. My teacher told me to be very careful with 'memory leaks' and I'm not sure how to free memory the right way because C++ has done it for me when I was doing console programming. I ran the program and it froze my computer for a minute after moving the character for a while so I guess I've done something wrong. Can you tell me how to properly free memory (if I've done it wrong) and if there's anything bad with my bitmap method that needs optimising? Thanks! Here's the code.
void LoadAndBlitBitmap(LPCSTR szFileName, HWND hwnd, HDC winDC, int xPos, int yPos)
{
//DEFINITIONS
/*TransparentBlt(destDC, destXpos, destYpos, sizeX, sizeY, srcDC, 0, 0, sizeX, sizeY, RGB(a, b, c)) = TRANSPARENT BITMAP BLIT. Ex. RGB(255, 255, 255) if background is white*/
/*BitBlt(destDC, destXpos, destYpos, sizeX, sizeY, srcDC, 0, 0, SRCCOPY); = SOLID BITMAP BLIT WITH NO TRANSPARENCY*/
//END DEFINITIONS
//-----Get the size of the window---------
RECT rect;
int win_width = 0;
int win_height = 0;
if(GetWindowRect(hwnd, &rect))
{
win_width = rect.right - rect.left; //Subtracting the right coordinate with the left gives the width
win_height = rect.bottom - rect.top; //Subtracting the bottom coordinate with the top gives the height
}
//----------------------------------------
HDC hdcMem = CreateCompatibleDC(winDC); //Create the DC that will hold the off-screen printing. (Double Buffering "Anti-Flicker" Method)
HBITMAP hbmMem = CreateCompatibleBitmap(winDC, win_width, win_height); //Create the bitmap with the size of the window
HANDLE hOld = SelectObject(hdcMem, hbmMem); //Set the paintable bitmap to the off-screen DC
//Draw to the off-screen DC
HBITMAP bitmap = (HBITMAP)LoadImage(NULL, szFileName, IMAGE_BITMAP, 69, 69, LR_LOADFROMFILE); //Load the .bmp from a file
HDC blockDC = CreateCompatibleDC(NULL); //Create a DC to hold the .bmp
SelectObject(blockDC, bitmap); //Select the .bmp to the DC
TransparentBlt(hdcMem, xPos, yPos, 69, 69, blockDC, 0, 0, 69, 69, RGB(255, 255, 255)); //Blit the .bmp to the DC*/
//Transfer off-screen DC to the screen
BitBlt(winDC, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Uninitialize and deallocate resources
SelectObject(hdcMem, hOld);
DeleteDC(hdcMem);
SelectObject(blockDC, hOld);
DeleteDC(blockDC);
DeleteObject(bitmap);
}
Two things are wrong:
SelectObject(blockDC, hOld);
hOld did not come from blockDC, it came from hdcMem. You are not even saving the old bitmap from blockDC. Change to:
HBITMAP hOld2 = SelectObject(blockDC, bitmap);
// and
SelectObject(blockDC, hOld2);
Secondly, you are not deleting hbmMem anywhere. At the bottom, add:
DeleteObject(hbmMem);
Actually, a third thing is wrong too - you're not checking for failure in any of the API calls you make. You should check if things like CreateCompatibleDC return NULL, and abort and cleanup if so.
I'm trying to set pixel by mouse click, but nothing happens when I click. Here is part of my code.
First, I control window size changing in WM_SIZE.
Than, at first time when I want to set pixel by mouse I get window's width and height, then copy window's content to memory HDC and HBITMAP (in Store Window) (HBITMAP size equal to (width,height)). In fact, I copy to memory only clear window.
And than in any case I set pixel to memory DC. In next WM_PAINT message handling I'm drawing memory DC to screen.
.....
case WM_SIZE:
{
CheckWidthHeight();
break;
}
case WM_MBUTTONDOWN:
{
if (firstTimeDraw)
{
CheckWidthHeight();
StoreWindow();
firstTimeDraw = false;
}
SetPixel(memoryDC, LOWORD(lParam), HIWORD(lParam), RGB(0,0,0));
break;
}
case WM_PAINT:
{
RestoreWindow();
break;
}
.....
where my functions and variables is:
HDC memoryDC;
HBITMAP memoryBitmap;
int width = 0, height = 0;
bool firstTimeDraw = true;
void CheckWidthHeight()
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
}
//Copy real window content to memory window
void StoreWindow()
{
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
BitBlt(memoryDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hDC);
}
//Copy memory windows content to real window at the screen
void RestoreWindow()
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
memoryDC = CreateCompatibleDC(hDC);
SelectObject(memoryDC, memoryBitmap);
BitBlt(hDC, 0, 0, width, height, memoryDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
What I'm doing wrong?
UPD:
A shot in the dark: You're handling the middle button click. Are you by any chance clicking on the left or right mouse buttons? :)
Ok. Now I use WM_LBUTTONUP or WM_LBUTTONDOWN. Nothing happens again.
UPD2:
When you change the memory DC, you'll also want to invalidate the part of the window that is affected so that Windows will generate a WM_PAINT message for it. InvalidateRect would be a good place to start.
I placed this code
RECT rect;
GetClientRect(hwnd, &rect);
InvalidateRect(hwnd, &rect, true);
before EndPaint. Nothing. Than I move it after EndPaint. Nothing.
In the WM_PAINT handler, you need to use a DC provided by BeginPaint and call EndPaint when you're done with it.
I do it in RestoreWindow().
I don't know yet what's the problem...
UPD3:
InvalidateRect() needs to happen in the WM_?BUTTONDOWN handler after the SetPixel (not in RestoreWindow())- it's what tells windows that you want to get a WM_PAINT in the first place.
Ok. I've done it before you wrote this message. Still don't work.
UPD4:
Thank you a lot, Remy! Thank you to all the rest. Now all right!!
Two things.
When you change the memory DC, you'll also want to invalidate the part of the window that is affected so that Windows will generate a WM_PAINT message for it. InvalidateRect would be a good place to start.
In the WM_PAINT handler, you need to use a DC provided by BeginPaint and call EndPaint when you're done with it.
When you call RestoreWindow() to draw the bitmap onscreen, you are wiping out your memoryDC variable that you used to draw the pixels with. The bitmap is still selected into the original HDC that you have now lost, and a bitmap cannot be selected into multiple HDCs at the same time (the MSDN documentation for SelectObject() says as much). So you are not actually drawing the bitmap onscreen at all.
There is no need to call CreateCompatibleDC() or SelectObject() inside of RestoreWindow() because you already have the bitmap and memory HDC set up inside of StoreWindow(), so they use them as-is instead.
Try this:
HDC memoryDC = NULL;
HBITMAP memoryBitmap = NULL;
int width = 0, height = 0;
void CheckWidthHeight()
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
}
void StoreWindow()
{
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
BitBlt(memoryDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hDC);
}
void RestoreWindow()
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
if (memoryDC)
BitBlt(hDC, 0, 0, width, height, memoryDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
...
case WM_SIZE:
{
CheckWidthHeight();
break;
}
case WM_LBUTTONDOWN:
{
if (!memoryDC)
StoreWindow();
if (memoryDC)
{
SetPixel(memoryDC, LOWORD(lParam), HIWORD(lParam), RGB(0,0,0));
RECT rect;
rect.left = LOWORD(lParam);
rect.top = HIWORD(lParam);
rect.right = rect.left + 1;
rect.bottom = rect.top + 1;
InvalidateRect(hwnd, &rect, TRUE);
}
break;
}
case WM_PAINT:
{
RestoreWindow();
break;
}
...
Is there a way to receive the colour values for each pixel in the client area of a window, with gdi?
As noted in comment by #JerryCoffin. Here's a simple example
hDC = GetDC(hwnd);
hBitmap = CreateCompatibleBitmap(hDC, width, height);
hMemDC = CreateCompatibleDC(hDC);
hOld = SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, width, height, hDC, x, y, SRCCOPY);
// Clean up
DeleteDC(hMemDC);
ReleaseDC(hwnd, hDC);
You should have a bitmap object selected into memory DC for which you can use GetPixel GDI function and then you can also extract the color values using GetRValue() , GetGValue() , and GetBValue() macros.