Fixed: #1826, #2416 FLA export - shapes - missing fills when fillStyle0/1 has incorrect orientation

Fixed: #2532, #1011, #2165 FLA export - shapes - missing fills on path crossings, small shapes
This commit is contained in:
Jindra Petřík
2025-09-26 20:03:20 +02:00
parent ee53b6e763
commit 4fdf17ccfc
12 changed files with 1783 additions and 217 deletions

View File

@@ -172,7 +172,7 @@ public final class Matrix implements Cloneable {
public java.awt.Point transform(java.awt.Point point) {
Point p = transform(point.x, point.y);
return new java.awt.Point((int) p.x, (int) p.y);
return new java.awt.Point((int) Math.round(p.x), (int) Math.round(p.y));
}
public Point2D transform(Point2D point) {

View File

@@ -132,6 +132,12 @@ public abstract class ShapeExporterBase implements IShapeExporter {
_lineStyles = cachedData.lineStyles;
_fillPaths = cachedData.fillPaths;
_linePaths = cachedData.linePaths;
handleFillPaths(_fillPaths);
}
protected void handleFillPaths(List<List<IEdge>> fillPaths) {
}
/**

View File

@@ -536,6 +536,18 @@ public class BezierEdge implements Serializable {
}
calcParams();
}
public static final double ROUND_VALUE = 1;
public void roundX() {
for (int i = 0; i < this.points.size(); i++) {
this.points.set(i, new Point2D.Double(
Math.round(this.points.get(i).getX() * ROUND_VALUE) / ROUND_VALUE,
Math.round(this.points.get(i).getY() * ROUND_VALUE) / ROUND_VALUE
));
}
calcParams();
}
@Override
public int hashCode() {
@@ -731,4 +743,19 @@ public class BezierEdge implements Serializable {
//rectIntersection(new Rectangle2D.Double(0,0,50,50), new Rectangle2D.Double(0,50,50,50), out);
//System.out.println("out = "+out);
}
public void shrinkToLine() {
if (points.size() == 3) {
double det = (points.get(1).getX() - points.get(0).getX())
* (points.get(2).getY() - points.get(0).getY())
- (points.get(1).getY() - points.get(0).getY())
* (points.get(2).getX() - points.get(0).getX());
if (det == 0) {
points.remove(1);
revPoints.remove(1);
calcParams();
}
}
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2010-2025 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.shapes;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
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.MORPHFILLSTYLE;
import com.jpexs.decompiler.flash.types.MORPHFILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.MORPHLINESTYLE2;
import com.jpexs.decompiler.flash.types.MORPHLINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
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 java.awt.Point;
import java.util.ArrayList;
import java.util.List;
/**
* Transforms shapes with matrix.
* @author JPEXS
*/
public class ShapeTransformer {
/**
* Transform styles.
* @param matrix Matrix
* @param fillStyles Fill styles
* @param lineStyles Line styles
* @param shapeNum Shape type (DefineShape = 1, DefineShape2 = 2, etc.)
*/
public void transformStyles(Matrix matrix, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, int shapeNum) {
List<FILLSTYLE> fillStyleToTransform = new ArrayList<>();
for (FILLSTYLE fs : fillStyles.fillStyles) {
fillStyleToTransform.add(fs);
}
double strokeScale = Math.max(Math.abs(matrix.scaleX), Math.abs(matrix.scaleY));
if (shapeNum >= 4) {
for (LINESTYLE2 ls : lineStyles.lineStyles2) {
if (ls.hasFillFlag) {
fillStyleToTransform.add(ls.fillType);
}
ls.width *= strokeScale;
}
} else {
for (LINESTYLE ls : lineStyles.lineStyles) {
ls.width *= strokeScale;
}
}
for (FILLSTYLE fs : fillStyleToTransform) {
switch (fs.fillStyleType) {
case FILLSTYLE.CLIPPED_BITMAP:
case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP:
case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP:
case FILLSTYLE.REPEATING_BITMAP:
fs.bitmapMatrix = new Matrix(fs.bitmapMatrix).preConcatenate(matrix).toMATRIX();
break;
case FILLSTYLE.LINEAR_GRADIENT:
case FILLSTYLE.RADIAL_GRADIENT:
case FILLSTYLE.FOCAL_RADIAL_GRADIENT:
fs.gradientMatrix = new Matrix(fs.gradientMatrix).preConcatenate(matrix).toMATRIX();
break;
}
}
}
/**
* Transform morph styles.
* @param matrix Matrix
* @param fillStyles Fill styles
* @param lineStyles Line styles
* @param morphShapeNum Morphshape type (DefineMorphshape = 1, DefineMorphshape2 = 2)
* @param doStart Modify start styles
* @param doEnd Modify end styles
*/
public void transformMorphStyles(Matrix matrix, MORPHFILLSTYLEARRAY fillStyles, MORPHLINESTYLEARRAY lineStyles, int morphShapeNum, boolean doStart, boolean doEnd) {
List<MORPHFILLSTYLE> fillStyleToTransform = new ArrayList<>();
for (MORPHFILLSTYLE fs : fillStyles.fillStyles) {
fillStyleToTransform.add(fs);
}
if (morphShapeNum == 2) {
for (MORPHLINESTYLE2 ls : lineStyles.lineStyles2) {
if (ls.hasFillFlag) {
fillStyleToTransform.add(ls.fillType);
}
}
}
for (MORPHFILLSTYLE fs : fillStyleToTransform) {
switch (fs.fillStyleType) {
case FILLSTYLE.CLIPPED_BITMAP:
case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP:
case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP:
case FILLSTYLE.REPEATING_BITMAP:
if (doStart) {
fs.startBitmapMatrix = new Matrix(fs.startBitmapMatrix).preConcatenate(matrix).toMATRIX();
}
if (doEnd) {
fs.endBitmapMatrix = new Matrix(fs.endBitmapMatrix).preConcatenate(matrix).toMATRIX();
}
break;
case FILLSTYLE.LINEAR_GRADIENT:
case FILLSTYLE.RADIAL_GRADIENT:
case FILLSTYLE.FOCAL_RADIAL_GRADIENT:
if (doStart) {
fs.startGradientMatrix = new Matrix(fs.startGradientMatrix).preConcatenate(matrix).toMATRIX();
}
if (doEnd) {
fs.endGradientMatrix = new Matrix(fs.endGradientMatrix).preConcatenate(matrix).toMATRIX();
}
break;
}
}
}
/**
* Transform SHAPE.
* @param matrix Matrix
* @param shape SHAPE
* @param shapeNum Shape type (DefineShape = 1, DefineShape2 = 2, etc.)
*/
public void transformSHAPE(Matrix matrix, SHAPE shape, int shapeNum) {
transformShapeRecords(matrix, shape.shapeRecords, shapeNum);
}
/**
* Transform SHAPERECORDs.
* @param matrix Matrix
* @param shapeRecords Records
* @param shapeNum Shape type (DefineShape = 1, DefineShape2 = 2, etc.)
*/
public void transformShapeRecords(Matrix matrix, List<SHAPERECORD> shapeRecords, int shapeNum) {
int x = 0;
int y = 0;
StyleChangeRecord lastStyleChangeRecord = null;
boolean wasMoveTo = false;
for (SHAPERECORD rec : shapeRecords) {
if (rec instanceof StyleChangeRecord) {
StyleChangeRecord scr = (StyleChangeRecord) rec;
lastStyleChangeRecord = scr;
if (scr.stateNewStyles) {
transformStyles(matrix, scr.fillStyles, scr.lineStyles, shapeNum);
}
if (scr.stateMoveTo) {
Point nextPoint = new Point(scr.moveDeltaX, scr.moveDeltaY);
x = scr.changeX(x);
y = scr.changeY(y);
Point nextPoint2 = matrix.transform(nextPoint);
scr.moveDeltaX = nextPoint2.x;
scr.moveDeltaY = nextPoint2.y;
scr.calculateBits();
wasMoveTo = true;
}
}
if (((rec instanceof StraightEdgeRecord) || (rec instanceof CurvedEdgeRecord)) && !wasMoveTo) {
if (lastStyleChangeRecord != null) {
Point nextPoint2 = matrix.transform(new Point(x, y));
if (nextPoint2.x != 0 || nextPoint2.y != 0) {
lastStyleChangeRecord.stateMoveTo = true;
lastStyleChangeRecord.moveDeltaX = nextPoint2.x;
lastStyleChangeRecord.moveDeltaY = nextPoint2.y;
lastStyleChangeRecord.calculateBits();
wasMoveTo = true;
}
}
}
if (rec instanceof StraightEdgeRecord) {
StraightEdgeRecord ser = (StraightEdgeRecord) rec;
ser.generalLineFlag = true;
ser.vertLineFlag = false;
Point currentPoint = new Point(x, y);
Point nextPoint = new Point(x + ser.deltaX, y + ser.deltaY);
x = ser.changeX(x);
y = ser.changeY(y);
Point currentPoint2 = matrix.transform(currentPoint);
Point nextPoint2 = matrix.transform(nextPoint);
ser.deltaX = nextPoint2.x - currentPoint2.x;
ser.deltaY = nextPoint2.y - currentPoint2.y;
ser.simplify();
}
if (rec instanceof CurvedEdgeRecord) {
CurvedEdgeRecord cer = (CurvedEdgeRecord) rec;
Point currentPoint = new Point(x, y);
Point controlPoint = new Point(x + cer.controlDeltaX, y + cer.controlDeltaY);
Point anchorPoint = new Point(x + cer.controlDeltaX + cer.anchorDeltaX, y + cer.controlDeltaY + cer.anchorDeltaY);
x = cer.changeX(x);
y = cer.changeY(y);
Point currentPoint2 = matrix.transform(currentPoint);
Point controlPoint2 = matrix.transform(controlPoint);
Point anchorPoint2 = matrix.transform(anchorPoint);
cer.controlDeltaX = controlPoint2.x - currentPoint2.x;
cer.controlDeltaY = controlPoint2.y - currentPoint2.y;
cer.anchorDeltaX = anchorPoint2.x - controlPoint2.x;
cer.anchorDeltaY = anchorPoint2.y - controlPoint2.y;
cer.calculateBits();
}
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Shape tools.
*/
package com.jpexs.decompiler.flash.shapes;

View File

@@ -66,6 +66,7 @@ import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter;
import com.jpexs.decompiler.flash.helpers.NulWriter;
import com.jpexs.decompiler.flash.helpers.StringBuilderTextWriter;
import com.jpexs.decompiler.flash.shapes.ShapeTransformer;
import com.jpexs.decompiler.flash.tags.ABCContainerTag;
import com.jpexs.decompiler.flash.tags.CSMSettingsTag;
import com.jpexs.decompiler.flash.tags.DefineButton2Tag;
@@ -143,7 +144,9 @@ import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.decompiler.flash.types.filters.GLOWFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
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.decompiler.flash.types.sound.MP3FRAME;
import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA;
@@ -249,7 +252,9 @@ public class XFLConverter {
*/
private final boolean DEBUG_EXPORT_LAYER_DEPTHS = false;
private static final DecimalFormat EDGE_DECIMAL_FORMAT = new DecimalFormat("0.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
private static final DecimalFormat EDGE_DECIMAL_FORMAT = new DecimalFormat("0.##", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
private static final double SMALL_DIVISOR = 20;
static {
EDGE_DECIMAL_FORMAT.setGroupingUsed(false);
@@ -287,12 +292,28 @@ public class XFLConverter {
}
}
private static String numEdgeToString(double value) {
if (value == Math.floor(value)) {
long lval = (long) value;
return "" + lval;
}
long integerPart = (long) Math.floor(value);
double fractionalPart = value - integerPart;
int fractionalPart256 = (int) Math.floor(fractionalPart * 256);
String h = Long.toHexString(integerPart).toUpperCase();
if (h.length() > 6) {
h = h.substring(h.length() - 6, h.length());
}
return "#" + h + "." + String.format("%02X", fractionalPart256);
}
private static String formatEdgeDouble(double value) {
if (value % 1 == 0) {
return "" + (int) value;
}
value = Math.round(value * 2.0) / 2.0;
return EDGE_DECIMAL_FORMAT.format(value);
//value = Math.round(value * 1000.0) / 1000.0;
//return EDGE_DECIMAL_FORMAT.format(value);
return numEdgeToString(value);
}
private static String convertShapeEdge(MATRIX mat, ShapeRecordAdvanced record, double x, double y) {
@@ -624,8 +645,8 @@ public class XFLConverter {
return false;
}
private static void convertShape(Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer) throws XMLStreamException {
List<String> layers = getShapeLayers(lastImportedId, characterNameMap, swf, mat, shapeNum, shapeRecords, fillStyles, lineStyles, morphshape);
private static void convertShape(Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer, int characterId, boolean small) throws XMLStreamException {
List<String> layers = getShapeLayers(lastImportedId, characterNameMap, swf, mat, shapeNum, shapeRecords, fillStyles, lineStyles, morphshape, characterId, small);
if (!useLayers) {
for (int l = layers.size() - 1; l >= 0; l--) {
writer.writeCharactersRaw(layers.get(l));
@@ -646,7 +667,35 @@ public class XFLConverter {
}
}
private static List<String> getShapeLayers(Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) throws XMLStreamException {
private static boolean isShapeSmall(ShapeTag shape) {
if (true) {
//return false;
}
int LIMIT = 1000;
if (shape.shapeBounds.getWidth() < LIMIT && shape.shapeBounds.getHeight() < LIMIT) {
return true;
}
return false;
/*List<SHAPERECORD> records = shape.shapes.shapeRecords;
int limit = 10;
for (SHAPERECORD rec : records) {
if ((rec instanceof StraightEdgeRecord) || (rec instanceof CurvedEdgeRecord)) {
int changeX = Math.abs(rec.changeX(0));
int changeY = Math.abs(rec.changeY(0));
int change = Math.max(changeX, changeY);
if (change != 0 && change < limit) {
return true;
}
}
}
return false;*/
}
private static List<String> getShapeLayers(Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, int characterId, boolean small) throws XMLStreamException {
if (mat == null) {
mat = new MATRIX();
}
@@ -654,6 +703,18 @@ public class XFLConverter {
List<ShapeRecordAdvanced> shapeRecordsAdvanced;
ShapeFixer fixer = morphshape ? new MorphShapeFixer() : new ShapeFixer();
Logger.getLogger(ShapeFixer.class.getName()).log(Level.FINE, "Fixing character {0}...", characterId);
if (small) {
shapeRecords = Helper.deepCopy(shapeRecords);
ShapeTransformer shapeTransformer = new ShapeTransformer();
Matrix scale = Matrix.getScaleInstance(SMALL_DIVISOR);
shapeTransformer.transformShapeRecords(scale, shapeRecords, shapeNum);
fillStyles = Helper.deepCopy(fillStyles);
lineStyles = Helper.deepCopy(lineStyles);
shapeTransformer.transformStyles(scale, fillStyles, lineStyles, shapeNum);
}
shapeRecordsAdvanced = fixer.fix(shapeRecords, shapeNum, fillStyles, lineStyles);
List<ShapeRecordAdvanced> edges = new ArrayList<>();
@@ -1030,6 +1091,20 @@ public class XFLConverter {
return ret;
}
private static Set<ShapeTag> getSmallShapes(SWF swf) {
Set<ShapeTag> result = new LinkedHashSet<>();
Map<Integer, CharacterTag> chars = swf.getCharacters(true);
for (int id : chars.keySet()) {
if (chars.get(id) instanceof ShapeTag) {
ShapeTag shape = (ShapeTag) chars.get(id);
if (isShapeSmall(shape)) {
result.add(shape);
}
}
}
return result;
}
private static Set<CharacterTag> getCharactersAndAllDependent(SWF swf) {
Set<CharacterTag> ret = new LinkedIdentityHashSet<>();
@@ -1285,10 +1360,13 @@ public class XFLConverter {
return (DEBUG_EXPORT_LAYER_DEPTHS ? "MaskedSymbol " : "Symbol ") + symbolId;
}
private static void convertSymbolInstance(int frame, AccessibilityBag accessibility, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, String name, MATRIX matrix, ColorTransform colorTransform, boolean cacheAsBitmap, int blendMode, List<FILTER> filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, Amf3Value metadata, CharacterTag tag, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException {
private static void convertSymbolInstance(int frame, AccessibilityBag accessibility, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, String name, MATRIX matrix, ColorTransform colorTransform, boolean cacheAsBitmap, int blendMode, List<FILTER> filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, Amf3Value metadata, CharacterTag tag, FLAVersion flaVersion, XFLXmlWriter writer, boolean small) throws XMLStreamException {
if (matrix == null) {
matrix = new MATRIX();
}
if (small) {
matrix = new Matrix(matrix).concatenate(Matrix.getScaleInstance(1 / SMALL_DIVISOR, 1 / SMALL_DIVISOR)).toMATRIX();
}
if (tag instanceof DefineButtonTag) {
DefineButtonTag bt = (DefineButtonTag) tag;
DefineButtonCxformTag bcx = (DefineButtonCxformTag) bt.getSwf().getCharacterIdTag(bt.buttonId, DefineButtonCxformTag.ID);
@@ -1496,16 +1574,16 @@ public class XFLConverter {
return date.getTime() / 1000;
}
private void convertLibrary(Reference<Integer> lastItemIdNumber, Set<CharacterTag> charactersExportedInFirstFrame, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, Map<CharacterTag, String> characterVariables, Map<CharacterTag, String> characterClasses, Map<CharacterTag, ScriptPack> characterScriptPacks, List<CharacterTag> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
private void convertLibrary(Reference<Integer> lastItemIdNumber, Set<CharacterTag> charactersExportedInFirstFrame, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, Map<CharacterTag, String> characterVariables, Map<CharacterTag, String> characterClasses, Map<CharacterTag, ScriptPack> characterScriptPacks, List<CharacterTag> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Set<ShapeTag> smallShapes) throws XMLStreamException {
statusStack.pushStatus("media");
convertMedia(lastItemIdNumber, charactersExportedInFirstFrame, lastImportedId, characterNameMap, characterImportLinkageURL, characters, swf, characterVariables, characterClasses, tags, files, datfiles, writer, statusStack);
statusStack.popStatus();
statusStack.pushStatus("symbols");
convertSymbols(lastItemIdNumber, charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, files, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
convertSymbols(lastItemIdNumber, charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, files, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, smallShapes);
statusStack.popStatus();
}
private void convertSymbols(Reference<Integer> lastItemIdNumber, Set<CharacterTag> charactersExportedInFirstFrame, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, Map<CharacterTag, String> characterVariables, Map<CharacterTag, String> characterClasses, Map<CharacterTag, ScriptPack> characterScriptPacks, List<CharacterTag> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<String, byte[]> files, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException {
private void convertSymbols(Reference<Integer> lastItemIdNumber, Set<CharacterTag> charactersExportedInFirstFrame, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, Map<CharacterTag, String> characterVariables, Map<CharacterTag, String> characterClasses, Map<CharacterTag, ScriptPack> characterScriptPacks, List<CharacterTag> nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap<String, byte[]> files, FLAVersion flaVersion, XFLXmlWriter writer, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Set<ShapeTag> smallShapes) throws XMLStreamException {
//boolean hasSymbol = false;
Reference<Integer> nextClipId = new Reference<>(-1);
writer.writeStartElement("symbols");
@@ -1643,7 +1721,7 @@ public class XFLConverter {
case 4:
ok = rec.buttonStateHitTest;
break;
}
}
if (!ok) {
break;
}
@@ -1666,18 +1744,18 @@ public class XFLConverter {
}
CharacterTag character = button.getSwf().getCharacter(rec.characterId);
if (character != null) {
MATRIX matrix = rec.placeMatrix;
MATRIX matrix = rec.placeMatrix;
XFLXmlWriter recCharWriter = new XFLXmlWriter();
if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(character))) {
ShapeTag shape = (ShapeTag) character;
statusStack.pushStatus(character.toString());
convertShape(lastImportedId, characterNameMap, character.getSwf(), matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter);
convertShape(lastImportedId, characterNameMap, character.getSwf(), matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter, rec.characterId, false /*nonlibrary*/);
statusStack.popStatus();
} else if (character instanceof MorphShapeTag) { //can happen for HIT_TEST frame
ShapeTag shape = ((MorphShapeTag) character).getStartShapeTag();
statusStack.pushStatus(character.toString());
convertShape(lastImportedId, characterNameMap, character.getSwf(), matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, true, false, recCharWriter);
convertShape(lastImportedId, characterNameMap, character.getSwf(), matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, true, false, recCharWriter, rec.characterId, false);
statusStack.popStatus();
} else if (character instanceof TextTag) {
statusStack.pushStatus(character.toString());
@@ -1692,13 +1770,19 @@ public class XFLConverter {
convertImageInstance(lastImportedId, characterNameMap, swf, null, matrix, (ImageTag) character, recCharWriter);
statusStack.popStatus();
} else {
convertSymbolInstance(-1, new AccessibilityBag() /*???*/, lastImportedId, characterNameMap, swf, null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, null, character.getSwf().getCharacter(rec.characterId), flaVersion, recCharWriter);
boolean small = false;
if (character instanceof ShapeTag) {
ShapeTag shape = (ShapeTag) character;
if (smallShapes.contains(shape)) {
small = true;
}
}
convertSymbolInstance(-1, new AccessibilityBag() /*???*/, lastImportedId, characterNameMap, swf, null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, null, character.getSwf().getCharacter(rec.characterId), flaVersion, recCharWriter, small);
}
int emptyDuration = frame - lastFrame - 1;
lastFrame = frame + duration - 1;
if (emptyDuration > 0) {
symbolStr.writeStartElement("DOMFrame", new String[]{
"index", Integer.toString(frame - emptyDuration),
@@ -1707,7 +1791,7 @@ public class XFLConverter {
symbolStr.writeElementValue("elements", "");
symbolStr.writeEndElement();
}
if (duration > 1) {
symbolStr.writeStartElement("DOMFrame", new String[]{
"index", Integer.toString(frame),
@@ -1716,12 +1800,12 @@ public class XFLConverter {
} else {
symbolStr.writeStartElement("DOMFrame", new String[]{
"index", Integer.toString(frame),
"keyMode", Integer.toString(KEY_MODE_NORMAL)});
"keyMode", Integer.toString(KEY_MODE_NORMAL)});
}
symbolStr.writeStartElement("elements");
symbolStr.writeCharactersRaw(recCharWriter.toString());
symbolStr.writeEndElement();
symbolStr.writeEndElement();
symbolStr.writeEndElement();
frame += duration - 1;
} else {
logger.log(Level.WARNING, "Character with id={0} was not found.", rec.characterId);
@@ -1743,9 +1827,9 @@ public class XFLConverter {
}
final ScriptPack spriteScriptPack = characterScriptPacks.containsKey(sprite) ? characterScriptPacks.get(sprite) : null;
extractMultilevelClips(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, sprite.getTags(), swf.getCharacterId(sprite), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
extractMultilevelClips(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, sprite.getTags(), swf.getCharacterId(sprite), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), sprite, swf.getCharacterId(sprite), characterVariables.get(sprite), nonLibraryShapes, tags, sprite.getTags(), getSymbolName(lastImportedId, characterNameMap, swf, symbol), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), sprite, swf.getCharacterId(sprite), characterVariables.get(sprite), nonLibraryShapes, tags, sprite.getTags(), getSymbolName(lastImportedId, characterNameMap, swf, symbol), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
} else if (symbol instanceof ShapeTag) {
symbolStr.writeStartElement("timeline");
@@ -1755,7 +1839,7 @@ public class XFLConverter {
symbolStr.writeStartElement("layers");
SHAPEWITHSTYLE shapeWithStyle = shape.getShapes();
if (shapeWithStyle != null) {
convertShape(lastImportedId, characterNameMap, symbol.getSwf(), null, shape.getShapeNum(), shapeWithStyle.shapeRecords, shapeWithStyle.fillStyles, shapeWithStyle.lineStyles, false, true, symbolStr);
convertShape(lastImportedId, characterNameMap, symbol.getSwf(), null, shape.getShapeNum(), shapeWithStyle.shapeRecords, shapeWithStyle.fillStyles, shapeWithStyle.lineStyles, false, true, symbolStr, symbol.getCharacterId(), smallShapes.contains(shape));
}
symbolStr.writeEndElement(); // layers
@@ -1786,11 +1870,11 @@ public class XFLConverter {
}
statusStack.pushStatus("extracting multilevel clips");
extractMultilevelClips(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, swf.getTags(), -1, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
extractMultilevelClips(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, swf.getTags(), -1, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
statusStack.popStatus();
statusStack.pushStatus("converting multiusage morphshapes");
extractMultiUsageMorphShapes(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, writer, swf, nonLibraryShapes, flaVersion, files, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
extractMultiUsageMorphShapes(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, writer, swf, nonLibraryShapes, flaVersion, files, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
statusStack.popStatus();
/*if (hasSymbol) {
@@ -2438,7 +2522,7 @@ public class XFLConverter {
writer.writeEndElement();
}
private static void convertFrames(AccessibilityBag accessibility, String symbolName, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, List<Integer> onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List<CharacterTag> nonLibraryShapes, ReadOnlyTagList timelineTags, int depth, FLAVersion flaVersion, XFLXmlWriter writer, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters) throws XMLStreamException {
private static void convertFrames(AccessibilityBag accessibility, String symbolName, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, List<Integer> onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List<CharacterTag> nonLibraryShapes, ReadOnlyTagList timelineTags, int depth, FLAVersion flaVersion, XFLXmlWriter writer, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Set<ShapeTag> smallShapes) throws XMLStreamException {
Logger.getLogger(XFLConverter.class.getName()).log(Level.FINE, "Converting frames of {0}", symbolName);
boolean lastIn = false;
XFLXmlWriter writer2 = new XFLXmlWriter();
@@ -2503,7 +2587,7 @@ public class XFLConverter {
shapeTweener = m;
shapeTween = false;
}
}
}
if (newCharId == -1 && newCharCls == null) {
newCharacter = character;
}
@@ -2627,12 +2711,12 @@ public class XFLConverter {
if ((character instanceof MorphShapeTag) && (!multiUsageMorphShapes.contains(character.getCharacterId()))) {
MorphShapeTag m2 = (MorphShapeTag) character;
statusStack.pushStatus(m2.toString());
convertShape(lastImportedId, characterNameMap, swf, matrix, m2.getShapeNum() == 1 ? 3 : 4, m2.getStartEdges().shapeRecords, m2.getFillStyles().getStartFillStyles(), m2.getLineStyles().getStartLineStyles(m2.getShapeNum()), true, false, addLastWriter);
convertShape(lastImportedId, characterNameMap, swf, matrix, m2.getShapeNum() == 1 ? 3 : 4, m2.getStartEdges().shapeRecords, m2.getFillStyles().getStartFillStyles(), m2.getLineStyles().getStartLineStyles(m2.getShapeNum()), true, false, addLastWriter, m2.getCharacterId(), false /*??*/);
statusStack.popStatus();
shapeTween = true;
} else {
SHAPEWITHSTYLE endShape = m.getShapeAtRatio(65535); //lastTweenRatio);
convertShape(lastImportedId, characterNameMap, swf, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(65535), m.getLineStyles().getLineStylesAt(m.getShapeNum(), 65535), true, false, addLastWriter);
convertShape(lastImportedId, characterNameMap, swf, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(65535), m.getLineStyles().getLineStylesAt(m.getShapeNum(), 65535), true, false, addLastWriter, m.getCharacterId(), false /*??*/);
}
Integer ease = EasingDetector.getEaseFromShapeRatios(morphShapeRatios);
@@ -2651,7 +2735,7 @@ public class XFLConverter {
}
if (character instanceof ShapeTag && standaloneShapeTweener != null) {
convertSymbolInstance(frame, accessibility, lastImportedId, characterNameMap, swf, instanceName, standaloneShapeTweenerMatrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, standaloneShapeTweener, flaVersion, elementsWriter);
convertSymbolInstance(frame, accessibility, lastImportedId, characterNameMap, swf, instanceName, standaloneShapeTweenerMatrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, standaloneShapeTweener, flaVersion, elementsWriter, false);
standaloneShapeTweener = null;
} else if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(character))) {
if (lastCharacter == character && Objects.equals(matrix, lastMatrix)) {
@@ -2659,7 +2743,7 @@ public class XFLConverter {
} else {
ShapeTag shape = (ShapeTag) character;
statusStack.pushStatus(character.toString());
convertShape(lastImportedId, characterNameMap, swf, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter);
convertShape(lastImportedId, characterNameMap, swf, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter, shape.getCharacterId(), false /*nonlibrary*/);
statusStack.popStatus();
}
shapeTween = false;
@@ -2671,14 +2755,14 @@ public class XFLConverter {
shapeTweener = null;
standaloneShapeTweener = m;
standaloneShapeTweenerMatrix = matrix;
convertSymbolInstance(frame, accessibility, lastImportedId, characterNameMap, swf, instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, flaVersion, elementsWriter);
convertSymbolInstance(frame, accessibility, lastImportedId, characterNameMap, swf, instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, flaVersion, elementsWriter, false);
} else {
morphShapeRatios.add(ratio == -1 ? 0 : ratio);
if (lastCharacter == m && Objects.equals(matrix, lastMatrix)) {
elementsWriter.writeCharactersRaw(lastElements);
} else {
statusStack.pushStatus(m.toString());
convertShape(lastImportedId, characterNameMap, swf, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter);
convertShape(lastImportedId, characterNameMap, swf, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter, m.getCharacterId(), false /*?*/);
statusStack.popStatus();
}
shapeTween = true;
@@ -2694,7 +2778,14 @@ public class XFLConverter {
} else if (character instanceof ImageTag) {
convertImageInstance(lastImportedId, characterNameMap, swf, instanceName, matrix, (ImageTag) character, elementsWriter);
} else if (character != null) {
convertSymbolInstance(frame, accessibility, lastImportedId, characterNameMap, swf, instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, flaVersion, elementsWriter);
boolean small = false;
if (character instanceof ShapeTag) {
ShapeTag shape = (ShapeTag) character;
if (smallShapes.contains(shape)) {
small = true;
}
}
convertSymbolInstance(frame, accessibility, lastImportedId, characterNameMap, swf, instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, flaVersion, elementsWriter, small);
}
}
@@ -3882,11 +3973,12 @@ public class XFLConverter {
List<Integer> multiUsageMorphShapes,
StatusStack statusStack,
Map<CharacterTag, String> characterImportLinkageURL,
Set<CharacterTag> characters
Set<CharacterTag> characters,
Set<ShapeTag> smallShapes
) throws XMLStreamException {
XFLXmlWriter symbolStr = new XFLXmlWriter();
extractMultilevelClips(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, timelineTags, spriteId, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
extractMultilevelClips(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, timelineTags, spriteId, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
if (nextClipId.getVal() < 0) {
nextClipId.setVal(swf.getNextCharacterId());
@@ -3904,7 +3996,7 @@ public class XFLConverter {
"lastModified", Long.toString(getTimestamp(swf))});
symbolStr.writeAttribute("symbolType", "graphic");
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), null, objectId, "", nonLibraryShapes, timelineTags, timelineTags, getMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), null, objectId, "", nonLibraryShapes, timelineTags, timelineTags, getMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
symbolStr.writeEndElement(); // DOMSymbolItem
String symbolStr2 = prettyFormatXML(symbolStr.toString());
@@ -3992,7 +4084,8 @@ public class XFLConverter {
List<Integer> multiUsageMorphShapes,
StatusStack statusStack,
Map<CharacterTag, String> characterImportLinkageURL,
Set<CharacterTag> characters
Set<CharacterTag> characters,
Set<ShapeTag> smallShapes
) throws XMLStreamException {
for (int objectId : multiUsageMorphShapes) {
@@ -4024,7 +4117,7 @@ public class XFLConverter {
}
}
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), null, objectId, "", nonLibraryShapes, swf.getTags(), new ReadOnlyTagList(timelineTags), getSymbolName(lastImportedId, characterNameMap, swf, swf.getCharacter(objectId)), flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>(), statusStack, characterImportLinkageURL, characters);
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), null, objectId, "", nonLibraryShapes, swf.getTags(), new ReadOnlyTagList(timelineTags), getSymbolName(lastImportedId, characterNameMap, swf, swf.getCharacter(objectId)), flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>(), statusStack, characterImportLinkageURL, characters, smallShapes);
symbolStr.writeEndElement(); // DOMSymbolItem
String symbolStr2 = prettyFormatXML(symbolStr.toString());
@@ -4062,7 +4155,8 @@ public class XFLConverter {
List<Integer> multiUsageMorphShapes,
StatusStack statusStack,
Map<CharacterTag, String> characterImportLinkageURL,
Set<CharacterTag> characters
Set<CharacterTag> characters,
Set<ShapeTag> smallShapes
) throws XMLStreamException {
int f = 0;
@@ -4275,7 +4369,7 @@ public class XFLConverter {
//set timelined?
delegatedTimeline.add(showFrame);
}
addExtractedClip(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, new ReadOnlyTagList(delegatedTimeline), spriteId, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
addExtractedClip(characterScriptPacks, lastItemIdNumber, lastImportedId, characterNameMap, new ReadOnlyTagList(delegatedTimeline), spriteId, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
placeToMaskedSymbol.put(secondPlace, new MultiLevelClip(secondPlace, nextClipId.getVal(), numFrames));
}
}
@@ -4297,7 +4391,7 @@ public class XFLConverter {
}
//Note: symbolId argument might be a virtual symbol like MaskedSymbol
private void convertTimelines(Map<CharacterTag, ScriptPack> characterScriptPacks, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, AbcIndexing abcIndex, CharacterTag sprite, int symbolId, String linkageIdentifier, List<CharacterTag> nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, String spriteName, FLAVersion flaVersion, HashMap<String, byte[]> files, XFLXmlWriter writer, ScriptPack scriptPack, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters) throws XMLStreamException {
private void convertTimelines(Map<CharacterTag, ScriptPack> characterScriptPacks, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, AbcIndexing abcIndex, CharacterTag sprite, int symbolId, String linkageIdentifier, List<CharacterTag> nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, String spriteName, FLAVersion flaVersion, HashMap<String, byte[]> files, XFLXmlWriter writer, ScriptPack scriptPack, Map<PlaceObjectTypeTag, MultiLevelClip> placeToMaskedSymbol, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Set<ShapeTag> smallShapes) throws XMLStreamException {
ScriptPack characterScriptPack = sprite == null ? null : characterScriptPacks.containsKey(sprite) ? characterScriptPacks.get(sprite) : null;
if (sprite == null && symbolId == -1) {
@@ -4688,7 +4782,7 @@ public class XFLConverter {
"color", randomOutlineColor(),
"layerType", "mask",
"locked", "true"});
convertFrames(accessibility, symbolName, lastImportedId, characterNameMap, swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, sceneTimelineTags, po.getDepth(), flaVersion, writer, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
convertFrames(accessibility, symbolName, lastImportedId, characterNameMap, swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, sceneTimelineTags, po.getDepth(), flaVersion, writer, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
writer.writeEndElement();
int parentIndex = index;
@@ -4710,7 +4804,7 @@ public class XFLConverter {
handledClips.add(po2);
for (int ndx = po.getClipDepth() - 1; ndx > po2.getClipDepth(); ndx--) {
boolean nonEmpty = writeLayer(accessibility, symbolName, lastImportedId, characterNameMap, swf, index, depthToFramesList.get(ndx), ndx, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, sceneTimelineTags, flaVersion, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
boolean nonEmpty = writeLayer(accessibility, symbolName, lastImportedId, characterNameMap, swf, index, depthToFramesList.get(ndx), ndx, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, sceneTimelineTags, flaVersion, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
for (int i = clipFrame; i <= lastFrame; i++) {
depthToFramesList.get(ndx).remove((Integer) i);
}
@@ -4789,7 +4883,7 @@ public class XFLConverter {
}
for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) {
boolean nonEmpty = writeLayer(accessibility, symbolName, lastImportedId, characterNameMap, swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, sceneTimelineTags, flaVersion, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
boolean nonEmpty = writeLayer(accessibility, symbolName, lastImportedId, characterNameMap, swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, sceneTimelineTags, flaVersion, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
for (int i = clipFrame; i <= lastFrame; i++) {
depthToFramesList.get(nd).remove((Integer) i);
}
@@ -4826,7 +4920,7 @@ public class XFLConverter {
}
}
boolean nonEmpty = writeLayer(accessibility, symbolName, lastImportedId, characterNameMap, swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, sceneTimelineTags, flaVersion, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
boolean nonEmpty = writeLayer(accessibility, symbolName, lastImportedId, characterNameMap, swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, sceneTimelineTags, flaVersion, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
if (nonEmpty) {
index++;
}
@@ -4865,7 +4959,7 @@ public class XFLConverter {
writer.writeEndElement(); //DOMLayer
}
private boolean writeLayer(AccessibilityBag accessibility, String symbolName, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, int index, List<Integer> onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List<CharacterTag> nonLibraryShapes, ReadOnlyTagList timelineTags, FLAVersion flaVersion, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters) throws XMLStreamException {
private boolean writeLayer(AccessibilityBag accessibility, String symbolName, Reference<Integer> lastImportedId, Map<CharacterTag, String> characterNameMap, SWF swf, int index, List<Integer> onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List<CharacterTag> nonLibraryShapes, ReadOnlyTagList timelineTags, FLAVersion flaVersion, List<Integer> multiUsageMorphShapes, StatusStack statusStack, Map<CharacterTag, String> characterImportLinkageURL, Set<CharacterTag> characters, Set<ShapeTag> smallShapes) 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));
@@ -4884,7 +4978,7 @@ public class XFLConverter {
layerPrev.writeCharacters(""); // todo honfika: hack to close start tag
String layerAfter = "</DOMLayer>";
int prevLength = writer.length();
convertFrames(accessibility, symbolName, lastImportedId, characterNameMap, swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, timelineTags, d, flaVersion, writer, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
convertFrames(accessibility, symbolName, lastImportedId, characterNameMap, swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, timelineTags, d, flaVersion, writer, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
statusStack.popStatus();
return writer.length() != prevLength;
}
@@ -5549,11 +5643,13 @@ public class XFLConverter {
}
convertFonts(lastItemIdNumber, lastImportedId, characterNameMap, swf, characters, domDocument, statusStack, characterVariables, characterClasses, charactersExportedInFirstFrame, characterImportLinkageURL);
convertLibrary(lastItemIdNumber, charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, statusStack);
Set<ShapeTag> smallShapes = getSmallShapes(swf);
convertLibrary(lastItemIdNumber, charactersExportedInFirstFrame, characterImportLinkageURL, characters, lastImportedId, characterNameMap, swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, smallShapes);
//domDocument.writeStartElement("timelines");
statusStack.pushStatus("main timeline");
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), null, -1, null, nonLibraryShapes, swf.getTags(), swf.getTags(), null, flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters);
convertTimelines(characterScriptPacks, lastImportedId, characterNameMap, swf, swf.getAbcIndex(), null, -1, null, nonLibraryShapes, swf.getTags(), swf.getTags(), null, flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack, characterImportLinkageURL, characters, smallShapes);
statusStack.popStatus();
//domDocument.writeEndElement();
@@ -6062,10 +6158,9 @@ public class XFLConverter {
}
private static void convertAdjustColorFilter(COLORMATRIXFILTER filter, XFLXmlWriter writer) throws XMLStreamException {
ColorMatrixConvertor colorMatrixConvertor = new ColorMatrixConvertor(filter.matrix);
writer.writeEmptyElement("AdjustColorFilter", new String[]{
"brightness", Integer.toString(colorMatrixConvertor.getBrightness()),
"contrast", Integer.toString(colorMatrixConvertor.getContrast()),

View File

@@ -0,0 +1,150 @@
package com.jpexs.decompiler.flash.xfl.shapefixer;
import com.jpexs.helpers.Reference;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.List;
/**
* Path orientation detector.
* @author JPEXS
*/
public final class PathOrientation {
public enum Orientation {
CLOCKWISE, COUNTER_CLOCKWISE, DEGENERATE, OPEN_CONTOUR
}
/**
* Result per closed subpath in drawing order. Open contours are reported
* once as OPEN_CONTOUR.
*/
public static void orientations(Shape shape, List<Orientation> result, List<Double> areas) {
// Flatten curves to line segments for robust area computation.
// flatness ~0.5px is usually fine; limit prevents infinite subdivision on pathological curves.
final double flatness = 0.5;
final int limit = 10;
PathIterator it = shape.getPathIterator((AffineTransform) null);
FlatteningPathIterator fpi = new FlatteningPathIterator(it, flatness, limit);
double[] coords = new double[6];
List<double[]> current = new ArrayList<>();
double startX = 0;
double startY = 0;
double lastX = 0;
double lastY = 0;
boolean hasOpen = false;
while (!fpi.isDone()) {
int seg = fpi.currentSegment(coords);
switch (seg) {
case PathIterator.SEG_MOVETO:
// Start a new subpath
if (!current.isEmpty()) {
// Previous subpath ended without SEG_CLOSE
result.add(Orientation.OPEN_CONTOUR);
areas.add(0.0);
hasOpen = true;
current.clear();
}
startX = lastX = coords[0];
startY = lastY = coords[1];
current.add(new double[]{lastX, lastY});
break;
case PathIterator.SEG_LINETO:
lastX = coords[0];
lastY = coords[1];
current.add(new double[]{lastX, lastY});
break;
case PathIterator.SEG_CLOSE:
// Close current subpath by linking back to start point
if (!current.isEmpty()) {
Reference<Orientation> orientationRef = new Reference<>(null);
Reference<Double> areaRef = new Reference<>(0.0);
orientationOfClosedRing(current, areaRef, orientationRef);
result.add(orientationRef.getVal());
areas.add(areaRef.getVal());
current.clear();
} else {
// SEG_CLOSE without points ignore
}
// Reset last point to start of next potential subpath
lastX = startX;
lastY = startY;
break;
default:
// Should not happen because we flattened, but keep for completeness
throw new IllegalStateException("Unexpected segment type: " + seg);
}
fpi.next();
}
// If path ended without SEG_CLOSE for the last subpath
if (!current.isEmpty()) {
result.add(Orientation.OPEN_CONTOUR);
areas.add(0.0);
hasOpen = true;
}
}
/**
* Compute orientation of a closed ring using the shoelace formula over its
* vertices.
*/
private static void orientationOfClosedRing(List<double[]> pts, Reference<Double> areaRef, Reference<Orientation> resultRef) {
// Ensure first != last; algorithm handles implicit closing edge (last->first)
if (pts.size() < 3) {
resultRef.setVal(Orientation.DEGENERATE);
areaRef.setVal(0.0);
return;
}
double area2 = 0.0; // 2 * signed area
for (int i = 0, n = pts.size(); i < n; i++) {
double[] a = pts.get(i);
double[] b = pts.get((i + 1) % n);
area2 += (a[0] * b[1]) - (b[0] * a[1]);
}
// Tolerance to treat near-zero areas as degenerate (units are in user space)
double eps = 1e-9;
if (Math.abs(area2) <= eps) {
resultRef.setVal(Orientation.DEGENERATE);
areaRef.setVal(0.0);
return;
}
resultRef.setVal(area2 > 0 ? Orientation.CLOCKWISE : Orientation.COUNTER_CLOCKWISE);
areaRef.setVal(Math.abs(area2/2));
}
public static void orientationSingleClosed(Shape shape, Reference<Orientation> orientationRef, Reference<Double> areaRef) {
List<Orientation> result = new ArrayList<>();
List<Double> areas = new ArrayList<>();
orientations(shape, result, areas);
if (result.isEmpty()) {
orientationRef.setVal(Orientation.DEGENERATE);
areaRef.setVal(0.0);
return;
}
// Prefer the first closed orientation encountered
for (int i = 0; i < result.size(); i++) {
Orientation o = result.get(i);
if (o != Orientation.OPEN_CONTOUR) {
orientationRef.setVal(o);
areaRef.setVal(areas.get(i));
return;
}
}
orientationRef.setVal(Orientation.OPEN_CONTOUR);
areaRef.setVal(0.0);
}
}

View File

@@ -111,7 +111,7 @@ public class ShapeFixer {
//int oldpct = 0;
loopi1:
for (int i1 = 0; i1 < shapes.size(); i1++) {
int layer = layers.get(i1);
int layer = layers.get(i1);
/*if (i1 % 10 == 0) {
int pct = i1 * 100 / shapes.size();
if (oldpct != pct) {
@@ -132,20 +132,19 @@ public class ShapeFixer {
if (layers.get(i2) != layer) {
continue;
}
//its with fills vs stroke only, we can ignore these, I hope
if (fillStyles0.get(i1) == 0
if (fillStyles0.get(i1) == 0
&& fillStyles1.get(i1) == 0
&& (fillStyles0.get(i2) != 0 || fillStyles1.get(i2) != 0)) {
continue;
}
if (fillStyles0.get(i2) == 0
if (fillStyles0.get(i2) == 0
&& fillStyles1.get(i2) == 0
&& (fillStyles0.get(i1) != 0 || fillStyles1.get(i1) != 0)) {
continue;
}
loopj2:
for (int j2 = 0; j2 < shapes.get(i2).size(); j2++) {
BezierEdge be2 = shapes.get(i2).get(j2);
@@ -214,13 +213,49 @@ public class ShapeFixer {
continue;
}
if (t1Ref.size() == 2) {
if (t1Ref.size() > 1) {
double eps = 1 / BezierEdge.ROUND_VALUE;
Point2D last = intPoints.get(0);
for (int i = 1; i < intPoints.size(); i++) {
Point2D current = intPoints.get(i);
if (current.distance(last) < eps) {
intPoints.remove(i);
t1Ref.remove(i);
t2Ref.remove(i);
i--;
continue;
}
last = current;
}
}
/*if (t1Ref.size() == 2) {
if (intPoints.get(0).distance(intPoints.get(1)) < 1/256f) {
t1Ref.remove(1);
t2Ref.remove(1);
intPoints.remove(1);
}
}
if (t1Ref.size() == 3) {
}*/
if (t1Ref.size() == 1) {
if ((t1Ref.get(0) == 0 || t1Ref.get(0) == 1)
&& (t2Ref.get(0) == 0 || t2Ref.get(0) == 1)) {
continue;
}
}
}
//sharing start end end point
if (t1Ref.size() == 2) {
if ((t1Ref.get(0) == 0 || t1Ref.get(0) == 1)
&& (t1Ref.get(1) == 0 || t1Ref.get(1) == 1)
&& (t2Ref.get(0) == 0 || t2Ref.get(0) == 1)
&& (t2Ref.get(1) == 0 || t2Ref.get(1) == 1)) {
continue;
}
}
if (DEBUG_PRINT) {
System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg());
System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2));
@@ -265,10 +300,15 @@ public class ShapeFixer {
be2L.setEndPoint(intP);
be2R.setBeginPoint(intP);
be1L.roundHalf();
be1R.roundHalf();
be2L.roundHalf();
be2R.roundHalf();
/*be1L.roundX();
be1R.roundX();
be2L.roundX();
be2R.roundX();*/
be1L.roundX();
be1R.roundX();
be2L.roundX();
be2R.roundX();
if (i1 == i2) {
if (j1 < j2) {
shapes.get(i1).remove(j2);
@@ -333,6 +373,13 @@ public class ShapeFixer {
}
}
}
for (int i1 = 0; i1 < shapes.size(); i1++) {
for (int j1 = 0; j1 < shapes.get(i1).size(); j1++) {
BezierEdge be1 = shapes.get(i1).get(j1);
be1.shrinkToLine();
}
}
}
private void splitToLayers(
@@ -493,6 +540,8 @@ public class ShapeFixer {
beforeHandle(shapeNum, shapes, fillStyles0, fillStyles1, lineStyles, layers, baseFillStyles, baseLineStyles, fillStyleLayers, lineStyleLayers);
if (Configuration.flaExportFixShapes.get()) {
SwitchedFillSidesFixer switchedFillSidesFixer = new SwitchedFillSidesFixer();
switchedFillSidesFixer.fixSwitchedFills(shapeNum, records, baseFillStyles, baseLineStyles, shapes, fillStyles0, fillStyles1, layers);
detectOverlappingEdges(shapes, fillStyles0, fillStyles1, lineStyles, layers);
}

View File

@@ -0,0 +1,664 @@
/*
* Copyright (C) 2010-2025 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.SWF;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.shape.CurvedEdge;
import com.jpexs.decompiler.flash.exporters.shape.IEdge;
import com.jpexs.decompiler.flash.exporters.shape.ShapeExporterBase;
import com.jpexs.decompiler.flash.math.BezierEdge;
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.GRADRECORD;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.Reference;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Switched fill sides fixer. This will fix orientation of fillstyle0 and
* fillstyle1 to be on left and right side of the vector.
*
* @author JPEXS
*/
public class SwitchedFillSidesFixer {
private static double polygonArea(List<IEdge> loop) {
double area = 0;
for (IEdge e : loop) {
assert (e != null);
if (e instanceof CurvedEdge) {
CurvedEdge ce = (CurvedEdge) e;
area += (e.getFromX() * ce.getControlY() - ce.getControlX() * e.getFromY());
area += (ce.getControlX() * ce.getToY() - ce.getToX() * ce.getControlY());
continue;
}
area += (e.getFromX() * e.getToY() - e.getToX() * e.getFromY());
}
return area / 2.0;
}
private BezierEdge iedgeToBezier(IEdge ie) {
assert (ie != null);
if (ie instanceof CurvedEdge) {
CurvedEdge ce = (CurvedEdge) ie;
return new BezierEdge(ce.getFromX(), ce.getFromY(),
ce.getControlX(), ce.getControlY(),
ce.getToX(), ce.getToY()
);
} else {
return new BezierEdge(ie.getFromX(), ie.getFromY(), ie.getToX(), ie.getToY());
}
}
private static class Polygon {
List<IEdge> list;
List<Polygon> children = new ArrayList<>();
boolean ccw = false;
GeneralPath path;
int fillStyle;
boolean filled = true;
Polygon parent = null;
Area areaObj;
double area;
Rectangle2D bbox;
public Polygon(List<IEdge> list, int fillStyle) {
this.list = list;
/*double polyArea = polygonArea(list);
if (polyArea < 0) {
ccw = true;
}*/
path = toPath();
//this.ccw = PathOrientation.orientationSingleClosed(path) == PathOrientation.Orientation.COUNTER_CLOCKWISE;
Reference<PathOrientation.Orientation> orientationRef = new Reference<>(null);
Reference<Double> areaRef = new Reference<>(0.0);
PathOrientation.orientationSingleClosed(path, orientationRef, areaRef);
this.ccw = orientationRef.getVal() == PathOrientation.Orientation.COUNTER_CLOCKWISE;
this.area = areaRef.getVal();
this.areaObj = new Area(path);
this.bbox = this.areaObj.getBounds2D();
this.fillStyle = fillStyle;
}
private GeneralPath toPath() {
GeneralPath gp = new GeneralPath();
int lastX = Integer.MAX_VALUE;
int lastY = Integer.MAX_VALUE;
for (IEdge e : list) {
if (lastX == Integer.MAX_VALUE || lastX != e.getFromX() || lastY != e.getFromY()) {
gp.moveTo(e.getFromX(), e.getFromY());
}
if (e instanceof CurvedEdge) {
CurvedEdge ce = (CurvedEdge) e;
gp.quadTo(ce.getControlX(), ce.getControlY(), ce.getToX(), ce.getToY());
} else {
gp.lineTo(e.getToX(), e.getToY());
}
lastX = e.getToX();
lastY = e.getToY();
}
if (lastX == list.get(0).getFromX() && lastY == list.get(0).getFromY()) {
gp.closePath();
}
return gp;
}
public boolean contains(Polygon other) {
if (other.areaObj.isEmpty()) {
return false;
}
Area diff = new Area(other.areaObj);
diff.subtract(areaObj);
return diff.isEmpty();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (IEdge e : list) {
sb.append("M ").append(e.getFromX()).append(" ").append(e.getFromY()).append(" ");
if (e instanceof CurvedEdge) {
CurvedEdge ce = (CurvedEdge) e;
sb.append("Q ").append(ce.getControlX()).append(" ").append(ce.getControlY()).append(" ");
} else {
sb.append("L ");
}
sb.append(e.getToX()).append(" ").append(e.getToY()).append(" ");
}
return sb.toString().trim();
}
}
static class GridIndex {
// Simple uniform grid over bbox domain
private final double cellSize;
private final Map<Long, List<Polygon>> cells = new HashMap<>();
private final double minX, minY;
GridIndex(Collection<Polygon> polys, double cellSize) {
this.cellSize = cellSize;
// Compute global origin (minX/minY) to keep keys small
double minx = Double.POSITIVE_INFINITY, miny = Double.POSITIVE_INFINITY;
for (Polygon w : polys) {
Rectangle2D b = w.bbox;
if (b.getMinX() < minx) {
minx = b.getMinX();
}
if (b.getMinY() < miny) {
miny = b.getMinY();
}
}
this.minX = minx;
this.minY = miny;
// Insert
for (Polygon w : polys) {
forEachCell(w.bbox, (gx, gy) -> {
cells.computeIfAbsent(key(gx, gy), k -> new ArrayList<>()).add(w);
});
}
}
private long key(int gx, int gy) {
// Pack two 32-bit ints into one long
return ((long) gx << 32) ^ (gy & 0xffffffffL);
}
private int gx(double x) {
return (int) Math.floor((x - minX) / cellSize);
}
private int gy(double y) {
return (int) Math.floor((y - minY) / cellSize);
}
private void forEachCell(Rectangle2D r, CellConsumer cc) {
int x0 = gx(r.getMinX());
int x1 = gx(r.getMaxX());
int y0 = gy(r.getMinY());
int y1 = gy(r.getMaxY());
for (int x = x0; x <= x1; x++) {
for (int y = y0; y <= y1; y++) {
cc.accept(x, y);
}
}
}
List<Polygon> query(Rectangle2D r) {
// Collect candidates from overlapping cells (deduplicated)
HashSet<Polygon> set = new HashSet<>();
forEachCell(r, (gx, gy) -> {
List<Polygon> bucket = cells.get(key(gx, gy));
if (bucket != null) {
set.addAll(bucket);
}
});
return new ArrayList<>(set);
}
}
interface CellConsumer {
void accept(int gx, int gy);
}
public static void buildContainment(List<Polygon> polygons) {
Map<Integer, List<Polygon>> byStyle = polygons.stream()
.collect(java.util.stream.Collectors.groupingBy(w -> w.fillStyle));
for (Map.Entry<Integer, List<Polygon>> e : byStyle.entrySet()) {
List<Polygon> group = e.getValue();
double avgW = group.stream().mapToDouble(w -> w.bbox.getWidth()).average().orElse(1.0);
double avgH = group.stream().mapToDouble(w -> w.bbox.getHeight()).average().orElse(1.0);
double cellSize = Math.max(1.0, Math.max(avgW, avgH));
GridIndex index = new GridIndex(group, cellSize);
group.sort((a, b) -> Double.compare(b.area, a.area));
for (int i = group.size() - 1; i >= 0; i--) {
Polygon inner = group.get(i);
List<Polygon> candidates = index.query(inner.bbox);
Polygon bestParent = null;
double bestArea = Double.POSITIVE_INFINITY;
for (Polygon outer : candidates) {
if (outer == inner) {
continue;
}
if (outer.area <= inner.area) {
continue; // only larger can contain
}
if (!outer.bbox.contains(inner.bbox)) {
continue; // cheap reject
}
if (outer.contains(inner)) {
if (outer.area < bestArea) {
bestArea = outer.area;
bestParent = outer;
}
}
}
if (bestParent != null) {
bestParent.children.add(inner);
}
}
}
}
private void fixSidesInLayer(
List<List<IEdge>> fillList,
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
int layer,
int startIndex, int endIndex,
Map<Integer, Integer> globalToLocalFillStyleMap
) {
layer++;
if (layer >= fillList.size()) {
Logger.getLogger(SwitchedFillSidesFixer.class.getName()).warning("FillResolver - Layer value larger than fill list size.");
return;
}
int fillStyleIdx = Integer.MAX_VALUE;
List<IEdge> currentList = new ArrayList<>();
List<List<IEdge>> allLists = new ArrayList<>();
List<Integer> listFills = new ArrayList<>();
int lastToX = Integer.MAX_VALUE;
int lastToY = Integer.MAX_VALUE;
int lastMoveToX = Integer.MAX_VALUE;
int lastMoveToY = Integer.MAX_VALUE;
for (int i = 0; i < fillList.get(layer).size(); i++) {
IEdge e = fillList.get(layer).get(i);
if (fillStyleIdx != e.getFillStyleIdx()
|| (e.getFromX() != lastToX) || (e.getFromY() != lastToY)
|| (e.getFromX() == lastMoveToX && e.getFromY() == lastMoveToY)) {
if (fillStyleIdx != Integer.MAX_VALUE) {
allLists.add(currentList);
listFills.add(fillStyleIdx);
currentList = new ArrayList<>();
}
fillStyleIdx = e.getFillStyleIdx();
lastMoveToX = e.getFromX();
lastMoveToY = e.getFromY();
}
currentList.add(e);
lastToX = e.getToX();
lastToY = e.getToY();
}
if (!currentList.isEmpty()) {
allLists.add(currentList);
listFills.add(fillStyleIdx);
}
List<Polygon> polygons = new ArrayList<>();
for (int i = 0; i < allLists.size(); i++) {
List<IEdge> list = allLists.get(i);
polygons.add(new Polygon(list, listFills.get(i)));
}
/*for (Polygon outer : polygons) {
for (Polygon inner : polygons) {
if (outer != inner && inner.fillStyle == outer.fillStyle) {
boolean cont = outer.contains(inner);
if (cont) {
if (inner.children.contains(outer)) {
inner.children.remove(outer);
}
outer.children.add(inner);
}
}
}
}
loopmod:
while (true) {
for (Polygon poly : polygons) {
for (int c = 0; c < poly.children.size(); c++) {
for (int c2 = 0; c2 < poly.children.size(); c2++) {
if (poly.children.get(c).children.contains(poly.children.get(c2))) {
poly.children.remove(c2);
continue loopmod;
}
}
}
}
break;
}*/
buildContainment(polygons);
for (Polygon poly : polygons) {
for (Polygon child : poly.children) {
child.parent = poly;
}
}
for (Polygon poly : polygons) {
int depth = 0;
Polygon parent = poly.parent;
while (parent != null) {
parent = parent.parent;
depth++;
}
poly.filled = depth % 2 == 0;
}
Map<BezierEdge, List<Integer>> beToFillStyle0List = new LinkedHashMap<>();
Map<BezierEdge, List<Integer>> beToFillStyle1List = new LinkedHashMap<>();
Map<BezierEdge, Integer> beToFillStyle0 = new LinkedHashMap<>();
Map<BezierEdge, Integer> beToFillStyle1 = new LinkedHashMap<>();
for (int i = 0; i < polygons.size(); i++) {
Polygon polygon = polygons.get(i);
List<IEdge> list = polygon.list;
fillStyleIdx = listFills.get(i);
boolean clockwise = !polygon.ccw;
for (IEdge e : list) {
BezierEdge be = iedgeToBezier(e);
BezierEdge beRev = be.reverse();
int localFs = globalToLocalFillStyleMap.get(fillStyleIdx);
BezierEdge search = new BezierEdge(180.0, -3040.0, 480.0, -3400.0);
boolean print = false;
if (be.equals(search)) {
System.err.println("xxx");
System.err.println("" + polygon);
print = true;
}
if (be.equals(search.reverse())) {
System.err.println("yyy");
print = true;
}
if (print) {
System.err.println("localFS: " + localFs);
System.err.println("filled: " + polygon.filled);
System.err.println("clockwise: " + clockwise);
}
if (polygon.filled == clockwise) {
if (!beToFillStyle1List.containsKey(be)) {
beToFillStyle1List.put(be, new ArrayList<>());
}
if (!beToFillStyle0List.containsKey(beRev)) {
beToFillStyle0List.put(beRev, new ArrayList<>());
}
beToFillStyle1List.get(be).add(localFs);
beToFillStyle0List.get(beRev).add(localFs);
if (print) {
System.err.println("setting FS1 and rev FS0");
}
} else {
if (!beToFillStyle0List.containsKey(be)) {
beToFillStyle0List.put(be, new ArrayList<>());
}
if (!beToFillStyle1List.containsKey(beRev)) {
beToFillStyle1List.put(beRev, new ArrayList<>());
}
beToFillStyle0List.get(be).add(localFs);
beToFillStyle1List.get(beRev).add(localFs);
if (print) {
System.err.println("setting FS0 and rev FS1");
}
}
if (print) {
System.err.println("");
}
}
}
for (BezierEdge be : beToFillStyle0List.keySet()) {
/*for (int i = beToFillStyle0List.get(be).size() - 1; i >= 0; i--) {
Integer fs = beToFillStyle0List.get(be).get(i);
if (beToFillStyle1List.containsKey(be) && beToFillStyle1List.get(be).contains(fs)) {
beToFillStyle0List.get(be).remove(fs);
beToFillStyle1List.get(be).remove(fs);
}
}*/
int fs = -1;
if (beToFillStyle0List.get(be).size() == 1) {
fs = beToFillStyle0List.get(be).get(0);
}
if (!beToFillStyle0.containsKey(be) || beToFillStyle0.get(be) > 0 || fs == -1) {
beToFillStyle0.put(be, fs);
}
if (!beToFillStyle1.containsKey(be.reverse()) || beToFillStyle1.get(be.reverse()) > 0 || fs == -1) {
beToFillStyle1.put(be.reverse(), fs);
}
}
for (BezierEdge be : beToFillStyle1List.keySet()) {
int fs = -1;
if (beToFillStyle1List.get(be).size() == 1) {
fs = beToFillStyle1List.get(be).get(0);
}
if (!beToFillStyle1.containsKey(be) || beToFillStyle1.get(be) > 0 || fs == -1) {
beToFillStyle1.put(be, fs);
}
if (!beToFillStyle0.containsKey(be.reverse()) || beToFillStyle0.get(be.reverse()) > 0 || fs == -1) {
beToFillStyle0.put(be.reverse(), fs);
}
}
for (int i = startIndex; i < endIndex; i++) {
List<BezierEdge> shape = shapes.get(i);
for (int j = 0; j < shape.size(); j++) {
BezierEdge be = shape.get(j);
if (be.isEmpty()) {
continue;
}
Integer fs0before = fillStyles0.get(i);
Integer fs1before = fillStyles1.get(i);
if (fs0before == 0 && fs1before == 0) { //only strokes
break;
}
Integer fs0after = beToFillStyle0.get(be);
Integer fs1after = beToFillStyle1.get(be);
if (fs0after == null) {
fs0after = 0;
}
if (fs1after == null) {
fs1after = 0;
}
if (fs0after == -1 || fs1after == -1) {
break;
}
if (fs0after == 0 && Objects.equals(fs1after, fs1before)) {
fs0after = fs0before;
} else if (fs1after == 0 && Objects.equals(fs0after, fs0before)) {
fs1after = fs1before;
}
fillStyles0.set(i, fs0after);
fillStyles1.set(i, fs1after);
if (!Objects.equals(fs0before, fs0after) || !Objects.equals(fs1before, fs1after)) {
Logger.getLogger(SwitchedFillSidesFixer.class.getName()).log(Level.FINE, "Changed edge {0} - old: {1}, {2} new: {3}, {4}", new Object[]{be, fs0before, fs1before, fs0after, fs1after});
}
break;
}
}
}
public void fixSwitchedFills(
int shapeNum,
List<SHAPERECORD> records,
FILLSTYLEARRAY fillStyles,
LINESTYLEARRAY lineStyles,
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> layers
) {
SHAPEWITHSTYLE shp = new SHAPEWITHSTYLE();
shp.shapeRecords = records;
shp.fillStyles = fillStyles;
shp.lineStyles = lineStyles;
List<List<IEdge>> fillList = new ArrayList<>();
SWF swf = new SWF();
new ShapeExporterBase(ShapeTag.WIND_EVEN_ODD, shapeNum, swf, shp, null) {
@Override
protected void handleFillPaths(List<List<IEdge>> fillPaths) {
fillList.addAll(fillPaths);
}
@Override
public void beginShape() {
}
@Override
public void endShape() {
}
@Override
public void beginFills() {
}
@Override
public void endFills() {
}
@Override
public void beginLines() {
}
@Override
public void endLines(boolean close) {
}
@Override
public void beginFill(RGB color) {
}
@Override
public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) {
}
@Override
public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) {
}
@Override
public void endFill() {
}
@Override
public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, float miterLimit, boolean noClose) {
}
@Override
public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) {
}
@Override
public void lineBitmapStyle(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) {
}
@Override
public void moveTo(double x, double y) {
}
@Override
public void lineTo(double x, double y) {
}
@Override
public void curveTo(double controlX, double controlY, double anchorX, double anchorY) {
}
};
Map<Integer, Integer> globalToLocalFillStyleMap = new LinkedHashMap<>();
int lastFs = 0;
globalToLocalFillStyleMap.put(0, 0);
for (int i = 0; i < fillStyles.fillStyles.length; i++) {
lastFs++;
globalToLocalFillStyleMap.put(lastFs, lastFs);
}
for (SHAPERECORD rec : records) {
if (rec instanceof StyleChangeRecord) {
StyleChangeRecord scr = (StyleChangeRecord) rec;
if (scr.stateNewStyles) {
for (int i = 0; i < scr.fillStyles.fillStyles.length; i++) {
lastFs++;
globalToLocalFillStyleMap.put(lastFs, i + 1);
}
}
}
}
int from = 0;
for (int i = 1; i < layers.size(); i++) {
if (!layers.get(i).equals(layers.get(i - 1))) {
fixSidesInLayer(fillList, shapes, fillStyles0, fillStyles1, layers.get(i - 1), from, i, globalToLocalFillStyleMap);
from = i;
}
}
if (!layers.isEmpty()) {
fixSidesInLayer(fillList, shapes, fillStyles0, fillStyles1, layers.get(layers.size() - 1), from, layers.size(), globalToLocalFillStyleMap);
}
}
}

View File

@@ -0,0 +1,476 @@
/*
* Copyright (C) 2010-2025 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 java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Switched fill sides fixer. Float version. This will fix orientation of
* fillstyle0 and fillstyle1 to be on left and right side of the vector.
*
* WIP: incomplete, use non-float version instead
*
* @author JPEXS
*/
public class SwitchedFillSidesFixerFloat {
class Edge {
int fromId;
int controlId = -1;
int toId;
int fillStyleIdx;
public Edge(int fromId, int controlId, int toId, int fillStyleIdx) {
this.fromId = fromId;
this.controlId = controlId;
this.toId = toId;
this.fillStyleIdx = fillStyleIdx;
}
public Edge(int fromId, int toId, int fillStyleIdx) {
this.fromId = fromId;
this.toId = toId;
this.fillStyleIdx = fillStyleIdx;
}
public Edge reverseWithNewFillStyle(int newFillStyleIdx) {
return new Edge(toId, controlId, fromId, newFillStyleIdx);
}
public Edge reverse() {
return new Edge(toId, controlId, fromId, fillStyleIdx);
}
public Edge sameWithNewFillStyle(int newFillStyleIdx) {
return new Edge(fromId, controlId, toId, newFillStyleIdx);
}
public BezierEdge toBezierEdge(List<Point2D> idToPoint) {
Point2D from = idToPoint.get(fromId);
Point2D to = idToPoint.get(toId);
if (controlId != -1) {
Point2D control = idToPoint.get(controlId);
return new BezierEdge(Arrays.asList(from, control, to));
}
return new BezierEdge(Arrays.asList(from, to));
}
}
boolean USE_REVERSE_LOOKUP = true;
private Map<Integer, List<Edge>> createEdgeMap(
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> layers,
int from,
int to,
List<Point2D> idToPoint,
Map<Point2D, Integer> pointToId
) {
Map<Integer, List<Edge>> currentFillEdgeMap = new HashMap<>();
for (int i = from; i < to; i++) {
List<Edge> subPath = new ArrayList<>();
for (BezierEdge be : shapes.get(i)) {
int fromId = pointToId.get(be.getBeginPoint());
int toId = pointToId.get(be.getEndPoint());
int controlId = -1;
if (be.points.size() == 3) {
controlId = pointToId.get(be.points.get(1));
}
subPath.add(new Edge(fromId, controlId, toId, fillStyles1.get(i)));
}
processSubPath(subPath, fillStyles0.get(i), fillStyles1.get(i), currentFillEdgeMap);
}
cleanEdgeMap(currentFillEdgeMap);
return currentFillEdgeMap;
}
private void processSubPath(List<Edge> subPath, int fillStyleIdx0, int fillStyleIdx1,
Map<Integer, List<Edge>> currentFillEdgeMap) {
List<Edge> path;
if (fillStyleIdx0 != 0) {
path = currentFillEdgeMap.get(fillStyleIdx0);
if (path == null) {
path = new ArrayList<>();
currentFillEdgeMap.put(fillStyleIdx0, path);
}
for (int j = subPath.size() - 1; j >= 0; j--) {
Edge rev = subPath.get(j).reverseWithNewFillStyle(fillStyleIdx0);
path.add(rev);
}
}
if (fillStyleIdx1 != 0) {
path = currentFillEdgeMap.get(fillStyleIdx1);
if (path == null) {
path = new ArrayList<>();
currentFillEdgeMap.put(fillStyleIdx1, path);
}
appendEdges(path, subPath);
}
}
private List<Edge> createPathFromEdgeMap(Map<Integer, List<Edge>> edgeMap) {
List<Edge> newPath = new ArrayList<>();
List<Integer> styleIdxArray = new ArrayList<>();
for (Integer styleIdx : edgeMap.keySet()) {
styleIdxArray.add(styleIdx);
}
Collections.sort(styleIdxArray);
for (int i = 0; i < styleIdxArray.size(); i++) {
appendEdges(newPath, edgeMap.get(styleIdxArray.get(i)));
}
return newPath;
}
private void appendEdges(List<Edge> v1, List<Edge> v2) {
for (int i = 0; i < v2.size(); i++) {
v1.add(v2.get(i));
}
}
private void cleanEdgeMap(Map<Integer, List<Edge>> edgeMap) {
for (Integer styleIdx : edgeMap.keySet()) {
List<Edge> subPath = edgeMap.get(styleIdx);
if (subPath != null && !subPath.isEmpty()) {
int idx;
Edge prevEdge = null;
List<Edge> tmpPath = new ArrayList<>();
Map<Integer, List<Edge>> coordMap = createCoordMap(subPath);
Map<Integer, List<Edge>> reverseCoordMap = createReverseCoordMap(subPath);
while (!subPath.isEmpty()) {
idx = 0;
while (idx < subPath.size()) {
if (prevEdge != null) {
Edge subPathEdge = subPath.get(idx);
if (prevEdge.toId != subPathEdge.fromId) {
Edge edge = findNextEdgeInCoordMap(coordMap, prevEdge);
if (edge != null) {
idx = subPath.indexOf(edge);
} else {
Edge revEdge = findNextEdgeInCoordMap(reverseCoordMap, prevEdge);
if (revEdge != null) {
if (USE_REVERSE_LOOKUP) {
idx = subPath.indexOf(revEdge);
Edge r = revEdge.reverseWithNewFillStyle(revEdge.fillStyleIdx);
updateEdgeInCoordMap(coordMap, revEdge, r);
updateEdgeInReverseCoordMap(reverseCoordMap, revEdge, r);
subPath.set(idx, r);
} else {
idx = 0;
prevEdge = null;
}
} else {
idx = 0;
prevEdge = null;
}
}
continue;
}
}
Edge edge = subPath.remove(idx);
tmpPath.add(edge);
removeEdgeFromCoordMap(coordMap, edge);
removeEdgeFromReverseCoordMap(reverseCoordMap, edge);
prevEdge = edge;
}
}
edgeMap.put(styleIdx, tmpPath);
}
}
}
private Map<Integer, List<Edge>> createCoordMap(List<Edge> path) {
Map<Integer, List<Edge>> coordMap = new HashMap<>();
for (int i = 0; i < path.size(); i++) {
Edge edge = path.get(i);
List<Edge> coordMapArray = coordMap.get(edge.fromId);
if (coordMapArray == null) {
List<Edge> list = new ArrayList<>();
list.add(path.get(i));
coordMap.put(edge.fromId, list);
} else {
coordMapArray.add(path.get(i));
}
}
return coordMap;
}
private Map<Integer, List<Edge>> createReverseCoordMap(List<Edge> path) {
Map<Integer, List<Edge>> coordMap = new HashMap<>();
for (int i = 0; i < path.size(); i++) {
Edge edge = path.get(i);
List<Edge> coordMapArray = coordMap.get(edge.toId);
if (coordMapArray == null) {
List<Edge> list = new ArrayList<>();
list.add(path.get(i));
coordMap.put(edge.toId, list);
} else {
coordMapArray.add(path.get(i));
}
}
return coordMap;
}
private void removeEdgeFromCoordMap(Map<Integer, List<Edge>> coordMap, Edge edge) {
List<Edge> coordMapArray = coordMap.get(edge.fromId);
if (coordMapArray != null) {
if (coordMapArray.size() == 1) {
coordMap.remove(edge.fromId);
} else {
int i = coordMapArray.indexOf(edge);
if (i > -1) {
coordMapArray.remove(i);
}
}
}
}
private void removeEdgeFromReverseCoordMap(Map<Integer, List<Edge>> coordMap, Edge edge) {
List<Edge> coordMapArray = coordMap.get(edge.toId);
if (coordMapArray != null) {
if (coordMapArray.size() == 1) {
coordMap.remove(edge.toId);
} else {
int i = coordMapArray.indexOf(edge);
if (i > -1) {
coordMapArray.remove(i);
}
}
}
}
private Edge findNextEdgeInCoordMap(Map<Integer, List<Edge>> coordMap, Edge edge) {
List<Edge> coordMapArray = coordMap.get(edge.toId);
if (coordMapArray != null && !coordMapArray.isEmpty()) {
return coordMapArray.get(0);
}
return null;
}
private Edge updateEdgeInCoordMap(Map<Integer, List<Edge>> coordMap, Edge edge, Edge newEdge) {
coordMap.get(edge.fromId).remove(edge);
if (!coordMap.containsKey(newEdge.fromId)) {
coordMap.put(newEdge.fromId, new ArrayList<>());
}
coordMap.get(newEdge.fromId).add(newEdge);
return null;
}
private Edge updateEdgeInReverseCoordMap(Map<Integer, List<Edge>> coordMap, Edge edge, Edge newEdge) {
coordMap.get(edge.toId).remove(edge);
if (!coordMap.containsKey(newEdge.toId)) {
coordMap.put(newEdge.toId, new ArrayList<>());
}
coordMap.get(newEdge.toId).add(newEdge);
return null;
}
private void fixSidesInLayer(List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> layers,
int from,
int to) {
Set<Point2D> allPoints = new LinkedHashSet<>();
for (int i = from; i < to; i++) {
for (BezierEdge be : shapes.get(i)) {
for (Point2D p : be.points) {
allPoints.add(p);
}
}
}
List<Point2D> idToPoint = new ArrayList<>(allPoints);
Map<Point2D, Integer> pointToId = new HashMap<>();
for (int i = 0; i < idToPoint.size(); i++) {
pointToId.put(idToPoint.get(i), i);
}
Map<Integer, List<Edge>> currentFillEdgeMap = createEdgeMap(shapes, fillStyles0, fillStyles1, layers, from, to, idToPoint, pointToId);
List<Edge> edges = createPathFromEdgeMap(currentFillEdgeMap);
//-------------------------------------
int fillStyleIdx = Integer.MAX_VALUE;
List<Edge> currentList = new ArrayList<>();
List<List<Edge>> allLists = new ArrayList<>();
List<Integer> listFills = new ArrayList<>();
int lastTo = -1;
for (int i = 0; i < edges.size(); i++) {
Edge e = edges.get(i);
if (fillStyleIdx != e.fillStyleIdx) { //|| e.fromId != lastTo) {
if (fillStyleIdx != Integer.MAX_VALUE) {
allLists.add(currentList);
listFills.add(fillStyleIdx);
currentList = new ArrayList<>();
}
fillStyleIdx = e.fillStyleIdx;
}
currentList.add(e);
lastTo = e.toId;
}
if (!currentList.isEmpty()) {
allLists.add(currentList);
listFills.add(fillStyleIdx);
}
Map<BezierEdge, Integer> beToFillStyle0 = new LinkedHashMap<>();
Map<BezierEdge, Integer> beToFillStyle1 = new LinkedHashMap<>();
for (int i = 0; i < allLists.size(); i++) {
List<Edge> list = allLists.get(i);
fillStyleIdx = listFills.get(i);
double poly = 0;
for (Edge e : list) {
Point2D fromP = idToPoint.get(e.fromId);
Point2D toP;
if (e.controlId != -1) {
toP = idToPoint.get(e.controlId);
poly += fromP.getX() * toP.getY() - toP.getX() * fromP.getY();
fromP = toP;
}
toP = idToPoint.get(e.toId);
poly += fromP.getX() * toP.getY() - toP.getX() * fromP.getY();
}
boolean clockwise = poly > 0;
for (Edge e : list) {
BezierEdge be = e.toBezierEdge(idToPoint);
BezierEdge beRev = be.reverse();
/*if (be.getBeginPoint().equals(new Point2D.Double(12580.0,4280.0))
&& be.getEndPoint().equals(new Point2D.Double(12680.0,4240.0))) {
System.err.println("xxx: " + be);
System.err.println("FS: " + fillStyleIdx);
System.err.println("ClockWise: " + clockwise);
}
if (be.getBeginPoint().equals(new Point2D.Double(12680.0,4240.0))
&& be.getEndPoint().equals(new Point2D.Double(12580.0,4280.0))) {
System.err.println("xxx2: " + be);
System.err.println("FS: " + fillStyleIdx);
System.err.println("ClockWise: " + clockwise);
}*/
if (be.getBeginPoint().equals(new Point2D.Double(12500.0, 3580.0))
&& be.points.get(1).equals(new Point2D.Double(12520.0, 3600.0))
&& be.getEndPoint().equals(new Point2D.Double(12560.0, 3580.0))) {
System.err.println("xxx: " + be);
System.err.println("FS: " + fillStyleIdx);
System.err.println("ClockWise: " + clockwise);
}
if (be.getBeginPoint().equals(new Point2D.Double(12560.0, 3580.0))
&& be.points.get(1).equals(new Point2D.Double(12520.0, 3600.0))
&& be.getEndPoint().equals(new Point2D.Double(12500.0, 3580.0))) {
System.err.println("xxx2: " + be);
System.err.println("FS: " + fillStyleIdx);
System.err.println("ClockWise: " + clockwise);
}
if (clockwise) {
beToFillStyle1.put(be, fillStyleIdx);
beToFillStyle0.put(beRev, fillStyleIdx);
} else {
beToFillStyle0.put(be, fillStyleIdx);
beToFillStyle1.put(beRev, fillStyleIdx);
}
}
}
for (int i = from; i < to; i++) {
List<BezierEdge> shape = shapes.get(i);
for (int j = 0; j < shape.size(); j++) {
BezierEdge be = shape.get(j);
Integer fs0before = fillStyles0.get(i);
Integer fs1before = fillStyles1.get(i);
if (fs0before == 0 && fs1before == 0) { //only strokes
break;
}
if (be.getBeginPoint().equals(new Point2D.Double(12580.0, 4280.0))
&& be.getEndPoint().equals(new Point2D.Double(12680.0, 4240.0))) {
System.err.println("yyy");
}
Integer fs0after = beToFillStyle0.get(be);
Integer fs1after = beToFillStyle1.get(be);
if (fs0after == null) {
fs0after = 0;
}
if (fs1after == null) {
fs1after = 0;
}
fillStyles0.set(i, fs0after);
fillStyles1.set(i, fs1after);
if (!Objects.equals(fs0before, fs0after) || !Objects.equals(fs1before, fs1after)) {
Logger.getLogger(SwitchedFillSidesFixerFloat.class.getName()).log(Level.FINE, "Changed edge {0} - old: {1}, {2} new: {3}, {4}", new Object[]{be, fs0before, fs1before, fs0after, fs1after});
}
break;
}
}
}
public void fixSwitchedFills(
List<List<BezierEdge>> shapes,
List<Integer> fillStyles0,
List<Integer> fillStyles1,
List<Integer> layers
) {
int from = 0;
for (int i = 1; i < layers.size(); i++) {
if (!layers.get(i).equals(layers.get(i - 1))) {
fixSidesInLayer(shapes, fillStyles0, fillStyles1, layers, from, i);
from = i;
}
}
if (!layers.isEmpty()) {
fixSidesInLayer(shapes, fillStyles0, fillStyles1, layers, from, layers.size());
}
}
}