/* * $RCSfile: Panner.java,v $ * * * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 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 MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF * USING, MODIFYING OR DISTRIBUTING THIS 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 THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for * use in the design, construction, operation or maintenance of any * nuclear facility. * * $Revision: 1.1 $ * $Date: 2005/02/11 04:40:48 $ * $State: Exp $ */ package com.ebixio.jai; 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.RenderingHints; 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 javax.media.jai.InterpolationBilinear; import javax.media.jai.PlanarImage; import javax.media.jai.TiledImage; import javax.media.jai.operator.ScaleDescriptor; import javax.media.jai.operator.SubsampleAverageDescriptor; 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. */ 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 = null; private boolean sliderOpaque = false; private Color sliderBorderColor = Color.WHITE; /** 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; private Point mouseDragStart = new Point(0,0); /** 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 itemRect = item.getBounds(); float scale_x; float scale_y; float imageWidth = (float) image.getWidth(); float imageHeight = (float) image.getHeight(); if ( imageWidth >= (float)itemRect.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 ) { if (scale_x > 1 || scale_y > 1) { InterpolationBilinear interp = new InterpolationBilinear(); Float zerof = new Float(0.0F); temp = (PlanarImage)ScaleDescriptor.create(image, new Float(scale_x), new Float(scale_y), zerof, zerof, interp, null); } else { RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); temp = (PlanarImage)SubsampleAverageDescriptor.create(image, new Double(scale_x), new Double(scale_y), qualityHints); } } 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)itemRect.width * scale_x); int th = (int) ((float)itemRect.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 item Swing object to be controlled. * @param image a PlanarImage to be displayed. * @param maxSize 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 * @param panner_width Width * @param panner_height Height */ 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 * @param item Swing object to be controlled. * @param image a PlanarImage to be displayed. * @param thumb a thumbnail 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. * @param item Swing object to be controlled. * @param image A PlanarImage to be displayed. * @param maxSize Max width or height for scaled image. */ public synchronized void set(JComponent item, PlanarImage image, int maxSize) { if (item == null) { return; } enableScale = true; initialize(item, image, null, maxSize); } public synchronized void set(JComponent item, PlanarImage image, PlanarImage thumb) { if (item == null) { return; } enableScale = true; initialize(item, image, thumb, Math.max(thumb.getWidth(), thumb.getHeight())); } 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) * @param x X * @param y Y */ public final void setSliderLocation(int x, int y) { mouseDragStart.move(sliderWidth/2, sliderHeight/2); 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) { sliderBorderColor = color; slider.setBorder( new CompoundBorder( LineBorder.createBlackLineBorder(), new LineBorder(color, 1)) ); } @Override public Dimension getMinimumSize() { return new Dimension(pannerWidth, pannerHeight); } @Override public Dimension getPreferredSize() { return getMinimumSize(); } @Override public Dimension getMaximumSize() { return getMinimumSize(); } @Override public final int getWidth() { return pannerWidth; } @Override public final int getHeight() { return pannerHeight; } /** force a fixed size. Called by the AWT. */ @Override 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 sliderWidth = width + 2; sliderHeight = height + 2; if (slider == null) { slider = new JLabel(); slider.setBorder( new CompoundBorder( LineBorder.createBlackLineBorder(), new LineBorder(sliderBorderColor, 1)) ); slider.setOpaque(sliderOpaque); add(slider); // add event handlers addMouseListener(new MouseClickHandler()); addMouseMotionListener(new MouseMotionHandler()); setOpaque(true); } slider.setBounds(0, 0, sliderWidth, sliderHeight); if (scrollObject != null) { scrollObject.setLocation(0, 0); } } // moves the slider box class MouseClickHandler extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { int mods = e.getModifiers(); Point p = e.getPoint(); if ( (mods & InputEvent.BUTTON1_MASK) != 0 ) { mouseDragStart = p; } } @Override public void mouseReleased(MouseEvent e) { } } class MouseMotionHandler extends MouseMotionAdapter { @Override 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; Point sliderLoc = slider.getLocation(); sliderLoc.x += px - mouseDragStart.x; sliderLoc.y += py - mouseDragStart.y; // Do not allow the slider to go outside the panner sliderLoc.x = Math.max(sliderLoc.x, 0); sliderLoc.y = Math.max(sliderLoc.y, 0); sliderLoc.x = Math.min(sliderLoc.x, pannerWidth - sliderWidth * 4/5); sliderLoc.y = Math.min(sliderLoc.y, pannerHeight - sliderHeight * 4/5); // slider origin slider.setLocation(sliderLoc); mouseDragStart.move(px, py); // slider center sliderX = px; sliderY = py; if ( scrollObject != null ) { int sx; int sy; sx = (int)((float)sliderLoc.x*scale + .5F); sy = (int)((float)sliderLoc.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; } // 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; } }