diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cb8a0490..56dcd5f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ All notable changes to this project will be documented in this file. - [#1866] FLA export - multilevel clipping handling - [#1866] FLA export - morphshape rounding fix - [#1866] FLA export - multiple usage of morphshapes +- [#503], [#1011], [#1257], [#1902], [#1903], [#2048] FLA export - shapes with overlapping edges ### Changed - Basic tag info panel always visible even when nothing to display (to avoid flickering) @@ -3236,6 +3237,12 @@ Major version of SWF to XML export changed to 2. [#2104]: https://www.free-decompiler.com/flash/issues/2104 [#2031]: https://www.free-decompiler.com/flash/issues/2031 [#1866]: https://www.free-decompiler.com/flash/issues/1866 +[#503]: https://www.free-decompiler.com/flash/issues/503 +[#1011]: https://www.free-decompiler.com/flash/issues/1011 +[#1257]: https://www.free-decompiler.com/flash/issues/1257 +[#1902]: https://www.free-decompiler.com/flash/issues/1902 +[#1903]: https://www.free-decompiler.com/flash/issues/1903 +[#2048]: https://www.free-decompiler.com/flash/issues/2048 [#2099]: https://www.free-decompiler.com/flash/issues/2099 [#2090]: https://www.free-decompiler.com/flash/issues/2090 [#2079]: https://www.free-decompiler.com/flash/issues/2079 @@ -3895,7 +3902,6 @@ Major version of SWF to XML export changed to 2. [#508]: https://www.free-decompiler.com/flash/issues/508 [#305]: https://www.free-decompiler.com/flash/issues/305 [#312]: https://www.free-decompiler.com/flash/issues/312 -[#503]: https://www.free-decompiler.com/flash/issues/503 [#304]: https://www.free-decompiler.com/flash/issues/304 [#306]: https://www.free-decompiler.com/flash/issues/306 [#507]: https://www.free-decompiler.com/flash/issues/507 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java index 145b47f88..a51d66f97 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java @@ -249,7 +249,7 @@ public class BitmapExporter extends ShapeExporterBase { finalizePath(); if (color == null) { fillPaint = defaultColor; - } else { + } else { fillPaint = color.toColor(); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java index 6de322fc8..9bfc2ab76 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java @@ -29,7 +29,6 @@ import com.jpexs.decompiler.flash.types.LINESTYLE2; import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.helpers.Helper; -import com.jpexs.helpers.Reference; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; @@ -55,9 +54,7 @@ public class ShapeForMorphExporter extends ShapeExporterBase { private int currentSegmentCount = 0; private int currentLineStyle = -1; private int currentFillStyle = -1; - - double currentShapeLen = 0; - + public List fillStyles = new ArrayList<>(); public List lineStyles = new ArrayList<>(); @@ -294,7 +291,6 @@ public class ShapeForMorphExporter extends ShapeExporterBase { fillStyleIndices.add(currentFillStyle); lineStyleIndices.add(currentLineStyle); } - currentShapeLen = 0; currentShape = new ArrayList<>(); currentBezierLengths = new ArrayList<>(); currentPointsSum = new Point2D.Double(); @@ -397,7 +393,6 @@ public class ShapeForMorphExporter extends ShapeExporterBase { lastX = x; lastY = y; currentBezierLengths.add(be.length()); - currentShapeLen += be.length(); currentSegmentCount++; } @@ -419,7 +414,6 @@ public class ShapeForMorphExporter extends ShapeExporterBase { lastX = anchorX; lastY = anchorY; currentBezierLengths.add(be.length()); - currentShapeLen += be.length(); currentSegmentCount++; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java index 125d4acdf..45f607675 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java @@ -20,8 +20,12 @@ import com.jpexs.helpers.Reference; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.Objects; import java.util.Stack; /** @@ -36,6 +40,21 @@ public class BezierEdge implements Serializable { this.points = points; } + @Override + public BezierEdge clone() { + return new BezierEdge(new ArrayList<>(points)); + } + + public boolean isEmpty() { + Point2D p1 = getBeginPoint(); + for (int i = 1; i < points.size(); i++) { + if (!points.get(i).equals(p1)) { + return false; + } + } + return true; + } + public BezierEdge(double x0, double y0, double x1, double y1) { points.add(new Point2D.Double(x0, y0)); points.add(new Point2D.Double(x1, y1)); @@ -50,11 +69,19 @@ public class BezierEdge implements Serializable { public Point2D getBeginPoint() { return points.get(0); } - + public Point2D getEndPoint() { return points.get(points.size() - 1); } - + + public void setBeginPoint(Point2D p) { + points.set(0, p); + } + + public void setEndPoint(Point2D p) { + points.set(points.size() - 1, p); + } + public Point2D pointAt(double t) { if (points.size() == 2) { double x = (1 - t) * points.get(0).getX() + t * points.get(1).getX(); @@ -100,7 +127,7 @@ public class BezierEdge implements Serializable { return b; } - private final double MIN_SIZE = 1.0; + private final double MIN_SIZE = 0.5; public double area() { Rectangle2D rect = bbox(); @@ -134,12 +161,82 @@ public class BezierEdge implements Serializable { return false; } - public boolean intersects(BezierEdge b2, List t1Ref, List t2Ref) { - return intersects(b2, + public List getIntersections(BezierEdge b2) { + if (points.size() == 2) { + if (b2.points.size() == 2) { + return Intersections.intersectLineLine(points.get(0), points.get(1), b2.points.get(0), b2.points.get(1), true); + } else { + return Intersections.intersectBezier2Line(b2.points.get(0), b2.points.get(1), b2.points.get(2), points.get(0), points.get(1)); + } + } else { + if (b2.points.size() == 2) { + return Intersections.intersectBezier2Line(points.get(0), points.get(1), points.get(2), b2.points.get(0), b2.points.get(1)); + } else { + return Intersections.intersectBezier2Bezier2(points.get(0), points.get(1), points.get(2), b2.points.get(0), b2.points.get(1), b2.points.get(2)); + } + } + } + + public boolean intersectsOld(BezierEdge b2, List t1Ref, List t2Ref) { + List interPoints = new ArrayList<>(); + List t1RefA = new ArrayList<>(); + List t2RefA = new ArrayList<>(); + boolean ret = intersects(b2, 0, 1, 0, - 1, t1Ref, t2Ref); + 1, t1RefA, t2RefA, interPoints); + Point2D last = new Point2D.Double(Double.MAX_VALUE, Double.MAX_VALUE); + int numSame = 0; + double sumT1 = 0; + double sumT2 = 0; + for (int i = 0; i < interPoints.size(); i++) { + double dist = interPoints.get(i).distance(last); + System.err.println("dist=" + dist); + if (dist <= 5.0) { + numSame++; + sumT1 += t1RefA.get(i); + sumT2 += t2RefA.get(i); + last = interPoints.get(i); + } else { + if (numSame > 0) { + t1Ref.add(sumT1 / numSame); + t2Ref.add(sumT2 / numSame); + } + numSame = 1; + last = interPoints.get(i); + sumT1 = t1RefA.get(i); + sumT2 = t2RefA.get(i); + } + } + if (numSame > 0) { + t1Ref.add(sumT1 / numSame); + t2Ref.add(sumT2 / numSame); + } + + return ret; + } + + public boolean intersects(BezierEdge b2, List t1Ref, List t2Ref, List intPoints) { + List inter = getIntersections(b2); + BezierUtils utils = new BezierUtils(); + for (Point2D p : inter) { + Point2D p1; + Point2D p3; + Point2D p2; + p1 = this.points.get(0); + p3 = this.points.get(this.points.size() - 1); + p2 = this.points.size() == 3 ? this.points.get(1) : new Point2D.Double((p1.getX() + p3.getX()) / 2, (p1.getY() + p3.getY()) / 2); + + t1Ref.add(utils.closestPointToBezier(p, p1, p2, p3)); + + p1 = b2.points.get(0); + p3 = b2.points.get(b2.points.size() - 1); + p2 = b2.points.size() == 3 ? b2.points.get(1) : new Point2D.Double((p1.getX() + p3.getX()) / 2, (p1.getY() + p3.getY()) / 2); + t2Ref.add(utils.closestPointToBezier(p, p1, p2, p3)); + } + intPoints.addAll(inter); + return !inter.isEmpty(); } private boolean intersects( @@ -149,7 +246,9 @@ public class BezierEdge implements Serializable { double start2, double end2, List t1Ref, - List t2Ref) { + List t2Ref, + List interPoints + ) { final double threshold = MIN_SIZE * 2.0; //? Rectangle2D bb1 = bbox(); Rectangle2D bb2 = b2.bbox(); @@ -161,7 +260,8 @@ public class BezierEdge implements Serializable { if (Double.compare(sumAreas, threshold) <= 0) { double t1 = (start1 + end1) / 2; double t2 = (start2 + end2) / 2; - + Point2D selPoint = getBeginPoint(); + interPoints.add(selPoint); t1Ref.add(t1); t2Ref.add(t2); return true; @@ -185,26 +285,25 @@ public class BezierEdge implements Serializable { double half2 = start2 + (end2 - start2) / 2.0; boolean ok = false; - if (b1a.intersects(b2a, start1, half1, start2, half2, t1Ref, t2Ref)) { + if (b1a.intersects(b2a, start1, half1, start2, half2, t1Ref, t2Ref, interPoints)) { ok = true; } - if (b1a.intersects(b2b, start1, half1, half2, end2, t1Ref, t2Ref)) { + if (b1a.intersects(b2b, start1, half1, half2, end2, t1Ref, t2Ref, interPoints)) { ok = true; } - if (b1b.intersects(b2a, half1, end1, start2, half2, t1Ref, t2Ref)) { + if (b1b.intersects(b2a, half1, end1, start2, half2, t1Ref, t2Ref, interPoints)) { ok = true; } - if (b1b.intersects(b2b, half1, end1, half2, end2, t1Ref, t2Ref)) { + if (b1b.intersects(b2b, half1, end1, half2, end2, t1Ref, t2Ref, interPoints)) { ok = true; } return ok; } - public double length() { double distance = 0; double epsilon = 1; - + Stack parts = new Stack(); parts.push(this); @@ -233,15 +332,44 @@ public class BezierEdge implements Serializable { return "{" + String.join("-", list) + "}"; } + public String toSvg() { + + DecimalFormat df = new DecimalFormat("0.###", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + df.setGroupingUsed(false); + + String ret = ""; + ret += "M "; + ret += df.format(points.get(0).getX()); + ret += " "; + ret += df.format(points.get(0).getY()); + ret += " "; + if (points.size() == 3) { + ret += "Q "; + } else { + ret += "L "; + } + ret += df.format(points.get(1).getX()); + ret += " "; + ret += df.format(points.get(1).getY()); + if (points.size() == 3) { + ret += " "; + ret += df.format(points.get(2).getX()); + ret += " "; + ret += df.format(points.get(2).getY()); + } + return ret; + } + public void split(double t, Reference left, Reference right) { List leftPoints = new ArrayList<>(); List rightPoints = new ArrayList<>(); BezierUtils bu = new BezierUtils(); bu.subdivide(points, t, leftPoints, rightPoints); left.setVal(new BezierEdge(leftPoints)); + rightPoints.set(0, leftPoints.get(leftPoints.size() - 1)); right.setVal(new BezierEdge(rightPoints)); } - + public BezierEdge reverse() { List revPoints = new ArrayList<>(); for (int i = points.size() - 1; i >= 0; i--) { @@ -250,6 +378,46 @@ public class BezierEdge implements Serializable { return new BezierEdge(revPoints); } + public void round() { + for (int i = 0; i < this.points.size(); i++) { + this.points.set(i, new Point2D.Double( + Math.round(this.points.get(i).getX()), + Math.round(this.points.get(i).getY()) + )); + } + } + + public void roundHalf() { + for (int i = 0; i < this.points.size(); i++) { + this.points.set(i, new Point2D.Double( + Math.round(this.points.get(i).getX() * 2.0) / 2.0, + Math.round(this.points.get(i).getY() * 2.0) / 2.0 + )); + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + Objects.hashCode(this.points); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BezierEdge other = (BezierEdge) obj; + return Objects.equals(this.points, other.points); + } + public static void main(String[] args) { List t1 = new ArrayList<>(); List t2 = new ArrayList<>(); @@ -276,12 +444,48 @@ public class BezierEdge implements Serializable { BezierEdge be5 = new BezierEdge(25, 0, 25, 100); BezierEdge be6 = new BezierEdge(0, 50, 100, 50); - System.out.println("lines " + be5 + " and " + be6); + //System.err.println("lines " + be5 + " and " + be6); + BezierEdge q1 = new BezierEdge(3469, 3124, 3320, 3148, 3355, 3215); + BezierEdge q2 = new BezierEdge(3442, 3191, 3316, 3146, 3317, 3071); + BezierEdge q3 = new BezierEdge(3310, 3222, 3450, 3172, 3300, 3181); + BezierEdge li = new BezierEdge(3423, 3040, 3277, 3164); + BezierEdge li2 = new BezierEdge(3399, 3095, 3365, 3039); - System.out.println("hasIntersection = " + be5.intersects(be6, t1, t2)); - System.out.println("intersection ts: " + t1 + ", " + t2); + List ints; + /*ints = q2.getIntersections(q1); + System.err.println("intersections is "+ints); + ints = q2.getIntersections(li); + System.err.println("intersections is "+ints); + ints = li2.getIntersections(li); + System.err.println("intersections is "+ints); + ints = q1.getIntersections(q3); + System.err.println("intersections is "+ints);*/ - Point2D c = new Point2D.Double( + BezierEdge qa = new BezierEdge(-81.0, -78.0, -85.0, -76.0, -86.0, -66.0); + BezierEdge qb = new BezierEdge(-166.0, 37.0, -172.0, -21.0, -81.0, -78.0); + /*ints = qa.getIntersections(qb); + System.err.println("intersections is " + ints); + BezierEdge qc = new BezierEdge(-106.0,39.0, -104.0,33.0); + BezierEdge qd = new BezierEdge(-104.0,33.0,-105.0,36.0,-102.0,26.0); + ints = qc.getIntersections(qd); + System.err.println("intersections is " + ints); + + ints = Intersections.intersectLineLine(new Point2D.Double(0,0), new Point2D.Double(10,0), new Point2D.Double(2,0), new Point2D.Double(5,0), true); + System.err.println("intersections is " + ints); + BezierEdge qe = new BezierEdge(-104,33,-104.5,35,-104,32); + BezierEdge qf = new BezierEdge(-106,39 ,-104,33); + ints = qe.getIntersections(qf);*/ + BezierEdge qg = new BezierEdge(-66, 139, -67, 140, -61, 135); + BezierEdge qh = new BezierEdge(-64, 169, -66.5, 139.5, -66, 139); + + List ps = new ArrayList<>(); + qg.intersects(qh, t1, t2, ps); + System.err.println("t1 is " + t1); + System.err.println("t2 is " + t2); + System.err.println("intersections is " + ps); + + + /*Point2D c = new Point2D.Double( (1 - t1.get(0)) * be5.points.get(0).getX() + t1.get(0) * be5.points.get(1).getX(), (1 - t1.get(0)) * be5.points.get(0).getY() + t1.get(0) * be5.points.get(1).getY() ); @@ -290,8 +494,7 @@ public class BezierEdge implements Serializable { BezierEdge be = new BezierEdge(0, 0, 100, 50, 0, 100); - System.out.println("be5.dist: " + be.length()); - + System.out.println("be5.dist: " + be.length());*/ //Rectangle2D out = new Rectangle2D.Double(); //rectIntersection(new Rectangle2D.Double(0,0,50,50), new Rectangle2D.Double(0,50,50,50), out); //System.out.println("out = "+out); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java new file mode 100644 index 000000000..636df6a30 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2010-2023 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.math; + +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Intersection calculating. Based on Node.js library kld-intersections: + * https://github.com/thelonious/kld-intersections/ + */ +public class Intersections { + + private static final double FLATNESS = 0.01; + + private static Point2D min(Point2D p1, Point2D p2) { + return new Point2D.Double(Math.min(p1.getX(), p2.getX()), Math.min(p1.getY(), p2.getY())); + } + + private static Point2D max(Point2D p1, Point2D p2) { + return new Point2D.Double(Math.max(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY())); + } + + private static Point2D multiply(Point2D p, double scalar) { + return new Point2D.Double(p.getX() * scalar, p.getY() * scalar); + } + + private static Point2D add(Point2D p1, Point2D p2) { + return new Point2D.Double(p1.getX() + p2.getX(), p1.getY() + p2.getY()); + } + + private static Point2D lerp(Point2D p1, Point2D p2, double t) { + double omt = 1.0 - t; + + return new Point2D.Double( + p1.getX() * omt + p2.getX() * t, + p1.getY() * omt + p2.getY() * t + ); + } + + private static List intersectLinePolyline(Point2D a1, Point2D a2, List points) { + List result = new ArrayList<>(); + + for (int i = 0; i < points.size() - 1; i++) { + Point2D b1 = points.get(i); + Point2D b2 = points.get(i + 1); + List inter = intersectLineLine(a1, a2, b1, b2, false); + for (Point2D p : inter) { //??? + if (!result.contains(p)) { + result.add(p); + } + } + } + + return result; + } + + private static List intersectPolylinePolyline(List points1, List points2) { + List result = new ArrayList<>(); + + for (int i = 0; i < points1.size() - 1; i++) { + Point2D a1 = points1.get(i); + Point2D a2 = points1.get(i + 1); + List inter = intersectLinePolyline(a1, a2, points2); + + result.addAll(inter); + } + + return result; + } + + private static Point2D vectorFromPoints(Point2D p1, Point2D p2) { + return new Point2D.Double( + p2.getX() - p1.getX(), + p2.getY() - p1.getY() + ); + } + + private static Point2D subtract(Point2D p1, Point2D p2) { + return new Point2D.Double(p1.getX() - p2.getX(), p1.getY() - p2.getY()); + } + + private static Point2D project(Point2D p1, Point2D that) { + double percent = dot(p1, that) / dot(that, that); + + return multiply(that, percent); + } + + private static Point2D perpendicular(Point2D p1, Point2D that) { + return subtract(p1, project(p1, that)); + } + + private static double vectorLength(Point2D p) { + return Math.sqrt(p.getX() * p.getX() + p.getY() * p.getY()); + } + + private static void tesselateInterior(double flatness, Point2D zeroVector, Point2D p1, Point2D p2, Point2D p3, List points) { + // round 1 + Point2D p4 = lerp(p1, p2, 0.5); + Point2D p5 = lerp(p2, p3, 0.5); + + // round 2 + Point2D p6 = lerp(p4, p5, 0.5); + + Point2D baseline = vectorFromPoints(p1, p3); + Point2D tangent = vectorFromPoints(p1, p2); + double dmax = 0; + + if (!zeroVector.equals(tangent)) { + Point2D perpendicular = perpendicular(baseline, tangent); + + dmax = vectorLength(perpendicular); + } + + if (dmax > flatness) { + tesselateInterior(flatness, zeroVector, p1, p4, p6, points); + points.add(new Point2D.Double(p6.getX(), p6.getY())); + tesselateInterior(flatness, zeroVector, p6, p5, p3, points); + } else { + points.add(new Point2D.Double(p6.getX(), p6.getY())); + } + } + + public static List quadraticBezierToToPolyline(Point2D p1, Point2D p2, Point2D p3) { + return quadraticBezierToToPolyline(p1, p2, p3, null); + } + + public static List quadraticBezierToToPolyline(Point2D p1, Point2D p2, Point2D p3, Double flatness) { + List points = new ArrayList<>(); + Point2D zeroVector = new Point2D.Double(0, 0); + + flatness = flatness != null ? flatness : 1.0; + + // add first point + points.add(p1); + + // add interior points + tesselateInterior(flatness, zeroVector, p1, p2, p3, points); + + // add last point + points.add(p3); + + return points; + } + + public static List intersectBezier2Bezier2Slow(Point2D a1, Point2D a2, Point2D a3, Point2D b1, Point2D b2, Point2D b3) { + List a = quadraticBezierToToPolyline(a1, a2, a3, FLATNESS); + List b = quadraticBezierToToPolyline(b1, b2, b3, FLATNESS); + return intersectPolylinePolyline(a, b); + } + + public static List intersectBezier2Bezier2(Point2D a1, Point2D a2, Point2D a3, Point2D b1, Point2D b2, Point2D b3) { + Point2D pa; + Point2D pb; + List result = new ArrayList<>(); + pa = multiply(a2, -2); + Point2D x = add(pa, a3); + Point2D c12 = add(a1, x); + + pa = multiply(a1, -2); + pb = multiply(a2, 2); + Point2D c11 = add(pa, pb); + + Point2D c10 = new Point2D.Double(a1.getX(), a1.getY()); + + pa = multiply(b2, -2); + Point2D c22 = add(b1, add(pa, b3)); + + pa = multiply(b1, -2); + pb = multiply(b2, 2); + Point2D c21 = add(pa, pb); + + Point2D c20 = new Point2D.Double(b1.getX(), b1.getY()); + + // bezout + double a = c12.getX() * c11.getY() - c11.getX() * c12.getY(); + double b = c22.getX() * c11.getY() - c11.getX() * c22.getY(); + double c = c21.getX() * c11.getY() - c11.getX() * c21.getY(); + double d = c11.getX() * (c10.getY() - c20.getY()) + c11.getY() * (-c10.getX() + c20.getX()); + double e = c22.getX() * c12.getY() - c12.getX() * c22.getY(); + double f = c21.getX() * c12.getY() - c12.getX() * c21.getY(); + double g = c12.getX() * (c10.getY() - c20.getY()) + c12.getY() * (-c10.getX() + c20.getX()); + + // determinant + Polynomial poly = new Polynomial(Arrays.asList( + -e * e, + -2 * e * f, + a * b - f * f - 2 * e * g, + a * c - 2 * f * g, + a * d - g * g + ) + ); + + List roots = poly.getRoots(); + for (double s : roots) { + if (0 <= s && s <= 1) { + Polynomial xp = new Polynomial(Arrays.asList( + c12.getX(), + c11.getX(), + c10.getX() - c20.getX() - s * c21.getX() - s * s * c22.getX() + ) + ); + xp.simplifyEquals(); + List xRoots = xp.getRoots(); + Polynomial yp = new Polynomial(Arrays.asList( + c12.getY(), + c11.getY(), + c10.getY() - c20.getY() - s * c21.getY() - s * s * c22.getY() + ) + ); + yp.simplifyEquals(); + List yRoots = yp.getRoots(); + + if (!xRoots.isEmpty() && !yRoots.isEmpty()) { + double TOLERANCE = 1e-4; + + checkRoots: + for (double xRoot : xRoots) { + if (0 <= xRoot && xRoot <= 1) { + for (int k = 0; k < yRoots.size(); k++) { + if (Math.abs(xRoot - yRoots.get(k)) < TOLERANCE) { + result.add(add(multiply(c22, s * s), (add(multiply(c21, s), c20)))); + break checkRoots; + } + } + } + } + } + } + } + + //JPEXS: fix rounding errors + if (a1.equals(b1) && !result.contains(a1)) { + result.add(0, a1); + } + if (a3.equals(b3) && !result.contains(a3)) { + result.add(a3); + } + if (a1.equals(b3) && !result.contains(a1)) { + result.add(0, a1); + } + if (a3.equals(b1) && !result.contains(a3)) { + result.add(0, a3); + } + + return result; + } + + private static double dot(Point2D p1, Point2D p2) { + return p1.getX() * p2.getX() + p1.getY() * p2.getY(); + } + + public static List intersectBezier2LineSlow(Point2D p1, Point2D p2, Point2D p3, Point2D a1, Point2D a2) { + List p = quadraticBezierToToPolyline(p1, p2, p3, FLATNESS); + return intersectLinePolyline(a1, a2, p); + } + + public static List intersectBezier2Line(Point2D p1, Point2D p2, Point2D p3, Point2D a1, Point2D a2) { + Point2D a; // temporary variables + Point2D min = min(a1, a2); // used to determine if point is on line segment + Point2D max = max(a1, a2); // used to determine if point is on line segment + List result = new ArrayList<>(); + + a = multiply(p2, -2); + Point2D c2 = add(p1, add(a, p3)); + + a = multiply(p1, -2); + Point2D b = multiply(p2, 2); + Point2D c1 = add(a, b); + + Point2D c0 = new Point2D.Double(p1.getX(), p1.getY()); + + // Convert line to normal form: ax + by + c = 0 + // Find normal to line: negative inverse of original line's slope + Point2D n = new Point2D.Double(a1.getY() - a2.getY(), a2.getX() - a1.getX()); + + // Determine new c coefficient + double cl = a1.getX() * a2.getY() - a2.getX() * a1.getY(); + + // Transform cubic coefficients to line's coordinate system and find roots + // of cubic + List roots = new Polynomial( + Arrays.asList( + dot(n, c2), + dot(n, c1), + dot(n, c0) + cl + ) + ).getRoots(); + + // Any roots in closed interval [0,1] are intersections on Bezier, but + // might not be on the line segment. + // Find intersections and calculate point coordinates + for (double t : roots) { + if (0 <= t && t <= 1) { + // We're within the Bezier curve + // Find point on Bezier + Point2D p4 = lerp(p1, p2, t); + Point2D p5 = lerp(p2, p3, t); + + Point2D p6 = lerp(p4, p5, t); + + // See if point is on line segment + // Had to make special cases for vertical and horizontal lines due + // to slight errors in calculation of p6 + if (a1.getX() == a2.getX()) { + if (min.getY() <= p6.getY() && p6.getY() <= max.getY()) { + result.add(p6); + } + } else if (a1.getY() == a2.getY()) { + if (min.getX() <= p6.getX() && p6.getX() <= max.getX()) { + result.add(p6); + } + } else if (min.getX() <= p6.getX() && p6.getX() <= max.getX() && min.getY() <= p6.getY() && p6.getY() <= max.getY()) { + result.add(p6); + } + } + } + + //JPEXS: fix rounding errors + if (a1.equals(p1) && !result.contains(a1)) { + result.add(0, a1); + } + if (a2.equals(p3) && !result.contains(a2)) { + result.add(a2); + } + if (a1.equals(p3) && !result.contains(a1)) { + result.add(0, a1); + } + if (a2.equals(p1) && !result.contains(a2)) { + result.add(0, a2); + } + + return result; + } + + public static List intersectLineLine(Point2D a1, Point2D a2, Point2D b1, Point2D b2, boolean addCoincident) { + List result = new ArrayList<>(); + + double ua_t = (b2.getX() - b1.getX()) * (a1.getY() - b1.getY()) - (b2.getY() - b1.getY()) * (a1.getX() - b1.getX()); + double ub_t = (a2.getX() - a1.getX()) * (a1.getY() - b1.getY()) - (a2.getY() - a1.getY()) * (a1.getX() - b1.getX()); + double u_b = (b2.getY() - b1.getY()) * (a2.getX() - a1.getX()) - (b2.getX() - b1.getX()) * (a2.getY() - a1.getY()); + + if (u_b != 0) { + double ua = ua_t / u_b; + double ub = ub_t / u_b; + + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result.add( + new Point2D.Double( + a1.getX() + ua * (a2.getX() - a1.getX()), + a1.getY() + ua * (a2.getY() - a1.getY()) + ) + ); + } else { + //No Intersection + } + } else if (ua_t == 0 || ub_t == 0) { + if (!addCoincident) { + return result; + } + //WARNING: This is actually not an intersection, + //but Coincident. But we treat it equally + + double a1v; + double a2v; + double b1v; + double b2v; + + if (!(a1.getX() == b1.getX() && a2.getX() == b2.getX() && a1.getX() == a2.getX())) { + a1v = a1.getX(); + a2v = a2.getX(); + b1v = b1.getX(); + b2v = b2.getX(); + } else { + a1v = a1.getY(); + a2v = a2.getY(); + b1v = b1.getY(); + b2v = b2.getY(); + } + + if (a1v > a2v) { + double td; + td = a1v; + a1v = a2v; + a2v = td; + } + + if (b1v > b2v) { + double td; + td = b1v; + b1v = b2v; + b2v = td; + } + + if (b1v < a1v) { + //swap a, b + + Point2D t; + + t = b1; + b1 = a1; + a1 = t; + + t = b2; + b2 = a2; + a2 = t; + + double td; + + td = b1v; + b1v = a1v; + a1v = td; + + td = b2v; + b2v = a2v; + a2v = td; + } + + if (a2v < b1v) { + //no overlap + return result; + } + + if (a2v == b1v) { + //single point, ignore + return result; + } + //A1 B1 A2 B2 + // |----|----|----| + result.add(b1); + result.add(a2); + } else { + //Parallel + } + + return result; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Polynomial.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Polynomial.java new file mode 100644 index 000000000..280f13665 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Polynomial.java @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2010-2023 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.math; + +import com.jpexs.helpers.Reference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * Based on Node.js library kld-polynomial: + * https://github.com/thelonious/kld-polynomial + */ +public class Polynomial { + + private double[] coefs; + private String _variable; + private int _s; + + public Polynomial(List coefs) { + this.coefs = new double[coefs.size()]; + + for (int i = 0; i < coefs.size(); i++) { + this.coefs[i] = coefs.get(coefs.size() - 1 - i); + } + + this._variable = "t"; + this._s = 0; + } + + public int getDegree() { + return this.coefs.length - 1; + } + + private List getLinearRoot() { + List result = new ArrayList<>(); + double a = this.coefs[1]; + + if (a != 0) { + result.add(-this.coefs[0] / a); + } + + return result; + } + + private List getQuadraticRoots() { + List results = new ArrayList<>(); + + if (this.getDegree() == 2) { + double a = this.coefs[2]; + double b = this.coefs[1] / a; + double c = this.coefs[0] / a; + double d = b * b - 4 * c; + + if (d > 0) { + double e = Math.sqrt(d); + + results.add(0.5 * (-b + e)); + results.add(0.5 * (-b - e)); + } else if (d == 0) { + // really two roots with same value, but we only return one + results.add(0.5 * -b); + } + // else imaginary results + } + + return results; + } + + private boolean coefSelectionFunc(int i, int n, double[] a) { + return i < n && a[i] < 0; + } + + private void find2Max(Reference max, Reference nearmax, double bi, int i, int n, double[] a) { + if (coefSelectionFunc(i, n, a)) { + if (max.getVal() < bi) { + nearmax.setVal(max.getVal()); + max.setVal(bi); + } else if (nearmax.getVal() < bi) { + nearmax.setVal(bi); + } + } + } + + ; + + /** + * + * @return negX, posX + */ + private double[] boundsUpperRealFujiwara() { + double[] ax = this.coefs; + double[] a = ax; + int n = a.length - 1; + double an = a[n]; + + if (an != 1) { + a = new double[a.length]; + for (int i = 0; i < this.coefs.length; i++) { + a[i] = ax[i] / an; + } + } + + double[] b = new double[a.length]; + for (int i = 0; i < a.length; i++) { + double v = a[i]; + if (i < n) { + b[i] = Math.pow(Math.abs((i == 0) ? v / 2 : v), 1 / (double) (n - i)); + } else { + b[i] = v; + } + } + + // eslint-disable-next-line unicorn/no-fn-reference-in-iterator + double max_pos = 0; + double nearmax_pos = 0; + for (int i = 0; i < b.length; i++) { + if (i < n && a[i] < 0) { + if (max_pos < b[i]) { + nearmax_pos = max_pos; + max_pos = b[i]; + } else if (nearmax_pos < b[i]) { + nearmax_pos = b[i]; + } + } + } + + double max_neg = 0; + double nearmax_neg = 0; + for (int i = 0; i < b.length; i++) { + if (i < n && ((n % 2 == i % 2) ? a[i] < 0 : a[i] > 0)) { + if (max_neg < b[i]) { + nearmax_neg = max_neg; + max_neg = b[i]; + } else if (nearmax_neg < b[i]) { + nearmax_neg = b[i]; + } + } + } + + return new double[]{ + -2 * max_neg, + 2 * max_pos + }; + } + + private class Rect { + + double minX; + double minY; + double maxX; + double maxY; + + public Rect(double minX, double minY, double maxX, double maxY) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + } + + private double[] boundsLowerRealFujiwara() { + Polynomial poly = new Polynomial(new ArrayList<>()); + + poly.coefs = new double[coefs.length]; + for (int i = 0; i < coefs.length; i++) { + poly.coefs[coefs.length - 1 - i] = coefs[i]; + } + + double[] res = poly.boundsUpperRealFujiwara(); + + res[0] = 1 / res[0]; + res[1] = 1 / res[1]; + + return res; + } + + private Rect bounds() { + double[] urb = this.boundsUpperRealFujiwara(); + Rect rb = new Rect(urb[0], 0, urb[1], 0); + + if (urb[0] == 0 && urb[1] == 0) { + return rb; + } + + if (urb[0] == 0) { + rb.minX = this.boundsLowerRealFujiwara()[1]; + } else if (urb[1] == 0) { + rb.maxX = this.boundsLowerRealFujiwara()[0]; + } + + if (rb.minX > rb.maxX) { + rb.minX = rb.maxX = 0; + } + + return rb; + // TODO: if sure that there are no complex roots + // (maybe by using Sturm's theorem) use: + // return this.boundsRealLaguerre(); + } + + private double eval(double x) { + if (Double.isNaN(x)) { + throw new RuntimeException("Parameter must be a number"); + } + + double result = 0; + + for (int i = this.coefs.length - 1; i >= 0; i--) { + result = result * x + this.coefs[i]; + } + + return result; + } + + public double zeroErrorEstimate() { + return zeroErrorEstimate(null); + } + + public double zeroErrorEstimate(Double maxAbsX) { + Polynomial poly = this; + double ERRF = 1e-15; + + if (maxAbsX == null) { + Rect rb = poly.bounds(); + + maxAbsX = Math.max(Math.abs(rb.minX), Math.abs(rb.maxX)); + } + + if (maxAbsX < 0.001) { + return 2 * Math.abs(poly.eval(ERRF)); + } + + int n = poly.coefs.length - 1; + double an = poly.coefs[n]; + + double m = 0; + for (int i = 0; i < poly.coefs.length; i++) { + double v = poly.coefs[i]; + double nm = v / an * Math.pow(maxAbsX, i); + m = nm > m ? nm : m; + } + + double x = 10 * ERRF * m; + return x; + } + + public List getCubicRoots() { + List results = new ArrayList<>(); + + if (this.getDegree() == 3) { + double c3 = this.coefs[3]; + double c2 = this.coefs[2] / c3; + double c1 = this.coefs[1] / c3; + double c0 = this.coefs[0] / c3; + + double a = (3 * c1 - c2 * c2) / 3; + double b = (2 * c2 * c2 * c2 - 9 * c1 * c2 + 27 * c0) / 27; + double offset = c2 / 3; + double discrim = b * b / 4 + a * a * a / 27; + double halfB = b / 2; + + double ZEROepsilon = this.zeroErrorEstimate(); + + if (Math.abs(discrim) <= ZEROepsilon) { + discrim = 0; + } + + if (discrim > 0) { + double e = Math.sqrt(discrim); + double root; // eslint-disable-line no-shadow + + double tmp = -halfB + e; + + if (tmp >= 0) { + root = Math.pow(tmp, 1 / 3.0); + } else { + root = -Math.pow(-tmp, 1 / 3.0); + } + + tmp = -halfB - e; + + if (tmp >= 0) { + root += Math.pow(tmp, 1 / 3.0); + } else { + root -= Math.pow(-tmp, 1 / 3.0); + } + + results.add(root - offset); + } else if (discrim < 0) { + double distance = Math.sqrt(-a / 3.0); + double angle = Math.atan2(Math.sqrt(-discrim), -halfB) / 3.0; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double sqrt3 = Math.sqrt(3); + + results.add(2 * distance * cos - offset); + results.add(-distance * (cos + sqrt3 * sin) - offset); + results.add(-distance * (cos - sqrt3 * sin) - offset); + } else { + double tmp; + + if (halfB >= 0) { + tmp = -Math.pow(halfB, 1 / 3.0); + } else { + tmp = Math.pow(-halfB, 1 / 3.0); + } + + results.add(2 * tmp - offset); + // really should return next root twice, but we return only one + results.add(-tmp - offset); + } + } + + return results; + } + + public void simplifyEquals() { + simplifyEquals(1e-12); + } + + public void simplifyEquals(double tolerance) { + for (int i = this.getDegree(); i >= 0; i--) { + if (Math.abs(this.coefs[i]) <= tolerance) { + double[] newc = new double[this.coefs.length - 1]; + for (int j = 0; j < newc.length; j++) { + newc[j] = this.coefs[j]; + } + this.coefs = newc; + } else { + break; + } + } + } + + private void divideEqualsScalar(double scalar) { + for (int i = 0; i < this.coefs.length; i++) { + this.coefs[i] /= scalar; + } + } + + private Polynomial getDerivative() { + Polynomial pol = new Polynomial(new ArrayList<>()); + List newCoefs = new ArrayList<>(); + pol.coefs = new double[this.coefs.length - 1]; + for (int i = 1; i < this.coefs.length; i++) { + pol.coefs[i - 1] = i * this.coefs[i]; + } + + return pol; + } + + public List getRoots() { + List result; + + this.simplifyEquals(); + + switch (this.getDegree()) { + case 0: + result = new ArrayList<>(); + break; + case 1: + result = this.getLinearRoot(); + break; + case 2: + result = this.getQuadraticRoots(); + break; + case 3: + result = this.getCubicRoots(); + break; + case 4: + result = this.getQuarticRoots(); + break; + default: + result = new ArrayList<>(); + } + + return result; + } + + private static Double sign(Double x) { + // eslint-disable-next-line no-self-compare + if (x == null) { + return null; + } + return x < 0 ? -1.0 : 1.0; + } + + private interface DoubleFunc { + + public double apply(double val); + } + + private static double newtonSecantBisection(double x0, DoubleFunc f, DoubleFunc df, int max_iterations, Double min, Double max) { + double x; + double prev_dfx = 0; + double dfx; + double prev_x_ef_correction = 0; + double x_correction; + double x_new; + double y; + Double y_atmin = null; + Double y_atmax = null; + + x = x0; + + double ACCURACY = 14; + double min_correction_factor = Math.pow(10, -ACCURACY); + boolean isBounded = min != null && max != null; + + if (isBounded) { + if (min > max) { + throw new RuntimeException("Min must be greater than max"); + } + + y_atmin = f.apply(min); + y_atmax = f.apply(max); + + if (Double.compare(sign(y_atmin), sign(y_atmax)) == 0) { + throw new RuntimeException("Y values of bounds must be of opposite sign"); + } + } + + for (int i = 0; i < max_iterations; i++) { + dfx = df.apply(x); + + if (dfx == 0) { + if (prev_dfx == 0) { + // error + throw new RuntimeException("df(x) is zero"); + } else { + // use previous derivation value + dfx = prev_dfx; + } + // or move x a little? + // dfx = df(x != 0 ? x + x * 1e-15 : 1e-15); + } + + prev_dfx = dfx; + y = f.apply(x); + x_correction = y / dfx; + x_new = x - x_correction; + + boolean isEnoughCorrection = (Math.abs(x_correction) <= min_correction_factor * Math.abs(x)) + || (prev_x_ef_correction == (x - x_correction) - x); + if (isEnoughCorrection) { + break; + } + + if (isBounded) { + if (Double.compare(sign(y), sign(y_atmax)) == 0) { + max = x; + y_atmax = y; + } else if (Double.compare(sign(y), sign(y_atmin)) == 0) { + min = x; + y_atmin = y; + } else { + x = x_new; + break; + } + + if ((x_new < min) || (x_new > max)) { + if (Double.compare(sign(y_atmin), sign(y_atmax)) == 0) { + break; + } + + double RATIO_LIMIT = 50; + double AIMED_BISECT_OFFSET = 0.25; // [0, 0.5) + double dy = y_atmax - y_atmin; + double dx = max - min; + + if (dy == 0) { + x_correction = x - (min + dx * 0.5); + } else if (Math.abs(dy / Math.min(y_atmin, y_atmax)) > RATIO_LIMIT) { + x_correction = x - (min + dx * (0.5 + (Math.abs(y_atmin) < Math.abs(y_atmax) ? -AIMED_BISECT_OFFSET : AIMED_BISECT_OFFSET))); + } else { + x_correction = x - (min - y_atmin / dy * dx); + } + x_new = x - x_correction; + + isEnoughCorrection = (Math.abs(x_correction) <= min_correction_factor * Math.abs(x)) + || (prev_x_ef_correction == (x - x_correction) - x); + if (isEnoughCorrection) { + break; + } + } + } + + prev_x_ef_correction = x - x_new; + x = x_new; + } + + return x; + } + + private List getQuarticRoots() { + List results = new ArrayList<>(); + int n = this.getDegree(); + + if (n == 4) { + Polynomial poly = new Polynomial(new ArrayList<>()); + + poly.coefs = Arrays.copyOf(this.coefs, this.coefs.length); + poly.divideEqualsScalar(poly.coefs[n]); + + double ERRF = 1e-15; + + if (Math.abs(poly.coefs[0]) < 10 * ERRF * Math.abs(poly.coefs[3])) { + poly.coefs[0] = 0; + } + + Polynomial poly_d = poly.getDerivative(); + List derrt = poly_d.getRoots(); + derrt.sort(new Comparator() { + @Override + public int compare(Double a, Double b) { + if (Double.compare(a, b) == 0) { + return 0; + } + if (a - b < 0) { + return -1; + } + return 1; + } + }); + List dery = new ArrayList<>(); + int nr = derrt.size() - 1; + Rect rb = this.bounds(); + + double maxabsX = Math.max(Math.abs(rb.minX), Math.abs(rb.maxX)); + double ZEROepsilon = this.zeroErrorEstimate(maxabsX); + + for (int i = 0; i <= nr; i++) { + dery.add(poly.eval(derrt.get(i))); + } + + for (int i = 0; i <= nr; i++) { + if (Math.abs(dery.get(i)) < ZEROepsilon) { + dery.set(i, 0.0); + } + } + + int i = 0; + double dx = Math.max(0.1 * (rb.maxX - rb.minX) / n, ERRF); + List guesses = new ArrayList<>(); + List minmax = new ArrayList<>(); + + if (nr > -1) { + if (dery.get(0) != 0) { + if (Double.compare(sign(dery.get(0)), sign(poly.eval(derrt.get(0) - dx) - dery.get(0))) != 0) { + guesses.add(derrt.get(0) - dx); + minmax.add(new double[]{rb.minX, derrt.get(0)}); + } + } else { + results.add(derrt.get(0)); + results.add(derrt.get(0)); + i++; + } + + for (; i < nr; i++) { + if (dery.get(i + 1) == 0) { + results.add(derrt.get(i + 1)); + results.add(derrt.get(i + 1)); + i++; + } else if (Double.compare(sign(dery.get(i)), sign(dery.get(i + 1))) != 0) { + guesses.add((derrt.get(i) + derrt.get(i + 1)) / 2); + minmax.add(new double[]{derrt.get(i), derrt.get(i + 1)}); + } + } + if (dery.get(nr) != 0 && Double.compare(sign(dery.get(nr)), sign(poly.eval(derrt.get(nr) + dx) - dery.get(nr))) != 0) { + guesses.add(derrt.get(nr) + dx); + minmax.add(new double[]{derrt.get(nr), rb.maxX}); + } + } + + DoubleFunc f = new DoubleFunc() { + @Override + public double apply(double x) { + return poly.eval(x); + } + }; + + DoubleFunc df = new DoubleFunc() { + @Override + public double apply(double x) { + return poly_d.eval(x); + } + }; + + if (!guesses.isEmpty()) { + for (i = 0; i < guesses.size(); i++) { + guesses.set(i, Polynomial.newtonSecantBisection(guesses.get(i), f, df, 32, minmax.get(i)[0], minmax.get(i)[1])); + } + } + + results.addAll(guesses); + } + + return results; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/CurvedEdgeRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/CurvedEdgeRecord.java index e56889ae2..a0e94f367 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/CurvedEdgeRecord.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/CurvedEdgeRecord.java @@ -92,4 +92,40 @@ public class CurvedEdgeRecord extends SHAPERECORD { calculateBits(); return !SWFOutputStream.fitsInUB(4, numBits); } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + this.controlDeltaX; + hash = 97 * hash + this.controlDeltaY; + hash = 97 * hash + this.anchorDeltaX; + hash = 97 * hash + this.anchorDeltaY; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CurvedEdgeRecord other = (CurvedEdgeRecord) obj; + if (this.controlDeltaX != other.controlDeltaX) { + return false; + } + if (this.controlDeltaY != other.controlDeltaY) { + return false; + } + if (this.anchorDeltaX != other.anchorDeltaX) { + return false; + } + return this.anchorDeltaY == other.anchorDeltaY; + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/EndShapeRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/EndShapeRecord.java index cf15ca4e7..74b9763c4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/EndShapeRecord.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/EndShapeRecord.java @@ -62,4 +62,28 @@ public class EndShapeRecord extends SHAPERECORD { public boolean isTooLarge() { return false; } + + @Override + public int hashCode() { + int hash = 7; + hash = 37 * hash + this.endOfShape; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EndShapeRecord other = (EndShapeRecord) obj; + return this.endOfShape == other.endOfShape; + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java index d257897bc..1784b2426 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java @@ -124,4 +124,40 @@ public class StraightEdgeRecord extends SHAPERECORD { calculateBits(); return !SWFOutputStream.fitsInUB(4, numBits); } + + @Override + public int hashCode() { + int hash = 5; + hash = 29 * hash + (this.generalLineFlag ? 1 : 0); + hash = 29 * hash + (this.vertLineFlag ? 1 : 0); + hash = 29 * hash + this.deltaX; + hash = 29 * hash + this.deltaY; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final StraightEdgeRecord other = (StraightEdgeRecord) obj; + if (this.generalLineFlag != other.generalLineFlag) { + return false; + } + if (this.vertLineFlag != other.vertLineFlag) { + return false; + } + if (this.deltaX != other.deltaX) { + return false; + } + return this.deltaY == other.deltaY; + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StyleChangeRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StyleChangeRecord.java index 0f50b87fa..2ca8f5f34 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StyleChangeRecord.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StyleChangeRecord.java @@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; import com.jpexs.decompiler.flash.types.annotations.Calculated; import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.SWFType; +import java.util.Objects; import java.util.Set; /** @@ -157,4 +158,73 @@ public final class StyleChangeRecord extends SHAPERECORD implements Cloneable { calculateBits(); return !SWFOutputStream.fitsInUB(5, moveBits); } + + @Override + public int hashCode() { + int hash = 5; + hash = 59 * hash + (this.stateNewStyles ? 1 : 0); + hash = 59 * hash + (this.stateLineStyle ? 1 : 0); + hash = 59 * hash + (this.stateFillStyle1 ? 1 : 0); + hash = 59 * hash + (this.stateFillStyle0 ? 1 : 0); + hash = 59 * hash + (this.stateMoveTo ? 1 : 0); + hash = 59 * hash + this.moveDeltaX; + hash = 59 * hash + this.moveDeltaY; + hash = 59 * hash + this.fillStyle0; + hash = 59 * hash + this.fillStyle1; + hash = 59 * hash + this.lineStyle; + /*hash = 59 * hash + Objects.hashCode(this.fillStyles); + hash = 59 * hash + Objects.hashCode(this.lineStyles);*/ + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final StyleChangeRecord other = (StyleChangeRecord) obj; + if (this.stateNewStyles != other.stateNewStyles) { + return false; + } + if (this.stateLineStyle != other.stateLineStyle) { + return false; + } + if (this.stateFillStyle1 != other.stateFillStyle1) { + return false; + } + if (this.stateFillStyle0 != other.stateFillStyle0) { + return false; + } + if (this.stateMoveTo != other.stateMoveTo) { + return false; + } + if (this.moveDeltaX != other.moveDeltaX) { + return false; + } + if (this.moveDeltaY != other.moveDeltaY) { + return false; + } + if (this.fillStyle0 != other.fillStyle0) { + return false; + } + if (this.fillStyle1 != other.fillStyle1) { + return false; + } + if (this.lineStyle != other.lineStyle) { + return false; + } + /*if (!Objects.equals(this.fillStyles, other.fillStyles)) { + return false; + } + return Objects.equals(this.lineStyles, other.lineStyles);*/ + return true; + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index e57fdb127..7ac66c1a5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -141,6 +141,7 @@ import com.jpexs.decompiler.flash.types.sound.MP3FRAME; import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; import com.jpexs.decompiler.flash.types.sound.SoundFormat; import com.jpexs.decompiler.flash.xfl.shapefixer.CurvedEdgeRecordAdvanced; +import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeFixer; import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeRecordAdvanced; import com.jpexs.decompiler.flash.xfl.shapefixer.StraightEdgeRecordAdvanced; import com.jpexs.decompiler.flash.xfl.shapefixer.StyleChangeRecordAdvanced; @@ -229,58 +230,53 @@ public class XFLConverter { /** * Adds "(depht xxx)" to layer name */ - private final boolean DEBUG_EXPORT_LAYER_DEPTHS = true; + private final boolean DEBUG_EXPORT_LAYER_DEPTHS = false; + + private static final DecimalFormat EDGE_DECIMAL_FORMAT = new DecimalFormat("0.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + + static { + EDGE_DECIMAL_FORMAT.setGroupingUsed(false); + } private static String formatEdgeDouble(double value, boolean curved, boolean morphshape) { if (value % 1 == 0) { return "" + (int) value; } - if (morphshape) { - value = Math.round(value * 2.0) / 2.0; - } - DecimalFormat df = new DecimalFormat(morphshape ? "0.#" : "0.##", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - df.setGroupingUsed(false); - String strValue = "" + df.format(value); - - if (curved) { - String[] parts = strValue.split("\\."); - return ("#" + Integer.toHexString(Integer.parseInt(parts[0])) + "." + Integer.toHexString(Integer.parseInt(parts[1]))).toUpperCase(Locale.ENGLISH); - } - return "" + strValue; + value = Math.round(value * 2.0) / 2.0; + return EDGE_DECIMAL_FORMAT.format(value); } private static String convertShapeEdge(MATRIX mat, ShapeRecordAdvanced record, double x, double y, boolean morphshape) { if (record instanceof StyleChangeRecordAdvanced) { StyleChangeRecordAdvanced scr = (StyleChangeRecordAdvanced) record; Point2D p = new Point2D.Double(scr.moveDeltaX, scr.moveDeltaY); - //p = new Matrix(mat).transform(p); + p = new Matrix(mat).transform(p); if (scr.stateMoveTo) { - //return "! " + formatEdgeDouble(p.getX(), false) + " " + formatEdgeDouble(p.getY(), false); - return ""; + return "! " + formatEdgeDouble(p.getX(), false, morphshape) + " " + formatEdgeDouble(p.getY(), false, morphshape); } } else if (record instanceof StraightEdgeRecordAdvanced) { StraightEdgeRecordAdvanced ser = (StraightEdgeRecordAdvanced) record; - Point2D p1 = new Point2D.Double(x, y); + Point2D p1 = new Point2D.Double(x, y); Point2D p = new Point2D.Double(x + ser.deltaX, y + ser.deltaY); p = new Matrix(mat).transform(p); p1 = new Matrix(mat).transform(p1); - - return "! " + formatEdgeDouble(p1.getX(), false, morphshape) + " " + formatEdgeDouble(p1.getY(), false, morphshape) - + "| " + formatEdgeDouble(p.getX(), false, morphshape) + " " + formatEdgeDouble(p.getY(), false, morphshape); + + return /*"! " + formatEdgeDouble(p1.getX(), false, morphshape) + " " + formatEdgeDouble(p1.getY(), false, morphshape) + +*/ "| " + formatEdgeDouble(p.getX(), false, morphshape) + " " + formatEdgeDouble(p.getY(), false, morphshape); } else if (record instanceof CurvedEdgeRecordAdvanced) { CurvedEdgeRecordAdvanced cer = (CurvedEdgeRecordAdvanced) record; double controlX = cer.controlDeltaX + x; double controlY = cer.controlDeltaY + y; double anchorX = cer.anchorDeltaX + controlX; double anchorY = cer.anchorDeltaY + controlY; - Point2D p1 = new Point2D.Double(x, y); + Point2D p1 = new Point2D.Double(x, y); Point2D control = new Point2D.Double(controlX, controlY); Point2D anchor = new Point.Double(anchorX, anchorY); p1 = new Matrix(mat).transform(p1); control = new Matrix(mat).transform(control); anchor = new Matrix(mat).transform(anchor); - return "! " + formatEdgeDouble(p1.getX(), false, morphshape) + " " + formatEdgeDouble(p1.getY(), false, morphshape) - + "[ " + formatEdgeDouble(control.getX(), true, morphshape) + " " + formatEdgeDouble(control.getY(), true, morphshape) + " " + formatEdgeDouble(anchor.getX(), true, morphshape) + " " + formatEdgeDouble(anchor.getY(), true, morphshape); + return /*"! " + formatEdgeDouble(p1.getX(), false, morphshape) + " " + formatEdgeDouble(p1.getY(), false, morphshape) + +*/ "[ " + formatEdgeDouble(control.getX(), true, morphshape) + " " + formatEdgeDouble(control.getY(), true, morphshape) + " " + formatEdgeDouble(anchor.getX(), true, morphshape) + " " + formatEdgeDouble(anchor.getY(), true, morphshape); } return ""; } @@ -299,7 +295,7 @@ public class XFLConverter { } } if (!hasMove) { - //ret.append("! ").append(formatEdgeDouble(startX, false)).append(" ").append(formatEdgeDouble(startY, false)); + ret.append("! ").append(formatEdgeDouble(startX, false, morphshape)).append(" ").append(formatEdgeDouble(startY, false, morphshape)); } double lastMoveToX = startX; double lastMoveToY = startY; @@ -321,7 +317,7 @@ public class XFLConverter { lineStyle = 0; } if (scr.stateLineStyle) { - lineStyle = scr.lineStyle; + lineStyle = scr.lineStyle; } if (scr.stateFillStyle0) { fillStyle0 = scr.fillStyle0; @@ -330,21 +326,21 @@ public class XFLConverter { fillStyle1 = scr.fillStyle1; } } - - + String edge = convertShapeEdge(mat, rec, x, y, morphshape); - + + String curPos = "! " + formatEdgeDouble(x, false, morphshape) + " " + formatEdgeDouble(y, false, morphshape); //ignore duplicated edges with only strokes #2031 if (fillStyle0 == 0 && fillStyle1 == 0 && lineStyle != 0) { - if (!edges.contains(edge)) { - edges.add(edge); + if (!edges.contains(curPos + edge)) { + edges.add(curPos + edge); ret.append(edge); } } else { - edges.add(edge); + edges.add(curPos + edge); ret.append(edge); } - + x = rec.changeX(x); y = rec.changeY(y); } @@ -473,7 +469,7 @@ public class XFLConverter { writer.writeAttribute("bitmapIsClipped", true); } - writer.writeStartElement("matrix"); + writer.writeStartElement("matrix"); MATRIX bitmapMatrix = fs.bitmapMatrix; bitmapMatrix = (new Matrix(mat)).concatenate(new Matrix(bitmapMatrix)).toMATRIX(); convertMatrix(bitmapMatrix, writer); @@ -526,10 +522,10 @@ public class XFLConverter { } writer.writeStartElement("matrix"); - + MATRIX gradientMatrix = fs.gradientMatrix; - gradientMatrix = (new Matrix(mat)).concatenate(new Matrix(gradientMatrix)).toMATRIX(); - + gradientMatrix = (new Matrix(mat)).concatenate(new Matrix(gradientMatrix)).toMATRIX(); + convertMatrix(gradientMatrix, writer); writer.writeEndElement(); GRADRECORD[] records; @@ -573,13 +569,13 @@ public class XFLConverter { writer.writeEndElement(); } - private static boolean shapeHasMultiLayers(HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles) throws XMLStreamException { - List layers = getShapeLayers(characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, false); + private static boolean shapeHasMultiLayers(SWF swf, HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles) throws XMLStreamException { + List layers = getShapeLayers(swf, characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, false); return layers.size() > 1; } - private static void convertShape(HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer) throws XMLStreamException { - List layers = getShapeLayers(characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, morphshape); + private static void convertShape(SWF swf, HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer) throws XMLStreamException { + List layers = getShapeLayers(swf, characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, morphshape); if (!useLayers) { for (int l = layers.size() - 1; l >= 0; l--) { writer.writeCharactersRaw(layers.get(l)); @@ -771,22 +767,27 @@ public class XFLConverter { return ret; } - private static List getShapeLayers(HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) throws XMLStreamException { + private static List getShapeLayers(SWF swf, HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) throws XMLStreamException { if (mat == null) { mat = new MATRIX(); } - List shapeRecordsAdvanced = new ArrayList<>(); - for (SHAPERECORD rec : shapeRecords) { - ShapeRecordAdvanced arec = ShapeRecordAdvanced.createFromSHAPERECORD(rec); - if (arec != null) { - shapeRecordsAdvanced.add(arec); + boolean doFix = true; + + List shapeRecordsAdvanced; + if (doFix) { + ShapeFixer fixer = new ShapeFixer(); + shapeRecordsAdvanced = fixer.fix(shapeRecords); + } else { + shapeRecordsAdvanced = new ArrayList<>(); + for (SHAPERECORD rec : shapeRecords) { + ShapeRecordAdvanced arec = ShapeRecordAdvanced.createFromSHAPERECORD(rec); + if (arec != null) { + shapeRecordsAdvanced.add(arec); + } } } - //#1903, #503, #1257 This is not working at all :-( - //ShapeFixer fixer = new ShapeFixer(); - //shapeRecordsAdvanced = fixer.fixShape(shapeRecordsAdvanced); List edges = new ArrayList<>(); int lineStyleCount = 0; int fillStyle0 = -1; @@ -877,6 +878,7 @@ public class XFLConverter { currentLayer.writeAttribute("edges", edgesSb.toString()); currentLayer.writeEndElement(); hasEdge = true; + //edges.clear(); } } if (currentLayer.length() > 0) { @@ -1145,7 +1147,7 @@ public class XFLConverter { } } } - + private static void walkMorphShapeUsages(ReadOnlyTagList timeLineTags, Map characters, Map usages) { Map depthMap = new HashMap<>(); for (Tag t : timeLineTags) { @@ -1171,24 +1173,23 @@ public class XFLConverter { if (ch == -1) { continue; } - + CharacterTag ct = characters.get(ch); if (ct instanceof MorphShapeTag) { if (!usages.containsKey(ch)) { usages.put(ch, 0); } int usageCount = usages.get(ch); - if (po.getRatio() <= 0) { - usageCount++; + if (po.getRatio() <= 0) { + usageCount++; usages.put(ch, usageCount); - } - } + } + } } } } } - private static List getMultiUsageMorphShapes(ReadOnlyTagList tags, HashMap characters) { List ret = new ArrayList<>(); Map usages = new TreeMap<>(); @@ -1200,8 +1201,8 @@ public class XFLConverter { } return ret; } - - private static List getNonLibraryShapes(ReadOnlyTagList tags, HashMap characters) { + + private static List getNonLibraryShapes(SWF swf, ReadOnlyTagList tags, HashMap characters) { HashMap usages = new HashMap<>(); walkShapeUsages(tags, characters, usages); List ret = new ArrayList<>(); @@ -1210,7 +1211,7 @@ public class XFLConverter { if (usages.get(ch) < 2) { if (characters.get(ch) instanceof ShapeTag) { ShapeTag shp = (ShapeTag) characters.get(ch); - if (!shapeHasMultiLayers(characters, null, shp.getShapeNum(), shp.getShapes().shapeRecords, shp.getShapes().fillStyles, shp.getShapes().lineStyles)) { + if (!shapeHasMultiLayers(swf, characters, null, shp.getShapeNum(), shp.getShapes().shapeRecords, shp.getShapes().fillStyles, shp.getShapes().lineStyles)) { ret.add(ch); } } @@ -1631,7 +1632,7 @@ public class XFLConverter { } private void convertSymbols(SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol, List multiUsageMorphShapes) throws XMLStreamException { - boolean hasSymbol = false; + boolean hasSymbol = false; Reference nextClipId = new Reference<>(-1); writer.writeStartElement("symbols"); for (int ch : characters.keySet()) { @@ -1639,10 +1640,6 @@ public class XFLConverter { if ((symbol instanceof ShapeTag) && nonLibraryShapes.contains(symbol.getCharacterId())) { continue; //shapes with 1 ocurrence and single layer are not added to library } - - /*if (!hasSymbol) { - - }*/ if ((symbol instanceof ShapeTag) || (symbol instanceof DefineSpriteTag) || (symbol instanceof ButtonTag)) { XFLXmlWriter symbolStr = new XFLXmlWriter(); @@ -1794,7 +1791,7 @@ public class XFLConverter { int characterId = character.getCharacterId(); if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId))) { ShapeTag shape = (ShapeTag) character; - convertShape(characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter); + convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter); } else if (character instanceof TextTag) { convertText(null, (TextTag) character, matrix, filters, null, recCharWriter); } else if (character instanceof DefineVideoStreamTag) { @@ -1842,10 +1839,10 @@ public class XFLConverter { continue; } final ScriptPack spriteScriptPack = characterScriptPacks.containsKey(sprite.spriteId) ? characterScriptPacks.get(sprite.spriteId) : null; - + extractMultilevelClips(sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes); - - convertTimeline(swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes); + + convertTimeline(swf, swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes); } else if (symbol instanceof ShapeTag) { symbolStr.writeStartElement("timeline"); @@ -1855,7 +1852,7 @@ public class XFLConverter { symbolStr.writeStartElement("layers"); SHAPEWITHSTYLE shapeWithStyle = shape.getShapes(); if (shapeWithStyle != null) { - convertShape(characters, null, shape.getShapeNum(), shapeWithStyle.shapeRecords, shapeWithStyle.fillStyles, shapeWithStyle.lineStyles, false, true, symbolStr); + convertShape(swf, characters, null, shape.getShapeNum(), shapeWithStyle.shapeRecords, shapeWithStyle.fillStyles, shapeWithStyle.lineStyles, false, true, symbolStr); } symbolStr.writeEndElement(); // layers @@ -1866,7 +1863,7 @@ public class XFLConverter { symbolStr.writeEndElement(); // DOMSymbolItem String symbolStr2 = prettyFormatXML(symbolStr.toString()); String symbolFile = "Symbol " + symbol.getCharacterId() + ".xml"; - files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); + files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); // write symbLink writer.writeStartElement("Include", new String[]{"href", symbolFile}); @@ -1890,8 +1887,6 @@ public class XFLConverter { }*/ writer.writeEndElement(); } - - private void convertMedia(SWF swf, Map characterVariables, Map characterClasses, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException { boolean hasMedia = false; @@ -2356,7 +2351,7 @@ public class XFLConverter { writer.writeEndElement(); // SoundEnvelope } } - + private static void convertFrame(boolean shapeTween, SoundStreamHeadTypeTag soundStreamHead, StartSoundTag startSound, int frame, int duration, String actionScript, String elements, HashMap files, XFLXmlWriter writer) throws XMLStreamException { DefineSoundTag sound = null; if (startSound != null) { @@ -2434,7 +2429,7 @@ public class XFLConverter { writer.writeEndElement(); } - private static void convertFrames(List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, List multiUsageMorphShapes) throws XMLStreamException { + private static void convertFrames(SWF swf, List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, List multiUsageMorphShapes) throws XMLStreamException { boolean lastIn = true; XFLXmlWriter writer2 = new XFLXmlWriter(); prevStr += ""; @@ -2463,14 +2458,12 @@ public class XFLConverter { //Add ShowFrameTag to the end when there is one last missing List timTags = timelineTags.toArrayList(); boolean needsFrameAdd = false; - SWF swf = null; for (int i = timTags.size() - 1; i >= 0; i--) { if (timTags.get(i) instanceof ShowFrameTag) { break; } if (timTags.get(i) instanceof PlaceObjectTypeTag) { needsFrameAdd = true; - swf = timTags.get(i).getSwf(); break; } } @@ -2551,7 +2544,7 @@ public class XFLConverter { } } } - + /*if (t instanceof ShowFrameTag) { if (t == timTags.get(timTags.size() - 1)) { if (shapeTween && character != null && (character instanceof MorphShapeTag)) { @@ -2563,7 +2556,6 @@ public class XFLConverter { } } }*/ - if (t instanceof RemoveTag) { RemoveTag rt = (RemoveTag) t; if (rt.getDepth() == depth) { @@ -2588,13 +2580,12 @@ public class XFLConverter { } } - if (t instanceof ShowFrameTag) { /*if (prevWasShapeTween) { prevWasShapeTween = false; continue; }*/ - + boolean shapeTweenNow = false; if (frame + 1 >= startFrame && (onlyFrames == null || onlyFrames.contains(frame + 1))) { @@ -2606,7 +2597,7 @@ public class XFLConverter { MorphShapeTag m = shapeTweener; XFLXmlWriter addLastWriter = new XFLXmlWriter(); SHAPEWITHSTYLE endShape = m.getShapeAtRatio(65535); //lastTweenRatio); - convertShape(characters, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(lastTweenRatio), m.getLineStyles().getLineStylesAt(m.getShapeNum(), lastTweenRatio), true, false, addLastWriter); + convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, endShape.shapeRecords, m.getFillStyles().getFillStylesAt(lastTweenRatio), m.getLineStyles().getLineStylesAt(m.getShapeNum(), lastTweenRatio), true, false, addLastWriter); //duration--; convertFrame(true, null, null, frame - duration, duration, "", lastElements, files, writer2); duration = 1; @@ -2620,10 +2611,10 @@ public class XFLConverter { standaloneShapeTweener = null; } else if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId))) { // || shapeTweener != null)) { ShapeTag shape = (ShapeTag) character; - convertShape(characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter); - + convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter); + shapeTween = false; - shapeTweener = null; + shapeTweener = null; } else if (character != null) { if (character instanceof MorphShapeTag) { MorphShapeTag m = (MorphShapeTag) character; @@ -2634,11 +2625,11 @@ public class XFLConverter { standaloneShapeTweenerMatrix = matrix; convertSymbolInstance(instanceName, matrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, character, characters, tags, flaVersion, elementsWriter); } else { - convertShape(characters, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter); - shapeTween = true; + convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter); + shapeTween = true; } } else { - shapeTween = false; + shapeTween = false; if (character instanceof TextTag) { convertText(instanceName, (TextTag) character, matrix, filters, clipActions, elementsWriter); } else if (character instanceof DefineVideoStreamTag) { @@ -2650,7 +2641,7 @@ public class XFLConverter { } } } - } + } } if (!shapeTweenNow) { frame++; @@ -2663,7 +2654,7 @@ public class XFLConverter { } else { duration++; } - + lastElements = elements; if (frame > endFrame) { if (lastIn) { @@ -3175,32 +3166,30 @@ public class XFLConverter { ret.add(0, varName); return String.join(".", ret); } - - + private void addExtractedClip( - ReadOnlyTagList timelineTags, - XFLXmlWriter writer, - SWF swf, - Reference nextClipId, - List nonLibraryShapes, - String backgroundColor, - HashMap characters, - FLAVersion flaVersion, - HashMap files, - Map placeToMaskedSymbol, - List multiUsageMorphShapes + ReadOnlyTagList timelineTags, + XFLXmlWriter writer, + SWF swf, + Reference nextClipId, + List nonLibraryShapes, + String backgroundColor, + HashMap characters, + FLAVersion flaVersion, + HashMap files, + Map placeToMaskedSymbol, + List multiUsageMorphShapes ) throws XMLStreamException { XFLXmlWriter symbolStr = new XFLXmlWriter(); - if (nextClipId.getVal() < 0) { nextClipId.setVal(swf.getNextCharacterId()); } else { nextClipId.setVal(nextClipId.getVal() + 1); } - + int objectId = nextClipId.getVal(); - + symbolStr.writeStartElement("DOMSymbolItem", new String[]{ "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns", "http://ns.adobe.com/xfl/2008/", @@ -3208,14 +3197,13 @@ public class XFLConverter { "lastModified", Long.toString(getTimestamp(swf))}); symbolStr.writeAttribute("symbolType", "graphic"); - convertTimeline(swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes); - + convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes); symbolStr.writeEndElement(); // DOMSymbolItem String symbolStr2 = prettyFormatXML(symbolStr.toString()); String symbolFile = generateMaskedSymbolName(objectId) + ".xml"; files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); - + writer.writeStartElement("Include", new String[]{"href", symbolFile}); writer.writeAttribute("itemIcon", "1"); writer.writeAttribute("loadImmediate", false); @@ -3224,15 +3212,14 @@ public class XFLConverter { //TODO: itemID="518de416-00000341" } writer.writeEndElement(); - + extractMultilevelClips(timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes); } - + private String generateMaskedSymbolName(int symbolId) { return (DEBUG_EXPORT_LAYER_DEPTHS ? "MaskedSymbol " : "Symbol ") + symbolId; } - - + private boolean getMorphshapeTimeline(int morphShapeId, ReadOnlyTagList tags, List outTimelineTags) { int morphDepth = -2; boolean onTrack = false; @@ -3251,8 +3238,7 @@ public class XFLConverter { PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; if (morphDepth == place.getDepth() && place.getCharacterId() != -1 - && place.getCharacterId() != morphShapeId - ) { + && place.getCharacterId() != morphShapeId) { outTimelineTags.add(t); onTrack = false; } else if ((morphDepth == -2 && place.getCharacterId() == morphShapeId) || morphDepth == place.getDepth()) { @@ -3281,18 +3267,18 @@ public class XFLConverter { outTimelineTags.add(t); break; } - } + } } - + if (!outTimelineTags.isEmpty()) { return true; - } + } return false; } - + private void extractMultiUsageMorphShapes( XFLXmlWriter writer, - SWF swf, + SWF swf, List nonLibraryShapes, String backgroundColor, HashMap characters, @@ -3300,9 +3286,9 @@ public class XFLConverter { HashMap files, List multiUsageMorphShapes ) throws XMLStreamException { - + for (int objectId : multiUsageMorphShapes) { - XFLXmlWriter symbolStr = new XFLXmlWriter(); + XFLXmlWriter symbolStr = new XFLXmlWriter(); symbolStr.writeStartElement("DOMSymbolItem", new String[]{ "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns", "http://ns.adobe.com/xfl/2008/", @@ -3313,10 +3299,10 @@ public class XFLConverter { List timelineTags = new ArrayList<>(); getMorphshapeTimeline(objectId, swf.getTags(), timelineTags); - + timelineTags = Helper.deepCopy(timelineTags); - - for (Tag t : timelineTags) { + + for (Tag t : timelineTags) { t.setSwf(swf); if (t instanceof PlaceObjectTypeTag) { PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; @@ -3325,9 +3311,8 @@ public class XFLConverter { } } } - - convertTimeline(swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, swf.getTags(), new ReadOnlyTagList(timelineTags), characters, "Symbol " + objectId, flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>()); + convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, swf.getTags(), new ReadOnlyTagList(timelineTags), characters, "Symbol " + objectId, flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>()); symbolStr.writeEndElement(); // DOMSymbolItem String symbolStr2 = prettyFormatXML(symbolStr.toString()); @@ -3344,10 +3329,10 @@ public class XFLConverter { writer.writeEndElement(); } } - + private void extractMultilevelClips(ReadOnlyTagList timelineTags, XFLXmlWriter writer, - SWF swf, + SWF swf, Reference nextClipId, List nonLibraryShapes, String backgroundColor, @@ -3363,7 +3348,7 @@ public class XFLConverter { Map depthToClipPlace = new HashMap<>(); Map clipFinishFrames = new HashMap<>(); Map clipStartFrames = new HashMap<>(); - + int maxDepth = getMaxDepth(timelineTags); Tag lastTag = null; for (Tag t : timelineTags) { @@ -3399,7 +3384,7 @@ public class XFLConverter { if (clipPlaces.isEmpty()) { return; } - + //Some sprites do not end with ShowFrame: if (lastTag != null && !(lastTag instanceof ShowFrameTag)) { f++; @@ -3420,10 +3405,10 @@ public class XFLConverter { depthToFramesList.get(d).add(i); } } - + Map>> frameToDepthToClips = new TreeMap<>(); - - for (f = 0; f < frameCount; f++) { + + for (f = 0; f < frameCount; f++) { for (int d = 0; d < maxDepth; d++) { for (int p = 0; p < clipPlaces.size(); p++) { PlaceObjectTypeTag po = clipPlaces.get(p); @@ -3443,27 +3428,27 @@ public class XFLConverter { } } } - - Set delegatedPlaces = new HashSet<>(); - + + Set delegatedPlaces = new HashSet<>(); + for (int fr : frameToDepthToClips.keySet()) { for (int d : frameToDepthToClips.get(fr).keySet()) { List places = frameToDepthToClips.get(fr).get(d); if (places.size() > 1) { - depthToFramesList.get(d).remove((Integer) fr); + depthToFramesList.get(d).remove((Integer) fr); PlaceObjectTypeTag secondPlace = places.get(1); if (delegatedPlaces.contains(secondPlace)) { continue; } delegatedPlaces.add(secondPlace); - - List delegatedTimeline = new ArrayList<>(); - f = 0; + + List delegatedTimeline = new ArrayList<>(); + f = 0; boolean removed = false; int numFrames = 0; lastTag = null; - Map depthStates = new HashMap<>(); - + Map depthStates = new HashMap<>(); + for (Tag t : timelineTags) { if (f < fr) { if (t instanceof PlaceObjectTypeTag) { @@ -3473,7 +3458,7 @@ public class XFLConverter { } if (place.getCharacterId() != -1) { depthStates.put(place.getDepth(), place.getCharacterId()); - } + } } if (t instanceof RemoveTag) { depthStates.remove(((RemoveTag) t).getDepth()); @@ -3482,7 +3467,7 @@ public class XFLConverter { if (f >= fr) { if (t instanceof PlaceObjectTypeTag) { PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; - if (place.getDepth() == secondPlace.getDepth()) { + if (place.getDepth() == secondPlace.getDepth()) { if (place.flagMove()) { removed = false; } else if (place.getClipDepth() == secondPlace.getClipDepth()) { @@ -3513,15 +3498,15 @@ public class XFLConverter { break; } f++; - } + } } if (!(lastTag instanceof ShowFrameTag)) { numFrames++; ShowFrameTag showFrame = new ShowFrameTag(swf); //set timelined? - delegatedTimeline.add(showFrame); - } - + delegatedTimeline.add(showFrame); + } + /* List delegatedTimeline2 = Helper.deepCopy(delegatedTimeline); for (int i = 0; i < delegatedTimeline2.size(); i++) { @@ -3627,8 +3612,8 @@ public class XFLConverter { } } } - - private void convertTimeline(AbcIndexing abcIndex, int spriteId, String linkageIdentifier, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, String name, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, ScriptPack scriptPack, Map placeToMaskedSymbol, List multiUsageMorphShapes) throws XMLStreamException { + + private void convertTimeline(SWF swf, AbcIndexing abcIndex, int spriteId, String linkageIdentifier, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, String name, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, ScriptPack scriptPack, Map placeToMaskedSymbol, List multiUsageMorphShapes) throws XMLStreamException { List classNames = new ArrayList<>(); //Searches for Object.registerClass("linkageIdentifier",mypkg.MyClass); @@ -3741,12 +3726,12 @@ public class XFLConverter { clipFrameSplitters.add(f); clipStartFrames.put(po, f); clipPlaces.add(po); - + if (depthToClipPlace.containsKey(po.getDepth())) { clipFinishFrames.put(depthToClipPlace.get(po.getDepth()), f - 1); } - - depthToClipPlace.put(po.getDepth(), po); + + depthToClipPlace.put(po.getDepth(), po); for (int j = tpos + 1; j <= timelineTags.size(); j++) { Tag t2 = timelineTags.get(j); if (t2 instanceof PlaceObject2Tag) { @@ -3759,7 +3744,7 @@ public class XFLConverter { if (t2 instanceof ShowFrameTag) { break; } - + } } else { if (!po.flagMove() && depthToClipPlace.containsKey(po.getDepth())) { @@ -3807,10 +3792,10 @@ public class XFLConverter { depthToFramesList.get(d).add(i); } } - + Map>> frameToDepthToClips = new TreeMap<>(); - - for (f = 0; f < frameCount; f++) { + + for (f = 0; f < frameCount; f++) { for (int d = 0; d < maxDepth; d++) { for (int p = 0; p < clipPlaces.size() - 1; p++) { PlaceObjectTypeTag po = clipPlaces.get(p); @@ -3833,9 +3818,9 @@ public class XFLConverter { } } } - + Set multiLevelsPlaces = new HashSet<>(); - + Set secondLevelPlaces = new HashSet<>(); Map secondToFirstLevelPlace = new HashMap<>(); for (int fr : frameToDepthToClips.keySet()) { @@ -3851,12 +3836,11 @@ public class XFLConverter { } } } - - Set handledClips = new HashSet<>(); for (int d = maxDepth; d >= 0; d--) { - loopp: for (int p = 0; p < clipPlaces.size() - 1; p++) { + loopp: + for (int p = 0; p < clipPlaces.size() - 1; p++) { PlaceObjectTypeTag po = clipPlaces.get(p); /* if (po != null && po.getClipDepth() == d && secondLevelPlaces.contains(po)) { int clipFrame = clipFrameSplitters.get(p); @@ -3873,39 +3857,37 @@ public class XFLConverter { } if (po != null && po.getClipDepth() == d) { int clipFrame = clipFrameSplitters.get(p); - int nextFrame = clipFinishFrames.get(po); + int nextFrame = clipFinishFrames.get(po); handledClips.add(po); - - + int lastFrame = nextFrame; for (int p2 = 0; p2 < clipPlaces.size() - 1; p2++) { PlaceObjectTypeTag po2 = clipPlaces.get(p2); if (po2 == null) { continue; - } + } int clipFrame2 = clipFrameSplitters.get(p2); int nextFrame2 = clipFinishFrames.get(po2); - if (lastFrame + 1 == clipFrame2 - && po.getDepth() == po2.getDepth() + if (lastFrame + 1 == clipFrame2 + && po.getDepth() == po2.getDepth() && po.getClipDepth() == po2.getClipDepth() - && !multiLevelsPlaces.contains(po2) - ) { + && !multiLevelsPlaces.contains(po2)) { lastFrame = nextFrame2; handledClips.add(po2); } } - + writer.writeStartElement("DOMLayer", new String[]{ "name", "Layer " + (index + 1) + (DEBUG_EXPORT_LAYER_DEPTHS ? " (depth " + po.getDepth() + " clipdepth:" + po.getClipDepth() + ")" : ""), "color", randomOutlineColor(), "layerType", "mask", "locked", "true"}); - convertFrames(depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes); + convertFrames(swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes); writer.writeEndElement(); - + int parentIndex = index; index++; - + for (int fx = clipFrame; fx <= lastFrame; fx++) { for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { if (!depthToFramesList.containsKey(nd) || !depthToFramesList.get(nd).contains(fx)) { @@ -3914,13 +3896,13 @@ public class XFLConverter { if (frameToDepthToClips.containsKey(fx) && frameToDepthToClips.get(fx).containsKey(nd)) { List clips = frameToDepthToClips.get(fx).get(nd); - if (clips.size() > 1) { + if (clips.size() > 1) { PlaceObjectTypeTag po2 = clips.get(1); if (handledClips.contains(po2)) { continue; } handledClips.add(po2); - + MultiLevelClip mlc = placeToMaskedSymbol.get(po2); writer.writeStartElement("DOMLayer", new String[]{ @@ -3946,11 +3928,9 @@ public class XFLConverter { "keyMode", "" + KEY_MODE_NORMAL }); writer.writeEmptyElement("elements"); - writer.writeEndElement(); + writer.writeEndElement(); } - - writer.writeStartElement("DOMFrame", new String[]{ "index", "" + clipFrame2, "duration", "" + mlc.numFrames, @@ -3975,24 +3955,23 @@ public class XFLConverter { writer.writeEndElement(); //elements writer.writeEndElement(); //DOMFrame - writer.writeEndElement(); //frames writer.writeEndElement(); - index++; - + index++; + for (int nd2 = po2.getDepth(); nd2 <= po2.getClipDepth(); nd2++) { for (int i = clipFrame2; i < clipFrame2 + mlc.numFrames; i++) { depthToFramesList.get(nd2).remove((Integer) i); - } - } + } + } } } } - } - - for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { - boolean nonEmpty = writeLayer(index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes); + } + + for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { + boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes); for (int i = clipFrame; i <= lastFrame; i++) { depthToFramesList.get(nd).remove((Integer) i); } @@ -4006,7 +3985,7 @@ public class XFLConverter { } } - boolean nonEmpty = writeLayer(index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes); + boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes); if (nonEmpty) { index++; } @@ -4044,7 +4023,7 @@ public class XFLConverter { writer.writeEndElement(); //DOMLayer } - private boolean writeLayer(int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files, List multiUsageMorphShapes) throws XMLStreamException { + private boolean writeLayer(SWF swf, int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files, List multiUsageMorphShapes) throws XMLStreamException { XFLXmlWriter layerPrev = new XFLXmlWriter(); layerPrev.writeStartElement("DOMLayer", new String[]{ "name", "Layer " + (index + 1) + (DEBUG_EXPORT_LAYER_DEPTHS ? " (depth " + d + ")" : ""), @@ -4056,12 +4035,12 @@ public class XFLConverter { } if (parentLayer != -1) { layerPrev.writeAttribute("parentLayerIndex", parentLayer); - layerPrev.writeAttribute("locked", true); + layerPrev.writeAttribute("locked", true); } layerPrev.writeCharacters(""); // todo honfika: hack to close start tag String layerAfter = ""; int prevLength = writer.length(); - convertFrames(onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes); + convertFrames(swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes); return writer.length() != prevLength; } @@ -4549,7 +4528,7 @@ public class XFLConverter { final HashMap datfiles = new HashMap<>(); HashMap characters = getCharacters(swf.getTags()); List multiUsageMorphShapes = getMultiUsageMorphShapes(swf.getTags(), characters); - List nonLibraryShapes = getNonLibraryShapes(swf.getTags(), characters); + List nonLibraryShapes = getNonLibraryShapes(swf, swf.getTags(), characters); Map characterClasses = getCharacterClasses(swf.getTags()); Map characterScriptPacks = getCharacterScriptPacks(swf, characterClasses); Map characterVariables = getCharacterVariables(swf.getTags()); @@ -4594,13 +4573,13 @@ public class XFLConverter { } Map placeToMaskedSymbol = new HashMap<>(); - - convertFonts(characterClasses, swf.getTags(), domDocument); + + convertFonts(characterClasses, swf.getTags(), domDocument); convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes); //domDocument.writeStartElement("timelines"); ScriptPack documentScriptPack = characterScriptPacks.containsKey(0) ? characterScriptPacks.get(0) : null; - convertTimeline(swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes); + convertTimeline(swf, swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes); //domDocument.writeEndElement(); if (hasAmfMetadata) { @@ -4618,6 +4597,8 @@ public class XFLConverter { domDocument.writeEndElement(); } catch (XMLStreamException ex) { logger.log(Level.SEVERE, null, ex); + } catch (Throwable ex) { + logger.log(Level.SEVERE, null, ex); } String domDocumentStr = prettyFormatXML(domDocument.toString()); @@ -5377,8 +5358,9 @@ public class XFLConverter { } } } - + private class MultiLevelClip { + public PlaceObjectTypeTag startClipPlaceTag; public int symbol; public int numFrames; @@ -5387,6 +5369,6 @@ public class XFLConverter { this.startClipPlaceTag = startClipPlaceTag; this.symbol = symbol; this.numFrames = numFrames; - } + } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java index 1cd87300a..5a8e8454f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java @@ -17,46 +17,111 @@ package com.jpexs.decompiler.flash.xfl.shapefixer; import com.jpexs.decompiler.flash.math.BezierEdge; +import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; +import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; +import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Reference; import java.awt.geom.Point2D; import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.Objects; /** * * @author JPEXS */ public class ShapeFixer { + + final boolean DEBUG_PRINT = false; - private void addToEdgeMap(Map> edgeMap, Edge edge) { - if (!edgeMap.containsKey(edge.getFrom())) { - edgeMap.put(edge.getFrom(), new ArrayList<>()); - } - if (!edgeMap.containsKey(edge.getTo())) { - edgeMap.put(edge.getTo(), new ArrayList<>()); - } - edgeMap.get(edge.getFrom()).add(edge); - edgeMap.get(edge.getTo()).add(edge.invert()); + private boolean isOnRight(Point2D a, Point2D b, Point2D c) { + return ((b.getX() - a.getX()) * (c.getY() - a.getY()) - (b.getY() - a.getY()) * (c.getX() - a.getX())) > 0; } - private boolean fixSingleEdge(List records) { - Map> edgeMap = new LinkedHashMap<>(); - double x = 0; - double y = 0; + private class BezierPair { + + BezierEdge be1; + BezierEdge be2; + + public BezierPair(BezierEdge be1, BezierEdge be2) { + this.be1 = be1; + this.be2 = be2; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.be1) + Objects.hashCode(this.be2); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BezierPair other = (BezierPair) obj; + if ((Objects.equals(this.be1, other.be1) && Objects.equals(this.be2, other.be2)) + || (Objects.equals(this.be1, other.be2) && Objects.equals(this.be2, other.be1))) { + return true; + } + return false; + } + + } + + public List fix( + List records + ) { + + List> shapes = new ArrayList<>(); + List currentShape = new ArrayList<>(); + List fillStyles0 = new ArrayList<>(); + List fillStyles1 = new ArrayList<>(); + List lineStyles = new ArrayList<>(); + List layers = new ArrayList<>(); + List fillStyleLayers = new ArrayList<>(); + List lineStyleLayers = new ArrayList<>(); + int fillStyle0 = 0; int fillStyle1 = 0; - - for (int r = 0; r < records.size(); r++) { - ShapeRecordAdvanced rec = records.get(r); - if (rec instanceof StyleChangeRecordAdvanced) { - StyleChangeRecordAdvanced scr = (StyleChangeRecordAdvanced) rec; + int lineStyle = 0; + int layer = -1; + int x = 0; + int y = 0; + for (SHAPERECORD rec : records) { + if (rec instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) rec; + if (scr.stateMoveTo + || scr.stateNewStyles + || scr.stateFillStyle0 + || scr.stateFillStyle1 + || scr.stateLineStyle) { + if (!currentShape.isEmpty()) { + shapes.add(currentShape); + fillStyles0.add(fillStyle0); + fillStyles1.add(fillStyle1); + lineStyles.add(lineStyle); + layers.add(layer); + currentShape = new ArrayList<>(); + } + } if (scr.stateNewStyles) { + layer++; fillStyle0 = 0; fillStyle1 = 0; + lineStyle = 0; + fillStyleLayers.add(scr.fillStyles); + lineStyleLayers.add(scr.lineStyles); } if (scr.stateFillStyle0) { fillStyle0 = scr.fillStyle0; @@ -64,297 +129,298 @@ public class ShapeFixer { if (scr.stateFillStyle1) { fillStyle1 = scr.fillStyle1; } + if (scr.stateLineStyle) { + lineStyle = scr.lineStyle; + } } - if (rec instanceof StraightEdgeRecordAdvanced) { - StraightEdgeRecordAdvanced ser = (StraightEdgeRecordAdvanced) rec; - addToEdgeMap(edgeMap, new Edge(fillStyle0, fillStyle1, r, false, x, y, x + ser.deltaX, y + ser.deltaY)); + if (rec instanceof StraightEdgeRecord) { + int x2 = rec.changeX(x); + int y2 = rec.changeY(y); + BezierEdge be = new BezierEdge(x, y, x2, y2); + currentShape.add(be); } - if (rec instanceof CurvedEdgeRecordAdvanced) { - CurvedEdgeRecordAdvanced cer = (CurvedEdgeRecordAdvanced) rec; - addToEdgeMap(edgeMap, new Edge(fillStyle0, fillStyle1, r, false, - x, y, - x + cer.controlDeltaX, y + cer.controlDeltaY, - x + cer.controlDeltaX + cer.anchorDeltaX, y + cer.controlDeltaY + cer.anchorDeltaY - )); + if (rec instanceof CurvedEdgeRecord) { + CurvedEdgeRecord cer = (CurvedEdgeRecord) rec; + int cx = x + cer.controlDeltaX; + int cy = y + cer.controlDeltaY; + int ax = cx + cer.anchorDeltaX; + int ay = cy + cer.anchorDeltaY; + BezierEdge be = new BezierEdge(x, y, cx, cy, ax, ay); + currentShape.add(be); + } + if (rec instanceof EndShapeRecord) { + if (!currentShape.isEmpty()) { + shapes.add(currentShape); + fillStyles0.add(fillStyle0); + fillStyles1.add(fillStyle1); + lineStyles.add(lineStyle); + layers.add(layer); + currentShape = new ArrayList<>(); + } } x = rec.changeX(x); y = rec.changeY(y); } - for (Point2D p : edgeMap.keySet()) { - List edges = edgeMap.get(p); - for (int i = 0; i < edges.size(); i++) { - for (int j = 0; j < edges.size(); j++) { - if (i == j) { - continue; - } - Edge edge1 = edges.get(i); - Edge edge2 = edges.get(j); - List newEdges1 = new ArrayList<>(); - List newEdges2 = new ArrayList<>(); + List splittedPairs = new ArrayList<>(); - if (edge1.intersection(edge2, newEdges1, newEdges2)) { + loopi1: + for (int i1 = 0; i1 < shapes.size(); i1++) { + layer = layers.get(i1); + loopj1: + for (int j1 = 0; j1 < shapes.get(i1).size(); j1++) { + BezierEdge be1 = shapes.get(i1).get(j1); + for (int i2 = 0; i2 < shapes.size(); i2++) { + if (layers.get(i2) == layer) { + loopj2: + for (int j2 = 0; j2 < shapes.get(i2).size(); j2++) { + BezierEdge be2 = shapes.get(i2).get(j2); - /*System.out.println("---------------------------------------"); - System.out.println("intersection edge "+edge1 + " and " +edge2); - System.out.println("newEdges1:"); - for(Edge e:newEdges1) { - System.out.println("..."+e); + if (i1 == i2 && j1 == j2) { + continue; + } + + + + if (be1.isEmpty()) { + shapes.get(i1).remove(j1); + j1--; + continue loopj1; + } + if (be2.isEmpty()) { + shapes.get(i2).remove(j2); + j2--; + continue loopj2; + } + + //duplicated edge + if (be1.equals(be2) || be1.equals(be2.reverse())) { + shapes.get(i2).remove(j2); + j2--; + if (DEBUG_PRINT) { + System.err.println("removing duplicate " + be1.toSvg() + " and " + be2.toSvg()); + } + continue; + } + + BezierPair pair = new BezierPair(be1, be2); + if (splittedPairs.contains(pair)) { + continue; + } + + List t1Ref = new ArrayList<>(); + List t2Ref = new ArrayList<>(); + List intPoints = new ArrayList<>(); + + if (DEBUG_PRINT) { + System.err.print("checking shape[" + i1 + "][" + j1 + "] to shape[" + i2 + "][" + j2 + "] : " + be1.toSvg() + " and " + be2.toSvg()); + } + + boolean isint = be1.intersects(be2, t1Ref, t2Ref, intPoints); + if (DEBUG_PRINT) { + System.err.println(" " + isint); + if (isint) { + System.err.println("xxx"); + } + } + //List inters = be.getIntersections(be2); + + if (!t1Ref.isEmpty()) { + + if (DEBUG_PRINT) { + System.err.println("be1 " + be1); + System.err.println("be2 " + be2); + System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg()); + System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2)); + System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2)); + System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2)); + + for (int n = 0; n < t1Ref.size(); n++) { + System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n)); + } + } + + + + if ((be1.getBeginPoint().equals(be2.getBeginPoint()) + || be1.getBeginPoint().equals(be2.getEndPoint()) + || be1.getEndPoint().equals(be2.getBeginPoint()) + || be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) { + if (DEBUG_PRINT) { + System.err.println("Same begin/end - continued"); + } + continue; + } + + if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1) + && (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) { + t1Ref.add(0, t1Ref.remove(1)); + t2Ref.add(0, t2Ref.remove(1)); + intPoints.add(0, intPoints.remove(1)); + } + + int splitPointIndex = 0; + if (intPoints.size() > 1) { + splitPointIndex = 1; + } + + splittedPairs.add(pair); + + Reference be1LRef = new Reference<>(null); + Reference be1RRef = new Reference<>(null); + be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef); + Reference be2LRef = new Reference<>(null); + Reference be2RRef = new Reference<>(null); + be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef); + + BezierEdge be1L = be1LRef.getVal(); + BezierEdge be1R = be1RRef.getVal(); + BezierEdge be2L = be2LRef.getVal(); + BezierEdge be2R = be2RRef.getVal(); + + Point2D intP = intPoints.get(splitPointIndex); + + be1L.setEndPoint(intP); + be1R.setBeginPoint(intP); + be2L.setEndPoint(intP); + be2R.setBeginPoint(intP); + + be1L.roundHalf(); + be1R.roundHalf(); + be2L.roundHalf(); + be2R.roundHalf(); + + if (i1 == i2 && j1 < j2) { + shapes.get(i1).remove(j2); + shapes.get(i2).remove(j1); + j2--; + } else { + shapes.get(i1).remove(j1); + shapes.get(i2).remove(j2); + if (i1 == i2) { + j1--; + } + } + + int n1 = j1; + int n2 = j2; + + if (!be1L.isEmpty()) { + shapes.get(i1).add(n1, be1L); + if (i1 == i2 && n2 > n1) { + n2++; + } + n1++; + if (DEBUG_PRINT) { + System.err.println("added " + be1L.toSvg()); + } + } + + if (!be1R.isEmpty()) { + shapes.get(i1).add(n1, be1R); + if (i1 == i2 && n2 > n1) { + n2++; + } + n1++; + if (DEBUG_PRINT) { + System.err.println("added " + be1R.toSvg()); + } + } + + if (!be2L.isEmpty()) { + shapes.get(i2).add(n2, be2L); + if (i1 == i2 && n1 > n2) { + n1++; + } + n2++; + if (DEBUG_PRINT) { + System.err.println("added " + be2L.toSvg()); + } + } + + if (!be2R.isEmpty()) { + shapes.get(i2).add(n2, be2R); + if (i1 == i2 && n1 > n2) { + n1++; + } + n2++; + if (DEBUG_PRINT) { + System.err.println("added " + be2R.toSvg()); + } + } + + j1--; + continue loopj1; + } } - System.out.println("newEdges2:"); - for(Edge e:newEdges2) { - System.out.println("..."+e); - }*/ - Point2D a = edge1.getFrom(); - Point2D b = edge1.getTo(); - Point2D c = edge2.getTo(); - - boolean edge2isRight = ((b.getX() - a.getX()) * (c.getY() - a.getY())) - ((b.getY() - a.getY()) * (c.getX() - a.getX())) > 0; - //edge2isRight = !edge2isRight; - - fillStyle0 = edge2isRight ? edge1.fillStyle0 : edge2.fillStyle0; - fillStyle1 = edge2isRight ? edge2.fillStyle1 : edge1.fillStyle1; - - StyleChangeRecordAdvanced moveCenter = new StyleChangeRecordAdvanced(); - moveCenter.stateMoveTo = true; - moveCenter.moveDeltaX = edge1.getFrom().getX(); - moveCenter.moveDeltaY = edge1.getFrom().getY(); - moveCenter.stateFillStyle0 = true; - moveCenter.stateFillStyle1 = true; - moveCenter.fillStyle0 = fillStyle0; - moveCenter.fillStyle1 = fillStyle1; - - //common line - StraightEdgeRecordAdvanced ser = new StraightEdgeRecordAdvanced(); - ser.deltaX = newEdges1.get(0).getTo().getX() - edge1.getFrom().getX(); - ser.deltaY = newEdges1.get(0).getTo().getY() - edge1.getFrom().getY(); - - StyleChangeRecordAdvanced scrStart1 = new StyleChangeRecordAdvanced(); - scrStart1.stateMoveTo = false; - scrStart1.stateFillStyle0 = true; - scrStart1.stateFillStyle1 = true; - scrStart1.fillStyle0 = newEdges1.get(1).fillStyle0; - scrStart1.fillStyle1 = newEdges1.get(1).fillStyle1; - - StyleChangeRecordAdvanced moveBack1 = new StyleChangeRecordAdvanced(); - moveBack1.stateMoveTo = true; - Edge edge1NotInverted = edge1.inverted ? edge1.invert() : edge1; - - moveBack1.moveDeltaX = edge1NotInverted.getTo().getX(); - moveBack1.moveDeltaY = edge1NotInverted.getTo().getY(); - moveBack1.stateFillStyle0 = true; - moveBack1.stateFillStyle1 = true; - moveBack1.fillStyle0 = edge1NotInverted.fillStyle0; - moveBack1.fillStyle1 = edge1NotInverted.fillStyle1; - - records.remove(edge1.recordIndex); - records.add(edge1.recordIndex, moveCenter); - records.add(edge1.recordIndex + 1, ser); - records.add(edge1.recordIndex + 2, scrStart1); - records.add(edge1.recordIndex + 3, newEdges1.get(1).toShapeRecordAdvanced()); - records.add(edge1.recordIndex + 4, moveBack1); - - if (edge2.recordIndex > edge1.recordIndex) { - edge2.recordIndex += 4; - } - - StyleChangeRecordAdvanced moveStart2 = new StyleChangeRecordAdvanced(); - moveStart2.stateMoveTo = true; - moveStart2.moveDeltaX = newEdges2.get(1).getFrom().getX(); - moveStart2.moveDeltaY = newEdges2.get(1).getFrom().getY(); - moveStart2.stateFillStyle0 = true; - moveStart2.stateFillStyle1 = true; - moveStart2.fillStyle0 = newEdges2.get(1).fillStyle0; - moveStart2.fillStyle1 = newEdges2.get(1).fillStyle1; - - StyleChangeRecordAdvanced moveBack2 = new StyleChangeRecordAdvanced(); - moveBack2.stateMoveTo = true; - Edge edge2NotInverted = edge2.inverted ? edge2.invert() : edge2; - - moveBack2.moveDeltaX = edge2NotInverted.getTo().getX(); - moveBack2.moveDeltaY = edge2NotInverted.getTo().getY(); - moveBack2.stateFillStyle0 = true; - moveBack2.stateFillStyle1 = true; - moveBack2.fillStyle0 = edge2NotInverted.fillStyle0; - moveBack2.fillStyle1 = edge2NotInverted.fillStyle1; - - records.remove(edge2.recordIndex); - records.add(edge2.recordIndex, moveStart2); - records.add(edge2.recordIndex + 1, newEdges2.get(1).toShapeRecordAdvanced()); - records.add(edge2.recordIndex + 2, moveBack2); - return true; } } } } - return false; - } - public List fixShape(List records) { - List ret = Helper.deepCopy(records); - while (fixSingleEdge(ret)) { - //nothing - } - for (ShapeRecordAdvanced rec : ret) { - rec.round(); + List ret = new ArrayList<>(); + + layer = -1; + double dx = 0; + double dy = 0; + for (int i = 0; i < shapes.size(); i++) { + List bes = shapes.get(i); + if (bes.isEmpty()) { + continue; + } + StyleChangeRecordAdvanced scr = new StyleChangeRecordAdvanced(); + scr.stateMoveTo = true; + dx = scr.moveDeltaX = bes.get(0).points.get(0).getX(); + dy = scr.moveDeltaY = bes.get(0).points.get(0).getY(); + + int newLayer = layers.get(i); + if (newLayer != layer) { + scr.stateNewStyles = true; + scr.fillStyles = fillStyleLayers.get(newLayer); + scr.lineStyles = lineStyleLayers.get(newLayer); + } + layer = newLayer; + + scr.stateFillStyle0 = true; + scr.fillStyle0 = fillStyles0.get(i); + scr.stateFillStyle1 = true; + scr.fillStyle1 = fillStyles1.get(i); + scr.stateLineStyle = true; + scr.lineStyle = lineStyles.get(i); + ret.add(scr); + for (BezierEdge be : bes) { + if (!be.getBeginPoint().equals(new Point2D.Double(dx, dy))) { + StyleChangeRecordAdvanced sm = new StyleChangeRecordAdvanced(); + sm.stateMoveTo = true; + sm.moveDeltaX = be.getBeginPoint().getX(); + sm.moveDeltaY = be.getBeginPoint().getY(); + ret.add(sm); + } + dx = be.getEndPoint().getX(); + dy = be.getEndPoint().getY(); + + ShapeRecordAdvanced sra = bezierToAdvancedRecord(be); + ret.add(sra); + } } return ret; } - class Edge { - - int recordIndex; - boolean inverted; - int fillStyle0 = 0; - int fillStyle1 = 0; - - List points = new ArrayList<>(); - - public Point2D getFrom() { - return points.get(0); - } - - public Point2D getTo() { - return points.get(points.size() - 1); - } - - public BezierEdge toBezierEdge() { - return new BezierEdge(new ArrayList<>(points)); - } - - public boolean intersection(Edge otherEdge, List newThisEdges, List newOtherEdges) { - List t1s = new ArrayList<>(); - List t2s = new ArrayList<>(); - BezierEdge be1 = this.toBezierEdge(); - BezierEdge be2 = otherEdge.toBezierEdge(); - if (!be1.intersects(be2, t1s, t2s)) { - return false; - } - Reference be1aRef = new Reference<>(null); - Reference be1bRef = new Reference<>(null); - Reference be2aRef = new Reference<>(null); - Reference be2bRef = new Reference<>(null); - - t1s.sort(new Comparator() { - @Override - public int compare(Double o1, Double o2) { - return Double.compare(o1, o2); - } - }); - - t2s.sort(new Comparator() { - @Override - public int compare(Double o1, Double o2) { - return Double.compare(o1, o2); - } - }); - - if (t1s.size() == 1) { - return false; - } - - double t1 = t1s.get(t1s.size() - 1); - double t2 = t2s.get(t2s.size() - 1); - /*System.out.println("t1 = "+t1); - System.out.println("t2 = "+t2);*/ - - be1.split(t1, be1aRef, be1bRef); - be2.split(t2, be2aRef, be2bRef); - - newThisEdges.add(new Edge(this.fillStyle0, this.fillStyle1, -1, this.inverted, be1aRef.getVal())); - newThisEdges.add(new Edge(this.fillStyle0, this.fillStyle1, -1, this.inverted, be1bRef.getVal())); - - newOtherEdges.add(new Edge(otherEdge.fillStyle0, otherEdge.fillStyle1, -1, otherEdge.inverted, be2aRef.getVal())); - newOtherEdges.add(new Edge(otherEdge.fillStyle0, otherEdge.fillStyle1, -1, otherEdge.inverted, be2bRef.getVal())); - - if (newThisEdges.get(0).isEmpty() || newOtherEdges.get(0).isEmpty()) { - newThisEdges.clear(); - newOtherEdges.clear(); - return false; - } - - return true; - } - - public Point2D pointAt(double t) { - return toBezierEdge().pointAt(t); - } - - public Edge invert() { - List newPoints = new ArrayList<>(); - for (int i = points.size() - 1; i >= 0; i--) { - newPoints.add(points.get(i)); - } - return new Edge(fillStyle1, fillStyle0, recordIndex, !inverted, newPoints); - } - - public Edge(int fillStyle0, int fillStyle1, int recordIndex, boolean inverted, BezierEdge be) { - List points2D = be.points; - List points = new ArrayList<>(); - for (Point2D p : points2D) { - points.add(new Point2D.Double(p.getX(), p.getY())); - } - this.points = points; - this.recordIndex = recordIndex; - this.inverted = inverted; - this.fillStyle0 = fillStyle0; - this.fillStyle1 = fillStyle1; - } - - public Edge(int fillStyle0, int fillStyle1, int recordIndex, boolean inverted, List points) { - this.points = points; - this.recordIndex = recordIndex; - this.inverted = inverted; - this.fillStyle0 = fillStyle0; - this.fillStyle1 = fillStyle1; - } - - public Edge(int fillStyle0, int fillStyle1, int recordIndex, boolean inverted, double fromX, double fromY, double toX, double toY) { - points.add(new Point2D.Double(fromX, fromY)); - points.add(new Point2D.Double(toX, toY)); - this.recordIndex = recordIndex; - this.inverted = inverted; - this.fillStyle0 = fillStyle0; - this.fillStyle1 = fillStyle1; - } - - public Edge(int fillStyle0, int fillStyle1, int recordIndex, boolean inverted, double fromX, double fromY, double controlX, double controlY, double toX, double toY) { - points.add(new Point2D.Double(fromX, fromY)); - points.add(new Point2D.Double(controlX, controlY)); - points.add(new Point2D.Double(toX, toY)); - this.recordIndex = recordIndex; - this.inverted = inverted; - this.fillStyle0 = fillStyle0; - this.fillStyle1 = fillStyle1; - - } - - @Override - public String toString() { - List list = new ArrayList<>(); - for (Point2D p : points) { - list.add("[" + p.getX() + "," + p.getY() + "]"); - } - return "{" + String.join("-", list) + "}"; - } - - public boolean isEmpty() { - return getFrom().equals(getTo()); - } - - public ShapeRecordAdvanced toShapeRecordAdvanced() { - if (points.size() == 3) { - CurvedEdgeRecordAdvanced cer = new CurvedEdgeRecordAdvanced(); - cer.controlDeltaX = points.get(1).getX() - points.get(0).getX(); - cer.controlDeltaY = points.get(1).getY() - points.get(0).getY(); - cer.anchorDeltaX = points.get(2).getX() - points.get(1).getX(); - cer.anchorDeltaY = points.get(2).getY() - points.get(1).getY(); - return cer; - } + private ShapeRecordAdvanced bezierToAdvancedRecord(BezierEdge be) { + if (be.points.size() == 2) { StraightEdgeRecordAdvanced ser = new StraightEdgeRecordAdvanced(); - ser.deltaX = points.get(1).getX() - points.get(0).getX(); - ser.deltaY = points.get(1).getY() - points.get(0).getY(); + ser.deltaX = be.points.get(1).getX() - be.points.get(0).getX(); + ser.deltaY = be.points.get(1).getY() - be.points.get(0).getY(); return ser; } + if (be.points.size() == 3) { + CurvedEdgeRecordAdvanced cer = new CurvedEdgeRecordAdvanced(); + cer.controlDeltaX = be.points.get(1).getX() - be.points.get(0).getX(); + cer.controlDeltaY = be.points.get(1).getY() - be.points.get(0).getY(); + cer.anchorDeltaX = be.points.get(2).getX() - be.points.get(1).getX(); + cer.anchorDeltaY = be.points.get(2).getY() - be.points.get(1).getY(); + return cer; + } + return null; } }