The user has to pick 4 points by clicking anywhere in the frame, and then the program is supposed to draw a Bezier curve. I've also included a method that draws small circles where the user click so it's easier to see.
I'm not getting any errors, but the curve is just not showing. Obviously, I'm missing something, but I can't figure out what.
Code:
public class Splines {
public Splines(){
JFrame frame = new JFrame("Bezier curves");
frame.add(new draw());
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public class draw extends JPanel implements MouseListener{
Point[] controlPoints = new Point[100];
ArrayList<Point> punkter = new ArrayList<>();
int pSize = punkter.size();
public draw(){
addMouseListener(this);
}
@Override
public void mousePressed(MouseEvent e) {
if (pSize==4) drawBezier(pSize,4,getGraphics());
drawPoint(e);
pSize++;
}
//Method drawing points to visualize the control points
public void drawPoint(MouseEvent evt){
Graphics g = getGraphics();
Graphics2D g2d = (Graphics2D) g;
punkter.add(new Point(evt.getX(), evt.getY()));
g2d.setColor(Color.red);
g2d.fillOval(punkter.get(pSize).x, punkter.get(pSize).y, 5, 5);
controlPoints[pSize] = punkter.get(pSize);
}
public void drawBezier(int i, int n, Graphics g) {
int j;
double t, delta;
Point curvePoints[] = new Point[n + 1];
delta = 1.0 / n;
for (j = 0; j <= n; j++) {
t = j * delta;
curvePoints[j] = new Point();
curvePoints[j].x = (int) Math.round(controlPoints[i - 3].x * (1.0 - t) * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 2].x * 3.0 * t * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 1].x * 3.0 * t * t * (1.0 - t)
+ controlPoints[i].x * t * t * t);
curvePoints[j].y = (int) Math.round(controlPoints[i - 3].y * (1.0 - t) * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 2].y * 3.0 * t * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 1].y * 3.0 * t * t * (1.0 - t)
+ controlPoints[i].y * t * t * t);
}
g.setColor(Color.red);
for (j = 0; j < n; j++)
g.drawLine(curvePoints[j].x, curvePoints[j].y, curvePoints[j + 1].x, curvePoints[j + 1].y);
} // End drawBezier
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public Dimension getPreferredSize() {
return new Dimension(600, 400);
}
}//End draw class
public static void main(String[] args) {
new Splines();
}//End main method
}//End Spline class
Although I strongly advise you to take into consideration what both MadProgrammer and Petter Friberg pointed out about painting in AWT
and Swing
, your immediate problem here is quite trivial.
Specifically, it lies within the drawBezier()
method which raises a NullPointerException
at the very first time it tries to access the controlPoints
array.
This, of course, happens because you are trying to access controlPoints[i]
when, in fact, i
has a value of 4 and controlPoints
is zero-based which means that you are referencing a, practically, non-existent element (
controlPoints[4]
is null
). See more on array initialization in Thorbjørn Ravn Andersen's answer, here.
The solution should be obvious to you by now:
curvePoints[j].x = (int) Math.round(controlPoints[i - 4].x * (1.0 - t) * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 3].x * 3.0 * t * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 2].x * 3.0 * t * t * (1.0 - t)
+ controlPoints[i - 1].x * t * t * t);
curvePoints[j].y = (int) Math.round(controlPoints[i - 4].y * (1.0 - t) * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 3].y * 3.0 * t * (1.0 - t) * (1.0 - t)
+ controlPoints[i - 2].y * 3.0 * t * t * (1.0 - t)
+ controlPoints[i - 1].y * t * t * t);
which delivers this:
I also made a small edit in mousePressed()
, so that it returns after the Bezier curve is drawn:
@Override
public void mousePressed(MouseEvent e) {
if (pSize==4) {
drawBezier(pSize,4,getGraphics());
return;
}
drawPoint(e);
pSize++;
}
If you do this edit, then try this:
After you click 5 times the Bezier curve is drawn. Now, minimize the window and restore it. You will see that it is blank. However, if you click once more inside you will see that the curve reappears (only the curve; not the control points). Why does this happen?
If you can answer this then you are getting one step closer to understanding some of the issues mentioned in the comments and Petter Friberg's answer.
Update / Appendix - Giving Persistence
The whole idea is to find a way to make your drawing persistent to arbitrary user actions, eg minimizing, resizing etc. As Petter Friberg pointed out, the way to do this is to override the paintComponent()
method.
How to do this? Well, actually it's quite simple:
First remove your drawPoint()
method completely. It's functionality will be split among mousePressed()
and paintComponent()
, like this:
@Override
public void mousePressed(MouseEvent e) {
if (pSize<4){
punkter.add(new Point(e.getX(), e.getY()));
controlPoints[pSize] = punkter.get(pSize);
pSize++;
repaint();
} else if (pSize==4){
// do other stuff, for example reset everything and start over
//pSize = 0;
//punkter.clear();
//for (int i= 0; i < controlPoints.length; i++){
// controlPoints[i]= null;
//}
//repaint();
//System.out.println("Reset");
}
}
and this:
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.red);
for (int i=0; i<pSize; i++){
g2d.fillOval(punkter.get(i).x, punkter.get(i).y, 5, 5);
}
if (pSize==4) {
drawBezier(pSize,4,g);
}
}
One thing worth pointing out here, is that you still need to invoke the superclass's paintComponent()
method and this is achieved in this line : super.paintComponent(g);
Also, getGraphics()
is no longer valid (if it ever was) as an argument to drawBezier()
; instead, the g
Graphics object is used.
By now the application has the persistence it lacked before.
Notice that there is nowhere in the code a direct call to paintComponent()
. Instead, it is invoked by the graphics subsystem when you call repaint()
, or the system decides that it is time to redraw. One way to see this is to comment out the call to repaint()
. Now there is not a single call to any drawing function. Does that mean that nothing gets drawn? Or, maybe, you can indirectly invoke the drawing by performing some, seemingly irrelevant, actions?