Blur and noise adjustment layers, Image Trim, Inner Glow (12.8c)

This commit is contained in:
雷电芽衣 committed 2026-09-28 08:35:59 -04:00
1 parent 7ae263de80
commit 31e2dd3864
60 files changed
+1673 -105

No files matched your search

+2
View File
@@ -209,6 +209,7 @@ CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, QWi
image->addSeparator();
add(image, QStringLiteral("canvasSize"), QStringLiteral("Canvas Size…"), QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C), [this] { projects().canvasSize(); });
add(image, QStringLiteral("imageSize"), QStringLiteral("Image Size…"), QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_I), [this] { projects().imageSize(); });
add(image, QStringLiteral("trim"), QStringLiteral("Trim…"), QKeySequence(), [this] { projects().trim(); });
image->addSeparator();
add(image, QStringLiteral("flipCanvasHorizontal"), QStringLiteral("Flip Canvas Horizontal"), QKeySequence(), [this] { session().flipCanvas(true); });
add(image, QStringLiteral("flipCanvasVertical"), QStringLiteral("Flip Canvas Vertical"), QKeySequence(), [this] { session().flipCanvas(false); });
@@ -365,6 +366,7 @@ void CompositorMenus::synchronize()
action(QStringLiteral("invert"))->setEnabled(s.canInvert());
action(QStringLiteral("canvasSize"))->setEnabled(drawn && p.canStart());
action(QStringLiteral("imageSize"))->setEnabled(drawn && p.canStart());
action(QStringLiteral("trim"))->setEnabled(drawn && p.canStart());
action(QStringLiteral("flipCanvasHorizontal"))->setEnabled(s.canEditLayers());
action(QStringLiteral("flipCanvasVertical"))->setEnabled(s.canEditLayers());
action(QStringLiteral("newAdjustmentLayer"))->setEnabled(s.canEditLayers());
+7
View File
@@ -92,6 +92,13 @@ EffectsSheet::EffectsSheet(EditorSession &session, LayerEffectKind kind, QWidget
slider(QStringLiteral("Opacity"), [](const LayerEffects &e) { return percent(e.outerGlow); },
[](LayerEffects &e, double value) { e.outerGlow->opacity = value / 100; }, 0, 100, QStringLiteral("%"));
break;
case LayerEffectKind::innerGlow:
header(QStringLiteral("Inner Glow"), false);
slider(QStringLiteral("Size"), [](const LayerEffects &e) { return e.innerGlow ? std::optional(e.innerGlow->size) : std::nullopt; },
[](LayerEffects &e, double value) { e.innerGlow->size = value; }, 0, 100, QStringLiteral("px"), 500);
slider(QStringLiteral("Opacity"), [](const LayerEffects &e) { return percent(e.innerGlow); },
[](LayerEffects &e, double value) { e.innerGlow->opacity = value / 100; }, 0, 100, QStringLiteral("%"));
break;
}
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
cancel->setObjectName(QStringLiteral("effectsCancel"));
+49
View File
@@ -1,6 +1,8 @@
#include "UI/LayerIcons.h"
#include "UI/ToolIcons.h"
#include <QPainterPath>
#include <cmath>
#include <numbers>
namespace {
void eye(QPainter &painter, bool slashed)
@@ -209,6 +211,47 @@ void sparkles(QPainter &painter)
star(QPointF(14.5, 14), 2);
}
// Swift's drop.fill: a filled teardrop, point up.
void drop(QPainter &painter)
{
QPainterPath path;
path.moveTo(9, 1.5);
path.cubicTo(11, 5, 14.5, 8.5, 14.5, 11.3);
path.cubicTo(14.5, 14.4, 12, 16.5, 9, 16.5);
path.cubicTo(6, 16.5, 3.5, 14.4, 3.5, 11.3);
path.cubicTo(3.5, 8.5, 7, 5, 9, 1.5);
painter.fillPath(path, painter.pen().color());
}
// Swift's wind: three strokes, the outer two curling back.
void wind(QPainter &painter)
{
QPainterPath top;
top.moveTo(1.5, 6);
top.lineTo(12, 6);
top.cubicTo(14.5, 6, 15, 2.5, 12.5, 2.5);
QPainterPath middle;
middle.moveTo(1.5, 9.5);
middle.lineTo(15, 9.5);
QPainterPath bottom;
bottom.moveTo(1.5, 13);
bottom.lineTo(10, 13);
bottom.cubicTo(12.5, 13, 13, 16.5, 10.5, 16.5);
for (const QPainterPath &path : {top, middle, bottom})
painter.drawPath(path);
}
// Swift's circle.dotted: twelve dots round a ring.
void dottedCircle(QPainter &painter)
{
painter.setBrush(painter.pen().color());
painter.setPen(Qt::NoPen);
for (int index = 0; index < 12; ++index) {
const double angle = index * std::numbers::pi / 6;
painter.drawEllipse(QPointF(9 + 6.8 * std::cos(angle), 9 + 6.8 * std::sin(angle)), 1.1, 1.1);
}
}
// Swift's scope: a ring with four ticks toward its middle.
void scope(QPainter &painter)
{
@@ -255,6 +298,9 @@ void LayerIcons::paint(QPainter &painter, LayerIcon icon, QPointF origin, double
case LayerIcon::axes: axes(painter); break;
case LayerIcon::scope: scope(painter); break;
case LayerIcon::lineDiagonal: painter.drawLine(QPointF(3, 15), QPointF(15, 3)); break;
case LayerIcon::drop: drop(painter); break;
case LayerIcon::wind: wind(painter); break;
case LayerIcon::dottedCircle: dottedCircle(painter); break;
}
painter.restore();
}
@@ -278,6 +324,9 @@ LayerIcon LayerIcons::symbol(AdjustmentKind kind)
case AdjustmentKind::exposure: return LayerIcon::plusMinusCircle;
case AdjustmentKind::gradientMap: return LayerIcon::paintPalette;
case AdjustmentKind::grain: return LayerIcon::circleGrid;
case AdjustmentKind::addNoise: return LayerIcon::dottedCircle;
case AdjustmentKind::gaussianBlur: return LayerIcon::drop;
case AdjustmentKind::motionBlur: return LayerIcon::wind;
case AdjustmentKind::invert: return LayerIcon::rightHalfCircle;
case AdjustmentKind::blackWhite: return LayerIcon::hatchedCircle;
case AdjustmentKind::colorBalance: return LayerIcon::axes;
+1 -1
View File
@@ -8,7 +8,7 @@
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,
scope, lineDiagonal
scope, lineDiagonal, drop, wind, dottedCircle
};
namespace LayerIcons {
+96
View File
@@ -0,0 +1,96 @@
#include "UI/TrimSheet.h"
#include "UI/KeyboardShortcuts.h"
#include <QButtonGroup>
#include <QCheckBox>
#include <QFrame>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QVBoxLayout>
#include <algorithm>
namespace {
QLabel *text(const QString &words, int pixels, QFont::Weight weight, QWidget *parent)
{
auto *label = new QLabel(words, parent);
label->setTextFormat(Qt::PlainText);
QFont font = label->font();
font.setPixelSize(pixels);
font.setWeight(weight);
label->setFont(font);
return label;
}
QFrame *divider(QWidget *parent)
{
auto *line = new QFrame(parent);
line->setFrameShape(QFrame::HLine);
line->setForegroundRole(QPalette::Mid);
return line;
}
}
TrimSheet::TrimSheet(std::function<void(std::optional<TrimOptions>)> finish, QWidget *parent)
: QWidget(parent), m_finish(std::move(finish)), m_basedOn(new QButtonGroup(this)), m_ok(new QPushButton(QStringLiteral("OK"), this))
{
setFixedWidth(320);
auto *column = new QVBoxLayout(this);
column->setContentsMargins(24, 24, 24, 24);
column->setSpacing(18);
column->addWidget(text(QStringLiteral("Trim"), 17, QFont::Bold, this));
// Swift's radio group, its label hidden behind the headline.
auto *basis = new QVBoxLayout;
basis->setSpacing(8);
basis->addWidget(text(QStringLiteral("Based On"), 13, QFont::DemiBold, this));
for (const TrimBasedOn option : allTrimBasedOn) {
auto *radio = new QRadioButton(rawValue(option), this);
radio->setObjectName(QStringLiteral("trimBasedOn%1").arg(int(option)));
radio->setChecked(option == TrimBasedOn::transparentPixels);
m_basedOn->addButton(radio, int(option));
basis->addWidget(radio);
}
column->addLayout(basis);
column->addWidget(divider(this));
auto *away = new QVBoxLayout;
away->setSpacing(8);
away->addWidget(text(QStringLiteral("Trim Away"), 13, QFont::DemiBold, this));
auto *grid = new QGridLayout;
grid->setHorizontalSpacing(24);
grid->setVerticalSpacing(8);
const std::array<QString, 4> names = {QStringLiteral("Top"), QStringLiteral("Bottom"), QStringLiteral("Left"), QStringLiteral("Right")};
for (size_t index = 0; index < names.size(); ++index) {
m_edges[index] = new QCheckBox(names[index], this);
m_edges[index]->setObjectName(QStringLiteral("trim") + names[index]);
m_edges[index]->setChecked(true);
grid->addWidget(m_edges[index], int(index / 2), int(index % 2));
// OK rests while no edge is checked.
connect(m_edges[index], &QCheckBox::toggled, this, [this] {
m_ok->setEnabled(std::any_of(m_edges.begin(), m_edges.end(), [](const QCheckBox *edge) { return edge->isChecked(); }));
});
}
grid->setColumnStretch(2, 1);
away->addLayout(grid);
column->addLayout(away);
column->addWidget(divider(this));
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
cancel->setObjectName(QStringLiteral("trimCancel"));
cancel->setAutoDefault(false);
connect(cancel, &QPushButton::clicked, this, [this] { m_finish(std::nullopt); });
m_ok->setObjectName(QStringLiteral("trimOK"));
m_ok->setDefault(true);
// Swift's configuredNativeShortcut: Return and Escape, as remapped.
NativeShortcut::bind(*this, m_ok, cancel);
connect(m_ok, &QPushButton::clicked, this, [this] {
m_finish(TrimOptions{.basedOn = TrimBasedOn(m_basedOn->checkedId()), .top = m_edges[0]->isChecked(), .bottom = m_edges[1]->isChecked(),
.left = m_edges[2]->isChecked(), .right = m_edges[3]->isChecked()});
});
auto *buttons = new QHBoxLayout;
buttons->addWidget(cancel);
buttons->addStretch(1);
buttons->addWidget(m_ok);
column->addLayout(buttons);
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Document/ImageTrim.h"
#include <QWidget>
#include <array>
#include <functional>
class QButtonGroup;
class QCheckBox;
class QPushButton;
// Swift's TrimSheet: the basis and the edges trimmed away.
class TrimSheet : public QWidget {
Q_OBJECT
public:
explicit TrimSheet(std::function<void(std::optional<TrimOptions>)> finish, QWidget *parent = nullptr);
private:
const std::function<void(std::optional<TrimOptions>)> m_finish;
QButtonGroup *const m_basedOn;
// Top, bottom, left, right.
std::array<QCheckBox *, 4> m_edges{};
QPushButton *const m_ok;
};