最近用到了图形旋转,花了不少时间查找材料,编码测试。而且还用到了20年前老师教给的三角函数,还有大学里面早已淡忘的矩阵运算。
呵呵,整理一下把,希望对大家有些帮助。
功能: 已知矢量OP,顺时针旋转α度,求P2点的坐标。
根据三角函数,我们可以很自然的写出:
P2.X = O.X + (int)(Math.Cos(alpha) * r) ;
P2.Y = O.Y + (int)(Math.Sin(alpha) * r) ; //哦,代码的颜色怎么不变呢? 噢,因为我刚开始在博客园写博客,所有不会使,呵呵,懒得理它了。
可惜,这样写是不对的。 原因嘛,一共有四个象限啊,当P1在不同的象限时候,结果是不同的。 论证过程。。。省掉N字。
嗯,先说个定理,可能大家会用到:
如上,半径 r 知道了,旋转角也知道了,P1P2的长度是多少?
呵呵,献丑了,手绘的。
这个定理,可以帮我们得到边长,或者得到某边对应角的大小。 在我的项目里面用到了。
那么上面的P2点,究竟是多少? 哦,我就省去复杂的推理过程了,直接出代码:
private Point GetNewPoint(double Rate, Point cirPos, Point startPos)
{
double Rage2 = Rate;// / 180 * Math.PI;
//B点绕A点转R度得到C点坐标,flag: 顺时针1,反时针-1:B是转的点,A是圆心
//C.X=(B.X-A.X)*COS(R*flag)-(B.Y-A.Y)*Sin(R*flag);
//C.Y= (B.Y-A.Y)*COS(R*flag)+(B.X-A.X)*sin(R*flag);
//转的点坐标-圆心坐标
//圆心坐标+计算坐标=新位置的坐标
double t1 = (startPos.X - cirPos.X) * Math.Cos(Rage2);
double t2 = (startPos.Y - cirPos.Y) * Math.Sin(Rage2);
int newx = (int)(t1 - t2);
int newy = (int)((startPos.Y - cirPos.Y) * Math.Cos(Rage2) + (startPos.X - cirPos.X) * Math.Sin(Rage2));
Point newpoint = new Point(cirPos.X + newx, cirPos.Y + newy);
return newpoint;
}
上面这个公式,不是个人推出来的,是教科书上的结论:
x0=|R|*cosA y0=|R|*sinA x1 =|R|*cos(A +B) y1=|R|*sin(A+B)
所以将x1,y1展开,有:
x1=|R|*(cosAcosB-sinAsinB)
y1=|R|*(sinAcosB+cosAsinB)
把 cosA = x0/|R| sinA = y0/|R| 代入上面的式子,得到
x1 = |R|*(x0*cosB/|R|-y0*sinB/|R|)
y1 = |R|*(y0*cosB/|R|+x0*sinB/|R|)
最终结果:
x1 = x0 * cosB - y0 * sinB
y1 = x0 * sinB + y0 * cosB
呵呵,三角函数方式,代码与原理交代完毕。 下面就是矩阵了。
通常在二维中,我们使用的是三阶矩阵,为什么呢? 因为二阶不够使啊,为什么呢? 因为二阶只能实现:缩放,旋转,对称变换,无法实现平移啊,
为什么呢? 因为,二阶不是万能神啊,为什么呢?万能神是不问为什么的哈。。无语。
程序中矩阵的样子是这样的:
我们敲一个C#中的矩阵看看:
namespace System.Drawing.Drawing2D
{
//
// 摘要:
// 封装表示几何变换的 3 x 3 仿射矩阵。此类不能被继承。
public sealed class Matrix : MarshalByRefObject, IDisposable
{
//
// 摘要:
// 将 System.Drawing.Drawing2D.Matrix 类的一个新实例初始化为单位矩阵。
public Matrix();
public Matrix(RectangleF rect, PointF[] plgpts);
//
// 摘要:
// 使用指定的元素初始化 System.Drawing.Drawing2D.Matrix 类的新实例。
//
// 参数:
// m11:
// 新的 System.Drawing.Drawing2D.Matrix 的第一行和第一列中的值。
//
// m12:
// 新的 System.Drawing.Drawing2D.Matrix 的第一行和第二列中的值。
//
// m21:
// 新的 System.Drawing.Drawing2D.Matrix 的第二行和第一列中的值。
//
// m22:
// 新的 System.Drawing.Drawing2D.Matrix 的第二行和第二列中的值。
//
// dx:
// 新的 System.Drawing.Drawing2D.Matrix 的第三行和第一列中的值。
//
// dy:
// 新的 System.Drawing.Drawing2D.Matrix 的第三行和第二列中的值。
public Matrix(float m11, float m12, float m21, float m22, float dx, float dy);
就是说,微软的matrix,我们可以定义为 x1 y1,x2 y2,x3 y3。 而微软会自动给我们补充第三列:0,0,1。这个查一下msdn就看到了。
哦,还是先上代码把,看看我们用matrix如何实现这种旋转。
private void MyTest()
{
Point pa = new Point(100, 100);
Point pb = new Point(200, 100);
DrawPoint(Brushes.Red, pa);
DrawPoint(Brushes.Black, pb);
DrawLine(Pens.Red, pa, pb);
double ang = Math.PI * 30 / 180;
Matrix m = new Matrix();
m.Translate(-pa.X, -pa.Y);
m.Rotate(30, MatrixOrder.Append);
m.Translate(pa.X, pa.Y, MatrixOrder.Append);
Point[] ps = new Point[1];
ps[0] = pb;
m.TransformPoints(ps);
DrawPoint(Brushes.Blue, ps[0]);
Graphics g = Graphics.FromHwnd(this.Handle);
Rectangle rect = new Rectangle(pa, new Size(0, 0));
rect.Inflate(100, 100);
g.DrawArc(Pens.Red, rect, 0, 30);
DrawPoint(Brushes.Green, ps[0]);
DrawLine(Pens.Red, pa, ps[0]);
}
private void DrawPoint(Brush b, Point p)
{
Rectangle rect = new Rectangle(p, new Size(5, 5));
Graphics g = Graphics.FromHwnd(this.Handle);
g.FillRectangle(b, rect);
}
private void DrawLine(Pen pen, Point p1, Point p2)
{
Graphics g = Graphics.FromHwnd(this.Handle);
g.DrawLine(pen, p1, p2);
}
这段代码就可以实现第一幅图的效果了。
我们在百度上查资料,可以看到:缩放矩阵,旋转矩阵,平移矩阵。 但是,如果进行一个点,绕另外一个点进行旋转,那么这个操作就是一个复合矩阵操作,是组合变换方式。
在实际应用过程中,我们在使用word、ppt、photoshop,经常会把图片旋转一下,可能是以左上角为圆心,旋转20度,或者以图片中心点为圆心旋转。问题来了,我们用的圆心,不同于屏幕左上角的圆心哦,图片的四个角点的坐标,都是基于屏幕左上角圆心的坐标。这个怎么整?
是的,从O,到O1(哈哈,我上面画了O撇,输入法输不上,干嘛不画个O1啊,砸到自己脚啦),我们是平移了一个位移。所以我们如果想旋转P1后得到P2(呵呵,你懂的),
我们应当这样做:第一步:将O1移到O。 第二步:将坐标系旋转。第三步:将O1移回原来位置。 这样P2就完成了在坐标系 X1O1Y1中,从P1点转到P2点的任务。
第一步:平移变换:
第二步:旋转变换:
第三步:平移变换:
注意,矩阵乘法是讲究先后次序的,如同领导人的排名,不可乱来哦。 呼。。。小工告成,同志们吉祥。