package robo.vision.widgets; /* * @(#)Panner.java 1.33 99/10/13 16:51:13 * * 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.Color; 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.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.awt.image.renderable.ParameterBlock; import javax.media.jai.InterpolationBilinear; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.TiledImage; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.SwingConstants; import javax.swing.border.CompoundBorder; import javax.swing.border.LineBorder; /** * An output widget used for panning. Panner 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 Dennis Sigel */ public final class Panner extends JComponent implements MouseListener, MouseMotionListener { /** The TiledImage used for background. */ private TiledImage pannerImage; private SampleModel sampleModel; private ColorModel colorModel; /** The image's tile parameters. */ private int minTileX; private int maxTileX; private int minTileY; private int maxTileY; private int tileWidth; private int tileHeight; private int tileGridXOffset; private int tileGridYOffset; /** The panner's parameters. */ private int pannerWidth; private int pannerHeight; /** The slider box properties. */ private JLabel slider; private boolean sliderOpaque; private Color sliderBorderColor; /** The x,y center of the slider box */ private int sliderX; private int sliderY; /** The dimensions of the slider box */ private int sliderWidth; private int sliderHeight; /** scale from panner to scroll object */ private float scale; /** Range of slider mapped values */ private float pannerMinH; private float pannerMaxH; private float pannerMinV; private float pannerMaxV; /** Slider current values */ private float hValue; private float vValue; /** The object to control */ private JComponent scrollObject = null; /** X,Y position tracker */ private JLabel odometer; /** enable or disabled scaling */ private boolean enableScale = false; /** Initializes the Panner. */ private synchronized void initialize(JComponent item, PlanarImage image, PlanarImage thumb, int maxSize) { scrollObject = item; PlanarImage temp = null; if ( image != null ) { Rectangle rect = item.getBounds(); float scale_x; float scale_y; float imageWidth = (float) image.getWidth(); float imageHeight = (float) image.getHeight(); if ( imageWidth >= (float)rect.height ) { scale_x = (float) maxSize / imageWidth; scale_y = scale_x; pannerWidth = maxSize; pannerHeight = (int)(imageHeight * scale_y); } else { scale_y = (float) maxSize / imageHeight; scale_x = scale_y; pannerHeight = maxSize; pannerWidth = (int)(imageWidth * scale_x); } scale = 1.0F / scale_x; // this must be put into a tiled image or scale gets // called on every repaint. if ( enableScale == true ) { InterpolationBilinear interp = new InterpolationBilinear(); ParameterBlock pb = new ParameterBlock(); pb.addSource(image); pb.add(scale_x); pb.add(scale_y); pb.add(0.0F); pb.add(0.0F); pb.add(interp); /*temp = JAI.create("scale", image, scale_x, scale_y, 0.0F, 0.0F, interp);*/ temp = JAI.create("scale", pb); } else { temp = thumb; } sampleModel = temp.getSampleModel(); // First check whether the opimage has already set a suitable ColorModel colorModel = temp.getColorModel(); if (colorModel == null) { // If not, then create one. colorModel = PlanarImage.createColorModel(sampleModel); if (colorModel == null) { throw new IllegalArgumentException("no color model"); } } pannerImage = new TiledImage(0, 0, pannerWidth, pannerHeight, 0, 0, sampleModel, colorModel); pannerImage.set(temp); minTileX = pannerImage.getMinTileX(); maxTileX = pannerImage.getMinTileX() + pannerImage.getNumXTiles() - 1; minTileY = pannerImage.getMinTileY(); maxTileY = pannerImage.getMinTileY() + pannerImage.getNumYTiles() - 1; tileWidth = pannerImage.getTileWidth(); tileHeight = pannerImage.getTileHeight(); tileGridXOffset = pannerImage.getTileGridXOffset(); tileGridYOffset = pannerImage.getTileGridYOffset(); int tw = (int) ((float)rect.width * scale_x); int th = (int) ((float)rect.height * scale_y); createSliderBox(tw, th); } else { createSliderBox(32, 32); } sliderX = 0; sliderY = 0; odometer = null; repaint(); } /** * Default constructor */ public Panner() { super(); setLayout(null); setPreferredSize(new Dimension(64, 64)); pannerWidth = 64; pannerHeight = 64; createSliderBox(4, 4); } /** * Constructs a Panner to display a scaled PlanarImage. * * @param Swing object to be controlled. * @param image a PlanarImage to be displayed. * @param Max width or height for scaled image. */ public Panner(JComponent item, PlanarImage image, int maxSize) { super(); setLayout(null); if ( item == null ) { return; } enableScale = true; initialize(item, image, null, maxSize); } /** * Constructs a Panner with no image */ public Panner(int panner_width, int panner_height) { super(); setLayout(null); setPreferredSize(new Dimension(panner_width, panner_height)); pannerWidth = panner_width; pannerHeight = panner_height; createSliderBox(pannerWidth/16, pannerHeight/16); } /** * Construct a Panner with a pre-scaled image */ public Panner(JComponent item, PlanarImage image, PlanarImage thumb) { super(); setLayout(null); if ( item == null ) { return; } int maxSize = thumb.getWidth(); enableScale = false; initialize(item, image, thumb, maxSize); } /** Changes the pannerImage image to a new PlanarImage. */ public synchronized void set(PlanarImage im, int maxSize, int vWidth, int vHeight) { /* initialize(im, maxSize, vWidth, vHeight); */ } 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; } public final void setScrollObject(JComponent o) { scrollObject = o; } /** Provides panning (moves slider box center location) */ public final void setSliderLocation(int x, int y) { moveit(x, y); } public final Point getSliderLocation() { return new Point(sliderX, sliderY); } public final void setSliderOpaque(boolean v) { sliderOpaque = v; slider.setOpaque(v); } public void setSliderBorderColor(Color color) { slider.setBorder( new CompoundBorder( LineBorder.createBlackLineBorder(), new LineBorder(color, 1)) ); } public Dimension getMinimumSize() { return new Dimension(pannerWidth, pannerHeight); } public Dimension getPreferredSize() { return getMinimumSize(); } public Dimension getMaximumSize() { return getMinimumSize(); } public final int getWidth() { return pannerWidth; } public final int getHeight() { return pannerHeight; } /** force a fixed size. Called by the AWT. */ public void setBounds(int x, int y, int width, int height) { if ( pannerImage != null ) { pannerWidth = pannerImage.getWidth(); pannerHeight = pannerImage.getHeight(); super.setBounds(x, y, pannerWidth, pannerHeight); } else { super.setBounds(x, y, pannerWidth, pannerHeight); } } private final void createSliderBox(int width, int height) { // create a custom navigator box slider = new JLabel(); slider.setBorder( new CompoundBorder( LineBorder.createBlackLineBorder(), new LineBorder(Color.white, 1)) ); sliderWidth = width + 2; sliderHeight = height + 2; slider.setBounds(0, 0, sliderWidth, sliderHeight); slider.setOpaque(false); add(slider); // add event handlers addMouseListener(new MouseClickHandler()); addMouseMotionListener(new MouseMotionHandler()); setOpaque(true); } // moves the slider box class MouseClickHandler extends MouseAdapter { public void mousePressed(MouseEvent e) { int mods = e.getModifiers(); Point p = e.getPoint(); if ( (mods & InputEvent.BUTTON1_MASK) != 0 ) { moveit(p.x, p.y); } } public void mouseReleased(MouseEvent e) { } } class MouseMotionHandler extends MouseMotionAdapter { public void mouseDragged(MouseEvent e) { Point p = e.getPoint(); int mods = e.getModifiers(); if ( (mods & InputEvent.BUTTON1_MASK) != 0 ) { moveit(p.x, p.y); } } } private final void moveit(int px, int py) { Insets inset = super.getInsets(); int pw = sliderWidth / 2; int ph = sliderHeight / 2; int x = px - pw; int y = py - ph; if ( px < inset.left ) x = -pw + inset.left; if ( py < inset.top ) y = -ph + inset.top; if ( px >= (pannerWidth - inset.right ) ) x = pannerWidth - pw - inset.right; if ( py >= (pannerHeight - inset.bottom) ) y = pannerHeight - ph - inset.bottom; // slider center sliderX = px; sliderY = py; // slider origin slider.setLocation(x, y); if ( scrollObject != null ) { int sx; int sy; sx = (int)((float)x*scale + .5F); sy = (int)((float)y*scale + .5F); if ( scrollObject instanceof ImageDisplay ) { ((ImageDisplay)scrollObject).setOrigin(-sx, -sy); } else { scrollObject.setLocation(-sx, -sy); } } } 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); } /** * 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. */ public synchronized void paintComponent(Graphics g) { Graphics2D g2D = null; if (g instanceof Graphics2D) { g2D = (Graphics2D)g; } else { System.err.println("not a Graphic2D"); return; } // if pannerImage is null, it's just a component if ( pannerImage == null ) { g2D.setColor(getBackground()); g2D.fillRect(0, 0, super.getWidth(), super.getHeight()); return; } // sigel - optimize later if ( slider != null && scrollObject != null ) { Rectangle rect = scrollObject.getBounds(); sliderWidth = (int) ((float)rect.width / scale + .5); sliderHeight = (int) ((float)rect.height / scale + .5); slider.setSize(new Dimension(sliderWidth, sliderHeight)); } // Get the clipping rectangle and translate it into image coordinates. Rectangle clipBounds = g.getClipBounds(); // Determine the extent of the clipping region in tile coordinates. int txmin, txmax, tymin, tymax; txmin = XtoTileX(clipBounds.x); txmin = maxInt(txmin, minTileX); txmin = minInt(txmin, maxTileX); txmax = XtoTileX(clipBounds.x + clipBounds.width - 1); txmax = maxInt(txmax, minTileX); txmax = minInt(txmax, maxTileX); tymin = YtoTileY(clipBounds.y); tymin = maxInt(tymin, minTileY); tymin = minInt(tymin, maxTileY); tymax = YtoTileY(clipBounds.y + clipBounds.height - 1); tymax = maxInt(tymax, minTileY); tymax = minInt(tymax, maxTileY); int xmin = pannerImage.getMinX(); int xmax = pannerImage.getMinX()+pannerImage.getWidth(); int ymin = pannerImage.getMinY(); int ymax = pannerImage.getMinY()+pannerImage.getHeight(); // Loop over tiles within the clipping region for (int tj = tymin; tj <= tymax; tj++) { for (int ti = txmin; ti <= txmax; ti++) { int tx = TileXtoX(ti); int ty = TileYtoY(tj); Raster tile = pannerImage.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); AffineTransform transform = AffineTransform.getTranslateInstance(tx, ty); 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(); Insets inset = super.getInsets(); if ( odometer != null ) { String output = " (" + (p.x-inset.left) + ", " + (p.y-inset.top) + ")"; 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); } // speed up math min and max by inlining private final int maxInt(int a, int b) { return a > b ? a : b; } private final int minInt(int a, int b) { return (a <= b) ? a : b; } }