Files
OmaPhoto/tests/LayerEffectsSurviveTests.cpp
T

163 lines
7.0 KiB
C++

#include "Document/CanvasSize.h"
#include "Document/ImageTrim.h"
#include "IO/CanvasResizer.h"
#include "IO/ImageResizer.h"
#include "SessionFixtures.h"
#include <QtTest>
// Swift 1.4.9's: effects, live text and shapes outlast size changes.
class LayerEffectsSurviveTests : public QObject {
Q_OBJECT
private slots:
void canvasSizeKeepsEffectsTextAndShapes();
void cropKeepsEffectsTextAndShapes();
void trimKeepsEffectsTextAndShapes();
void resolutionOnlyImageSizeKeepsEffectsTextAndShapes();
void imageSizeKeepsEffectsScaledWithTheImage();
void aStretchScalesByTheFactorsMeanWithinRange();
};
namespace {
LayerEffects effects()
{
LayerEffects made;
made.stroke = StrokeEffect{.size = 4};
made.shadow = ShadowEffect{.distance = 20, .blur = 10};
made.outerGlow = OuterGlowEffect{.size = 8};
return made;
}
// What a layer carries beyond its pixels.
struct Extras {
std::optional<LayerEffects> effects;
std::optional<LayerTextStyle> text;
std::optional<LayerShapeStyle> shape;
friend bool operator==(const Extras &, const Extras &) = default;
};
std::vector<Extras> extras(const EditorSession &session, const std::vector<QUuid> &ids)
{
std::vector<Extras> found;
for (const QUuid id : ids) {
const ImageLayer layer = layerWith(session, id);
found.push_back({layer.effects, layer.liveText() ? std::optional(layer.liveText()->style) : std::nullopt,
layer.liveShape() ? std::optional(layer.liveShape()->style) : std::nullopt});
}
return found;
}
// A clear canvas holding text and a shape, with effects.
std::unique_ptr<EditorSession> textAndShape(std::vector<QUuid> &ids)
{
auto session = std::make_unique<EditorSession>();
session->createDocument(200, 120);
session->selectTool(NavigationTool::type);
session->beginText(QPointF(30, 60));
TextDraft draft = session->textDraft().value();
draft.style.content = QStringLiteral("Text");
if (!session->applyText(draft))
throw std::runtime_error("no text");
const QUuid text = session->activeLayerID().value();
session->selectTool(NavigationTool::shape);
session->beginShape(QPointF(120, 30));
session->dragShape(QPointF(170, 80), false, false);
session->finishShape();
const QUuid shape = session->activeLayerID().value();
rewrite(*session, [&](ProjectSnapshot &snapshot) { record(snapshot, text).effects = record(snapshot, shape).effects = effects(); });
ids = {text, shape};
const std::vector<Extras> before = extras(*session, ids);
if (shape == text || !before[0].text || !before[1].shape)
throw std::runtime_error("no live text or shape");
return session;
}
}
void LayerEffectsSurviveTests::canvasSizeKeepsEffectsTextAndShapes()
{
std::vector<QUuid> ids;
const auto session = textAndShape(ids);
const std::vector<Extras> before = extras(*session, ids);
session->applyDocumentSize(CanvasResizer::resize(session->projectSnapshot().value(), CanvasSizeOptions{.width = 260, .height = 160, .anchor = 4}),
QStringLiteral("Canvas Size"));
QCOMPARE(session->document().value().width, 260);
QVERIFY(extras(*session, ids) == before);
}
void LayerEffectsSurviveTests::cropKeepsEffectsTextAndShapes()
{
std::vector<QUuid> ids;
const auto session = textAndShape(ids);
const std::vector<Extras> before = extras(*session, ids);
session->selectTool(NavigationTool::crop);
session->setCropRect(QRectF(10, 10, 180, 100));
bool done = false;
session->commitCrop([&done] { done = true; });
QTRY_VERIFY(done);
QCOMPARE(session->document().value().width, 180);
QVERIFY(extras(*session, ids) == before);
}
void LayerEffectsSurviveTests::trimKeepsEffectsTextAndShapes()
{
std::vector<QUuid> ids;
const auto session = textAndShape(ids);
const std::vector<Extras> before = extras(*session, ids);
session->applyDocumentSize(ImageTrim::trim(session->projectSnapshot().value(), TrimOptions()).value(), QStringLiteral("Trim"));
QVERIFY(session->document().value().width < 200);
QVERIFY(extras(*session, ids) == before);
}
void LayerEffectsSurviveTests::resolutionOnlyImageSizeKeepsEffectsTextAndShapes()
{
std::vector<QUuid> ids;
const auto session = textAndShape(ids);
const std::vector<Extras> before = extras(*session, ids);
session->applyImageSize(ImageResizer::resize(session->projectSnapshot().value(), ImageSizeOptions{.width = 200, .height = 120, .resolution = 300}));
QCOMPARE(session->document().value().resolution, 300.0);
QVERIFY(extras(*session, ids) == before);
}
// Resampled pixels take their effects with them, as Scale Styles.
void LayerEffectsSurviveTests::imageSizeKeepsEffectsScaledWithTheImage()
{
std::vector<QUuid> ids;
const auto session = textAndShape(ids);
session->applyImageSize(ImageResizer::resize(session->projectSnapshot().value(), ImageSizeOptions{.width = 100, .height = 60, .resolution = 72}));
QCOMPARE(session->document().value().width, 100);
for (const Extras &kept : extras(*session, ids)) {
QVERIFY(!kept.text && !kept.shape);
const LayerEffects scaled = kept.effects.value();
QVERIFY(scaled.stroke.value().size == 2 && scaled.outerGlow.value().size == 4);
QVERIFY(scaled.shadow.value().distance == 10 && scaled.shadow.value().blur == 5 && scaled.shadow.value().angle == effects().shadow.value().angle);
}
}
void LayerEffectsSurviveTests::aStretchScalesByTheFactorsMeanWithinRange()
{
LayerEffects made;
made.stroke = StrokeEffect{.size = 300};
made.shadow = ShadowEffect{.distance = 3000, .blur = 300, .opacity = 0.4};
made.innerShadow = InnerShadowEffect{.distance = 10, .blur = 4};
made.innerGlow = InnerGlowEffect{.size = 6};
made.colorOverlay = ColorOverlayEffect{.opacity = 0.3};
const LayerEffects doubled = made.scaled(2);
// Each held to the range the effect accepts.
QVERIFY(doubled.stroke.value().size == 500 && doubled.shadow.value().distance == 5000 && doubled.shadow.value().blur == 500);
QVERIFY(doubled.innerShadow.value().distance == 20 && doubled.innerShadow.value().blur == 8 && doubled.innerGlow.value().size == 12);
QVERIFY(doubled.shadow.value().opacity == 0.4 && doubled.colorOverlay == made.colorOverlay && !doubled.outerGlow && doubled.isValid());
// Stretched 4 by 1: the mean factor is 2.
EditorSession session;
session.createDocument(20, 20);
QImage pixels(20, 20, QImage::Format_RGBA8888_Premultiplied);
pixels.fill(Qt::red);
session.insert(ImportedImage(pixels, pixels, QStringLiteral("Red")));
const QUuid id = session.activeLayerID().value();
rewrite(session, [&](ProjectSnapshot &snapshot) { record(snapshot, id).effects = effects(); });
session.applyImageSize(ImageResizer::resize(session.projectSnapshot().value(), ImageSizeOptions{.width = 80, .height = 20, .resolution = 72}));
QVERIFY(layerWith(session, id).effects == effects().scaled(2));
QCOMPARE(layerWith(session, id).effects.value().stroke.value().size, 8.0);
}
QTEST_MAIN(LayerEffectsSurviveTests)
#include "LayerEffectsSurviveTests.moc"