DPI aware screen capture

2019-07-31 19:21发布

问题:

I used AutoIt's _ScreenCapture_Capture() before without any trouble. But on Windows 10 with a 4k resolution screen I'm using DPI scaling at 200%. _ScreenCapture_capture() now gets something 2x zoomed in and half the location I asked for.

For instance :

 _ScreenCapture_Capture("c:\a.bmp", 100, 100, 200, 200)
;                             Path,  X1,  Y1,  X2,  Y2

Does not return a 100x100 pixel square of the screen (at 100,100 to 200,200). It returns a 100x100 pixel square (at 50,50 to 100,100) instead.

I did find a solution :

DllCall("User32.dll", "bool", "SetProcessDPIAware")

However this screws up the GUI looks. So I then found this code:

GUISetFont(8.5 * _GDIPlus_GraphicsGetDPIRatio()[0])
Func _GDIPlus_GraphicsGetDPIRatio($iDPIDef = 96)
  Local $aResults[2] = [1, 1]
  _GDIPlus_Startup()
  Local $hGfx = _GDIPlus_GraphicsCreateFromHWND(0)
  If @error Then Return SetError(1, @extended, $aResults)
  #forcedef $__g_hGDIPDll, $ghGDIPDll
  $aResult = DllCall($__g_hGDIPDll, "int", "GdipGetDpiX", "handle", $hGfx, "float*", 0)
  If @error Then Return SetError(2, @extended, $aResults)
  Local $iDPI = $aResult[2]
  Local $aresults[2] = [$iDPIDef / $iDPI, $iDPI / $iDPIDef]
  _GDIPlus_GraphicsDispose($hGfx)
  _GDIPlus_Shutdown()
  Return $aresults
EndFunc   ;==>_GDIPlus_GraphicsGetDPIRatio

Which works great for the GUI but doesn't help for _ScreenCapture_Capture() calls. Seems I can either have a good looking- or a functioning program, not both.

How could I combine these two solutions to get both a good GUI and a functioning program? If I knew how to do the opposite of :

DllCall("User32.dll", "bool", "SetProcessDPIAware")

Then when I need to capture a screen portion I could turn on compatibility, then immediately turn it off when done capturing. But I don't know how to 'UNsetprocessdpiaware'.

回答1:

If you are using Windows 10 A update (10.0.14393) you can spin up a separate thread and make the thread alone Per Monitor DPI aware, and leave your main GUI as dpi unaware.

See

SetThreadDpiAwarenessContext function

If you are building using a version of visual studio before 2015 you can declare a pointer to the function prototype

DPI_AWARENESS_CONTEXT WINAPI SetThreadDpiAwarenessContext(
  _In_  DPI_AWARENESS_CONTEXT  dpiContext
);

and use GetProcAddress to test for the presence of the function before you call it. Anything in that thread will give you raw physical information if you used

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE

Note that this function is not available on Windows 10 10.0.10586. You must test for its availability.