90 lines
3.8 KiB
C++
90 lines
3.8 KiB
C++
#pragma once
|
|
#include "Rendering/EditorCanvas.h"
|
|
#include "IO/ImageExporter.h"
|
|
#include "RenderFixtures.h"
|
|
#include "SessionFixtures.h"
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <cmath>
|
|
#include <QtTest>
|
|
|
|
// Compositor 1.4's GPU canvas tests, held against the CPU canvas.
|
|
struct ContractScene {
|
|
EditorSession session;
|
|
CanvasView canvas{session};
|
|
ContractScene(int width, int height, int canvasWidth, int canvasHeight)
|
|
{
|
|
session.createDocument(width, height);
|
|
session.setShowsTransformControls(false);
|
|
canvas.resize(canvasWidth, canvasHeight);
|
|
session.viewport.resize(QSizeF(canvasWidth, canvasHeight), 1, QSizeF(width, height));
|
|
}
|
|
QImage shot() { return canvas.grab().toImage().convertToFormat(QImage::Format_ARGB32); }
|
|
QPointF origin() { return session.viewport.viewPoint(QPointF(0, 0), session.document().value().size()); }
|
|
QImage exported() { return ImageExporter::render(session.projectSnapshot().value()).image.convertToFormat(QImage::Format_ARGB32); }
|
|
// What the canvas shows at each document pixel's centre.
|
|
QImage documentShot()
|
|
{
|
|
canvas.synchronizeDisplay();
|
|
const QImage view = shot();
|
|
const QSizeF size = session.document().value().size();
|
|
const double zoom = session.viewport.pointsPerPixel();
|
|
QImage result(size.toSize(), QImage::Format_ARGB32);
|
|
for (int y = 0; y < result.height(); ++y) {
|
|
for (int x = 0; x < result.width(); ++x)
|
|
result.setPixel(x, y, view.pixel(int(origin().x() + (x + 0.5) * zoom), int(origin().y() + (y + 0.5) * zoom)));
|
|
}
|
|
return result;
|
|
}
|
|
// The export over the checkerboard, as the canvas shows it.
|
|
QImage exportedOnCanvas()
|
|
{
|
|
const QImage exported = ImageExporter::render(session.projectSnapshot().value()).image;
|
|
const double zoom = session.viewport.pointsPerPixel();
|
|
QImage result(exported.size(), QImage::Format_ARGB32_Premultiplied);
|
|
for (int y = 0; y < result.height(); ++y) {
|
|
for (int x = 0; x < result.width(); ++x) {
|
|
const bool light = (int(std::floor((x + 0.5) * zoom / 10)) + int(std::floor((y + 0.5) * zoom / 10))) % 2 == 0;
|
|
result.setPixel(x, y, QColor::fromRgbF(light ? 0.35 : 0.30, light ? 0.35 : 0.30, light ? 0.35 : 0.30).rgb());
|
|
}
|
|
}
|
|
QPainter painter(&result);
|
|
painter.drawImage(QRectF(result.rect()), exported);
|
|
painter.end();
|
|
return result.convertToFormat(QImage::Format_ARGB32);
|
|
}
|
|
int gapToExport(const QPainterPath &skip = QPainterPath()) { return gap(documentShot(), exportedOnCanvas(), skip); }
|
|
// The worst channel gap inside the border, `skip` aside.
|
|
static int gap(const QImage &one, const QImage &other, const QPainterPath &skip = QPainterPath())
|
|
{
|
|
int worst = 0;
|
|
for (int y = 1; y < one.height() - 1; ++y) {
|
|
for (int x = 1; x < one.width() - 1; ++x) {
|
|
if (skip.contains(QPointF(x + 0.5, y + 0.5)))
|
|
continue;
|
|
const QRgb a = one.pixel(x, y), b = other.pixel(x, y);
|
|
worst = std::max({worst, std::abs(qRed(a) - qRed(b)), std::abs(qGreen(a) - qGreen(b)), std::abs(qBlue(a) - qBlue(b))});
|
|
}
|
|
}
|
|
return worst;
|
|
}
|
|
// A band round a path, where overlays draw.
|
|
static QPainterPath band(const QPainterPath &path, double width = 6)
|
|
{
|
|
QPainterPathStroker stroker;
|
|
stroker.setWidth(width);
|
|
return stroker.createStroke(path);
|
|
}
|
|
};
|
|
|
|
// A mask dark on the left, light on the right.
|
|
inline ImportedImage rampMask(int width, int height)
|
|
{
|
|
QImage mask(width, height, QImage::Format_Grayscale8);
|
|
for (int y = 0; y < height; ++y) {
|
|
for (int x = 0; x < width; ++x)
|
|
mask.scanLine(y)[x] = uchar(x * 255 / (width - 1));
|
|
}
|
|
return LayerMask::assetFrom(mask);
|
|
}
|