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,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();
|
||||
}
|
||||
@@ -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())));
|
||||
}
|
||||
@@ -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())));
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 —"));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user