Reputation: 7159
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:
Upvotes: 0
Views: 1923
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
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:
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
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-loop
s 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
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
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