[Qt教程] 第16篇 2D绘图(六)坐标系统

楼主

 发表于 2013-5-2 20:08:12 | 查看: 738| 回复: 0
坐标系统
版权声明

该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处!
导语

前面一节我们讲解了图片的显示,其中很多地方都用到了坐标的变化。这一节我们将讲解Qt的坐标系统,分为两部分来讲解:第一部分主要讲解前面一节的那几个函数,它们分别是translate()平移变换、scale()比例变换、rotate()旋转变换、shear()扭曲变换。最后还会介绍两个有用的函数save()和restore(),利用它们来保存和弹出坐标系的状态,从而实现快速利用几个变换函数来绘图。
       第二部分会和大家一起来研究一下Qt的坐标系统,其中可能会涉及到多个坐标,大家一定要亲自动手操作感悟一下,不然很难理解的!
环境:Windows Xp + Qt 4.8.4+Qt Creator2.6.2
目录

第一部分 Qt坐标系统应用
一、坐标系统简介
二、坐标系统变换
三、坐标系统的保存
第二部分 坐标系统深入研究
一、获得坐标信息
二、研究变换后的坐标系统
三、研究绘图设备的坐标系统
正文

第一部分 Qt坐标系统应用
一、坐标系统简介

Qt中每一个窗口都有一个坐标系统,默认的,窗口左上角为坐标原点,水平向右依次增大,水平向左依次减小,垂直向下依次增大,垂直向上依次减小。原点即为(0,0)点,以像素为单位增减。

下面仍然在上一节的程序中进行代码演示,更改paintEvent()的内容如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setBrush(Qt::red);
    painter.drawRect(0, 0, 100, 100);
    painter.setBrush(Qt::yellow);
    painter.drawRect(-50, -50, 100, 100);
}

我们先在原点(0,0)绘制了一个长宽都是100像素的红色矩形,又在(-50,-50)点绘制了一个同样大小的**矩形。可以看到,我们只能看到**矩形的四分之一部分。运行程序,效果如下图所示。

二、坐标系统变换
默认的,QPainter在相关设备的坐标系统上进行绘制,在进行绘图时,可以使用QPainter::scale()函数缩放坐标系统;使用QPainter::rotate()函数顺时针旋转坐标系统;使用QPainter::translate()函数平移坐标系统;还可以使用QPainter::shear()围绕原点来扭曲坐标系统。如下图所示。
坐标系统的2D变换由QTransform类实现,我们可以使用前面提到的那些便捷函数进行坐标系统变换,当然也可以通过QTransform类实现。
1.平移变换。将paintEvent()函数内容更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    // 平移变换
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0, 0, 50, 50);
    painter.translate(100, 100);  //将点(100,100)设为原点
    painter.setBrush(Qt::red);
    painter.drawRect(0, 0, 50, 50);
    painter.translate(-100, -100);
    painter.drawLine(0, 0, 20, 20);
}
运行程序,效果如下图所示。
这里先在原点(0, 0)绘制了一个宽、高均为50的正方形,然后使用translate()函数将坐标系统进行了平移,使(100, 100)点成为了新原点,所以我们再次进行绘制的时候,虽然drawRect()中的逻辑坐标还是(0, 0)点,但实际显示出来的却是在(100, 100)点的红色正方形。可以再次使用translate()函数进行反向平移,使原点重新回到窗口左上角。
2.缩放变换。将paintEvent()函数中的内容更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    // 缩放
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0, 0, 100, 100);
    painter.scale(2, 2); //放大两倍
    painter.setBrush(Qt::red);
    painter.drawRect(50, 50, 50, 50);
}
运行程序,效果如下图所示。
可以看到,当我们使用scale()函数将坐标系统的横、纵坐标都放大两倍以后,逻辑上的(50, 50)点变成了窗口上的(100, 100)点,而逻辑上的长度50,绘制到窗口上的长度却是100。
3.扭曲变换。将paintEvent()函数更改如下:

void Dialog::paintEvent(QPaintEvent *)
{    
           // 扭曲    
           QPainter painter(this);    
           painter.setBrush(Qt::yellow);   
           painter.drawRect(0, 0, 50, 50);    
           painter.shear(0, 1); //纵向扭曲变形    
           painter.setBrush(Qt::red);    
           painter.drawRect(50, 0, 50, 50);
}

运行程序,效果如下图所示。
shear()有两个参数,第一个是对横向进行扭曲,第二个是对纵向进行扭曲,而取值就是扭曲的程度。比如程序中对纵向扭曲值为1,那么就是红色正方形左边的边下移一个单位,右边的边下移两个单位,值为1就表明右边的边比左边的边多下移一个单位。大家可以更改取值,测试效果。
4.旋转变换。将paintEvent()函数更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    // 旋转
    QPainter painter(this);
    painter.drawLine(0, 0, 100, 0);
    painter.rotate(30); //以原点为中心,顺时针旋转30度
    painter.drawLine(0, 0, 100, 0);
    painter.translate(100, 100);
    painter.rotate(30);
    painter.drawLine(0, 0, 100, 0);
}
运行程序,效果如下图所示。
这里先绘制了一条水平的直线,然后将坐标系统旋转了30度,又绘制了一条直线。可以看到,默认是以原点(0, 0)为中心旋转的。如果想改变旋转中心,可以使用translate()函数,比如这里将中心移动到了(100, 100)点,然后旋转了30度,又绘制了一条直线。我们的本意是想在新的原点从水平方向旋转30度进行绘制,可是实际效果却超过了30度。这是由于前面已经使用rotate()函数旋转过坐标系统了,后面的旋转会在前面的基础上进行。
下面我们再次更改paintEvent()函数:

void Dialog::paintEvent(QPaintEvent *)
{    // 旋转    
      QPainter painter(this);   
      painter.drawLine(0, 0, 100, 0);   
      painter.rotate(30); //以原点为中心,顺时针旋转30度    
      painter.drawLine(0, 0, 100, 0);     
      painter.rotate(-30); // 反向旋转     
      painter.translate(100, 100);    
      painter.rotate(30);    
      painter.drawLine(0, 0, 100, 0);
}

运行程序,效果如下图所示。
这次我们在移动原点以前先将坐标系统反向旋转,可以看到,第二次旋转也是从水平方向开始的。
其实,前面讲到的这几个变换函数都是如此,他们改变了坐标系统以后,如果不进行逆向操作,坐标系统是无法自动复原的。针对这个问题,下面我们将讲解两个非常实用的函数来实现坐标系统的保存和还原。
三、坐标系统的保存
我们可以先利用save()函数来保存坐标系现在的状态,然后进行变换操作,操作完之后,再用restore()函数将以前的坐标系状态恢复,其实就是一个入栈和出栈的操作。下面来看一个具体的例子,更改paintEvent()函数如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.save(); //保存坐标系状态
    painter.translate(100,100);
    painter.drawLine(0, 0, 50, 50);
    painter.restore(); //恢复以前的坐标系状态
    painter.drawLine(0, 0, 50, 50);
}
运行程序,效果如下图所示。利用好这两个函数,可以实现坐标系快速切换,绘制出不同的图形。
第二部分 坐标系统深入研究
在第一部分,我们主要学习了常用的一些坐标变换,虽然在编程中,这些变换已经可以满足大部分的应用需求。不过,大家是否也感觉到现在对坐标的变换依然很模糊,没有一个透彻的认识。下面咱们就一点一点来研究一下坐标系统的变换。
一、获得坐标信息
前面图形的变换都是我们眼睛看到的,为了更具有说服力,下面将获取具体的坐标数据,通过参考数据来进一步了解坐标变换。
1.首先在dialog.h文件中添加头文件包含:
#include <QMouseEvent>
然后添加一个protected鼠标事件处理函数声明:
void mousePressEvent(QMouseEvent *);
2.到dialog.cpp文件中,先添加头文件包含:
#include <QDebug>
然后添加函数定义:

void Dialog::mousePressEvent(QMouseEvent *event)
{    
         qDebug() << event->pos();

}
这里应用了qDebug()函数,该函数可以在程序运行时将程序中的一些信息输出到控制面板,在QtCreator中会将信息输出到其下面的“应用程序输出”窗口。这个函数很有用,在进行简单的程序调试时,都可以利用该函数进行。我们这里利用它将鼠标指针的坐标值输出出来。
3.将paintEvent()函数更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawRect(0, 0, 50, 50);
}
现在运行程序,然后将鼠标在绘制的正方形右下角顶点处点击,在QtCreator的应用程序输出窗口就会输出相应点的坐标信息。如下图所示。大家也可以点击一下其他的地方,查看输出信息。
二、研究变换后的坐标系统
1.首先研究放大后的坐标系统,将paintEvent()函数更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    // 放大
    QPainter painter(this);
    painter.drawRect(0, 0, 50, 50);
    painter.scale(1, 2);
    painter.drawRect(50, 50, 50, 50);
}
这里,我们将纵坐标放大了两倍,而横坐标没有改变。运行程序,效果如下图所示。
大家可以查看一下第二个矩形的各个顶点的坐标,左上角是(50, 100)也就是说纵坐标扩大了两倍,查看其它点,会发现左右两条边长都变成了100。
2.研究旋转后的坐标系统。修改paintEvent()函数如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawLine(0, 0, 100, 0);
    painter.rotate(45);
    painter.setPen(Qt::red);
    painter.drawLine(0, 0, 100, 0);
}
这里我们先绘制了一条水平的直线,然后将坐标系统旋转45度,再次绘制了一条相同的红色直线。运行程序,效果如下图所示。
大家可以查看一下各处的坐标,虽然旋转后直线位置发生了变化,但是坐标其实是没有变化的。
我们也可以利用这种方法来测试一下应用其他变换函数后坐标的变化,这里就不再敖述。
三、研究绘图设备的坐标系统
除了可以在QWidget等窗口部件上进行绘制以外,还可以在QPixmap、QImage等上面进行绘制,这些均称为绘图设备。下面我们就以QPixmap为例,来研究一下它的坐标系统。
1.首先更改paintEvent()函数如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200, 200);
    pix.fill(Qt::red);   //背景填充为红色
    painter.drawPixmap(0, 0, pix);
}
    在前面我们已经讲过,QPixmap可以用来显示图片。其实QPixmap本身就是一个绘图设备,可以在它上面直接绘图。这里先生成了一个宽和高都是200像素的QPixmap类对象(注意,必须在构建时指定其大小),然后为其填充了红色,最后在窗口的原点进行了绘制。为了表述方面,下面将QPixmap对象称为画布,这里就是先绘制了一个红色画布。
    我们运行程序,并在红色画布的左上角和右下角分别点击,查看输出的坐标。如下图所示。因为点击位置的误差,所以两个点可能不是顶点。
2.下面我们接着更改paintEvent()的代码:

void Dialog::paintEvent(QPaintEvent *)
{    
      QPainter painter(this);    
      QPixmap pix(200, 200);    
      pix.fill(Qt::red);   //背景填充为红色    
      painter.drawPixmap(100, 100, pix);
}

这次我们在(100, 100)点重新绘制了画布,现在运行程序,发现画布左上角坐标确实为(100,100),这个就是我们窗口中的坐标,是没有什么疑问的。效果如下图所示。
窗口和画布都是绘图设备,那么画布本身有没有自己的坐标系统呢?我们接着研究!
3.我们继续更改paintEvent()函数:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200, 200);
    pix.fill(Qt::red);
    //新建QPainter类对象,在pix上进行绘图
    QPainter pp(&pix);
    //在pix上的(0,0)点和(50,50)点之间绘制直线
    pp.drawLine(0, 0, 50, 50);
    painter.drawPixmap(100, 100, pix);
}
这里我们为画布pix创建了一个QPainter对象pp,注意这个pp只能在画布上绘画,然后我们在画布上绘制了一条从原点(0, 0)开始的直线。运行程序,效果如下图所示。
可以看到,直线是从画布的左上角开始绘制的,也就是说,画布也有自己的坐标系统,坐标原点在画布的左上角。

下面补充说明一下:QPainter painter(this) ,this就表明了是在窗口上进行绘图,所以利用painter进行的绘图都是在窗口部件上的,painter进行的坐标变换,是变化的窗口的坐标系;而利用pp进行的绘图都是在画布上进行的,如果它进行坐标变化,就是变化的画布的坐标系。

而通过坐标数值,我们可以得出下面两条结论:
第一,QWidget和QPixmap各有一套坐标系统,它们互不影响。可以看到,无论画布在窗口的什么位置,它的坐标原点依然在左上角,为(0,0)点,没有变。
第二,我们所得到的鼠标指针的坐标值是窗口坐标系统的,不是画布的坐标。
4.下面这个例子将对比分析扩大窗口坐标或画布坐标的异同。
首先将paintEvent()函数更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    //放大前输出画布的大小
    qDebug() << pix.size();
    pix.fill(Qt::red);
    QPainter pp(&pix);
    //画布的坐标扩大2倍
    pp.scale(2, 2);
    //在画布上的(0,0)点和(50,50)点之间绘制直线
    pp.drawLine(0, 0, 50, 50);
    //放大后输出画布的大小
    qDebug() << pix.size();
    painter.drawPixmap(0, 0, pix);
}
这里我们将画布坐标系统放大了两倍,然后从原点开始绘制了一条直线,并分别输出了画布放大前后的大小。运行程序,效果如下图所示。
下面再次更改paintEvent()函数:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    qDebug() << pix.size();
    //窗口坐标扩大2倍
    painter.scale(2,2);
    pix.fill(Qt::red);
    QPainter pp(&pix);
    pp.drawLine(0, 0, 50, 50);
    qDebug() << pix.size();
    painter.drawPixmap(0, 0, pix);
}
这里与前面唯一的不同是:这里放大了窗口的坐标系统,而前面放大的是画布的坐标系统。运行程序,效果如下图所示。
可以看到,整个画布的可见面积变大了。直线虽然长度依然是100,但是这次的效果跟前面明显不同,因为是窗口坐标变大,所以在上面绘出的线条有了明显的颗粒感。
上面两个程序虽然最终输出的数据是一样的,但实际效果还是有很大不同的。大家可以根据需要进行选择性应用。
结语

       在这一节中我们讲述了坐标相关的多个知识点,经过本节的学习,大家应该已经对Qt的2D绘图有了一个浅显的认识,下一节我们将做一个比较实用的涂鸦板例子。
       Qt的坐标系统是很有必要好好研究的,它对深入学习应用Qt绘图很有帮助。如果大家想更系统的学习Qt坐标系统,可以参考《Qt Creator快速入门》的第10章相关内容。


涉及到的源码:
 painter_2_1.zip (6.91 KB, 下载次数: 5) 
 painter_2_2.zip (6.68 KB, 下载次数: 3) 
 painter_2_3.zip (6.83 KB, 下载次数: 3) 

[Qt教程] 第16篇 2D绘图(六)坐标系统相关推荐

  1. [Qt教程] 第20篇 2D绘图(十)图形视图框架(下)

    [Qt教程] 第20篇 2D绘图(十)图形视图框架(下) 楼主  发表于 2013-5-4 15:43:02 | 查看: 861| 回复: 0 图形视图框架(下) 版权声明 该文章原创于Qter开源社 ...

  2. [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

    [Qt教程] 第19篇 2D绘图(九)图形视图框架(上) 楼主  发表于 2013-5-4 15:26:20 | 查看: 1798| 回复: 26 图形视图框架(上) 版权声明 导语 在前面讲的基本绘 ...

  3. [Qt教程] 第17篇 2D绘图(七)涂鸦板

    [Qt教程] 第17篇 2D绘图(七)涂鸦板 楼主  发表于 2013-5-2 21:37:41 | 查看: 1255| 回复: 16 涂鸦板 版权声明 该文章原创于Qter开源社区(www.qter ...

  4. [Qt教程] 第11篇 2D绘图(一)绘制简单图形

    [Qt教程] 第11篇 2D绘图(一)绘制简单图形 楼主  发表于 2013-4-23 12:52:35 | 查看: 1398| 回复: 5 绘制简单图形 版权声明 该文章原创于Qter开源社区,作者 ...

  5. [Qt教程] 第18篇 2D绘图(八)双缓冲绘图

    [Qt教程] 第18篇 2D绘图(八)双缓冲绘图 楼主  发表于 2013-5-2 22:07:23 | 查看: 789| 回复: 1 双缓冲绘图 版权声明 该文章原创于Qter开源社区(www.qt ...

  6. [Qt教程] 第15篇 2D绘图(五)绘制图片

    [Qt教程] 第15篇 2D绘图(五)绘制图片 楼主  发表于 2013-5-2 17:59:00 | 查看: 886| 回复: 3 绘制图片 版权声明 该文章原创于Qter开源社区(www.qter ...

  7. [Qt教程] 第14篇 2D绘图(四)绘制路径

    [Qt教程] 第14篇 2D绘图(四)绘制路径 楼主  发表于 2013-4-27 12:40:52 | 查看: 611| 回复: 0 绘制路径 版权声明 该文章原创于Qter开源社区(www.qte ...

  8. [Qt教程] 第13篇 2D绘图(三)绘制文字

    [Qt教程] 第13篇 2D绘图(三)绘制文字 楼主  发表于 2013-4-25 23:04:46 | 查看: 720| 回复: 0 绘制文字 版权声明 该文章原创于Qter开源社区,作者yafei ...

  9. [Qt教程] 第12篇 2D绘图(二)渐变填充

    [Qt教程] 第12篇 2D绘图(二)渐变填充 楼主  发表于 2013-4-23 17:59:31 | 查看: 689| 回复: 1 渐变填充 版权声明 该文章原创于Qter开源社区,作者yafei ...

最新文章

  1. layout折叠后显示标题
  2. matlab错误原因,matlab常见错误分析
  3. MySQL之父直播公开课来了!与腾讯云CDB专家天团联袂分享
  4. SAP Spartacus forms.scss的引用问题
  5. java包含关系图_Java——Spring框架完整依赖关系图!再复习了解加工一下吧?
  6. java se下载完怎么启动_【Java SE】如何安装JDK以及配置Java运行环境
  7. 火狐浏览器的hoxx附件还能用吗_Haspit
  8. Vue安装教程 (一)
  9. 计算机基础知识实验课教案,高职《计算机网络基础》实验课程教学设计
  10. Hexo 的next主题下添加网易云音乐作BGM
  11. LDT面试:实验室开发诊断试剂监管模式(Laboratory Developed Test,LDT)
  12. 神经网络训练样本的标签,训练图像识别神经网络
  13. Web设计网站软件推荐
  14. 淘宝/天猫API:img2text-图片识别商品接口
  15. 一句话笔记-关于Linux挂载U盘乱码
  16. Beauty Box for Premiere Pro Mac(pr人像磨皮美容插件)
  17. 元器件 失效分析 过程介绍
  18. 基于JAVA疫情小区通报系统计算机毕业设计源码+系统+数据库+lw文档+部署
  19. html页面如何变成百分之百,css height 100% CSS成功设置DIV高度百分之百
  20. 微服务间的桥梁 Feign

热门文章

  1. java生成可执行文件的方法总结
  2. OnInit 和 Page_Init 事件有什么不同
  3. php数组添加数组_PHP数组
  4. 智能大棚计算机系统,一套智能大棚需要哪些设备?我来告诉你!
  5. php中mysql函数和mysqli函数的用法区别
  6. 2005年上海交通大学计算机研究生机试真题
  7. openwrt如何单独编译uboot
  8. Linux shell去除字符串中所有空格
  9. js去掉字符串中的所有空格
  10. 十六、python沉淀之路--迭代器