1

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
mKorbel
  • 109,525
  • 20
  • 134
  • 319
rasperryPi
  • 377
  • 2
  • 4
  • 12
  • 1
    Don't use `getGraphics`, this is not how custom painting works in Swing. See [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) for more details about how painting works in Swing – MadProgrammer Oct 16 '15 at 21:57
  • What should I be looking for in these articles that are relevant to my problem? – rasperryPi Oct 16 '15 at 22:14
  • It would be really helpful if you could elaborate. I'm quite a beginner when it comes to understanding how Java works, so it would help me understand why it's not working. – rasperryPi Oct 16 '15 at 22:20

2 Answers2

3

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:

enter image description here


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?

Community
  • 1
  • 1
sokin
  • 824
  • 2
  • 13
  • 20
  • It is in fact a problem that anytime a write a code that includes GUI, the graphics in the screen disappear whenever I resize or minimize the window. I've read Petter Friberg's several times trying to understand what it is that is causing this problem, but I think I would need some more explaining. Would you care to explain a little deeper? Also, your suggestion solved my immediate problem, so thanks for that! – rasperryPi Oct 18 '15 at 00:37
  • 1
    Glad I helped. Now, the comment area is rather small for an explanation so I will append a small section to my answer, that you can use as a starting point. – sokin Oct 18 '15 at 21:39
2

The JPanel to draw itself calls the protected void paintComponent(Graphics gr)

I suggest that you override this metod, put up a List<Point> of where the user clicks and in this metod you draw all your curves.

Yuor code goes something like this

@Override
protected void paintComponent(Graphics g) {
  super.paintComponent(g)
  ...loop your points and draw your cruves on g
}

So when user do mousePressed add to the List<Point> and call the repaint metod on your JPanel

Hence now you draw on the Graphics but when the JPanel is refreshed by your swing application in draws the components that you add to it (that are none). Bascially your are drawing the curves out of the swing context to a Grafics g that will be redrawn when swing decieds in the paintComponent.

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
  • Could you elaborate on some of these points regarding why my program is not working. I'm a beginner when it comes to understanding how Java works, so it would really help me understand why it's not working. – rasperryPi Oct 16 '15 at 22:19
  • 1
    Swing decide when its time to repaint your application (for example when you resize the frame, you can call the repaint function on a JComponent to force the repaint, when repaint is done swing calls the paintComponent(Graphics g). So basically what you where doing was out of context – Petter Friberg Oct 16 '15 at 22:27
  • 1
    Furthmore swing tends to avoid repainting as this is time consuming thats why sometimes you can have a problem with this process, because your repaint call is a suggestion to swing that will execute your command when its repaint thread is ready – Petter Friberg Oct 16 '15 at 22:29