Fixed #2123 FLA export - morphshapes fixer

More FLA export progress info
This commit is contained in:
Jindra Petřík
2023-12-30 18:06:08 +01:00
parent fa03866191
commit 41f6e40369
14 changed files with 1206 additions and 217 deletions
@@ -17,6 +17,10 @@
package com.jpexs.decompiler.flash.math;
import com.jpexs.helpers.Reference;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
@@ -489,12 +493,68 @@ public class BezierEdge implements Serializable {
BezierEdge qm = new BezierEdge(-1957, 2676, -1957, 2676.5, -1957.5, 2676.5);
BezierEdge qn = new BezierEdge(-1957, 2675.5, -1957, 2676, -1957, 2676);
List<Point2D> ps = new ArrayList<>();
/*List<Point2D> ps = new ArrayList<>();
qm.intersects(qn, t1, t2, ps);
System.err.println("t1 is " + t1);
System.err.println("t2 is " + t2);
System.err.println("intersections is " + ps);
*/
/*Shape r1 = new Rectangle2D.Double(0, 0, 200, 100);
GeneralPath r2 = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
r2.moveTo(0, 0);
r2.lineTo(100, 0);
r2.lineTo(150, 0);
r2.lineTo(200, 0);
r2.lineTo(200, 100);
r2.lineTo(0, 100);
r2.closePath();
GeneralPath sh1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
sh1.moveTo(0, 0);
sh1.quadTo(100, 100, 0, 100);
sh1.lineTo(0, 0);
sh1.closePath();
BezierEdge bex = new BezierEdge(0, 0, 100, 100, 0, 100);
Reference<BezierEdge> bex1Ref = new Reference<>(null);
Reference<BezierEdge> bex2Ref = new Reference<>(null);
bex.split(0.2, bex1Ref, bex2Ref);
BezierEdge bex1 = bex1Ref.getVal();
BezierEdge bex2 = bex2Ref.getVal();
GeneralPath sh2 = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
sh2.moveTo(bex1.getBeginPoint().getX(), bex1.getBeginPoint().getY());
sh2.quadTo(10+bex1.points.get(1).getX(), bex1.points.get(1).getY(), bex1.getEndPoint().getX(), bex1.getEndPoint().getY());
sh2.quadTo(bex2.points.get(1).getX(), bex2.points.get(1).getY(), bex2.getEndPoint().getX(), bex2.getEndPoint().getY());
sh2.lineTo(0, 0);
sh2.closePath();
Area a1 = new Area(r1);
Area a2 = new Area(r2);
//a1.exclusiveOr(a2);
*/
/*GeneralPath p1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
p1.moveTo(0, 0);
p1.lineTo(200, 0);
p1.lineTo(200, 200);
p1.lineTo(0, 200);
p1.lineTo(0, 0);
p1.closePath();
p1.moveTo(50, 50);
p1.lineTo(150, 50);
p1.lineTo(150, 150);
p1.lineTo(50, 150);
p1.lineTo(50, 50);
p1.closePath();
System.err.println("cont:" + p1.contains(100, 100));
System.err.println("cont:" + p1.contains(150, 150)); */
//System.err.println("minDist = " + minDist+", maxDist = "+ maxDist);
//System.err.println("eArea = " + Areas.calcArea(a1));
/*Point2D c = new Point2D.Double(
(1 - t1.get(0)) * be5.points.get(0).getX() + t1.get(0) * be5.points.get(1).getX(),
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.math;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author JPEXS
*/
public class Distances {
public static double getBatchDistance(List<BezierEdge> batch1, List<BezierEdge> batch2) {
Area a1 = batchToArea(batch1);
Area a2 = batchToArea(batch2);
return areaDist(a1, a2);
}
private static Area batchToArea(List<BezierEdge> batch) {
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
if (batch.isEmpty()) {
return new Area();
}
path.moveTo(batch.get(0).getBeginPoint().getX(), batch.get(0).getBeginPoint().getY());
for (BezierEdge be : batch) {
if (be.points.size() == 3) {
path.quadTo(be.points.get(1).getX(),
be.points.get(1).getY(),
be.points.get(2).getX(),
be.points.get(2).getY());
} else {
path.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY());
}
}
path.closePath();
return new Area(path);
}
private static double areaDist(Area a1, Area a2) {
List<Point2D> points1 = getAreaPoints(a1);
List<Point2D> points2 = getAreaPoints(a2);
double minDist = Double.MAX_VALUE;
double maxDist = 0;
for (Point2D p : points1) {
double dist = Double.MAX_VALUE;
for (Point2D p2 : points2) {
double d = p.distance(p2);
if (d < dist) {
dist = d;
}
}
if (dist < minDist) {
minDist = dist;
}
if (dist > maxDist) {
maxDist = dist;
}
}
return maxDist;
}
private static List<Point2D> getAreaPoints(Area area) {
double F = 1.0;
List<Point2D> points = new ArrayList<>();
PathIterator pi = area.getPathIterator(null, 0.1);
double[] coords = new double[6];
double xPrev = 0;
double yPrev = 0;
double xBegin = 0;
double yBegin = 0;
while (!pi.isDone()) {
int code = pi.currentSegment(coords);
switch (code) {
case PathIterator.SEG_MOVETO:
xBegin = coords[0];
yBegin = coords[1];
points.add(new Point2D.Double(xBegin, yBegin));
break;
case PathIterator.SEG_LINETO:
//coords[0], coords[1]
//Point2D np = new Point2D.Double(coords[0], coords[1]);
double dx = coords[0] - xPrev;
double dy = coords[1] - yPrev;
double dz = Math.sqrt(dx * dx + dy * dy);
double divisor;
/*if (dy > dx) {
divisor = dy / F;
} else {
divisor = dx / F;
}*/
divisor = dz / F;
for (int d = 1; d <= divisor; d++) {
Point2D p2 = new Point2D.Double(xPrev + d * dx / divisor, yPrev + d * dy / divisor);
points.add(p2);
}
//points.add(np);
//System.err.println("dx, dy: "+dx+", "+dy);
break;
case PathIterator.SEG_CLOSE:
break;
default:
throw new RuntimeException("Curved edge not expected");
}
xPrev = coords[0];
yPrev = coords[1];
//System.err.println("pos: "+xPrev+", "+ yPrev);
pi.next();
}
return points;
}
}
@@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.decompiler.flash.types.annotations.SWFVersion;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.Set;
/**
@@ -248,4 +249,52 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ
return morphFillStyle;
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + this.fillStyleType;
hash = 23 * hash + (this.inShape3 ? 1 : 0);
hash = 23 * hash + Objects.hashCode(this.color);
hash = 23 * hash + Objects.hashCode(this.gradientMatrix);
hash = 23 * hash + Objects.hashCode(this.gradient);
hash = 23 * hash + this.bitmapId;
hash = 23 * hash + Objects.hashCode(this.bitmapMatrix);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final FILLSTYLE other = (FILLSTYLE) obj;
if (this.fillStyleType != other.fillStyleType) {
return false;
}
if (this.inShape3 != other.inShape3) {
return false;
}
if (this.bitmapId != other.bitmapId) {
return false;
}
if (!Objects.equals(this.color, other.color)) {
return false;
}
if (!Objects.equals(this.gradientMatrix, other.gradientMatrix)) {
return false;
}
if (!Objects.equals(this.gradient, other.gradient)) {
return false;
}
return Objects.equals(this.bitmapMatrix, other.bitmapMatrix);
}
}
@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.types.annotations.EnumValue;
import com.jpexs.decompiler.flash.types.annotations.SWFArray;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import java.io.Serializable;
import java.util.Arrays;
/**
*
@@ -109,4 +110,36 @@ public class GRADIENT implements Serializable {
}
return morphGradient;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + this.spreadMode;
hash = 97 * hash + this.interpolationMode;
hash = 97 * hash + Arrays.deepHashCode(this.gradientRecords);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final GRADIENT other = (GRADIENT) obj;
if (this.spreadMode != other.spreadMode) {
return false;
}
if (this.interpolationMode != other.interpolationMode) {
return false;
}
return Arrays.deepEquals(this.gradientRecords, other.gradientRecords);
}
}
@@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.types;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import java.io.Serializable;
import java.util.Objects;
/**
*
@@ -55,4 +56,36 @@ public class GRADRECORD implements Serializable {
morphGradRecord.endRatio = endGradRecord.ratio;
return morphGradRecord;
}
@Override
public int hashCode() {
int hash = 5;
hash = 19 * hash + this.ratio;
hash = 19 * hash + (this.inShape3 ? 1 : 0);
hash = 19 * hash + Objects.hashCode(this.color);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final GRADRECORD other = (GRADRECORD) obj;
if (this.ratio != other.ratio) {
return false;
}
if (this.inShape3 != other.inShape3) {
return false;
}
return Objects.equals(this.color, other.color);
}
}
@@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.tags.base.NeedsCharacters;
import com.jpexs.decompiler.flash.types.annotations.ConditionalType;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
/**
@@ -84,4 +85,32 @@ public class LINESTYLE implements NeedsCharacters, Serializable, ILINESTYLE {
morphLineStyle.endWidth = width;
return morphLineStyle;
}
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + this.width;
hash = 53 * hash + Objects.hashCode(this.color);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final LINESTYLE other = (LINESTYLE) obj;
if (this.width != other.width) {
return false;
}
return Objects.equals(this.color, other.color);
}
}
@@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.types.annotations.EnumValue;
import com.jpexs.decompiler.flash.types.annotations.Reserved;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
/**
@@ -232,4 +233,76 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE {
}
return morphLineStyle2;
}
@Override
public int hashCode() {
int hash = 7;
hash = 83 * hash + this.width;
hash = 83 * hash + this.startCapStyle;
hash = 83 * hash + this.joinStyle;
hash = 83 * hash + (this.hasFillFlag ? 1 : 0);
hash = 83 * hash + (this.noHScaleFlag ? 1 : 0);
hash = 83 * hash + (this.noVScaleFlag ? 1 : 0);
hash = 83 * hash + (this.pixelHintingFlag ? 1 : 0);
hash = 83 * hash + this.reserved;
hash = 83 * hash + (this.noClose ? 1 : 0);
hash = 83 * hash + this.endCapStyle;
hash = 83 * hash + Float.floatToIntBits(this.miterLimitFactor);
hash = 83 * hash + Objects.hashCode(this.color);
hash = 83 * hash + Objects.hashCode(this.fillType);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final LINESTYLE2 other = (LINESTYLE2) obj;
if (this.width != other.width) {
return false;
}
if (this.startCapStyle != other.startCapStyle) {
return false;
}
if (this.joinStyle != other.joinStyle) {
return false;
}
if (this.hasFillFlag != other.hasFillFlag) {
return false;
}
if (this.noHScaleFlag != other.noHScaleFlag) {
return false;
}
if (this.noVScaleFlag != other.noVScaleFlag) {
return false;
}
if (this.pixelHintingFlag != other.pixelHintingFlag) {
return false;
}
if (this.reserved != other.reserved) {
return false;
}
if (this.noClose != other.noClose) {
return false;
}
if (this.endCapStyle != other.endCapStyle) {
return false;
}
if (Float.floatToIntBits(this.miterLimitFactor) != Float.floatToIntBits(other.miterLimitFactor)) {
return false;
}
if (!Objects.equals(this.color, other.color)) {
return false;
}
return Objects.equals(this.fillType, other.fillType);
}
}
@@ -112,4 +112,36 @@ public class RGB implements Serializable {
return "[RGB red:" + red + ", green:" + green + ", blue:" + blue + "]";
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + this.red;
hash = 97 * hash + this.green;
hash = 97 * hash + this.blue;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final RGB other = (RGB) obj;
if (this.red != other.red) {
return false;
}
if (this.green != other.green) {
return false;
}
return this.blue == other.blue;
}
}
@@ -110,4 +110,54 @@ public class RGBA extends RGB implements Serializable {
return "[RGB red:" + red + ", green:" + green + ", blue:" + blue + ", alpha:" + alpha + "]";
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + this.red;
hash = 97 * hash + this.green;
hash = 97 * hash + this.blue;
hash = 97 * hash + this.alpha;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof RGB) {
final RGB other = (RGBA) obj;
if (this.alpha != 255) {
return false;
}
if (this.red != other.red) {
return false;
}
if (this.green != other.green) {
return false;
}
if (this.blue != other.blue) {
return false;
}
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
final RGBA other = (RGBA) obj;
if (this.red != other.red) {
return false;
}
if (this.green != other.green) {
return false;
}
if (this.blue != other.blue) {
return false;
}
return this.alpha == other.alpha;
}
}
@@ -241,6 +241,34 @@ public class XFLConverter {
EDGE_DECIMAL_FORMAT.setGroupingUsed(false);
}
private static class StatusStack {
private Stack<String> statuses = new Stack<>();
private ProgressListener progressListener;
public StatusStack(ProgressListener progressListener) {
this.progressListener = progressListener;
}
private void update() {
if (progressListener == null) {
return;
}
String status = String.join(", ", statuses);
progressListener.status(status);
}
public void pushStatus(String status) {
statuses.push(status);
update();
}
public void popStatus() {
statuses.pop();
update();
}
}
private static String formatEdgeDouble(double value, boolean curved, boolean morphshape) {
if (value % 1 == 0) {
return "" + (int) value;
@@ -783,9 +811,10 @@ public class XFLConverter {
}
List<ShapeRecordAdvanced> shapeRecordsAdvanced;
useFixer = true;
if (useFixer) {
ShapeFixer fixer = new ShapeFixer();
shapeRecordsAdvanced = fixer.fix(shapeRecords);
shapeRecordsAdvanced = fixer.fix(shapeRecords, morphshape, shapeNum, fillStyles, lineStyles);
} else {
shapeRecordsAdvanced = new ArrayList<>();
for (SHAPERECORD rec : shapeRecords) {
@@ -1029,6 +1058,10 @@ public class XFLConverter {
* @return
*/
private static String removeOnlyStrokeEdgesBeforeSameFilled(String layer) {
if (true) {
//This is moved to ShapeFixer
return layer;
}
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(false);
@@ -1061,6 +1094,16 @@ public class XFLConverter {
String fillStyle0 = fillStyle0Node != null ? fillStyle0Node.getNodeValue() : null;
String fillStyle1 = fillStyle1Node != null ? fillStyle1Node.getNodeValue() : null;
if ("0".equals(fillStyle0)) {
fillStyle0 = null;
}
if ("0".equals(fillStyle1)) {
fillStyle1 = null;
}
if ("0".equals(strokeStyle)) {
strokeStyle = null;
}
if (prevStrokeOnly != null
&& strokeStyle != null
&& strokeStyle.equals(prevStrokeOnly)
@@ -1631,29 +1674,33 @@ public class XFLConverter {
return date.getTime() / 1000;
}
private void convertLibrary(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, Map<Integer, ScriptPack> characterScriptPacks, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, ProgressListener progressListener) throws XMLStreamException {
private void convertLibrary(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, Map<Integer, ScriptPack> characterScriptPacks, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
//TODO: Imported assets
//linkageImportForRS="true" linkageIdentifier="xxx" linkageURL="yyy.swf"
convertMedia(swf, characterVariables, characterClasses, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, progressListener);
convertSymbols(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, progressListener);
statusStack.pushStatus("media");
convertMedia(swf, characterVariables, characterClasses, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, statusStack);
statusStack.popStatus();
statusStack.pushStatus("symbols");
convertSymbols(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
statusStack.popStatus();
}
private void convertSymbols(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, Map<Integer, ScriptPack> characterScriptPacks, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, ProgressListener progressListener) throws XMLStreamException {
private void convertSymbols(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, Map<Integer, ScriptPack> characterScriptPacks, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
//boolean hasSymbol = false;
Reference<Integer> nextClipId = new Reference<>(-1);
writer.writeStartElement("symbols");
for (int ch : characters.keySet()) {
CharacterTag symbol = characters.get(ch);
if (progressListener != null && symbol != null) {
progressListener.status(symbol.toString());
}
if (symbol == null) {
continue;
}
if ((symbol instanceof ShapeTag) && nonLibraryShapes.contains(symbol.getCharacterId())) {
continue; //shapes with 1 ocurrence and single layer are not added to library
}
}
if ((symbol instanceof ShapeTag) || (symbol instanceof DefineSpriteTag) || (symbol instanceof ButtonTag)) {
statusStack.pushStatus(symbol.toString());
XFLXmlWriter symbolStr = new XFLXmlWriter();
symbolStr.writeStartElement("DOMSymbolItem", new String[]{
@@ -1848,13 +1895,14 @@ public class XFLConverter {
} else if (symbol instanceof DefineSpriteTag) {
DefineSpriteTag sprite = (DefineSpriteTag) symbol;
if (sprite.getTags().isEmpty()) { //probably AS2 class
statusStack.popStatus();
continue;
}
final ScriptPack spriteScriptPack = characterScriptPacks.containsKey(sprite.spriteId) ? characterScriptPacks.get(sprite.spriteId) : null;
extractMultilevelClips(sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes);
extractMultilevelClips(sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
convertTimeline(swf, swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes);
convertTimeline(swf, swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
} else if (symbol instanceof ShapeTag) {
symbolStr.writeStartElement("timeline");
@@ -1888,19 +1936,25 @@ public class XFLConverter {
//TODO: itemID="518de416-00000341"
}
writer.writeEndElement();
//hasSymbol = true;
}
//hasSymbol = true;
statusStack.popStatus();
}
}
extractMultilevelClips(swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes);
extractMultiUsageMorphShapes(writer, swf, nonLibraryShapes, backgroundColor, characters, flaVersion, files, multiUsageMorphShapes);
statusStack.pushStatus("extracting multilevel clips");
extractMultilevelClips(swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
statusStack.popStatus();
statusStack.pushStatus("converting multiusage morphshapes");
extractMultiUsageMorphShapes(writer, swf, nonLibraryShapes, backgroundColor, characters, flaVersion, files, multiUsageMorphShapes, statusStack);
statusStack.popStatus();
/*if (hasSymbol) {
}*/
writer.writeEndElement();
}
private void convertMedia(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, ProgressListener progressListener) throws XMLStreamException {
private void convertMedia(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException {
boolean hasMedia = false;
for (int ch : characters.keySet()) {
CharacterTag symbol = characters.get(ch);
@@ -1923,10 +1977,7 @@ public class XFLConverter {
CharacterTag symbol = characters.get(ch);
if (symbol instanceof ImageTag) {
if (progressListener != null) {
progressListener.status(symbol.toString());
}
statusStack.pushStatus(symbol.toString());
ImageTag imageTag = (ImageTag) symbol;
boolean allowSmoothing = false;
@@ -2013,7 +2064,9 @@ public class XFLConverter {
writer.writeAttribute("frameBottom", image.getHeight());
writer.writeEndElement();
mediaCount++;
statusStack.popStatus();
} else if (/*(symbol instanceof SoundStreamHeadTypeTag) || FIXME */(symbol instanceof DefineSoundTag)) {
statusStack.pushStatus(symbol.toString());
int soundFormat = 0;
int soundRate = 0;
boolean soundType = false;
@@ -2180,7 +2233,9 @@ public class XFLConverter {
writer.writeEndElement();
mediaCount++;
statusStack.popStatus();
} else if (symbol instanceof DefineVideoStreamTag) {
statusStack.pushStatus(symbol.toString());
DefineVideoStreamTag video = (DefineVideoStreamTag) symbol;
String videoType = "no media";
switch (video.codecID) {
@@ -2260,6 +2315,7 @@ public class XFLConverter {
}
mediaCount++;
statusStack.popStatus();
}
}
@@ -2446,7 +2502,7 @@ public class XFLConverter {
writer.writeEndElement();
}
private static void convertFrames(SWF swf, List<Integer> onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List<Integer> nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap<Integer, CharacterTag> characters, int depth, FLAVersion flaVersion, HashMap<String, byte[]> files, XFLXmlWriter writer, List<Integer> multiUsageMorphShapes) throws XMLStreamException {
private static void convertFrames(SWF swf, List<Integer> onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List<Integer> nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap<Integer, CharacterTag> characters, int depth, FLAVersion flaVersion, HashMap<String, byte[]> files, XFLXmlWriter writer, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
boolean lastIn = true;
XFLXmlWriter writer2 = new XFLXmlWriter();
prevStr += "<frames>";
@@ -2648,8 +2704,10 @@ public class XFLConverter {
} else {
if (lastCharacter == m && Objects.equals(matrix, lastMatrix)) {
elementsWriter.writeCharactersRaw(lastElements);
} else {
} else {
statusStack.pushStatus(m.toString());
convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter, false);
statusStack.popStatus();
}
lastCharacter = m;
@@ -2659,7 +2717,9 @@ public class XFLConverter {
} else {
shapeTween = false;
if (character instanceof TextTag) {
statusStack.pushStatus(character.toString());
convertText(instanceName, (TextTag) character, matrix, filters, clipActions, elementsWriter);
statusStack.popStatus();
} else if (character instanceof DefineVideoStreamTag) {
convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions, elementsWriter);
} else if (character instanceof ImageTag) {
@@ -2724,7 +2784,7 @@ public class XFLConverter {
}
}
private static void convertFonts(Map<Integer, String> characterClasses, ReadOnlyTagList tags, XFLXmlWriter writer, ProgressListener progressListener) throws XMLStreamException {
private static void convertFonts(Map<Integer, String> characterClasses, ReadOnlyTagList tags, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException {
boolean hasFont = false;
for (Tag t : tags) {
if (t instanceof FontTag) {
@@ -2744,9 +2804,7 @@ public class XFLConverter {
for (Tag t : tags) {
if (t instanceof FontTag) {
if (progressListener != null) {
progressListener.status(t.toString());
}
statusStack.pushStatus(t.toString());
SWF swf = t.getSwf();
FontTag font = (FontTag) t;
int fontId = font.getFontId();
@@ -2767,6 +2825,7 @@ public class XFLConverter {
String fontChars = font.getCharacters();
if ("".equals(fontChars)) {
statusStack.popStatus();
continue;
}
String embeddedCharacters = fontChars;
@@ -2827,6 +2886,7 @@ public class XFLConverter {
}
writer.writeEndElement();
statusStack.popStatus();
}
}
@@ -3214,7 +3274,8 @@ public class XFLConverter {
FLAVersion flaVersion,
HashMap<String, byte[]> files,
Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol,
List<Integer> multiUsageMorphShapes
List<Integer> multiUsageMorphShapes,
StatusStack statusStack
) throws XMLStreamException {
XFLXmlWriter symbolStr = new XFLXmlWriter();
@@ -3233,7 +3294,7 @@ public class XFLConverter {
"lastModified", Long.toString(getTimestamp(swf))});
symbolStr.writeAttribute("symbolType", "graphic");
convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes);
convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
symbolStr.writeEndElement(); // DOMSymbolItem
String symbolStr2 = prettyFormatXML(symbolStr.toString());
@@ -3249,7 +3310,7 @@ public class XFLConverter {
}
writer.writeEndElement();
extractMultilevelClips(timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes);
extractMultilevelClips(timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
}
private String generateMaskedSymbolName(int symbolId) {
@@ -3320,10 +3381,13 @@ public class XFLConverter {
HashMap<Integer, CharacterTag> characters,
FLAVersion flaVersion,
HashMap<String, byte[]> files,
List<Integer> multiUsageMorphShapes
List<Integer> multiUsageMorphShapes,
StatusStack statusStack
) throws XMLStreamException {
for (int objectId : multiUsageMorphShapes) {
statusStack.pushStatus(characters.get(objectId).toString());
XFLXmlWriter symbolStr = new XFLXmlWriter();
symbolStr.writeStartElement("DOMSymbolItem", new String[]{
"xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance",
@@ -3348,7 +3412,7 @@ public class XFLConverter {
}
}
convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, swf.getTags(), new ReadOnlyTagList(timelineTags), characters, "Symbol " + objectId, flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>());
convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, swf.getTags(), new ReadOnlyTagList(timelineTags), characters, "Symbol " + objectId, flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>(), statusStack);
symbolStr.writeEndElement(); // DOMSymbolItem
String symbolStr2 = prettyFormatXML(symbolStr.toString());
@@ -3363,6 +3427,7 @@ public class XFLConverter {
//TODO: itemID="518de416-00000341"
}
writer.writeEndElement();
statusStack.popStatus();
}
}
@@ -3376,7 +3441,8 @@ public class XFLConverter {
FLAVersion flaVersion,
HashMap<String, byte[]> files,
Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol,
List<Integer> multiUsageMorphShapes
List<Integer> multiUsageMorphShapes,
StatusStack statusStack
) throws XMLStreamException {
int f = 0;
@@ -3642,14 +3708,14 @@ public class XFLConverter {
}
}
System.err.println("clip repeats in " + found);*/
addExtractedClip(new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes);
addExtractedClip(new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
placeToMaskedSymbol.put(secondPlace, new MultiLevelClip(secondPlace, nextClipId.getVal(), numFrames));
}
}
}
}
private void convertTimeline(SWF swf, AbcIndexing abcIndex, int spriteId, String linkageIdentifier, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap<Integer, CharacterTag> characters, String name, FLAVersion flaVersion, HashMap<String, byte[]> files, XFLXmlWriter writer, ScriptPack scriptPack, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes) throws XMLStreamException {
private void convertTimeline(SWF swf, AbcIndexing abcIndex, int spriteId, String linkageIdentifier, List<Integer> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap<Integer, CharacterTag> characters, String name, FLAVersion flaVersion, HashMap<String, byte[]> files, XFLXmlWriter writer, ScriptPack scriptPack, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
List<String> classNames = new ArrayList<>();
//Searches for Object.registerClass("linkageIdentifier",mypkg.MyClass);
@@ -3918,7 +3984,7 @@ public class XFLConverter {
"color", randomOutlineColor(),
"layerType", "mask",
"locked", "true"});
convertFrames(swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes);
convertFrames(swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes, statusStack);
writer.writeEndElement();
int parentIndex = index;
@@ -4007,7 +4073,7 @@ public class XFLConverter {
}
for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) {
boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes);
boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack);
for (int i = clipFrame; i <= lastFrame; i++) {
depthToFramesList.get(nd).remove((Integer) i);
}
@@ -4021,7 +4087,7 @@ public class XFLConverter {
}
}
boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes);
boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack);
if (nonEmpty) {
index++;
}
@@ -4059,8 +4125,9 @@ public class XFLConverter {
writer.writeEndElement(); //DOMLayer
}
private boolean writeLayer(SWF swf, int index, List<Integer> onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List<Integer> nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap<Integer, CharacterTag> characters, FLAVersion flaVersion, HashMap<String, byte[]> files, List<Integer> multiUsageMorphShapes) throws XMLStreamException {
private boolean writeLayer(SWF swf, int index, List<Integer> onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List<Integer> nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap<Integer, CharacterTag> characters, FLAVersion flaVersion, HashMap<String, byte[]> files, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
XFLXmlWriter layerPrev = new XFLXmlWriter();
statusStack.pushStatus("layer " + (index + 1));
//System.err.println("- writing layer " + (index + 1) + (startFrame == 0 && endFrame == Integer.MAX_VALUE ? ", all frames": ", frame " + startFrame + " to " + endFrame));
layerPrev.writeStartElement("DOMLayer", new String[]{
"name", "Layer " + (index + 1) + (DEBUG_EXPORT_LAYER_DEPTHS ? " (depth " + d + ")" : ""),
@@ -4077,7 +4144,8 @@ public class XFLConverter {
layerPrev.writeCharacters(""); // todo honfika: hack to close start tag
String layerAfter = "</DOMLayer>";
int prevLength = writer.length();
convertFrames(swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes);
convertFrames(swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes, statusStack);
statusStack.popStatus();
return writer.length() != prevLength;
}
@@ -4608,16 +4676,17 @@ public class XFLConverter {
}
Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol = new HashMap<>();
StatusStack statusStack = new StatusStack(progressListener);
convertFonts(characterClasses, swf.getTags(), domDocument, progressListener);
convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, progressListener);
convertFonts(characterClasses, swf.getTags(), domDocument, statusStack);
convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
//domDocument.writeStartElement("timelines");
ScriptPack documentScriptPack = characterScriptPacks.containsKey(0) ? characterScriptPacks.get(0) : null;
if (progressListener != null) {
progressListener.status("main timeline");
}
convertTimeline(swf, swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes);
statusStack.pushStatus("main timeline");
convertTimeline(swf, swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
statusStack.popStatus();
//domDocument.writeEndElement();
if (hasAmfMetadata) {
@@ -17,7 +17,11 @@
package com.jpexs.decompiler.flash.xfl.shapefixer;
import com.jpexs.decompiler.flash.math.BezierEdge;
import com.jpexs.decompiler.flash.math.Distances;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
@@ -25,18 +29,26 @@ import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.Reference;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Shape fixer.
* This will walk a shape and split crossed edges so FLA editor can properly load it.
* Shape fixer. This will walk a shape and split crossed edges so FLA editor can
* properly load it. It also fixes morphshape - removes duplicated paths, calculate holes.
*
* @author JPEXS
*/
public class ShapeFixer {
boolean DEBUG_PRINT = false;
private class BezierPair {
@@ -47,7 +59,7 @@ public class ShapeFixer {
public BezierPair(BezierEdge be1, BezierEdge be2) {
this.be1 = be1;
this.be2 = be2;
this.be2 = be2;
}
@Override
@@ -60,7 +72,7 @@ public class ShapeFixer {
@Override
public boolean equals(Object obj) {
//simplifyed to be fast, not so neccessarily accurate
//simplified to be fast, not so neccessarily accurate
return hashCode() == obj.hashCode();
/*if (this == obj) {
return true;
@@ -81,10 +93,22 @@ public class ShapeFixer {
}
public List<ShapeRecordAdvanced> fix(
List<SHAPERECORD> records
) {
private boolean isEmptyBatch(List<BezierEdge> batch) {
for (BezierEdge be : batch) {
if (!be.isEmpty()) {
return false;
}
}
return true;
}
public List<ShapeRecordAdvanced> fix(
List<SHAPERECORD> records,
boolean morphshape,
int shapeNum,
FILLSTYLEARRAY baseFillStyles,
LINESTYLEARRAY baseLineStyles
) {
List<List<BezierEdge>> shapes = new ArrayList<>();
List<BezierEdge> currentShape = new ArrayList<>();
List<Integer> fillStyles0 = new ArrayList<>();
@@ -164,6 +188,416 @@ public class ShapeFixer {
y = rec.changeY(y);
}
if (morphshape) {
//Remove empty edges
for (int i = 0; i < shapes.size(); i++) {
List<BezierEdge> list = shapes.get(i);
for (int j = 0; j < list.size(); j++) {
if (list.get(j).isEmpty()) {
list.remove(j);
j--;
}
}
}
List<List<BezierEdge>> closedShapes = new ArrayList<>();
List<Integer> closedFillStyle = new ArrayList<>();
List<Integer> closedLineStyle = new ArrayList<>();
List<Integer> closedLayers = new ArrayList<>();
List<Integer> closedShapesI = new ArrayList<>();
List<Integer> closedShapesJ = new ArrayList<>();
Map<Integer, Integer> replacements = new LinkedHashMap<>();
for (int i = 0; i < shapes.size(); i++) {
Point2D lastMoveTo = null;
BezierEdge lastEdge = null;
int moveToIndex = 0;
List<BezierEdge> batch = new ArrayList<>();
for (int j = 0; j < shapes.get(i).size(); j++) {
BezierEdge be = shapes.get(i).get(j);
batch.add(be);
if (lastMoveTo != null && be.getEndPoint().equals(lastMoveTo)) {
closedFillStyle.add(fillStyles0.get(i));
closedLineStyle.add(lineStyles.get(i));
closedShapes.add(batch);
closedLayers.add(layers.get(i));
closedShapesI.add(i);
closedShapesJ.add(moveToIndex);
moveToIndex = j + 1;
lastMoveTo = be.getEndPoint();
batch = new ArrayList<>();
}
if (lastEdge != null) {
if (!lastEdge.getEndPoint().equals(be.getBeginPoint())) {
lastMoveTo = be.getBeginPoint();
moveToIndex = j;
}
} else {
lastMoveTo = be.getBeginPoint();
}
lastEdge = be;
}
}
Set<Integer> removedShapes = new HashSet<>();
for (int i1 = 0; i1 < closedShapes.size(); i1++) {
if (replacements.containsKey(i1)) {
continue;
}
for (int i2 = 0; i2 < closedShapes.size(); i2++) {
if (i1 == i2) {
continue;
}
if (replacements.containsKey(i2)) {
continue;
}
if (closedLayers.get(i1) != closedLayers.get(i2)) {
continue;
}
if (closedFillStyle.get(i1) > 0 && closedFillStyle.get(i2) > 0) {
if (closedFillStyle.get(i1) != closedFillStyle.get(i2)) {
FILLSTYLEARRAY fa = closedLayers.get(i1) == -1 ? baseFillStyles : fillStyleLayers.get(closedLayers.get(i1));
FILLSTYLE fs1 = fa.fillStyles[closedFillStyle.get(i1) - 1];
FILLSTYLE fs2 = fa.fillStyles[closedFillStyle.get(i2) - 1];
if (!fs1.equals(fs2)) {
continue;
}
}
}
if (closedLineStyle.get(i1) > 0 && closedLineStyle.get(i2) > 0) {
if (closedLineStyle.get(i1) != closedLineStyle.get(i2)) {
LINESTYLEARRAY lsa = closedLayers.get(i1) == -1 ? baseLineStyles : lineStyleLayers.get(closedLayers.get(i1));
if (shapeNum <= 3) {
LINESTYLE ls1 = lsa.lineStyles[closedLineStyle.get(i1) - 1];
LINESTYLE ls2 = lsa.lineStyles[closedLineStyle.get(i2) - 1];
if (!ls1.equals(ls2)) {
continue;
}
} else {
LINESTYLE2 ls1 = lsa.lineStyles2[closedLineStyle.get(i1) - 1];
LINESTYLE2 ls2 = lsa.lineStyles2[closedLineStyle.get(i2) - 1];
if (!ls1.equals(ls2)) {
continue;
}
}
}
}
if (closedShapes.get(i1).size() <= 1 || closedShapes.get(i2).size() <= 1) {
continue;
}
if (closedLineStyle.get(i1) > 0 && closedLineStyle.get(i2) == 0) {
continue;
}
if (isEmptyBatch(closedShapes.get(i1)) || isEmptyBatch(closedShapes.get(i2))) {
continue;
}
double dist = Distances.getBatchDistance(closedShapes.get(i1), closedShapes.get(i2));
if (dist <= 10) { //magic
/*System.err.println("dist = " + dist);
System.err.println("removed");
System.err.println("removed shape["+closedShapesI.get(i2)+"]["+closedShapesJ.get(i2)+"], fs "+ closedFillStyle.get(i2) + ", ls " + closedLineStyle.get(i2));
System.err.println("left shape["+closedShapesI.get(i1)+"]["+closedShapesJ.get(i1)+"], fs "+ closedFillStyle.get(i1)+ ", ls " + closedLineStyle.get(i1));
*/
replacements.put(i2, i1);
removedShapes.add(i2);
}
}
}
for (int i = 0; i < closedShapes.size(); i++) {
int repI = replacements.containsKey(i) ? replacements.get(i) : i;
List<BezierEdge> listI = closedShapes.get(repI);
for (int j = i + 1; j < closedShapes.size(); j++) {
/*if (i == j) {
break;
}*/
if (removedShapes.contains(j) && !replacements.containsKey(j)) {
continue;
}
int repJ = replacements.containsKey(j) ? replacements.get(j) : j;
List<BezierEdge> listJ = closedShapes.get(repJ);
if (closedFillStyle.get(i) != closedFillStyle.get(j)
|| closedLineStyle.get(i) != closedLineStyle.get(j)) {
continue;
}
if (listI.equals(listJ)) {
replacements.remove(j);
removedShapes.add(j);
}
}
}
for (int i = closedShapes.size() - 1; i >= 0; i--) {
int to = closedShapesJ.get(i) + closedShapes.get(i).size() - 1;
int from = closedShapesJ.get(i);
List<BezierEdge> list = shapes.get(closedShapesI.get(i));
if (removedShapes.contains(i)) {
//System.err.println("removing shape["+closedShapesI.get(i)+"]["+from+" to "+to+"]");
for (int j = to; j >= from; j--) {
list.remove(j);
}
}
if (replacements.containsKey(i)) {
list.addAll(from, closedShapes.get(replacements.get(i)));
}
if (!removedShapes.contains(i)) {
for (int j = to; j >= from; j--) {
if (list.get(j).isEmpty()) {
list.remove(j);
}
}
}
}
/*
* This will remove a stroked path with no fill which has same
* stroke as subsequent path (or is its prefix). This happens in the
* morphshape edges. This needs to be cleaned up before exporting to FLA.
*/
List<BezierEdge> prevList = null;
int prevI = -1;
for (int i = 0; i < shapes.size(); i++) {
List<BezierEdge> list = shapes.get(i);
if (list.isEmpty()) {
continue;
}
if (prevList != null) {
int prevFillStyle0 = fillStyles0.get(i - 1);
int prevFillStyle1 = fillStyles1.get(i - 1);
int prevLineStyle = lineStyles.get(i - 1);
lineStyle = lineStyles.get(i);
fillStyle0 = fillStyles0.get(i);
fillStyle1 = fillStyles1.get(i);
if (fillStyle0 == 0 && fillStyle1 == 0 && lineStyle != 0 && lineStyle == prevLineStyle) {
if (prevList.size() >= list.size()) {
boolean isPrefix = true;
for (int j = 0; j < list.size(); j++) {
if (!prevList.get(j).equals(list.get(j))) {
isPrefix = false;
break;
}
}
if (isPrefix) {
shapes.get(i).clear();
continue;
}
}
} else if (prevFillStyle0 == 0 && prevFillStyle1 == 0 && prevLineStyle != 0 && lineStyle == prevLineStyle) {
//list startswitch prevList
if (list.size() >= prevList.size()) {
boolean isPrefix = true;
for (int j = 0; j < prevList.size(); j++) {
if (!prevList.get(j).equals(list.get(j))) {
isPrefix = false;
break;
}
}
if (isPrefix) {
shapes.get(prevI).clear();
}
}
}
}
prevI = i;
prevList = list;
}
//Clear obvious duplicates
//System.err.println("Clear obvious duplicates...");
prevList = null;
prevI = -1;
//if (false)
for (int i = 0; i < shapes.size(); i++) {
List<BezierEdge> list = shapes.get(i);
if (list.isEmpty()) {
continue;
}
if (prevList != null) {
if (fillStyles0.get(i) == fillStyles0.get(prevI)
&& lineStyles.get(i) == lineStyles.get(prevI)) {
if (list.equals(prevList)) {
System.err.println("clearing " + i);
prevList.clear();
}
}
}
prevI = i;
prevList = list;
}
//----------------------------FIND "holes" = apply wind even odd -------------
closedShapes = new ArrayList<>();
closedFillStyle = new ArrayList<>();
closedLineStyle = new ArrayList<>();
closedLayers = new ArrayList<>();
closedShapesI = new ArrayList<>();
closedShapesJ = new ArrayList<>();
Set<Integer> closedHolesI = new LinkedHashSet<>();
for (int i = 0; i < shapes.size(); i++) {
Point2D lastMoveTo = null;
BezierEdge lastEdge = null;
int moveToIndex = 0;
List<BezierEdge> batch = new ArrayList<>();
for (int j = 0; j < shapes.get(i).size(); j++) {
BezierEdge be = shapes.get(i).get(j);
batch.add(be);
if (lastMoveTo != null && be.getEndPoint().equals(lastMoveTo)) {
closedFillStyle.add(fillStyles0.get(i));
closedLineStyle.add(lineStyles.get(i));
closedShapes.add(batch);
closedLayers.add(layers.get(i));
closedShapesI.add(i);
closedShapesJ.add(moveToIndex);
moveToIndex = j + 1;
lastMoveTo = be.getEndPoint();
batch = new ArrayList<>();
}
if (lastEdge != null) {
if (!lastEdge.getEndPoint().equals(be.getBeginPoint())) {
lastMoveTo = be.getBeginPoint();
moveToIndex = j;
}
} else {
lastMoveTo = be.getBeginPoint();
}
lastEdge = be;
}
}
//reversing anti-clockwise
for (int i = 0; i < closedShapes.size(); i++) {
List<BezierEdge> list = closedShapes.get(i);
if (list.isEmpty()) {
continue;
}
List<Point2D> points = new ArrayList<>();
points.add(list.get(0).getBeginPoint());
for (BezierEdge be : list) {
if (be.points.size() == 3) {
points.add(be.points.get(1));
}
points.add(be.getEndPoint());
}
double sum = 0;
for (int j = 0; j < points.size(); j++) {
Point2D p1 = points.get(j);
Point2D p2 = points.get((j + 1) % points.size());
sum += (p1.getX() * p2.getY() - p2.getX() * p1.getY());
}
if (sum < 0) { //anti clockwise
//reverse the list
List<BezierEdge> rev = new ArrayList<>();
for (int j = 0; j < list.size(); j++) {
rev.add(list.get(list.size() - 1 - j).reverse());
}
int shapeI = closedShapesI.get(i);
int shapeJ = closedShapesJ.get(i);
for (int j = 0; j < list.size(); j++) {
shapes.get(shapeI).set(shapeJ + j, rev.get(j));
}
}
}
Map<Integer, List<Integer>> fillStyleToClosed = new LinkedHashMap<>();
for (int i = 0; i < closedShapes.size(); i++) {
int fs = closedFillStyle.get(i);
if (fs != 0) {
if (!fillStyleToClosed.containsKey(fs)) {
fillStyleToClosed.put(fs, new ArrayList<>());
}
fillStyleToClosed.get(fs).add(i);
}
}
for (int fs : fillStyleToClosed.keySet()) {
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
Map<Integer, GeneralPath> closedPaths = new LinkedHashMap<>();
for (int i : fillStyleToClosed.get(fs)) {
List<BezierEdge> closed = closedShapes.get(i);
if (closed.isEmpty()) {
continue;
}
GeneralPath closedPath = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
closedPath.moveTo(closed.get(0).getBeginPoint().getX(), closed.get(0).getBeginPoint().getY());
path.moveTo(closed.get(0).getBeginPoint().getX(), closed.get(0).getBeginPoint().getY());
boolean isEmpty = true;
for (BezierEdge be : closed) {
if (be.isEmpty()) {
continue;
}
isEmpty = false;
if (be.points.size() == 3) {
closedPath.quadTo(be.points.get(1).getX(), be.points.get(1).getY(), be.getEndPoint().getX(), be.getEndPoint().getY());
path.quadTo(be.points.get(1).getX(), be.points.get(1).getY(), be.getEndPoint().getX(), be.getEndPoint().getY());
} else {
closedPath.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY());
path.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY());
}
}
closedPath.closePath();
path.closePath();
if (!isEmpty) {
closedPaths.put(i, closedPath);
}
}
for (int i : closedPaths.keySet()) {
GeneralPath region = closedPaths.get(i);
Rectangle r = region.getBounds();
double px;
double py;
do {
px = r.getX() + r.getWidth() * Math.random();
py = r.getY() + r.getHeight() * Math.random();
} while (!region.contains(px, py));
if (!path.contains(px, py)) {
closedHolesI.add(i);
}
}
}
for (int i = closedShapes.size() - 1; i >= 0; i--) {
if (closedHolesI.contains(i)) {
int to = closedShapesJ.get(i) + closedShapes.get(i).size() - 1;
int from = closedShapesJ.get(i);
List<BezierEdge> list = shapes.get(closedShapesI.get(i));
//System.err.println("removing hole["+closedShapesI.get(i)+"]["+from+" to "+to+"]");
for (int j = to; j >= from; j--) {
list.remove(j);
}
int shapeI = closedShapesI.get(i);
shapes.add(shapeI + 1, closedShapes.get(i));
closedShapes.set(i, new ArrayList<>());
fillStyles0.add(shapeI + 1, 0);
fillStyles1.add(shapeI + 1, fillStyles0.get(shapeI));
lineStyles.add(shapeI + 1, lineStyles.get(shapeI));
layers.add(shapeI + 1, layers.get(shapeI));
}
}
}
//------------------- detecting overlapping edges --------------------
List<BezierPair> splittedPairs = new ArrayList<>();
loopi1:
@@ -173,193 +607,182 @@ public class ShapeFixer {
for (int j1 = 0; j1 < shapes.get(i1).size(); j1++) {
BezierEdge be1 = shapes.get(i1).get(j1);
for (int i2 = 0; i2 < shapes.size(); i2++) {
if (layers.get(i2) == layer) {
loopj2:
for (int j2 = 0; j2 < shapes.get(i2).size(); j2++) {
BezierEdge be2 = shapes.get(i2).get(j2);
if (layers.get(i2) != layer) {
continue;
}
loopj2:
for (int j2 = 0; j2 < shapes.get(i2).size(); j2++) {
BezierEdge be2 = shapes.get(i2).get(j2);
if (i1 == i2 && j1 == j2) {
continue;
if (i1 == i2 && j1 == j2) {
continue;
}
if (be1.isEmpty()) {
shapes.get(i1).remove(j1);
if (i1 == i2 && j2 > j1) {
j2--;
}
if (be1.isEmpty()) {
shapes.get(i1).remove(j1);
if (i1 == i2 && j2 > j1) {
j2--;
}
j1--;
continue loopj1;
}
if (be2.isEmpty()) {
shapes.get(i2).remove(j2);
if (i1 == i2 && j1 > j2) {
j1--;
continue loopj1;
}
if (be2.isEmpty()) {
shapes.get(i2).remove(j2);
if (i1 == i2 && j1 > j2) {
j1--;
}
j2--;
continue loopj2;
}
j2--;
continue loopj2;
}
//duplicated edge
if (be1.equals(be2) || be1.equals(be2.reverse())) {
shapes.get(i2).remove(j2);
if (i1 == i2 && j1 > j2) {
j1--;
}
j2--;
if (DEBUG_PRINT) {
System.err.println("removing duplicate " + be1.toSvg() + " and " + be2.toSvg());
}
//duplicated edge
if (be1.equals(be2) || be1.equals(be2.reverse())) {
shapes.get(i2).remove(j2);
if (i1 == i2 && j1 > j2) {
j1--;
}
j2--;
if (DEBUG_PRINT) {
System.err.println("removing duplicate " + be1.toSvg() + " and " + be2.toSvg());
}
continue;
}
BezierPair pair = new BezierPair(be1, be2);
if (splittedPairs.contains(pair)) {
continue;
}
List<Double> t1Ref = new ArrayList<>();
List<Double> t2Ref = new ArrayList<>();
List<Point2D> intPoints = new ArrayList<>();
if (DEBUG_PRINT) {
//System.err.print("checking shape[" + i1 + "][" + j1 + "] to shape[" + i2 + "][" + j2 + "] : " + be1.toSvg() + " and " + be2.toSvg());
}
boolean isint = be1.intersects(be2, t1Ref, t2Ref, intPoints);
if (DEBUG_PRINT) {
//System.err.println(" " + isint);
}
if (!t1Ref.isEmpty()) {
if ((be1.getBeginPoint().equals(be2.getBeginPoint())
|| be1.getBeginPoint().equals(be2.getEndPoint())
|| be1.getEndPoint().equals(be2.getBeginPoint())
|| be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) {
continue;
}
BezierPair pair = new BezierPair(be1, be2);
if (splittedPairs.contains(pair)) {
continue;
}
List<Double> t1Ref = new ArrayList<>();
List<Double> t2Ref = new ArrayList<>();
List<Point2D> intPoints = new ArrayList<>();
if (DEBUG_PRINT) {
System.err.print("checking shape[" + i1 + "][" + j1 + "] to shape[" + i2 + "][" + j2 + "] : " + be1.toSvg() + " and " + be2.toSvg());
}
System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg());
System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2));
System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2));
System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2));
boolean isint = be1.intersects(be2, t1Ref, t2Ref, intPoints);
if (DEBUG_PRINT) {
System.err.println(" " + isint);
if (isint) {
System.err.println("xxx");
for (int n = 0; n < t1Ref.size(); n++) {
System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n));
}
}
if (!t1Ref.isEmpty()) {
if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1)
&& (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) {
t1Ref.add(0, t1Ref.remove(1));
t2Ref.add(0, t2Ref.remove(1));
intPoints.add(0, intPoints.remove(1));
}
if (DEBUG_PRINT) {
System.err.println("be1 " + be1);
System.err.println("be2 " + be2);
System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg());
System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2));
System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2));
System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2));
for (int n = 0; n < t1Ref.size(); n++) {
System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n));
}
}
int splitPointIndex = 0;
if (intPoints.size() > 1) {
splitPointIndex = 1;
}
splittedPairs.add(pair);
if ((be1.getBeginPoint().equals(be2.getBeginPoint())
|| be1.getBeginPoint().equals(be2.getEndPoint())
|| be1.getEndPoint().equals(be2.getBeginPoint())
|| be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) {
if (DEBUG_PRINT) {
System.err.println("Same begin/end - continued");
}
continue;
}
Reference<BezierEdge> be1LRef = new Reference<>(null);
Reference<BezierEdge> be1RRef = new Reference<>(null);
be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef);
Reference<BezierEdge> be2LRef = new Reference<>(null);
Reference<BezierEdge> be2RRef = new Reference<>(null);
be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef);
if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1)
&& (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) {
t1Ref.add(0, t1Ref.remove(1));
t2Ref.add(0, t2Ref.remove(1));
intPoints.add(0, intPoints.remove(1));
}
BezierEdge be1L = be1LRef.getVal();
BezierEdge be1R = be1RRef.getVal();
BezierEdge be2L = be2LRef.getVal();
BezierEdge be2R = be2RRef.getVal();
int splitPointIndex = 0;
if (intPoints.size() > 1) {
splitPointIndex = 1;
}
Point2D intP = intPoints.get(splitPointIndex);
splittedPairs.add(pair);
be1L.setEndPoint(intP);
be1R.setBeginPoint(intP);
be2L.setEndPoint(intP);
be2R.setBeginPoint(intP);
Reference<BezierEdge> be1LRef = new Reference<>(null);
Reference<BezierEdge> be1RRef = new Reference<>(null);
be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef);
Reference<BezierEdge> be2LRef = new Reference<>(null);
Reference<BezierEdge> be2RRef = new Reference<>(null);
be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef);
BezierEdge be1L = be1LRef.getVal();
BezierEdge be1R = be1RRef.getVal();
BezierEdge be2L = be2LRef.getVal();
BezierEdge be2R = be2RRef.getVal();
Point2D intP = intPoints.get(splitPointIndex);
be1L.setEndPoint(intP);
be1R.setBeginPoint(intP);
be2L.setEndPoint(intP);
be2R.setBeginPoint(intP);
be1L.roundHalf();
be1R.roundHalf();
be2L.roundHalf();
be2R.roundHalf();
if (i1 == i2 && j1 < j2) {
be1L.roundHalf();
be1R.roundHalf();
be2L.roundHalf();
be2R.roundHalf();
if (i1 == i2) {
if (j1 < j2) {
shapes.get(i1).remove(j2);
shapes.get(i2).remove(j1);
j2--;
} else {
shapes.get(i1).remove(j1);
shapes.get(i2).remove(j2);
if (i1 == i2) {
j1--;
}
j1--;
}
int n1 = j1;
int n2 = j2;
if (!be1L.isEmpty()) {
shapes.get(i1).add(n1, be1L);
if (i1 == i2 && n2 > n1) {
n2++;
}
n1++;
if (DEBUG_PRINT) {
System.err.println("added " + be1L.toSvg());
}
}
if (!be1R.isEmpty()) {
shapes.get(i1).add(n1, be1R);
if (i1 == i2 && n2 > n1) {
n2++;
}
n1++;
if (DEBUG_PRINT) {
System.err.println("added " + be1R.toSvg());
}
}
if (!be2L.isEmpty()) {
shapes.get(i2).add(n2, be2L);
if (i1 == i2 && n1 > n2) {
n1++;
}
n2++;
if (DEBUG_PRINT) {
System.err.println("added " + be2L.toSvg());
}
}
if (!be2R.isEmpty()) {
shapes.get(i2).add(n2, be2R);
if (i1 == i2 && n1 > n2) {
n1++;
}
n2++;
if (DEBUG_PRINT) {
System.err.println("added " + be2R.toSvg());
}
}
j1--;
continue loopj1;
} else {
shapes.get(i1).remove(j1);
shapes.get(i2).remove(j2);
}
int n1 = j1;
int n2 = j2;
if (!be1L.isEmpty()) {
shapes.get(i1).add(n1, be1L);
if (DEBUG_PRINT) {
System.err.println("added " + be1L.toSvg() + " to j1=" + n1);
}
if (i1 == i2 && n2 >= n1) {
n2++;
}
n1++;
}
if (!be1R.isEmpty()) {
shapes.get(i1).add(n1, be1R);
if (DEBUG_PRINT) {
System.err.println("added " + be1R.toSvg() + " to j1=" + n1);
}
if (i1 == i2 && n2 >= n1) {
n2++;
}
n1++;
}
if (!be2L.isEmpty()) {
shapes.get(i2).add(n2, be2L);
if (DEBUG_PRINT) {
System.err.println("added " + be2L.toSvg() + " to j2=" + n2);
}
n2++;
}
if (!be2R.isEmpty()) {
shapes.get(i2).add(n2, be2R);
if (DEBUG_PRINT) {
System.err.println("added " + be2R.toSvg() + " to j2=" + n2);
}
n2++;
}
j1--;
continue loopj1;
}
}
}