坐标系统变换

在QPainter的默认坐标系中,点(0,0)位于绘图设备的左上角,x坐标向右增长,y坐标向下增长。默认坐标系的每个像素占1x1大小的区域。

理论上,像素的中心取决于半像素坐标。例如,窗口部件的左上角像素覆盖了点 (0, 0) 到点(1, 1)的区域,它的中心在 (0.5,0.5) 位置。如果告诉 QPainter 绘制一个像素,例如 (100,100) ,它会相应地在两个方向做+0.5 的偏移,使得像素点的中心位置在(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) 四个像素点着浅灰色,给人的印象是一个像素正好位于四个像素的重合处。如果不需要这种效果,可以通过指定半像素坐标或者通过偏移 QPainter( +0.5, +0.5) 来避免这种效果的出现。

当绘制图形时,例如线、矩形和椭圆,可以使用相似的规则。图 8.7 表明当反走样关闭时调用由drawRect(2, 2, 6, 5) 时不同画笔宽度产生的变化。值得特别注意的是,6x5宽度为1的矩形可以有效地覆盖 7x6 的区域。这不同于旧的工具,包括早期的 Qt 版本,其本质是要支持真正可缩放的、与分辨率无关的矢量图。图 8.8 表明当反走样打开时调用 drawRect(2, 2, 6, 5) 产生的结果,图 8.9表明当指定半像素坐标时产生的结果。

既然已经了解了默认的坐标系统,就可以进一步看一下怎样使用 QPainter视口、窗口、世界矩阵。(在上下文中,术语"窗口"说的不是顶层窗口部件窗口,"视口"跟 ScrollArea的视口也没有关系。)

视口和窗口密不可分。视口是物理坐标系下指定的任意矩形。窗口也是指同一矩形,只不过是在逻辑坐标系下。当绘制图形时,在逻辑坐标系下指定的点,这些坐标都是基于当前的窗口-视口设定并以线性代数的方式转换为物理坐标的。

默认情况下,视口和窗口都被设置成设备的矩形。例如,如果设备是 320 x 200 的矩形,视口和窗口都是左上角为 (0, 0) 的320 x 200 的相同矩形。这种情况下,逻辑坐标系和物理坐标系是一致的。

这种窗口-视口机制对于编写独立于绘制设备大小和分辨率的绘制代码是很有用的。例如,如果想让逻辑坐标从(-50, -50)到(+50, +50),并且(0, 0)在中间,可以这样设置窗口:

painter.setWindow(-50, -50, 100, 100);

(-50, -50)指定了原点,(100, 100)指定了宽和高。这意味着逻辑坐标(-50, -50)对应物理坐标(0, 0),而逻辑坐标(+50, +50)对应物理坐标(320, 200),如图所示,这个例子没有改变视口。

世界变换

世界变换:是在窗口-视口转换之外使用的变换矩阵。它允许移动、缩放、旋转或者拉伸绘制的项。

PaintedWidget.h

#ifndef PAINTEDWIDGET_H
#define PAINTEDWIDGET_H#include <QWidget>
#include <QPainter>QT_BEGIN_NAMESPACE
namespace Ui { class PaintedWidget; }
QT_END_NAMESPACEclass PaintedWidget : public QWidget
{Q_OBJECTpublic:PaintedWidget(QWidget *parent = nullptr);~PaintedWidget();protected:void paintEvent(QPaintEvent *event);private:Ui::PaintedWidget *ui;
};#endif // PAINTEDWIDGET_H

PaintedWidget.cpp

#include "paintedwidget.h"
#include "ui_paintedwidget.h"PaintedWidget::PaintedWidget(QWidget *parent): QWidget(parent), ui(new Ui::PaintedWidget)
{ui->setupUi(this);
}PaintedWidget::~PaintedWidget()
{delete ui;
}void PaintedWidget::paintEvent(QPaintEvent *event)
{QPainter painter(this);QFont font("Courier", 24); //font用于设置字体,显示更加方便painter.setFont(font);painter.drawText(50, 50, "Hello, world!"); //drawText()用于绘制文本,50,50指代文本左下角的坐标QTransform transform;transform.rotate(+45.0); //rotate用于将文本顺时针旋转,将文本顺时针旋转45度painter.setWorldTransform(transform);painter.drawText(60, 60, "Hello, world!");
}

main.cpp

#include "paintedwidget.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);PaintedWidget w;w.show();return a.exec();
}

模仿烤箱的定时器

为了描述绘制器的变换,我们写一个OvenTimer模仿烤箱的定时器,它是烤箱内置的钟表。用户可以单击刻度来设置持续时间。转轮会自动地逆时针转到0,OverTimer在这一点发射timeout()信号。

OvenTimer.h

#ifndef OVENTIMER_H
#define OVENTIMER_H#include <QDateTime>
#include <QWidget>class QTimer;class OvenTimer : public QWidget
{Q_OBJECTpublic:OvenTimer(QWidget *parent = 0);void setDuration(int secs);int duration() const;void draw(QPainter *painter);signals:void timeout();protected:void paintEvent(QPaintEvent *event);void mousePressEvent(QMouseEvent *event);private:QDateTime finishTime;QTimer *updateTimer;QTimer *finishTimer;
};#endif

OvenTimer.cpp

#include <QtGui>
#include <cmath>#ifndef M_PI
#define M_PI 3.14159265359
#endif#include "oventimer.h"const double DegreesPerMinute = 7.0;
const double DegreesPerSecond = DegreesPerMinute / 60;
const int MaxMinutes = 45;
const int MaxSeconds = MaxMinutes * 60;
const int UpdateInterval = 5;OvenTimer::OvenTimer(QWidget *parent): QWidget(parent)
{finishTime = QDateTime::currentDateTime();  //获取当前时间updateTimer = new QTimer(this);connect(updateTimer, SIGNAL(timeout()), this, SLOT(update()));finishTimer = new QTimer(this);finishTimer->setSingleShot(true);connect(finishTimer, SIGNAL(timeout()), this, SIGNAL(timeout()));connect(finishTimer, SIGNAL(timeout()), updateTimer, SLOT(stop()));QFont font;font.setPointSize(8);   //设置窗口字体的大小setFont(font);
}void OvenTimer::setDuration(int secs)   //设置了烤箱定时器的持续时间为给定的秒数。
{secs = qBound(0, secs, MaxSeconds); //防止越界0~MaxSecondsfinishTime = QDateTime::currentDateTime().addSecs(secs);if (secs > 0) {updateTimer->start(UpdateInterval * 1000);finishTimer->start(secs * 1000);} else {updateTimer->stop();finishTimer->stop();}update();
}int OvenTimer::duration() const //获取定时器完成前剩余的秒数。
{int secs = QDateTime::currentDateTime().secsTo(finishTime);if (secs < 0)secs = 0;return secs;
}void OvenTimer::mousePressEvent(QMouseEvent *event)
{QPointF point = event->pos() - rect().center();double theta = std::atan2(-point.x(), -point.y()) * 180.0 / M_PI;setDuration(duration() + int(theta / DegreesPerSecond));update();
}void OvenTimer::paintEvent(QPaintEvent * /* event */)
{QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing, true);int side = qMin(width(), height()); //返回两个参数中较小的一个painter.setViewport((width() - side) / 2, (height() - side) / 2,side, side);painter.setWindow(-50, -50, 100, 100);draw(&painter);
}void OvenTimer::draw(QPainter *painter)
{static const int triangle[3][2] = {{ -2, -49 }, { +2, -49 }, { 0, -47 }};QPen thickPen(palette().foreground(), 1.5); //palette().foreground()得到笔刷QPen thinPen(palette().foreground(), 0.5);QColor niceBlue(150, 150, 200);painter->setPen(thinPen);painter->setBrush(palette().foreground());painter->drawPolygon(QPolygon(3, &triangle[0][0]));QConicalGradient coneGradient(0, 0, -90.0);coneGradient.setColorAt(0.0, Qt::darkGray);coneGradient.setColorAt(0.2, niceBlue);coneGradient.setColorAt(0.5, Qt::white);coneGradient.setColorAt(1.0, Qt::darkGray);painter->setBrush(coneGradient);painter->drawEllipse(-46, -46, 92, 92);QRadialGradient haloGradient(0, 0, 20, 0, 0);haloGradient.setColorAt(0.0, Qt::lightGray);haloGradient.setColorAt(0.8, Qt::darkGray);haloGradient.setColorAt(0.9, Qt::white);haloGradient.setColorAt(1.0, Qt::black);painter->setPen(Qt::NoPen);painter->setBrush(haloGradient);painter->drawEllipse(-20, -20, 40, 40);QLinearGradient knobGradient(-7, -25, 7, -25);knobGradient.setColorAt(0.0, Qt::black);knobGradient.setColorAt(0.2, niceBlue);knobGradient.setColorAt(0.3, Qt::lightGray);knobGradient.setColorAt(0.8, Qt::white);knobGradient.setColorAt(1.0, Qt::black);painter->rotate(duration() * DegreesPerSecond);painter->setBrush(knobGradient);painter->setPen(thinPen);painter->drawRoundRect(-7, -25, 14, 50, 99, 49);for (int i = 0; i <= MaxMinutes; ++i) {if (i % 5 == 0) {painter->setPen(thickPen);painter->drawLine(0, -41, 0, -44);painter->drawText(-15, -41, 30, 30,Qt::AlignHCenter | Qt::AlignTop,QString::number(i));} else {painter->setPen(thinPen);painter->drawLine(0, -42, 0, -44);}painter->rotate(-DegreesPerMinute);}
}

在构造函数中,创建了两个 QTimer对象:updateTimer用来每隔5秒刷新窗口部件的外观, finishTimer 在定时器达到0时发射 timeout()信号。 finishTìmer 只需要执行一次,因此我们调用setSingleShot(true)时。默认情况下,定时器会重复触发直到它们被停止或销毁。最后一个 connect()调用是一个优化,目的是当定时器停止运行时停止更新窗口部件。

在构造函数的末尾,我们把用来绘制窗口部件的字体的磅值设置为8磅。这保证了显示的数字大小大体一致。

setDuration()
setDuration()函数设置了烤箱定时器的持续时间为给定的秒数。使用Qt的qBound()函数意味着可以不必写这样的代码:

if(secs < 0)
{secs = 0;
}
else if(secs > MaxSeconds)
{secs = MaxSeconds;
}

我们通过在当前时间[从QDataTime::currentDateTime()获得]加上间隔时间得到结束时间,并且把它保存到finishTime私有变量中。在结束的地方,我们调用update(),用新的间隔时间重绘窗口部件。

finishTime变量的类型是QDataTime。这个变量保存日期和时间,避免了由于当前时间是在午夜之前并且完成时间是在午夜之后而产生的折回缺陷。

duration()
duration()返回定时器完成前剩余的秒数。如果定时器未激活,返回0。

mousePressEvent()
当用户点击窗口部件时,我们使用一个巧妙且有效的数学公式找到最近的刻度,并且使用这个结果来设置新的持续时间,然后安排一个重绘。用户点过的刻度移到了顶点,然后随着时间逆时针移动到0。

paintEvent()
在paintEvent()函数中,我们把视口设置成窗口部件中最大的正方形,把窗口设置成(-50, -50, 100, 100)的矩形,也就是说,100x100的矩形从(-50, -50)扩展到(+50, +50)。qMIn()模板函数返回两个参数中最小的一个。然后调用draw()函数实际执行绘图。

如果没有设置视口为正方形,当窗口部件被缩放为非正方形的矩形时,烤箱定时器就会变为椭圆。为了避免这种变形,必须把视口和窗口设置成具有相同纵横比的矩形。

draw()
首先,在窗口部件顶部的0位置绘制一个小三角形。这个三角形的三个坐标都是由代码直接给定的,我们用drawPolygon()绘制它。

窗口-视口的方便之处就在于可以在绘制命令中使用硬编码,却能得到很好的缩放效果。

我们绘制外面的圆,用锥形渐变填充。锥形的中心点位于(0,0),角度是-90度。

我们用辐射渐变填充内部圆。渐变的中心点和焦点都在(0,0)。渐变的半径是20。

我们调用rotate()来旋转绘图器的坐标系统。在旧的坐标系统中,0-minute的标记在最上面;现在这个标记移到了对应着剩余时间的位置。在这个旋转之后,我们绘制了矩形把手,因为它的方位依赖于旋转的角度。

在for循环中,我们在外面圆的边缘绘制了标记,并且绘制的数字都是5分钟的倍数。文本被放置在标记下边一个不可见的矩形里。在每个迭代的最后,顺时针旋转绘图器7度,这相当于1分钟。下次将把标记绘制在圆的其他位置,尽管传给drawLine()和drawText()的坐标总是一样的。

for循环中的代码有一个小缺陷,如果执行了更多的迭代,这一问题会变得很明显。每次调用rotate(),就高效地用一个旋转矩阵去乘当前的世界变换,从而创建一个新的世界变换。浮点数的舍入误差不断累积,得到了越来越不准确的世界变换。用下面的办法重写这段代码可以避免这个问题,使用save()和restore()函数为每次迭代保存和加载原始的矩阵:

for(int i = 0; i <= MaxMinutes; ++i)
{painter->save();painter->rotate(-i*DegreesPerMinute);if(i%5 == 0){painter->setPen(thickPen);painter->drawLine(0, -41, 0, -44);painter->drawText(-15, -41, 30, 30, Qt::AlignHCenter | Qt::AlignTop, QString::number(i));}else{painter->setPen(thinPen);painter->drawLine(0, -42, 0, -44);}painter->restore();
}

实现烤箱定时器的另外一个办法是自己计算(x,y)位置,使用sin()和cos()函数找到圆上的位置。但之后将仍然需要利用移动和旋转并以一定的角度来绘制文本。

main.cpp

#include "oventimer.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);OvenTimer w;w.show();return a.exec();
}

Qt4_坐标系统变换相关推荐

  1. DirectX 9的坐标系统变换

    http://www.cppblog.com/lovedday/archive/2007/05/04/23389.html DirectX 9的坐标系统变换 世界坐标系到摄影坐标系的变换 摄影坐标系的 ...

  2. VTK修炼之道9:坐标系统及空间变换(窗口-视图分割)

    1.坐标系统 计算机图形学里常用的坐标系统主要有四种,分别是:Model坐标系统.World坐标系统.View坐标系统和Display坐标系统,以及两种表示坐标点的方式:以屏幕像素值为单位和归一化坐标 ...

  3. Qt 2D绘图之二:抗锯齿渲染和坐标系统

    一.抗锯齿渲染 1.1 逻辑绘图 图形基元的大小(宽度和高度)始终与其数学模型相对应,下图示意了忽略其渲染时使用的画笔的宽度的样子. 1.2 物理绘图(默认情况) 在默认的情况下,绘制会产生锯齿,并且 ...

  4. 【Qt】2D绘图之坐标系统

    00. 目录 文章目录 00. 目录 01. 概述 02. 开发环境 03. Qt坐标系统 04. 坐标系统变换 05. 平移变换 06. 缩放变换 07. 扭曲变换 08. 旋转变换 09. 附录 ...

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

    [Qt教程] 第16篇 2D绘图(六)坐标系统 楼主  发表于 2013-5-2 20:08:12 | 查看: 738| 回复: 0 坐标系统 版权声明 该文章原创于Qter开源社区(www.qter ...

  6. 《Real Time Rendering》第四章 图形变换

    图形变换是一个将例如点.向量或者颜色等实体进行某种转换的操作.对于计算机图形学的先驱者,掌握图形变换是极为重要的.有了他们,你就可以对象.光源以及摄像机进行定位,变形以及动画添加.你也可以确认所有的计 ...

  7. 坐标系统与投影变换及在ARCGIS中的应用

    原文链接:http://blog.csdn.net/jax_lee/article/details/6764516 概述: 本文共可分为如下几个部分组成: 地球椭球体(Ellipsoid) 大地基准面 ...

  8. 03-VTK基础概念(2)

    3.3 光照 剧场里有各式各样的灯光,三维渲染场景中也一样,可以有多个光照存在.光照和相机是三维渲染场景必备的因素,如果没有指定(像3.1.1_RenderCylinder例子,我们没有给Render ...

  9. SVG中的坐标系统和坐标变换

    视野和世界 2D绘图中很多人会有一个误区,就是我绘图的区域是一个矩形区域.无论新建一个画布还是创建了一个容器,心里都想象里面有一个矩形区域.其实,在SVG当中,矩形区域只是视野,是我们看到的部分.实际 ...

最新文章

  1. 第九周项目一-深复制体验(1)
  2. 网络推广外包——网络推广外包指出网站优化首先考虑关键词分类
  3. 使用SMARTFORM字段参数设置解决SMARTFORM中数量、金额字段显示问题
  4. 前端学习(2685):重读vue电商网站6之如何重置表单
  5. table 权限 展示页面
  6. 微软一站式示例代码库(中文版)2011-03-10版本, 新添加20个示例
  7. java中的weblogic_Java访问Weblogic中的连接池
  8. 汇编语言--x86汇编指令集大全
  9. 微信上老师发的试卷怎样打印?
  10. 分枝定界法matlab,分支定界法 求混合0-1规划问题
  11. Python三大神器之pip用法详解
  12. 如何把大写金额变为小写数字_人民币金额(数字)大小写转换在线工具
  13. gitbub(cp:http://www.linuxidc.com/Linux/2014-03/97821.htm)
  14. 车牌限行:受雾霾天气影响,某市决定当雾霾指数超过设定值时对车辆进行限行,假设车牌号全为数字,且长度不超过6位,限行规则如下:
  15. 多渠道打包,生成不同包名的包
  16. 怎么在cmd中运行python脚本_cmd中运行python脚本智能使用流程
  17. 电脑没声音 小喇叭不见了怎么办
  18. VS Code 的常用快捷键和插件(一)
  19. authorization权限控制_授权(Authorization)
  20. 狄利克雷过程(dirichlet process )和分布

热门文章

  1. 对php的感受100字_这首由半个古风圈歌手演唱的王者荣耀100英雄群像歌,燃炸了...
  2. fonts.googleapis.com加载过慢导致的项目启动过慢的问题
  3. git ssh配置文件 服务器_【GIT】日常开发中的这些Git技巧你知道吗?
  4. [ZJOI2008]骑士
  5. hiho_100_八数码
  6. JTable 的使用
  7. eclipse注释模板
  8. 无法打开登录所请求的数据库 sa。登录失败。 用户 sa 登录失败。
  9. 实用 VSCODE 8类插件安利,提高开发效率
  10. python调用图灵api_python调用API实现智能回复机器人