【学习笔记】Windows GDI绘图(三)坐标系和坐标转换

坐标系Coordinate System和坐标转换Transformation

GDI+ 提供世界转换和页面转换,以便可以转换(旋转、缩放、转换等)绘制的内容。 这两种转换还支持在各种坐标系中工作。

坐标系类型

GDI+ 使用三个坐标空间:世界World、页面Page和设备Device。
世界坐标是用于为特定图形世界建模的坐标,并且是你传递给 .NET Framework 中的方法的坐标。
页面坐标指绘图图面使用的坐标系,例如窗体或控件。
设备坐标是在其上进行绘制的物理设备(如屏幕或纸张)所使用的坐标。 调用 myGraphics.DrawLine(myPen, 0, 0, 160, 80) 时,传递给 DrawLine 方法的点((0, 0) 和 (160, 80))位于世界坐标空间中。

修改坐标原点TranslateTransform

using(var myPen=new Pen(Color.Red,2))
{
    //原点在(0,0)
    e.Graphics.DrawLine(myPen, 0, 0, 200, 100);
    //修改坐标原点为(100,80)
    e.Graphics.TranslateTransform(100, 80);
    //原点在(100,80)
    e.Graphics.DrawLine(myPen, 0, 0, 200, 100);
}

在这里插入图片描述

度量单位PageUnit

[System.ComponentModel.Description("修改PageUnit")]
public void Demo03_02(PaintEventArgs e)
{
    using (var redPen = new Pen(Color.FromArgb(128, Color.Red), 5))
    using (var greenPen = new Pen(Color.FromArgb(128, Color.Green), 4))
    using (var bluePen = new Pen(Color.FromArgb(128, Color.Blue), 0.1f))
    using (var font = new Font("Times New Roman", 20))
    {
        float offsetX = 4;
        float offsetY = 4;
        e.Graphics.TranslateTransform(offsetX, offsetY);
        var inchPixel = InchToPixel(offsetX, e.Graphics.DpiX);
        e.Graphics.DrawString($"{offsetX}英寸={inchPixel}像素", font, Brushes.Black, new Point(inchPixel, inchPixel));

        var mmPixel = MMToPixel(offsetX, e.Graphics.DpiX);
        e.Graphics.DrawString($"{offsetX}毫米={mmPixel}像素", font, Brushes.Black, new Point(mmPixel, mmPixel));

        //画一条线到英寸坐标系原点 
        e.Graphics.DrawLine(Pens.Black, new Point(0, inchPixel), new Point(inchPixel, inchPixel));

        //画一条线到毫米坐标系原点 
        e.Graphics.DrawLine(Pens.Black, new Point(0, mmPixel), new Point(mmPixel, mmPixel));


        e.Graphics.PageUnit = GraphicsUnit.Inch;
        //注意这里原点偏移(4,4)单位:英寸,线段是(0,0)-(100,200),单位也是英寸,bluePen的线宽也是0.1英寸
        e.Graphics.DrawLine(bluePen, 0, 0, 100, 200);

        e.Graphics.PageUnit = GraphicsUnit.Millimeter;
        //这里的原点偏移(4,4)单位:毫米,线段是(0,0)-(100,200),单位:毫米,greenPen的线宽是4毫米
        e.Graphics.DrawLine(greenPen, 0, 0, 100, 200);

        e.Graphics.PageUnit = GraphicsUnit.Pixel;
        //这里的原点偏移(4,4)单位:像素,线段是(0,0)-(100,200),单位:像素,redPen的线宽是5像素
        e.Graphics.DrawLine(redPen, 0, 0, 100, 200);
    }
}

/// <summary>
/// 毫米转像素
/// </summary>
/// <param name="mm"></param>
/// <param name="dpi"></param>
/// <returns></returns>
private int MMToPixel(float mm, float dpi)
{
    return (int)(mm * dpi / 25.4f);
}
/// <summary>
/// 英寸转像素
/// </summary>
/// <param name="inch"></param>
/// <param name="dpi"></param>
/// <returns></returns>
private int InchToPixel(float inch, float dpi)
{
    return (int)(inch * dpi);
}

在这里插入图片描述
当TranslateTransform(4,4)
PageUnit = GraphicsUnit.Inch
DrawLine(bluePen, 0, 0, 100, 200)
DPI=96时

坐标空间线段坐标
世界(0,0)到(100,200)
页面(4,4)到(104,204)
设备(384,3844)到(9984,19584)

页面缩放PageScale

 // 创建矩形
 Rectangle rectangle1 = new Rectangle(20, 20, 50, 100);

 // 绘制轮廓
 e.Graphics.DrawRectangle(Pens.Blue, rectangle1);


 // 页面缩放2倍  
 e.Graphics.PageScale = 2.0F;

 // 修改坐标原点,实际为10*2,即(20,20)
 e.Graphics.TranslateTransform(10.0F, 10.0F);

 // 绘制轮廓实际为 (40,40,100,200)加上坐标原点的偏移后,设备坐标为(60,60,100,200)
 e.Graphics.DrawRectangle(Pens.Tomato, rectangle1);

 // Set the page scale and origin back to their original values.
 e.Graphics.PageScale = 1.0F;
 e.Graphics.ResetTransform();

 SolidBrush transparentBrush = new SolidBrush(Color.FromArgb(50,Color.Yellow));

 // 注意此处的坐标计算
 // x = (10 + 20) * 2
 // y = (10 + 20) * 2
 // Width = 50 * 2
 // Length = 100 * 2
 Rectangle newRectangle = new Rectangle(60, 60, 100, 200);

 //此处填充与上面修改坐标及放大后的位置一样
 e.Graphics.FillRectangle(transparentBrush, newRectangle);

PageScale

转换的矩阵表示形式

矩阵的加减法

1、运算规则
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
  简言之,两个矩阵相加减,即它们相同位置的元素相加减!
  注意:只有对于两个行数、列数分别相等的矩阵(即同型矩阵),加减法运算才有意义,即加减运算是可行的.
  2、运算性质 (假设运算都是可行的)
  满足交换律和结合律
  交换律  A+B=B+A
  结合律 (A+B)+C=A+(B+C)

矩阵与数的乘法

1、 运算规则
  数 乘矩阵A,就是将数 乘矩阵A中的每一个元素,记为 或 .
  特别地,称 称为 的负矩阵.
  2、 运算性质
  满足结合律和分配律
  结合律: (λμ)A=λ(μA) ; (λ+μ)A =λA+μA.
  分配律: λ (A+B)=λA+λB.

矩阵与矩阵相乘

一个 mxn 的矩阵可以与一个 nxp 的矩阵相乘,得到一个 mxp 矩阵。第一个矩阵的列数必须与第二个矩阵的行数相同。
  
(a, b) • (c, d) = ac + bd

(a, b, c) • (d, e, f) = ad + be + cf

例如,(2, 3) 和 (5, 4) 的点积是 (2)(5) + (3)(4) = 22。 (2, 5, 1) 和 (4, 3, 1) 的点积是 (2)(4) + (5)(3) + (1)(1) = 24。 请注意,两个向量的点积是一个数字,而不是另一个向量。 另请注意,只有当两个向量具有相同数量的分量时,才能计算点积。

令 A(i, j) 为矩阵 A 中第 i 行第 j 列的条目。 例如,A(3, 2) 是矩阵 A 中第 3 行第 2 列的条目。 假设 A、B 和 C 是矩阵,且 AB = C。C 的条目的计算如下:

C(i, j) = (A 的 i 行) • (B 的 j 列)
在这里插入图片描述
如果将平面中的点视为 1×2 矩阵,则可以通过将其乘以 2×2 矩阵来变换该点。 下图显示了应用于点 (2, 1) 的几个变换。
在这里插入图片描述
上图所显的所有变换都是线性变换。
假设要从点 (2, 1) 开始,将其旋转 90 度,沿 x 方向平移 3 个单位,并沿 y 方向平移 4 个单位。
在这里插入图片描述
例如,点 (2, 1) 由矩阵 [2 1 1] 表示。 下图显示了一个仿射变换(旋转 90 度;沿 x 方向平移 3 个单位,沿 y 方向平移 4 个单位),表示为与一个 3×3 矩阵相乘。
在这里插入图片描述
在这里插入图片描述

复合变换

GDI+的Matrix类提供了几种构建复合变换的方法:
Multiply:相乘
Rotate:旋转(正数顺时针,负数逆时针)
RotateAt:绕某点旋转
Scale:缩放
Shear:剪切转换(垂直或水平线变成斜线)
Translate:平移

// 定义矩形
Rectangle rect = new Rectangle(100, 100, 100, 50);
//未变换前
e.Graphics.DrawRectangle(Pens.Black, rect);
Matrix myMatrix = new Matrix();
//绕原点(0,0)顺时针旋转30度
myMatrix.Rotate(30);
e.Graphics.Transform = myMatrix;
e.Graphics.DrawRectangle(Pens.Red, rect);

//垂直放大2倍
myMatrix.Scale(1, 2, MatrixOrder.Append);
e.Graphics.Transform = myMatrix;
e.Graphics.DrawRectangle(Pens.Green, rect);

//水平平移50
myMatrix.Translate(50, 0, MatrixOrder.Append);
e.Graphics.Transform = myMatrix;
e.Graphics.DrawRectangle(Pens.Blue, rect);

在这里插入图片描述
(注:利用前面所学,自己写了个带刻度尺的Panel控件,后续图像在此控件上显示)

全局变换和局部变换

全局转换是适用于给定 Graphics 对象绘制的每个项的转换。 相比之下,局部转换是适用于要绘制的特定项的转换。

全局转换

构造Graphics对象,然后修改Transform属性。Transform 属性是一个 Matrix 对象,因此它可保存任何仿射变换序列。 存储在 Transform 属性中的转换称为世界转换。 Graphics 类提供了几种构建复合世界转换的方法:MultiplyTransformRotateTransformScaleTransformTranslateTransform

var rect = new Rectangle(100, 100, 300, 200);
//1、原始矩形
e.Graphics.DrawEllipse(Pens.Red, rect);

//2、垂直方向缩放0.5
e.Graphics.ScaleTransform(1, 0.5f);
e.Graphics.DrawEllipse(Pens.Green, rect);

//3、x方向平移50像素
e.Graphics.TranslateTransform(50,0, MatrixOrder.Append);
e.Graphics.DrawEllipse(Pens.Blue, rect);

//4、绕原点顺时针旋转30度
e.Graphics.RotateTransform(30, MatrixOrder.Append);
e.Graphics.DrawEllipse(Pens.Black, rect);

全局变换

局部转换

局部转换适用于要绘制的特定项。 例如,GraphicsPath 对象有一个 Transform 方法,通过它可转换该路径的数据点。

var rect = new Rectangle(150, 100, 300, 200);
using (var myMatrix = new Matrix())
{
    //绕原点顺时针旋转30度
    myMatrix.Rotate(30);
    using (var myGraphicsPath = new GraphicsPath())
    {
        myGraphicsPath.AddRectangle(rect);
        myGraphicsPath.Transform(myMatrix);
        //1、没变换的矩形
        e.Graphics.DrawRectangle(Pens.Black, rect);
        //2、局部变换后的矩形
        e.Graphics.DrawPath(Pens.Red, myGraphicsPath);
    }
}

局部变换

全局变换与局部变换结合(y轴向上)

可将世界转换与局部转换相结合,以实现各种结果。 例如,可使用世界转换来修改坐标系,并使用局部转换来旋转和缩放在新坐标系上绘制的对象。

using (var blueBrush = new SolidBrush(Color.FromArgb(127, Color.Blue)))
using (var greenBrush = new SolidBrush(Color.FromArgb(127, Color.Green)))
using (var myMatrix = new Matrix(1, 0,
                                 0, -1,
                                 0, 0))//y轴方向向上
{
    e.Graphics.Transform = myMatrix;
    //向右200像素,向下250像素
    e.Graphics.TranslateTransform(200, 250, MatrixOrder.Append);

    //水平坐标线(向右)
    e.Graphics.DrawLine(Pens.Black, -50, 0, 200, 0);
    e.Graphics.DrawString("x", Font, Brushes.Black, 200, 0);

    //不平坐标线(向左)
    e.Graphics.DrawLine(Pens.Black, 0, -50, 0, 200);
    //注意,这里的文字方向是向下(不是向上)
    e.Graphics.DrawString("y", Font, Brushes.Black, 0, 200);

    using (var myGraphicsPath = new GraphicsPath())
    {
        var rect = new Rectangle(0, 0, 100, 100);
        myGraphicsPath.AddRectangle(rect);

        e.Graphics.FillPath(blueBrush, myGraphicsPath);

        using (var myPathMatrix = new Matrix())
        {
            myPathMatrix.Scale(2, 1);
            myPathMatrix.Rotate(20, MatrixOrder.Append);
            myGraphicsPath.Transform(myPathMatrix);

            e.Graphics.FillPath(greenBrush, myGraphicsPath);
        }
    }
}

全局变换与局部变换结合

https://learn.microsoft.com/zh-cn/dotnet/desktop/winforms/advanced/coordinate-systems-and-transformations?view=netframeworkdesktop-4.8

  • 35
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图南堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值