c# GDI+的region锯齿现象解决方法尝试
一. 产生原因
- 常见使用
GDI+在发布时,已经嵌入了抗锯齿功能,我们一般在画图之前加入如下几行代码,画图时就可以开启抗锯齿功能。
g.SmoothingMode = SmoothingMode.AntiAlias;
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
一个完整的画图程序例子如下:
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之间)插入其他灰度值,从而形成一种渐变色的视觉效果,那么从视觉上“台阶”现象也就可以减弱。
具体原理和相关技术可见大神博客:https://blog.****.net/fishmai/article/details/61916766
- 问题出现
当我们需要对图像做各类布尔运算时,往往需要将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插值奇异问题。
最后让我们看看简单的效果吧。
左边的黄色部分未经过处理,放大至图中这样时,曲线的锯齿已不能接受,肉眼可辨;
右边绿色部分经过处理,可见锯齿状现象得到了缓解,图像处于可接受范围。