Files
OmaPhoto/tests/CameraRawColorTests.cpp
T

168 lines
7.2 KiB
C++

#include "CameraRawFixtures.h"
#include <QtTest>
#include <cmath>
// Curve, Color Mixer and Color Grading, rule by rule.
using namespace CameraRawFixtures;
namespace {
using Points = std::vector<CurvePoint>;
Points repaired(const Points &points)
{
CameraRawCurveSettings curve;
curve.rgb = points;
return curve.normalized().rgb;
}
int shadowTint(double balance)
{
CameraRawSettings settings;
settings.grading.shadows.saturation = 100;
settings.grading.balance = balance;
const std::array<int, 4> pixel = pixels(settings.apply(image(4, 4, 0.12, 0.12, 0.12)))[0];
return pixel[0] - pixel[1];
}
}
class CameraRawColorTests : public QObject {
Q_OBJECT
private slots:
void curvesAreRepairedIntoOrder();
void aToneFindsItsRegion();
void aNudgeMovesTheFirstNearestPointWithinRange();
void splitsAndAmountsStayInTheirRanges();
void mixerFamiliesOverlapRoundTheWheel();
void pointColorsStayInTheirRanges();
void gradingWheelsAndBalanceReachTheKernel();
void aPointColorReachesOnlyItsRange();
void everyGroupSaysWhenItAdjusts();
};
void CameraRawColorTests::curvesAreRepairedIntoOrder()
{
QCOMPARE(repaired({{1, 1}, {0.5, 0.2}, {0, 0}}), (Points{{0, 0}, {0.5, 0.2}, {1, 1}}));
QCOMPARE(repaired({{0.3, 0.4}}), CameraRawCurveSettings::linear());
QCOMPARE(repaired({{0, 0}, {std::nan(""), 0.5}, {1, 1}}), CameraRawCurveSettings::linear());
// Ends pinned to 0 and 1, heights clamped.
QCOMPARE(repaired({{0.1, -0.5}, {0.9, 1.5}}), CameraRawCurveSettings::linear());
// Inner points keep a hundredth from ends and neighbours.
QCOMPARE(repaired({{0, 0}, {0.995, 0.5}, {1, 1}}), (Points{{0, 0}, {0.99, 0.5}, {1, 1}}));
QCOMPARE(repaired({{0, 0}, {0.5, 0.3}, {0.505, 0.6}, {1, 1}}), (Points{{0, 0}, {0.5, 0.3}, {1, 1}}));
QCOMPARE(repaired({{0, 0}, {0.004, 0.3}, {1, 1}}), CameraRawCurveSettings::linear());
// Exactly a hundredth past the last kept is too near.
QCOMPARE(repaired({{0, 0}, {0.5, 0.3}, {0.5 + 0.01, 0.6}, {1, 1}}), (Points{{0, 0}, {0.5, 0.3}, {1, 1}}));
QCOMPARE(repaired({{0, 0}, {0.5, 0.3}, {0.5 + 0.0100001, 0.6}, {1, 1}}).size(), size_t(4));
// A curve of one point passes its input through.
CameraRawCurveSettings curve;
const std::vector<float> table = curve.channelTable({{0.5, 0.7}});
QCOMPARE(table[100], float(100 / 255.0));
}
void CameraRawColorTests::aToneFindsItsRegion()
{
// Region picks the amount a tone moves.
CameraRawCurveSettings regions;
regions.region(0.1) = 1;
regions.region(0.3) = 2;
regions.region(0.6) = 3;
regions.region(0.9) = 4;
QVERIFY(regions.shadows == 1 && regions.darks == 2 && regions.lights == 3 && regions.highlights == 4);
}
void CameraRawColorTests::aNudgeMovesTheFirstNearestPointWithinRange()
{
const CameraRawCurveSettings curve;
// Halfway between two points, the first is nearest.
QCOMPARE(curve.nudged(CameraRawPointChannel::green, 0.5, 0.2).green, (Points{{0, 0.2}, {1, 1}}));
QCOMPARE(curve.nudged(CameraRawPointChannel::blue, 0.9, 5).blue, (Points{{0, 0}, {1, 1}}));
QCOMPARE(curve.nudged(CameraRawPointChannel::rgb, 0.1, -5).rgb, (Points{{0, 0}, {1, 1}}));
QCOMPARE(curve.nudged(CameraRawPointChannel::rgb, 0.9, -0.25).rgb, (Points{{0, 0}, {1, 0.75}}));
}
void CameraRawColorTests::splitsAndAmountsStayInTheirRanges()
{
CameraRawCurveSettings curve;
curve.shadowSplit = 25;
curve.darkSplit = 20;
curve.lightSplit = 26;
curve.refineSaturation = 150;
const CameraRawCurveSettings normal = curve.normalized();
QVERIFY(normal.darkSplit == 27 && normal.lightSplit == 29 && normal.refineSaturation == 100);
curve.shadowSplit = 1;
QCOMPARE(curve.normalized().shadowSplit, 5.0);
}
void CameraRawColorTests::mixerFamiliesOverlapRoundTheWheel()
{
const std::array<double, 8> near = CameraRawMixerSettings::weights(350);
QCOMPARE(near[0], 0.75);
QCOMPARE(near[7], 0.0);
const std::array<double, 8> orange = CameraRawMixerSettings::weights(30);
QVERIFY(orange[0] == 0.25 && orange[1] == 1 && orange[2] == 0.25 && orange[3] == 0);
CameraRawMixerSettings mixer;
mixer.points = std::vector<CameraRawPointColor>(9);
QCOMPARE(mixer.normalized().points.size(), size_t(8));
}
void CameraRawColorTests::pointColorsStayInTheirRanges()
{
const CameraRawPointColor wild{.hue = 400, .saturation = 2, .luminance = -1, .hueShift = 300, .saturationShift = -300, .luminanceShift = 101,
.hueRange = 200, .saturationRange = 0.01, .luminanceRange = 2};
const CameraRawPointColor normal = wild.normalized();
QVERIFY(normal.hue == 360 && normal.saturation == 1 && normal.luminance == 0 && normal.hueShift == 100 && normal.saturationShift == -100);
QVERIFY(normal.luminanceShift == 100 && normal.hueRange == 180 && normal.saturationRange == 0.05 && normal.luminanceRange == 1);
QCOMPARE(CameraRawPointColor{.hueRange = 2}.normalized().hueRange, 5.0);
const CameraRawGradeWheel wheel = CameraRawGradeWheel{400, -20, -150}.normalized();
QVERIFY(wheel.hue == 360 && wheel.saturation == 0 && wheel.luminance == -100);
CameraRawGradingSettings grading;
grading.blending = 150;
grading.balance = -150;
QVERIFY(grading.normalized().blending == 100 && grading.normalized().balance == -100);
}
void CameraRawColorTests::gradingWheelsAndBalanceReachTheKernel()
{
// Balance runs in hundredths: half is between none and all.
const int none = shadowTint(0), half = shadowTint(50), all = shadowTint(100);
QVERIFY2(none > half && half > all, qPrintable(QStringLiteral("%1 %2 %3").arg(none).arg(half).arg(all)));
// Luminance runs in hundredths too: measured once, then held.
const auto lifted = [](double luminance) {
CameraRawSettings settings;
settings.grading.shadows.luminance = luminance;
return pixels(settings.apply(image(4, 4, 0.12, 0.12, 0.12)))[0][0];
};
QVERIFY(lifted(0) == 31 && lifted(50) == 53 && lifted(100) == 76);
}
void CameraRawColorTests::aPointColorReachesOnlyItsRange()
{
CameraRawSettings settings;
settings.mixer.points = {CameraRawPointColor{.hue = 0, .saturation = 1, .luminance = 0.5, .luminanceShift = -100, .hueRange = 30}};
const std::array<int, 4> red = pixels(settings.apply(image(4, 4, 1, 0, 0)))[0];
QVERIFY(red[0] < 240);
// Fifty degrees off lies outside thirty either way.
const QImage orange = image(4, 4, 1, 50 / 60.0, 0);
QVERIFY(pixels(settings.apply(orange)) == pixels(orange));
}
void CameraRawColorTests::everyGroupSaysWhenItAdjusts()
{
CameraRawCurveSettings refined;
refined.refineSaturation = 10;
CameraRawCurveSettings blue;
blue.blue = {{0, 0.1}, {1, 1}};
QVERIFY(refined.adjusts() && blue.adjusts() && !CameraRawCurveSettings().adjusts());
CameraRawMixerSettings lighter;
lighter.points = {CameraRawPointColor{.luminanceShift = 10}};
QVERIFY(lighter.adjusts() && !CameraRawMixerSettings{.points = {CameraRawPointColor{.hue = 40}}}.adjusts());
CameraRawGradingSettings brighter;
brighter.global.luminance = 10;
CameraRawGradingSettings balanced;
balanced.balance = 50;
QVERIFY(brighter.adjusts() && !balanced.adjusts());
}
QTEST_GUILESS_MAIN(CameraRawColorTests)
#include "CameraRawColorTests.moc"