三次贝塞尔样条曲线和图像曲线调整
三次贝塞尔样条曲线的构建及其在图像曲线调整中的应用

引言
这个程序是我的第二个图像编辑工具 --- 图像曲线调整。第一个是 自由图像变换。这个图像工具包含两个用户控件,ImageCurve
和 Canvas
。ImageCurve
控件之前已经编写过 (链接)。但它们完全不同。旧版本基于二次贝塞尔曲线,而这个新版本基于三次贝塞尔样条曲线,并且工作方式更像 Photoshop。
三次贝塞尔样条曲线
我们知道 C# 提供了 DrawCurve
方法来绘制曲线,但我们无法获取 DrawCurve
绘制的曲线上的点的坐标。为了调整图像曲线,我们必须自己构造曲线。
使用许多控制点制作可控设计的曲线的一种简单方法是使用贝塞尔样条曲线。要指定三次贝塞尔曲线,我们需要四个控制点 P0、P1、P2、P3,曲线的表达式为
P(t)=(1-t)3P0+3(1-t)2tP1+3(1-t)t2P2+t3P3, for 0<=t<1
点 P0 和 P3 在曲线上,但 P1 和 P2 通常不在曲线上。因此,我们必须从各个片段的数据点计算控制点。有关数学背景,请阅读 这篇论文。
为了获得控制点,我们首先构造增广矩阵 [M|C],然后完全行简化为 [I|P]
private void getControlPoints()
{
if (dataPoint != null && dataPoint.Length == 3)
{
controlPoint = new Vector[3];
controlPoint[0] = dataPoint[0];
controlPoint[1] =(6 * dataPoint[1] - dataPoint[0] - dataPoint[2])/4;
controlPoint[2] = dataPoint[2];
}
if (dataPoint!=null && dataPoint.Length> 3)
{
int n = dataPoint.Length;
controlPoint = new Vector[n];
double[] diag = new double[n]; // tridiagonal matrix a(i , i)
double[] sub = new double[n]; // tridiagonal matrix a(i , i-1)
double[] sup = new double[n]; // tridiagonal matrix a(i , i+1)
for (int i = 0; i < n; i++)
{
controlPoint[i] = dataPoint[i];
diag[i] = 4;
sub[i] = 1;
sup[i] = 1;
}
controlPoint[1] = 6 * controlPoint[1] - controlPoint[0];
controlPoint[n - 2] = 6 * controlPoint[n - 2] - controlPoint[n - 1];
for (int i = 2; i < n - 2; i++)
{
controlPoint[i] = 6 * controlPoint[i];
}
// Gaussian elimination from row 1 to n-2
for (int i = 2; i < n - 1; i++)
{
sub[i] = sub[i] / diag[i - 1];
diag[i] = diag[i] - sub[i] * sup[i - 1];
controlPoint[i] = controlPoint[i] - sub[i] * controlPoint[i - 1];
}
controlPoint[n - 2] = controlPoint[n - 2] / diag[n - 2];
for (int i = n - 3; i >0; i--)
{
controlPoint[i] = (controlPoint[i] - sup[i] * controlPoint[i + 1]) / diag[i];
}
}
一旦我们知道控制点,就可以使用函数 P(t)=(1-t)3P0+3(1-t) 2tP1+3(1-t)t2P2+t3P3 来获得样条曲线,其中 t 基于轴的精度
for (int i = 0; i < controlPoint.Length - 1; i++)
{
Vector b1 = controlPoint[i] * 2.0 / 3.0 + controlPoint[i + 1] / 3.0;
Vector b2 = controlPoint[i] / 3.0 + controlPoint[i + 1] * 2.0 / 3.0;
int n = 1;
if(isXcalibrated)
n=(int)((dataPoint[i + 1].X - dataPoint[i].X) / precision);
else n = (int)((dataPoint[i + 1].Y - dataPoint[i].Y) / precision);
if (n == 0) n = 1;
if (n < 0) n = -n;
for (int j = 0; j < n; j++ )
{
double t = (double)j / (double)n;
Vector v = (1 - t) * (1 - t) * (1 - t) * dataPoint[i] +
3 * (1 - t) * (1 - t) * t * b1 +
3 * (1 - t) * t * t * b2 + t * t * t * dataPoint[i + 1];
splinePoint.Add(v);
}
}
在这个程序中,t 基于 x 轴精度。为了在屏幕上绘制样条曲线,我设置 t = 5
...
YLScsDrawing.Geometry.Spline spline = new YLScsDrawing.Geometry.Spline();
spline.ListDataPoint = keyPt;
spline.Precision = 5;
Point[] splinePt=spline.SplinePoint;
g.DrawLines(new Pen(Color.Black), splinePt);
g.DrawLine(new Pen(Color.Black), keyPt[keyPt.Count - 1],
splinePt[splinePt.Length - 1]);
并且我设置 t = 1
来获取图像级别
YLScsDrawing.Geometry.Spline sp = new YLScsDrawing.Geometry.Spline();
sp.DataPoint = pts;
sp.Precision = 1.0;
Point[] spt=sp.SplinePoint;
for (int i = 0; i < spt.Length; i++)
{
int n = spt[i].Y;
if (n < 0) n = 0;
if (n > 255) n = 255;
level[pts[0].X + i] = (byte)n;
}
控件 ImageCurve
在构造三次贝塞尔样条曲线之后,我们可以指定一条曲线来编辑图像颜色,其 X 轴为图像输入级别,Y 轴为图像输出级别。这就是新的用户控件 ImageCurve
。
这个控件可以让用户添加曲线的控制点
for (int i = 1; i < keyPt.Count; i++)
{
if (e.X > keyPt[i-1].X+20 && e.Y > 0 &&
e.X < keyPt[i].X-20 && e.Y < this.Height)
{
keyPt.Insert(i, e.Location); // add a point
drag = true;
moveflag = i;
this.Cursor = Cursors.Hand;
Invalidate();
}
}
它还可以让用户删除曲线的控制点
if (drag && moveflag > 0 && moveflag < keyPt.Count - 1)
{
if (e.X > keyPt[moveflag - 1].X + 20 && e.X < keyPt[moveflag + 1].X - 20)
{
keyPt[moveflag] = e.Location;
}
else
{
keyPt.RemoveAt(moveflag); // remove a point
drag = false;
}
}
它的工作方式类似于 PhotoShop。
控件 Canvas
我在这里介绍的最后一个控件是 Canvas
。它看起来像 PictureBox
。但它是可缩放和可滚动的,并且始终将图片保持在中心。这是我一年前编写的另一个 可缩放和可滚动的PictureBox。希望你喜欢它们。
谢谢
感谢您尝试这个程序。任何建议都将不胜感激。
历史
- 2009 年 5 月 12 日:首次发布