Files
OmaPhoto/tests/CameraRawGradingLensTests.cpp
T

272 lines
14 KiB
C++

#include "Document/BrushStroke.h"
#include "Document/EditorSession.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawControls.h"
#include "UI/CameraRawDetailOpticsControls.h"
#include "UI/CameraRawGeometryCalibrationControls.h"
#include "UI/FloatingPanel.h"
#include "UI/LayerIcons.h"
#include <QAbstractButton>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QPushButton>
#include <QToolButton>
#include <QStyleOptionSlider>
#include <QtTest>
#include <cmath>
#include <numbers>
// Swift's Color Grading, Detail, Optics, Geometry and Calibration groups.
namespace {
// The knob's middle, where a double click resets.
QPoint knob(const CameraRawSlider &slider)
{
QStyleOptionSlider option;
option.initFrom(&slider);
option.orientation = Qt::Horizontal;
option.minimum = slider.minimum();
option.maximum = slider.maximum();
option.sliderPosition = slider.sliderPosition();
option.sliderValue = slider.value();
return slider.style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, &slider).center();
}
template <typename Controls> struct Group {
EditorSession session;
std::unique_ptr<Controls> controls;
Group()
{
session.createDocument(8, 8);
QImage image = BrushRaster::context(8, 8, false);
image.fill(QColor(200, 120, 60));
session.insert(ImportedImage(image, image, QStringLiteral("Warm")));
session.beginFilter(FilterKind::cameraRaw);
controls = std::make_unique<Controls>(session);
controls->resize(374, 700);
controls->show();
}
template <typename Widget> Widget &child(const QString &name) const
{
auto *found = controls->template findChild<Widget *>(name);
if (!found)
throw std::runtime_error(name.toStdString());
return *found;
}
const CameraRawSettings &raw() const { return session.filterEdit().value().settings.cameraRaw; }
const CameraRawPanel &panel() const { return session.filterEdit().value().rawPanel; }
void set(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = session.filterEdit().value().settings;
change(settings.cameraRaw);
session.updateFilter(settings, true);
}
};
}
class CameraRawGradingLensTests : public QObject {
Q_OBJECT
private slots:
void theWheelsSetHueAndSaturationAndResetOnADoubleClick();
void theWheelsHueRunsCounterclockwise();
void onePageShowsOneWheelAndTheGradingFitsTheDock();
void detailRoundsItsRowsAndDimsIdleNoiseDetail();
void opticsTogglesProfileDefringeAndHueRanges();
void geometryUprightGuidesAndRoundedRows();
void calibrationChoosesItsProcessAndPrimaries();
};
void CameraRawGradingLensTests::theWheelsSetHueAndSaturationAndResetOnADoubleClick()
{
Group<CameraRawGradingControls> group;
auto *box = group.controls->findChild<QWidget *>(QStringLiteral("gradeWheelMidtones"));
QWidget *wheel = nullptr;
for (QWidget *child : box->findChildren<QWidget *>())
if (child->size() == QSize(86, 86))
wheel = child;
QVERIFY(wheel);
// Straight up is hue 90; at the rim, 100.
QTest::mousePress(wheel, Qt::LeftButton, {}, QPoint(43, 6));
QTest::mouseRelease(wheel, Qt::LeftButton, {}, QPoint(43, 6));
QVERIFY(std::abs(group.raw().grading.midtones.hue - 90) < 0.5 && group.raw().grading.midtones.saturation == 100);
QVERIFY(group.raw().grading.shadows.hue == 0);
QCOMPARE(box->findChild<QLabel *>(QStringLiteral("gradeReadout"))->text(), QString("90° 100"));
// The left rim, 37 points out: hue 180, saturation 100.
QTest::mousePress(wheel, Qt::LeftButton, {}, QPoint(43 - 37, 43));
QVERIFY(std::abs(group.raw().grading.midtones.hue - 180) < 0.5 && group.raw().grading.midtones.saturation == 100);
QTest::mouseRelease(wheel, Qt::LeftButton, {}, QPoint(6, 43));
// A press outside the circle does nothing.
QTest::mousePress(wheel, Qt::LeftButton, {}, QPoint(1, 1));
QVERIFY(std::abs(group.raw().grading.midtones.hue - 180) < 0.5);
QTest::mouseRelease(wheel, Qt::LeftButton, {}, QPoint(1, 1));
QTest::mouseDClick(wheel, Qt::LeftButton, {}, QPoint(43, 43));
QVERIFY(group.raw().grading.midtones.hue == 0 && group.raw().grading.midtones.saturation == 0);
auto *luminance = box->findChild<CameraRawSlider *>(QStringLiteral("gradeLuminance"));
luminance->setValue(750);
QCOMPARE(group.raw().grading.midtones.luminance, 50.0);
QCOMPARE(luminance->width(), 96);
group.child<CameraRawSlider>(QStringLiteral("gradeBlendingSlider")).setValue(200);
QCOMPARE(group.raw().grading.blending, 20.0);
auto &balance = group.child<CameraRawSlider>(QStringLiteral("gradeBalanceSlider"));
balance.setValue(250);
QCOMPARE(group.raw().grading.balance, -50.0);
QTest::mouseDClick(&balance, Qt::LeftButton, {}, knob(balance));
QCOMPARE(group.raw().grading.balance, 0.0);
}
// 1.2.9's 96edd5f: the rainbow runs the way the hue does.
void CameraRawGradingLensTests::theWheelsHueRunsCounterclockwise()
{
Group<CameraRawGradingControls> group;
auto *box = group.controls->findChild<QWidget *>(QStringLiteral("gradeWheelShadows"));
QWidget *wheel = nullptr;
for (QWidget *child : box->findChildren<QWidget *>())
if (child->size() == QSize(86, 86))
wheel = child;
const QImage drawn = wheel->grab().toImage();
const auto hueAt = [&drawn](double degrees) {
const double radians = degrees * std::numbers::pi / 180;
return drawn.pixelColor(int(43 + std::cos(radians) * 30), int(43 - std::sin(radians) * 30)).hsvHueF() * 360;
};
// Dragged to 120, the wheel shows green; 240, blue.
QVERIFY2(std::abs(hueAt(120) - 120) < 20, qPrintable(QString::number(hueAt(120))));
QVERIFY2(std::abs(hueAt(240) - 240) < 20, qPrintable(QString::number(hueAt(240))));
QVERIFY2(std::abs(hueAt(60) - 60) < 20, qPrintable(QString::number(hueAt(60))));
}
void CameraRawGradingLensTests::onePageShowsOneWheelAndTheGradingFitsTheDock()
{
Group<CameraRawGradingControls> group;
auto &page = group.child<QComboBox>(QStringLiteral("gradePage"));
QCOMPARE(page.count(), 5);
const auto shown = [&group] {
QStringList names;
for (QWidget *box : group.controls->findChildren<QWidget *>())
if (box->objectName().startsWith(QLatin1String("gradeWheel")) && box->isVisible())
names << box->objectName();
names.sort();
return names;
};
QCOMPARE(shown(), (QStringList{"gradeWheelHighlights", "gradeWheelMidtones", "gradeWheelShadows"}));
emit page.activated(4);
QVERIFY(group.panel().gradePage == CameraRawGradePage::global);
QCOMPARE(shown(), QStringList{"gradeWheelGlobal"});
emit page.activated(2);
QCOMPARE(shown(), QStringList{"gradeWheelMidtones"});
// Swift's theDockedPanelFitsTheGradingWheels: padding 24 a side, inset 18.
CameraRawGradingControls grading(group.session);
QVERIFY2(grading.sizeHint().width() <= FloatingPanel::dockedWidth - 48 - 18, qPrintable(QString::number(grading.sizeHint().width())));
}
void CameraRawGradingLensTests::detailRoundsItsRowsAndDimsIdleNoiseDetail()
{
Group<CameraRawDetailControls> group;
auto &amount = group.child<CameraRawSlider>(QStringLiteral("sharpenAmountSlider"));
amount.setValue(333);
QCOMPARE(group.raw().detail.sharpenAmount, 50.0);
auto &radius = group.child<CameraRawSlider>(QStringLiteral("sharpenRadiusSlider"));
QTest::mouseDClick(&radius, Qt::LeftButton, {}, knob(radius));
QCOMPARE(group.raw().detail.sharpenRadius, 10.0);
auto &detail = group.child<CameraRawSlider>(QStringLiteral("noiseLuminanceDetailSlider"));
QVERIFY(!detail.isEnabled());
group.child<CameraRawSlider>(QStringLiteral("noiseLuminanceSlider")).setValue(400);
QVERIFY(detail.isEnabled() && !group.child<CameraRawSlider>(QStringLiteral("noiseColorDetailSlider")).isEnabled());
// Alt on Masking shows the mask; other rows clear it.
QWindow *window = group.controls->windowHandle();
QTest::keyPress(window, Qt::Key_Alt, Qt::AltModifier);
group.child<CameraRawSlider>(QStringLiteral("sharpenMaskingSlider")).setValue(500);
QVERIFY(group.panel().sharpenMask && group.raw().detail.sharpenMasking == 50);
amount.setValue(400);
QVERIFY(!group.panel().sharpenMask);
QTest::keyRelease(window, Qt::Key_Alt);
group.child<CameraRawSlider>(QStringLiteral("sharpenMaskingSlider")).setValue(600);
QVERIFY(!group.panel().sharpenMask);
}
void CameraRawGradingLensTests::opticsTogglesProfileDefringeAndHueRanges()
{
Group<CameraRawOpticsControls> group;
auto &profile = group.child<QWidget>(QStringLiteral("profileDistortionSlider"));
QVERIFY(!profile.isVisible());
group.child<QCheckBox>(QStringLiteral("enableLensProfile")).click();
QVERIFY(group.raw().optics.enableLensProfile && profile.isVisible());
group.child<QCheckBox>(QStringLiteral("removeChromaticAberration")).click();
QVERIFY(group.raw().optics.removeChromaticAberration);
// A range from zero rounds; a signed range does not.
group.child<CameraRawSlider>(QStringLiteral("purpleAmountSlider")).setValue(333);
QCOMPARE(group.raw().optics.purpleAmount, 33.0);
group.child<CameraRawSlider>(QStringLiteral("distortionSlider")).setValue(333);
QVERIFY(std::abs(group.raw().optics.distortion - -33.4) < 1e-9);
auto &midpoint = group.child<CameraRawSlider>(QStringLiteral("lensMidpointSlider"));
midpoint.setValue(0);
QTest::mouseDClick(&midpoint, Qt::LeftButton, {}, knob(midpoint));
QCOMPARE(group.raw().optics.vignetteMidpoint, 50.0);
// Hue sliders round, and reset to their own ends.
auto &low = group.child<CameraRawSlider>(QStringLiteral("greenHueLow"));
low.setValue(111);
QCOMPARE(group.raw().optics.greenHueLow, 40.0);
QTest::mouseDClick(&low, Qt::LeftButton, {}, knob(low));
QCOMPARE(group.raw().optics.greenHueLow, 60.0);
auto &high = group.child<CameraRawSlider>(QStringLiteral("purpleHueHigh"));
QTest::mouseDClick(&high, Qt::LeftButton, {}, knob(high));
QCOMPARE(group.raw().optics.purpleHueHigh, 310.0);
auto &hint = group.child<QLabel>(QStringLiteral("defringeHint"));
QVERIFY(!hint.isVisible());
group.child<QToolButton>(QStringLiteral("defringeSampler")).click();
QVERIFY(group.panel().samplesDefringe && hint.isVisible());
}
void CameraRawGradingLensTests::geometryUprightGuidesAndRoundedRows()
{
Group<CameraRawGeometryControls> group;
auto &draw = group.child<QPushButton>(QStringLiteral("drawGuides"));
QVERIFY(!draw.isVisible());
group.child<QToolButton>(QStringLiteral("upright1")).click();
QVERIFY(group.raw().geometry.upright == CameraRawUprightMode::guided && draw.isVisible());
QVERIFY(!group.child<QPushButton>(QStringLiteral("clearGuides")).isVisible());
const QImage line = LayerIcons::pixmap(LayerIcon::lineDiagonal, 14, draw.palette().color(QPalette::ButtonText), draw.devicePixelRatioF()).toImage();
QVERIFY(draw.icon().pixmap(QSize(14, 14), draw.devicePixelRatioF()).toImage() == line);
draw.click();
QVERIFY(group.panel().drawingGeometryGuide && group.child<QLabel>(QStringLiteral("drawGuidesHint")).isVisible());
group.set([](CameraRawSettings &settings) { settings.geometry.guides = {{0.1, 0.5, 0.9, 0.5}}; });
group.child<QPushButton>(QStringLiteral("clearGuides")).click();
QVERIFY(group.raw().geometry.guides.empty());
// Off puts the guide tool away.
group.child<QToolButton>(QStringLiteral("upright0")).click();
QVERIFY(group.raw().geometry.upright == CameraRawUprightMode::off && !group.panel().drawingGeometryGuide);
emit group.child<QComboBox>(QStringLiteral("projection")).activated(1);
QVERIFY(group.raw().geometry.projection == CameraRawProjection::rectilinear);
group.child<CameraRawSlider>(QStringLiteral("rotateSlider")).setValue(333);
QCOMPARE(group.raw().geometry.rotate, -15.0);
group.child<CameraRawSlider>(QStringLiteral("offsetYSlider")).setValue(333);
QCOMPARE(group.raw().geometry.offsetY, -33.0);
group.child<QCheckBox>(QStringLiteral("constrainCrop")).click();
QVERIFY(group.raw().geometry.constrainCrop);
}
void CameraRawGradingLensTests::calibrationChoosesItsProcessAndPrimaries()
{
Group<CameraRawCalibrationControls> group;
auto &process = group.child<QComboBox>(QStringLiteral("processVersion"));
QCOMPARE(process.currentText(), QString("Version 6"));
QCOMPARE(group.child<QLabel>(QStringLiteral("processSummary")).text(), summary(CameraRawProcessVersion::version6));
emit process.activated(1);
QVERIFY(group.raw().calibration.process == CameraRawProcessVersion::version2);
QCOMPARE(group.child<QLabel>(QStringLiteral("processSummary")).text(), summary(CameraRawProcessVersion::version2));
for (const auto &[name, member] : {std::pair("shadowTint", &CameraRawCalibrationSettings::shadowTint), std::pair("redHue", &CameraRawCalibrationSettings::redHue),
std::pair("redSaturation", &CameraRawCalibrationSettings::redSaturation),
std::pair("greenHue", &CameraRawCalibrationSettings::greenHue),
std::pair("greenSaturation", &CameraRawCalibrationSettings::greenSaturation),
std::pair("blueHue", &CameraRawCalibrationSettings::blueHue),
std::pair("blueSaturation", &CameraRawCalibrationSettings::blueSaturation)}) {
auto &slider = group.child<CameraRawSlider>(QString::fromLatin1(name) + QStringLiteral("Slider"));
slider.setValue(666);
QVERIFY2(group.raw().calibration.*member == 33, name);
QTest::mouseDClick(&slider, Qt::LeftButton, {}, knob(slider));
QVERIFY2(group.raw().calibration.*member == 0, name);
}
}
QTEST_MAIN(CameraRawGradingLensTests)
#include "CameraRawGradingLensTests.moc"