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

+240
View File
@@ -0,0 +1,240 @@
#include "UI/CameraRawColorControls.h"
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QVBoxLayout>
#include <cmath>
#include <numbers>
namespace {
CameraRawGradeWheel &wheelOf(CameraRawSettings &settings, int key)
{
CameraRawGradingSettings &grading = settings.grading;
return key == 0 ? grading.shadows : key == 1 ? grading.midtones : key == 2 ? grading.highlights : grading.global;
}
// Swift's GradeWheel: angle sets hue, distance saturation.
class GradeWheel : public QWidget {
public:
GradeWheel(std::function<CameraRawGradeWheel()> value, std::function<void(double, double)> set, std::function<void()> reset, QWidget *parent)
: QWidget(parent), m_value(std::move(value)), m_set(std::move(set)), m_reset(std::move(reset))
{
setFixedSize(86, 86);
setToolTip(QStringLiteral("Drag to set hue and saturation. Double-click to reset this wheel."));
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const double side = std::min(width(), height()), radius = side / 2 - 6;
const QPointF centre(side / 2, side / 2);
// Hue runs counterclockwise from red, as Qt's cone does.
QConicalGradient hues(centre, 0);
for (int degrees = 0; degrees <= 360; degrees += 30)
hues.setColorAt(degrees / 360.0, QColor::fromHsvF(float(degrees % 360) / 360, 1, 1));
painter.setOpacity(0.85);
painter.setPen(Qt::NoPen);
painter.setBrush(hues);
painter.drawEllipse(centre, side / 2, side / 2);
painter.setOpacity(1);
painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(QColor::fromRgbF(1, 1, 1, 0.8f), 1));
painter.drawEllipse(centre, side / 2 - 0.5, side / 2 - 0.5);
const CameraRawGradeWheel wheel = m_value();
const double angle = wheel.hue * std::numbers::pi / 180, distance = wheel.saturation / 100 * radius;
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::white);
painter.drawEllipse(centre + QPointF(std::cos(angle) * distance, -std::sin(angle) * distance), 5, 5);
}
// Swift's contentShape: presses land inside the circle alone.
bool inside(QPointF at) const
{
const double side = std::min(width(), height());
return QLineF(QPointF(side / 2, side / 2), at).length() <= side / 2;
}
void mousePressEvent(QMouseEvent *event) override
{
m_dragging = event->button() == Qt::LeftButton && inside(event->position());
if (m_dragging)
drag(event->position());
}
void mouseMoveEvent(QMouseEvent *event) override
{
if (m_dragging && event->buttons().testFlag(Qt::LeftButton))
drag(event->position());
}
void mouseReleaseEvent(QMouseEvent *) override { m_dragging = false; }
void mouseDoubleClickEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton && inside(event->position()))
m_reset();
}
private:
void drag(QPointF at)
{
const double side = std::min(width(), height()), radius = side / 2 - 6;
const double dx = at.x() - side / 2, dy = side / 2 - at.y();
double degrees = std::atan2(dy, dx) * 180 / std::numbers::pi;
if (degrees < 0)
degrees += 360;
m_set(degrees, std::min(100.0, std::hypot(dx, dy) / radius * 100));
}
const std::function<CameraRawGradeWheel()> m_value;
const std::function<void(double, double)> m_set;
const std::function<void()> m_reset;
bool m_dragging = false;
};
const std::array<QString, 5> pages{QStringLiteral("Three-Way"), QStringLiteral("Shadows"), QStringLiteral("Midtones"), QStringLiteral("Highlights"),
QStringLiteral("Global")};
QLabel *small(const QString &text, QWidget *parent)
{
auto *label = new QLabel(text, parent);
QFont font = label->font();
font.setPixelSize(10);
label->setFont(font);
return label;
}
}
CameraRawGradingControls::CameraRawGradingControls(EditorSession &session, QWidget *parent)
: QWidget(parent), m_session(session), m_page(new QComboBox(this)), m_threeWay(new QWidget(this)), m_single(new QWidget(this))
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
// Five segments want more room than the panel: a menu.
m_page->setObjectName(QStringLiteral("gradePage"));
for (const QString &page : pages)
m_page->addItem(page);
m_page->setToolTip(QStringLiteral("Three-Way shows shadows, midtones, and highlights. The other choices show one wheel."));
m_page->setSizeAdjustPolicy(QComboBox::AdjustToContents);
auto *hidden = new QLabel(QStringLiteral("Grading"), this);
hidden->setBuddy(m_page);
hidden->hide();
connect(m_page, &QComboBox::activated, this, [this](int page) {
if (!m_session.filterEdit())
return;
CameraRawPanel raw = m_session.filterEdit()->rawPanel;
raw.gradePage = CameraRawGradePage(page);
m_session.setCameraRawPanel(raw);
});
column->addWidget(m_page, 0, Qt::AlignLeft);
auto *three = new QHBoxLayout(m_threeWay);
three->setContentsMargins(0, 0, 0, 0);
three->setSpacing(30);
for (int key = 0; key < 3; ++key)
three->addWidget(wheel(pages.at(size_t(key) + 1), key));
three->addStretch(1);
column->addWidget(m_threeWay);
auto *single = new QHBoxLayout(m_single);
single->setContentsMargins(0, 0, 0, 0);
for (int key = 0; key < 4; ++key)
single->addWidget(wheel(pages.at(size_t(key) + 1), key));
single->addStretch(1);
column->addWidget(m_single);
const auto slider = [this, column](const QString &name, const QString &title, double low, double high, double reset, const QString &help,
double CameraRawGradingSettings::*key) {
const auto set = [this, key](double value) { update([key, value](CameraRawSettings &settings) { settings.grading.*key = value; }); };
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = 78, .fixedTitle = true, .fieldWidth = 0},
[this, key] { return raw().grading.*key; }, set, set, [set, reset] { set(reset); }, this);
column->addWidget(row);
m_rows.push_back(row);
};
slider(QStringLiteral("gradeBlending"), QStringLiteral("Blending"), 0, 100, 50, QStringLiteral("Controls how much the three tonal wheels overlap."),
&CameraRawGradingSettings::blending);
slider(QStringLiteral("gradeBalance"), QStringLiteral("Balance"), -100, 100, 0, QStringLiteral("Shifts the wheels toward shadows or highlights."),
&CameraRawGradingSettings::balance);
connect(&m_session, &EditorSession::changed, this, &CameraRawGradingControls::synchronize);
synchronize();
}
// Swift's wheel: its title, the wheel, the readout and brightness.
QWidget *CameraRawGradingControls::wheel(const QString &title, int key)
{
auto *box = new QWidget;
box->setObjectName(QStringLiteral("gradeWheel") + title);
auto *column = new QVBoxLayout(box);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(4);
QLabel *heading = small(title, box);
heading->setToolTip(QStringLiteral("Drag inside the wheel. Angle sets hue, distance sets saturation."));
column->addWidget(heading, 0, Qt::AlignHCenter);
column->addWidget(new GradeWheel([this, key] {
CameraRawSettings settings = raw();
return wheelOf(settings, key);
},
[this, key](double hue, double saturation) {
update([key, hue, saturation](CameraRawSettings &settings) {
wheelOf(settings, key).hue = hue;
wheelOf(settings, key).saturation = saturation;
});
},
[this, key] {
update([key](CameraRawSettings &settings) {
wheelOf(settings, key).hue = 0;
wheelOf(settings, key).saturation = 0;
});
},
box),
0, Qt::AlignHCenter);
QLabel *readout = small(QString(), box);
readout->setObjectName(QStringLiteral("gradeReadout"));
readout->setToolTip(QStringLiteral("Hue and saturation of this wheel."));
column->addWidget(readout, 0, Qt::AlignHCenter);
const auto set = [this, key](double value) { update([key, value](CameraRawSettings &settings) { wheelOf(settings, key).luminance = value; }); };
// Three columns fit when each slider takes 96.
auto *luminance = new CameraRawSlider(-100, 100, {}, QStringLiteral("Brightness added by this wheel."), set, [set] { set(0); }, box);
luminance->setObjectName(QStringLiteral("gradeLuminance"));
luminance->setFixedWidth(96);
column->addWidget(luminance, 0, Qt::AlignHCenter);
m_wheels.push_back({key, box});
return box;
}
CameraRawSettings CameraRawGradingControls::raw() const
{
return m_session.filterEdit() ? m_session.filterEdit()->settings.cameraRaw : CameraRawSettings();
}
void CameraRawGradingControls::update(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = m_session.filterEdit() ? m_session.filterEdit()->settings : FilterSettings();
change(settings.cameraRaw);
m_session.updateFilter(settings, m_session.filterEdit() ? m_session.filterEdit()->preview : true);
}
void CameraRawGradingControls::synchronize()
{
const CameraRawGradePage page = m_session.filterEdit() ? m_session.filterEdit()->rawPanel.gradePage : CameraRawGradePage::threeWay;
{
const QSignalBlocker quiet(m_page);
m_page->setCurrentIndex(int(page));
}
m_threeWay->setVisible(page == CameraRawGradePage::threeWay);
m_single->setVisible(page != CameraRawGradePage::threeWay);
CameraRawSettings settings = raw();
for (const auto &[key, box] : m_wheels) {
// A single page shows the wheel it names.
if (box->parentWidget() == m_single)
box->setVisible(int(page) == key + 1);
const CameraRawGradeWheel &wheel = wheelOf(settings, key);
box->findChild<QLabel *>(QStringLiteral("gradeReadout"))->setText(QStringLiteral("%1° %2").arg(std::lround(wheel.hue)).arg(std::lround(wheel.saturation)));
box->findChild<CameraRawSlider *>(QStringLiteral("gradeLuminance"))->display(wheel.luminance);
box->update();
}
for (CameraRawRow *row : m_rows)
row->synchronize();
}
+316
View File
@@ -0,0 +1,316 @@
#include "Rendering/EyedropperIcon.h"
#include "UI/CameraRawColorControls.h"
#include "UI/LayerIcons.h"
#include <QAbstractButton>
#include <QButtonGroup>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QPainter>
#include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout>
namespace {
const std::array<QString, 3> tabs{QStringLiteral("Hue"), QStringLiteral("Saturation"), QStringLiteral("Luminance")};
QColor hsb(double degrees, double saturation, double brightness)
{
return QColor::fromHsvF(float(std::fmod(degrees, 360) / 360), float(saturation), float(brightness));
}
// Swift's familyTrack: the family's hue, strength or lightness.
CameraRawSliderTrack familyTrack(int index, int tab)
{
const double hue = CameraRawMixerSettings::centers.at(size_t(index));
using Kind = CameraRawSliderTrack::Kind;
return {tab == 1 ? Kind::saturation : tab == 2 ? Kind::luminance : Kind::hue, hue};
}
// Swift's swatch: a filled circle, ringed white when chosen.
class Swatch : public QAbstractButton {
public:
Swatch(double side, QWidget *parent) : QAbstractButton(parent), m_side(side) { setFixedSize(int(side) + 4, int(side) + 4); }
QColor colour;
bool chosen = false;
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const QRectF circle(2, 2, m_side, m_side);
painter.setPen(Qt::NoPen);
painter.setBrush(colour);
painter.drawEllipse(circle);
if (chosen) {
painter.setPen(QPen(Qt::white, 2));
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(circle);
}
}
private:
const double m_side;
};
void clear(QWidget *box)
{
for (QWidget *child : box->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly))
delete child;
}
}
CameraRawMixerControls::CameraRawMixerControls(EditorSession &session, QWidget *parent)
: QWidget(parent), m_session(session), m_hsl(new QWidget(this)), m_families(new QWidget(m_hsl)), m_color(new QWidget(this)),
m_colorRows(new QWidget(m_color)), m_point(new QWidget(this)), m_sampler(new QToolButton(m_point)), m_picked(new QWidget(m_point)),
m_pointControls(new QWidget(m_point)), m_pointRows(new QWidget(m_pointControls)),
m_visualize(new QCheckBox(QStringLiteral("Visualize Range"), m_pointControls)), m_target(new QPushButton(QStringLiteral("Targeted Adjustment"), this))
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
m_page = cameraRawSegments({QStringLiteral("HSL"), QStringLiteral("Color"), QStringLiteral("Point Color")}, QStringLiteral("mixerPage"),
QStringLiteral("HSL lists every color. Color edits one family. Point Color adjusts a color you pick."), column,
[this](int page) { panel([page](CameraRawPanel &raw) { raw.mixerPage = CameraRawMixerPage(page); }); });
auto *hsl = new QVBoxLayout(m_hsl);
hsl->setContentsMargins(0, 0, 0, 0);
hsl->setSpacing(8);
m_tab = cameraRawSegments({tabs[0], tabs[1], tabs[2]}, QStringLiteral("mixerTab"),
QStringLiteral("Hue shifts the color, Saturation its strength, and Luminance its brightness."), hsl,
[this](int tab) { panel([tab](CameraRawPanel &raw) { raw.mixerTab = CameraRawMixerTab(tab); }); });
new QVBoxLayout(m_families);
m_families->layout()->setContentsMargins(0, 0, 0, 0);
m_families->layout()->setSpacing(8);
hsl->addWidget(m_families);
column->addWidget(m_hsl);
auto *color = new QVBoxLayout(m_color);
color->setContentsMargins(0, 0, 0, 0);
color->setSpacing(8);
auto *swatches = new QHBoxLayout;
for (int index = 0; index < 8; ++index) {
auto *swatch = new Swatch(18, m_color);
swatch->setObjectName(QStringLiteral("mixerSwatch%1").arg(index));
swatch->colour = hsb(CameraRawMixerSettings::centers.at(size_t(index)), 0.8, 0.9);
swatch->setToolTip(QStringLiteral("Edit %1.").arg(CameraRawMixerSettings::names.at(size_t(index))));
connect(swatch, &QAbstractButton::clicked, this, [this, index] { panel([index](CameraRawPanel &raw) { raw.mixerSwatch = index; }); });
swatches->addWidget(swatch);
m_swatches.push_back(swatch);
}
swatches->addStretch(1);
color->addLayout(swatches);
new QVBoxLayout(m_colorRows);
m_colorRows->layout()->setContentsMargins(0, 0, 0, 0);
m_colorRows->layout()->setSpacing(8);
color->addWidget(m_colorRows);
column->addWidget(m_color);
auto *point = new QVBoxLayout(m_point);
point->setContentsMargins(0, 0, 0, 0);
point->setSpacing(8);
auto *pickRow = new QHBoxLayout;
m_sampler->setObjectName(QStringLiteral("pointColorSampler"));
m_sampler->setAutoRaise(true);
m_sampler->setToolTip(QStringLiteral("Click the picture to save a color. Up to eight colors."));
connect(m_sampler, &QToolButton::clicked, this, [this] { panel([](CameraRawPanel &raw) { raw.samplesPointColor = !raw.samplesPointColor; }); });
pickRow->addWidget(m_sampler);
new QHBoxLayout(m_picked);
m_picked->layout()->setContentsMargins(0, 0, 0, 0);
pickRow->addWidget(m_picked);
pickRow->addStretch(1);
point->addLayout(pickRow);
auto *controls = new QVBoxLayout(m_pointControls);
controls->setContentsMargins(0, 0, 0, 0);
controls->setSpacing(8);
new QVBoxLayout(m_pointRows);
m_pointRows->layout()->setContentsMargins(0, 0, 0, 0);
m_pointRows->layout()->setSpacing(8);
controls->addWidget(m_pointRows);
m_visualize->setObjectName(QStringLiteral("visualizeRange"));
m_visualize->setToolTip(QStringLiteral("Dims the picture outside this color's range. It is not kept when you press OK."));
connect(m_visualize, &QAbstractButton::clicked, this, [this](bool on) { updatePoint([on](CameraRawPointColor &color) { color.visualize = on; }); });
controls->addWidget(m_visualize);
point->addWidget(m_pointControls);
column->addWidget(m_point);
m_target->setObjectName(QStringLiteral("mixerTargeted"));
m_target->setCheckable(true);
m_target->setAutoDefault(false);
m_target->setToolTip(QStringLiteral("Drag a color in the picture. Nearby color families move together."));
connect(m_target, &QPushButton::clicked, this, [this] {
panel([](CameraRawPanel &raw) {
raw.targetsCurve = false;
raw.targetsMixer = !raw.targetsMixer;
});
});
column->addWidget(m_target, 0, Qt::AlignLeft);
connect(&m_session, &EditorSession::changed, this, &CameraRawMixerControls::synchronize);
synchronize();
}
void CameraRawMixerControls::row(QWidget *box, CameraRawRow::Spec spec, std::function<double(const CameraRawSettings &)> value,
std::function<void(CameraRawSettings &, double)> write, double reset)
{
const auto set = [this, write](double number) { update([write, number](CameraRawSettings &settings) { write(settings, number); }); };
box->layout()->addWidget(new CameraRawRow(std::move(spec), [this, value] { return value(raw()); }, set, set, [set, reset] { set(reset); }, box));
}
void CameraRawMixerControls::rebuildFamilies(int tab)
{
clear(m_families);
for (int index = 0; index < 8; ++index) {
const QString &name = CameraRawMixerSettings::names.at(size_t(index));
const auto family = [tab](CameraRawSettings &settings) -> std::array<double, 8> & {
return tab == 1 ? settings.mixer.saturation : tab == 2 ? settings.mixer.luminance : settings.mixer.hue;
};
row(m_families,
{.name = QStringLiteral("family") + name, .title = name, .help = QStringLiteral("%1 of %2.").arg(tabs.at(size_t(tab)), name),
.track = familyTrack(index, tab), .titleWidth = 78, .fixedTitle = true, .fieldWidth = 48},
[family, index](const CameraRawSettings &settings) {
CameraRawSettings copy = settings;
return family(copy).at(size_t(index));
},
[family, index](CameraRawSettings &settings, double value) { family(settings).at(size_t(index)) = value; }, 0);
}
}
void CameraRawMixerControls::rebuildColor(int swatch)
{
clear(m_colorRows);
const std::array<QString, 3> help{QStringLiteral("Shifts the selected color family around the wheel."),
QStringLiteral("Makes the selected color family stronger or quieter."),
QStringLiteral("Makes the selected color family lighter or darker.")};
for (int tab = 0; tab < 3; ++tab) {
const auto family = [tab](CameraRawSettings &settings) -> std::array<double, 8> & {
return tab == 1 ? settings.mixer.saturation : tab == 2 ? settings.mixer.luminance : settings.mixer.hue;
};
row(m_colorRows,
{.name = QStringLiteral("color") + tabs.at(size_t(tab)), .title = tabs.at(size_t(tab)), .help = help.at(size_t(tab)),
.track = familyTrack(swatch, tab), .titleWidth = 88, .fixedTitle = true, .fieldWidth = 0},
[family, swatch](const CameraRawSettings &settings) {
CameraRawSettings copy = settings;
return family(copy).at(size_t(swatch));
},
[family, swatch](CameraRawSettings &settings, double value) { family(settings).at(size_t(swatch)) = value; }, 0);
}
}
void CameraRawMixerControls::rebuildPoint(int index, double hue)
{
clear(m_pointRows);
using Kind = CameraRawSliderTrack::Kind;
const auto point = [this, index](const QString &name, const QString &title, const QString &help, double CameraRawPointColor::*key, double low,
double high, double reset, CameraRawSliderTrack track) {
row(m_pointRows,
{.name = name, .title = title, .help = help, .low = low, .high = high, .track = track, .titleWidth = 110, .fixedTitle = true, .fieldWidth = 0},
[index, key](const CameraRawSettings &settings) {
const std::vector<CameraRawPointColor> &points = settings.mixer.points;
return size_t(index) < points.size() ? points[size_t(index)].*key : 0.0;
},
[index, key](CameraRawSettings &settings, double value) {
if (size_t(index) < settings.mixer.points.size())
settings.mixer.points[size_t(index)].*key = value;
},
reset);
};
point(QStringLiteral("hueShift"), QStringLiteral("Hue Shift"), QStringLiteral("Shifts the picked color around the color wheel."),
&CameraRawPointColor::hueShift, -100, 100, 0, {Kind::hue, hue});
point(QStringLiteral("saturationShift"), QStringLiteral("Saturation Shift"), QStringLiteral("Makes the picked color stronger or quieter."),
&CameraRawPointColor::saturationShift, -100, 100, 0, {Kind::saturation, hue});
point(QStringLiteral("luminanceShift"), QStringLiteral("Luminance Shift"), QStringLiteral("Makes the picked color lighter or darker."),
&CameraRawPointColor::luminanceShift, -100, 100, 0, {Kind::luminance, hue});
point(QStringLiteral("hueRange"), QStringLiteral("Hue Range"), QStringLiteral("How far in hue the adjustment reaches."), &CameraRawPointColor::hueRange,
5, 180, 30, {});
point(QStringLiteral("saturationRange"), QStringLiteral("Saturation Range"), QStringLiteral("How far in saturation the adjustment reaches."),
&CameraRawPointColor::saturationRange, 0.05, 1, 0.4, {});
point(QStringLiteral("luminanceRange"), QStringLiteral("Luminance Range"), QStringLiteral("How far in brightness the adjustment reaches."),
&CameraRawPointColor::luminanceRange, 0.05, 1, 0.4, {});
}
CameraRawSettings CameraRawMixerControls::raw() const
{
return m_session.filterEdit() ? m_session.filterEdit()->settings.cameraRaw : CameraRawSettings();
}
void CameraRawMixerControls::update(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = m_session.filterEdit() ? m_session.filterEdit()->settings : FilterSettings();
change(settings.cameraRaw);
m_session.updateFilter(settings, m_session.filterEdit() ? m_session.filterEdit()->preview : true);
}
// Swift's updatePoint: the chosen point, when it still exists.
void CameraRawMixerControls::updatePoint(const std::function<void(CameraRawPointColor &)> &change)
{
const int index = m_session.filterEdit() ? m_session.filterEdit()->rawPanel.pointIndex : 0;
update([index, &change](CameraRawSettings &settings) {
if (index >= 0 && size_t(index) < settings.mixer.points.size())
change(settings.mixer.points[size_t(index)]);
});
}
void CameraRawMixerControls::panel(const std::function<void(CameraRawPanel &)> &change)
{
if (!m_session.filterEdit())
return;
CameraRawPanel raw = m_session.filterEdit()->rawPanel;
change(raw);
m_session.setCameraRawPanel(raw);
}
void CameraRawMixerControls::synchronize()
{
const std::optional<FilterEdit> &edit = m_session.filterEdit();
const CameraRawPanel shown = edit ? edit->rawPanel : CameraRawPanel();
const CameraRawSettings settings = raw();
m_page->button(int(shown.mixerPage))->setChecked(true);
m_tab->button(int(shown.mixerTab))->setChecked(true);
m_hsl->setVisible(shown.mixerPage == CameraRawMixerPage::hsl);
m_color->setVisible(shown.mixerPage == CameraRawMixerPage::color);
m_point->setVisible(shown.mixerPage == CameraRawMixerPage::point);
if (m_shownTab != int(shown.mixerTab)) {
m_shownTab = int(shown.mixerTab);
rebuildFamilies(*m_shownTab);
}
const int swatch = std::min(7, shown.mixerSwatch);
if (m_shownSwatch != swatch) {
m_shownSwatch = swatch;
rebuildColor(swatch);
}
for (size_t index = 0; index < m_swatches.size(); ++index) {
static_cast<Swatch *>(m_swatches[index])->chosen = int(index) == shown.mixerSwatch;
m_swatches[index]->update();
}
const std::vector<CameraRawPointColor> &points = settings.mixer.points;
if (m_shownPicked != points.size()) {
m_shownPicked = points.size();
clear(m_picked);
for (size_t index = 0; index < points.size(); ++index) {
auto *pick = new Swatch(16, m_picked);
pick->setObjectName(QStringLiteral("pickedColor%1").arg(index));
pick->setToolTip(QStringLiteral("Select this picked color."));
connect(pick, &QAbstractButton::clicked, this, [this, index] { panel([index](CameraRawPanel &raw) { raw.pointIndex = int(index); }); });
m_picked->layout()->addWidget(pick);
// Shown now: a layout shows late children only later.
pick->show();
}
}
for (size_t index = 0; index < points.size(); ++index) {
auto *pick = m_picked->findChild<Swatch *>(QStringLiteral("pickedColor%1").arg(index));
pick->colour = hsb(points[index].hue, points[index].saturation, points[index].luminance);
pick->chosen = int(index) == shown.pointIndex;
pick->update();
}
const bool chosen = shown.pointIndex >= 0 && size_t(shown.pointIndex) < points.size();
m_pointControls->setVisible(chosen);
if (chosen && m_shownPoint != std::pair(shown.pointIndex, points[size_t(shown.pointIndex)].hue)) {
m_shownPoint = std::pair(shown.pointIndex, points[size_t(shown.pointIndex)].hue);
rebuildPoint(shown.pointIndex, m_shownPoint->second);
}
{
const QSignalBlocker quiet(m_visualize);
m_visualize->setChecked(chosen && points[size_t(shown.pointIndex)].visualize);
}
m_sampler->setIcon(EyedropperIcon::icon(palette().color(shown.samplesPointColor ? QPalette::Highlight : QPalette::PlaceholderText), devicePixelRatioF()));
for (CameraRawRow *each : findChildren<CameraRawRow *>())
each->synchronize();
m_target->setChecked(shown.targetsMixer);
m_target->setIcon(QIcon(LayerIcons::pixmap(LayerIcon::scope, 14, palette().color(QPalette::ButtonText), devicePixelRatioF())));
}
+355
View File
@@ -0,0 +1,355 @@
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawRow.h"
#include "UI/LayerIcons.h"
#include <QButtonGroup>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout>
QButtonGroup *cameraRawSegments(const QStringList &titles, const QString &name, const QString &help, QVBoxLayout *column, std::function<void(int)> choose)
{
auto *box = new QWidget;
auto *row = new QHBoxLayout(box);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(0);
auto *group = new QButtonGroup(box);
group->setExclusive(true);
for (int index = 0; index < titles.size(); ++index) {
auto *button = new QToolButton(box);
button->setText(titles[index]);
button->setCheckable(true);
button->setToolTip(help);
button->setObjectName(name + QString::number(index));
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
group->addButton(button, index);
row->addWidget(button);
}
QObject::connect(group, &QButtonGroup::idClicked, box, std::move(choose));
column->addWidget(box);
return group;
}
// Swift's curve graph: diagonal, curve, dividers or points.
class CameraRawCurveGraph : public QWidget {
public:
explicit CameraRawCurveGraph(CameraRawCurveControls &owner) : QWidget(&owner), m_owner(owner)
{
setObjectName(QStringLiteral("cameraRawCurveGraph"));
setFixedHeight(150);
}
protected:
bool parametric() const
{
return !m_owner.m_session.filterEdit() || m_owner.m_session.filterEdit()->rawPanel.curvePage == CameraRawCurvePage::parametric;
}
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPainterPath frame;
frame.addRoundedRect(QRectF(rect()), 4, 4);
painter.setClipPath(frame);
painter.fillRect(rect(), QColor::fromRgbF(0, 0, 0, 0.35f));
const double w = width(), h = height();
painter.setPen(QPen(QColor::fromRgbF(1, 1, 1, 0.25f), 1));
painter.drawLine(QPointF(0, h), QPointF(w, 0));
const CameraRawCurveSettings curve = m_owner.raw().curve;
std::vector<double> samples;
if (parametric()) {
for (int index = 0; index < 64; ++index)
samples.push_back(curve.parametric(index / 63.0));
} else {
for (const float value : curve.channelTable(m_owner.currentPoints()))
samples.push_back(value);
}
QPainterPath line;
for (size_t index = 0; index < samples.size(); ++index) {
const QPointF point(double(index) / double(std::max<size_t>(1, samples.size() - 1)) * w, (1 - samples[index]) * h);
if (index == 0)
line.moveTo(point);
else
line.lineTo(point);
}
painter.strokePath(line, QPen(Qt::white, 1.5));
if (parametric()) {
painter.setPen(QPen(Qt::white, 3));
for (const double split : {curve.shadowSplit, curve.darkSplit, curve.lightSplit})
painter.drawLine(QPointF(split / 100 * w, h - 8), QPointF(split / 100 * w, h));
return;
}
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::white);
for (const CurvePoint &point : m_owner.currentPoints())
painter.drawEllipse(QRectF(point.x * w - 4, (1 - point.y) * h - 4, 8, 8));
}
// Swift's two drags: still, a press adds; moved, it drags.
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton)
m_press = event->position();
}
void mouseMoveEvent(QMouseEvent *event) override
{
if (!m_press || !event->buttons().testFlag(Qt::LeftButton))
return;
if (!m_moved && QLineF(*m_press, event->position()).length() < 2)
return;
m_moved = true;
if (parametric())
moveDivider(event->position().x());
else
movePoint(event->position());
}
void mouseReleaseEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton && m_press && !m_moved && event->position() == *m_press && !parametric())
addPoint(event->position());
m_press.reset();
m_moved = false;
}
// The double click's own release must add nothing back.
void mouseDoubleClickEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton && !parametric())
removePoint(event->position());
}
private:
void moveDivider(double x)
{
const double value = std::min(98.0, std::max(2.0, x / std::max(double(width()), 1.0) * 100));
const CameraRawCurveSettings curve = m_owner.raw().curve;
const std::array<double, 3> splits{curve.shadowSplit, curve.darkSplit, curve.lightSplit};
size_t nearest = 0;
for (size_t index = 1; index < 3; ++index)
if (std::abs(splits[index] - value) < std::abs(splits[nearest] - value))
nearest = index;
m_owner.update([nearest, value](CameraRawSettings &settings) {
(nearest == 0 ? settings.curve.shadowSplit : nearest == 1 ? settings.curve.darkSplit : settings.curve.lightSplit) = value;
});
}
void addPoint(QPointF at)
{
std::vector<CurvePoint> points = m_owner.currentPoints();
points.push_back({std::min(0.99, std::max(0.01, at.x() / width())), std::min(1.0, std::max(0.0, 1 - at.y() / height()))});
m_owner.store(points);
}
// The nearest inner point by x; ends stay.
std::optional<size_t> nearestInner(const std::vector<CurvePoint> &points, double x) const
{
std::optional<size_t> nearest;
for (size_t index = 1; index + 1 < points.size(); ++index)
if (!nearest || std::abs(points[index].x - x) < std::abs(points[*nearest].x - x))
nearest = index;
return nearest;
}
void movePoint(QPointF at)
{
std::vector<CurvePoint> points = m_owner.currentPoints();
const double x = at.x() / width();
const std::optional<size_t> index = nearestInner(points, x);
if (!index)
return;
points[*index] = {std::min(0.98, std::max(0.02, x)), std::min(1.0, std::max(0.0, 1 - at.y() / height()))};
m_owner.store(points);
}
void removePoint(QPointF at)
{
std::vector<CurvePoint> points = m_owner.currentPoints();
const double x = at.x() / width();
const std::optional<size_t> index = nearestInner(points, x);
if (!index || !(std::abs(points[*index].x - x) < 0.04))
return;
points.erase(points.begin() + std::ptrdiff_t(*index));
m_owner.store(points);
}
CameraRawCurveControls &m_owner;
std::optional<QPointF> m_press;
bool m_moved = false;
};
namespace {
QLabel *caption(const QString &text, int pixels)
{
auto *label = new QLabel(text);
QFont font = label->font();
font.setPixelSize(pixels);
label->setFont(font);
label->setForegroundRole(QPalette::PlaceholderText);
return label;
}
}
CameraRawCurveControls::CameraRawCurveControls(EditorSession &session, QWidget *parent)
: QWidget(parent), m_session(session), m_graph(new CameraRawCurveGraph(*this)), m_amounts(new QWidget(this)), m_points(new QWidget(this)),
m_selected(caption(QString(), 10)), m_preset(new QComboBox(m_points)), m_target(new QPushButton(QStringLiteral("Targeted Adjustment"), this))
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
m_page = cameraRawSegments({QStringLiteral("Parametric"), QStringLiteral("Point")}, QStringLiteral("curvePage"),
QStringLiteral("Parametric lifts tonal regions. Point places anchors on the curve."), column,
[this](int page) { panel([page](CameraRawPanel &raw) { raw.curvePage = CameraRawCurvePage(page); }); });
m_channelBox = new QWidget(this);
auto *channelColumn = new QVBoxLayout(m_channelBox);
channelColumn->setContentsMargins(0, 0, 0, 0);
m_channel = cameraRawSegments({QStringLiteral("RGB"), QStringLiteral("Red"), QStringLiteral("Green"), QStringLiteral("Blue")},
QStringLiteral("pointChannel"), QStringLiteral("RGB changes brightness. Red, green, and blue also shift the color."),
channelColumn, [this](int channel) { panel([channel](CameraRawPanel &raw) { raw.pointChannel = CameraRawPointChannel(channel); }); });
column->addWidget(m_channelBox);
column->addWidget(m_graph);
auto *amounts = new QVBoxLayout(m_amounts);
amounts->setContentsMargins(0, 0, 0, 0);
amounts->setSpacing(8);
const auto amount = [this, amounts](const QString &name, const QString &title, double CameraRawCurveSettings::*key, const QString &help) {
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .titleWidth = 88, .fixedTitle = true, .fieldWidth = 48},
[this, key] { return raw().curve.*key; },
[this, key](double value) { update([key, value](CameraRawSettings &settings) { settings.curve.*key = value; }); },
[this, key](double value) { update([key, value](CameraRawSettings &settings) { settings.curve.*key = value; }); },
[this, key] { update([key](CameraRawSettings &settings) { settings.curve.*key = 0; }); });
amounts->addWidget(row);
m_rows.push_back(row);
};
amount(QStringLiteral("curveHighlights"), QStringLiteral("Highlights"), &CameraRawCurveSettings::highlights, QStringLiteral("Lifts or lowers the brightest tones."));
amount(QStringLiteral("curveLights"), QStringLiteral("Lights"), &CameraRawCurveSettings::lights, QStringLiteral("Lifts or lowers the light tones."));
amount(QStringLiteral("curveDarks"), QStringLiteral("Darks"), &CameraRawCurveSettings::darks, QStringLiteral("Lifts or lowers the dark tones."));
amount(QStringLiteral("curveShadows"), QStringLiteral("Shadows"), &CameraRawCurveSettings::shadows, QStringLiteral("Lifts or lowers the darkest tones."));
column->addWidget(m_amounts);
auto *points = new QVBoxLayout(m_points);
points->setContentsMargins(0, 0, 0, 0);
points->setSpacing(8);
m_selected->setObjectName(QStringLiteral("curveSelectedPoint"));
m_selected->setToolTip(QStringLiteral("Input and output of the selected curve point."));
points->addWidget(m_selected);
m_preset->setObjectName(QStringLiteral("curvePreset"));
m_preset->addItems({QStringLiteral("Custom"), QStringLiteral("Linear"), QStringLiteral("Medium Contrast"), QStringLiteral("Strong Contrast")});
m_preset->setToolTip(QStringLiteral("Replaces this curve with a straight line or a contrast curve."));
auto *presetRow = new QHBoxLayout;
auto *presetTitle = new QLabel(QStringLiteral("Preset"), m_points);
presetTitle->setBuddy(m_preset);
presetRow->addWidget(presetTitle);
presetRow->addWidget(m_preset);
presetRow->addStretch(1);
points->addLayout(presetRow);
connect(m_preset, &QComboBox::activated, this, [this](int preset) {
if (preset == 1)
store(CameraRawCurveSettings::linear());
else if (preset == 2)
store(CameraRawCurveSettings::mediumContrast());
else if (preset == 3)
store(CameraRawCurveSettings::strongContrast());
});
m_refine = new CameraRawRow({.name = QStringLiteral("refineSaturation"), .title = QStringLiteral("Refine Saturation"),
.help = QStringLiteral("How much the RGB curve also changes color strength. Zero keeps it to brightness."), .titleWidth = 88,
.fixedTitle = true, .fieldWidth = 48},
[this] { return raw().curve.refineSaturation; },
[this](double value) { update([value](CameraRawSettings &settings) { settings.curve.refineSaturation = value; }); },
[this](double value) { update([value](CameraRawSettings &settings) { settings.curve.refineSaturation = value; }); },
[this] { update([](CameraRawSettings &settings) { settings.curve.refineSaturation = 0; }); });
points->addWidget(m_refine);
m_rows.push_back(m_refine);
column->addWidget(m_points);
m_target->setObjectName(QStringLiteral("curveTargeted"));
m_target->setCheckable(true);
m_target->setAutoDefault(false);
m_target->setToolTip(QStringLiteral("Drag on the picture to move the curve for the tone under the pointer."));
connect(m_target, &QPushButton::clicked, this, [this] {
panel([](CameraRawPanel &raw) {
raw.targetsMixer = false;
raw.targetsCurve = !raw.targetsCurve;
});
});
column->addWidget(m_target, 0, Qt::AlignLeft);
connect(&m_session, &EditorSession::changed, this, &CameraRawCurveControls::synchronize);
synchronize();
}
CameraRawSettings CameraRawCurveControls::raw() const
{
return m_session.filterEdit() ? m_session.filterEdit()->settings.cameraRaw : CameraRawSettings();
}
std::vector<CurvePoint> CameraRawCurveControls::currentPoints() const
{
const CameraRawCurveSettings curve = raw().curve;
switch (m_session.filterEdit() ? m_session.filterEdit()->rawPanel.pointChannel : CameraRawPointChannel::rgb) {
case CameraRawPointChannel::rgb: return curve.rgb;
case CameraRawPointChannel::red: return curve.red;
case CameraRawPointChannel::green: return curve.green;
case CameraRawPointChannel::blue: return curve.blue;
}
throw std::logic_error("unknown point channel");
}
void CameraRawCurveControls::update(const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = m_session.filterEdit() ? m_session.filterEdit()->settings : FilterSettings();
change(settings.cameraRaw);
m_session.updateFilter(settings, m_session.filterEdit() ? m_session.filterEdit()->preview : true);
}
void CameraRawCurveControls::store(const std::vector<CurvePoint> &points)
{
const CameraRawPointChannel channel = m_session.filterEdit() ? m_session.filterEdit()->rawPanel.pointChannel : CameraRawPointChannel::rgb;
update([&points, channel](CameraRawSettings &settings) {
(channel == CameraRawPointChannel::rgb ? settings.curve.rgb
: channel == CameraRawPointChannel::red ? settings.curve.red
: channel == CameraRawPointChannel::green ? settings.curve.green
: settings.curve.blue) = points;
});
}
void CameraRawCurveControls::panel(const std::function<void(CameraRawPanel &)> &change)
{
if (!m_session.filterEdit())
return;
CameraRawPanel raw = m_session.filterEdit()->rawPanel;
change(raw);
m_session.setCameraRawPanel(raw);
}
void CameraRawCurveControls::synchronize()
{
const std::optional<FilterEdit> &edit = m_session.filterEdit();
const bool point = edit && edit->rawPanel.curvePage == CameraRawCurvePage::point;
m_page->button(point ? 1 : 0)->setChecked(true);
m_channelBox->setVisible(point);
const CameraRawPointChannel channel = edit ? edit->rawPanel.pointChannel : CameraRawPointChannel::rgb;
m_channel->button(int(channel))->setChecked(true);
m_graph->setToolTip(point ? QStringLiteral("Drag a point. Click the curve to add one. Double-click a point to remove it.")
: QStringLiteral("Drag a divider to change which tones the neighboring sliders affect."));
m_graph->update();
m_amounts->setVisible(!point);
m_points->setVisible(point);
const std::vector<CurvePoint> points = currentPoints();
// Swift's selectedPoint: the last inner point, else the last.
const CurvePoint selected = points.size() > 2 ? points[points.size() - 2] : points.back();
m_selected->setText(QStringLiteral("In %1 Out %2").arg(std::lround(selected.x * 255)).arg(std::lround(selected.y * 255)));
const QSignalBlocker quiet(m_preset);
m_preset->setCurrentIndex(points == CameraRawCurveSettings::linear() ? 1
: points == CameraRawCurveSettings::mediumContrast() ? 2
: points == CameraRawCurveSettings::strongContrast() ? 3
: 0);
m_refine->setVisible(channel == CameraRawPointChannel::rgb);
for (CameraRawRow *row : m_rows)
row->synchronize();
m_target->setChecked(edit && edit->rawPanel.targetsCurve);
m_target->setIcon(QIcon(LayerIcons::pixmap(LayerIcon::scope, 14, palette().color(QPalette::ButtonText), devicePixelRatioF())));
}
+105
View File
@@ -0,0 +1,105 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/CameraRawRow.h"
#include <QWidget>
class QAbstractButton;
class QButtonGroup;
class QComboBox;
class QLabel;
class QPushButton;
class QToolButton;
class QVBoxLayout;
// Swift's segmented pickers: touching checkable buttons, one chosen.
QButtonGroup *cameraRawSegments(const QStringList &titles, const QString &name, const QString &help, QVBoxLayout *column, std::function<void(int)> choose);
// Swift's CameraRawCurveControls: parametric regions or point curves.
class CameraRawCurveControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawCurveControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
CameraRawSettings raw() const;
std::vector<CurvePoint> currentPoints() const;
void update(const std::function<void(CameraRawSettings &)> &change);
void store(const std::vector<CurvePoint> &points);
void panel(const std::function<void(CameraRawPanel &)> &change);
friend class CameraRawCurveGraph;
EditorSession &m_session;
QButtonGroup *m_page;
QWidget *m_channelBox;
QButtonGroup *m_channel;
QWidget *const m_graph;
QWidget *const m_amounts;
QWidget *const m_points;
QLabel *const m_selected;
QComboBox *const m_preset;
CameraRawRow *m_refine = nullptr;
std::vector<CameraRawRow *> m_rows;
QPushButton *const m_target;
};
// Swift's CameraRawMixerControls: HSL, one family, or picked colours.
class CameraRawMixerControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawMixerControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
CameraRawSettings raw() const;
void update(const std::function<void(CameraRawSettings &)> &change);
void updatePoint(const std::function<void(CameraRawPointColor &)> &change);
void panel(const std::function<void(CameraRawPanel &)> &change);
// Rows following a tab, a swatch or a point.
void rebuildFamilies(int tab);
void rebuildColor(int swatch);
void rebuildPoint(int index, double hue);
void row(QWidget *box, CameraRawRow::Spec spec, std::function<double(const CameraRawSettings &)> value,
std::function<void(CameraRawSettings &, double)> write, double reset);
EditorSession &m_session;
QButtonGroup *m_page;
QButtonGroup *m_tab;
QWidget *const m_hsl;
QWidget *const m_families;
QWidget *const m_color;
QWidget *const m_colorRows;
std::vector<QAbstractButton *> m_swatches;
QWidget *const m_point;
QToolButton *const m_sampler;
QWidget *const m_picked;
QWidget *const m_pointControls;
QWidget *const m_pointRows;
QAbstractButton *const m_visualize;
QPushButton *const m_target;
// What the rebuilt rows were made for.
std::optional<int> m_shownTab;
std::optional<int> m_shownSwatch;
std::optional<std::pair<int, double>> m_shownPoint;
size_t m_shownPicked = 0;
};
// Swift's CameraRawGradingControls: the wheels, Blending, Balance.
class CameraRawGradingControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawGradingControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
CameraRawSettings raw() const;
void update(const std::function<void(CameraRawSettings &)> &change);
QWidget *wheel(const QString &title, int key);
EditorSession &m_session;
QComboBox *const m_page;
QWidget *const m_threeWay;
QWidget *const m_single;
std::vector<std::pair<int, QWidget *>> m_wheels;
std::vector<CameraRawRow *> m_rows;
};
+23 -67
View File
@@ -1,5 +1,8 @@
#include "UI/CameraRawControls.h"
#include "UI/ColorPickerSheet.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawDetailOpticsControls.h"
#include "UI/CameraRawGeometryCalibrationControls.h"
#include "UI/CameraRawRow.h"
#include "UI/LayerIcons.h"
#include "Rendering/EyedropperIcon.h"
#include <QApplication>
@@ -51,20 +54,6 @@ bool adjusts(const CameraRawSettings &raw, int section)
throw std::logic_error("unknown Camera Raw section");
}
// Swift's `.number`: at most `decimals` places, none trailing.
QString shown(double value, int decimals, QLocale locale)
{
locale.setNumberOptions(QLocale::OmitGroupSeparator);
QString text = locale.toString(value, 'f', decimals);
if (decimals == 0)
return text;
while (text.endsWith(locale.zeroDigit()))
text.chop(locale.zeroDigit().size());
if (text.endsWith(locale.decimalPoint()))
text.chop(locale.decimalPoint().size());
return text;
}
// Swift's graph: the three ribbons, or the vectorscope's cells.
class ScopeGraph : public QWidget {
public:
@@ -206,10 +195,13 @@ CameraRawControls::CameraRawControls(EditorSession &session, QWidget *parent)
light(group(Section::light, groups));
color(group(Section::color, groups));
effects(group(Section::effects, groups));
// Curve to Calibration fill their groups with 12.8b2.
for (const Section section : {Section::curve, Section::colorMixer, Section::colorGrading, Section::detail, Section::optics, Section::geometry,
Section::calibration})
group(section, groups);
group(Section::curve, groups)->addWidget(new CameraRawCurveControls(session));
group(Section::colorMixer, groups)->addWidget(new CameraRawMixerControls(session));
group(Section::colorGrading, groups)->addWidget(new CameraRawGradingControls(session));
group(Section::detail, groups)->addWidget(new CameraRawDetailControls(session));
group(Section::optics, groups)->addWidget(new CameraRawOpticsControls(session));
group(Section::geometry, groups)->addWidget(new CameraRawGeometryControls(session));
group(Section::calibration, groups)->addWidget(new CameraRawCalibrationControls(session));
groups->addStretch(1);
auto *scroll = new QScrollArea(this);
scroll->setObjectName(QStringLiteral("cameraRawGroups"));
@@ -274,43 +266,17 @@ void CameraRawControls::slider(QVBoxLayout *column, const QString &name, const Q
double low, double high, int decimals, std::optional<CameraRawClipping> clipping, const QString &help,
CameraRawSliderTrack track, double reset)
{
auto *box = new QWidget;
auto *label = new QLabel(title, box);
label->setMinimumWidth(labelWidth);
label->setToolTip(help);
const double step = std::pow(10.0, decimals);
auto *slider = new CameraRawSlider(
low, high, track, help, [this, key, step, clipping](double value) { assign(key, std::round(value * step) / step, clipping); },
[this, key, reset] { assign(key, reset, std::nullopt); }, box);
slider->setObjectName(name + QStringLiteral("Slider"));
const size_t index = m_rows.size();
auto *field = new PickerField([this, index] {
PickerField &edited = *m_rows[index].field;
bool number = false;
const double typed = edited.locale().toDouble(edited.text(), &number);
if (edited.isModified() && number && std::isfinite(typed))
assign(m_rows[index].key, typed, std::nullopt);
edited.setModified(false);
synchronize();
}, nullptr, box);
field->setObjectName(name + QStringLiteral("Field"));
field->setAccessibleName(title);
field->setPlaceholderText(title);
field->setToolTip(help);
field->setAlignment(Qt::AlignRight);
field->setFixedWidth(56);
// Swift's double click on the title resets the slider.
label->setProperty("resetRow", qulonglong(index));
label->setProperty("resetValue", reset);
auto *row = new QHBoxLayout(box);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(10);
row->addWidget(label);
row->addWidget(slider, 1);
row->addWidget(field);
box->setAccessibleName(title);
column->addWidget(box);
m_rows.push_back(Row{std::move(key), decimals, slider, field});
auto *row = new CameraRawRow(
{.name = name, .title = title, .help = help, .low = low, .high = high, .decimals = decimals, .track = track, .titleResets = true},
[this, key] {
CameraRawSettings settings = raw();
return key(settings);
},
[this, key, step, clipping](double value) { assign(key, std::round(value * step) / step, clipping); },
[this, key](double typed) { assign(key, typed, std::nullopt); }, [this, key, reset] { assign(key, reset, std::nullopt); });
column->addWidget(row);
m_rows.push_back(row);
}
QLabel *CameraRawControls::subheadline(const QString &title, QVBoxLayout *column)
@@ -372,11 +338,6 @@ CameraRawSettings CameraRawControls::raw() const
bool CameraRawControls::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick && watched->property("resetRow").isValid()) {
const Row &row = m_rows.at(size_t(watched->property("resetRow").toULongLong()));
assign(row.key, watched->property("resetValue").toDouble(), std::nullopt);
return true;
}
// Alt let go ends the clipping and sharpen-mask views.
if (event->type() == QEvent::KeyRelease && static_cast<QKeyEvent *>(event)->key() == Qt::Key_Alt && !static_cast<QKeyEvent *>(event)->isAutoRepeat()
&& m_session.filterEdit()) {
@@ -409,13 +370,8 @@ void CameraRawControls::synchronize()
group.eye->setToolTip((shown ? QStringLiteral("Hide %1 in the preview") : QStringLiteral("Show %1 in the preview")).arg(name));
group.eye->setAccessibleName((shown ? QStringLiteral("Hide %1") : QStringLiteral("Show %1")).arg(name));
}
for (Row &row : m_rows) {
CameraRawSettings copy = settings;
const double value = row.key(copy);
row.slider->display(value);
if (!(row.field->hasFocus() && row.field->isModified()))
row.field->setText(shown(value, row.decimals, row.field->locale()));
}
for (CameraRawRow *row : m_rows)
row->synchronize();
const std::optional<std::array<int, 3>> readout = edit ? edit->rawPanel.readout : std::nullopt;
m_readout->setText(readout ? QStringLiteral("R %1 G %2 B %3").arg((*readout)[0]).arg((*readout)[1]).arg((*readout)[2])
: QStringLiteral("R — G — B —"));
+2 -8
View File
@@ -3,7 +3,7 @@
#include "UI/CameraRawSlider.h"
#include <QWidget>
class PickerField;
class CameraRawRow;
class QComboBox;
class QLabel;
class QToolButton;
@@ -30,12 +30,6 @@ private:
QToolButton *eye;
QWidget *body;
};
struct Row {
std::function<double &(CameraRawSettings &)> key;
int decimals;
CameraRawSlider *slider;
PickerField *field;
};
QVBoxLayout *group(Section section, QVBoxLayout *column);
// Swift's slider: a title, the slider and an exact field.
void slider(QVBoxLayout *column, const QString &name, const QString &title, std::function<double &(CameraRawSettings &)> key, double low, double high, int decimals,
@@ -54,7 +48,7 @@ private:
QWidget *const m_scope;
QLabel *const m_readout;
std::vector<Group> m_groups;
std::vector<Row> m_rows;
std::vector<CameraRawRow *> m_rows;
QComboBox *m_whiteBalance = nullptr;
QToolButton *m_whiteBalanceSampler = nullptr;
QLabel *m_whiteBalanceHint = nullptr;
+254
View File
@@ -0,0 +1,254 @@
#include "UI/CameraRawDetailOpticsControls.h"
#include "Rendering/EyedropperIcon.h"
#include "UI/CameraRawControls.h"
#include <QCheckBox>
#include <QGraphicsOpacityEffect>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
namespace {
QLabel *text(const QString &words, int pixels, bool secondary)
{
auto *label = new QLabel(words);
QFont font = label->font();
font.setPixelSize(pixels);
label->setFont(font);
label->setWordWrap(true);
if (secondary)
label->setForegroundRole(QPalette::PlaceholderText);
return label;
}
// Swift's `.opacity(0.45).disabled(…)` over a group of rows.
QWidget *group(QVBoxLayout *column)
{
auto *box = new QWidget;
auto *rows = new QVBoxLayout(box);
rows->setContentsMargins(0, 0, 0, 0);
rows->setSpacing(8);
box->setGraphicsEffect(new QGraphicsOpacityEffect(box));
column->addWidget(box);
return box;
}
void dim(QWidget *box, bool active)
{
box->setEnabled(active);
static_cast<QGraphicsOpacityEffect *>(box->graphicsEffect())->setOpacity(active ? 1 : 0.45);
}
void update(EditorSession &session, const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = session.filterEdit() ? session.filterEdit()->settings : FilterSettings();
change(settings.cameraRaw);
session.updateFilter(settings, session.filterEdit() ? session.filterEdit()->preview : true);
}
CameraRawSettings raw(const EditorSession &session)
{
return session.filterEdit() ? session.filterEdit()->settings.cameraRaw : CameraRawSettings();
}
}
CameraRawDetailControls::CameraRawDetailControls(EditorSession &session, QWidget *parent) : QWidget(parent), m_session(session)
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
column->addWidget(text(QStringLiteral("Sharpening"), 11, false));
row(column, QStringLiteral("sharpenAmount"), QStringLiteral("Amount"), &CameraRawDetailSettings::sharpenAmount, 150, 0, false,
QStringLiteral("Controls how strong the sharpening is."));
row(column, QStringLiteral("sharpenRadius"), QStringLiteral("Radius"), &CameraRawDetailSettings::sharpenRadius, 100, 10, false,
QStringLiteral("How far from each edge the sharpening reaches, in pixels."));
row(column, QStringLiteral("sharpenDetail"), QStringLiteral("Detail"), &CameraRawDetailSettings::sharpenDetail, 100, 25, false,
QStringLiteral("Emphasizes fine texture over broader edges."));
row(column, QStringLiteral("sharpenMasking"), QStringLiteral("Masking"), &CameraRawDetailSettings::sharpenMasking, 100, 0, true,
QStringLiteral("Limits sharpening to stronger edges. Hold Alt to see the mask."));
column->addWidget(text(QStringLiteral("Noise Reduction"), 11, false));
row(column, QStringLiteral("noiseLuminance"), QStringLiteral("Luminance"), &CameraRawDetailSettings::noiseLuminance, 100, 0, false,
QStringLiteral("Smooths grain and noise in brightness."));
m_luminanceDetail = group(column);
auto *luminance = static_cast<QVBoxLayout *>(m_luminanceDetail->layout());
row(luminance, QStringLiteral("noiseLuminanceDetail"), QStringLiteral("Luminance Detail"), &CameraRawDetailSettings::noiseLuminanceDetail, 100, 50,
false, QStringLiteral("Preserves fine texture while luminance noise is reduced."));
row(luminance, QStringLiteral("noiseLuminanceContrast"), QStringLiteral("Luminance Contrast"), &CameraRawDetailSettings::noiseLuminanceContrast, 100, 0,
false, QStringLiteral("Keeps local contrast after luminance smoothing."));
row(column, QStringLiteral("noiseColor"), QStringLiteral("Color"), &CameraRawDetailSettings::noiseColor, 100, 0, false,
QStringLiteral("Smooths colored speckles."));
m_colorDetail = group(column);
auto *color = static_cast<QVBoxLayout *>(m_colorDetail->layout());
row(color, QStringLiteral("noiseColorDetail"), QStringLiteral("Color Detail"), &CameraRawDetailSettings::noiseColorDetail, 100, 50, false,
QStringLiteral("Preserves colored edges while color noise is reduced."));
row(color, QStringLiteral("noiseColorSmoothness"), QStringLiteral("Color Smoothness"), &CameraRawDetailSettings::noiseColorSmoothness, 100, 50, false,
QStringLiteral("Makes the color smoothing softer or tighter."));
connect(&m_session, &EditorSession::changed, this, &CameraRawDetailControls::synchronize);
synchronize();
}
void CameraRawDetailControls::row(QVBoxLayout *column, const QString &name, const QString &title, double CameraRawDetailSettings::*key,
double high, double reset, bool maskingPreview, const QString &help)
{
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = 0, .high = high, .titleWidth = CameraRawControls::labelWidth},
[this, key] { return raw(m_session).detail.*key; },
[this, key, maskingPreview](double value) { assign(key, std::round(value), maskingPreview); },
[this, key](double value) { assign(key, value, false); }, [this, key, reset] { assign(key, reset, false); });
column->addWidget(row);
m_rows.push_back(row);
}
// Swift's assignDetail: the sharpen mask shows while Alt is held.
void CameraRawDetailControls::assign(double CameraRawDetailSettings::*key, double value, bool maskingPreview)
{
if (!m_session.filterEdit())
return;
CameraRawPanel panel = m_session.filterEdit()->rawPanel;
panel.sharpenMask = maskingPreview && QGuiApplication::keyboardModifiers().testFlag(Qt::AltModifier);
m_session.setCameraRawPanel(panel);
::update(m_session, [key, value](CameraRawSettings &settings) { settings.detail.*key = value; });
}
void CameraRawDetailControls::synchronize()
{
const CameraRawDetailSettings detail = raw(m_session).detail;
dim(m_luminanceDetail, detail.noiseLuminance > 0);
dim(m_colorDetail, detail.noiseColor > 0);
for (CameraRawRow *row : m_rows)
row->synchronize();
}
CameraRawOpticsControls::CameraRawOpticsControls(EditorSession &session, QWidget *parent) : QWidget(parent), m_session(session)
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
const auto toggle = [this, column](const QString &name, const QString &title, const QString &help, bool CameraRawOpticsSettings::*key) {
auto *box = new QCheckBox(title, this);
box->setObjectName(name);
box->setToolTip(help);
connect(box, &QCheckBox::clicked, this, [this, key](bool on) { update([key, on](CameraRawOpticsSettings &optics) { optics.*key = on; }); });
column->addWidget(box);
return box;
};
m_chromatic = toggle(QStringLiteral("removeChromaticAberration"), QStringLiteral("Remove Chromatic Aberration"),
QStringLiteral("Pulls red and blue fringes apart toward the center to reduce color edging."),
&CameraRawOpticsSettings::removeChromaticAberration);
m_profile = toggle(QStringLiteral("enableLensProfile"), QStringLiteral("Enable Lens Profile Corrections"),
QStringLiteral("Applies generic profile strength when camera metadata is not available."), &CameraRawOpticsSettings::enableLensProfile);
m_profileRows = new QWidget(this);
auto *profile = new QVBoxLayout(m_profileRows);
profile->setContentsMargins(0, 0, 0, 0);
profile->setSpacing(8);
profile->addWidget(text(QStringLiteral("No lens metadata on this layer. Profile sliders set generic correction strength."), 10, true));
row(profile, QStringLiteral("profileDistortion"), QStringLiteral("Distortion"), &CameraRawOpticsSettings::profileDistortion, 0, 100, 100,
QStringLiteral("How much of the profile distortion correction is applied."));
row(profile, QStringLiteral("profileVignetting"), QStringLiteral("Vignetting"), &CameraRawOpticsSettings::profileVignetting, 0, 100, 100,
QStringLiteral("How much of the profile vignetting correction is applied."));
column->addWidget(m_profileRows);
column->addWidget(text(QStringLiteral("Manual"), 11, false));
row(column, QStringLiteral("distortion"), QStringLiteral("Distortion"), &CameraRawOpticsSettings::distortion, -100, 100, 0,
QStringLiteral("Straightens barrel or pincushion bending."));
auto *defringe = new QHBoxLayout;
defringe->setSpacing(10);
auto *title = new QLabel(QStringLiteral("Defringe"), this);
title->setMinimumWidth(CameraRawControls::labelWidth);
title->setToolTip(QStringLiteral("Click a purple or green fringe to set its hue range."));
m_defringe = new QToolButton(this);
m_defringe->setObjectName(QStringLiteral("defringeSampler"));
m_defringe->setAutoRaise(true);
m_defringe->setToolTip(QStringLiteral("Click a purple or green fringe to set its hue range."));
connect(m_defringe, &QToolButton::clicked, this, [this] {
if (!m_session.filterEdit())
return;
CameraRawPanel panel = m_session.filterEdit()->rawPanel;
panel.samplesDefringe = !panel.samplesDefringe;
m_session.setCameraRawPanel(panel);
});
defringe->addWidget(title);
defringe->addWidget(m_defringe);
defringe->addStretch(1);
column->addLayout(defringe);
m_defringeHint = text(QStringLiteral("Click the fringe on the layer. Click the eyedropper again to stop."), 10, true);
m_defringeHint->setObjectName(QStringLiteral("defringeHint"));
column->addWidget(m_defringeHint);
row(column, QStringLiteral("purpleAmount"), QStringLiteral("Purple Amount"), &CameraRawOpticsSettings::purpleAmount, 0, 100, 0,
QStringLiteral("Weakens purple fringes inside the purple hue range."));
column->addWidget(hueRange(QStringLiteral("purpleHue"), QStringLiteral("Purple Hue"), &CameraRawOpticsSettings::purpleHueLow,
&CameraRawOpticsSettings::purpleHueHigh, 270, 310, QStringLiteral("Hue range where purple defringe runs.")));
row(column, QStringLiteral("greenAmount"), QStringLiteral("Green Amount"), &CameraRawOpticsSettings::greenAmount, 0, 100, 0,
QStringLiteral("Weakens green fringes inside the green hue range."));
column->addWidget(hueRange(QStringLiteral("greenHue"), QStringLiteral("Green Hue"), &CameraRawOpticsSettings::greenHueLow,
&CameraRawOpticsSettings::greenHueHigh, 60, 120, QStringLiteral("Hue range where green defringe runs.")));
row(column, QStringLiteral("lensVignetting"), QStringLiteral("Vignetting"), &CameraRawOpticsSettings::vignetteAmount, -100, 100, 0,
QStringLiteral("Brightens or darkens the corners to counter lens falloff."));
row(column, QStringLiteral("lensMidpoint"), QStringLiteral("Midpoint"), &CameraRawOpticsSettings::vignetteMidpoint, 0, 100, 50,
QStringLiteral("Moves the vignette correction inward or outward."));
connect(&m_session, &EditorSession::changed, this, &CameraRawOpticsControls::synchronize);
synchronize();
}
// Swift's opticsSlider: ranges from zero round the slider's value.
void CameraRawOpticsControls::row(QVBoxLayout *column, const QString &name, const QString &title, double CameraRawOpticsSettings::*key, double low,
double high, double reset, const QString &help)
{
const auto set = [this, key](double value) { update([key, value](CameraRawOpticsSettings &optics) { optics.*key = value; }); };
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = CameraRawControls::labelWidth},
[this, key] { return raw(m_session).optics.*key; },
[set, low](double value) { set(low < 0 ? value : std::round(value)); }, set, [set, reset] { set(reset); });
column->addWidget(row);
m_rows.push_back(row);
}
// Swift's hueRange: Low and High, inset past the titles.
QWidget *CameraRawOpticsControls::hueRange(const QString &name, const QString &title, double CameraRawOpticsSettings::*low,
double CameraRawOpticsSettings::*high, double lowReset, double highReset, const QString &help)
{
auto *box = new QWidget;
auto *column = new QVBoxLayout(box);
column->setContentsMargins(CameraRawControls::labelWidth + 10, 0, 0, 0);
column->setSpacing(4);
QLabel *heading = text(title, 10, true);
heading->setToolTip(help);
column->addWidget(heading);
auto *row = new QHBoxLayout;
row->setSpacing(8);
for (const bool start : {true, false}) {
double CameraRawOpticsSettings::*key = start ? low : high;
const QString words = start ? QStringLiteral("Start of the hue range, in degrees.") : QStringLiteral("End of the hue range, in degrees.");
QLabel *end = text(start ? QStringLiteral("Low") : QStringLiteral("High"), 10, false);
end->setToolTip(words);
const double reset = start ? lowReset : highReset;
auto *slider = new CameraRawSlider(
0, 360, {}, words, [this, key](double value) { update([key, value](CameraRawOpticsSettings &optics) { optics.*key = std::round(value); }); },
[this, key, reset] { update([key, reset](CameraRawOpticsSettings &optics) { optics.*key = reset; }); }, box);
slider->setObjectName(name + (start ? QStringLiteral("Low") : QStringLiteral("High")));
row->addWidget(end);
row->addWidget(slider, 1);
m_hueSliders.push_back({slider, key});
}
column->addLayout(row);
return box;
}
void CameraRawOpticsControls::update(const std::function<void(CameraRawOpticsSettings &)> &change)
{
::update(m_session, [&change](CameraRawSettings &settings) { change(settings.optics); });
}
void CameraRawOpticsControls::synchronize()
{
const CameraRawOpticsSettings optics = raw(m_session).optics;
const QSignalBlocker chromatic(m_chromatic), profile(m_profile);
m_chromatic->setChecked(optics.removeChromaticAberration);
m_profile->setChecked(optics.enableLensProfile);
m_profileRows->setVisible(optics.enableLensProfile);
const bool sampling = m_session.filterEdit() && m_session.filterEdit()->rawPanel.samplesDefringe;
m_defringe->setIcon(EyedropperIcon::icon(palette().color(sampling ? QPalette::Highlight : QPalette::PlaceholderText), devicePixelRatioF()));
m_defringeHint->setVisible(sampling);
for (CameraRawRow *row : m_rows)
row->synchronize();
for (const auto &[slider, key] : m_hueSliders)
slider->display(optics.*key);
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/CameraRawRow.h"
#include <QWidget>
class QAbstractButton;
class QLabel;
class QToolButton;
class QVBoxLayout;
// Swift's CameraRawDetailControls: sharpening and noise reduction.
class CameraRawDetailControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawDetailControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
// Swift's sharpenSlider; Masking shows its mask while Alt is held.
void row(QVBoxLayout *column, const QString &name, const QString &title, double CameraRawDetailSettings::*key, double high, double reset,
bool maskingPreview, const QString &help);
void assign(double CameraRawDetailSettings::*key, double value, bool maskingPreview);
EditorSession &m_session;
std::vector<CameraRawRow *> m_rows;
QWidget *m_luminanceDetail = nullptr;
QWidget *m_colorDetail = nullptr;
};
// Swift's CameraRawOpticsControls: profile, distortion, defringe, vignetting.
class CameraRawOpticsControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawOpticsControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
void row(QVBoxLayout *column, const QString &name, const QString &title, double CameraRawOpticsSettings::*key, double low, double high, double reset,
const QString &help);
QWidget *hueRange(const QString &name, const QString &title, double CameraRawOpticsSettings::*low, double CameraRawOpticsSettings::*high,
double lowReset, double highReset, const QString &help);
void update(const std::function<void(CameraRawOpticsSettings &)> &change);
EditorSession &m_session;
std::vector<CameraRawRow *> m_rows;
std::vector<std::pair<CameraRawSlider *, double CameraRawOpticsSettings::*>> m_hueSliders;
QAbstractButton *m_chromatic = nullptr;
QAbstractButton *m_profile = nullptr;
QWidget *m_profileRows = nullptr;
QToolButton *m_defringe = nullptr;
QLabel *m_defringeHint = nullptr;
};
@@ -0,0 +1,215 @@
#include "UI/CameraRawGeometryCalibrationControls.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawControls.h"
#include "UI/LayerIcons.h"
#include <QButtonGroup>
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
namespace {
QLabel *text(const QString &words, int pixels, bool secondary)
{
auto *label = new QLabel(words);
QFont font = label->font();
font.setPixelSize(pixels);
label->setFont(font);
label->setWordWrap(true);
if (secondary)
label->setForegroundRole(QPalette::PlaceholderText);
return label;
}
// Swift's labelled Picker: its title, then the menu.
QComboBox *picker(const QString &title, const QStringList &choices, const QString &help, QWidget *parent, QVBoxLayout *column)
{
auto *menu = new QComboBox(parent);
menu->addItems(choices);
menu->setToolTip(help);
auto *label = new QLabel(title, parent);
label->setBuddy(menu);
auto *row = new QHBoxLayout;
row->addWidget(label);
row->addWidget(menu);
row->addStretch(1);
column->addLayout(row);
return menu;
}
CameraRawSettings raw(const EditorSession &session)
{
return session.filterEdit() ? session.filterEdit()->settings.cameraRaw : CameraRawSettings();
}
void write(EditorSession &session, const std::function<void(CameraRawSettings &)> &change)
{
FilterSettings settings = session.filterEdit() ? session.filterEdit()->settings : FilterSettings();
change(settings.cameraRaw);
session.updateFilter(settings, session.filterEdit() ? session.filterEdit()->preview : true);
}
// Swift's Geometry and Calibration rows: the slider rounds.
CameraRawRow *rounded(const QString &name, const QString &title, const QString &help, double low, double high, std::function<double()> value,
std::function<void(double)> set, QWidget *parent)
{
return new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = CameraRawControls::labelWidth},
std::move(value), [set](double number) { set(std::round(number)); }, set, [set] { set(0); }, parent);
}
}
CameraRawGeometryControls::CameraRawGeometryControls(EditorSession &session, QWidget *parent)
: QWidget(parent), m_session(session), m_guided(new QWidget(this)), m_draw(new QPushButton(QStringLiteral("Draw Guides"), m_guided)),
m_drawHint(text(QStringLiteral("Drag on the layer to place a guide. Draw at least two lines."), 10, true)),
m_clear(new QPushButton(QStringLiteral("Clear Guides"), m_guided)),
m_constrain(new QCheckBox(QStringLiteral("Constrain Crop"), this))
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
column->addWidget(text(QStringLiteral("Upright"), 11, false));
m_upright = cameraRawSegments({QStringLiteral("Off"), QStringLiteral("Guided")}, QStringLiteral("upright"),
QStringLiteral("Off leaves the picture as it is. Guided straightens from lines you draw on the picture."), column, [this](int mode) {
update([mode](CameraRawGeometrySettings &geometry) { geometry.upright = CameraRawUprightMode(mode); });
if (CameraRawUprightMode(mode) != CameraRawUprightMode::guided && m_session.filterEdit()) {
CameraRawPanel panel = m_session.filterEdit()->rawPanel;
panel.drawingGeometryGuide = false;
m_session.setCameraRawPanel(panel);
}
});
auto *guided = new QVBoxLayout(m_guided);
guided->setContentsMargins(0, 0, 0, 0);
guided->setSpacing(8);
m_draw->setObjectName(QStringLiteral("drawGuides"));
m_draw->setCheckable(true);
m_draw->setAutoDefault(false);
m_draw->setToolTip(QStringLiteral("Draw two or more lines on the preview that should be level or vertical."));
connect(m_draw, &QPushButton::clicked, this, [this] {
if (!m_session.filterEdit())
return;
CameraRawPanel panel = m_session.filterEdit()->rawPanel;
panel.drawingGeometryGuide = !panel.drawingGeometryGuide;
m_session.setCameraRawPanel(panel);
});
guided->addWidget(m_draw, 0, Qt::AlignLeft);
m_drawHint->setObjectName(QStringLiteral("drawGuidesHint"));
guided->addWidget(m_drawHint);
m_clear->setObjectName(QStringLiteral("clearGuides"));
m_clear->setAutoDefault(false);
m_clear->setToolTip(QStringLiteral("Remove every guide line."));
connect(m_clear, &QPushButton::clicked, this, [this] { update([](CameraRawGeometrySettings &geometry) { geometry.guides.clear(); }); });
guided->addWidget(m_clear, 0, Qt::AlignLeft);
column->addWidget(m_guided);
m_projection = picker(QStringLiteral("Projection"), {QStringLiteral("Perspective"), QStringLiteral("Rectilinear")},
QStringLiteral("Perspective allows stronger keystone. Rectilinear keeps the warp gentler."), this, column);
m_projection->setObjectName(QStringLiteral("projection"));
connect(m_projection, &QComboBox::activated, this,
[this](int index) { update([index](CameraRawGeometrySettings &geometry) { geometry.projection = CameraRawProjection(index); }); });
const auto slider = [this, column](const QString &name, const QString &title, double CameraRawGeometrySettings::*key, double low, double high,
const QString &help) {
auto *row = rounded(name, title, help, low, high, [this, key] { return raw(m_session).geometry.*key; },
[this, key](double value) { update([key, value](CameraRawGeometrySettings &geometry) { geometry.*key = value; }); }, this);
column->addWidget(row);
m_rows.push_back(row);
};
slider(QStringLiteral("vertical"), QStringLiteral("Vertical"), &CameraRawGeometrySettings::vertical, -100, 100,
QStringLiteral("Straightens vertical lines toward the center."));
slider(QStringLiteral("horizontal"), QStringLiteral("Horizontal"), &CameraRawGeometrySettings::horizontal, -100, 100,
QStringLiteral("Straightens horizontal lines toward the center."));
slider(QStringLiteral("rotate"), QStringLiteral("Rotate"), &CameraRawGeometrySettings::rotate, -45, 45, QStringLiteral("Rotates the picture around its center."));
slider(QStringLiteral("aspect"), QStringLiteral("Aspect"), &CameraRawGeometrySettings::aspect, -100, 100, QStringLiteral("Stretches width relative to height."));
slider(QStringLiteral("scale"), QStringLiteral("Scale"), &CameraRawGeometrySettings::scale, -100, 100,
QStringLiteral("Zooms the transformed picture within the frame."));
slider(QStringLiteral("offsetX"), QStringLiteral("Offset X"), &CameraRawGeometrySettings::offsetX, -100, 100, QStringLiteral("Moves the picture left or right."));
slider(QStringLiteral("offsetY"), QStringLiteral("Offset Y"), &CameraRawGeometrySettings::offsetY, -100, 100, QStringLiteral("Moves the picture up or down."));
m_constrain->setObjectName(QStringLiteral("constrainCrop"));
m_constrain->setToolTip(QStringLiteral("Crops empty edges after the transform and fits the result back into the frame."));
connect(m_constrain, &QAbstractButton::clicked, this, [this](bool on) { update([on](CameraRawGeometrySettings &geometry) { geometry.constrainCrop = on; }); });
column->addWidget(m_constrain);
connect(&m_session, &EditorSession::changed, this, &CameraRawGeometryControls::synchronize);
synchronize();
}
void CameraRawGeometryControls::update(const std::function<void(CameraRawGeometrySettings &)> &change)
{
write(m_session, [&change](CameraRawSettings &settings) { change(settings.geometry); });
}
void CameraRawGeometryControls::synchronize()
{
const CameraRawGeometrySettings geometry = raw(m_session).geometry;
const bool drawing = m_session.filterEdit() && m_session.filterEdit()->rawPanel.drawingGeometryGuide;
m_upright->button(int(geometry.upright))->setChecked(true);
m_guided->setVisible(geometry.upright == CameraRawUprightMode::guided);
m_draw->setChecked(drawing);
m_draw->setIcon(QIcon(LayerIcons::pixmap(LayerIcon::lineDiagonal, 14, palette().color(QPalette::ButtonText), devicePixelRatioF())));
m_drawHint->setVisible(drawing);
m_clear->setVisible(!geometry.guides.empty());
const QSignalBlocker projection(m_projection), constrain(m_constrain);
m_projection->setCurrentIndex(int(geometry.projection));
m_constrain->setChecked(geometry.constrainCrop);
for (CameraRawRow *row : m_rows)
row->synchronize();
}
CameraRawCalibrationControls::CameraRawCalibrationControls(EditorSession &session, QWidget *parent)
: QWidget(parent), m_session(session), m_process(new QComboBox(this)), m_summary(text(QString(), 10, true))
{
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(8);
for (int version = 0; version < 6; ++version)
m_process->addItem(rawValue(CameraRawProcessVersion(version)));
m_process->setObjectName(QStringLiteral("processVersion"));
m_process->setToolTip(QStringLiteral("Chooses how strongly the calibration sliders below are applied. Version 6 is the current default."));
auto *label = new QLabel(QStringLiteral("Process"), this);
label->setBuddy(m_process);
auto *processRow = new QHBoxLayout;
processRow->addWidget(label);
processRow->addWidget(m_process);
processRow->addStretch(1);
column->addLayout(processRow);
connect(m_process, &QComboBox::activated, this, [this](int version) {
write(m_session, [version](CameraRawSettings &settings) { settings.calibration.process = CameraRawProcessVersion(version); });
});
m_summary->setObjectName(QStringLiteral("processSummary"));
column->addWidget(m_summary);
const auto slider = [this, column](const QString &name, const QString &title, double CameraRawCalibrationSettings::*key, const QString &help) {
auto *row = rounded(name, title, help, -100, 100, [this, key] { return raw(m_session).calibration.*key; },
[this, key](double value) { write(m_session, [key, value](CameraRawSettings &settings) { settings.calibration.*key = value; }); },
this);
column->addWidget(row);
m_rows.push_back(row);
};
column->addWidget(text(QStringLiteral("Shadows"), 11, false));
slider(QStringLiteral("shadowTint"), QStringLiteral("Tint"), &CameraRawCalibrationSettings::shadowTint, QStringLiteral("Adds green or magenta to the darkest tones."));
column->addWidget(text(QStringLiteral("Red Primary"), 11, false));
slider(QStringLiteral("redHue"), QStringLiteral("Hue"), &CameraRawCalibrationSettings::redHue, QStringLiteral("Shifts how red is interpreted."));
slider(QStringLiteral("redSaturation"), QStringLiteral("Saturation"), &CameraRawCalibrationSettings::redSaturation,
QStringLiteral("Strengthens or weakens the red primary."));
column->addWidget(text(QStringLiteral("Green Primary"), 11, false));
slider(QStringLiteral("greenHue"), QStringLiteral("Hue"), &CameraRawCalibrationSettings::greenHue, QStringLiteral("Shifts how green is interpreted."));
slider(QStringLiteral("greenSaturation"), QStringLiteral("Saturation"), &CameraRawCalibrationSettings::greenSaturation,
QStringLiteral("Strengthens or weakens the green primary."));
column->addWidget(text(QStringLiteral("Blue Primary"), 11, false));
slider(QStringLiteral("blueHue"), QStringLiteral("Hue"), &CameraRawCalibrationSettings::blueHue, QStringLiteral("Shifts how blue is interpreted."));
slider(QStringLiteral("blueSaturation"), QStringLiteral("Saturation"), &CameraRawCalibrationSettings::blueSaturation,
QStringLiteral("Strengthens or weakens the blue primary."));
connect(&m_session, &EditorSession::changed, this, &CameraRawCalibrationControls::synchronize);
synchronize();
}
void CameraRawCalibrationControls::synchronize()
{
const CameraRawCalibrationSettings calibration = raw(m_session).calibration;
{
const QSignalBlocker quiet(m_process);
m_process->setCurrentIndex(int(calibration.process));
}
m_summary->setText(summary(calibration.process));
m_summary->setToolTip(summary(calibration.process));
for (CameraRawRow *row : m_rows)
row->synchronize();
}
@@ -0,0 +1,45 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/CameraRawRow.h"
#include <QWidget>
class QAbstractButton;
class QButtonGroup;
class QComboBox;
class QLabel;
class QPushButton;
// Swift's CameraRawGeometryControls: Upright, guides, the transform.
class CameraRawGeometryControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawGeometryControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
void update(const std::function<void(CameraRawGeometrySettings &)> &change);
EditorSession &m_session;
QButtonGroup *m_upright = nullptr;
QWidget *const m_guided;
QPushButton *const m_draw;
QLabel *const m_drawHint;
QPushButton *const m_clear;
QComboBox *m_projection = nullptr;
QAbstractButton *const m_constrain;
std::vector<CameraRawRow *> m_rows;
};
// Swift's CameraRawCalibrationControls: the process and the primaries.
class CameraRawCalibrationControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawCalibrationControls(EditorSession &session, QWidget *parent = nullptr);
void synchronize();
private:
EditorSession &m_session;
QComboBox *const m_process;
QLabel *const m_summary;
std::vector<CameraRawRow *> m_rows;
};
+79
View File
@@ -0,0 +1,79 @@
#include "UI/CameraRawRow.h"
#include "UI/ColorPickerSheet.h"
#include <QEvent>
#include <QHBoxLayout>
#include <QLocale>
#include <QLabel>
#include <cmath>
namespace {
// Swift's `.number`: at most `decimals` places, none trailing.
QString shown(double value, int decimals, QLocale locale)
{
locale.setNumberOptions(QLocale::OmitGroupSeparator);
QString text = locale.toString(value, 'f', decimals);
if (decimals == 0)
return text;
while (text.endsWith(locale.zeroDigit()))
text.chop(locale.zeroDigit().size());
if (text.endsWith(locale.decimalPoint()))
text.chop(locale.decimalPoint().size());
return text;
}
}
CameraRawRow::CameraRawRow(Spec spec, std::function<double()> value, std::function<void(double)> slide, std::function<void(double)> type,
std::function<void()> reset, QWidget *parent)
: QWidget(parent), m_spec(std::move(spec)), m_value(std::move(value)), m_reset(std::move(reset)), m_title(new QLabel(m_spec.title, this)),
m_slider(new CameraRawSlider(m_spec.low, m_spec.high, m_spec.track, m_spec.help, std::move(slide), m_reset, this))
{
if (m_spec.fixedTitle)
m_title->setFixedWidth(m_spec.titleWidth);
else
m_title->setMinimumWidth(m_spec.titleWidth);
m_title->setToolTip(m_spec.help);
m_slider->setObjectName(m_spec.name + QStringLiteral("Slider"));
auto *row = new QHBoxLayout(this);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(10);
row->addWidget(m_title);
row->addWidget(m_slider, 1);
if (m_spec.fieldWidth > 0) {
m_field = new PickerField([this, type = std::move(type)] {
bool number = false;
const double typed = m_field->locale().toDouble(m_field->text(), &number);
if (m_field->isModified() && number && std::isfinite(typed))
type(typed);
m_field->setModified(false);
synchronize();
}, nullptr, this);
m_field->setObjectName(m_spec.name + QStringLiteral("Field"));
m_field->setAccessibleName(m_spec.title);
m_field->setPlaceholderText(m_spec.title);
m_field->setToolTip(m_spec.help);
m_field->setAlignment(Qt::AlignRight);
m_field->setFixedWidth(m_spec.fieldWidth);
row->addWidget(m_field);
}
if (m_spec.titleResets)
m_title->installEventFilter(this);
setAccessibleName(m_spec.title);
}
void CameraRawRow::synchronize()
{
const double value = m_value();
m_slider->display(value);
if (m_field && !(m_field->hasFocus() && m_field->isModified()))
m_field->setText(shown(value, m_spec.decimals, m_field->locale()));
}
// Swift's double click on the title resets the slider.
bool CameraRawRow::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_title && event->type() == QEvent::MouseButtonDblClick) {
m_reset();
return true;
}
return QWidget::eventFilter(watched, event);
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "UI/CameraRawSlider.h"
#include <QWidget>
#include <functional>
class PickerField;
class QLabel;
// Swift's Camera Raw rows: title, slider, exact field.
class CameraRawRow : public QWidget {
Q_OBJECT
public:
struct Spec {
QString name;
QString title;
QString help;
double low = -100;
double high = 100;
int decimals = 0;
CameraRawSliderTrack track = {};
// The title's width: at least this, or exactly when fixed.
int titleWidth = 96;
bool fixedTitle = false;
// No field at zero.
int fieldWidth = 56;
// A double click on the title resets it.
bool titleResets = false;
};
// `slide` takes the slider's own value, `type` a typed number.
CameraRawRow(Spec spec, std::function<double()> value, std::function<void(double)> slide, std::function<void(double)> type,
std::function<void()> reset, QWidget *parent = nullptr);
// Shows the value; a focused field keeps its typing.
void synchronize();
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
const Spec m_spec;
const std::function<double()> m_value;
const std::function<void()> m_reset;
QLabel *const m_title;
CameraRawSlider *const m_slider;
PickerField *m_field = nullptr;
};
+2 -3
View File
@@ -215,8 +215,7 @@ CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, QWi
QMenu *filter = bar.addMenu(QStringLiteral("Fil&ter"));
for (const FilterKind kind : allFilterKinds) {
// Camera Raw's entry arrives with its panel (12.8b).
if (kind != FilterKind::contentAwareFill && kind != FilterKind::cameraRaw && !isImageAdjustment(kind))
if (kind != FilterKind::contentAwareFill && !isImageAdjustment(kind))
add(filter, filterName(kind), rawValue(kind) + QStringLiteral("…"), QKeySequence(), [this, kind] { session().beginFilter(kind); });
}
@@ -358,7 +357,7 @@ void CompositorMenus::synchronize()
action(QString::fromLatin1(name))->setEnabled(s.canModifySelection());
action(QStringLiteral("levels"))->setEnabled(s.canAdjustColors() && !s.hueSaturation());
for (const FilterKind kind : allFilterKinds) {
if (kind != FilterKind::contentAwareFill && kind != FilterKind::cameraRaw)
if (kind != FilterKind::contentAwareFill)
action(filterName(kind))->setEnabled(s.canAdjustColors() && !s.hueSaturation());
}
action(QStringLiteral("hueSaturation"))->setEnabled(s.canAdjustColors());
+12
View File
@@ -208,6 +208,16 @@ void sparkles(QPainter &painter)
star(QPointF(14, 4.5), 3);
star(QPointF(14.5, 14), 2);
}
// Swift's scope: a ring with four ticks toward its middle.
void scope(QPainter &painter)
{
painter.drawEllipse(QPointF(9, 9), 6, 6);
painter.drawLine(QPointF(9, 1.5), QPointF(9, 6));
painter.drawLine(QPointF(9, 12), QPointF(9, 16.5));
painter.drawLine(QPointF(1.5, 9), QPointF(6, 9));
painter.drawLine(QPointF(12, 9), QPointF(16.5, 9));
}
}
void LayerIcons::paint(QPainter &painter, LayerIcon icon, QPointF origin, double side, const QColor &colour)
@@ -243,6 +253,8 @@ void LayerIcons::paint(QPainter &painter, LayerIcon icon, QPointF origin, double
case LayerIcon::rightHalfCircle: rightHalfCircle(painter); break;
case LayerIcon::hatchedCircle: hatchedCircle(painter); break;
case LayerIcon::axes: axes(painter); break;
case LayerIcon::scope: scope(painter); break;
case LayerIcon::lineDiagonal: painter.drawLine(QPointF(3, 15), QPointF(15, 3)); break;
}
painter.restore();
}
+2 -1
View File
@@ -7,7 +7,8 @@
// The layers panel's icons, drawn: SF Symbols have no twin.
enum class LayerIcon {
eye, eyeSlash, chevronRight, chevronDown, folder, link, newLayer, newFolder, addMask, trash, layers, text,
halfFilledCircle, sliders, curvePath, plusMinusCircle, paintPalette, circleGrid, sparkles, rightHalfCircle, hatchedCircle, axes
halfFilledCircle, sliders, curvePath, plusMinusCircle, paintPalette, circleGrid, sparkles, rightHalfCircle, hatchedCircle, axes,
scope, lineDiagonal
};
namespace LayerIcons {