diff --git a/src/com/jpexs/decompiler/flash/gui/BezierUtils.java b/src/com/jpexs/decompiler/flash/gui/BezierUtils.java
new file mode 100644
index 000000000..8cd6a0a0a
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/gui/BezierUtils.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2022 JPEXS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.jpexs.decompiler.flash.gui;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * Bezier utils.
+ * Based on
+ * https://code.google.com/archive/p/degrafa/source/default/source
+ * which is derived from an algorithm in the book "Graphics Gems"
+ *
+ * This modification does not have cubic curves support.
+ */
+public class BezierUtils {
+
+ private static final int MAX_DEPTH = 64; // maximum recursion depth
+ private static final double[] Z_QUAD = new double[]{1.0, 2.0 / 3.0, 1.0 / 3.0, 1 / 3.0, 2.0 / 3.0, 1.0};
+ private static final double EPSILON = 1.0 * Math.pow(2, -MAX_DEPTH - 1); // flatness tolerance
+
+ public Point2D pointAt(double t, Point2D p0, Point2D p1, Point2D p2) {
+ double xt = (1 - t) * (1 - t) * p0.getX() + 2 * (1 - t) * t * p1.getX() + t * t * p2.getX();
+ double yt = (1 - t) * (1 - t) * p0.getY() + 2 * (1 - t) * t * p1.getY() + t * t * p2.getY();
+ return new Point2D.Double(xt, yt);
+ }
+
+ public double closestPointToBezier(Point2D _p, Point2D p0, Point2D p1, Point2D p2) {
+ Point2D p = p0;
+ double deltaX = p.getX() - _p.getX();
+ double deltaY = p.getY() - _p.getY();
+ double d0 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ p = p2;
+ deltaX = p.getX() - _p.getX();
+ deltaY = p.getY() - _p.getY();
+ double d1 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ int n = 2; // degree of input Bezier curve
+
+ // array of control points
+ List v = new ArrayList<>();
+ v.add(p0);
+ v.add(p1);
+ v.add(p2);
+
+ // instead of power form, convert the function whose zeros are required to Bezier form
+ List w = toBezierForm(_p, v);
+
+ // Find roots of the Bezier curve with control points stored in 'w' (algorithm is recursive, this is root depth of 0)
+ List roots = findRoots(w, 2 * n - 1, 0);
+
+ // compare the candidate distances to the endpoints and declare a winner :)
+ double tMinimum;
+ double dMinimum;
+ if (d0 < d1) {
+ tMinimum = 0;
+ dMinimum = d0;
+ } else {
+ tMinimum = 1;
+ dMinimum = d1;
+ }
+
+ // tbd - compare 2-norm squared
+ for (int i = 0; i < roots.size(); i++) {
+ double t = roots.get(i);
+ if (t >= 0 && t <= 1) {
+ p = pointAt(t, p0, p1, p2);
+ deltaX = p.getX() - _p.getX();
+ deltaY = p.getY() - _p.getY();
+ double d = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+
+ if (d < dMinimum) {
+ tMinimum = t;
+ dMinimum = d;
+ }
+ }
+ }
+
+ // tbd - alternate optima.
+ return tMinimum;
+
+ }
+
+ private List toBezierForm(Point2D _p, List _v) {
+ List c = new ArrayList<>(); // V(i) - P
+ List d = new ArrayList<>(); // V(i+1) - V(i)
+ List w = new ArrayList<>(); // control-points for Bezier curve whose zeros represent candidates for closest point to the input parametric curve
+ int n = _v.size() - 1; //degree of B(t)
+ int degree = 2 * n - 1; // degree of B(t) . P
+ double pX = _p.getX();
+ double pY = _p.getY();
+
+ Point2D v;
+ for (int i = 0; i <= n; i++) {
+ v = _v.get(i);
+ c.add(new Point2D.Double(v.getX() - pX, v.getY() - pY));
+ }
+
+ double s = n;
+ for (int i = 0; i <= n - 1; i++) {
+ v = _v.get(i);
+ Point2D v1 = _v.get(i + 1);
+ d.add(new Point2D.Double(s * (v1.getX() - v.getX()), s * (v1.getY() - v.getY())));
+ }
+
+ List cd = new ArrayList<>();
+ for (int row = 0; row <= n - 1; row++) {
+ Point2D di = d.get(row);
+ double dX = di.getX();
+ double dY = di.getY();
+ for (int col = 0; col <= n; col++) {
+ int k = getLinearIndex(n + 1, row, col);
+ cd.add(dX * c.get(col).getX() + dY * c.get(col).getY());
+ }
+ }
+
+ // Bezier is uniform parameterized
+ double dInv = 1.0 / degree;
+ for (int i = 0; i <= degree; i++) {
+ w.add(new Point2D.Double(i * dInv, 0));
+ }
+
+ // reference to appropriate pre-computed coefficients
+ double[] z = Z_QUAD;
+
+ // accumulate y-coords of the control points along the skew diagonal of the (n-1) x n matrix of c.d and z values
+ int m = n - 1;
+ for (int k = 0; k <= n + m; k++) {
+ int lb = Math.max(0, k - m);
+ int ub = Math.min(k, n);
+ for (int i = lb; i <= ub; i++) {
+ int j = k - i;
+ Point2D p = w.get(i + j);
+ int index = getLinearIndex(n + 1, j, i);
+ p.setLocation(p.getX(), p.getY() + cd.get(index) * z[index]);
+ w.set(i + j, p);
+ }
+ }
+ return w;
+ }
+
+ // how many times does the Bezier curve cross the horizontal axis - the number of roots is less than or equal to this count
+ private int crossingCount(List _v, int _degree) {
+ int nCrossings = 0;
+ int sign = _v.get(0).getY() < 0 ? -1 : 1;
+ int oldSign = sign;
+ for (int i = 1; i <= _degree; i++) {
+ sign = _v.get(i).getY() < 0 ? -1 : 1;
+ if (sign != oldSign) {
+ nCrossings++;
+ }
+
+ oldSign = sign;
+ }
+
+ return nCrossings;
+ }
+
+ // convert 2D array indices in a k x n matrix to a linear index (this is an interim step ahead of a future implementation optimized for 1D array indexing)
+ private int getLinearIndex(int _n, int _row, int _col) {
+ // no range-checking; you break it ... you buy it!
+ return _row * _n + _col;
+ }
+
+ /**
+ * subdivide( _c:Array, _t:Number, _left:Array, _right:Array ) - deCasteljau
+ * subdivision of an arbitrary-order Bezier curve
+ *
+ * @param _c:Array array of control points for the Bezier curve
+ * @param _t:Number t-parameter at which the curve is subdivided (must be in
+ * (0,1) = no check at this point
+ * @param _left:Array reference to an array in which the control points,
+ * Array of Point references, of the left control
+ * cage after subdivision are stored
+ * @param _right:Array reference to an array in which the control points,
+ * Array of Point references, of the right control
+ * cage after subdivision are stored
+ *
+ * @since 1.0
+ *
+ */
+ public void subdivide(List _c, double _t, List _left, List _right) {
+ int degree = _c.size() - 1;
+ int n = degree + 1;
+ List p = new ArrayList<>(_c);
+ double t1 = 1.0 - _t;
+
+ for (int i = 1; i <= degree; ++i) {
+ for (int j = 0; j <= degree - i; ++j) {
+ Point2D vertex = new Point2D.Double();
+ int ij = getLinearIndex(n, i, j);
+ int im1j = getLinearIndex(n, i - 1, j);
+ int im1jp1 = getLinearIndex(n, i - 1, j + 1);
+
+ vertex.setLocation(t1 * p.get(im1j).getX() + _t * p.get(im1jp1).getX(), t1 * p.get(im1j).getY() + _t * p.get(im1jp1).getY());
+ while (ij >= p.size()) {
+ p.add(new Point2D.Double(0, 0));
+ }
+ p.set(ij, vertex);
+ }
+ }
+
+ for (int j = 0; j <= degree; j++) {
+ int index = getLinearIndex(n, j, 0);
+ _left.add(p.get(index));
+ }
+
+ for (int j = 0; j <= degree; j++) {
+ int index = getLinearIndex(n, degree - j, j);
+ _right.add(p.get(index));
+ }
+ }
+
+ // is the control polygon for a Bezier curve suitably linear for subdivision to terminate?
+ private boolean isControlPolygonLinear(List _v, int _degree) {
+ // Given array of control points, _v, find the distance from each interior control point to line connecting v[0] and v[degree]
+
+ // implicit equation for line connecting first and last control points
+ double a = _v.get(0).getY() - _v.get(_degree).getY();
+ double b = _v.get(_degree).getX() - _v.get(0).getX();
+ double c = _v.get(0).getX() * _v.get(_degree).getY() - _v.get(_degree).getX() * _v.get(0).getY();
+
+ double abSquared = a * a + b * b;
+ List distance = new ArrayList<>(); // Distances from control points to line
+
+ distance.add(0.0);
+ for (int i = 1; i < _degree; i++) {
+ // Compute distance from each of the points to that line
+ distance.add(a * _v.get(i).getX() + b * _v.get(i).getY() + c);
+ if (distance.get(i) > 0.0) {
+ distance.set(i, (distance.get(i) * distance.get(i)) / abSquared);
+ }
+ if (distance.get(i) < 0.0) {
+ distance.set(i, -((distance.get(i) * distance.get(i)) / abSquared));
+ }
+ }
+
+ // Find the largest distance
+ double maxDistanceAbove = 0.0;
+ double maxDistanceBelow = 0.0;
+ for (int i = 1; i < _degree; i++) {
+ if (distance.get(i) < 0.0) {
+ maxDistanceBelow = Math.min(maxDistanceBelow, distance.get(i));
+ }
+ if (distance.get(i) > 0.0) {
+ maxDistanceAbove = Math.max(maxDistanceAbove, distance.get(i));
+ }
+ }
+
+ // Implicit equation for zero line
+ double a1 = 0.0;
+ double b1 = 1.0;
+ double c1 = 0.0;
+
+ // Implicit equation for "above" line
+ double a2 = a;
+ double b2 = b;
+ double c2 = c + maxDistanceAbove;
+
+ double det = a1 * b2 - a2 * b1;
+ double dInv = 1.0 / det;
+
+ double intercept1 = (b1 * c2 - b2 * c1) * dInv;
+
+ // Implicit equation for "below" line
+ a2 = a;
+ b2 = b;
+ c2 = c + maxDistanceBelow;
+
+ double intercept2 = (b1 * c2 - b2 * c1) * dInv;
+
+ // Compute intercepts of bounding box
+ double leftIntercept = Math.min(intercept1, intercept2);
+ double rightIntercept = Math.max(intercept1, intercept2);
+
+ double error = 0.5 * (rightIntercept - leftIntercept);
+
+ return error < EPSILON;
+ }
+
+ // return roots in [0,1] of a polynomial in Bernstein-Bezier form
+ private List findRoots(List _w, int _degree, int _depth) {
+ List t = new ArrayList<>(); // t-values of roots
+ int m = 2 * _degree - 1;
+
+ switch (crossingCount(_w, _degree)) {
+ case 0:
+ return new ArrayList<>();
+ case 1:
+ // Unique solution - stop recursion when the tree is deep enough (return 1 solution at midpoint)
+ if (_depth >= MAX_DEPTH) {
+ t.add(0.5 * (_w.get(0).getX() + _w.get(m).getX()));
+ return t;
+ }
+
+ if (isControlPolygonLinear(_w, _degree)) {
+ t.add(computeXIntercept(_w, _degree));
+ return t;
+ }
+ break;
+ }
+
+ // Otherwise, solve recursively after subdividing control polygon
+ List left = new ArrayList<>();
+ List right = new ArrayList<>();
+
+ // child solutions
+ subdivide(_w, 0.5, left, right);
+ List leftT = findRoots(left, _degree, _depth + 1);
+ List rightT = findRoots(right, _degree, _depth + 1);
+
+ t.addAll(leftT);
+ t.addAll(rightT);
+ return t;
+ }
+
+ // compute intersection of line segnet from first to last control point with horizontal axis
+ private double computeXIntercept(List _v, int _degree) {
+ double XNM = _v.get(_degree).getX() - _v.get(0).getX();
+ double YNM = _v.get(_degree).getY() - _v.get(0).getY();
+ double XMK = _v.get(0).getX();
+ double YMK = _v.get(0).getY();
+
+ double detInv = -1.0 / YNM;
+
+ return (XNM * YMK - YNM * XMK) * detInv;
+ }
+
+}
diff --git a/src/com/jpexs/decompiler/flash/gui/DisplayPoint.java b/src/com/jpexs/decompiler/flash/gui/DisplayPoint.java
index fb928ff08..562b6d291 100644
--- a/src/com/jpexs/decompiler/flash/gui/DisplayPoint.java
+++ b/src/com/jpexs/decompiler/flash/gui/DisplayPoint.java
@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash.gui;
+import java.awt.geom.Point2D;
+
/**
*
* @author JPEXS
@@ -25,6 +27,14 @@ public class DisplayPoint {
public int y;
public boolean onPath;
+ public DisplayPoint(Point2D point) {
+ this(point, true);
+ }
+ public DisplayPoint(Point2D point, boolean onPath) {
+ x = (int)Math.round(point.getX());
+ y = (int)Math.round(point.getY());
+ this.onPath = onPath;
+ }
public DisplayPoint(DisplayPoint src) {
this(src.x, src.y, src.onPath);
}
@@ -43,4 +53,7 @@ public class DisplayPoint {
return "["+x+","+y+"]";
}
+ public Point2D toPoint2D(){
+ return new Point2D.Double(x, y);
+ }
}
diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
index 3eb5b4589..5984d10fb 100644
--- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
@@ -246,6 +246,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
private static Cursor shearYCursor;
private static Cursor movePointCursor;
private static Cursor defaultCursor;
+ private static Cursor addPointCursor;
private Point2D offsetPoint = new Point2D.Double(0, 0);
@@ -273,9 +274,15 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
private List hilightedPoints = null;
+ //private DisplayPoint closestPoint = null;
private List pointsUnderCursor = new ArrayList<>();
private List selectedPoints = new ArrayList<>();
+ private Integer pathPointUnderCursor = null;
+ private Double pathPointPosition = null;
+
+ private DisplayPoint closestPoint = null;
+
private List selectedPointsOriginalValues = new ArrayList<>();
private int hilightEdgeColorStep = 10;
@@ -321,10 +328,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
}
- private boolean firePointAdded(List points, int position, DisplayPoint point) {
+ private boolean fireEdgeSplit(List points, int position, double splitPoint) {
boolean result = true;
for (PointUpdateListener listener : pointUpdateListeners) {
- result = result && listener.pointAdded(points, position, point);
+ result = result && listener.edgeSplit(points, position, splitPoint);
}
return result;
}
@@ -339,9 +346,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
private void applyPointsXY() {
try {
- int x = (int)Math.round(parseDouble(pointXTextField.getText()) * SWF.unitDivisor);
- int y = (int)Math.round(parseDouble(pointYTextField.getText()) * SWF.unitDivisor);
-
+ int x = (int) Math.round(parseDouble(pointXTextField.getText()) * SWF.unitDivisor);
+ int y = (int) Math.round(parseDouble(pointYTextField.getText()) * SWF.unitDivisor);
+
java.awt.Point minSelectedPoint = getMinSelectedPoint();
if (minSelectedPoint == null) {
return;
@@ -351,15 +358,15 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
point.x = point.x - minSelectedPoint.x + x;
point.y = point.y - minSelectedPoint.y + y;
}
-
- firePointsUpdated(hilightedPoints);
+
+ firePointsUpdated(hilightedPoints);
} catch (NumberFormatException nfe) {
//ignore
- }
+ }
}
-
+
private java.awt.Point getMinSelectedPoint() {
- if (selectedPoints.isEmpty()) {
+ if (selectedPoints.isEmpty()) {
return null;
}
int minX = Integer.MAX_VALUE;
@@ -375,7 +382,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
return new java.awt.Point(minX, minY);
}
-
+
private void calculatePointsXY() {
Point2D minPoint = getMinSelectedPoint();
if (minPoint == null) {
@@ -383,7 +390,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
pointYTextField.setText("");
return;
}
-
+
pointXTextField.setText(formatDouble(minPoint.getX() / SWF.unitDivisor));
pointYTextField.setText(formatDouble(minPoint.getY() / SWF.unitDivisor));
}
@@ -494,7 +501,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
shearYCursor = loadCursor("shear_y", 5, 9);
movePointCursor = loadCursor("move_point", 0, 0);
defaultCursor = loadCursor("default", 0, 0);
-
+ addPointCursor = loadCursor("add_point", 0, 0);
} catch (IOException ex) {
Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex);
}
@@ -788,13 +795,13 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
RECT timRect = timelined.getRect();
double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value;
- AffineTransform t = new AffineTransform();
- t.translate(offsetPoint.getX() - (int) (timRect.Xmin * zoomDouble / SWF.unitDivisor),
+ AffineTransform trans = new AffineTransform();
+ trans.translate(offsetPoint.getX() - (int) (timRect.Xmin * zoomDouble / SWF.unitDivisor),
offsetPoint.getY() - (int) (timRect.Ymin * zoomDouble / SWF.unitDivisor));
- t.scale(1 / SWF.unitDivisor, 1 / SWF.unitDivisor);
- t.scale(zoomDouble, zoomDouble);
+ trans.scale(1 / SWF.unitDivisor, 1 / SWF.unitDivisor);
+ trans.scale(zoomDouble, zoomDouble);
AffineTransform oldTransform = g2.getTransform();
- g2.setTransform(t);
+ g2.setTransform(trans);
if (hilightedEdge != null) {
g2.setStroke(new BasicStroke((float) (SWF.unitDivisor * 6 / zoomDouble)));
@@ -831,7 +838,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-
+
g2.setStroke(new BasicStroke((float) (SWF.unitDivisor * 1 / zoomDouble)));
double pointSize = SWF.unitDivisor * 4 / zoomDouble;
//selectedPoints
@@ -856,6 +863,51 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
g2.draw(pointShape);
}
+
+ /*DisplayPoint p = closestPoint;
+ if (p != null) {
+ Shape pointShape;
+ pointShape = new Ellipse2D.Double(p.x - pointSize, p.y - pointSize, pointSize * 2, pointSize * 2);
+ g2.setPaint(Color.blue);
+ g2.fill(pointShape);
+ }
+
+ Integer puc = pathPointUnderCursor;
+ if (puc != null) {
+ p = hilightedPoints.get((int) puc);
+ Shape pointShape;
+ pointShape = new Ellipse2D.Double(p.x - pointSize, p.y - pointSize, pointSize * 2, pointSize * 2);
+ g2.setPaint(Color.pink);
+ g2.fill(pointShape);
+
+ if (puc - 1 >= 0) {
+ p = hilightedPoints.get(puc - 1);
+ pointShape = new Ellipse2D.Double(p.x - pointSize, p.y - pointSize, pointSize * 2, pointSize * 2);
+ g2.setPaint(Color.cyan);
+ g2.fill(pointShape);
+ }
+
+ }*/
+ /*for (int i = 0; i < points.size(); i++) {
+ DisplayPoint p = points.get(i);
+ if (!p.onPath) {
+ DisplayPoint p0 = points.get(i - 1);
+ DisplayPoint p1 = points.get(i);
+ DisplayPoint p2 = points.get(i + 1);
+
+ for (int j = 0; j <= 10; j++) {
+ double t = j / 10.0;
+ double xt = (1 - t) * (1 - t) * p0.x + 2 * t * (1 - t) * p1.x + t * t * p2.x;
+ double yt = (1 - t) * (1 - t) * p0.y + 2 * t * (1 - t) * p1.y + t * t * p2.y;
+ //System.out.println("P("+t+") = "+xt+","+yt);
+
+ Shape pointShape;
+ pointShape = new Ellipse2D.Double(xt - pointSize, yt - pointSize, pointSize * 2, pointSize * 2);
+ g2.setPaint(Color.green);
+ g2.fill(pointShape);
+ }
+ }
+ }*/
}
g2.setTransform(oldTransform);
}
@@ -918,6 +970,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
return altDown;
}
+ public boolean isCtrlDown() {
+ return ctrlDown;
+ }
+
public IconPanel() {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
@@ -961,7 +1017,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
return o2 - o1;
}
});
- for (int i:selectedPointsDesc) {
+ for (int i : selectedPointsDesc) {
firePointRemoved(hilightedPoints, i);
}
selectedPoints.clear();
@@ -1079,7 +1135,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
}
if (shiftDown) {
- for (int p: selectedPoints) {
+ for (int p : selectedPoints) {
if (!newSelectedPoints.contains(p)) {
newSelectedPoints.add(p);
}
@@ -1088,6 +1144,15 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
selectedPoints = newSelectedPoints;
calculatePointsXY();
}
+
+ if (ctrlDown && pathPointUnderCursor != null) {
+ fireEdgeSplit(hilightedPoints, pathPointUnderCursor, pathPointPosition);
+ selectedPoints.clear();
+ pointsUnderCursor.clear();
+ pathPointUnderCursor = null;
+ pathPointPosition = 0.0;
+ repaint();
+ }
}
dragStart = null;
@@ -1656,12 +1721,28 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
}
+ class DistanceItem {
+
+ public double distance;
+ public int pathPoint;
+ public double pathPosition;
+ public DisplayPoint closestPoint;
+
+ public DistanceItem(double distance, int pathPoint, double pathPosition, DisplayPoint closestPoint) {
+ this.distance = distance;
+ this.pathPoint = pathPoint;
+ this.pathPosition = pathPosition;
+ this.closestPoint = closestPoint;
+ }
+
+ }
+
@Override
public void mouseMoved(MouseEvent e) {
List points = hilightedPoints;
if (points != null) {
- Cursor cursor = selectCursor;
int maxDistance = 5;
+ double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value;
List newPointsUnderCursor = new ArrayList<>();
for (int i = 0; i < points.size(); i++) {
DisplayPoint p = points.get(i);
@@ -1670,17 +1751,91 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
int ey = e.getY();
if (ex > ip.getX() - maxDistance && ex < ip.getX() + maxDistance) {
if (ey > ip.getY() - maxDistance && ey < ip.getY() + maxDistance) {
- cursor = movePointCursor;
newPointsUnderCursor.add(i);
}
}
}
- if (dragStart == null) {
- pointsUnderCursor = newPointsUnderCursor;
+
+ Point2D p = toTransformPoint(e.getPoint());
+
+ List distanceList = new ArrayList<>();
+ for (int i = 0; i < points.size() - 1; i++) {
+ if (points.get(i).onPath && points.get(i + 1).onPath) {
+ DisplayPoint p0 = points.get(i);
+ DisplayPoint p1 = points.get(i + 1);
+
+ //y = mx + b
+ double lineDistance;
+ Point2D closestPoint;
+ if (p1.x == p0.x) {
+ lineDistance = Math.abs(p1.x - p.getX());
+ closestPoint = new Point2D.Double(p1.x, p.getY());
+ } else if (p1.y == p0.y) {
+ lineDistance = Math.abs(p1.y - p.getY());
+ closestPoint = new Point2D.Double(p.getX(), p1.y);
+ } else {
+ double m = (p1.y - p0.y) / (double) (p1.x - p0.x);
+ double b = p0.y - m * p0.x;
+
+ double m_perp = - 1 / m;
+
+ double b_perp = p.getY() - m_perp * p.getX();
+
+ double x = (b_perp - b) / (m - m_perp);
+ double y = m * x + b;
+ closestPoint = new Point2D.Double(x, y);
+ lineDistance = p.distance(closestPoint);
+ }
+
+ double minX = Math.min(p0.x, p1.x) - maxDistance * SWF.unitDivisor / zoomDouble;
+ double minY = Math.min(p0.y, p1.y) - maxDistance * SWF.unitDivisor / zoomDouble;
+
+ double maxX = Math.max(p0.x, p1.x) + maxDistance * SWF.unitDivisor / zoomDouble;
+ double maxY = Math.max(p0.y, p1.y) + maxDistance * SWF.unitDivisor / zoomDouble;
+
+ if (p.getX() >= minX && p.getX() <= maxX && p.getY() >= minY && p.getY() <= maxY) {
+ double t = p0.toPoint2D().distance(closestPoint) / p0.toPoint2D().distance(p1.toPoint2D());
+ if (lineDistance <= maxDistance * SWF.unitDivisor / zoomDouble) {
+ distanceList.add(new DistanceItem(lineDistance, i + 1, t, new DisplayPoint(closestPoint)));
+ }
+ }
+
+
+ }
+ if (i < points.size() - 2 && !points.get(i + 1).onPath) {
+ DisplayPoint p0 = points.get(i);
+ DisplayPoint p1 = points.get(i + 1);
+ DisplayPoint p2 = points.get(i + 2);
+
+ BezierUtils bezierUtils = new BezierUtils();
+ double t = bezierUtils.closestPointToBezier(p, p0.toPoint2D(), p1.toPoint2D(), p2.toPoint2D());
+ DisplayPoint closestPoint = new DisplayPoint(bezierUtils.pointAt(t, p0.toPoint2D(), p1.toPoint2D(), p2.toPoint2D()));
+ double curveDistance = Math.sqrt((p.getX() - closestPoint.x) * (p.getX() - closestPoint.x)
+ + (p.getY() - closestPoint.y) * (p.getY() - closestPoint.y));
+ if (curveDistance <= maxDistance * SWF.unitDivisor / zoomDouble) {
+ distanceList.add(new DistanceItem(curveDistance, i + 1, t, closestPoint));
+ }
+ }
}
- if (getCursor() != cursor) {
- setCursor(cursor);
+ distanceList.sort(new Comparator() {
+ @Override
+ public int compare(DistanceItem o1, DistanceItem o2) {
+ return Double.compare(o1.distance, o2.distance);
+ }
+ });
+ if (dragStart == null) {
+ if (!distanceList.isEmpty()) {
+ DistanceItem di = distanceList.get(0);
+ pathPointUnderCursor = di.pathPoint;
+ pathPointPosition = di.pathPosition;
+ closestPoint = di.closestPoint;
+ } else {
+ pathPointUnderCursor = null;
+ pathPointPosition = null;
+ closestPoint = null;
+ }
+ pointsUnderCursor = newPointsUnderCursor;
}
return;
}
@@ -2030,7 +2185,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
debugLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
topPanel.add(debugLabel);
- DocumentListener documentListener = new DocumentListener(){
+ DocumentListener documentListener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
applyPointsXY();
@@ -2044,21 +2199,21 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
@Override
public void changedUpdate(DocumentEvent e) {
applyPointsXY();
- }
+ }
};
-
+
pointEditPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
pointEditPanel.add(new JLabel(AppStrings.translate("edit.points.x")));
- pointXTextField = new JTextField(6);
+ pointXTextField = new JTextField(6);
pointXTextField.getDocument().addDocumentListener(documentListener);
pointEditPanel.add(pointXTextField);
pointEditPanel.add(new JLabel(AppStrings.translate("edit.points.y")));
pointYTextField = new JTextField(6);
pointYTextField.getDocument().addDocumentListener(documentListener);
pointEditPanel.add(pointYTextField);
-
+
pointEditPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
-
+
topPanel.add(pointEditPanel);
pointEditPanel.setVisible(false);
add(topPanel, BorderLayout.NORTH);
@@ -3068,6 +3223,23 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
} else {
debugLabel.setText(ret.toString());
}
+ if (hilightedPoints != null) {
+ Cursor newCursor;
+ if (!pointsUnderCursor.isEmpty()) {
+ newCursor = movePointCursor;
+ } else if (pathPointUnderCursor != null) {
+ if (iconPanel.isCtrlDown()) {
+ newCursor = addPointCursor;
+ } else {
+ newCursor = defaultCursor;
+ }
+ } else {
+ newCursor = selectCursor;
+ }
+ if (iconPanel.getCursor() != newCursor) { //call setcursor only when needed to avoid cursor flickering when dragging in the tree
+ iconPanel.setCursor(newCursor);
+ }
+ }
if (freeTransformDepth == -1 && hilightedPoints == null) {
Cursor newCursor;
if (iconPanel.isAltDown()) {
diff --git a/src/com/jpexs/decompiler/flash/gui/PointUpdateListener.java b/src/com/jpexs/decompiler/flash/gui/PointUpdateListener.java
index c6b01f75d..2c5e0ce49 100644
--- a/src/com/jpexs/decompiler/flash/gui/PointUpdateListener.java
+++ b/src/com/jpexs/decompiler/flash/gui/PointUpdateListener.java
@@ -25,7 +25,7 @@ import java.util.List;
public interface PointUpdateListener {
public void pointsUpdated(List points);
- public boolean pointAdded(List points, int position, DisplayPoint point);
+ public boolean edgeSplit(List points, int position, double splitPoint);
public boolean pointRemoved(List points, int position);
}
diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java
index 948a47d9a..1297f17d2 100644
--- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java
@@ -780,9 +780,80 @@ public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel
}
@Override
- public boolean pointAdded(List points, int position, DisplayPoint point) {
- return true;
- }
+ public boolean edgeSplit(List points, int position, double splitPoint) {
+ ShapeTag shape = (ShapeTag) displayEditTag;
+ int pointsPos = 0;
+ int x = 0;
+ int y = 0;
+ for (int i = 0; i < shape.shapes.shapeRecords.size(); i++) {
+ SHAPERECORD rec = shape.shapes.shapeRecords.get(i);
+ if (rec instanceof StyleChangeRecord) {
+ StyleChangeRecord scr = (StyleChangeRecord) rec;
+ if (scr.stateMoveTo) {
+ pointsPos++;
+ }
+ }
+ if (rec instanceof StraightEdgeRecord) {
+ StraightEdgeRecord ser = (StraightEdgeRecord) rec;
+ if (pointsPos == position) {
+ StraightEdgeRecord newSer = new StraightEdgeRecord();
+ newSer.generalLineFlag = true;
+ newSer.deltaX = (int)Math.round(ser.deltaX * (1-splitPoint));
+ newSer.deltaY = (int)Math.round(ser.deltaY * (1-splitPoint));
+ newSer.simplify();
+ ser.generalLineFlag = true;
+ ser.deltaX -= newSer.deltaX;
+ ser.deltaY -= newSer.deltaY;
+ ser.simplify();
+ shape.shapes.shapeRecords.add(i + 1, newSer);
+ points.add(position, new DisplayPoint(new Point2D.Double(x + ser.deltaX, y + ser.deltaY)));
+ shape.getSwf().clearShapeCache();
+ displayEditImagePanel.repaint();
+ return true;
+ }
+ pointsPos += 1;
+ ser.simplify();
+ }
+ if (rec instanceof CurvedEdgeRecord) {
+ CurvedEdgeRecord cer = (CurvedEdgeRecord) rec;
+ if (pointsPos == position) {
+ Point2D p0 = new Point2D.Double(x, y);
+ Point2D p1 = new Point2D.Double(x + cer.controlDeltaX, y + cer.controlDeltaY);
+ Point2D p2 = new Point2D.Double(x + cer.controlDeltaX + cer.anchorDeltaX, y + cer.controlDeltaY + cer.anchorDeltaY);
+ List v = new ArrayList<>();
+ v.add(p0);
+ v.add(p1);
+ v.add(p2);
+ BezierUtils bu = new BezierUtils();
+ List left = new ArrayList<>();
+ List right = new ArrayList<>();
+ bu.subdivide(v, splitPoint, left, right);
+ cer.controlDeltaX = (int)Math.round(left.get(1).getX() - left.get(0).getX());
+ cer.controlDeltaY = (int)Math.round(left.get(1).getY() - left.get(0).getY());
+ cer.anchorDeltaX = (int)Math.round(left.get(2).getX() - left.get(1).getX());
+ cer.anchorDeltaY = (int)Math.round(left.get(2).getY() - left.get(1).getY());
+
+ CurvedEdgeRecord newCer = new CurvedEdgeRecord();
+ newCer.controlDeltaX = (int)Math.round(right.get(1).getX() - right.get(0).getX());
+ newCer.controlDeltaY = (int)Math.round(right.get(1).getY() - right.get(0).getY());
+ newCer.anchorDeltaX = (int)Math.round(right.get(2).getX() - right.get(1).getX());
+ newCer.anchorDeltaY = (int)Math.round(right.get(2).getY() - right.get(1).getY());
+ shape.shapes.shapeRecords.add(i + 1, newCer);
+ points.remove(position);
+ points.add(position, new DisplayPoint(new Point2D.Double(left.get(1).getX(), left.get(1).getY()), false));
+ points.add(position + 1, new DisplayPoint(new Point2D.Double(left.get(2).getX(), left.get(2).getY())));
+ points.add(position + 2, new DisplayPoint(new Point2D.Double(right.get(1).getX(), right.get(1).getY()), false));
+ shape.getSwf().clearShapeCache();
+ displayEditImagePanel.repaint();
+ return true;
+ }
+ pointsPos += 2;
+ }
+ x = rec.changeX(x);
+ y = rec.changeY(y);
+ }
+ return false;
+ }
@Override
public boolean pointRemoved(List points, int position) {
diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/cursors/add_point.png b/src/com/jpexs/decompiler/flash/gui/graphics/cursors/add_point.png
new file mode 100644
index 000000000..999940fc2
Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/cursors/add_point.png differ