mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-25 07:25:34 +00:00
Shape fixer 2 WIP
This commit is contained in:
@@ -23,6 +23,7 @@ import java.io.Serializable;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
@@ -157,6 +158,15 @@ public class BezierEdge implements Serializable {
|
||||
points.set(points.size() - 1, p);
|
||||
calcParams();
|
||||
}
|
||||
|
||||
public void setControlPoint(Point2D p) {
|
||||
if(points.size() == 2) {
|
||||
points.add(1, p);
|
||||
} else {
|
||||
points.set(1, p);
|
||||
}
|
||||
calcParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the point at specified position.
|
||||
@@ -222,6 +232,10 @@ public class BezierEdge implements Serializable {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (points.size() == 3 && Double.isNaN(points.get(1).getX())) {
|
||||
System.err.println("xxx");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,9 +293,9 @@ public class BezierEdge implements Serializable {
|
||||
* @return List of intersections
|
||||
*/
|
||||
public List<Point2D> getIntersections(BezierEdge b2) {
|
||||
if (!Intersections.rectIntersection(bbox, b2.bbox)) {
|
||||
/*if (!Intersections.rectIntersection(bbox, b2.bbox)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}*/
|
||||
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);
|
||||
@@ -578,7 +592,7 @@ public class BezierEdge implements Serializable {
|
||||
));
|
||||
}
|
||||
calcParams();
|
||||
}
|
||||
}
|
||||
|
||||
public static final double ROUND_VALUE = 2;
|
||||
|
||||
@@ -591,7 +605,7 @@ public class BezierEdge implements Serializable {
|
||||
}
|
||||
calcParams();
|
||||
}
|
||||
|
||||
|
||||
public void roundN(double n) {
|
||||
for (int i = 0; i < this.points.size(); i++) {
|
||||
this.points.set(i, new Point2D.Double(
|
||||
@@ -832,4 +846,77 @@ public class BezierEdge implements Serializable {
|
||||
|
||||
return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public boolean isQuad() {
|
||||
return points.size() == 3;
|
||||
}
|
||||
|
||||
public Point2D midPoint() {
|
||||
if (!isQuad()) {
|
||||
return new Point2D.Double((points.get(0).getX() + points.get(1).getX()) * 0.5, (points.get(0).getY() + points.get(1).getY()) * 0.5);
|
||||
} else {
|
||||
double t = 0.5;
|
||||
double mt = 1 - t;
|
||||
double mx = mt * mt * points.get(0).getX() + 2 * mt * t * points.get(1).getX() + t * t * points.get(2).getX();
|
||||
double my = mt * mt * points.get(0).getY() + 2 * mt * t * points.get(1).getY() + t * t * points.get(2).getY();
|
||||
return new Point2D.Double(mx, my);
|
||||
}
|
||||
}
|
||||
|
||||
public Point2D unitNormal() {
|
||||
double dx;
|
||||
double dy;
|
||||
if (!isQuad()) {
|
||||
dx = points.get(1).getX() - points.get(0).getX();
|
||||
dy = points.get(1).getY() - points.get(0).getY();
|
||||
} else {
|
||||
// derivative at t=0.5
|
||||
double t = 0.5;
|
||||
double mt = 1 - t;
|
||||
dx = 2 * mt * (points.get(1).getX() - points.get(0).getX()) + 2 * t * (points.get(2).getX() - points.get(1).getX());
|
||||
dy = 2 * mt * (points.get(1).getY() - points.get(0).getY()) + 2 * t * (points.get(2).getY() - points.get(1).getY());
|
||||
}
|
||||
// left-hand normal = (-dy, dx)
|
||||
double nx = -dy, ny = dx;
|
||||
double len = Math.hypot(nx, ny);
|
||||
if (len == 0) {
|
||||
return new Point2D.Double(0, 0);
|
||||
}
|
||||
return new Point2D.Double(nx / len, ny / len);
|
||||
}
|
||||
|
||||
public BezierEdge toQuad() {
|
||||
if (!isQuad()) {
|
||||
return new BezierEdge(Arrays.asList(getBeginPoint(), midPoint() ,getEndPoint()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Point2D getControlPoint() {
|
||||
if (isQuad()) {
|
||||
return points.get(1);
|
||||
}
|
||||
return midPoint();
|
||||
}
|
||||
|
||||
public List<OverlapInterval> overlap(BezierEdge other) {
|
||||
BezierEdge q1 = this.toQuad();
|
||||
BezierEdge q2 = other.toQuad();
|
||||
return QuadOverlap.findQuadraticOverlaps(
|
||||
(Point2D.Double) q1.getBeginPoint(), (Point2D.Double) q1.getControlPoint(), (Point2D.Double) q1.getEndPoint(),
|
||||
(Point2D.Double) q2.getBeginPoint(), (Point2D.Double) q2.getControlPoint(), (Point2D.Double) q2.getEndPoint(), 10
|
||||
);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
BezierEdge be1 = new BezierEdge(1000,0,1000,1000);
|
||||
BezierEdge be2 = new BezierEdge(1000.25, -500, 1000.25, 2000);
|
||||
|
||||
List<OverlapInterval> intervals = be1.overlap(be2);
|
||||
for (OverlapInterval i : intervals) {
|
||||
System.err.println("" + be1.pointAt(i.t0)+" to " + be1.pointAt(i.t1)+" on BE1 AND "
|
||||
+ be2.pointAt(i.u0)+" to " + be2.pointAt(i.u1)+" on BE2"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +500,12 @@ public class Intersections {
|
||||
//No Intersection
|
||||
}
|
||||
} else if (ua_t == 0 || ub_t == 0) {
|
||||
|
||||
if (true) {
|
||||
//no overlapping
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!addCoincident) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.jpexs.decompiler.flash.math;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class OverlapInterval {
|
||||
public final double t0, t1; // on curve A
|
||||
public final double u0, u1; // on curve B
|
||||
public OverlapInterval(double t0, double t1, double u0, double u1) {
|
||||
// normalize so t0<=t1 and u0<=u1
|
||||
this.t0 = Math.min(t0, t1);
|
||||
this.t1 = Math.max(t0, t1);
|
||||
this.u0 = Math.min(u0, u1);
|
||||
this.u1 = Math.max(u0, u1);
|
||||
}
|
||||
@Override public String toString() {
|
||||
return String.format(Locale.ENGLISH, "A:[%.6f, %.6f], B:[%.6f, %.6f]", t0, t1, u0, u1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.jpexs.decompiler.flash.math;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class QuadOverlap {
|
||||
|
||||
/** Polyline with parameter values for each vertex. */
|
||||
private static class ParamPoly {
|
||||
final List<Point2D.Double> pts = new ArrayList<>();
|
||||
final List<Double> params = new ArrayList<>();
|
||||
}
|
||||
|
||||
/** Public entry: find overlapping sub-curve intervals within tolerance. */
|
||||
public static List<OverlapInterval> findQuadraticOverlaps(
|
||||
Point2D.Double A0, Point2D.Double A1, Point2D.Double A2,
|
||||
Point2D.Double B0, Point2D.Double B1, Point2D.Double B2,
|
||||
double tol) {
|
||||
|
||||
// Tolerances: tune as needed
|
||||
final double flatTol = tol * 0.5; // Hausdorff bound for flattening
|
||||
final double distTol = tol; // max allowed normal offset between near-collinear segments
|
||||
final double angleTol = 0.01; // radians; ~0.057° — treat as parallel if below
|
||||
|
||||
ParamPoly polyA = flattenQuad(A0, A1, A2, flatTol);
|
||||
ParamPoly polyB = flattenQuad(B0, B1, B2, flatTol);
|
||||
|
||||
List<OverlapInterval> raw = overlapPolylines(polyA, polyB, distTol, angleTol);
|
||||
return mergeIntervals(raw, 1e-6, 1e-6);
|
||||
}
|
||||
|
||||
// ---------- Step 1: Adaptive flattening with param tracking ----------
|
||||
|
||||
/** Flatten quadratic Bézier via recursive de Casteljau, recording t at each vertex. */
|
||||
private static ParamPoly flattenQuad(Point2D.Double P0, Point2D.Double P1, Point2D.Double P2, double flatTol) {
|
||||
ParamPoly out = new ParamPoly();
|
||||
// Start with first vertex
|
||||
out.pts.add(new Point2D.Double(P0.x, P0.y));
|
||||
out.params.add(0.0);
|
||||
// Recursive subdivision stack
|
||||
subdivideQuad(P0, P1, P2, 0.0, 1.0, flatTol, out);
|
||||
// Ensure last vertex
|
||||
if (out.params.get(out.params.size() - 1) < 1.0 - 1e-15) {
|
||||
out.pts.add(new Point2D.Double(P2.x, P2.y));
|
||||
out.params.add(1.0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Subdivide until control polygon is flat enough. */
|
||||
private static void subdivideQuad(Point2D.Double P0, Point2D.Double P1, Point2D.Double P2,
|
||||
double t0, double t1, double flatTol, ParamPoly out) {
|
||||
if (quadFlatEnough(P0, P1, P2, flatTol)) {
|
||||
// Append end point and parameter
|
||||
out.pts.add(new Point2D.Double(P2.x, P2.y));
|
||||
out.params.add(t1);
|
||||
return;
|
||||
}
|
||||
// de Casteljau split at t=0.5
|
||||
SplitRes s = splitQuadHalf(P0, P1, P2);
|
||||
double tm = (t0 + t1) * 0.5;
|
||||
subdivideQuad(P0, s.P01, s.P012, t0, tm, flatTol, out);
|
||||
subdivideQuad(s.P012, s.P12, P2, tm, t1, flatTol, out);
|
||||
}
|
||||
|
||||
/** Flatness test: distance of control point to baseline. */
|
||||
private static boolean quadFlatEnough(Point2D.Double P0, Point2D.Double P1, Point2D.Double P2, double tol) {
|
||||
double d = distPointToSegment(P1, P0, P2);
|
||||
return d <= tol;
|
||||
}
|
||||
|
||||
private static class SplitRes {
|
||||
Point2D.Double P01, P12, P012;
|
||||
}
|
||||
|
||||
/** Split quadratic at 0.5 via de Casteljau. */
|
||||
private static SplitRes splitQuadHalf(Point2D.Double P0, Point2D.Double P1, Point2D.Double P2) {
|
||||
Point2D.Double P01 = lerp(P0, P1, 0.5);
|
||||
Point2D.Double P12 = lerp(P1, P2, 0.5);
|
||||
Point2D.Double P012 = lerp(P01, P12, 0.5);
|
||||
SplitRes r = new SplitRes();
|
||||
r.P01 = P01; r.P12 = P12; r.P012 = P012;
|
||||
return r;
|
||||
}
|
||||
|
||||
private static Point2D.Double lerp(Point2D.Double a, Point2D.Double b, double t) {
|
||||
return new Point2D.Double(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
||||
}
|
||||
|
||||
// ---------- Step 2: Segment-segment near-coincidence with param mapping ----------
|
||||
|
||||
private static List<OverlapInterval> overlapPolylines(ParamPoly A, ParamPoly B, double distTol, double angleTol) {
|
||||
List<OverlapInterval> out = new ArrayList<>();
|
||||
for (int i = 0; i + 1 < A.pts.size(); i++) {
|
||||
Point2D.Double a0 = A.pts.get(i), a1 = A.pts.get(i + 1);
|
||||
double ta0 = A.params.get(i), ta1 = A.params.get(i + 1);
|
||||
|
||||
// Precompute direction and length for A
|
||||
double ax = a1.x - a0.x, ay = a1.y - a0.y;
|
||||
double alen = Math.hypot(ax, ay);
|
||||
if (alen == 0) continue;
|
||||
double ux = ax / alen, uy = ay / alen; // unit dir
|
||||
|
||||
for (int j = 0; j + 1 < B.pts.size(); j++) {
|
||||
Point2D.Double b0 = B.pts.get(j), b1 = B.pts.get(j + 1);
|
||||
double ub0 = B.params.get(j), ub1 = B.params.get(j + 1);
|
||||
|
||||
double bx = b1.x - b0.x, by = b1.y - b0.y;
|
||||
double blen = Math.hypot(bx, by);
|
||||
if (blen == 0) continue;
|
||||
double vx = bx / blen, vy = by / blen;
|
||||
|
||||
// Angle test: |sin(theta)| = |ux*vy - uy*vx|
|
||||
double sinTh = Math.abs(ux * vy - uy * vx);
|
||||
if (sinTh > angleTol) continue;
|
||||
|
||||
// Normal offset (distance between supporting lines) test
|
||||
// Compute signed distance of b0 to line A
|
||||
double nx = -uy, ny = ux; // left normal of A
|
||||
double off = ((b0.x - a0.x) * nx + (b0.y - a0.y) * ny);
|
||||
if (Math.abs(off) > distTol) continue;
|
||||
|
||||
// Project endpoints onto A direction to get 1D intervals
|
||||
double a0s = 0.0, a1s = alen;
|
||||
double b0s = (b0.x - a0.x) * ux + (b0.y - a0.y) * uy;
|
||||
double b1s = (b1.x - a0.x) * ux + (b1.y - a0.y) * uy;
|
||||
|
||||
// Normalize B's interval direction (ensure b0s <= b1s)
|
||||
double bbMin = Math.min(b0s, b1s);
|
||||
double bbMax = Math.max(b0s, b1s);
|
||||
|
||||
double s0 = Math.max(a0s, bbMin);
|
||||
double s1 = Math.min(a1s, bbMax);
|
||||
if (s1 <= s0) continue; // no overlap along the axis
|
||||
|
||||
// Convert 1D s back to points on A-line, then to params on A/B segments
|
||||
// Fraction along A segment:
|
||||
double fa0 = clamp01((s0 - a0s) / (a1s - a0s));
|
||||
double fa1 = clamp01((s1 - a0s) / (a1s - a0s));
|
||||
|
||||
// For B: we need to know which endpoint was smaller
|
||||
boolean bIncreasing = b0s <= b1s;
|
||||
double fb0 = (s0 - (bIncreasing ? b0s : b1s)) / (Math.abs(b1s - b0s));
|
||||
double fb1 = (s1 - (bIncreasing ? b0s : b1s)) / (Math.abs(b1s - b0s));
|
||||
fb0 = clamp01(fb0); fb1 = clamp01(fb1);
|
||||
|
||||
// Map fractions to global t/u via linear interpolation of per-vertex params
|
||||
double tStart = lerp(ta0, ta1, fa0);
|
||||
double tEnd = lerp(ta0, ta1, fa1);
|
||||
double uStart = lerp(ub0, ub1, bIncreasing ? fb0 : (1.0 - fb0));
|
||||
double uEnd = lerp(ub0, ub1, bIncreasing ? fb1 : (1.0 - fb1));
|
||||
|
||||
out.add(new OverlapInterval(tStart, tEnd, uStart, uEnd));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double clamp01(double x) {
|
||||
if (x < 0) return 0;
|
||||
if (x > 1) return 1;
|
||||
return x;
|
||||
}
|
||||
|
||||
private static double lerp(double a, double b, double t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
// ---------- Step 3: Merge touching intervals ----------
|
||||
|
||||
/** Merge intervals that touch within tolerances. */
|
||||
private static List<OverlapInterval> mergeIntervals(List<OverlapInterval> in, double tTol, double uTol) {
|
||||
if (in.isEmpty()) return in;
|
||||
// Sort by t0 then u0
|
||||
Collections.sort(in, (p, q) -> {
|
||||
int c = Double.compare(p.t0, q.t0);
|
||||
if (c != 0) return c;
|
||||
return Double.compare(p.u0, q.u0);
|
||||
});
|
||||
List<OverlapInterval> out = new ArrayList<>();
|
||||
OverlapInterval cur = in.get(0);
|
||||
for (int i = 1; i < in.size(); i++) {
|
||||
OverlapInterval nx = in.get(i);
|
||||
if (Math.abs(nx.t0 - cur.t1) <= tTol && Math.abs(nx.u0 - cur.u1) <= uTol) {
|
||||
// Extend
|
||||
cur = new OverlapInterval(cur.t0, Math.max(cur.t1, nx.t1), cur.u0, Math.max(cur.u1, nx.u1));
|
||||
} else {
|
||||
out.add(cur);
|
||||
cur = nx;
|
||||
}
|
||||
}
|
||||
out.add(cur);
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------- Geometry helpers ----------
|
||||
|
||||
/** Distance of point C to segment AB. */
|
||||
private static double distPointToSegment(Point2D.Double C, Point2D.Double A, Point2D.Double B) {
|
||||
double vx = B.x - A.x, vy = B.y - A.y;
|
||||
double wx = C.x - A.x, wy = C.y - A.y;
|
||||
double vv = vx * vx + vy * vy;
|
||||
if (vv == 0) return Math.hypot(wx, wy);
|
||||
double t = (wx * vx + wy * vy) / vv;
|
||||
if (t <= 0) return Math.hypot(wx, wy);
|
||||
if (t >= 1) return Math.hypot(C.x - B.x, C.y - B.y);
|
||||
double px = A.x + t * vx, py = A.y + t * vy;
|
||||
return Math.hypot(C.x - px, C.y - py);
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,7 @@ import com.jpexs.decompiler.flash.types.sound.SoundFormat;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.CurvedEdgeRecordAdvanced;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.MorphShapeFixer;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeFixer;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeFixer2;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.ShapeRecordAdvanced;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.StraightEdgeRecordAdvanced;
|
||||
import com.jpexs.decompiler.flash.xfl.shapefixer.StyleChangeRecordAdvanced;
|
||||
@@ -409,10 +410,10 @@ public class XFLConverter {
|
||||
y = rec.changeY(y);
|
||||
}
|
||||
//hack for morphshapes. TODO: make this better
|
||||
if (close && (Double.compare(lastMoveToX, x) != 0 || Double.compare(lastMoveToY, y) != 0)) {
|
||||
/*if (close && (Double.compare(lastMoveToX, x) != 0 || Double.compare(lastMoveToY, y) != 0)) {
|
||||
StraightEdgeRecordAdvanced ser = new StraightEdgeRecordAdvanced(lastMoveToX - x, lastMoveToY - y);
|
||||
ret.append(convertShapeEdge(mat, ser, x, y));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private static String getScaleMode(ILINESTYLE lineStyle) {
|
||||
@@ -702,8 +703,8 @@ public class XFLConverter {
|
||||
|
||||
List<ShapeRecordAdvanced> shapeRecordsAdvanced;
|
||||
|
||||
ShapeFixer fixer = morphshape ? new MorphShapeFixer() : new ShapeFixer();
|
||||
Logger.getLogger(ShapeFixer.class.getName()).log(Level.FINE, "Fixing character {0}...", characterId);
|
||||
ShapeFixer2 fixer = new ShapeFixer2(); //morphshape ? new MorphShapeFixer() : new ShapeFixer();
|
||||
Logger.getLogger(ShapeFixer2.class.getName()).log(Level.FINE, "Fixing character {0}...", characterId);
|
||||
|
||||
if (small) {
|
||||
shapeRecords = Helper.deepCopy(shapeRecords);
|
||||
@@ -779,6 +780,27 @@ public class XFLConverter {
|
||||
int lastFillStyle1 = fillStyle1;
|
||||
int lastFillStyle0 = fillStyle0;
|
||||
int lastStrokeStyle = strokeStyle;
|
||||
|
||||
if (scr.stateFillStyle0) {
|
||||
int fillStyle0_new = scr.fillStyle0;
|
||||
/*if (morphshape) { //???
|
||||
fillStyle1 = fillStyle0_new;
|
||||
} else {*/
|
||||
fillStyle0 = fillStyle0_new;
|
||||
//}
|
||||
}
|
||||
if (scr.stateFillStyle1) {
|
||||
int fillStyle1_new = scr.fillStyle1;
|
||||
/*if (morphshape) {
|
||||
fillStyle0 = fillStyle1_new;
|
||||
} else {*/
|
||||
fillStyle1 = fillStyle1_new;
|
||||
//}
|
||||
}
|
||||
if (scr.stateLineStyle) {
|
||||
strokeStyle = scr.lineStyle;
|
||||
}
|
||||
|
||||
if (scr.stateNewStyles) {
|
||||
XFLXmlWriter fillsNewStr = new XFLXmlWriter();
|
||||
XFLXmlWriter strokesNewStr = new XFLXmlWriter();
|
||||
@@ -844,26 +866,7 @@ public class XFLConverter {
|
||||
currentLayer.writeCharactersRaw(fillsNewStr.toString());
|
||||
currentLayer.writeCharactersRaw(strokesNewStr.toString());
|
||||
currentLayer.writeStartElement("edges");
|
||||
}
|
||||
if (scr.stateFillStyle0) {
|
||||
int fillStyle0_new = scr.fillStyle0;
|
||||
if (morphshape) { //???
|
||||
fillStyle1 = fillStyle0_new;
|
||||
} else {
|
||||
fillStyle0 = fillStyle0_new;
|
||||
}
|
||||
}
|
||||
if (scr.stateFillStyle1) {
|
||||
int fillStyle1_new = scr.fillStyle1;
|
||||
if (morphshape) {
|
||||
fillStyle0 = fillStyle1_new;
|
||||
} else {
|
||||
fillStyle1 = fillStyle1_new;
|
||||
}
|
||||
}
|
||||
if (scr.stateLineStyle) {
|
||||
strokeStyle = scr.lineStyle;
|
||||
}
|
||||
}
|
||||
if (!edges.isEmpty()) {
|
||||
if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
|
||||
currentLayer.writeStartElement("Edge");
|
||||
@@ -888,7 +891,11 @@ public class XFLConverter {
|
||||
edges.clear();
|
||||
}
|
||||
}
|
||||
edges.add(edge);
|
||||
if (edge instanceof StyleChangeRecordAdvanced && !((StyleChangeRecordAdvanced) edge).stateMoveTo) {
|
||||
//ignore
|
||||
} else {
|
||||
edges.add(edge);
|
||||
}
|
||||
x = edge.changeX(x);
|
||||
y = edge.changeY(y);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.xfl.shapefixer;
|
||||
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class Layer {
|
||||
FILLSTYLEARRAY fillStyleArray = null;
|
||||
LINESTYLEARRAY lineStyleArray = null;
|
||||
List<Path> paths = new ArrayList<>();
|
||||
|
||||
public void round(boolean wasSmall) {
|
||||
for (Path p : paths) {
|
||||
p.round(wasSmall);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
package com.jpexs.decompiler.flash.xfl.shapefixer;
|
||||
|
||||
import com.jpexs.decompiler.flash.math.BezierEdge;
|
||||
import com.jpexs.decompiler.flash.math.Intersections;
|
||||
import com.jpexs.decompiler.flash.math.OverlapInterval;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class OverlappingEdgesSplitter {
|
||||
private static class BezierEdgeWrapper {
|
||||
|
||||
BezierEdge be;
|
||||
int layer;
|
||||
int pathIndex;
|
||||
int edgeIndex;
|
||||
|
||||
public BezierEdgeWrapper(BezierEdge be, int layer, int shapeIndex, int edgeIndex) {
|
||||
this.be = be;
|
||||
this.layer = layer;
|
||||
this.pathIndex = shapeIndex;
|
||||
this.edgeIndex = edgeIndex;
|
||||
}
|
||||
|
||||
double minX() {
|
||||
return bbox().getMinX();
|
||||
}
|
||||
|
||||
double maxX() {
|
||||
return bbox().getMaxX();
|
||||
}
|
||||
|
||||
double minY() {
|
||||
return bbox().getMinY();
|
||||
}
|
||||
|
||||
double maxY() {
|
||||
return bbox().getMaxY();
|
||||
}
|
||||
|
||||
Rectangle2D bbox() {
|
||||
return be.bbox();
|
||||
}
|
||||
}
|
||||
|
||||
static final class Event implements Comparable<Event> {
|
||||
|
||||
final Type type;
|
||||
final double x;
|
||||
final BezierEdgeWrapper e;
|
||||
|
||||
enum Type {
|
||||
START, END
|
||||
}
|
||||
|
||||
public Event(Type type, double x, BezierEdgeWrapper e) {
|
||||
this.type = type;
|
||||
this.x = x;
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Event o) {
|
||||
int cx = Double.compare(this.x, o.x);
|
||||
if (cx != 0) {
|
||||
return cx;
|
||||
}
|
||||
int ct = this.type.ordinal() - o.type.ordinal();
|
||||
if (ct != 0) {
|
||||
return ct;
|
||||
}
|
||||
int ce = System.identityHashCode(this.e) - System.identityHashCode(o.e);
|
||||
return ce;
|
||||
}
|
||||
}
|
||||
|
||||
static class BezierEdgePair {
|
||||
BezierEdgeWrapper be1;
|
||||
BezierEdgeWrapper be2;
|
||||
|
||||
public BezierEdgePair(BezierEdgeWrapper be1, BezierEdgeWrapper be2) {
|
||||
this.be1 = be1;
|
||||
this.be2 = be2;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Sweep {
|
||||
|
||||
Map<BezierEdgeWrapper, List<Double>> splitPoints = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPoints2D = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPointsControl = new LinkedHashMap<>();
|
||||
//Map<BezierEdgePair, List<OverlapInterval>> overlapIntervals = new LinkedHashMap<>();
|
||||
final java.util.Comparator<BezierEdgeWrapper> statusCmp = (e1, e2) -> {
|
||||
int cMinY = Double.compare(e1.minY(), e2.minY());
|
||||
if (cMinY != 0) {
|
||||
return cMinY;
|
||||
}
|
||||
return Integer.compare(System.identityHashCode(e1), System.identityHashCode(e2));
|
||||
};
|
||||
final java.util.TreeSet<BezierEdgeWrapper> status = new TreeSet<>(statusCmp);
|
||||
//final java.util.Set<BezierEdgeWrapper> status = new HashSet<>();
|
||||
final java.util.PriorityQueue<Event> pq = new java.util.PriorityQueue<>();
|
||||
|
||||
// eps values for numeric robustness
|
||||
static final double EPS = 1e-9;
|
||||
|
||||
void addEdge(BezierEdgeWrapper e) {
|
||||
pq.add(new Event(Event.Type.START, e.minX(), e));
|
||||
pq.add(new Event(Event.Type.END, e.maxX(), e));
|
||||
}
|
||||
|
||||
void run() {
|
||||
//int total = pq.size();
|
||||
//int cnt = 0;
|
||||
while (!pq.isEmpty()) {
|
||||
/*if (cnt % 1000 == 0) {
|
||||
System.err.println("Percent done: " + (Math.round((cnt * 100.0 / total) * 100.0) / 100.0));
|
||||
}
|
||||
cnt++;*/
|
||||
Event ev = pq.poll();
|
||||
|
||||
switch (ev.type) {
|
||||
case START:
|
||||
BezierEdgeWrapper beMaxY = new BezierEdgeWrapper(null, 0, 0, 0) {
|
||||
@Override
|
||||
double minY() {
|
||||
return ev.e.maxY();
|
||||
}
|
||||
};
|
||||
|
||||
for (BezierEdgeWrapper e2 : status.headSet(beMaxY, true)) {
|
||||
/*if (e2.minY() > maxY) {
|
||||
break;
|
||||
}*/
|
||||
checkPair(ev.e, e2);
|
||||
}
|
||||
status.add(ev.e);
|
||||
break;
|
||||
case END:
|
||||
status.remove(ev.e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPair(BezierEdgeWrapper e1, BezierEdgeWrapper e2) {
|
||||
if (e1 == null || e2 == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Double> t1Ref = new ArrayList<>();
|
||||
List<Double> t2Ref = new ArrayList<>();
|
||||
List<Point2D> intPoint = new ArrayList<>();
|
||||
|
||||
if (!Intersections.rectIntersection(e1.bbox(), e2.bbox())) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasIntersections = e1.be.intersects(e2.be, t1Ref, t2Ref, intPoint);
|
||||
List<OverlapInterval> overlapIntervals = e1.be.overlap(e2.be);
|
||||
|
||||
if (!hasIntersections && overlapIntervals.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!splitPoints.containsKey(e1)) {
|
||||
splitPoints.put(e1, new ArrayList<>());
|
||||
}
|
||||
if (!splitPoints.containsKey(e2)) {
|
||||
splitPoints.put(e2, new ArrayList<>());
|
||||
}
|
||||
|
||||
if (!splitPoints2D.containsKey(e1)) {
|
||||
splitPoints2D.put(e1, new ArrayList<>());
|
||||
}
|
||||
if (!splitPoints2D.containsKey(e2)) {
|
||||
splitPoints2D.put(e2, new ArrayList<>());
|
||||
}
|
||||
if (!splitPointsControl.containsKey(e1)) {
|
||||
splitPointsControl.put(e1, new ArrayList<>());
|
||||
}
|
||||
if (!splitPointsControl.containsKey(e2)) {
|
||||
splitPointsControl.put(e2, new ArrayList<>());
|
||||
}
|
||||
splitPoints.get(e1).addAll(t1Ref);
|
||||
splitPoints.get(e2).addAll(t2Ref);
|
||||
splitPoints2D.get(e1).addAll(intPoint);
|
||||
splitPoints2D.get(e2).addAll(intPoint);
|
||||
for (int i = 0; i < intPoint.size(); i++) {
|
||||
splitPointsControl.get(e1).add(null);
|
||||
splitPointsControl.get(e2).add(null);
|
||||
}
|
||||
|
||||
|
||||
if (!overlapIntervals.isEmpty()) {
|
||||
for (OverlapInterval interval : overlapIntervals) {
|
||||
if (interval.t0 == interval.t1) {
|
||||
continue;
|
||||
}
|
||||
BezierEdge middle;
|
||||
if (interval.t0 == 1.0) {
|
||||
if (interval.t1 == 0.0) {
|
||||
middle = e1.be;
|
||||
} else {
|
||||
middle = e1.be.split(Arrays.asList(interval.t1)).get(0);
|
||||
}
|
||||
} else {
|
||||
List<BezierEdge> splitted = e1.be.split(Arrays.asList(interval.t0, interval.t1));
|
||||
middle = splitted.get(1);
|
||||
}
|
||||
|
||||
splitPoints.get(e1).add(interval.t0);
|
||||
splitPoints.get(e1).add(interval.t1);
|
||||
splitPoints.get(e2).add(interval.u0);
|
||||
splitPoints.get(e2).add(interval.u1);
|
||||
|
||||
|
||||
System.err.println("Overlapping " + e1.be.toSvg()+" AND " + e2.be.toSvg()+" by " + middle.toSvg());
|
||||
|
||||
splitPoints2D.get(e1).add(middle.getBeginPoint());
|
||||
splitPoints2D.get(e1).add(middle.getEndPoint());
|
||||
splitPointsControl.get(e1).add(middle.getControlPoint());
|
||||
splitPointsControl.get(e1).add(middle.getControlPoint());
|
||||
|
||||
splitPoints2D.get(e2).add(middle.getBeginPoint());
|
||||
splitPoints2D.get(e2).add(middle.getEndPoint());
|
||||
splitPointsControl.get(e2).add(middle.getControlPoint());
|
||||
splitPointsControl.get(e2).add(middle.getControlPoint());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class TPoint {
|
||||
double t;
|
||||
Point2D point;
|
||||
Point2D controlPoint;
|
||||
|
||||
public TPoint(double t, Point2D point, Point2D controlPoint) {
|
||||
this.t = t;
|
||||
this.point = point;
|
||||
this.controlPoint = controlPoint;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void handleBewList(List<BezierEdgeWrapper> bewList, List<Layer> layers) {
|
||||
Map<Integer, List<BezierEdgeWrapper>> bewMap = bewList.stream()
|
||||
.collect(Collectors.groupingBy(b -> b.layer));
|
||||
|
||||
for (Map.Entry<Integer, List<BezierEdgeWrapper>> entry : bewMap.entrySet()) {
|
||||
|
||||
Set<BezierEdgeWrapper> bewsToIgnore = new LinkedHashSet<>();
|
||||
|
||||
Map<BezierEdge, BezierEdgeWrapper> existingEdges = new HashMap<>();
|
||||
|
||||
//eliminate duplicates
|
||||
for (BezierEdgeWrapper bew1 : entry.getValue()) {
|
||||
BezierEdge be = bew1.be;
|
||||
BezierEdge rev = bew1.be.reverse();
|
||||
|
||||
BezierEdgeWrapper prevBew = existingEdges.get(be);
|
||||
if (prevBew != null) {
|
||||
bewsToIgnore.add(prevBew);
|
||||
}
|
||||
existingEdges.put(be, bew1);
|
||||
|
||||
BezierEdgeWrapper prevRevBew = existingEdges.get(rev);
|
||||
if (prevRevBew != null) {
|
||||
bewsToIgnore.add(prevRevBew);
|
||||
}
|
||||
existingEdges.put(rev, bew1);
|
||||
}
|
||||
|
||||
//eliminate duplicates
|
||||
/*for (BezierEdgeWrapper bew1 : entry.getValue()) {
|
||||
for (BezierEdgeWrapper bew2 : entry.getValue()) {
|
||||
if (bew1 != bew2) {
|
||||
if (bew1.beOriginal.equals(bew2.beOriginal)
|
||||
|| bew1.beOriginal.equalsReverse(bew2.beOriginal)) {
|
||||
bewsToIgnore.add(bew1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
boolean useSweep = true;
|
||||
|
||||
Map<BezierEdgeWrapper, List<Double>> splitPointsMap = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPoints2DMap = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPointsControlMap = new LinkedHashMap<>();
|
||||
|
||||
if (useSweep) {
|
||||
Sweep sweep = new Sweep();
|
||||
for (BezierEdgeWrapper bew : entry.getValue()) {
|
||||
if (bewsToIgnore.contains(bew)) {
|
||||
continue;
|
||||
}
|
||||
sweep.addEdge(bew);
|
||||
}
|
||||
sweep.run();
|
||||
splitPointsMap = sweep.splitPoints;
|
||||
splitPoints2DMap = sweep.splitPoints2D;
|
||||
splitPointsControlMap = sweep.splitPointsControl;
|
||||
} else {
|
||||
|
||||
for (BezierEdgeWrapper bew1 : entry.getValue()) {
|
||||
for (BezierEdgeWrapper bew2 : entry.getValue()) {
|
||||
if (bew1 != bew2) {
|
||||
List<Double> t1Ref = new ArrayList<>();
|
||||
List<Double> t2Ref = new ArrayList<>();
|
||||
List<Point2D> intPoints = new ArrayList<>();
|
||||
if (bew1.be.intersects(bew2.be, t1Ref, t2Ref, intPoints)) {
|
||||
if (!splitPointsMap.containsKey(bew1)) {
|
||||
splitPointsMap.put(bew1, new ArrayList<>());
|
||||
}
|
||||
splitPointsMap.get(bew1).addAll(t1Ref);
|
||||
|
||||
if (!splitPointsMap.containsKey(bew2)) {
|
||||
splitPointsMap.put(bew2, new ArrayList<>());
|
||||
}
|
||||
splitPointsMap.get(bew2).addAll(t2Ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<BezierEdgeWrapper> splittedBewList = new ArrayList<>(splitPointsMap.keySet());
|
||||
|
||||
splittedBewList.sort((BezierEdgeWrapper o1, BezierEdgeWrapper o2) -> {
|
||||
int dShapeIndex = o1.pathIndex - o2.pathIndex;
|
||||
if (dShapeIndex != 0) {
|
||||
return dShapeIndex;
|
||||
}
|
||||
int dEIndex = o1.edgeIndex - o2.edgeIndex;
|
||||
if (dEIndex != 0) {
|
||||
return dEIndex;
|
||||
}
|
||||
return System.identityHashCode(o1) - System.identityHashCode(o2);
|
||||
});
|
||||
|
||||
for (int i = splittedBewList.size() - 1; i >= 0; i--) {
|
||||
BezierEdgeWrapper bew = splittedBewList.get(i);
|
||||
|
||||
List<Double> splitT = splitPointsMap.get(bew);
|
||||
List<Point2D> splitPoint = splitPoints2DMap.get(bew);
|
||||
List<Point2D> splitControls = splitPointsControlMap.get(bew);
|
||||
|
||||
List<TPoint> splits = new ArrayList<>();
|
||||
for (int j = 0; j < splitT.size(); j++) {
|
||||
splits.add(new TPoint(splitT.get(j), splitPoint.get(j), splitControls.get(j)));
|
||||
}
|
||||
|
||||
splits.sort((a, b) -> Double.compare(a.t, b.t));
|
||||
|
||||
BezierEdge be = bew.be;
|
||||
List<Double> realSplitT = new ArrayList<>();
|
||||
for (TPoint tp : splits) {
|
||||
if (tp.t == 0.0 || tp.t == 1.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
realSplitT.add(tp.t);
|
||||
}
|
||||
|
||||
if (realSplitT.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<BezierEdge> splitted = be.split(realSplitT);
|
||||
if (splits.get(0).t != 0.0) {
|
||||
splits.add(0, new TPoint(0.0, bew.be.getBeginPoint(), null));
|
||||
}
|
||||
if (splits.get(splits.size() - 1).t != 1.0) {
|
||||
splits.add(new TPoint(1.0, bew.be.getEndPoint(), null));
|
||||
}
|
||||
|
||||
int p = 0;
|
||||
for (int j = 0; j < splits.size(); j++) {
|
||||
if (splits.get(j).t == 0.0 || splits.get(j).t == 1.0) {
|
||||
continue;
|
||||
}
|
||||
splitted.get(p).setBeginPoint(splits.get(j - 1).point);
|
||||
splitted.get(p).setEndPoint(splits.get(j).point);
|
||||
if (splits.get(j - 1).controlPoint == splits.get(j).controlPoint && splits.get(j).controlPoint != null) {
|
||||
splitted.get(p).setControlPoint(splits.get(j).controlPoint);
|
||||
}
|
||||
p++;
|
||||
}
|
||||
layers.get(bew.layer).paths.get(bew.pathIndex).edges.remove(bew.edgeIndex);
|
||||
int pos = 0;
|
||||
for (BezierEdge bes : splitted) {
|
||||
layers.get(bew.layer).paths.get(bew.pathIndex).edges.add(bew.edgeIndex + pos, bes);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void splitOverlappingEdges(
|
||||
List<Layer> layers
|
||||
) {
|
||||
List<BezierEdgeWrapper> strokesBewList = new ArrayList<>();
|
||||
List<BezierEdgeWrapper> fillsBewList = new ArrayList<>();
|
||||
|
||||
for (int layer = 0; layer < layers.size(); layer++) {
|
||||
for (int p = 0; p < layers.get(layer).paths.size(); p++) {
|
||||
Path path = layers.get(layer).paths.get(p);
|
||||
for (int e = 0; e < path.edges.size(); e++) {
|
||||
BezierEdge be = path.edges.get(e);
|
||||
BezierEdgeWrapper bew = new BezierEdgeWrapper(be, layer, p, e);
|
||||
if (path.fillStyle0 == 0 && path.fillStyle1 == 0) {
|
||||
strokesBewList.add(bew);
|
||||
} else {
|
||||
fillsBewList.add(bew);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleBewList(strokesBewList, layers);
|
||||
handleBewList(fillsBewList, layers);
|
||||
|
||||
for (int layer = 0; layer < layers.size(); layer++) {
|
||||
for (int p = 0; p < layers.get(layer).paths.size(); p++) {
|
||||
Path path = layers.get(layer).paths.get(p);
|
||||
for (int e = 0; e < path.edges.size(); e++) {
|
||||
BezierEdge be1 = path.edges.get(e);
|
||||
be1.shrinkToLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.xfl.shapefixer;
|
||||
|
||||
import com.jpexs.decompiler.flash.math.BezierEdge;
|
||||
import com.jpexs.helpers.Reference;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class Path {
|
||||
|
||||
private static final double EPS = 1e-9;
|
||||
|
||||
public List<BezierEdge> edges = new ArrayList<>();
|
||||
public int fillStyle0 = 0;
|
||||
public int fillStyle1 = 0;
|
||||
public int lineStyle = 0;
|
||||
|
||||
public Area area = null;
|
||||
|
||||
public Double areaValue = null;
|
||||
public boolean counterClockWise = false;
|
||||
public Rectangle2D bbox = null;
|
||||
|
||||
public List<Path> children = new ArrayList<>();
|
||||
public Path parent = null;
|
||||
public boolean filled = false;
|
||||
|
||||
private void calculateOrientation() {
|
||||
Reference<PathArea.Orientation> orientationRef = new Reference<>(null);
|
||||
Reference<Double> areaRef = new Reference<>(0.0);
|
||||
PathArea.orientationSingleClosed(area, orientationRef, areaRef);
|
||||
areaValue = areaRef.getVal();
|
||||
this.counterClockWise = orientationRef.getVal() == PathArea.Orientation.COUNTER_CLOCKWISE;
|
||||
this.bbox = area.getBounds2D();
|
||||
}
|
||||
|
||||
public boolean contains(Path other) {
|
||||
if (other.area.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Area diff = new Area(other.area);
|
||||
diff.subtract(area);
|
||||
return diff.isEmpty();
|
||||
}
|
||||
|
||||
public boolean contains(Point2D point) {
|
||||
if (area == null) {
|
||||
toArea();
|
||||
}
|
||||
return area.contains(point);
|
||||
}
|
||||
|
||||
|
||||
public boolean contains(double x, double y) {
|
||||
if (area == null) {
|
||||
toArea();
|
||||
}
|
||||
return area.contains(x, y);
|
||||
}
|
||||
|
||||
public void round(boolean wasSmall) {
|
||||
for (int e = 0; e < edges.size(); e++) {
|
||||
BezierEdge be = edges.get(e);
|
||||
/*if (wasSmall) {
|
||||
be.roundN(2); //this value works best for #1011, why? Also for #2532 it is okay.
|
||||
} else {
|
||||
be.roundN(100); //this value works best for #2165, it's not multiplied by 20 like not small :-(
|
||||
}*/
|
||||
be.roundN(1);
|
||||
if (be.isEmpty()) {
|
||||
edges.remove(e);
|
||||
e--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void fromArea() {
|
||||
calculateOrientation();
|
||||
List<BezierEdge> newEdges = new ArrayList<>();
|
||||
PathIterator it = area.getPathIterator(null);
|
||||
double[] c = new double[6];
|
||||
|
||||
double startX = 0.0;
|
||||
double startY = 0.0; // subpath start (for closing)
|
||||
double prevX = 0.0;
|
||||
double prevY = 0.0; // previous "current point"
|
||||
|
||||
while (!it.isDone()) {
|
||||
int type = it.currentSegment(c);
|
||||
|
||||
switch (type) {
|
||||
case PathIterator.SEG_MOVETO: {
|
||||
// Start of a new subpath
|
||||
startX = prevX = c[0];
|
||||
startY = prevY = c[1];
|
||||
break;
|
||||
}
|
||||
case PathIterator.SEG_LINETO: {
|
||||
// Line from (prevX, prevY) to (x, y)
|
||||
double x = c[0], y = c[1];
|
||||
newEdges.add(new BezierEdge(prevX, prevY, x, y));
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
break;
|
||||
}
|
||||
case PathIterator.SEG_QUADTO: {
|
||||
// Quadratic from (prevX, prevY) with control (cx, cy) to (x, y)
|
||||
double cx = c[0], cy = c[1];
|
||||
double x = c[2], y = c[3];
|
||||
newEdges.add(new BezierEdge(prevX, prevY, cx, cy, x, y));
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
break;
|
||||
}
|
||||
case PathIterator.SEG_CUBICTO: {
|
||||
// Area may contain cubics if the original shape had them.
|
||||
// We only support lines and quadratics here.
|
||||
throw new IllegalArgumentException("Cubic Bezier segments (SEG_CUBICTO) are not supported.");
|
||||
}
|
||||
case PathIterator.SEG_CLOSE: {
|
||||
// Close current subpath: add a final edge back to start if not already there
|
||||
if (!almostEqual(prevX, startX) || !almostEqual(prevY, startY)) {
|
||||
newEdges.add(new BezierEdge(prevX, prevY, startX, startY));
|
||||
prevX = startX;
|
||||
prevY = startY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Should not happen for AWT paths
|
||||
throw new IllegalStateException("Unknown PathIterator segment type: " + type);
|
||||
}
|
||||
|
||||
it.next();
|
||||
}
|
||||
this.edges = newEdges;
|
||||
}
|
||||
|
||||
private boolean almostEqual(double a, double b) {
|
||||
return Math.abs(a - b) <= EPS;
|
||||
}
|
||||
|
||||
public void toArea() {
|
||||
GeneralPath gp = new GeneralPath();
|
||||
double x = Double.POSITIVE_INFINITY;
|
||||
double y = Double.POSITIVE_INFINITY;
|
||||
boolean empty = true;
|
||||
for (BezierEdge be : edges) {
|
||||
if (be.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (x != be.getBeginPoint().getX() || y != be.getBeginPoint().getY()) {
|
||||
gp.moveTo(be.getBeginPoint().getX(), be.getBeginPoint().getY());
|
||||
}
|
||||
if (be.isQuad()) {
|
||||
gp.quadTo(be.points.get(1).getX(), be.points.get(1).getY(), be.getEndPoint().getX(), be.getEndPoint().getY());
|
||||
} else {
|
||||
gp.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY());
|
||||
}
|
||||
x = be.getEndPoint().getX();
|
||||
y = be.getEndPoint().getY();
|
||||
empty = false;
|
||||
}
|
||||
try {
|
||||
this.area = empty ? new Area() : new Area(gp);
|
||||
} catch (InternalError ie) {
|
||||
System.err.println("INTERNAL error on PATH " + toString());
|
||||
this.area = new Area();
|
||||
}
|
||||
calculateOrientation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Point2D lastPoint = null;
|
||||
for (BezierEdge e : edges) {
|
||||
if (lastPoint == null || !lastPoint.equals(e.getBeginPoint())) {
|
||||
sb.append("M ").append(e.getBeginPoint().getX()).append(" ").append(e.getBeginPoint().getY()).append(" ");
|
||||
}
|
||||
if (e.isQuad()) {
|
||||
sb.append("Q ").append(e.points.get(1).getX()).append(" ").append(e.points.get(1).getY()).append(" ");
|
||||
} else {
|
||||
sb.append("L ");
|
||||
}
|
||||
sb.append(e.getEndPoint().getX()).append(" ").append(e.getEndPoint().getY()).append(" ");
|
||||
lastPoint = e.getEndPoint();
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
}
|
||||
@@ -870,7 +870,7 @@ public class ShapeFixer {
|
||||
|
||||
if (Configuration.flaExportFixShapes.get()) {
|
||||
SwitchedFillSidesFixer switchedFillSidesFixer = new SwitchedFillSidesFixer();
|
||||
switchedFillSidesFixer.fixSwitchedFills(shapeNum, records, baseFillStyles, baseLineStyles, shapes, fillStyles0, fillStyles1, layers);
|
||||
switchedFillSidesFixer.fixSwitchedFills(shapeNum, records, baseFillStyles, baseLineStyles, shapes, fillStyles0, fillStyles1, lineStyles, layers);
|
||||
detectOverlappingEdges(shapes, fillStyles0, fillStyles1, lineStyles, layers, wasSmall);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,967 @@
|
||||
package com.jpexs.decompiler.flash.xfl.shapefixer;
|
||||
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.configuration.Configuration;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.CurvedEdge;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.IEdge;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.ShapeExporterBase;
|
||||
import com.jpexs.decompiler.flash.math.BezierEdge;
|
||||
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.GRADRECORD;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.RGB;
|
||||
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.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.Reference;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class ShapeFixer2 {
|
||||
|
||||
private List<Layer> splitToLayers(
|
||||
List<SHAPERECORD> records,
|
||||
FILLSTYLEARRAY baseFillStyles,
|
||||
LINESTYLEARRAY baseLineStyles
|
||||
) {
|
||||
List<Layer> result = new ArrayList<>();
|
||||
|
||||
Layer currentLayer = new Layer();
|
||||
currentLayer.fillStyleArray = baseFillStyles;
|
||||
currentLayer.lineStyleArray = baseLineStyles;
|
||||
|
||||
List<BezierEdge> currentEdges = new ArrayList<>();
|
||||
|
||||
int fillStyle0 = 0;
|
||||
int fillStyle1 = 0;
|
||||
int lineStyle = 0;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
for (SHAPERECORD rec : records) {
|
||||
assert (rec != null);
|
||||
if (rec instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr = (StyleChangeRecord) rec;
|
||||
if (scr.stateMoveTo
|
||||
|| scr.stateNewStyles
|
||||
|| scr.stateFillStyle0
|
||||
|| scr.stateFillStyle1
|
||||
|| scr.stateLineStyle) {
|
||||
if (!currentEdges.isEmpty()) {
|
||||
Path path = new Path();
|
||||
path.edges = currentEdges;
|
||||
path.fillStyle0 = fillStyle0;
|
||||
path.fillStyle1 = fillStyle1;
|
||||
path.lineStyle = lineStyle;
|
||||
currentLayer.paths.add(path);
|
||||
currentEdges = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
if (scr.stateNewStyles) {
|
||||
if (!currentLayer.paths.isEmpty()) {
|
||||
result.add(currentLayer);
|
||||
}
|
||||
currentLayer = new Layer();
|
||||
currentLayer.fillStyleArray = scr.fillStyles;
|
||||
currentLayer.lineStyleArray = scr.lineStyles;
|
||||
fillStyle0 = 0;
|
||||
fillStyle1 = 0;
|
||||
lineStyle = 0;
|
||||
}
|
||||
if (scr.stateFillStyle0) {
|
||||
fillStyle0 = scr.fillStyle0;
|
||||
}
|
||||
if (scr.stateFillStyle1) {
|
||||
fillStyle1 = scr.fillStyle1;
|
||||
}
|
||||
if (scr.stateLineStyle) {
|
||||
lineStyle = scr.lineStyle;
|
||||
}
|
||||
}
|
||||
if (rec instanceof StraightEdgeRecord) {
|
||||
int x2 = rec.changeX(x);
|
||||
int y2 = rec.changeY(y);
|
||||
BezierEdge be = new BezierEdge(x, y, x2, y2);
|
||||
currentEdges.add(be);
|
||||
}
|
||||
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);
|
||||
currentEdges.add(be);
|
||||
}
|
||||
if (rec instanceof EndShapeRecord) {
|
||||
if (!currentEdges.isEmpty()) {
|
||||
Path path = new Path();
|
||||
path.edges = currentEdges;
|
||||
path.fillStyle0 = fillStyle0;
|
||||
path.fillStyle1 = fillStyle1;
|
||||
path.lineStyle = lineStyle;
|
||||
currentLayer.paths.add(path);
|
||||
result.add(currentLayer);
|
||||
currentEdges = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
x = rec.changeX(x);
|
||||
y = rec.changeY(y);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ShapeRecordAdvanced> combineLayers(List<Layer> layers, FILLSTYLEARRAY baseFillStyles, LINESTYLEARRAY baseLineStyles) {
|
||||
List<ShapeRecordAdvanced> ret = new ArrayList<>();
|
||||
double dx;
|
||||
double dy;
|
||||
for (int i = 0; i < layers.size(); i++) {
|
||||
Layer layer = layers.get(i);
|
||||
if (layer.paths.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (layer.fillStyleArray != baseFillStyles && layer.lineStyleArray != baseLineStyles) {
|
||||
StyleChangeRecordAdvanced scr = new StyleChangeRecordAdvanced();
|
||||
scr.stateNewStyles = true;
|
||||
scr.fillStyles = layer.fillStyleArray;
|
||||
scr.lineStyles = layer.lineStyleArray;
|
||||
scr.stateFillStyle0 = true;
|
||||
scr.fillStyle0 = 0;
|
||||
scr.stateFillStyle1 = true;
|
||||
scr.fillStyle1 = 0;
|
||||
scr.stateLineStyle = true;
|
||||
scr.lineStyle = 0;
|
||||
ret.add(scr);
|
||||
}
|
||||
for (Path path : layer.paths) {
|
||||
if (path.edges.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
StyleChangeRecordAdvanced scr = new StyleChangeRecordAdvanced();
|
||||
scr.stateMoveTo = true;
|
||||
dx = scr.moveDeltaX = path.edges.get(0).points.get(0).getX();
|
||||
dy = scr.moveDeltaY = path.edges.get(0).points.get(0).getY();
|
||||
|
||||
scr.stateFillStyle0 = true;
|
||||
scr.fillStyle0 = path.fillStyle0;
|
||||
scr.stateFillStyle1 = true;
|
||||
scr.fillStyle1 = path.fillStyle1;
|
||||
scr.stateLineStyle = true;
|
||||
scr.lineStyle = path.lineStyle;
|
||||
ret.add(scr);
|
||||
for (BezierEdge be : path.edges) {
|
||||
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;
|
||||
}
|
||||
|
||||
private ShapeRecordAdvanced bezierToAdvancedRecord(BezierEdge be) {
|
||||
if (be.points.size() == 2) {
|
||||
StraightEdgeRecordAdvanced ser = new StraightEdgeRecordAdvanced();
|
||||
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;
|
||||
}
|
||||
|
||||
public List<ShapeRecordAdvanced> fix(
|
||||
List<SHAPERECORD> records,
|
||||
int shapeNum,
|
||||
FILLSTYLEARRAY baseFillStyles,
|
||||
LINESTYLEARRAY baseLineStyles,
|
||||
boolean wasSmall
|
||||
) {
|
||||
|
||||
List<Layer> layers = splitToLayers(records, baseFillStyles, baseLineStyles);
|
||||
|
||||
getSingleFillLayers(layers, records, baseFillStyles, baseLineStyles, shapeNum);
|
||||
|
||||
/*for (Layer layer : layers) {
|
||||
subtractAreas(layer);
|
||||
}
|
||||
|
||||
removeEmpty(layers);*/
|
||||
|
||||
OverlappingEdgesSplitter splitter = new OverlappingEdgesSplitter();
|
||||
splitter.splitOverlappingEdges(layers);
|
||||
|
||||
for (Layer layer : layers) {
|
||||
detectEdgeFills(layer);
|
||||
}
|
||||
|
||||
for (Layer layer : layers) {
|
||||
layer.round(wasSmall);
|
||||
}
|
||||
|
||||
removeEmpty(layers);
|
||||
|
||||
/*for (Layer layer : layers) {
|
||||
fixFillSides(layer);
|
||||
}*/
|
||||
|
||||
System.err.println("=============");
|
||||
for (int i = 0; i < layers.size(); i++) {
|
||||
for (int j = 0; j < layers.get(i).paths.size(); j++) {
|
||||
Path p = layers.get(i).paths.get(j);
|
||||
System.err.println(p.toString());
|
||||
//System.err.println("FS0: " + p.fillStyle0);
|
||||
//System.err.println("FS1: " + p.fillStyle1);
|
||||
//System.err.println("LS: " + p.lineStyle);
|
||||
//System.err.println("----------");
|
||||
}
|
||||
}
|
||||
//System.exit(0);
|
||||
return combineLayers(layers, baseFillStyles, baseLineStyles);
|
||||
}
|
||||
|
||||
private void removeEmpty(List<Layer> layers) {
|
||||
for (int i = 0; i < layers.size(); i++) {
|
||||
for (int j = 0; j < layers.get(i).paths.size(); j++) {
|
||||
for (int e = 0; e < layers.get(i).paths.get(j).edges.size(); e++) {
|
||||
if (layers.get(i).paths.get(j).edges.get(e).isEmpty()) {
|
||||
layers.get(i).paths.get(j).edges.remove(e);
|
||||
e--;
|
||||
}
|
||||
}
|
||||
if (layers.get(i).paths.get(j).edges.isEmpty()) {
|
||||
layers.get(i).paths.remove(j);
|
||||
j--;
|
||||
if (layers.get(i).paths.isEmpty()) {
|
||||
layers.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detectEdgeFills(Layer layer) {
|
||||
|
||||
double epsBase = 1e-4;
|
||||
|
||||
List<Rectangle2D> bboxes = new ArrayList<>();
|
||||
for (Path path : layer.paths){
|
||||
path.toArea();
|
||||
bboxes.add(path.bbox);
|
||||
}
|
||||
Set<BezierEdge> allEdges = new HashSet<>();
|
||||
List<StyledEdge> styledEdges = new ArrayList<>();
|
||||
for (Path path : layer.paths) {
|
||||
for (BezierEdge be : path.edges) {
|
||||
if (allEdges.contains(be) || allEdges.contains(be.reverse())) {
|
||||
continue;
|
||||
}
|
||||
Point2D mid = be.midPoint();
|
||||
Point2D n = be.unitNormal();
|
||||
double len = Math.hypot(be.getEndPoint().getX() - be.getBeginPoint().getX(), be.getEndPoint().getY() - be.getBeginPoint().getY());
|
||||
double eps = Math.max(epsBase, 1e-4 * len);
|
||||
|
||||
double xL = mid.getX() - n.getX()*eps;
|
||||
double yL = mid.getY() - n.getY()*eps;
|
||||
double xR = mid.getX() + n.getX()*eps;
|
||||
double yR = mid.getY() + n.getY()*eps;
|
||||
|
||||
int leftPath = topmostPathAt(xL, yL, layer.paths, bboxes);
|
||||
int rightPath = topmostPathAt(xR, yR, layer.paths, bboxes);
|
||||
|
||||
int leftFill = leftPath == -1 ? 0 : layer.paths.get(leftPath).fillStyle0;
|
||||
int rightFill = rightPath == -1 ? 0 : layer.paths.get(rightPath).fillStyle0;
|
||||
|
||||
int leftLine = leftPath == -1 ? 0 : layer.paths.get(leftPath).lineStyle;
|
||||
int rightLine = rightPath == -1 ? 0 : layer.paths.get(rightPath).lineStyle;
|
||||
|
||||
int newLine = (leftLine == path.lineStyle || rightLine == path.lineStyle) ? path.lineStyle : 0;
|
||||
|
||||
if (leftFill == rightFill && newLine == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
allEdges.add(be);
|
||||
|
||||
StyledEdge styledEdge = new StyledEdge(be, leftFill, rightFill, newLine);
|
||||
styledEdges.add(styledEdge);
|
||||
}
|
||||
}
|
||||
layer.paths.clear();
|
||||
|
||||
int lastFs0 = -1;
|
||||
int lastFs1 = -1;
|
||||
int lastLs = -1;
|
||||
Path currentPath = null;
|
||||
for (StyledEdge edge : styledEdges) {
|
||||
if (edge.fillStyle0 != lastFs0 || edge.fillStyle1 != lastFs1 || edge.lineStyle != lastLs) {
|
||||
currentPath = new Path();
|
||||
currentPath.fillStyle0 = edge.fillStyle0;
|
||||
currentPath.fillStyle1 = edge.fillStyle1;
|
||||
currentPath.lineStyle = edge.lineStyle;
|
||||
layer.paths.add(currentPath);
|
||||
}
|
||||
|
||||
assert(currentPath != null);
|
||||
currentPath.edges.add(edge.edge);
|
||||
|
||||
lastFs0 = edge.fillStyle0;
|
||||
lastFs1 = edge.fillStyle1;
|
||||
lastLs = edge.lineStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private static int topmostPathAt(
|
||||
double x, double y,
|
||||
List<Path> paths,
|
||||
List<Rectangle2D> bboxes
|
||||
) {
|
||||
|
||||
for (int i = paths.size() - 1; i >= 0; i--) {
|
||||
Rectangle2D bb = bboxes.get(i);
|
||||
if (!bb.contains(x, y)) {
|
||||
continue;
|
||||
}
|
||||
// Use contains on the shape with its own winding rule
|
||||
if (paths.get(i).contains(x, y)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static class StyledEdge {
|
||||
BezierEdge edge;
|
||||
int fillStyle0 = 0;
|
||||
int fillStyle1 = 0;
|
||||
int lineStyle = 0;
|
||||
|
||||
public StyledEdge(BezierEdge edge) {
|
||||
this.edge = edge;
|
||||
}
|
||||
|
||||
public StyledEdge(BezierEdge edge, int fillStyle0, int fillStyle1, int lineStyle) {
|
||||
this.edge = edge;
|
||||
this.fillStyle0 = fillStyle0;
|
||||
this.fillStyle1 = fillStyle1;
|
||||
this.lineStyle = lineStyle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void fixFillSides(Layer layer) {
|
||||
List<Path> paths = layer.paths;
|
||||
|
||||
buildContainment(paths);
|
||||
|
||||
for (Path poly : paths) {
|
||||
for (Path child : poly.children) {
|
||||
child.parent = poly;
|
||||
}
|
||||
}
|
||||
|
||||
for (Path path : paths) {
|
||||
int depth = 0;
|
||||
|
||||
Path parent = path.parent;
|
||||
while (parent != null) {
|
||||
parent = parent.parent;
|
||||
depth++;
|
||||
}
|
||||
path.filled = depth % 2 == 0;
|
||||
}
|
||||
|
||||
for (Path path: paths) {
|
||||
boolean clockwise = !path.counterClockWise;
|
||||
int fillStyle = path.fillStyle0;
|
||||
path.fillStyle0 = 0;
|
||||
if (path.filled == clockwise) {
|
||||
path.fillStyle1 = fillStyle;
|
||||
} else {
|
||||
path.fillStyle0 = fillStyle;
|
||||
}
|
||||
}
|
||||
|
||||
List<Path> newPaths = new ArrayList<>();
|
||||
Map<BezierEdge, List<Integer>> edge2Fs0 = new HashMap<>();
|
||||
Map<BezierEdge, List<Integer>> edge2Fs1 = new HashMap<>();
|
||||
|
||||
for (Path path : paths) {
|
||||
for (BezierEdge edge : path.edges) {
|
||||
if (path.fillStyle0 == 0 && path.fillStyle1 == 0) {
|
||||
continue;
|
||||
}
|
||||
BezierEdge edgeRev = edge.reverse();
|
||||
|
||||
|
||||
if (!edge2Fs0.containsKey(edge)) {
|
||||
edge2Fs0.put(edge, new ArrayList<>());
|
||||
}
|
||||
edge2Fs0.get(edge).add(path.fillStyle0);
|
||||
|
||||
if (!edge2Fs1.containsKey(edgeRev)) {
|
||||
edge2Fs1.put(edgeRev, new ArrayList<>());
|
||||
}
|
||||
edge2Fs1.get(edgeRev).add(path.fillStyle0);
|
||||
|
||||
if (!edge2Fs1.containsKey(edge)) {
|
||||
edge2Fs1.put(edge, new ArrayList<>());
|
||||
}
|
||||
edge2Fs1.get(edge).add(path.fillStyle1);
|
||||
|
||||
|
||||
if (!edge2Fs0.containsKey(edgeRev)) {
|
||||
edge2Fs0.put(edgeRev, new ArrayList<>());
|
||||
}
|
||||
edge2Fs0.get(edgeRev).add(path.fillStyle1);
|
||||
}
|
||||
}
|
||||
|
||||
Set<BezierEdge> existingEdges = new LinkedHashSet<>();
|
||||
|
||||
for (int p = paths.size() - 1; p >= 0; p--) {
|
||||
Path path = paths.get(p);
|
||||
int lastFs0 = -1;
|
||||
int lastFs1 = -1;
|
||||
for (int e = path.edges.size() - 1; e >= 0; e--) {
|
||||
BezierEdge edge = path.edges.get(e);
|
||||
|
||||
List<Integer> fs0List = edge2Fs0.get(edge);
|
||||
List<Integer> fs1List = edge2Fs1.get(edge);
|
||||
|
||||
if (p == 1 && e == 34) {
|
||||
System.err.println("yyy");
|
||||
}
|
||||
|
||||
for (int i = 0; i < fs0List.size(); i++) {
|
||||
Integer fs = fs0List.get(i);
|
||||
if (fs1List.contains(fs)) {
|
||||
fs0List.remove(i);
|
||||
fs1List.remove(fs);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int fs0 = 0;
|
||||
if (!fs0List.isEmpty()) {
|
||||
fs0 = fs0List.get(fs0List.size() - 1);
|
||||
}
|
||||
int fs1 = 0;
|
||||
if (!fs1List.isEmpty()) {
|
||||
fs1 = fs1List.get(fs1List.size() - 1);
|
||||
}
|
||||
|
||||
if (fs0 == 0 && fs1 == 0 && path.lineStyle == 0) {
|
||||
System.err.println("no fill or linestyle - " + edge.toSvg() + " original fs = " + path.fillStyle0+", ls = " + path.lineStyle);
|
||||
path.edges.remove(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
BezierEdge edgeRev = edge.reverse();
|
||||
|
||||
if (existingEdges.contains(edge) || existingEdges.contains(edgeRev)) {
|
||||
path.edges.remove(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
existingEdges.add(edge);
|
||||
existingEdges.add(edgeRev);
|
||||
|
||||
if (lastFs0 > -1 && lastFs1 > -1 && fs0 != lastFs0 || fs1 != lastFs1) {
|
||||
Path newPath = new Path();
|
||||
newPath.edges = new ArrayList<>();
|
||||
newPath.fillStyle0 = lastFs0;
|
||||
newPath.fillStyle1 = lastFs1;
|
||||
newPath.lineStyle = path.lineStyle;
|
||||
for (int n = e + 1; e + 1 < path.edges.size(); n++) {
|
||||
newPath.edges.add(path.edges.remove(e + 1));
|
||||
}
|
||||
if (!newPath.edges.isEmpty()) {
|
||||
paths.add(p + 1, newPath);
|
||||
}
|
||||
}
|
||||
|
||||
lastFs0 = fs0;
|
||||
lastFs1 = fs1;
|
||||
}
|
||||
if (lastFs0 > -1 && lastFs1 > -1) {
|
||||
path.fillStyle0 = lastFs0;
|
||||
path.fillStyle1 = lastFs1;
|
||||
}
|
||||
if (path.edges.isEmpty()) {
|
||||
paths.remove(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void subtractAreas(Layer layer) {
|
||||
for (Path path : layer.paths) {
|
||||
path.toArea();
|
||||
}
|
||||
|
||||
for (int p1 = 0; p1 < layer.paths.size(); p1++) {
|
||||
System.err.println("FROM " + layer.paths.get(p1));
|
||||
for (int p2 = p1 + 1; p2 < layer.paths.size(); p2++) {
|
||||
System.err.println("Subtract " + layer.paths.get(p2));
|
||||
layer.paths.get(p1).area.subtract(layer.paths.get(p2).area);
|
||||
}
|
||||
layer.paths.get(p1).fromArea();
|
||||
System.err.println("Result: " + layer.paths.get(p1));
|
||||
System.err.println("");
|
||||
}
|
||||
}
|
||||
|
||||
private void getSingleFillLayers(List<Layer> layers, List<SHAPERECORD> records, FILLSTYLEARRAY baseFillStyles, LINESTYLEARRAY baseLineStyles, int shapeNum) {
|
||||
SHAPEWITHSTYLE shp = new SHAPEWITHSTYLE();
|
||||
shp.shapeRecords = records;
|
||||
shp.fillStyles = baseFillStyles;
|
||||
shp.lineStyles = baseLineStyles;
|
||||
|
||||
List<List<IEdge>> fillList = new ArrayList<>();
|
||||
|
||||
SWF swf = new SWF();
|
||||
new ShapeExporterBase(ShapeTag.WIND_EVEN_ODD, shapeNum, swf, shp, null) {
|
||||
@Override
|
||||
protected void handleFillPaths(List<List<IEdge>> fillPaths) {
|
||||
fillList.addAll(fillPaths);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginShape() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endShape() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginFills() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endFills() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginLines() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endLines(boolean close) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginFill(RGB color) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endFill() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, float miterLimit, boolean noClose) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineBitmapStyle(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(double x, double y) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lineTo(double x, double y) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void curveTo(double controlX, double controlY, double anchorX, double anchorY) {
|
||||
}
|
||||
};
|
||||
|
||||
Map<Integer, Integer> globalToLocalFillStyleMap = new LinkedHashMap<>();
|
||||
Map<Integer, Integer> globalToLocalLineStyleMap = new LinkedHashMap<>();
|
||||
int lastFs = 0;
|
||||
int lastLs = 0;
|
||||
globalToLocalFillStyleMap.put(0, 0);
|
||||
globalToLocalLineStyleMap.put(0, 0);
|
||||
for (int i = 0; i < baseFillStyles.fillStyles.length; i++) {
|
||||
lastFs++;
|
||||
globalToLocalFillStyleMap.put(lastFs, lastFs);
|
||||
}
|
||||
for (int i = 0; i < (shapeNum == 4 ? baseLineStyles.lineStyles2.length : baseLineStyles.lineStyles.length); i++) {
|
||||
lastLs++;
|
||||
globalToLocalLineStyleMap.put(lastLs, lastLs);
|
||||
}
|
||||
for (SHAPERECORD rec : records) {
|
||||
if (rec instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr = (StyleChangeRecord) rec;
|
||||
if (scr.stateNewStyles) {
|
||||
for (int i = 0; i < scr.fillStyles.fillStyles.length; i++) {
|
||||
lastFs++;
|
||||
globalToLocalFillStyleMap.put(lastFs, i + 1);
|
||||
}
|
||||
for (int i = 0; i < (shapeNum == 4 ? scr.lineStyles.lineStyles2.length : scr.lineStyles.lineStyles.length); i++) {
|
||||
lastLs++;
|
||||
globalToLocalLineStyleMap.put(lastLs, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//assert(layers.size() == fillList.size());
|
||||
for (int i = 0; i < fillList.size(); i++) {
|
||||
if (fillList.get(i).isEmpty()) {
|
||||
fillList.remove(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (int layer = 0; layer < layers.size(); layer++) {
|
||||
Layer layerObj = layers.get(layer);
|
||||
layerObj.paths.clear();
|
||||
int fillStyleIdx = Integer.MAX_VALUE;
|
||||
int lineStyleIdx = Integer.MAX_VALUE;
|
||||
List<IEdge> currentList = new ArrayList<>();
|
||||
List<List<IEdge>> allLists = new ArrayList<>();
|
||||
List<Integer> listFills = new ArrayList<>();
|
||||
List<Integer> listLines = new ArrayList<>();
|
||||
int lastToX = Integer.MAX_VALUE;
|
||||
int lastToY = Integer.MAX_VALUE;
|
||||
int lastMoveToX = Integer.MAX_VALUE;
|
||||
int lastMoveToY = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < fillList.get(layer).size(); i++) {
|
||||
IEdge e = fillList.get(layer).get(i);
|
||||
if (fillStyleIdx != e.getFillStyleIdx()
|
||||
|| lineStyleIdx != e.getLineStyleIdx()
|
||||
|| (e.getFromX() != lastToX) || (e.getFromY() != lastToY)
|
||||
|| (e.getFromX() == lastMoveToX && e.getFromY() == lastMoveToY)) {
|
||||
if (fillStyleIdx != Integer.MAX_VALUE) {
|
||||
allLists.add(currentList);
|
||||
listFills.add(fillStyleIdx);
|
||||
listLines.add(lineStyleIdx);
|
||||
currentList = new ArrayList<>();
|
||||
}
|
||||
fillStyleIdx = e.getFillStyleIdx();
|
||||
lineStyleIdx = e.getLineStyleIdx();
|
||||
lastMoveToX = e.getFromX();
|
||||
lastMoveToY = e.getFromY();
|
||||
}
|
||||
currentList.add(e);
|
||||
lastToX = e.getToX();
|
||||
lastToY = e.getToY();
|
||||
}
|
||||
if (!currentList.isEmpty()) {
|
||||
allLists.add(currentList);
|
||||
listFills.add(fillStyleIdx);
|
||||
listLines.add(lineStyleIdx);
|
||||
}
|
||||
|
||||
/*List<ClosedPath> closedPaths = new ArrayList<>();
|
||||
for (int i = 0; i < allLists.size(); i++) {
|
||||
List<IEdge> list = allLists.get(i);
|
||||
closedPaths.add(new ClosedPath(list, listFills.get(i)));
|
||||
}
|
||||
|
||||
buildContainment(closedPaths);
|
||||
|
||||
for (ClosedPath poly : closedPaths) {
|
||||
for (ClosedPath child : poly.children) {
|
||||
child.parent = poly;
|
||||
}
|
||||
}
|
||||
|
||||
for (ClosedPath poly : closedPaths) {
|
||||
int depth = 0;
|
||||
|
||||
ClosedPath parent = poly.parent;
|
||||
while (parent != null) {
|
||||
parent = parent.parent;
|
||||
depth++;
|
||||
}
|
||||
|
||||
poly.filled = depth % 2 == 0;
|
||||
}
|
||||
|
||||
Map<BezierEdge, List<Integer>> beToFillStyle0List = new LinkedHashMap<>();
|
||||
Map<BezierEdge, List<Integer>> beToFillStyle1List = new LinkedHashMap<>();
|
||||
|
||||
Map<BezierEdge, Integer> beToFillStyle0 = new LinkedHashMap<>();
|
||||
Map<BezierEdge, Integer> beToFillStyle1 = new LinkedHashMap<>();
|
||||
|
||||
*/
|
||||
for (int i = 0; i < allLists.size(); i++) {
|
||||
List<IEdge> list = allLists.get(i);
|
||||
fillStyleIdx = listFills.get(i);
|
||||
lineStyleIdx = listLines.get(i);
|
||||
//boolean clockwise = !polygon.ccw;
|
||||
|
||||
Path path = new Path();
|
||||
layerObj.paths.add(path);
|
||||
|
||||
int localFs = globalToLocalFillStyleMap.get(fillStyleIdx);
|
||||
int localLs = globalToLocalLineStyleMap.get(lineStyleIdx);
|
||||
|
||||
/*if (polygon.filled == clockwise) {
|
||||
path.fillStyle1 = localFs;
|
||||
} else {
|
||||
path.fillStyle0 = localFs;
|
||||
}*/
|
||||
path.fillStyle0 = localFs;
|
||||
path.lineStyle = localLs;
|
||||
|
||||
for (IEdge e : list) {
|
||||
BezierEdge be = iEdgeToBezier(e);
|
||||
//BezierEdge beRev = be.reverse();
|
||||
path.edges.add(be);
|
||||
|
||||
//BezierEdge search = new BezierEdge(180.0, -3040.0, 480.0, -3400.0);
|
||||
//boolean print = false;
|
||||
|
||||
/*if (be.equals(search)) {
|
||||
System.err.println("xxx");
|
||||
System.err.println("" + polygon);
|
||||
print = true;
|
||||
}
|
||||
if (be.equals(search.reverse())) {
|
||||
System.err.println("yyy");
|
||||
print = true;
|
||||
}*/
|
||||
/*if (print) {
|
||||
System.err.println("localFS: " + localFs);
|
||||
System.err.println("filled: " + polygon.filled);
|
||||
System.err.println("clockwise: " + clockwise);
|
||||
}
|
||||
|
||||
if (polygon.filled == clockwise) {
|
||||
if (!beToFillStyle1List.containsKey(be)) {
|
||||
beToFillStyle1List.put(be, new ArrayList<>());
|
||||
}
|
||||
if (!beToFillStyle0List.containsKey(beRev)) {
|
||||
beToFillStyle0List.put(beRev, new ArrayList<>());
|
||||
}
|
||||
beToFillStyle1List.get(be).add(localFs);
|
||||
beToFillStyle0List.get(beRev).add(localFs);
|
||||
|
||||
if (print) {
|
||||
System.err.println("setting FS1 and rev FS0");
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!beToFillStyle0List.containsKey(be)) {
|
||||
beToFillStyle0List.put(be, new ArrayList<>());
|
||||
}
|
||||
if (!beToFillStyle1List.containsKey(beRev)) {
|
||||
beToFillStyle1List.put(beRev, new ArrayList<>());
|
||||
}
|
||||
|
||||
beToFillStyle0List.get(be).add(localFs);
|
||||
beToFillStyle1List.get(beRev).add(localFs);
|
||||
|
||||
if (print) {
|
||||
System.err.println("setting FS0 and rev FS1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (print) {
|
||||
System.err.println("");
|
||||
}*/
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private BezierEdge iEdgeToBezier(IEdge ie) {
|
||||
assert (ie != null);
|
||||
if (ie instanceof CurvedEdge) {
|
||||
CurvedEdge ce = (CurvedEdge) ie;
|
||||
return new BezierEdge(ce.getFromX(), ce.getFromY(),
|
||||
ce.getControlX(), ce.getControlY(),
|
||||
ce.getToX(), ce.getToY()
|
||||
);
|
||||
} else {
|
||||
return new BezierEdge(ie.getFromX(), ie.getFromY(), ie.getToX(), ie.getToY());
|
||||
}
|
||||
}
|
||||
|
||||
static class GridIndex {
|
||||
|
||||
// Simple uniform grid over bbox domain
|
||||
private final double cellSize;
|
||||
private final Map<Long, List<Path>> cells = new HashMap<>();
|
||||
private final double minX;
|
||||
private final double minY;
|
||||
|
||||
GridIndex(Collection<Path> polys, double cellSize) {
|
||||
this.cellSize = cellSize;
|
||||
// Compute global origin (minX/minY) to keep keys small
|
||||
double minx = Double.POSITIVE_INFINITY;
|
||||
double miny = Double.POSITIVE_INFINITY;
|
||||
for (Path w : polys) {
|
||||
Rectangle2D b = w.bbox;
|
||||
if (b.getMinX() < minx) {
|
||||
minx = b.getMinX();
|
||||
}
|
||||
if (b.getMinY() < miny) {
|
||||
miny = b.getMinY();
|
||||
}
|
||||
}
|
||||
this.minX = minx;
|
||||
this.minY = miny;
|
||||
|
||||
// Insert
|
||||
for (Path w : polys) {
|
||||
forEachCell(w.bbox, (gx, gy) -> {
|
||||
cells.computeIfAbsent(key(gx, gy), k -> new ArrayList<>()).add(w);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private long key(int gx, int gy) {
|
||||
// Pack two 32-bit ints into one long
|
||||
return ((long) gx << 32) ^ (gy & 0xffffffffL);
|
||||
}
|
||||
|
||||
private int gx(double x) {
|
||||
return (int) Math.floor((x - minX) / cellSize);
|
||||
}
|
||||
|
||||
private int gy(double y) {
|
||||
return (int) Math.floor((y - minY) / cellSize);
|
||||
}
|
||||
|
||||
private void forEachCell(Rectangle2D r, CellConsumer cc) {
|
||||
int x0 = gx(r.getMinX());
|
||||
int x1 = gx(r.getMaxX());
|
||||
int y0 = gy(r.getMinY());
|
||||
int y1 = gy(r.getMaxY());
|
||||
for (int x = x0; x <= x1; x++) {
|
||||
for (int y = y0; y <= y1; y++) {
|
||||
cc.accept(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Path> query(Rectangle2D r) {
|
||||
// Collect candidates from overlapping cells (deduplicated)
|
||||
HashSet<Path> set = new HashSet<>();
|
||||
forEachCell(r, (gx, gy) -> {
|
||||
List<Path> bucket = cells.get(key(gx, gy));
|
||||
if (bucket != null) {
|
||||
set.addAll(bucket);
|
||||
}
|
||||
});
|
||||
return new ArrayList<>(set);
|
||||
}
|
||||
}
|
||||
|
||||
interface CellConsumer {
|
||||
|
||||
void accept(int gx, int gy);
|
||||
}
|
||||
|
||||
private static void buildContainment(List<Path> polygons) {
|
||||
|
||||
Map<Integer, List<Path>> byStyle = polygons.stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(w -> w.fillStyle0));
|
||||
|
||||
for (Map.Entry<Integer, List<Path>> e : byStyle.entrySet()) {
|
||||
List<Path> group = e.getValue();
|
||||
if (e.getKey() == 0) { //no fill
|
||||
continue;
|
||||
}
|
||||
|
||||
double avgW = group.stream().mapToDouble(w -> w.bbox.getWidth()).average().orElse(1.0);
|
||||
double avgH = group.stream().mapToDouble(w -> w.bbox.getHeight()).average().orElse(1.0);
|
||||
double cellSize = Math.max(1.0, Math.max(avgW, avgH));
|
||||
|
||||
GridIndex index = new GridIndex(group, cellSize);
|
||||
|
||||
group.sort((a, b) -> Double.compare(b.areaValue, a.areaValue));
|
||||
|
||||
for (int i = group.size() - 1; i >= 0; i--) {
|
||||
Path inner = group.get(i);
|
||||
List<Path> candidates = index.query(inner.bbox);
|
||||
|
||||
Path bestParent = null;
|
||||
double bestArea = Double.POSITIVE_INFINITY;
|
||||
|
||||
for (Path outer : candidates) {
|
||||
if (outer == inner) {
|
||||
continue;
|
||||
}
|
||||
if (outer.areaValue <= inner.areaValue) {
|
||||
continue; // only larger can contain
|
||||
}
|
||||
if (!outer.bbox.contains(inner.bbox)) {
|
||||
continue; // cheap reject
|
||||
}
|
||||
if (outer.contains(inner)) {
|
||||
if (outer.areaValue < bestArea) {
|
||||
bestArea = outer.areaValue;
|
||||
bestParent = outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestParent != null) {
|
||||
bestParent.children.add(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -279,6 +280,80 @@ public class SwitchedFillSidesFixer {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExistingEdge {
|
||||
int fillStyle0;
|
||||
int fillStyle1;
|
||||
int lineStyle;
|
||||
|
||||
BezierEdge be;
|
||||
|
||||
public ExistingEdge(int fillStyle0, int fillStyle1, int lineStyle, BezierEdge be) {
|
||||
this.fillStyle0 = fillStyle0;
|
||||
this.fillStyle1 = fillStyle1;
|
||||
this.lineStyle = lineStyle;
|
||||
this.be = be;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = lineStyle == 0 ? 0 : 1;
|
||||
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 ExistingEdge other = (ExistingEdge) obj;
|
||||
|
||||
if ((this.fillStyle0 == 0 && this.fillStyle1 == 0)
|
||||
!= (other.fillStyle0 == 0 && other.fillStyle1 == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.be.equals(other.be)) {
|
||||
/*if (this.fillStyle0 != other.fillStyle0) {
|
||||
return false;
|
||||
}
|
||||
if (this.fillStyle1 != other.fillStyle1) {
|
||||
return false;
|
||||
}
|
||||
if (this.lineStyle != other.lineStyle) {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
return true;
|
||||
}
|
||||
if (this.be.equalsReverse(other.be)) {
|
||||
/*if (this.fillStyle0 != other.fillStyle1) {
|
||||
return false;
|
||||
}
|
||||
if (this.fillStyle1 != other.fillStyle0) {
|
||||
return false;
|
||||
}
|
||||
if (this.lineStyle != other.lineStyle) {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void fixSidesInLayer(
|
||||
@@ -286,8 +361,10 @@ public class SwitchedFillSidesFixer {
|
||||
List<List<BezierEdge>> shapes,
|
||||
List<Integer> fillStyles0,
|
||||
List<Integer> fillStyles1,
|
||||
List<Integer> lineStyles,
|
||||
int layer,
|
||||
int startIndex, int endIndex,
|
||||
List<Integer> layers,
|
||||
int startIndex, Reference<Integer> endIndex,
|
||||
Map<Integer, Integer> globalToLocalFillStyleMap
|
||||
) {
|
||||
|
||||
@@ -427,6 +504,8 @@ public class SwitchedFillSidesFixer {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Set<ExistingEdge> existingEdges = new HashSet<>();
|
||||
|
||||
for (BezierEdge be : beToFillStyle0List.keySet()) {
|
||||
/*for (int i = beToFillStyle0List.get(be).size() - 1; i >= 0; i--) {
|
||||
@@ -461,20 +540,39 @@ public class SwitchedFillSidesFixer {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
for (int i = startIndex; i < endIndex.getVal(); i++) {
|
||||
List<BezierEdge> shape = shapes.get(i);
|
||||
|
||||
int lastFs0 = -1;
|
||||
int lastFs1 = -1;
|
||||
int lastLs = -1;
|
||||
List<Integer> newFillStyles0 = new ArrayList<>();
|
||||
List<Integer> newFillStyles1 = new ArrayList<>();
|
||||
List<Integer> newLineStyles = new ArrayList<>();
|
||||
List<List<BezierEdge>> newShapes = new ArrayList<>();
|
||||
List<BezierEdge> currentShape = new ArrayList<>();
|
||||
List<Integer> newLayers = new ArrayList<>();
|
||||
|
||||
Integer fs0before = fillStyles0.get(i);
|
||||
Integer fs1before = fillStyles1.get(i);
|
||||
int ls = lineStyles.get(i);
|
||||
|
||||
shapes.remove(i);
|
||||
fillStyles0.remove(i);
|
||||
fillStyles1.remove(i);
|
||||
lineStyles.remove(i);
|
||||
layers.remove(i);
|
||||
|
||||
for (int j = 0; j < shape.size(); j++) {
|
||||
BezierEdge be = shape.get(j);
|
||||
if (be.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer fs0before = fillStyles0.get(i);
|
||||
Integer fs1before = fillStyles1.get(i);
|
||||
|
||||
if (fs0before == 0 && fs1before == 0) { //only strokes
|
||||
}
|
||||
|
||||
|
||||
/*if (fs0before == 0 && fs1before == 0) { //only strokes
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
Integer fs0after = beToFillStyle0.get(be);
|
||||
Integer fs1after = beToFillStyle1.get(be);
|
||||
@@ -487,7 +585,11 @@ public class SwitchedFillSidesFixer {
|
||||
}
|
||||
|
||||
if (fs0after == -1 || fs1after == -1) {
|
||||
break;
|
||||
//??
|
||||
fs0after = fs0before;
|
||||
fs1after = fs1before;
|
||||
//break;
|
||||
Logger.getLogger(SwitchedFillSidesFixer.class.getName()).log(Level.FINE, "More than 2 fillstyles for {0} - old: {1}, {2} new: {3}, {4}", new Object[]{be, fs0before, fs1before, fs0after, fs1after});
|
||||
}
|
||||
|
||||
if (fs0after == 0 && Objects.equals(fs1after, fs1before)) {
|
||||
@@ -495,33 +597,75 @@ public class SwitchedFillSidesFixer {
|
||||
} else if (fs1after == 0 && Objects.equals(fs0after, fs0before)) {
|
||||
fs1after = fs1before;
|
||||
}
|
||||
|
||||
fillStyles0.set(i, fs0after);
|
||||
fillStyles1.set(i, fs1after);
|
||||
|
||||
ExistingEdge ee = new ExistingEdge(fs0after, fs1after, ls, be);
|
||||
if (existingEdges.contains(ee)) {
|
||||
Logger.getLogger(SwitchedFillSidesFixer.class.getName()).log(Level.FINE, "Duplicated edge {0} - old: {1}, {2} new: {3}, {4}", new Object[]{be, fs0before, fs1before, fs0after, fs1after});
|
||||
continue;
|
||||
}
|
||||
existingEdges.add(ee);
|
||||
|
||||
if (lastFs0 != fs0after || lastFs1 != fs1after || lastLs != ls) {
|
||||
if (!currentShape.isEmpty()) {
|
||||
newShapes.add(currentShape);
|
||||
currentShape = new ArrayList<>();
|
||||
newFillStyles0.add(lastFs0);
|
||||
newFillStyles1.add(lastFs1);
|
||||
newLineStyles.add(lastLs);
|
||||
newLayers.add(layer - 1);
|
||||
}
|
||||
lastFs0 = fs0after;
|
||||
lastFs1 = fs1after;
|
||||
lastLs = ls;
|
||||
}
|
||||
|
||||
currentShape.add(be);
|
||||
|
||||
/*fillStyles0.set(i, fs0after);
|
||||
fillStyles1.set(i, fs1after);*/
|
||||
|
||||
if (!Objects.equals(fs0before, fs0after) || !Objects.equals(fs1before, fs1after)) {
|
||||
Logger.getLogger(SwitchedFillSidesFixer.class.getName()).log(Level.FINE, "Changed edge {0} - old: {1}, {2} new: {3}, {4}", new Object[]{be, fs0before, fs1before, fs0after, fs1after});
|
||||
}
|
||||
break;
|
||||
//break;
|
||||
}
|
||||
if (!currentShape.isEmpty()) {
|
||||
newShapes.add(currentShape);
|
||||
newFillStyles0.add(lastFs0);
|
||||
newFillStyles1.add(lastFs1);
|
||||
newLineStyles.add(lastLs);
|
||||
newLayers.add(layer - 1);
|
||||
}
|
||||
|
||||
/*if (newShapes.size() > 1) {
|
||||
Logger.getLogger(SwitchedFillSidesFixer.class.getName()).log(Level.FINE, "Multi shape - size = {0}", new Object[]{newShapes.size()});
|
||||
}*/
|
||||
endIndex.setVal(endIndex.getVal() - 1 + newShapes.size());
|
||||
shapes.addAll(i, newShapes);
|
||||
fillStyles0.addAll(i, newFillStyles0);
|
||||
fillStyles1.addAll(i, newFillStyles1);
|
||||
lineStyles.addAll(i, newLineStyles);
|
||||
layers.addAll(i, newLayers);
|
||||
i += newShapes.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void fixSwitchedFills(
|
||||
int shapeNum,
|
||||
List<SHAPERECORD> records,
|
||||
FILLSTYLEARRAY fillStyles,
|
||||
LINESTYLEARRAY lineStyles,
|
||||
FILLSTYLEARRAY baseFillStyles,
|
||||
LINESTYLEARRAY baseLineStyles,
|
||||
List<List<BezierEdge>> shapes,
|
||||
List<Integer> fillStyles0,
|
||||
List<Integer> fillStyles1,
|
||||
List<Integer> lineStyles,
|
||||
List<Integer> layers
|
||||
) {
|
||||
|
||||
SHAPEWITHSTYLE shp = new SHAPEWITHSTYLE();
|
||||
shp.shapeRecords = records;
|
||||
shp.fillStyles = fillStyles;
|
||||
shp.lineStyles = lineStyles;
|
||||
shp.fillStyles = baseFillStyles;
|
||||
shp.lineStyles = baseLineStyles;
|
||||
|
||||
List<List<IEdge>> fillList = new ArrayList<>();
|
||||
|
||||
@@ -600,7 +744,7 @@ public class SwitchedFillSidesFixer {
|
||||
Map<Integer, Integer> globalToLocalFillStyleMap = new LinkedHashMap<>();
|
||||
int lastFs = 0;
|
||||
globalToLocalFillStyleMap.put(0, 0);
|
||||
for (int i = 0; i < fillStyles.fillStyles.length; i++) {
|
||||
for (int i = 0; i < baseFillStyles.fillStyles.length; i++) {
|
||||
lastFs++;
|
||||
globalToLocalFillStyleMap.put(lastFs, lastFs);
|
||||
}
|
||||
@@ -619,12 +763,15 @@ public class SwitchedFillSidesFixer {
|
||||
int from = 0;
|
||||
for (int i = 1; i < layers.size(); i++) {
|
||||
if (!layers.get(i).equals(layers.get(i - 1))) {
|
||||
fixSidesInLayer(fillList, shapes, fillStyles0, fillStyles1, layers.get(i - 1), from, i, globalToLocalFillStyleMap);
|
||||
Reference<Integer> endIndexRef = new Reference<>(i);
|
||||
fixSidesInLayer(fillList, shapes, fillStyles0, fillStyles1, lineStyles, layers.get(i - 1), layers, from, endIndexRef, globalToLocalFillStyleMap);
|
||||
i = endIndexRef.getVal();
|
||||
from = i;
|
||||
}
|
||||
}
|
||||
if (!layers.isEmpty()) {
|
||||
fixSidesInLayer(fillList, shapes, fillStyles0, fillStyles1, layers.get(layers.size() - 1), from, layers.size(), globalToLocalFillStyleMap);
|
||||
Reference<Integer> endIndexRef = new Reference<>(layers.size());
|
||||
fixSidesInLayer(fillList, shapes, fillStyles0, fillStyles1, lineStyles, layers.get(layers.size() - 1), layers, from, endIndexRef, globalToLocalFillStyleMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user