Camera Raw panel's remaining groups and the Filter menu entry (12.8b2)

This commit is contained in:
雷电芽衣 committed 2026-09-28 05:40:09 -04:00
1 parent bb8129e8d5
commit 7ae263de80
26 files changed
+3227 -82

No files matched your search

+260
View File
@@ -0,0 +1,260 @@
#include "Document/BrushStroke.h"
#include "Document/EditorSession.h"
#include "UI/CameraRawColorControls.h"
#include "UI/LayerIcons.h"
#include <QAbstractButton>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QPushButton>
#include <QToolButton>
#include <QStyleOptionSlider>
#include <QtTest>
// Swift's Curve and Color Mixer 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, 600);
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);
}
};
void press(QWidget &widget, QPoint at)
{
QTest::mousePress(&widget, Qt::LeftButton, {}, at);
}
void drag(QWidget &widget, QPoint to)
{
QMouseEvent move(QEvent::MouseMove, to, widget.mapToGlobal(to), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(&widget, &move);
}
}
class CameraRawCurveMixerTests : public QObject {
Q_OBJECT
private slots:
void theParametricPageMovesRegionsAndDividers();
void thePointPageAddsMovesAndRemovesPoints();
void presetsAndRefineSaturation();
void targetedAdjustmentArmsOneAtATime();
void hslRowsWriteTheirTabsFamilies();
void theColorPageEditsTheChosenFamily();
void pointColorsArePickedChosenAndShifted();
};
void CameraRawCurveMixerTests::theParametricPageMovesRegionsAndDividers()
{
Group<CameraRawCurveControls> group;
QVERIFY(group.child<QToolButton>(QStringLiteral("curvePage0")).isChecked() && !group.child<QWidget>(QStringLiteral("curveSelectedPoint")).isVisible());
for (const auto &[name, member] : {std::pair("curveHighlights", &CameraRawCurveSettings::highlights), std::pair("curveLights", &CameraRawCurveSettings::lights),
std::pair("curveDarks", &CameraRawCurveSettings::darks), std::pair("curveShadows", &CameraRawCurveSettings::shadows)}) {
auto &slider = group.child<CameraRawSlider>(QString::fromLatin1(name) + QStringLiteral("Slider"));
slider.setValue(800);
QCOMPARE(group.raw().curve.*member, 60.0);
// A double click on the knob resets it.
QTest::mouseDClick(&slider, Qt::LeftButton, {}, knob(slider));
QCOMPARE(group.raw().curve.*member, 0.0);
}
// A drag moves the nearest divider, within 2 and 98.
auto &graph = group.child<QWidget>(QStringLiteral("cameraRawCurveGraph"));
press(graph, QPoint(int(graph.width() * 0.3), 75));
drag(graph, QPoint(int(graph.width() * 0.35), 75));
QVERIFY(std::abs(group.raw().curve.shadowSplit - 35) < 1 && group.raw().curve.darkSplit == 50);
drag(graph, QPoint(graph.width() + 40, 75));
QVERIFY(group.raw().curve.lightSplit == 98 || group.raw().curve.shadowSplit == 98 || group.raw().curve.darkSplit == 98);
QTest::mouseRelease(&graph, Qt::LeftButton, {}, QPoint(graph.width() + 40, 75));
// A double click here leaves the point curve's points.
group.set([](CameraRawSettings &settings) { settings.curve.rgb = {{0, 0}, {0.5, 0.5}, {1, 1}}; });
QTest::mouseDClick(&graph, Qt::LeftButton, {}, QPoint(graph.width() / 2, graph.height() / 2));
QCOMPARE(group.raw().curve.rgb.size(), size_t(3));
}
void CameraRawCurveMixerTests::thePointPageAddsMovesAndRemovesPoints()
{
Group<CameraRawCurveControls> group;
group.child<QToolButton>(QStringLiteral("curvePage1")).click();
QVERIFY(group.panel().curvePage == CameraRawCurvePage::point);
group.child<QToolButton>(QStringLiteral("pointChannel1")).click();
QVERIFY(group.panel().pointChannel == CameraRawPointChannel::red);
auto &graph = group.child<QWidget>(QStringLiteral("cameraRawCurveGraph"));
const int w = graph.width(), h = graph.height();
// A still click adds a point where it lands.
QTest::mouseClick(&graph, Qt::LeftButton, {}, QPoint(w / 2, h / 4));
QCOMPARE(group.raw().curve.red.size(), size_t(3));
QVERIFY(std::abs(group.raw().curve.red[1].x - 0.5) < 0.01 && std::abs(group.raw().curve.red[1].y - 0.75) < 0.01);
QVERIFY(group.raw().curve.rgb == CameraRawCurveSettings::linear());
QCOMPARE(group.child<QLabel>(QStringLiteral("curveSelectedPoint")).text(), QString("In %1 Out %2").arg(std::lround(group.raw().curve.red[1].x * 255))
.arg(std::lround(group.raw().curve.red[1].y * 255)));
// A drag moves the nearest inner point.
press(graph, QPoint(w / 2, h / 4));
drag(graph, QPoint(int(w * 0.6), h / 2));
QTest::mouseRelease(&graph, Qt::LeftButton, {}, QPoint(int(w * 0.6), h / 2));
QCOMPARE(group.raw().curve.red.size(), size_t(3));
QVERIFY(std::abs(group.raw().curve.red[1].x - 0.6) < 0.01 && std::abs(group.raw().curve.red[1].y - 0.5) < 0.01);
// A double click far off removes nothing; near, it does.
QTest::mouseDClick(&graph, Qt::LeftButton, {}, QPoint(int(w * 0.1), h / 2));
QCOMPARE(group.raw().curve.red.size(), size_t(3));
const size_t before = group.raw().curve.red.size();
QTest::mouseDClick(&graph, Qt::LeftButton, {}, QPoint(int(w * 0.6), h / 2));
QCOMPARE(group.raw().curve.red.size(), before - 1);
}
void CameraRawCurveMixerTests::presetsAndRefineSaturation()
{
Group<CameraRawCurveControls> group;
group.child<QToolButton>(QStringLiteral("curvePage1")).click();
auto &preset = group.child<QComboBox>(QStringLiteral("curvePreset"));
QCOMPARE(preset.currentText(), QString("Linear"));
emit preset.activated(3);
QVERIFY(group.raw().curve.rgb == CameraRawCurveSettings::strongContrast());
QCOMPARE(preset.currentText(), QString("Strong Contrast"));
emit preset.activated(2);
QVERIFY(group.raw().curve.rgb == CameraRawCurveSettings::mediumContrast());
emit preset.activated(1);
QVERIFY(group.raw().curve.rgb == CameraRawCurveSettings::linear());
group.set([](CameraRawSettings &settings) { settings.curve.rgb = {{0, 0}, {0.4, 0.3}, {1, 1}}; });
QCOMPARE(preset.currentText(), QString("Custom"));
// Custom changes nothing; Refine Saturation shows for RGB alone.
emit preset.activated(0);
QCOMPARE(group.raw().curve.rgb.size(), size_t(3));
auto &refine = group.child<CameraRawSlider>(QStringLiteral("refineSaturationSlider"));
QVERIFY(refine.isVisible());
refine.setValue(750);
QCOMPARE(group.raw().curve.refineSaturation, 50.0);
group.child<QToolButton>(QStringLiteral("pointChannel2")).click();
QVERIFY(!refine.isVisible());
}
void CameraRawCurveMixerTests::targetedAdjustmentArmsOneAtATime()
{
Group<CameraRawCurveControls> curve;
CameraRawMixerControls mixer(curve.session);
auto &curveTarget = curve.child<QPushButton>(QStringLiteral("curveTargeted"));
auto *mixerTarget = mixer.findChild<QPushButton *>(QStringLiteral("mixerTargeted"));
curveTarget.click();
QVERIFY(curve.panel().targetsCurve && curveTarget.isChecked());
mixerTarget->click();
QVERIFY(curve.panel().targetsMixer && !curve.panel().targetsCurve && !curveTarget.isChecked() && mixerTarget->isChecked());
mixerTarget->click();
QVERIFY(!curve.panel().targetsMixer);
mixerTarget->click();
curveTarget.click();
QVERIFY(curve.panel().targetsCurve && !curve.panel().targetsMixer);
// Both carry Swift's scope glyph.
const qreal ratio = curveTarget.devicePixelRatioF();
const QImage scope = LayerIcons::pixmap(LayerIcon::scope, 14, curveTarget.palette().color(QPalette::ButtonText), ratio).toImage();
QVERIFY(curveTarget.icon().pixmap(QSize(14, 14), ratio).toImage() == scope && mixerTarget->icon().pixmap(QSize(14, 14), ratio).toImage() == scope);
}
void CameraRawCurveMixerTests::hslRowsWriteTheirTabsFamilies()
{
Group<CameraRawMixerControls> group;
QVERIFY(group.child<QToolButton>(QStringLiteral("mixerPage0")).isChecked());
for (int tab = 0; tab < 3; ++tab) {
group.child<QToolButton>(QStringLiteral("mixerTab%1").arg(tab)).click();
for (int index = 0; index < 8; ++index) {
const QString name = CameraRawMixerSettings::names.at(size_t(index));
auto &slider = group.child<CameraRawSlider>(QStringLiteral("family") + name + QStringLiteral("Slider"));
QCOMPARE(slider.toolTip(), QStringLiteral("%1 of %2.").arg(tab == 0 ? "Hue" : tab == 1 ? "Saturation" : "Luminance", name));
slider.setValue(600 + index * 10);
const std::array<double, 8> &family = tab == 0 ? group.raw().mixer.hue : tab == 1 ? group.raw().mixer.saturation : group.raw().mixer.luminance;
QVERIFY(std::abs(family.at(size_t(index)) - (20 + index * 2)) < 1e-9);
}
}
// Each tab's rows show its own track: saturation starts gray.
const QImage drawn = group.child<CameraRawSlider>(QStringLiteral("familyGreensSlider")).grab().toImage();
group.child<QToolButton>(QStringLiteral("mixerTab1")).click();
const QImage gray = group.child<CameraRawSlider>(QStringLiteral("familyGreensSlider")).grab().toImage();
QVERIFY(drawn != gray);
const QColor start = gray.pixelColor(10, gray.height() / 2);
QVERIFY(std::abs(start.red() - start.green()) < 12);
}
void CameraRawCurveMixerTests::theColorPageEditsTheChosenFamily()
{
Group<CameraRawMixerControls> group;
group.child<QToolButton>(QStringLiteral("mixerPage1")).click();
QVERIFY(group.panel().mixerPage == CameraRawMixerPage::color);
group.child<QAbstractButton>(QStringLiteral("mixerSwatch5")).click();
QCOMPARE(group.panel().mixerSwatch, 5);
group.child<CameraRawSlider>(QStringLiteral("colorLuminanceSlider")).setValue(250);
QCOMPARE(group.raw().mixer.luminance[5], -50.0);
QCOMPARE(group.raw().mixer.luminance[4], 0.0);
group.child<CameraRawSlider>(QStringLiteral("colorHueSlider")).setValue(750);
QCOMPARE(group.raw().mixer.hue[5], 50.0);
QVERIFY(!group.controls->findChild<QWidget *>(QStringLiteral("colorHueField")));
}
void CameraRawCurveMixerTests::pointColorsArePickedChosenAndShifted()
{
Group<CameraRawMixerControls> group;
group.child<QToolButton>(QStringLiteral("mixerPage2")).click();
group.child<QToolButton>(QStringLiteral("pointColorSampler")).click();
QVERIFY(group.panel().samplesPointColor);
QVERIFY(!group.child<QWidget>(QStringLiteral("visualizeRange")).isVisible());
group.set([](CameraRawSettings &settings) {
settings.mixer.points = {CameraRawPointColor{.hue = 30, .saturation = 1, .luminance = 0.5},
CameraRawPointColor{.hue = 200, .saturation = 0.5, .luminance = 0.5}};
});
QVERIFY(group.child<QAbstractButton>(QStringLiteral("pickedColor1")).isVisible());
group.child<QAbstractButton>(QStringLiteral("pickedColor1")).click();
QCOMPARE(group.panel().pointIndex, 1);
group.child<CameraRawSlider>(QStringLiteral("hueShiftSlider")).setValue(750);
QCOMPARE(group.raw().mixer.points[1].hueShift, 50.0);
QCOMPARE(group.raw().mixer.points[0].hueShift, 0.0);
auto &hueRange = group.child<CameraRawSlider>(QStringLiteral("hueRangeSlider"));
hueRange.setValue(1000);
QCOMPARE(group.raw().mixer.points[1].hueRange, 180.0);
QTest::mouseDClick(&hueRange, Qt::LeftButton, {}, knob(hueRange));
QCOMPARE(group.raw().mixer.points[1].hueRange, 30.0);
auto &range = group.child<CameraRawSlider>(QStringLiteral("saturationRangeSlider"));
QTest::mouseDClick(&range, Qt::LeftButton, {}, knob(range));
group.child<CameraRawSlider>(QStringLiteral("saturationRangeSlider")).setValue(0);
QCOMPARE(group.raw().mixer.points[1].saturationRange, 0.05);
group.child<QCheckBox>(QStringLiteral("visualizeRange")).click();
QVERIFY(group.raw().mixer.points[1].visualize && !group.raw().mixer.points[0].visualize);
group.child<QCheckBox>(QStringLiteral("visualizeRange")).click();
QVERIFY(!group.raw().mixer.points[1].visualize);
}
QTEST_MAIN(CameraRawCurveMixerTests)
#include "CameraRawCurveMixerTests.moc"
+271
View File
@@ -0,0 +1,271 @@
#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"
+250
View File
@@ -0,0 +1,250 @@
#include "CameraRawRowTable.h"
#include "Document/BrushStroke.h"
#include "UI/CameraRawColorControls.h"
#include "UI/FilterSheet.h"
#include <QAbstractButton>
#include <QComboBox>
#include <QToolButton>
#include <QWindow>
// The groups' drawing, pages and gestures, and the shipped panel.
namespace {
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, 900);
controls->show();
if (!QTest::qWaitForWindowExposed(controls.get()))
throw std::runtime_error("the controls never showed");
}
template <typename Widget> Widget &child(const QString &name) const { return *controls->template findChild<Widget *>(name); }
const CameraRawSettings &raw() const { return session.filterEdit().value().settings.cameraRaw; }
void set(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = session.filterEdit().value().settings;
change(settings.cameraRaw);
session.updateFilter(settings, true);
}
void arm(const std::function<void(CameraRawPanel &)> &change)
{
CameraRawPanel panel = session.filterEdit().value().rawPanel;
change(panel);
session.setCameraRawPanel(panel);
}
};
bool near(QColor seen, QColor wanted, int tolerance = 4)
{
return std::abs(seen.red() - wanted.red()) <= tolerance && std::abs(seen.green() - wanted.green()) <= tolerance
&& std::abs(seen.blue() - wanted.blue()) <= tolerance;
}
QColor over(QColor top, double alpha, QColor below)
{
return QColor(int(std::lround(top.red() * alpha + below.red() * (1 - alpha))), int(std::lround(top.green() * alpha + below.green() * (1 - alpha))),
int(std::lround(top.blue() * alpha + below.blue() * (1 - alpha))));
}
// A real double click's four events, through the window.
void doubleClick(QWidget &widget, QPoint at)
{
QTest::mouseDClick(widget.window()->windowHandle(), Qt::LeftButton, {}, widget.mapTo(widget.window(), at));
}
}
class CameraRawGroupDrawTests : public QObject {
Q_OBJECT
private slots:
void theCurvePagesShowTheirOwnControls();
void theCurveGraphDrawsItsGround();
void endPointsStayAndARealDoubleClickRemoves();
void theCurveGroupWorksInsideTheShippedSheet();
void swatchesAndTracksFollowTheirFamilies();
void theWheelDrawsAndTakesItsOwnCircle();
void aTypedFieldKeepsItsTypingThroughChanges();
};
void CameraRawGroupDrawTests::theCurvePagesShowTheirOwnControls()
{
Group<CameraRawCurveControls> group;
auto &channel = group.child<QToolButton>(QStringLiteral("pointChannel0"));
auto &amounts = group.child<CameraRawSlider>(QStringLiteral("curveLightsSlider"));
auto &preset = group.child<QComboBox>(QStringLiteral("curvePreset"));
QVERIFY(!channel.isVisible() && amounts.isVisible() && !preset.isVisible());
QCOMPARE(group.child<QToolButton>(QStringLiteral("curvePage0")).toolTip(), QString("Parametric lifts tonal regions. Point places anchors on the curve."));
group.arm([](CameraRawPanel &panel) { panel.curvePage = CameraRawCurvePage::point; });
QVERIFY(channel.isVisible() && !amounts.isVisible() && preset.isVisible());
QStringList presets;
for (int index = 0; index < preset.count(); ++index)
presets << preset.itemText(index);
QCOMPARE(presets, (QStringList{"Custom", "Linear", "Medium Contrast", "Strong Contrast"}));
QCOMPARE(preset.toolTip(), QString("Replaces this curve with a straight line or a contrast curve."));
QCOMPARE(channel.toolTip(), QString("RGB changes brightness. Red, green, and blue also shift the color."));
QCOMPARE(group.child<QWidget>(QStringLiteral("cameraRawCurveGraph")).toolTip(),
QString("Drag a point. Click the curve to add one. Double-click a point to remove it."));
QCOMPARE(group.child<QWidget>(QStringLiteral("curveSelectedPoint")).toolTip(), QString("Input and output of the selected curve point."));
QCOMPARE(group.child<QAbstractButton>(QStringLiteral("curveTargeted")).toolTip(),
QString("Drag on the picture to move the curve for the tone under the pointer."));
}
void CameraRawGroupDrawTests::theCurveGraphDrawsItsGround()
{
Group<CameraRawCurveControls> group;
auto &graph = group.child<QWidget>(QStringLiteral("cameraRawCurveGraph"));
QCOMPARE(graph.height(), 150);
const QColor window = graph.palette().color(QPalette::Window);
const QColor ground = over(Qt::black, 0.35, window);
QImage drawn = graph.grab().toImage();
const int w = drawn.width(), h = drawn.height();
// Away from every line: black at 35% over the window.
QVERIFY2(near(drawn.pixelColor(w * 3 / 4, h * 3 / 4), ground), qPrintable(drawn.pixelColor(w * 3 / 4, h * 3 / 4).name()));
// Each divider rises 8 points, 3 wide, from the bottom.
for (const double split : {25.0, 50.0, 75.0}) {
QVERIFY(near(drawn.pixelColor(int(split / 100 * w), h - 3), Qt::white, 30));
QVERIFY(near(drawn.pixelColor(int(split / 100 * w), h - 12), ground));
}
group.arm([](CameraRawPanel &panel) { panel.curvePage = CameraRawCurvePage::point; });
group.set([](CameraRawSettings &settings) { settings.curve.rgb = {{0, 0}, {0.5, 0.25}, {1, 1}}; });
drawn = graph.grab().toImage();
// A dot 8 across; the diagonal at 25% white.
QVERIFY(near(drawn.pixelColor(w / 2, int(h * 0.75)), Qt::white, 10));
QVERIFY(near(drawn.pixelColor(w / 2 + 3, int(h * 0.75)), Qt::white, 60));
QVERIFY(near(drawn.pixelColor(w / 4, h - int(h * 0.25)), over(Qt::white, 0.25, ground), 40));
}
void CameraRawGroupDrawTests::endPointsStayAndARealDoubleClickRemoves()
{
Group<CameraRawCurveControls> group;
group.arm([](CameraRawPanel &panel) { panel.curvePage = CameraRawCurvePage::point; });
auto &graph = group.child<QWidget>(QStringLiteral("cameraRawCurveGraph"));
const int w = graph.width(), h = graph.height();
// With no inner point a drag moves nothing.
QTest::mousePress(&graph, Qt::LeftButton, {}, QPoint(2, h - 2));
QMouseEvent move(QEvent::MouseMove, QPointF(40, 40), graph.mapToGlobal(QPointF(40, 40)), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(&graph, &move);
QTest::mouseRelease(&graph, Qt::LeftButton, {}, QPoint(40, 40));
QVERIFY(group.raw().curve.rgb == CameraRawCurveSettings::linear());
// An inner point is held within 0.02 and 0.98.
group.set([](CameraRawSettings &settings) { settings.curve.rgb = {{0, 0}, {0.5, 0.5}, {1, 1}}; });
QTest::mousePress(&graph, Qt::LeftButton, {}, QPoint(w / 2, h / 2));
QMouseEvent far(QEvent::MouseMove, QPointF(-50, h / 2), graph.mapToGlobal(QPointF(-50, h / 2)), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(&graph, &far);
QTest::mouseRelease(&graph, Qt::LeftButton, {}, QPoint(-50, h / 2));
QCOMPARE(group.raw().curve.rgb.size(), size_t(3));
QVERIFY(std::abs(group.raw().curve.rgb[1].x - 0.02) < 1e-9 && group.raw().curve.rgb.front().x == 0 && group.raw().curve.rgb.back().x == 1);
// A real double click: press, release, double click, release.
group.set([](CameraRawSettings &settings) { settings.curve.rgb = {{0, 0}, {0.5, 0.5}, {1, 1}}; });
doubleClick(graph, QPoint(w / 2, h / 2));
QCOMPARE(group.raw().curve.rgb.size(), size_t(2));
}
void CameraRawGroupDrawTests::theCurveGroupWorksInsideTheShippedSheet()
{
EditorSession session;
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);
FilterSheet sheet(session);
sheet.resize(440, 1400);
sheet.show();
QVERIFY(QTest::qWaitForWindowExposed(&sheet));
sheet.findChild<QToolButton *>(QStringLiteral("CurveSection"))->click();
CameraRawPanel panel = session.filterEdit().value().rawPanel;
panel.curvePage = CameraRawCurvePage::point;
session.setCameraRawPanel(panel);
auto *graph = sheet.findChild<QWidget *>(QStringLiteral("cameraRawCurveGraph"));
QVERIFY(graph && graph->isVisible());
QVERIFY(!graph->grab().isNull());
const QPoint at(graph->width() / 2, graph->height() / 4);
QTest::mouseClick(graph->window()->windowHandle(), Qt::LeftButton, {}, graph->mapTo(graph->window(), at));
QCOMPARE(session.filterEdit().value().settings.cameraRaw.curve.rgb.size(), size_t(3));
}
void CameraRawGroupDrawTests::swatchesAndTracksFollowTheirFamilies()
{
Group<CameraRawMixerControls> group;
group.arm([](CameraRawPanel &panel) {
panel.mixerPage = CameraRawMixerPage::color;
panel.mixerSwatch = 5;
});
// Each swatch: its centre at 0.8 and 0.9, chosen ringed.
for (int index = 0; index < 8; ++index) {
auto &swatch = group.child<QAbstractButton>(QStringLiteral("mixerSwatch%1").arg(index));
const QImage drawn = swatch.grab().toImage();
const QColor wanted = QColor::fromHsvF(float(CameraRawMixerSettings::centers.at(size_t(index)) / 360), 0.8f, 0.9f);
QVERIFY2(near(drawn.pixelColor(drawn.width() / 2, drawn.height() / 2), wanted), qPrintable(QString::number(index)));
QCOMPARE(near(drawn.pixelColor(drawn.width() / 2, 2), Qt::white, 40), index == 5);
QCOMPARE(swatch.toolTip(), QStringLiteral("Edit %1.").arg(CameraRawMixerSettings::names.at(size_t(index))));
}
// The Color page's hue row spans Blues, 50 each side.
const auto end = [&group](const char *name) {
const QImage drawn = group.child<CameraRawSlider>(QString::fromLatin1(name)).grab().toImage();
return drawn.pixelColor(drawn.width() - 6, drawn.height() / 2);
};
QVERIFY2(std::abs(end("colorHueSlider").hsvHue() - 290) < 8, qPrintable(end("colorHueSlider").name()));
// A point picked anew with another hue rebuilds its tracks.
group.arm([](CameraRawPanel &panel) {
panel.mixerPage = CameraRawMixerPage::point;
panel.pointIndex = 0;
});
group.set([](CameraRawSettings &settings) { settings.mixer.points = {CameraRawPointColor{.hue = 30, .saturation = 1, .luminance = 0.5}}; });
QVERIFY2(std::abs(end("hueShiftSlider").hsvHue() - 80) < 8, qPrintable(end("hueShiftSlider").name()));
group.set([](CameraRawSettings &settings) { settings.mixer.points = {CameraRawPointColor{.hue = 200, .saturation = 1, .luminance = 0.5}}; });
QVERIFY2(std::abs(end("hueShiftSlider").hsvHue() - 250) < 8, qPrintable(end("hueShiftSlider").name()));
const QImage picked = group.child<QAbstractButton>(QStringLiteral("pickedColor0")).grab().toImage();
QVERIFY(near(picked.pixelColor(picked.width() / 2, picked.height() / 2), QColor::fromHsvF(200.0f / 360, 1, 0.5f)));
QVERIFY(near(picked.pixelColor(picked.width() / 2, 2), Qt::white, 40));
}
void CameraRawGroupDrawTests::theWheelDrawsAndTakesItsOwnCircle()
{
Group<CameraRawGradingControls> group;
auto *shadows = group.controls->findChild<QWidget *>(QStringLiteral("gradeWheelShadows"));
auto *midtones = group.controls->findChild<QWidget *>(QStringLiteral("gradeWheelMidtones"));
// Swift's HStack: 30 points between the three columns.
QCOMPARE(midtones->x() - (shadows->x() + shadows->width()), 30);
QWidget *wheel = nullptr;
for (QWidget *child : shadows->findChildren<QWidget *>())
if (child->size() == QSize(86, 86))
wheel = child;
group.set([](CameraRawSettings &settings) { settings.grading.shadows = {90, 50, 0}; });
const QImage drawn = wheel->grab().toImage();
const QColor window = wheel->palette().color(QPalette::Window);
// Red at 85% on the right; the dot sits above.
QVERIFY2(near(drawn.pixelColor(80, 43), over(Qt::red, 0.85, window), 12), qPrintable(drawn.pixelColor(80, 43).name()));
QVERIFY(near(drawn.pixelColor(43, 43 - 18), Qt::white, 10));
QVERIFY(!near(drawn.pixelColor(43, 43 + 18), Qt::white, 60));
// A double click in a corner resets nothing.
QTest::mouseDClick(wheel, Qt::LeftButton, {}, QPoint(2, 2));
QCOMPARE(group.raw().grading.shadows.saturation, 50.0);
QTest::mouseDClick(wheel, Qt::LeftButton, {}, QPoint(43, 43));
QCOMPARE(group.raw().grading.shadows.saturation, 0.0);
QCOMPARE(wheel->toolTip(), QString("Drag to set hue and saturation. Double-click to reset this wheel."));
}
void CameraRawGroupDrawTests::aTypedFieldKeepsItsTypingThroughChanges()
{
Group<CameraRawCurveControls> group;
auto *field = group.controls->findChild<PickerField *>(QStringLiteral("curveLightsField"));
field->setFocus();
QTRY_VERIFY(field->hasFocus());
field->selectAll();
QTest::keyClicks(field, QStringLiteral("42"));
group.set([](CameraRawSettings &settings) { settings.curve.darks = 10; });
QCOMPARE(field->text(), QString("42"));
QTest::keyClick(field, Qt::Key_Return);
QCOMPARE(group.raw().curve.lights, 42.0);
}
QTEST_MAIN(CameraRawGroupDrawTests)
#include "CameraRawGroupDrawTests.moc"
+239
View File
@@ -0,0 +1,239 @@
#include "CameraRawRowTable.h"
#include "Document/BrushStroke.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawDetailOpticsControls.h"
#include "UI/CameraRawGeometryCalibrationControls.h"
#include <QCheckBox>
#include <QToolButton>
// Every row of the seven groups against Swift's own values.
namespace {
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);
}
void show()
{
controls = std::make_unique<Controls>(session);
controls->resize(374, 900);
controls->show();
}
void set(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = session.filterEdit().value().settings;
change(settings.cameraRaw);
session.updateFilter(settings, true);
}
void arm(const std::function<void(CameraRawPanel &)> &change)
{
CameraRawPanel panel = session.filterEdit().value().rawPanel;
change(panel);
session.setCameraRawPanel(panel);
}
};
template <typename Settings> std::function<double(const CameraRawSettings &)> of(Settings CameraRawSettings::*group, double Settings::*member)
{
return [group, member](const CameraRawSettings &settings) { return settings.*group.*member; };
}
std::function<double(const CameraRawSettings &)> family(int tab, int index)
{
return [tab, index](const CameraRawSettings &settings) {
const std::array<double, 8> &values = tab == 1 ? settings.mixer.saturation : tab == 2 ? settings.mixer.luminance : settings.mixer.hue;
return values.at(size_t(index));
};
}
std::function<double(const CameraRawSettings &)> point(double CameraRawPointColor::*member)
{
return [member](const CameraRawSettings &settings) { return settings.mixer.points.at(0).*member; };
}
}
class CameraRawGroupRowsTests : public QObject {
Q_OBJECT
private slots:
void curveRows();
void mixerRows();
void gradingRows();
void detailRows();
void opticsRows();
void geometryRows();
void calibrationRows();
};
void CameraRawGroupRowsTests::curveRows()
{
Group<CameraRawCurveControls> group;
group.show();
const auto curve = [](double CameraRawCurveSettings::*member) { return of(&CameraRawSettings::curve, member); };
for (const RowCase &row : std::vector<RowCase>{
{"curveHighlights", "Highlights", "Lifts or lowers the brightest tones.", -100, 100, 0, false, 48, 88, true, false, curve(&CameraRawCurveSettings::highlights)},
{"curveLights", "Lights", "Lifts or lowers the light tones.", -100, 100, 0, false, 48, 88, true, false, curve(&CameraRawCurveSettings::lights)},
{"curveDarks", "Darks", "Lifts or lowers the dark tones.", -100, 100, 0, false, 48, 88, true, false, curve(&CameraRawCurveSettings::darks)},
{"curveShadows", "Shadows", "Lifts or lowers the darkest tones.", -100, 100, 0, false, 48, 88, true, false, curve(&CameraRawCurveSettings::shadows)}})
checkRow(*group.controls, group.session, row);
group.arm([](CameraRawPanel &panel) { panel.curvePage = CameraRawCurvePage::point; });
checkRow(*group.controls, group.session,
{"refineSaturation", "Refine Saturation", "How much the RGB curve also changes color strength. Zero keeps it to brightness.", -100, 100, 0, false,
48, 88, true, false, curve(&CameraRawCurveSettings::refineSaturation)});
}
void CameraRawGroupRowsTests::mixerRows()
{
Group<CameraRawMixerControls> group;
group.show();
const std::array<const char *, 3> tabs{"Hue", "Saturation", "Luminance"};
for (int tab = 0; tab < 3; ++tab) {
group.arm([tab](CameraRawPanel &panel) { panel.mixerTab = CameraRawMixerTab(tab); });
for (int index = 0; index < 8; ++index) {
const QByteArray name = "family" + CameraRawMixerSettings::names.at(size_t(index)).toUtf8();
const QByteArray title = CameraRawMixerSettings::names.at(size_t(index)).toUtf8();
const QByteArray help = QStringLiteral("%1 of %2.").arg(QString::fromLatin1(tabs.at(size_t(tab))), QString::fromUtf8(title)).toUtf8();
checkRow(*group.controls, group.session, {name.constData(), title.constData(), help.constData(), -100, 100, 0, false, 48, 78, true, true, family(tab, index)});
}
}
group.arm([](CameraRawPanel &panel) {
panel.mixerPage = CameraRawMixerPage::color;
panel.mixerSwatch = 3;
});
checkRow(*group.controls, group.session,
{"colorHue", "Hue", "Shifts the selected color family around the wheel.", -100, 100, 0, false, 0, 88, true, true, family(0, 3)});
checkRow(*group.controls, group.session,
{"colorSaturation", "Saturation", "Makes the selected color family stronger or quieter.", -100, 100, 0, false, 0, 88, true, true, family(1, 3)});
checkRow(*group.controls, group.session,
{"colorLuminance", "Luminance", "Makes the selected color family lighter or darker.", -100, 100, 0, false, 0, 88, true, true, family(2, 3)});
group.arm([](CameraRawPanel &panel) {
panel.mixerPage = CameraRawMixerPage::point;
panel.pointIndex = 0;
});
group.set([](CameraRawSettings &settings) { settings.mixer.points = {CameraRawPointColor{.hue = 200, .saturation = 0.6, .luminance = 0.5}}; });
for (const RowCase &row : std::vector<RowCase>{
{"hueShift", "Hue Shift", "Shifts the picked color around the color wheel.", -100, 100, 0, false, 0, 110, true, true, point(&CameraRawPointColor::hueShift)},
{"saturationShift", "Saturation Shift", "Makes the picked color stronger or quieter.", -100, 100, 0, false, 0, 110, true, true,
point(&CameraRawPointColor::saturationShift)},
{"luminanceShift", "Luminance Shift", "Makes the picked color lighter or darker.", -100, 100, 0, false, 0, 110, true, true,
point(&CameraRawPointColor::luminanceShift)},
{"hueRange", "Hue Range", "How far in hue the adjustment reaches.", 5, 180, 30, false, 0, 110, true, false, point(&CameraRawPointColor::hueRange)},
{"saturationRange", "Saturation Range", "How far in saturation the adjustment reaches.", 0.05, 1, 0.4, false, 0, 110, true, false,
point(&CameraRawPointColor::saturationRange)},
{"luminanceRange", "Luminance Range", "How far in brightness the adjustment reaches.", 0.05, 1, 0.4, false, 0, 110, true, false,
point(&CameraRawPointColor::luminanceRange)}})
checkRow(*group.controls, group.session, row);
}
void CameraRawGroupRowsTests::gradingRows()
{
Group<CameraRawGradingControls> group;
group.show();
checkRow(*group.controls, group.session,
{"gradeBlending", "Blending", "Controls how much the three tonal wheels overlap.", 0, 100, 50, false, 0, 78, true, false,
of(&CameraRawSettings::grading, &CameraRawGradingSettings::blending)});
checkRow(*group.controls, group.session,
{"gradeBalance", "Balance", "Shifts the wheels toward shadows or highlights.", -100, 100, 0, false, 0, 78, true, false,
of(&CameraRawSettings::grading, &CameraRawGradingSettings::balance)});
}
void CameraRawGroupRowsTests::detailRows()
{
Group<CameraRawDetailControls> group;
// Idle noise rows rest; raised, they take writes.
group.set([](CameraRawSettings &settings) {
settings.detail.noiseLuminance = 30;
settings.detail.noiseColor = 30;
});
group.show();
const auto detail = [](double CameraRawDetailSettings::*member) { return of(&CameraRawSettings::detail, member); };
for (const RowCase &row : std::vector<RowCase>{
{"sharpenAmount", "Amount", "Controls how strong the sharpening is.", 0, 150, 0, true, 56, 96, false, false, detail(&CameraRawDetailSettings::sharpenAmount)},
{"sharpenRadius", "Radius", "How far from each edge the sharpening reaches, in pixels.", 0, 100, 10, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::sharpenRadius)},
{"sharpenDetail", "Detail", "Emphasizes fine texture over broader edges.", 0, 100, 25, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::sharpenDetail)},
{"sharpenMasking", "Masking", "Limits sharpening to stronger edges. Hold Alt to see the mask.", 0, 100, 0, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::sharpenMasking)},
{"noiseLuminanceDetail", "Luminance Detail", "Preserves fine texture while luminance noise is reduced.", 0, 100, 50, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::noiseLuminanceDetail)},
{"noiseLuminanceContrast", "Luminance Contrast", "Keeps local contrast after luminance smoothing.", 0, 100, 0, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::noiseLuminanceContrast)},
{"noiseColorDetail", "Color Detail", "Preserves colored edges while color noise is reduced.", 0, 100, 50, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::noiseColorDetail)},
{"noiseColorSmoothness", "Color Smoothness", "Makes the color smoothing softer or tighter.", 0, 100, 50, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::noiseColorSmoothness)},
{"noiseLuminance", "Luminance", "Smooths grain and noise in brightness.", 0, 100, 0, true, 56, 96, false, false,
detail(&CameraRawDetailSettings::noiseLuminance)},
{"noiseColor", "Color", "Smooths colored speckles.", 0, 100, 0, true, 56, 96, false, false, detail(&CameraRawDetailSettings::noiseColor)}})
checkRow(*group.controls, group.session, row);
}
void CameraRawGroupRowsTests::opticsRows()
{
Group<CameraRawOpticsControls> group;
group.set([](CameraRawSettings &settings) { settings.optics.enableLensProfile = true; });
group.show();
const auto optics = [](double CameraRawOpticsSettings::*member) { return of(&CameraRawSettings::optics, member); };
for (const RowCase &row : std::vector<RowCase>{
{"profileDistortion", "Distortion", "How much of the profile distortion correction is applied.", 0, 100, 100, true, 56, 96, false, false,
optics(&CameraRawOpticsSettings::profileDistortion)},
{"profileVignetting", "Vignetting", "How much of the profile vignetting correction is applied.", 0, 100, 100, true, 56, 96, false, false,
optics(&CameraRawOpticsSettings::profileVignetting)},
{"distortion", "Distortion", "Straightens barrel or pincushion bending.", -100, 100, 0, false, 56, 96, false, false,
optics(&CameraRawOpticsSettings::distortion)},
{"purpleAmount", "Purple Amount", "Weakens purple fringes inside the purple hue range.", 0, 100, 0, true, 56, 96, false, false,
optics(&CameraRawOpticsSettings::purpleAmount)},
{"greenAmount", "Green Amount", "Weakens green fringes inside the green hue range.", 0, 100, 0, true, 56, 96, false, false,
optics(&CameraRawOpticsSettings::greenAmount)},
{"lensVignetting", "Vignetting", "Brightens or darkens the corners to counter lens falloff.", -100, 100, 0, false, 56, 96, false, false,
optics(&CameraRawOpticsSettings::vignetteAmount)},
{"lensMidpoint", "Midpoint", "Moves the vignette correction inward or outward.", 0, 100, 50, true, 56, 96, false, false,
optics(&CameraRawOpticsSettings::vignetteMidpoint)}})
checkRow(*group.controls, group.session, row);
}
void CameraRawGroupRowsTests::geometryRows()
{
Group<CameraRawGeometryControls> group;
group.show();
const auto geometry = [](double CameraRawGeometrySettings::*member) { return of(&CameraRawSettings::geometry, member); };
for (const RowCase &row : std::vector<RowCase>{
{"vertical", "Vertical", "Straightens vertical lines toward the center.", -100, 100, 0, true, 56, 96, false, false, geometry(&CameraRawGeometrySettings::vertical)},
{"horizontal", "Horizontal", "Straightens horizontal lines toward the center.", -100, 100, 0, true, 56, 96, false, false,
geometry(&CameraRawGeometrySettings::horizontal)},
{"rotate", "Rotate", "Rotates the picture around its center.", -45, 45, 0, true, 56, 96, false, false, geometry(&CameraRawGeometrySettings::rotate)},
{"aspect", "Aspect", "Stretches width relative to height.", -100, 100, 0, true, 56, 96, false, false, geometry(&CameraRawGeometrySettings::aspect)},
{"scale", "Scale", "Zooms the transformed picture within the frame.", -100, 100, 0, true, 56, 96, false, false, geometry(&CameraRawGeometrySettings::scale)},
{"offsetX", "Offset X", "Moves the picture left or right.", -100, 100, 0, true, 56, 96, false, false, geometry(&CameraRawGeometrySettings::offsetX)},
{"offsetY", "Offset Y", "Moves the picture up or down.", -100, 100, 0, true, 56, 96, false, false, geometry(&CameraRawGeometrySettings::offsetY)}})
checkRow(*group.controls, group.session, row);
}
void CameraRawGroupRowsTests::calibrationRows()
{
Group<CameraRawCalibrationControls> group;
group.show();
const auto calibration = [](double CameraRawCalibrationSettings::*member) { return of(&CameraRawSettings::calibration, member); };
for (const RowCase &row : std::vector<RowCase>{
{"shadowTint", "Tint", "Adds green or magenta to the darkest tones.", -100, 100, 0, true, 56, 96, false, false,
calibration(&CameraRawCalibrationSettings::shadowTint)},
{"redHue", "Hue", "Shifts how red is interpreted.", -100, 100, 0, true, 56, 96, false, false, calibration(&CameraRawCalibrationSettings::redHue)},
{"redSaturation", "Saturation", "Strengthens or weakens the red primary.", -100, 100, 0, true, 56, 96, false, false,
calibration(&CameraRawCalibrationSettings::redSaturation)},
{"greenHue", "Hue", "Shifts how green is interpreted.", -100, 100, 0, true, 56, 96, false, false, calibration(&CameraRawCalibrationSettings::greenHue)},
{"greenSaturation", "Saturation", "Strengthens or weakens the green primary.", -100, 100, 0, true, 56, 96, false, false,
calibration(&CameraRawCalibrationSettings::greenSaturation)},
{"blueHue", "Hue", "Shifts how blue is interpreted.", -100, 100, 0, true, 56, 96, false, false, calibration(&CameraRawCalibrationSettings::blueHue)},
{"blueSaturation", "Saturation", "Strengthens or weakens the blue primary.", -100, 100, 0, true, 56, 96, false, false,
calibration(&CameraRawCalibrationSettings::blueSaturation)}})
checkRow(*group.controls, group.session, row);
}
QTEST_MAIN(CameraRawGroupRowsTests)
#include "CameraRawGroupRowsTests.moc"
+346
View File
@@ -0,0 +1,346 @@
#include "CameraRawRowTable.h"
#include "Document/BrushStroke.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawDetailOpticsControls.h"
#include "UI/CameraRawGeometryCalibrationControls.h"
#include "Rendering/EyedropperIcon.h"
#include <QCheckBox>
#include <QAbstractButton>
#include <QComboBox>
#include <QPushButton>
#include <QToolButton>
// The groups' words, captions, pages and the states they show.
namespace {
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);
}
void show()
{
controls = std::make_unique<Controls>(session);
controls->resize(374, 900);
controls->show();
}
template <typename Widget> Widget &child(const QString &name) const { return *controls->template findChild<Widget *>(name); }
const CameraRawSettings &raw() const { return session.filterEdit().value().settings.cameraRaw; }
void set(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = session.filterEdit().value().settings;
change(settings.cameraRaw);
session.updateFilter(settings, true);
}
void arm(const std::function<void(CameraRawPanel &)> &change)
{
CameraRawPanel panel = session.filterEdit().value().rawPanel;
change(panel);
session.setCameraRawPanel(panel);
}
};
// Every text a view holds: labels, buttons, menus and tooltips.
QSet<QString> words(QWidget &root)
{
QSet<QString> found;
for (QWidget *widget : root.findChildren<QWidget *>()) {
found << widget->toolTip();
if (auto *label = qobject_cast<QLabel *>(widget))
found << label->text();
if (auto *button = qobject_cast<QAbstractButton *>(widget))
found << button->text();
if (auto *menu = qobject_cast<QComboBox *>(widget))
for (int index = 0; index < menu->count(); ++index)
found << menu->itemText(index);
}
return found;
}
void holds(QWidget &root, const QStringList &expected)
{
const QSet<QString> found = words(root);
for (const QString &text : expected)
QVERIFY2(found.contains(text), qPrintable(text));
}
}
class CameraRawGroupWordsTests : public QObject {
Q_OBJECT
private slots:
void everyViewCarriesSwiftsWords();
void captionsBuddiesAndTitlesThatDoNotReset();
void pickersAndButtonsShowThePanel();
void theMixerShowsOnePageAtATime();
void theWheelReadsItsLowerHalfAndDrawsItsRim();
void theCurveShowsItsLineItsSelectedPointAndRepaints();
void widgetsFollowTheModelAndGuardsHold();
};
void CameraRawGroupWordsTests::everyViewCarriesSwiftsWords()
{
Group<CameraRawCurveControls> curve;
curve.show();
holds(*curve.controls, {"Parametric", "Point", "RGB", "Red", "Green", "Blue", "Parametric lifts tonal regions. Point places anchors on the curve.",
"RGB changes brightness. Red, green, and blue also shift the color.",
"Drag a divider to change which tones the neighboring sliders affect.", "Targeted Adjustment", "Preset"});
Group<CameraRawMixerControls> mixer;
mixer.set([](CameraRawSettings &settings) { settings.mixer.points = {CameraRawPointColor{.hue = 30, .saturation = 1, .luminance = 0.5}}; });
mixer.show();
holds(*mixer.controls, {"HSL", "Color", "Point Color", "Hue", "Saturation", "Luminance",
"HSL lists every color. Color edits one family. Point Color adjusts a color you pick.",
"Hue shifts the color, Saturation its strength, and Luminance its brightness.",
"Click the picture to save a color. Up to eight colors.", "Visualize Range",
"Dims the picture outside this color's range. It is not kept when you press OK.", "Targeted Adjustment",
"Drag a color in the picture. Nearby color families move together.", "Select this picked color."});
Group<CameraRawGradingControls> grading;
grading.show();
holds(*grading.controls, {"Three-Way", "Shadows", "Midtones", "Highlights", "Global",
"Three-Way shows shadows, midtones, and highlights. The other choices show one wheel.",
"Drag inside the wheel. Angle sets hue, distance sets saturation.", "Hue and saturation of this wheel.",
"Brightness added by this wheel.", "0° 0"});
Group<CameraRawDetailControls> detail;
detail.show();
holds(*detail.controls, {"Sharpening", "Noise Reduction"});
Group<CameraRawOpticsControls> optics;
optics.show();
holds(*optics.controls, {"Remove Chromatic Aberration", "Pulls red and blue fringes apart toward the center to reduce color edging.",
"Enable Lens Profile Corrections", "Applies generic profile strength when camera metadata is not available.",
"No lens metadata on this layer. Profile sliders set generic correction strength.", "Manual", "Defringe",
"Click a purple or green fringe to set its hue range.",
"Click the fringe on the layer. Click the eyedropper again to stop.", "Purple Hue", "Green Hue",
"Hue range where purple defringe runs.", "Hue range where green defringe runs.", "Low", "High",
"Start of the hue range, in degrees.", "End of the hue range, in degrees."});
Group<CameraRawGeometryControls> geometry;
geometry.show();
holds(*geometry.controls, {"Upright", "Off", "Guided", "Off leaves the picture as it is. Guided straightens from lines you draw on the picture.",
"Draw Guides", "Draw two or more lines on the preview that should be level or vertical.",
"Drag on the layer to place a guide. Draw at least two lines.", "Clear Guides", "Remove every guide line.",
"Projection", "Perspective", "Rectilinear", "Perspective allows stronger keystone. Rectilinear keeps the warp gentler.",
"Constrain Crop", "Crops empty edges after the transform and fits the result back into the frame."});
Group<CameraRawCalibrationControls> calibration;
calibration.show();
holds(*calibration.controls, {"Process", "Version 1", "Version 6",
"Chooses how strongly the calibration sliders below are applied. Version 6 is the current default.", "Shadows",
"Red Primary", "Green Primary", "Blue Primary", summary(CameraRawProcessVersion::version6)});
}
void CameraRawGroupWordsTests::captionsBuddiesAndTitlesThatDoNotReset()
{
Group<CameraRawOpticsControls> optics;
optics.show();
// Low left of its slider, High left of the other.
auto &low = optics.child<CameraRawSlider>(QStringLiteral("purpleHueLow"));
auto &high = optics.child<CameraRawSlider>(QStringLiteral("purpleHueHigh"));
QLabel *lowCaption = nullptr, *highCaption = nullptr;
for (QLabel *label : low.parentWidget()->findChildren<QLabel *>()) {
if (label->text() == QLatin1String("Low") && std::abs(label->y() - low.y()) < 20 && !lowCaption)
lowCaption = label;
if (label->text() == QLatin1String("High") && std::abs(label->y() - low.y()) < 20 && !highCaption)
highCaption = label;
}
QVERIFY(lowCaption && highCaption);
QVERIFY(lowCaption->x() < low.x() && low.x() < highCaption->x() && highCaption->x() < high.x());
Group<CameraRawGeometryControls> geometry;
geometry.show();
auto &projection = geometry.child<QComboBox>(QStringLiteral("projection"));
bool named = false;
for (QLabel *label : geometry.controls->findChildren<QLabel *>())
named = named || (label->text() == QLatin1String("Projection") && label->buddy() == &projection);
QVERIFY(named);
// A sub-view's title never resets; only the main groups' do.
Group<CameraRawCurveControls> curve;
curve.show();
auto &lights = curve.child<CameraRawSlider>(QStringLiteral("curveLightsSlider"));
lights.setValue(800);
QTest::mouseDClick(lights.parentWidget()->findChild<QLabel *>(), Qt::LeftButton);
QCOMPARE(curve.raw().curve.lights, 60.0);
}
void CameraRawGroupWordsTests::pickersAndButtonsShowThePanel()
{
Group<CameraRawCurveControls> curve;
curve.show();
curve.arm([](CameraRawPanel &panel) {
panel.curvePage = CameraRawCurvePage::point;
panel.pointChannel = CameraRawPointChannel::blue;
});
QVERIFY(curve.child<QToolButton>(QStringLiteral("curvePage1")).isChecked() && curve.child<QToolButton>(QStringLiteral("pointChannel3")).isChecked());
Group<CameraRawMixerControls> mixer;
mixer.show();
mixer.arm([](CameraRawPanel &panel) {
panel.mixerPage = CameraRawMixerPage::point;
panel.mixerTab = CameraRawMixerTab::luminance;
});
QVERIFY(mixer.child<QToolButton>(QStringLiteral("mixerPage2")).isChecked() && mixer.child<QToolButton>(QStringLiteral("mixerTab2")).isChecked());
Group<CameraRawGeometryControls> geometry;
geometry.show();
geometry.set([](CameraRawSettings &settings) { settings.geometry.upright = CameraRawUprightMode::guided; });
QVERIFY(geometry.child<QToolButton>(QStringLiteral("upright1")).isChecked());
auto &draw = geometry.child<QPushButton>(QStringLiteral("drawGuides"));
QVERIFY(!draw.isChecked());
geometry.arm([](CameraRawPanel &panel) { panel.drawingGeometryGuide = true; });
QVERIFY(draw.isChecked());
}
void CameraRawGroupWordsTests::theMixerShowsOnePageAtATime()
{
Group<CameraRawMixerControls> mixer;
mixer.show();
const auto shown = [&mixer] {
return std::tuple(mixer.child<CameraRawSlider>(QStringLiteral("familyRedsSlider")).isVisible(),
mixer.child<QAbstractButton>(QStringLiteral("mixerSwatch0")).isVisible(),
mixer.child<QToolButton>(QStringLiteral("pointColorSampler")).isVisible());
};
QVERIFY(shown() == std::tuple(true, false, false));
mixer.arm([](CameraRawPanel &panel) { panel.mixerPage = CameraRawMixerPage::color; });
QVERIFY(shown() == std::tuple(false, true, false));
mixer.arm([](CameraRawPanel &panel) { panel.mixerPage = CameraRawMixerPage::point; });
QVERIFY(shown() == std::tuple(false, false, true));
}
void CameraRawGroupWordsTests::theWheelReadsItsLowerHalfAndDrawsItsRim()
{
Group<CameraRawGradingControls> grading;
grading.show();
QWidget *wheel = nullptr;
for (QWidget *child : grading.controls->findChild<QWidget *>(QStringLiteral("gradeWheelShadows"))->findChildren<QWidget *>())
if (child->size() == QSize(86, 86))
wheel = child;
// Straight down from the middle is hue 270.
QTest::mousePress(wheel, Qt::LeftButton, {}, QPoint(43, 80));
QTest::mouseRelease(wheel, Qt::LeftButton, {}, QPoint(43, 80));
QVERIFY2(std::abs(grading.raw().grading.shadows.hue - 270) < 0.5, qPrintable(QString::number(grading.raw().grading.shadows.hue)));
// The rim: white at 80% round the fill's edge.
const QImage drawn = wheel->grab().toImage();
const QColor rim = drawn.pixelColor(43, 0), inside = drawn.pixelColor(43, 4);
QVERIFY2(std::min({rim.red(), rim.green(), rim.blue()}) > 150 && std::min({inside.red(), inside.green(), inside.blue()}) < 60,
qPrintable(rim.name() + QStringLiteral(" ") + inside.name()));
}
void CameraRawGroupWordsTests::theCurveShowsItsLineItsSelectedPointAndRepaints()
{
Group<CameraRawCurveControls> curve;
curve.show();
auto &graph = curve.child<QWidget>(QStringLiteral("cameraRawCurveGraph"));
// Lights at 100 lift their region's middle by 0.22.
curve.set([](CameraRawSettings &settings) { settings.curve.lights = 100; });
QImage drawn = graph.grab().toImage();
const int w = drawn.width(), h = drawn.height();
// The column's whitest pixel sits on the lifted curve.
int brightest = 0;
for (int y = 0; y < h; ++y)
if (qGray(drawn.pixel(int(0.625 * w), y)) > qGray(drawn.pixel(int(0.625 * w), brightest)))
brightest = y;
QVERIFY2(std::abs(brightest - (1 - 0.845) * h) < 3, qPrintable(QString::number(brightest)));
// A write repaints the graph without being asked.
struct Paints : QObject {
int count = 0;
bool eventFilter(QObject *, QEvent *event) override
{
count += event->type() == QEvent::Paint;
return false;
}
} paints;
graph.installEventFilter(&paints);
// Pending paints settle first; only the write's paint counts.
QTest::qWait(100);
paints.count = 0;
curve.set([](CameraRawSettings &settings) { settings.curve.darkSplit = 60; });
QTRY_VERIFY(paints.count > 0);
// The selected point is the last inner one.
curve.arm([](CameraRawPanel &panel) { panel.curvePage = CameraRawCurvePage::point; });
curve.set([](CameraRawSettings &settings) { settings.curve.rgb = {{0, 0}, {0.2, 0.3}, {0.6, 0.7}, {1, 1}}; });
QCOMPARE(curve.child<QLabel>(QStringLiteral("curveSelectedPoint")).text(), QString("In 153 Out 179"));
}
void CameraRawGroupWordsTests::widgetsFollowTheModelAndGuardsHold()
{
// Settings from before the view shows appear in every control.
Group<CameraRawOpticsControls> optics;
optics.set([](CameraRawSettings &settings) {
settings.optics.removeChromaticAberration = true;
settings.optics.enableLensProfile = true;
settings.optics.greenHueLow = 100;
});
optics.show();
QVERIFY(optics.child<QCheckBox>(QStringLiteral("removeChromaticAberration")).isChecked());
QVERIFY(optics.child<QCheckBox>(QStringLiteral("enableLensProfile")).isChecked());
QVERIFY(std::abs(optics.child<CameraRawSlider>(QStringLiteral("greenHueLow")).shown() - 100) <= 0.36);
optics.arm([](CameraRawPanel &panel) { panel.samplesDefringe = true; });
const qreal ratio = optics.controls->devicePixelRatioF();
const auto accent = [ratio](const QWidget &button) { return EyedropperIcon::icon(button.palette().color(QPalette::Highlight), ratio).pixmap(QSize(14, 14), ratio).toImage(); };
auto &defringe = optics.child<QToolButton>(QStringLiteral("defringeSampler"));
QVERIFY(defringe.icon().pixmap(QSize(14, 14), ratio).toImage() == accent(defringe));
Group<CameraRawGeometryControls> geometry;
geometry.set([](CameraRawSettings &settings) {
settings.geometry.projection = CameraRawProjection::rectilinear;
settings.geometry.constrainCrop = true;
});
geometry.show();
QCOMPARE(geometry.child<QComboBox>(QStringLiteral("projection")).currentIndex(), 1);
QVERIFY(geometry.child<QCheckBox>(QStringLiteral("constrainCrop")).isChecked());
Group<CameraRawCalibrationControls> calibration;
calibration.show();
QCOMPARE(calibration.child<QLabel>(QStringLiteral("processSummary")).toolTip(), summary(CameraRawProcessVersion::version6));
Group<CameraRawMixerControls> mixer;
mixer.show();
mixer.arm([](CameraRawPanel &panel) {
panel.mixerPage = CameraRawMixerPage::point;
panel.samplesPointColor = true;
});
auto &sampler = mixer.child<QToolButton>(QStringLiteral("pointColorSampler"));
QVERIFY(sampler.icon().pixmap(QSize(14, 14), ratio).toImage() == accent(sampler));
// Grading's page, hidden label, luminance and a repaint.
Group<CameraRawGradingControls> grading;
grading.show();
auto &page = grading.child<QComboBox>(QStringLiteral("gradePage"));
grading.arm([](CameraRawPanel &panel) { panel.gradePage = CameraRawGradePage::highlights; });
QCOMPARE(page.currentIndex(), 3);
bool named = false;
for (QLabel *label : grading.controls->findChildren<QLabel *>())
named = named || (label->text() == QLatin1String("Grading") && label->buddy() == &page);
QVERIFY(named);
grading.arm([](CameraRawPanel &panel) { panel.gradePage = CameraRawGradePage::threeWay; });
QWidget *box = grading.controls->findChild<QWidget *>(QStringLiteral("gradeWheelShadows"));
grading.set([](CameraRawSettings &settings) { settings.grading.shadows.luminance = 50; });
QVERIFY(std::abs(box->findChild<CameraRawSlider *>(QStringLiteral("gradeLuminance"))->shown() - 50) <= 0.2);
QWidget *wheel = nullptr;
for (QWidget *child : box->findChildren<QWidget *>())
if (child->size() == QSize(86, 86))
wheel = child;
struct Paints : QObject {
int count = 0;
bool eventFilter(QObject *, QEvent *event) override
{
count += event->type() == QEvent::Paint;
return false;
}
} paints;
wheel->installEventFilter(&paints);
QTest::qWait(100);
paints.count = 0;
grading.set([](CameraRawSettings &settings) { settings.grading.shadows.hue = 45; });
QTRY_VERIFY(paints.count > 0);
// A move that began off the circle drags nothing.
QTest::mousePress(wheel, Qt::LeftButton, {}, QPoint(1, 1));
QMouseEvent move(QEvent::MouseMove, QPointF(43, 6), wheel->mapToGlobal(QPointF(43, 6)), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(wheel, &move);
QTest::mouseRelease(wheel, Qt::LeftButton, {}, QPoint(43, 6));
QCOMPARE(grading.raw().grading.shadows.hue, 45.0);
// Clicks on the parametric graph leave the point curve alone.
Group<CameraRawCurveControls> curve;
curve.show();
auto &graph = curve.child<QWidget>(QStringLiteral("cameraRawCurveGraph"));
QTest::mouseClick(&graph, Qt::LeftButton, {}, QPoint(graph.width() / 2, 20));
QTest::mouseDClick(graph.window()->windowHandle(), Qt::LeftButton, {}, graph.mapTo(graph.window(), QPoint(graph.width() / 2, 20)));
QVERIFY(curve.raw().curve.rgb == CameraRawCurveSettings::linear());
}
QTEST_MAIN(CameraRawGroupWordsTests)
#include "CameraRawGroupWordsTests.moc"
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/CameraRawRow.h"
#include "UI/ColorPickerSheet.h"
#include <QLabel>
#include <QStyleOptionSlider>
#include <QtTest>
// One Camera Raw row as Swift's view builds it.
struct RowCase {
const char *name;
const char *title;
const char *help;
double low;
double high;
double reset;
// Swift's slider rounds to whole numbers.
bool whole;
// The field's width, none at zero.
int field;
int titleWidth;
bool fixedTitle;
bool coloured;
std::function<double(const CameraRawSettings &)> read;
};
// The knob's middle, where a double click resets.
inline 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();
}
// Checks a row's words, layout, writes, reset, typing and track.
inline void checkRow(QWidget &root, const EditorSession &session, const RowCase &row)
{
const QString name = QString::fromLatin1(row.name);
auto *slider = root.findChild<CameraRawSlider *>(name + QStringLiteral("Slider"));
QVERIFY2(slider, row.name);
auto *shown = qobject_cast<CameraRawRow *>(slider->parentWidget());
QVERIFY2(shown, row.name);
auto *title = shown->findChild<QLabel *>();
const auto read = [&] { return row.read(session.filterEdit().value().settings.cameraRaw); };
QCOMPARE(title->text(), QString::fromUtf8(row.title));
QCOMPARE(title->toolTip(), QString::fromUtf8(row.help));
QCOMPARE(slider->toolTip(), QString::fromUtf8(row.help));
QCOMPARE(shown->accessibleName(), QString::fromUtf8(row.title));
QCOMPARE(title->minimumWidth(), row.titleWidth);
QVERIFY2(row.fixedTitle == (title->maximumWidth() == row.titleWidth), row.name);
auto *field = shown->findChild<PickerField *>(name + QStringLiteral("Field"));
QVERIFY2(bool(field) == (row.field > 0), row.name);
if (field) {
QCOMPARE(field->width(), row.field);
QCOMPARE(field->placeholderText(), QString::fromUtf8(row.title));
QCOMPARE(field->accessibleName(), QString::fromUtf8(row.title));
QCOMPARE(field->toolTip(), QString::fromUtf8(row.help));
QVERIFY(field->alignment().testFlag(Qt::AlignRight));
}
// A third of the travel, rounded where Swift rounds.
slider->setValue(333);
const double third = row.low + (row.high - row.low) * 0.333;
QVERIFY2(std::abs(read() - (row.whole ? std::round(third) : third)) < 1e-9, qPrintable(name + QStringLiteral(" slides to ") + QString::number(read())));
QTest::mouseDClick(slider, Qt::LeftButton, {}, knob(*slider));
QVERIFY2(std::abs(read() - row.reset) < 1e-9, qPrintable(name + QStringLiteral(" resets to ") + QString::number(read())));
// The row follows the model in its knob and field.
QVERIFY2(std::abs(slider->shown() - row.reset) <= (row.high - row.low) / 1000, qPrintable(name + QStringLiteral(" shows ") + QString::number(slider->shown())));
if (field)
QCOMPARE(field->text(), field->locale().toString(row.reset, 'f', 0));
if (field) {
// A typed number applies as typed, unrounded.
const double typed = row.low + (row.high - row.low) * 0.625 + 0.25;
field->setFocus();
field->selectAll();
QTest::keyClicks(field, field->locale().toString(typed, 'f', 2));
QTest::keyClick(field, Qt::Key_Return);
QVERIFY2(std::abs(read() - typed) < 1e-9, qPrintable(name + QStringLiteral(" types ") + QString::number(read())));
}
// A coloured track shows colour past the knob, plain gray.
if (row.reset < row.high) {
QTest::mouseDClick(slider, Qt::LeftButton, {}, knob(*slider));
const QImage drawn = slider->grab().toImage();
const QColor end = drawn.pixelColor(drawn.width() - 6, drawn.height() / 2);
const bool gray = std::abs(end.red() - end.green()) < 12 && std::abs(end.green() - end.blue()) < 12;
QVERIFY2(gray != row.coloured, qPrintable(name + QStringLiteral(" track ") + end.name()));
}
}
+5 -1
View File
@@ -60,7 +60,11 @@ void CompositorMenusTests::everyEntryHasSwiftsShortcutWithCtrlForCommand()
for (int index = 3; index < 8; ++index)
adjustments << menus[4]->actions()[index]->objectName();
QCOMPARE(adjustments, (QStringList{"blackWhite", "colorBalance", "exposure", "gradientMap", "grain"}));
QCOMPARE(menus[5]->actions().size(), 5);
// Swift's Filter menu: Camera Raw after Lens Correction.
QStringList filters;
for (QAction *entry : menus[5]->actions())
filters << entry->text();
QCOMPARE(filters, (QStringList{"Gaussian Blur…", "Motion Blur…", "Add Noise…", "Lens Correction…", "Camera Raw Filter…", "Remove Background…"}));
QCOMPARE(menus[6]->actions().size(), 22);
}
+5
View File
@@ -218,6 +218,11 @@ compositor_test(CameraRawWiringTests)
compositor_test(CameraRawSliderTests)
compositor_test(CameraRawPanelTests)
compositor_test(CameraRawCanvasTests)
compositor_test(CameraRawCurveMixerTests)
compositor_test(CameraRawGradingLensTests)
compositor_test(CameraRawGroupRowsTests)
compositor_test(CameraRawGroupDrawTests)
compositor_test(CameraRawGroupWordsTests)
target_include_directories(CameraRawWiringTests PRIVATE ${OMAPHOTO_KERNELS})
compositor_test(LayerEffectsRendererTests)
compositor_test(LayerFlipTests)