Search code examples
javaswingpaintcomponentgraphics2dmousemotionlistener

How to draw a continuous curve of repeated ovals on speedy mouse cursor dragging?


This code is for drawing on a JPanel. In the paintComponent(Graphics) I am trying to draw curves via repeated Graphics2D#fillOval(x, y, with, height).

The app is working OK, and when I drag the mouse cursor slowly; it draws a continuous curve as I need. But when I speed up dragging the mouse cursor, the result is separated dots and not a continuous curve.

So how to make it draw a continuous curve even if I speed up dragging?

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;

public class Painter extends JPanel {

    int x, y;
    ArrayList<Point> points;

    public Painter() {

        setBackground(Color.white);
        points = new ArrayList<>();

        MouseHandler listener = new MouseHandler();
        this.addMouseMotionListener(listener);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(600, 600);
    }

    private class MouseHandler extends MouseAdapter implements MouseMotionListener {

        @Override
        public void mouseDragged(MouseEvent e) {
            Point point = new Point(e.getX(), e.getY());
            points.add(point);
            repaint();
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        for (Point point : points) {
            g2d.fillOval(point.x, point.y, 15, 15);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame();
                f.setContentPane(new Painter());
                f.pack();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setVisible(true);
            }
        });
    }
}

Solution

  • As mentioned in comment to your previous similar question:

    • Don't draw discrete ovals in your paintComponent method.
    • Instead connect the points hold in the List in the paintComponent by drawing lines between adjacent points.
    • If you need to make the line thicker, change the Stroke property of the Graphics2D object, using one that has a wider thickness.
    • Be careful with Strokes however since often you don't want the property change to propagate down the paint chain. This means that sometimes you will want to copy the Graphics object and set the Stroke on the new Graphics object and paint with that, then dispose of it.
    • The simplest way to create a Stroke is to use the BasicStroke class, e.g., new BasicStroke(6f) will get you a nice thick curve.

    For example:

    import java.awt.*;
    import java.awt.event.*;
    import java.util.ArrayList;
    
    import javax.swing.*;
    
    public class Painter2 extends JPanel {
    
        private static final float STROKE_WIDTH = 15f;
        private static final Stroke STROKE = new BasicStroke(STROKE_WIDTH, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        int x, y;
        ArrayList<Point> points;
    
        public Painter2() {
    
            setBackground(Color.white);
            points = new ArrayList<>();
    
            MouseHandler listener = new MouseHandler();
            this.addMouseMotionListener(listener);
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(600, 600);
        }
    
        private class MouseHandler extends MouseAdapter implements MouseMotionListener {
    
            @Override
            public void mouseDragged(MouseEvent e) {
                Point point = new Point(e.getX(), e.getY());
                points.add(point);
                repaint();
            }
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
    
            Graphics2D g2d = (Graphics2D) g;
            g2d.setStroke(STROKE);
            for (int i = 1; i < points.size(); i++) {
                int x1 = points.get(i - 1).x;
                int y1 = points.get(i - 1).y;
                int x2 = points.get(i).x;
                int y2 = points.get(i).y;
                g2d.drawLine(x1, y1, x2, y2);
            }
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame f = new JFrame();
                    f.setContentPane(new Painter2());
                    f.pack();
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        }
    }
    

    Or better still:

    import java.awt.*;
    import java.awt.event.*;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class Painter2 extends JPanel {
    
        private static final float STROKE_WIDTH = 15f;
        private static final Stroke STROKE = new BasicStroke(STROKE_WIDTH, BasicStroke.CAP_ROUND,
                BasicStroke.JOIN_ROUND);
        private static final Color CURVES_COLOR = Color.BLUE;
        private static final Color TEMP_CURVE_COLOR = Color.PINK;
        private List<List<Point>> curvesList = new ArrayList<>();
        private List<Point> tempCurve = null;
    
        public Painter2() {
            setBackground(Color.white);
            MouseHandler listener = new MouseHandler();
            addMouseListener(listener);
            addMouseMotionListener(listener);
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(600, 600);
        }
    
        private class MouseHandler extends MouseAdapter implements MouseMotionListener {
            @Override
            public void mousePressed(MouseEvent e) {
                tempCurve = new ArrayList<>();
                tempCurve.add(e.getPoint());
                repaint();
            }
    
            @Override
            public void mouseDragged(MouseEvent e) {
                tempCurve.add(e.getPoint());
                repaint();
            }
    
            @Override
            public void mouseReleased(MouseEvent e) {
                tempCurve.add(e.getPoint());
                curvesList.add(tempCurve);
                tempCurve = null;
                repaint();
            }
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
    
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setStroke(STROKE);
            g2.setColor(CURVES_COLOR);
            for (List<Point> curve : curvesList) {
                drawCurve(g2, curve);
            }
    
            if (tempCurve != null) {
                g2.setColor(TEMP_CURVE_COLOR);
                drawCurve(g2, tempCurve);
            }
    
            g2.dispose();
        }
    
        private void drawCurve(Graphics2D g2, List<Point> ptList) {
            for (int i = 1; i < ptList.size(); i++) {
                int x1 = ptList.get(i - 1).x;
                int y1 = ptList.get(i - 1).y;
                int x2 = ptList.get(i).x;
                int y2 = ptList.get(i).y;
                g2.drawLine(x1, y1, x2, y2);
            }
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame f = new JFrame();
                    f.setContentPane(new Painter2());
                    f.pack();
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        }
    }