How to draw lines following the mouse movement?

Asked

Viewed 899 times

1

I need a JPanel, that draws lines following the mouse pointer while it is dragged, and when released it needs to stop drawing.

It turns out that whenever two or more lines are drawn in a row, they connect to the previous lines, this should not occur.

How to drag lines without them connecting themselves, similar to Microsoft’s Paint software?

Here is my code:

    package test;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

//Essa classe tem o objetivo de imitar o recurso de desenhar linhas do softwere paint, da Microsoft.

public class DrawingSketch extends JFrame {



    private static final long serialVersionUID = 5661286812709693531L;
    private JPanel contentPane;
    private JPanel draft;
    private int x;
    private int y;
    private ArrayList<Integer> cX = new ArrayList<>();
    private ArrayList<Integer> cY = new ArrayList<>();
    private Integer xa;
    private Integer ya;
    protected int mX;
    protected int mY;
    public int i;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    DrawingSketch frame = new DrawingSketch();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public DrawingSketch() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 900, 700);
        setLocationRelativeTo(null);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);

        draft = new Draft();
        draft.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
                //As coordenadas x e y estão sendo armazenadas nas coleções cX e CY respectivamente.
                cX.add(e.getX());
                cY.add(e.getY());
                draft.repaint();
            }
        });
        draft.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        });
        contentPane.add(draft, BorderLayout.CENTER);
        draft.setLayout(null);
    }

    public class Draft extends JPanel {

        /**
         * 
         */
        private static final long serialVersionUID = 4886600019364448097L;

        public Draft() {

        }

        @Override
        protected void paintComponent(Graphics g) {
            // TODO Auto-generated method stub
            super.paintComponent(g);
            for (i = 1; i < largerBeetweenXeY(cX.size(), cY.size()); i++) {
                // x é o valor final de x.
                x = cX.get(i);
                //y é o valor final de y.
                y = cY.get(i);
                // xa é o valor inicial de x. 
                xa = cX.get(i - 1);
                // ya é o valor inicial de y.
                ya = cY.get(i - 1);

                // Observação, se for usada a formula: xa = cX.get(i) e ya = cY.get(i), sem o i -1, o que é obtido e uma sequência de pontos
                // desconexos, ou seja, com espaço vazio entre eles. As formulas xa = cX.get(i - 1) e ya = cY.get(i - 1), garantem que a linha
                // fique perfeita, no entanto, com o traçado de duas ou mais linhas, as linhas se ligam, o que não deveria acontecer.

                Graphics2D g1 = (Graphics2D)g;
                RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g1.setRenderingHints(rh);
                g1.drawLine(xa, ya, x, y);
            }
        }

    }

    /**
     * @param int size valor de x.
     * @param int size2 valor de y.
     * Verifica o limite da iteração em paintComponent.
     */
    public int largerBeetweenXeY(int size, int size2) {
        if (size > size2) {
            return size;
        } else {
            return size2;
        }
    }
}

1 answer

4


The most practical method is at the end of the answer, after the Updating


What happens is that you save all the coordinates in the list, and each time you finish and start a new line, the paintComponent() redesigns the screen using all stored coordinates, including the final coordinate of one line and the initial of another, as he does not know how to differentiate this.

An interesting way to solve this I found is to insert "negative coordinates", so that it is possible to check when one line ended and when another started.

To make it easier to work with coordinates, it is recommended that you work with the class Point, so there is no need to create 2 lists to store each coordinate axis.

Create a variable in your main class that represents a list of coordinates:

private ArrayList<Point> points = new ArrayList<>();

is where we store the coordinates, including the "null".

In the method mouseDragged(), add each coordinate where the mouse drags within the component area to the list, and then force the redesign. By changing your code, it was the way below:

draft.addMouseMotionListener(new MouseMotionAdapter() {
    
    @Override
    public void mouseDragged(MouseEvent e) {
        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));

        points.add(e.getPoint());
        draft.repaint();
    }
    
});

In the method mouseReleased(), add the final coordinate and the "negative" coordinate to the list. This point with negative coordinates will serve as a reference to identify that a line has ended.

draft.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseReleased(MouseEvent e) {
        //adiciona uma coordenada "nula" para ignorarmos
        //no paintComponent
        points.add(e.getPoint());
        points.add(new Point(-1, -1));
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
});

Now, in the paintComponent(), We need to control how the lines are drawn. The shape I used was similar to yours, taking a point and its successor to create each segment of the lines. The "cat jump" is to check if the next coordinate of the current iteration is "negative", if it is, we skip 2 iterations, because we don’t want to connect the end of one line with the beginning of the other:

@Override
protected void paintComponent(Graphics g) {
    // TODO Auto-generated method stub
    super.paintComponent(g);

    int i = 0;
    while (i < points.size() - 1) {
        Point currentPoint = points.get(i);
        Point nextPoint = points.get(i + 1);

        if (nextPoint.x != -1 && nextPoint.y != -1) {
            Graphics2D g1 = (Graphics2D) g;
            RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g1.setRenderingHints(rh);
            g1.drawLine(currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y);
            i++;

        } else {
            // quando as coordenadas do ponto seguinte forem (-1, -1),
            // pulamos essa iteração para evitar que a linha anterior
            // seja ligada a nova linha que está sendo desenhada
            i += 2;
        }
    }
}

Thus, the loop will jump between the end point of a line and the negative point, going straight to the beginning point of the other line.

Your class with the changes will be much cleaner and simplified, see:

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

//Essa classe tem o objetivo de imitar o recurso de desenhar linhas do softwere paint, da Microsoft.

public class DrawingSketch extends JFrame {

    private static final long serialVersionUID = 5661286812709693531L;
    private JPanel contentPane;
    private JPanel draft;

    private ArrayList<Point> points = new ArrayList<>();

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    DrawingSketch frame = new DrawingSketch();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public DrawingSketch() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 900, 700);
        setLocationRelativeTo(null);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);

        draft = new Draft();
        draft.addMouseMotionListener(new MouseMotionAdapter() {

            @Override
            public void mouseDragged(MouseEvent e) {
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));

                points.add(e.getPoint());
                draft.repaint();
            }

        });
        draft.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                // adiciona uma coordenada nula para ignorarmos
                // no paintComponent
                points.add(e.getPoint());
                points.add(new Point(-1, -1));
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        });
        contentPane.add(draft, BorderLayout.CENTER);
        draft.setLayout(null);
    }

    public class Draft extends JPanel {

        /**
         * 
         */
        private static final long serialVersionUID = 4886600019364448097L;

        public Draft() {

        }

        @Override
        protected void paintComponent(Graphics g) {
            // TODO Auto-generated method stub
            super.paintComponent(g);
    
            int i = 0;
            while (i < points.size() - 1) {
                Point currentPoint = points.get(i);
                Point nextPoint = points.get(i + 1);
    
                if (nextPoint.x != -1 && nextPoint.y != -1) {
                    Graphics2D g1 = (Graphics2D) g;
                    RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
                    g1.setRenderingHints(rh);
                    g1.drawLine(currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y);
                    i++;
    
                } else {
                    // quando as coordenadas do ponto seguinte forem (-1, -1),
                    // pulamos essa iteração para evitar que a linha anterior
                    // seja ligada a nova linha que está sendo desenhada
                    i += 2;
                }
            }
        }
    }
}

Working:

inserir a descrição da imagem aqui


Updating

There is a much easier and practical way to do this, without having to store and redesign a list of points on the screen. The solution is to use BufferedImage, that nothing else is an image-shaped representation of the drawing. Thus, the work of the paintComponent() is much smaller than iterating a list with numerous dots.

I leave the code below with comments on the changes:

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class Draft extends JPanel {

    //usaremos para armazenar e desenhar novas imagens
    private BufferedImage bfImage;
    //usaremos para desenhar as linhas conforme movimentacao
    //do mouse na tela
    private Point oldPoint = null;
    private Point newPoint = null;
    
    private static final long serialVersionUID = 4886600019364448097L;

    public Draft() {

        //crio uma instancia de bufferedimage do mesmo tamanho do painel
        bfImage = new BufferedImage(getSize().width, getSize().height, BufferedImage.TYPE_INT_ARGB);
        
        addMouseMotionListener(new MouseMotionAdapter() {

            @Override
            public void mouseDragged(MouseEvent e) {
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
                //armazenamos o novo ponto arrastado
                //atualizamos a imagem na tela
                //invertemos os pontos, pois após tela desenhada
                //o ponto atual passa a ser antigo
                newPoint = e.getPoint();
                updateImage();
                oldPoint = newPoint;
            }

        });
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                //ao liberar o mouse, armazenamos o ponto atual para finalizar
                //o desenho da atual linha e "limpamos" as duas referencias de pontos                    
                newPoint = e.getPoint();
                updateImage();
                newPoint = null;
                oldPoint = null;
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }

            @Override
            public void mousePressed(MouseEvent e) {
                //ao pressionar o mouse, verificamos se o ponto antigo existe
                //pois ele é o ponto de partida para desenhar o primeiro segmento
                //da nova linha
                if (oldPoint == null) {
                    oldPoint = e.getPoint();
                }
            }

        });
    }
    
    //sobrescrevi o método getSize para que o tamanho do painel possa
    //ser informado corretamente ao bufferedImage
    @Override
    public Dimension getSize() {
        return new Dimension(500, 350);
    }

    private void updateImage() {
        //se o ponto atual não for nulo, criamos um grafico no buffer e desenhamos o
        //segmento da linha atual, forçando o redesenho da tela com repaint()
        if (newPoint != null) {

            Graphics2D g2 = bfImage.createGraphics();
            g2.setColor(Color.BLACK);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.drawLine(oldPoint.x, oldPoint.y, newPoint.x, newPoint.y);
            g2.dispose();
            repaint();
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        //apenas passamos o buffer para o paintcomponent desenhar
        //o que está nele.
        g.drawImage(bfImage, 0, 0, null);
    }
}

And to use, just add an instance to your screen:

seuFrame.add(new Draft());

With these changes, they increase the possibilities, including saving an image as a file, to recover an already saved drawing, among others that mspaint already does. :)

  • 1

    @jsantos1991 opa, went unnoticed this, corrected, thanks for the alert :D

Browser other questions tagged

You are not signed in. Login or sign up in order to post.