(15)Qt绘图(two)

2023-10-27

目录

坐标变换

平移坐标轴

缩放坐标轴

旋转坐标轴

定时器加坐标轴旋转实现动画旋转

transform旋转(可设置旋转轴)

绕X轴旋转

绕Y轴旋转

绕Z轴旋转

错切

Y轴错切

X轴错切

画家的保存与坐标复原

基本图形绘制

绘制点

绘制线

绘制矩形

普通矩形绘制

圆角矩形绘制

 填充矩形绘制

绘制圆形

绘制弧、扇形、弦

绘制弧

绘制扇形

绘制弦

绘制折线

绘制多边形

绘制路径

基本路径绘制

 填充规则

绘制贝塞尔曲线

二次贝塞尔曲线绘制

三次贝塞尔曲线绘制

绘制文字

静态文本绘制

普通文本绘制

字体相关函数

图片绘制

QPixmap

 指定位置裁剪

透明绘制

瓦片图绘制

图像的保存

QImage

QPicture

QBitmap

碰撞检测


坐标变换

平移坐标轴

painter.translate(50,50);
painter.drawPixmap(0,0, QPixmap(":/bk1.jpg").scaled(150,100));

缩放坐标轴

painter.drawPixmap(0,0, QPixmap(":/bk1.jpg").scaled(100,100));
painter.scale(0.5,0.5);
painter.drawPixmap(200,200, QPixmap(":/bk1.jpg").scaled(100,100));

旋转坐标轴

painter.translate(width()/2 - 100,height()/2 - 100);
painter.rotate(30); // 角度(默认以坐标原点旋转)(正数顺时针,负数逆时针)
painter.drawPixmap(0,0, QPixmap(":/bk1.jpg").scaled(200,200));

定时器加坐标轴旋转实现动画旋转

QTimer* timer = new QTimer(this);
timer->callOnTimeout(this, QOverload<>::of(&QWidget::update));
timer->start(10);

static int angle = 0;
painter.translate(width()/2 - 100,height()/2 - 100);
painter.rotate(angle++);
painter.drawPixmap(0,0, QPixmap(":/bk1.jpg").scaled(200,200));

transform旋转(可设置旋转轴)

绕X轴旋转

QTimer* timer = new QTimer(this);
timer->callOnTimeout(this, QOverload<>::of(&QWidget::update));
timer->start(10);
//
static int angle = 0;
QTransform transform;
transform.rotate(angle++, Qt::XAxis);   // 绕X轴旋转
painter.setTransform(transform);

painter.drawPixmap(0, 0 , QPixmap(":/bk1.jpg").scaled(200,200));

绕Y轴旋转

QTimer* timer = new QTimer(this);
timer->callOnTimeout(this, QOverload<>::of(&QWidget::update));
timer->start(10);
//
static int angle = 0;
QTransform transform;
transform.rotate(angle++, Qt::YAxis);   // 绕Y轴旋转
painter.setTransform(transform);

painter.drawPixmap(0, 0 , QPixmap(":/bk1.jpg").scaled(200,200));

绕Z轴旋转

QTimer* timer = new QTimer(this);
timer->callOnTimeout(this, QOverload<>::of(&QWidget::update));
timer->start(10);
//
static int angle = 0;
QTransform transform;
transform.rotate(angle++, Qt::ZAxis);   // 绕Z轴旋转
painter.setTransform(transform);

painter.drawPixmap(0, 0 , QPixmap(":/bk1.jpg").scaled(200,200));

错切

Y轴错切

// y轴错切
painter.translate(width()/2 - 100, height()/2 -100);
painter.shear(0, 0.5); // [-1, 1]
painter.drawPixmap(0, 0 , QPixmap(":/bk1.jpg").scaled(200,200));

 

X轴错切

// x轴错切
painter.translate(width()/2 - 100, height()/2 -100);
painter.shear(0.5, 0); // [-1, 1]
painter.drawPixmap(0, 0 , QPixmap(":/bk1.jpg").scaled(200,200));

画家的保存与坐标复原

painter.save();       // 画家状态保存
// 坐标变换
// 图形绘制
painter.restore();    // 画家状态复原

基本图形绘制

绘制点

painter.setPen(Qt::red);
painter.drawPoint(50,50);  // 绘制单个点
// 绘制多个点
QPoint points[] = {{0,0},{1,1},{2,2},{3,3}};
painter->drawPoints(points, 4);
// 绘制多个点
QPolygon polygon;
for(int i = 0; i < 100; i++)
{
    polygon.append({i,i});
}
painter.drawPoints(polygon);

绘制线

// 绘制一条线
painter.drawLine(0,0,100,100);
// 绘制多条线
QList<QLine> lines = {{0,0,100,100}, {100,100, 0,200}};
painter.drawLines(lines);

绘制矩形

普通矩形绘制

painter.drawRect(50,50,100,100);

圆角矩形绘制

// 最后一个参数为默认值,此时第三第四个参数为具体的值
painter.drawRoundedRect(QRect(200,200,100,100), 50, 50,Qt::SizeMode::AbsoluteSize);

// 此时第三第四个参数为百分比 [0,100]
painter.drawRoundedRect(QRect(200,200,100,100), 50, 50,Qt::SizeMode::RelativeSize);

 填充矩形绘制

painter.fillRect(50,50,100,100,QColor(255,0,255));

绘制圆形

painter.drawEllipse(0,0,200,200);						// 矩形区域绘制
painter.drawEllipse(QPoint(100,100),50,50);				// 中心点,两轴

绘制弧、扇形、弦

绘制弧

painter.drawRect(100,100,200,200);
painter.setPen(Qt::red);
painter.drawArc(QRect(100,100,200,200), 0, 16 * 90);

绘制扇形

painter.drawRect(100,100,200,200);
painter.setPen(Qt::red);
painter.drawPie(QRect(100,100,200,200), 0, 16 * 90);

绘制弦

painter.drawRect(100,100,200,200);
painter.setPen(Qt::red);
painter.drawChord(QRect(100,100,200,200), 0, 16 * 90);

绘制折线

QPolygon poly;
poly << QPoint(0, 0) << QPoint(100,100) << QPoint(200, 100);
painter.drawPolyline(poly);
QPoint pos[3] = { QPoint(0, 0) , QPoint(100,100) , QPoint(200, 100)};
painter.drawPolyline(pos, 3);

 

绘制多边形

painter.setBrush(Qt::green);
QPolygon poly = {{0,0},{100,0},{100,100},{0,100},{200,0}};
painter.drawPolygon(poly);

绘制路径

基本路径绘制

painter.setBrush(Qt::green);
QPainterPath path;
path.lineTo(200,200);
path.lineTo(0,200);
path.moveTo(400,400);
path.lineTo(width(),height());
painter.drawPath(path);

 填充规则

painter.setBrush(Qt::green);
QPainterPath path;
path.addRect(QRect(0,0,200,200));
path.addRect(QRect(100,100,200,200));
path.setFillRule(Qt::FillRule::WindingFill);      // 缠绕填充
//path.setFillRule(Qt::FillRule::OddEvenFill);    // 奇偶填充
painter.drawPath(path);

绘制贝塞尔曲线

二次贝塞尔曲线绘制

// 二次贝塞尔曲线
QPainterPath path;
path.moveTo(200,200);
path.quadTo(QPoint(300,300), QPoint(400,200));
painter.drawPath(path);

三次贝塞尔曲线绘制

// 三次贝塞尔曲线
QPainterPath path;
path.moveTo(0,0);
path.cubicTo(QPoint(10,100), QPoint(300,500), QPoint(200,200));
painter.drawPath(path);

绘制文字

静态文本绘制

// 绘制坐标即为左上角坐标
painter.drawStaticText(QPoint(0,0), QStaticText("Hello World"));

普通文本绘制

注意: 绘制文字是以左下角为原点绘制的

painter.setPen(Qt::red);
painter.setFont(QFont("微软雅黑",28,QFont::Bold,true));
painter.drawText(0,50, QString("Hello World"));

 

// 将绘制字体固定在矩形区域内 (会自动换行)
painter.drawText(QRect(0,0,100,200), "Hello World Hello World!");

 

字体相关函数

// 设置字体
painter->setFont();
// 获取字体
painter->font();
// 获取字体信息
painter->fontInfo();
// 获取字体数据
painter->fontMetrics();

图片绘制

QPixmap

针对输出显示优化的图像绘制

 

 指定位置裁剪

painter.drawPixmap(100, 100, QPixmap("mm.jpg").scaled(100,100));
painter.drawPixmap(QRect(0,0,100,100),                  // 绘制位置
                   QPixmap("mm.jpg").scaled(100,100),   // 图像
                   QRect(50,50,50,50));                 // 裁剪区域

drawPixmap(const QPoint &point, const QPixmap &pixmap, const QRect &source)

透明绘制

分别准备一张原码图和一张掩码图

 

//掩码图和原图大小必须一致
//掩码图白色区域为透明,黑色区域为绘制
painter.setRenderHint(QPainter::RenderHint::Antialiasing);
QBitmap mask("mask.jpg");
QPixmap pix = QPixmap("snowball.jpg").scaled(mask.size());
pix.setMask(mask);
painter.drawPixmap(0, 0,pix);

瓦片图绘制

// 函数原型
drawPixmapFragments(const QPainter::PixmapFragment *fragments, 
                    int fragmentCount, 
                    const QPixmap &pixmap, 
                    QPainter::PixmapFragmentHints hints = PixmapFragmentHints())
QPainter::PixmapFragment pixFrag = QPainter::PixmapFragment::create(QPointF(0,0), QRectF(0,0,100,100));
painter.drawPixmapFragments(&pixFrag, 2, QPixmap("mm.jpg").scaled(100,100));

图像的保存

m_pixmap = QPixmap(640,480);
QPainter painter(&m_pixmap);
painter.fillRect(m_pixmap.rect(),Qt::blue);
// 保存图片
m_pixmap.save("hello.png");

QImage

专门进行图像处理的

QImage m_img = QImage(640,480,QImage::Format_RGBA8888);
m_img.fill(Qt::transparent);
QPainter painter(&m_img);
painter.fillRect(QRect(0,0,100,100), Qt::blue);
m_img.save("img.png");

QPicture

Qt独有的图像格式

QPainter painter;
painter.begin(&pic);
painter.drawEillipse(0,0,200,200);
painter.end();
pic.save("pic.pic");

// 绘制不能直接使用QPicture的构造函数加载文件
QPicture pict;
pict.load("pic.pic");
painter->drawPicture(0,0,pict);

QBitmap

位图(黑白图)

QBitmap bitmap("mm.jpg");
painter.drawImage(QRect(0, 0, 100, 100), bitmap.toImage());

碰撞检测

// 碰撞检测
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QStaticText>
#include <QPainterPath>
#include <QKeyEvent>

// 碰撞检测
class Sprite
{
public:
    Sprite() = default;
    Sprite(int x,int y,int w,int h, const QPixmap& pix)
        :m_pos(x,y),m_size(w,h),m_pixmap(pix)
    {

    }
    void draw(QPainter* painter)
    {
        painter->drawPixmap(QRect(m_pos,m_size), m_pixmap);
    }
    void setPos(int x,int y)
    {
        m_pos.rx() = x;
        m_pos.ry() = y;
    }
    void moveBy(int dx,int dy)
    {
        m_pos.rx() += dx;
        m_pos.ry() += dy;
    }
    void updateCollider()
    {
        collider.clear();
        collider.addRect(QRect(m_pos, m_size));
    }
    void printPos()
    {
        qInfo() << m_pos;
    }
private:
    QPoint m_pos;
    QSize  m_size;
    QPixmap m_pixmap;
public:
    QPainterPath collider;  // 碰撞器
};

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);

        sp1 = new Sprite(0,0,50,50,QPixmap("mm.jpg"));
        sp2 = new Sprite(100,0,50,50, QPixmap("snowball.jpg"));
    }
    ~Widget()
    {

    }
protected:
    void paintEvent(QPaintEvent* ev) override
    {
        QPainter painter(this);
        sp1->draw(&painter);
        sp2->draw(&painter);
    }
    void keyPressEvent(QKeyEvent *ev) override
    {
        switch(ev->key())
        {
        case Qt::Key_Up:
            sp2->moveBy(0, -3);
            break;
        case Qt::Key_Down:
            sp2->moveBy(0, 3);
            break;
        case Qt::Key_Left:
            sp2->moveBy(-3, 0);
            break;
        case Qt::Key_Right:
            sp2->moveBy(3, 0);
            break;
        }
        if(sp1->collider.intersects(sp2->collider))
        {
            sp2->setPos(width() - 50, 0);
        }
        update();

        // 判断碰撞
        sp2->updateCollider();
        sp1->updateCollider();
    }
private:
    Sprite* sp1 = nullptr;
    Sprite* sp2 = nullptr;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Widget w;
    w.show();

    return a.exec();
}
#include "main.moc"

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(15)Qt绘图(two) 的相关文章

  • 取消任务

    我尝试运行一个关于取消任务的简单示例 如下所示 CancellationTokenSource tokenSource2 new CancellationTokenSource CancellationToken token2 tokenS
  • HttpWebRequest/HttpResponse:如何在响应中发送数据?

    我有一个客户端和一个服务器 在客户端我有 HttpWebRequest request HttpWebRequest WebRequest Create http localhost fa Default aspx request Meth
  • SQLite .NET 性能,如何加快速度?

    在我的系统上 约 86000 个 SQLite 插入需要长达 20 分钟 意味着每秒约 70 个插入 我要做数百万 我怎样才能加快速度 对每一行的 SQLiteConnection 对象调用 Open 和 Close 会降低性能吗 交易有帮
  • 使用 C++ 和 BOOST 读取 JSON 文件

    HTTP 服务器向我发送一个 JSON 响应 字符串 如下所示 folders id 109 parent id 110 path 1 105 110 id 110 parent id 105 path 1 105 files id 26
  • LNK2019 错误,无法解析的外部符号

    错误逐字读取 1 gt yes obj error LNK2019 unresolved external symbol int cdecl availableMoves int const int const 4 int availabl
  • 如何终止会话或会话 ID (ASP.NET/C#)

    当用户单击注销按钮时 如何销毁会话 会话 名称 我正在 MSDN 上查看 ASP NET API Reference 它似乎没有太多信息 看来还是比较有限的 但我找不到 ASP NET 类等的任何其他页面 我努力了 Session Aban
  • 如何在asp.net中的Lucene.net中进行模糊搜索?

    我们已经创建了 lucene net 索引并基于此 URL 进行搜索http sonyblogpost blogspot in http sonyblogpost blogspot in 但我们想要如下的输出 示例 如果我搜索 精选 我想显
  • C# 无法将欧元符号打印到文件中(使用 Excel 打开时)

    我在使用 Web api 控制器的 get 方法时遇到问题 此方法返回一个 HttpResponseMessage 对象 该对象具有带有 csv 文件的 HttpContent 其中包含欧元符号 当该方法返回文件时 不会打印欧元符号 该方法
  • 使用 C# 反序列化 JSON 以返回项目

    我有以下内容 documents keyPhrases search results Azure Search fast search indexing sophisticated search capabilities Build gre
  • 确定数组的大小(如果传递给函数)

    如果将数组传递给另一个函数 未传递大小 是否可以确定数组的大小 数组的初始化类似于 int array XXX 我知道不可能执行 sizeof 因为它会返回指针的大小 我问的原因是因为我需要在传递数组的另一个函数内运行 for 循环 我尝试
  • 如何从二维字节数组创建图像?

    在我的项目中 经过长时间的处理 我从红外摄像头获得了一个二维字节数组 字节数组中保存图像 如何在 C 中将该字节数组转换为图像 我知道通过 MemoryStream ms new MemoryStream byteArray System
  • 使用无状态会话延迟查找字典值

    在我的应用程序中 我设置了一个三元字典映射 以便对于给定用户 我可以检索属于该用户的对象的每个实例的 设置 也就是说 我有类似的东西 public class User public virtual IDictionary
  • 防止重入并确保某些操作获取锁的正确方法是什么?

    我正在设计一个基类 当继承该基类时 它将针对多线程环境中的上下文提供业务功能 每个实例可能都有长时间运行的初始化操作 所以我想让这些对象可重用 为此 我需要能够 为这些对象之一分配上下文以允许其完成工作 防止对象在已有上下文的情况下被分配新
  • OpenCV:将垫子除以标量的最简单方法是什么

    我认为标题中已经包含了很多内容 显然我可以迭代和划分 但我认为有一种内置的方法 我看见cvConvertScale但这不适用于类型cv Mat 我知道标量乘法的缩放运算 cv Mat M float alpha cv Mat Result
  • 家庭自动化图书馆[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我是一名 C 开发人员 希望将家庭自动化作为一种 爱好 我做了一些研究 但想知道是否有人知道支持 In
  • 如何有效确保小数值至少有 N 位小数

    我想在进行算术运算之前有效地确保十进制值至少有 N 个位置 在下面的示例中 3 显然我可以格式化 0 000 然后解析 但它的效率相对较低 我正在寻找一种避免与字符串转换的解决方案 我尝试过以下解决方案 decimal d 1 23M d
  • 使用 Doxygen 记录 C++ 中的宏函数

    如何使用 Doxygen 在 C 中记录宏函数 并在我的非 Evil 代码的文档中引用它 更具体地说 我在 Message H 中定义了一些名为 Message 的常规类 用户可以继承该类来定义自己的消息 在另一个文件 MessageHel
  • x86 中有加速 SHA (SHA1/2/256/512) 编码的指令吗?

    一个例子 在x86 是硬件加速 AES 的指令集 http en wikipedia org wiki AES instruction set 但是x86中是否有加速SHA SHA1 2 256 512 编码的指令 以及在x86上编码SHA
  • 用于将 cython 中的许多 C++ 类包装到单个共享对象的项目结构

    我在文档 邮件列表和这个问题在这里 https stackoverflow com questions 10300660 cython and distutils 但我想得到一个更直接的答案来解决我的具体情况 我正在通过尝试一点一点地包装我
  • SELECT 语句会受到 SQL 注入攻击吗?

    实际上有2个问题 我知道我必须尽可能多地使用存储过程 但我想知道以下内容 A 我可以从 SELECT 语句 例如 Select from MyTable 获得 SQL 注入攻击吗 B 另外 当我在 ASP NET 中使用 SQLDataSo

随机推荐

  • Spring+SpringMVC+Hibernate整合

    前几个星期老师在课堂上教了我们Spring和SpringMVC以及Hibernate 但自己一直没有实践过 所以今天就用Spring SpringMVC Hibernate整合做了一个用户登陆的模块 好让自己以后搞项目有一个可以参考的流程
  • 【大数据Hive】hive 事务表使用详解

    目录 一 前言 二 Hive事务背景知识 hive事务实现原理 hive事务原理之 delta文件夹命名格式
  • cgroup实践---使用cgroup限制mongodb进程内存

    多个业务需要共享mongodb集群资源 采用cgroup做资源隔离 限制mongod进程的内存 1 mkdir cgroup mount t cgroup o memory mongomemcg cgroup 创建cgroup环境 2 mk
  • 《区块链技术与应用》北大肖臻老师——课程笔记【4-5】

    区块链技术与应用 北大肖臻老师 课程笔记 4 5 一 比特币协议 比特币脚本 BitCoin Script Paxos协议 比特币 中的共识协议 consensus in BitCoin 女巫攻击 sybil attack 分叉攻击 for
  • 前沿交互技术在游戏中的应用

    获取数据的挑战 不过 生物特征识别的数据收集并不简单 在成本和时间承诺方面 它几乎是折扣可用性的极端对立 所有这些方法都需要专门的设备和软件来收集测量值并记录数据 对数据的分析也可能很耗时 并且需要比调查和可用性方法更高的统计复杂度 Per
  • 服务器端使用visdom进行可视化并更换端口(亲测有效)

    Visdom 是 Facebook 开源的一款用于创建 组织和共享实时丰富数据的可视化工具 通常结合pytorch结合使用很方便 服务器端使用visdom进行可视化 启动visdom本地可视化的方法很简单 只要在终端输入visdom或pyt
  • 笔记本怎么查看hdmi版本_同宗不同命!看着一样的笔记本的接口为啥差这么多?...

    点击上方 电脑爱好者 关注我们 每一款最新上市的笔记本身上都会配备两种USB接口 一种是标准的USB Type A 另一种就是USB Type C 后者也就是新款手机常用的支持正反插的新型接口 但是 长得一样 的USB接口 在传输速度和功能
  • [Linux]套接字通信

    摘于https subingwen cn 作者 苏丙榅 侵删 文章目录 1 套接字 socket 1 1 概念 1 2 网络协议 1 3 socket编程 1 3 1 字节序 1 3 2 IP地址转换 1 3 3 sockaddr 数据结构
  • xshell传输文件到服务器(ubuntu)(上传下载)

    一 利用xshell上传下载内容 点击xftp按钮 绿色按钮 出现一个对话框 对话框左边是本地的文件预览 对话框右边是服务器当前路径的文件预览 上传下载文件 直接拖拉内容 二 其他方式 1 xshell连接服务器 本地虚拟机 2 首先在服务
  • batch_size

    目录 3 5 Batch Size 3 5 1 为什么需要 Batch Size 3 5 2 Batch Size 值的选择 3 5 3 在合理范围内 增大Batch Siz
  • Qt 中QButtonGroup 的用法

    今天我们介绍下QButtonGroup的用法 按照字面意思理解就是按钮组 QButtonGroup能够用到很多地方 比如和QStackedWidget合起来使用能够达到实现tab 也可以单独使用形成多个按钮单选的需求 构造 QButtonG
  • vite+vue3+ts (1-创建工程)

    1 使用npm创建工程 node版本必须在12以上 npm init vitejs app 2 输入工程名 选择vue vue ts PS D vite gt npm init vitejs app npx 7 安装成功 用时 1 703
  • 【图片识别】基于Hough变化的答题卡识别(Matlab代码实现)

    本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 为了提高视频图像关键帧提取及修复效果 设计了一种基于计算机视觉的视频图像关键帧提取及修复方法 基于计算机视觉进行视频图像采集 采用阈值分割法建立灰
  • Error (suppressible): (vsim-3601) Iteration limit 10000000 reached at time 10520 ns.

    modelsim仿真拨错 vsim 3601 Iteration limit 10000000 reached 仿真迭代达到限制次数 超出迭代界限 问题 代码中存在逻辑回环 即将一个组合逻辑单元赋值产生的敏感变量与另一个组合逻辑相关 同时作
  • SQL数据库笛卡尔积、投影、选择、连接运算

    笛卡尔积 笛卡尔积之后 列数 R列数 S列数 行数 R列数 S列数 投影 主要从列的角度进行运算 投影之后不仅取消了原关系中某些列 也可能取消某些元组 元组就是行 目的是为了避免重复行 选择 图片中 式子意思是 从关系R中找到B列里等于 并
  • 【Mo 人工智能技术博客】深度神经网络——中文语音识别

    1 背景介绍 语音是人类自然的交互方式 计算机发明之后让机器能够 听懂 人类的语言 理解语言含义 并能做出正确回答就成为了人们追求的目标 这个过程主要采用了 3 种技术 即自动语音识别 automatic speech recognitio
  • 详解 ERC-20 vs ERC-777、ERC-721 vs ERC-1155: 它们有何不同?

    ERC 20 ERC 777 ERC 721 和 ERC 1155 是以太坊上最受欢迎的通证标准 它们具体指什么以及各有什么不同 1 什么是ERC 在我们开始深入讲不同的通证标准之前 需要追根溯源一下什么是 ERC ERC 是 Ethere
  • 获取html table下元素

    1 js 获取table下列表数据 var table document getElementById yhwclwhjlList 获取id为 yhwclwhjlList 的table var rows table rows length
  • 虚拟DOM和diff算法

    虚拟DOM Virtual dom 也就是我们常说的虚拟节点 它是通过JS的Object对象模拟DOM中的节点 然后再通过特定的render方法将其渲染成真实的DOM的节点 为什么要使用虚拟DOM呢 因为操作真实DOM的耗费的性能代价太高
  • (15)Qt绘图(two)

    目录 坐标变换 平移坐标轴 缩放坐标轴 旋转坐标轴 定时器加坐标轴旋转实现动画旋转 transform旋转 可设置旋转轴 绕X轴旋转 绕Y轴旋转 绕Z轴旋转 错切 Y轴错切 X轴错切 画家的保存与坐标复原 基本图形绘制 绘制点 绘制线 绘制