Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// Copyright (C) 2015 QUIt Coding <info@quitcoding.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qcanvaspainter.h"
#include "qcanvaspainter_p.h"
#include "engine/qcpainterengine_p.h"
#include "engine/qcpainterrhirenderer_p.h"
#include <rhi/qrhi.h>
#include "qcanvasbrush.h"
#include "qcanvascustombrush.h"
#include "qcanvasimage_p.h"
#include "qcanvasboxshadow.h"
#include <QLoggingCategory>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(QC_INFO, "qt.qcpainter.general")
/*!
\class QCanvasPainter
\since 6.11
\brief The QCanvasPainter class performs hardware-accelerated painting on QRhi.
\inmodule QtCanvasPainter
Qt Canvas Painter (QCanvasPainter) provides painting API optimized for
hardware-accelerated (GPU) painting. The API closely follows the HTML Canvas 2D
Context specification, ported to Qt C++. It is also influenced by QPainter,
but with a more compact API.
Here is a simple example of using QCanvasPainter to create a round button.
\table
\row
\li \inlineimage qcpainter-buttonexample.webp
{Yellow rounded button labeled CLICK! with a soft drop shadow}
\li
\code
QRectF rect(40, 70, 120, 60);
QRectF shadowRect = rect.translated(2, 4);
// Paint shadow
QCanvasBoxShadow shadow(shadowRect);
shadow.setRadius(30);
shadow.setBlur(15);
shadow.setColor("#60373F26");
p->drawBoxShadow(shadow);
// Paint rounded rect
p->beginPath();
p->roundRect(rect, 30);
p->setFillStyle("#DBEB00");
p->fill();
// Paint text
p->setTextAlign(QCanvasPainter::TextAlign::Center);
p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
QFont font("Titillium Web", 18);
p->setFont(font);
p->setFillStyle("#373F26");
p->fillText("CLICK!", rect);
\endcode
\endtable
Here is another example of painting a simple graph.
\table
\row
\li \inlineimage qcpainter-graphexample.webp
{Shadowed S-curve shading from green to red over a dark grid with
white axes}
\li
\code
// Paint grid
QCanvasGridPattern grid(0, 0, 10, 10);
grid.setLineColor("#404040");
grid.setBackgroundColor("#202020");
p->setFillStyle(grid);
p->fillRect(0, 0, width(), height());
// Paint axis
p->setFillStyle(QColorConstants::White);
p->fillRect(0, 0.5 * height() - 1, width(), 2);
p->fillRect(0.5 * width() - 1, 0, 2, height());
// Paint shadowed graph
p->beginPath();
p->moveTo(20, height() * 0.8);
p->bezierCurveTo(width() * 0.2, height() * 0.4,
width() * 0.5, height() * 0.8,
width() - 20, height() * 0.2);
p->setAntialias(10);
p->setLineWidth(12);
p->setStrokeStyle("#D0000000");
p->stroke();
p->setAntialias(1);
p->setLineWidth(6);
QCanvasLinearGradient lg(0, 0, 0, height());
lg.setStartColor(QColorConstants::Red);
lg.setEndColor(QColorConstants::Green);
p->setStrokeStyle(lg);
p->stroke();
\endcode
\endtable
\section1 Features
For the most part, and from the naming perspective, QCanvasPainter follows
This makes the API familiar to use for many developers, and with the ability
to easily reuse existing canvas code. But the aim is NOT to be 100% compatible with
the HTML canvas. QCanvasPainter omits some of the features to make it simpler,
more performant on QRhi hardware accelerated graphics API, and to better target
modern UI needs. Due to these reasons, QCanvasPainter also has additional features
compared to HTML Canvas 2D Context.
These are some of the functionality that are at least currently missing compared
to HTML Canvas:
\list
\li Clipping: All clipping is (transformed) rectangle and clipping to path shapes
is not supported.
\li Dashes: Strokes are always solid lines, dashed/dotted stroke patterns are
not supported.
\li Path testing: There are no isPointInPath() or isPointInStroke() methods.
\li Text stroking: No support for outline stroking of text.
\li Filter: Canvas SVG filter effects are not supported.
\li CompositeModes: The number of composite modes is limited to 3, which can
be supported without rendering into extra buffers.
\li Shadows: Built-in shadow methods are not supported.
\endlist
On the other hand, some of the additional features QCanvasPainter offers compared
to HTML Canvas include:
\list
\li Path groups: QCanvasPainter allows painting to static paths and caching these
paths as groups for optimal GPU usage.
\li Adjustable antialiasing: Due to path vertex antialiasing and SDF text
rendering, the pixel amount of antialiasing can be freely adjusted for
smoother painting.
\li Box Gradient: In addition to linear, radial and conical gradients,
QCanvasPainter also supports rounded rectangle box gradient.
\li Box Shadow: QCanvasPainter also supports CSS box-shadow type of brush. The
rendering uses a SDF approach similar to Qt Quick RectangularShadow, making
it very performant.
\li Grid patterns: QCanvasPainter supports QCanvasGridPattern for dynamic grid and bar
pattern styles.
\li Custom brushes: QCanvasPainter also allows filling & stroking with custom
vertex and fragment shaders (QCanvasCustomBrush). These custom brushes can also
be used for text.
\li Text wrapping: QCanvasPainter supports automatic wrapping of text into
multiple lines, with different wrapping modes.
\li Color effects: In addition to globalAlpha, QCanvasPainter also supports
global brightness, contrast and saturation.
\li Tinted images: QCanvasPainter adds tint color support for painted images
and image patterns.
\endlist
QCanvasPainter is architecture agnostic, and usable for both Qt Quick and
Qt Widgets applications. Actually, it is usable even without either of
those, with just QWindow and QRhi. To utilize QCanvasPainter, use it from
one of these classes, depending on the architecture of your application:
\list
\li Qt Quick: Use \l QCanvasPainterItem and \l QCanvasPainterItemRenderer.
\li Qt Widgets: Use \l QCanvasPainterWidget.
\li QRhi-based QWindow, or offscreen QRhi buffers: Use \l QCanvasPainterFactory and \l QCanvasRhiPaintDriver.
\endlist
\section1 Winding rules
QCanvasPainter uses NonZero ({Qt::WindingFill}) fillrule by default. To select the filling
based on the path points direction, disable the winding forcing by calling
\l {setWindingEnforce()}{setWindingEnforce(false)}.
\table
\row
\li \inlineimage qcpainter-pathwinding2.webp
{Green downward triangle with a smaller unfilled triangular hole near
its top}
\li
\code
p->setWindingEnforce(false);
p->beginPath();
// Outer shape, counterclockwise
p->moveTo(20, 20);
p->lineTo(100, 180);
p->lineTo(180, 20);
p->closePath();
// Inner shape, clockwise
p->moveTo(100, 40);
p->lineTo(125, 90);
p->lineTo(75, 90);
p->closePath();
p->fill();
p->stroke();
\endcode
\endtable
However, a more common case is relying on winding enforcing and setting
the preferred winding using \l setPathWinding() or \l beginHoleSubPath()
and \l beginSolidSubPath() helpers.
\table
\row
\li \inlineimage qcpainter-pathwinding3.webp
{Green rounded square outline enclosing a rounded hole that contains a
solid bar and dot}
\li
\code
p->beginPath();
p->roundRect(20, 20, 160, 160, 30);
// Start painting holes
p->beginHoleSubPath();
p->roundRect(40, 40, 120, 120, 10);
// Start painting solid
p->beginSolidSubPath();
p->rect(60, 60, 80, 20);
p->circle(100, 120, 20);
p->fill();
p->stroke();
\endcode
\endtable
*/
/*!
\enum QCanvasPainter::PathWinding
PathWinding is used to specify the direction of path drawing.
This direction is used to determine if a subpath is solid or hole in the path.
\value CounterClockWise (default) Counter-clockwise winding for solid shapes.
\value ClockWise Clockwise winding for holes.
\sa setPathWinding()
*/
/*!
\enum QCanvasPainter::PathConnection
With some drawing methods PathConnection is used to specify if the new
path should be connected to the last point of the previous path.
\value NotConnected There is no line drawn from the last point of
the previous path to the first point of the current path.
\value Connected The last point of the previous path will be connected to
the first point of the current path.
\sa arc()
*/
/*!
\enum QCanvasPainter::LineCap
LineCap is used to define how the end of the line (cap) is drawn.
\value Butt (default) Square line ending that does not cover the end point of the line.
\value Round Round line ending.
\value Square Square line ending that covers the end point and extends beyond it by half the line width.
\sa setLineCap()
*/
/*!
\enum QCanvasPainter::LineJoin
LineJoin is used to define how the joins between two connected lines are drawn.
\value Round Circular arc between the two lines is filled.
\value Bevel The triangular notch between the two lines is filled.
\value Miter (default) The outer edges of the lines are extended to meet at an angle, and this area is filled.
\sa setLineJoin(), setMiterLimit()
*/
/*!
\enum QCanvasPainter::TextAlign
TextAlign is used to define how the text is aligned horizontally.
\value Left Align the left side of the text horizontally to the specified position.
\value Right Align the right side of the text horizontally to the specified position.
\value Center Align the center of the text horizontally to the specified position.
\value Start (default) The text is aligned at the normal start of the line (left-aligned for left-to-right locales, right-aligned for right-to-left locales).
\value End The text is aligned at the normal end of the line (right-aligned for left-to-right locales, left-aligned for right-to-left locales).
\sa setTextAlign(), setTextDirection(), fillText()
*/
/*!
\enum QCanvasPainter::TextBaseline
TextBaseline is used to define how the text is aligned (baselined) vertically.
\value Top Align the top of the text vertically to the specified position.
\value Hanging Align the hanging baseline of the text vertically to the specified position.
\value Middle Align the middle of the text vertically to the specified position.
\value Alphabetic (default) Align the baseline of the text vertically to the specified position.
\value Bottom Align the bottom of the text vertically to the specified position.
\sa setTextBaseline(), fillText()
*/
/*!
\enum QCanvasPainter::TextDirection
TextDirection is used to define how the text is aligned horizontally.
\value LeftToRight The text direction is left-to-right.
\value RightToLeft The text direction is right-to-left.
\value Inherit (default) The text direction is inherited from QGuiApplication layoutDirection. See https://doc.qt.io/qt-6/qguiapplication.html#layoutDirection-prop.
\value Auto The text direction is detected automatically based from the text string. See \l QString::isRightToLeft().
\note As this requires analyzing the text, it is potentially slower than other options.
\sa setTextDirection(), setTextAlign(), fillText()
*/
/*!
\enum QCanvasPainter::CompositeOperation
Qt Canvas Painter supports 3 composite operations:
\value SourceOver The default value. Draws new shapes on top of the existing content.
\value SourceAtop The new shape is only drawn where it overlaps the existing content.
\value DestinationOut The existing content is kept where it doesn't overlap with the new shape.
\sa setGlobalCompositeOperation()
*/
/*!
\enum QCanvasPainter::WrapMode
WrapMode is used to define how the text is wrapped to multiple lines.
\value NoWrap (default) No wrapping will be performed. If the text contains insufficient newlines, then contentWidth will exceed a set width.
\value Wrap If possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
\value WordWrap Wrapping is done on word boundaries only. If a word is too long, content width will exceed a set width.
\value WrapAnywhere Wrapping is done at any point on a line, even if it occurs in the middle of a word.
\sa setTextWrapMode(), fillText()
*/
/*!
\enum QCanvasPainter::FillRule
\since 6.12
This enum specifies how paths are filled.
\value NonZero The path is filled using the non zero winding rule. With this rule, we determine whether a point is inside the shape by using the following method. Draw a horizontal line from the point to a location outside the shape. Determine whether the direction of the line at each intersection point is up or down. The winding number is determined by summing the direction of each intersection. If the number is non zero, the point is inside the shape. This fill mode can also in most cases be considered as the intersection of closed shapes. This mode is the default.
\value EvenOdd The path is filled using the odd even fill rule. With this rule, we determine whether a point is inside the shape by using the following method. Draw a horizontal line from the point to a location outside the shape, and count the number of intersections. If the number of intersections is an odd number, the point is inside the shape.
\sa fill()
*/
/*!
\enum QCanvasPainter::ImageFlag
This enum specifies flags related to images. Use with
\l addImage() to set the flags.
\value GenerateMipmaps Set this to generate mipmaps for the image.
Mipmaps should be used when smoother output is preferred for images
which are scaled to smaller than the original size.
\value RepeatX Use with image pattern to repeat image in X-coordinate.
\value RepeatY Use with image pattern to repeat image in Y-coordinate.
\value Repeat Use with image pattern to repeat image in both coordinates.
\value FlipY Flips (inverses) image in Y direction when rendered.
\value Premultiplied Image data has premultiplied alpha.
\value Nearest Image interpolation is Nearest instead Linear
*/
/*!
Constructs a painter.
*/
QCanvasPainter::QCanvasPainter()
: d_ptr(new QCanvasPainterPrivate)
{
}
/*!
Destroys the painter.
*/
QCanvasPainter::~QCanvasPainter()
= default;
// *** State Handling ***
/*!
Pushes and saves the current render state into a state stack.
A matching restore() must be used to restore the state.
\table
\row
\li \inlineimage qcpainter-save.webp
{Two rectangle outlines with a slightly rotated rectangle between
them}
\li
\code
p->strokeRect(20, 20, 160, 40);
// Save and adjust the paint state
p->save();
p->setStrokeStyle(QColorConstants::Black);
p->setLineWidth(3);
p->rotate(0.1);
p->strokeRect(20, 80, 180, 20);
// Restore the saved paint state
p->restore();
p->strokeRect(20, 140, 160, 40);
\endcode
\endtable
\sa restore()
*/
void QCanvasPainter::save()
{
Q_D(QCanvasPainter);
d->m_e->save();
}
/*!
Pops and restores the current render state.
So previously saved state will be restored.
If save() has not been called and the state stack
is empty, calling this does nothing.
\sa save()
*/
void QCanvasPainter::restore()
{
Q_D(QCanvasPainter);
d->m_e->restore();
}
/*!
Resets the current painter state to default values.
This includes the stroke and fill styles and the related properties, the
transformation matrix, the clipping region, the text-related settings, the
compositing properties, the drawing state stack, the antialiasing settings.
\note This method differs from the HTML Canvas 2D Context reset() method
in that it doesn't visually clear the canvas buffers.
\note The values of setWindingEnforce() and setHighQualityStroking() are not
changed.
\table
\row
\li \inlineimage qcpainter-reset.webp
{Rotated green rounded rectangle above a filled black square and an
outlined square drawn after reset}
\li
\code
// Adjust the paint state
p->setStrokeStyle("#00414A");
p->setFillStyle("#2CDE85");
p->setLineWidth(10);
QRectF rect(20, 40, 160, 50);
p->translate(rect.center());
p->rotate(qDegreesToRadians(-25));
p->translate(-rect.center());
p->beginPath();
p->roundRect(rect, 20);
p->fill();
p->stroke();
// Reset to default paint state
p->reset();
p->fillRect(20, 140, 60, 40);
p->strokeRect(120, 140, 60, 40);
\endcode
\endtable
\sa save(), restore()
*/
void QCanvasPainter::reset()
{
Q_D(QCanvasPainter);
d->m_e->reset();
}
// *** Render styles ***
/*!
Sets the stroke style to a solid \a color.
The default stroke style is solid black color (0, 0, 0, 1).
\table
\row
\li \inlineimage qcpainter-strokestyle.webp
{Three nested square outlines in black, dark teal, and green}
\li
\code
p->setStrokeStyle(QColorConstants::Black);
p->strokeRect(20, 20, 160, 160);
p->setStrokeStyle(QColor(0, 65, 74));
p->strokeRect(40, 40, 120, 120);
p->setStrokeStyle("#2CDE85");
p->strokeRect(60, 60, 80, 80);
\endcode
\endtable
*/
void QCanvasPainter::setStrokeStyle(QColor color)
{
Q_D(QCanvasPainter);
d->m_e->setStrokeColor(color);
}
static QCPaint paintFromBrush(const QCanvasBrush &brush, QCanvasPainter *painter)
{
if (const auto *brushPrivate = QCanvasBrushPrivate::get(brush))
return brushPrivate->createPaint(painter);
return QCPaint{};
}
/*!
\overload
Sets the stroke style to \a brush.
The default stroke style is solid black (0, 0, 0, 1).
\table
\row
\li \inlineimage qcpainter-strokestyle2.webp
{Two nested square outlines with diagonal gradients of green
to black and green to yellow}
\li
\code
QCanvasLinearGradient g1(180, 20, 20, 180);
g1.setStartColor(QColor(44, 222, 133));
g1.setEndColor(Qt::black);
p->setStrokeStyle(g1);
p->strokeRect(20, 20, 160, 160);
g1.setEndColor(Qt::yellow);
p->setStrokeStyle(g1);
p->strokeRect(40, 40, 120, 120);
\endcode
\endtable
*/
void QCanvasPainter::setStrokeStyle(const QCanvasBrush &brush)
{
Q_D(QCanvasPainter);
if (brush.type() == QCanvasBrush::BrushType::Custom)
d->m_e->setCustomStrokeBrush(brush);
else
d->m_e->setStrokePaint(paintFromBrush(brush, this));
}
/*!
Sets the fill style to a solid \a color.
The default fill style is solid black (0, 0, 0, 1).
\table
\row
\li \inlineimage qcpainter-fillstyle.webp
{Concentric filled squares in black, dark teal, and green}
\li
\code
p->setFillStyle(QColorConstants::Black);
p->fillRect(20, 20, 160, 160);
p->setFillStyle(QColor(0, 65, 74));
p->fillRect(40, 40, 120, 120);
p->setFillStyle("#2CDE85");
p->fillRect(60, 60, 80, 80);
\endcode
\endtable
*/
void QCanvasPainter::setFillStyle(QColor color)
{
Q_D(QCanvasPainter);
d->m_e->setFillColor(color);
}
/*!
\overload
Sets the fill style to \a brush.
The default fill style is solid black (0, 0, 0, 1).
\table
\row
\li \inlineimage qcpainter-fillstyle2.webp
{Green square filled with a radial gradient fading to dark teal at the
edges}
\li
\code
QCanvasRadialGradient g2(140, 40, 0, 300);
g2.setStartColor(QColor(44, 222, 133));
g2.setEndColor(QColor(0, 65, 74));
p->setFillStyle(g2);
p->fillRect(20, 20, 160, 160);
g2.setCenterPosition(100, 100);
p->setFillStyle(g2);
p->fillRect(40, 40, 120, 120);
\endcode
\endtable
*/
void QCanvasPainter::setFillStyle(const QCanvasBrush &brush)
{
Q_D(QCanvasPainter);
if (brush.type() == QCanvasBrush::BrushType::Custom)
d->m_e->setCustomFillBrush(brush);
else
d->m_e->setFillPaint(paintFromBrush(brush, this));
}
/*!
Sets the miter limit to \a limit. Miter limit controls when a sharp corner
is beveled. When the corner length would become longer than this limit,
QCanvasPainter::LineJoin::Bevel will be applied between the lines instead.
This only has effect with the QCanvasPainter::LineJoin::Miter line join.
The default limit is \c 10.0.
\table
\row
\li \inlineimage qcpainter-miterlimit.webp
\li
\code
QCanvasPath path;
path.moveTo(20, 40);
path.lineTo(30, 130);
path.lineTo(40, 40);
path.lineTo(55, 80);
p->setLineJoin(QCanvasPainter::LineJoin::Miter);
p->setMiterLimit(20);
p->stroke(path);
p->setMiterLimit(8);
p->translate(65, 0);
p->stroke(path);
p->setMiterLimit(2);
p->translate(65, 0);
p->stroke(path);
\endcode
\endtable
\sa setLineJoin()
*/
void QCanvasPainter::setMiterLimit(qreal limit)
{
Q_D(QCanvasPainter);
d->m_e->setMiterLimit(float(limit));
}
/*!
Sets the line width of strokes to \a width in pixels.
The default line width is \c 1.0. When antialiasing is enabled,
widths thinner than a single pixel will automatically fade the
opacity, creating a smooth output.
\table
\row
\li \inlineimage qcpainter-linewidth.webp
{Stack of wavy curves drawn with progressively increasing line
widths}
\li
\code
for (int i = 1; i < 10 ; i++) {
int y = i * 20;
p->setLineWidth(0.5 * i);
p->beginPath();
p->moveTo(20, y);
p->bezierCurveTo(80, y + 20, 120,
y - 20, 180, y);
p->stroke();
}
\endcode
\endtable
\sa stroke()
*/
void QCanvasPainter::setLineWidth(qreal width)
{
Q_D(QCanvasPainter);
d->m_e->setLineWidth(float(width));
}
/*!
Sets the end of the line for strokes to \a cap.
The default line cap is \c QCanvasPainter::LineCap::Butt.
\table
\row
\li \inlineimage qcpainter-linecap.webp
{Three horizontal lines showing butt, square, and round line caps}
\li
\code
QCanvasPath path;
path.moveTo(40, 60);
path.lineTo(160, 60);
p->setLineCap(QCanvasPainter::LineCap::Butt);
p->stroke(path, -1);
p->setLineCap(QCanvasPainter::LineCap::Square);
p->translate(0, 40);
p->stroke(path, -1);
p->setLineCap(QCanvasPainter::LineCap::Round);
p->translate(0, 40);
p->stroke(path, -1);
\endcode
\endtable
*/
void QCanvasPainter::setLineCap(LineCap cap)
{
Q_D(QCanvasPainter);
d->m_e->setLineCap(cap);
}
/*!
Sets the line join of strokes to \a join.
The default line join is \c QCanvasPainter::LineJoin::Miter.
\table
\row
\li \inlineimage qcpainter-linejoin.webp
{Three zigzag paths showing miter, bevel, and round line joins}
\li
\code
QCanvasPath path;
path.moveTo(40, 20);
path.lineTo(100, 80);
path.lineTo(160, 40);
path.lineTo(160, 70);
p->setLineJoin(QCanvasPainter::LineJoin::Miter);
p->stroke(path, -1);
p->setLineJoin(QCanvasPainter::LineJoin::Bevel);
p->translate(0, 50);
p->stroke(path, -1);
p->setLineJoin(QCanvasPainter::LineJoin::Round);
p->translate(0, 50);
p->stroke(path, -1);
\endcode
\endtable
\sa setMiterLimit()
*/
void QCanvasPainter::setLineJoin(LineJoin join)
{
Q_D(QCanvasPainter);
d->m_e->setLineJoin(join);
}
/*!
Sets the global alpha (transparency) value to \a alpha. This alpha value is
applied to all rendered shapes. Already transparent paths will get
proportionally more transparent as well.
Alpha should be between 0.0 (fully transparent) and 1.0 (fully opaque).
By default alpha is \c 1.0.
\table
\row
\li \inlineimage qcpainter-globalalpha.webp
{Four Qt logos on green tiles fading to fully transparent across the
grid}
\li
\code
static QImage logo(":/qt_logo2.png");
QCanvasImage image = p->addImage(logo);
p->setFillStyle("#d9f720");
for (int i = 0; i < 4; i++) {
qreal x = 100 * (i % 2);
qreal y = 100 * (i / 2);
QRectF rect(x, y, 100, 100);
p->setGlobalAlpha(1.0 - i * 0.3);
p->fillRect(rect);
p->drawImage(image, rect);
}
\endcode
\endtable
*/
void QCanvasPainter::setGlobalAlpha(qreal alpha)
{
Q_D(QCanvasPainter);
d->m_e->setGlobalAlpha(float(alpha));
}
/*!
Sets the global composite operation mode to \a operation. This mode is
applied to all painting operations. The default mode is
\c QCanvasPainter::CompositeOperation::SourceOver.
\note To work properly, composite modes require enabling \c alphaBlending
and using a transparent background for the canvas.
\table
\row
\li \inlineimage qcpainter-globalcomposite.webp
\li
\code
QList<QCanvasPainter::CompositeOperation> modes = {
QCanvasPainter::CompositeOperation::SourceOver,
QCanvasPainter::CompositeOperation::SourceAtop,
QCanvasPainter::CompositeOperation::DestinationOut
};
for (int i = 0; i < modes.size(); i++) {
// First item - SourceOver mode
p->setGlobalCompositeOperation(QCanvasPainter::CompositeOperation::SourceOver);
qreal y = 5 + i * 65;
p->setFillStyle("#D9F720");
p->fillRect(20, y, 140, 40);
// Second item - Different modes
p->setGlobalCompositeOperation(modes[i]);
p->beginPath();
p->ellipse(130, y + 30, 60, 25);
p->setStrokeStyle("#00414A");
p->setFillStyle("#2CDE85");
p->fill();
p->stroke();
}
\endcode
\endtable
*/
void QCanvasPainter::setGlobalCompositeOperation(CompositeOperation operation)
{
Q_D(QCanvasPainter);
d->m_e->setGlobalCompositeOperation(operation);
}
/*!
Sets the global brightness to \a value. This brightness is
applied to all rendered shapes. A value of 0 will cause painting
to be completely black. Value can also be bigger than 1.0, to
increase the brightness.
By default, brightness is \c 1.0.
\table
\row
\li \inlineimage qcpainter-globalbrightness.webp
{Four Qt logos on green tiles darkening from bright to nearly black
across the grid}
\li
\code
static QImage logo(":/qt_logo2.png");
QCanvasImage image = p->addImage(logo);
p->setFillStyle("#d9f720");
for (int i = 0; i < 4; i++) {
qreal x = 100 * (i % 2);
qreal y = 100 * (i / 2);
QRectF rect(x, y, 100, 100);
p->setGlobalBrightness(1.5 - i * 0.45);
p->fillRect(rect);
p->drawImage(image, rect);
}
\endcode
\endtable
*/
void QCanvasPainter::setGlobalBrightness(qreal value)
{
Q_D(QCanvasPainter);
d->m_e->setGlobalBrightness(float(value));
}
/*!
Sets the global contrast to \a value. This contrast is
applied to all rendered shapes. A value of 0 will cause painting
to be completely gray (0.5, 0.5, 0.5). Value can also be bigger
than 1.0, to increase the contrast.
By default, contrast is \c 1.0.
\table
\row
\li \inlineimage qcpainter-globalcontrast.webp
{Four Qt logos on green tiles ranging from high contrast to washed-out
gray}
\li
\code
static QImage logo(":/qt_logo2.png");
QCanvasImage image = p->addImage(logo);
p->setFillStyle("#d9f720");
for (int i = 0; i < 4; i++) {
qreal x = 100 * (i % 2);
qreal y = 100 * (i / 2);
QRectF rect(x, y, 100, 100);
p->setGlobalContrast(1.5 - i * 0.45);
p->fillRect(rect);
p->drawImage(image, rect);
}
\endcode
\endtable
*/
void QCanvasPainter::setGlobalContrast(qreal value)
{
Q_D(QCanvasPainter);
d->m_e->setGlobalContrast(float(value));
}
/*!
Sets the global saturation to \a value. This saturation is
applied to all rendered shapes. A value of 0 will disable saturation
and cause painting to be completely grayscale. Value can also be bigger
than 1.0, to increase the saturation.
By default, saturation is \c 1.0.
\table
\row
\li \inlineimage qcpainter-globalsaturation.webp
{Four Qt logos on green tiles fading from vivid color to grayscale
across the grid}
\li
\code
static QImage logo(":/qt_logo2.png");
QCanvasImage image = p->addImage(logo);
p->setFillStyle("#d9f720");
for (int i = 0; i < 4; i++) {
qreal x = 100 * (i % 2);
qreal y = 100 * (i / 2);
QRectF rect(x, y, 100, 100);
p->setGlobalSaturation(1.5 - i * 0.5);
p->fillRect(rect);
p->drawImage(image, rect);
}
\endcode
\endtable
*/
void QCanvasPainter::setGlobalSaturation(qreal value)
{
Q_D(QCanvasPainter);
d->m_e->setGlobalSaturation(float(value));
}
/*!
Sets the path fill rule to \a fillRule. This value is applied to all fill() calls
after the rule has been set. The default fill rule is \c QCanvasPainter::FillRule::NonZero
\since 6.12
\table
\row
\li \inlineimage qcpainter-fillrule.webp
{Two overlapping green five-pointed stars, one filled solid and one
with an open center}
\li
\code
auto paintStar = [p]() {
p->beginPath();
p->moveTo(120, 60);
for (int i = 1; i < 6; ++i) {
p->lineTo(60 + 60 * cos(0.8 * i * M_PI),
60 + 60 * sin(0.8 * i * M_PI));
}
p->fill();
p->stroke();
};
p->setFillRule(QCanvasPainter::FillRule::NonZero);
paintStar();
p->translate(75, 75);
p->setFillRule(QCanvasPainter::FillRule::EvenOdd);
paintStar();
\endcode
\endtable
*/
void QCanvasPainter::setFillRule(FillRule fillRule)
{
Q_D(QCanvasPainter);
d->m_e->setFillRule(fillRule);
}
// *** Transforms ***
/*!
Resets the current transform to an identity matrix.
*/
void QCanvasPainter::resetTransform()
{
Q_D(QCanvasPainter);
d->m_e->resetTransform();
}
/*!
Resets the current transform and uses \a transform instead.
\table
\row
\li \inlineimage qcpainter-transform.webp
{Small green rounded square above a larger rotated and scaled rounded
square}
\li
\code
p->beginPath();
p->roundRect(80, 20, 40, 40, 10);
p->fill();
p->stroke();
QTransform t;
t.translate(100, 20);
t.rotate(45);
t.scale(2.0, 2.0);
p->setTransform(t);
p->beginPath();
p->roundRect(20, 20, 40, 40, 10);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::setTransform(const QTransform &transform)
{
Q_D(QCanvasPainter);
d->m_e->setTransform(transform);
}
/*!
Multiplies the current coordinate system by the specified \a transform.
\table
\row
\li \inlineimage qcpainter-transform2.webp
{Ten green rounded bars arranged in a ring by repeated rotation around
a center}
\li
\code
QTransform t;
t.translate(100, 100);
t.rotate(36);
t.translate(-100, -100);
for (int i = 0; i < 10; i++) {
p->transform(t);
p->beginPath();
p->roundRect(80, 15, 40, 20, 10);
p->fill();
p->stroke();
}
\endcode
\endtable
*/
void QCanvasPainter::transform(const QTransform &transform)
{
Q_D(QCanvasPainter);
d->m_e->transform(transform);
}
/*!
Translates the current coordinate system by \a x and \a y.
\table
\row
\li \inlineimage qcpainter-translate.webp
{Two identical green rounded rectangles, the lower one shifted down by
the translation}
\li
\code
auto paintRect = [p]() {
p->beginPath();
p->roundRect(20, 20, 160, 60, 10);
p->fill();
p->stroke();
};
paintRect();
p->translate(0, 100);
paintRect();
\endcode
\endtable
*/
void QCanvasPainter::translate(qreal x, qreal y)
{
Q_D(QCanvasPainter);
d->m_e->translate(float(x), float(y));
}
/*!
\fn void QCanvasPainter::translate(QPointF point)
\overload
Translates the current coordinate system by \a point.
*/
/*!
Rotates the current coordinate system clockwise by \a angle.
The angle is specified in radians. Use qDegreesToRadians() to convert from
degrees to radians.
\table
\row
\li \inlineimage qcpainter-rotate.webp
{Green rounded rectangle rotated 45 degrees with the text Cute!
rotated along it}
\li
\code
QRectF rect(20, 70, 160, 60);
p->translate(rect.center());
p->rotate(-M_PI / 4);
p->translate(-rect.center());
p->beginPath();
p->roundRect(rect, 10);
p->fill();
p->stroke();
p->setFillStyle(QColorConstants::Black);
p->fillText("Cute!", rect);
\endcode
\endtable
*/
void QCanvasPainter::rotate(qreal angle)
{
Q_D(QCanvasPainter);
d->m_e->rotate(float(angle));
}
/*!
Skews (shears) the current coordinate system along X axis by \a angleX
and along Y axis by \a angleY. Angles are specified in radians.
\table
\row
\li \inlineimage qcpainter-skew.webp
{Green rounded rectangle with text Cute! skewed into a parallelogram}
\li
\code
QRectF rect(40, 70, 120, 60);
p->translate(rect.center());
p->skew(-0.6);
p->translate(-rect.center());
p->beginPath();
p->roundRect(rect, 10);
p->fill();
p->stroke();
p->setFillStyle(QColorConstants::Black);
p->fillText("Cute!", rect);
\endcode
\endtable
*/
void QCanvasPainter::skew(qreal angleX, qreal angleY)
{
Q_D(QCanvasPainter);
d->m_e->skew(float(angleX), float(angleY));
}
/*!
Scales the current coordinate system by \a scale. Both x and y coordinates
are scaled evenly.
\table
\row
\li \inlineimage qcpainter-scale.webp
{Nested square outlines shrinking toward the center by repeated
scaling}
\li
\code
QRectF rect(20, 20, 160, 160);
for (int i = 0; i < 20; i++) {
p->beginPath();
p->roundRect(rect, 10);
p->stroke();
p->translate(rect.center());
p->scale(0.8);
p->translate(-rect.center());
}
\endcode
\endtable
*/
void QCanvasPainter::scale(qreal scale)
{
Q_D(QCanvasPainter);
d->m_e->scale(float(scale), float(scale));
}
/*!
\overload
Scales the current coordinate system by \a scaleX and \a scaleY.
*/
void QCanvasPainter::scale(qreal scaleX, qreal scaleY)
{
Q_D(QCanvasPainter);
d->m_e->scale(float(scaleX), float(scaleY));
}
/*!
Returns the current transform.
*/
QTransform QCanvasPainter::getTransform() const
{
Q_D(const QCanvasPainter);
return d->m_e->currentTransform();
}
/*!
\internal
Sets the current brush transform to \a transform. This transform is
applied to both stroke and fill brushes.
\note Setting the brush transform will change the internal state.
Make sure to set it back to the identity transform before doing other
paint operations.
*/
void QCanvasPainterPrivate::setBrushTransform(const QTransform &transform)
{
m_e->setBrushTransform(transform);
}
// *** Clipping ***
/*!
Sets the current scissor rectangle to (\a x, \a y, \a width, \a height).
The scissor rectangle is transformed by the current transform.
\note Clipping has some performance cost and it should only be used
when needed.
\table
\row
\li \inlineimage qcpainter-cliprect.webp
{Green quarter-circle and the text Clip me clipped to a square region}
\li
\code
QRectF viewArea(20, 20, 160, 160);
p->setClipRect(viewArea);
p->beginPath();
p->circle(40, 40, 110);
p->fill();
p->setFillStyle(Qt::black);
p->fillText("Clip me...", 40, 100);
p->strokeRect(viewArea);
\endcode
\endtable
\sa resetClipping()
*/
void QCanvasPainter::setClipRect(qreal x, qreal y, qreal width, qreal height)
{
Q_D(QCanvasPainter);
d->m_e->setClipRect(QRectF(x, y, width, height));
}
/*!
\fn void QCanvasPainter::setClipRect(const QRectF &rect)
\overload
Sets the current scissor rectangle to \a rect.
The scissor rectangle is transformed by the current transform.
\note Clipping has some performance cost and it should only be used
when needed.
\sa resetClipping()
*/
/*!
Resets and disables clipping.
\sa setClipRect()
*/
void QCanvasPainter::resetClipping()
{
Q_D(QCanvasPainter);
d->m_e->resetClipRect();
}
// *** Paths ***
/*!
Begins drawing a new path while clearing the current path.
*/
void QCanvasPainter::beginPath()
{
Q_D(QCanvasPainter);
d->m_e->beginPath();
}
/*!
Closes the current sub-path with a line segment.
This is equivalent to lineTo([starting point]) as the last path element.
*/
void QCanvasPainter::closePath()
{
Q_D(QCanvasPainter);
d->m_e->closePath();
}
/*!
Starts a new sub-path with ( \a x, \a y) as the first point.
*/
void QCanvasPainter::moveTo(qreal x, qreal y)
{
Q_D(QCanvasPainter);
d->m_e->moveTo(float(x), float(y));
}
/*!
\fn void QCanvasPainter::moveTo(QPointF point)
\overload
Starts a new sub-path with \a point as the first point.
*/
/*!
Adds line segment from the last point in the path to the ( \a x, \a y) point.
\table
\row
\li \inlineimage qcpainter-line.webp
{Two straight line segments meeting at a point to form a corner}
\li
\code
p->beginPath();
p->moveTo(20, 20);
p->lineTo(140, 180);
p->lineTo(180, 120);
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::lineTo(qreal x, qreal y)
{
Q_D(QCanvasPainter);
d->m_e->lineTo(float(x), float(y));
}
/*!
\fn void QCanvasPainter::lineTo(QPointF point)
\overload
Adds line segment from the last point in the path to the \a point.
*/
/*!
Adds a cubic bezier segment from the last point in the path via two
control points (\a cp1X, \a cp1Y and \a cp2X, \a cp2Y) to the specified point (\a x, \a y).
\table
\row
\li \inlineimage qcpainter-beziercurve.webp
{Smooth S-shaped cubic Bezier curve}
\li
\code
p->beginPath();
p->moveTo(20, 20);
p->bezierCurveTo(150, 50, 50, 250, 180, 120);
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::bezierCurveTo(qreal cp1X, qreal cp1Y, qreal cp2X, qreal cp2Y, qreal x, qreal y)
{
Q_D(QCanvasPainter);
d->m_e->bezierTo(float(cp1X), float(cp1Y), float(cp2X), float(cp2Y), float(x), float(y));
}
/*!
\fn void QCanvasPainter::bezierCurveTo(QPointF controlPoint1, QPointF controlPoint2, QPointF endPoint)
\overload
Adds a cubic bezier segment from the last point in the path via two
control points (\a controlPoint1 and \a controlPoint2) to the specified point \a endPoint.
*/
/*!
Adds a quadratic bezier segment from the last point in the path via
a control point (\a cpX, \a cpY) to the specified point (\a x, \a y).
\table
\row
\li \inlineimage qcpainter-quadraticcurve.webp
{Pointed green leaf shape formed by two quadratic curves}
\li
\code
p->beginPath();
p->moveTo(20, 20);
p->quadraticCurveTo(150, 50, 180, 180);
p->quadraticCurveTo(20, 220, 20, 20);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::quadraticCurveTo(qreal cpX, qreal cpY, qreal x, qreal y)
{
Q_D(QCanvasPainter);
d->m_e->quadTo(float(cpX), float(cpY), float(x), float(y));
}
/*!
\fn void QCanvasPainter::quadraticCurveTo(QPointF controlPoint, QPointF endPoint)
\overload
Adds a quadratic bezier segment from the last point in the path via
a \a controlPoint to the specified \a endPoint.
*/
/*!
Adds an arc segment at the corner defined by the last path point,
and two specified points (\a x1, \a y1 and \a x2, \a y2) with \a radius.
The arc is automatically connected to the path's latest point with
a straight line if necessary.
\table
\row
\li \inlineimage qcpainter-arcto.webp
{Hooked path with a horizontal top segment, a large rounded curve,
and a small curl at the end}
\li
\code
p->beginPath();
p->moveTo(20, 20);
p->arcTo(240, 20, 20, 220, 50);
p->arcTo(20, 220, 20, 20, 30);
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::arcTo(qreal x1, qreal y1, qreal x2, qreal y2, qreal radius)
{
Q_D(QCanvasPainter);
d->m_e->arcTo(float(x1), float(y1), float(x2), float(y2), float(radius));
}
/*!
\fn void QCanvasPainter::arcTo(QPointF controlPoint1, QPointF controlPoint2, qreal radius)
\overload
Adds an arc segment at the corner defined by the last path point,
and two specified points (\a controlPoint1 and \a controlPoint2) with \a radius.
The arc is automatically connected to the path's latest point with
a straight line if necessary.
*/
/*!
Creates a new circle arc shaped sub-path. The arc center is at \a centerX, \a centerY,
with \a radius, and the arc is drawn from angle \a a0 to \a a1,
and swept in \a direction (ClockWise or CounterClockWise).
When \a connection is \l{PathConnection::}{NotConnected}, arc does not add a line from the previous
path position to the start of the arc.
Angles are specified in radians.
\table
\row
\li \inlineimage qcpainter-arc.webp
{Green pie shape filling three quarters of a circle, leaving one
quadrant empty}
\li
\code
p->beginPath();
p->moveTo(100, 100);
p->arc(100, 100, 80, 0, 1.5 * M_PI);
p->closePath();
p->fill();
p->stroke();
\endcode
\endtable
\note While HTML canvas 2D context uses arc() for painting circles, with
QCanvasPainter it is recommended to use \l circle() or \l ellipse() for those.
*/
void QCanvasPainter::arc(qreal centerX, qreal centerY, qreal radius, qreal a0, qreal a1, PathWinding direction, PathConnection connection)
{
Q_D(QCanvasPainter);
const bool isConnected = connection == PathConnection::Connected;
d->m_e->addArc(float(centerX), float(centerY), float(radius), float(a0), float(a1),
direction, isConnected);
}
/*!
\fn void QCanvasPainter::arc(QPointF centerPoint, qreal radius, qreal a0, qreal a1, PathWinding direction, PathConnection connection)
\overload
Creates a new circle arc shaped sub-path. The arc center is at \a centerPoint,
with \a radius, and the arc is drawn from angle \a a0 to \a a1,
and swept in \a direction (ClockWise or CounterClockWise).
When \a connection is \l{PathConnection::}{NotConnected}, arc does not add a line from the previous
path position to the start of the arc.
Angles are specified in radians.
\note While HTML canvas 2D context uses arc() for painting circles, with
QCanvasPainter it is recommended to use \l circle() or \l ellipse() for those.
*/
/*!
Creates a new rectangle shaped sub-path in position \a x, \a y with
size \a width, \a height.
\table
\row
\li \inlineimage qcpainter-rect.webp
{Filled green square with a dark teal outline}
\li
\code
p->beginPath();
p->rect(20, 20, 160, 160);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::rect(qreal x, qreal y, qreal width, qreal height)
{
Q_D(QCanvasPainter);
d->m_e->addRect(float(x), float(y), float(width), float(height));
}
/*!
\fn void QCanvasPainter::rect(const QRectF &rect)
\overload
Creates a new rectangle shaped sub-path at \a rect.
This is an overloaded method using QRectF.
*/
/*!
Creates a new rounded rectangle shaped sub-path in position \a x, \a y with
size \a width, \a height. Corners rounding will be \a radius.
\table
\row
\li \inlineimage qcpainter-roundrect.webp
{Green rounded square with a dark teal outline}
\li
\code
p->beginPath();
p->roundRect(20, 20, 160, 160, 30);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::roundRect(qreal x, qreal y, qreal width, qreal height, qreal radius)
{
Q_D(QCanvasPainter);
d->m_e->addRoundRect(float(x), float(y), float(width), float(height), float(radius));
}
/*!
\fn void QCanvasPainter::roundRect(const QRectF &rect, qreal radius)
\overload
Creates a new rounded rectangle shaped sub-path at \a rect with \a radius corners.
This is an overloaded method using QRectF.
*/
/*!
\overload
Creates a new rounded rectangle shaped sub-path in position \a x, \a y with
size \a width, \a height. Corners rounding can be varying per-corner, with
\a radiusTopLeft, \a radiusTopRight, \a radiusBottomRight, \a radiusBottomLeft.
\table
\row
\li \inlineimage qcpainter-roundrect2.webp
{Green square with a sharp top-left corner and three rounded corners
of differing radius}
\li
\code
p->beginPath();
p->roundRect(20, 20, 160, 160,
0, 40, 20, 80);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::roundRect(qreal x, qreal y, qreal width, qreal height, qreal radiusTopLeft, qreal radiusTopRight, qreal radiusBottomRight, qreal radiusBottomLeft)
{
Q_D(QCanvasPainter);
d->m_e->addRoundRect(float(x), float(y), float(width), float(height),
float(radiusTopLeft), float(radiusTopRight),
float(radiusBottomRight), float(radiusBottomLeft));
}
/*!
\fn void QCanvasPainter::roundRect(const QRectF &rect, qreal radiusTopLeft, qreal radiusTopRight, qreal radiusBottomRight, qreal radiusBottomLeft)
\overload
Creates a new rounded rectangle shaped sub-path at \a rect. Corners rounding can be
varying per-corner, with \a radiusTopLeft, \a radiusTopRight, \a radiusBottomRight,
\a radiusBottomLeft.
*/
/*!
Creates a new ellipse shaped sub-path centered at ( \a centerX, \a centerY) with \a radiusX and \a radiusY.
\table
\row
\li \inlineimage qcpainter-ellipse.webp
{Wide green ellipse with a dark teal outline}
\li
\code
p->beginPath();
p->ellipse(100, 100, 80, 60);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::ellipse(qreal centerX, qreal centerY, qreal radiusX, qreal radiusY)
{
Q_D(QCanvasPainter);
d->m_e->addEllipse(float(centerX), float(centerY), float(radiusX), float(radiusY));
}
/*!
\fn void QCanvasPainter::ellipse(QPointF centerPoint, qreal radiusX, qreal radiusY)
\overload
Creates a new ellipse shaped sub-path centered at \a centerPoint with \a radiusX and \a radiusY.
*/
/*!
\fn void QCanvasPainter::ellipse(const QRectF &rect)
\overload
Creates a new ellipse shaped sub-path into \a rect.
This ellipse will cover the \a rect area.
\table
\row
\li \inlineimage qcpainter-ellipse2.webp
{Tall ellipse outline fitted inside a filled green rectangle}
\li
\code
QRectF rect(40, 20, 120, 160);
p->fillRect(rect);
p->beginPath();
p->ellipse(rect);
p->stroke();
\endcode
\endtable
*/
/*!
Creates a new circle shaped sub-path centered at ( \a centerX, \a centerY) with \a radius.
\table
\row
\li \inlineimage qcpainter-circle.webp
{Green circle with a dark teal outline}
\li
\code
p->beginPath();
p->circle(100, 100, 80);
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::circle(qreal centerX, qreal centerY, qreal radius)
{
Q_D(QCanvasPainter);
d->m_e->addCircle(float(centerX), float(centerY), float(radius));
}
/*!
\fn void QCanvasPainter::circle(QPointF centerPoint, qreal radius)
\overload
Creates a new circle shaped sub-path centered at \a centerPoint with \a radius.
*/
/*!
Adds \a path into the current path.
\note QCanvasPainter uses NonZero (Qt::WindingFill) fillrule by default,
while QPainterPath defaults to Qt::OddEvenFill. So remember to switch the
fillRule if needed, for example, by using \l setFillRule().
\note This method is available mostly for the compatibility with QPainter
and QPainterPath. It does not increase performance compared to painting
the path directly with QCanvasPainter methods.
*/
void QCanvasPainter::addPath(const QPainterPath &path)
{
Q_D(QCanvasPainter);
d->m_e->addPath(path);
}
/*!
Adds \a path into the current path, optionally using \a transform to
alter the path points. When \a transform is not provided (or it is
identity matrix), this operation is very fast as it reuses the path data.
\table
\row
\li \inlineimage qcpainter-addpath.webp
{Two identical green circles, the second a translated copy of the
first}
\li
\code
// m_path is QCanvasPath
if (m_path.isEmpty())
m_path.circle(60, 60, 40);
p->beginPath();
p->addPath(m_path);
p->addPath(m_path, QTransform::fromTranslate(80, 80));
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::addPath(const QCanvasPath &path, const QTransform &transform)
{
Q_D(QCanvasPainter);
d->m_e->addPath(path, transform);
}
/*!
Adds \a path into the current path, starting from the command at \a start
and including \a count amount of commands. Optionally using \a transform to
alter the path points.
The range of \a start and \a count is checked, so that commands are not
accessed more than \l QCanvasPath::commandsSize().
In case the path shouldn't continue from the current path position, call
first \l moveTo() e.g. with \c{path.positionAt(start - 1)}.
\table
\row
\li \inlineimage qcpainter-addpath2.webp
{Sine wave above a shorter section of the same wave copied below it}
\li
\code
// m_path is QCanvasPath
if (m_path.isEmpty()) {
m_path.moveTo(20, 60);
for (int i = 1; i < 160; i++) {
m_path.lineTo(20 + i,
60 + 20 * sin(0.1 * i));
}
}
p->stroke(m_path);
p->beginPath();
p->addPath(m_path, 20, 100,
QTransform::fromTranslate(0, 80));
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::addPath(const QCanvasPath &path,
qsizetype start, qsizetype count,
const QTransform &transform)
{
Q_D(QCanvasPainter);
d->m_e->addPath(path, start, count, transform);
}
/*!
Sets the current sub-path \a winding to either CounterClockWise (default) or ClockWise.
CounterClockWise draws solid subpaths while ClockWise draws holes.
\table
\row
\li \inlineimage qcpainter-pathwinding.webp
{Green rounded square with a small circle and a rectangle punched out
as holes}
\li
\code
p->beginPath();
p->roundRect(20, 20, 160, 160, 40);
p->setPathWinding(QCanvasPainter::PathWinding::ClockWise);
p->circle(140, 60, 20);
p->rect(60, 120, 80, 30);
p->fill();
p->stroke();
\endcode
\endtable
\sa beginHoleSubPath(), beginSolidSubPath()
*/
void QCanvasPainter::setPathWinding(PathWinding winding)
{
Q_D(QCanvasPainter);
d->m_e->setPathWinding(winding);
}
/*!
Start a solid subpath. This is equivalent to
\c setPathWinding(QCanvasPainter::PathWinding::CounterClockWise))
\sa beginHoleSubPath()
*/
void QCanvasPainter::beginSolidSubPath()
{
Q_D(QCanvasPainter);
d->m_e->setPathWinding(PathWinding::CounterClockWise);
}
/*!
Start a hole subpath. This is equivalent to
\c setPathWinding(QCanvasPainter::PathWinding::ClockWise))
\table
\row
\li \inlineimage qcpainter-beginhole.webp
{Green circle with a square hole that contains a smaller solid green
circle}
\li
\code
p->beginPath();
p->circle(100, 100, 80);
p->beginHoleSubPath();
p->rect(60, 60, 80, 80);
p->beginSolidSubPath();
p->circle(100, 100, 20);
p->fill();
p->stroke();
\endcode
\endtable
\sa beginSolidSubPath()
*/
void QCanvasPainter::beginHoleSubPath()
{
Q_D(QCanvasPainter);
d->m_e->setPathWinding(PathWinding::ClockWise);
}
/*!
Fills the current path with the current fill style, and the current fill rule.
\sa setFillStyle() setFillRule()
\table
\row
\li \inlineimage qcpainter-fill.webp
{Two green bars and a green circle filled together into one combined
shape}
\li
\code
p->beginPath();
p->rect(20, 20, 40, 160);
p->rect(140, 20, 40, 160);
p->circle(100, 100, 60);
p->fill();
\endcode
\endtable
*/
void QCanvasPainter::fill()
{
Q_D(QCanvasPainter);
d->m_e->fill(d->m_e->fillRule());
}
/*!
\overload
\since 6.12
Fills the current path with the current fill style, and fill rule \a fillRule.
\sa setFillStyle(), setFillRule()
*/
void QCanvasPainter::fill(FillRule fillRule)
{
Q_D(QCanvasPainter);
d->m_e->fill(fillRule);
}
/*!
Strokes the current path with the current stroke style.
\sa setStrokeStyle()
\table
\row
\li \inlineimage qcpainter-stroke.webp
{Two vertical bar outlines with an overlapping circle outline}
\li
\code
p->beginPath();
p->rect(20, 20, 40, 160);
p->rect(140, 20, 40, 160);
p->circle(100, 100, 60);
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::stroke()
{
Q_D(QCanvasPainter);
d->m_e->stroke();
}
/*!
\overload
Fills the \a path with the current fill style and fill rule, and belonging
into \a pathGroup. Painting through QCanvasPath is optimal when the path
contains more commands and is mostly static.
When \a pathGroup is \c -1, the path will not be cached on the GPU side.
This is the default. To request caching of path data, pass a
value equal to or greater than \c 0. More information about using path
cache groups can be found in the \l{QCanvasPath} documentation.
Calling beginPath() before this method is not required.
\table
\row
\li \inlineimage qcpainter-fill2.webp
{Stack of filled horizontal bars of varying widths forming an
hourglass outline}
\li
\code
// m_path is QCanvasPath
if (m_path.isEmpty()) {
for (int i = 0; i < 16; i++) {
qreal w = 100 + 60 * sin(i);
m_path.rect(100 - w * 0.5,
22 + i * 10,
w, 6);
}
}
p->fill(m_path);
\endcode
\endtable
\sa setFillStyle(), setFillRule(), removePathGroup()
*/
void QCanvasPainter::fill(const QCanvasPath &path, int pathGroup)
{
Q_D(QCanvasPainter);
d->m_e->fill(path, d->m_e->fillRule(), pathGroup);
}
/*!
\overload
\since 6.12
Fills the \a path with the current fill style and fill rule \a fillRule
*/
void QCanvasPainter::fill(const QCanvasPath &path, FillRule fillRule, int pathGroup)
{
Q_D(QCanvasPainter);
d->m_e->fill(path, fillRule, pathGroup);
}
/*!
\overload
Strokes the \a path with the current stroke style and belonging into \a
pathGroup. Painting through QCanvasPath is optimal when the path
contains more commands and is mostly static.
When \a pathGroup is \c -1, the path's rendering-related data will not be
cached. This is the default. To request caching of path data, pass a
value equal to or greater than \c 0. More information about using path cache
groups can be found in the \l{QCanvasPath} documentation.
Calling beginPath() before this method is not required.
\table
\row
\li \inlineimage qcpainter-stroke2.webp
{Row of narrow vertical bar outlines of varying heights}
\li
\code
// m_path is QCanvasPath
if (m_path.isEmpty()) {
for (int i = 0; i < 16; i++) {
int h = 100 + 60 * sin(i);
m_path.rect(22 + i * 10,
180 - h,
6, h);
}
}
p->stroke(m_path);
\endcode
\endtable
\sa setStrokeStyle(), removePathGroup()
*/
void QCanvasPainter::stroke(const QCanvasPath &path, int pathGroup)
{
Q_D(QCanvasPainter);
d->m_e->stroke(path, pathGroup);
}
/*
\internal
Sets the stencil clip to \a rects, restricting subsequent rendering
to the inside of the rectangles, respecting the current transformation.
If \a rects is empty, stencil clipping is disabled.
If stencil clipping is already active, the new clip region is
intersected with the existing one.
Note that stencil clipping is not anti-aliased.
\sa setClipRect()
*/
void QCanvasPainterPrivate::setStencilClip(const QList<QRectF> &rects)
{
m_e->setStencilClip(rects);
}
/*
\internal
\a clipPath must be a simple path: all sub-paths must be convex
and non-overlapping.
*/
void QCanvasPainterPrivate::setStencilClip(const QVectorPath &clipPath)
{
m_e->setStencilClip(clipPath);
}
// *** Direct drawing ***
/*!
Draws a filled rectangle into the specified position ( \a x, \a y) with size \a width, \a height.
\note This is provided for convenience. When filling more than just a single rect,
prefer using rect().
\table
\row
\li \inlineimage qcpainter-fillrect.webp
{Solid green filled square}
\li
\code
p->fillRect(20, 20, 160, 160);
// The above code does same as:
// p->beginPath();
// p->rect(20, 20, 160, 160);
// p->fill();
\endcode
\endtable
*/
void QCanvasPainter::fillRect(qreal x, qreal y, qreal width, qreal height)
{
Q_D(QCanvasPainter);
d->m_e->beginPath();
d->m_e->addRect(float(x), float(y), float(width), float(height));
d->m_e->fill(FillRule::NonZero);
}
/*!
\fn void QCanvasPainter::fillRect(const QRectF &rect)
\overload
Draws a filled rectangle into \a rect.
This is an overloaded method using QRectF.
\note This is provided for convenience. When filling more than just a single rect,
prefer using rect().
*/
/*!
Erases the pixels in a rectangular area by filling the rectangle
specified by \a x, \a y, \a width, \a height with transparent black.
As clearing does not need blending, it can be faster than fillRect().
\table
\row
\li \inlineimage qcpainter-clearrect.webp
{Green circle with a vertical rectangular notch erased from its top}
\li
\code
p->beginPath();
p->circle(100, 100, 80);
p->fill();
p->stroke();
p->clearRect(60, 0, 80, 120);
\endcode
\endtable
*/
void QCanvasPainter::clearRect(qreal x, qreal y, qreal width, qreal height)
{
Q_D(QCanvasPainter);
d->m_e->beginPath();
d->m_e->addRect(float(x), float(y), float(width), float(height));
d->m_e->fillForClear();
}
/*!
\fn void QCanvasPainter::clearRect(const QRectF &rect)
\overload
Erases the pixels in a rectangular area by filling the rectangle
specified by \a rect with transparent black.
This is an overloaded method using QRectF.
*/
/*!
Draws a stroked rectangle into the specified position ( \a x, \a y) with size \a width, \a height.
\note This is provided for convenience. When stroking more than just a single rect,
prefer using rect().
\table
\row
\li \inlineimage qcpainter-strokerect.webp
{Dark teal square outline}
\li
\code
p->strokeRect(20, 20, 160, 160);
// The above code does same as:
// p->beginPath();
// p->rect(20, 20, 160, 160);
// p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::strokeRect(qreal x, qreal y, qreal width, qreal height)
{
Q_D(QCanvasPainter);
d->m_e->beginPath();
d->m_e->addRect(float(x), float(y), float(width), float(height));
d->m_e->stroke();
}
/*!
\fn void QCanvasPainter::strokeRect(const QRectF &rect)
\overload
Draws a stroked rectangle into \a rect.
This is an overloaded method using QRectF.
\note This is provided for convenience. When stroking more than just a single rect,
prefer using rect().
*/
// *** Shadows ***
/*!
Draws a box \a shadow. The shadow will be painted with
position, size, color, blur etc. defined by \a shadow.
Calling beginPath() before this method is not required.
\note To visually see the area covered by drawBoxShadow(), set
\c QCPAINTER_DEBUG_SHADOW_RECT environment variable.
\table
\row
\li \inlineimage qcpainter-shadowbox.webp
{Green rounded square casting a soft gray box shadow on all sides}
\li
\code
QRectF rect(40, 40, 120, 120);
QRectF shadowRect = rect.translated(-2, 4);
QCanvasBoxShadow shadow(shadowRect);
shadow.setBlur(30);
p->drawBoxShadow(shadow);
p->beginPath();
p->roundRect(rect, 30);
p->setFillStyle("#2CDE85");
p->fill();
\endcode
\endtable
\sa QCanvasBoxShadow
*/
void QCanvasPainter::drawBoxShadow(const QCanvasBoxShadow &shadow)
{
Q_D(QCanvasPainter);
d->drawBoxShadow(this, shadow);
}
// *** Images ***
/*!
Draw \a image into \a x, \a y, at its default size.
\table
\row
\li \inlineimage qcpainter-drawimage.webp
{Green Qt logo drawn at its default size}
\li
\code
static QImage logo(":/qt_logo.png");
QCanvasImage image = p->addImage(logo);
p->drawImage(image, 36, 36);
\endcode
\endtable
\sa addImage()
*/
void QCanvasPainter::drawImage(const QCanvasImage &image, qreal x, qreal y)
{
Q_D(QCanvasPainter);
if (image.isNull())
return;
const float dpr = float(image.devicePixelRatio());
d->m_e->drawImageId(image.id(), float(x), float(y),
image.width() / dpr, image.height() / dpr, image.tintColor());
d->markTextureIdUsed(image.id());
}
/*!
\overload
Draw \a image into \a x, \a y, at given \a width and \a height.
\table
\row
\li \inlineimage qcpainter-drawimage2.webp
{Green Qt logo stretched taller to fit a custom width and height}
\li
\code
static QImage logo(":/qt_logo.png");
QCanvasImage image = p->addImage(logo);
p->drawImage(image, 50, 0, 100, 200);
\endcode
\endtable
\sa addImage()
*/
void QCanvasPainter::drawImage(const QCanvasImage &image, qreal x, qreal y, qreal width, qreal height)
{
Q_D(QCanvasPainter);
if (image.isNull())
return;
d->m_e->drawImageId(image.id(), float(x), float(y), float(width), float(height),
image.tintColor());
d->markTextureIdUsed(image.id());
}
/*!
\fn void QCanvasPainter::drawImage(const QCanvasImage &image, const QRectF &destinationRect)
\overload
Draw \a image into position and size of \a destinationRect.
\sa addImage()
*/
/*!
\overload
Draw \a image into position and size of \a destinationRect, from \a sourceRect area of image.
\table
\row
\li \inlineimage qcpainter-drawimage3.webp
{Enlarged Q letter cropped from a source region of the Qt logo}
\li
\code
static QImage logo(":/qt_logo.png");
QCanvasImage image = p->addImage(logo);
QRectF sourceRect(20, 30, 54, 76);
QRectF destinationRect(0, 0, 200, 200);
p->drawImage(image, sourceRect, destinationRect);
\endcode
\endtable
\sa addImage()
*/
void QCanvasPainter::drawImage(const QCanvasImage &image, const QRectF &sourceRect, const QRectF &destinationRect)
{
Q_D(QCanvasPainter);
if (image.isNull() || sourceRect.width() <= 0 || sourceRect.height() <= 0)
return;
const qreal sx = sourceRect.x();
const qreal sy = sourceRect.y();
const qreal sw = sourceRect.width();
const qreal sh = sourceRect.height();
const qreal dx = destinationRect.x();
const qreal dy = destinationRect.y();
const qreal dw = destinationRect.width();
const qreal dh = destinationRect.height();
const qreal startX = dx - sx * (dw/sw);
const qreal startY = dy - sy * (dh/sh);
const qreal endX = dw * image.width() / sw;
const qreal endY = dh * image.height() / sh;
d->m_e->drawImageIdAt(image.id(), float(startX), float(startY), float(endX), float(endY),
float(dx), float(dy), float(dw), float(dh), image.tintColor());
d->markTextureIdUsed(image.id());
}
// *** Text ***
/*!
Sets the \a font as currently active font.
*/
void QCanvasPainter::setFont(const QFont &font)
{
Q_D(QCanvasPainter);
d->setFont(font);
}
/*!
Sets the horizontal alignment of text to \a align.
The default alignment is \c QCanvasPainter::TextAlign::Start.
\table
\row
\li \inlineimage qcpainter-textalign.webp
{Labels Left, Center, Right, Start, and End positioned around a
vertical reference line}
\li
\code
QFont font("Titillium Web", 22);
p->setFont(font);
p->fillRect(100, 0, 1, 200);
p->setTextAlign(QCanvasPainter::TextAlign::Left);
p->fillText("Left", 100, 40);
p->setTextAlign(QCanvasPainter::TextAlign::Center);
p->fillText("Center", 100, 70);
p->setTextAlign(QCanvasPainter::TextAlign::Right);
p->fillText("Right", 100, 100);
p->setTextAlign(QCanvasPainter::TextAlign::Start);
p->fillText("Start", 100, 130);
p->setTextAlign(QCanvasPainter::TextAlign::End);
p->fillText("End", 100, 160);
\endcode
\endtable
\sa setTextBaseline()
*/
void QCanvasPainter::setTextAlign(QCanvasPainter::TextAlign align)
{
Q_D(QCanvasPainter);
d->m_e->setTextAlignment(align);
}
/*!
Sets the vertical alignment (baseline) of text to \a baseline.
The default alignment is \c QCanvasPainter::TextBaseline::Alphabetic.
\table
\row
\li \inlineimage qcpainter-textbaseline.webp
{Labels Bottom, Middle, Top, Alphabetic, and Hanging aligned to
horizontal baselines}
\li
\code
QFont font("Titillium Web", 16);
p->setFont(font);
p->fillRect(0, 60, 200, 1);
p->fillRect(0, 140, 200, 1);
p->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
p->fillText("Bottom", 40, 60);
p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
p->fillText("Middle", 100, 60);
p->setTextBaseline(QCanvasPainter::TextBaseline::Top);
p->fillText("Top", 160, 60);
p->setTextBaseline(QCanvasPainter::TextBaseline::Alphabetic);
p->fillText("Alphabetic", 50, 140);
p->setTextBaseline(QCanvasPainter::TextBaseline::Hanging);
p->fillText("Hanging", 150, 140);
\endcode
\endtable
\sa setTextAlign()
*/
void QCanvasPainter::setTextBaseline(QCanvasPainter::TextBaseline baseline)
{
Q_D(QCanvasPainter);
d->m_e->setTextBaseline(baseline);
}
/*!
Sets the direction of text to \a direction.
The default direction is \c QCanvasPainter::TextDirection::Inherit.
\table
\row
\li \inlineimage qcpainter-textdirection.webp
\li
\code
p->setTextAlign(QCanvasPainter::TextAlign::Center);
p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
QFont font("Titillium Web", 32);
p->setFont(font);
p->setFillStyle(QColorConstants::Black);
p->fillText("Hi!", 100, 50);
p->setTextDirection(QCanvasPainter::TextDirection::RightToLeft);
p->fillText("Hi!", 100, 150);
\endcode
\endtable
*/
void QCanvasPainter::setTextDirection(QCanvasPainter::TextDirection direction)
{
Q_D(QCanvasPainter);
d->m_e->setTextDirection(direction);
}
/*!
Sets the text wrap mode to \a wrapMode.
The default wrap mode is \c QCanvasPainter::WrapMode::NoWrap.
\table
\row
\li \inlineimage qcpainter-textwrapmode.webp
{Same string in three boxes showing no wrap, word wrap, and wrap
anywhere}
\li
\code
QRectF r1(50, 5, 100, 60);
QRectF r2(50, 70, 100, 60);
QRectF r3(50, 135, 100, 60);
p->strokeRect(r1);
p->strokeRect(r2);
p->strokeRect(r3);
QString s("This is a long string.");
p->setTextWrapMode(QCanvasPainter::WrapMode::NoWrap);
p->fillText(s, r1);
p->setTextWrapMode(QCanvasPainter::WrapMode::Wrap);
p->fillText(s, r2);
p->setTextWrapMode(QCanvasPainter::WrapMode::WrapAnywhere);
p->fillText(s, r3);
\endcode
\endtable
*/
void QCanvasPainter::setTextWrapMode(QCanvasPainter::WrapMode wrapMode)
{
Q_D(QCanvasPainter);
d->m_e->setTextWrapMode(wrapMode);
}
/*!
Sets the line height adjustment in pixels to \a height
for wrapped text. The default line height is \c 0.
\table
\row
\li \inlineimage qcpainter-textlineheight.webp
{Two-line text in three boxes showing line height of -10, 0, and 10
pixels}
\li
\code
QRectF r1(40, 5, 120, 60);
QRectF r2(40, 70, 120, 60);
QRectF r3(40, 135, 120, 60);
p->strokeRect(r1);
p->strokeRect(r2);
p->strokeRect(r3);
p->setTextLineHeight(-10);
p->fillText("Text with line height: -10", r1);
p->setTextLineHeight(0);
p->fillText("Text with line height: 0", r2);
p->setTextLineHeight(10);
p->fillText("Text with line height: 10", r3);
\endcode
\endtable
*/
void QCanvasPainter::setTextLineHeight(qreal height)
{
Q_D(QCanvasPainter);
d->m_e->setTextLineHeight(float(height));
}
/*!
Sets the current text antialiasing amount. The value \a antialias
is a multiplier to normal antialiasing, meaning that \c 0.0 disables
antialiasing and 2.0 doubles it. The default value is 1.0.
\note Due to the text antialiasing technique used (SDF),
the maximum antialiasing amount is quite limited and this
affects less when the font size is small.
\table
\row
\li \inlineimage qcpainter-textantialias.webp
{Lines labeled Antialiasing 1.0 to 4.0 growing progressively softer
and blurrier}
\li
\code
QFont font("Titillium Web", 20);
p->setFont(font);
p->setTextAntialias(1.0);
p->fillText("Antialiasing: 1.0", 100, 25);
p->setTextAntialias(2.0);
p->fillText("Antialiasing: 2.0", 100, 75);
p->setTextAntialias(3.0);
p->fillText("Antialiasing: 3.0", 100, 125);
p->setTextAntialias(4.0);
p->fillText("Antialiasing: 4.0", 100, 175);
\endcode
\endtable
*/
void QCanvasPainter::setTextAntialias(qreal antialias)
{
Q_D(QCanvasPainter);
d->m_e->setTextAntialias(float(antialias));
}
/*!
Draws \a text string at the specified location ( \a x, \a y), with
current textAlign and textBaseline. To make the text wrap into multiple
lines, set the text wrap mode using \l setTextWrapMode() to something
else than the default \c NoWrap. Optional \a maxWidth parameter
defaults to \c 0. Meaning that when e.g. \c WordWrap has been set,
text is wrapped on every word. Set the \a maxWidth parameter to
preferred row width in pixels.
\table
\row
\li \inlineimage qcpainter-filltext.webp
\li
\code
p->setTextAlign(QCanvasPainter::TextAlign::Center);
p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
QString text("Qt Canvas Painter");
p->fillText(text, 100, 30);
p->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
p->fillText(text, 100, 90);
p->fillText(text, 100, 160, 120);
\endcode
\endtable
\sa setTextAlign(), setTextBaseline(), setTextWrapMode()
*/
void QCanvasPainter::fillText(const QString &text, qreal x, qreal y, qreal maxWidth)
{
Q_D(QCanvasPainter);
d->fillText(text, x, y, maxWidth);
}
/*!
\fn void QCanvasPainter::fillText(const QString &text, QPointF point, qreal maxWidth)
\overload
Draws \a text string at the specified \a point, with
current textAlign and textBaseline. To make the text wrap into multiple
lines, set the text wrap mode using \l setTextWrapMode() to something
else than the default \c NoWrap. Optional \a maxWidth parameter
defaults to \c 0. Meaning that when e.g. \c WordWrap has been set,
text is wrapped on every word. Set the \a maxWidth parameter to
preferred row width in pixels.
This is an overloaded method using QPointF.
\sa setTextAlign(), setTextBaseline(), setTextWrapMode()
*/
/*!
\overload
Draws \a text string inside \a rect, with current textAlign and textBaseline.
Width of the rect parameter is used as maxWidth.
This is an overloaded method using QRectF. It is often useful to set the
text baseline to \l TextBaseline::Top or \l TextBaseline::Middle when painting
text with this method.
\table
\row
\li \inlineimage qcpainter-filltext2.webp
\li
\code
p->setTextAlign(QCanvasPainter::TextAlign::Center);
p->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
QString text("Qt Canvas Painter");
QRectF rect1(10, 20, 180, 50);
p->strokeRect(rect1);
p->fillText(text, rect1);
p->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
QRectF rect2(60, 90, 80, 90);
p->strokeRect(rect2);
p->fillText(text, rect2);
\endcode
\endtable
\sa setTextAlign(), setTextBaseline(), setTextWrapMode()
*/
void QCanvasPainter::fillText(const QString &text, const QRectF &rect)
{
Q_D(QCanvasPainter);
d->fillText(text, rect);
}
/*!
Measures bounding box of a \a text string at (\a x, \a y).
To measure multi-line text, set the text wrap mode using \l setTextWrapMode()
to something else than the default \c NoWrap. Also set the optional \a maxWidth
parameter to preferred row width in pixels.
Returns QRectF with values [xmin, ymin, width, height].
Measured values are returned in local coordinate space.
\table
\row
\li \inlineimage qcpainter-textboundingbox.webp
{Text Built with Qt shown on one line and wrapped to two, each in its
measured bounding box}
\li
\code
QString s("Built with Qt");
QPointF pos1(20, 20);
QRectF box1 = p->textBoundingBox(s, pos1);
p->strokeRect(box1);
p->fillText(s, pos1);
p->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
p->setTextAlign(QCanvasPainter::TextAlign::Center);
QPointF pos2(100, 80);
QRectF box2 = p->textBoundingBox(s, pos2, 100);
p->strokeRect(box2);
p->fillText(s, pos2, 100);
\endcode
\endtable
*/
QRectF QCanvasPainter::textBoundingBox(const QString &text, qreal x, qreal y, qreal maxWidth) const
{
Q_D(const QCanvasPainter);
return d->textBoundingBox(text, x, y, maxWidth);
}
/*!
\fn QRectF QCanvasPainter::textBoundingBox(const QString &text, QPointF point, qreal maxWidth) const
\overload
Measures bounding box of a \a text string at \a point.
To measure multi-line text, set the text wrap mode using \l setTextWrapMode()
to something else than the default \c NoWrap. Also set the optional \a maxWidth
parameter to preferred row width in pixels.
Returns QRectF with values [xmin, ymin, width, height].
Measured values are returned in local coordinate space.
*/
/*!
\overload
Measures bounding box of a \a text string at \a rect.
Returns QRectF with values [xmin, ymin, width, height].
Measured values are returned in local coordinate space.
*/
QRectF QCanvasPainter::textBoundingBox(const QString &text, const QRectF &rect) const
{
Q_D(const QCanvasPainter);
return d->textBoundingBox(text, rect);
}
/*!
Sets the current antialiasing amount to \a antialias in pixels.
More antialias means smoother painting. This only affects fill and stroke painting,
not images or texts.
The default value is \c 1.0 and the maximum value is \c 10.0.
Antialiasing can be modified per-path so it can be set before each stroke/fill.
To disable antialiasing and reduce the rendering cost, set the antialias to \c 0.
\table
\row
\li \inlineimage qcpainter-antialias.webp
{Stack of wavy lines growing progressively softer and blurrier with
increasing antialiasing}
\li
\code
p->setLineWidth(6);
for (int i = 1; i < 10 ; i++) {
int y = i * 20;
p->setAntialias(i);
p->beginPath();
p->moveTo(20, y);
p->bezierCurveTo(80, y + 20, 120,
y - 20, 180, y);
p->stroke();
}
\endcode
\endtable
\sa setTextAntialias()
*/
void QCanvasPainter::setAntialias(qreal antialias)
{
Q_D(QCanvasPainter);
d->m_e->setAntialias(float(antialias));
}
/*!
Sets the winding enforcing to \a enabled.
Setting this to \c false disables enforcing
of path winding to match what has been set into setPathWinding().
Disabling allows e.g. creating holes into paths by adding the points
in clock wise order. Disabling can also increase the performance.
The default value is \c true.
\table
\row
\li \inlineimage qcpainter-pathwinding2.webp
\li
\code
p->setWindingEnforce(false);
p->beginPath();
// Outer shape, counterclockwise
p->moveTo(20, 20);
p->lineTo(100, 180);
p->lineTo(180, 20);
p->closePath();
// Inner shape, clockwise
p->moveTo(100, 40);
p->lineTo(125, 90);
p->lineTo(75, 90);
p->closePath();
p->fill();
p->stroke();
\endcode
\endtable
*/
void QCanvasPainter::setWindingEnforce(bool enabled)
{
Q_D(QCanvasPainter);
d->m_e->setWindingEnforceEnabled(enabled);
}
/*!
Sets the high quality stroking to \a enabled.
Setting this to \c true gives a more correct rendering in some less common
cases where stroking overlaps and doesn't have full opacity. Enabling it
results into higher rendering cost. The default value is \c false.
\table
\row
\li \inlineimage qcpainter-highqualitystroking.webp
\li
\code
// Increase the line width and reduce opacity to get
// the stroking flaws visible.
p->setLineWidth(20);
p->setGlobalAlpha(0.5);
if (m_path.isEmpty()) {
m_path.moveTo(30, 30);
m_path.lineTo(40, 30);
m_path.lineTo(80, 170);
m_path.lineTo(80, 30);
m_path.lineTo(70, 30);
}
p->setHighQualityStroking(true);
p->stroke(m_path);
p->setHighQualityStroking(false);
p->translate(90, 0);
p->stroke(m_path);
\endcode
\endtable
*/
void QCanvasPainter::setHighQualityStroking(bool enabled)
{
Q_D(QCanvasPainter);
d->m_e->setHighQualityStrokingEnabled(enabled);
}
/*!
Adds \a image with \a flags available for the painter as a texture.
Returns QCanvasImage with the texture id and other information about the image.
Returned QCanvasImage can then be used with \l drawImage and \l QCanvasImagePattern.
After calling this method, the \a image QImage does not need to be kept in memory.
Calling with the same \a image is a cheap operation, since a cache hit is
expected.
Care must be taken when optimizing to call addImage() only once. That is not
always sufficient, depending on the application design. For example, if the
underlying graphics resources are lost, e.g. because the painter is
associated with a new QRhi under the hood due to moving a widget to a new
top-level, then calling this function is essential in order to re-create the
native graphics textures from \a image.
\sa drawImage(), removeImage()
*/
QCanvasImage QCanvasPainter::addImage(const QImage &image, QCanvasPainter::ImageFlags flags)
{
Q_D(QCanvasPainter);
return d->getQCanvasImage(image, flags);
}
/*!
\overload
Adds \a texture with \a flags available for the painter as a texture.
The returned QCanvasImage can be used with \l drawImage and \l QCanvasImagePattern.
\note The ownership of \a texture is \b not taken.
\sa drawImage(), removeImage()
*/
QCanvasImage QCanvasPainter::addImage(QRhiTexture *texture, QCanvasPainter::ImageFlags flags)
{
Q_D(QCanvasPainter);
return d->getQCanvasImage(texture, flags);
}
/*!
\overload
Registers \a canvas with \a flags to the painter so that it is available as an image.
The returned QCanvasImage can be used with \l drawImage and \l QCanvasImagePattern.
\note \a canvas continues to manage the underlying native graphics
resources, meaning removeImage() does not render \a canvas invalid.
\sa drawImage(), removeImage()
*/
QCanvasImage QCanvasPainter::addImage(const QCanvasOffscreenCanvas &canvas, QCanvasPainter::ImageFlags flags)
{
Q_D(QCanvasPainter);
return d->getQCanvasImage(canvas, flags);
}
/*!
Releases the resources associated with \a image and removes the image from
the painter.
\note This does not need to be normally called, because resources such as
textures are released in the painter destructor anyway. This function is
useful when there is a need to reduce memory usage due to having a lot
images, and some of them are not used anymore.
\note Removed images can not be used in paint operations anymore.
\note Resources such as the textures created with the underlying 3D API may
not get released immediately. Such operations may get deferred to subsequent
frames, typically when this QCanvasPainter begins painting again after the active
set of draw calls has been submitted.
\sa addImage()
*/
void QCanvasPainter::removeImage(const QCanvasImage &image)
{
Q_D(QCanvasPainter);
if (image.isNull())
return;
const int id = image.id();
switch (QCanvasImagePrivate::get(&image)->type) {
case QCanvasImagePrivate::DataType::GradientTextureFromImage:
case QCanvasImagePrivate::DataType::TextureFromImage:
d->m_imageTracker.removeTextureId(id);
break;
case QCanvasImagePrivate::DataType::ImportedTexture:
d->m_nativeTextureCache.removeIf([id](auto it) { return it.value().id() == id; });
d->m_pendingNativeTextureDelete.insert(id);
break;
case QCanvasImagePrivate::DataType::Unknown:
Q_UNREACHABLE();
break;
}
}
/*!
Schedules dropping unused textures from the cache.
Additionally, other caches and pools may get shrunk upon
calling this function, in order to minimize memory usage.
This may potentially lead to more expensive drawing calls
afterwards.
*/
void QCanvasPainter::cleanupResources()
{
Q_D(QCanvasPainter);
d->m_imageTracker.removeTemporaryResources();
d->m_e->releaseUnusedResources();
}
/*!
\return an approximation in kilobytes of the memory used by the image
(pixel) data for all active QCanvasImage instances for this painter that were
created by the addImage() overload taking a QImage. It also includes the
data from internally created images for \l{QCanvasGradient}{gradients}.
QCanvasPainter does not keep copies of the CPU-side QImage data once
addImage() has returned. Therefore, the result of this function is an
approximation of the GPU memory that is used for textures.
\note The value is only an estimate based on the image format and
dimensions. Qt has no knowledge of how the data for textures is stored and
laid out in memory on the GPU side.
Offscreen canvases and externally managed textures registered via the other
addImage() overloads are not taken into account by this function.
For every valid QCanvasImage, the individual size in bytes can always be queried
by calling \l{QCanvasImage::}{sizeInBytes()}. That function returns valid results also
when the QCanvasImage was created from a QCanvasOffscreenCanvas or QRhiTexture, but it
does not consider mipmap or multisample data.
\sa activeImageCount(), addImage(), removeImage()
*/
qsizetype QCanvasPainter::activeImageMemoryUsage() const
{
Q_D(const QCanvasPainter);
return d->m_imageTracker.dataAmount() / 1000;
}
/*!
\return the number of active QCanvasImage objects registered with this QCanvasPainter.
This also includes the internally created images for
\l{QCanvasGradient}{gradients}.
QCanvasImage objects created by registering QCanvasOffscreenCanvas instances or
externally managed textures are not taken into account by this function.
\sa activeImageMemoryUsage(), addImage(), removeImage()
*/
qsizetype QCanvasPainter::activeImageCount() const
{
Q_D(const QCanvasPainter);
return d->m_imageTracker.size();
}
/*!
Removes \a pathGroup from the painter cache. Calling fill() or stroke()
for \a pathGroup after this, will regenerate the path into the group cache.
\note This does not need to be normally called as paths are removed
in the painter destructor. Only use this to reduce memory
usage when \a pathGroup is not needed anymore or e.g. when the path
has a lot less commands that it has had in the past and buffer size
should be reduced.
\sa fill(), stroke()
*/
void QCanvasPainter::removePathGroup(int pathGroup)
{
Q_D(QCanvasPainter);
d->m_e->removePathGroup(pathGroup);
}
// ***** Private *****
// Marks all unused temporary textures to be removed.
void QCanvasImageTracker::removeTemporaryResources()
{
m_doingResourcesRemoval = true;
}
// Marks a texture with imageId to be removed
void QCanvasImageTracker::removeTextureId(int imageId)
{
m_cleanupTextures.insert(imageId);
}
// Handles texture removal from GPU and from the cache
void QCanvasImageTracker::handleRemoveTextures()
{
Q_ASSERT(m_painterPrivate);
if (m_cleanupTextures.isEmpty() && !m_doingResourcesRemoval) {
// Nothing to be done, reset the used textures list.
m_usedTextureIDs.clear();
return;
}
auto it = m_data.begin();
while (it != m_data.end()) {
QCanvasImagePrivate *ip = QCanvasImagePrivate::get(&*it);
bool remove = m_cleanupTextures.contains(ip->id);
bool removeUnusedGradients = m_doingResourcesRemoval &&
ip->type == QCanvasImagePrivate::DataType::GradientTextureFromImage &&
!m_usedTextureIDs.contains(ip->id);
if (remove || removeUnusedGradients) {
m_painterPrivate->m_e->deleteImage(ip->id);
m_dataAmount -= ip->sizeInBytes;
it = m_data.erase(it);
} else {
++it;
}
}
// Cleanups done, so reset
m_cleanupTextures.clear();
m_usedTextureIDs.clear();
m_doingResourcesRemoval = false;
}
void QCanvasImageTracker::clear()
{
m_data.clear();
m_cleanupTextures.clear();
m_usedTextureIDs.clear();
m_dataAmount = 0;
m_doingResourcesRemoval = false;
}
// Marks imageId as being currently used.
void QCanvasImageTracker::markTextureIdUsed(int imageId)
{
if (imageId > 0)
m_usedTextureIDs.insert(imageId);
}
QCanvasPainterPrivate::QCanvasPainterPrivate()
{
m_imageTracker.m_painterPrivate = this;
m_e = std::make_unique<QCPainterEngine>();
const int defaultMaxTextures = 1024;
static int maxTexturesEnv = qEnvironmentVariableIntValue("QCPAINTER_MAX_TEXTURES");
m_maxTextures = maxTexturesEnv > 0 ? maxTexturesEnv : defaultMaxTextures;
static const bool trackingDisabledEnv = qEnvironmentVariableIsSet("QCPAINTER_DISABLE_TEXTURE_USAGE_TRACKING");
m_trackingDisabled = trackingDisabledEnv;
}
QCanvasPainterPrivate::~QCanvasPainterPrivate()
{
// Cleanup possible pending textures
handleCleanupTextures();
}
// Return aligned QRectF.
// Note: textAlignment must be an effective alignment (Left, Center or Right).
static QRectF textAlignedRectFromPoint(QCanvasPainter::TextAlign textAlignment, qreal x, qreal y, qreal maxWidth)
{
QRectF rect;
if (textAlignment == QCanvasPainter::TextAlign::Left) {
rect.setRect(x, y, maxWidth, 0);
} else if (textAlignment == QCanvasPainter::TextAlign::Center) {
const qreal hWidth = maxWidth * 0.5;
rect.setRect(x - hWidth, y, maxWidth, 0);
} else {
rect.setRect(x - maxWidth, y, maxWidth, 0);
}
return rect;
}
// Delete the textures marked to be cleaned.
// This is also automatically called by engine to keep the amount
// of (gradient) textures in control.
void QCanvasPainterPrivate::handleCleanupTextures()
{
if (m_renderer && m_renderer->ctx) {
m_imageTracker.handleRemoveTextures();
for (int id : std::as_const(m_pendingNativeTextureDelete))
m_e->deleteImage(id); // does not destroy the actual QRhiTexture since it is not owned
m_pendingNativeTextureDelete.clear();
}
}
// Removes all data from m_imageTracker, does not destroy the actual textures. To
// be called when the renderer is going away (and so all rhi textures are going
// to be deleted by it).
void QCanvasPainterPrivate::clearTextureCache()
{
m_imageTracker.clear();
m_nativeTextureCache.clear(); // these we do not own anyway
}
// Marks that this imageId was used during the paint operation.
void QCanvasPainterPrivate::markTextureIdUsed(int imageId) {
if (!m_trackingDisabled)
m_imageTracker.markTextureIdUsed(imageId);
}
qint64 QCanvasPainterPrivate::generateImageKey(const QImage &image, QCanvasPainter::ImageFlags flags) const
{
// Reserve the low byte for the flags (all ImageFlags fit in 8 bits).
return (image.cacheKey() << 8) | (int(flags) & 0xFF);
}
// Get the texture id for the image. Id will be unique for the image+flags, or imageKey
// in case that is provided.
QCanvasImage QCanvasPainterPrivate::getQCanvasImage(const QImage &image, QCanvasPainter::ImageFlags flags, qint64 imageKey)
{
Q_ASSERT(m_renderer);
QCanvasImage qcimage;
qint64 key = imageKey;
// the imageKey is 0 except when coming from QCanvasGradient
QCanvasImagePrivate::DataType type = QCanvasImagePrivate::DataType::GradientTextureFromImage;
if (imageKey == 0) {
key = generateImageKey(image, flags);
type = QCanvasImagePrivate::DataType::TextureFromImage;
}
qcimage = m_imageTracker.image(key);
if (qcimage.isNull()) {
// Image is not in cache
if (image.isNull()) {
qCWarning(QC_INFO, "Cannot add an empty (null) image");
} else {
QImage convertedImage;
if (image.format() != QImage::Format_RGBA8888 && image.format() != QImage::Format_RGBA8888_Premultiplied) {
qCDebug(QC_INFO) << "Converting image" << key << "from" << image.format() << "to" << QImage::Format_RGBA8888_Premultiplied;
convertedImage = image.convertedTo(QImage::Format_RGBA8888_Premultiplied);
} else {
convertedImage = image;
}
if (convertedImage.format() == QImage::Format_RGBA8888_Premultiplied)
flags |= QCanvasPainter::ImageFlag::Premultiplied;
else
flags &= ~int(QCanvasPainter::ImageFlag::Premultiplied);
QCanvasImagePrivate *ip = QCanvasImagePrivate::get(&qcimage);
ip->id = m_e->createImage(convertedImage.width(), convertedImage.height(), flags, convertedImage.constBits());
ip->width = convertedImage.width();
ip->height = convertedImage.height();
ip->devicePixelRatio = convertedImage.devicePixelRatio();
ip->sizeInBytes = convertedImage.sizeInBytes();
ip->type = type;
m_imageTracker.insert(key, qcimage);
// When the amount of cache reaches the limit and tracking is enabled,
// remove all the unused temporary textures automatically.
if (!m_trackingDisabled && m_imageTracker.size() > m_maxTextures) {
qCDebug(QC_INFO) << "Removing temporary gradient textures as max amount of" << m_maxTextures << "was reached.";
m_imageTracker.removeTemporaryResources();
}
}
}
return qcimage;
}
QCanvasImage QCanvasPainterPrivate::getQCanvasImage(QRhiTexture *texture, QCanvasPainter::ImageFlags flags)
{
Q_ASSERT(m_renderer);
if (!texture)
return {};
QCanvasImage qcimage;
const quint64 key = texture->globalResourceId();
const auto cached = m_nativeTextureCache.constFind(key);
if (cached != m_nativeTextureCache.cend()) {
qcimage = cached.value();
QCanvasImagePrivate *ip = QCanvasImagePrivate::get(&qcimage);
if (ip->width != texture->pixelSize().width() || ip->height != texture->pixelSize().height())
qcimage = {};
}
if (qcimage.isNull()) {
QCanvasImagePrivate *ip = QCanvasImagePrivate::get(&qcimage);
ip->id = m_renderer->renderCreateNativeTexture(texture, flags)->id;
ip->width = texture->pixelSize().width();
ip->height = texture->pixelSize().height();
ip->devicePixelRatio = 1.0f;
quint32 byteSize = 0;
QCPainterRhiRenderer::textureFormatInfo(texture->format(), texture->pixelSize(), nullptr, &byteSize, nullptr);
ip->sizeInBytes = byteSize;
ip->type = QCanvasImagePrivate::DataType::ImportedTexture;
m_nativeTextureCache.insert(key, qcimage);
}
return qcimage;
}
QCanvasImage QCanvasPainterPrivate::getQCanvasImage(const QCanvasOffscreenCanvas &canvas, QCanvasPainter::ImageFlags flags)
{
Q_ASSERT(m_renderer);
if (canvas.isNull())
return {};
if (m_renderer->isOffscreenCanvasYUp())
flags.setFlag(QCanvasPainter::ImageFlag::FlipY, !flags.testFlag(QCanvasPainter::ImageFlag::FlipY));
// From the QCanvasImage and painter perspective an offscreen canvas is no
// different from a QDCImage created directly from a QRhiTexture.
return getQCanvasImage(canvas.texture(), flags);
}
void QCanvasPainterPrivate::setFont(const QFont &font)
{
m_e->state.font = font;
}
void QCanvasPainterPrivate::fillText(const QString &text, qreal x, qreal y, qreal maxWidth)
{
// Unify point & rect APIs behavior.
const QRectF rect = textAlignedRectFromPoint(m_e->effectiveTextAlign(text), x, y, maxWidth);
fillText(text, rect);
}
void QCanvasPainterPrivate::fillText(const QString &text, const QRectF &rect)
{
m_e->fillText(text, rect);
}
void QCanvasPainterPrivate::fillShapedText(QFontEngine *fontEngine, const quint32 *glyphIndexes,
const QFixedPoint *glyphPositions, int glyphCount)
{
m_e->fillShapedText(fontEngine, glyphIndexes, glyphPositions, glyphCount);
}
QRectF QCanvasPainterPrivate::textBoundingBox(const QString &text, qreal x, qreal y, qreal maxWidth) const
{
// Unify point & rect APIs behavior.
const QRectF rect = textAlignedRectFromPoint(m_e->effectiveTextAlign(text), x, y, maxWidth);
return m_e->textBoundingBox(text, rect);
}
QRectF QCanvasPainterPrivate::textBoundingBox(const QString &text, const QRectF &rect) const
{
return m_e->textBoundingBox(text, rect);
}
void QCanvasPainterPrivate::drawBoxShadow(QCanvasPainter *painter, const QCanvasBoxShadow &shadow)
{
QCanvasBrush shadowBrush = shadow;
const auto paint = paintFromBrush(shadowBrush, painter);
const auto r = shadow.boundingRect();
m_e->fillPlainRect(paint, r.x(), r.y(), r.width(), r.height());
static bool shadowRectDebug = qEnvironmentVariableIsSet("QCPAINTER_DEBUG_SHADOW_RECT");
if (Q_UNLIKELY(shadowRectDebug)) {
m_e->save();
m_e->setStrokeColor(QColorConstants::Red);
m_e->setLineWidth(1.0f);
painter->strokeRect(r);
m_e->restore();
}
}
void QCanvasPainterPrivate::setAntialiasingEnabled(bool enabled)
{
m_e->setAntialiasingEnabled(enabled);
}
/*!
\return a new offscreen canvas with the given \a pixelSize, \a sampleCount,
and \a flags.
The size of the canvas is specified in pixels. The \a pixelSize, \a
sampleCount, and \a flags properties are immutable afterwards. To get a
canvas with a different size, sample count, or flags, create a new one.
To target an offscreen canvas with with draw commands, call the appropriate
\l QCanvasRhiPaintDriver::beginPaint() overload when working with the lower level
API, or \l QCanvasPainterWidget::beginCanvasPainting() or \l
QCanvasPainterItemRenderer::beginCanvasPainting() when using the convenience
widget or Qt Quick item classes.
Normally the contents of the canvas is cleared when painting to it. To
disable this, pass \l{QCanvasOffscreenCanvas::Flag::}{PreserveContents} in \a flags.
To request multisample rendering onto the canvas (multisample antialiasing,
MSAA), set a sample count larger than 1, such as 4 or 8. Preserving the
canvas contents between render passes is not supported however when
multisampling is enabled, and the \l{QCanvasOffscreenCanvas::Flag::}{PreserveContents} flag will not
work in this case.
\note An offscreen canvas is backed by a texture independent from the main
render target, and rendering to it always happens with a scale factor
(device pixel ratio) of 1. For example, to create a canvas matching the true
rendering dimensions of a QCanvasPainterWidget, pass \c{size() *
devicePixelRatio()} as \a pixelSize, because the \c size() of the widget is
just the logical size without the scale factor included.
*/
QCanvasOffscreenCanvas QCanvasPainter::createCanvas(QSize pixelSize, int sampleCount, QCanvasOffscreenCanvas::Flags flags)
{
Q_D(QCanvasPainter);
if (!d->m_renderer || !d->m_renderer->ctx) {
qWarning("createCanvas() cannot be called without renderer");
return {};
}
return d->m_renderer->createCanvas(pixelSize, sampleCount, flags);
}
/*!
Destroys the resources backing \a canvas. \a canvas becomes a
\l{QCanvasOffscreenCanvas::isNull()}{null canvas} then.
The painter automatically does this upon its destruction. Therefore, calling
this function is only necessary when releasing the associated resources is
desired right away.
*/
void QCanvasPainter::destroyCanvas(QCanvasOffscreenCanvas &canvas)
{
Q_D(QCanvasPainter);
if (!d->m_renderer || !d->m_renderer->ctx) {
qWarning("destroyCanvas() cannot be called without renderer");
return;
}
d->m_renderer->destroyCanvas(canvas);
}
QT_END_NAMESPACE