I am new to java and I am trying to draw a curve. The functionality I am trying to implement is that the curves should be defined by a mouse drag followed by a mouse click.
Once the drag action is done, I want a line to be drawn from the start of the drag to the end. Clicking on the canvas after that should be considered the third point necessary to define the curve and the line drawn should be changed into a curve.
How do I do that?
I've gone through different posts here about how to draw Bezier curves but I am super confused. Here's a chunk of my code to draw rectangles, ovals and lines using mouse events:
public void mousePressed(MouseEvent e) {
xCoordinateInitial = e.getX(); //Initialize x-coordinate to the mouse x-coordinate
yCoordinateInitial = e.getY() + shiftInY; //Initialize y-coordinate to the mouse y-coordinate
System.out.println("X-coordinate: " + xCoordinateInitial);
System.out.println("Y-coordinate: " + yCoordinateInitial);
}
public void mouseReleased(MouseEvent e) {
Graphics2D G = (Graphics2D) getGraphics();
G.setStroke(new BasicStroke(lineThickness));
G.setColor(colorSelected);
G.setPaint(colorSelected);
xCoordinateFinal = e.getX(); //Set final x-coordinate to the mouse x-coordinate after drag
yCoordinateFinal = e.getY() + shiftInY; //Set final y-coordinate to the mouse y-coordinate after drag
int x = xCoordinateInitial;
int y = yCoordinateInitial;
int width = xCoordinateFinal - xCoordinateInitial; //Setting width
int height = yCoordinateFinal - yCoordinateInitial; //Setting height
if (yCoordinateFinal < yCoordinateInitial) {
y = yCoordinateFinal;
height = yCoordinateInitial - yCoordinateFinal;
}
if (xCoordinateFinal < xCoordinateInitial) {
x = xCoordinateFinal;
width = xCoordinateInitial - xCoordinateFinal;
}
// Shape Selection
switch (shapeSelected) {
case Line:
G.drawLine(xCoordinateInitial, yCoordinateInitial, xCoordinateFinal, yCoordinateFinal);
break;
case Rectangle:
G.fillRect(x, y, width, height);
break;
case Oval:
G.fillOval(x, y, width, height);
break;
case Curve :
// To implement
}
}
});
I tried to understand the following but I couldn't make it work:
Path2D p = new GeneralPath();
p.moveTo(x1, y1);
p.curveTo(bx1, by1, bx2, by2, x2, y2);
G.draw(p);
Let's start by having a read through the JavaDocs for Path2D#curveTo
public final void curveTo(double x1,
double y1,
double x2,
double y2,
double x3,
double y3)
Adds a curved segment, defined by three new points, to the path by drawing a Bézier curve that intersects both the current coordinates and the specified coordinates (x3,y3), using the specified points (x1,y1) and (x2,y2) as Bézier control points. All coordinates are specified in double precision.
Okay, let's be fair, that takes a little bit of reading to understand, which is probably better addressed with some graphics.
The basic concept is, you have three control points, one of which is acting as a "look at" constraint.
Let's assume you have three points, anchor
, target
and click
. anchor
is the first point, target
is the point dragged to and click
is the point which was clicked AFTER the drag operation completed.
If we use the order anchor, target, click
, we end up with a curve looking something like
where P1 == anchor
, P2 == target
and P3 == click
. As you can see, the P2
is acting as the "look at constraint" and is trying to pull the resulting curve towards it.
However, if, instead, we use anchor, click, target
, we end up within something more like...
where P1 == anchor
, P2 == click
and P3 == target
. As you can see, the P2
is acting as the "look at constraint" and is trying to pull the resulting curve towards it.
This is essentially how a Bézier curve works
Okay, so to put it all together, I wrote some test code, which you can use to change the parameter and test the idea
One thing this example does is it paints a "fake" example of the intended curve as the mouse is moved, after the first two points are established, giving you some visual feed back of what the curve will look like, until the user clicks the mouse and the path is set
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private boolean dragging = false;
private Point anchor;
private Point target;
private Shape fakePath;
private List<Shape> shapes = new ArrayList<>(25);
public TestPane() {
addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
dragging = true;
target = new Point(e.getPoint());
repaint();
}
@Override
public void mouseMoved(MouseEvent e) {
if (target != null && anchor != null) {
fakePath = makePath(anchor, e.getPoint(), target);
repaint();
}
}
});
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (anchor == null) {
target = null;
anchor = new Point(e.getPoint());
}
}
@Override
public void mouseReleased(MouseEvent e) {
dragging = false;
}
@Override
public void mouseClicked(MouseEvent e) {
if (anchor != null && target != null) {
fakePath = null;
shapes.add(makePath(anchor, e.getPoint(), target));
anchor = null;
target = null;
repaint();
}
}
});
}
protected Path2D makePath(Point p1, Point p2, Point p3) {
Path2D p = new GeneralPath();
p.moveTo(p1.getX(), p1.getY());
p.curveTo(p1.getX(), p1.getY(),
p2.getX(), p2.getY(),
p3.getX(), p3.getY());
return p;
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.setColor(Color.BLACK);
for (Shape shape : shapes) {
g2d.draw(shape);
}
if (anchor != null && target != null) {
g2d.setColor(Color.GREEN);
g2d.draw(new Line2D.Double(anchor, target));
}
if (fakePath != null) {
g2d.setColor(Color.BLUE);
g2d.draw(fakePath);
}
g2d.dispose();
}
}
}