14.26: adaptive Contrast, Highlights and Shadows; Dehaze, Texture, Clarity and Sharpening

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 13:33:45 -04:00
1 parent a1453df517
commit 32f6362787
12 files changed
+289 -153

No files matched your search

+57 -21
View File
@@ -11,6 +11,7 @@ private slots:
void theTablesDecodeAsWritten();
void aStageBlendsTheTablesAroundItsValue();
void eachSliderPicksItsOwnTables();
void theAdaptiveSlidersBlendAcrossBrightness();
void neutralizeFindsTheGridsGrayest();
void identitySlidersComposeNothing();
};
@@ -32,14 +33,16 @@ int indexOf(const uint8_t *table)
void CameraRawTablesTests::theTablesDecodeAsWritten()
{
const std::vector<uint8_t> &tables = CameraRawTables::tables();
QCOMPARE(tables.size(), size_t(147) * 17 * 17 * 17 * 3);
QCOMPARE(tables.size(), size_t(285) * 17 * 17 * 17 * 3);
quint64 hash = 0xcbf29ce484222325ULL;
for (const uint8_t byte : tables)
hash = (hash ^ byte) * 0x100000001b3ULL;
QCOMPARE(hash, quint64(0x6a9f829e703f2df6ULL));
QCOMPARE(hash, quint64(0x770d674136320424ULL));
QCOMPARE(std::vector<int>(entry(40, 2456), entry(40, 2456) + 3), (std::vector<int>{128, 128, 128}));
QCOMPARE(std::vector<int>(entry(81, 100), entry(81, 100) + 3), (std::vector<int>{0, 4, 32}));
QCOMPARE(std::vector<int>(entry(146, 4912), entry(146, 4912) + 3), (std::vector<int>{255, 255, 255}));
QCOMPARE(std::vector<int>(entry(200, 2456), entry(200, 2456) + 3), (std::vector<int>{172, 172, 172}));
QCOMPARE(std::vector<int>(entry(284, 100), entry(284, 100) + 3), (std::vector<int>{70, 108, 255}));
}
void CameraRawTablesTests::aStageBlendsTheTablesAroundItsValue()
@@ -64,29 +67,62 @@ void CameraRawTablesTests::eachSliderPicksItsOwnTables()
CameraRawSettings settings;
settings.exposure = 1.25;
settings.temperature = 30;
settings.contrast = -100;
settings.whites = 100;
settings.blacks = 12.5;
settings.saturation = -62.5;
settings.vibrance = 80;
const CameraRawTables::Stages stages = CameraRawTables::stages(settings);
QCOMPARE(stages.before.size(), size_t(3));
QCOMPARE(stages.after.size(), size_t(4));
const std::vector<CameraRawStage> stages = CameraRawTables::stages(settings, {});
QCOMPARE(stages.size(), size_t(6));
const auto pair = [](const CameraRawStage &stage) {
return std::vector<double>{double(indexOf(stage.table[0])), double(indexOf(stage.table[1])), stage.weight[0], stage.weight[1]};
};
QCOMPARE(pair(stages.before[0]), (std::vector<double>{93, 94, 0.5, 0.5}));
QCOMPARE(indexOf(stages.before[1].table[0]), 5 * 9 + 4);
QCOMPARE(pair(stages.before[2]), (std::vector<double>{102, 103, 1, 0}));
QCOMPARE(pair(stages.after[0]), (std::vector<double>{118, 119, 0, 1}));
QCOMPARE(pair(stages.after[1]), (std::vector<double>{124, 125, 0.5, 0.5}));
QCOMPARE(pair(stages.after[2]), (std::vector<double>{130, 131, 0.5, 0.5}));
QVERIFY(indexOf(stages.after[3].table[0]) == 145 && std::abs(stages.after[3].weight[1] - 0.2f) < 1e-6);
QCOMPARE(pair(stages[0]), (std::vector<double>{93, 94, 0.5, 0.5}));
QCOMPARE(indexOf(stages[1].table[0]), 5 * 9 + 4);
QCOMPARE(pair(stages[2]), (std::vector<double>{109, 110, 0, 1}));
QCOMPARE(pair(stages[3]), (std::vector<double>{115, 116, 0.5, 0.5}));
QCOMPARE(pair(stages[4]), (std::vector<double>{121, 122, 0.5, 0.5}));
QVERIFY(indexOf(stages[5].table[0]) == 136 && std::abs(stages[5].weight[1] - 0.2f) < 1e-6);
// Tint alone still makes the white balance stage.
CameraRawSettings tint;
tint.tint = -25;
const CameraRawTables::Stages tinted = CameraRawTables::stages(tint);
QVERIFY(tinted.before.size() == 1 && tinted.after.empty() && indexOf(tinted.before[0].table[0]) == 4 * 9 + 3);
const std::vector<CameraRawStage> tinted = CameraRawTables::stages(tint, {});
QVERIFY(tinted.size() == 1 && indexOf(tinted[0].table[0]) == 4 * 9 + 3);
}
// Seven brightnesses down, seven values across, each read alone.
void CameraRawTablesTests::theAdaptiveSlidersBlendAcrossBrightness()
{
const auto four = [](const CameraRawStage &stage) {
return std::vector<double>{double(indexOf(stage.table[0])), double(indexOf(stage.table[1])), double(indexOf(stage.table[2])),
double(indexOf(stage.table[3])), stage.weight[0], stage.weight[1], stage.weight[2], stage.weight[3]};
};
CameraRawSettings settings;
settings.contrast = 10;
settings.highlights = -100;
settings.shadows = 100;
settings.saturation = 25;
const std::vector<CameraRawStage> stages = CameraRawTables::stages(settings, {120, 250, 30});
QCOMPARE(stages.size(), size_t(4));
QCOMPARE(four(stages[0]), (std::vector<double>{155, 156, 162, 163, 0.23076923191547394, 0.1538461595773697, 0.3692307770252228, 0.2461538463830948}));
QCOMPARE(four(stages[1]), (std::vector<double>{187, 188, 194, 195, 1, 0, 0, 0}));
QCOMPARE(four(stages[2]), (std::vector<double>{276, 277, 283, 284, 0, 0, 0, 1}));
QCOMPARE(indexOf(stages[3].table[0]), 125);
// Contrast reads the brightest channel, Highlights the log-average.
settings = CameraRawSettings();
settings.contrast = -37.5;
settings.highlights = 50;
const std::vector<CameraRawStage> low = CameraRawTables::stages(settings, {53, 0, 224});
QCOMPARE(four(low[0]), (std::vector<double>{139, 140, 146, 147, 0.5, 0.5, 0, 0}));
QCOMPARE(four(low[1]), (std::vector<double>{226, 227, 233, 234, 0, 0, 0, 1}));
// On a measured point the lower pair takes it whole.
settings = CameraRawSettings();
settings.shadows = 25;
const std::vector<CameraRawStage> on = CameraRawTables::stages(settings, {0, 104, 0});
QCOMPARE(four(on[0]), (std::vector<double>{246, 247, 253, 254, 0, 0, 0, 1}));
// A mid-gray picture reads 128 by every measure.
const QImage mid = gray(8, 8);
const CameraRawTables::Brightness read = CameraRawTables::brightness(mid.constBits(), 8, 8, mid.bytesPerLine());
QVERIFY(std::abs(read.brightest - 128) < 0.01 && std::abs(read.luminance - 128) < 0.01 && std::abs(read.logBrightest - 128) < 0.01);
}
void CameraRawTablesTests::neutralizeFindsTheGridsGrayest()
@@ -118,11 +154,11 @@ void CameraRawTablesTests::neutralizeFindsTheGridsGrayest()
void CameraRawTablesTests::identitySlidersComposeNothing()
{
const CameraRawTables::Stages none = CameraRawTables::stages(CameraRawSettings());
QVERIFY(none.before.empty() && none.after.empty() && CameraRawTables::compose(none.before).empty());
const std::vector<CameraRawStage> none = CameraRawTables::stages(CameraRawSettings(), {});
QVERIFY(none.empty() && CameraRawTables::compose(none).empty());
CameraRawSettings exposure;
exposure.exposure = 0.5;
const std::vector<float> composed = CameraRawTables::compose(CameraRawTables::stages(exposure).before);
const std::vector<float> composed = CameraRawTables::compose(CameraRawTables::stages(exposure, {}));
QCOMPARE(composed.size(), size_t(33) * 33 * 33 * 3);
// The corners are its table's own black and white.
QVERIFY(composed[0] == entry(92, 0)[0] / 255.0f && composed.back() == entry(92, 4912)[2] / 255.0f);
@@ -131,7 +167,7 @@ void CameraRawTablesTests::identitySlidersComposeNothing()
three.exposure = 1;
three.temperature = 40;
three.contrast = 50;
const std::vector<float> run = CameraRawTables::compose(CameraRawTables::stages(three).before);
const std::vector<float> run = CameraRawTables::compose(CameraRawTables::stages(three, {}));
const auto at = [&](int r, int g, int b) {
const size_t index = ((size_t(r) * 33 + g) * 33 + b) * 3;
return std::array<float, 3>{run[index], run[index + 1], run[index + 2]};
@@ -139,8 +175,8 @@ void CameraRawTablesTests::identitySlidersComposeNothing()
const auto near = [](std::array<float, 3> got, std::array<float, 3> expected) {
return std::abs(got[0] - expected[0]) < 1e-6f && std::abs(got[1] - expected[1]) < 1e-6f && std::abs(got[2] - expected[2]) < 1e-6f;
};
QVERIFY(near(at(16, 16, 16), {0.94074458f, 0.85085034f, 0.72421813f}));
QVERIFY(near(at(16, 4, 28), {0.85322261f, 0.50834316f, 0.98791242f}));
QVERIFY(near(at(16, 16, 16), {0.94272572f, 0.86208665f, 0.74603087f}));
QVERIFY(near(at(16, 4, 28), {0.86772656f, 0.55830115f, 0.98769760f}));
}
QTEST_GUILESS_MAIN(CameraRawTablesTests)