diff --git a/CHANGELOG.md b/CHANGELOG.md index b70bc5b23..8883a0dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ All notable changes to this project will be documented in this file. - [#1636] Goto usage exception and incorrect trait position - [#1648] Search - loaded search results mixed - [#1650] Empty search results from history after reloading SWF file +- [#1651] FLA Export - mask layers ## [14.1.0] - 2021-03-05 ### Added @@ -2105,6 +2106,7 @@ All notable changes to this project will be documented in this file. [#1332]: https://www.free-decompiler.com/flash/issues/1332 [#1648]: https://www.free-decompiler.com/flash/issues/1648 [#1650]: https://www.free-decompiler.com/flash/issues/1650 +[#1651]: https://www.free-decompiler.com/flash/issues/1651 [#1561]: https://www.free-decompiler.com/flash/issues/1561 [#1623]: https://www.free-decompiler.com/flash/issues/1623 [#1622]: https://www.free-decompiler.com/flash/issues/1622 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index 1ab40ffbf..abd72168c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -2241,7 +2241,7 @@ public class XFLConverter { writer.writeEndElement(); } - private static void convertFrames(String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer) throws XMLStreamException { + private static void convertFrames(List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer) throws XMLStreamException { XFLXmlWriter writer2 = new XFLXmlWriter(); prevStr += ""; int frame = -1; @@ -2372,42 +2372,54 @@ public class XFLConverter { } if (t instanceof ShowFrameTag) { - XFLXmlWriter elementsWriter = new XFLXmlWriter(); - if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId) || shapeTweener != null)) { - ShapeTag shape = (ShapeTag) character; - convertShape(characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter); - shapeTween = false; - shapeTweener = null; - } else if (character != null) { - if (character instanceof MorphShapeTag) { - MorphShapeTag m = (MorphShapeTag) character; - convertShape(characters, matrix, 3, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter); - shapeTween = true; - } else { - shapeTween = false; - if (character instanceof TextTag) { - convertText(instanceName, (TextTag) character, matrix, filters, clipActions, elementsWriter); - } else if (character instanceof DefineVideoStreamTag) { - convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions, elementsWriter); - } else { - convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, characters, tags, flaVersion, elementsWriter); + if (frame + 1 >= startFrame && (onlyFrames == null || onlyFrames.contains(frame + 1))) { + + XFLXmlWriter elementsWriter = new XFLXmlWriter(); + + if (frame + 1 <= endFrame) { + if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId) || shapeTweener != null)) { + ShapeTag shape = (ShapeTag) character; + convertShape(characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter); + shapeTween = false; + shapeTweener = null; + } else if (character != null) { + if (character instanceof MorphShapeTag) { + MorphShapeTag m = (MorphShapeTag) character; + convertShape(characters, matrix, 3, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter); + shapeTween = true; + } else { + shapeTween = false; + if (character instanceof TextTag) { + convertText(instanceName, (TextTag) character, matrix, filters, clipActions, elementsWriter); + } else if (character instanceof DefineVideoStreamTag) { + convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions, elementsWriter); + } else { + convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, characters, tags, flaVersion, elementsWriter); + } + } } } - } + frame++; + String elements = elementsWriter.toString(); + if (!elements.equals(lastElements) && frame > 0) { + convertFrame(lastShapeTween, null, null, frame - duration, duration, "", lastElements, files, writer2); + duration = 1; + } else if (frame == 0) { + duration = 1; + } else { + duration++; + } - frame++; - String elements = elementsWriter.toString(); - if (!elements.equals(lastElements) && frame > 0) { - convertFrame(lastShapeTween, null, null, frame - duration, duration, "", lastElements, files, writer2); - duration = 1; - } else if (frame == 0) { - duration = 1; + lastShapeTween = shapeTween; + lastElements = elements; } else { - duration++; + frame++; + if (frame == 0) { + duration = 1; + } else { + duration++; + } } - - lastShapeTween = shapeTween; - lastElements = elements; } } if (!lastElements.isEmpty()) { @@ -2878,26 +2890,89 @@ public class XFLConverter { } int layerCount = getLayerCount(timelineTags); - Stack parentLayers = new Stack<>(); - for (int d = layerCount; d >= 1; d--, index++) { - for (Tag t : timelineTags) { - if (t instanceof PlaceObjectTypeTag) { - PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; - if (po.getClipDepth() == d) { - for (int m = po.getDepth(); m < po.getClipDepth(); m++) { - parentLayers.push(index); + List clipFrameSplitters = new ArrayList<>(); + List clipPlaces = new ArrayList<>(); + int f = 0; + + Map depthToClipPlace = new HashMap<>(); + Map clipFinishFrames = new HashMap<>(); + for (Tag t : timelineTags) { + if (t instanceof ShowFrameTag) { + f++; + } + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; + if (po.getClipDepth() > -1) { + clipFrameSplitters.add(f); + clipPlaces.add(po); + depthToClipPlace.put(po.getDepth(), po); + } else { + if (!po.flagMove() && depthToClipPlace.containsKey(po.getDepth())) { + clipFinishFrames.put(depthToClipPlace.get(po.getDepth()), f - 1); + depthToClipPlace.remove(po.getDepth()); + } + } + } + if (t instanceof RemoveTag) { + RemoveTag re = (RemoveTag) t; + if (depthToClipPlace.containsKey(re.getDepth())) { + clipFinishFrames.put(depthToClipPlace.get(re.getDepth()), f - 1); + depthToClipPlace.remove(re.getDepth()); + } + } + } + + int frameCount = f; + + if (!depthToClipPlace.isEmpty()) { + for (PlaceObjectTypeTag po : depthToClipPlace.values()) { + clipFinishFrames.put(po, frameCount - 1); + } + } + + if (clipFrameSplitters.isEmpty() || clipFrameSplitters.get(0) != 0) { + clipFrameSplitters.add(0, 0); + clipPlaces.add(0, null); + } + + clipFrameSplitters.add(frameCount); + clipPlaces.add(null); + + Map> depthToFramesList = new HashMap<>(); + for (int d = layerCount; d >= 1; d--) { + depthToFramesList.put(d, new ArrayList<>()); + for (int i = 0; i < frameCount; i++) { + depthToFramesList.get(d).add(i); + } + } + for (int d = layerCount; d >= 1; d--) { + + for (int p = 0; p < clipPlaces.size() - 1; p++) { + PlaceObjectTypeTag po = clipPlaces.get(p); + if (po != null && po.getClipDepth() == d) { + int clipFrame = clipFrameSplitters.get(p); + int nextFrame = clipFinishFrames.get(po); + writer.writeStartElement("DOMLayer", new String[]{ + "name", "Layer " + (index + 1), + "color", randomOutlineColor(), + "layerType", "mask", + "locked", "true"}); + convertFrames(null, clipFrame, nextFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer); + writer.writeEndElement(); + + int parentIndex = index; + index++; + int nd = d; + for (int m = po.getDepth() + 1; m < po.getClipDepth(); m++) { + nd--; + boolean nonEmpty = writeLayer(index, depthToFramesList.get(nd), nd, clipFrame, nextFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files); + for (int i = clipFrame; i <= nextFrame; i++) { + depthToFramesList.get(nd).remove((Integer) i); + } + if (nonEmpty) { + index++; } - - writer.writeStartElement("DOMLayer", new String[]{ - "name", "Layer " + (index + 1), - "color", randomOutlineColor(), - "layerType", "mask", - "locked", "true"}); - convertFrames("", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer); - writer.writeEndElement(); - index++; - break; } } } @@ -2915,35 +2990,12 @@ public class XFLConverter { } } if (hasClipDepth) { - index--; continue; } - int parentLayer = -1; - if (!parentLayers.isEmpty()) { - parentLayer = parentLayers.pop(); - } - XFLXmlWriter layerPrev = new XFLXmlWriter(); - layerPrev.writeStartElement("DOMLayer", new String[]{ - "name", "Layer " + (index + 1), - "color", randomOutlineColor() - }); - if (d == 1) { - layerPrev.writeAttribute("current", true); - layerPrev.writeAttribute("isSelected", true); - } - if (parentLayer != -1) { - if (parentLayer != d) { - layerPrev.writeAttribute("parentLayerIndex", parentLayer); - layerPrev.writeAttribute("locked", true); - } - } - layerPrev.writeCharacters(""); // todo honfika: hack to close start tag - String layerAfter = ""; - int prevLength = writer.length(); - convertFrames(layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer); - if (writer.length() == prevLength) { - index--; + boolean nonEmpty = writeLayer(index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files); + if (nonEmpty) { + index++; } } @@ -2954,6 +3006,29 @@ public class XFLConverter { writer.writeEndElement(); } + private boolean writeLayer(int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files) throws XMLStreamException { + XFLXmlWriter layerPrev = new XFLXmlWriter(); + layerPrev.writeStartElement("DOMLayer", new String[]{ + "name", "Layer " + (index + 1), + "color", randomOutlineColor() + }); + if (d == 1) { + layerPrev.writeAttribute("current", true); + layerPrev.writeAttribute("isSelected", true); + } + if (parentLayer != -1) { + if (parentLayer != d) { + layerPrev.writeAttribute("parentLayerIndex", parentLayer); + layerPrev.writeAttribute("locked", true); + } + } + layerPrev.writeCharacters(""); // todo honfika: hack to close start tag + String layerAfter = ""; + int prevLength = writer.length(); + convertFrames(onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer); + return writer.length() != prevLength; + } + private static void writeFile(AbortRetryIgnoreHandler handler, final byte[] data, final String file) throws IOException, InterruptedException { new RetryTask(() -> { try (FileOutputStream fos = new FileOutputStream(file)) { diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 1b19e2f13..fbff1744e 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -1201,7 +1201,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } private void showSelectedName() { - if (selectedDepth > -1 && frame > -1) { + if (selectedDepth > -1 && frame > -1 && timelined != null) { DepthState ds = timelined.getTimeline().getFrame(frame).layers.get(selectedDepth); if (ds != null) { CharacterTag cht = timelined.getTimeline().swf.getCharacter(ds.characterId); @@ -1573,7 +1573,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { textTag = null; newTextTag = null; - displayObjectCache.clear(); + displayObjectCache.clear(); } private void nextFrame(Timer thisTimer, final int cnt, final int timeShouldBe) {