在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};
}