Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "canvaspainting_cpptests.h"
#include <QMetaObject>
#include <QMetaMethod>
#include <QDebug>
#include <QImage>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonValue>
#include <QtGui/private/qvectorpath_p.h>
#include <QtGui/private/qpainterpath_p.h>
#include <private/qcanvaspainter_p.h>
#include <QCanvasLinearGradient>
#include <QCanvasRadialGradient>
#include <QCanvasConicalGradient>
#include <QCanvasBoxGradient>
#include <QCanvasImagePattern>
#include <QCanvasGridPattern>
#include <QCanvasBoxShadow>
#include <QCanvasPath>
#include <QCanvasCustomBrush>
#include <QFontDatabase>
#include <QScopeGuard>
#include <QVector4D>
QStringList CanvasPainterLancelotCppTests::keys()
{
QStringList result;
const QMetaObject *o = metaObject();
for (int i = o->methodOffset(); i < o->methodCount(); ++i)
result.append(QString::fromLatin1(o->method(i).methodSignature()).replace("()", ""));
return result;
}
void CanvasPainterLancelotCppTests::run(const QString &key, QCanvasPainter *painter, const QSize &canvasPixelSize)
{
this->painter = painter;
this->canvasPixelSize = canvasPixelSize;
const QString methodName = key + QLatin1String("()");
const QMetaObject *o = metaObject();
for (int i = o->methodOffset(); i < o->methodCount(); ++i) {
if (methodName == QString::fromLatin1(o->method(i).methodSignature()))
o->method(i).invoke(this);
}
}
void CanvasPainterLancelotCppTests::simpleDrawing()
{
float size = std::min(width(), height());
QPointF center(width()/2, height()/2);
// Paint the background circle
QCanvasRadialGradient gradient1(center.x(), center.y() - size*0.1, 0, size*0.6);
gradient1.setStartColor("#909090");
gradient1.setEndColor("#404040");
painter->beginPath();
painter->circle(center, size*0.46);
painter->setFillStyle(gradient1);
painter->fill();
painter->setStrokeStyle("#202020");
painter->setLineWidth(size*0.02);
painter->stroke();
// Text line 1
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
QFont font1;
font1.setWeight(QFont::Weight::Bold);
font1.setItalic(true);
font1.setPixelSize(size*0.07);
painter->setFont(font1);
painter->setFillStyle("#B0D040");
painter->fillText("Hello", center.x(), center.y() - size*0.18);
// Text line 2
QFont font2;
font2.setWeight(QFont::Weight::Thin);
font2.setPixelSize(size*0.07);
painter->setFont(font2);
painter->fillText(QLatin1String("Lancelot"), center.x(), center.y() - size*0.08);
// Paint heart
painter->setFillStyle(Qt::green);
painter->setLineCap(QCanvasPainter::LineCap::Round);
painter->setStrokeStyle("#B0D040");
painter->beginPath();
painter->moveTo(center.x(), center.y() + size*0.3);
painter->bezierCurveTo(center.x() - size*0.25, center.y() + size*0.1,
center.x() - size*0.05, center.y() + size*0.05,
center.x(), center.y() + size*0.15);
painter->bezierCurveTo(center.x() + size*0.05, center.y() + size*0.05,
center.x() + size*0.25, center.y() + size*0.1,
center.x(), center.y() + size*0.3);
painter->stroke();
painter->fill();
}
void CanvasPainterLancelotCppTests::testPathWindingUncachedPath()
{
painter->setLineWidth(4);
painter->setLineJoin(QCanvasPainter::LineJoin::Bevel);
painter->fillText("CW, Default, set DisableWindingEnforce, clear DisableWindingEnforce", 10, 10);
QCanvasPath p;
p.moveTo(20, 20);
p.lineTo(100, 180);
p.lineTo(180, 20);
p.closePath();
p.moveTo(100, 40);
p.lineTo(125, 90);
p.lineTo(75, 90);
p.closePath();
QCanvasPath p2;
p2.moveTo(20, 20);
p2.lineTo(100, 180);
p2.lineTo(180, 20);
p2.closePath();
p2.setPathWinding(QCanvasPainter::PathWinding::ClockWise);
p2.moveTo(100, 40);
p2.lineTo(125, 90);
p2.lineTo(75, 90);
p2.closePath();
painter->setFillStyle(Qt::green);
painter->setStrokeStyle(Qt::red);
painter->fill(p2, -1);
painter->stroke(p2, -1);
painter->translate(200, 0);
painter->fill(p, -1);
painter->stroke(p, -1);
painter->setWindingEnforce(false);
painter->translate(200, 0);
painter->fill(p, -1);
painter->stroke(p, -1);
painter->setWindingEnforce(true);
painter->translate(200, 0);
painter->fill(p, -1);
painter->stroke(p, -1);
}
void CanvasPainterLancelotCppTests::testPathWinding()
{
painter->setLineWidth(4);
painter->setLineJoin(QCanvasPainter::LineJoin::Bevel);
painter->fillText("CW, Default, set DisableWindingEnforce, clear DisableWindingEnforce", 10, 10);
QCanvasPath p;
p.moveTo(20, 20);
p.lineTo(100, 180);
p.lineTo(180, 20);
p.closePath();
p.moveTo(100, 40);
p.lineTo(125, 90);
p.lineTo(75, 90);
p.closePath();
QCanvasPath p2;
p2.moveTo(20, 20);
p2.lineTo(100, 180);
p2.lineTo(180, 20);
p2.closePath();
p2.setPathWinding(QCanvasPainter::PathWinding::ClockWise);
p2.moveTo(100, 40);
p2.lineTo(125, 90);
p2.lineTo(75, 90);
p2.closePath();
painter->setFillStyle(Qt::green);
painter->setStrokeStyle(Qt::red);
painter->fill(p2, 234);
painter->stroke(p2, 234);
painter->translate(200, 0);
painter->fill(p, 234);
painter->stroke(p, 234);
painter->setWindingEnforce(false);
painter->translate(200, 0);
painter->fill(p, 234);
painter->stroke(p, 234);
painter->setWindingEnforce(true);
painter->translate(200, 0);
painter->fill(p, 234);
painter->stroke(p, 234);
painter->resetTransform();
painter->translate(0, 200);
painter->setFillStyle(Qt::black);
painter->fillText("Repeat the last and first (cache should hit for both)", 10, 0);
painter->setFillStyle(Qt::green);
painter->translate(0, 20);
painter->fill(p, 234);
painter->stroke(p, 234);
painter->translate(200, 0);
painter->fill(p2, 234);
painter->stroke(p2, 234);
}
void CanvasPainterLancelotCppTests::testStraightLinesInPathUncachedPath()
{
painter->translate(0, 10);
painter->fillText("Default (AA = 1)", 10, 40);
painter->fillText("Disabled", 10, 80);
painter->fillText("AA = 2.5", 10, 120);
painter->fillText("Disabled", 10, 160);
painter->fillText("AA = 5.0", 10, 200);
painter->fillText("Disabled", 10, 240);
painter->fillText("AA = 8.0", 10, 280);
painter->translate(100, -50);
painter->setLineWidth(10);
QCanvasPath p;
p.moveTo(20, 50);
p.lineTo(380, 50);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->setAntialias(0);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->setAntialias(2.5f);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->setAntialias(0);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->setAntialias(5.0f);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->setAntialias(0);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->setAntialias(8.0f);
painter->translate(0, 40);
painter->stroke(p, -1);
}
void CanvasPainterLancelotCppTests::testStraightLinesInPath()
{
painter->translate(0, 10);
painter->fillText("Default (AA = 1)", 10, 40);
painter->fillText("Disabled", 10, 80);
painter->fillText("AA = 2.5", 10, 120);
painter->fillText("Disabled", 10, 160);
painter->fillText("AA = 5.0", 10, 200);
painter->fillText("Disabled", 10, 240);
painter->fillText("AA = 8.0", 10, 280);
painter->translate(100, -50);
painter->setLineWidth(10);
QCanvasPath p;
p.moveTo(20, 50);
p.lineTo(380, 50);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->setAntialias(0);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->setAntialias(2.5f);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->setAntialias(0);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->setAntialias(5.0f);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->setAntialias(0);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->setAntialias(8.0f);
painter->translate(0, 40);
painter->stroke(p, 123);
}
void CanvasPainterLancelotCppTests::testPathCaching()
{
painter->setLineWidth(10);
// shorter line
QCanvasPath p;
p.moveTo(20, 50);
p.lineTo(380, 50);
// longer line
QCanvasPath p2;
p2.moveTo(20, 50);
p2.lineTo(580, 50);
// This should render the following, if the engine works as expected, each
// entry refers to two short lines with one longer line below, all three should
// look identical when it comes to line width and AA.
//
// width 10 default AA
// width 20 default AA
// width 10 default AA
// width 10 AA increased to 8
// width 10 default AA
// draw both with the same group
// width 10 default AA
painter->translate(0, 40);
painter->stroke(p, 123);
painter->translate(400, 0);
painter->stroke(p, 123);
painter->translate(-400, 0);
painter->translate(0, 40);
painter->stroke(p2, 123);
// repeat with a different line width
// width 20 default AA
painter->setLineWidth(20);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->translate(400, 0);
painter->stroke(p, 123);
painter->translate(-400, 0);
painter->translate(0, 40);
painter->stroke(p2, 123);
// without caching
// width 10 default AA
painter->setLineWidth(10);
painter->translate(0, 40);
painter->stroke(p, -1);
painter->translate(400, 0);
painter->stroke(p, -1);
painter->translate(-400, 0);
painter->translate(0, 40);
painter->stroke(p2, -1);
// change AA
// width 10 AA increased to 8
painter->setAntialias(8);
painter->translate(0, 40);
painter->stroke(p, 123);
painter->translate(400, 0);
painter->stroke(p, 123);
painter->translate(-400, 0);
painter->translate(0, 40);
painter->stroke(p2, 123);
// change group for both
// width 10 default AA
painter->setAntialias(1);
painter->translate(0, 40);
painter->stroke(p, 124);
painter->translate(400, 0);
painter->stroke(p, 124);
painter->translate(-400, 0);
painter->translate(0, 40);
painter->stroke(p2, 125);
}
void CanvasPainterLancelotCppTests::testPathFill()
{
QCanvasPainter::ImageFlags flags = QCanvasPainter::ImageFlag::Repeat;
QCanvasImage rgbImage = painter->addImage(QImage(":/images/dome_rgb32.png"), flags);
QCanvasImage argbImage = painter->addImage(QImage(":/images/dome_argb32.png"), flags);
painter->setStrokeStyle("#afff0000");
painter->setLineWidth(4);
QCanvasPath p;
p.ellipse(10 + 100, 10 + 100, 100, 100);
painter->setFillStyle(QCanvasImagePattern(rgbImage));
painter->fill(p);
painter->stroke(p);
painter->setFillStyle(QCanvasImagePattern(argbImage));
painter->save();
painter->translate(210, 0);
painter->fill(p);
painter->stroke(p);
painter->restore();
painter->setFillStyle(QCanvasImagePattern(rgbImage));
painter->save();
painter->translate(0, 210);
painter->fill(p);
painter->restore();
painter->setFillStyle(QCanvasImagePattern(argbImage));
painter->save();
painter->translate(210, 210);
painter->fill(p);
painter->restore();
QCanvasImagePattern rgbpatternOffs(rgbImage);
rgbpatternOffs.setStartPosition(-30, -30);
painter->setFillStyle(rgbpatternOffs);
painter->save();
painter->translate(420, 0);
painter->fill(p);
painter->stroke(p);
painter->restore();
QCanvasImagePattern argbpatternOffs(argbImage);
argbpatternOffs.setStartPosition(-30, -30);
painter->setFillStyle(argbpatternOffs);
painter->save();
painter->translate(630, 0);
painter->fill(p);
painter->stroke(p);
painter->restore();
painter->setFillStyle(rgbpatternOffs);
painter->save();
painter->translate(420, 210);
painter->fill(p);
painter->restore();
painter->setFillStyle(argbpatternOffs);
painter->save();
painter->translate(630, 210);
painter->fill(p);
painter->restore();
painter->setFillStyle(Qt::black);
painter->fillText("No offset RGB/ARGB", 150, 450);
painter->fillText("-30 offset RGB/ARGB", 550, 450);
painter->fillText("10 offset, 45 degrees rotation", 200, 550);
painter->fillText("Open curve fill", 550, 550);
rgbpatternOffs.setRotation(qDegreesToRadians(45));
painter->setFillStyle(rgbpatternOffs);
painter->save();
painter->translate(0, 550);
painter->fill(p);
painter->stroke(p);
painter->restore();
argbpatternOffs.setRotation(qDegreesToRadians(45));
painter->setFillStyle(argbpatternOffs);
painter->save();
painter->translate(210, 550);
painter->fill(p);
painter->stroke(p);
painter->restore();
QCanvasPath curvePath;
curvePath.moveTo(10, 10);
curvePath.quadraticCurveTo(200, 100, 10, 200);
painter->save();
painter->translate(630, 550);
painter->fill(curvePath);
painter->stroke(curvePath);
painter->restore();
}
void CanvasPainterLancelotCppTests::testCurveImpl(int pathGroup)
{
QCanvasPath star;
star.moveTo(50, 0);
star.lineTo(30, 90);
star.lineTo(100, 60);
star.lineTo(0, 20);
star.lineTo(80, 100);
star.closePath();
QCanvasPath rectncircle;
rectncircle.rect(0, 0, 75, 75);
rectncircle.circle(50, 50, 25);
QCanvasPath curve;
curve.moveTo(100, 0);
curve.bezierCurveTo(100, 100, 50, 50, 0, 100);
curve.closePath();
QCanvasPath curve2;
curve2.moveTo(200, 0);
curve2.quadraticCurveTo(200, 200, 250, 250);
curve2.closePath();
QCanvasPath arc;
arc.moveTo(300, 200);
arc.arcTo(600, 400, 200, 300, 40);
arc.closePath();
painter->setStrokeStyle(Qt::black);
painter->setLineWidth(10);
painter->setFillStyle("#7f7fff");
int pg1 = pathGroup;
int pg2 = pathGroup != -1 ? pathGroup + 1 : -1;
int pg3 = pathGroup != -1 ? pathGroup + 2 : -1;
int pg4 = pathGroup != -1 ? pathGroup + 3 : -1;
auto f = [&] {
painter->fill(star, pg1);
painter->stroke(star, pg1);
painter->translate(100, 0);
painter->fill(rectncircle, pg2);
painter->stroke(rectncircle, pg2);
painter->translate(100, 0);
painter->fill(curve, pg3);
painter->stroke(curve, pg3);
painter->translate(-100, 0);
painter->fill(curve2, pg4);
painter->stroke(curve2, pg4);
};
f();
auto g = [&] {
painter->save();
painter->translate(50, 60);
painter->rotate(qDegreesToRadians(10));
painter->scale(1.0f, 0.7f);
f();
painter->restore();
};
painter->setLineWidth(0);
painter->setFillStyle("#ff7f7f");
painter->translate(0, 300);
f();
painter->translate(200, 0);
QCanvasLinearGradient g1(0, 0, 100, 100);
g1.setStartColor("#428321");
g1.setEndColor("#d4d723");
painter->setFillStyle(g1);
g();
painter->resetTransform();
QCanvasGridPattern gp(10, 10, 40, 40);
gp.setLineColor("#808080");
gp.setBackgroundColor("#202020");
painter->setFillStyle(gp);
painter->translate(0, 400);
painter->setLineWidth(4);
painter->fill(arc, pg1);
painter->stroke(arc, pg1);
}
void CanvasPainterLancelotCppTests::testCurve()
{
testCurveImpl(-1);
}
void CanvasPainterLancelotCppTests::testCurveWithPathCaching()
{
testCurveImpl(99);
}
void CanvasPainterLancelotCppTests::testSomeText()
{
painter->setFillStyle(Qt::black);
painter->fillText("Text that is drawn outside the bounds...", -5, 5);
painter->translate(20, 20);
auto drawText = [&] {
painter->save();
QFont f;
f.setPointSize(10);
painter->setFont(f);
painter->fillText("10 pt, normal", 0, 20);
f.setPointSize(12);
painter->setFont(f);
painter->fillText("12 pt, normal", 0, 40);
f.setPointSize(10);
f.setBold(true);
painter->setFont(f);
painter->fillText("10 pt, bold", 0, 60);
f.setBold(false);
f.setItalic(true);
painter->setFont(f);
painter->fillText("10 pt, italic", 0, 80);
painter->translate(0, 100);
painter->setFillStyle("#7fff0000");
f.setPointSize(10);
f.setItalic(false);
painter->setFont(f);
painter->fillText("alpha, 10 pt, normal", 0, 20);
f.setPointSize(12);
painter->setFont(f);
painter->fillText("alpha, 12 pt, normal", 0, 40);
f.setBold(true);
painter->setFont(f);
painter->fillText("alpha, 12 pt, bold", 0, 60);
f.setItalic(true);
painter->setFont(f);
painter->fillText("alpha, 12 pt, bold, italic", 0, 80);
painter->translate(0, 100);
painter->setFillStyle(Qt::black);
painter->save();
painter->scale(0.9f);
f = QFont();
f.setPointSize(10);
painter->setFont(f);
painter->fillText("scaled, 10pt, normal", 0, 20);
f.setPointSize(12);
painter->setFont(f);
painter->fillText("scaled, 12pt, normal", 0, 40);
f.setBold(true);
painter->setFont(f);
painter->fillText("scaled, 12 pt, bold", 0, 60);
f.setItalic(true);
painter->setFont(f);
painter->fillText("scaled, 12 pt, bold, italic", 0, 80);
painter->restore();
painter->translate(200, 200);
painter->setFillStyle(Qt::black);
painter->save();
painter->scale(-1, -1);
f = QFont();
f.setPointSize(10);
painter->setFont(f);
painter->fillText("flipped, 10pt, normal", 0, 20);
f.setPointSize(12);
painter->setFont(f);
painter->fillText("flipped, 12pt, normal", 0, 40);
f.setBold(true);
painter->setFont(f);
painter->fillText("flipped, 12 pt, bold", 0, 60);
f.setItalic(true);
painter->setFont(f);
painter->fillText("flipped, 12 pt, bold, italic", 0, 80);
painter->restore();
painter->translate(-200, 20);
painter->setFillStyle(Qt::black);
painter->save();
painter->translate(200, 90);
painter->rotate(qDegreesToRadians(185));
f = QFont();
f.setPointSize(10);
painter->setFont(f);
painter->fillText("rotated, 10pt, normal", 0, 20);
f.setPointSize(12);
painter->setFont(f);
painter->fillText("rotated, 12pt, normal", 0, 40);
f.setBold(true);
painter->setFont(f);
painter->fillText("rotated, 12 pt, bold", 0, 60);
f.setItalic(true);
painter->setFont(f);
painter->fillText("rotated, 12 pt, bold, italic", 0, 80);
painter->restore();
painter->translate(0, 100);
QCanvasLinearGradient grad(0, 0, 200, 0);
grad.setColorAt(0, Qt::red);
grad.setColorAt(0.5f, "#00ff00");
grad.setColorAt(1, Qt::blue);
painter->setFillStyle(grad);
f = QFont();
f.setPointSize(10);
painter->setFont(f);
painter->fillText("gradient, 10 pt, normal", 0, 0);
f.setPointSize(12);
painter->setFont(f);
painter->fillText("gradient, 12pt, normal", 0, 20);
f.setBold(true);
painter->setFont(f);
painter->fillText("gradient, 12 pt, bold", 0, 40);
f.setItalic(true);
painter->setFont(f);
painter->fillText("gradient, 12 pt, bold, italic", 0, 60);
painter->restore();
};
drawText();
painter->translate(250, 0);
painter->fillText("clipped to rectangle", 25, 640);
painter->save();
painter->setStrokeStyle("#3f000000");
painter->strokeRect(20, 0, 100, 620);
painter->setClipRect(20, 0, 100, 620);
painter->setStrokeStyle(Qt::black);
drawText();
painter->restore();
painter->setFillStyle(Qt::black);
painter->translate(150, 0);
painter->translate(0, 75);
painter->save();
QFont font;
font.setPointSize(16);
painter->setFont(font);
painter->fillText("e😃m😇o😍j😜i😸!✈️", 0, 40);
painter->restore();
painter->translate(0, 75);
painter->save();
font.setPointSize(12);
font.setUnderline(true);
painter->setFont(font);
painter->fillText("Underlined text drawing", 0, 20);
painter->restore();
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setStrikeOut(true);
painter->setFont(font);
painter->fillText("Struck out text drawing", 0, 20);
painter->restore();
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setOverline(true);
painter->setFont(font);
painter->fillText("Overlined text drawing", 0, 20);
painter->restore();
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setUnderline(true);
font.setOverline(true);
font.setStrikeOut(true);
painter->setFont(font);
painter->fillText("Under+Over+Strike text drawing", 0, 20);
painter->restore();
// Text decorations combined with color emoji. The mixed string exercises a
// decoration line spanning both monochrome (SDF) glyphs and color (emoji)
// glyphs, while the pure-emoji string verifies that decorations are still
// drawn when no monochrome glyph is present (the decoration relies on the
// SDF solid tile, which must be created even for emoji-only runs).
const QString mixedEmoji = "e😃m😇o😍j😜i😸!✈️";
const QString pureEmoji = "😃😇😍😜😸✈️";
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setUnderline(true);
painter->setFont(font);
painter->fillText(mixedEmoji, 0, 20);
painter->restore();
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setStrikeOut(true);
painter->setFont(font);
painter->fillText(mixedEmoji, 0, 20);
painter->restore();
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setUnderline(true);
font.setOverline(true);
font.setStrikeOut(true);
painter->setFont(font);
painter->fillText(mixedEmoji, 0, 20);
painter->restore();
painter->translate(0, 35);
painter->save();
font = QFont();
font.setPointSize(12);
font.setUnderline(true);
font.setOverline(true);
font.setStrikeOut(true);
painter->setFont(font);
painter->fillText(pureEmoji, 0, 20);
painter->restore();
}
void CanvasPainterLancelotCppTests::testManyEmojis()
{
// Exercises growth of the color glyph atlas (QCRhiColorGlyphCache): each
// distinct glyph claims a new atlas slot, so drawing many of them forces the
// atlas texture to grow past its initial size several times within a single frame.
painter->setFillStyle(Qt::black);
const int sizes[] = { 16, 20, 24, 28, 32 };
float y = 24.0f;
bool full = false;
for (int pt : sizes) {
if (full)
break;
QFont font;
font.setPointSize(pt);
painter->setFont(font);
const float cell = pt * 1.6f;
float x = 8.0f;
for (char32_t cp = 0x1F600; cp <= 0x1F64F; ++cp) {
painter->fillText(QString::fromUcs4(&cp, 1), x, y);
x += cell;
if (x > width() - cell) {
x = 8.0f;
y += cell;
if (y > height() - cell) {
full = true;
break;
}
}
}
y += cell * 1.5f; // gap before the next (larger) size block
}
}
void CanvasPainterLancelotCppTests::testEmojiFillColor()
{
// Color (COLR) fonts mix glyphs with baked-in palette colors and glyphs
// that are painted in the current text ("foreground") color. The bundled
// test font maps the *-keycap ligature to its single COLR glyph (fixed
// palette colors), while the emoji-presentation airplane resolves to a
// plain outline glyph, which color fonts render in the foreground color.
// Each row repeats the same string with a different fill style: the
// letters and the airplane must follow it, the keycap must not. The rows
// must not share glyph bitmaps, as the color is part of the color glyph
// cache key. The system emoji font is not used, keeping the output
// independent of the platform's emoji designs.
const int fontId = QFontDatabase::addApplicationFont(
QStringLiteral(":/fonts/QtEmojiTestFont-Regular.ttf"));
if (fontId < 0)
return;
const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
if (families.isEmpty())
return;
QFontDatabase::addApplicationEmojiFontFamily(families.first());
const auto cleanup = qScopeGuard([&] {
QFontDatabase::removeApplicationEmojiFontFamily(families.first());
QFontDatabase::removeApplicationFont(fontId);
});
// * + VS16 + U+20E3 (keycap ligature) and U+2708 + VS16 (airplane).
const QString text = QStringLiteral("Ab *️⃣ ✈️");
const QColor colors[] = { Qt::black, Qt::red, QColor(0, 128, 0), Qt::blue };
QFont font;
font.setPointSize(16);
painter->setFont(font);
float y = 30.0f;
for (const QColor &color : colors) {
painter->setFillStyle(color);
painter->fillText(text, 8, y);
y += 45.0f;
}
}
void CanvasPainterLancelotCppTests::testCanvasPathCommandsWithAndWithoutPathGroup()
{
QCanvasPath path;
path.moveTo(10.f, 10.f);
path.lineTo(100.f, 10.f);
path.lineTo(100.f, 80.f);
path.lineTo(10.f, 80.f);
path.closePath();
path.moveTo(QPointF(120.f, 10.f));
path.lineTo(QPointF(200.f, 10.f));
path.lineTo(QPointF(200.f, 80.f));
path.closePath();
path.moveTo(10.f, 120.f);
path.bezierCurveTo(10.f, 90.f, 80.f, 90.f, 80.f, 120.f);
path.moveTo(QPointF(100.f, 120.f));
path.bezierCurveTo(QPointF(100.f, 90.f), QPointF(180.f, 90.f), QPointF(180.f, 120.f));
path.moveTo(10.f, 160.f);
path.quadraticCurveTo(55.f, 130.f, 100.f, 160.f);
path.moveTo(QPointF(120.f, 160.f));
path.quadraticCurveTo(QPointF(165.f, 130.f), QPointF(210.f, 160.f));
path.moveTo(10.f, 200.f);
path.arcTo(10.f, 250.f, 70.f, 250.f, 35.f);
path.moveTo(QPointF(120.f, 200.f));
path.arcTo(QPointF(120.f, 250.f), QPointF(180.f, 250.f), 35.f);
path.moveTo(260.f, 225.f);
path.arc(230.f, 225.f, 30.f, 0.f, float(M_PI));
path.arc(330.f, 225.f, 30.f, 0.f, float(M_PI),
QCanvasPainter::PathWinding::CounterClockWise,
QCanvasPainter::PathConnection::NotConnected);
path.arc(QPointF(430.f, 225.f), 30.f, 0.f, 2.f * float(M_PI),
QCanvasPainter::PathWinding::ClockWise,
QCanvasPainter::PathConnection::NotConnected);
path.rect(10.f, 280.f, 80.f, 50.f);
path.rect(QRectF(110.f, 280.f, 80.f, 50.f));
path.roundRect(210.f, 280.f, 80.f, 50.f, 10.f);
path.roundRect(QRectF(310.f, 280.f, 80.f, 50.f), 10.f);
path.roundRect(10.f, 350.f, 80.f, 50.f, 5.f, 10.f, 15.f, 20.f);
path.roundRect(QRectF(110.f, 350.f, 80.f, 50.f), 5.f, 10.f, 15.f, 20.f);
path.ellipse(260.f, 375.f, 40.f, 22.f);
path.ellipse(QRectF(310.f, 353.f, 80.f, 44.f));
path.circle(460.f, 375.f, 22.f);
path.circle(QPointF(530.f, 375.f), 22.f);
path.setPathWinding(QCanvasPainter::PathWinding::CounterClockWise);
path.circle(80.f, 460.f, 50.f);
path.beginHoleSubPath(); // ClockWise
path.circle(80.f, 460.f, 30.f);
path.beginSolidSubPath(); // CounterClockWise
path.circle(80.f, 460.f, 12.f);
painter->setFillStyle(Qt::green);
painter->fill(path);
painter->setStrokeStyle(Qt::red);
painter->setLineWidth(4);
painter->stroke(path);
int group = 5;
painter->scale(0.5f);
painter->translate(0, 1050);
painter->fill(path, group);
painter->stroke(path, group);
group = 6;
painter->translate(1000, -1050);
painter->fill(path, group);
painter->stroke(path, group);
painter->translate(0, 1050);
painter->fill(path, group);
painter->stroke(path, group);
}
void CanvasPainterLancelotCppTests::testHighQualityStroking()
{
painter->setHighQualityStroking(false);
painter->setStrokeStyle(QColor(192, 0, 0, 127));
painter->setLineWidth(20);
painter->beginPath();
painter->moveTo(10, 10);
painter->lineTo(80, 80);
painter->moveTo(10, 80);
painter->lineTo(80, 10);
painter->stroke();
painter->setFillStyle(QColor(0, 192, 0, 127));
painter->fillRect(10, 110, 80, 80);
painter->setHighQualityStroking(true);
painter->beginPath();
painter->moveTo(10, 210);
painter->lineTo(80, 280);
painter->moveTo(10, 280);
painter->lineTo(80, 210);
painter->stroke();
}
// ----------- tests adapted from Gallery example start below
void CanvasPainterLancelotCppTests::testRects()
{
float topMargin = height() * 0.02f;
float anim = 0.1f;
{
int rects = 4;
float margin = width() * 0.02f;
float w = width() / (rects+1) - margin;
float posX = w*0.5 + margin;
float posY = topMargin;
// No stops, so transparent
QRectF rect1(posX,posY,w,w);
painter->setFillStyle(QCanvasLinearGradient(0, rect1.y(), 0, rect1.y()+rect1.height()));
painter->fillRect(rect1);
posX += w + margin;
QCanvasLinearGradient g1(0, 0, 0, 0);
g1.setStartColor(QColor(0,255,0,255));
g1.setEndColor(QColor(255,0,0,255));
g1.setStartPosition(posX + (w*0.4f * anim), 0);
g1.setEndPosition(posX + w - (w*0.4f * anim), 0);
painter->setFillStyle(g1);
painter->fillRect(posX,posY,w,w);
posX += w + margin;
QRectF rect2(posX,posY,w,w);
QCanvasLinearGradient g2(rect2.x(), rect2.y(), rect2.x()+rect2.width(), rect2.y()+rect2.height());
g2.setStartColor(QColor(anim*255, 255, 255));
g2.setEndColor(QColor(255, anim*255, 255, 255-anim*255));
painter->setFillStyle(g2);
painter->fillRect(rect2);
posX += w + margin;
QRectF rect3(posX,posY,w,w);
QCanvasLinearGradient g3(rect3.x(), rect3.y(), rect3.x(), rect3.y()+rect3.height());
g3.setColorAt(0.0f, QColorConstants::Black);
g3.setColorAt(0.2f, QColorConstants::Red);
g3.setColorAt(0.4f, QColorConstants::Green);
g3.setColorAt(0.6f, QColorConstants::Blue);
g3.setColorAt(0.8f, QColorConstants::White);
g3.setColorAt(1.0f, QColorConstants::Transparent);
painter->setFillStyle(g3);
painter->fillRect(rect3);
}
{
QCanvasRadialGradient radGrad;
radGrad.setStops({{ 0.0f, QColor(255, 0, 0, 255) },
{ 0.1f, QColor(255, 255, 0, 255) },
{ 0.5f, QColor(0, 255, 0, 255) },
{ 1.0f, QColor(0, 0, 0, 0) }
});
int rects = 4;
float margin = width()*0.02f;
float w = width() / (rects+1) - margin;
float posX = w*0.5 + margin;
float posY = topMargin + (w+margin);
// No stops, so transparent
QRectF rect1(posX,posY,w,w);
painter->setFillStyle(QCanvasRadialGradient(rect1.x(), rect1.y(), 0, rect1.width()));
painter->fillRect(rect1);
posX += w + margin;
QRectF rect2(posX,posY,w,w);
QCanvasRadialGradient g1;
g1.setStartColor(QColor(0,255,0,255));
g1.setEndColor(QColor(255,0,0,255));
g1.setCenterPosition(rect2.x() + rect2.width()/2, rect2.y() + rect2.height()/2);
g1.setOuterRadius(0.5*w);
g1.setInnerRadius(0.4*w*anim);
painter->setFillStyle(g1);
painter->fillRect(rect2);
posX += w + margin;
QRectF rect3(posX,posY,w,w);
QCanvasRadialGradient g3(rect3.x() + rect3.width()/2, rect3.y() + rect3.height()/2, w/4, w/2);
g3.setStartColor(QColor(anim*255, 255, 255));
g3.setEndColor(QColor(255, anim*255, 255, 255-anim*255));
painter->setFillStyle(g3);
painter->fillRect(rect3);
posX += w + margin;
QRectF rect4(posX,posY,w,w);
radGrad.setCenterPosition(rect4.x() + rect4.width()/2, rect4.y() + rect4.height()/2);
radGrad.setOuterRadius(w/2);
radGrad.setInnerRadius(w/16);
auto stops = radGrad.stops();
stops[2].position = 0.2 + 0.6 * anim;
stops[0].color = QColor(255, anim*255, 0, 255-anim*255);
radGrad.setStops(stops);
painter->setFillStyle(radGrad);
painter->fillRect(rect4);
}
{
int rects = 4;
float margin = width()*0.02f;
float w = width() / (rects+1) - margin;
float posX = w*0.5 + margin;
float posY = topMargin + 2*(w+margin);
// No stops, so transparent
QRectF rect1(posX,posY,w,w);
QCanvasBoxGradient g0(rect1);
g0.setFeather(w/2);
painter->setFillStyle(g0);
painter->fillRect(rect1);
posX += w + margin;
QRectF rect2(posX,posY,w,w);
QCanvasBoxGradient g1;
g1.setStartColor(QColor(0,255,0,255));
g1.setEndColor(QColor(255,0,0,255));
g1.setRect(rect2);
g1.setRadius(w/4);
g1.setFeather(w/2 - anim*w/3);
painter->setFillStyle(g1);
painter->fillRect(rect2);
posX += w + margin;
QRectF rect3(posX,posY,w,w);
QCanvasBoxGradient g3(rect3);
g3.setFeather(w/4);
g3.setRadius(w/4);
g3.setStartColor(QColor(anim*255, 255, 255));
g3.setEndColor(QColor(255, anim*255, 255, 255-anim*255));
painter->setFillStyle(g3);
painter->fillRect(rect3);
posX += w + margin;
QRectF rect4(posX,posY,w,w);
QCanvasBoxGradient g4(rect4);
g4.setFeather(w/4);
g4.setRadius(w/3);
g4.setColorAt(0.0f, QColor(255, 255, 255, 255));
g4.setColorAt(0.2f, QColor(0, 0, 0, 255));
g4.setColorAt(0.4f, QColor(255, anim*255, 0, 255));
g4.setColorAt(0.6f, QColor(0, 0, 0, 255));
g4.setColorAt(0.8f, QColor(255, 255, 255, 255));
g4.setColorAt(1.0f, QColor(0, 0, 0, 0));
painter->setFillStyle(g4);
painter->fillRect(rect4);
}
{
int rects = 4;
float margin = width()*0.02f;
float w = width() / (rects+1) - margin;
float posX = w*0.5 + margin;
float posY = topMargin + 3*(w+margin);
// No stops, so transparent
QRectF rect1(posX,posY,w,w);
QCanvasConicalGradient g1;
g1.setCenterPosition(rect1.center());
painter->setFillStyle(g1);
painter->fillRect(rect1);
posX += w + margin;
QRectF rect2(posX,posY,w,w);
QCanvasConicalGradient g2;
g2.setCenterPosition(rect2.center());
g2.setStartColor(QColor(0,255,0,255));
g2.setEndColor(QColor(255,0,0,255));
painter->setFillStyle(g2);
painter->fillRect(rect2);
posX += w + margin;
QRectF rect3(posX,posY,w,w);
QCanvasConicalGradient g3;
g3.setCenterPosition(rect3.center());
g3.setStartAngle(-0.5f * float(M_PI));
g3.setStartColor(QColor(anim*255, 255, 255));
g3.setEndColor(QColor(255, anim*255, 255, 255-anim*255));
painter->setFillStyle(g3);
painter->fillRect(rect3);
posX += w + margin;
QRectF rect4(posX,posY,w,w);
QCanvasConicalGradient g4;
g4.setCenterPosition(rect4.x() + anim * rect4.width(), rect4.y() + rect4.height()/2);
g4.setStartAngle(anim);
g4.setStartColor(QColor(255, 255, 0, 255));
g4.setColorAt(0.25, QColor(0, 255, 0, 255));
g4.setColorAt(0.5, QColor(0, 0, 255, 255));
g4.setColorAt(0.75, QColor(255, 0, 255, 255));
g4.setEndColor(QColor(255, 255, 0, 255));
painter->setFillStyle(g4);
painter->fillRect(rect4);
}
}
void CanvasPainterLancelotCppTests::testRects2()
{
float topMargin = height() * 0.02f;
float anim = 0.1f;
QCanvasPainter::ImageFlags flags = QCanvasPainter::ImageFlag::Repeat | QCanvasPainter::ImageFlag::GenerateMipmaps;
QCanvasImage patternImage = painter->addImage(QImage(":/images/pattern1.png"), flags);
QCanvasImage patternImage2 = painter->addImage(QImage(":/images/pattern2.png"), flags);
QCanvasImage patternImage3 = painter->addImage(QImage(":/images/pattern3.png"), flags);
{
int rects = 3;
float margin = width()*0.02f;
float w = width() / (rects+2) - margin;
float posX = w + margin;
float posY = topMargin;
QRectF rect1(posX,posY,w,w);
QCanvasImagePattern g4(patternImage2, rect1.topLeft(), rect1.size());
QColor aColor = QColor::fromRgbF(1.0f,
0.5f + 0.5f * sin(anim),
0.5f + 0.5f * sin(anim * 2 + M_PI),
1.0f);
g4.setTintColor(aColor);
painter->setFillStyle(g4);
painter->fillRect(rect1);
posX += w + margin;
QRectF rect2(posX,posY,w,w);
QCanvasImagePattern g1;
g1.setImage(patternImage);
g1.setStartPosition(rect2.x()+w/2, rect2.y()+w/2);
g1.setImageSize(w/5, w/3);
g1.setRotation(anim);
QColor opacityTint(255, 255, 255, 127 + anim * 128);
g1.setTintColor(opacityTint);
painter->setFillStyle(g1);
painter->fillRect(rect2);
posX += w + margin;
QRectF rect3(posX,posY,w,w);
QCanvasImagePattern g3(patternImage);
g3.setStartPosition(rect3.x(), rect3.y());
g3.setImageSize(w*0.1 + anim * w*0.2f, w*0.15f + anim * w*0.3f);
painter->setFillStyle(g3);
painter->fillRect(rect3);
}
{
int rects = 3;
float margin = width()*0.02f;
float border = margin + margin * anim;
float w = width() / (rects+2) - margin;
float w2 = w - border;
float posX = w + margin + border/2;
float posY = topMargin + 2*(w+margin) + border/2;
QRectF rect1(posX,posY,w2,w2);
painter->setLineWidth(border);
painter->setFillStyle(QCanvasImagePattern(patternImage3));
QCanvasLinearGradient g1(posX, posY, posX+w2, posY+w2);
g1.setStartColor("#ffffff");
g1.setEndColor("#000000");
painter->setStrokeStyle(g1);
painter->beginPath();
painter->roundRect(rect1, border);
painter->fill();
painter->stroke();
posX += w + margin;
QRectF rect2(posX,posY,w2,w2);
g1.setStartPosition(posX, posY);
g1.setEndPosition(posX+w2, posY+w2);
painter->setFillStyle(g1);
painter->setStrokeStyle(QCanvasImagePattern(patternImage3));
painter->beginPath();
painter->roundRect(rect2, border);
painter->fill();
painter->stroke();
posX += w + margin;
QRectF rect3(posX,posY,w2,w2);
QCanvasRadialGradient g2(posX+w2/4, posY+w2/4, 0, w2);
g2.setStartColor("#900000ff");
g2.setEndColor("#90ff0000");
painter->setStrokeStyle(g2);
QCanvasImagePattern p1 = QCanvasImagePattern(patternImage3);
p1.setImageSize(16, 16);
p1.setStartPosition(anim*40, 0);
painter->setFillStyle(p1);
painter->beginPath();
painter->roundRect(rect3, border);
painter->fill();
painter->stroke();
}
}
void CanvasPainterLancelotCppTests::testPaths()
{
float topMargin = height() * 0.02f;
float anim = 0.1f;
{
int rects = 3;
float margin = width()*0.1f;
float w = width() / (rects) - margin;
float lineWidth = width()*0.02f;
float posX = margin/2;
float posY = topMargin;
painter->setFillStyle(0xFF948979);
painter->setStrokeStyle(0xFFDFD0B8);
painter->setLineWidth(lineWidth);
painter->setLineCap(QCanvasPainter::LineCap::Round);
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
painter->beginPath();
painter->rect(posX, posY, w, w);
posX += w + margin;
painter->roundRect(posX, posY, w, w, w/2*anim);
posX += w + margin;
painter->roundRect(posX, posY, w, w, w/2*anim, 0, w/2*(cos(anim)*0.5+0.5), w/4*(sin(anim*10)*0.5+0.5));
posY += w+margin;
posX = margin/2;
painter->ellipse(posX+w/2, posY+w/2, w/3+w/4*anim, w/3+w/4*(1-anim));
posX += w + margin;
painter->moveTo(posX+w/2, posY+w/2);
painter->arc(posX+w/2, posY+w/2, w/2, 0, 0.5f + anim*5);
painter->closePath();
posX += w + margin;
QRectF r(posX, posY, w, w);
painter->ellipse(r);
float a = w*0.1f*anim;
float a2 = w*0.1f*(1-anim);
r.adjust(w*0.1f+a, w*0.1f+a2, -w*0.1f-a, -w*0.1f-a2);
painter->beginHoleSubPath();
painter->ellipse(r);
r.adjust(w*0.2f, w*0.2f, -w*0.2f, -w*0.2f);
painter->beginSolidSubPath();
painter->rect(r);
painter->stroke();
painter->fill();
posY += w+margin;
posX = margin/2;
painter->beginPath();
painter->moveTo(posX, posY);
painter->bezierCurveTo(posX+w*0.2f*anim, posY+w*0.8f*anim, posX+w*0.8f*anim, posY+w*0.2f*anim, posX+w, posY+w);
posX += w + margin;
painter->moveTo(posX, posY);
painter->quadraticCurveTo(posX+w*0.8f*anim, posY+w*0.2f*anim, posX+w, posY+w);
posX += w + margin;
painter->moveTo(posX, posY);
painter->arcTo(posX, posY+w, posX+w, posY+w, w*anim);
painter->stroke();
}
}
void CanvasPainterLancelotCppTests::testPaths2()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
{
int rects = 3;
float margin = width()*0.1f;
float w = width() / (rects) - margin;
float posX = margin/2;
float posY = topMargin;
float lineWidth = width()*0.02f;
painter->setFillStyle(0x00941979);
painter->setStrokeStyle(0x0031D0B8);
painter->setLineWidth(lineWidth);
painter->setLineCap(QCanvasPainter::LineCap::Round);
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
painter->setLineCap(QCanvasPainter::LineCap::Round);
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
painter->beginPath();
painter->moveTo(posX, posY);
painter->lineTo(posX, posY+w);
painter->lineTo(posX+w*anim, posY);
painter->stroke();
posX += w + margin;
painter->setLineCap(QCanvasPainter::LineCap::Butt);
painter->setLineJoin(QCanvasPainter::LineJoin::Miter);
painter->beginPath();
painter->moveTo(posX, posY);
painter->lineTo(posX, posY+w);
painter->lineTo(posX+w*anim, posY);
painter->stroke();
posX += w + margin;
painter->setLineCap(QCanvasPainter::LineCap::Square);
painter->setLineJoin(QCanvasPainter::LineJoin::Bevel);
painter->beginPath();
painter->moveTo(posX, posY);
painter->lineTo(posX, posY+w);
painter->lineTo(posX+w*anim, posY);
painter->stroke();
}
}
void CanvasPainterLancelotCppTests::testTransforms()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
{
int rects = 3;
float margin = width()*0.1f;
float w = width() / (rects) - margin;
float posX = margin/2;
float lineWidth = width()*0.01f;
float posY = topMargin;
painter->setFillStyle(0xFFFFFFFF);
painter->setStrokeStyle(0xFF000000);
painter->setLineWidth(lineWidth);
painter->fillRect(posX, posY, w, w);
painter->strokeRect(posX, posY, w, w);
// Save the painter state
painter->save();
// Modify painter state
posX += w + margin;
painter->setFillStyle(0xFF900000);
painter->setStrokeStyle(0xFFE00000);
painter->setLineWidth(lineWidth*2);
painter->setLineCap(QCanvasPainter::LineCap::Round);
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
painter->translate(posX+w/2, posY+w/2);
painter->rotate(1.0 - anim);
painter->scale(0.2f + 0.8f*anim);
painter->translate(-(posX+w/2), -(posY+w/2));
painter->fillRect(posX, posY, w, w);
painter->strokeRect(posX, posY, w, w);
posX += w + margin;
// And restore painter state back
painter->restore();
painter->fillRect(posX, posY, w, w);
painter->strokeRect(posX, posY, w, w);
posY += w+margin;
posX = margin/2;
painter->setStrokeStyle(0xFFDFD0B8);
painter->save();
painter->translate(w/8 * anim, w/4 * anim);
painter->strokeRect(posX, posY, w/2, w/2);
posX += w + margin;
painter->translate(w/8 * anim, w/4 * anim);
painter->strokeRect(posX, posY, w/2, w/2);
posX += w + margin;
painter->translate(w/8 * anim, w/4 * anim);
painter->strokeRect(posX, posY, w/2, w/2);
painter->restore();
posY += w+margin;
posX = margin/2;
painter->save();
painter->translate(posX+w/2, posY+w/2);
painter->skew(0.4f * anim);
painter->translate(-(posX+w/2), -(posY+w/2));
painter->strokeRect(posX, posY, w, w);
painter->restore();
posX += w + margin;
painter->save();
painter->translate(posX+w/2, posY+w/2);
painter->skew(0.0f, 0.4f * anim);
painter->translate(-(posX+w/2), -(posY+w/2));
painter->strokeRect(posX, posY, w, w);
painter->restore();
posX += w + margin;
QTransform t;
t.translate(posX+w/2, posY+w/2);
t.rotate(anim*20);
float scale = 0.5f + 0.5f*anim;
t.scale(scale, scale);
t.translate(-(posX+w/2), -(posY+w/2));
painter->setTransform(t);
painter->strokeRect(posX, posY, w, w);
painter->setClipRect(posX, posY, w, w);
// Duplicate transformation
QTransform t2 = painter->getTransform();
painter->transform(t2);
painter->setStrokeStyle(0xFF948979);
painter->strokeRect(posX, posY, w, w);
painter->resetClipping();
posY += w+margin;
posX = margin/2;
painter->resetTransform();
painter->setLineWidth(lineWidth);
QRectF r(posX, posY, w, w);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(r);
r.adjust(lineWidth/2, lineWidth/2, -lineWidth/2, -lineWidth/2);
painter->setClipRect(r);
painter->beginPath();
painter->circle(posX + anim*w, posY + w/2, w/2);
painter->fill();
painter->resetClipping();
posX += w + margin;
painter->setFillStyle(0xFFDFD0B8);
painter->fillRect(posX, posY, w, w);
painter->setClipRect(int(posX + w/2), int(posY + w/2), int(w/2)+2, int(w/2)+2);
painter->clearRect(posX, posY, w, w);
painter->resetClipping();
posX += w + margin;
painter->setFillStyle(0xFFDFD0B8);
painter->setStrokeStyle(0xFF948979);
painter->beginPath();
painter->roundRect(posX, posY, w, w, w / 4);
painter->fill();
painter->stroke();
float cRectSize = (0.6 + 0.4 * sin(anim)) * w;
QRectF cRect(posX + anim*w, posY + w/2 - cRectSize/2, cRectSize, cRectSize);
painter->translate(cRect.center());
painter->rotate(anim);
painter->translate(-cRect.center());
painter->clearRect(cRect);
}
}
void CanvasPainterLancelotCppTests::testTransforms2()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
{
int rects = 3;
float margin = width()*0.1f;
float w = width() / (rects) - margin;
float posX = margin/2;
float lineWidth = width()*0.01f;
float posY = topMargin;
painter->setFillStyle(0xFFFFFFFF);
painter->resetClipping();
posY += w+margin;
posX = margin/2;
painter->resetTransform();
painter->setLineWidth(lineWidth);
QRectF r(posX, posY, w, w);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(r);
r.adjust(lineWidth/2, lineWidth/2, -lineWidth/2, -lineWidth/2);
painter->setClipRect(r);
painter->beginPath();
painter->circle(posX + anim*w, posY + w/2, w/2);
painter->fill();
painter->resetClipping();
posX += w + margin;
painter->setFillStyle(0xFFDFD0B8);
painter->fillRect(posX, posY, w, w);
painter->setClipRect(int(posX + w/2), int(posY + w/2), int(w/2)+2, int(w/2)+2);
painter->clearRect(posX, posY, w, w);
painter->resetClipping();
posX += w + margin;
painter->setFillStyle(0xFFDFD0B8);
painter->setStrokeStyle(0xFF948979);
painter->beginPath();
painter->roundRect(posX, posY, w, w, w / 4);
painter->fill();
painter->stroke();
float cRectSize = (0.6 + 0.4 * sin(anim)) * w;
QRectF cRect(posX + anim*w, posY + w/2 - cRectSize/2, cRectSize, cRectSize);
painter->translate(cRect.center());
painter->rotate(anim);
painter->translate(-cRect.center());
painter->clearRect(cRect);
painter->resetTransform();
posY += w+margin;
posX = margin/2;
painter->translate(posX, posY);
painter->setFillStyle(Qt::cyan);
painter->setLineWidth(3);
painter->beginPath();
painter->roundRect(0, 0, w, w, w / 4);
painter->stroke();
painter->setFillStyle(0xFF66AACC);
painter->rotate(-10 * float(M_PI)/180);
painter->fillRect(0, 0, w, w);
painter->setFillStyle(0xFF77CCEE);
painter->rotate(-10 * float(M_PI)/180);
painter->fillRect(0, 0, w, w);
painter->resetTransform();
posX += w+margin;
painter->translate(posX, posY);
painter->beginPath();
painter->roundRect(0, 0, w, w, w / 4);
painter->stroke();
QTransform t;
t.rotate(-10);
painter->setFillStyle(0xFF66AACC);
painter->transform(t);
painter->fillRect(0, 0, w, w);
painter->setFillStyle(0xFF77CCEE);
painter->transform(t);
painter->fillRect(0, 0, w, w);
}
}
void CanvasPainterLancelotCppTests::testGridPatterns()
{
float topMargin = height() * 0.02f;
float anim = 0.2f;
{
int rects = 2;
float margin = width() * 0.05f;
float w = width() / rects - margin;
float h = height() * 0.25;
float posX = margin/2;
float posY = topMargin;
QRectF rect1(posX, posY, w, h);
float cellZoom = 1.0 + 0.95 * sin(0.5 * anim);
QPointF cp1 = rect1.center();
// Minor grid
QCanvasGridPattern gp1(cp1.x(), cp1.y(), w * 0.1 * cellZoom, h * 0.1 * cellZoom);
gp1.setLineColor("#404040");
gp1.setBackgroundColor("#202020");
painter->setFillStyle(gp1);
painter->fillRect(rect1);
// Major grid
QCanvasGridPattern gp2(cp1.x(), cp1.y(), w * cellZoom, h * cellZoom);
gp2.setLineColor("#d0d0d0");
gp2.setBackgroundColor(Qt::transparent);
painter->setFillStyle(gp2);
painter->fillRect(rect1);
posX += w + margin;
QRectF rect2(posX, posY, w, h);
QPointF cp2 = rect2.center();
float g1 = w * 0.1f;
QCanvasGridPattern gp3(cp2.x(), cp2.y(), g1, g1);
QColor color1(anim * 150, 80, 40);
gp3.setLineColor(color1);
gp3.setBackgroundColor("#202020");
gp3.setLineWidth(g1 * 0.5f);
gp3.setFeather(g1 * 0.5f);
gp3.setRotation(anim);
painter->setFillStyle(gp3);
painter->fillRect(rect2);
posY += h + margin;
posX = margin/2;
QRectF rect3(posX, posY, w, h);
QCanvasGridPattern gp4;
float bar = w * 0.1f;
gp4.setStartPosition(15 * anim, 0);
gp4.setCellSize(bar, 0);
gp4.setLineWidth(bar * 0.5f);
gp4.setRotation(float(M_PI) / 4);
QCanvasGridPattern gp5;
gp5.setCellSize(0, 4);
gp5.setBackgroundColor("#202020");
gp5.setLineColor("#404040");
painter->setStrokeStyle(gp4);
painter->setFillStyle(gp5);
painter->setLineWidth(0.5f * bar);
painter->beginPath();
painter->roundRect(rect3, bar);
painter->fill();
painter->stroke();
posX += w + margin;
QRectF rect4(posX, posY, w, h);
QPointF cp4 = rect4.center();
QCanvasGridPattern gp6;
float strokeW = 10 * anim;
gp6.setLineColor(Qt::transparent);
gp6.setBackgroundColor(Qt::white);
gp6.setStartPosition(cp4.x(), cp4.y());
gp6.setCellSize(rect4.width() / 5, rect4.height() / 5);
gp6.setLineWidth(strokeW);
painter->setStrokeStyle(gp6);
painter->setLineWidth(4);
painter->beginPath();
painter->roundRect(rect4, 10);
painter->stroke();
}
}
void CanvasPainterLancelotCppTests::testShadows()
{
float topMargin = height() * 0.02f;
float anim = 0.3f;
{
int rects = 3;
float margin = width() * 0.15f;
float w = width() / rects - margin;
float h = w;
float posX = margin/2;
float posY = topMargin + margin * 0.5;
QRectF rect1(posX, posY, w, h);
QCanvasBoxShadow bs1(rect1);
painter->drawBoxShadow(bs1);
rect1.translate(w + margin, 0);
bs1.setRect(rect1);
bs1.setBlur(0.15 * w);
painter->drawBoxShadow(bs1);
rect1.translate(w + margin, 0);
bs1.setRect(rect1);
bs1.setBlur(0.3 * w);
painter->drawBoxShadow(bs1);
posY += h + margin * 0.5;
QRectF rect2(posX, posY, w, h);
QCanvasBoxShadow bs2(rect2);
bs2.setRadius(h/2);
painter->drawBoxShadow(bs2);
rect2.translate(w + margin, 0);
bs2.setRect(rect2);
bs2.setBlur(0.15 * w);
painter->drawBoxShadow(bs2);
rect2.translate(w + margin, 0);
bs2.setRect(rect2);
bs2.setBlur(0.3 * w);
painter->drawBoxShadow(bs2);
posY += h + margin * 0.5;
QRectF rect3(posX, posY, w, h);
QCanvasBoxShadow bs3(rect3);
bs3.setBlur(h/8);
bs3.setColor("#393E46");
bs3.setTopLeftRadius(h/4 + h/4 * sin(anim));
bs3.setTopRightRadius(h/4 + h/4 * sin(anim + 0.5 * M_PI));
bs3.setBottomLeftRadius(h/4 + h/4 * sin(anim + 1.0 * M_PI));
bs3.setBottomRightRadius(h/4 + h/4 * sin(anim + 1.5 * M_PI));
painter->drawBoxShadow(bs3);
rect3.translate(w + margin, 0);
bs3.setColor("#ffffff");
bs3.setRect(rect3);
painter->drawBoxShadow(bs3);
rect3.translate(w + margin, 0);
bs3.setColor("#40ffff00");
bs3.setRect(rect3);
painter->drawBoxShadow(bs3);
}
}
void CanvasPainterLancelotCppTests::testShadows2()
{
float topMargin = height() * 0.02f;
float anim = 0.3f;
{
int rects = 3;
float margin = width() * 0.15f;
float w = width() / rects - margin;
float h = w;
float posX = margin/2;
float posY = topMargin + margin * 0.5;
posY += h + margin * 0.5;
float blur = w * 0.3;
float radius = 5 + anim * (h / 2 - 5);
float shadowOffsetX = blur * 0.3;
float shadowOffsetY = blur * 0.3;
QRectF buttonRect(posX, posY, w, h);
// Double shadows
QRectF shadow1Rect = buttonRect.translated(-shadowOffsetX, shadowOffsetY);
QCanvasBoxShadow shadow;
QColor c("#222831");
QColor cl = c.lighter(200);
QColor cd = c.darker(250);
cl.setAlphaF(0.3f);
cd.setAlphaF(0.8f);
shadow.setBlur(blur);
shadow.setRadius(radius);
shadow.setRect(shadow1Rect);
shadow.setColor(cd);
painter->drawBoxShadow(shadow);
QRectF shadow2Rect = buttonRect.translated(shadowOffsetX, -shadowOffsetY);
shadow.setRect(shadow2Rect);
shadow.setColor(cl);
painter->drawBoxShadow(shadow);
// Button on top of shadows
painter->beginPath();
painter->roundRect(buttonRect, radius);
painter->setFillStyle(c);
painter->fill();
buttonRect.translate(w + margin, 0);
shadow1Rect.translate(w + margin, 0);
shadow2Rect.translate(w + margin, 0);
radius = h / 8;
shadow.setRadius(radius);
shadow.setBlur(h/16 + anim * h/4);
// Double shadows
shadow.setRect(shadow1Rect);
shadow.setColor(cd);
painter->drawBoxShadow(shadow);
shadow.setRect(shadow2Rect);
shadow.setColor(cl);
painter->drawBoxShadow(shadow);
// Button on top of shadows
painter->beginPath();
painter->roundRect(buttonRect, radius);
painter->setFillStyle(c);
painter->fill();
buttonRect.translate(w + margin, 0);
shadow1Rect.translate(w + margin, 0);
shadow2Rect.translate(w + margin, 0);
radius = h / 8;
shadow.setRadius(radius);
shadow.setBlur(h * 0.2);
// Double shadows
float t1 = shadowOffsetX * sin(anim);
shadow1Rect.translate(t1, -t1);
shadow.setRect(shadow1Rect);
shadow.setColor(cd);
painter->drawBoxShadow(shadow);
float t2 = shadowOffsetX * sin(anim + M_PI);
shadow2Rect.translate(t2, -t2);
shadow.setRect(shadow2Rect);
shadow.setColor(cl);
painter->drawBoxShadow(shadow);
// Button on top of shadows
painter->beginPath();
painter->roundRect(buttonRect, radius);
painter->setFillStyle(c);
painter->fill();
}
}
void CanvasPainterLancelotCppTests::drawCompositeItem1(float x, float y, float w, float h, QCanvasPainter::CompositeOperation mode)
{
float anim = 0.5f;
painter->setGlobalCompositeOperation(QCanvasPainter::CompositeOperation::SourceOver);
// First item
painter->beginPath();
painter->roundRect(x, y, w, h * 0.7, w * 0.2);
painter->setFillStyle("#DFD0B8");
painter->fill();
painter->setGlobalCompositeOperation(mode);
// Second item
painter->beginPath();
painter->circle(x + w * 0.5f, y + h * (0.6f + 0.4f * anim), w * 0.4f);
painter->setFillStyle("#948979");
painter->fill();
}
void CanvasPainterLancelotCppTests::drawCompositeItem2(float x, float y, float w, float h, QCanvasPainter::CompositeOperation mode)
{
float anim = 0.5f;
painter->setGlobalCompositeOperation(QCanvasPainter::CompositeOperation::SourceOver);
painter->setLineWidth(w * 0.05f);
// First item
painter->beginPath();
painter->roundRect(x, y, w, h * 0.7, w * 0.2);
painter->setFillStyle("#DFD0B8");
painter->fill();
painter->setStrokeStyle("#ffffff");
painter->stroke();
painter->setGlobalCompositeOperation(mode);
// Second item
painter->beginPath();
painter->circle(x + w * 0.5f, y + h * (0.6f + 0.4f * anim), w * 0.4f);
painter->setFillStyle("#948979");
painter->fill();
painter->setStrokeStyle("#000000");
painter->stroke();
}
void CanvasPainterLancelotCppTests::drawCompositeItem3(float x, float y, float w, float h, QCanvasPainter::CompositeOperation mode)
{
float anim = 0.5f;
painter->setGlobalCompositeOperation(QCanvasPainter::CompositeOperation::SourceOver);
painter->setLineWidth(w * 0.05f);
painter->setAntialias(10.0f);
// First item
painter->beginPath();
painter->roundRect(x, y, w, h * 0.7, w * 0.2);
painter->setFillStyle("#DFD0B8");
painter->fill();
painter->setStrokeStyle("#ffffff");
painter->stroke();
painter->setGlobalCompositeOperation(mode);
// Second item
painter->setGlobalAlpha(0.5);
painter->beginPath();
painter->circle(x + w * 0.5f, y + h * (0.6f + 0.4f * anim), w * 0.4f);
painter->setFillStyle("#948979");
painter->fill();
painter->setStrokeStyle("#000000");
painter->stroke();
painter->setGlobalAlpha(1.0f);
}
void CanvasPainterLancelotCppTests::testCompositeModes()
{
float topMargin = height() * 0.02f;
painter->clearRect(0, 0, width(), height());
painter->setGlobalAlpha(0.7f);
{
int rects = 3;
float margin = width() * 0.1f;
float w = width() / rects - margin;
float h = w * 1.2;
float posX = margin/2;
float posY = topMargin;
drawCompositeItem1(posX, posY, w, h, QCanvasPainter::CompositeOperation::SourceOver);
posX += w + margin;
drawCompositeItem1(posX, posY, w, h, QCanvasPainter::CompositeOperation::SourceAtop);
posX += w + margin;
drawCompositeItem1(posX, posY, w, h, QCanvasPainter::CompositeOperation::DestinationOut);
posY += h + margin;
posX = margin/2;
drawCompositeItem2(posX, posY, w, h, QCanvasPainter::CompositeOperation::SourceOver);
posX += w + margin;
drawCompositeItem2(posX, posY, w, h, QCanvasPainter::CompositeOperation::SourceAtop);
posX += w + margin;
drawCompositeItem2(posX, posY, w, h, QCanvasPainter::CompositeOperation::DestinationOut);
posY += h + margin;
posX = margin/2;
drawCompositeItem3(posX, posY, w, h, QCanvasPainter::CompositeOperation::SourceOver);
posX += w + margin;
drawCompositeItem3(posX, posY, w, h, QCanvasPainter::CompositeOperation::SourceAtop);
posX += w + margin;
drawCompositeItem3(posX, posY, w, h, QCanvasPainter::CompositeOperation::DestinationOut);
}
painter->setGlobalAlpha(1.0f);
}
void CanvasPainterLancelotCppTests::testImages()
{
float topMargin = height() * 0.02f;
float anim = 0.1f;
QCanvasImage patternImage2 = painter->addImage(QImage(":/images/pattern2.png"),
QCanvasPainter::ImageFlag::Repeat | QCanvasPainter::ImageFlag::GenerateMipmaps);
QCanvasImage testImage = painter->addImage(QImage(":/images/qt_development_white.png"));
QCanvasImage image3Gray = painter->addImage(QImage(":/images/face-smile-bw.png"));
{
float margin = height()*0.05f;
float posY = topMargin;
QRectF rect1(0, 0, width(), height());
auto pattern = QCanvasImagePattern(patternImage2);
pattern.setTintColor(QColor(10, 10, 10));
painter->setFillStyle(pattern);
painter->fillRect(rect1);
// Draw image scaled to view width
float imageRatio = float(testImage.height()) / testImage.width();
float imgX = margin;
float imgW = width() - 2 * margin;
QRectF imgRect(imgX, posY, imgW, imgW * imageRatio);
painter->drawImage(testImage, imgRect);
// Draw just the "Development" part of the image
posY += imgRect.height() + margin;
float cropW = 105;
QRectF sourceArea(cropW, 0, testImage.width() - cropW, testImage.height());
float cx = width() * 0.5f;
float r = width() * 0.1f + (anim * width() * 0.2f);
QRectF targetArea(cx-r, posY, r*2, width()*0.1);
painter->drawImage(testImage, sourceArea, targetArea);
// Draw rotating & scaling & tinted image
posY += targetArea.height() + height() * 0.15;
QRectF rect(cx-r * 0.5f, posY - r * 0.5f, r, r);
QPointF c(rect.x()+rect.width() * 0.5f, rect.y()+rect.height() * 0.5f);
painter->save();
painter->translate(c);
painter->rotate(anim);
painter->translate(-c);
QColor tintColor(255, 255, 255 - anim * 200);
image3Gray.setTintColor(tintColor);
painter->drawImage(image3Gray,rect);
painter->restore();
}
}
void CanvasPainterLancelotCppTests::testImages2()
{
float topMargin = height() * 0.02f;
QCanvasImage image3Plain = painter->addImage(QImage(":/images/pattern2.png"));
QCanvasImage image3Nearest = painter->addImage(QImage(":/images/pattern2.png"), QCanvasPainter::ImageFlag::Nearest);
QCanvasImage image3Mips = painter->addImage(QImage(":/images/pattern2.png"), QCanvasPainter::ImageFlag::GenerateMipmaps);
QCanvasImage image3NearestMips = painter->addImage(QImage(":/images/pattern2.png"),
QCanvasPainter::ImageFlag::Nearest | QCanvasPainter::ImageFlag::GenerateMipmaps);
{
float posY = topMargin;
float cx = width() * 0.5f;
QRectF rect1(0, 0, width(), height());
painter->setFillStyle(Qt::gray);
painter->fillRect(rect1);
// Scaled images with and without mipmapping
posY = height() * 0.15f;
float posY2 = posY + (height() * 0.3f);
bool useNearest = false;
{
float posX = width()*0.1f;
float sizeDiff = width() * 0.025f;
float centerY = 4 * sizeDiff;
for (int i = 0; i < 7; ++i) {
float size = (i + 1) * sizeDiff;
QRectF rect2(posX, posY+centerY, size, size);
painter->drawImage(useNearest ? image3Nearest : image3Plain, rect2);
QRectF rect3(posX, posY2+centerY, size, size);
painter->drawImage(useNearest ? image3NearestMips : image3Mips, rect3);
posX += size + 2;
}
QFont font;
float fontSize = width() * 0.04f;
font.setPixelSize(fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
QString scaling = useNearest ? "NEAREST" : "LINEAR";
QString offString = QString("MIPMAPS: OFF, TEXTURE: %1").arg(scaling);
QString onString = QString("MIPMAPS: ON, TEXTURE: %1").arg(scaling);
float text1PosY = posY + 0.5f * centerY;
float text2PosY = posY2 + 0.5f * centerY;
painter->setFillStyle(QColorConstants::White);
painter->setFont(font);
painter->fillText(offString, cx, text1PosY);
painter->fillText(onString, cx, text2PosY);
}
}
}
void CanvasPainterLancelotCppTests::testImages3()
{
float topMargin = height() * 0.02f;
QCanvasImage image3Plain = painter->addImage(QImage(":/images/pattern2.png"));
QCanvasImage image3Nearest = painter->addImage(QImage(":/images/pattern2.png"), QCanvasPainter::ImageFlag::Nearest);
QCanvasImage image3Mips = painter->addImage(QImage(":/images/pattern2.png"), QCanvasPainter::ImageFlag::GenerateMipmaps);
QCanvasImage image3NearestMips = painter->addImage(QImage(":/images/pattern2.png"),
QCanvasPainter::ImageFlag::Nearest | QCanvasPainter::ImageFlag::GenerateMipmaps);
{
float posY = topMargin;
float cx = width() * 0.5f;
QRectF rect1(0, 0, width(), height());
painter->setFillStyle(Qt::gray);
painter->fillRect(rect1);
// Scaled images with and without mipmapping
posY = height() * 0.15f;
float posY2 = posY + (height() * 0.3f);
bool useNearest = true;
{
float posX = width()*0.1f;
float sizeDiff = width() * 0.025f;
float centerY = 4 * sizeDiff;
for (int i = 0; i < 7; ++i) {
float size = (i + 1) * sizeDiff;
QRectF rect2(posX, posY+centerY, size, size);
painter->drawImage(useNearest ? image3Nearest : image3Plain, rect2);
QRectF rect3(posX, posY2+centerY, size, size);
painter->drawImage(useNearest ? image3NearestMips : image3Mips, rect3);
posX += size + 2;
}
QFont font;
float fontSize = width() * 0.04f;
font.setPixelSize(fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
QString scaling = useNearest ? "NEAREST" : "LINEAR";
QString offString = QString("MIPMAPS: OFF, TEXTURE: %1").arg(scaling);
QString onString = QString("MIPMAPS: ON, TEXTURE: %1").arg(scaling);
float text1PosY = posY + 0.5f * centerY;
float text2PosY = posY2 + 0.5f * centerY;
painter->setFillStyle(QColorConstants::White);
painter->setFont(font);
painter->fillText(offString, cx, text1PosY);
painter->fillText(onString, cx, text2PosY);
}
}
}
void CanvasPainterLancelotCppTests::testHighDpiImages()
{
// QImageReader sets a device pixel ratio of N on images whose file name
// ends with an "@Nx" suffix, unless disabled by an env.var.. QCanvasImage
// carries that ratio over and uses it e.g. for drawImage() without an
// explicit size. (by using pixel_width / dpr as width etc.)
QCanvasImage logo1x = painter->addImage(QImage(":/images/qt_development_white.png"));
QCanvasImage logo2x = painter->addImage(QImage(":/images/qt_development_white@2x.png"));
const auto repeatFlags = QCanvasPainter::ImageFlag::Repeat;
QCanvasImage pattern1x = painter->addImage(QImage(":/images/pattern2.png"), repeatFlags);
QCanvasImage pattern2x = painter->addImage(QImage(":/images/pattern2@2x.png"), repeatFlags);
if (logo2x.size() != logo1x.size() || !qFuzzyCompare(logo2x.devicePixelRatio(), 2.0)) {
qWarning("Something is wrong with @2 image loading");
painter->setFillStyle(Qt::red);
painter->fillRect(0, 0, width(), height());
return;
}
painter->setFillStyle(Qt::darkGray);
painter->fillRect(0, 0, width(), height());
QFont font;
font.setPixelSize(15);
painter->setFont(font);
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
auto label = [&](const QString &text, float x, float y) {
painter->setFillStyle(Qt::white);
painter->fillText(text, x, y);
};
label("drawImage(img, x, y): @1x (top) then @2x renders at half size", 20, 30);
painter->drawImage(logo1x, 20, 40);
painter->drawImage(logo2x, 20, 130);
label("drawImage(img, x, y, w, h): explicit size, @1x and @2x identical", 20, 220);
painter->drawImage(pattern1x, 20, 235, 110, 110);
painter->drawImage(pattern2x, 150, 235, 110, 110);
label("image pattern default tile size: @1x (left) then @2x (denser)", 20, 380);
painter->setFillStyle(QCanvasImagePattern(pattern1x));
painter->fillRect(20, 395, 360, 180);
painter->setFillStyle(QCanvasImagePattern(pattern2x));
painter->fillRect(400, 395, 360, 180);
label("drawImage(img, srcRect, dstRect): @1x and @2x identical", 20, 620);
const QRectF src(110, 0, 232, 72); // the "Development" part, in device pixels
painter->drawImage(logo1x, src, QRectF(20, 635, 360, 110));
painter->drawImage(logo2x, src, QRectF(400, 635, 360, 110));
}
void CanvasPainterLancelotCppTests::testAntialiasing()
{
float topMargin = height() * 0.02f;
float anim = 0.1f;
{
int rects = 3;
float margin = std::floor(width() * 0.1f);
float w = std::floor(width() / (rects) - margin);
float posX = margin/2;
float lineWidth = width()*0.01f;
float posY = std::floor(topMargin);
painter->setLineCap(QCanvasPainter::LineCap::Round);
painter->setStrokeStyle(Qt::black);
painter->setLineWidth(lineWidth);
painter->setAntialias(0);
painter->beginPath();
painter->roundRect(posX, posY, w, w, w/4);
painter->stroke();
posX += w + margin;
painter->setAntialias(1.0f);
painter->beginPath();
painter->roundRect(posX, posY, w, w, w/4);
painter->stroke();
posX += w + margin;
painter->setAntialias(1.0f + anim*8);
painter->beginPath();
painter->roundRect(posX, posY, w, w, w/4);
painter->stroke();
posY += w+margin;
posX = margin/2;
int lines = 12;
painter->setAntialias(0);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
// Align to improve thin lines painting
painter->moveTo(int(posX), int(posY+i*w/lines));
painter->lineTo(int(posX+w), int(posY+i*w/lines));
painter->stroke();
}
posX += w + margin;
painter->setAntialias(1.0f);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(posX, posY+i*w/lines);
painter->lineTo(posX+w, posY+i*w/lines);
painter->stroke();
}
posX += w + margin;
painter->setAntialias(1.0f + anim*5);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(posX, posY+i*w/lines);
painter->lineTo(posX+w, posY+i*w/lines);
painter->stroke();
}
posY += w+margin;
posX = margin/2;
painter->setAntialias(0);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(int(posX), int(posY+i*w/lines));
painter->lineTo(int(posX+w), int(posY+i*w/lines) + 5);
painter->stroke();
}
posX += w + margin;
painter->setAntialias(1.0);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(posX, posY+i*w/lines);
painter->lineTo(posX+w, posY+i*w/lines+5);
painter->stroke();
}
posX += w + margin;
painter->setAntialias(1.0f + anim*5);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(posX, posY+i*w/lines);
painter->lineTo(posX+w, posY+i*w/lines+5);
painter->stroke();
}
}
}
void CanvasPainterLancelotCppTests::testAntialiasing2()
{
float topMargin = height() * 0.02f;
float anim = 0.1f;
{
int rects = 3;
float margin = std::floor(width() * 0.1f);
float w = std::floor(width() / (rects) - margin);
float posX = margin/2;
float posY = std::floor(topMargin);
int lines = 12;
posY += w+margin;
posX = margin/2;
posY += anim*4;
painter->setAntialias(0);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(int(posX), int(posY+i*w/lines));
painter->lineTo(int(posX+w), int(posY+i*w/lines) + 5);
painter->stroke();
}
posX += w + margin;
painter->setAntialias(1.0f);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(posX, posY+i*w/lines);
painter->lineTo(posX+w, posY+i*w/lines+5);
painter->stroke();
}
posX += w + margin;
painter->setAntialias(1.0f + anim*5);
for (int i=0 ; i<lines ; i++) {
painter->beginPath();
painter->setLineWidth((i + 1) * 0.5f);
painter->moveTo(posX, posY+i*w/lines);
painter->lineTo(posX+w, posY+i*w/lines+5);
painter->stroke();
}
}
}
void CanvasPainterLancelotCppTests::testTextAlignment()
{
float topMargin = height() * 0.02f;
float anim = 0.7f;
{
float margin = height() * 0.15f;
float fontSize = height() * 0.03f;
float posX = margin/2;
float posY = topMargin + margin/4;
QFont f3;
f3.setPixelSize(fontSize);
painter->setFont(f3);
// Test horizontal alignments
posX = width() * 0.5f;
painter->setFillStyle(Qt::black);
painter->fillRect(posX-1, posY-fontSize, 1, fontSize*3);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
painter->setFillStyle(Qt::gray);
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
painter->fillText("Left", posX, posY);
painter->setFillStyle(Qt::green);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->fillText("Center", posX, posY+fontSize);
painter->setFillStyle(Qt::blue);
painter->setTextAlign(QCanvasPainter::TextAlign::Right);
painter->fillText("Right", posX, posY+fontSize*2);
// Test vertical (baseline) alignments
posY += margin;
posX = width() * 0.1f;
const float spacing = width() * 0.19;
painter->setFillStyle(Qt::cyan);
painter->fillRect(posX, posY-1, width()*0.8f, 1);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setFillStyle(Qt::gray);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
painter->fillText("Top", posX, posY);
painter->setFillStyle(Qt::red);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Hanging);
painter->fillText("Hanging", posX+spacing, posY);
painter->setFillStyle(Qt::green);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
painter->fillText("Middle", posX + 2 * spacing, posY);
painter->setFillStyle(Qt::blue);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Alphabetic);
painter->fillText("Alphabetic", posX + 3 * spacing, posY);
painter->setFillStyle(Qt::darkYellow);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
painter->fillText("Bottom", posX + 4 * spacing, posY);
// Test horizontal alignments with rect
posY += margin;
QRectF r1(width() * 0.5f, posY, 0, fontSize);
float animX = width() * 0.4f * anim;
r1.adjust(-animX, 0, animX, 0);
painter->setStrokeStyle("#549221");
painter->strokeRect(r1);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
painter->setFillStyle(Qt::black);
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
painter->fillText("Left", r1);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->fillText("Center", r1);
painter->setTextAlign(QCanvasPainter::TextAlign::Right);
painter->fillText("Right", r1);
// Test vertical (baseline) alignments with rect
posY += margin;
posX = width() * 0.1f;
float rectW = width() * 0.8f;
QRectF r2(posX, posY, rectW, margin * anim);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(r2);
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Alphabetic);
painter->fillText("Left & Alphabetic", r2);
painter->setTextAlign(QCanvasPainter::TextAlign::Right);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
painter->fillText("Right & Bottom", r2);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
painter->fillText("Center & Middle", r2);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
painter->fillText("Center & Top", r2);
posY += margin;
painter->strokeRect(posX, posY, rectW, 8.5 * fontSize);
painter->setTextDirection(QCanvasPainter::TextDirection::LeftToRight);
QRectF r3(posX, posY, rectW, fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::Start);
painter->fillText("LeftToRight: Start-aligned", r3);
posY += fontSize;
QRectF r4(posX, posY, rectW, fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::End);
painter->fillText("LeftToRight: End-aligned", r4);
painter->setTextDirection(QCanvasPainter::TextDirection::RightToLeft);
posY += fontSize*2;
QRectF r5(posX, posY, rectW, fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::Start);
painter->fillText("RightToLeft: Start-aligned", r5);
posY += fontSize;
QRectF r6(posX, posY, rectW, fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::End);
painter->fillText("RightToLeft: End-aligned", r6);
// Autodetect direction from the text string.
painter->setTextDirection(QCanvasPainter::TextDirection::Auto);
posY += fontSize*2;
QRectF r7(posX, posY, rectW, fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::Start);
painter->fillText("English", r7);
posY += fontSize;
QRectF r8(posX, posY, rectW, fontSize);
painter->setTextAlign(QCanvasPainter::TextAlign::Start);
painter->fillText("خامل", r8);
painter->setTextDirection(QCanvasPainter::TextDirection::LeftToRight);
}
}
void CanvasPainterLancelotCppTests::testTextWrapping()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
{
float margin = height() * 0.02f;
float fontSize = height() * 0.03f;
float posX = margin + (anim * width() * 0.2f);
float posY = topMargin;
float rectW = (width() - 2 * margin) - (anim * width() * 0.4f);
float rectH = height() * 0.18;
QFont f3;
f3.setPixelSize(fontSize);
painter->setFont(f3);
// Test text alignment and line wrapping
QString s("This is a test string which is slightly longer one. Yes, we want long string for this.");
painter->setFillStyle(Qt::black);
painter->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
painter->setTextLineHeight(-10);
QRectF rect1(posX, posY, rectW, rectH);
painter->setStrokeStyle(0xFF393E46);
painter->strokeRect(rect1);
painter->fillText(s, rect1);
QRectF boundingRect1 = painter->textBoundingBox(s, rect1);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(boundingRect1);
posY += rectH + margin;
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
painter->setTextLineHeight(0);
QRectF rect2(posX, posY, rectW, rectH);
painter->setStrokeStyle(0xFF393E46);
painter->strokeRect(rect2);
painter->fillText(s, rect2);
QRectF boundingRect2 = painter->textBoundingBox(s, rect2);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(boundingRect2);
posY += rectH + margin;
painter->setTextAlign(QCanvasPainter::TextAlign::Right);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Bottom);
painter->setTextLineHeight(10);
QRectF rect3(posX, posY, rectW, rectH);
painter->setStrokeStyle(0xFF393E46);
painter->strokeRect(rect3);
painter->fillText(s, rect3);
QRectF boundingRect3 = painter->textBoundingBox(s, rect3);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(boundingRect3);
posY += 2.0 * rectH + margin;
posX = 0.5f * width();
QFont f4;
f4.setPixelSize(width() * 0.04f);
painter->setFont(f4);
painter->setTextLineHeight(0);
painter->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
float circleRadius = width() * 0.1f * (1.0 + anim);
float textMargin = width() * 0.04f;
float textW = 2 * (circleRadius - textMargin);
QString text = QStringLiteral("Circle radius: %1").arg(qRound(circleRadius));
painter->fillText(text, posX, posY, textW);
painter->beginPath();
painter->setLineWidth(1);
painter->circle(posX, posY, circleRadius);
painter->setStrokeStyle(0xFF393E46);
painter->stroke();
QRectF boundingRect4 = painter->textBoundingBox(text, posX, posY, textW);
painter->setStrokeStyle(0xFFDFD0B8);
painter->strokeRect(boundingRect4);
}
}
void CanvasPainterLancelotCppTests::testTextWrapping2()
{
QFont font1;
painter->setFillStyle(QColorConstants::Black);
painter->setLineWidth(2);
QList<QCanvasPainter::TextAlign> aligns {
QCanvasPainter::TextAlign::Left,
QCanvasPainter::TextAlign::Center,
QCanvasPainter::TextAlign::Right,
};
for (int i = 0; i < 3; i++) {
float posX = 50;
float posY = i * 250;
font1.setPixelSize(12 + i * 5);
font1.setWordSpacing(10 - i * 5);
font1.setItalic(i == 1);
font1.setWeight(i == 2 ? QFont::Weight::ExtraBold : QFont::Weight::Normal);
painter->setFont(font1);
for (auto a : aligns) {
painter->setTextAlign(a);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
painter->setTextWrapMode(QCanvasPainter::WrapMode::NoWrap);
QString text("Qt Canvas Painter");
QPointF p1(posX, posY + 20);
painter->fillText(text, p1);
painter->strokeRect(painter->textBoundingBox(text, p1));
painter->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
QPointF p2(posX, posY + 90);
painter->fillText(text, p2);
painter->strokeRect(painter->textBoundingBox(text, p2));
QPointF p3(posX, posY + 190);
painter->fillText(text, p3, 100);
painter->strokeRect(painter->textBoundingBox(text, p3, 100));
posX += 350;
}
}
}
void CanvasPainterLancelotCppTests::testTextBrushes()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
QCanvasPainter::ImageFlags flags = QCanvasPainter::ImageFlag::Repeat | QCanvasPainter::ImageFlag::GenerateMipmaps;
QCanvasImage patternImage2 = painter->addImage(QImage(":/images/pattern2.png"), flags);
{
float margin = height() * 0.04f;
float posX = margin * 0.5f;
float posY = topMargin + margin + 0.1f;
float fontSize1 = width() * 0.09f;
float fontSize2 = width() * 0.13f;
QFont f;
f.setPixelSize(fontSize1);
painter->setFont(f);
float w = width();
QCanvasLinearGradient g1(0, 0, 0, 0);
g1.setStartColor(QColor(100,150,100));
g1.setEndColor(QColor(0,0,0));
g1.setStartPosition(posX + (w*0.5f * anim), 0);
g1.setEndPosition(posX + w + 1 - (w*0.5f * anim), 0);
painter->setFillStyle(g1);
painter->fillText("Text with linear gradient", posX, posY);
posY += margin + fontSize1;
float h = 80;
QCanvasRadialGradient g2;
g2.setStartColor(QColor(100, 100, 20, 255));
g2.setColorAt(0.5, QColor(140, 50, 20, 255));
g2.setEndColor(QColor(0,0,0,0));
g2.setCenterPosition(posX + w/2, posY + h/2);
g2.setOuterRadius(0.6*w*anim);
g2.setInnerRadius(0.1*w*anim);
painter->setFillStyle(g2);
painter->fillText("Text with radial gradient", posX, posY);
posY += margin + fontSize2;
QCanvasConicalGradient g4;
g4.setCenterPosition(width() * 0.5, posY - fontSize2 * 0.5);
g4.setStartAngle(anim);
g4.setStartColor(QColor(255, 255, 0, 255));
g4.setColorAt(0.25, QColor(0, 255, 0, 255));
g4.setColorAt(0.5, QColor(0, 0, 255, 255));
g4.setColorAt(0.75, QColor(255, 0, 255, 255));
g4.setEndColor(QColor(255, 255, 0, 255));
painter->setFillStyle(g4);
f.setPixelSize(fontSize2);
painter->setFont(f);
painter->fillText("MULTIGRADIENT", posX, posY);
painter->setFillStyle(Qt::black);
painter->fillRect(0, posY, width(), height() - posY);
posY += margin + fontSize2;
QCanvasImagePattern p1 = QCanvasImagePattern(patternImage2);
p1.setImageSize(64, 64);
p1.setStartPosition(0, anim * 10);
painter->setFillStyle(p1);
painter->fillText("IMAGE PATTERN", posX, posY);
posY += margin + fontSize2;
QCanvasGridPattern p2;
p2.setStartPosition(anim * 10, 0);
p2.setLineWidth(2);
p2.setCellSize(4, 4);
p2.setLineColor("#00414A");
p2.setBackgroundColor("#2CDE85");
painter->setFillStyle(p2);
painter->fillText("GRID PATTERN", posX, posY);
}
}
void CanvasPainterLancelotCppTests::testTextCustomBrushes()
{
float margin = height() * 0.04f;
float fontSize1 = width() * 0.09f;
float fontSize2 = width() * 0.13f;
// Light gray background so the glow brush's transparent regions are visible.
painter->setFillStyle(Qt::white);
painter->fillRect(0, 0, width(), height());
QCanvasCustomBrush wobbleBrush;
wobbleBrush.setVertexShader(":/wobblebrush.vert.qsb");
wobbleBrush.setFragmentShader(":/wobblebrush.frag.qsb");
QCanvasCustomBrush glowBrush;
glowBrush.setFragmentShader(":/glowbrush.frag.qsb");
glowBrush.setData1(QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
// Four lines (fontSize1, fontSize2, fontSize1, fontSize2) with three
// inter-line margins, centered both horizontally and vertically.
float totalHeight = 2.0f * fontSize1 + 2.0f * fontSize2 + 3.0f * margin;
float posX = width() * 0.5f;
float posY = (height() - totalHeight) * 0.5f + fontSize1 * 0.5f;
QFont f;
f.setPixelSize(fontSize1);
painter->setFont(f);
painter->setFillStyle(wobbleBrush);
painter->fillText("Custom Brush", posX, posY);
posY += fontSize1 * 0.5f + margin + fontSize2 * 0.5f;
f.setPixelSize(fontSize2);
painter->setFont(f);
painter->setFillStyle(wobbleBrush);
painter->fillText("WOBBLE", posX, posY);
posY += fontSize2 * 0.5f + margin + fontSize1 * 0.5f;
f.setPixelSize(fontSize1);
painter->setFont(f);
painter->setFillStyle(glowBrush);
painter->fillText("Text with some glowing", posX, posY);
posY += fontSize1 * 0.5f + margin + fontSize2 * 0.5f;
f.setPixelSize(fontSize2);
painter->setFont(f);
painter->setFillStyle(glowBrush);
painter->fillText("GLOWING", posX, posY);
}
void CanvasPainterLancelotCppTests::testTextFonts()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
{
float margin = height() * 0.04f;
float posX = margin/2;
float posY = topMargin;
painter->setFillStyle("#123321");
painter->fillText("This is text without setting any font", posX, posY);
posY += margin;
QFont f;
painter->setFillStyle("#13dc6d");
f.setPixelSize(20);
painter->setFont(f);
painter->fillText("Size 20px", posX, posY);
posY += margin;
f.setPointSize(20);
painter->setFont(f);
painter->fillText("Size 20pt", posX, posY);
posY += margin;
painter->setFillStyle("#17367e");
painter->setTextAntialias(1.0 + anim * 5.0);
f.setPixelSize(14);
painter->setFont(f);
painter->fillText("Size 14px, blurring", posX, posY);
posY += margin;
f.setPixelSize(24);
painter->setFont(f);
painter->fillText("Size 24px, blurring", posX, posY);
posY += 1.3 * margin;
f.setPixelSize(34);
painter->setFont(f);
painter->fillText("Size 34px, blurring", posX, posY);
painter->setTextAntialias(1.0);
posY += margin;
painter->setFillStyle("#8c1cb8");
f.setPixelSize(margin * 0.8);
f.setWeight(QFont::Weight::Light);
f.setItalic(false);
painter->setFont(f);
posY += margin;
painter->fillText("Style: Light", posX, posY);
f.setItalic(true);
painter->setFont(f);
posY += margin;
painter->fillText("Style: Light Italic", posX, posY);
f.setWeight(QFont::Weight::Normal);
f.setItalic(false);
painter->setFont(f);
posY += margin;
painter->fillText("Style: Normal", posX, posY);
f.setItalic(true);
painter->setFont(f);
posY += margin;
painter->fillText("Style: Normal Italic", posX, posY);
f.setWeight(QFont::Weight::Bold);
f.setItalic(false);
painter->setFont(f);
posY += margin;
painter->fillText("Style: Bold", posX, posY);
f.setItalic(true);
painter->setFont(f);
posY += margin;
painter->fillText("Style: Bold Italic", posX, posY);
f.setItalic(false);
f.setCapitalization(QFont::SmallCaps);
painter->setFont(f);
posY += margin;
painter->fillText("Style: SmallCaps", posX, posY);
posY += 2*margin;
QFont f2("Pacifico", margin);
painter->setFont(f2);
painter->fillText("This is text with custom font", posX, posY);
posY += 2*margin;
QFont f5;
f5.setPixelSize(margin*0.8f);
f5.setLetterSpacing(QFont::AbsoluteSpacing, 6 * anim - 2);
painter->setFont(f5);
painter->setFillStyle("#c11b63");
painter->fillText("This is text with varying letter spacing", posX, posY);
posY += 2*margin;
f5.setLetterSpacing(QFont::AbsoluteSpacing, 0);
f5.setWordSpacing(20 * anim - 5);
painter->setFont(f5);
painter->fillText("This is text with varying word spacing", posX, posY);
}
}
void CanvasPainterLancelotCppTests::testTextDecorations()
{
painter->setFillStyle(Qt::black);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Alphabetic);
struct Variant { const char *label; bool u; bool o; bool s; };
const Variant variants[] = {
{ "plain", false, false, false },
{ "underline", true, false, false },
{ "overline", false, true, false },
{ "strikeout", false, false, true },
{ "u+s", true, false, true },
{ "u+o+s", true, true, true },
};
// Several sizes including 12pt — the size that exposed the strikeout
// pixel-snapping bug fixed alongside this test.
const int sizes[] = { 8, 10, 12, 14, 18, 24, 32 };
const float colX = 10.0f;
const float colWidth = width() / float(std::size(variants));
float posY = 24.0f;
for (int size : sizes) {
for (size_t i = 0; i < std::size(variants); ++i) {
const Variant &v = variants[i];
QFont f;
f.setPixelSize(size);
f.setUnderline(v.u);
f.setOverline(v.o);
f.setStrikeOut(v.s);
painter->setFont(f);
painter->fillText(QString::fromLatin1("%1 %2px").arg(QLatin1String(v.label)).arg(size),
colX + i * colWidth, posY);
}
posY += size + 12.0f;
}
// Verify decorations interact correctly with bold/italic and colored fills.
posY += 16.0f;
const struct { QFont::Weight w; bool italic; bool u; bool o; bool s; QColor color; const char *label; } styled[] = {
{ QFont::Weight::Bold, false, false, false, true, Qt::black, "bold strikeout" },
{ QFont::Weight::Normal, true, true, false, false, Qt::black, "italic underline" },
{ QFont::Weight::Bold, true, true, true, true, QColor(180, 40, 40), "bold+italic all, red" },
{ QFont::Weight::Normal, false, false, true, false, QColor( 40, 90, 180), "overline, blue" },
};
for (const auto &row : styled) {
QFont f;
f.setPixelSize(16);
f.setWeight(row.w);
f.setItalic(row.italic);
f.setUnderline(row.u);
f.setOverline(row.o);
f.setStrikeOut(row.s);
painter->setFont(f);
painter->setFillStyle(row.color);
painter->fillText(QLatin1String(row.label), colX, posY);
posY += 28.0f;
}
}
void CanvasPainterLancelotCppTests::testTextDecorationsWrapping()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
float margin = height() * 0.02f;
float fontSize = height() * 0.03f;
float posX = margin + (anim * width() * 0.2f);
float posY = topMargin;
float rectW = (width() - 2 * margin) - (anim * width() * 0.4f);
float rectH = height() * 0.18f;
painter->setFillStyle(Qt::black);
painter->setTextWrapMode(QCanvasPainter::WrapMode::WordWrap);
struct Variant { const char *label; bool u; bool o; bool s; };
const Variant variants[] = {
{ "Underline wrapped: ", true, false, false },
{ "Overline wrapped: ", false, true, false },
{ "Strikeout wrapped: ", false, false, true },
{ "U+O+S wrapped: ", true, true, true },
};
const QString filler = QStringLiteral(
"this is a long test string that has to wrap onto multiple lines so we can verify decorations span every wrapped line.");
for (const Variant &v : variants) {
QFont f;
f.setPixelSize(fontSize);
f.setUnderline(v.u);
f.setOverline(v.o);
f.setStrikeOut(v.s);
painter->setFont(f);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Top);
painter->setTextAlign(QCanvasPainter::TextAlign::Left);
painter->setTextLineHeight(0);
QRectF rect(posX, posY, rectW, rectH);
// painter->setStrokeStyle(0xFF393E46);
// painter->strokeRect(rect);
const QString s = QLatin1String(v.label) + filler;
painter->fillText(s, rect);
// QRectF boundingRect = painter->textBoundingBox(s, rect);
// painter->setStrokeStyle(0xFFDFD0B8);
// painter->strokeRect(boundingRect);
posY += rectH + margin;
}
}
void CanvasPainterLancelotCppTests::testTextDecorationsBrushes()
{
float topMargin = height() * 0.02f;
float anim = 0.5f;
QCanvasPainter::ImageFlags flags = QCanvasPainter::ImageFlag::Repeat | QCanvasPainter::ImageFlag::GenerateMipmaps;
QCanvasImage patternImage2 = painter->addImage(QImage(":/images/pattern2.png"), flags);
float margin = height() * 0.04f;
float posX = margin * 0.5f;
float posY = topMargin + margin + 0.1f;
float fontSize1 = width() * 0.09f;
float fontSize2 = width() * 0.13f;
QFont f;
f.setPixelSize(fontSize1);
f.setUnderline(true);
f.setOverline(true);
f.setStrikeOut(true);
painter->setFont(f);
float w = width();
QCanvasLinearGradient g1(0, 0, 0, 0);
g1.setStartColor(QColor(100, 150, 100));
g1.setEndColor(QColor(0, 0, 0));
g1.setStartPosition(posX + (w * 0.5f * anim), 0);
g1.setEndPosition(posX + w + 1 - (w * 0.5f * anim), 0);
painter->setFillStyle(g1);
painter->fillText("Text with linear gradient", posX, posY);
posY += margin + fontSize1;
float h = 80;
QCanvasRadialGradient g2;
g2.setStartColor(QColor(100, 100, 20, 255));
g2.setColorAt(0.5, QColor(140, 50, 20, 255));
g2.setEndColor(QColor(0, 0, 0, 0));
g2.setCenterPosition(posX + w / 2, posY + h / 2);
g2.setOuterRadius(0.6 * w * anim);
g2.setInnerRadius(0.1 * w * anim);
painter->setFillStyle(g2);
painter->fillText("Text with radial gradient", posX, posY);
posY += margin + fontSize2;
QCanvasConicalGradient g4;
g4.setCenterPosition(width() * 0.5, posY - fontSize2 * 0.5);
g4.setStartAngle(anim);
g4.setStartColor(QColor(255, 255, 0, 255));
g4.setColorAt(0.25, QColor(0, 255, 0, 255));
g4.setColorAt(0.5, QColor(0, 0, 255, 255));
g4.setColorAt(0.75, QColor(255, 0, 255, 255));
g4.setEndColor(QColor(255, 255, 0, 255));
painter->setFillStyle(g4);
f.setPixelSize(fontSize2);
f.setUnderline(true);
f.setOverline(true);
f.setStrikeOut(true);
painter->setFont(f);
painter->fillText("MULTIGRADIENT", posX, posY);
painter->setFillStyle(Qt::black);
painter->fillRect(0, posY, width(), height() - posY);
posY += margin + fontSize2;
QCanvasImagePattern p1 = QCanvasImagePattern(patternImage2);
p1.setImageSize(64, 64);
p1.setStartPosition(0, anim * 10);
painter->setFillStyle(p1);
painter->fillText("IMAGE PATTERN", posX, posY);
posY += margin + fontSize2;
QCanvasGridPattern p2;
p2.setStartPosition(anim * 10, 0);
p2.setLineWidth(2);
p2.setCellSize(4, 4);
p2.setLineColor("#00414A");
p2.setBackgroundColor("#2CDE85");
painter->setFillStyle(p2);
painter->fillText("GRID PATTERN", posX, posY);
}
void CanvasPainterLancelotCppTests::testTextDecorationsCustomBrushes()
{
float margin = height() * 0.04f;
float fontSize1 = width() * 0.09f;
float fontSize2 = width() * 0.13f;
// Light gray background so the glow brush's transparent regions are visible.
painter->setFillStyle(Qt::white);
painter->fillRect(0, 0, width(), height());
// At the time of making this text, custom brushes are not used for text decorations.
// Instead the last color is used. This should be fixed later.
painter->setFillStyle(Qt::green);
QCanvasCustomBrush wobbleBrush;
wobbleBrush.setVertexShader(":/wobblebrush.vert.qsb");
wobbleBrush.setFragmentShader(":/wobblebrush.frag.qsb");
QCanvasCustomBrush glowBrush;
glowBrush.setFragmentShader(":/glowbrush.frag.qsb");
glowBrush.setData1(QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
painter->setTextAlign(QCanvasPainter::TextAlign::Center);
painter->setTextBaseline(QCanvasPainter::TextBaseline::Middle);
// Four lines (fontSize1, fontSize2, fontSize1, fontSize2) with three
// inter-line margins, centered both horizontally and vertically.
float totalHeight = 2.0f * fontSize1 + 2.0f * fontSize2 + 3.0f * margin;
float posX = width() * 0.5f;
float posY = (height() - totalHeight) * 0.5f + fontSize1 * 0.5f;
QFont f;
f.setPixelSize(fontSize1);
f.setUnderline(true);
f.setOverline(true);
f.setStrikeOut(true);
painter->setFont(f);
painter->setFillStyle(wobbleBrush);
painter->fillText("Custom Brush", posX, posY);
posY += fontSize1 * 0.5f + margin + fontSize2 * 0.5f;
f.setPixelSize(fontSize2);
painter->setFont(f);
painter->setFillStyle(wobbleBrush);
painter->fillText("WOBBLE", posX, posY);
posY += fontSize2 * 0.5f + margin + fontSize1 * 0.5f;
f.setPixelSize(fontSize1);
painter->setFont(f);
painter->setFillStyle(glowBrush);
painter->fillText("Text with some glowing", posX, posY);
posY += fontSize1 * 0.5f + margin + fontSize2 * 0.5f;
f.setPixelSize(fontSize2);
painter->setFont(f);
painter->setFillStyle(glowBrush);
painter->fillText("GLOWING", posX, posY);
}
void CanvasPainterLancelotCppTests::testCanvasPathWithAddPath()
{
const int personPathGroup = 5;
const int heartPathGroup = 5;
const float d = std::min(width(), height()) * 0.1f;
QCanvasPath personPath;
personPath.addPath(QStringLiteral(
"M152,84a36,36,0,1,0-36-36A36.04061,36.04061,0,0,0,152,84Zm0-48Z"
"m64,112a12.00028,12.00028,0,0,1-12,12c-37.20215,0-55.50781-19.66406"
"-70.21729-35.46484-.45459-.48828-.89794-.96192-1.34667-1.44239l-8.02393,"
"18.45484,34.5625,24.68774A11.999,11.999,0,0,1,164,176v56a12,12,0,0,1-24,"
"0V182.17578l-25.37207-18.12353L83.00488,236.78516a12.00021,12.00021,0,0,"
"1-22.00976-9.57032l37.55322-86.37255.02881-.06592,13.74463-31.61377c-"
"8.09571-.96167-18.24219,2.0072-30.35889,8.94482a159.5463,159.5463,0,0,0-"
"29.47754,22.37793,12.0001,12.0001,0,0,1-16.9707-16.9707A183.31075,"
"183.31075,0,0,1,70.03711,97.28027c36.06689-20.65234,58.03027-11.69433,"
"70.104-.54589,3.93116,3.63085,7.63037,7.60449,11.208,11.44726C164.667,"
"122.4873,177.24561,136,204,136A12.00028,12.00028,0,0,1,216,148Z"
));
painter->setLineWidth(d * 0.1f);
painter->setFillStyle(Qt::white);
painter->setStrokeStyle(Qt::black);
{
const float cx = 120.0f;
const float cy = 120.0f;
painter->translate(width() * 0.2f - cx, height() * 0.5f - cy);
painter->fill(personPath, personPathGroup);
painter->stroke(personPath, personPathGroup);
painter->resetTransform();
}
painter->setFillStyle(QColor(0xE0, 0x20, 0x20));
painter->setStrokeStyle(Qt::black);
painter->setLineWidth(d * 0.05f);
QCanvasPath heartPath;
heartPath.moveTo(0, d / 4);
heartPath.quadraticCurveTo(0, 0, d / 4, 0);
heartPath.quadraticCurveTo(d / 2, 0, d / 2, d / 4);
heartPath.quadraticCurveTo(d / 2, 0, d * 3 / 4, 0);
heartPath.quadraticCurveTo(d, 0, d, d / 4);
heartPath.quadraticCurveTo(d, d / 2, d * 3 / 4, d * 3 / 4);
heartPath.lineTo(d / 2, d);
heartPath.lineTo(d / 4, d * 3 / 4);
heartPath.quadraticCurveTo(0, d / 2, 0, d / 4);
const int items = 20;
for (int i = 0; i < items; i++) {
float posX = 0.4f * width() + 0.5f * (width() - d) * (float(i) / items);
float posY = (height() - d) * 0.5f + (height() - d) * 0.3f * std::sin(float(i) / items * 10.0f);
painter->resetTransform();
painter->translate(posX, posY);
painter->stroke(heartPath, heartPathGroup);
painter->fill(heartPath, heartPathGroup);
}
painter->resetTransform();
}
void CanvasPainterLancelotCppTests::testTiger()
{
struct SvgData {
QColor fill = QColorConstants::Transparent;
QColor stroke = QColorConstants::Transparent;
float lineWidth = 0;
QString path;
QCanvasPath canvasPath;
};
QList<SvgData> tigerData;
QFile tigerFile(":/images/tiger.json");
if (!tigerFile.open(QIODevice::ReadOnly)) {
qWarning() << "Failed to open tiger.json";
return;
}
const QJsonDocument tigerDoc = QJsonDocument::fromJson(tigerFile.readAll());
const QJsonArray json = tigerDoc["data"].toArray();
for (const QJsonValue &p : json) {
SvgData d;
const QString fillString = p["fill"].toString();
if (!fillString.isEmpty())
d.fill = QColor::fromString(fillString);
const QString strokeString = p["stroke"].toString();
if (!strokeString.isEmpty())
d.stroke = QColor::fromString(strokeString);
d.lineWidth = p["width"].toDouble();
d.path = p["path"].toString();
tigerData << d;
}
const int pathGroup = 1;
QTransform t;
t.translate(width() * 0.5f, height() * 0.5f);
const float s = std::min(width(), height()) / 1000.0f;
t.scale(s, s);
t.translate(-100, -100);
painter->setTransform(t);
for (auto &path : tigerData) {
if (path.fill != QColorConstants::Transparent) {
painter->setFillStyle(path.fill);
if (path.canvasPath.isEmpty())
path.canvasPath.addPath(path.path);
painter->fill(path.canvasPath, pathGroup);
}
if (path.stroke != QColorConstants::Transparent) {
if (path.lineWidth > 0)
painter->setLineWidth(path.lineWidth);
painter->setStrokeStyle(path.stroke);
if (path.canvasPath.isEmpty())
path.canvasPath.addPath(path.path);
painter->stroke(path.canvasPath, pathGroup);
}
}
painter->resetTransform();
}
// ----------------------------------------------------------------------------
// Tests adapted from tests/manual/paintertest
// ----------------------------------------------------------------------------
void CanvasPainterLancelotCppTests::testClipRect()
{
// Untransformed setClipRect(): adapted from testClipRectBug in paintertest.cpp.
painter->setFillStyle(Qt::red);
painter->fillRect(10, 10, 300, 100);
painter->setClipRect(100, 10, 100, 500);
painter->setFillStyle(Qt::green);
painter->fillRect(10, 150, 300, 100);
painter->resetClipping();
painter->setFillStyle(Qt::blue);
painter->fillRect(10, 290, 300, 100);
}
void CanvasPainterLancelotCppTests::testClipRectWithTransform()
{
// setClipRect() under a rotated painter, then resetClipping() and a second clip.
// Adapted from testClip in paintertest.cpp.
painter->rotate(float(M_PI) / 10);
painter->strokeRect(9, 9, 102, 102);
painter->setClipRect(10, 10, 100, 100);
painter->setFillStyle(Qt::red);
painter->fillRect(0, 0, 300, 300);
painter->rotate(float(M_PI) / 20);
painter->translate(50, 0);
painter->resetClipping();
painter->strokeRect(9, 149, 252, 52);
painter->setClipRect(10, 150, 250, 50);
painter->setFillStyle(Qt::green);
painter->beginPath();
painter->ellipse(QRectF(10, 150, 300, 100));
painter->fill();
}
void CanvasPainterLancelotCppTests::testSaveRestore()
{
painter->setClipRect(0, 0, 150, 500);
painter->beginPath();
painter->ellipse(QRectF(10, 10, 300, 100));
painter->setFillStyle(Qt::red);
painter->fill();
painter->save();
painter->setClipRect(150, 10, 300, 500);
painter->beginPath();
painter->roundRect(10, 150, 300, 100, 20);
painter->setFillStyle(Qt::green);
painter->fill();
painter->restore();
// Original clip (left half) should be restored here.
painter->setFillStyle(Qt::blue);
painter->fillRect(10, 300, 300, 100);
}
void CanvasPainterLancelotCppTests::testPathFillRule()
{
painter->setFillStyle(QColor(255, 0, 0, 100));
painter->setStrokeStyle(Qt::blue);
painter->translate(0, 50);
// Winding fill (default) — interior fully filled
painter->beginPath();
painter->moveTo(100, 0);
painter->lineTo(60, 100);
painter->lineTo(160, 40);
painter->lineTo(40, 40);
painter->lineTo(140, 100);
painter->lineTo(100, 0);
painter->fill();
painter->stroke();
painter->translate(0, 150);
// Even-odd fill — centre of star is transparent
painter->beginPath();
painter->moveTo(100, 0);
painter->lineTo(60, 100);
painter->lineTo(160, 40);
painter->lineTo(40, 40);
painter->lineTo(140, 100);
painter->lineTo(100, 0);
painter->setFillRule(QCanvasPainter::FillRule::EvenOdd);
painter->fill();
painter->stroke();
painter->translate(0, 150);
// Reversed point order — different winding, different fill with winding rule
painter->beginPath();
painter->moveTo(100, 0);
painter->lineTo(160, 40);
painter->lineTo(140, 100);
painter->lineTo(60, 100);
painter->lineTo(40, 40);
painter->lineTo(100, 0);
painter->fill();
painter->stroke();
}
void CanvasPainterLancelotCppTests::testStencilClip()
{
auto *painterPriv = QCanvasPainterPrivate::get(painter);
// setStencilClip(QList<QRectF>): draw shapes clipped to a union of rects,
// then clear the clip and verify the unclipped draw reaches everywhere.
// Adapted from testClipRegion in paintertest.cpp.
painter->setStrokeStyle(Qt::red);
const QList<QRectF> clipRects{
QRectF(100, 100, 200, 100),
QRectF(50, 10, 150, 50),
};
for (const auto &rect : clipRects)
painter->strokeRect(rect.x(), rect.y(), rect.width(), rect.height());
painterPriv->setStencilClip(clipRects);
// Blue semi-transparent ellipse — only shows inside the clip rects
painter->setFillStyle(QColor(0, 0, 255, 160));
painter->beginPath();
painter->ellipse(QRectF(10, 10, 400, 150));
painter->fill();
// Orange non-convex star — also clipped
painter->save();
painter->setFillStyle(QColor(255, 128, 0));
painter->scale(2.0f, 2.0f);
painter->beginPath();
painter->moveTo(100, 0);
painter->lineTo(60, 100);
painter->lineTo(160, 40);
painter->lineTo(40, 40);
painter->lineTo(140, 100);
painter->lineTo(100, 0);
painter->fill();
painter->restore();
// Clear clip — magenta ellipse should be fully visible
painterPriv->setStencilClip(QList<QRectF>{});
painter->setFillStyle(Qt::magenta);
painter->beginPath();
painter->ellipse(QRectF(100, 250, 100, 50));
painter->fill();
}
void CanvasPainterLancelotCppTests::testStencilClipTransform()
{
auto *painterPriv = QCanvasPainterPrivate::get(painter);
// setStencilClip() applied while the painter is rotated.
// Adapted from testClipRegionTransform in paintertest.cpp.
painter->setStrokeStyle(Qt::red);
painter->rotate(float(M_PI) / 180.0 * 15.0);
const QRectF rect(100, 100, 200, 100);
painter->strokeRect(rect.x(), rect.y(), rect.width(), rect.height());
painterPriv->setStencilClip({rect});
painter->rotate(-float(M_PI) / 180.0 * 15.0);
painter->setFillStyle(QColor(255, 255, 0, 192));
painter->fillRect(0, 0, 500, 400);
}
void CanvasPainterLancelotCppTests::testStencilClipIntersect()
{
auto *painterPriv = QCanvasPainterPrivate::get(painter);
// Two successive setStencilClip() calls should intersect.
// Adapted from testClipRegionIntersect in paintertest.cpp.
painter->setStrokeStyle(Qt::red);
const QRectF rect(50, 50, 200, 100);
painter->strokeRect(rect.x(), rect.y(), rect.width(), rect.height());
painter->save();
painter->rotate(float(M_PI) / 180.0 * 15.0);
painter->strokeRect(rect.x(), rect.y(), rect.width(), rect.height());
painter->restore();
// Fill yellow inside the (unrotated) first clip
painterPriv->setStencilClip({rect});
painter->setFillStyle(QColor(255, 255, 0, 192));
painter->fillRect(0, 0, 500, 400);
// Intersect with the same rect rotated 15°; only the intersection turns blue
painter->rotate(float(M_PI) / 180.0 * 15.0);
painterPriv->setStencilClip({rect});
painter->rotate(-float(M_PI) / 180.0 * 15.0);
painter->setFillStyle(QColor(0, 0, 255, 128));
painter->fillRect(0, 0, 500, 400);
}
void CanvasPainterLancelotCppTests::testStrokingImpl()
{
painter->save();
painter->setStrokeStyle(Qt::darkGreen);
painter->setLineWidth(7);
painter->beginPath();
painter->ellipse(QRectF(10, 10, 400, 100));
painter->stroke();
painter->setStrokeStyle(QColor(192, 0, 0, 127));
painter->translate(0, 150);
painter->beginPath();
painter->moveTo(100, 0);
painter->lineTo(60, 100);
painter->lineTo(160, 40);
painter->lineTo(40, 40);
painter->lineTo(140, 100);
painter->lineTo(100, 0);
painter->stroke();
painter->restore();
}
void CanvasPainterLancelotCppTests::testStrokingWithStencilClip()
{
auto *painterPriv = QCanvasPainterPrivate::get(painter);
painter->setStrokeStyle(Qt::red);
const QList<QRectF> clipRects{
QRectF(100, 100, 200, 150),
QRectF(50, 10, 150, 50),
};
for (const auto &rect : clipRects)
painter->strokeRect(rect.x(), rect.y(), rect.width(), rect.height());
painterPriv->setStencilClip(clipRects);
testStrokingImpl();
painter->setFillStyle(QColor(255, 255, 0, 192));
painter->fillRect(150, 0, 50, 400);
}
void CanvasPainterLancelotCppTests::testHighQualityStrokingWithStencilClip()
{
auto *painterPriv = QCanvasPainterPrivate::get(painter);
painter->setHighQualityStroking(true);
painter->setStrokeStyle(Qt::red);
const QList<QRectF> clipRects{
QRectF(100, 100, 200, 150),
QRectF(50, 10, 150, 50),
};
for (const auto &rect : clipRects)
painter->strokeRect(rect.x(), rect.y(), rect.width(), rect.height());
painterPriv->setStencilClip(clipRects);
testStrokingImpl();
painter->setFillStyle(QColor(255, 255, 0, 192));
painter->fillRect(150, 0, 50, 400);
}
void CanvasPainterLancelotCppTests::testGradientSpread()
{
// Gradient pad/spread: start and end positions are inside the fill rect,
// so the transparent edge colours fill the rest of the rect.
// Adapted from testGradientPad in paintertest.cpp.
const QCanvasGradientStops stops = {
{0.0f, "transparent"},
{0.001f, "red"},
{0.5f, "lightblue"},
{0.999f, "red"},
{1.0f, "transparent"},
};
const QRectF rect(50, 50, 200, 100);
QCanvasLinearGradient lg;
lg.setStops(stops);
lg.setStartPosition(QPointF(100, 50));
lg.setEndPosition(QPointF(200, 50));
painter->setFillStyle(lg);
painter->fillRect(rect);
painter->strokeRect(rect);
}
void CanvasPainterLancelotCppTests::testGradientCaching()
{
// Validates that the gradient cache does not confuse a linear gradient
// with a conical gradient that shares the same colour stops (reversed).
// Adapted from testGradientBug in paintertest.cpp.
const QCanvasGradientStops stops = {
{0.0f, "green"},
{0.2f, "black"},
{0.4f, "blue"},
{0.6f, "white"},
{0.8f, "yellow"},
{1.0f, "red"},
};
const QRectF rect1(50, 50, 100, 100);
QCanvasLinearGradient lg;
for (const auto &stop : stops)
lg.setColorAt(stop.position, stop.color);
lg.setStartPosition(rect1.topLeft());
lg.setEndPosition(rect1.topRight());
const QRectF rect2(200, 50, 100, 100);
QCanvasConicalGradient cg(rect2.center(), 0);
for (const auto &stop : stops)
cg.setColorAt(1.0 - stop.position, stop.color);
painter->setFillStyle(lg);
painter->fillRect(rect1);
painter->setFillStyle(cg);
painter->fillRect(rect2);
}
void CanvasPainterLancelotCppTests::testTextDirection()
{
// setTextDirection() with all four modes applied to bidirectional strings.
// Adapted from the bidi section of testText in paintertest.cpp.
const QStringList bidiStrings{
QStringLiteral("ABC 123"),
QStringLiteral("123 مرحبًا"),
QStringLiteral("مرحبًا 123"),
QStringLiteral("ABC 123 مرحبًا"),
};
struct Row {
const char *label;
QCanvasPainter::TextDirection dir;
float y;
};
const Row rows[] = {
{"Inherit", QCanvasPainter::TextDirection::Inherit, 60.0f},
{"Auto", QCanvasPainter::TextDirection::Auto, 130.0f},
{"LeftToRight", QCanvasPainter::TextDirection::LeftToRight, 200.0f},
{"RightToLeft", QCanvasPainter::TextDirection::RightToLeft, 270.0f},
};
QFont labelFont;
labelFont.setPointSize(10);
QFont textFont;
textFont.setPointSize(14);
for (const auto &row : rows) {
painter->setFont(labelFont);
painter->setFillStyle(Qt::black);
painter->fillText(QLatin1String(row.label), 10.0f, row.y - 10.0f);
painter->setFont(textFont);
painter->setFillStyle(Qt::darkGreen);
painter->setTextDirection(row.dir);
float x = 10.0f;
for (const auto &s : bidiStrings) {
QRectF r(x, row.y, 185.0f, 25.0f);
painter->fillText(s, r);
painter->strokeRect(painter->textBoundingBox(s, r));
x += 195.0f;
}
}
}
void CanvasPainterLancelotCppTests::testVectorPathStencilClip()
{
auto *painterPriv = QCanvasPainterPrivate::get(painter);
// setStencilClip(QVectorPath): intersect a rect-based clip with a
// QVectorPath-based clip.
// Adapted from testVectorPath in paintertest.cpp.
const QRectF clipRegionRect{50, 50, 400, 150};
constexpr int count = 14;
const qreal points[count * 2] = {
10, 10,
10, 200,
190, 200,
190, 10,
10, 10,
250, 10,
150, 160,
350, 50,
350, 10,
250, 10,
60, 180,
60, 160,
80, 170,
60, 180,
};
const QPainterPath::ElementType elements[count] = {
QPainterPath::MoveToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::MoveToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::MoveToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
QPainterPath::LineToElement,
};
QVectorPath path(points, count, elements, QVectorPath::WindingFill);
painter->strokeRect(clipRegionRect);
// First stencil clip: the bounding rect
painterPriv->setStencilClip({clipRegionRect});
// Second stencil clip: the vector path (intersects with the first)
painterPriv->setStencilClip(path);
painter->setFillStyle(Qt::red);
painter->fillRect(QRectF(0, 0, 500, 500));
// Clear clip, then stroke the path outline in blue for reference
painterPriv->setStencilClip(QList<QRectF>{});
painter->beginPath();
painter->addPath(path.convertToPainterPath());
painter->setStrokeStyle(Qt::blue);
painter->stroke();
}