Shape fixer 2 WIP

This commit is contained in:
Jindra Petřík
2025-10-04 14:40:11 +02:00
parent 5db66ae32b
commit 313590ebfd
11 changed files with 2184 additions and 51 deletions

View File

@@ -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"
);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}
}