Java/2D Graphics GUI/Curve
Содержание
A spline factory instance
<source lang="java"> /*
* This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */
//package no.geosoft.cc.geometry.spline;
/**
* A spline factory instance. * * @author */
class CatmullRomSpline extends CubicSpline {
/** * Construct a Catmull-Rom spline. Package local; Use the SplineFactory * to create splines of this type. The control points are used according * to the definition of Catmull-Rom splines. * * @param controlPoints Control points of spline (x0,y0,z0,x1,y1,z1,...) * @param nParts Number of parts in generated spline. */ CatmullRomSpline (double controlPoints[], int nParts) { super (controlPoints, nParts); }
protected void initialize (double controlPoints[], int nParts) { nParts_ = nParts; // Endpoints are added twice to force in the generated array controlPoints_ = new double[controlPoints.length + 6]; System.arraycopy (controlPoints, 0, controlPoints_, 3, controlPoints.length); controlPoints_[0] = controlPoints_[3]; controlPoints_[1] = controlPoints_[4]; controlPoints_[2] = controlPoints_[5]; controlPoints_[controlPoints_.length - 3] = controlPoints_[controlPoints_.length - 6]; controlPoints_[controlPoints_.length - 2] = controlPoints_[controlPoints_.length - 5]; controlPoints_[controlPoints_.length - 1] = controlPoints_[controlPoints_.length - 4]; } protected double blend (int i, double t) { if (i == -2) return ((-t + 2) * t - 1) * t / 2; else if (i == -1) return (((3 * t - 5) * t) * t + 2) / 2; else if (i == 0) return ((-3 * t + 4) * t + 1) * t / 2; else return ((t - 1) * t * t) / 2; }
}
</source>
Curve with QuadCurve2D
<source lang="java"> import java.awt.Canvas; import java.awt.Color; import java.awt.Container; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.QuadCurve2D; import java.awt.geom.Rectangle2D; import java.util.Vector; import javax.swing.JApplet; import javax.swing.JFrame; public class QuadCurve extends JApplet {
DrawingCanvas canvas; public static void main(String[] a){ JFrame f = new JFrame(); QuadCurve curve = new QuadCurve(); curve.init(); f.getContentPane().add(curve); f.setDefaultCloseOperation(1); f.setSize(650,250); f.setVisible(true); } public void init() { Container container = getContentPane(); canvas = new DrawingCanvas(); container.add(canvas); } class DrawingCanvas extends Canvas { Vector quadCurves; QuadCurve2D selectedCurve = null; Rectangle2D boundingRec = null; public DrawingCanvas() { setBackground(Color.white); setSize(400, 200); // width and height of canvas quadCurves = new Vector(); quadCurves.addElement(new QuadCurve2D.Float(20, 20, 80, 160, 120, 20)); quadCurves.addElement(new QuadCurve2D.Float(120, 100, 160, 40, 200, 180)); quadCurves.addElement(new QuadCurve2D.Float(240, 20, 220, 60, 260, 120)); quadCurves.addElement(new QuadCurve2D.Float(250, 160, 260, 140, 280, 180)); quadCurves.addElement(new QuadCurve2D.Float(300, 180, 340, 40, 380, 120)); quadCurves.addElement(new QuadCurve2D.Float(20, 180, 80, 170, 120, 190)); } public void paint(Graphics g) { Graphics2D g2D = (Graphics2D) g; for (int i = 0; i < quadCurves.size(); i++) { g2D.draw((QuadCurve2D) quadCurves.elementAt(i)); } } }
}
</source>
Draw curve with mouse
<source lang="java"> import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Color; import java.awt.Container; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; import java.awt.geom.QuadCurve2D; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public class CubicCurveMouse extends JFrame {
DrawingCanvas canvas; JLabel label = new JLabel("Mouse Location (x, y): "); JLabel coords = new JLabel(""); public CubicCurveMouse() { super(); Container container = getContentPane(); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(1, 2)); panel.add(label); panel.add(label); panel.add(coords); container.add(panel, BorderLayout.SOUTH); canvas = new DrawingCanvas(); container.add(canvas); addWindowListener(new WindowEventHandler()); setSize(300,300); setVisible(true); } class WindowEventHandler extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public static void main(String arg[]) { new CubicCurveMouse(); } class DrawingCanvas extends Canvas { float x1, y1, xc1cur, yc1cur, xc1new, yc1new, xc2cur, yc2cur, xc2new, yc2new, x4cur, y4cur, x4new, y4new; int pressNo = 0; int dragFlag1 = -1; int dragFlag2 = -1; boolean clearFlag = false; float dashes[] = { 5f, 5f }; BasicStroke stroke; public DrawingCanvas() { setBackground(Color.white); addMouseListener(new MyMouseListener()); addMouseMotionListener(new MyMouseListener()); setSize(400, 400); stroke = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10f, dashes, 0f); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { Graphics2D g2D = (Graphics2D) g; if (pressNo == 1) { g2D.setXORMode(getBackground()); g2D.setColor(Color.black); g2D.setStroke(stroke); // Erase the currently existing line g2D.draw(new Line2D.Float(x1, y1, x4cur, y4cur)); // Draw the new line g2D.draw(new Line2D.Float(x1, y1, x4new, y4new)); // Update the currently existing coordinate values x4cur = x4new; y4cur = y4new; }else if (pressNo == 2) { g2D.setXORMode(getBackground()); g2D.setColor(Color.black); g2D.setStroke(stroke); if (dragFlag1 != -1) { g2D.draw(new QuadCurve2D.Float(x1, y1, xc1cur, yc1cur, x4new, y4new)); } dragFlag1++; // Reset the drag-flag g2D.draw(new QuadCurve2D.Float(x1, y1, xc1new, yc1new, x4new, y4new)); xc1cur = xc1new; yc1cur = yc1new; }else if (pressNo == 3) { g2D.setXORMode(getBackground()); g2D.setColor(Color.black); if (dragFlag2 != -1) { g2D.draw(new CubicCurve2D.Float(x1, y1, xc1new, yc1new, xc2cur, yc2cur, x4new, y4new)); } dragFlag2++; // Reset the drag flag g2D.draw(new CubicCurve2D.Float(x1, y1, xc1new, yc1new, xc2new, yc2new, x4new, y4new)); xc2cur = xc2new; yc2cur = yc2new; } if (clearFlag) { g2D.setXORMode(getBackground()); g2D.setColor(Color.black); g2D.setStroke(stroke); g2D.draw(new Line2D.Float(x1, y1, x4new, y4new)); g2D.draw(new QuadCurve2D.Float(x1, y1, xc1new, yc1new, x4new, y4new)); clearFlag = false; } } class MyMouseListener extends MouseAdapter implements MouseMotionListener { public void mousePressed(MouseEvent e) { if (pressNo == 0) { pressNo++; x1 = x4cur = e.getX(); y1 = y4cur = e.getY(); } else if (pressNo == 1) { pressNo++; xc1cur = e.getX(); yc1cur = e.getY(); } else if (pressNo == 2) { pressNo++; xc2cur = e.getX(); yc2cur = e.getY(); } } public void mouseReleased(MouseEvent e) { if (pressNo == 1) { x4new = e.getX(); y4new = e.getY(); canvas.repaint(); } else if (pressNo == 2) { xc1new = e.getX(); yc1new = e.getY(); canvas.repaint(); } else if (pressNo == 3) { xc2new = e.getX(); yc2new = e.getY(); canvas.repaint(); pressNo = 0; dragFlag1 = -1; dragFlag2 = -1; clearFlag = true; } } public void mouseDragged(MouseEvent e) { if (pressNo == 1) { x4new = e.getX(); y4new = e.getY(); String string = "(" + Integer.toString(e.getX()) + ", " + Integer.toString(e.getY()) + ")"; coords.setText(string); canvas.repaint(); } else if (pressNo == 2) { xc1new = e.getX(); yc1new = e.getY(); String string = "(" + Integer.toString(e.getX()) + ", " + Integer.toString(e.getY()) + ")"; coords.setText(string); canvas.repaint(); } else if (pressNo == 3) { xc2new = e.getX(); yc2new = e.getY(); String string = "(" + Integer.toString(e.getX()) + ", " + Integer.toString(e.getY()) + ")"; coords.setText(string); canvas.repaint(); } } public void mouseMoved(MouseEvent e) { String string = "(" + Integer.toString(e.getX()) + ", " + Integer.toString(e.getY()) + ")"; coords.setText(string); } } }
}
</source>
Interpolates given points by a bezier curve
<source lang="java"> /*
* Copyright (c) 2005 David Benson * * See LICENSE file in distribution for licensing details of this source file */
import java.awt.Point; import java.awt.geom.Point2D; /**
* Interpolates given points by a bezier curve. The first * and the last two points are interpolated by a quadratic * bezier curve; the other points by a cubic bezier curve. * * Let p a list of given points and b the calculated bezier points, * then one get the whole curve by: * * sharedPath.moveTo(p[0]) * sharedPath.quadTo(b[0].x, b[0].getY(), p[1].x, p[1].getY()); * * for(int i = 2; i < p.length - 1; i++ ) { * Point b0 = b[2*i-3]; * Point b1 = b[2*i-2]; * sharedPath.curveTo(b0.x, b0.getY(), b1.x, b1.getY(), p[i].x, p[i].getY()); * } * * sharedPath.quadTo(b[b.length-1].x, b[b.length-1].getY(), p[n - 1].x, p[n - 1].getY()); * * @author krueger */
public class Bezier {
private static final float AP = 0.5f; private Point2D[] bPoints; /** * Creates a new Bezier curve. * @param points */ public Bezier(Point2D[] points) { int n = points.length; if (n < 3) { // Cannot create bezier with less than 3 points return; } bPoints = new Point[2 * (n - 2)]; double paX, paY; double pbX = points[0].getX(); double pbY = points[0].getY(); double pcX = points[1].getX(); double pcY = points[1].getY(); for (int i = 0; i < n - 2; i++) { paX = pbX; paY = pbY; pbX = pcX; pbY = pcY; pcX = points[i + 2].getX(); pcY = points[i + 2].getY(); double abX = pbX - paX; double abY = pbY - paY; double acX = pcX - paX; double acY = pcY - paY; double lac = Math.sqrt(acX * acX + acY * acY); acX = acX /lac; acY = acY /lac; double proj = abX * acX + abY * acY; proj = proj < 0 ? -proj : proj; double apX = proj * acX; double apY = proj * acY; double p1X = pbX - AP * apX; double p1Y = pbY - AP * apY; bPoints[2 * i] = new Point((int) p1X, (int) p1Y); acX = -acX; acY = -acY; double cbX = pbX - pcX; double cbY = pbY - pcY; proj = cbX * acX + cbY * acY; proj = proj < 0 ? -proj : proj; apX = proj * acX; apY = proj * acY; double p2X = pbX - AP * apX; double p2Y = pbY - AP * apY; bPoints[2 * i + 1] = new Point((int) p2X, (int) p2Y); } } /** * Returns the calculated bezier points. * @return the calculated bezier points */ public Point2D[] getPoints() { return bPoints; } /** * Returns the number of bezier points. * @return number of bezier points */ public int getPointCount() { return bPoints.length; } /** * Returns the bezier points at position i. * @param i * @return the bezier point at position i */ public Point2D getPoint(int i) { return bPoints[i]; }
}
</source>
Move the curve control point and redraw the curve
<source lang="java"> import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.QuadCurve2D; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.event.MouseInputAdapter; public class MainClass {
public static void main(String[] args){ JFrame frame = new JFrame(); frame.getContentPane().add(new CurveApplet()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(200, 200); frame.setVisible(true); }
} class CurveApplet extends JPanel {
public CurveApplet() { super(new BorderLayout()); pane = new CurvePane(); add(pane,"Center"); MouseHandler handler = new MouseHandler(); pane.addMouseListener(handler); pane.addMouseMotionListener(handler); } class CurvePane extends JComponent { public CurvePane() { quadCurve = new QuadCurve2D.Double( startQ.x, startQ.y, control.x, control.y, endQ.x, endQ.y); cubicCurve = new CubicCurve2D.Double( startC.x, startC.y, controlStart.x, controlStart.y, controlEnd.x, controlEnd.y, endC.x, endC.y); } public void paint(Graphics g) { Graphics2D g2D = (Graphics2D) g; quadCurve.ctrlx = ctrlQuad.getCenter().x; quadCurve.ctrly = ctrlQuad.getCenter().y; cubicCurve.ctrlx1 = ctrlCubic1.getCenter().x; cubicCurve.ctrly1 = ctrlCubic1.getCenter().y; cubicCurve.ctrlx2 = ctrlCubic2.getCenter().x; cubicCurve.ctrly2 = ctrlCubic2.getCenter().y; g2D.setPaint(Color.BLUE); g2D.draw(quadCurve); g2D.draw(cubicCurve); g2D.setPaint(Color.RED); ctrlQuad.draw(g2D); ctrlCubic1.draw(g2D); ctrlCubic2.draw(g2D); Line2D.Double tangent = new Line2D.Double(startQ, ctrlQuad.getCenter()); g2D.draw(tangent); tangent = new Line2D.Double(endQ, ctrlQuad.getCenter()); g2D.draw(tangent); tangent = new Line2D.Double(startC, ctrlCubic1.getCenter()); g2D.draw(tangent); tangent = new Line2D.Double(endC, ctrlCubic2.getCenter()); g2D.draw(tangent); } } Point2D.Double startQ = new Point2D.Double(50, 75); Point2D.Double endQ = new Point2D.Double(150, 75); Point2D.Double control = new Point2D.Double(80, 25); Point2D.Double startC = new Point2D.Double(50, 150); Point2D.Double endC = new Point2D.Double(150, 150); Point2D.Double controlStart = new Point2D.Double(80, 100); Point2D.Double controlEnd = new Point2D.Double(160, 100); Marker ctrlQuad = new Marker(control); Marker ctrlCubic1 = new Marker(controlStart); Marker ctrlCubic2 = new Marker(controlEnd); QuadCurve2D.Double quadCurve; CubicCurve2D.Double cubicCurve; CurvePane pane = new CurvePane(); class Marker { public Marker(Point2D.Double control) { center = control; circle = new Ellipse2D.Double(control.x - radius, control.y - radius, 2.0 * radius, 2.0 * radius); } public void draw(Graphics2D g2D) { g2D.draw(circle); } Point2D.Double getCenter() { return center; } public boolean contains(double x, double y) { return circle.contains(x, y); } public void setLocation(double x, double y) { center.x = x; center.y = y; circle.x = x - radius; circle.y = y - radius; } Ellipse2D.Double circle; Point2D.Double center; static final double radius = 3; } class MouseHandler extends MouseInputAdapter { public void mousePressed(MouseEvent e) { if (ctrlQuad.contains(e.getX(), e.getY())) selected = ctrlQuad; else if (ctrlCubic1.contains(e.getX(), e.getY())) selected = ctrlCubic1; else if (ctrlCubic2.contains(e.getX(), e.getY())) selected = ctrlCubic2; } public void mouseReleased(MouseEvent e) { selected = null; } public void mouseDragged(MouseEvent e) { if (selected != null) { selected.setLocation(e.getX(), e.getY()); pane.repaint(); } } Marker selected = null; }
}
</source>
Spline 2D
<source lang="java"> /*
* @(#)Spline2D.java * * Copyright (c) 2003 Martin Krueger * Copyright (c) 2005 David Benson * */
import java.awt.geom.Point2D; import java.util.Arrays; /**
* Interpolates points given in the 2D plane. The resulting spline * is a function s: R -> R^2 with parameter t in [0,1]. * * @author krueger */
public class Spline2D {
/** * Array representing the relative proportion of the total distance * of each point in the line ( i.e. first point is 0.0, end point is * 1.0, a point halfway on line is 0.5 ). */ private double[] t; private Spline splineX; private Spline splineY; /** * Total length tracing the points on the spline */ private double length; /** * Creates a new Spline2D. * @param points */ public Spline2D(Point2D[] points) { double[] x = new double[points.length]; double[] y = new double[points.length]; for(int i = 0; i< points.length; i++) { x[i] = points[i].getX(); y[i] = points[i].getY(); } init(x, y); } /** * Creates a new Spline2D. * @param x * @param y */ public Spline2D(double[] x, double[] y) { init(x, y); } private void init(double[] x, double[] y) { if (x.length != y.length) { throw new IllegalArgumentException("Arrays must have the same length."); } if (x.length < 2) { throw new IllegalArgumentException("Spline edges must have at least two points."); } t = new double[x.length]; t[0] = 0.0; // start point is always 0.0 // Calculate the partial proportions of each section between each set // of points and the total length of sum of all sections for (int i = 1; i < t.length; i++) { double lx = x[i] - x[i-1]; double ly = y[i] - y[i-1]; // If either diff is zero there is no point performing the square root if ( 0.0 == lx ) { t[i] = Math.abs(ly); } else if ( 0.0 == ly ) { t[i] = Math.abs(lx); } else { t[i] = Math.sqrt(lx*lx+ly*ly); } length += t[i]; t[i] += t[i-1]; } for(int i = 1; i< (t.length)-1; i++) { t[i] = t[i] / length; } t[(t.length)-1] = 1.0; // end point is always 1.0 splineX = new Spline(t, x); splineY = new Spline(t, y); } /** * @param t 0 <= t <= 1 */ public double[] getPoint(double t) { double[] result = new double[2]; result[0] = splineX.getValue(t); result[1] = splineY.getValue(t); return result; } /** * Used to check the correctness of this spline */ public boolean checkValues() { return (splineX.checkValues() && splineY.checkValues()); } public double getDx(double t) { return splineX.getDx(t); } public double getDy(double t) { return splineY.getDx(t); } public Spline getSplineX() { return splineX; } public Spline getSplineY() { return splineY; } public double getLength() { return length; }
} /* This code is PUBLIC DOMAIN */
/**
* Interpolates given values by B-Splines. * * @author krueger */ class Spline { private double[] xx; private double[] yy; private double[] a; private double[] b; private double[] c; private double[] d; /** tracks the last index found since that is mostly commonly the next one used */ private int storageIndex = 0; /** * Creates a new Spline. * @param xx * @param yy */ public Spline(double[] xx, double[] yy) { setValues(xx, yy); } /** * Set values for this Spline. * @param xx * @param yy */ public void setValues(double[] xx, double[] yy) { this.xx = xx; this.yy = yy; if (xx.length > 1) { calculateCoefficients(); } } /** * Returns an interpolated value. * @param x * @return the interpolated value */ public double getValue(double x) { if (xx.length == 0) { return Double.NaN; } if (xx.length == 1) { if (xx[0] == x) { return yy[0]; } else { return Double.NaN; } } int index = Arrays.binarySearch(xx, x); if (index > 0) { return yy[index]; } index = - (index + 1) - 1; //TODO linear interpolation or extrapolation if (index < 0) { return yy[0]; } return a[index] + b[index] * (x - xx[index]) + c[index] * Math.pow(x - xx[index], 2) + d[index] * Math.pow(x - xx[index], 3); } /** * Returns an interpolated value. To be used when a long sequence of values * are required in order, but ensure checkValues() is called beforehand to * ensure the boundary checks from getValue() are made * @param x * @return the interpolated value */ public double getFastValue(double x) { // Fast check to see if previous index is still valid if (storageIndex > -1 && storageIndex < xx.length-1 && x > xx[storageIndex] && x < xx[storageIndex + 1]) { } else { int index = Arrays.binarySearch(xx, x); if (index > 0) { return yy[index]; } index = - (index + 1) - 1; storageIndex = index; } //TODO linear interpolation or extrapolation if (storageIndex < 0) { return yy[0]; } double value = x - xx[storageIndex]; return a[storageIndex] + b[storageIndex] * value + c[storageIndex] * (value * value) + d[storageIndex] * (value * value * value); } /** * Used to check the correctness of this spline */ public boolean checkValues() { if (xx.length < 2) { return false; } else { return true; } } /** * Returns the first derivation at x. * @param x * @return the first derivation at x */ public double getDx(double x) { if (xx.length == 0 || xx.length == 1) { return 0; } int index = Arrays.binarySearch(xx, x); if (index < 0) { index = - (index + 1) - 1; } return b[index] + 2 * c[index] * (x - xx[index]) + 3 * d[index] * Math.pow(x - xx[index], 2); } /** * Calculates the Spline coefficients. */ private void calculateCoefficients() { int N = yy.length; a = new double[N]; b = new double[N]; c = new double[N]; d = new double[N]; if (N == 2) { a[0] = yy[0]; b[0] = yy[1] - yy[0]; return; } double[] h = new double[N - 1]; for (int i = 0; i < N - 1; i++) { a[i] = yy[i]; h[i] = xx[i + 1] - xx[i]; // h[i] is used for division later, avoid a NaN if (h[i] == 0.0) { h[i] = 0.01; } } a[N - 1] = yy[N - 1]; double[][] A = new double[N - 2][N - 2]; double[] y = new double[N - 2]; for (int i = 0; i < N - 2; i++) { y[i] = 3 * ((yy[i + 2] - yy[i + 1]) / h[i + 1] - (yy[i + 1] - yy[i]) / h[i]); A[i][i] = 2 * (h[i] + h[i + 1]); if (i > 0) { A[i][i - 1] = h[i]; } if (i < N - 3) { A[i][i + 1] = h[i + 1]; } } solve(A, y); for (int i = 0; i < N - 2; i++) { c[i + 1] = y[i]; b[i] = (a[i + 1] - a[i]) / h[i] - (2 * c[i] + c[i + 1]) / 3 * h[i]; d[i] = (c[i + 1] - c[i]) / (3 * h[i]); } b[N - 2] = (a[N - 1] - a[N - 2]) / h[N - 2] - (2 * c[N - 2] + c[N - 1]) / 3 * h[N - 2]; d[N - 2] = (c[N - 1] - c[N - 2]) / (3 * h[N - 2]); } /** * Solves Ax=b and stores the solution in b. */ public void solve(double[][] A, double[] b) { int n = b.length; for (int i = 1; i < n; i++) { A[i][i - 1] = A[i][i - 1] / A[i - 1][i - 1]; A[i][i] = A[i][i] - A[i - 1][i] * A[i][i - 1]; b[i] = b[i] - A[i][i - 1] * b[i - 1]; } b[n - 1] = b[n - 1] / A[n - 1][n - 1]; for (int i = b.length - 2; i >= 0; i--) { b[i] = (b[i] - A[i][i + 1] * b[i + 1]) / A[i][i]; } }
}
</source>