120 lines
4.4 KiB
C++
120 lines
4.4 KiB
C++
#include "Document/EditorSession.h"
|
|
#include <QtTest>
|
|
|
|
// Swift's Smoothing: the brush trails the pointer on a string.
|
|
namespace {
|
|
std::unique_ptr<EditorSession> painting(double smoothing, NavigationTool tool = NavigationTool::brush)
|
|
{
|
|
auto session = std::make_unique<EditorSession>();
|
|
session->createDocument(200, 200, true);
|
|
session->selectTool(tool);
|
|
BrushSettings brush = session->brushSettings();
|
|
brush.diameter = 4;
|
|
brush.hardness = 1;
|
|
brush.smoothing = smoothing;
|
|
session->setBrushSettings(brush);
|
|
return session;
|
|
}
|
|
|
|
QPointF painted(const EditorSession &session)
|
|
{
|
|
return session.shiftLineStart().value();
|
|
}
|
|
|
|
// The committed pixels at a document point, clear outside them.
|
|
int ink(const EditorSession &session, int x, int y)
|
|
{
|
|
const ImageLayer &layer = session.document().value().layers.back();
|
|
const QImage image = layer.asset.value().image();
|
|
const QPoint at = QPoint(x, y) - layer.origin().toPoint();
|
|
if (layer.size() != QSizeF(image.size()))
|
|
throw std::runtime_error("the layer is scaled");
|
|
return image.rect().contains(at) ? qAlpha(image.pixel(at)) : 0;
|
|
}
|
|
}
|
|
|
|
class BrushSmoothingTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void theBrushWaitsUntilTheStringIsTaut();
|
|
void theStrokeEndsWhereTheHandDid();
|
|
void theStringIsMeasuredOnScreen();
|
|
void onlyTheBrushSmoothsAndACancelLetsGo();
|
|
};
|
|
|
|
void BrushSmoothingTests::theBrushWaitsUntilTheStringIsTaut()
|
|
{
|
|
const auto session = painting(10);
|
|
session->beginBrush(QPointF(50, 50));
|
|
session->continueBrush(QPointF(55, 50));
|
|
QCOMPARE(painted(*session), QPointF(50, 50));
|
|
// Taut at exactly its length, the string still holds.
|
|
session->continueBrush(QPointF(60, 50));
|
|
QCOMPARE(painted(*session), QPointF(50, 50));
|
|
session->continueBrush(QPointF(70, 50));
|
|
QCOMPARE(painted(*session), QPointF(60, 50));
|
|
// Pulled at an angle, the brush moves along the string.
|
|
session->continueBrush(QPointF(70, 60));
|
|
const double reach = 10 * std::sqrt(0.5);
|
|
QVERIFY(std::abs(painted(*session).x() - (70 - reach)) < 1e-9 && std::abs(painted(*session).y() - (60 - reach)) < 1e-9);
|
|
// Without Smoothing the brush is the pointer.
|
|
const auto exact = painting(0);
|
|
exact->beginBrush(QPointF(50, 50));
|
|
exact->continueBrush(QPointF(51, 50));
|
|
QCOMPARE(painted(*exact), QPointF(51, 50));
|
|
}
|
|
|
|
void BrushSmoothingTests::theStrokeEndsWhereTheHandDid()
|
|
{
|
|
const auto session = painting(10);
|
|
session->beginBrush(QPointF(50, 50));
|
|
session->continueBrush(QPointF(80, 50));
|
|
QVERIFY(session->finishBrushImmediately());
|
|
QVERIFY(ink(*session, 79, 50) > 0 && ink(*session, 70, 50) > 0);
|
|
QCOMPARE(ink(*session, 86, 50), 0);
|
|
// A string never pulled taut still ends at the pointer.
|
|
const auto still = painting(10);
|
|
still->beginBrush(QPointF(50, 50));
|
|
still->continueBrush(QPointF(57, 50));
|
|
QVERIFY(still->finishBrushImmediately());
|
|
QVERIFY(ink(*still, 56, 50) > 0);
|
|
QCOMPARE(ink(*still, 62, 50), 0);
|
|
}
|
|
|
|
void BrushSmoothingTests::theStringIsMeasuredOnScreen()
|
|
{
|
|
const auto session = painting(10);
|
|
session->viewport.setZoom(2, QPointF(), session->document().value().size());
|
|
session->beginBrush(QPointF(50, 50));
|
|
session->continueBrush(QPointF(60, 50));
|
|
QCOMPARE(painted(*session), QPointF(55, 50));
|
|
}
|
|
|
|
void BrushSmoothingTests::onlyTheBrushSmoothsAndACancelLetsGo()
|
|
{
|
|
const auto healing = painting(10, NavigationTool::spotHealing);
|
|
healing->beginBrush(QPointF(50, 50));
|
|
healing->continueBrush(QPointF(52, 50));
|
|
QCOMPARE(painted(*healing), QPointF(52, 50));
|
|
// The eraser is the Brush in another mode: it smooths.
|
|
const auto erasing = painting(10);
|
|
erasing->setBrushMode(BrushToolMode::erase);
|
|
erasing->beginBrush(QPointF(50, 50));
|
|
erasing->continueBrush(QPointF(55, 50));
|
|
QCOMPARE(painted(*erasing), QPointF(50, 50));
|
|
// A cancelled stroke leaves no anchor for the next.
|
|
const auto session = painting(10);
|
|
session->beginBrush(QPointF(50, 50));
|
|
session->continueBrush(QPointF(80, 50));
|
|
session->cancelBrush();
|
|
session->beginBrush(QPointF(100, 100));
|
|
session->continueBrush(QPointF(115, 100));
|
|
QCOMPARE(painted(*session), QPointF(105, 100));
|
|
// A cancel drops the hand's point: nothing lands.
|
|
session->cancelBrush();
|
|
QVERIFY(session->finishBrushImmediately());
|
|
}
|
|
|
|
QTEST_GUILESS_MAIN(BrushSmoothingTests)
|
|
#include "BrushSmoothingTests.moc"
|