Sharper canvas, PSD adjustments and masks, Photoshop's Saturation, Levels and Hue/Saturation on every core, a growing blur's preview kept, the hierarchy kept (13.8)
This commit is contained in:
1 parent
765e071c0c
commit
cb85b86d58
46 files changed
+1041
-186
No files matched your search
@@ -0,0 +1,155 @@
|
||||
#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"
|
||||
Reference in new issue
Block a user