我通过稍微修改 Microsoft 提供的Magnification API 示例创建了一个应用程序,以实现对在放大镜窗口中捕获和显示的每个帧的自定义转换。
我用 将MagSetImageScalingCallback
my 函数设置为回调。调用回调没有问题,并且可以轻松地操作源图像和目标图像,因为原始位作为指针(srcdata 和 destdata)传递给回调。
使用设置为 16 ms (~60Hz) 的计时器刷新窗口。使用InvalidateRect
API 调用刷新。问题是当放大镜窗口出现闪烁时。这种情况尤其发生在开始菜单出现时,如果启用“Peek”或每次在前台有一个具有动态内容的窗口时。
我试图拦截WM_ERASEBKGND并InvalidateRect
使用 FALSE 作为第三个参数调用,但这没有帮助。我试图UpdateWindow
在无效之前添加一个调用,但没有任何改变。
Windows 10 附带的放大镜应用程序没有同样的问题。我想知道为什么会发生这种情况以及如何摆脱闪烁。
为了重现问题,请从上面的链接下载 Magnification API 示例,然后将文件MagnifierSample.cpp中的内容替换为以下源代码:
// Ensure that the following definition is in effect before winuser.h is included.
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <wincodec.h>
#include <magnification.h>
// For simplicity, the sample uses a constant magnification factor.
#define RESTOREDWINDOWSTYLES WS_POPUP
// Global variables and strings.
HINSTANCE hInst;
const TCHAR WindowClassName[]= TEXT("MagnifierWindow");
const TCHAR WindowTitle[]= TEXT("Screen Magnifier Sample");
const UINT timerInterval = 16; // close to the refresh rate @60hz
HWND hwndMag;
HWND hwndHost;
RECT magWindowRect;
RECT hostWindowRect;
// Forward declarations.
ATOM RegisterHostWindowClass(HINSTANCE hInstance);
BOOL SetupMagnifier(HINSTANCE hinst);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK UpdateMagWindow(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
BOOL isFullScreen = FALSE;
//
// FUNCTION: WinMain()
//
// PURPOSE: Entry point for the application.
//
int APIENTRY WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE /*hPrevInstance*/,
_In_ LPSTR /*lpCmdLine*/,
_In_ int nCmdShow)
{
if (FALSE == MagInitialize())
{
return 0;
}
if (FALSE == SetupMagnifier(hInstance))
{
return 0;
}
ShowWindow(hwndHost, nCmdShow);
UpdateWindow(hwndHost);
// Create a timer to update the control.
UINT_PTR timerId = SetTimer(hwndHost, 0, timerInterval, UpdateMagWindow);
// Main message loop.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Shut down.
KillTimer(NULL, timerId);
MagUninitialize();
return (int) msg.wParam;
}
//
// FUNCTION: HostWndProc()
//
// PURPOSE: Window procedure for the window that hosts the magnifier control.
//
LRESULT CALLBACK HostWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//
// FUNCTION: RegisterHostWindowClass()
//
// PURPOSE: Registers the window class for the window that contains the magnification control.
//
ATOM RegisterHostWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = HostWndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wcex.lpszClassName = WindowClassName;
return RegisterClassEx(&wcex);
}
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty) {
UINT row;
UINT column;
BYTE *matrix;
memset(destdata, 0, destheader.cbSize);
matrix = (BYTE *) destdata;
//Set alpha bits for the resulting image to 255 (fully opaque)
for (row = 0; row < destheader.height; row++) {
for (column = 3; column < destheader.width; column += 4) {
matrix[(row * destheader.width) + column] = 0xFF;
}
}
Sleep(20);
return TRUE;
}
//
// FUNCTION: SetupMagnifier
//
// PURPOSE: Creates the windows and initializes magnification.
//
BOOL SetupMagnifier(HINSTANCE hinst)
{
// Set bounds of host window according to screen size.
hostWindowRect.top = 0;
hostWindowRect.bottom = GetSystemMetrics(SM_CYSCREEN);
hostWindowRect.left = 0;
hostWindowRect.right = GetSystemMetrics(SM_CXSCREEN);
// Create the host window.
RegisterHostWindowClass(hinst);
hwndHost = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED, WindowClassName, WindowTitle, RESTOREDWINDOWSTYLES, 0, 0, hostWindowRect.right, hostWindowRect.bottom, NULL, NULL, hInst, NULL);
if (!hwndHost)
{
return FALSE;
}
// Make the window opaque.
SetLayeredWindowAttributes(hwndHost, 0, 255, LWA_ALPHA);
SetWindowLong(hwndHost, GWL_EXSTYLE, GetWindowLong(hwndHost, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE);
// Create a magnifier control that fills the client area.
magWindowRect = hostWindowRect;
hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), WS_CHILD | WS_VISIBLE, magWindowRect.left, magWindowRect.top, magWindowRect.right, magWindowRect.bottom, hwndHost, NULL, hInst, NULL );
if (!hwndMag)
{
return FALSE;
}
MagSetImageScalingCallback(hwndMag, &myCallBack);
// Set the source rectangle for the magnifier control.
MagSetWindowSource(hwndMag, magWindowRect);
return 1;
}
//
// FUNCTION: UpdateMagWindow()
//
// PURPOSE: Sets the source rectangle and updates the window. Called by a timer.
//
void CALLBACK UpdateMagWindow(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR /*idEvent*/, DWORD /*dwTime*/)
{
// Reclaim topmost status, to prevent unmagnified menus from remaining in view.
SetWindowPos(hwndHost, HWND_TOPMOST, 0, 0, magWindowRect.right, magWindowRect.bottom, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
// Force redraw.
InvalidateRect(hwndMag, NULL, FALSE);
}
请注意,我添加了设置回调的代码
BOOL ret = MagSetImageScalingCallback(hwndMag, &myCallBack);
然后我创建了这个回调:
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
该Sleep
语句“模拟”了我的自定义转换所花费的时间。
如果您使用 Edge 打开 youtube 视频,然后运行MagnifierSample.exe可执行文件,您应该会看到一个黑屏闪烁,当屏幕闪烁时,您应该看到放大镜窗口后面的内容(黑屏)。这正是我的应用程序中正在发生的事情。睡眠值设置为 20,但我不知道回调实际需要多长时间。我只是猜测它可能需要超过 16 毫秒。
要重现该问题,请执行以下步骤:
- 手动运行 MagnifierSample.exe(不要使用调试器运行它)
- 您应该会看到黑屏
- 按窗口键
- 在前台设置一个带有视频或动态内容的窗口
- 点击黑色窗口让任务栏消失
- 您应该看到屏幕有时会闪烁
是否可以设置类似于MagSetImageScalingCallback
普通窗口句柄的回调?我知道我可以使用 WindowProc 回调来拦截发送到窗口的消息但是我无权访问 rawbits,除非我使用CreateDIBSection
,SelectObject
等等......但当时图像已经发送到目标窗口,我没有机会改造它。