WPF模糊位图

背景:分辨率独立性

 

WPF 从一开始就被设计为独立于分辨率。因此,鼓励您使用物理测量单位(如英寸),而不是根据像素来设计 UI。当您在 WPF 中指定坐标时,您实际上使用的是“测量单位”,它被定义为 1/96 英寸。之所以选择此选项,是因为 96 DPI 显示器设置非常普遍。

当 WPF 呈现到屏幕时,它会考虑系统 DPI,您可以从显示控制面板中设置它。当然,其中一个问题是没有人正确设置它。你最后一次拿出尺子,测量你的屏幕,然后用你的分辨率除以结果是什么时候?人们没有正确设置 DPI 的另一个原因是 Windows 在非标准 DPI 设置方面并不总是看起来不错。这是我们将来会改进的地方。当然,由于每次调整分辨率时都必须重新计算 DPI 设置,因此最好是硬件能够报告它。由于这些原因,WPF 可能实际上无法在屏幕上绘制一英寸,但它应该相当接近。

分辨率独立性的另一个方面是能够以比像素更高的精度进行绘制。如果您回想一下 GDI API,绘图调用将它们的坐标指定为整数,这与像素网格相匹配。WPF 使用浮点数指定其坐标,因此您可以轻松地指定坐标,如 (1.2,5.234)。这有时被称为子像素定位。当然,考虑到每个像素都有一个最终颜色,渲染小于一个像素意味着什么?答案是我们将所有贡献者混合到一个给定的像素。您可以将其视为加权平均值,因此如果一个图元覆盖了大部分像素,它将比其他只覆盖很少像素的图元贡献更多。但是混合会丢失细节,所以这可能非常明显。

位图:它们应该是什么尺寸?

位图通常由设计师使用高端编辑工具制作。假设目标是保留设计师的意图,我们应该以多大的尺寸显示图像?如果设计师制作了 96x96 位图,在 96 DPI 的机器上它将是 1 英寸 x 1 英寸。但是,如果您在超高端 300 DPI 显示器上显示该位图(我见过一个,它很棒),那么该位图在每个维度上都将小于一厘米。那么,设计师的意思是 96 像素还是 1 英寸?有趣的是,现代图像格式允许您将设计者的预期 DPI 嵌入到文件中。WPF 将利用此信息,并缩放位图,使其最终成为所需的大小。但是,这里我们有一个问题 - WPF 总是假定设计者的意思是图像具有一定的物理尺寸。通常,设计师的意思是让图像具有一定的像素大小——毕竟,他们仔细地为图像中的每个像素分配颜色。设计者的意图不是要达到一定的物理尺寸,而是他们使用的工具通常会将一些 DPI 设置标记到文件中(通常是一些标准 DPI 设置,如 72 或 96)。WPF 看到此 DPI 信息并缩放图像以匹配。

问题一:像素缩放

位图很困难,因为它们以每像素颜色信息的形式编码“高频”信息。如果位图以稍微不同的大小显示,结果可能会非常模糊。我不会用很多例子让你厌烦,但考虑一下这个。假设您有一个 3x3 像素的位图。它看起来像这样:

现在,如果我们稍微缩放一下,使图像适合 4x4 像素,结果会受到以下事实的影响:我们不能在目标网格中平等地表示每个源像素。事实上,每个原始像素“扩展”了 1/3。但是当然,最终的像素只能有一种颜色,所以我们必须合并重叠的样本。我们可以尝试各种混合技术,但它们都有问题。结果将如下所示:

人眼可以很容易地检测到这种伪影,我们倾向于将结果与失焦或模糊联系起来。这可能很烦人。

另一个问题:像素对齐

正如我在背景部分中提到的,WPF 可以以亚像素精度进行渲染。如果您以正确的大小渲染原始 3x3 位图,但在像素坐标 (0.33,0.33) 上,会发生什么情况?红实线最终跨越两个像素列,我们能做的最好的事情就是混合贡献,这将产生与前面描述的比例几乎相同的结果。

SnapsToDevicePixels

WPF 预计会出现人们希望与像素网格对齐而不是使用亚像素精度的情况。您可以在任何 UIElement 上设置 SnapsToDevicePixels 属性。这将导致我们尝试渲染到像素网格,但是有很多情况不起作用 - 包括图像。我们将寻求在未来改进这一点。

怎么办?

一切都没有丢失。即使 WPF 非常希望独立于分辨率,我们也可以强制它自己进行像素对齐。事实上,这甚至不是很难。我们只需要做两件事:

  1. 将我们自己调整为真实的像素大小。
  2. 将自己定位在像素边界上。

对于调整大小,我们可以轻松地参与度量传递,并返回与所需像素大小相等的度量大小。CompositionTarget.TransformFromDevice 为我们提供了用于考虑系统 DPI 的变换。像素大小在“设备”坐标中,我们从设备转换为度量单位。很容易。

对于定位,我们可以参与排列通道,并且可能对自己应用一个渲染变换来适当地偏移。然而,对于这个例子,我专注于位图,所以我选择参与渲染并将适当的偏移量传递给 DrawImage 调用。无论哪种方式都行得通,但真正的问题是我们需要知道什么时候可能会改变我们的立场。WPF 布局系统非常保守,这意味着它会尽量减少工作量。一旦你被测量和安排好了,除非你的个人布局状态无效,否则你不会再次被调用。如果某个父母决定让你稍微调动一下,你很可能不会再次被安排。我通过订阅 LayoutUpdated 事件来解决这个问题。即使此事件是 UIElement 上的实例事件,它实际上在任何布局活动发生时都会被提升。幸运的是,这几乎就是我们想要的。

位图类

下面包含的代码引入了一个名为 Bitmap 的新类。Bitmap 是 Image 的替代品,但它不显示任何图像源,它只显示位图源。这让我可以访问 PixelWidth 和 PixelHeight 属性来确定合适的大小。这门课的重要方面是:

  • 派生自 UIElement 而不是 FrameworkElement,因为我不想要 MinWidth、MaxWidth 甚至 Width 之类的东西。
  • Bitmap.Source 可以设置为任何 BitmapSource。
  • 测量时,它会返回适当的测量单位来显示位图的 PixelWidth 和 PixelHeight
  • 渲染时,它将偏移它绘制的图像以与像素网格对齐
  • 每当布局更新时,它会检查是否需要重新渲染以再次与像素网格对齐。

它是一个非常简单的类,请查看源代码以获取详细信息。

示例应用程序

项目中包含一个非常简单的示例应用程序。我在其中嵌入了许多带有高频数据的小位图。此外,我将位图编码为各种设计器 DPI。在应用程序中,顶部堆栈面板包含位图实例。底部堆栈面板包含 Image 的实例。您可以清楚地看到 Image 如何响应位图 DPI,而 Bitmap 则没有。为了好玩,您可以使用箭头键来调整根上的渲染变换。位图将捕捉到最近的像素,而图像将使用亚像素定位。

有不利的一面吗?

当然。通过使用位图的像素大小,位图在不同的机器上看起来会非常不同。300 DPI 超级显示器将使您的位图看起来非常小。此外,由于我们与像素边界对齐,因此动画将导致位图跳跃完整像素。亚像素定位非常适合动画。最后,由于四舍五入到像素网格,您可以获得元素之间的间隙。如果你能忍受这些限制,那么也许这个 Bitmap 类会对你有用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值