DPI 和 DIP

本文介绍物理像素和设备独立像素(DIP)之间的差异,以及 WIN2D 中处理 DPI(每英寸点数)的方式。

Win2D 的设计方式使许多应用可以忽略此区别,因为它提供了合理的默认行为,在低 DPI 设备和高 DPI 设备上运行时,这些行为将执行正确的操作。 如果你的应用具有更专门的需求,或者你对“明智的默认”含义有不同的意见,请阅读关于流利的详细信息...

什么是 DPI?

DPI 表示“每英寸点数”。 这是输出显示器(如计算机监视器或手机屏幕)像素密度的近似度量值。 DPI 越高,小点越多,就构成了显示器。

DPI 只是近似度量值,因为并非所有显示硬件都可以依赖来报告准确的信息。 某些计算机监视器根本不向操作系统报告 DPI,或者用户可能已将系统配置为使用与实际硬件不同的 DPI 进行呈现(例如,更改 UI 文本元素的大小)。 应用程序可以使用 DPI 来选择绘制大小的大小,但不应依赖它作为显示大小的确切物理度量。

DPI 值为 96 被视为中性默认值。

什么是像素?

像素是单个彩色点。 计算机图形中的图像由二维网格中排列的许多像素组成。 可以将像素视为生成所有图像的原子。

像素的物理大小可能因一个显示器而异。 当计算机连接到大型但分辨率低的监视器或外部显示器时,像素可能相当大,但在具有 1080p 的手机上,仅显示几英寸的像素,像素很小。

在 Win2D 中,每当看到使用整数数据类型指定位置或大小的 API(或包含整数的 BitmapSize 等结构)时,这意味着 API 以像素单位运行。

大多数 Win2D API 使用 DIP,而不是像素。

什么是 DIP?

DIP 表示“独立于设备的像素”。 这是一个虚拟化单元,可能与物理像素相同、更大或更小。

像素和 DIP 之间的比率由 DPI 决定:

pixels = dips * dpi / 96

当 DPI 为 96 时,像素和 DIP 相同。 使用更高的 DPI 时,单个 DIP 可能对应于多个像素(或部分像素,在 DPI 不是 96 的确切倍数的情况下)。

大多数Windows 运行时 API(包括 Win2D)使用 DIP 而不是像素。 这具有保持图形大小大致相同的物理大小的优势,无论应用在哪个显示上运行。 例如,如果应用指定按钮宽为 100 个 DIP,则当在高 DPI 设备(如手机或 4k 监视器)上运行时,此按钮将自动缩放为宽度超过 100 像素,因此它仍然是一个合理的大小,可供用户单击。 如果按钮大小以像素为单位指定,另一方面,在此类高 DPI 显示器上,它看起来非常小,因此应用必须执行更多工作,以不同的方式调整每种屏幕的布局。

在 Win2D 中,每当看到使用浮点数据类型指定位置或大小的 API(或包含浮点值的 Vector2 或 Size 等结构)时,这意味着 API 在 DIP 中运行。

若要在 DIP 和像素之间转换,请使用方法和 ConvertDipsToPixels(Single, CanvasDpiRounding) ConvertPixelsToDips(Int32)

具有 DPI 的 Win2D 资源

包含位图图像的所有 Win2D 资源也有关联的 DPI 属性:

所有其他资源类型都独立于 DPI。 例如,单个 CanvasDevice 实例可用于绘制多个不同 DPIs 的控件或呈现目标,因此设备本身没有 DPI。

同样, CanvasCommandList 没有 DPI,因为它包含矢量绘图指令,而不是位图图像。 当命令列表绘制到呈现目标或控件(具有 DPI)时,DPI 才会在光栅化过程中发挥作用。

控件 DPI

Win2D 控件(CanvasControlCanvasVirtualControlCanvasAnimatedControl)自动使用与应用正在运行的显示相同的 DPI。 这与 XAML、CoreWindow 和其他Windows 运行时 API 使用的坐标系匹配。

如果 DPI 发生更改(例如,如果应用移动到其他显示器),控件将引发 CreateResources 事件并传递一个 CanvasCreateResourcesReason DpiChanged。 应用应通过重新创建依赖于控件 DPI 的任何资源(如 rendertargets)来响应此事件。

Rendertarget DPI

可以绘制到的内容(不仅 CanvasRenderTarget 包括呈现目标类似类型 CanvasSwapChainCanvasImageSource而且具有自己的 DPI),但与这些类型的控件不同,这些类型不直接连接到显示器,因此 Win2D 无法自动确定 DPI 应是什么。 如果要绘制到稍后将复制到屏幕的呈现目标,则可能希望呈现目标使用与屏幕相同的 DPI,但如果出于其他目的(例如生成要上传到网站的图像)绘制,则默认的 96 DPI 会更合适。

为了简化这两种使用模式,Win2D 提供了两种类型的构造函数重载:

CanvasRenderTarget(ICanvasResourceCreator, width, height, dpi)
CanvasRenderTarget(ICanvasResourceCreatorWithDpi, width, height)

接口 ICanvasResourceCreatorCanvasDevice Win2D 控件实现。 由于设备本身没有任何特定的 DPI,因此在创建呈现目标时必须显式指定 DPI。

例如,若要创建默认 DPI 呈现目标,其中 DIP 和像素将始终相同:

const float defaultDpi = 96;
var rtWithFixedDpi = new CanvasRenderTarget(canvasDevice, width, height, defaultDpi);

ICanvasResourceCreatorWithDpiICanvasResourceCreator通过添加 DPI 属性进行扩展。 此接口由 Win2D 控件实现,并可轻松创建呈现目标,该呈现目标将自动继承与从中创建的控件相同的 DPI:

var rtWithSameDpiAsDisplay = new CanvasRenderTarget(canvasControl, width, height);

位图 DPI

CanvasBitmap与呈现目标不同,不会自动从控件继承 DPI。 创建和加载位图的方法包括重载以显式指定 DPI,但如果省略,位图 DPI 默认为 96,而不考虑当前显示配置。

位图与其他类型不同的原因是它们是输入数据的源,而不是要绘制到的输出。 因此,位图的重要事项不是输出最终结束位置的 DPI,而是源图像的 DPI,这与当前显示设置完全无关。

如果加载 100x100 默认 DPI 位图,然后将其绘制到呈现目标上,则位图将从 100 个 100 个 DIP 缩放为 96 DPI(即 100 像素)到目标呈现目标 DPI 的 100 个 100 个 DIP(如果它是高 DPI 呈现目标)的像素数。 生成的图像大小将始终为 100 个 DIP(因此不会有令人不快的布局意外),但如果将低 DPI 源位图纵向扩展到更高的 DPI 目标,它可能会遇到一些模糊。

为了获得最高 DPI 的清晰度,某些应用程序可能希望在不同的分辨率下提供多个位图图像集,并在加载时选择最接近目标控件 DPI 的版本。 其他应用可能更喜欢只提供高 DPI 位图,并允许 Win2D 在较低的 DPI 显示器上运行时缩小这些位图(纵向缩减通常比纵向扩展更好)。 在任一情况下,都可以将位图 DPI 指定为参数 LoadAsync(ICanvasResourceCreator, String, Single)

请注意,某些位图文件格式包含自己的 DPI 元数据,但 Win2D 会忽略此元数据,因为它通常设置不正确。 相反,加载位图时必须显式指定 DPI。

CanvasDrawingSession DPI

CanvasDrawingSession 从绘制到的任何控件、呈现目标、交换链等中继承其 DPI。

默认情况下,所有绘图操作都在 DIP 中运行。 如果希望以像素为单位工作,可以通过属性更改 Units 此值。

效果 DPI

图像效果管道从绘制到的任何 CanvasDrawingSession 效果继承其 DPI。 在内部,效果处理始终以像素为单位运行。 参数值(如大小或位置)在 DIP 中指定,但在进行任何实际图像操作之前,这些单位将转换为像素。

当与目标绘图会话不同的 DPI 位图用作效果源图像时,将自动在位图和效果之间插入内部 DpiCompensationEffect 。 这会缩放位图以匹配目标 DPI,这通常是所需的。 如果这不是所需内容,可以插入自己的实例 DpiCompensationEffect 来自定义行为。

注意

如果实现自定义效果,请考虑应用等效的 DPI 处理方案,以确保与内置 Win2D 效果一起使用时的行为一致。

合成 API

Microsoft.Graphics.Canvas.Composition API 在低于 Win2D XAML 控件的水平运行,因此它们不会尝试代表你自动处理 DPI。 由你决定想要在其中操作的单位,并设置在合成可视化树中实现该转换所需的任何转换。

Windows.UI.Composition API,例如 CreateDrawingSurface 始终以像素单位指定大小。 使用 Win2D 绘制到合成图面时,可以指定在调用 CreateDrawingSession(CompositionDrawingSurface, Rect, Single)时要使用的任何 DPI。 通过返回 CanvasDrawingSession 的所有绘图将相应地纵向扩展或缩减。

如何测试 DPI 处理

测试应用在响应更改显示 DPI 时是否执行正确操作的最简单方法是在 Windows 10 或 Windows 11 上运行,并在应用运行时更改显示设置:

  • 右键单击桌面背景并选择“显示设置”
  • 移动标记为“更改文本、应用和其他项大小”的滑块
  • 单击“应用”按钮
  • 选择“稍后注销”

如果没有 Windows 10 或 Windows 11,还可以使用 Windows 模拟器进行测试。 在 Visual Studio 工具栏中,将“本地计算机”设置更改为“模拟器”,然后使用“更改分辨率”图标在以下两者之间切换模拟显示:

  • 100% (DPI = 96)
  • 140% (DPI = 134.4)
  • 180% (DPI = 172.8)