From 0d19653b8e236fa90d2cab0f92c8f6e685e45557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 20 Dec 2025 14:29:28 +0100 Subject: [PATCH] Fixed: #2589 SVG export - subsprite animation, sprite offsets --- CHANGELOG.md | 2 ++ .../flash/exporters/MorphShapeExporter.java | 6 ++--- .../flash/exporters/ShapeExporter.java | 2 +- .../flash/exporters/TextExporter.java | 2 +- .../exporters/commonshape/SVGExporter.java | 24 ++++++++++++++----- .../flash/tags/DefineEditTextTag.java | 2 +- .../flash/tags/DefineSpriteTag.java | 4 ++-- .../flash/tags/DefineVideoStreamTag.java | 2 +- .../decompiler/flash/tags/base/ButtonTag.java | 2 +- .../flash/tags/base/DrawableTag.java | 4 +++- .../decompiler/flash/tags/base/FontTag.java | 2 +- .../decompiler/flash/tags/base/ImageTag.java | 2 +- .../flash/tags/base/MorphShapeTag.java | 2 +- .../decompiler/flash/tags/base/ShapeTag.java | 2 +- .../flash/tags/base/StaticTextTag.java | 2 +- .../decompiler/flash/timeline/Timeline.java | 15 ++++++++---- 16 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97e5d6f3d..8e66eab9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. - [#2582] Font normalizer setting small texts when no assigned glyph found - PDF export - not rendering video stream frames - [#2585] AS1/2 direct editation - continue and break in for..in loop +- [#2589] SVG export - subsprite animation, sprite offsets ### Changed - [#2575] dumpSWF CLI command only allows single SWF dump (no imports, etc.) @@ -4071,6 +4072,7 @@ Major version of SWF to XML export changed to 2. [#2566]: https://www.free-decompiler.com/flash/issues/2566 [#2582]: https://www.free-decompiler.com/flash/issues/2582 [#2585]: https://www.free-decompiler.com/flash/issues/2585 +[#2589]: https://www.free-decompiler.com/flash/issues/2589 [#2556]: https://www.free-decompiler.com/flash/issues/2556 [#2536]: https://www.free-decompiler.com/flash/issues/2536 [#2537]: https://www.free-decompiler.com/flash/issues/2537 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java index 791592515..30d8711d2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java @@ -131,7 +131,7 @@ public class MorphShapeExporter { rect2.xMin *= settings.zoom; rect2.yMin *= settings.zoom; SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "shape"); - mst.getStartShapeTag().toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); + mst.getStartShapeTag().toSVG(0, 0, exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fileEnd))) { @@ -141,7 +141,7 @@ public class MorphShapeExporter { rect2.xMin *= settings.zoom; rect2.yMin *= settings.zoom; SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "shape"); - mst.getEndShapeTag().toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); + mst.getEndShapeTag().toSVG(0, 0, exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } break; @@ -153,7 +153,7 @@ public class MorphShapeExporter { rect2.xMin *= settings.zoom; rect2.yMin *= settings.zoom; SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "morphshape"); - mst.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); + mst.toSVG(0, 0, exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } break; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java index ed9695d53..1407bb365 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java @@ -117,7 +117,7 @@ public class ShapeExporter { rect2.xMin *= settings.zoom; rect2.yMin *= settings.zoom; SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "shape"); - st.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); + st.toSVG(0, 0, exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } break; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java index 95d8dc112..4ee322d3e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java @@ -91,7 +91,7 @@ public class TextExporter { try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { ExportRectangle rect = new ExportRectangle(textTag.getRect()); SVGExporter exporter = new SVGExporter(rect, settings.zoom, "text"); - textTag.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); + textTag.toSVG(0, 0, exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } }, handler).run(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java index 08f758b88..4cc706d35 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java @@ -123,21 +123,27 @@ public class SVGExporter implements RequiresNormalizedFonts { public final ColorTransform colorTransform; public final int ratio; public final boolean clipped; + public int frame; + public int time; - public ExportKey(Tag tag, ColorTransform colorTransform, int ratio, boolean clipped) { + public ExportKey(Tag tag, ColorTransform colorTransform, int ratio, boolean clipped, int frame, int time) { this.tag = tag; this.colorTransform = colorTransform; this.ratio = ratio; this.clipped = clipped; + this.frame = frame; + this.time = time; } @Override public int hashCode() { int hash = 7; - hash = 79 * hash + Objects.hashCode(this.tag); - hash = 79 * hash + Objects.hashCode(this.colorTransform); - hash = 79 * hash + this.ratio; - hash = 79 * hash + (this.clipped ? 1 : 0); + hash = 11 * hash + Objects.hashCode(this.tag); + hash = 11 * hash + Objects.hashCode(this.colorTransform); + hash = 11 * hash + this.ratio; + hash = 11 * hash + (this.clipped ? 1 : 0); + hash = 11 * hash + this.frame; + hash = 11 * hash + this.time; return hash; } @@ -159,11 +165,17 @@ public class SVGExporter implements RequiresNormalizedFonts { if (this.clipped != other.clipped) { return false; } + if (this.frame != other.frame) { + return false; + } + if (this.time != other.time) { + return false; + } if (!Objects.equals(this.tag, other.tag)) { return false; } return Objects.equals(this.colorTransform, other.colorTransform); - } + } } public SVGExporter(ExportRectangle bounds, double zoom, String objectType) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index a1028751f..7eae29c9b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -1283,7 +1283,7 @@ public class DefineEditTextTag extends TextTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { int realTextId = getSwf().getCharacterId(this); if (exporter.getNormalizedTexts().containsKey(realTextId) && exporter.getNormalizedTexts().get(realTextId) instanceof DefineEditTextTag) { DefineEditTextTag normalizedText = (DefineEditTextTag) exporter.getNormalizedTexts().get(realTextId); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java index 5f73c0999..0248dcf5f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java @@ -466,8 +466,8 @@ public class DefineSpriteTag extends DrawableTag implements Timelined { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { - getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1, transformation, strokeTransformation); + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + getTimeline().toSVG(frame, time, null, 0, exporter, colorTransform, level + 1, transformation, strokeTransformation); } @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java index 691ad02f2..309012cd8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java @@ -420,7 +420,7 @@ public class DefineVideoStreamTag extends DrawableTag implements BoundedTag, Tim } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { } @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index d78e7deef..e1ffb3f28 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -131,7 +131,7 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1, transformation, strokeTransformation); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java index 68b462653..97b5e4cf1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java @@ -109,6 +109,8 @@ public abstract class DrawableTag extends CharacterTag implements BoundedTag { /** * Converts the drawable to SVG. + * @param frame Frame + * @param time Time * @param exporter SVG exporter * @param ratio Ratio * @param colorTransform Color transform @@ -117,7 +119,7 @@ public abstract class DrawableTag extends CharacterTag implements BoundedTag { * @param strokeTransformation Stroke transformation * @throws IOException On I/O error */ - public abstract void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException; + public abstract void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException; /** * Converts the drawable to HTML canvas. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java index 03e0e233d..98c8f5705 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java @@ -724,7 +724,7 @@ public abstract class FontTag extends DrawableTag implements AloneTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java index 7385cf5ac..e1bc66a99 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java @@ -346,7 +346,7 @@ public abstract class ImageTag extends DrawableTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, getShape(1), getCharacterId(), exporter, null, colorTransform, 1, exporter.getZoom(), strokeTransformation); shapeExporter.export(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java index 3ca7ee0e1..325e68867 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java @@ -420,7 +420,7 @@ public abstract class MorphShapeTag extends DrawableTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { if (ratio == -2) { SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java index b89bff41e..de062df83 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java @@ -277,7 +277,7 @@ public abstract class ShapeTag extends DrawableTag implements LazyObject { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { SVGShapeExporter shapeExporter = new SVGShapeExporter(getWindingRule(), getShapeNum(), swf, getShapes(), getCharacterId(), exporter, null, colorTransform, 1, exporter.getZoom(), strokeTransformation); shapeExporter.export(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java index f64af118a..27e42877b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java @@ -1051,7 +1051,7 @@ public abstract class StaticTextTag extends TextTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { + public void toSVG(int frame, int time, SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { int realTextId = getSwf().getCharacterId(this); if (exporter.getNormalizedTexts().containsKey(realTextId) && exporter.getNormalizedTexts().get(realTextId) instanceof StaticTextTag) { StaticTextTag normalizedText = (StaticTextTag) exporter.getNormalizedTexts().get(realTextId); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 522eb2e66..9de69f6ae 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -1963,7 +1963,7 @@ public class Timeline { Tag drawableTag = (Tag) drawable; RECT boundRect = drawable.getRect(); - ExportRectangle rect = new ExportRectangle(boundRect); + //ExportRectangle rect = new ExportRectangle(boundRect); DefineScalingGridTag scalingGrid = character.getScalingGridTag(); @@ -1975,8 +1975,12 @@ public class Timeline { clips.add(clip); } else { boolean createNew = false; + int mtime = time + layer.time; - SVGExporter.ExportKey exportKey = new SVGExporter.ExportKey(drawableTag, clrTrans, layer.ratio, layer.clipDepth > -1); + int dframe = mtime % drawable.getNumFrames(); + int dtime = mtime - dframe; + + SVGExporter.ExportKey exportKey = new SVGExporter.ExportKey(drawableTag, clrTrans, layer.ratio, layer.clipDepth > -1, dframe, dtime); boolean hasSmallStroke = tagHasSmallStrokes(exporter, drawable, absMat); if (!hasSmallStroke && exporter.exportedTags.containsKey(exportKey)) { @@ -1989,11 +1993,12 @@ public class Timeline { createNew = true; } if (createNew) { - exporter.createDefGroup(new ExportRectangle(boundRect), assetName); - drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1, transformation, absMat); + exporter.createDefGroup(new ExportRectangle(boundRect), assetName); + + drawable.toSVG(dframe, dtime, exporter, layer.ratio, clrTrans, level + 1, transformation, absMat); exporter.endGroup(); } - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + Matrix mat = new Matrix(layer.matrix); //Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); exporter.addUse(mat, boundRect, assetName, layer.instanceName, scalingGrid == null ? null : scalingGrid.splitter, String.valueOf(drawable.getCharacterId()), String.join("___", drawable.getClassNames()), layer.blendMode, layer.filters); } }