一、简介
本教程是关于 DPI、屏幕缩放、多显示器设置的,并试图回答“为什么我的脚本在一台计算机上工作,但在另一台计算机上停止工作”的问题。这是一个相当复杂的话题,我也不清楚它的一些细节,但无论如何我们都会尝试。
为了便于参考,我将在此处附上本教程中使用的工具:Dpi.ahk 库和随附的 WindowSpyDpi 工具。
本教程将介绍什么是DPI和显示分辨率,讨论程序的DPI感知,如何调整DPI的脚本,最后我们将看看如何制作不关心DPI的脚本(主要是如何以这种方式保存坐标,以及如何执行ImageSearch而不考虑DPI)。我们不会讨论如何使 AHK GUI 更具 DPI 感知能力。
二、什么是 DPI 和分辨率?
首先,让我们定义 DPI,即每英寸点数的缩写:DPI 衡量一英寸线中可容纳多少个“点”(或像素)。重要的是,DPI 不是屏幕分辨率,也不代表显示器中的物理像素数量。
主要由于历史原因,显示器的默认 DPI 被定义为 96DPI,在 Windows 的设置中,这对应于 100% 的屏幕缩放。这意味着缩放到 100% 的显示器将在 96 像素上显示 1 英寸的内容。
你可能会问为什么我使用“逻辑英寸”这个词而不仅仅是“英寸”。这是因为 Windows 通常不考虑实际的物理屏幕尺寸。一些显示器不报告其物理尺寸,在某些情况下,谈论物理尺寸甚至没有意义:例如,如果使用投影仪,那么它与墙壁的距离决定了屏幕的物理尺寸,而 Windows 没有很好的方法来获取该信息。这就是为什么 Windows 以逻辑英寸为单位进行处理,而 DPI 确定一个这样的英寸的像素数。
相应地,对于字体大小,1pt(磅)定义为 1/72 逻辑英寸,因此 72pt 字体将在 96DPI 屏幕上显示为 96 像素高。如果显示在缩放到 150% 的显示器上,则 72pt 字体的高度为 96*1.5=144 像素。
那么显示分辨率和DPI有什么区别呢?显示分辨率定义 Windows 用于显示输出的物理像素数。如果实际使用 1920×1080 屏幕,但将显示分辨率设置为 960×540(小两倍),则图像质量会降低两倍(显示内容的像素将减少两倍)。显示分辨率不能真正设置为高于屏幕的物理分辨率,这没有意义。
另一方面,正如我们已经知道的那样,DPI 意味着一英寸的内容使用了多少像素。DPI 越高,用于显示相同数量内容的像素就越多,因此显示内容的质量就越高。
如果我们有两个屏幕,物理尺寸相同,但一个物理分辨率为 1200×800,另一个分辨率为 600×400,那么运行 @ 200% 缩放的 1200×800 看起来会更清晰:对于相同的图像,它的像素密度将是其两倍。
了解分辨率与 DPI 的一种简单方法是玩弄您的显示器设置,在 Windows 10 中,这些设置位于“开始”菜单的“更改显示分辨率”下。我将以我的 14 英寸笔记本电脑为例,它具有 16:9 的纵横比和 1920×1080 像素分辨率(或每英寸像素 (PPI) 为 ~157)。默认显示设置为 DPI 为 144(比例为 150%),显示分辨率为 1920×1080。但是,如果我将分辨率更改为 1600×900 并缩放到 125%,屏幕上的所有内容看起来都与以前完全相同 (1920/150 = 1600/125),但一切都更加模糊。这是因为现在我们用于显示内容的像素比以前少。
希望这能清楚地说明为什么DPI是必要的:更高的DPI可以在高分辨率显示器上获得更清晰的图像。此外,由于现代高分辨率显示器的像素比 10-20 年前多得多,因此 96 的历史 DPI 可能会变得不舒服,因为所有内容都显示得太小了。此外,一些用户(包括视障人士)可能更喜欢更大的内容,而更高的 DPI 允许在不降低质量(分辨率)的情况下做到这一点。
三、DPI 意识
假设我们不仅有一台显示器,而是两台显示器。并假设这些监视器具有不同的分辨率和 DPI。在这种情况下,在显示器之间移动窗口并尝试找出要使用鼠标移动/单击的坐标时,事情会变得棘手。
我的以下示例使用我当前的设置:主显示器是 14 英寸笔记本电脑屏幕,纵横比为 16:9,分辨率为 1920×1080 像素,DPI 为 144 (150%);辅助显示器是 34 英寸屏幕,纵横比为 21:9,分辨率为 3440×1440,DPI 为 96 (100%);运行最新的 Windows 10(截至 2023 年 9 月 1 日)。
在此设置中,AHK 内置变量 A_ScreenWidth 和 A_ScreenHeight 分别包含 1920 和 1080,因为这是主监视器的分辨率。
我们的第一项任务是将计算器应用程序移动到第二台显示器的 x100 y100 坐标。在主显示器上,这很简单:WinMove(100, 100)工作得很好。但是,默认情况下,AHK 脚本和其他程序是 DPI 系统感知的,这意味着它们期望主监视器的 DPI 无处不在,并且 Windows 会为此进行调整。因此,如果我们尝试使用WinMove(A_ScreenWidth+100, 100),我们得到一个意想不到的结果:
显然,这是错误的,最可能的罪魁祸首是不同的DPI。这可以通过使用WinMove(A_ScreenWidth*144/96+100, 100)相反,这会将窗口移动到正确的位置。为什么这种计算可以解决当前的问题,我将留给读者思考。但是,很容易看出,如果处理三台显示器或获取和计算主显示器和辅助显示器窗口的 DPI 和生成的坐标,这将变得越来越混乱。幸运的是,Windows 为此提供了一个解决方案:每个监视器的 DPI 感知。这会导致 Windows 调整坐标,同时考虑其他监视器的 DPI。
将我们的脚本设置为每监视器感知很简单:只需将以下行添加到脚本的顶部:DllCall(“SetThreadDpiAwarenessContext”, “ptr”, -3, “ptr”)
推荐额外阅读:AHK 文档 DPI 缩放部分。
是的,这修复了 WinMove、MouseMove 和其他相关功能!案件解决了?不!
四、调整 DPI
虽然 DPI 每监视器感知可以解决很多问题,但它并不能解决缩放坐标的问题。即,WindowSpy 报告调整为 DPI 的客户端和窗口坐标。这意味着,如果我们在一个 DPI 中记录一个坐标,那么它将在另一个 DPI 中不起作用。
例如,让我们尝试编写一个脚本来单击“7”按钮,该按钮需要在我的两个显示器上工作。
(请注意,此图像是在主监视器上拍摄的,因此不需要每个监视器的 DPI。
让我们使用这些客户端坐标来单击:点击(67, 494).很简单,可以在主显示器上运行,但让我们在第二台显示器上尝试一下:
当然,光标最终会位于错误的位置,因为 DPI 144 中记录的坐标将表示 DPI 96 中的不同点。通过除以 144 再乘以 96 来调整 DPI 可以解决该问题:点击(67*96/144, 494*96/144).
但是,现在代码在主显示器上不起作用!
似乎要完全解决此问题,我们需要获取窗口所在显示器的 DPI,然后相应地进行调整。这可以通过以下功能实现:
; Gets the DPI for the specified window
WinGetDpi(WinTitle?, WinText?, ExcludeTitle?, ExcludeText?) {
return (hMonitor := DllCall("MonitorFromWindow", "ptr", WinExist(WinTitle?, WinText?, ExcludeTitle?, ExcludeText?), "int", 2, "ptr") ; MONITOR_DEFAULTTONEAREST
, DllCall("Shcore.dll\GetDpiForMonitor", "ptr", hMonitor, "int", 0, "uint*", &dpiX:=0, "uint*", &dpiY:=0), dpiX)
}
使用 WinGetDpi 函数,我们可以重试点击 (67*WinGetDpi(“A”)/144, 494*WinGetDpi(“A”)/144).成功,它可以在两台显示器上正常工作!即使我们在一台显示器上启动应用程序并将其拖到另一台显示器上,或更改 DPI,或在具有不同分辨率的计算机中使用它,也是如此。将计算器的大小调整到更大的尺寸自然会破坏它,因为按钮会移动到不同的位置。
五、简化 DPI 调整
为了简化调整 DPI 的过程,我编写了 Dpi.ahk 库和随附的 WindowSpyDpi 工具。
WindowSpyDpi 调整为可识别每个监视器的 DPI,并自动将所有坐标和大小缩放为 DPI 96。然后,可以将 Dpi 库与坐标一起使用以执行所需的操作,并且代码应对任何 DPI 执行相同的操作。
例如,WindowSpyDpi 将计算器(调整为尽可能小的大小)数字 7 按钮客户端坐标报告为 x47 y329。这意味着DPI.Click(47, 329)应该在所有显示器上自动工作。
一些注意事项:
1)由于从非常高的DPI(例如192)缩减到96会损失一些精度,因此可以在WindowSpyDpi以及包含Dpi.ahk的脚本中调整A_StandardDpi变量,以及任意大的数字(例如新闻部。标准DPI := 960) 可以改用。
2) 由于所有额外的计算,Dpi 函数必然比本机函数慢。不过,这应该不会引起注意,除非每秒调用函数数千次。
3) Dpi 库可能会使用硬编码坐标破坏现有脚本,因为它会更改现有脚本未考虑的 DPI 感知。它还可能更改 GUI 和 GUI 菜单的外观,因此可能需要使用SetThreadDpiAwarenessContext(-2)在创建这些之前。
六、与 DPI 无关的 ImageSearch 和 FindText
:在某些情况下,还可以跨多个 DPI 执行 ImageSearch。这涉及使用 ImageSearch 的*W和*小时将图像缩放到正确大小的选项。这并不总是有效,并且几乎总是需要调整变化(*n) 到高级别,因为缩放后的图像永远不会与在目标 DPI 下捕获的图像完全相同。此外,抗锯齿和不同的字体间距/大小可能会使图像发生太大变化,以至于无法在原生 DPI 以外的任何其他 DPI 中找到它:这在浏览器和基于浏览器的应用程序(例如 Spotify、Discord 等)中尤其如此。我发现 FindText 在 DPI 上工作更可靠,即使在使用图像文件时也是如此。
但是,有时事情会解决。以下代码适用于屏幕比例 100% 到 225%,以查找计算器图标:
#include DPI.ahk
wTitle := "Calculator"
WinActivate wTitle
WinWaitActive wTitle
WinGetPos(&wX, &wY, &wW, &wH, wTitle)
if DPI.ImageSearch(&outX, &outY, 0, 0, wW, wH, "*150 Calculator_icon_225%.png",,216)
MouseMove outX, outY
else
MsgBox("Could not find image")
注意:216在 DPI 中。ImageSearch 是文件Calculator_icon_225%.png的 DPI,因为它是在辅助监视器上拍摄的,并且图像元数据包含主监视器 (144) 的 DPI,这将不起作用。如果图像是在主监视器上捕获的,则可以省略该参数。
我发现 FindText 是一种更简单、更宽容的图像搜索方法。以下代码在 Chrome 中查找重新加载按钮:
#include DPI.ahk
#include FindText.ahk
wTitle := "ahk_exe chrome.exe"
WinActivate wTitle
WinWaitActive wTitle
WinGetPos(&wX, &wY, &wW, &wH, wTitle)
Text:="|<ChromeReloadDPI216>**50$41.0000000000000000000000000000000000000y00000DzU2001s3kA00Dzyss00zUDvE03w07wU07U03100S006201g00A403k00k80B0030E0S00A0U0w00zz01M000002U000005000000/000000S000000w000801c000s01s003k03M007U03k00S007k01s007s0Dk007w1z0007TzQ0007U3U0003zw00000T0000000000000000000000000000004"
if (ok:=FindText(&X, &Y, wX, wY, wX+wW, wY+wH, 0.3, 0.2, Text, , 0, , , , , zoomW:=DPI.GetForWindow(wTitle)/216, zoomW)) ; The Text was taken at DPI 216 (225%)
FindText().MouseTip(X, Y)
else
MsgBox("Could not find image")
FindText 在使用图像文件进行搜索方面似乎也更成功,例如用于 Chrome 重新加载按钮:
#include DPI.ahk
#include FindText.ahk
wTitle := "ahk_exe chrome.exe"
WinActivate wTitle
WinWaitActive wTitle
WinGetPos(&wX, &wY, &wW, &wH, wTitle)
if (ok:=FindText(&X, &Y, wX, wY, wX+wW, wY+wH, 0.2, 0.1, "##10$Chrome_Reload_100%.png", , 0, , , , , zoomW:=DPI.GetForWindow(wTitle)/96, zoomW))
FindText().MouseTip(X, Y)
else
MsgBox("Could not find image")
七、附加
功能 以下内容便于在 DPI 和比例之间进行转换:
DPI Scale Percentage
96 1 100%
120 1.25 125%
144 1.5 150%
168 1.75 175%
192 2 200%
216 2.25 225%
240 2.5 250%
288 3 300%
336 3.5 350%
八、总结
希望本教程具有指导意义,在编写它时,我肯定学到了很多东西。如果有人对如何使DPI的调整更容易或如何更可靠地进行图像搜索有想法,请务必发表评论!