/*
* ImageArea.java
*
* This is a heavily modified version of a file downloaded
* From JavaWorld jw-0424-funandgames.zip that accompanied
* an article by Jeff Friesen, JavaWorld.com, 04/24/06
* http://www.javaworld.com/javaworld/jw-04-2006/jw-0424-funandgames.html
*
*
* Modified for use with Image Tools
*/
package net.codebuilders.desktop.imagetools;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.*;
import javax.swing.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Hashtable;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.RoundRectangle2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/**
* This class defines a specialized panel for displaying a captured image.
*/
public class ImageArea extends JPanel {
ImageToolsModel itModel = null;
// logger
Logger logger = null;
/**
* Stroke-defined outline of selection rectangle.
*/
private BasicStroke bsSelect;
/**
* A gradient paint is used to create a distinctive-looking selection
* rectangle outline.
*/
private GradientPaint gpSelect;
/**
* Stroke-defined outline of annotation shapes.
*/
private BasicStroke bsAnnotate;
/**
* A gradient paint is used to create annotation
*/
private GradientPaint gpAnnotate;
/**
* Displayed image's Image object, which is actually a BufferedImage.
*/
private Image image;
/**
* Mouse coordinates when mouse button pressed.
*/
private int srcx, srcy;
/**
* Latest mouse coordinates during drag operation.
*/
private int destx, desty;
/**
* Location and extents of selection rectangle.
* for cropping
*/
private Rectangle rectSelection;
/**
* Location and extents of selection line.
* for drawing a line
*/
private Line2D lineSelection;
/**
* Location and extents of selection point.
* for drawing text
*/
private Point2D pointSelection;
/**
* Ready to crop, usually after rectangle selection
*/
private boolean readyToCrop;
// var to hold current mouse listener
private int mouseEvtType;
// constants for mouse events
public static final int DRAW_CROP_RECTANGLE = 0;
public static final int DRAW_LINE = 1;
public static final int DRAW_TEXT = 2;
public static final int DRAW_RECTANGLE = 3;
MouseListener ml;
MouseMotionListener mml;
// The LineBreakMeasurer used to line-break the paragraph.
private LineBreakMeasurer lineMeasurer;
// The LineBreakMeasurer used to get the size the paragraph
// for the background rectangle
private LineBreakMeasurer rectMeasurer;
private float breakWidth = (float) 200.0;
// index of the first character in the paragraph.
private int paragraphStart;
// index of the first character after the end of the paragraph.
private int paragraphEnd;
private Hashtable<TextAttribute, Object> map;
private AttributedString attStr;
/**
* Construct an ImageArea component.
*/
public ImageArea() {
// get the model to get text from
itModel = ImageToolsApp.getApplication().getItModel();
// setup logger
logger = Logger.getLogger(ImageArea.class.getName());
// Create a selection Rectangle. It's better to create one Rectangle
// here than a Rectangle each time paintComponent() is called, to reduce
// unnecessary object creation.
rectSelection = new Rectangle();
lineSelection = new Line2D.Float();
pointSelection = new Point2D.Float();
// Define the stroke for drawing selection rectangle outline.
bsSelect = new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
0, new float[]{12, 12}, 0);
// Define the gradient paint for coloring selection rectangle outline.
gpSelect = new GradientPaint(0.0f, 0.0f, Color.red, 1.0f, 1.0f, Color.white,
true);
// Define the stroke for drawing annotation.
bsAnnotate = new BasicStroke(2, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND);
// Define the gradient paint for coloring annotation.
gpAnnotate = new GradientPaint(0.0f, 0.0f, Color.red, 1.0f, 1.0f, Color.white,
true);
this.readyToCrop = false;
// Install a mouse listener that sets things up for a selection drag.
// setup crop by default
// this.setupCropMouseListeners();
this.setMouseEvtType(ImageArea.DRAW_CROP_RECTANGLE);
map = new Hashtable<TextAttribute, Object>();
// get textFont value from model
map.put(TextAttribute.FAMILY, itModel.getFontName());
// get text height value from model
map.put(TextAttribute.SIZE, new Float(itModel.getFontSize()));
// make a new attributed string
attStr = new AttributedString(itModel.getText(), map);
// listen for ImageTools Model to update fontName
itModel.pcs.addPropertyChangeListener("fontName", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
// update map
map.put(TextAttribute.FAMILY, itModel.getFontName());
// make a new attributed string
attStr = new AttributedString(itModel.getText(), map);
}
});
// listen for ImageTools Model to update fontSize
itModel.pcs.addPropertyChangeListener("fontSize", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
// update map
map.put(TextAttribute.SIZE, itModel.getFontSize());
// make a new attributed string
attStr = new AttributedString(itModel.getText(), map);
}
});
// listen for ImageTools Model to update text
itModel.pcs.addPropertyChangeListener("text", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
// make a new attributed string
attStr = new AttributedString(itModel.getText(), map);
}
});
} // end constructor
/**
* Crop the image to the dimensions of the selection rectangle.
*
* @return true if cropping succeeded
*/
public boolean crop() {
// There is nothing to crop if the selection rectangle is only a single
// point.
if (srcx == destx && srcy == desty) {
return false;
}
// Assume success.
boolean succeeded = true;
// Compute upper-left and lower-right coordinates for selection rectangle
// corners.
int x1 = (srcx < destx) ? srcx : destx;
int y1 = (srcy < desty) ? srcy : desty;
int x2 = (srcx > destx) ? srcx : destx;
int y2 = (srcy > desty) ? srcy : desty;
// Compute width and height of selection rectangle.
int width = (x2 - x1) + 1;
int height = (y2 - y1) + 1;
// Create a buffer to hold cropped image.
BufferedImage biCrop = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = biCrop.createGraphics();
// Perform the crop operation.
try {
BufferedImage bi = (BufferedImage) image;
BufferedImage bi2 = bi.getSubimage(x1, y1, width, height);
g2d.drawImage(bi2, null, 0, 0);
} catch (RasterFormatException e) {
succeeded = false;
}
g2d.dispose();
if (succeeded) {
setImage(biCrop, true); // Implicitly remove selection rectangle.
} else {
// Prepare to remove selection rectangle.
srcx = destx;
srcy = desty;
// Explicitly remove selection rectangle.
repaint();
}
setReadyToCrop(false);
return succeeded;
}
/**
* Return the current image.
*
* @return Image reference to current image
*/
public Image getImage() {
return image;
}
/**
* Repaint the ImageArea with the current image's pixels.
*
* @param g graphics context
*/
@Override
public void paintComponent(Graphics g) {
// Repaint the component's background.
super.paintComponent(g);
// If an image has been defined, draw that image using the Component
// layer of this ImageArea object as the ImageObserver.
if (image != null) {
g.drawImage(image, 0, 0, this);
}
// this.paintRectangle(g);
switch (getMouseEvtType()) {
case DRAW_CROP_RECTANGLE:
this.paintRectangle(g);
break;
case DRAW_LINE:
this.paintLine(g);
break;
case DRAW_TEXT:
this.paintText(g);
break;
case DRAW_RECTANGLE:
this.paintRectangle(g);
break;
} // end switch
}
// called by paintComponent
public void paintRectangle(Graphics g) {
// Draw the selection rectangle if present.
if (srcx != destx || srcy != desty) {
// Compute upper-left and lower-right coordinates for selection
// rectangle corners.
int x1 = (srcx < destx) ? srcx : destx;
int y1 = (srcy < desty) ? srcy : desty;
int x2 = (srcx > destx) ? srcx : destx;
int y2 = (srcy > desty) ? srcy : desty;
// Establish selection rectangle origin.
rectSelection.x = x1;
rectSelection.y = y1;
// Establish selection rectangle extents.
rectSelection.width = (x2 - x1) + 1;
rectSelection.height = (y2 - y1) + 1;
// Draw selection rectangle.
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(bsSelect);
g2d.setPaint(gpSelect);
g2d.draw(rectSelection);
g2d.dispose();
} // end if
}
// called by paintComponent
public void paintLine(Graphics g) {
// paint selection line
// Draw the selection line if present.
if (srcx != destx || srcy != desty) {
lineSelection.setLine(srcx, srcy, destx, desty);
// Draw selection line.
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(bsSelect);
g2d.setPaint(gpSelect);
//g2d.setColor(Color.RED);
g2d.draw(lineSelection);
g2d.dispose();
} // end if
}
// called by paintComponent
public void paintText(Graphics g) {
// paint text
// Draw the text if the mouse has moved.
if (srcx != destx || srcy != desty) {
// Draw selection line.
Graphics2D g2d = (Graphics2D) g;
// set the pre-placement color to light gray
g2d.setColor(Color.LIGHT_GRAY);
// Create a new LineBreakMeasurer from the paragraph.
// It will be cached and re-used.
if (lineMeasurer == null) {
AttributedCharacterIterator paragraph = attStr.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2d.getFontRenderContext();
lineMeasurer = new LineBreakMeasurer(paragraph, frc);
}
// Set break width to width of Component.
breakWidth = itModel.getTextBoxWidth();
float drawPosY = desty;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = layout.isLeftToRight()
? (0 + destx) : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g2d, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
g2d.dispose();
lineMeasurer = null;
} // end if
}
/**
* Establish a new image and update the display.
*
* @param image new image's Image reference
*/
public void setImage(Image image, boolean fireEvent) {
// added fireEvent because capture command starts in the View,
// happens in the model where it's updated. Fires an event back to the
// View, the view updates this image area and here we fired another
// event back to the View
// DEBUG
System.out.println("entering ImageArea setImage");
// copy to old
Image old = this.image;
// DEBUG
if (old != null) {
System.out.println("old = " + old.toString());
} else {
System.out.println("image was null");
}
// set new image
this.image = image;
// DEBUG
System.out.println("new = " + this.image.toString());
// Set this panel's preferred size to the image's size, to influence the
// display of scrollbars.
setPreferredSize(new Dimension(image.getWidth(this),
image.getHeight(this)));
// Present scrollbars as necessary.
revalidate();
// Prepare to remove any selection rectangle.
srcx = destx;
srcy = desty;
// Update the image displayed on the panel.
repaint();
if (fireEvent) {
// DEBUG
System.out.println("imageArea.setImage firePropertyChange");
firePropertyChange("image", old, this.image);
}
}
private void setupCropMouseListeners() {
this.tearDownMouseListeners();
this.ml = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a subimage.
// This is the reason for the if (image == null) test.
if (image == null) {
return;
}
destx = srcx = e.getX();
desty = srcy = e.getY();
repaint();
}
// TEST to crop on release or fire event
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse released.");
// check for rectangle
if (srcx != destx || srcy != desty) {
setReadyToCrop(true);
System.out.println("Ready to crop.");
} else {
setReadyToCrop(false);
System.out.println("Not ready to crop.");
}
}
};
this.addMouseListener(ml);
// Install a mouse motion listener to update the selection rectangle
// during drag operations.
this.mml = new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a
// subimage. This is the reason for the if (image == null)
// test.
if (image == null) {
return;
}
destx = e.getX();
desty = e.getY();
repaint();
}
};
this.addMouseMotionListener(mml);
}
private void setupRectangleMouseListeners() {
// remove existing mouse listeners
this.tearDownMouseListeners();
this.ml = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try to draw.
// This is the reason for the if (image == null) test.
if (image == null) {
return;
}
destx = srcx = e.getX();
desty = srcy = e.getY();
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("Mouse released.");
int width = image.getWidth(null);
int height = image.getHeight(null);
BufferedImage biNew = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = biNew.createGraphics();
// Copy current image to old
try {
BufferedImage biOld = (BufferedImage) image;
g2d.drawImage(biOld, null, 0, 0);
} catch (RasterFormatException rfe) {
// log error
logger.log(Level.SEVERE, null, rfe);
}
// draw the real rectangle
g2d.setStroke(bsAnnotate);
// g2d.setPaint(gpAnnotate);
// get the color from the model
g2d.setColor(itModel.getColor());
g2d.draw(rectSelection);
g2d.dispose();
// set the image
setImage(biNew, true);
}
};
this.addMouseListener(ml);
// Install a mouse motion listener to update the selection rectangle
// during drag operations.
// MouseMotionListener mml;
this.mml = new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a
// subimage. This is the reason for the if (image == null)
// test.
if (image == null) {
return;
}
destx = e.getX();
desty = e.getY();
repaint();
}
};
this.addMouseMotionListener(mml);
}
private void setupLineMouseListeners() {
this.tearDownMouseListeners();
this.ml = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a subimage.
// This is the reason for the if (image == null) test.
if (image == null) {
return;
}
destx = srcx = e.getX();
desty = srcy = e.getY();
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
int width = image.getWidth(null);
int height = image.getHeight(null);
BufferedImage biNew = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = biNew.createGraphics();
// Copy current image to old
try {
BufferedImage biOld = (BufferedImage) image;
g2d.drawImage(biOld, null, 0, 0);
} catch (RasterFormatException rfe) {
// log error
logger.log(Level.SEVERE, null, rfe);
}
// draw the real line
g2d.setStroke(bsAnnotate);
// g2d.setPaint(gpAnnotate);
// get the color from the model
g2d.setColor(itModel.getColor());
g2d.draw(lineSelection);
setImage(biNew, true);
}
};
this.addMouseListener(ml);
this.mml = new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a
// subimage. This is the reason for the if (image == null)
// test.
if (image == null) {
return;
}
destx = e.getX();
desty = e.getY();
repaint();
}
};
this.addMouseMotionListener(mml);
}
private void setupTextMouseListeners() {
// this.removeMouseListener(ml);
this.tearDownMouseListeners();
// setupRectangleMouseListeners currently work
// except for pemenently adding line to image
this.ml = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a subimage.
// This is the reason for the if (image == null) test.
if (image == null) {
return;
}
destx = srcx = e.getX();
desty = srcy = e.getY();
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
int width = image.getWidth(null);
int height = image.getHeight(null);
// variables for text background rectangle
double rectPosX = destx;
double rectPosY = desty;
double rectWidth = 0;
double rectHeight = 0;
BufferedImage biNew = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = biNew.createGraphics();
// Copy current image to old
try {
BufferedImage biOld = (BufferedImage) image;
// should copy image
// BufferedImage bi2 = bi.getSubimage(0, 0, width, height);
g2d.drawImage(biOld, null, 0, 0);
} catch (RasterFormatException rfe) {
// log error
logger.log(Level.SEVERE, null, rfe);
}
// begin text background rectangle
// color for background of text
g2d.setColor(Color.WHITE);
// Create a new LineBreakMeasurer from the paragraph.
// It will be cached and re-used.
if (rectMeasurer == null) {
AttributedCharacterIterator paragraph = attStr.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2d.getFontRenderContext();
rectMeasurer = new LineBreakMeasurer(paragraph, frc);
}
// Set break width to width of Component.
breakWidth = itModel.getTextBoxWidth();
// Set position to the index of the first character in the paragraph.
rectMeasurer.setPosition(paragraphStart);
// make a copy of measurer and loop through to get dimensions
// calculate the rectangle size ant set variables
LineBreakMeasurer lmCopy = rectMeasurer;
// Get lines until the entire paragraph has been displayed.
while (lmCopy.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lmCopy.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
// TODO adjust the RightToLeft case with destx also
// LeftToRight has been fixed
rectPosX = layout.isLeftToRight()
? (0 + destx) : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
rectPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
// layout.draw(g2d, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
rectPosY += layout.getDescent() + layout.getLeading();
// add the y move to rectangle height
System.out.println("rectHeight was " + rectHeight);
rectHeight += layout.getAscent() + layout.getDescent() +
layout.getLeading();
System.out.println("rectHeight is now " + rectHeight);
// if text line width is greater than rectWidth
// set rectWidth to line width
if (layout.getAdvance() > rectWidth) {
rectWidth = layout.getAdvance();
}
}
System.out.println("rectangle position and size= " +
rectPosX + ", " + desty + ", " +
rectWidth + ", " + rectHeight);
// draw a rectangle around text with padding
g2d.fill(new RoundRectangle2D.Double((destx - 4) , (desty - 4),
(rectWidth + 8),
(rectHeight + 8), 10, 10));
// finish
lmCopy = null;
rectMeasurer = null;
// end text rectangle
// color for text
g2d.setColor(itModel.getColor());
// Create a new LineBreakMeasurer from the paragraph.
// It will be cached and re-used.
if (lineMeasurer == null) {
AttributedCharacterIterator paragraph = attStr.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g2d.getFontRenderContext();
lineMeasurer = new LineBreakMeasurer(paragraph, frc);
}
// Set break width to width of Component.
breakWidth = itModel.getTextBoxWidth();
float drawPosY = desty;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
// TODO adjust the RightToLeft case with destx also
// LeftToRight has been fixed
float drawPosX = layout.isLeftToRight()
? (0 + destx) : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g2d, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
setImage(biNew, true);
g2d.dispose();
lineMeasurer = null;
}
};
this.addMouseListener(ml);
this.mml = new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a
// subimage. This is the reason for the if (image == null)
// test.
if (image == null) {
return;
}
destx = e.getX();
desty = e.getY();
repaint();
}
};
this.addMouseMotionListener(mml);
}
public void tearDownMouseListeners() {
this.removeMouseListener(ml);
this.removeMouseMotionListener(mml);
}
/**
* @return the mouseEvtType
*/
public int getMouseEvtType() {
return mouseEvtType;
}
/**
* @param mouseEvtType the mouseEvtType to set
* setup the proper mouse listener
*/
public void setMouseEvtType(int mouseEvtType) {
this.mouseEvtType = mouseEvtType;
// setup the proper mouse listener
switch (getMouseEvtType()) {
case DRAW_CROP_RECTANGLE:
this.setupCropMouseListeners();
break;
case DRAW_LINE:
this.setupLineMouseListeners();
break;
case DRAW_TEXT:
this.setupTextMouseListeners();
break;
case DRAW_RECTANGLE:
this.setupRectangleMouseListeners();
break;
} // end switch
}
/**
* @return the readyToCrop
*/
public boolean isReadyToCrop() {
return readyToCrop;
}
/**
* @param readyToCrop the readyToCrop to set
*/
public void setReadyToCrop(boolean readyToCrop) {
this.readyToCrop = readyToCrop;
}
}