I created a graphical component that allows you to view an image and allows you to make a selection of a part of the image: the selection of a portion of the image is accomplished by drawing a rectangle on this image (using drag-and-drop).
To this purpose, I used this example, which created a subclass of JLabel
in order to draw the image and in order to deal with the drawing of the rectangle. Then I put an instance of this subclass within a JPanel
, in order to have the image always positioned at the center of the panel.
FigurePanel.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
public class FigurePanel extends JPanel
{
private SelectionLabel imageLabel = null;
public FigurePanel()
{
this.setLayout(new GridBagLayout());
imageLabel = new SelectionLabel();
this.add(imageLabel, null);
}
public void setImage(Image image)
{
imageLabel.setImage(image);
}
private class SelectionLabel extends JLabel
{
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public SelectionLabel()
{
super();
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void setImage(Image image)
{
currentRect = null;
rectToDraw = null;
previousRectDrawn.setBounds(0, 0, 0, 0);
setIcon(new ImageIcon(image));
}
private class SelectionListener extends MouseInputAdapter
{
@Override
public void mousePressed(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect(getWidth(), getHeight());
repaint();
}
@Override
public void mouseDragged(MouseEvent e)
{
updateSize(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
updateSize(e);
}
/*
* Update the size of the current rectangle
* and call repaint. Because currentRect
* always has the same origin, translate it
* if the width or height is negative.
*
* For efficiency (though
* that isn't an issue for this program),
* specify the painting region using arguments
* to the repaint() call.
*
*/
void updateSize(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect.setSize(x - currentRect.x,
y - currentRect.y);
updateDrawableRect(getWidth(), getHeight());
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y,
totalRepaint.width, totalRepaint.height);
}
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //paints the background and image
//If currentRect exists, paint a box on top.
if (currentRect != null) {
//Draw a rectangle on top of the image.
g.setXORMode(Color.white); //Color of line varies
//depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y,
rectToDraw.width - 1, rectToDraw.height - 1);
System.out.println("rectToDraw: " + rectToDraw);
}
}
private void updateDrawableRect(int compWidth, int compHeight)
{
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
//Make the width and height positive, if necessary.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
//The rectangle shouldn't extend past the drawing area.
if ((x + width) > compWidth) {
width = compWidth - x;
}
if ((y + height) > compHeight) {
height = compHeight - y;
}
//Update rectToDraw after saving old value.
if (rectToDraw != null) {
previousRectDrawn.setBounds(
rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
} else {
rectToDraw = new Rectangle(x, y, width, height);
}
}
}
}
FigurePanelTest.java
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
public class FigurePanelTest extends JFrame
{
public FigurePanelTest()
{
FigurePanel imagePanel = new FigurePanel();
JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(
new ActionListener()
{
@Override
public void actionPerformed(ActionEvent evt)
{
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try
{
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
);
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(imageScrollPane, BorderLayout.CENTER);
container.add(imageButton, BorderLayout.NORTH);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
}
The private class SelectionLabel
is the class SelectionArea
from this example.
When a new rectangle is drawn, a message is printed on the console. Now I would replace the printing of the message with the firing of a custom event, so that the position and size of the rectangle are accessible to the application business logic.
I read how to create a custom event in Java. Moreover, this article identifies two super types for creating events: EventObject and AWTEvent. This articles states:
Normally you extend AWTEvent for events generated by a graphical component and EventObject any other time.
Since the event concerning the selection of a part of the image is generated by a graphical component (that is the FigurePanel
panel), I could implement the ImageSelectionEvent
class by extending AWTEvent
, as the following code snippet.
public class ImageSelectionEvent extends AWTEvent
{
public ImageSelectionEvent(Object source, int id) {
super(source, id);
}
}
The documentation identifies the id as the event type. So, what value should be assigned to this parameter?
Moreover, why does the constructor of EventObject
class be devoid of the id
parameter?
When creating an event class, you must guarantee that the event is immutable. The event generator will share the same event instance among the listeners; so ensure any one listener cannot change the event's state.
What about this?