一.  产生原因

  • 常见使用

GDI+在发布时,已经嵌入了抗锯齿功能,我们一般在画图之前加入如下几行代码,画图时就可以开启抗锯齿功能。

            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.SmoothingMode = SmoothingMode.HighQuality;

            g.CompositingQuality = CompositingQuality.HighQuality; 



       

一个完整的画图程序例子如下:

            Graphics g = pictureBox2.CreateGraphics();
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            PointF[] pts_paint;
            pts_paint = new PointF[] { new PointF(0.0f, 0.0f), new PointF(100.0f, 0.0f), new PointF(50.0f, 200.0f) };

            Brush BrushColor = new SolidBrush(Color.Red);g.FillPolygon(BrushColor, pts_paint);

  • 抗锯齿原理简述

简单来讲,c#的抗锯齿是从视觉上消除或减弱了这种情况。如果作画区域的灰度值为0,非作画区的灰度值为255,在画斜线时,如果清晰度不高,那么像素点之间的台阶感也就体现了出来。此时,如果能在作画区域的两侧(0和255之间)插入其他灰度值,从而形成一种渐变色的视觉效果,那么从视觉上“台阶”现象也就可以减弱。

具体原理和相关技术可见大神博客:

  • 问题出现

当我们需要对图像做各类布尔运算时,往往需要将PointF类型转化为Region类型。此时,用region作画并且开启反锯齿功能,我们会发现图像已经不抗锯齿,c#自带的功能失效了。

原因暂时不知,现在只知道region存储时是按照一个一个的小矩形存储,画图时不再能抗锯齿或许也是这个原因。



二. 解决方法尝试

因为前面说的用PointF画图可以开启抗锯齿功能,那我们的思路就是把Region 转化为 PointF,并且进行简单插值和滤波处理。

  • Region 转化为 PointF

首先把region变成pointF数组(注意:这一步不是结尾,pointF不是一个顺序连接的点列),转化顺序为Region->RectangleF->GraphicsPath-> PointF,可以参考如下代码。

            #region  把region类型转为pointF类型
            Region region1 = new Region();
            GraphicsPath Gpath1 = new GraphicsPath();
            Matrix matrix1 = new Matrix();
            RectangleF[] Rpath = region1.GetRegionScans(matrix1);
            Gpath1.AddRectangles(Rpath);
            PointF[] pts_inter = Gpath1.PathPoints;

            #endregion

接下来对得到的pts_inter进行一个排序和整理,在此之前,可以观察pts_inter的存储方式,pts_inter的存储方式如下图:

            //
            //----------(n-1)-------(n-2)---- --------//
            //-------------|                 |------- -------//
            //----(n-5)-(n-4)-------(n-3)---(n-6)----//
            //------|                                      |-------//
            //----.........................................................----//
            //----.........................................................----//
            //----8---7-------------------6----9----//
            //--------|                              |---------//
            //--------4----3-------2------5--------//
            //------------ -|            |----------------//
            //------------ -0-------1----------------//

我们要提取的就是1, 5(or 2),  9(or 6), ... , (n-6)(or(n-3)), (n-2), (n-1), (n-5)(or(n-4)), ... , 8(or 7), 4(or 3), 0

这样所有的矩形外围点就可以首尾相连。(这里如果一行有多个矩形,可能会出问题,还要进一步思考如何解决)

            int n_inter = pts_inter.Length;
            PointF[] pts_sort = new PointF[n_inter / 2 + 2];
            pts_sort[n_inter / 4] = pts_inter[n_inter - 1];   //(n-1)
            pts_sort[n_inter / 4 + 1] = pts_inter[n_inter - 2];  //(n-2)
            for(int i = 0; i < n_inter; i++)
            {
                if(i % 4 == 1)
                {
                    if (i == 1)
                        pts_sort[(i - 1) / 4] = pts_inter[i];
                    else
                        pts_sort[(i - 1) / 4] = pts_inter[i].X > pts_inter[i - 3].X ? pts_inter[i] : pts_inter[i - 3];
                }
                else if(i % 4 == 0)
                {
                    if (i == 0)
                        pts_sort[n_inter / 2 + 1 - i / 4] = pts_inter[i];
                    else
                        pts_sort[n_inter / 2 + 1 - i / 4] = pts_inter[i].X < pts_inter[i - 1].X ? pts_inter[i] : pts_inter[i - 1];
                }

            }

这样得到的pts_sort就可以拿来画图了。

从上面得到的pts_sort用来画图时,还是有一些锯齿状出现,为此,尝试插值和滤波得到较为平滑的点列。

  • 插值

我们先考虑简单的线性插值,即保留原有点,在两点中点处插入新的值。

            #region 插值
            int n_sort = pts_sort.Length;
            PointF[] ptsLinear = new PointF[n_sort * 2];
            for(int j = 0; j < n_sort * 2; j++)
            {
                if(j % 2 == 0)
                {
                    ptsLinear[j] = pts_sort[j / 2];
                }
                else
                {
                    if(j == n_sort * 2 - 1)
                    {
                        ptsLinear[j].X = (float)0.5 * (pts_sort[0].X + pts_sort[n_sort - 1].X);
                        ptsLinear[j].Y = (float)0.5 * (pts_sort[0].Y + pts_sort[n_sort - 1].Y);
                    }
                    else
                    {
                        int i_front = (j - 1) / 2;
                        int i_back = (j + 1) / 2;
                        ptsLinear[j].X = (float)0.5 * (pts_sort[i_front].X + pts_sort[i_back].X);
                        ptsLinear[j].Y = (float)0.5 * (pts_sort[i_front].Y + pts_sort[i_back].Y);
                    }
                }

            }

楼主之前也写过Hermite三次插值,并且认为这样可以填充椭圆形被矩形化后消去的一部分,但是因为奇异问题,所以还在调试解决中。

  • 滤波


下面我们对得到的ptsLinear进行低通滤波,因为我这边对精度要求并没有到像素级别,所以采用了滑动平均滤波,这是一种比较简单的低通滤波器。

楼主在这里不再赘述了,因为我们这里的点数组是几乎首尾相连的,所以第一个滑动平均值也可加入末尾的值。针对我这边遇到的具体问题,我选了五点滑动平均,滑动平均的点过多,可能会和原来的曲线相去甚远,毕竟这里的采样率不比信号里的500Hz这样的高采样率。

三. 结论和效果

楼主在这里还有几个内容尚有欠缺:1. 矩形外围点排序问题,如果一行有多个矩形,可能会出问题;2. 三次Hermite插值奇异问题。

最后让我们看看简单的效果吧。

左边的黄色部分未经过处理,放大至图中这样时,曲线的锯齿已不能接受,肉眼可辨;

右边绿色部分经过处理,可见锯齿状现象得到了缓解,图像处于可接受范围。

unity如何消除LineRenderer的锯齿 unity锯齿严重_抗锯齿