美文网首页
Dpi缩放问题

Dpi缩放问题

作者: 赵海洋 | 来源:发表于2021-04-11 11:53 被阅读0次

    在win10高分屏中,设置200%缩放时,我们的游戏程序(使用了cef)只显示游戏画面的四分之一。

    参考资料:

    1. 关于Windows高DPI的一些简单总结_daiafei-CSDN博客
    2. windows - How to use manifest to control disable DPI scaling and compatibility - Stack Overflow
    3. Improving the high-DPI experience in GDI based Desktop Apps - Windows Developer Blog

    DPI

    什么DPI? 全称是dots per inch (DPI), 也就是每英寸的点数,在显示器上就是每英寸的像素个数,Window上一般默认是96 dpi 作为100% 的缩放比率, 但是要注意的是该值未必是真正的显示器物理值, 只是Windows里我们的一个参考标准。

    下面我们思考为什么DPI设置高了之后, 我们看到的字体会变大? 因为系统字体是是以固定大小(宋体10号字,物理尺寸为(10/72)英寸)设计的, 当我们DPI设置高了之后 ,说明该字体要占有更多的像素, 在屏幕分辨率不变的前提下, 看起来也就大了。所以如果我们设置高DPI,通常也意味着我们的显示器是高分辨率, 里面的字体看起来太小了, 我们需要提高DPI来把内容放大。

    那么我们的程序如何才能支持高DPI? 对于高DPI的支持, 不同操作系统有不同的方案。通常来说如果我们程序支持高DPI, 意味着我们要对绘画的内容进行相应的放大, 比如字体,图片和控件等。当然, 如果我们用的是系统字体(比如GetStockObject(DEFAULT_GUI_FONT)), 那么这种情况下我们不用操心, 因为系统会对该字体在高DPI时进行相应的放大; 如果我们是用CreateFont自己创建的字体, 那就要我们自己对该字体进行放大了。

    XP是如何对高DPI进行支持的?

    XP对高DPI的支持比较差劲, 大部分情况下就是字体的放大, 当然我们程序也可以通过GetDeviceCaps(hDC, LOGPIXELSX)获取DPI后自己对绘画的内容进行缩放。

    Vista/Win7/Win8是如何对高DPI进行支持的?

    我们知道Vista/Win7我们可以禁止DWM(Desktop Window Manager), 该模式我们称之为Basic模式, 这种模式下的高DPI效果和XP一样。

    对于DWM没有禁掉的情况, Vista/Win7/Win8 对高DPI的支持又分为2种情况:

    1. 一种XP风格的高DPi支持, 这种方式我们上面讨论过了(字体放大);
    2. 还有一种是通过 DWM 虚拟化支持的 高DPI方式, 下面我们讨论下该方式:

    该种方式的高DPI支持是通过DWM的缩放实现的, 具体过程是这样的, 比如我们当前系统的DPI是200%, 我们程序运行时,系统会告诉你当前DPI仍然是96(100%), 所以我们程序会仍然按照100%的方式进行绘画, 但是但是系统给我们的坐标是根据DPI缩小过后的(也就是我们对窗口调用GetWindowRect或是通过GetSystemMetrics(SM_CXSCREEN) 得到的大小会比实际大小减半) , 当我们画完之后, DWM再对整个窗口进行200% 放大后画到屏幕上, 这样看起来我们的程序就自动支持高DPI了。

    这种方式看起来很美妙, 但是它也有缺点, 主要是经过缩放后的内容看起来会变模糊, 比如文字会有明显的锯齿。

    既然DWM虚拟化用户效果有时不是那么好, 那么我们很多时候可能会自己支持高DPI, 如何让我们的程序禁用该效果?

    事实上我们可以对每个进程对DWM虚拟化的支持进行设置和查询, 系统给我们提供了2个APi: SetProcessDPIAware IsProcessDPIAware , 通过调用 SetProcessDPIAware , 我们告诉系统不要对我们的程序进行DWM虚拟化。

    这里还有特殊情况也提一下: 我们在高DPI下通过窗口句柄取到的坐标信息是和目标程序是否支持DWM虚拟化相关联的, 我们对其他支持DWM虚拟化的程序窗口调用GetWindowRect, 取到的坐标也是经过DWM缩放后的坐标; 对禁用DWM虚拟化程序的窗口调用GetWindowRect, 取到的坐标则是没有经过缩放的原始坐标。

    最后我们再讨论下Win8.1 对高DPI的支持, WIn8.1对高DPi以3种方式支持 Process_DPI_Awareness

    typedef enum _Process_DPI_Awareness { 
      Process_DPI_Unaware            = 0,
      Process_System_DPI_Aware       = 1,
      Process_Per_Monitor_DPI_Aware  = 2
    } Process_DPI_Awareness;
    

    下面我们依次讨论这3种方式:

    第一种Unaware, 该种方式是告诉系统, 我的程序不支持DPI aware, 请通过DWM虚拟化帮我们实现。 该方式和上面Win7/Win8对高DPI的支持的实现基本一样,主要区别是它通过GetWindowRect取到的坐标都是经过DWM缩放后的, 无论对方窗口是不是支持DWM虚拟化。

    第二种方式是System DPI aware, 该方式下告诉系统, 我的程序会在启动的显示器上自己支持DPI aware, 所以不需要对我进行DWM 虚拟化。 但是当我的程序被拖动到其他DPI不一样的显示器时, 请对我们先进行system DWM虚拟化缩放。

    第三种方式是Per Monitor DPI aware, 该方式是告诉系统, 请永远不要对我进行DWM虚拟化,我会自己针对不同的Monitor的DPi缩放比率进行缩放。

    再介绍下相关API:

    最后,简单总结下, 从上面我们可以看到微软在不同操作系统上对高DPI支持的改进线路,很多方面也体现了他们对老程序兼容性上的考虑, DWM虚拟化虽然很简单, 却丢失了用户体验。

    PS, 我在我机器上测试发现,桌面程序基本上只有微软自己的程序能做到在高DPI下完美支持, 其他大部分程序(即使如Chrome)也是通过DWM虚拟化实现的高DPI支持。当然现在WPF和Window store App基本上都是内置支持高DPI的。

    Manifest控制

    通过Manifest能避免调用SetProcessDpiAwareness 来设置。

    <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
    </windowsSettings>
    </application>
    

    dpiAware 节点即控制SetProcessDpiAwareness 的参数,使用以下值分别对应宏定义:

    • Process_DPI_Unaware : False
    • Process_System_DPI_Aware: True
    • Process_Per_Monitor_DPI_Aware: True/PM

    一般使用 True/PM 即可保持程序的大小在不同显示器上都使用原像素大小(高分屏下窗口就显得小了)。

    相关文章

      网友评论

          本文标题:Dpi缩放问题

          本文链接:https://www.haomeiwen.com/subject/yedakltx.html