WPF 绘制对齐像素的清晰显示的线条
原文:WPF 绘制对齐像素的清晰显示的线条

版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:http://blog.csdn.net/wpwalter/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系(walter.lv@qq.com)。 https://blog.csdn.net/WPwalter/article/details/78858762

此前有小伙伴询问我为何他 1 像素的线条显示发虚,然后我告诉他是“像素对齐”的问题,然而他设置了各种对齐像素的属性依旧没有作用。于是我对此进行了一系列试验,对 WPF 像素对齐的各种方法进行了一次总结。此后在 StackOverflow 中,我回答了 graphics - WPF DrawingContext seems ignore SnapToDevicePixels - Stack Overflow 问题。

阅读本文,我们将了解解决 WPF 像素对齐的四种方法以及其各自的适用范围和副作用。


为什么要做像素对齐

看线条!这是 3 像素的线条:

然而论其原因,就是因为我们屏幕太渣~哦~不,是因为绘制的线条没有与屏幕像素对齐,具体来说是视觉对象(Visual)的位置不在整数像素上或尺寸不是整数像素。而与此同时屏幕的点距又太大以至于我们看出来绘制的线条和屏幕像素之间的差异。

然而为什么 WPF 不默认为我们对齐像素呢?这是因为要对齐像素必定带来尺寸上的偏差;这是绘制尺寸精度和最终呈现效果之间的平衡。在 MacBook、Surface Pro 这些高档显示屏上,根本不用管这样的平衡问题;但在渣渣显示器上,微软把这种平衡的控制交给了应用的开发者。

处理像素对齐的四种方法

方法一:布局取整 UseLayoutRounding

实际效果是:

根本就不起作用

事实上我们从 .NET Framework 源码可以得知,UseLayoutRounding 实际只处理 UI 元素对自己子级控件的布局取整。一旦整棵布局树种有任何一个不是整数(或者 DPI 相乘后不是整数),那么就依然没有解决问题。

方法二:对齐设备像素 SnapsToDevicePixels

这是一个会沿着逻辑树继承的属性,只要最顶层设置了这个属性,里面的元素都会具备此特性。不过,他只处理矩形的渲染,也就是说,只对 Border Rectangle 这些类型的元素生效,其他的包括自己写的元素基本都是不管用的。

它有一个好处,是像素对齐的情况下同时能够保证显示不足或超过 1 像素时,也能带一点儿透明或者超过一点像素。

方法三:使用 DrwingContext 绘制并配合 GuidelineSet

如果自己处理绘制,则可以在 OnRender 方法中使用 DrawingContext 来绘制各种各样的形状。DrawingContext 有方法 PushGuidelineSet,而 PushGuidelineSet 就是用来处理对齐的。

以下是四种不同方式的对齐效果对比,其中上面一半是直接对齐(即绘制过程是紧贴着的),下面一半则是多个部分带上一点偏移(即并不是紧贴):


▲ 看不清的可以考虑方法看

于是要想像素对齐,必须:

  • 布局或绘制时,UI 元素之间一点偏移或空隙都不能有,一点都不行
  • SnapsToDevicePixelsGuidelineSet 在实际对齐中有效,而 UseLayoutRounding 就是在逗你

GuidelineSet 的使用可以参考我在 StackOverflow 上的回答:graphics - WPF DrawingContext seems ignore SnapToDevicePixels - Stack Overflow。

以下是我编写的用于辅助绘制对齐线条的扩展方法:

public static class SnapDrawingExtensions
{public static void DrawSnappedLinesBetweenPoints(this DrawingContext dc,Pen pen, double lineThickness, params Point[] points){var guidelineSet = new GuidelineSet();foreach (var point in points){guidelineSet.GuidelinesX.Add(point.X);guidelineSet.GuidelinesY.Add(point.Y);}var half = lineThickness / 2;points = points.Select(p => new Point(p.X + half, p.Y + half)).ToArray();dc.PushGuidelineSet(guidelineSet);for (var i = 0; i < points.Length - 1; i = i + 2){dc.DrawLine(pen, points[i], points[i + 1]);}dc.Pop();}
}

注意添加到 GuidelineSet 的尺寸不需要是整数,也不需要计算对齐屏幕的位置,只需要随便指定一个值即可,但相邻的绘制元素的值需要在 double 级别完全相同,多一点少一点都不行

OnRender 中调用它绘制:

protected override void OnRender(DrawingContext dc)
{// Draw four horizontal lines and one vertical line.// Notice that even the point X or Y is not an integer, the line is still snapped to device.dc.DrawSnappedLinesBetweenPoints(_pen, LineThickness,new Point(0, 0), new Point(320, 0),new Point(0, 40), new Point(320, 40),new Point(0, 80.5), new Point(320, 80.5),new Point(0, 119.7777), new Point(320, 119.7777),new Point(0, 0), new Point(0, 120));
}

方法四:RenderOptions.EdgeMode

这是纯渲染级别的附加属性,对所有 UI 元素有效。这个属性很神奇,一旦设置,元素就再也不会出现模糊的边缘了,一定是硬像素边缘。不足半像素的全部删掉,超过半像素的变为 1 个像素。

以为它可以解决问题?——Too young, too simple.

你希望能够绘制 1 像素的线条,实际上它会让你有时看得见 1 像素线条,有时看的是 2 像素线条,有时居然完全看不见!!!

如果你都作用对象上还有其它视觉对象,它们也会一并变成了“硬边缘”,是可以看得见一个个像素的边缘。

各种方法适用范围总结

  1. 如果画粗线条粗边框,那么 RenderOptions.EdgeMode 最适合了,因为设置起来最方便,可以设置到所有的 UI 元素上。由于边框很粗,所以多一个少一个像素用户也注意不到。
  2. 如果是画细边框,那么使用 Border 配合 SnapsToDevicePixels 可以解决,无论是 0.8 像素还是 1.0 像素,1.2 像素,都能在准确地显示其粗细的基础之上还保证像素对齐。
  3. 如果图形比较复杂,比如绘制表格或者其它各种交叉了线条的图形,那么使用 DrawingContext 绘制,并设置 GuidelineSet 对齐。
  4. 如果窗口非常简单,既没有缩放,UI 元素也不多,可以考虑使用 UseLayoutRounding 碰碰运气,万一界面简单到只需要整数对齐就够了呢?
  5. 特别说明,上面四种方法不足与应对所有的像素对齐情况,如果还是没办法对齐……节哀把……我们一起找偏方……

posted on 2018-09-21 22:03 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/9688697.html

WPF 绘制对齐像素的清晰显示的线条相关推荐

  1. 手机1像素线粗_iOS绘制1像素线的正确姿势

    一.前言 事情的起因是这样的,因为需求的原因,有一个页面的cell分割线需要自定义,于是我的同事很顺其自然地用了个view,并将其高度设为1,来作为cell分割线使用.一切看起来都那么平静,直到有一天 ...

  2. 分享一个算法,计算能在任何背景色上清晰显示的前景色

    背景色千差万别,如果希望在这样复杂的背景色下显示清晰可辨的前景色(例如显示文字),那如何选择这样的前景色才能确保适用于所有的背景呢? 灰度图的心理学公式 红绿蓝三色是非常不直观的颜色表示的方法,如果不 ...

  3. R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示均值、中位数)实战

    R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示均值.中位数)实战 目录

  4. R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示均值、标准偏差)实战

    R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示均值.标准偏差)实战 目录

  5. R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示中位数、分位数)实战

    R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示中位数.分位数)实战 目录

  6. R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示散点、抖动点jitter)实战

    R语言使用ggplot2包使用geom_violin函数绘制分组小提琴图(配置显示散点.抖动点jitter)实战 目录

  7. R语言ggplot2可视化百分比显示实战:纵轴显示为百分比、在柱状图上显示百分比、按照因子变量绘制分组子图(纵轴显示为百分比)、可视化图中显示数据百分比

    R语言ggplot2可视化百分比显示实战:纵轴显示为百分比.在柱状图上显示百分比.按照因子变量绘制分组子图(纵轴显示为百分比).可视化图中显示数据百分比 目录

  8. wpf采用Xps实现文档显示、套打功能

    原文:wpf采用Xps实现文档显示.套打功能 近期的一个项目需对数据进行套打,用户要求现场不允许安装office.页面预览显示必须要与文档完全一致,xps文档来对数据进行处理.Wpf的Document ...

  9. WPF绘制自定义窗口

    原文:WPF绘制自定义窗口 WPF是制作界面的一大利器,下面就用WPF模拟一下360的软件管理界面,360软件管理界面如下: 界面不难,主要有如下几个要素: 窗体的圆角 自定义标题栏及按钮 自定义状态 ...

最新文章

  1. 5G 在轨道运输网络中的需求
  2. hdu-4825(01字典树)
  3. nginx日志切割并使用flume-ng收集日志
  4. Cloud for Customer UI toolbar里按钮的渲染逻辑
  5. 分布式事务2PC、3PC模型
  6. 机器学习必备:前20名Python人工智能和机器学习开源项目
  7. mysql怎样实现先判断后联合_MYSQ创建联合索引,字段的先后顺序,对查询的影响分析...
  8. 弗尤博客(十一)之搜索博文
  9. 漫步微积分十——复合函数和链式法则
  10. [DP思考录]向左走,向右走: Observer模式 VS Mediator模式
  11. 黄章谈魅族5G手机计划:明年推出 后年终端才算成熟
  12. linux搭建raid5命令,Linux下用mdadm命令创建软RAID5
  13. 25个常用Matplotlib图的Python代码,干货收藏!
  14. Shopee平台很火爆,但是产品价格设置的不合理,努力都是白费的
  15. 性能调优需要考虑的三大方面
  16. 被发哥拉着跪向观众,朱军为何如此惊魂?
  17. PyTorch Python API:FX || Intro
  18. Python中对一个数组各个数进行累加(反差分计算) cumsum()函数
  19. 解决output directory already exists
  20. 跨境电商账号矩阵运营方法论

热门文章

  1. Java基础 —— 变量,选择,循环,数组,输入与输出等
  2. ConcurrentHashMap原理,jdk7和jdk8的区别
  3. 懒汉式,同步代码块线程不安全
  4. Git服务器报错:host key for (ip地址) has changed and you have requested strict checking
  5. linux命令编写四位数密码本,grub-crypt命令 – 对口令进行加密
  6. 这8种SQL用法,我用错吗?求答案!!!
  7. 设计模式:迪米特原则
  8. 关于WCF、WebAPI、WCFREST、WebService之间的区别总结
  9. python递归迭代_Python入门基础知识点(python迭代器和递归)
  10. v-charts加载动画_加载动画-用户体验写作练习