【QT】坐标系统和坐标变换

目录

[1 坐标变换函数](#1 坐标变换函数)

[1.1 坐标平移](#1.1 坐标平移)

[1.2 坐标旋转](#1.2 坐标旋转)

[1.3 缩放](#1.3 缩放)

[1.4 状态保存与恢复](#1.4 状态保存与恢复)

[2 坐标变换绘图实例](#2 坐标变换绘图实例)

[2.1 绘制3个五角星的程序](#2.1 绘制3个五角星的程序)

[2.2 绘制五角星的PainterPath的定义](#2.2 绘制五角星的PainterPath的定义)

[3 视口和窗口](#3 视口和窗口)

[3.1 视口和窗口的定义与原理](#3.1 视口和窗口的定义与原理)

[3.2 视口和窗口的使用实例](#3.2 视口和窗口的使用实例)

[4 绘图叠加的效果](#4 绘图叠加的效果)


1 坐标变换函数

QPainter在窗口上绘图的默认坐标系统如图8-1所示,这是绘图设备的物理坐标。为了绘图 的方便,QPainter提供了一些坐标变换的功能,通过平移、旋转等坐标变换,得到一个逻辑坐标 系统,使用逻辑坐标系统在某些时候绘图更方便。坐标变换函数见表8-5。


常用的坐标变换是平移、旋转和缩放,使用 世界坐标变换矩阵也可以实现这些变换功能,但 是需要单独定义一个 QTransform 类的变量,对于 QPainter 来说,简单的坐标变换使用 QPainter 自 有的坐标变换函数就足够了。

1.1 坐标平移

坐标平移函数是translate(),其中一种参数形式的函数原型是:
void translate(qreal dx,qreal dy)
表示将坐标系统水平方向平移个单位,垂直方向平移个单位,在缺省的坐标系统中,单位就是像素。如果是从原始状态平移(dx,dy),那么平移后的坐标原点就移到了(dx,dy)。
假设一个绘图窗口宽度为300像素,高度为200像素,则其原始坐标系统如图8-10左所示; 若执行平移函数translate(150,100),则坐标系统水平向右平移150像素,向下平移100像素,平 移后的坐标系统如图8-10右所示,坐标原点在窗口的中心,而左上角的坐标变为(-150,-100), 右下角的坐标变为(150,100)。如此将坐标原点变换到窗口中心在绘制某些图形时是非常方便的。

1.2 坐标旋转

坐标旋转的函数是rotate(),其函数原型为:
void rotate(qreal angle)
它是将坐标系统绕坐标原点顺时针旋转angle角度,单位是度。当angle为正数时是顺时针旋 转,为负数时是逆时针旋转。
在图8-10右的基础上,若执行rotate(90),则得到图8-11所示的坐标系统。

注意:旋转之后并不改变窗口矩形的实际大小,只是改变了坐标轴的方向。
在图8-11的新坐标系下,窗口左上角的坐标变成了(-100,150),而右下角的坐标变成了(100,-150)。

1.3 缩放

缩放函数是scale(),其函数原型为:
void scale(qreal sx,qreal sy)
其中,sx,sy分别为横向和纵向缩放比例,比例大于1是放大,小于1是缩小。

1.4 状态保存与恢复

进行坐标变换时,QPainter内部实际上有一个坐标变换矩阵,用save()保存当前坐标状态,用 restore()恢复上次保存的坐标状态,这两个函数 必须配对使用 , 操作的是一个堆栈对象 。
resetTransform()函数则是复位所有坐标变换操作,恢复原始的坐标系统。

2 坐标变换绘图实例

2.1 绘制3个五角星的程序

创建一个基于QWidget的窗口的应用程序samp8_2,窗体上不放置任何组件。在Widget类的 构造函数和paintEvent()事件中编写代码,代码内容如下。

cpp 复制代码
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    setPalette(QPalette(Qt::white)); //设置窗口背景色
    setAutoFillBackground(true);
    resize(600,300); //固定初始化窗口大小
}

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter    painter(this);//创建QPainter对象
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::TextAntialiasing);

//生成五角星的5个顶点的,假设原点在五角星中心
    qreal   R=100; //半径
    const   qreal Pi=3.14159;
    qreal   deg=Pi*72/180;
    QPoint points[5]={
        QPoint(R,0),
        QPoint(R*std::cos(deg),-R*std::sin(deg)),
        QPoint(R*std::cos(2*deg),-R*std::sin(2*deg)),
        QPoint(R*std::cos(3*deg),-R*std::sin(3*deg)),
        QPoint(R*std::cos(4*deg),-R*std::sin(4*deg)),
    };
//设置字体
    QFont   font;
    font.setPointSize(12);
    font.setBold(true);
    painter.setFont(font);
//设置画笔
    QPen    penLine;
    penLine.setWidth(2); //线宽
    penLine.setColor(Qt::blue); //划线颜色
    penLine.setStyle(Qt::SolidLine);//线的类型,实线、虚线等
    penLine.setCapStyle(Qt::FlatCap);//线端点样式
    penLine.setJoinStyle(Qt::BevelJoin);//线的连接点样式
    painter.setPen(penLine);
//设置画刷
    QBrush  brush;
    brush.setColor(Qt::yellow); //画刷颜色
    brush.setStyle(Qt::SolidPattern); //画刷填充样式
    painter.setBrush(brush);
//设计绘制五角星的PainterPath,以便重复使用
    QPainterPath starPath;
    starPath.moveTo(points[0]);
    starPath.lineTo(points[2]);
    starPath.lineTo(points[4]);
    starPath.lineTo(points[1]);
    starPath.lineTo(points[3]);
    starPath.closeSubpath(); //闭合路径,最后一个点与第一个点相连
    starPath.addText(points[0],font,"0"); //显示端点编号
    starPath.addText(points[1],font,"1");
    starPath.addText(points[2],font,"2");
    starPath.addText(points[3],font,"3");
    starPath.addText(points[4],font,"4");
//绘图
    painter.save(); //保存坐标状态
    painter.translate(100,120);
    painter.drawPath(starPath); //画星星
    painter.drawText(0,0,"S1");
    painter.restore(); //恢复坐标状态

    painter.translate(300,120); //平移
    painter.scale(0.8,0.8); //缩放
    painter.rotate(90); //顺时针旋转
    painter.drawPath(starPath);//画星星
    painter.drawText(0,0,"S2");

    painter.resetTransform(); //复位所有坐标变换
    painter.translate(500,120); //平移
    painter.rotate(-145); //逆时针旋转
    painter.drawPath(starPath);//画星星
    painter.drawText(0,0,"S3");
}

运行该实例程序,得到如图8·12所示的结果,在窗口上绘制了3个五角星。

第1个是原始的五角星;第2个是缩小为0.8倍,顺时针旋转90度的五角星;第3个是逆时 针旋转145度的五角星。这个程序中使用到了QPainterPath和QPainter的坐标变换功能。

2.2 绘制五角星的PainterPath的定义

首先假设一个五角星的中心点是原点,第0个点在x轴上,五角星外接圆半径为100,计算 出5个点的坐标,保存在points数组中。
然后定义了一个QPainterPath类的变量starPath,用于记录画五角星的过程,就是几个点的连 线过程,并且标注点的编号。使用QPainterPath的优点就是定义一个QPainterPath类型的变量记录
一个复杂图形的绘制过程后,可以重复使用。虽然points数组中的点的坐标是假设五角星的中心 点是原点,在绘制不同的五角星时只需将坐标平移到新的原点位置,就可以绘制不同的五角星。
绘制第1个五角星的程序是:

painter.save(); //保存坐标状态
painter.translate(100,120);
painter.drawPath(starPath); //画星星
painter.drawText(0,0,"S1");
painter.restore(); //恢复坐标状态
这里,save()函数保存当前坐标状态(也就是坐标的原始状态),然后将坐标原点平移到(100,120), 调用绘制路径的函数drawPath(starPath)绘制五角星,在五角星的中心标注"S1"表示第1个五角星,最后调用restore()函数恢复上次的坐标状态。这样就以(100,120)为中心点绘制了第1个五角星。
绘制第2个五角星的程序是:
painter.translate(300,120); //平移
painter.scale(0.8,0.8); //缩放
painter.rotate(90); //顺时针旋转
painter.drawPath(starPath);//画星星
painter.drawText(0,0,"S2");
这里首先调用坐标平移函数translate(300,120)。由于上次restore()之后回到坐标初始状态,所 以这次平移后,坐标原点到了物理坐标(300,120)。而如果没有上一个restore(),会在上一次的坐
标基础上平移。
绘图之前调用了缩放函数scale(0.8,0.8),使得缩小到原来的0.8,再顺时针旋转90°,然后调 用绘制路径函数drawPath(starPath)绘制五角星,就得到了第2个五角星。
绘制第3个五角星时首先使坐标复位,即:
painter.resetTransform();//复位所有坐标变换
这样会复位所有坐标变换,又回到了原始坐标。

3 视口和窗口

3.1 视口和窗口的定义与原理

绘图设备的物理坐标是基本的坐标系,通过QPainter的平移、旋转等变换可以得到更容易操 作的逻辑坐标。
为了实现更方便的坐标, QPainter还提供了视口(Viewport)和窗口(Window)坐标系,通 过QPainter内部的坐标变换矩阵自动转换为绘图设备的物理坐标。
视口表示绘图设备的任意一个矩形区域的物理坐标,可以只选取物理坐标的一个矩形区域用 于绘图。默认情况下,视口等于绘图设备的整个矩形区。
窗口与视口是同一个矩形,只不过是用逻辑坐标定义的坐标系。窗口可以直接定义矩形区的 逻辑坐标范围。图8-13是对视口和窗口的图示说明。

图8-13左图中的矩形框代表绘图设备的物理大小和坐标范围,假设宽度为300像素,高度为 200像素。现在要取其中间的一个正方形区域作为视口, 灰色的正方形就是视口 ,绘图设备的物 理坐标中,视口的左上角坐标为(50,0),右下角坐标为(250,200)。定义此视口,可以使用QPainter 的setViewport()函数,其函数原型为:
void QPainter::setViewport(int x,int y,int width,int height)
要定义图8-13左图中的视口,使用下面的语句:
painter.setViewport(50,0,200,200);
表示从绘图设备物理坐标系统的起点(50,0)开始,取宽度为200、高度为200的一个矩形区域 作为视口。
对于图8-13左图的视口所表示的正方形区域,定义一个窗口(图8-13右图), 窗口坐标的中心在 正方形中心,并设置正方形的逻辑边长为100。可使用QPainter的setWindow()函数,其函数原型为:
void QPainter::setWindow(int x,int y,int width,int height)
所以,此处定义窗口的语句是:
painter.setWindow(-50,-50,100,100);
它表示对应于视口的矩形区域,其窗口左上角的逻辑坐标是(-50,-50),窗口宽度为100,高度 为100。这里设置的窗口还是一个正方形,使得从视口到窗口变换时,长和宽的变化比例是相同 的。实际可以任意指定窗口的逻辑坐标范围,长和宽的变化比例不相同也是可以的。

3.2 视口和窗口的使用实例

使用窗口坐标的优点是,只需按照窗口坐标定义来绘图,而不用管实际的物理坐标范围的大小。 例 如在一个固定边长为100的正方形窗口内绘图,当实际绘图设备大小变化时,绘制的图形会自动变化大 小。这样,就可以将绘图功能与绘图设备隔离开来,使得绘图功能适用于不同大小、不同类型的设备。
实例samp8_3演示了使用视口和窗口的方法,项目创建与samp8_1类似,只在Widegt的 paintEvent()事件里添加绘图代码。

cpp 复制代码
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    int W=width();
    int H=height();
    int side=qMin(W,H);//取长和宽的小值
    QRect rect((W-side)/2, (H-side)/2,side,side); //viewport矩形区
    painter.drawRect(rect); //Viewport大小
//    painter.setViewport((W-side)/2, (H-side)/2,side,side);//设置Viewport,物理坐标范围
    painter.setViewport(rect);//设置Viewport
    painter.setWindow(-100,-100,200,200); // 设置窗口大小,逻辑坐标
    painter.setRenderHint(QPainter::Antialiasing);
//    p.setRenderHint(QPainter::TextAntialiasing);

//设置画笔
    QPen pen;
    pen.setWidth(1); //线宽
    pen.setColor(Qt::red); //划线颜色
    pen.setStyle(Qt::SolidLine);//线的类型,实线、虚线等
    painter.setPen(pen);
    for(int i=0; i<36;i++)
    {
        painter.drawEllipse(QPoint(50,0),50,50);
        painter.rotate(10);
    }
}

运行实例程序samp8_3,可以得到如图8-14所示的图形效果。当窗口的宽度大于高度时,以 高度作为正方形的边长;当高度大于宽度时,以宽度作为正方形边长,且图形是自动缩放的。

图8-14 实例samp8_3使用窗口坐标的绘图效果
程序首先定义了一个正方形视口,正方形以绘图设备的长、宽中较小者为边长。 然后定义窗口,定义的窗口是原点在中心,边长为 200 的正方形。
图8-14的图形效果实际上是画了36个圆得到的,循环部分的代码如下:

for(int i=0; i<36;i++)
{
painter.drawEllipse(QPoint(50,0),50,50);
painter.rotate(10);
}
每个圆的圆心在X轴上的(50,0),半径为50。画完一个圆之后坐标系旋转10°,再画相同的 圆,这就巧妙应用了坐标轴的旋转。

4 绘图叠加的效果

对上面的程序稍作修改,增加渐变填充和叠加效果的设置。

cpp 复制代码
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    int W=width();
    int H=height();
    int side=qMin(W,H);//取长和宽的小值
    QRect rect((W-side)/2, (H-side)/2,side,side); //viewport矩形区
    painter.drawRect(rect); //Viewport大小
//    painter.setViewport((W-side)/2, (H-side)/2,side,side);//设置Viewport,物理坐标范围
    painter.setViewport(rect);//设置Viewport
    painter.setWindow(-100,-100,200,200); // 设置窗口大小,逻辑坐标
    painter.setRenderHint(QPainter::Antialiasing);
//    p.setRenderHint(QPainter::TextAntialiasing);

//设置画笔
    QPen pen;
    pen.setWidth(1); //线宽
    pen.setColor(Qt::red); //划线颜色
    pen.setStyle(Qt::SolidLine);//线的类型,实线、虚线等
    painter.setPen(pen);
//线性渐变
    QLinearGradient  linearGrad(0,0,100,0);//从左到右,
    linearGrad.setColorAt(0,Qt::yellow);//起点颜色
    linearGrad.setColorAt(1,Qt::green);//终点颜色
    linearGrad.setSpread(QGradient::PadSpread);  //展布模式
    painter.setBrush(linearGrad);

    //设置复合模式
    painter.setCompositionMode(QPainter::RasterOp_NotSourceXorDestination);
//    painter.setCompositionMode(QPainter::CompositionMode_Difference);
//    painter.setCompositionMode(QPainter::CompositionMode_Exclusion);
    for(int i=0; i<36;i++)
    {
        painter.drawEllipse(QPoint(50,0),50,50);
        painter.rotate(10);
    }
}

在上面的程序中,对单个圆使用了线性渐变填充,单个圆从左到右,由黄色渐变为绿色。使用QPainter::setCompositionMode()函数设置组合模式,即后面绘制的图与前面绘制的图的叠加模式。函数参数是一个QPainter::CompositionMode枚举类型值,可以查看Qt帮助,这个枚举

类型有近40种取值,表示了后绘制图形与前面图形的不同叠加运算方式。

图8-15是其中两种叠加模式下的绘图效果,可以发现采用不同的叠加模式,可以得到不同的绘图效果,甚至是意想不到的绚丽效果。用户可以自己修改程序,设置不同渐变颜色、渐变填充模式、不同叠加模式,也许能绘制出更炫的图形呢。

图8-15 渐变填充和叠加效果,(左)CompositionMode_Difference模式叠加,(右)RasterOp_NotSourceXorDestination模式叠加

相关推荐
猷咪20 分钟前
C++基础
开发语言·c++
IT·小灰灰22 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧24 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q24 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳024 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾24 分钟前
php 对接deepseek
android·开发语言·php
2601_9498683628 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
星火开发设计42 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
qq_177767371 小时前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos
一匹电信狗1 小时前
【LeetCode_21】合并两个有序链表
c语言·开发语言·数据结构·c++·算法·leetcode·stl