QPainter 提供了高度优化的功能来完成大多数绘图 GUI 程序所需的工作。它可以绘制从简单的线条到复杂的形状(如馅饼和弦)的所有内容。它还可以绘制对齐的文本和像素图。通常,它在“自然”坐标系中绘制,但它也可以进行视图和世界变换。QPainter 可以对任何继承QPaintDevice类的对象进行操作。
设置QPainter
您可以自定义几个设置以根据您的喜好绘制 QPainter:
font () 是用于绘制文本的字体。如果painter isActive (),您可以分别使用fontInfo () 和fontMetrics () 函数检索有关当前设置的字体及其度量的信息。
Brush () 定义用于填充形状的颜色或图案。
pen () 定义用于绘制线条或边界的颜色或点画。
backgroundMode () 定义是否有背景(),即它是Qt::OpaqueMode还是Qt::TransparentMode。
background () 仅在backgroundMode () 是Qt::OpaqueMode并且pen () 是点画时适用。在这种情况下,它描述了点画中背景像素的颜色。
BrushOrigin () 定义平铺画笔的原点,通常是小部件背景的原点。
viewport ()、window ()、worldTransform ()组成了painter的坐标变换系统。有关详细信息,请参阅坐标变换部分和坐标系文档。
hasClipping () 告诉画家是否剪辑。(paint device 也剪辑。)如果painter 剪辑,它剪辑到clipRegion ()。
layoutDirection () 定义了画家在绘制文本时使用的布局方向。
worldMatrixEnabled () 告诉世界变换是否启用。
viewTransformEnabled () 告诉是否启用视图转换。
请注意,其中一些设置反映了某些绘图设备中的设置,例如QWidget::font ()。QPainter ::begin () 函数(或等效的 QPainter 构造函数)从绘图设备复制这些属性。
您可以随时通过调用save () 函数保存 QPainter 的状态,该函数将所有可用设置保存在内部堆栈中。restore () 函数将它们弹回。
绘画
QPainter提供了绘制大部分图元的函数:drawPoint ()、drawPoints ()、drawLine ()、drawRect ()、drawRoundedRect ()、drawEllipse ()、drawArc ()、drawPie ()、 drawChord ()、 drawPolyline () 、drawPolygon ()、drawConvexPolygon () 和 drawCubicBezier()。两个便捷函数drawRects () 和drawLines (),使用当前画笔和画笔在给定的QRects或QLines数组中绘制给定数量的矩形或线条。
QPainter 类还提供了使用给定QBrush填充给定QRect的fillRect () 函数,以及擦除给定矩形内区域的eraseRect () 函数。
所有这些函数都有整数和浮点版本。
如果你需要绘制一个复杂的形状,尤其是如果你需要重复这样做,可以考虑创建一个QPainterPath并使用 drawPath () 来绘制它。
QPainter 还提供了用给定的QBrush填充给定的QPainterPath的fillPath () 函数,以及绘制给定路径的轮廓(即描边路径)的strokePath () 函数。
文本绘制
文本绘制是使用drawText () 完成的。当您需要细粒度定位时,boundingRect () 会告诉您给定的drawText () 命令将在哪里绘制。
绘制像素图和图像
有绘制像素图/图像的函数,即drawPixmap ()、drawImage ()和drawTiledPixmap ()。drawPixmap () 和drawImage () 产生相同的结果,除了drawPixmap () 在屏幕上更快,而drawImage ( ) 在QPrinter或其他设备上可能更快。
有一个drawPicture () 函数可以绘制整个QPicture的内容。drawPicture () 函数是唯一一个忽略所有画家设置的函数,因为QPicture有自己的设置。
绘制像素图和图像的高分辨率版本
高分辨率版本的像素图具有大于 1的设备像素比值(参见QImageReader,QPixmap::devicePixelRatio ())。如果它与底层QPaintDevice的值匹配,它会直接绘制到设备上,而不应用额外的转换。
例如,将设备像素比为 2 的 64x64 像素大小的QPixmap绘制到设备像素比为 2 的高 DPI 屏幕上时就是这种情况。请注意,像素图在用户空间中实际上是 32x32 像素。Qt 中根据像素图大小计算布局几何的代码路径将使用此大小。这样做的最终效果是像素图显示为高 DPI 像素图而不是大像素图。
渲染质量
使用 QPainter 获得最佳渲染效果,应使用平台无关的QImage作为绘图设备;即使用QImage将确保结果在任何平台上都具有相同的像素表示。
QPainter 类还通过其RenderHint枚举和对浮点精度的支持提供了一种控制渲染质量的方法:所有用于绘制图元的函数都有一个浮点版本。这些通常与QPainter::Antialiasing渲染提示结合使用。
RenderHint枚举为 QPainter 指定标志,这些标志可能会或可能不会被任何给定引擎尊重。QPainter::Antialiasing表示引擎应该尽可能对图元的边缘进行抗锯齿,QPainter::TextAntialiasing表示引擎应该尽可能对文本进行抗锯齿,QPainter::SmoothPixmapTransform表示引擎应该使用平滑像素图变换算法。
renderHints () 函数返回一个标志,该标志指定为此绘制器设置的呈现提示。使用setRenderHint () 函数设置或清除当前设置的RenderHints。
坐标变换
通常,QPainter 在设备自己的坐标系(通常是像素)上运行,但 QPainter 对坐标变换有很好的支持。
最常用的变换是缩放、旋转、平移和剪切。使用scale () 函数将坐标系缩放给定的偏移量,使用rotate () 函数将其顺时针旋转,并使用translate () 函数将其平移(即向点添加给定的偏移量)。您还可以使用shear () 函数围绕原点扭转坐标系。有关剪切坐标系的可视化,请参见仿射变换示例。
另请参阅Transformations示例,该示例显示了转换如何影响 QPainter 呈现图形基元的方式。特别是它显示了转换顺序如何影响结果。
所有的转换操作都在转换worldTransform () 上进行。矩阵将平面中的一个点转换为另一个点。有关变换矩阵的更多信息,请参阅坐标系和QTransform文档。
setWorldTransform () 函数可以替换或添加到当前设置的worldTransform ( )。resetTransform () 函数重置使用translate ()、scale ()、shear ()、rotate ()、setWorldTransform ()、setViewport () 和setWindow () 函数进行的任何转换。deviceTransform _() 返回从逻辑坐标转换为平台相关绘图设备的设备坐标的矩阵。仅当在依赖平台的句柄上使用平台绘制命令时才需要后一个功能,并且平台本身不会进行转换。
使用 QPainter 绘图时,我们使用逻辑坐标指定点,然后将其转换为绘图设备的物理坐标。逻辑坐标到物理坐标的映射由QPainter 的combinedTransform () 处理,它是viewport () 和window () 以及worldTransform () 的组合。viewport () 表示指定任意矩形的物理坐标,window ( ) 在逻辑坐标中描述相同的矩形,worldTransform () 与变换矩阵相同。
剪裁
QPainter 可以将任何绘图操作裁剪为矩形、区域或矢量路径。当前剪辑可使用函数clipRegion () 和clipPath () 获得。首选路径还是区域(更快)取决于底层的paintEngine ()。例如,QImage绘制引擎更喜欢路径,而 X11 绘制引擎更喜欢区域。设置剪辑是在画家的逻辑坐标中完成的。
在 QPainter 的剪辑之后,绘画设备也可能会被剪辑。例如,大多数小部件会剪掉子小部件使用的像素,而大多数打印机会剪掉靠近纸张边缘的区域。clipRegion () 或**hasClipping ()**的返回值不会反映这种额外的裁剪。
构图模式
QPainter 提供了CompositionMode枚举,它定义了数字图像合成的 Porter-Duff 规则;它描述了一个模型,用于将一个图像(源)中的像素与另一图像(目标)中的像素组合起来。
两种最常见的组合形式是Source和SourceOver。源用于将不透明对象绘制到绘图设备上。在这种模式下,源中的每个像素都会替换目标中的相应像素。在SourceOver合成模式下,源对象是透明的并绘制在目标之上。
请注意,合成变换是按像素进行的。出于这个原因,使用图形基元本身及其边界矩形之间存在差异:边界矩形包含 alpha == 0 的像素(即基元周围的像素)。这些像素将覆盖其他图像的像素,从而有效地清除这些像素,而图元仅覆盖其自己的区域。
限制
如果您在 Qt 的基于光栅的绘制引擎中使用坐标,请务必注意,虽然可以使用大于 +/- 2 15的坐标,但不能保证显示使用超出此范围的坐标执行的任何绘制;绘图可能会被剪裁。这是由于short int在实现中使用了。
Qt 的描边器生成的轮廓只是在处理弯曲形状时的近似值。在大多数情况下,不可能用另一条贝塞尔曲线段来表示一条贝塞尔曲线段的轮廓,因此 Qt 通过使用几条较小的曲线来近似曲线轮廓。出于性能原因,Qt 对这些轮廓使用多少曲线是有限制的,因此当使用较大的笔宽或比例时,轮廓误差会增加。要生成具有较小错误的轮廓,可以使用QPainterPathStroker类,它具有 setCurveThreshold 成员函数,让用户指定容错。另一种解决方法是先将路径转换为多边形,然后再绘制多边形。
表现
QPainter 是一个丰富的框架,允许开发人员进行各种各样的图形操作,例如渐变、合成模式和矢量图形。QPainter 可以在各种不同的硬件和软件堆栈上做到这一点。自然地,硬件和软件的底层组合对性能有一定的影响,并且确保每个单独的操作与合成模式、画笔、剪辑、转换等的所有各种组合结合起来都是快速的,这几乎是一项不可能完成的任务,因为排列的数量。作为折衷方案,我们选择了 QPainter API 和后端的一个子集,在给定的硬件和软件组合下,我们可以保证性能尽可能好。
作为高性能引擎,我们关注的后端是:
Raster - 这个后端在纯软件中实现所有渲染,并且总是用于渲染到 QImages 中。为了获得最佳性能,请仅使用格式类型QImage::Format_ARGB32_Premultiplied、QImage::Format_RGB32或QImage::Format_RGB16。任何其他格式,包括QImage::Format_ARGB32,性能明显较差。该引擎默认用于QWidget和QPixmap。
OpenGL 2.0 (ES) - 此后端是硬件加速图形的主要后端。它可以在支持 OpenGL 2.0 或 OpenGL/ES 2.0 规范的台式机和嵌入式设备上运行。这包括过去几年生产的大多数图形芯片。可以通过在QOpenGLWidget上使用 QPainter 来启用引擎。
这些操作是:
简单的转换,即平移和缩放,加上 0、90、180、270 度旋转。
drawPixmap() 结合简单的转换和非平滑转换模式的不透明度(QPainter::SmoothPixmapTransform未作为渲染提示启用)。
矩形填充纯色、双色线性渐变和简单变换。
具有简单变换和相交剪辑的矩形剪辑。
合成模式QPainter::CompositionMode_Source和QPainter::CompositionMode_SourceOver。
圆角矩形填充使用纯色和双色线性渐变填充。
3x3 修补像素图,通过 qDrawBorderPixmap。
此列表指示在性能至关重要的应用程序中可以安全使用哪些功能。对于某些设置,其他操作可能也很快,但在大量使用它们之前,建议在最终运行软件的系统上进行基准测试和验证。在某些情况下,可以使用昂贵的操作,例如当结果缓存在QPixmap中时。
另请参阅QPaintDevice、QPaintEngine、Qt SVG、基本绘图示例和绘图实用程序函数。
QPainter p(this);if (text() == "圆")//椭圆{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));//拐点是圆形,连接也是方形p.setBrush(QBrush(Qt::green, Qt::SolidPattern));int diameter = width() > height() ? height() - 2 : width() - 2;int x = (width() - diameter) / 2;int y = (height() - diameter) / 2;p.drawEllipse(x, y, diameter, diameter);}else if (text() == "椭圆")//椭圆{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));//拐点是圆形,连接也是方形p.setBrush(QBrush(Qt::green, Qt::SolidPattern));p.drawEllipse(1, 1, width()-2, height()-2);}else if (text() == "矩形")//矩形{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin));//拐点是方形,连接也是方p.setBrush(QBrush(Qt::green, Qt::SolidPattern));p.drawRect(rect().adjusted(1,1,-1,-1));}else if (text() == "三角形")//画三角形{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::NoPen/*, 1, Qt::SolidLine, Qt::FlatCap,Qt::MiterJoin*/));//拐点是尖角p.setBrush(QBrush(Qt::green, Qt::SolidPattern));int hlen = 120;int vlen = hlen / 2;int x = (width() - hlen) / 2;int y = (height() - vlen) / 2;QRect r = rect().adjusted(x, y, -x, -y);QPolygon triangle;triangle << r.topLeft() << r.topRight() << (r.adjusted(0, 0, -r.width() / 2, 0)).bottomRight();//三点坐标p.drawPolygon(triangle); //画三角形}else if (text() == "线")//画线{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap));int x1 =30;int x2 = width() - x1;QVector<QLine> lines;lines << QLine(x1, 1, x2, 1);lines << QLine(x1, height()/2, x2, height() / 2);lines << QLine(x1, height() - 1, x2,height()- 1);p.drawLines(lines);}else if (text() == "线2")//画线{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));int hlen = 20;int vlen = hlen / 2;int w = (width() - hlen)/2;int h = (height() - vlen)/2;int x1 = w;int y1 = h;int x2 = width() - w;int y2 = h;int x3 = width()/2;int y3 = h+vlen;QPolygon triangle;triangle << QPoint(x1, y1) << QPoint(x3, y3) << QPoint(x2, y2) ;//三点坐标p.drawPolyline(triangle); //画箭头}else if (text() == "圆角矩形")//圆角矩形{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));p.setBrush(QBrush(Qt::green, Qt::SolidPattern));p.drawRoundRect(rect().adjusted(1,1,-1,-1), 8, 8);}else if (text() == "圆角矩形2")//圆角矩形{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::NoPen/*, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin*/));p.setBrush(QBrush(Qt::green, Qt::SolidPattern));QRect r = rect().adjusted(1, 1, -1, -1);int radius = qMin(r.width(), r.height()) / 2;int diam = 2 * radius;int x1, y1, x2, y2;r.getCoords(&x1, &y1, &x2, &y2);QPainterPath path;path.moveTo(x2, y1 + radius);path.arcTo(QRect(x2 - diam, y1, diam, diam), 0.0, +90.0);path.lineTo(x1 + radius, y1);path.arcTo(QRect(x1, y1, diam, diam), 90.0, +90.0);path.lineTo(x1, y2 - radius);path.arcTo(QRect(x1, y2 - diam, diam, diam), 180.0, +90.0);path.lineTo(x1 + radius, y2);path.arcTo(QRect(x2 - diam, y2 - diam, diam, diam), 270.0, +90.0);path.closeSubpath();p.drawPath(path);}else if (text() == "圆角矩形3")//圆角矩形{p.setRenderHint(QPainter::Antialiasing, true);p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));p.setBrush(QBrush(Qt::green, Qt::SolidPattern));QRect r = rect().adjusted(1, 1, -1, -1);int radius = qMin(r.width(), r.height()) / 2;int diam = radius/2;int x1, y1, x2, y2;r.getCoords(&x1, &y1, &x2, &y2);QPainterPath path;path.moveTo(x2, y1 + radius);path.arcTo(QRect(x2 - diam, y1, diam, diam), 0.0, +90.0);path.lineTo(x1 + radius, y1);path.arcTo(QRect(x1, y1, diam, diam), 90.0, +90.0);path.lineTo(x1, y2 - radius);path.arcTo(QRect(x1, y2 - diam, diam, diam), 180.0, +90.0);path.lineTo(x1 + radius, y2);path.arcTo(QRect(x2 - diam, y2 - diam, diam, diam), 270.0, +90.0);path.closeSubpath();p.drawPath(path);}
QPainter官方文档