C# GDI+绘图

来源:互联网 发布:大数据平台技术架构 编辑:程序博客网 时间:2024/05/16 15:05

C# GDI+绘图

Graphics

  • e.Graphics.在窗体或控件的 Paint 事件中接收对图形对象的引用,作为 PaintEventArgs 的一部分。 在为控件创建绘制代码时,通常会使用此方法来获取对图形对象的引用。 同样,您也可以在对对象的PaintEventHandler编程或处理 PrintDocument 的 PrintPage 事件时获取作为 PrintPageEventArgs 的属性的图形对象。

  • control.CreateGraphics(),调用某控件或窗体的 CreateGraphics 方法以获取对 Graphics 对象的引用,该对象表示该控件或窗体的绘图图面。 如果想在已存在的窗体或控件上绘图,请使用此方法。

  • 从 Image 继承的任何对象创建 Graphics 对象。 此方法在您需要更改已存在的图像时十分有用。

坐标系

主要介绍两种坐标系Page Coordinates 和 World Coordinates.

Page 页面坐标是控件的坐标系,下称老坐标系。一般地,page坐标系是原点在页面(控件)左上角,x轴竖直向下,y轴水平向右的左手坐标系。

设备坐标系是在其上进行绘制的物理设备(如屏幕,纸张)所使用的坐标系。设备坐标的单位只有像素,而页面坐标单位可以设置。在默认情形下,页面坐标和设备坐标相同

World 世界坐标是自己定义的任意的坐标系,也称用户坐标系,建模坐标系,下称新坐标系。建模坐标可以任意设置。

“世界变换”可以将世界坐标系转换为页面坐标系,而“页面变换”可以将页面坐标转换为设备坐标。一般我们用到的是世界变换。

世界变换的矩阵保存在Graphics.Transform 中。是Matrix(System.Drawing.Drawing2D.Matrix)对象。

坐标系的变换

默认的坐标系的变换是坐标变换。设变换的矩阵为P,新基=老基*P,P是从老坐标系到新坐标系的变换阵。老坐标=P*新坐标。P即是世界变换的矩阵。

结合性

设从依次进行三个变换P1,P2,P3,那么在默认状态下,结合性为prepend。

新基=老基*P1*P2*P3,老坐标=P1*P2*P3*新坐标

从点变换的角度看:变换后的坐标=P1*P2*P3*变换前的坐标。这种结合性非常不理想,
所以微软又提供了一种结合性append。变换后的坐标=P3*P2*P1*变换前的坐标,这种结合性非常理想,只要将世界坐标系里面点的坐标一步一步变到第四象限(从世界坐标系的角度看)里面的页面坐标系的区域即可。

由于世界坐标系一般为右手系,一般在最后一步加上
g.ScaleTransform(1, -1, System.Drawing.Drawing2D.MatrixOrder.Append)
即可变为左手系。

所以对于变换矩阵(初始变换矩阵为I)来讲,prepend相当于右乘(I*P),append相当于左乘(P*I),当然,我们更喜欢左乘。

为啥右乘叫prepend,左乘叫append?因为微软的坐标是行向量!,所以从我们的角度来看,相当于y'=x'P' ,(以’表示转置,以x,y表示(点)变换前后的对应点的坐标(列)向量)这样来看y'=x'*P1'*P2'*P3',这样来看,确实是在后面乘,把线性代数中以列向量表示坐标的左乘叫称append也就不足为奇了。

为什么变换的顺序十分重要?因为矩阵乘法没有交换律。(特殊情形下如果矩阵乘法满足交换律,则称这两个矩阵是可交换的。

Matrix

基于微软喜欢行向量,所以二维仿射变换的齐次矩阵也被转置了一下。微软的Matrix定义如下

Matrix=[[m11 m12 0],        [m21 m22 0],        [dx dy 1]]

构造函数

public Matrix(float m11,float m12,float m21,float m22,float dx,float dy)public Matrix(RectangleF,PointF[])

其中第二种方法是求出将一个矩形变换成平行四边形的变换阵。
矩形由RectangleF定义,平行四边形由左上,右上,左下三个点的数组定义。

Maritx的一些方法与Graphics的相应的Transform对应。矩阵的乘相当于变换的复合。

下面对其中主要的几种方法作简要介绍

public void Scale(float scaleX,float scaleY) //对应 m11,m22public void Shear(float shearX,float shearY)// m21 m12public void Translate(float offsetX,float offsetY)//dx dy

全局变换和局部变换

全局变换是应用于给定的Graphics对象绘制的每个项目的变换。

与此相反,局部变换是应用于要绘制的特定项目的变换GraphicsPath。
应用时要先创建GraphicsPath对象,再通过Add…方式在上面作图,可以通过Graphics.DrawPath方法将该对象的所有项目进行绘制,通过Graphics.FillPath方法对GraphicsPath上的所有项目进行填充。

矢量数据的显示

下面用append方式讲解

  1. 将要显示的区域扩大一点,因为在PictureBox边缘的项目容易被挡住而显示不完整。
  2. 将扩大后的显示区域平移到第一象限原点附近。
  3. 将第二步的结果缩放到合适位置。
  4. 反射到第四象限的绘图区域(页面坐标系)

这个实现的顺序是2 1 4 3,将就着看

  public float ViewPort2Page(Graphics g, RectangleF viewport, int w, int h)        {            g.ResetTransform();            g.TranslateTransform((-viewport.X + 0.01f * viewport.Width), (-(viewport.Y + viewport.Height) - 0.01f * viewport.Height), MatrixOrder.Append);            g.ScaleTransform(1, -1, MatrixOrder.Append);            float sx = 1.02f * viewport.Width / w, sy = 1.02f * viewport.Height / h;            float dx = 0, dy = 0;            if (sx != 0 && sy != 0)                if (sx < sy)                {                    sx = sy;                    g.ScaleTransform(1 / sy, 1 / sy, MatrixOrder.Append);                    dx = (w - viewport.Width * 1.01f / sx) / 2f;                }                else                {                    sy = sx;                    g.ScaleTransform(1 / sx, 1 / sx, MatrixOrder.Append);                    dy = (h - viewport.Height * 1.01f / sy) / 2f;                }            g.TranslateTransform(dx, dy, MatrixOrder.Append);            return sx;        }

Q:为啥要返回一个缩放因子s(这里因为sx=sy,将其统一记为s)?
A: 因为将页面坐标系缩放之后,如果再按默认宽度(1 pixel)画图,可能会显示得非常大,将这个默认宽度乘以s即可得到想要的正常的宽度。

杂项

下面称要显示的区域为视口viewport,其由一个RectangleF 来实现。表示世界坐标系中要显示的区域。

  • 平移即视口的平移,可以想象成视口在世界坐标系里的滑动。

  • 单点缩放即视口的缩放,可以理解为视口以这个点为基点放大缩小。

  • 开窗放大即视口的新建,可以理解为以所画矩形(窗)为新的视口。需要注意的是,从屏幕上的开窗放大一般伴随着坐标的变换。

  • 多图层的绘制策略:先绘制面状要素,再绘制线状要素,最后绘制点状要素。

  • 既然世界变换的矩阵可以将世界坐标变为页面坐标,那么对该矩阵求逆(Invert方法)即可将页面坐标变为世界坐标(TransformPoints方法可以进行点数组的转换)。

  • MouseWheel事件使用时需要在主窗体的load事件中加上 DrawPanel.MouseWheel += new MouseEventHandler(DrawPanel_MouseWheel);e.Delta>0 向上滚,e.Delta<0 向下滚

  • 开窗放大和平移会用到MouseDown,MouseMove,MouseUp事件。其中e.X,e.Y获取鼠标的设备坐标。e.Buttton获取按下键的枚举值有(MouseButtons.Middle)等。

参考资料

  1. 微软MSDN官方文档
  2. 课件GDI+(Graphics类)中的坐标
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 离职证明不给开怎么办 交离职报告不批怎么办 比亚迪s7噪音大怎么办 比亚迪f3噪音大怎么办 买房首付差10万怎么办 车贷合同没给我怎么办 车内老是有灰尘怎么办 车在北京怎么办进京证 五证合一后社保怎么办 五证齐全烂尾了怎么办 5万罚款交不起怎么办 炼铅环保手续要怎么办 贴膏药过敏红肿太痒了怎么办 没工作想贷款5万怎么办 燃气管超过2米了怎么办 建行燃气卡丢了怎么办 周浦燃气卡丢了怎么办 长沙燃气卡丢了怎么办 郑州燃气卡丢了怎么办 租房燃气卡丢了怎么办 洛阳燃气卡丢了怎么办 零线火线都带电怎么办 档案里年龄错了怎么办 档案年龄大了怎么办呢 吃菌子致幻了怎么办 野外吃了毒蘑菇怎么办 头顶头发稀少怎么办头顶头发稀 遇见无赖的人该怎么办 扶了老人被讹诈怎么办 遇见碰瓷讹人的怎么办 假机油用了4年怎么办 苹果6手机变砖头怎么办 苹果8升级变砖头怎么办 苹果id锁变砖头怎么办 钥匙断在锁里了怎么办? u型锁忽然打不开怎么办 密码门锁没电了怎么办 智能门锁没电了怎么办 十字锁钥匙丢了怎么办 罐头的拉环断了怎么办 锁坏了 打不开了怎么办