#include "CameraRawFixtures.h" #include #include // Curve, Color Mixer and Color Grading, rule by rule. using namespace CameraRawFixtures; namespace { using Points = std::vector; 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 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 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 near = CameraRawMixerSettings::weights(350); QCOMPARE(near[0], 0.75); QCOMPARE(near[7], 0.0); const std::array 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(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 comes from Photoshop's tables: Python's own lookup. 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) == 46 && lifted(100) == 56); } void CameraRawColorTests::aPointColorReachesOnlyItsRange() { CameraRawSettings settings; settings.mixer.points = {CameraRawPointColor{.hue = 0, .saturation = 1, .luminance = 0.5, .luminanceShift = -100, .hueRange = 30}}; const std::array 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"