Fixed #1679 FLA export - MorphShapes (shape tween)

This commit is contained in:
Jindra Petřík
2022-11-03 19:51:43 +01:00
parent 88440526d6
commit 6d0597eece
3 changed files with 164 additions and 112 deletions

View File

@@ -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;

View File

@@ -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 = "<x>" + layer + "</x>";
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<Tag> 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 = "</frames>" + afterStr;