2

I've been working with Path2D.Double for about a day or so now and in this post I asked for help regarding setting control points to allow the path to pass through specified points. I mashed together some code as a quick test bed that randomly generates points and then curves the path to those points based on the information mentioned in the above post.

Here is the runnable code, sorry it's monolithic, I wanted it to be postable so you can follow along:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;

public class Curver {
    public static ArrayList<Point2D> points = new ArrayList<Point2D>();
    /**
     * @param args
     */
    public static void main(String[] args) {

        final JFrame window = new JFrame();
        window.setPreferredSize(new Dimension(500,500));
        window.setLocationRelativeTo(null); 

        window.addMouseListener(new MouseListener() {

            @Override
            public void mouseReleased(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mousePressed(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mouseExited(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mouseEntered(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mouseClicked(MouseEvent e) {
                new Thread(new Runnable(){
                    @Override
                    public void run() {

                        Random random = new Random();

                        while(true){

                            points.add(new Point2D.Double(random.nextInt(window.getWidth()-1)+1,random.nextInt(window.getHeight()-1)+1));
                            System.out.println(points.size());

                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

                        }

                    }

                }).start();
            }
        });


        window.pack();
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(true);

        int div = 7;
        int d = 4;

        Path2D.Double path = new Path2D.Double();
        Point2D currentPoint;
        double startX, startY;
        double interiorX_In, interiorY_In;
        double interiorX_Out, interiorY_Out;
        double endX, endY;

        Graphics2D g;


        while(true){

            g = (Graphics2D) window.getGraphics();
            g.setColor(Color.blue);

            for(int i = 0; i < points.size(); i++){

                currentPoint = points.get(i);
                g.fillRect((int)currentPoint.getX(), (int)currentPoint.getY(), d, d);

                if (i == 0){ 
                    // Don't attempt any line drawing as we don't have enough points.
                    path.moveTo(currentPoint.getX(), currentPoint.getY());
                } else if (i == 1 && points.size() > 2){ 
                    // draw first curve with knowledge of third point (third point is not end point)
                    startX = points.get(i-1).getX() + (currentPoint.getX() - points.get(i-1).getX()) / div; // from start
                    startY = points.get(i-1).getY() + (currentPoint.getY() - points.get(i-1).getY()) / div;

                    interiorX_In = currentPoint.getX() - (points.get(i+1).getX() - points.get(i-1).getX()) / div; // to interior
                    interiorY_In = currentPoint.getY() - (points.get(i+1).getY() - points.get(i-1).getY()) / div;

                    path.curveTo(startX, startY, interiorX_In, interiorY_In, currentPoint.getX(), currentPoint.getY());

                } else if (i == 1){ // Only 2 points in list so use a straight line until more points are available.
                    g.drawLine((int) points.get(i-1).getX(), (int) points.get(i-1).getY(), (int)currentPoint.getX(), (int)currentPoint.getY());
                } else if (i >= 1 && i < points.size()-1){ // interior to interior edge.

                    interiorX_Out = points.get(i-1).getX() + (currentPoint.getX() - points.get(i-2).getX()) / div; // from interior
                    interiorY_Out = points.get(i-1).getY() + (currentPoint.getY() - points.get(i-2).getY()) / div;

                    interiorX_In = currentPoint.getX() - (points.get(i+1).getX() - points.get(i-1).getX()) / div; // to interior
                    interiorY_In = currentPoint.getY() - (points.get(i+1).getY() - points.get(i-1).getY()) / div;

                    path.curveTo(interiorX_Out, interiorY_Out, interiorX_In, interiorY_In, currentPoint.getX(), currentPoint.getY());

                } else { // from interior point to end point.

                    interiorX_Out = points.get(i-1).getX() + (currentPoint.getX() - points.get(i-2).getX()) / div; // from interior
                    interiorY_Out = points.get(i-1).getY() + (currentPoint.getY() - points.get(i-2).getY()) / div;

                    endX = currentPoint.getX() - (currentPoint.getX() - points.get(i-1).getX()) / div; // to end
                    endY = currentPoint.getY() - (currentPoint.getY() - points.get(i-1).getY()) / div;

                    path.curveTo(interiorX_Out, interiorY_Out, endX, endY, currentPoint.getX(), currentPoint.getY());

                }

            } // end for 

            g.clearRect(0, 0, window.getWidth(), window.getHeight());
            g.draw(path);
            path.reset();


            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } 
    } 
}
  • Just click the JFrame to start plotting random points - the console will report the number of points being plotted.

The issue I have is once I get upwards or 60-65 random points the path flickers and seems to shrink on screen and generally exhibits weird behaviour? As I said the code may not be perfect so could anyone give me some pointers as to how to make it less buggy.

I assume the weird behaviour comes from constructing longer and longer paths per repaint of the JFrame? Is there perhaps a way I could either draw the path as a contiguous line as I suppose the path is interpreted as a sequence of segments? Or perhaps make the path cumulative as opposed to reconstructing the whole thing for each iteration of the run loop ?

Looking forward to any advice offered - but please try to explain as I am relatively new to painting and paths etc.

Thanks in advance.


Thanks to @camickr for their post. Here is the re-written code that works flawlessly, 3,500+ random points and not a single glitch:

> import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Curver extends JFrame implements ActionListener, MouseListener{

    public Timer animationTicker = new Timer(50, this);
    public ArrayList<Point2D> points = new ArrayList<Point2D>();
    private Canvas canvas;

    public Curver(){
        this.setPreferredSize(new Dimension(500,500));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
        this.setLayout(new BorderLayout());
        canvas = new Canvas(points);
        this.add(canvas, BorderLayout.CENTER);
        this.addMouseListener(this);
        this.pack();
        this.setVisible(true);
    }

    class Canvas extends JPanel{

        int div = 7;
        int d = 4;

        Path2D.Double path = new Path2D.Double();
        Point2D currentPoint;
        double startX, startY;
        double interiorX_In, interiorY_In;
        double interiorX_Out, interiorY_Out;
        double endX, endY;
        Graphics2D g2;
        ArrayList<Point2D> points;

        public Canvas(ArrayList<Point2D> points){
            this.setMinimumSize(new Dimension(100,100));
            this.points = points;
        }

        @Override
        public void paintComponent(Graphics g) {

            g2 = (Graphics2D) g;
            g.setColor(Color.blue);

            synchronized(points){
            for(int i = 0; i < points.size(); i++){

                currentPoint = points.get(i);
                g2.fillRect((int)currentPoint.getX(), (int)currentPoint.getY(), d, d);

                if (i == 0){ 
                    // Don't attempt any line drawing as we don't have enough points.
                    path.moveTo(currentPoint.getX(), currentPoint.getY());
                } else if (i == 1 && points.size() > 2){ 
                    // draw first curve with knowledge of third point (third point is not end point)
                    startX = points.get(i-1).getX() + (currentPoint.getX() - points.get(i-1).getX()) / div; // from start
                    startY = points.get(i-1).getY() + (currentPoint.getY() - points.get(i-1).getY()) / div;

                    interiorX_In = currentPoint.getX() - (points.get(i+1).getX() - points.get(i-1).getX()) / div; // to interior
                    interiorY_In = currentPoint.getY() - (points.get(i+1).getY() - points.get(i-1).getY()) / div;

                    path.curveTo(startX, startY, interiorX_In, interiorY_In, currentPoint.getX(), currentPoint.getY());

                } else if (i == 1){ // Only 2 points in list so use a straight line until more points are available.
                    g2.drawLine((int) points.get(i-1).getX(), (int) points.get(i-1).getY(), (int)currentPoint.getX(), (int)currentPoint.getY());
                } else if (i >= 1 && i < points.size()-1){ // interior to interior edge.

                    interiorX_Out = points.get(i-1).getX() + (currentPoint.getX() - points.get(i-2).getX()) / div; // from interior
                    interiorY_Out = points.get(i-1).getY() + (currentPoint.getY() - points.get(i-2).getY()) / div;

                    interiorX_In = currentPoint.getX() - (points.get(i+1).getX() - points.get(i-1).getX()) / div; // to interior
                    interiorY_In = currentPoint.getY() - (points.get(i+1).getY() - points.get(i-1).getY()) / div;

                    path.curveTo(interiorX_Out, interiorY_Out, interiorX_In, interiorY_In, currentPoint.getX(), currentPoint.getY());

                } else { // from interior point to end point.

                    interiorX_Out = points.get(i-1).getX() + (currentPoint.getX() - points.get(i-2).getX()) / div; // from interior
                    interiorY_Out = points.get(i-1).getY() + (currentPoint.getY() - points.get(i-2).getY()) / div;

                    endX = currentPoint.getX() - (currentPoint.getX() - points.get(i-1).getX()) / div; // to end
                    endY = currentPoint.getY() - (currentPoint.getY() - points.get(i-1).getY()) / div;

                    path.curveTo(interiorX_Out, interiorY_Out, endX, endY, currentPoint.getX(), currentPoint.getY());

                }

            } // end for 

            g.clearRect(0, 0, this.getWidth(), this.getHeight());
            g2.draw(path);
            path.reset();
            }
        }
    } // end of inner class

    @Override
    public void actionPerformed(ActionEvent e) {
        this.canvas.repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("Thread running");
                Random random = new Random();

                while(true){
                    synchronized(points){
                        points.add(new Point2D.Double(random.nextInt(getWidth()-1)+1,random.nextInt(getHeight()-1)+1));
                        System.out.println(points.size());
                    }
                    try {
                        Thread.sleep(60);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

            }

        }).start();
    }

    @Override
    public void mousePressed(MouseEvent e) {}

    @Override
    public void mouseReleased(MouseEvent e) {}

    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e) {}


    public static void main(String[] args) {
        Curver curver = new Curver();
        curver.animationTicker.start();
    }
}
Community
  • 1
  • 1
James C
  • 464
  • 1
  • 4
  • 12
  • It might not be the main problem but it probably doesn't help that the points list is accessed from two threads without any synchronization. Try locking on the list with `synchronized (points) { ... }` while drawing and before adding points. – Boann Apr 08 '13 at 16:31
  • @Boann I understand what you are saying however this issue is repeatable without the thread in the mouse click handler - I only put it there because I was getting bored of 50 - 70 mouse clicks per debug. You will see if you move the random point logic out of the thread the problem remains. – James C Apr 08 '13 at 16:35

1 Answers1

4
g = (Graphics2D) window.getGraphics();

I didn't look at your code too closely but the forum recommendation is to not use the getGraphics() method to do painting.

Custom painting is done by overriding the paintComponent() method of a JPanel (or JComponent). Then you add the panel to the frame. Read the Swing tutorial for more information on custom painting.

Also, don't use "while (true)" logic. Swing will repaint as required. If you want animation of some kind then use a Swing Timer.

camickr
  • 321,443
  • 19
  • 166
  • 288