FLA export - fixers refactoring

This commit is contained in:
Jindra Petřík
2023-11-12 23:37:16 +01:00
parent e57d37d59a
commit 9bfb8b117d
3 changed files with 557 additions and 451 deletions

View File

@@ -141,6 +141,7 @@ import com.jpexs.decompiler.flash.types.sound.MP3FRAME;
import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA;
import com.jpexs.decompiler.flash.types.sound.SoundFormat;
import com.jpexs.decompiler.flash.xfl.shapefixer.CurvedEdgeRecordAdvanced;
import com.jpexs.decompiler.flash.xfl.shapefixer.MorphShapeFixer;
import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeFixer;
import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeRecordAdvanced;
import com.jpexs.decompiler.flash.xfl.shapefixer.StraightEdgeRecordAdvanced;
@@ -612,8 +613,8 @@ public class XFLConverter {
return false;
}
private static void convertShape(SWF swf, HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer, boolean useFixer) throws XMLStreamException {
List<String> layers = getShapeLayers(swf, characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, morphshape, useFixer);
private static void convertShape(SWF swf, HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer) throws XMLStreamException {
List<String> layers = getShapeLayers(swf, characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, morphshape);
if (!useLayers) {
for (int l = layers.size() - 1; l >= 0; l--) {
writer.writeCharactersRaw(layers.get(l));
@@ -805,25 +806,16 @@ public class XFLConverter {
return ret;
}
private static List<String> getShapeLayers(SWF swf, HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useFixer) throws XMLStreamException {
private static List<String> getShapeLayers(SWF swf, HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) throws XMLStreamException {
if (mat == null) {
mat = new MATRIX();
}
List<ShapeRecordAdvanced> shapeRecordsAdvanced;
useFixer = true;
if (useFixer) {
ShapeFixer fixer = new ShapeFixer();
shapeRecordsAdvanced = fixer.fix(shapeRecords, morphshape, shapeNum, fillStyles, lineStyles);
} else {
shapeRecordsAdvanced = new ArrayList<>();
for (SHAPERECORD rec : shapeRecords) {
ShapeRecordAdvanced arec = ShapeRecordAdvanced.createFromSHAPERECORD(rec);
if (arec != null) {
shapeRecordsAdvanced.add(arec);
}
}
}
ShapeFixer fixer = morphshape ? new MorphShapeFixer() : new ShapeFixer();
shapeRecordsAdvanced = fixer.fix(shapeRecords, shapeNum, fillStyles, lineStyles);
List<ShapeRecordAdvanced> edges = new ArrayList<>();
int lineStyleCount = 0;
@@ -1850,7 +1842,7 @@ public class XFLConverter {
int characterId = character.getCharacterId();
if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId))) {
ShapeTag shape = (ShapeTag) character;
convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter, true);
convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter);
} else if (character instanceof TextTag) {
convertText(null, (TextTag) character, matrix, filters, null, recCharWriter);
} else if (character instanceof DefineVideoStreamTag) {
@@ -1912,7 +1904,7 @@ public class XFLConverter {
symbolStr.writeStartElement("layers");
SHAPEWITHSTYLE shapeWithStyle = shape.getShapes();
if (shapeWithStyle != null) {
convertShape(swf, characters, null, shape.getShapeNum(), shapeWithStyle.shapeRecords, shapeWithStyle.fillStyles, shapeWithStyle.lineStyles, false, true, symbolStr, true);
convertShape(swf, characters, null, shape.getShapeNum(), shapeWithStyle.shapeRecords, shapeWithStyle.fillStyles, shapeWithStyle.lineStyles, false, true, symbolStr);
}
symbolStr.writeEndElement(); // layers
@@ -2672,7 +2664,7 @@ public class XFLConverter {
MorphShapeTag m = shapeTweener;
XFLXmlWriter addLastWriter = new XFLXmlWriter();
SHAPEWITHSTYLE endShape = m.getShapeAtRatio(65535); //lastTweenRatio);
convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(lastTweenRatio), m.getLineStyles().getLineStylesAt(m.getShapeNum(), lastTweenRatio), true, false, addLastWriter, false);
convertShape(swf, 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;
@@ -2688,7 +2680,7 @@ public class XFLConverter {
standaloneShapeTweener = null;
} else if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId))) { // || shapeTweener != null)) {
ShapeTag shape = (ShapeTag) character;
convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter, true);
convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter);
shapeTween = false;
shapeTweener = null;
@@ -2706,7 +2698,7 @@ public class XFLConverter {
elementsWriter.writeCharactersRaw(lastElements);
} 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);
convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter);
statusStack.popStatus();
}

View File

@@ -0,0 +1,528 @@
/*
* 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.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 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.Set;
/**
*
* Morphshape fixer. Works as ShapeFixer but also removes duplicated paths,
* calculates holes.
*
* @author JPEXS
*/
public class MorphShapeFixer extends ShapeFixer {
/**
* Hook from base ShapeFixer
*
* @param shapeNum
* @param shapes
* @param fillStyles0
* @param fillStyles1
* @param lineStyles
* @param layers
* @param baseFillStyles
* @param baseLineStyles
* @param fillStyleLayers
* @param lineStyleLayers
*/
@Override
protected void beforeHandle(int shapeNum, List<List<BezierEdge>> shapes, List<Integer> fillStyles0, List<Integer> fillStyles1, List<Integer> lineStyles, List<Integer> layers, FILLSTYLEARRAY baseFillStyles, LINESTYLEARRAY baseLineStyles, List<FILLSTYLEARRAY> fillStyleLayers, List<LINESTYLEARRAY> lineStyleLayers) {
removeEmptyEdges(shapes);
mergeSimilar(shapeNum, shapes, fillStyles0, lineStyles, layers, baseFillStyles, baseLineStyles, fillStyleLayers, lineStyleLayers);
mergeWithSamePrefix(shapes, fillStyles0, fillStyles1, lineStyles);
clearDuplicatePathsNextToEachOther(shapes, fillStyles0, lineStyles);
fixHolesAndAntiClockwise(shapes, fillStyles0, fillStyles1, lineStyles, layers);
}
private boolean isEmptyPath(List<BezierEdge> path) {
for (BezierEdge be : path) {
if (!be.isEmpty()) {
return false;
}
}
return true;
}
private void fixHolesAndAntiClockwise(
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> lineStyles,
List<Integer> layers
) {
List<List<BezierEdge>> closedShapes = new ArrayList<>();
List<Integer> closedFillStyle = new ArrayList<>();
List<Integer> closedShapesI = new ArrayList<>();
List<Integer> 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));
closedShapes.add(batch);
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);
//add this path as new with fillstyle1 instead of fillstyle0
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));
}
}
}
private void clearDuplicatePathsNextToEachOther(
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> lineStyles
) {
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) {
if (fillStyles0.get(i) == fillStyles0.get(prevI)
&& lineStyles.get(i) == lineStyles.get(prevI)) {
if (list.equals(prevList)) {
prevList.clear();
}
}
}
prevI = i;
prevList = list;
}
}
/**
* 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.
*/
private void mergeWithSamePrefix(
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> lineStyles
) {
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);
int lineStyle = lineStyles.get(i);
int fillStyle0 = fillStyles0.get(i);
int 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;
}
}
/**
* Merges similar paths. This happens in morphshapes when one shape is
* transformed into multiple shapes.
*
* @param shapeNum
* @param shapes
* @param fillStyles0
* @param lineStyles
* @param layers
* @param baseFillStyles
* @param baseLineStyles
* @param fillStyleLayers
* @param lineStyleLayers
*/
private void mergeSimilar(
int shapeNum,
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> lineStyles,
List<Integer> layers,
FILLSTYLEARRAY baseFillStyles,
LINESTYLEARRAY baseLineStyles,
List<FILLSTYLEARRAY> fillStyleLayers,
List<LINESTYLEARRAY> lineStyleLayers
) {
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 (isEmptyPath(closedShapes.get(i1)) || isEmptyPath(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);
}
}
}
}
}
/**
*
* @param shapes
*/
private void removeEmptyEdges(List<List<BezierEdge>> shapes) {
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--;
}
}
}
}
}

View File

@@ -17,11 +17,7 @@
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;
@@ -29,21 +25,14 @@ 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. It also fixes morphshape - removes duplicated paths, calculate holes.
* properly load it.
*
* @author JPEXS
*/
@@ -92,19 +81,23 @@ public class ShapeFixer {
}
}
private boolean isEmptyBatch(List<BezierEdge> batch) {
for (BezierEdge be : batch) {
if (!be.isEmpty()) {
return false;
}
}
return true;
protected void beforeHandle(
int shapeNum,
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> lineStyles,
List<Integer> layers,
FILLSTYLEARRAY baseFillStyles,
LINESTYLEARRAY baseLineStyles,
List<FILLSTYLEARRAY> fillStyleLayers,
List<LINESTYLEARRAY> lineStyleLayers
) {
}
public List<ShapeRecordAdvanced> fix(
List<SHAPERECORD> records,
boolean morphshape,
int shapeNum,
FILLSTYLEARRAY baseFillStyles,
LINESTYLEARRAY baseLineStyles
@@ -187,415 +180,8 @@ public class ShapeFixer {
x = rec.changeX(x);
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));
}
}
}
beforeHandle(shapeNum, shapes, fillStyles0, fillStyles1, lineStyles, layers, baseFillStyles, baseLineStyles, fillStyleLayers, lineStyleLayers);
//------------------- detecting overlapping edges --------------------
List<BezierPair> splittedPairs = new ArrayList<>();