package robo.vision.widgets;
/*
* @(#)ImageDisplay.java 1.39 00/04/13 16:50:08
*
* Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved.
*
* Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
* modify and redistribute this software in source and binary code form,
* provided that i) this copyright notice and license appear on all copies of
* the software; and ii) Licensee does not utilize the software in a manner
* which is disparaging to Sun.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* This software is not designed or intended for use in on-line control of
* aircraft, air traffic, aircraft navigation or aircraft communications; or in
* the design, construction, operation or maintenance of any nuclear
* facility. Licensee represents and warrants that it will not use or
* redistribute the Software for such purposes.
*/
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ByteLookupTable;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.LookupOp;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import javax.media.jai.PlanarImage;
import javax.media.jai.RasterFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
/**
* An output widget for a PlanarImage. ImageDisplay subclasses
* javax.swing.JComponent, and can be used in any context that calls for a
* JComponent. It monitors resize and update events and automatically
* requests tiles from its source on demand.
*
* <p> Due to the limitations of BufferedImage, only TYPE_BYTE of band
* 1, 2, 3, 4, and TYPE_USHORT of band 1, 2, 3 images can be displayed
* using this widget.
*
* @author Daniel Rice
* @author Dennis Sigel
*/
public class ImageDisplay extends JComponent
implements MouseListener, MouseMotionListener {
/** The source PlanarImage. */
protected PlanarImage source;
/** The image's SampleModel. */
protected SampleModel sampleModel;
/** The image's ColorModel or one we supply. */
protected ColorModel colorModel;
/** The image's min X tile. */
protected int minTileX;
/** The image's max X tile. */
protected int maxTileX;
/** The image's min Y tile. */
protected int minTileY;
/** The image's max Y tile. */
protected int maxTileY;
/** The image's tile width. */
protected int tileWidth;
/** The image's tile height. */
protected int tileHeight;
/** The image's tile grid X offset. */
protected int tileGridXOffset;
/** The image's tile grid Y offset. */
protected int tileGridYOffset;
protected int originX = 0;
protected int originY = 0;
protected int shift_x = 0;
protected int shift_y = 0;
protected JLabel odometer = null;
protected int componentWidth;
protected int componentHeight;
/** Brightness control */
protected BufferedImageOp biop = null;
protected boolean brightnessEnabled = false;
protected int brightness = 0;
protected byte[] lutData;
/** Initializes the ImageDisplay. */
private synchronized void initialize() {
if ( source == null ) return;
componentWidth = source.getWidth();
componentHeight = source.getHeight();
setPreferredSize(new Dimension(componentWidth, componentHeight));
this.sampleModel = source.getSampleModel();
// First check whether the opimage has already set a suitable ColorModel
this.colorModel = source.getColorModel();
if (this.colorModel == null) {
// If not, then create one.
this.colorModel = PlanarImage.createColorModel(this.sampleModel);
if (this.colorModel == null) {
throw new IllegalArgumentException("no color model");
}
}
minTileX = source.getMinTileX();
maxTileX = source.getMinTileX() + source.getNumXTiles() - 1;
minTileY = source.getMinTileY();
maxTileY = source.getMinTileY() + source.getNumYTiles() - 1;
tileWidth = source.getTileWidth();
tileHeight = source.getTileHeight();
tileGridXOffset = source.getTileGridXOffset();
tileGridYOffset = source.getTileGridYOffset();
}
/**
* Default constructor
*/
public ImageDisplay() {
super();
source = null;
lutData = new byte[256];
for ( int i = 0; i < 256; i++ ) {
lutData[i] = (byte)i;
}
componentWidth = 64;
componentHeight = 64;
setPreferredSize(new Dimension(componentWidth, componentHeight));
setOrigin(0, 0);
setBrightnessEnabled(true);
}
/**
* Constructs an ImageDisplay to display a PlanarImage.
*
* @param source a PlanarImage to be displayed.
*/
public ImageDisplay(PlanarImage im) {
super();
source = im;
initialize();
lutData = new byte[256];
for ( int i = 0; i < 256; i++ ) {
lutData[i] = (byte)i;
}
setOrigin(0, 0);
setBrightnessEnabled(true);
}
/**
* Constructs an ImageDisplay of fixed size (no image)
*
* @param width - display width
* @param height - display height
*/
public ImageDisplay(int width, int height) {
super();
source = null;
lutData = new byte[256];
for ( int i = 0; i < 256; i++ ) {
lutData[i] = (byte)i;
}
componentWidth = width;
componentHeight = height;
setPreferredSize(new Dimension(componentWidth, componentHeight));
setOrigin(0, 0);
setBrightnessEnabled(true);
}
/** Changes the source image to a new PlanarImage. */
public void set(PlanarImage im) {
source = im;
initialize();
repaint();
}
public void set(PlanarImage im, int x, int y) {
source = im;
initialize();
setOrigin(x, y);
}
public PlanarImage getImage() {
return source;
}
public final JLabel getOdometer() {
if ( odometer == null ) {
odometer = new JLabel();
odometer.setVerticalAlignment(SwingConstants.CENTER);
odometer.setHorizontalAlignment(SwingConstants.LEFT);
odometer.setText(" ");
addMouseListener(this);
addMouseMotionListener(this);
}
return odometer;
}
/** Provides panning */
public final void setOrigin(int x, int y) {
// shift to box origin
originX = -x;
originY = -y;
repaint();
}
public int getXOrigin() {
return originX;
}
public int getYOrigin() {
return originY;
}
/** Records a new size. Called by the AWT. */
public void setBounds(int x, int y, int width, int height) {
Insets insets = getInsets();
int w;
int h;
if ( source == null ) {
w = width;
h = height;
} else {
w = source.getWidth();
h = source.getHeight();
if ( width < w ) {
w = width;
}
if ( height < h ) {
h = height;
}
}
componentWidth = w + insets.left + insets.right;
componentHeight = h + insets.top + insets.bottom;
super.setBounds(x+shift_x, y+shift_y, componentWidth, componentHeight);
}
public void setLocation(int x, int y) {
shift_x = x;
shift_y = y;
super.setLocation(x, y);
}
private final int XtoTileX(int x) {
return (int) Math.floor((double) (x - tileGridXOffset)/tileWidth);
}
private final int YtoTileY(int y) {
return (int) Math.floor((double) (y - tileGridYOffset)/tileHeight);
}
private final int TileXtoX(int tx) {
return tx*tileWidth + tileGridXOffset;
}
private final int TileYtoY(int ty) {
return ty*tileHeight + tileGridYOffset;
}
private static final void debug(String msg) {
System.out.println(msg);
}
private final byte clampByte(int v) {
if ( v > 255 ) {
return (byte)255;
} else if ( v < 0 ) {
return (byte)0;
} else {
return (byte)v;
}
}
private final void setBrightnessEnabled(boolean v) {
brightnessEnabled = v;
if ( brightnessEnabled == true ) {
biop = new AffineTransformOp(new AffineTransform(),
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
} else {
biop = null;
}
}
public final boolean getBrightnessEnabled() {
return brightnessEnabled;
}
public final void setBrightness(int b) {
if ( b != brightness && brightnessEnabled == true ) {
for ( int i = 0; i < 256; i++ ) {
lutData[i] = clampByte(i+b);
}
repaint();
}
}
/**
* Paint the image onto a Graphics object. The painting is
* performed tile-by-tile, and includes a grey region covering the
* unused portion of image tiles as well as the general
* background. At this point the image must be byte data.
*/
public synchronized void paintComponent(Graphics g) {
Graphics2D g2D = null;
if (g instanceof Graphics2D) {
g2D = (Graphics2D)g;
} else {
return;
}
// if source is null, it's just a component
if ( source == null ) {
g2D.setColor(getBackground());
g2D.fillRect(0, 0, componentWidth, componentHeight);
return;
}
int transX = -originX;
int transY = -originY;
// Get the clipping rectangle and translate it into image coordinates.
Rectangle clipBounds = g.getClipBounds();
if (clipBounds == null) {
clipBounds = new Rectangle(0, 0, componentWidth, componentHeight);
}
// clear the background (clip it) [minimal optimization here]
if ( transX > 0 ||
transY > 0 ||
transX < (componentWidth-source.getWidth()) ||
transY < (componentHeight-source.getHeight())) {
g2D.setColor(getBackground());
g2D.fillRect(0, 0, componentWidth, componentHeight);
}
clipBounds.translate(-transX, -transY);
// Determine the extent of the clipping region in tile coordinates.
int txmin, txmax, tymin, tymax;
int ti, tj;
txmin = XtoTileX(clipBounds.x);
txmin = Math.max(txmin, minTileX);
txmin = Math.min(txmin, maxTileX);
txmax = XtoTileX(clipBounds.x + clipBounds.width - 1);
txmax = Math.max(txmax, minTileX);
txmax = Math.min(txmax, maxTileX);
tymin = YtoTileY(clipBounds.y);
tymin = Math.max(tymin, minTileY);
tymin = Math.min(tymin, maxTileY);
tymax = YtoTileY(clipBounds.y + clipBounds.height - 1);
tymax = Math.max(tymax, minTileY);
tymax = Math.min(tymax, maxTileY);
Insets insets = getInsets();
// Loop over tiles within the clipping region
for (tj = tymin; tj <= tymax; tj++) {
for (ti = txmin; ti <= txmax; ti++) {
int tx = TileXtoX(ti);
int ty = TileYtoY(tj);
Raster tile = source.getTile(ti, tj);
if ( tile != null ) {
DataBuffer dataBuffer = tile.getDataBuffer();
WritableRaster wr = tile.createWritableRaster(sampleModel,
dataBuffer,
null);
BufferedImage bi = new BufferedImage(colorModel,
wr,
colorModel.isAlphaPremultiplied(),
null);
// correctly handles band offsets
if ( brightnessEnabled == true ) {
SampleModel sm = sampleModel.createCompatibleSampleModel(tile.getWidth(),
tile.getHeight());
WritableRaster raster = RasterFactory.createWritableRaster(sm, null);
BufferedImage bimg = new BufferedImage(colorModel,
raster,
colorModel.isAlphaPremultiplied(),
null);
// don't move this code
ByteLookupTable lutTable = new ByteLookupTable(0, lutData);
LookupOp lookup = new LookupOp(lutTable, null);
lookup.filter(bi, bimg);
g2D.drawImage(bimg, biop, tx+transX+insets.left, ty+transY+insets.top);
} else {
AffineTransform transform;
transform = AffineTransform.getTranslateInstance(tx + transX + insets.left,
ty + transY + insets.top);
g2D.drawRenderedImage(bi, transform);
}
}
}
}
}
// mouse interface
public final void mouseEntered(MouseEvent e) {
}
public final void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
int mods = e.getModifiers();
if ( odometer != null ) {
String output = " (" + p.x + ", " + p.y + ")";
odometer.setText(output);
}
}
public final void mouseReleased(MouseEvent e) {
Point p = e.getPoint();
if ( odometer != null ) {
String output = " (" + p.x + ", " + p.y + ")";
odometer.setText(output);
}
}
public final void mouseClicked(MouseEvent e) {
}
public final void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if ( odometer != null ) {
String output = " (" + p.x + ", " + p.y + ")";
odometer.setText(output);
}
}
public final void mouseDragged(MouseEvent e) {
mousePressed(e);
}
}