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

[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) 
时间: 2025-01-21 14:24:11

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

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

[Qt教程] 第20篇 2D绘图(十)图形视图框架(下) 楼主  发表于 2013-5-4 15:43:02 | 查看: 861| 回复: 0 图形视图框架(下) 版权声明 该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处! 导语 环境:Windows Xp + Qt 4.8.4+QtCreator 2.6.2 目录 三.场景(QGraphicsScene) (一)场景层 (二)索引算法 (三)边界矩形 (四)图形项查找 (五)事件处理和传播 (

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

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) 楼主  发表于 2013-5-4 15:26:20 | 查看: 1798| 回复: 26 图形视图框架(上) 版权声明 导语 在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们.但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动,检测它们的碰撞和叠加:或者我们想让自己绘制的图形可以拖动位置,进行缩放和旋转等操作.实现这些功能,要是还使用以前的方法,那么会十分困难.解决这些问题,可以使用Qt提供的图形视图框架.  

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

[Qt教程] 第17篇 2D绘图(七)涂鸦板 楼主  发表于 2013-5-2 21:37:41 | 查看: 1255| 回复: 16 涂鸦板 版权声明 该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处! 导语        通过前面几节的学习,大家应该已经对Qt中2D绘图有了一定的认识,这一节我们将应用前面讲到的内容,编写一个简单的涂鸦板程序,这一节只是实现最基本的鼠标画线功能. 环境:Windows Xp + Qt 4.8.4+QtCreat

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

[Qt教程] 第11篇 2D绘图(一)绘制简单图形 楼主  发表于 2013-4-23 12:52:35 | 查看: 1398| 回复: 5 绘制简单图形 版权声明 该文章原创于Qter开源社区,作者yafeilinux,转载请注明出处! 导语 Qt中提供了强大的2D绘图系统,可以使用相同的API在屏幕和绘图设备上进行绘制,它主要基于QPainter.QPaintDevice和QPaintEngine这三个类.其中QPainter用来执行绘图操作:QPaintDevice提供绘图设备,它是一个二

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

[Qt教程] 第12篇 2D绘图(二)渐变填充 楼主  发表于 2013-4-23 17:59:31 | 查看: 689| 回复: 1 渐变填充 版权声明 该文章原创于Qter开源社区,作者yafeilinux,转载请注明出处! 导语 在前一节提到了在画刷中可以使用渐变填充.QGradient类就是用来和QBrush一起指定渐变填充的.Qt现在支持三种类型的渐变填充: 线性渐变(linear gradient)在开始点和结束点之间插入颜色: 辐射渐变(radial gradient)在焦点和环绕

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

[Qt教程] 第13篇 2D绘图(三)绘制文字 楼主  发表于 2013-4-25 23:04:46 | 查看: 720| 回复: 0 绘制文字 版权声明 该文章原创于Qter开源社区,作者yafeilinux,转载请注明出处! 导语 Qt中除了绘制图形以外,还可以使用QPainter::darwText()函数来绘制文字,也可以使用QPainter::setFont()设置文字所使用的字体,使用QPainter::fontInfo()函数可以获取字体的信息,它返回QFontInfo类对象.在绘

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

[Qt教程] 第18篇 2D绘图(八)双缓冲绘图 楼主  发表于 2013-5-2 22:07:23 | 查看: 789| 回复: 1 双缓冲绘图 版权声明 该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处! 导语 在前面一节中,讲述了如何实现简单的涂鸦板,这一次我们将实现在涂鸦板上绘制图形,这里以矩形为例进行讲解.在后面还会提出双缓冲绘图的概念. 环境:Windows Xp + Qt 4.8.4+QtCreator 2.6.2 目录 一.绘制矩

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

[Qt教程] 第15篇 2D绘图(五)绘制图片 楼主  发表于 2013-5-2 17:59:00 | 查看: 886| 回复: 3 绘制图片 版权声明 该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处! 导语 Qt提供了四个类来处理图像数据:QImage.QPixmap.QBitmap和QPicture,它们也都是常用的绘图设备.其中QImage主要用来进行I/O处理,它对I/O处理操作进行了优化,而且也可以用来直接访问和操作像素:QPixma

[Qt教程] 第46篇 进阶(六) 国际化

[Qt教程] 第46篇 进阶(六) 国际化 楼主  发表于 2013-10-7 09:23:45 | 查看: 109| 回复: 0 国际化 版权声明 该文章原创于Qter开源社区 导语 在第2篇中讲述如何显示中文时,曾提到使用QTextCodec和tr()的方式直接显示中文,其实这只是一种临时的方法,方便我们快速完成程序,显示效果.当真正要发布一个程序时,最好的方式是在程序中使用英文字符串,而后使用国际化工具进行翻译. 国际化的英文表述为Internationalization,通常简写为I18