14.22: effects, text and shapes outlast size changes and adjustments

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 12:03:49 -04:00
1 parent bafb07978a
commit e8b74c6e47
17 files changed
+226 -38

No files matched your search

+2 -1
View File
@@ -24,7 +24,8 @@ ImageLayer::ImageLayer(const ProjectLayerRecord &record, std::optional<ImportedI
: id(record.id), asset(std::move(asset)), transform(record.transform), name(record.name), isVisible(record.isVisible),
parentID(record.parentID), isGroup(record.isGroup == true), opacity(record.opacity.value_or(1)),
blendMode(record.blendMode.value_or(LayerBlendMode::normal)), maskSourceID(record.maskSourceID), mask(std::move(mask)),
adjustment(record.adjustment), effects(record.effects)
adjustment(record.adjustment), shape(LayerShape::loaded(record.shape, this->asset)), effects(record.effects),
text(LayerText::loaded(record.text, this->asset))
{
}
-6
View File
@@ -32,12 +32,6 @@ void EditorSession::installProject(const ProjectSnapshot &snapshot, const QStrin
m_guideDrag = std::nullopt;
m_transformEdit = std::nullopt;
m_document = CanvasDocument(snapshot);
// Install loads shapes and text; Swift's resize rebuild does not.
for (size_t index = 0; index < m_document->layers.size(); ++index) {
ImageLayer &layer = m_document->layers[index];
layer.shape = LayerShape::loaded(snapshot.manifest.layers[index].shape, layer.asset);
layer.text = LayerText::loaded(snapshot.manifest.layers[index].text, layer.asset);
}
setActiveLayerID(snapshot.manifest.activeLayerID);
m_projectPath = path;
m_renamingLayerID = std::nullopt;
+1 -2
View File
@@ -133,9 +133,8 @@ void EditorSession::mergeFloatingTransform(const TransformEdit &edit, const Floa
layer.transform = merged.transform;
layer.isGroup = false;
layer.mask = merged.mask;
// Swift rebuilds the layer without shape, effects and text.
// Swift rebuilds the layer without shape and text.
layer.shape = std::nullopt;
layer.effects = std::nullopt;
layer.text = std::nullopt;
m_document->selection = moved;
setActiveLayerID(source.id);
+1 -2
View File
@@ -165,9 +165,8 @@ void EditorSession::finishHueSaturationCommit()
beginEdit(QStringLiteral("Hue/Saturation"));
ImageLayer &layer = m_document->layers[index];
layer.asset = ImportedImage(made.pixels->image, made.pixels->thumbnail.value_or(made.pixels->image), layer.name);
// Swift rebuilds the layer without shape, effects and text.
// Swift rebuilds the layer without shape and text.
layer.shape = std::nullopt;
layer.effects = std::nullopt;
layer.text = std::nullopt;
endEdit();
}
+18
View File
@@ -85,6 +85,24 @@ bool LayerEffects::isValid() const
&& (!innerShadow || innerShadow->isValid()) && (!outerGlow || outerGlow->isValid()) && (!innerGlow || innerGlow->isValid());
}
// Swift 1.4.9's Scale Styles for Image Size.
LayerEffects LayerEffects::scaled(double factor) const
{
const auto scale = [factor](double value, double limit) { return std::min(limit, value * factor); };
LayerEffects result = *this;
if (result.stroke)
result.stroke->size = scale(stroke->size, 500);
if (result.shadow)
result.shadow->distance = scale(shadow->distance, 5000), result.shadow->blur = scale(shadow->blur, 500);
if (result.innerShadow)
result.innerShadow->distance = scale(innerShadow->distance, 5000), result.innerShadow->blur = scale(innerShadow->blur, 500);
if (result.outerGlow)
result.outerGlow->size = scale(outerGlow->size, 500);
if (result.innerGlow)
result.innerGlow->size = scale(innerGlow->size, 500);
return result;
}
std::vector<LayerEffectKind> LayerEffects::kinds() const
{
std::vector<LayerEffectKind> result;
+2
View File
@@ -127,6 +127,8 @@ struct LayerEffects {
void setEnabled(bool enabled, LayerEffectKind kind);
// The effects that are switched on.
LayerEffects visible() const;
// Resampled by `factor`: sizes, distances and blurs, within range.
LayerEffects scaled(double factor) const;
friend bool operator==(const LayerEffects &, const LayerEffects &) = default;
};
+1 -2
View File
@@ -361,9 +361,8 @@ void EditorSession::finishLevelsCommit()
beginEdit(QStringLiteral("Levels"));
ImageLayer layer = current;
layer.asset = made.asset;
// Swift rebuilds the layer without shape, effects and text.
// Swift rebuilds the layer without shape and text.
layer.shape = std::nullopt;
layer.effects = std::nullopt;
layer.text = std::nullopt;
m_document->layers[index] = layer;
endEdit();
+1 -2
View File
@@ -211,9 +211,8 @@ void EditorSession::finishInvert()
const ImportedImage asset(*result.image, PixelAdjust::thumbnail(*result.image), current.name);
beginEdit(QStringLiteral("Invert"));
m_document->layers[size_t(index)].asset = asset;
// Swift rebuilds the layer without shape, effects and text.
// Swift rebuilds the layer without shape and text.
m_document->layers[size_t(index)].shape = std::nullopt;
m_document->layers[size_t(index)].effects = std::nullopt;
m_document->layers[size_t(index)].text = std::nullopt;
}
endEdit();
+1 -1
View File
@@ -41,7 +41,7 @@ ProjectSnapshot resized(const ProjectSnapshot &snapshot, const CanvasSizeOptions
.opacity = layer.opacity, .blendMode = layer.blendMode, .maskFile = layer.maskFile,
.maskEnabled = layer.maskEnabled, .maskSourceID = layer.maskSourceID, .adjustment = layer.adjustment,
.maskPlacement = placement,
.maskLinked = layer.maskLinked, .shape = layer.shape,
.maskLinked = layer.maskLinked, .shape = layer.shape, .effects = layer.effects,
.text = layer.text});
}
std::map<QUuid, ImportedImage> images = snapshot.images;
+4 -1
View File
@@ -42,6 +42,8 @@ ProjectSnapshot resized(const ProjectSnapshot &snapshot, const ImageSizeOptions
if (options.width * options.height > DocumentLimits::maxSurfacePixels)
throw ProjectError(ProjectError::Kind::tooLarge);
const double sx = double(options.width) / double(old.width), sy = double(options.height) / double(old.height);
// Effects scale by the factors' mean; text and shapes flatten.
const double effectScale = std::sqrt(sx * sy);
// Guides scale with the pixels.
if (manifest.guides) {
for (CanvasGuide &guide : *manifest.guides)
@@ -108,7 +110,8 @@ ProjectSnapshot resized(const ProjectSnapshot &snapshot, const ImageSizeOptions
.opacity = layer.opacity, .blendMode = layer.blendMode, .maskFile = layer.maskFile,
.maskEnabled = layer.maskEnabled, .maskSourceID = layer.maskSourceID, .adjustment = layer.adjustment,
.maskPlacement = placement,
.maskLinked = layer.maskLinked});
.maskLinked = layer.maskLinked,
.effects = layer.effects ? std::optional(layer.effects->scaled(effectScale)) : std::nullopt});
}
qCInfo(lcIO) << "resized" << old.width << "x" << old.height << "to" << options.width << "x" << options.height;
return {.manifest = manifest, .images = images, .masks = masks};