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

+19
View File
@@ -81,6 +81,9 @@ void EditorSession::openAdjustmentEditor(QUuid id, const LayerAdjustment &origin
case AdjustmentKind::exposure:
case AdjustmentKind::gradientMap:
case AdjustmentKind::grain:
case AdjustmentKind::addNoise:
case AdjustmentKind::gaussianBlur:
case AdjustmentKind::motionBlur:
case AdjustmentKind::blackWhite:
case AdjustmentKind::colorBalance: {
FilterSettings settings;
@@ -90,6 +93,12 @@ void EditorSession::openAdjustmentEditor(QUuid id, const LayerAdjustment &origin
settings.grain = original.grain();
settings.blackWhite = original.blackWhite();
settings.colorBalance = original.colorBalance();
settings.radius = original.gaussianRadius();
settings.angle = original.resolvedMotionAngle();
settings.distance = original.resolvedMotionDistance();
settings.amount = original.resolvedNoiseAmount();
settings.gaussian = original.resolvedNoiseGaussian();
settings.monochromatic = original.resolvedNoiseMonochromatic();
m_filterEdit.emplace(filterKind(original.kind).value(), layer, std::nullopt, settings, std::nullopt);
break;
}
@@ -121,6 +130,16 @@ LayerAdjustment EditorSession::editedAdjustment() const
case AdjustmentKind::grain: value.setGrain(m_filterEdit.value().settings.grain); break;
case AdjustmentKind::blackWhite: value.setBlackWhite(m_filterEdit.value().settings.blackWhite); break;
case AdjustmentKind::colorBalance: value.setColorBalance(m_filterEdit.value().settings.colorBalance); break;
case AdjustmentKind::gaussianBlur: value.setGaussianRadius(m_filterEdit.value().settings.radius); break;
case AdjustmentKind::motionBlur:
value.setResolvedMotionAngle(m_filterEdit.value().settings.angle);
value.setResolvedMotionDistance(m_filterEdit.value().settings.distance);
break;
case AdjustmentKind::addNoise:
value.setResolvedNoiseAmount(m_filterEdit.value().settings.amount);
value.setResolvedNoiseGaussian(m_filterEdit.value().settings.gaussian);
value.setResolvedNoiseMonochromatic(m_filterEdit.value().settings.monochromatic);
break;
case AdjustmentKind::invert: throw std::logic_error("Invert has no editor to read");
}
return value;
+2 -2
View File
@@ -134,8 +134,8 @@ QImage PixelFilter::run(const FilterJob &job)
break;
case FilterKind::addNoise:
image = drawn(job.image);
noise_add(image.bits(), size_t(width), size_t(height), size_t(image.bytesPerLine()), float(settings.amount), settings.gaussian ? 1 : 0,
settings.monochromatic ? 1 : 0, job.seed);
noise_add_at(image.bits(), size_t(width), size_t(height), size_t(image.bytesPerLine()), float(settings.amount), settings.gaussian ? 1 : 0,
settings.monochromatic ? 1 : 0, job.seed, int64_t(std::floor(job.noiseOrigin.x())), int64_t(std::floor(job.noiseOrigin.y())));
break;
case FilterKind::lensCorrection: {
// Relative to the image's size, so previews bend alike.
+2
View File
@@ -87,6 +87,8 @@ struct FilterJob {
// The point colour shown alone, or −1 for the grade.
int visualizesPointColor = -1;
bool showsSharpenMask = false;
// A live adjustment's noise field, fixed in its region's pixels.
QPointF noiseOrigin{};
};
// Swift's PixelFilter.
+105
View File
@@ -0,0 +1,105 @@
#include "Document/ImageTrim.h"
#include "Document/CanvasSize.h"
#include "IO/CanvasResizer.h"
#include "IO/ImageExporter.h"
#include <cstdlib>
extern "C" {
#include "BrushPixels.h"
}
QString rawValue(TrimBasedOn basedOn)
{
switch (basedOn) {
case TrimBasedOn::transparentPixels: return QStringLiteral("Transparent Pixels");
case TrimBasedOn::topLeftPixelColor: return QStringLiteral("Top Left Pixel Color");
case TrimBasedOn::bottomRightPixelColor: return QStringLiteral("Bottom Right Pixel Color");
}
throw std::logic_error("unknown trim basis");
}
bool TrimOptions::trimsAny() const
{
return top || bottom || left || right;
}
namespace {
// Unchecked edges stay at the image's; content is never empty.
QRect kept(int left, int top, int right, int bottom, int width, int height, const TrimOptions &options)
{
const int minX = options.left ? left : 0, minY = options.top ? top : 0;
const int maxX = options.right ? right : width, maxY = options.bottom ? bottom : height;
return QRect(minX, minY, maxX - minX, maxY - minY);
}
// Swift's calculateColorTrimRect: rows scanned in from both ends.
std::optional<QRect> colorTrimRect(const QImage &pixels, QPoint sample, const TrimOptions &options)
{
const int width = pixels.width(), height = pixels.height(), tolerance = options.tolerance;
const uchar *target = pixels.constScanLine(sample.y()) + sample.x() * 4;
const auto matches = [&](const uchar *row, int x) {
const uchar *pixel = row + x * 4;
for (int channel = 0; channel < 4; ++channel) {
if (std::abs(pixel[channel] - target[channel]) > tolerance)
return false;
}
return true;
};
int left = width, right = 0, top = height, bottom = 0;
for (int y = 0; y < height; ++y) {
const uchar *row = pixels.constScanLine(y);
int first = 0;
while (first < width && matches(row, first))
++first;
if (first == width)
continue;
// The unmatched pixel at `first` stops this scan.
int last = width;
while (matches(row, last - 1))
--last;
left = std::min(left, first);
right = std::max(right, last);
top = std::min(top, y);
bottom = y + 1;
}
// Every pixel matched the sample.
if (right == 0)
return std::nullopt;
return kept(left, top, right, bottom, width, height, options);
}
}
std::optional<QRect> ImageTrim::calculateTrimRect(const QImage &image, const TrimOptions &options)
{
if (!options.trimsAny() || image.isNull())
return std::nullopt;
// Swift's try? returns nil; running out here is explained instead.
const QImage pixels = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
if (pixels.isNull())
throw ExportError(ExportError::Kind::render);
const int width = pixels.width(), height = pixels.height();
switch (options.basedOn) {
case TrimBasedOn::transparentPixels: {
size_t edges[4] = {0, 0, 0, 0};
brush_alpha_bounds(pixels.constBits(), size_t(width), size_t(height), size_t(pixels.bytesPerLine()), edges);
// A right edge of zero: every pixel is clear.
if (edges[2] == 0)
return std::nullopt;
return kept(int(edges[0]), int(edges[1]), int(edges[2]), int(edges[3]), width, height, options);
}
case TrimBasedOn::topLeftPixelColor: return colorTrimRect(pixels, QPoint(0, 0), options);
case TrimBasedOn::bottomRightPixelColor: return colorTrimRect(pixels, QPoint(width - 1, height - 1), options);
}
throw std::logic_error("unknown trim basis");
}
std::optional<ProjectSnapshot> ImageTrim::trim(const ProjectSnapshot &snapshot, const TrimOptions &options)
{
const std::optional<QRect> rect = calculateTrimRect(ImageExporter::render(snapshot).image, options);
if (!rect)
return std::nullopt;
if (*rect == QRect(0, 0, int(snapshot.manifest.width), int(snapshot.manifest.height)))
return snapshot;
return CanvasResizer::resize(snapshot, CanvasSizeOptions{.width = rect->width(), .height = rect->height(),
.contentOffset = QPointF(-rect->left(), -rect->top())});
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "IO/ProjectStore.h"
#include <QImage>
#include <QRect>
#include <array>
#include <optional>
// What Trim compares each edge against.
enum class TrimBasedOn { transparentPixels, topLeftPixelColor, bottomRightPixelColor };
inline constexpr std::array allTrimBasedOn{TrimBasedOn::transparentPixels, TrimBasedOn::topLeftPixelColor, TrimBasedOn::bottomRightPixelColor};
// Swift's rawValue: the sheet's words.
QString rawValue(TrimBasedOn basedOn);
struct TrimOptions {
TrimBasedOn basedOn = TrimBasedOn::transparentPixels;
bool top = true;
bool bottom = true;
bool left = true;
bool right = true;
// Per channel, premultiplied: how far a pixel may stray.
quint8 tolerance = 0;
bool trimsAny() const;
friend bool operator==(const TrimOptions &, const TrimOptions &) = default;
};
namespace ImageTrim {
// The rect to keep, or none when nothing would remain.
std::optional<QRect> calculateTrimRect(const QImage &image, const TrimOptions &options);
// The rendered canvas trimmed by the canvas resizer, or none.
std::optional<ProjectSnapshot> trim(const ProjectSnapshot &snapshot, const TrimOptions &options);
}
+105 -2
View File
@@ -13,6 +13,9 @@ QString rawValue(AdjustmentKind kind)
case AdjustmentKind::exposure: return QStringLiteral("Exposure");
case AdjustmentKind::gradientMap: return QStringLiteral("Gradient Map");
case AdjustmentKind::grain: return QStringLiteral("Grain");
case AdjustmentKind::addNoise: return QStringLiteral("Add Noise");
case AdjustmentKind::gaussianBlur: return QStringLiteral("Gaussian Blur");
case AdjustmentKind::motionBlur: return QStringLiteral("Motion Blur");
case AdjustmentKind::invert: return QStringLiteral("Invert");
case AdjustmentKind::blackWhite: return QStringLiteral("Black & White");
case AdjustmentKind::colorBalance: return QStringLiteral("Color Balance");
@@ -36,6 +39,9 @@ std::optional<FilterKind> filterKind(AdjustmentKind kind)
case AdjustmentKind::exposure: return FilterKind::exposure;
case AdjustmentKind::gradientMap: return FilterKind::gradientMap;
case AdjustmentKind::grain: return FilterKind::grain;
case AdjustmentKind::addNoise: return FilterKind::addNoise;
case AdjustmentKind::gaussianBlur: return FilterKind::gaussianBlur;
case AdjustmentKind::motionBlur: return FilterKind::motionBlur;
case AdjustmentKind::blackWhite: return FilterKind::blackWhite;
case AdjustmentKind::colorBalance: return FilterKind::colorBalance;
// Panels of their own; Invert has nothing to set.
@@ -106,6 +112,85 @@ void LayerAdjustment::setColorBalance(const ColorBalanceSettings &value)
colorBalanceSettings = value;
}
double LayerAdjustment::gaussianRadius() const
{
return blurRadius.value_or(10);
}
void LayerAdjustment::setGaussianRadius(double value)
{
blurRadius = value;
}
double LayerAdjustment::resolvedMotionAngle() const
{
return motionAngle.value_or(0);
}
void LayerAdjustment::setResolvedMotionAngle(double value)
{
motionAngle = value;
}
double LayerAdjustment::resolvedMotionDistance() const
{
return motionDistance.value_or(10);
}
void LayerAdjustment::setResolvedMotionDistance(double value)
{
motionDistance = value;
}
double LayerAdjustment::resolvedNoiseAmount() const
{
return noiseAmount.value_or(10);
}
void LayerAdjustment::setResolvedNoiseAmount(double value)
{
noiseAmount = value;
}
bool LayerAdjustment::resolvedNoiseGaussian() const
{
return noiseGaussian.value_or(false);
}
void LayerAdjustment::setResolvedNoiseGaussian(bool value)
{
noiseGaussian = value;
}
bool LayerAdjustment::resolvedNoiseMonochromatic() const
{
return noiseMonochromatic.value_or(false);
}
void LayerAdjustment::setResolvedNoiseMonochromatic(bool value)
{
noiseMonochromatic = value;
}
quint32 LayerAdjustment::resolvedNoiseSeed() const
{
return noiseSeed.value_or(0);
}
void LayerAdjustment::setResolvedNoiseSeed(quint32 value)
{
noiseSeed = value;
}
double LayerAdjustment::samplingMargin() const
{
switch (kind) {
case AdjustmentKind::gaussianBlur: return gaussianRadius() * 3 + 2;
case AdjustmentKind::motionBlur: return resolvedMotionDistance() / 2 + 2;
default: return 0;
}
}
// Bounds refuse what is no number: no isFinite terms.
bool LayerAdjustment::isValid() const
{
@@ -130,10 +215,11 @@ bool LayerAdjustment::isValid() const
return false;
}
return curves.isValid() && exposure().isValid() && gradientMap().isValid() && grain().isValid() && blackWhite().isValid()
&& colorBalance().isValid();
&& colorBalance().isValid() && gaussianRadius() >= 0.1 && gaussianRadius() <= 250 && std::abs(resolvedMotionAngle()) <= 90
&& resolvedMotionDistance() >= 1 && resolvedMotionDistance() <= 2000 && resolvedNoiseAmount() >= 0.1 && resolvedNoiseAmount() <= 400;
}
QImage LayerAdjustment::apply(const QImage &image, std::optional<QRectF> region) const
QImage LayerAdjustment::apply(const QImage &image, std::optional<QRectF> region, double scale) const
{
switch (kind) {
case AdjustmentKind::hsv:
@@ -144,6 +230,21 @@ QImage LayerAdjustment::apply(const QImage &image, std::optional<QRectF> region)
case AdjustmentKind::gradientMap: return gradientMap().apply(image);
case AdjustmentKind::blackWhite: return blackWhite().apply(image);
case AdjustmentKind::colorBalance: return colorBalance().apply(image);
case AdjustmentKind::addNoise:
case AdjustmentKind::gaussianBlur:
case AdjustmentKind::motionBlur: {
const FilterSettings settings{.radius = gaussianRadius(),
.angle = resolvedMotionAngle(),
.distance = resolvedMotionDistance(),
.amount = resolvedNoiseAmount(),
.gaussian = resolvedNoiseGaussian(),
.monochromatic = resolvedNoiseMonochromatic()};
FilterJob job{filterKind(kind).value(), image, settings, scale, std::nullopt, QTransform(), resolvedNoiseSeed()};
// Swift's region origin, counted in the image's own pixels.
if (region)
job.noiseOrigin = region->topLeft() * (image.width() / region->width());
return PixelFilter::run(job);
}
case AdjustmentKind::invert: return PixelInvert::run(PixelInvert::Job{image, false, QTransform(), std::nullopt});
case AdjustmentKind::grain: {
// Grain sits in the document, however the image is cut.
@@ -169,6 +270,8 @@ void EditorSession::addAdjustment(AdjustmentKind kind)
grain.seed = QRandomGenerator::global()->generate();
adjustment.setGrain(grain);
}
if (kind == AdjustmentKind::addNoise)
adjustment.setResolvedNoiseSeed(QRandomGenerator::global()->generate());
layer.adjustment = adjustment;
const std::optional<ImageLayer> active = activeLayer();
layer.parentID = active && active->isGroup ? m_activeLayerID : active ? active->parentID : std::nullopt;
+31 -6
View File
@@ -10,10 +10,11 @@
#include <optional>
// An adjustment layer's kind, in the Layer menu's order.
enum class AdjustmentKind { hsv, levels, curves, exposure, gradientMap, grain, invert, blackWhite, colorBalance };
inline constexpr std::array allAdjustmentKinds{AdjustmentKind::hsv, AdjustmentKind::levels, AdjustmentKind::curves,
AdjustmentKind::exposure, AdjustmentKind::gradientMap, AdjustmentKind::grain,
AdjustmentKind::invert, AdjustmentKind::blackWhite, AdjustmentKind::colorBalance};
enum class AdjustmentKind { hsv, levels, curves, exposure, gradientMap, grain, addNoise, gaussianBlur, motionBlur, invert, blackWhite, colorBalance };
inline constexpr std::array allAdjustmentKinds{AdjustmentKind::hsv, AdjustmentKind::levels, AdjustmentKind::curves,
AdjustmentKind::exposure, AdjustmentKind::gradientMap, AdjustmentKind::grain,
AdjustmentKind::addNoise, AdjustmentKind::gaussianBlur, AdjustmentKind::motionBlur,
AdjustmentKind::invert, AdjustmentKind::blackWhite, AdjustmentKind::colorBalance};
// Swift's rawValue: the new layer's name and the manifest's word.
QString rawValue(AdjustmentKind kind);
// The kind a manifest names, or none.
@@ -40,6 +41,14 @@ struct LayerAdjustment {
std::optional<GrainSettings> grainSettings = std::nullopt;
std::optional<BlackWhiteSettings> blackWhiteSettings = std::nullopt;
std::optional<ColorBalanceSettings> colorBalanceSettings = std::nullopt;
// Absent in projects from before blur and noise layers.
std::optional<double> blurRadius = std::nullopt;
std::optional<double> motionAngle = std::nullopt;
std::optional<double> motionDistance = std::nullopt;
std::optional<double> noiseAmount = std::nullopt;
std::optional<bool> noiseGaussian = std::nullopt;
std::optional<bool> noiseMonochromatic = std::nullopt;
std::optional<quint32> noiseSeed = std::nullopt;
HueSaturationSettings resolvedHSV() const;
// Swift's computed properties over the optional settings.
ExposureSettings exposure() const;
@@ -52,8 +61,24 @@ struct LayerAdjustment {
void setBlackWhite(const BlackWhiteSettings &value);
ColorBalanceSettings colorBalance() const;
void setColorBalance(const ColorBalanceSettings &value);
double gaussianRadius() const;
void setGaussianRadius(double value);
double resolvedMotionAngle() const;
void setResolvedMotionAngle(double value);
double resolvedMotionDistance() const;
void setResolvedMotionDistance(double value);
double resolvedNoiseAmount() const;
void setResolvedNoiseAmount(double value);
bool resolvedNoiseGaussian() const;
void setResolvedNoiseGaussian(bool value);
bool resolvedNoiseMonochromatic() const;
void setResolvedNoiseMonochromatic(bool value);
quint32 resolvedNoiseSeed() const;
void setResolvedNoiseSeed(quint32 value);
// Document pixels a partial redraw reads past its rect.
double samplingMargin() const;
bool isValid() const;
// `region`: the document `image` covers, whole pixels by default.
QImage apply(const QImage &image, std::optional<QRectF> region = std::nullopt) const;
// Region: the document shown; scale: image pixels a document pixel.
QImage apply(const QImage &image, std::optional<QRectF> region = std::nullopt, double scale = 1) const;
friend bool operator==(const LayerAdjustment &, const LayerAdjustment &) = default;
};
+5
View File
@@ -11,6 +11,7 @@ void take(LayerEffects &into, const LayerEffects &from, LayerEffectKind kind)
case LayerEffectKind::colorOverlay: into.colorOverlay = from.colorOverlay; return;
case LayerEffectKind::innerShadow: into.innerShadow = from.innerShadow; return;
case LayerEffectKind::outerGlow: into.outerGlow = from.outerGlow; return;
case LayerEffectKind::innerGlow: into.innerGlow = from.innerGlow; return;
}
throw std::logic_error("unknown effect kind");
}
@@ -75,6 +76,10 @@ void EditorSession::addEffect(LayerEffectKind kind)
if (!effects.outerGlow)
effects.outerGlow = OuterGlowEffect();
break;
case LayerEffectKind::innerGlow:
if (!effects.innerGlow)
effects.innerGlow = InnerGlowEffect();
break;
}
setEffects(effects, id, QStringLiteral("Add ") + rawValue(kind));
selectEffect(kind, id, true);
+14 -2
View File
@@ -61,6 +61,11 @@ bool OuterGlowEffect::isValid() const
return within(size, 0, 500) && within(opacity, 0, 1) && colourIsValid(red, green, blue);
}
bool InnerGlowEffect::isValid() const
{
return within(size, 0, 500) && within(opacity, 0, 1) && colourIsValid(red, green, blue);
}
QString rawValue(LayerEffectKind kind)
{
switch (kind) {
@@ -69,6 +74,7 @@ QString rawValue(LayerEffectKind kind)
case LayerEffectKind::colorOverlay: return QStringLiteral("Color Overlay");
case LayerEffectKind::innerShadow: return QStringLiteral("Inner Shadow");
case LayerEffectKind::outerGlow: return QStringLiteral("Outer Glow");
case LayerEffectKind::innerGlow: return QStringLiteral("Inner Glow");
}
throw std::logic_error("unknown effect kind");
}
@@ -76,7 +82,7 @@ QString rawValue(LayerEffectKind kind)
bool LayerEffects::isValid() const
{
return (!stroke || stroke->isValid()) && (!shadow || shadow->isValid()) && (!colorOverlay || colorOverlay->isValid())
&& (!innerShadow || innerShadow->isValid()) && (!outerGlow || outerGlow->isValid());
&& (!innerShadow || innerShadow->isValid()) && (!outerGlow || outerGlow->isValid()) && (!innerGlow || innerGlow->isValid());
}
std::vector<LayerEffectKind> LayerEffects::kinds() const
@@ -97,6 +103,7 @@ bool LayerEffects::contains(LayerEffectKind kind) const
case LayerEffectKind::colorOverlay: return colorOverlay.has_value();
case LayerEffectKind::innerShadow: return innerShadow.has_value();
case LayerEffectKind::outerGlow: return outerGlow.has_value();
case LayerEffectKind::innerGlow: return innerGlow.has_value();
}
throw std::logic_error("unknown effect kind");
}
@@ -109,6 +116,7 @@ bool LayerEffects::isEnabled(LayerEffectKind kind) const
case LayerEffectKind::colorOverlay: return colorOverlay && colorOverlay->isEnabled();
case LayerEffectKind::innerShadow: return innerShadow && innerShadow->isEnabled();
case LayerEffectKind::outerGlow: return outerGlow && outerGlow->isEnabled();
case LayerEffectKind::innerGlow: return innerGlow && innerGlow->isEnabled();
}
throw std::logic_error("unknown effect kind");
}
@@ -121,6 +129,7 @@ std::optional<PaletteColor> LayerEffects::color(LayerEffectKind kind) const
case LayerEffectKind::colorOverlay: return colorOverlay ? std::optional(colorOverlay->color()) : std::nullopt;
case LayerEffectKind::innerShadow: return innerShadow ? std::optional(innerShadow->color()) : std::nullopt;
case LayerEffectKind::outerGlow: return outerGlow ? std::optional(outerGlow->color()) : std::nullopt;
case LayerEffectKind::innerGlow: return innerGlow ? std::optional(innerGlow->color()) : std::nullopt;
}
throw std::logic_error("unknown effect kind");
}
@@ -156,6 +165,7 @@ void LayerEffects::setColor(const PaletteColor &color, LayerEffectKind kind)
case LayerEffectKind::colorOverlay: paint(colorOverlay, color); return;
case LayerEffectKind::innerShadow: paint(innerShadow, color); return;
case LayerEffectKind::outerGlow: paint(outerGlow, color); return;
case LayerEffectKind::innerGlow: paint(innerGlow, color); return;
}
throw std::logic_error("unknown effect kind");
}
@@ -168,6 +178,7 @@ void LayerEffects::remove(LayerEffectKind kind)
case LayerEffectKind::colorOverlay: colorOverlay.reset(); return;
case LayerEffectKind::innerShadow: innerShadow.reset(); return;
case LayerEffectKind::outerGlow: outerGlow.reset(); return;
case LayerEffectKind::innerGlow: innerGlow.reset(); return;
}
throw std::logic_error("unknown effect kind");
}
@@ -180,11 +191,12 @@ void LayerEffects::setEnabled(bool enabled, LayerEffectKind kind)
case LayerEffectKind::colorOverlay: enable(colorOverlay, enabled); return;
case LayerEffectKind::innerShadow: enable(innerShadow, enabled); return;
case LayerEffectKind::outerGlow: enable(outerGlow, enabled); return;
case LayerEffectKind::innerGlow: enable(innerGlow, enabled); return;
}
throw std::logic_error("unknown effect kind");
}
LayerEffects LayerEffects::visible() const
{
return LayerEffects{shown(stroke), shown(shadow), shown(colorOverlay), shown(innerShadow), shown(outerGlow)};
return LayerEffects{shown(stroke), shown(shadow), shown(colorOverlay), shown(innerShadow), shown(outerGlow), shown(innerGlow)};
}
+19 -4
View File
@@ -88,10 +88,24 @@ struct OuterGlowEffect {
friend bool operator==(const OuterGlowEffect &, const OuterGlowEffect &) = default;
};
// A glow inside the layer's edges, fading inward.
struct InnerGlowEffect {
std::optional<bool> enabled = std::nullopt;
double size = 10;
double red = 1;
double green = 1;
double blue = 1;
double opacity = 0.75;
bool isEnabled() const { return enabled.value_or(true); }
PaletteColor color() const { return {red, green, blue}; }
bool isValid() const;
friend bool operator==(const InnerGlowEffect &, const InnerGlowEffect &) = default;
};
// Swift's raw values are the panel's and the menu's words.
enum class LayerEffectKind { stroke, shadow, colorOverlay, innerShadow, outerGlow };
inline constexpr std::array allLayerEffectKinds{LayerEffectKind::stroke, LayerEffectKind::shadow, LayerEffectKind::colorOverlay,
LayerEffectKind::innerShadow, LayerEffectKind::outerGlow};
enum class LayerEffectKind { stroke, shadow, colorOverlay, innerShadow, outerGlow, innerGlow };
inline constexpr std::array allLayerEffectKinds{LayerEffectKind::stroke, LayerEffectKind::shadow, LayerEffectKind::colorOverlay,
LayerEffectKind::innerShadow, LayerEffectKind::outerGlow, LayerEffectKind::innerGlow};
QString rawValue(LayerEffectKind kind);
// What a layer draws round itself; its pixels stay untouched.
@@ -101,7 +115,8 @@ struct LayerEffects {
std::optional<ColorOverlayEffect> colorOverlay = std::nullopt;
std::optional<InnerShadowEffect> innerShadow = std::nullopt;
std::optional<OuterGlowEffect> outerGlow = std::nullopt;
bool isEmpty() const { return !stroke && !shadow && !colorOverlay && !innerShadow && !outerGlow; }
std::optional<InnerGlowEffect> innerGlow = std::nullopt;
bool isEmpty() const { return !stroke && !shadow && !colorOverlay && !innerShadow && !outerGlow && !innerGlow; }
bool isValid() const;
std::vector<LayerEffectKind> kinds() const;
bool contains(LayerEffectKind kind) const;