diff --git a/CHANGELOG.md b/CHANGELOG.md
index c48da43f0..4bc83501a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
- [#1860] FLA export - EmptyStackException during exporting MorphShape
- [#1782] FLA export - exporting from SWF files inside bundles (like binarysearch)
- Expand correct tree on SWF load
+- [#1679] FLA export - MorphShapes (shape tween)
## [16.0.3] - 2022-11-02
### Fixed
@@ -2459,6 +2460,7 @@ All notable changes to this project will be documented in this file.
[alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7
[#1860]: https://www.free-decompiler.com/flash/issues/1860
[#1782]: https://www.free-decompiler.com/flash/issues/1782
+[#1679]: https://www.free-decompiler.com/flash/issues/1679
[#1817]: https://www.free-decompiler.com/flash/issues/1817
[#1816]: https://www.free-decompiler.com/flash/issues/1816
[#1859]: https://www.free-decompiler.com/flash/issues/1859
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 8ea363080..84bf72e52 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
@@ -230,8 +230,8 @@ public abstract class MorphShapeTag extends DrawableTag {
}
StyleChangeRecord scr = scr1.clone();
if (scr1.stateMoveTo || scr2.stateMoveTo) {
- scr.moveDeltaX = startPosX + (endPosX - startPosX) * ratio / MAX_RATIO;
- scr.moveDeltaY = startPosY + (endPosY - startPosY) * ratio / MAX_RATIO;
+ scr.moveDeltaX = startPosX + (int)Math.round((endPosX - startPosX) * ratio / (double)MAX_RATIO);
+ scr.moveDeltaY = startPosY + (int)Math.round((endPosY - startPosY) * ratio / (double)MAX_RATIO);
scr.stateMoveTo = scr.moveDeltaX != posX || scr.moveDeltaY != posY;
}
finalRecords.add(scr);
@@ -264,10 +264,10 @@ public abstract class MorphShapeTag extends DrawableTag {
continue;
}
CurvedEdgeRecord cer = new CurvedEdgeRecord();
- cer.controlDeltaX = cer1.controlDeltaX + (cer2.controlDeltaX - cer1.controlDeltaX) * ratio / MAX_RATIO;
- cer.controlDeltaY = cer1.controlDeltaY + (cer2.controlDeltaY - cer1.controlDeltaY) * ratio / MAX_RATIO;
- cer.anchorDeltaX = cer1.anchorDeltaX + (cer2.anchorDeltaX - cer1.anchorDeltaX) * ratio / MAX_RATIO;
- cer.anchorDeltaY = cer1.anchorDeltaY + (cer2.anchorDeltaY - cer1.anchorDeltaY) * ratio / MAX_RATIO;
+ cer.controlDeltaX = cer1.controlDeltaX + (int)Math.round((cer2.controlDeltaX - cer1.controlDeltaX) * ratio / (double)MAX_RATIO);
+ cer.controlDeltaY = cer1.controlDeltaY + (int)Math.round((cer2.controlDeltaY - cer1.controlDeltaY) * ratio / (double)MAX_RATIO);
+ cer.anchorDeltaX = cer1.anchorDeltaX + (int)Math.round((cer2.anchorDeltaX - cer1.anchorDeltaX) * ratio / (double)MAX_RATIO);
+ cer.anchorDeltaY = cer1.anchorDeltaY + (int)Math.round((cer2.anchorDeltaY - cer1.anchorDeltaY) * ratio / (double)MAX_RATIO);
startPosX += cer1.controlDeltaX + cer1.anchorDeltaX;
startPosY += cer1.controlDeltaY + cer1.anchorDeltaY;
endPosX += cer2.controlDeltaX + cer2.anchorDeltaX;
@@ -290,8 +290,8 @@ public abstract class MorphShapeTag extends DrawableTag {
StraightEdgeRecord ser = new StraightEdgeRecord();
ser.generalLineFlag = true;
ser.vertLineFlag = false;
- ser.deltaX = ser1.deltaX + (ser2.deltaX - ser1.deltaX) * ratio / MAX_RATIO;
- ser.deltaY = ser1.deltaY + (ser2.deltaY - ser1.deltaY) * ratio / MAX_RATIO;
+ ser.deltaX = ser1.deltaX + (int)Math.round((ser2.deltaX - ser1.deltaX) * ratio / (double)MAX_RATIO);
+ ser.deltaY = ser1.deltaY + (int)Math.round((ser2.deltaY - ser1.deltaY) * ratio / (double)MAX_RATIO);
startPosX += ser1.deltaX;
startPosY += ser1.deltaY;
endPosX += ser2.deltaX;
@@ -307,7 +307,7 @@ public abstract class MorphShapeTag extends DrawableTag {
shape.shapeRecords = finalRecords;
return shape;
}
-
+
@Override
public int getUsedParameters() {
return PARAMETER_RATIO;
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 d11b7ade5..c79f73f73 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
@@ -137,11 +137,13 @@ import com.jpexs.helpers.SerializableImage;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.awt.Font;
import java.awt.Point;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -156,6 +158,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -163,11 +167,17 @@ import javax.xml.stream.XMLStreamException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -246,7 +256,7 @@ public class XFLConverter {
}
}
if (!hasMove) {
- ret.append("!").append(startX).append(" ").append(startY);
+ ret.append("! ").append(startX).append(" ").append(startY);
}
for (SHAPERECORD rec : records) {
convertShapeEdge(mat, rec, x, y, ret);
@@ -810,47 +820,34 @@ public class XFLConverter {
strokesNewStr.writeStartElement("strokes");
if (fillStyleCount > 0 || lineStyleCount > 0) {
- if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
-
- boolean empty = false;
- if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
- if (shapeNum == 4) {
- if (strokeStyleOrig > 0) {
- if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles2[strokeStyleOrig]).hasFillFlag) {
- RGBA color = (RGBA) actualLinestyles.lineStyles2[strokeStyleOrig].getColor();
- if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) {
- empty = true;
- }
- }
- }
- }
+ if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
+ currentLayer.writeStartElement("Edge");
+ if (fillStyle0 > -1) {
+ currentLayer.writeAttribute("fillStyle0", fillStyle0);
}
- if (!empty) {
- currentLayer.writeStartElement("Edge");
- if (fillStyle0 > -1) {
- currentLayer.writeAttribute("fillStyle0", fillStyle0);
- }
- if (fillStyle1 > -1) {
- currentLayer.writeAttribute("fillStyle1", fillStyle1);
- }
- if (strokeStyle > -1) {
- currentLayer.writeAttribute("strokeStyle", strokeStyle);
- }
- StringBuilder edgesSb = new StringBuilder();
- convertShapeEdges(startEdgeX, startEdgeY, mat, edges, edgesSb);
- currentLayer.writeAttribute("edges", edgesSb.toString());
- currentLayer.writeEndElement();
- hasEdge = true;
+ if (fillStyle1 > -1) {
+ currentLayer.writeAttribute("fillStyle1", fillStyle1);
}
+ if (strokeStyle > -1) {
+ currentLayer.writeAttribute("strokeStyle", strokeStyle);
+ }
+ StringBuilder edgesSb = new StringBuilder();
+ convertShapeEdges(startEdgeX, startEdgeY, mat, edges, edgesSb);
+ currentLayer.writeAttribute("edges", edgesSb.toString());
+ currentLayer.writeEndElement();
+ hasEdge = true;
}
-
}
if (currentLayer.length() > 0) {
currentLayer.writeEndElement(); // edges
currentLayer.writeEndElement(); // DOMShape
}
if (currentLayer.length() > 0 && hasEdge) { //no empty layers
- layers.add(currentLayer.toString());
+ String s = currentLayer.toString();
+ if (morphshape) {
+ s = removeOnlyStrokeEdgesBeforeSameFilled(s);
+ }
+ layers.add(s);
}
currentLayer.setLength(0);
hasEdge = false;
@@ -905,43 +902,28 @@ public class XFLConverter {
styleChange = true;
}
if (scr.stateLineStyle) {
- strokeStyle = scr.lineStyle;// == 0 ? 0 : lineStyleCount - lastLineStyleCount + scr.lineStyle;
+ strokeStyle = scr.lineStyle;
strokeStyleOrig = scr.lineStyle - 1;
styleChange = true;
}
if (!edges.isEmpty()) {
- if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
- boolean empty = false;
- if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
- if (shapeNum == 4) {
- if (strokeStyleOrig > 0) {
- if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles2[strokeStyleOrig]).hasFillFlag) {
- RGBA color = (RGBA) actualLinestyles.lineStyles2[strokeStyleOrig].getColor();
- if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) {
- empty = true;
- }
- }
- }
- }
+ if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
+ currentLayer.writeStartElement("Edge");
+ if (lastFillStyle0 > -1) {
+ currentLayer.writeAttribute("fillStyle0", lastFillStyle0);
}
- if (!empty) {
- currentLayer.writeStartElement("Edge");
- if (lastFillStyle0 > -1) {
- currentLayer.writeAttribute("fillStyle0", lastFillStyle0);
- }
- if (lastFillStyle1 > -1) {
- currentLayer.writeAttribute("fillStyle1", lastFillStyle1);
- }
- if (lastStrokeStyle > -1) {
- currentLayer.writeAttribute("strokeStyle", lastStrokeStyle);
- }
- StringBuilder edgesSb = new StringBuilder();
- convertShapeEdges(startEdgeX, startEdgeY, mat, edges, edgesSb);
- currentLayer.writeAttribute("edges", edgesSb.toString());
- currentLayer.writeEndElement();
- hasEdge = true;
+ if (lastFillStyle1 > -1) {
+ currentLayer.writeAttribute("fillStyle1", lastFillStyle1);
}
-
+ if (lastStrokeStyle > -1) {
+ currentLayer.writeAttribute("strokeStyle", lastStrokeStyle);
+ }
+ StringBuilder edgesSb = new StringBuilder();
+ convertShapeEdges(startEdgeX, startEdgeY, mat, edges, edgesSb);
+ currentLayer.writeAttribute("edges", edgesSb.toString());
+ currentLayer.writeEndElement();
+ hasEdge = true;
+
startEdgeX = x;
startEdgeY = y;
}
@@ -953,38 +935,22 @@ public class XFLConverter {
y = edge.changeY(y);
}
if (!edges.isEmpty()) {
- if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
-
- boolean empty = false;
- if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
- if (shapeNum == 4) {
- if (strokeStyleOrig > 0) {
- if (actualLinestyles != null && !((LINESTYLE2) actualLinestyles.lineStyles2[strokeStyleOrig]).hasFillFlag) {
- RGBA color = (RGBA) actualLinestyles.lineStyles2[strokeStyleOrig].getColor();
- if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0) {
- empty = true;
- }
- }
- }
- }
+ if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
+ currentLayer.writeStartElement("Edge");
+ if (fillStyle0 > -1) {
+ currentLayer.writeAttribute("fillStyle0", fillStyle0);
}
- if (!empty) {
- currentLayer.writeStartElement("Edge");
- if (fillStyle0 > -1) {
- currentLayer.writeAttribute("fillStyle0", fillStyle0);
- }
- if (fillStyle1 > -1) {
- currentLayer.writeAttribute("fillStyle1", fillStyle1);
- }
- if (strokeStyle > -1) {
- currentLayer.writeAttribute("strokeStyle", strokeStyle);
- }
- StringBuilder edgesSb = new StringBuilder();
- convertShapeEdges(startEdgeX, startEdgeY, mat, edges, edgesSb);
- currentLayer.writeAttribute("edges", edgesSb.toString());
- currentLayer.writeEndElement();
- hasEdge = true;
+ if (fillStyle1 > -1) {
+ currentLayer.writeAttribute("fillStyle1", fillStyle1);
}
+ if (strokeStyle > -1) {
+ currentLayer.writeAttribute("strokeStyle", strokeStyle);
+ }
+ StringBuilder edgesSb = new StringBuilder();
+ convertShapeEdges(startEdgeX, startEdgeY, mat, edges, edgesSb);
+ currentLayer.writeAttribute("edges", edgesSb.toString());
+ currentLayer.writeEndElement();
+ hasEdge = true;
}
}
edges.clear();
@@ -993,11 +959,86 @@ public class XFLConverter {
currentLayer.writeEndElement(); // DOMShape
if (currentLayer.length() > 0 && hasEdge) { //no empty layers
- layers.add(currentLayer.toString());
+ String s = currentLayer.toString();
+ if (morphshape) {
+ s = removeOnlyStrokeEdgesBeforeSameFilled(s);
+ }
+ layers.add(s);
}
}
return layers;
}
+
+ /**
+ * A hack. This will remove a stroked path with no fill which has same stroke as subsequent path.
+ * This happens in the morphshape edges. This needs to be cleaned up before exporting to FLA.
+ *
+ * @param layer
+ * @return
+ */
+ private static String removeOnlyStrokeEdgesBeforeSameFilled(String layer) {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+
+ dbf.setNamespaceAware(false);
+ dbf.setValidating(false);
+ DocumentBuilder db;
+ try {
+ db = dbf.newDocumentBuilder();
+ String docString = "" + layer + "";
+ Document doc = db.parse(new ByteArrayInputStream(docString.getBytes("UTF-8")));
+ NodeList edgesParentList = doc.getElementsByTagName("edges");
+ String prevStrokeOnly = null;
+ String prevEdgesStr = "";
+ for (int j = 0; j < edgesParentList.getLength(); j++) {
+ Node edgesParent = edgesParentList.item(j);
+ NodeList edges = edgesParent.getChildNodes();
+ Node prevNode = null;
+ for (int i = 0; i < edges.getLength(); i++) {
+ Node edge = edges.item(i);
+ if (edge.getNodeType() == Node.TEXT_NODE) {
+ continue;
+ }
+ NamedNodeMap attributes = edge.getAttributes();
+ Node strokeStyleNode = attributes.getNamedItem("strokeStyle");
+ Node fillStyle0Node = attributes.getNamedItem("fillStyle0");
+ Node fillStyle1Node = attributes.getNamedItem("fillStyle1");
+ Node edgesNode = attributes.getNamedItem("edges");
+ String edgesStr = edgesNode.getNodeValue();
+
+ String strokeStyle = strokeStyleNode != null ? strokeStyleNode.getNodeValue() : null;
+ String fillStyle0 = fillStyle0Node != null ? fillStyle0Node.getNodeValue() : null;
+ String fillStyle1 = fillStyle1Node != null ? fillStyle1Node.getNodeValue() : null;
+
+ if (prevStrokeOnly != null &&
+ strokeStyle != null &&
+ strokeStyle.equals(prevStrokeOnly) &&
+ prevEdgesStr.equals(edgesStr)) {
+ Node edgeToRemove = prevNode;
+ edgeToRemove.getParentNode().removeChild(edgeToRemove);
+ }
+
+ prevStrokeOnly = null;
+ if (strokeStyle != null && fillStyle0 == null && fillStyle1 == null) {
+ prevStrokeOnly = strokeStyle;
+ }
+ prevNode = edge;
+ prevEdgesStr = edgesStr;
+ }
+ }
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(doc), new StreamResult(writer));
+ String output = writer.getBuffer().toString();
+ output = output.trim();
+ output = output.substring(3, output.length() - 4);
+ return output;
+ } catch (TransformerException | SAXException | IOException | ParserConfigurationException ex) {
+ Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return layer;
+ }
private static int getLayerCount(ReadOnlyTagList tags) {
int maxDepth = 0;
@@ -2269,8 +2310,8 @@ public class XFLConverter {
int characterId = -1;
int ratio = -1;
boolean shapeTween = false;
- boolean lastShapeTween = false;
MorphShapeTag shapeTweener = null;
+ int lastTweenRatio = -1;
//Add ShowFrameTag to the end when there is one last missing
List timTags = timelineTags.toArrayList();
@@ -2360,7 +2401,8 @@ public class XFLConverter {
if (shapeTween && character != null) {
MorphShapeTag m = (MorphShapeTag) character;
shapeTweener = m;
- shapeTween = false;
+ shapeTween = false;
+ lastTweenRatio = ratio;
}
character = null;
metadata = null;
@@ -2384,7 +2426,18 @@ public class XFLConverter {
if (frame + 1 <= endFrame) {
lastIn = true;
- if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId) || shapeTweener != null)) {
+ if (shapeTweener != null) {
+ MorphShapeTag m = shapeTweener;
+ XFLXmlWriter addLastWriter = new XFLXmlWriter();
+ SHAPEWITHSTYLE endShape = m.getShapeAtRatio(lastTweenRatio);
+ convertShape(characters, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(lastTweenRatio), m.getLineStyles().getLineStylesAt(m.getShapeNum(), lastTweenRatio), true, false, addLastWriter);
+ duration--;
+ convertFrame(true, null, null, frame - duration, duration, "", lastElements, files, writer2);
+ duration = 1;
+ lastElements = addLastWriter.toString();
+ shapeTweener = null;
+ }
+ 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;
@@ -2409,7 +2462,7 @@ public class XFLConverter {
frame++;
String elements = elementsWriter.toString();
if (!elements.equals(lastElements) && frame > 0) {
- convertFrame(lastShapeTween, null, null, frame - duration, duration, "", lastElements, files, writer2);
+ convertFrame(false, null, null, frame - duration, duration, "", lastElements, files, writer2);
duration = 1;
} else if (frame == 0) {
duration = 1;
@@ -2417,19 +2470,16 @@ public class XFLConverter {
duration++;
}
- lastShapeTween = shapeTween;
lastElements = elements;
if (frame > endFrame) {
if (lastIn) {
lastElements = "";
- lastShapeTween = false;
lastIn = false;
}
}
} else {
if (lastIn) {
lastElements = "";
- lastShapeTween = false;
lastIn = false;
}
frame++;
@@ -2443,7 +2493,7 @@ public class XFLConverter {
}
if (!lastElements.isEmpty()) {
frame++;
- convertFrame(lastShapeTween, null, null, (frame - duration < 0 ? 0 : frame - duration), duration, "", lastElements, files, writer2);
+ convertFrame(false, null, null, (frame - duration < 0 ? 0 : frame - duration), duration, "", lastElements, files, writer2);
}
afterStr = "" + afterStr;