目录
一、Qt绘制系统
1.1Qt绘制基本概念
1.2 绘制代码举例
1.3画家
1.3.1 QPainter的工作原理:
1.3.2 自定义绘制饼状图:
1.4画笔和画刷
1.4.1画笔
1.4.2 画刷填充样式
1.5 反走样和渐变
1.6绘制设备
1.7坐标变换
1.8QPainterPath
1.9绘制文字
1.10QMovie
二、图形视图框架的结构
一、Qt绘制系统
1.1Qt绘制基本概念
Qt 的绘图系统允许使用相同的 API 在屏幕和打印设备上进行绘制。整个绘图系统基于 QPainter,QPainterDevice 和 QPaintEngine 三个类。
QPainter 用来执行绘制的操作;QPaintDevice 是一个二维空间的抽象,这个二维空间可以由QPainter 在上面进行绘制;QPaintEngine 提供了画笔 painter 在不同的设备上进行绘制的统一的接口。QPaintEngine 类用在 QPainter 和 QPaintDevice 之间,并且通常对开发人员是透明的,除非你需要自定义一个设备,这时候你就必须重新定义 QPaintEngine 了。
Qt 的绘图系统实际上是说,使用 QPainter 使用画笔画刷等工具在 QPainterDevice 上面进行绘制,它们之间使用 QPaintEngine 引擎进行通讯。
1.2 绘制代码举例
Qt 绘图系统提供了三个主要的参数设置,画笔(pen)、画刷(brush)和字体(font)。
//QPainter 接收一个 QPaintDevice*类型的参数。
//QPaintDevice 有很多子类,比如 QImage,以及QWidget。
//我们希望在这个 widget上画,因此传入的是 this 指针。
void QMyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawLine(0, 0, 100, 50);
painter.setPen(Qt::red);
painter.drawRect(10, 10, 80, 40);
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::blue);
painter.drawEllipse(10, 10, 80, 40);
//反走样 可以处理毛边效果 但是该算法复杂,不会默认开启,若要像素操作则不能开启这个
painter.setRenderHint(QPainter::Antialiasing, true);
//渐变是绘图中很常见的一种功能,可以把几种颜色混合在一起,自然地过渡,而不是一下子变成另一种颜色。
//渐变一般是用在填充里面的,所以渐变的设置就是在 QBrush 里面。
//Qt 提供了三种渐变画刷,分别是线性渐变(QLinearGradient)、辐射渐变(QRadialGradient)、角度渐变(QConicalGradient)。
QLinearGradient linearGradient(10, 50, 90, 90);//起点xy 终点xy
linearGradient.setColorAt(0.2, Qt::white);//渐变1/5处设置白色
linearGradient.setColorAt(0.6, Qt::green);//3/5处设置绿色
linearGradient.setColorAt(0.8, Qt::red);//4/5处设置绿色
linearGradient.setColorAt(1.0, Qt::black);//终点设置黑色
painter.setBrush(QBrush(linearGradient));
painter.drawEllipse(10, 50, 80, 40);
painter.setPen(QPen(QBrush(linearGradient), 10));//线条渐变
painter.drawLine(10, 50, 90, 90);
//角度渐变
//QPainter的画刷、画笔、填充、渐变、坐标等属性记录在状态机中
//当前改动后便会使用当前属性 改变画刷,坐标后只影响改动后
int r = 60;
QConicalGradient conicalGradient(0, 0, 0);//角度渐变的起始点 0,0 起始角度 0
conicalGradient.setColorAt(0.0, Qt::red);
conicalGradient.setColorAt(60.0 / 360.0, Qt::yellow);
conicalGradient.setColorAt(120.0 / 360.0, Qt::green);
conicalGradient.setColorAt(180.0 / 360.0, Qt::cyan);
conicalGradient.setColorAt(240.0 / 360.0, Qt::blue);
conicalGradient.setColorAt(300.0 / 360.0, Qt::magenta);
conicalGradient.setColorAt(1.0, Qt::red);//角度回到起始 red
painter.setBrush(QBrush(conicalGradient));
painter.setPen(QPen(QBrush(conicalGradient),5));
painter.translate(90+r, r+10);//修改坐标原点00到r,r
painter.drawEllipse(QPoint(0, 0),r, r);
//修改坐标 状态机恢复
painter.fillRect(10, 10, 50, 100, Qt::red);
painter.save(); //保持当前状态
painter.translate(100, 0); // 向右平移 100px
painter.fillRect(10, 10, 50, 100, Qt::yellow);
painter.restore();//绘制完之后在恢复之前状态
painter.save();
painter.translate(300, 0); // 向右平移 300px
painter.rotate(30); // 顺时针旋转 30 度
painter.fillRect(10, 10, 50, 100, Qt::green);
painter.restore();
painter.save();
painter.translate(400, 0); // 向右平移 400px
painter.scale(2, 3); // 横坐标单位放大 2 倍,纵坐标放大 3 倍
painter.fillRect(10, 10, 50, 100, Qt::blue);
painter.restore();
painter.save();
painter.translate(600, 0); // 向右平移 600px
painter.shear(0, 1); // 横向不变,纵向扭曲 1 倍
painter.fillRect(10, 10, 50, 100, Qt::cyan);
painter.restore();
//不同的绘图设备
//绘图设备是指继承 QPainterDevice 的子类。Qt 一共提供了四个这样的类,分别是 QPixmap、QBitmap、QImage 和 QPicture。
}
1.3画家
1.3.1 QPainter的工作原理:
当你调用QPainter的绘图函数(例如drawRect()或drawText())时,实际上是向一个命令队列添加了一个命令。这个命令队列会在稍后被处理,并将结果绘制到目标设备(例如QPixmap或QWidget)。绘图命令被压入命令队列时,不会立即绘制到屏幕,而是会在事件循环的下一次迭代中被处理。这意味着你可以在一个函数中调用多次绘图命令,而不必担心每次调用都会导致屏幕刷新。
1.3.2 自定义绘制饼状图:
bool PmsPie::eventFilter(QObject *watcher, QEvent *event)
{
if (event && ui.page_1->isVisibleTo(this)
&& ui.page_1 == watcher
&& event->type() == QEvent::Paint) {
QPainter painter(ui.page_1);
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing, true);
shared_ptr<int>pHelper(new int(0),
[&](int *data) {
delete data;
painter.restore();//恢复画笔最初状态
});
painter.save();//保存画笔最初状态
auto rect = ui.page_1->rect();
painter.fillRect(rect, Qt::white);
//*2 绘制内切弧线一般越界不显示 最大180
int circle_diameter = qMin(rect.width(), rect.height() * 2);
QRect rect_circle(8, 8, circle_diameter - 8, circle_diameter - 8);
painter.setPen(Qt::NoPen);//设置NoPen
const int bg_height = 2;//两条弧线之间的宽度 刻度尺宽度
const int bg_span_angle = 180 * 16;//弧度转化16 从右边逆时针
painter.setBrush(QColor("#f5f5f5"));
//drawPie(外切rect adjusted调整大小后返回调整后的 adjust调整自身)
painter.drawPie(rect_circle, 0, bg_span_angle);//0-180度的饼图作为刻度
painter.setBrush(Qt::white);//adjust实际效果和调整对角坐标一样
painter.drawPie(rect_circle.adjusted(bg_height, bg_height, -bg_height, -bg_height), 0, bg_span_angle);
const int dp_height = 6;
int percent = 70;
int offset = dp_height - bg_height;
int start_angle = 180 * (100 - percent) / 100 * 16;
int span_angle = 180 * percent / 100 * 16; //30%
painter.setBrush(Qt::green);
painter.drawPie(rect_circle.adjusted(-offset, -offset, offset, offset), start_angle, span_angle);
painter.setBrush(Qt::white);
painter.drawPie(rect_circle.adjusted(offset, offset, -offset, -offset), 0, 360 * 16);
//绘制文字
QString str = QString("提升%1%").arg(30);
const int text_offset = 8;
auto font = painter.font();
font.setBold(true);
font.setPixelSize(circle_diameter * 0.086);
painter.setPen(QPen(Qt::red, 8));
painter.setFont(font);
QRect rectText = QRect(rect_circle.left(), rect_circle.top(), rect_circle.width(), rect_circle.height() / 2);
painter.drawText(rectText.adjusted(text_offset, text_offset, -text_offset, -text_offset), Qt::AlignHCenter | Qt::AlignBottom, str);
//参考矩形绘制文本 可指定在矩形的位置
}
return QWidget::eventFilter(watcher, event);
}
1.4画笔和画刷
1.4.1画笔
风格种类 Qt::PenStyle
如果需要对绘制的线条设置不同的颜色,那么我们就需要给painter设置一个画笔QPen。Pen有样式(style),宽度(width), 颜色(brush), 笔帽样式(capStyle)和(连接样式)joinStyle。
-
style使用Qt::PenStyle定义线条类型。默认是Qt::PenStyle::SolidLine
-
brush用于填充画笔笔生成的笔触。 使用QBrush类来指定画笔的颜色。
-
capStyle帽样式决定了可以使用QPainter绘制的线结束帽,
-
joinStyle连接样式描述了如何绘制两条线之间的连接。
通过使用相应的setStyle(),settwidth (), setBrush(), setCapStyle()和setJoinStyle()函数,可以很容易地修改各种设置(注意,当改变画笔的属性时,画家的画笔必须重置)。注意,不存在宽度为 0 的线。假设你设置 width 为 0,QPainter依然会绘制出一条线,而这个线的宽度为 1 像素。也就是说,画笔宽度通常至少是 1 像素。
笔帽样式:
连接样式:
1.4.2 画刷填充样式
QBrush定义了QPainter的填充模式,具有样式、颜色、渐变以及纹理等属性;
样式:画刷的style()定义了填充的样式,使用Qt::BrushStyle枚举,默认值是Qt::NoBrush,也就是不进行任何填充。我们可以从下面的图示中看到各种填充样式的区别。
颜色:画刷的color()定义了填充模式的颜色,这个颜色可以是 Qt 预定义的颜色常量,也就是Qt::GlobalColor,也可以是任意QColor对象。
画刷的gradient()定义了渐变填充:这个属性只有在样式是Qt::LinearGradientPattern、Qt::RadialGradientPattern或者Qt::ConicalGradientPattern之一时才有效。渐变可以由QGradient对象表示。Qt 提供了三种渐变:QLinearGradient、QConicalGradient和QRadialGradient,它们都是QGradient的子类。
1.5 反走样和渐变
painter.setRenderHint(QPainter::Antialiasing, true);
我们在光栅图形显示器上绘制非水平、非垂直的直线或多边形边界时,或多或少会呈现锯齿状外观。这是因为直线和多边形的边界是连续的,而光栅则是由离散的点组成。在光栅显示设备上表现直线、多边形等,必须在离散位置采样。由于采样不充分重建后造成的信息失真,就叫走样;用于减少或消除这种效果的技术,就称为反走样。反走样是图形学中的重要概念,用以防止通常所说的“锯齿”现象的出现。很多系统的绘图 API 里面都内置了有关反走样的算法,不过由于性能问题,默认一般是关闭的,Qt 也不例外。
虽然反走样图像质量更好,但是由于反走样算法会使得局部像素比较模糊,因为需要以一种近似色代替原像素,这样看起来会模糊而圆滑,对于一些显示要求不高或者必须进行像素级操作的应用,不可反走样。
渐变:渐变是绘图中很常见的一种功能,简单来说就是可以把几种颜色混合在一起,让它们能够自然地过渡,而不是一下子变成另一种颜色;渐变一般是用在填充里面的,所以,设置渐变是在QBrush里面gradient()。
1.6绘制设备
QPixmap专门为图像在屏幕上的显示做了优化;QBitmap是QPixmap的一个子类,它的色深限定为1,你可以使用QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。QImage专门为图像的像素级访问做了优化。QPicture则可以记录和重现QPainter的各条命令
QPixmap继承了QPaintDevice:因此,你可以使用QPainter直接在上面绘制图形。QPixmap也可以接受一个字符串作为一个文件的路径来显示这个文件,比如你想在程序之中打开 png、jpeg 之类的文件,就可以使用QPixmap。QPixmap是针对屏幕进行特殊优化的,它与实际的底层显示设备息息相关。注意,这里说的显示设备并不是硬件,而是操作系统提供的原生的绘图引擎。所以,在不同的操作系统平台下,QPixmap的显示可能会有所差别。在使用QPixmap时,你可以直接使用传值的形式,不需要传指针,因为QPixmap提供了“隐式数据共享”。简单来说,就是一般对于大型数据(图像无疑就是这种“大型数据”),为性能起见,通常会采用传指针的方式,但是由于QPixmap内置了隐式数据共享,所以只要知道传递QPixmap。
QBitmap继承自QPixmap:因此具有QPixmap的所有特性。不同之处在于,QBitmap的色深始终为 1,即一位二进制表现颜色(非黑即白)。所以说,QBitmap实际上是只有黑白两色的图像数据。由于QBitmap色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷。
QImage与QPixmap相比:最大的优势在于能够进行像素级别的操作。可以把QImage想象成一个 RGB 颜色的二维数组,记录了每一像素的颜色。
QImage绘制二维码代码:
qrencode则是一款由C语言(完全兼容C++)写成的一个QR码生成与解码的函数库.它以GNU LGPL协议发布。
void Qmultiwidgettest::InitialQRCode()
{
QRcode *qrcode = NULL;
qrcode = QRcode_encodeString("https://www.baidu.com", 2, QR_ECLEVEL_Q, QR_MODE_8, 1);
if (NULL == qrcode)
{
return;
}
const size_t scale = 4;
qint32 qrcodeWidth = qrcode->width > 0 ? qrcode->width : 1;
qrcodeWidth = scale * qrcodeWidth;
QImage image = QImage(qrcodeWidth, qrcodeWidth, QImage::Format_ARGB32);//The image is stored using a 32-bit ARGB format (0xAARRGGBB).Alpha 透明度
QPainter painter(&image);
QColor background(Qt::white);
painter.setBrush(background);
painter.setPen(Qt::NoPen);
painter.drawRect(0, 0, qrcodeWidth, qrcodeWidth);
QColor foreground(Qt::black);
painter.setBrush(foreground);
for (qint32 y = 0; y < qrcode->width; y++)
{
for (qint32 x = 0; x < qrcode->width; x++)
{
unsigned char b = qrcode->data[y * qrcode->width + x];
if (b & 0x01)
{
QRectF r(scale*x, scale*y, scale, scale);
painter.drawRects(&r, 1);
}
}
}
/*if (!image.save("qrcode.png")) //保存到本地
{
}*/
//ui.label_8->setPixmap(QPixmap::fromImage(image));
ui.label_8->setPixmap(QBitmap::fromImage(image));//QBitmap继承QPixmap 色深为1
QRcode_free(qrcode);
}
1.7坐标变换
图形学大部分算法依赖于矩阵计算,坐标变换便是其中的代表:每一种变换都对应着一个矩阵乘法。 QTransform 用于指定坐标系的 2D 转换 - 平移、缩放、扭曲(剪切)、旋转或投影坐标系。绘制图形时,通常会使用。QTransform 与 QMatrix 不同之处在于,它是一个真正的 3x3 矩阵,允许视角转换,QTransform 的 toAffine() 方法允许将 QTransform 转换到 QMatrix。如果视角转换已在矩阵指定,则转换将导致数据丢失。
A QTransform object contains a 3 x 3 matrix. The m31 (dx) and m32 (dy) elements specify horizontal and vertical translation. The m11 and m22 elements specify horizontal and vertical scaling. The m21 and m12 elements specify horizontal and vertical shearing. And finally, the m13 and m23 elements specify horizontal and vertical projection, with m33 as an additional projection factor.
x' = m11*x + m21*y + dx
y' = m22*y + m12*x + dy
if (is not affine) {
w' = m13*x + m23*y + m33
x' /= w'
y' /= w'
}
1.8QPainterPath
QPainter是一个状态机。那么,有时我想保存下当前的状态:当我临时绘制某些图像时,就可能想这么做。当然,我们有最原始的办法:将可能改变的状态,比如画笔颜色、粗细等,在临时绘制结束之后再全部恢复。对此,QPainter提供了内置的函数:save()和restore()。save()就是保存下当前状态;restore()则恢复上一次保存的结果。这两个函数必须成对出现:QPainter使用栈来保存数据,每一次save(),将当前状态压入栈顶,restore()则弹出栈顶进行恢复。
而QPainterPath 提供了一个容器用于存储绘图操作以便可以创建和复用绘图,相较于QPainter每次状态保存和恢复更便利。
1.9绘制文字
除了绘制图形以外,还可以使QPainter::darwText()函数来绘制文字,也可以使用QPainter::setFont()设置文字所使用的字体,使用QPainter::fontInfo()函数可以获取字体的信息,它返回QFontInfo类对象。在绘制文字时会默认使用抗锯齿。
//绘制文字
QString str = QString("提升%1%").arg(30);
const int text_offset = 8;
auto font = painter.font();
font.setBold(true);
font.setPixelSize(circle_diameter * 0.086);
painter.setPen(QPen(Qt::red, 8));
painter.setFont(font);
QRect rectText = QRect(rect_circle.left(), rect_circle.top(), rect_circle.width(), rect_circle.height() / 2);
painter.drawText(rectText.adjusted(text_offset, text_offset, -text_offset, -text_offset), Qt::AlignHCenter | Qt::AlignBottom, str);
//参考矩形绘制文本 可指定在矩形的位置
1.10QMovie
QMovie类用于显示没有声音的简单动画。在实际运用中,可以用来显示Gif格式的动画 。
{
auto movie = new QMovie(tr(":/ToolIcon/工具栏/a589d01596db63a93d83e0bfcfcc1008.gif"));
int totalFrames = movie->frameCount();//总帧数
QPixmap frame = movie->currentPixmap();//当前帧返回为PixMap对象
QImage frameImage = movie->currentImage();
//设置gif的加载方式,默认不会缓存所有帧 只有缓存后才可以跳帧jumpToFrame()、控制速度setSpeed()
movie->setCacheMode(QMovie::CacheAll);
movie->setScaledSize(ui.label_gif->size());//设置动画大小
connect(movie, &QMovie::stateChanged, [=]() {
QString str;
switch (movie->state())
{
case QMovie::Paused:
str = "Paused"; break;//播放暂停 可以重新start
case QMovie::Running:
str = "Running"; break;//初始未播放 或者播放完 并且不会播放任何内容
case QMovie::NotRunning:
str = "NotRunning"; break;
}
qDebug() << str;
});
ui.label_gif->setMovie(movie);
movie->start();
if (movie->state() == QMovie::Running)
{
movie->setSpeed(200);//200%速度播放
}
}