Blur and noise adjustment layers, Image Trim, Inner Glow (12.8c)
This commit is contained in:
1 parent
7ae263de80
commit
31e2dd3864
60 files changed
+1673
-105
No files matched your search
@@ -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());
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Reference in new issue
Block a user