I’m writing a program that lets the user draw a hollow rectangle on the screen on top of an image. They can also click on the image and the clicks are joined up to form polygons.
Joining up points works fine, but when the user drags to draw a rectangle, the previously drawn rectangles and polygons disappear. The runnable code is below;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ImageLabeller extends JFrame {
static boolean drawRectangle = false;
/**
* some java stuff to get rid of warnings
*/
private static final long serialVersionUID = 1L;
/**
* main window panel
*/
JPanel appPanel = null;
/**
* toolbox - put all buttons here
*/
JPanel toolboxPanel = null;
/**
* image panel - displays image and editing area
*/
static ImagePanel imagePanel;
/**
* handles New Object button action
*/
public ImageLabeller(String imageFilename) {
try {
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent event) {
System.out.println("Bye bye!");
System.exit(0);
}
});
// Create and set up the image panel.
// setup main window panel
setExtendedState(Frame.MAXIMIZED_BOTH);
appPanel = new JPanel();
appPanel.setLayout(new BoxLayout(appPanel, BoxLayout.Y_AXIS));
this.setContentPane(appPanel);
imagePanel = new ImagePanel(imageFilename);
imagePanel.setOpaque(true); // content panes must be opaque
imagePanel.setBorder(BorderFactory.createLineBorder(Color.green));
// create toolbox panel
toolboxPanel = new JPanel();
toolboxPanel.setBorder(BorderFactory.createLineBorder(Color.black));
JButton newPolyButton = new JButton("New object");
newPolyButton.setMnemonic(KeyEvent.VK_N);
// newPolyButton.setSize(50, 20);
newPolyButton.setToolTipText("Click to add new object");
newPolyButton.addActionListener(new DrawListener());
JButton newSquareButton = new JButton("New Square");
newSquareButton.setMnemonic(KeyEvent.VK_S);
// newPolyButton.setSize(50, 20);
newSquareButton.setEnabled(true);
newSquareButton.setToolTipText("Click to add new square");
newSquareButton.addActionListener(new SquareListener());
// add all buttons to toolboxPanel
toolboxPanel.add(newPolyButton);
toolboxPanel.add(newSquareButton);
// add all panels to appPanel
appPanel.add(toolboxPanel);
appPanel.add(imagePanel);
// appPanel.add(Box.createRigidArea(new Dimension(0,10)));
// display all the stuff
this.pack();
this.setVisible(true);
} catch (Exception e) {
System.err.println("Image: ");
e.printStackTrace();
}
}
public static void addNewPolygon() {
imagePanel.addNewPolygon();
}
public static void addNewRectangle() {
//imagePanel.addNewRectangle();
}
static class DrawListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
addNewPolygon();
}
}
static class SquareListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
drawRectangle = true;
imagePanel.drawingRectangle = true;
System.out.println(imagePanel.drawingRectangle);
}
}
public static void main (String args []) {
new ImageLabeller("/change to/a photo/ of your choice.jpg");
}
}
_
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class ImagePanel extends JPanel implements MouseListener,
MouseMotionListener {
Rectangle currentRectangle = null;
boolean drawingRectangle = false;
/**
* some java stuff to get rid of warnings
*/
private static final long serialVersionUID = 1L;
/**
* image to be tagged
*/
BufferedImage image = null;
/**
* list of current polygon's vertices
*/
ArrayList<Point> currentPolygon = null;
/**
* list of polygons
*/
ArrayList<ArrayList<Point>> polygonsList = null;
ArrayList<Rectangle> rectangleList = null;
/**
* extended constructor - loads image to be labelled
*
* @param imageName
* - path to image
* @throws Exception
* if error loading the image
*/
public ImagePanel(String imageName) throws Exception {
currentPolygon = new ArrayList<Point>();
polygonsList = new ArrayList<ArrayList<Point>>();
rectangleList = new ArrayList<Rectangle>();
image = ImageIO.read(new File(imageName));
Dimension panelSize = new Dimension(image.getWidth(), image.getHeight());
this.setSize(panelSize);
this.setMinimumSize(panelSize);
this.setPreferredSize(panelSize);
this.setMaximumSize(panelSize);
setBounds(0, 0, image.getWidth(), image.getHeight());
addMouseListener(this);
addMouseMotionListener(this);
this.setVisible(true);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Paint Component");
Graphics2D g2d = (Graphics2D) g;
// Paint image on screen
g2d.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
// display all the completed polygons
for (ArrayList<Point> polygon : polygonsList) {
drawPolygon(polygon);
finishPolygon(polygon);
System.out.println("Polly");
}
// Display all completed squares
for (Rectangle r : rectangleList) {
drawRectangle(r);
System.out.println("Square");
}
// display current polygon
if (currentPolygon != null) {
drawPolygon(currentPolygon);
}
// display current square
if (currentRectangle != null) {
drawRectangle(currentRectangle);
}
}
/**
* displays a polygon without last stroke
*
* @param polygon
* to be displayed
*/
public void drawPolygon(ArrayList<Point> polygon) {
Graphics2D g = (Graphics2D) this.getGraphics();
// set to red so I can see when it's being redrawn
g.setColor(Color.RED);
g.setStroke(new BasicStroke(3));
for (int i = 0; i < polygon.size(); i++) {
Point currentVertex = polygon.get(i);
if (i != 0) {
Point prevVertex = polygon.get(i - 1);
g.drawLine(prevVertex.getX(), prevVertex.getY(),
currentVertex.getX(), currentVertex.getY());
}
g.fillOval(currentVertex.getX() - 5, currentVertex.getY() - 5, 10,
10);
}
}
public void drawRectangle(Rectangle r) {
Graphics2D g = (Graphics2D) this.getGraphics();
g.setStroke(new BasicStroke(3));
g.setColor(Color.BLUE);
g.drawLine(r.getX1(), r.getY1(), r.getX2(), r.getY1());
g.drawLine(r.getX1(), r.getY1(), r.getX1(), r.getY2());
g.drawLine(r.getX2(), r.getY2(), r.getX2(), r.getY1());
g.drawLine(r.getX2(), r.getY2(), r.getX1(), r.getY2());
System.out.println(r.getX1() + " " + r.getY1() + " " + r.getX2());
System.out.println("Drawn rectangle");
}
/**
* displays last stroke of the polygon (arch between the last and first
* vertices)
*
* @param polygon
* to be finished
*/
public void finishPolygon(ArrayList<Point> polygon) {
// if there are less than 3 vertices than nothing to be completed
if (polygon.size() >= 3) {
Point firstVertex = polygon.get(0);
Point lastVertex = polygon.get(polygon.size() - 1);
Graphics2D g = (Graphics2D) this.getGraphics();
g.setColor(Color.GREEN);
g.setStroke(new BasicStroke(3));
g.drawLine(firstVertex.getX(), firstVertex.getY(),
lastVertex.getX(), lastVertex.getY());
}
}
/**
* moves current polygon to the list of polygons and makes pace for a new
* one
*/
public void addNewPolygon() {
// finish the current polygon if any
if (currentPolygon != null) {
finishPolygon(currentPolygon);
polygonsList.add(currentPolygon);
}
currentPolygon = new ArrayList<Point>();
}
public void mouseClicked(MouseEvent e) {
if (!drawingRectangle) {
int x = e.getX();
int y = e.getY();
// check if the cursor is within image area
if (x > image.getWidth() || y > image.getHeight()) {
// if not do nothing
return;
}
Graphics2D g = (Graphics2D) this.getGraphics();
// if the left button than we will add a vertex to poly
if (e.getButton() == MouseEvent.BUTTON1) {
g.setColor(Color.GREEN);
if (currentPolygon.size() != 0) {
Point lastVertex = currentPolygon
.get(currentPolygon.size() - 1);
g.setStroke(new BasicStroke(3));
g.drawLine(lastVertex.getX(), lastVertex.getY(), x, y);
}
g.fillOval(x - 5, y - 5, 10, 10);
currentPolygon.add(new Point(x, y));
System.out.println(x + " " + y + " polygon point");
}
}
}
public void mouseEntered(MouseEvent arg0) {
}
public void mouseExited(MouseEvent arg0) {
}
public void mousePressed(MouseEvent arg0) {
if (drawingRectangle) {
currentRectangle = new Rectangle(arg0.getX(), arg0.getY(),
arg0.getX(), arg0.getY(), Color.BLACK);
}
}
public void mouseReleased(MouseEvent arg0) {
if (drawingRectangle) {
rectangleList.add(new Rectangle(currentRectangle.getX1(),
currentRectangle.getY1(), arg0.getX(), arg0.getY(),
currentRectangle.getColor()));
System.out.println(currentRectangle.getX1() + " "
+ currentRectangle.getY1() + " " + arg0.getX() + " "
+ arg0.getY() + " rectangle point");
// unnecessary when mouseDragged calls paintComponent directly?
drawRectangle(new Rectangle(currentRectangle.getX1(),
currentRectangle.getY1(), arg0.getX(), arg0.getY(),
currentRectangle.getColor()));
currentRectangle = null;
drawingRectangle = false;
}
}
public void mouseDragged(MouseEvent arg0) {
if (drawingRectangle) {
currentRectangle = new Rectangle(currentRectangle.getX1(),
currentRectangle.getY1(), arg0.getX(), arg0.getY(),
currentRectangle.getColor());
System.out.println(currentRectangle.getX1() + " "
+ currentRectangle.getY1() + " " + arg0.getX() + " "
+ arg0.getX() + " " + "Dragging");
repaint();
// It works better using this instead on repaint()
// Graphics g = this.getGraphics();
// paintComponent(g);
}
}
public void mouseMoved(MouseEvent arg0) {
// TODO Auto-generated method stub
}
}
–
public class Point {
private int x = 0;
private int y = 0;
public Point() {
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
–
import java.awt.Color;
import java.awt.Graphics2D;
public class Rectangle {
// Initialize variables
private int x1; // x coordinate of first endpoint
private int y1; // y coordinate of first endpoint
private int x2; // x coordinate of second endpoint
private int y2; // y coordinate of second endpoint
private Color colour; // colour of the shape
// A no-parameter constructor that sets all the coordinates of the shape to
// 0 and the
// colour to Color.BLACK
public Rectangle() {
x1 = 0;
y1 = 0;
x2 = 0;
y2 = 0;
colour = Color.BLACK;
}
// A constructor that initializes the coordinates and colour to the values
// of the
// parameters supplied.
public Rectangle(int x1, int y1, int x2, int y2, Color col) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.colour = col;
}
public void setX1(int x1) {
this.x1 = x1;
}
public void setY1(int y1) {
this.y1 = y1;
}
public void setX2(int x2) {
this.x2 = x2;
}
public void setY2(int y2) {
this.y2 = y2;
}
public void setColor(Color colour) {
this.colour = colour;
}
public int getX1() {
return this.x1;
}
public int getY1() {
return this.y1;
}
public int getX2() {
return this.x2;
}
public int getY2() {
return this.y2;
}
public Color getColor() {
return this.colour;
}
public int getWidth() {
return (Math.abs(x2 - x1));
}
public int getHeight() {
return (Math.abs(y2 - y1));
}
public int getUpperLeftX() {
return (Math.min(x1, x2));
}
public int getUpperLeftY() {
return (Math.min(y1, y2));
}
}
Sorry for the mass of code, I tried to trim out as much as I could.
Clicking on the image draws points which are joined up to create lines. When the user clicks the “New Object” button, the first and last points are joined to create a polygon. This all works fine, but if you click “New Square” and drag on the image, all previous shapes flicker as the mouse is moved and disappear when the mouse button is released. If “New Square” is clicked again (a necessity of poor coding on my behalf so far) and another square drawn, the ‘disappeared’ shapes can be seen flickering again, but then disappear once the mouse is released.
I’m calling repaint() in the mouseDragged(…) event, which I thought was all that was necessary. It actually works (almost) as wanted when I change
repaint();
for
Graphics g = this.getGraphics();
paintComponent(g);
but every book and article I’ve read said I should never call paintComponent myself, ever. An issue with calling paintComponent however is that the background image tends to flicker alot.
If repaint() calls paintComponent, why do they lead to different results?
I also don’t understand why, when using repaint() in the mouseDragged event, I must , I must also call drawRectangle(…) in mouseReleased for the square to be seen at all, but when using paintComponent, I don’t?
Any advice or pointers are greatly appreciated, thankyou.
In your drawRectangle and drawPolygon you are re-getting a graphics object, but you are calling them from paintComponent. this is what’s causing the odd behaviour, you should pass the graphics from painComponent in to those methods.
I am seeing other odd behaviour though, like the polygons stay green until I’ve finished painting a square, when they then turn red, but one of the lines stays green and eventually vanishes. I didn’t look in to that too much.
Also, polygon drawing and Point and Rectangle are supported in awt, is there any reason you chose to create your own classes for those?