Camera Raw panel's remaining groups and the Filter menu entry (12.8b2)
This commit is contained in:
1 parent
bb8129e8d5
commit
7ae263de80
26 files changed
+3227
-82
No files matched your search
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user