I created an application by slightly modifying the Magnification API sample provided by Microsoft to implement a custom transformation of each frame captured and displayed in the Magnifier window.
I used the MagSetImageScalingCallback
to set the my function as a callback. The callback is invoked without problem and the source and destination image can be easily manipulated because the raw bits are passed to the callback as pointers (srcdata and destdata).
The window is refreshed with a timer which is set to 16 ms (~60Hz). The refresh is invoked by using the InvalidateRect
API. The problem is that when the magnifier window suffer from flickering. This happens especially when the start menu appears, if "Peek" is enabled or every time there is a window on foreground which has dynamic content.
I tried to intercept the WM_ERASEBKGND and invoke the InvalidateRect
with FALSE as third parameter but this didn't help. I tried to add an UpdateWindow
invocation before the invalidate but nothing changed.
The Magnifier application provided with Windows 10 doesn't have the same problem. I'm wondering why this is happening and how can I get rid of flickering.
In order to reproduce che problem download the Magnification API sample from the link above then replace the content in the file MagnifierSample.cpp with this source code:
// 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);
}
Notice that I added the code to set the callback
BOOL ret = MagSetImageScalingCallback(hwndMag, &myCallBack);
then I created this callback:
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
The Sleep
statement "simulate" the time taken by my custom transformation.
If you open a youtube video with Edge and then you run the MagnifierSample.exe executable you should see a black screen flickering sometime and when the screen flickers you should see the content behind the magnifier window (the black screen). This is exactly what is happening in my application. The sleep value is set to 20 but I don't known how long the callback is actually taking. I just guessed that it might take more than 16 ms.
To reproduce the problem perform the following steps:
- Run the MagnifierSample.exe manually (don't use the debugger to run it)
- You should see a black screen
- Press the window key
- Set a window with a video or a dynamic content in foreground
- Click on the black window to let the taskbar disappear
- You should see the screen flashing sometime
Is it possible to set a callback similar to the MagSetImageScalingCallback
for a normal window handle ? I know I can use the WindowProc callback to intercept the messages sent to the window however I don't have access to the rawbits unless I use CreateDIBSection
, SelectObject
etc ...but at that time the image has been already sent to the destination window and I don't have the chance to transform it.