/* * $RCSfile: ImageDisplay.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:47 $ * $State: Exp $ */ package com.ebixio.jai; import com.ebixio.util.Log; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; 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 java.util.logging.Level; import javax.media.jai.PlanarImage; import javax.media.jai.RasterFactory; import javax.swing.JComponent; import javax.swing.SwingWorker; /** * 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. */ public class ImageDisplay extends JComponent { /** The source PlanarImage. */ protected PlanarImage source; /** The image's SampleModel. */ protected SampleModel sampleModel; /** The image's ColorModel or one we supply. */ protected ColorModel colorModel = null; /** 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 int componentWidth; protected int componentHeight; /** Brightness control */ protected BufferedImageOp biop = null; protected boolean brightnessEnabled = false; protected int brightness = 0; protected byte[] lutData; public SwingWorker imgLoader = null; /** Initializes the ImageDisplay. */ private synchronized void initialize() { if ( source == null ) return; //Log.log("ImageDisplay", Level.INFO, true); Log.log(Level.FINEST, "ImageDisplay"); try { componentWidth = source.getWidth(); componentHeight = source.getHeight(); } catch (Exception e) { // Invalid image file return; } Log.log(Level.FINEST, "ImageDisplay::initialize Init 2"); setPreferredSize(new Dimension(componentWidth, componentHeight)); this.sampleModel = source.getSampleModel(); // First check whether the opimage has already set a suitable ColorModel if (this.colorModel == null) { 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(false); } /** * Constructs an ImageDisplay to display a PlanarImage. * * @param im 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(false); } /** * 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. * The initialize() call could take a long time (1s) to load the image. We don't * want to block the UI thread for that long, so we execute initialize() in a * separate thread and update the UI (repaint) after that's done. * @param im The new image to load. */ public void set(PlanarImage im) { source = im; if (imgLoader != null && !imgLoader.isDone()) { //Log.log("ImageDisplay::set cancel thread"); imgLoader.cancel(true); } imgLoader = new SwingWorker<Boolean, Void>() { @Override public Boolean doInBackground() { initialize(); return new Boolean(true); } @Override public void done() { setOrigin(0, 0); repaint(); } }; imgLoader.execute(); } public void set(PlanarImage im, int x, int y) { source = im; initialize(); setOrigin(x, y); } public PlanarImage getImage() { return source; } /** Provides panning * @param x The X coordinate * @param y The Y coordinate */ 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. * @param x X * @param y Y * @param width Width * @param height Height */ @Override 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); } @Override public void setLocation(int x, int y) { shift_x = x; shift_y = y; super.setLocation(x, y); } private int XtoTileX(int x) { return (int) Math.floor((double) (x - tileGridXOffset)/tileWidth); } private int YtoTileY(int y) { return (int) Math.floor((double) (y - tileGridYOffset)/tileHeight); } private int TileXtoX(int tx) { return tx*tileWidth + tileGridXOffset; } private int TileYtoY(int ty) { return ty*tileHeight + tileGridYOffset; } private static void debug(String msg) { System.out.println(msg); } private byte clampByte(int v) { if ( v > 255 ) { return (byte)255; } else if ( v < 0 ) { return (byte)0; } else { return (byte)v; } } private 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(); brightness = b; } } /** * Sets the upper threshold (the lightest color displayed). Anything lighter * than this value will be clamped to max (255). * @param t A value between 0 and 255. */ public final void setThreshold(int t) { if (brightnessEnabled = true) { for (int i = 0; i < 256; i++) { if (i > t) { lutData[i] = (byte)255; } else { lutData[i] = (byte)i; } } 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. */ @Override public synchronized void paintComponent(Graphics g) { Graphics2D g2D; if (g instanceof Graphics2D) { g2D = (Graphics2D)g; } else { return; } // if source is null, it's just a component if ( source == null || (imgLoader != null && !imgLoader.isDone())) { 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.setColor(Color.red); 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 = Raster.createWritableRaster(sampleModel, dataBuffer, null); BufferedImage bi; try { bi = new BufferedImage(colorModel, wr, colorModel.isAlphaPremultiplied(), null); } catch (Exception e) { this.colorModel = PlanarImage.createColorModel(this.sampleModel); 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); try { lookup.filter(bi, bimg); g2D.drawImage(bimg, biop, tx+transX+insets.left, ty+transY+insets.top); } catch (Exception e) { // We must have used an indexed image above... g2D.drawRenderedImage(bi, AffineTransform.getTranslateInstance(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); } } } } } }