Files
OmaPhoto/tests/HighDpiCanvasTests.cpp
T

239 lines
9.1 KiB
C++

#include "Document/BrushStroke.h"
#include "Rendering/AdjustmentSurface.h"
#include "Rendering/EditorCanvas.h"
#include "RenderFixtures.h"
#include "UI/JPEGExportSheet.h"
#include "UI/LevelsSheet.h"
#include <QScrollBar>
#include <QtTest>
// Widgets at device pixel ratio 2: two coordinate systems.
namespace {
struct Probe : QWidget {
std::function<void(QPainter &)> body;
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
if (body)
body(painter);
}
};
// A probe at an offset inside a window, both shown.
struct Framed {
QWidget window;
Probe child;
Framed()
{
window.resize(300, 200);
child.setParent(&window);
child.setGeometry(57, 42, 200, 100);
window.show();
if (!QTest::qWaitForWindowExposed(&window))
throw std::runtime_error("the window never showed");
}
};
}
class HighDpiCanvasTests : public QObject {
Q_OBJECT
private slots:
void theScreenScalesByTwo();
void theVisibleRectIsTheWidgetInItsOwnPoints();
void theSurfaceCoversAnOffsetWidgetInDevicePixels();
void theCanvasDrawsDevicePixels();
void levelsEyedroppersDrawDevicePixels();
void theJPEGPreviewCountsDevicePixels();
void gridDashesAreScreenPoints();
};
void HighDpiCanvasTests::theScreenScalesByTwo()
{
QWidget widget;
QCOMPARE(widget.devicePixelRatio(), 2.0);
}
void HighDpiCanvasTests::theVisibleRectIsTheWidgetInItsOwnPoints()
{
Framed framed;
std::vector<QRectF> seen;
framed.child.body = [&](QPainter &painter) {
seen.push_back(BrushRaster::visibleRect(painter));
painter.translate(10, 5);
seen.push_back(BrushRaster::visibleRect(painter));
painter.setClipRect(QRectF(10, 10, 50, 20));
seen.push_back(BrushRaster::visibleRect(painter));
};
// Through a grab and through the window's own store alike.
framed.child.grab();
framed.child.repaint();
QCOMPARE(int(seen.size()), 6);
for (size_t pass = 0; pass < 6; pass += 3) {
QCOMPARE(seen[pass], QRectF(0, 0, 200, 100));
QCOMPARE(seen[pass + 1], QRectF(-10, -5, 200, 100));
QCOMPARE(seen[pass + 2], QRectF(10, 10, 50, 20));
}
}
void HighDpiCanvasTests::theSurfaceCoversAnOffsetWidgetInDevicePixels()
{
Framed framed;
QSize allocated;
framed.child.body = [&](QPainter &painter) {
AdjustmentSurface::draw(painter, [&](QPainter &surface) {
allocated = QSize(surface.device()->width(), surface.device()->height());
surface.fillRect(QRectF(0, 0, 200, 100), Qt::red);
});
};
QImage shot = framed.child.grab().toImage().convertToFormat(QImage::Format_ARGB32);
QCOMPARE(shot.size(), QSize(400, 200));
QCOMPARE(allocated, QSize(400, 200));
for (const QPoint &point : {QPoint(0, 0), QPoint(399, 0), QPoint(0, 199), QPoint(399, 199), QPoint(200, 100)})
QCOMPARE(shot.pixel(point), qRgb(255, 0, 0));
// Through the window the child keeps its store offset.
const QImage whole = framed.window.grab().toImage().convertToFormat(QImage::Format_ARGB32);
QCOMPARE(whole.size(), QSize(600, 400));
for (const QPoint &point : {QPoint(114, 84), QPoint(513, 84), QPoint(114, 283), QPoint(513, 283), QPoint(314, 184)})
QCOMPARE(whole.pixel(point), qRgb(255, 0, 0));
QVERIFY(whole.pixel(113, 84) != qRgb(255, 0, 0));
QVERIFY(whole.pixel(514, 283) != qRgb(255, 0, 0));
// A clip bounds the surface, in device pixels too.
framed.child.body = [&](QPainter &painter) {
painter.fillRect(QRectF(0, 0, 200, 100), Qt::black);
painter.setClipRect(QRectF(10, 10, 50, 20));
AdjustmentSurface::draw(painter, [&](QPainter &surface) {
allocated = QSize(surface.device()->width(), surface.device()->height());
surface.fillRect(QRectF(0, 0, 200, 100), Qt::red);
});
};
shot = framed.child.grab().toImage().convertToFormat(QImage::Format_ARGB32);
QCOMPARE(allocated, QSize(100, 40));
QCOMPARE(shot.pixel(20, 20), qRgb(255, 0, 0));
QCOMPARE(shot.pixel(119, 59), qRgb(255, 0, 0));
QCOMPARE(shot.pixel(10, 10), qRgb(0, 0, 0));
QCOMPARE(shot.pixel(120, 60), qRgb(0, 0, 0));
}
void HighDpiCanvasTests::theCanvasDrawsDevicePixels()
{
EditorSession session;
session.createDocument(2, 1);
QImage edge = BrushRaster::context(2, 1, false);
edge.setPixel(0, 0, qRgba(0, 0, 0, 255));
edge.setPixel(1, 0, qRgba(255, 255, 255, 255));
session.insert(ImportedImage(edge, edge, "Edge"), QPointF(1, 0.5));
session.setShowsTransformControls(false);
QWidget window;
auto *canvas = new CanvasView(session, &window);
QPalette dark = canvas->palette();
dark.setColor(QPalette::Base, QColor(0x1b, 0x1b, 0x1b));
canvas->setPalette(dark);
window.resize(300, 200);
canvas->setGeometry(0, 0, 40, 20);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
QTRY_COMPARE(session.viewport.backingScale, 2.0);
QCOMPARE(session.viewport.viewSize, QSizeF(40, 20));
session.zoom(4);
QCOMPARE(session.viewport.pointsPerPixel(), 2.0);
const QImage shot = canvas->grab().toImage().convertToFormat(QImage::Format_ARGB32);
QCOMPARE(shot.size(), QSize(80, 40));
// The two document pixels span eight device pixels, hard-edged.
QCOMPARE(qRed(shot.pixel(37, 19)), 0);
QCOMPARE(qRed(shot.pixel(39, 19)), 0);
QCOMPARE(qRed(shot.pixel(40, 19)), 255);
QCOMPARE(qRed(shot.pixel(42, 19)), 255);
QCOMPARE(qRed(shot.pixel(2, 2)), 27);
}
void HighDpiCanvasTests::levelsEyedroppersDrawDevicePixels()
{
EditorSession session;
session.createDocument(2, 1);
QImage red(2, 1, QImage::Format_RGBA8888_Premultiplied);
red.fill(Qt::red);
session.insert(ImportedImage(red, red, QStringLiteral("Red")));
session.beginLevels();
LevelsSheet sheet(session);
sheet.show();
QVERIFY(QTest::qWaitForWindowExposed(&sheet));
// Fourteen points: the glyph spans all 28 pixels.
for (QPushButton *button : sheet.findChildren<QPushButton *>()) {
if (button->text() == QStringLiteral("Black")) {
const QImage drawn = button->icon().pixmap(QSize(14, 14)).toImage();
QCOMPARE(drawn.size(), QSize(28, 28));
int right = 0;
for (int y = 0; y < 28; ++y) {
for (int x = 16; x < 28; ++x)
right += qAlpha(drawn.pixel(x, y)) > 0;
}
QVERIFY(right > 20);
}
}
}
void HighDpiCanvasTests::theJPEGPreviewCountsDevicePixels()
{
QImage image(1200, 800, QImage::Format_RGBA8888_Premultiplied);
for (int y = 0; y < 800; ++y)
for (int x = 0; x < 1200; ++x)
image.setPixelColor(x, y, QColor((x * 7 + y) % 256, (x + y * 3) % 256, (x * y) % 256));
JPEGPreview preview(QSize(1200, 800), [] {}, nullptr);
preview.setImage(image, false);
preview.show();
QVERIFY(QTest::qWaitForWindowExposed(&preview));
// 600 by 400 points: Fit 0.825, averaged per device pixel.
QCOMPARE(preview.shownZoom(), 0.825);
QImage fitted = preview.viewport()->grab().toImage().convertToFormat(QImage::Format_RGB32);
fitted.setDevicePixelRatio(1);
QCOMPARE(fitted.size(), QSize(1120, 660));
QCOMPARE(fitted.copy(65, 0, 990, 660), image.scaled(990, 660, Qt::IgnoreAspectRatio, Qt::SmoothTransformation).convertToFormat(QImage::Format_RGB32));
// At 100% an image pixel is a device pixel.
preview.setZoom(1);
QCoreApplication::processEvents();
QCOMPARE(preview.horizontalScrollBar()->maximum(), int(std::ceil(600 - preview.viewport()->width())));
}
void HighDpiCanvasTests::gridDashesAreScreenPoints()
{
EditorSession session;
session.createDocument(100, 100);
QWidget window;
auto *canvas = new CanvasView(session, &window);
window.resize(100, 100);
canvas->setGeometry(0, 0, 100, 100);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
QTRY_COMPARE(session.viewport.backingScale, 2.0);
session.zoom(1);
const QImage bare = canvas->grab().toImage();
session.setShowsGrid(true);
session.setLayoutGrid(LayoutGrid(50, 1));
GridAppearance dashed;
dashed.preset = GridAppearance::Preset::black;
dashed.opacity = 100;
dashed.style = GridAppearance::Style::dashedLines;
session.setGridAppearance(dashed);
canvas->synchronizeDisplay();
const QImage drawn = canvas->grab().toImage();
// Four points on is eight device rows, not four.
const int column = int(std::round(session.viewport.viewPoint(QPointF(50, 0), QSizeF(100, 100)).x() * 2));
int run = 0, longest = 0;
for (int y = 4; y < 180; ++y) {
const bool inked = std::abs(qGray(drawn.pixel(column, y)) - qGray(bare.pixel(column, y))) + std::abs(qGray(drawn.pixel(column - 1, y)) - qGray(bare.pixel(column - 1, y))) > 20;
run = inked ? run + 1 : 0;
longest = std::max(longest, run);
}
QVERIFY2(longest >= 7 && longest <= 9, qPrintable(QString::number(longest)));
}
int main(int argc, char **argv)
{
// Every widget on this screen scales by two.
qputenv("QT_SCALE_FACTOR", "2");
QApplication app(argc, argv);
HighDpiCanvasTests tests;
return QTest::qExec(&tests, argc, argv);
}
#include "HighDpiCanvasTests.moc"