GDI+闪烁
GDI+ flickering
所以我正在尝试制作 Gyazo(截图工具)的廉价副本
问题是光标坐标在闪烁,如何防止?我已经试过了 WM_ERASEBKGND
但没有任何帮助。
还有我的代码还有什么问题吗?有什么不好的做法/技巧吗?
#include <Windows.h>
#include <string>
#include <gdiplus.h>
#pragma comment (lib, "Gdiplus.lib")
// Store the "screenshot" when first launching the program
HBITMAP hbm;
// This draws the cursor coordinates close to the cursor
void DrawCursorCoords(Gdiplus::Graphics &graphics, Gdiplus::Bitmap &bitmap, Gdiplus::Color c)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
std::wstring x = std::to_wstring(cursorPos.x);
std::wstring y = std::to_wstring(cursorPos.y);
graphics.DrawString(x.c_str(), x.length(), &Gdiplus::Font(L"Consolas", 16), Gdiplus::PointF(cursorPos.x, cursorPos.y), &Gdiplus::SolidBrush(c));
graphics.DrawString(y.c_str(), y.length(), &Gdiplus::Font(L"Consolas", 16), Gdiplus::PointF(cursorPos.x, cursorPos.y + 16), &Gdiplus::SolidBrush(c));
}
// Paint our stuff
void Paint(HDC &hdc)
{
Gdiplus::Graphics * gfx = new Gdiplus::Graphics(hdc);
Gdiplus::Bitmap * bmap = new Gdiplus::Bitmap(hbm, (HPALETTE)0);
gfx->DrawImage(bmap, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
if (GetAsyncKeyState(VK_LBUTTON))
DrawCursorCoords(*gfx, *bmap, Gdiplus::Color::Red);
else
DrawCursorCoords(*gfx, *bmap, Gdiplus::Color::Green);
delete gfx;
delete bmap;
}
LRESULT APIENTRY WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
Paint(hdc);
EndPaint(hwnd, &ps);
break;
}
case WM_TIMER:
{
InvalidateRect(hwnd, NULL, NULL);
break;
}
case WM_CLOSE:
{
DestroyWindow(hwnd);
break;
}
case WM_RBUTTONUP:
case WM_KEYDOWN:
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
case WM_ERASEBKGND: return TRUE;
default: return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0L;
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
char className[] = "_className";
HWND hwnd = NULL;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)(0);
wc.lpszMenuName = NULL;
wc.lpszClassName = className;
if (RegisterClass(&wc))
{
hwnd = CreateWindowEx(
WS_EX_TRANSPARENT | WS_EX_TOPMOST,
className, NULL,
WS_POPUP | WS_VISIBLE,
0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
NULL, NULL, hInstance, NULL);
}
if (!hwnd) return FALSE;
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
SetTimer(hwnd, 1, 1, NULL);
return TRUE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Take a screenshot and store it to 'hbm'
HWND hwndDesktop = GetDesktopWindow();
HDC hdcDesktop = GetDC(hwndDesktop);
HDC hdcCapture = CreateCompatibleDC(hdcDesktop);
hbm = CreateCompatibleBitmap(hdcDesktop, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
SelectObject(hdcCapture, hbm);
BitBlt(hdcCapture, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
hdcDesktop, 0, 0, SRCCOPY | CAPTUREBLT);
// Start GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
MSG msg;
InitInstance(hInstance, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Gdiplus::GdiplusShutdown(gdiplusToken);
DeleteObject(hbm);
ReleaseDC(hwndDesktop, hdcDesktop);
DeleteDC(hdcDesktop);
DeleteDC(hdcCapture);
return msg.wParam;
}
您需要使用缓冲区来绘制。你可以创建一个内存dc,或者使用BeginBufferedPaint
:
#include <uxtheme.h>
#pragma comment (lib, "uxtheme.lib")
...
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
HDC memdc;
auto hbuff = BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &memdc);
Paint(memdc);
EndBufferedPaint(hbuff, TRUE);
EndPaint(hwnd, &ps);
break;
}
这应该可以解决闪烁问题。我建议删除计时器并改为在鼠标移动中更新绘画:
case WM_MOUSEMOVE:
InvalidateRect(hwnd, NULL, NULL);
break;
您可能还想探索带有 SetLayeredWindowAttributes
的 WS_EX_LAYERED
标志,这将创建一个透明的 window,显示其下方的桌面。它不需要 GDI+ 来绘制简单的文本。
此外,Gdiplus 的大部分 类 都有不同的构造函数,可以让您避免使用 new
/delete
。示例:
void DrawCursorCoords(Gdiplus::Graphics &graphics, Gdiplus::Bitmap&, Gdiplus::Color c)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
std::wstring x = std::to_wstring(cursorPos.x);
std::wstring y = std::to_wstring(cursorPos.y);
Gdiplus::Font font(L"Consolas", (Gdiplus::REAL)16);
Gdiplus::SolidBrush brush(c);
graphics.DrawString(x.c_str(), (int)x.length(), &font,
Gdiplus::PointF((Gdiplus::REAL)cursorPos.x, (Gdiplus::REAL)cursorPos.y),
&brush);
graphics.DrawString(y.c_str(), (int)y.length(), &font,
Gdiplus::PointF((Gdiplus::REAL)cursorPos.x, (Gdiplus::REAL)(cursorPos.y + 16)),
&brush);
}
void Paint(HDC &hdc)
{
Gdiplus::Graphics gfx(hdc);
Gdiplus::Bitmap bmap(hbm, (HPALETTE)0);
gfx.DrawImage(&bmap, 0, 0,
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
if(GetAsyncKeyState(VK_LBUTTON))
DrawCursorCoords(gfx, bmap, (Gdiplus::Color)Gdiplus::Color::Red);
else
DrawCursorCoords(gfx, bmap, (Gdiplus::Color)Gdiplus::Color::Green);
}
或者您可以声明 Gdiplus::Bitmap *bmap = new Gdiplus::Bitmap(hbm, NULL);
这会复制 hbm
,因此您可以通过将 bmap
声明为全局来提高效率,而 create/destroy 仅声明它一次。
ReleaseDC(hwndDesktop, hdcDesktop);
DeleteDC(hdcDesktop); //<- not required
DeleteDC(hdcDesktop)
不是必需的。 hdcDesktop
来自 GetDC
,已被 ReleaseDC
清理
hbm = CreateCompatibleBitmap(...)
SelectObject(hdcCapture, hbm);
...
DeleteObject(hbm);
您还应该按如下方式恢复旧位图:
hbm = CreateCompatibleBitmap(...)
auto oldbitmap = SelectObject(hdcCapture, hbm);
...
//cleanup
SelectObject(hdcCapture, oldbitmap);
DeleteObject(hbm);
尽管如果您不恢复旧位图,windows 无论如何都会尝试修复错误,所以大多数时候不会有任何问题。
所以我正在尝试制作 Gyazo(截图工具)的廉价副本
问题是光标坐标在闪烁,如何防止?我已经试过了 WM_ERASEBKGND
但没有任何帮助。
还有我的代码还有什么问题吗?有什么不好的做法/技巧吗?
#include <Windows.h>
#include <string>
#include <gdiplus.h>
#pragma comment (lib, "Gdiplus.lib")
// Store the "screenshot" when first launching the program
HBITMAP hbm;
// This draws the cursor coordinates close to the cursor
void DrawCursorCoords(Gdiplus::Graphics &graphics, Gdiplus::Bitmap &bitmap, Gdiplus::Color c)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
std::wstring x = std::to_wstring(cursorPos.x);
std::wstring y = std::to_wstring(cursorPos.y);
graphics.DrawString(x.c_str(), x.length(), &Gdiplus::Font(L"Consolas", 16), Gdiplus::PointF(cursorPos.x, cursorPos.y), &Gdiplus::SolidBrush(c));
graphics.DrawString(y.c_str(), y.length(), &Gdiplus::Font(L"Consolas", 16), Gdiplus::PointF(cursorPos.x, cursorPos.y + 16), &Gdiplus::SolidBrush(c));
}
// Paint our stuff
void Paint(HDC &hdc)
{
Gdiplus::Graphics * gfx = new Gdiplus::Graphics(hdc);
Gdiplus::Bitmap * bmap = new Gdiplus::Bitmap(hbm, (HPALETTE)0);
gfx->DrawImage(bmap, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
if (GetAsyncKeyState(VK_LBUTTON))
DrawCursorCoords(*gfx, *bmap, Gdiplus::Color::Red);
else
DrawCursorCoords(*gfx, *bmap, Gdiplus::Color::Green);
delete gfx;
delete bmap;
}
LRESULT APIENTRY WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
Paint(hdc);
EndPaint(hwnd, &ps);
break;
}
case WM_TIMER:
{
InvalidateRect(hwnd, NULL, NULL);
break;
}
case WM_CLOSE:
{
DestroyWindow(hwnd);
break;
}
case WM_RBUTTONUP:
case WM_KEYDOWN:
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
case WM_ERASEBKGND: return TRUE;
default: return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0L;
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
char className[] = "_className";
HWND hwnd = NULL;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)(0);
wc.lpszMenuName = NULL;
wc.lpszClassName = className;
if (RegisterClass(&wc))
{
hwnd = CreateWindowEx(
WS_EX_TRANSPARENT | WS_EX_TOPMOST,
className, NULL,
WS_POPUP | WS_VISIBLE,
0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
NULL, NULL, hInstance, NULL);
}
if (!hwnd) return FALSE;
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
SetTimer(hwnd, 1, 1, NULL);
return TRUE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Take a screenshot and store it to 'hbm'
HWND hwndDesktop = GetDesktopWindow();
HDC hdcDesktop = GetDC(hwndDesktop);
HDC hdcCapture = CreateCompatibleDC(hdcDesktop);
hbm = CreateCompatibleBitmap(hdcDesktop, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
SelectObject(hdcCapture, hbm);
BitBlt(hdcCapture, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
hdcDesktop, 0, 0, SRCCOPY | CAPTUREBLT);
// Start GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
MSG msg;
InitInstance(hInstance, nCmdShow);
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Gdiplus::GdiplusShutdown(gdiplusToken);
DeleteObject(hbm);
ReleaseDC(hwndDesktop, hdcDesktop);
DeleteDC(hdcDesktop);
DeleteDC(hdcCapture);
return msg.wParam;
}
您需要使用缓冲区来绘制。你可以创建一个内存dc,或者使用BeginBufferedPaint
:
#include <uxtheme.h>
#pragma comment (lib, "uxtheme.lib")
...
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
HDC memdc;
auto hbuff = BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &memdc);
Paint(memdc);
EndBufferedPaint(hbuff, TRUE);
EndPaint(hwnd, &ps);
break;
}
这应该可以解决闪烁问题。我建议删除计时器并改为在鼠标移动中更新绘画:
case WM_MOUSEMOVE:
InvalidateRect(hwnd, NULL, NULL);
break;
您可能还想探索带有 SetLayeredWindowAttributes
的 WS_EX_LAYERED
标志,这将创建一个透明的 window,显示其下方的桌面。它不需要 GDI+ 来绘制简单的文本。
此外,Gdiplus 的大部分 类 都有不同的构造函数,可以让您避免使用 new
/delete
。示例:
void DrawCursorCoords(Gdiplus::Graphics &graphics, Gdiplus::Bitmap&, Gdiplus::Color c)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
std::wstring x = std::to_wstring(cursorPos.x);
std::wstring y = std::to_wstring(cursorPos.y);
Gdiplus::Font font(L"Consolas", (Gdiplus::REAL)16);
Gdiplus::SolidBrush brush(c);
graphics.DrawString(x.c_str(), (int)x.length(), &font,
Gdiplus::PointF((Gdiplus::REAL)cursorPos.x, (Gdiplus::REAL)cursorPos.y),
&brush);
graphics.DrawString(y.c_str(), (int)y.length(), &font,
Gdiplus::PointF((Gdiplus::REAL)cursorPos.x, (Gdiplus::REAL)(cursorPos.y + 16)),
&brush);
}
void Paint(HDC &hdc)
{
Gdiplus::Graphics gfx(hdc);
Gdiplus::Bitmap bmap(hbm, (HPALETTE)0);
gfx.DrawImage(&bmap, 0, 0,
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
if(GetAsyncKeyState(VK_LBUTTON))
DrawCursorCoords(gfx, bmap, (Gdiplus::Color)Gdiplus::Color::Red);
else
DrawCursorCoords(gfx, bmap, (Gdiplus::Color)Gdiplus::Color::Green);
}
或者您可以声明 Gdiplus::Bitmap *bmap = new Gdiplus::Bitmap(hbm, NULL);
这会复制 hbm
,因此您可以通过将 bmap
声明为全局来提高效率,而 create/destroy 仅声明它一次。
ReleaseDC(hwndDesktop, hdcDesktop); DeleteDC(hdcDesktop); //<- not required
DeleteDC(hdcDesktop)
不是必需的。 hdcDesktop
来自 GetDC
,已被 ReleaseDC
hbm = CreateCompatibleBitmap(...) SelectObject(hdcCapture, hbm); ... DeleteObject(hbm);
您还应该按如下方式恢复旧位图:
hbm = CreateCompatibleBitmap(...)
auto oldbitmap = SelectObject(hdcCapture, hbm);
...
//cleanup
SelectObject(hdcCapture, oldbitmap);
DeleteObject(hbm);
尽管如果您不恢复旧位图,windows 无论如何都会尝试修复错误,所以大多数时候不会有任何问题。