Filter › Dither, as Compositor 1.3.3 (12.15)

This commit is contained in:
雷电芽衣 committed 2026-09-30 07:55:57 -04:00
1 parent aa5a44e517
commit bc9d43f1e8
25 files changed
+1973 -17

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
#include "UI/FilterSheet.h"
#include "UI/ColorPaletteControls.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFontDatabase>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSlider>
#include <QVBoxLayout>
// Swift's ditherControls, beside the filter sheet's other kinds.
QComboBox *FilterSheet::menu(const QString &title, const QString &name, const QString &help)
{
auto *box = new QWidget(this);
auto *row = new QHBoxLayout(box);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(10);
auto *label = new QLabel(title, box);
auto *choices = new QComboBox(box);
choices->setObjectName(name);
choices->setSizeAdjustPolicy(QComboBox::AdjustToContents);
label->setBuddy(choices);
box->setToolTip(help);
row->addWidget(label);
row->addWidget(choices);
row->addStretch(1);
m_column->addWidget(box);
return choices;
}
void FilterSheet::dither()
{
const auto key = [](double DitherSettings::*field) { return [field](FilterSettings &settings) -> double & { return settings.dither.*field; }; };
const auto box = [this] { return m_controls.back().slider->parentWidget(); };
// Style: grouped as Swift's menu, a rule between groups.
QComboBox *style = menu(QStringLiteral("Style"), QStringLiteral("ditherStyle"), QString());
for (const DitherStyle each : allDitherStyles) {
if (style->count() > 0 && ditherGroup(each) != ditherGroup(DitherStyle(style->itemData(style->count() - 1).toInt())))
style->insertSeparator(style->count());
style->addItem(rawValue(each), int(each));
}
connect(style, &QComboBox::activated, this,
[this, style](int index) { update([&](FilterSettings &settings) { settings.dither.style = DitherStyle(style->itemData(index).toInt()); }); });
m_follows.emplace_back([style](const FilterSettings &settings) {
const QSignalBlocker quiet(style);
style->setCurrentIndex(style->findData(int(settings.dither.style)));
});
control(QStringLiteral("Pixel Size"), key(&DitherSettings::pixelSize), DitherSettings::pixelSizeLow, DitherSettings::pixelSizeHigh, QStringLiteral("px"), 0, false);
box()->setToolTip(QStringLiteral("Make each dithered pixel this many pixels across, for a chunky old-screen look"));
control(QStringLiteral("Cell Size"), key(&DitherSettings::cellSize), DitherSettings::cellSizeLow, DitherSettings::cellSizeHigh, QStringLiteral("px"), 0, false);
m_shownWhen.emplace_back(box(), [](const FilterSettings &settings) { return isHalftone(settings.dither.style) || settings.dither.style == DitherStyle::ascii; });
control(QStringLiteral("Angle"), key(&DitherSettings::angle), -90, 90, QStringLiteral("°"), 0, false);
m_shownWhen.emplace_back(box(), [](const FilterSettings &settings) { return isHalftone(settings.dither.style); });
// ASCII's characters, typed as they will draw.
auto *characters = new QWidget(this);
auto *row = new QHBoxLayout(characters);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(10);
auto *label = new QLabel(QStringLiteral("Characters"), characters);
auto *field = new QLineEdit(characters);
field->setObjectName(QStringLiteral("ditherCharacters"));
field->setPlaceholderText(QStringLiteral("Characters"));
field->setAccessibleName(QStringLiteral("Characters"));
field->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
label->setBuddy(field);
characters->setToolTip(QStringLiteral("The characters to draw with, in any order: each spot gets the one whose ink best matches its tone"));
row->addWidget(label);
row->addWidget(field, 1);
m_column->addWidget(characters);
connect(field, &QLineEdit::textEdited, this, [this](const QString &text) { update([&text](FilterSettings &settings) { settings.dither.characters = text; }); });
// Left, it shows what the settings kept of the typing.
connect(field, &QLineEdit::editingFinished, this, [this, field] { field->setText(settings().dither.characters); });
m_follows.emplace_back([field](const FilterSettings &settings) {
// Typing keeps its own text; the settings hold it normalized.
if (!field->hasFocus() && field->text() != settings.dither.characters)
field->setText(settings.dither.characters);
});
m_shownWhen.emplace_back(characters, [](const FilterSettings &settings) { return settings.dither.style == DitherStyle::ascii; });
control(QStringLiteral("Tones"), key(&DitherSettings::levels), DitherSettings::levelsLow, DitherSettings::levelsHigh, QString(), 0, false);
box()->setToolTip(QStringLiteral("Tones per channel: 2 is pure black and white"));
m_shownWhen.emplace_back(box(), [](const FilterSettings &settings) { return hasTones(settings.dither.style); });
control(QStringLiteral("Diffusion"), key(&DitherSettings::diffusion), 0, 100, QStringLiteral("%"), 0, false);
box()->setToolTip(QStringLiteral("How much of each pixel's error spreads to its neighbors. Less gives flatter areas"));
m_shownWhen.emplace_back(box(), [](const FilterSettings &settings) { return diffuses(settings.dither.style); });
control(QStringLiteral("Density"), key(&DitherSettings::density), -100, 100, QString(), 0, false);
box()->setToolTip(QStringLiteral("More ink (darker) or less before dithering"));
control(QStringLiteral("Contrast"), key(&DitherSettings::contrast), -100, 100, QString(), 0, false);
// Colors: a menu, as Swift's: the panel keeps its size.
QComboBox *colors = menu(QStringLiteral("Colors"), QStringLiteral("ditherColors"), QString());
for (const DitherColors each : allDitherColors)
colors->addItem(rawValue(each), int(each));
connect(colors, &QComboBox::activated, this, [this](int index) { update([index](FilterSettings &settings) { settings.dither.colors = DitherColors(index); }); });
m_follows.emplace_back([colors](const FilterSettings &settings) {
const QSignalBlocker quiet(colors);
colors->setCurrentIndex(int(settings.dither.colors));
});
// Two Colors: the dark and the light swatch.
auto *pair = new QWidget(this);
auto *swatches = new QHBoxLayout(pair);
swatches->setContentsMargins(0, 0, 0, 0);
swatches->setSpacing(8);
for (const bool light : {false, true}) {
auto *title = new QLabel(light ? QStringLiteral("Light") : QStringLiteral("Dark"), pair);
auto *swatch = new SwatchButton([this, light] {
const AdjustmentColor colour = light ? settings().dither.light : settings().dither.dark;
return PaletteColor{colour.red, colour.green, colour.blue};
}, 6, 1.5, 1, pair);
swatch->setObjectName(light ? QStringLiteral("ditherLight") : QStringLiteral("ditherDark"));
swatch->setFixedSize(24, 24);
swatch->setToolTip(light ? QStringLiteral("Choose the light color") : QStringLiteral("Choose the dark color"));
swatch->setAccessibleName(light ? QStringLiteral("Light color") : QStringLiteral("Dark color"));
connect(swatch, &QAbstractButton::clicked, this, [this, light] { m_session.openDitherColorPicker(light); });
m_follows.emplace_back([swatch](const FilterSettings &) { swatch->update(); });
if (light)
swatches->addSpacing(10);
swatches->addWidget(title);
swatches->addWidget(swatch);
}
swatches->addStretch(1);
m_column->addWidget(pair);
m_shownWhen.emplace_back(pair, [](const FilterSettings &settings) { return settings.dither.colors == DitherColors::twoColors; });
QComboBox *shape = menu(QStringLiteral("Pixel Shape"), QStringLiteral("ditherPixelShape"),
QStringLiteral("Draw each chunky pixel as a solid square, or as a round dot like a dot-matrix screen"));
for (const DitherPixelShape each : allDitherPixelShapes)
shape->addItem(rawValue(each), int(each));
connect(shape, &QComboBox::activated, this, [this](int index) { update([index](FilterSettings &settings) { settings.dither.pixelShape = DitherPixelShape(index); }); });
m_follows.emplace_back([shape](const FilterSettings &settings) {
const QSignalBlocker quiet(shape);
shape->setCurrentIndex(int(settings.dither.pixelShape));
});
m_shownWhen.emplace_back(shape->parentWidget(), [](const FilterSettings &settings) { return settings.dither.pixelSize > 1; });
flag(QStringLiteral("Light on Dark"), QStringLiteral("Draw the marks for the light tones on the dark color, like a glowing screen"),
[](FilterSettings &settings) -> bool & { return settings.dither.lightOnDark; });
m_shownWhen.emplace_back(m_flags.back().first, [](const FilterSettings &settings) { return drawsMarks(settings.dither.style); });
}
+6 -2
View File
@@ -185,6 +185,7 @@ FilterSheet::FilterSheet(EditorSession &session, QWidget *parent)
control(QStringLiteral("Amount"), key(&FilterSettings::amount), 0.1, 400, QStringLiteral("%"), 1, true);
noise();
break;
case FilterKind::dither: dither(); break;
case FilterKind::vignette:
case FilterKind::bloomGlow:
case FilterKind::tonalContrast: finishing(m_session.filterEdit()->kind); break;
@@ -317,12 +318,15 @@ void FilterSheet::synchronize()
m_pickerColour = picked;
m_session.previewGradientMapColor();
m_session.previewVignetteColor();
m_session.previewDitherColor();
}
if (m_vignetteSwatch)
m_vignetteSwatch->update();
const FilterEdit &edit = m_session.filterEdit().value();
for (Control &control : m_controls)
showControl(control, edit.settings);
for (const auto &follow : m_follows)
follow(edit.settings);
if (m_curves)
m_curves->synchronize();
if (m_gradientMap)
@@ -345,8 +349,8 @@ void FilterSheet::synchronize()
m_error->setVisible(edit.previewError.has_value());
// An adjustment layer's editor ignores the selection.
m_limited->setVisible(!m_session.adjustmentOriginal() && m_session.selection());
// While the result is made, OK waits and says so.
const bool working = edit.committing || edit.preparing;
// Slow filters say they work; a quick preview would flicker.
const bool working = edit.committing || (edit.preparing && isAutomatic(edit.kind));
m_spinner->setVisible(working);
m_activity->setVisible(working);
m_activity->setText(edit.committing ? QStringLiteral("Applying…") : QStringLiteral("Working…"));
+7
View File
@@ -7,6 +7,7 @@ class CurvesControls;
class PickerField;
class QButtonGroup;
class QCheckBox;
class QComboBox;
class QLabel;
class QProgressBar;
class QPushButton;
@@ -75,6 +76,10 @@ private:
void noise();
// Vignette's colour and rows, Bloom / Glow's, Tonal Contrast's.
void finishing(FilterKind kind);
// Swift's ditherControls: style, sizes, tones, colours, shape.
void dither();
// A titled menu of choices: the row and its box.
QComboBox *menu(const QString &title, const QString &name, const QString &help);
// Remove Background's words, Quality, and Advanced's three rows.
void background();
// Black & White's six families and Tint; Color Balance's nine.
@@ -101,6 +106,8 @@ private:
std::vector<std::pair<QCheckBox *, std::function<bool &(FilterSettings &)>>> m_flags;
// Rows under Swift's `if`: Advanced's, Tint's.
std::vector<std::pair<QWidget *, std::function<bool(const FilterSettings &)>>> m_shownWhen;
// Controls that show the settings as they stand: Dither's menus.
std::vector<std::function<void(const FilterSettings &)>> m_follows;
QCheckBox *const m_preview;
QLabel *const m_error;
QLabel *const m_limited;