Outer Glow, brush Smoothing and the rest of Compositor 1.2.0

This commit is contained in:
雷电芽衣 committed 2026-09-27 07:00:12 -04:00
1 parent 2cd0d1b86d
commit 374ba8ec73
32 files changed
+624 -47

No files matched your search

+2
View File
@@ -31,6 +31,8 @@ struct BrushSettings {
double blue = 0;
// Caps the whole stroke: overlapping dabs never exceed it.
double opacity = 1;
// 0–100: the brush trails the pointer on this string.
double smoothing = 0;
// Erase clears the layer's pixels instead of painting on them.
bool erasing = false;
// Spot healing takes nearby pixels instead of the colour.
+27 -2
View File
@@ -111,6 +111,8 @@ void EditorSession::beginBrush(QPointF point)
m_brushStroke->clone = clone;
m_brushStroke->isBlur = m_tool == NavigationTool::blur;
m_brushStroke->append(point);
m_brushAnchor = point;
m_brushPointer = point;
m_lastBrushPoint = LastBrushPoint{point, layer.id, m_isMaskSelected};
++m_brushRevision;
resumeFileRequests();
@@ -135,9 +137,13 @@ void EditorSession::continueBrush(QPointF point)
}
if (!m_brushStroke)
return;
m_brushPointer = point;
const std::optional<QPointF> painted = smoothed(point);
if (!painted)
return;
try {
m_brushStroke->append(point);
m_lastBrushPoint->point = point;
m_brushStroke->append(*painted);
m_lastBrushPoint->point = *painted;
++m_brushRevision;
notify();
} catch (const ProjectError &error) {
@@ -149,6 +155,20 @@ void EditorSession::continueBrush(QPointF point)
}
}
// Photoshop's string: the brush moves once pulled taut.
std::optional<QPointF> EditorSession::smoothed(QPointF point)
{
if (m_tool != NavigationTool::brush || !(m_brushSettings.smoothing > 0) || !m_brushAnchor)
return point;
const double radius = m_brushSettings.smoothing / std::max(0.01, viewport.zoom());
const QPointF delta = point - *m_brushAnchor;
const double distance = std::hypot(delta.x(), delta.y());
if (!(distance > radius))
return std::nullopt;
m_brushAnchor = *m_brushAnchor + delta * ((distance - radius) / distance);
return m_brushAnchor;
}
// Where a Shift-click's line starts, on the same target.
std::optional<QPointF> EditorSession::shiftLineStart() const
{
@@ -161,6 +181,8 @@ void EditorSession::cancelBrush()
{
m_warpStroke.reset();
m_brushStroke.reset();
m_brushAnchor.reset();
m_brushPointer.reset();
++m_brushRevision;
resumeFileRequests();
notify();
@@ -180,6 +202,9 @@ bool EditorSession::finishBrushImmediately()
if (m_isProjectBusy)
return false;
try {
// Smoothing leaves the brush short; the stroke ends there.
if (m_brushPointer && m_brushAnchor && *m_brushPointer != *m_brushAnchor && m_tool == NavigationTool::brush && m_brushSettings.smoothing > 0)
m_brushStroke->append(*m_brushPointer);
m_brushStroke->flush();
if (m_brushStroke->settings.healing)
m_brushStroke->heal();
+2
View File
@@ -22,3 +22,5 @@ public:
private:
void finishRasterCommit();
// Where Smoothing lets the brush go; none while slack.
std::optional<QPointF> smoothed(QPointF point);
+3
View File
@@ -427,6 +427,9 @@ private:
// Clone Stamp and the Smear keep their own soft tips.
std::map<int, ParkedTip> m_parkedBrushTips{{1, {40, 0, 1}}, {2, {40, 0, 1}}};
std::optional<LastBrushPoint> m_lastBrushPoint;
// Smoothing's brush, and the pointer it trails.
std::optional<QPointF> m_brushAnchor;
std::optional<QPointF> m_brushPointer;
int m_brushRevision = 0;
std::optional<PendingOpacityDigit> m_pendingOpacityDigit;
std::optional<CommittingRaster> m_committingRaster;
+2
View File
@@ -104,6 +104,8 @@ double LayerEffectsRenderer::margin(const LayerEffects &all)
margin = std::max(margin, effects.stroke->size);
if (effects.shadow)
margin = std::max(margin, effects.shadow->distance + effects.shadow->blur * 3);
if (effects.outerGlow)
margin = std::max(margin, effects.outerGlow->size * 3);
return std::ceil(margin) + 2;
}
+5
View File
@@ -10,6 +10,7 @@ void take(LayerEffects &into, const LayerEffects &from, LayerEffectKind kind)
case LayerEffectKind::shadow: into.shadow = from.shadow; return;
case LayerEffectKind::colorOverlay: into.colorOverlay = from.colorOverlay; return;
case LayerEffectKind::innerShadow: into.innerShadow = from.innerShadow; return;
case LayerEffectKind::outerGlow: into.outerGlow = from.outerGlow; return;
}
throw std::logic_error("unknown effect kind");
}
@@ -70,6 +71,10 @@ void EditorSession::addEffect(LayerEffectKind kind)
if (!effects.innerShadow)
effects.innerShadow = InnerShadowEffect();
break;
case LayerEffectKind::outerGlow:
if (!effects.outerGlow)
effects.outerGlow = OuterGlowEffect();
break;
}
setEffects(effects, id, QStringLiteral("Add ") + rawValue(kind));
selectEffect(kind, id, true);
+14 -2
View File
@@ -56,6 +56,11 @@ bool InnerShadowEffect::isValid() const
return shadowIsValid(angle, distance, blur, opacity) && colourIsValid(red, green, blue);
}
bool OuterGlowEffect::isValid() const
{
return within(size, 0, 500) && within(opacity, 0, 1) && colourIsValid(red, green, blue);
}
QString rawValue(LayerEffectKind kind)
{
switch (kind) {
@@ -63,6 +68,7 @@ QString rawValue(LayerEffectKind kind)
case LayerEffectKind::shadow: return QStringLiteral("Drop Shadow");
case LayerEffectKind::colorOverlay: return QStringLiteral("Color Overlay");
case LayerEffectKind::innerShadow: return QStringLiteral("Inner Shadow");
case LayerEffectKind::outerGlow: return QStringLiteral("Outer Glow");
}
throw std::logic_error("unknown effect kind");
}
@@ -70,7 +76,7 @@ QString rawValue(LayerEffectKind kind)
bool LayerEffects::isValid() const
{
return (!stroke || stroke->isValid()) && (!shadow || shadow->isValid()) && (!colorOverlay || colorOverlay->isValid())
&& (!innerShadow || innerShadow->isValid());
&& (!innerShadow || innerShadow->isValid()) && (!outerGlow || outerGlow->isValid());
}
std::vector<LayerEffectKind> LayerEffects::kinds() const
@@ -90,6 +96,7 @@ bool LayerEffects::contains(LayerEffectKind kind) const
case LayerEffectKind::shadow: return shadow.has_value();
case LayerEffectKind::colorOverlay: return colorOverlay.has_value();
case LayerEffectKind::innerShadow: return innerShadow.has_value();
case LayerEffectKind::outerGlow: return outerGlow.has_value();
}
throw std::logic_error("unknown effect kind");
}
@@ -101,6 +108,7 @@ bool LayerEffects::isEnabled(LayerEffectKind kind) const
case LayerEffectKind::shadow: return shadow && shadow->isEnabled();
case LayerEffectKind::colorOverlay: return colorOverlay && colorOverlay->isEnabled();
case LayerEffectKind::innerShadow: return innerShadow && innerShadow->isEnabled();
case LayerEffectKind::outerGlow: return outerGlow && outerGlow->isEnabled();
}
throw std::logic_error("unknown effect kind");
}
@@ -112,6 +120,7 @@ std::optional<PaletteColor> LayerEffects::color(LayerEffectKind kind) const
case LayerEffectKind::shadow: return shadow ? std::optional(shadow->color()) : std::nullopt;
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;
}
throw std::logic_error("unknown effect kind");
}
@@ -146,6 +155,7 @@ void LayerEffects::setColor(const PaletteColor &color, LayerEffectKind kind)
case LayerEffectKind::shadow: paint(shadow, color); return;
case LayerEffectKind::colorOverlay: paint(colorOverlay, color); return;
case LayerEffectKind::innerShadow: paint(innerShadow, color); return;
case LayerEffectKind::outerGlow: paint(outerGlow, color); return;
}
throw std::logic_error("unknown effect kind");
}
@@ -157,6 +167,7 @@ void LayerEffects::remove(LayerEffectKind kind)
case LayerEffectKind::shadow: shadow.reset(); return;
case LayerEffectKind::colorOverlay: colorOverlay.reset(); return;
case LayerEffectKind::innerShadow: innerShadow.reset(); return;
case LayerEffectKind::outerGlow: outerGlow.reset(); return;
}
throw std::logic_error("unknown effect kind");
}
@@ -168,11 +179,12 @@ void LayerEffects::setEnabled(bool enabled, LayerEffectKind kind)
case LayerEffectKind::shadow: enable(shadow, enabled); return;
case LayerEffectKind::colorOverlay: enable(colorOverlay, enabled); return;
case LayerEffectKind::innerShadow: enable(innerShadow, enabled); return;
case LayerEffectKind::outerGlow: enable(outerGlow, enabled); return;
}
throw std::logic_error("unknown effect kind");
}
LayerEffects LayerEffects::visible() const
{
return LayerEffects{shown(stroke), shown(shadow), shown(colorOverlay), shown(innerShadow)};
return LayerEffects{shown(stroke), shown(shadow), shown(colorOverlay), shown(innerShadow), shown(outerGlow)};
}
+18 -3
View File
@@ -74,10 +74,24 @@ struct InnerShadowEffect {
friend bool operator==(const InnerShadowEffect &, const InnerShadowEffect &) = default;
};
// A soft glow round the outside of the layer.
struct OuterGlowEffect {
std::optional<bool> enabled = std::nullopt;
double size = 20;
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 OuterGlowEffect &, const OuterGlowEffect &) = default;
};
// Swift's raw values are the panel's and the menu's words.
enum class LayerEffectKind { stroke, shadow, colorOverlay, innerShadow };
enum class LayerEffectKind { stroke, shadow, colorOverlay, innerShadow, outerGlow };
inline constexpr std::array allLayerEffectKinds{LayerEffectKind::stroke, LayerEffectKind::shadow, LayerEffectKind::colorOverlay,
LayerEffectKind::innerShadow};
LayerEffectKind::innerShadow, LayerEffectKind::outerGlow};
QString rawValue(LayerEffectKind kind);
// What a layer draws round itself; its pixels stay untouched.
@@ -86,7 +100,8 @@ struct LayerEffects {
std::optional<ShadowEffect> shadow = std::nullopt;
std::optional<ColorOverlayEffect> colorOverlay = std::nullopt;
std::optional<InnerShadowEffect> innerShadow = std::nullopt;
bool isEmpty() const { return !stroke && !shadow && !colorOverlay && !innerShadow; }
std::optional<OuterGlowEffect> outerGlow = std::nullopt;
bool isEmpty() const { return !stroke && !shadow && !colorOverlay && !innerShadow && !outerGlow; }
bool isValid() const;
std::vector<LayerEffectKind> kinds() const;
bool contains(LayerEffectKind kind) const;