QPainter绘图

1.QPainter与QPaintDevice

Qt的绘图系统使用户可以在屏幕或打印设备上用相同的API绘图,绘图系统基于QPainter、QPaintDevice和QPaintEngine类。QPainter是用来进行绘图操作的类,QPaintDevice是一个可以使用QPainter进行绘图的抽象的二维界面,QPaintEngine给QPainter提供在不同设备上绘图的接口。QPaintEngine类由QPainter和QPaintDevice内部使用,应用程序一般无需和QPaintEngine打交道,除非要创建自己的设备类型。

一般的绘图设备包括QWidget、QPixmap、QImage等,这些绘图设备为QPainter提供一个“画布”。

2.paintEvent事件和绘图区

    QWidget类及其子类是最常用的绘图设备,从QWidget类继承的类都有paintEvent()事件,要在设备上绘图,只需重定义此事件并编写响应代码。创建一个QPainter对象获取绘图设备的接口,然后就可以在绘图设备的“画布”上绘图了。

在paintEvent()事件里绘图的基本程序结构是:

void Widget::paintEvent(QPaintEvent *event)
{
   QPainter   painter(this);  //创建与绘图设备关联的QPainter对象
...//painter在设备的窗口上画图
}

首先创建一个属于本绘图设备的QPainter对象painter,然后使用这个painter在绘图设备的窗口上画图。

QWidget的绘图区就是其窗口内部区域。如图1所示是在一个QWidget窗口上绘制了一个填充矩形(这个实心矩形及其边框是程序绘制的图形,其他直线和文字是为说明而加的),整个窗口内部的矩形区就是QPainter可以绘图的区域。

QWidget的内部绘图区的坐标系统如图1所示,坐标系统的单位是像素。左上角坐标为(0,0),向右是X轴正方向,向下是Y轴正方向,绘图区的宽度由QWidget::width()函数获取,高度由QWidget::height()函数获取,所以,绘图区右下角的的点的坐标是(width(), height())。这个坐标系统是QWidget绘图区的局部物理坐标,称为视口(viewport)坐标。相应的还有逻辑坐标,称为窗口(window)坐标,后面再详细介绍。

在这里插入图片描述

图1 在QWidget继承的窗口上绘图

使用QPainter在QWidget上绘图就是在这样的一个矩形区域里绘图。

3.QPainter绘图的主要属性

   ; 用QPainter在绘图设备上绘图,主要是绘制一些基本的图形元素,包括点、直线、圆形、矩形、曲线、文字等,控制这些绘图元素特性的主要是QPainter的3个属性,分别如下。

pen属性:是一个QPen对象,用于控制线条的颜色、宽度、线型等,如图8-1所示矩形边框的线条的特性就是由pen属性决定的。
brush属性:是一个QBrush对象,用于设置一个区域的填充特性,可以设置填充颜色、填充方式、渐变特性等,还可以采用图片做材质填充。图1中的矩形用黄色填充就是由brush属性设置决定的。
font属性:是一个QFont对象,用于绘制文字时,设置文字的字体样式、大小等属性。
使用这3个属性基本就控制了绘图的基本特点,当然还有一些其他的功能结合使用,比如叠加模式、旋转和缩放等功能。

4.创建实例

为演示QPainter绘图的基本功能,创建一个Qt Widget Application项目,并选择窗口基类为QWidget,自动创建窗体。创建后的项目有一个Widget类,为了简化代码功能,Widget窗口里不再放置任何其他组件,只用来绘图。

下面是Widget类的完整定义。只是重新定义了paintEvent()事件,在此事件里编写绘图的代码。Q_DECL_OVERRIDE宏表示这个函数是对父类虚函数的重载

class Widget : public QWidget
{
   Q_OBJECT
protected:
   void   paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
public:
   explicit Widget(QWidget *parent = 0);
   ~Widget();
private:
   Ui::Widget *ui;
};

下面是Widget类构造函数和paintEvent()函数的代码,在界面上绘制如图1所示的一个填充矩形,演示了QPainter绘图的基本过程。

Widget::Widget(QWidget *parent) :  QWidget(parent),  ui(new Ui::Widget)
{
   ui->setupUi(this);
   setPalette(QPalette(Qt::white)); //设置窗口为白色背景
   setAutoFillBackground(true);
}
void Widget::paintEvent(QPaintEvent *event)
{
   QPainter   painter (this);//创建QPainter对象
   painter.setRenderHint(QPainter::Antialiasing);
   painter.setRenderHint(QPainter::TextAntialiasing);
   int W=this->width(); //绘图区宽度
   int H=this->height(); //绘图区高度
   QRect   rect(W/4,H/4,W/2,H/2); //中间区域矩形框
//设置画笔
   QPen   pen;
   pen.setWidth(3); //线宽
   pen.setColor(Qt::red); //划线颜色
   pen.setStyle(Qt::SolidLine);//线的样式,实线、虚线等
   pen.setCapStyle(Qt::FlatCap);//线端点样式
   pen.setJoinStyle(Qt::BevelJoin);//线的连接点样式
   painter.setPen(pen);
//设置画刷
   QBrush  brush;
   brush.setColor(Qt::yellow); //画刷颜色
   brush.setStyle(Qt::SolidPattern); //画刷填充样式
   painter.setBrush(brush);
//绘图
   painter.drawRect(rect);
}

在paintEvent()函数中,首先创建与Widget关联的QPainter对象painter,这样就可以用这个painter在Widget上绘图了。

下面的代码获取Widget窗口的宽度和高度,并定义了位于中间区域的矩形rect,这个矩形的大小随Widget的大小变化而变化,因为它的大小定义中利用了Widget的宽度和高度,而不是固定大小。

int W=this->width(); //绘图区宽度
int H=this->height(); //绘图区高度
QRect   rect(W/4,H/4,W/2,H/2); //中间区域矩形框

然后定义了一个QPen类的对象pen,设置其线宽、颜色、线型等,然后设置为painter的pen。

再定义了一个QBrush类的对象brush,设置其颜色为黄色,填充方式为实体填充,然后设置为painter的brush。

这样设置好painter的pen和brush属性后,调用QPainter类的drawRect()函数,就可以绘制前面定义的矩形了,矩形框的线条特性由pen决定,填充特性由brush决定。运行程序就可以得到如图8-1所示的居于界面中间的填充矩形框。

为了不使程序结构太过于复杂,在paintEvent()函数里直接设置pen和brush的各种属性,而不是设计复杂的界面程序来修改这些设置。要实现什么绘图功能,只需在paintEvent()函数里直接修改代码即可。

QPen的主要功能

    QPen用于绘图时对线条进行设置,主要包括线宽、颜色、线型等,表1是QPen类的主要接口函数。通常一个设置函数都有一个对应的读取函数,例如setColor()用于设置画笔颜色,对应的读取画笔颜色的函数为color(),表1仅列出设置函数(省略了函数参数中的const关键字)。

表1 QPen的主要函数
函数原型|功能 ----|------ void setColor(QColor &color)|设置画笔颜色,即线条颜色 void setWidth(int width)|设置线条宽度 void setStyle (Qt::PenStyle style)|设置线条样式,参数为Qt::PenStyle枚举类型 void setCapStyle (Qt::PenCapStyle style)|设置线条端点样式,参数为Qt::PenCapStyle枚举类型 void setJoinStyle (Qt::PenJoinStyle style)|设置连接样式,参数为Qt::PenJoinStyle枚举类型

线条颜色和宽度的设置无需多说,QPen影响线条特性的另外3个主要属性是线条样式(style)、端点样式(capStyle)和连接样式(joinStyle)。

1.线条样式

setStyle(Qt::PenStyle style)函数用于设置线条样式,参数是一个枚举类型Qt::PenStyle的常量,几种典型的线条样式的绘图效果如图2所示。Qt::PenStyle类型还有一个常量Qt::NoPen表示不绘制线条。
在这里插入图片描述

图2 各种样式的线条(来自Qt帮助文件)

除了几种基本的线条样式外,用户还可以自定义线条样式,自定义线条样式时需要用到setDashOffset()和setDashPattern()函数。

2.线条端点样式

setCapStyle (Qt::PenCapStyle style)函数用于设置线条端点样式,参数是一个枚举类型Qt::PenCapStyle的常量,该枚举类型的3种取值及其绘图效果如图3所示。

在这里插入图片描述

图3 各种线条端点样式(来自Qt帮助文件)

3.线条连接样式

setJoinStyle (Qt::PenJoinStyle style)函数用于设置线条连接样式,参数是一个枚举类型Qt::PenJoinStyle 的常量,该枚举类型的取值及其绘图效果如图4所示。

在这里插入图片描述

图4 各种线条连接样式(来自Qt帮助文件)

3. QBrush的主要功能

QBrush定义了QPainter绘图时的填充特性,包括填充颜色、填充样式、材质填充时的材质图片等,其主要函数见表8-2(省略了函数参数中的const关键字)。

表2 QBrush的主要函数
函数原型功能
void setColorr(QColor &color)设置画刷颜色,实体填充时即为填充颜色
void setStyle(Qt::BrushStyle style)设置画刷样式,参数为Qt::BrushStyle枚举类型
void setTexture(QPixmap &pixmap)设置一个QPixmap类型的图片作为画刷的图片,画刷样式自动设置为Qt::TexturePattern
void setTextureImage(QImage &image)设置一个QImage类型的图片作为画刷的图片,画刷样式自动设置为Qt::TexturePattern

setStyle(Qt::BrushStyle style)函数设置画刷的样式,参数是Qt::BrushStyle style枚举类型,该枚举类型典型的几种取值见表3,详细的取值请参考Qt的帮助文件。几种典型取值的填充效果如图5所示。

表3 枚举类型Qt:: BrushStyle几个主要常量及其意义

枚举常量描述
Qt:: NoBrush不填充
Qt:: SolidPattern单一颜色填充
Qt:: HorPattern水平线填充
Qt:: VerPattern垂直线填充
Qt:: LinearGradientPattern线性渐变,需要使用QLinearGradient类对象作为Brush
Qt:: RadialGradientPattern辐射渐变,需要使用QRadialGradient类对象作为Brush
Qt:: ConicalGradientPattern圆锥型渐变,需要使用QConicalGradient类对象作为Brush
Qt::TexturePattern材质填充,需要指定texture或textureImage图片

渐变填充需要使用专门的类作为Brush赋值给QPainter,这部分在后面详细介绍。其他各种线型填充只需设置类型参数即可,使用材质需要设置材质图片。

在这里插入图片描述

图5 Qt::BrushStyle几种填充样式(来自Qt帮助文件)

下面是使用资源文件里的一个图片进行材质填充的示例程序,用材质图片填充一个矩形,程序运行结果如图8-6所示。

void Widget::paintEvent(QPaintEvent *event)
{
   QPainter   painter(this); 
   int W=this->width(); //绘图区宽度
   int H=this->height(); //绘图区高度
   QRect   rect(W/4,H/4,W/2,H/2); //中间区域矩形框
//设置画笔
   QPen   pen;
   pen.setWidth(3); //线宽
   pen.setColor(Qt::red); //划线颜色
   pen.setStyle(Qt::SolidLine);//线的类型,实线、虚线等
   painter.setPen(pen);
//设置画刷
   QPixmap texturePixmap(":images/images/texture.jpg");
   QBrush  brush;
   brush.setStyle(Qt::TexturePattern); //画刷填充样式
   brush.setTexture(texturePixmap); //设置材质图片
   painter.setBrush(brush);
//绘图
   painter.drawRect(rect);
}

4. 渐变填充

使用渐变填充需要用渐变类的对象作为Painter的brush,有3个实现渐变填充的类。

QLinearGradient:线性渐变。指定一个起点及其颜色,终点及其颜色,还可以指定中间的某个点的颜色,起点至终点之间的颜色会线性插值计算,得到线性渐变的填充颜色。
QRadialGradient:有简单辐射渐变和扩展辐射渐变两种方式。简单辐射渐变是在一个圆内的一个焦点和一个端点之间生成渐变颜色,扩展辐射渐变是在一个焦点圆和一个中心圆之间生成渐变色。
QConicalGradient:圆锥形渐变,围绕一个中心点逆时针生成渐变颜色。
这3种渐变的示例效果如图6所示。
在这里插入图片描述

图6 3种渐变填充的效果(来自Qt帮助文件):(左)QLinearGradient;(中)QRadialGradient;(右)QConicalGradient

这3个渐变类都继承自QGradient类,除了生成渐变颜色的方式不同之外,在设定的渐变颜色坐标范围之外,还需要用QGradient类的setSpread(QGradient::Spread method)函数设置延展方式。枚举类型QGradient::Spread有3种取值,分别表示3种延展效果,图8-7是使用辐射渐变时3种延展方式的效果。setSpread()对圆锥形渐变不起作用。

PadSpread模式是用结束点的颜色填充外部区域,这是缺省的方式。
RepeatSpread模式是重复使用渐变方式填充外部区域。
ReflectSpread是反射式重复使用渐变方式填充外部区域。

在这里插入图片描述

图7 3种渐变延展效果(来自Qt帮助文件)

下面的代码演示使用渐变效果绘图,程序中使用了辐射渐变。

void Widget::paintEvent(QPaintEvent *event)
{
   QPainter   painter(this); 
   int W=this->width();
   int H=this->height();
//径向渐变
   QRadialGradient  radialGrad(W/2,H/2,qMax(W/8,H/8),W/2,H/2);
   radialGrad.setColorAt(0,Qt::green);
   radialGrad.setColorAt(1,Qt::blue);
   radialGrad.setSpread(QGradient::ReflectSpread);
   painter.setBrush(radialGrad);
//绘图
   painter.drawRect(this->rect()); //填充更大区域,会有延展效果
}

上面的代码中定义QRadialGradient对象时使用的构造函数原型是:

QRadialGradient(qreal cx, qreal cy, qreal radius, qreal fx, qreal fy)

其中,(cx, cy)是辐射填充的中心点,程序中设置为(W/2, H/2),也就是Widget窗口的中心;radius是辐射填充区的半径,程序中设置为qMax(W/8, H/8);(fx, fy)是焦点坐标,程序中设置为(W/2, H/2),与中心点相同。

设置辐射渐变的起点颜色和终点颜色的语句是:

radialGrad.setColorAt(0,Qt::green);
radialGrad.setColorAt(1,Qt::blue);

这里的“点”使用了逻辑坐标,0表示起点,即辐射中心点;1表示终点,即填充区圆的圆周。

再用setSpread()函数设置延展方式为QGradient::ReflectSpread。

最后的绘图语句是:

painter.drawRect(this->rect());

这里绘制一个矩形,但是使用的矩形是this->rect(),即Widget窗口的整个矩形区域,它大于定义的辐射填充区域,所以会有延展效果。程序运行效果如图8所示。
在这里插入图片描述
图8 辐射填充效果

若使用线性渐变填充,示例代码如下(只列出QLinearGradient定义的部分)。定义QLinearGradient对象时指定了线性渐变的起点和终点,设置颜色时可以在起点和终点之间设置多个点的颜色值。

//   QLinearGradient  linearGrad(rect.left(),rect.top(),
//                 rect.right(),rect.bottom()); //对角线
   QLinearGradient  linearGrad(rect.left(), rect.top(), rect.right(), rect.top());//从左到右
   linearGrad.setColorAt(0,Qt::blue);//起点颜色
   linearGrad.setColorAt(0.5,Qt::green);//中间点颜色
   linearGrad.setColorAt(1,Qt::red);//终点颜色
   linearGrad.setSpread(QGradient::ReflectSpread);  //展布模式
   painter.setBrush(linearGrad);

创建QLinearGradient对象时传递了两个坐标点,分别表示填充区的起点和终点,起点和终点的定义方式不同,可以实现水平渐变、垂直渐变、或对角渐变等不同效果。

使用圆锥形渐变的示例代码如下。创建QConicalGradient对象时指定了中心点坐标和起始角度,然后设置多个点的颜色。但是注意,圆锥形填充没有延展效果。

QConicalGradient  coniGrad(W/2,H/2,45);
coniGrad.setColorAt(0,Qt::yellow);
coniGrad.setColorAt(0.5,Qt::blue);
coniGrad.setColorAt(1,Qt::green);
painter.setBrush(coniGrad);

5 QPainter绘制基本图形元件

1.基本图形元件

QPainter提供了很多绘制基本图形的功能,包括点、直线、椭圆、矩形、曲线等,由这些基本的图形可以构成复杂的图形。QPainter中提供的绘制基本图元的函数见表8-4。每个函数基本上都有多种参数形式,这里只列出函数名,给出了其中一种参数形式的示例代码,并且假设已经通过以下的代码获得了绘图窗口的painter、窗口宽度W和高度H。

QPainter painter(this);
int W=this->width(); //绘图区宽度
int H=this->height(); //绘图区高度

表8-4 QPainter绘制基本图形元件的函数

函数名功能和示例代码示例图形
drawArc画弧线,例如

QRect rect(W/4,H/4,W/2,H/2);
int startAngle = 90 * 16; //起始90°
int spanAngle = 90 * 16; //旋转90°
painter.drawArc(rect, startAngle, spanAngle);

在这里插入图片描述
drawChord画一段弦,例如

QRect rect(W/4,H/4,W/2,H/2);
int startAngle = 90 * 16; //起始90°
int spanAngle = 90 * 16; //旋转90°
painter. drawChord (rect, startAngle, spanAngle);

图片
drawConvexPolygon根据给定的点画凸多边形

QPoint points[4]={
QPoint(5W/12,H/4),
QPoint(3
W/4,5H/12),
QPoint(5
W/12,3H/4),
QPoint(W/4,5
H/12), };
painter.drawConvexPolygon(points, 4);

图片 139
drawEllipse画椭圆
QRect rect(W/4,H/4,W/2,H/2);
painter.drawEllipse(rect);
图片 153
drawImage在指定的矩形区域内绘制图片
QRect rect(W/4,H/4,W/2,H/2);
QImage image(“:images/images/qt.jpg”);
painter.drawImage(rect, image);
图片 19
drawLine画直线
QLine Line(W/4,H/4,W/2,H/2);
painter.drawLine(Line);
图片 157
drawLines画一批直线
QRect rect(W/4,H/4,W/2,H/2);
QVector Lines;
Lines.append(QLine(rect.topLeft(),rect.bottomRight()));
Lines.append(QLine(rect.topRight(),rect.bottomLeft()));
Lines.append(QLine(rect.topLeft(),rect.bottomLeft()));
Lines.append(QLine(rect.topRight(),rect.bottomRight()));
painter.drawLines(Lines);
图片 160
drawPath绘制由QPainterPath对象定义的路线
QRect rect(W/4,H/4,W/2,H/2);
QPainterPath path;
path.addEllipse(rect);
path.addRect(rect);
painter.drawPath(path);
图片 162
drawPie绘制扇形
QRect rect(W/4,H/4,W/2,H/2);
int startAngle = 40 * 16;//起始40°
int spanAngle = 120 * 16;//旋转120°
painter.drawPie(rect, startAngle, spanAngle);
图片 165
drawPixmap绘制Pixmap图片
QRect rect(W/4,H/4,W/2,H/2);
QPixmap pixmap(“:images/images/qt.jpg”);
painter.drawPixmap(rect, pixmap);
drawPoint画一个点
painter.drawPoint(QPoint(W/2,H/2));
drawPoints画一批点
QPoint points[]={
QPoint(5W/12,H/4),
QPoint(3
W/4,5H/12),
QPoint(2
W/4,5*H/12) };
painter.drawPoints(points, 3);
drawPolygon画多边形,最后一个点会和第一个点闭合
QPoint points[]={
QPoint(5W/12,H/4),
QPoint(3
W/4,5H/12),
QPoint(5
W/12,3H/4),
QPoint(2
W/4,5*H/12) };
painter.drawPolygon(points, 4);
图片 167
drawPolyline画多点连接的线,最后一个点不会和第一个点连接
QPoint points[]={
QPoint(5W/12,H/4),
QPoint(3
W/4,5H/12),
QPoint(5
W/12,3H/4),
QPoint(2
W/4,5*H/12), };
painter.drawPolyline(points, 4);
图片 171
drawRect画矩形
QRect rect(W/4,H/4,W/2,H/2);
painter.drawRect(rect);
图片 173
drawRoundedRect画圆角矩形
QRect rect(W/4,H/4,W/2,H/2);
painter.drawRoundedRect(rect,20,20);
图片 266
drawText绘制文本,只能绘制单行文字,字体的大小等属性由QPainter::font()决定。
QRect rect(W/4,H/4,W/2,H/2);
QFont font;
font.setPointSize(30);
font.setBold(true);
painter.setFont(font);
painter.drawText (rect,“Hello,Qt”);
eraseRect擦除某个矩形区域,等效于用背景色填充该区域
QRect rect(W/4,H/4,W/2,H/2);
painter.eraseRect(rect);
fillPath填充某个QPainterPath定义的绘图路径,但是轮廓线不显示
QRect rect(W/4,H/4,W/2,H/2);
QPainterPath path;
path.addEllipse(rect);
path.addRect(rect);
painter.fillPath(path,Qt::red); 图片 273
fillRect填充一个矩形,无边框线
QRect rect(W/4,H/4,W/2,H/2);
painter.fillRect (rect,Qt::green);
图片 33

这些基本图形元件的绘制用户可以通过修改samp8_1的paintEvent()里的代码进行测试,这里就不再详细举例和说明了。

2.QPainterPath的使用

在表8-4列举的QPainter绘制基本图形元件的函数中,一般的图形元件的绘制都比较简单和直观,只有drawPath()函数是绘制一个复合的图形对象,它使用一个QPainterPath类型的参数作为绘图对象。drawPath()函数的原型是:

void QPainter::drawPath(const QPainterPath &path)

QPainterPath是一系列绘图操作的顺序集合,便于重复使用。一个PainterPath由许多基本的绘图操作组成,如绘图点移动、划线、画圆、画矩形等,一个闭合的PainterPath是终点和起点连接起来的绘图路径。使用QPainterPath的优点是绘制某些复杂形状时只需创建一个PainterPath,然后调用QPainter::drawPath()就可以重复使用。例如绘制一个复杂的星星图案需要多次调用lineto()函数,定义一个QPainterPath类型的变量path记录这些绘制过程,再调用drawPath(path)就可以完成星型图案的绘制。

QPainterPath提供了很多函数可以添加各种基本图形元件的绘制,其功能与QPainter提供的绘制基本图件的功能类似,也有一些用于PainterPath的专用函数,如closeSubpath()、connectPath()等,对于QPainterPath的函数功能不做详细说明,可以参考Qt帮助文件查看QPainterPath类的详细描述。

2 坐标系统和坐标变换

1 坐标变换函数

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

表1 QPainter有关坐标变换操作的函数

分组函数原型功能
坐标变换void translate(qreal dx, qreal dy)坐标系统平移一定的偏移量,坐标原点平移到新的点
void rotate(qreal angle)坐标系统顺时针旋转一个角度
void scale(qreal sx, qreal sy)坐标系统缩放
void shear(qreal sh, qreal sv)坐标系统做扭转变换
状态保存与恢复void save()保存painter当前的状态,就是将当前状态压入堆栈
void restore()恢复上一次状态,就是从堆栈中弹出上次的状态
void resetTransform()复位所有的坐标变换

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

1.坐标平移

坐标平移函数是translate(),其中一种参数形式的函数原型是:

void  translate(qreal dx, qreal dy)

表示将坐标系统水平方向平移dx个单位,垂直方向平移dy个单位,在缺省的坐标系统中,单位就是像素。如果是从原始状态平移(dx, dy),那么平移后的坐标原点就移到了(dx, dy)。

假设一个绘图窗口宽度为300像素,高度为200像素,则其原始坐标系统如图8-10左所示;若执行平移函数translate(150, 100),则坐标系统水平向右平移150像素,向下平移100像素,平移后的坐标系统如图 8-10 右所示,坐标原点在窗口的中心,而左上角的坐标变为(−150, −100),右下角的坐标变为(150, 100)。如此将坐标原点变换到窗口中心在绘制某些图形时是非常方便的。

2.坐标旋转

坐标旋转的函数是rotate(),其函数原型为:

void rotate(qreal angle)
它是将坐标系统绕坐标原点顺时针旋转angle角度,单位是度。当angle为正数时是顺时针旋转,为负数时是逆时针旋转。

在图8-10右的基础上,若执行rotate(90),则得到图8-11所示的坐标系统。

在这里插入图片描述

图8-10 (左)原始坐标系统;(右)平移(150,100)后的坐标系统

在这里插入图片描述

图8-11 对图8-10右图旋转90°之后的坐标系
注意 
旋转之后并不改变窗口矩形的实际大小,只是改变了坐标轴的方向。

在图8-11的新坐标系下,窗口左上角的坐标变成了(-100, 150),而右下角的坐标变成了(100, -150)。

3.缩放

缩放函数是scale(),其函数原型为:

void  scale(qreal sx, qreal sy)

其中,sx, sy分别为横向和纵向缩放比例,比例大于1是放大,小于1是缩小。

4.状态保存与恢复

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

resetTransform()函数则是复位所有坐标变换操作,恢复原始的坐标系统。

2 坐标变换绘图实例

1.绘制3个五角星的程序

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

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); 
   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); //顺时针旋转90度
   painter.drawPath(starPath);//画星星
   painter.drawText(0,0,"S2");

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

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

在这里插入图片描述

图8-12 使用QPainterPath和坐标变换的绘图效果

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

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); //顺时针旋转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 视口和窗口

1.视口和窗口的定义与原理

绘图设备的物理坐标是基本的坐标系,通过QPainter的平移、旋转等变换可以得到更容易操作的逻辑坐标。

为了实现更方便的坐标,QPainter还提供了视口(Viewport)和窗口(Window)坐标系,通过QPainter内部的坐标变换矩阵自动转换为绘图设备的物理坐标。

视口表示绘图设备的任意一个矩形区域的物理坐标,可以只选取物理坐标的一个矩形区域用于绘图。默认情况下,视口等于绘图设备的整个矩形区。

窗口与视口是同一个矩形,只不过是用逻辑坐标定义的坐标系。窗口可以直接定义矩形区的逻辑坐标范围。图8-13是对视口和窗口的图示说明。

在这里插入图片描述

图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。这里设置的窗口还是一个正方形,使得从视口到窗口变换时,长和宽的变化比例是相同的。实际可以任意指定窗口的逻辑坐标范围,长和宽的变化比例不相同也是可以的。

2.视口和窗口的使用实例

使用窗口坐标的优点是,只需按照窗口坐标定义来绘图,而不用管实际的物理坐标范围的大小。例如在一个固定边长为100的正方形窗口内绘图,当实际绘图设备大小变化时,绘制的图形会自动变化大小。这样,就可以将绘图功能与绘图设备隔离开来,使得绘图功能适用于不同大小、不同类型的设备。

实例samp8_3演示了使用视口和窗口的方法,项目创建与samp8_1类似,只在Widegt的paintEvent()事件里添加绘图代码。

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(rect);//设置Viewport
   painter.setWindow(-100,-100,200,200); // 设置窗口大小,逻辑坐标
   painter.setRenderHint(QPainter::Antialiasing);
//设置画笔
   QPen   pen;
   pen.setWidth(1); //线宽
   pen.setColor(Qt::red); //划线颜色
   pen.setStyle(Qt::SolidLine);//线的类型 
   painter.setPen(pen);
   for(int i=0; i

运行实例程序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 绘图叠加的效果

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

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(rect);//设置Viewport
   painter.setWindow(-100,-100,200,200); // 设置窗口大小,逻辑坐标
   painter.setRenderHint(QPainter::Antialiasing);
//设置画笔
   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

在上面的程序中,对单个圆使用了线性渐变填充,单个圆从左到右,由黄色渐变为绿色。

使用QPainter::setCompositionMode()函数设置组合模式,即后面绘制的图与前面绘制的图的叠加模式。函数参数是一个QPainter::CompositionMode枚举类型值,可以查看Qt帮助,这个枚举类型有近40种取值,表示了后绘制图形与前面图形的不同叠加运算方式。

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

在这里插入图片描述

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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐