Files
OmaPhoto/tests/LayerRendererTests.cpp
雷电芽衣andClaude c864304695
Release / deb (push) Canceled after 0s
Release / rpm (push) Canceled after 0s
Release / arch (push) Canceled after 0s
Release / publish (push) Canceled after 0s
OmaPhoto 1.0.0: the Linux port of Compositor
A layered image editor for Linux and Omarchy, ported from Compositor
(github.com/robbietilton/Compositor), whose Swift source is its
specification. It reads and writes Compositor's .comp projects.

Packages for Ubuntu, Fedora and Arch come from the release workflow;
scripts/install/ holds one install script per distribution family,
and flake.nix serves NixOS.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-26 00:44:52 -04:00

490 lines
22 KiB
C++

#include "IO/ImageExporter.h"
#include "Rendering/LayerRenderer.h"
#include "RenderFixtures.h"
#include <QPixmap>
#include "AddressSpaceLimit.h"
#include <QtTest>
#include <algorithm>
class LayerRendererTests : public QObject {
Q_OBJECT
private slots:
void placesScalesRotatesAndFlips();
void opacityScalesTheWholeLayer();
void layerOpacityIgnoresThePaintersOwn();
void aMaskMultipliesCoverage();
void aWhiteMaskChangesNothing();
void aBlackMaskHidesARotatedLayerCompletely();
void aGrayMaskHalvesARotatedLayer();
void aMaskedEdgeIsCoverageSquared();
void maskedLayersFollowRotationAndPainterScale();
void masksAreSampledLikeTheirLayer();
void compositeReducesTheMaskByItsOwnWidth();
void aMaskMustBeEightBitGray();
void aMaskedLayerOutsideTheDeviceDrawsNothing();
void aSurfaceThatCannotBeAllocatedThrows();
void aThrowingDrawLeavesThePainterBalanced();
void aShrinkingPainterDrawsFromHalvings();
void oddSizedReductionsKeepTheirOverhang();
void nearestLayersKeepHardEdges();
void nearestStaysBlockyAndSmoothInterpolates();
void drawingLeavesThePainterAsItFoundIt();
void interpolationAndReductionRules();
};
void LayerRendererTests::placesScalesRotatesAndFlips()
{
const QImage image = coordinates(4, 2);
QImage surface = BrushRaster::context(12, 12, false);
QPainter painter(&surface);
const LayerTransform plain = placedAt({2, 3}, {4, 2});
LayerRenderer::draw(image, plain, plain.center(), painter);
QCOMPARE(surface.pixel(2, 3), qRgba(0, 0, 0, 255));
QCOMPARE(surface.pixel(5, 4), qRgba(30, 10, 0, 255));
QCOMPARE(surface.pixel(6, 4), qRgba(0, 0, 0, 0));
surface.fill(0);
painter.save();
painter.translate(5, 6);
LayerRenderer::draw(image, plain, plain.center(), painter);
painter.restore();
QCOMPARE(surface.pixel(7, 9), qRgba(0, 0, 0, 255));
QCOMPARE(surface.pixel(10, 10), qRgba(30, 10, 0, 255));
QCOMPARE(surface.pixel(2, 3), qRgba(0, 0, 0, 0));
surface.fill(0);
LayerTransform flipped = plain;
flipped.flipX = true;
LayerRenderer::draw(image, flipped, flipped.center(), painter);
QCOMPARE(surface.pixel(2, 3), qRgba(30, 0, 0, 255));
QCOMPARE(surface.pixel(5, 4), qRgba(0, 10, 0, 255));
flipped.flipX = false;
flipped.flipY = true;
surface.fill(0);
LayerRenderer::draw(image, flipped, flipped.center(), painter);
QCOMPARE(surface.pixel(2, 3), qRgba(0, 10, 0, 255));
QCOMPARE(surface.pixel(5, 4), qRgba(30, 0, 0, 255));
surface.fill(0);
LayerTransform turned = placedAt({4, 3}, {4, 2});
turned.rotation = 90;
LayerRenderer::draw(image, turned, turned.center(), painter);
QCOMPARE(surface.pixel(6, 2), qRgba(0, 0, 0, 255));
QCOMPARE(surface.pixel(5, 2), qRgba(0, 10, 0, 255));
QCOMPARE(surface.pixel(6, 5), qRgba(30, 0, 0, 255));
QCOMPARE(surface.pixel(7, 3), qRgba(0, 0, 0, 0));
surface.fill(0);
LayerRenderer::draw(image, plain, plain.center() * 2, painter, {.scale = 2});
QCOMPARE(surface.pixel(4, 6), qRgba(0, 0, 0, 255));
QCOMPARE(surface.pixel(11, 9), qRgba(30, 10, 0, 255));
QCOMPARE(surface.pixel(3, 6), qRgba(0, 0, 0, 0));
}
void LayerRendererTests::opacityScalesTheWholeLayer()
{
QImage surface = BrushRaster::context(4, 4, false);
QPainter painter(&surface);
const LayerTransform transform = placedAt({0, 0}, {4, 4});
LayerRenderer::draw(solid(4, 4, qRgba(200, 100, 0, 255)), transform, transform.center(), painter, {.opacity = 0.5});
const QRgb pixel = surface.pixel(1, 1);
QVERIFY(std::abs(qAlpha(pixel) - 128) <= 1);
QVERIFY(std::abs(qRed(pixel) - 100) <= 1);
QVERIFY(std::abs(qGreen(pixel) - 50) <= 1);
QCOMPARE(qBlue(pixel), 0);
}
void LayerRendererTests::aMaskMultipliesCoverage()
{
QImage mask = gray(4, 2, 255);
mask.scanLine(0)[1] = mask.scanLine(1)[1] = 128;
mask.scanLine(0)[2] = mask.scanLine(1)[2] = 0;
QImage surface = BrushRaster::context(4, 2, false);
QPainter painter(&surface);
const LayerTransform transform = placedAt({0, 0}, {4, 2});
LayerRenderer::draw(solid(4, 2, qRgba(0, 200, 0, 255)), transform, transform.center(), painter, {.mask = mask});
painter.end();
QCOMPARE(surface.pixel(0, 0), qRgba(0, 200, 0, 255));
QVERIFY(std::abs(qAlpha(surface.pixel(1, 1)) - 128) <= 1);
QVERIFY(std::abs(qGreen(surface.pixel(1, 1)) - 100) <= 1);
QCOMPARE(surface.pixel(2, 0), qRgba(0, 0, 0, 0));
QCOMPARE(surface.pixel(3, 1), qRgba(0, 200, 0, 255));
}
void LayerRendererTests::aMaskMustBeEightBitGray()
{
QImage surface = BrushRaster::context(4, 4, false);
QPainter painter(&surface);
const LayerTransform transform = placedAt({0, 0}, {4, 4});
QVERIFY_THROWS_EXCEPTION(std::logic_error, LayerRenderer::draw(solid(4, 4, qRgba(9, 9, 9, 255)), transform,
transform.center(), painter,
{.mask = solid(4, 4, qRgba(255, 255, 255, 255))}));
}
void LayerRendererTests::aMaskedLayerOutsideTheDeviceDrawsNothing()
{
QImage surface = solid(4, 4, qRgba(1, 2, 3, 255));
QPainter painter(&surface);
const LayerTransform away = placedAt({100, 100}, {4, 4});
LayerRenderer::draw(solid(4, 4, qRgba(9, 9, 9, 255)), away, away.center(), painter, {.mask = gray(4, 4, 255)});
LayerRenderer::draw(solid(4, 4, qRgba(9, 9, 9, 255)), away, away.center(), painter, {.blendMode = LayerBlendMode::hue});
painter.end();
QCOMPARE(surface, solid(4, 4, qRgba(1, 2, 3, 255)));
}
void LayerRendererTests::aSurfaceThatCannotBeAllocatedThrows()
{
QImage device = BrushRaster::context(8192, 8192, true);
QPainter painter(&device);
const LayerTransform transform = placedAt({0, 0}, {8192, 8192});
const QImage image = solid(2, 2, qRgba(9, 9, 9, 255)), mask = gray(2, 2, 255);
std::optional<AddressSpaceLimit> limit(std::in_place, 16 * 1024 * 1024);
std::optional<ExportError::Kind> thrown;
try {
LayerRenderer::draw(image, transform, transform.center(), painter, {.mask = mask});
} catch (const ExportError &error) {
thrown = error.kind;
}
limit.reset();
QCOMPARE(thrown, std::optional(ExportError::Kind::render));
// Room for the colour surface, not for the mask's quarter.
const qint64 surfaceBytes = qint64(8192) * 8192 * 4;
limit.emplace(surfaceBytes + surfaceBytes / 8);
std::optional<ExportError::Kind> maskThrown;
try {
LayerRenderer::draw(image, transform, transform.center(), painter, {.mask = mask});
} catch (const ExportError &error) {
maskThrown = error.kind;
}
limit.reset();
QCOMPARE(maskThrown, std::optional(ExportError::Kind::render));
}
void LayerRendererTests::aShrinkingPainterDrawsFromHalvings()
{
QImage stripes = BrushRaster::context(2048, 64, false);
for (int y = 0; y < 64; ++y) {
for (int x = 0; x < 2048; ++x)
stripes.setPixel(x, y, x % 4 == 1 || x % 4 == 2 ? qRgba(255, 255, 255, 255) : qRgba(0, 0, 0, 255));
}
QImage surface = BrushRaster::context(256, 8, false);
QPainter painter(&surface);
painter.scale(0.125, 0.125);
const LayerTransform transform{.origin = {0, 0}, .size = {2048, 64}, .sampling = LayerSampling::high};
LayerRenderer::draw(stripes, transform, transform.center(), painter);
painter.end();
for (int x = 8; x < 248; ++x)
QVERIFY(std::abs(qRed(surface.pixel(x, 4)) - 127) <= 8);
QImage fineMask = gray(2048, 64, 0);
for (int y = 0; y < 64; ++y) {
for (int x = 0; x < 2048; ++x)
fineMask.scanLine(y)[x] = x % 4 == 1 || x % 4 == 2 ? 255 : 0;
}
QImage masked = BrushRaster::context(256, 8, false);
QPainter masking(&masked);
masking.scale(0.125, 0.125);
LayerRenderer::draw(solid(2048, 64, qRgba(255, 255, 255, 255)), transform, transform.center(), masking, {.mask = fineMask});
masking.end();
for (int x = 8; x < 248; ++x)
QVERIFY(std::abs(qAlpha(masked.pixel(x, 4)) - 127) <= 8);
}
void LayerRendererTests::oddSizedReductionsKeepTheirOverhang()
{
// 1025 px halve twice to 257, covering 1028 source pixels.
QImage edge = BrushRaster::context(1025, 16, false);
QImage edgeMask = gray(1025, 16, 0);
for (int y = 0; y < 16; ++y) {
for (int x = 0; x < 1025; ++x) {
edge.setPixel(x, y, x < 1000 ? qRgba(0, 0, 0, 255) : qRgba(255, 255, 255, 255));
edgeMask.scanLine(y)[x] = x < 1000 ? 0 : 255;
}
}
const LayerTransform transform{.origin = {0, 0}, .size = {1025, 16}, .sampling = LayerSampling::high};
QImage direct = BrushRaster::context(260, 4, false), masked = direct;
QPainter first(&direct), second(&masked);
LayerRenderer::draw(edge, transform, transform.center() * 0.25, first, {.scale = 0.25});
LayerRenderer::draw(solid(1025, 16, qRgba(255, 255, 255, 255)), transform, transform.center() * 0.25, second,
{.scale = 0.25, .mask = edgeMask});
first.end();
second.end();
QVERIFY(qRed(direct.pixel(249, 2)) < 40);
QVERIFY(qRed(direct.pixel(250, 2)) > 215);
QVERIFY(qAlpha(masked.pixel(249, 2)) < 40);
QVERIFY(qAlpha(masked.pixel(250, 2)) > 215);
QImage aside = BrushRaster::context(260, 4, false);
QPainter third(&aside);
LayerRenderer::draw(edge, transform, transform.center() * 0.25, third, {.scale = 0.25, .mask = gray(1025, 16, 255)});
third.end();
QVERIFY(qRed(aside.pixel(249, 2)) < 40);
QVERIFY(qRed(aside.pixel(250, 2)) > 215);
}
void LayerRendererTests::nearestLayersKeepHardEdges()
{
const auto partialPixels = [&](LayerSampling sampling) {
QImage surface = BrushRaster::context(24, 24, false);
QPainter painter(&surface);
const LayerTransform transform{.origin = {6, 6}, .size = {12, 12}, .rotation = 30, .sampling = sampling};
LayerRenderer::draw(solid(12, 12, qRgba(255, 255, 255, 255)), transform, transform.center(), painter);
painter.end();
int partial = 0;
for (int y = 0; y < 24; ++y) {
for (int x = 0; x < 24; ++x)
partial += qAlpha(surface.pixel(x, y)) > 0 && qAlpha(surface.pixel(x, y)) < 255;
}
return partial;
};
QCOMPARE(partialPixels(LayerSampling::nearest), 0);
QVERIFY(partialPixels(LayerSampling::smooth) > 10);
}
void LayerRendererTests::maskedLayersFollowRotationAndPainterScale()
{
QImage mask = gray(2, 1, 255);
mask.scanLine(0)[1] = 0;
QImage surface = BrushRaster::context(40, 40, false);
QPainter painter(&surface);
painter.translate(20, 0);
painter.scale(2, 2);
LayerTransform transform = placedAt({0, 0}, {10, 4});
transform.rotation = 90;
LayerRenderer::draw(solid(2, 1, qRgba(255, 0, 0, 255)), transform, transform.center(), painter, {.mask = mask});
painter.end();
QCOMPARE(surface.pixel(30, 0), qRgba(255, 0, 0, 255));
QCOMPARE(surface.pixel(30, 3), qRgba(255, 0, 0, 255));
QCOMPARE(surface.pixel(30, 9), qRgba(0, 0, 0, 0));
QCOMPARE(surface.pixel(24, 0), qRgba(0, 0, 0, 0));
QCOMPARE(surface.pixel(27, 1), qRgba(255, 0, 0, 255));
QCOMPARE(surface.pixel(33, 1), qRgba(255, 0, 0, 255));
QCOMPARE(surface.pixel(35, 1), qRgba(0, 0, 0, 0));
}
void LayerRendererTests::nearestStaysBlockyAndSmoothInterpolates()
{
QImage pair = BrushRaster::context(2, 1, false);
pair.setPixel(0, 0, qRgba(0, 0, 0, 255));
pair.setPixel(1, 0, qRgba(255, 255, 255, 255));
const auto row = [&](LayerSampling sampling) {
QImage surface = BrushRaster::context(8, 1, false);
QPainter painter(&surface);
const LayerTransform transform{.origin = {0, 0}, .size = {8, 1}, .sampling = sampling};
LayerRenderer::draw(pair, transform, transform.center(), painter);
painter.end();
QList<int> values;
for (int x = 0; x < 8; ++x)
values << qRed(surface.pixel(x, 0));
return values;
};
QCOMPARE(row(LayerSampling::nearest), QList<int>({0, 0, 0, 0, 255, 255, 255, 255}));
for (LayerSampling sampling : {LayerSampling::smooth, LayerSampling::high}) {
const QList<int> smooth = row(sampling);
QVERIFY(std::is_sorted(smooth.begin(), smooth.end()));
QVERIFY(std::count_if(smooth.begin(), smooth.end(), [](int value) { return value > 0 && value < 255; }) >= 2);
}
}
void LayerRendererTests::interpolationAndReductionRules()
{
QCOMPARE(LayerRenderer::interpolation(LayerSampling::nearest, 0.5), InterpolationQuality::none);
QCOMPARE(LayerRenderer::interpolation(LayerSampling::nearest, 4), InterpolationQuality::none);
QCOMPARE(LayerRenderer::interpolation(LayerSampling::high, 1), InterpolationQuality::low);
QCOMPARE(LayerRenderer::interpolation(LayerSampling::high, 1.01), InterpolationQuality::high);
QCOMPARE(LayerRenderer::interpolation(LayerSampling::smooth, 3), InterpolationQuality::low);
QCOMPARE(quality(LayerSampling::nearest), InterpolationQuality::none);
QCOMPARE(quality(LayerSampling::smooth), InterpolationQuality::low);
QCOMPARE(quality(LayerSampling::high), InterpolationQuality::high);
const QImage image = coordinates(9, 5);
const LayerRenderer::Reduced kept = LayerRenderer::reduced(image, 2, 1, LayerSampling::nearest);
QCOMPARE(kept.level, 0);
QCOMPARE(kept.image.cacheKey(), image.cacheKey());
const LayerRenderer::Reduced half = LayerRenderer::reduced(image, 2, 2, LayerSampling::high);
QCOMPARE(half.level, 1);
QCOMPARE(half.image.size(), QSize(5, 3));
QCOMPARE(half.widthScale, 10.0 / 9);
QCOMPARE(half.heightScale, 6.0 / 5);
QCOMPARE(LayerRenderer::coverage(half, QRectF(-9, -5, 18, 10)), QRectF(-9, -5, 20, 12));
QCOMPARE(LayerRenderer::reduced(image, 9, 1, LayerSampling::high).level, 0);
QImage surface = BrushRaster::context(4, 4, false);
QPainter painter(&surface);
QCOMPARE(LayerRenderer::deviceScale(painter), 1.0);
painter.scale(3, 3);
painter.rotate(40);
QVERIFY(std::abs(LayerRenderer::deviceScale(painter) - 3) < 1e-9);
}
void LayerRendererTests::layerOpacityIgnoresThePaintersOwn()
{
const LayerTransform transform = placedAt({0, 0}, {2, 2});
const QImage image = solid(2, 2, qRgba(200, 100, 0, 255));
for (const QImage &mask : {QImage(), gray(2, 2, 255)}) {
QImage surface = BrushRaster::context(2, 2, false);
QPainter painter(&surface);
painter.setOpacity(0.25);
LayerRenderer::draw(image, transform, transform.center(), painter, {.mask = mask});
QCOMPARE(painter.opacity(), 0.25);
painter.end();
QCOMPARE(surface.pixel(0, 0), qRgba(200, 100, 0, 255));
}
}
void LayerRendererTests::aWhiteMaskChangesNothing()
{
const QImage image = coordinates(6, 4, 40);
for (LayerBlendMode mode : {LayerBlendMode::normal, LayerBlendMode::multiply, LayerBlendMode::colorBurn}) {
const LayerTransform transform{.origin = {1, 1}, .size = {6, 4}, .sampling = LayerSampling::smooth};
const QImage paper = solid(20, 14, qRgba(40, 90, 160, 255));
QImage direct = paper, masked = paper;
QPainter first(&direct), second(&masked);
for (QPainter *painter : {&first, &second}) {
painter->translate(2, 1);
painter->scale(2, 2);
}
LayerRenderer::draw(image, transform, transform.center(), first, {.opacity = 0.7, .blendMode = mode});
LayerRenderer::draw(image, transform, transform.center(), second,
{.opacity = 0.7, .blendMode = mode, .mask = gray(6, 4, 255)});
first.end();
second.end();
for (int y = 0; y < 14; ++y) {
for (int x = 0; x < 20 * 4; ++x)
QVERIFY(std::abs(direct.constScanLine(y)[x] - masked.constScanLine(y)[x]) <= 2);
}
QVERIFY(direct != paper);
}
}
void LayerRendererTests::masksAreSampledLikeTheirLayer()
{
QImage step = gray(2, 1, 0);
step.scanLine(0)[1] = 255;
const auto alphas = [&](LayerSampling sampling) {
QImage surface = BrushRaster::context(8, 1, false);
QPainter painter(&surface);
const LayerTransform transform{.origin = {0, 0}, .size = {8, 1}, .sampling = sampling};
LayerRenderer::draw(solid(2, 1, qRgba(255, 255, 255, 255)), transform, transform.center(), painter, {.mask = step});
painter.end();
QList<int> values;
for (int x = 0; x < 8; ++x)
values << qAlpha(surface.pixel(x, 0));
return values;
};
QCOMPARE(alphas(LayerSampling::nearest), QList<int>({0, 0, 0, 0, 255, 255, 255, 255}));
const QList<int> smooth = alphas(LayerSampling::smooth);
QVERIFY(std::is_sorted(smooth.begin(), smooth.end()));
QVERIFY(std::count_if(smooth.begin(), smooth.end(), [](int value) { return value > 0 && value < 255; }) >= 2);
}
void LayerRendererTests::compositeReducesTheMaskByItsOwnWidth()
{
QImage stripes = gray(1024, 8, 0);
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 1024; ++x)
stripes.scanLine(y)[x] = x % 4 == 1 || x % 4 == 2 ? 255 : 0;
}
QImage surface = BrushRaster::context(1024, 8, false);
QPainter painter(&surface);
// The mask covers an eighth of the layer's extent.
const QRectF extent(0, 0, 1024, 8), maskBounds(0, 0, 128, 8);
LayerRenderer::composite(painter, QTransform(), extent, LayerSampling::high, InterpolationQuality::low, {.mask = stripes},
maskBounds, [&](QPainter &aside) { aside.fillRect(extent, Qt::white); });
painter.end();
for (int x = 8; x < 120; ++x)
QVERIFY(std::abs(qAlpha(surface.pixel(x, 4)) - 127) <= 12);
QCOMPARE(qAlpha(surface.pixel(400, 4)), 0);
}
void LayerRendererTests::aBlackMaskHidesARotatedLayerCompletely()
{
QImage surface = BrushRaster::context(24, 24, false);
QPainter painter(&surface);
const LayerTransform transform{.origin = {8, 8}, .size = {8, 8}, .rotation = 30, .sampling = LayerSampling::smooth};
LayerRenderer::draw(solid(8, 8, qRgba(255, 255, 255, 255)), transform, transform.center(), painter, {.mask = gray(8, 8, 0)});
painter.end();
QCOMPARE(surface, BrushRaster::context(24, 24, false));
}
void LayerRendererTests::aGrayMaskHalvesARotatedLayer()
{
QImage surface = BrushRaster::context(24, 24, false);
QPainter painter(&surface);
const LayerTransform transform{.origin = {4, 4}, .size = {16, 16}, .rotation = 30, .sampling = LayerSampling::smooth};
LayerRenderer::draw(solid(16, 16, qRgba(255, 255, 255, 255)), transform, transform.center(), painter, {.mask = gray(16, 16, 128)});
painter.end();
QVERIFY(std::abs(qAlpha(surface.pixel(12, 12)) - 128) <= 1);
QVERIFY(std::abs(qAlpha(surface.pixel(9, 14)) - 128) <= 1);
QCOMPARE(qAlpha(surface.pixel(0, 0)), 0);
for (int y = 0; y < 24; ++y) {
for (int x = 0; x < 24; ++x)
QVERIFY(qAlpha(surface.pixel(x, y)) <= 129);
}
}
void LayerRendererTests::aMaskedEdgeIsCoverageSquared()
{
// CoreGraphics antialiases the mask clip and the image alike.
QImage direct = BrushRaster::context(24, 24, false), masked = direct;
QPainter first(&direct), second(&masked);
const LayerTransform transform{.origin = {4, 4}, .size = {16, 16}, .rotation = 30, .sampling = LayerSampling::smooth};
const QImage white = solid(16, 16, qRgba(255, 255, 255, 255));
LayerRenderer::draw(white, transform, transform.center(), first);
LayerRenderer::draw(white, transform, transform.center(), second, {.mask = gray(16, 16, 255)});
first.end();
second.end();
int partial = 0;
for (int y = 0; y < 24; ++y) {
for (int x = 0; x < 24; ++x) {
const int edge = qAlpha(direct.pixel(x, y));
partial += edge > 30 && edge < 225;
QVERIFY(std::abs(qAlpha(masked.pixel(x, y)) - (edge * edge + 127) / 255) <= 2);
}
}
QVERIFY(partial >= 20);
}
void LayerRendererTests::aThrowingDrawLeavesThePainterBalanced()
{
QTest::failOnWarning(QRegularExpression(".*saved states.*"));
const LayerTransform transform = placedAt({0, 0}, {4, 4});
const QImage image = solid(4, 4, qRgba(9, 9, 9, 255));
QPixmap pixmap(4, 4);
QPainter painter(&pixmap);
painter.translate(1, 2);
const QTransform before = painter.worldTransform();
QVERIFY_THROWS_EXCEPTION(std::logic_error, LayerRenderer::draw(image, transform, transform.center(), painter,
{.blendMode = LayerBlendMode::hue}));
QVERIFY_THROWS_EXCEPTION(std::logic_error, LayerRenderer::draw(image, transform, transform.center(), painter,
{.mask = image}));
QCOMPARE(painter.worldTransform(), before);
QCOMPARE(painter.opacity(), 1.0);
QCOMPARE(painter.compositionMode(), QPainter::CompositionMode_SourceOver);
QVERIFY(painter.end());
}
void LayerRendererTests::drawingLeavesThePainterAsItFoundIt()
{
QImage surface = BrushRaster::context(8, 8, false);
QPainter painter(&surface);
painter.translate(1, 2);
const QTransform before = painter.worldTransform();
const LayerTransform transform{.origin = {0, 0}, .size = {4, 4}, .rotation = 10, .sampling = LayerSampling::smooth};
const LayerRenderer::Options options[] = {{.opacity = 0.5, .blendMode = LayerBlendMode::multiply},
{.opacity = 0.5, .blendMode = LayerBlendMode::screen, .mask = gray(4, 4, 200)},
{.opacity = 0.5, .blendMode = LayerBlendMode::hue}};
for (const LayerRenderer::Options &option : options) {
LayerRenderer::draw(solid(4, 4, qRgba(9, 9, 9, 255)), transform, transform.center(), painter, option);
QCOMPARE(painter.worldTransform(), before);
QCOMPARE(painter.opacity(), 1.0);
QCOMPARE(painter.compositionMode(), QPainter::CompositionMode_SourceOver);
QVERIFY(!painter.testRenderHint(QPainter::SmoothPixmapTransform));
QVERIFY(!painter.testRenderHint(QPainter::Antialiasing));
}
}
QTEST_MAIN(LayerRendererTests)
#include "LayerRendererTests.moc"