mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-04 23:45:09 +00:00
Fixed: #2532, #1011, #2165 FLA export - shapes - missing fills on path crossings, small shapes
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Shape tools.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.shapes;
|
||||
@@ -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()),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user