package robo.vision.widgets; /* * @(#)Contrast.java 1.14 99/06/20 09:36:14 * * 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.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.image.DataBuffer; import java.awt.image.renderable.ParameterBlock; import javax.media.jai.JAI; import javax.media.jai.LookupTableJAI; import javax.media.jai.PlanarImage; 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 2D sliders. Contrast subclasses * javax.swing.JComponent, and can be used in any context * that calls for a * JComponent. It monitors resize and * update events. * * This method rescales the data contents of a * rendered images and returns a 3band byte * image suitable for display. The operation * cannot be reversed, so a copy of the original * image is required to reset the contrast. * * This method can emulate window-level, contrast * stretching and low-high thresholding. Bands * cannot be independently altered. Data is * rescaled, thresholded and reformatted into * the range 0-255. * * * @author Dennis Sigel */ public final class Contrast extends JComponent implements MouseListener, MouseMotionListener { /** The display target */ ImageDisplay display; /** The original unmodified image */ private PlanarImage source; /** The size parameters. */ private int componentWidth; private int componentHeight; /** The slider box properties. */ private JLabel slider; private boolean sliderOpaque; private Color sliderBorderColor; private Color sliderColor; /** 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; /** Slider current values and limits */ private double hValue = 0.0; private double vValue = 0.0; /** range (limits) of 2D slider */ private double hmin = 0.0; private double hmax = 256.0; private double vmin = 0.0; private double vmax = 512.0; /** mapping between box size and limits */ private double hslope = 1.0; private double hy_int = 0.0; private double vslope = 1.0; private double vy_int = 0.0; /** X,Y position tracker */ private JLabel odometer; /** * Default constructor */ public Contrast() { super(); setLayout(null); componentWidth = 64; componentHeight = 64; setPreferredSize(new Dimension(64, 64)); createSliderBox(4, 4); } /** * Constructs a Contrast object of a given size * with no image set. */ public Contrast(int width, int height) { super(); setLayout(null); componentWidth = width; componentHeight = height; setPreferredSize(new Dimension(64, 64)); createSliderBox(4, 4); } /** * Constructs a Contrast object of a given size */ public Contrast(PlanarImage d, int width, int height) { super(); setLayout(null); source = d; componentWidth = width; componentHeight = height; setPreferredSize(new Dimension(width, height)); createSliderBox(componentWidth/16, componentHeight/16); map(); } public void setSource(PlanarImage d) { source = d; } 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; } /* relation between box size and limits */ private final void map() { Insets insets = super.getInsets(); double nw = (double) (componentWidth - insets.left - insets.right); double nh = (double) (componentHeight - insets.top - insets.bottom); hslope = (hmax - hmin) / nw; hy_int = hmax - nw*hslope; vslope = (vmax - vmin) / nh; vy_int = vmax - nh*vslope; } /** 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 final void setSliderColor(Color color) { slider.setBackground(color); } public void setSliderBorderColor(Color color) { slider.setBorder( new CompoundBorder( LineBorder.createBlackLineBorder(), new LineBorder(color, 1)) ); } public Dimension getMinimumSize() { return new Dimension(componentWidth, componentHeight); } public Dimension getPreferredSize() { return getMinimumSize(); } public Dimension getMaximumSize() { return getMinimumSize(); } public final int getWidth() { return componentWidth; } public final int getHeight() { return componentHeight; } // use for window-level public void setSliderLimits(double hmin, double hmax, double vmin, double vmax) { this.hmin = hmin; this.hmax = hmax; this.vmin = vmin; this.vmax = vmax; map(); } // typically level public double getCurrentHValue() { return 0.0; } // typically window public double getCurrentVValue() { return 0.0; } /** force a fixed size. Called by the AWT. */ public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, componentWidth, componentHeight); } 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); } else if ( (mods & InputEvent.BUTTON2_MASK) != 0 ) { reset(); } } 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); } } } public final void reset() { moveit(componentWidth/2, componentHeight/2); } public final void setDisplay(ImageDisplay d) { display = d; } /** * px and py are true mouse positions * x and y is position of slider center */ private final void moveit(int px, int py) { int x; int y; Insets insets = super.getInsets(); // containment of slider box if ( px < insets.left ) { x = insets.left; } else if ( px >= (componentWidth - insets.right) ) { x = componentWidth - insets.right; } else { x = px; } if ( py < insets.top ) { y = insets.top; } else if ( py >= (componentHeight - insets.bottom) ) { y = componentHeight - insets.bottom; } else { y = py; } // slider center sliderX = x; sliderY = y; // slider origin slider.setLocation(x-sliderWidth/2, y-sliderHeight/2); // calculate contrast from slider position map(); double nx = (double) x - insets.left; double ny = (double) y - insets.top; double window = vslope * ny + vy_int; double level = hslope * nx + hy_int; if ( source != null ) { PlanarImage dst = getWindowLevelImage(source, window, level); display.set(dst); } } public synchronized void paintComponent(Graphics g) { Graphics2D g2D = null; if (g instanceof Graphics2D) { g2D = (Graphics2D)g; } else { System.err.println("not a Graphic2D"); return; } g2D.setColor(getBackground()); g2D.fillRect(0, 0, super.getWidth(), super.getHeight()); } // 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); } /** auto contrast mapping (returns a byte image) */ public static final PlanarImage getAutoRescaledImage(PlanarImage image) { ParameterBlock pb = null; PlanarImage dst = null; int bands = image.getSampleModel().getNumBands(); int dtype = image.getSampleModel().getDataType(); double rmin; double rmax; if ( dtype == DataBuffer.TYPE_BYTE ) { rmin = 255.0; rmax = 0.0; pb = new ParameterBlock(); pb = new ParameterBlock(); pb.addSource(image); pb.add(null); pb.add(1); pb.add(1); dst = JAI.create("extrema", pb, null); double[][] extrema = (double[][])dst.getProperty("extrema"); int numBands = dst.getSampleModel().getNumBands(); // find the overall min, max (all bands) for ( int i = 0; i < numBands; i++ ) { if ( extrema[0][i] < rmin ) rmin = extrema[0][i]; if ( extrema[1][i] > rmax ) rmax = extrema[1][i]; } double[] slope = new double[numBands]; double[] y_int = new double[numBands]; for ( int i = 0; i < numBands; i++ ) { slope[i] = (255.0D - 0.0D) / (rmax - rmin); y_int[i] = 255.0D - slope[i]*rmax; } // rescale from xxx to byte range pb = new ParameterBlock(); pb.addSource(dst); pb.add(slope); pb.add(y_int); dst = JAI.create("rescale", pb, null); // produce a byte image pb = new ParameterBlock(); pb.addSource(dst); pb.add(DataBuffer.TYPE_BYTE); dst = JAI.create("format", pb, null); } else if ( dtype == DataBuffer.TYPE_SHORT ) { } else if ( dtype == DataBuffer.TYPE_USHORT ) { } else if ( dtype == DataBuffer.TYPE_INT ) { } else if ( dtype == DataBuffer.TYPE_FLOAT ) { } else if ( dtype == DataBuffer.TYPE_DOUBLE ) { } return dst; } /** returns a 3band byte image */ public static final PlanarImage getRescaledImage(PlanarImage image, double low, double high) { if ( image == null ) { return null; } ParameterBlock pb = null; PlanarImage dst = null; int bands = image.getSampleModel().getNumBands(); int dtype = image.getSampleModel().getDataType(); double rmin; double rmax; double slope; double y_int; if ( dtype == DataBuffer.TYPE_BYTE ) { // use a lookup table for rescaling if ( high != low ) { slope = 256.0 / (high - low); y_int = 256.0 - slope*high; } else { slope = 0.0; y_int = 0.0; } byte lut[][] = new byte[bands][256]; for ( int i = 0; i < 256; i++ ) { for ( int j = 0; j < bands; j++ ) { int value = (int)(slope*i + y_int); if ( value < (int)low ) { value = 0; } else if ( value > (int)high ) { value = 255; } else { value &= 0xFF; } lut[j][i] = (byte) value; } } LookupTableJAI lookup = new LookupTableJAI(lut); pb = new ParameterBlock(); pb.addSource(image); pb.add(lookup); dst = JAI.create("lookup", pb, null); } else if ( dtype == DataBuffer.TYPE_SHORT || dtype == DataBuffer.TYPE_USHORT ) { // use a lookup table for rescaling if ( high != low ) { slope = 256.0 / (high - low); y_int = 256.0 - slope*high; } else { slope = 0.0; y_int = 0.0; } byte lut[][] = new byte[bands][65536]; for ( int i = 0; i < 65535; i++ ) { for ( int j = 0; j < bands; j++ ) { int value = (int)(slope*i + y_int); if ( dtype == DataBuffer.TYPE_USHORT ) { value &= 0xFFFF; } if ( value < (int)low ) { value = 0; } else if ( value > (int)high ) { value = 255; } else { value &= 0xFF; } lut[j][i] = (byte) value; } } LookupTableJAI lookup = new LookupTableJAI(lut); pb = new ParameterBlock(); pb.addSource(image); pb.add(lookup); dst = JAI.create("lookup", pb, null); } else if ( dtype == DataBuffer.TYPE_INT || dtype == DataBuffer.TYPE_FLOAT || dtype == DataBuffer.TYPE_DOUBLE ) { // use the rescale and format ops if ( high != low ) { slope = 256.0 / (high - low); y_int = 256.0 - slope*high; } else { slope = 0.0; y_int = 0.0; } pb = new ParameterBlock(); pb.addSource(image); pb.add(slope); pb.add(y_int); dst = JAI.create("rescale", pb, null); // produce a byte image pb = new ParameterBlock(); pb.addSource(dst); pb.add(DataBuffer.TYPE_BYTE); dst = JAI.create("format", pb, null); } return dst; } public static final PlanarImage getWindowLevelImage(PlanarImage image, double window, double level) { double low = level - window/2.0; double high = level + window/2.0; return getRescaledImage(image, low, high); } }