How to draw lines following the mouse movement?

1

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

It turns out that whenever you draw two or more rows in a row, they connect to the previous rows, this should not happen.

How do you manage to drag lines without them binding themselves, similar to Microsoft Paint software?

Here's 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;
        }
    }
}
    
asked by anonymous 13.03.2017 / 15:55

1 answer

4
  

The most practical method is at the end of the response after the Update

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

An interesting way to solve this is to insert "negative coordinates", so that you can check when a line has finished and when another line has started.

To make it easier to work with coordinates, we recommend that you work with the 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<>();

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

In the mouseDragged() ", add each coordinate where the mouse drags within the component area to the list, and then forces the redraw. Changing in your code, it looks like this:

draft.addMouseMotionListener(new MouseMotionAdapter() {

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

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

});

In the mouseReleased() ", add the final coordinate and the" negative "coordinate to the list. This point with negative coordinates is what 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 paintComponent() , we need to control how lines are drawn. The form I used was similar to yours, taking a point and its successor to create each segment of the lines. The "cat leap" is to check if the next coordinate of the current iteration is "negative", if it is, skip 2 iterations, since we do not want to connect the end of a 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;
        }
    }
}

In this way, the loop will jump between the end point of a line and the negative point, going straight to the starting point of the other line.

Your class with the changes will be much cleaner and simpler, 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;
                }
            }
        }
    }
}

Running:

Update

Thereisamucheasierandmorepracticalwayofdoingthis,withouthavingtostoreandredrawalistofpointsonthescreen.Theworkaroundistouse BufferedImage , which is nothing more an image representation of the drawing. Thus, paintComponent() work is much smaller than iterating through a list with many points.

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, the possibilities increase, including saving an image as a file, recovering a saved drawing, among others that msPaint already does. :)

    
15.03.2017 / 18:28