WPF DrawingVisual 1像素的直线 如何避免抗锯齿导致的模糊效果

    科技2025-11-08  18

    在WPF中用DrawingVisual 话直线的时候,往往会画出模糊的效果

    明明宽度设置成了1,但实际画出来的偏偏有两个像素,看起来有点模糊,这对于强迫症的人可忍不了!

    上代码:

    namespace OneLinePiexl { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var canvas=new MyCanvas(); grid.Children.Add(canvas); canvas.plot(); } } public class MyCanvas : Canvas { private DrawingVisual _drawingVisual = new DrawingVisual(); public MyCanvas() { this.AddVisualChild(_drawingVisual); } public Pen p = new Pen(Brushes.Black, 1.0); public void plot() { var dc = _drawingVisual.RenderOpen(); dc.DrawLine(p, new Point(0, 100), new Point(100, 100)); dc.Close(); } protected override int VisualChildrenCount { get { return 1; } } protected override Visual GetVisualChild(int index) { if (index == 0) return _drawingVisual; throw new IndexOutOfRangeException(); } } }

    现象:

    放大后明显可以看出来,左边的线宽有两个像素,而且看起来比较模糊,右边的线宽只有1像素。

    其实我是想得到右边的效果。

    那么为什么会这样呢,明明我的两个点都是整数,而且线宽指定为1

    原因是因为WPF是默认采用了抗锯齿效果。首先解释下什么是抗锯齿

     

    这就是一个明显的抗锯齿效果:

    上半部分使用了抗锯齿效果,下半部分则没有抗锯齿,可以看出来,使用抗锯齿其实就是将连接的像素点模糊化处理,使其看起来更像一条连续的线。而没有抗锯齿的线条看起来会比较生硬

    当然这在4k屏幕中跟本看不出来,只有在渣渣显示器中,放大才会明显的感觉出来

    加上抗锯齿的效果,会使得图形看起来比较圆滑。这是游戏中经常的做法

     

    OK,了解了抗锯齿,回到正题,这是画曲线有抗锯齿可以理解, 那为什么我画一条直线,也抗锯齿了呢?

    刚提到WPF默认使用抗锯齿,不管你画什么线,总之先抗了再说

    当逻辑像素点画完后没有和物理像素对齐,那么会自动加上抗锯齿的效果、

     

    假设我们的屏幕是这样的,每个格子代表一个物理像素

    水平为X轴,垂直为Y轴

    现在我们要画一条(2,0),(2,9) 的宽度为1的直线,我们想象的线是这样的:

    但是别忘了,这是屏幕,最小单位是一个像素,不是我们理想中的样子。所以实际上画出来的是这样的:

     

    这根蓝色的粗线才是我们即将画出来的线。可是还是不对,这跟线横跨了两个像素,左边半个像素,右边半个像素,组成了一个像素的宽度,在显示器中最小单位就是一个像素,所以这种画法是不可能出现的(根本做不到)

    同时这钟情况也达到了之前说的:当逻辑像素点画完后没有和物理像素对齐

    那么WPF会自动加上抗锯齿效果,也就是这样:

    绿色的线,宽度为2像素,然后横跨了第二个和第三个像素。同时会把颜色虚化,按照原来的颜色取一个相对淡一点的颜色作为整根线的颜色。这就是抗锯齿的效果

     

     

    OK,明白了为什么原理,那么如何解决?

    答案就是 个Point的X,Y做修正

    Public static Point[] FixPoints(Point p1,Point p2,double width) { Double corrector=0.5; Bool isViertical=(p1.X==p2.X);//是否垂直 Bool isHorizontal=(p1.Y==p2.Y);//是否水平 Bool isWidthOdd=width%2!=0 //宽度是否为基数 If(!isVeritical&&!isHorizontal) { Return Point[]{p1,p2}; } //先修正成整数,若有小数,必然会抗锯齿 p1.X=(int)p1.X; P1.Y=(int)p1.Y; P2.X=(int)p2.X; P2.Y=(int)p2.Y; If(isVertical) { If(isWidthOdd) { P1.X=p1.x+corrector; P2.X=p2.x+corrector; } Else if(isHorizontal) { If(isWidthOdd) { P1.Y=p1.y+corrector; P2.y=p2.y+corrector; } } Return new Point[]{p1,p2}; }

     

    Processed: 0.013, SQL: 8