Jad Chahine
Jad Chahine

Reputation: 7159

How to add a background grid to JPanel when drawing lines?

I am developing a drawing tool in which the user can draw a line on a JPanel.

He will choose a start point then he drags this line to an end point to create the line.

Note that the Java Point class is used here to define the coordinates of each point.

Below are the simple code used in this scenario:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
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 javax.swing.JFrame;
import javax.swing.JPanel;

public class DrawLine extends JPanel {

    private MouseHandler mouseHandler = new MouseHandler();
    private Point p1 = new Point(0, 0);
    private Point p2 = new Point(0, 0);
    private boolean drawing;

    public DrawLine() {
        this.setPreferredSize(new Dimension(400, 200));
        this.addMouseListener(mouseHandler);
        this.addMouseMotionListener(mouseHandler);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.blue);
        g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(new BasicStroke(8,
            BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
        g.drawLine(p1.x, p1.y, p2.x, p2.y);
    }

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            drawing = true;
            p1 = e.getPoint();
            p2 = p1;
            repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            drawing = false;
            p2 = e.getPoint();
            repaint();
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (drawing) {
                p2 = e.getPoint();
                repaint();
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("LinePanel");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new DrawLine().display();
            }
        });
    }
} 

What I want to do is to draw a grid of 20 pixels each square.

And the user will choose the scale in cm so that every 20 pixels will be 50 cm for example.

While the user is drawing on the panel, the grid must stay as a background so that he/she can use it to determine the dimensions of the lines in cm.

To make it clearer, in C# I used a picturebox and assigned a background image of grid and used to draw on it as in the figure below:

enter image description here

Upvotes: 0

Views: 1923

Answers (3)

rdonuk
rdonuk

Reputation: 3956

You can draw all cells in paintComponent()

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
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.util.ArrayList;

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

public class DrawLine extends JPanel {

    private MouseHandler mouseHandler = new MouseHandler();
    private Point p1 = new Point(0, 0);
    private Point p2 = new Point(0, 0);
    private boolean drawing;

    //Store lines in an arraylist
    private ArrayList<Line> lines = new ArrayList<>();

    public DrawLine() {
        setBackground(Color.white);
        this.setPreferredSize(new Dimension(400, 200));
        this.addMouseListener(mouseHandler);
        this.addMouseMotionListener(mouseHandler);
    }

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

        //Grid start
        g.setColor(Color.lightGray);
        int sideLength = 20;
        int nRowCount = getHeight() / sideLength;
        int currentX = sideLength;
        for (int i = 0; i < nRowCount; i++) {
            g.drawLine(0, currentX, getWidth(), currentX);
            currentX = currentX + sideLength;
        }

        int nColumnCount = getWidth() / sideLength;
        int currentY = sideLength;
        for (int i = 0; i < nColumnCount; i++) {
            g.drawLine(currentY, 0, currentY, getHeight());
            currentY = currentY + sideLength;
        }
        //Grid end

        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.blue);
        g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(new BasicStroke(8,
            BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
        g.drawLine(p1.x, p1.y, p2.x, p2.y);

        //draw all previous lines
        for (int i = 0; i < lines.size(); i++) { 
            g.drawLine(lines.get(i).p1.x, lines.get(i).p1.y, lines.get(i).p2.x, lines.get(i).p2.y);
        }
    }

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            drawing = true;
            p1 = e.getPoint();
            p2 = p1;
            repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            drawing = false;
            p2 = e.getPoint();
            repaint();
            lines.add(new Line(p1, p2));
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (drawing) {
                p2 = e.getPoint();
                repaint();
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("LinePanel");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new DrawLine().display();
            }
        });
    }

    public class Line {
        Point p1;
        Point p2;

        public Line(Point p1, Point p2) {
            this.p1 = p1;
            this.p2 = p2;
        }
    }
} 

Upvotes: 1

MadProgrammer
MadProgrammer

Reputation: 347244

Per example each side of the little square in the grid will have a dimension of 5 cm , so when the user draw a line that takes 4 squares, the line will have a length of 20 cm.

In theory, you should be able to use Pythagorean theorem to calculate the PPI (AKA DPI) which goes something like

ppi = sqrt(wp^2+hp^2) / di

Where:

  • wp is the number of pixels wide
  • hp is the number of pixels high
  • di is the diagonal size in inches of the screen (ie 22")

For example, a screen of 2560x1600 by 22" gives a ppi of 137. The problem is, getting the diagonal size of the screen.

It has been suggested that you can use Toolkit.getDefaultToolkit().getScreenResolution(), but this is known to return an incorrect value (ie on my screen it returns 102 when it should return 137)

So, what to do? When else fails, fake it.

The following example defines a DPI of 96 (which is a very old common screen resolution, which Java/Swing use to based on, can't say it is now or not, but it use to be)

The following defines a grid of 5cmx5cm cells, with a total width/height of 20cm

Grid

import java.awt.Color;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    // The number of CMs per Inch
    public static final double CM_PER_INCH = 0.393700787d;
    // The number of Inches per CMs
    public static final double INCH_PER_CM = 2.545d;
    // The number of Inches per mm's
    public static final double INCH_PER_MM = 25.45d;

    /**
     * Converts the given pixels to cm's based on the supplied DPI
     *
     * @param pixels
     * @param dpi
     * @return
     */
    public static double pixelsToCms(double pixels, double dpi) {
        return inchesToCms(pixels / dpi);
    }

    /**
     * Converts the given cm's to pixels based on the supplied DPI
     *
     * @param cms
     * @param dpi
     * @return
     */
    public static double cmsToPixel(double cms, double dpi) {
        return cmToInches(cms) * dpi;
    }

    /**
     * Converts the given cm's to inches
     *
     * @param cms
     * @return
     */
    public static double cmToInches(double cms) {
        return cms * CM_PER_INCH;
    }

    /**
     * Converts the given inches to cm's
     *
     * @param inch
     * @return
     */
    public static double inchesToCms(double inch) {
        return inch * INCH_PER_CM;
    }

    public static final double SCREEN_DPI = 72.0;

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension((int)Math.round(cmsToPixel(20d, SCREEN_DPI)), (int)Math.round(cmsToPixel(20d, SCREEN_DPI)));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            double cellSize = cmsToPixel(5d, SCREEN_DPI);
            Rectangle2D cell = new Rectangle2D.Double(0, 0, cellSize, cellSize);

            g2d.setColor(Color.LIGHT_GRAY);
            double x = 0;
            double y = 0;
            //System.out.println("Columns = " + (getWidth() / cellSize));
            //System.out.println("Rows = " + (getHeight()/ cellSize));
            while (y + cellSize < getHeight()) {
                x = 0;
                while (x + cellSize < getWidth()) {
                    g2d.translate(x, y);
                    g2d.draw(cell);
                    g2d.translate(-x, -y);
                    x += cellSize;
                }
                y += cellSize;
            }

            g2d.dispose();
        }

    }
}

You might note, that when you resize the window, the grid does not update until there is enough room for a new row/column, this was done deliberately to demonstrate the algorithm.

You can allow the grid to overflow the visible bounds of the view by changing the while-loops which draw the grid...

while (y < getHeight()) {
    x = 0;
    while (x < getWidth()) {
        g2d.translate(x, y);
        g2d.draw(cell);
        g2d.translate(-x, -y);
        x += cellSize;
    }
    y += cellSize;
}

The point of all this is, you can no change the SCREEN_DPI to whatever you want. Want to do print preview, change it to 300 or 72 or whatever you want. Because it's a "known" value, you can convert from one resolution to another relatively easily.

Here's an idea, calculate the PPI of your screen and change the SCREEN_DPI to match and have a look at the results ;)

Thank you but your example is about scaling the lines to view them in real dimensions on the screen or upon printing. What I want is to determine a scale of centimeters to pixels. (several cm for 20 px), I edit the question now please check it.

So? Change your SCREEN_DPI to match, for example

public static final double SCREEN_DPI = cmsToInches(20);

Now you have a measurement which will allow for 20 pixels per cm

20 pixels per cm

Basically you need to know how many pixels represent a cm, then you can simply convert from cm's to pixels and back again...

1cm = 20pixels, 2cm = 40pixels, 3cm = ... so and so forth. I'm sure you can see how the math works for that

Upvotes: 1

djs
djs

Reputation: 4065

I'm not sure if this is the best solution, but this worked for me:

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

    // ADDED CODE --- draws horizontal lines --- //
    for (int i = 1; i < 500; i += 10) {
        g.drawLine(i, 1, i, 500);
    }

    // ADDED CODE --- draws vertical lines --- //
    for (int i = 1; i < 500; i += 10) {
        g.drawLine(1, i, 500, i);
    }

    Graphics2D g2d = (Graphics2D) g;
    g2d.setColor(Color.blue);
    g2d.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setStroke(new BasicStroke(8,
        BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));

    g.drawLine(p1.x, p1.y, p2.x, p2.y);
}

Instead of hard-coding that 500 as the upper-bound of the loop, that could be some value you choose as the maxWidth and maxHeight of your JPanel.

Upvotes: 0

Related Questions