#include "UI/CameraRawDetailOpticsControls.h" #include "Rendering/EyedropperIcon.h" #include "UI/CameraRawControls.h" #include #include #include #include #include #include #include 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(box->graphicsEffect())->setOpacity(active ? 1 : 0.45); } void update(EditorSession &session, const std::function &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(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(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 &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); }