经过前面的章节,我们已经能够画出一些东西来,主要就是使用QPainter的相关函数。今天,我们要看的是QPainter的坐标系统。
 
同很多坐标系统一样,QPainter的默认坐标的原点(0, 0)位于屏幕的左上角,X轴正方向是水平向右,Y轴正方向是竖直向下。在这个坐标系统中,每个像素占据1 x 1的空间。你可以把它想象成是一张坐标值,其中的每个小格都是1个像素。这么说来,一个像素的中心实际上是一个“半像素坐标系”,也就是说,像素(x, y)的中心位置其实是在(x + 0.5, y + 0.5)的位置上。因此,如果我们使用QPainter在(100, 100)处绘制一个像素,那么,这个像素的中心坐标是(100.5, 100.5)。
 
这种细微的差别在实际应用中,特别是对坐标要求精确的系统中是很重要的。首先,只有在禁止反走样,也就是默认状态下,才会有这0.5像素的偏移;如果使用了反走样,那么,我们画(100, 100)位置的像素时,QPainter会在(99.5, 99.5),(99.5, 100.5),(100.5, 99.5)和(100.5, 100.5)四个位置绘制一个亮色的像素,这么产生的效果就是在这四个像素的焦点处(100, 100)产生了一个像素。如果不需要这个特性,就需要将QPainter的坐标系平移(0.5, 0.5)。
 
这一特性在绘制直线、矩形等图形的时候都会用到。下图给出了在没有反走样技术时,使用drawRect(2, 2, 6, 5)绘制一个矩形的示例。在No Pen的情况下,请注意矩形左上角的像素是在(2, 2),其中心位置是在(2.5, 2.5)的位置。然后注意下有不同的Pen的值的绘制样式,在Pen宽为1时,实际画出的矩形的面积是7 x 6的(图出自C++ GUI Programming with Qt4, 2nd Edition):
在具有反走样时,使用drawRect(2, 2, 6, 5)的效果如下(图出自C++ GUI Programming with Qt4, 2nd Edition):
注意我们前面说过,通过平移QPainter的坐标系来消除着0.5像素的差异。下面给出了使用drawRect(2.5, 2.5, 6, 5)在反走样情况下绘制的矩形(图出自C++ GUI Programming with Qt4, 2nd Edition):
请对比与上图的区别。
 
在上述的QPainter的默认坐标系下,QPainter提供了视口(viewport)窗口(window)机制,用于绘制与绘制设备的大小和分辨率无关的图形。视口和窗口是紧密的联系在一起的,它们一般都是矩形。视口是由物理坐标确定其大小,而窗口则是由逻辑坐标决定。我们在使用QPainter进行绘制时,传给QPainter的是逻辑坐标,然后,Qt的绘图机制会使用坐标变换将逻辑坐标转换成物理坐标后进行绘制。
 
通常,视口和窗口的坐标是一致的。比如一个600 x 800的widget(这是一个widget,或许是一个对话框,或许是一个面板等等),默认情况下,视口和窗口都是一个320 x 200的矩形,原点都在(0, 0),此时,视口和窗口的坐标是相同的。
 
注意到QPainter提供了setWindow()和setViewport()函数,用来设置视口和窗口的矩形大小。比如,在上面所述的320 x 200的widget中,我们要设置一个从(-50, -50)到(+50, +50),原点在中心的矩形窗口,就可以使用
 
painter.setWindow(-50, -50, 100, 100);
 
其中,(-50, -50)指明了原点,100, 100指明了窗口的长和宽。这里的“指明原点”意思是,逻辑坐标的(-50, -50)对应着物理坐标的(0, 0);“长和宽”说明,逻辑坐标系下的长100,宽100实际上对应物理坐标系的长320,宽200。
 
或许你已经发现这么一个好处,我们可以随时改变window的范围,而不改变底层物理坐标系。这就是前面所说的,视口与窗口的作用:“绘制与绘制设备的大小和分辨率无关的图形”,如下图所示(图出自C++ GUI Programming with Qt4, 2nd Edition):
 
 
除了视口与窗口的变化,QPainter还提供了一个“世界坐标系”,同样也可以变换图形。所不同的是,视口与窗口实际上是统一图形在两个坐标系下的表达,而世界坐标系的变换是通过改变坐标系来平移、缩放、旋转、剪切图形。为了清楚起见,我们来看下面一个例子:
 
void PaintedWidget::paintEvent(QPaintEvent *event)
{
        QPainter painter(this);
        QFont font("Courier", 24);
        painter.setFont(font);
        painter.drawText(50, 50, "Hello, world!");
        QTransform transform;
        transform.rotate(+45.0);
        painter.setWorldTransform(transform);
        painter.drawText(60, 60, "Hello, world!");
}
 
为了显示方便,我在这里使用了QFont改变了字体。QPainter的drawText()函数提供了绘制文本的功能。它有几种重载形式,我们使用了其中的一种,即制定文本的坐标然后绘制。需要注意的是,这里的坐标是文字左下角的坐标(特别提醒这一点,因为很多绘图系统,比如Java2D都是把左上角作为坐标点的)!下面是运行结果:
 
我们使用QTransform做了一个rotate变换。这个变换就是旋转,而且是顺时针旋转45度。然后我们使用这个变换设置了QPainter的世界坐标系,注意到QPainter是一个状态机,所以这种变换并不会改变之前的状态,因此只有第二个Hello,
world!被旋转了。确切的说,被旋转的是坐标系而不是这个文字!请注意体会这两种说法的不同。

Qt学习之路(28): 坐标变换的更多相关文章

  1. Qt 学习之路 2(28):坐标系统

    Qt 学习之路 2(28):坐标系统 豆子 2012年11月25日 Qt 学习之路 2 59条评论 在经历过实际操作,以及前面一节中我们见到的那个translate()函数之后,我们可以详细了解下 Q ...

  2. Qt 学习之路 2(71):线程简介

    Qt 学习之路 2(71):线程简介 豆子 2013年11月18日 Qt 学习之路 2 30条评论 前面我们讨论了有关进程以及进程间通讯的相关问题,现在我们开始讨论线程.事实上,现代的程序中,使用线程 ...

  3. Qt 学习之路 2(67):访问网络(3)

    Qt 学习之路 2(67):访问网络(3) 豆子 2013年11月5日 Qt 学习之路 2 16条评论 上一章我们了解了如何使用我们设计的NetWorker类实现我们所需要的网络操作.本章我们将继续完 ...

  4. Qt 学习之路 2(66):访问网络(2)

    Home / Qt 学习之路 2 / Qt 学习之路 2(66):访问网络(2) Qt 学习之路 2(66):访问网络(2)  豆子  2013年10月31日  Qt 学习之路 2  27条评论 上一 ...

  5. Qt 学习之路 2(65):访问网络(1)

    Home / Qt 学习之路 2 / Qt 学习之路 2(65):访问网络(1) Qt 学习之路 2(65):访问网络(1)  豆子  2013年10月11日  Qt 学习之路 2  18条评论 现在 ...

  6. Qt 学习之路 2(64):使用 QJsonDocument 处理 JSON

    Home / Qt 学习之路 2 / Qt 学习之路 2(64):使用 QJsonDocument 处理 JSON Qt 学习之路 2(64):使用 QJsonDocument 处理 JSON  豆子 ...

  7. Qt 学习之路 2(63):使用 QJson 处理 JSON

    Home / Qt 学习之路 2 / Qt 学习之路 2(63):使用 QJson 处理 JSON Qt 学习之路 2(63):使用 QJson 处理 JSON  豆子  2013年9月9日  Qt ...

  8. Qt 学习之路 2(62):保存 XML

    Home / Qt 学习之路 2 / Qt 学习之路 2(62):保存 XML Qt 学习之路 2(62):保存 XML  豆子  2013年8月26日  Qt 学习之路 2  9条评论 前面几章我们 ...

  9. Qt 学习之路 2(60):使用 DOM 处理 XML

    Qt 学习之路 2(60):使用 DOM 处理 XML  豆子  2013年8月3日  Qt 学习之路 2  9条评论 DOM 是由 W3C 提出的一种处理 XML 文档的标准接口.Qt 实现了 DO ...

随机推荐

  1. 分位函数(四分位数)概念与pandas中的quantile函数

    p分位函数(四分位数)概念与pandas中的quantile函数 函数原型 DataFrame.quantile(q=0.5, axis=0, numeric_only=True, interpola ...

  2. ==与equals与hashCode的区别联系。

    前言:对于引用类型的变量,它涉及到两块内存,一块是堆中的内存,用于存放new出来的对象(就是对象的具体内容):另一块是栈内存,用来存放变量在堆内存中的地址. 1,“==” 判断两个变量是否是同一个变量 ...

  3. HDU 4123 Bob’s Race(树形DP,rmq)

    Bob’s Race Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  4. 《Go语言实战》摘录:6.2 并发 - goroutine

    6.2 goroutine

  5. Delphi 获取命令行输出的函数

    function GetDosOutput(CommandLine: string; Work: string = 'C:\'): string; var SA: TSecurityAttribute ...

  6. The Win32 Rundll and Rundll32 Interface Related Topics

    The Win32 Rundll and Rundll32 Interface Related Topics Microsoft Knowledge Base Article Q164787 Appl ...

  7. 认识TWICImage类

    Graphics 单元新增了 TWICImage 类, 该类的功能源于新增的 Wincodec.pas 单元. WIC 支持的图像有 BMP.PNG.ICO.JPEG.GIF.TIFF.HDP(HDP ...

  8. 新版ADT创建项目时出现appcompat_v7的问题

    做Android开发的朋友最近会发现,更新ADT至22.6.0版本之后,创建新的安装项目,会出现appcompat_v7的内容.并且是创建一个新的内容就会出现.这到底是怎么回事呢?原来appcompa ...

  9. NSString 拼接字符串

    NSString* string; // 结果字符串 NSString* string1, string2; //已存在的字符串,需要将string1和string2连接起来 //方法1. strin ...

  10. zTree树

    <link rel="stylesheet" href="/static/zTreeV3/css/metroStyle/metroStyle.css" t ...