Files
OmaPhoto/tests/PSDAdjustmentTests.cpp
T

156 lines
7.1 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Document/BrushStroke.h"
#include "IO/PSD/PSDDocumentBuilder.h"
#include "IO/PSD/PSDReader.h"
#include <QtTest>
// Swift's PSDAdjustmentTests: adjustments and masks as Photoshop writes them.
namespace {
QByteArray shorts(const std::vector<int> &values)
{
QByteArray data;
for (const int value : values) {
const quint16 bits = quint16(qint16(value));
data.append(char(bits >> 8)).append(char(bits & 0xFF));
}
return data;
}
// A white patch on a gray grid, as text rows.
QStringList rows(const QImage &mask)
{
QStringList result;
for (int y = 0; y < mask.height(); ++y) {
QString row;
for (int x = 0; x < mask.width(); ++x)
row += mask.constScanLine(y)[x] > 127 ? u'#' : u'.';
result << row;
}
return result;
}
QImage whitePatch()
{
QImage patch = BrushRaster::context(2, 2, true);
patch.fill(255);
return patch;
}
}
class PSDAdjustmentTests : public QObject {
Q_OBJECT
private slots:
void levelsGammaIsInHundredths();
void hueSaturationReadsMasterAndEachRange();
void maskPatchSitsWhereItIsOnTheCanvas();
void maskPatchFollowsAPixelLayersGrid();
};
void PSDAdjustmentTests::levelsGammaIsInHundredths()
{
// RGB 2–254 at gamma 2.50, the rest untouched; 29 records.
std::vector<int> values{2, 2, 254, 0, 255, 250};
for (int channel = 0; channel < 3; ++channel)
values.insert(values.end(), {0, 255, 0, 255, 100});
QByteArray data = shorts(values);
data.append(QByteArray(292 - data.size(), 0));
const LevelsSettings levels = PSDAdjustments::levels(data).value().levels;
QVERIFY(levels.ranges[0] == (LevelRange{.black = 2, .gamma = 2.5, .white = 254, .outputBlack = 0, .outputWhite = 255}));
for (size_t channel = 1; channel < 4; ++channel)
QCOMPARE(levels.ranges[channel].gamma, 1.0);
QVERIFY(!PSDAdjustments::levels(data.left(291)));
}
void PSDAdjustmentTests::hueSaturationReadsMasterAndEachRange()
{
// Version 2, Colorize off; its values, the Master's, the Reds'.
QByteArray data = shorts({2}) + QByteArray(2, 0) + shorts({23, 25, 0, 5, 4, 0, 315, 345, 15, 45, 0, -30, 10});
// Yellows' band wraps: −30 reads as 330.
data += shorts({-30, 15, 45, 75, 7, 8, 9});
data += shorts(std::vector<int>(4 * 7, 0));
const HueSaturationSettings settings = PSDAdjustments::hue(data).value().hsvSettings.value();
QVERIFY(!settings.colorize);
QVERIFY(settings.adjustments.at(ColorRange::master) == (RangeAdjustment{5, 4, 0}));
QVERIFY(settings.adjustments.at(ColorRange::reds) == (RangeAdjustment{0, -30, 10}));
QVERIFY(settings.bands.at(ColorRange::reds) == (HueBand{315, 345, 15, 45}));
QVERIFY(settings.bands.at(ColorRange::yellows) == (HueBand{330, 15, 45, 75}));
QVERIFY(settings.adjustments.at(ColorRange::yellows) == (RangeAdjustment{7, 8, 9}));
QVERIFY(settings.adjustments.at(ColorRange::magentas) == RangeAdjustment());
// Colorize on: its own values apply, and nothing else.
data[2] = 1;
const HueSaturationSettings colorized = PSDAdjustments::hue(data).value().hsvSettings.value();
QVERIFY(colorized.colorize && colorized.adjustments.at(ColorRange::master) == (RangeAdjustment{23, 25, 0}));
QVERIFY(!colorized.adjustments.contains(ColorRange::reds));
QVERIFY(colorized.bands.at(ColorRange::reds) == defaultBand(ColorRange::reds));
// Cut short: the ranges that fit; under 16 bytes, none.
data[2] = 0;
const HueSaturationSettings cut = PSDAdjustments::hue(data.left(16 + 14 + 13)).value().hsvSettings.value();
QVERIFY(cut.adjustments.at(ColorRange::reds) == (RangeAdjustment{0, -30, 10}));
QVERIFY(!cut.adjustments.contains(ColorRange::yellows));
QVERIFY(!PSDAdjustments::hue(data.left(15)));
}
void PSDAdjustmentTests::maskPatchSitsWhereItIsOnTheCanvas()
{
// A white patch at (3, 1), black round it.
PSDRecord record;
record.id = QUuid::createUuid();
record.maskBounds = QRectF(3, 1, 2, 2);
record.maskDefault = 0;
const ImageLayer layer(QStringLiteral("Levels"), QSizeF(6, 4));
const QImage mask = PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value();
QCOMPARE(mask.size(), QSize(6, 4));
QCOMPARE(rows(mask), QStringList({"......", "...##.", "...##.", "......"}));
// Through the builder, the patch at the bottom left.
record.mask = whitePatch();
record.maskBounds = QRectF(0, 2, 2, 2);
record.adjustment = LayerAdjustment{.kind = AdjustmentKind::levels};
record.kind = PSDLayerKind::adjustment;
const PSDImport imported = PSDDocumentBuilder::makeImport(PSDDocument{6, 4, 72, {record}});
QCOMPARE(rows(imported.layers.at(0).mask.value().asset.image()), QStringList({"......", "......", "##....", "##...."}));
QCOMPARE(imported.conversions.size(), size_t(1));
QCOMPARE(imported.conversions[0].message, QStringLiteral("Adjustment parameters may not match Photoshop exactly."));
}
void PSDAdjustmentTests::maskPatchFollowsAPixelLayersGrid()
{
QImage pixels = BrushRaster::context(4, 2, false);
pixels.fill(Qt::red);
ImageLayer layer(ImportedImage(pixels, pixels, QStringLiteral("Red")), QPointF(2, 1));
PSDRecord record;
record.maskBounds = QRectF(3, 1, 2, 2);
record.maskDefault = 0;
QCOMPARE(rows(PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value()), QStringList({".##.", ".##."}));
// A layer stretched twice as wide: the patch is too.
layer.transform.size = QSizeF(2, 2);
QCOMPARE(rows(PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value()), QStringList({"..##", "..##"}));
// Already on the grid: the patch itself.
QImage grid = BrushRaster::context(4, 2, true);
record.maskBounds = QRectF(2, 1, 2, 2);
QCOMPARE(PSDDocumentBuilder::maskOnLayerGrid(grid, record, layer, QSizeF(6, 4)).value().cacheKey(), grid.cacheKey());
// No bounds, or a sizeless layer: the patch stays.
record.maskBounds = QRectF(2, 1, 0, 2);
QCOMPARE(PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value().size(), QSize(2, 2));
record.maskBounds = QRectF(2, 1, 2, 0);
QCOMPARE(PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value().size(), QSize(2, 2));
record.maskBounds = QRectF(2, 1, 2, 2);
for (const QSizeF size : {QSizeF(0, 2), QSizeF(2, 0)}) {
layer.transform.size = size;
QCOMPARE(PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value().size(), QSize(2, 2));
}
// The grid's bounds, a smaller patch: drawn up to it.
layer.transform.size = QSizeF(4, 2);
record.maskBounds = QRectF(2, 1, 4, 2);
QCOMPARE(rows(PSDDocumentBuilder::maskOnLayerGrid(whitePatch(), record, layer, QSizeF(6, 4)).value()), QStringList({"####", "####"}));
// The grid's size, a pixel along: placed there.
QImage wide = BrushRaster::context(4, 2, true);
wide.fill(255);
record.maskBounds = QRectF(3, 1, 4, 2);
QCOMPARE(rows(PSDDocumentBuilder::maskOnLayerGrid(wide, record, layer, QSizeF(6, 4)).value()), QStringList({".###", ".###"}));
}
QTEST_GUILESS_MAIN(PSDAdjustmentTests)
#include "PSDAdjustmentTests.moc"