package fr.unistra.pelican.gui;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.image.renderable.ParameterBlock;
import javax.media.jai.*;
import javax.swing.*;
import com.sun.media.jai.widget.DisplayJAI;
import fr.unistra.pelican.ByteImage;
import fr.unistra.pelican.IntegerImage;
/**
* This is a Scrollable and Zoomable picture in form of a { @link javax.swing.JPanel }.
* Its main purpose is being added to a { @link javax.swing.JScrollPane }.
* <p>
* For the GUI, that means :
* <p>
* Scroll the PZPicture as usual in the JScrollPane. When the PZPicture is in
* {@link #DEFAULT_MODE}, you can roll your mouse wheel to navigate, as usual.
* <p>
* Roll your mouse wheel when the PZPicture is in {@link #ZOOM_MODE} to zoom on it.
* <p>
* Pan the PZPicture by dragging & dropping your mouse.
*
* @author witz
*
*/
public class CustomDisplayJAI extends DisplayJAI implements MouseWheelListener {
///////////////
// CONSTANTS //
///////////////
public static final long serialVersionUID = 1L;
public static final int DEFAULT_MODE = 0;
public static final int ZOOM_MODE = 1;
public static final double ZOOM_IN_FACTOR = 1.1;
public static final double ZOOM_OUT_FACTOR = 0.9;
public static final boolean ZOOM_IN = true;
public static final boolean ZOOM_OUT = false;
public static final boolean DRAW_ON = true;
public static final boolean DRAW_OFF = false;
public static final int DEFAULT_BRUSH_SIZE = 3;
public static final int MAX_BRUSH_SIZE = 100;
public static final int MIN_BRUSH_SIZE = 1;
////////////
// FIELDS //
////////////
/** X coordinate of the cursor's last position. */
private int xLast;
/** Y coordinate of the cursor's last position. */
private int yLast;
/** Area taken up by graphics. */
private Dimension area;
private AffineTransform transform;
int ow;
int oh;
private BufferedImage original;
private RenderedImage rendered;
private double hscalemax;
private double hscalemin;
private double hscale = 1.;
private double wscalemax;
private double wscalemin;
private double wscale = 1.;
public boolean horizontalScrollEnabled = false;
private int navigateMode = DEFAULT_MODE;
private boolean drawMode;
/** Determines if the user is drawing or not. */
private boolean drawing = false;
/** The markers image. */
public TiledImage raster;
/** Indice of the transparency of the markers (255 = completely visible). */
public int rasterTransparency = 255;
/** The pen. */
public Stroke stroke;
/** The pen's color. It's a 1-sized float array, wich only element is between [0;1].
* Precisely, it's a fraction of 255. */
public float color[];
/** A lookup table associating all possible labels with their color ( 1 eraser+256 shades ). */
public int[][] colorMap = new int[257][4];
/** The color marker image (after the createColorMarkerImage process). */
private RenderedOp colorMarkerImage;
/** Default to it if any. */
public MouseWheelListener defaultMouseWheelListener;
/////////////////
// CONSTRUCTOR //
/////////////////
public CustomDisplayJAI() {
super();
this.addMouseListener( this );
this.addMouseMotionListener( this );
this.addMouseWheelListener( this );
this.stroke = new BasicStroke( DEFAULT_BRUSH_SIZE );
this.color = new float[1];
this.color[0] = 1f / 256f;
this.transform = new AffineTransform();
this.area = new Dimension();
this.drawEnabled( DRAW_ON );
}
/////////////
// METHODS //
/////////////
@Override
public void set( RenderedImage im ) { this.set(im, (ByteImage) null); }
/** Method which sets the background image and creates the markers image based
* on markers image passed in parameter, or on on dimension of the background image
* if {tt}markers{/tt} equals to {tt}nil{/tt}.
* @param image
* @param markersImage
*/
public void set( RenderedImage image, ByteImage markersImage ) {
// super.set( image );
this.original = (BufferedImage)image;
Dimension odd = this.getOWH();
this.ow = odd.width;
this.oh = odd.height;
this.rendered = toBuf( this.original.getScaledInstance( ow,oh, Image.SCALE_DEFAULT ) );
// define maximum and minimum sizes (with respect to the original image resolution)
// to wich the rendered image can be scaled.
int rw = Toolkit.getDefaultToolkit().getScreenSize().width;
int rh = Toolkit.getDefaultToolkit().getScreenSize().height;
int hmax,wmax, hmin,wmin;
if ( this.ow > this.oh ) {
wmax = Math.max( this.ow, rw );
hmax = (int)( (double)( wmax*this.oh ) / (double)this.ow );
wmin = Math.min( this.ow, 10 );
hmin = (int)( (double)( wmin*this.oh ) / (double)this.ow );
} else {
hmax = Math.max( this.oh, rh );
wmax = (int)( (double)( hmax*this.ow ) / (double)this.oh );
hmin = Math.min( this.oh, 10 );
wmin = (int)( (double)( hmin*this.ow ) / (double)this.oh );
}
this.hscalemax = (double)hmax / (double)oh;
this.wscalemax = (double)wmax / (double)ow;
this.hscalemin = (double)hmin / (double)oh;
this.wscalemin = (double)wmin / (double)ow;
punch();
// Create a marker image without an alpha channel
this.raster = fr.unistra.pelican.util.Tools.createGrayImage( markersImage,this.ow,this.oh );
// repaint the component
this.createColorMarkerImage();
this.repaint();
}
/** Method which sets the background image and creates the markers image based
* on markers image passed in parameter, or on on dimension of the background image
* if {tt}markers{/tt} equals to {tt}nil{/tt}.
* @param image
* @param markersImage
*/
public void set( RenderedImage image, IntegerImage markersImage ) {
this.original = (BufferedImage)image;
Dimension odd = this.getOWH();
this.ow = odd.width;
this.oh = odd.height;
this.rendered = toBuf( this.original.getScaledInstance( ow,oh, Image.SCALE_DEFAULT ) );
// define maximum and minimum sizes (with respect to the original image resolution)
// to wich the rendered image can be scaled.
int rw = Toolkit.getDefaultToolkit().getScreenSize().width;
int rh = Toolkit.getDefaultToolkit().getScreenSize().height;
int hmax,wmax, hmin,wmin;
if ( this.ow > this.oh ) {
wmax = Math.max( this.ow, rw );
hmax = (int)( (double)( wmax*this.oh ) / (double)this.ow );
wmin = Math.min( this.ow, 10 );
hmin = (int)( (double)( wmin*this.oh ) / (double)this.ow );
} else {
hmax = Math.max( this.oh, rh );
wmax = (int)( (double)( hmax*this.ow ) / (double)this.oh );
hmin = Math.min( this.oh, 10 );
wmin = (int)( (double)( hmin*this.ow ) / (double)this.oh );
}
this.hscalemax = (double)hmax / (double)oh;
this.wscalemax = (double)wmax / (double)ow;
this.hscalemin = (double)hmin / (double)oh;
this.wscalemin = (double)wmin / (double)ow;
punch();
// Create a marker image without an alpha channel
this.raster = fr.unistra.pelican.util.Tools.createGrayImage( markersImage,this.ow,this.oh );
// repaint the component
this.createColorMarkerImage();
this.repaint();
}
public void setNavigateMode( int navigateMode ) {
if ( navigateMode == DEFAULT_MODE
|| navigateMode == ZOOM_MODE )
this.navigateMode = navigateMode;
else this.navigateMode = DEFAULT_MODE;
}
public void drawEnabled( boolean enabled ) {
this.drawMode = enabled;
if ( enabled ) this.setCursor( Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR ) );
else this.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
}
/** Return a {@link java.awt.Dimension} object containing the original width and height of
* the original image {@link original}. This method erases the previous values contained in
* <tt>width</tt> and <tt>height</tt>.
* This method is vital, 'cause if you just do something simple like :
* <pre>
* width = this.original.getWidth(null);
* height = int oh = this.original.getHeight(null);
* </pre>
* <p>
* ... you're just screwed. T_T
*/
private Dimension getOWH() {
int ow = this.original.getWidth( null );
int oh = this.original.getHeight( null );
while ( ow < 0 || oh < 0 ) {
try { Thread.sleep(25); }
catch (InterruptedException e) {}
ow = this.original.getWidth( null );
oh = this.original.getHeight( null );
}
return new Dimension( ow,oh );
}
private void punch() {
int rw = (int)( this.rendered.getWidth() * this.transform.getScaleX() );
int rh = (int)( this.rendered.getHeight() * this.transform.getScaleY() );
if ( this.area.width != rw || this.area.width != rh ) {
this.area.width = rw;
this.area.height = rh;
// Update client's preferred size because the area taken up
// by the graphics has gotten larger or smaller.
this.setPreferredSize( area );
// Let the others know to update themselves
this.revalidate();
}
}
/** Zoom on the rendered image if <tt>zoom</tt> equals to { @link #ZOOM_IN }.
* Dezoom on the rendered image if <tt>zoom</tt> equals to { @link #ZOOM_OUT }.
* The zoom ratio is of 10% in any case.
* @param zoom { @link #ZOOM_IN } or { @link #ZOOM_OUT }.
*/
public double zoom( boolean zoom )
{
if ( zoom == ZOOM_IN )
this.rescale( this.hscale * ZOOM_IN_FACTOR, this.wscale * ZOOM_IN_FACTOR );
else // zoom == ZOOM_OUT
this.rescale( this.hscale * ZOOM_OUT_FACTOR, this.wscale * ZOOM_OUT_FACTOR );
return this.hscale;
}
/** Resets the rendered image to its initial scale. */
public void resetScale() { this.rescale( 1. ); }
/** Sets the rendered image to the scale <tt>scale</tt>.
* @param scale New image scale.
*/
public void rescale( double scale ) { this.rescale( scale,scale ); }
/** Sets the rendered image scale to <tt>ws</tt> in the X dimension,
* and to <tt>hs</tt> in the X dimension.
* @param ws New image scale in X.
* @param hs New image scale in Y.
*/
public void rescale( double ws, double hs ) {
this.hscale = hs;
this.wscale = ws;
if ( this.hscale > hscalemax ) this.hscale = this.hscalemax;
if ( this.wscale > wscalemax ) this.wscale = this.wscalemax;
if ( this.hscale < hscalemin ) this.hscale = this.hscalemin;
if ( this.wscale < wscalemin ) this.wscale = this.wscalemin;
// int w = (int)( this.ow * this.wscale );
// int h = (int)( this.oh * this.hscale );
// this.rendered = toBuf( this.original.getScaledInstance( w,h, Image.SCALE_DEFAULT ) );
this.transform = new AffineTransform(
AffineTransform.getScaleInstance( this.wscale,this.hscale ) );
punch();
repaint();
}
public static BufferedImage toBuf( Image image ) {
if( image instanceof BufferedImage ) return( (BufferedImage)image );
else {
image = new ImageIcon(image).getImage();
BufferedImage bufferedImage = new BufferedImage( image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_ARGB );
Graphics g = bufferedImage.createGraphics();
g.drawImage(image,0,0,null);
g.dispose();
return( bufferedImage );
}
}
@Override
public void paintComponent( Graphics g ) {
super.paintComponent( g );
Graphics2D g2d2 = (Graphics2D) g;
javax.media.jai.GraphicsJAI gj = javax.media.jai.GraphicsJAI.createGraphicsJAI(g2d2,this);
gj.drawRenderedImage( this.rendered, this.transform );
if ( colorMarkerImage != null ) {
// Draw marker image
gj.drawRenderedImage( colorMarkerImage, this.transform );
}
}
/** Initializes {@link #colorMarkerImage}. */
public void createColorMarkerImage() {
byte[][] lut = new byte[4][256];
for ( int i = 0 ; i < 256 ; i++ ) {
lut[0][i] = (byte) colorMap[i][0]; // reds
lut[1][i] = (byte) colorMap[i][1]; // greens
lut[2][i] = (byte) colorMap[i][2]; // blues
lut[3][i] = (byte) colorMap[i][3]; // alphas
}
LookupTableJAI table = new LookupTableJAI(lut);
ParameterBlock pb = new ParameterBlock();
pb.addSource( this.raster );
pb.add( table );
this.colorMarkerImage = JAI.create( "Lookup",pb,null );
}
public void mouseDragged( MouseEvent e ) {
if ( this.drawMode == DRAW_ON ) {
if ( this.drawing == true ) {
if ( this.raster != null ) {
Graphics2D g2d = this.raster.createGraphics();
// Set line width and marker (Aplha is 1.0 this time,
// because lut handels transparency)
g2d.setStroke( this.stroke );
g2d.setColor( new Color( this.raster.getColorModel().getColorSpace(),
this.color,
1.0f ) );
Point pe = new Point( e.getX(),e.getY() );
Point pl = new Point( this.xLast,this.yLast );
AffineTransform inverse = null;
try{ inverse = this.transform.createInverse(); }
catch( NoninvertibleTransformException ex ) { /* This never happens.*/ }
inverse.transform( pe,pe );
inverse.transform( pl,pl );
// Draw the line
g2d.draw( new Line2D.Double( pe, pl ) );
// Update
createColorMarkerImage();
// Repaint the component
repaint();
this.xLast = e.getX();
this.yLast = e.getY();
}
}
} else {
Container c = CustomDisplayJAI.this.getParent();
if ( c instanceof JViewport ) {
JViewport viewport = (JViewport) c;
Point p = viewport.getViewPosition();
int x = p.x - (e.getX() - this.xLast);
int y = p.y - (e.getY() - this.yLast);
int xMax = CustomDisplayJAI.this.getWidth() - viewport.getWidth();
int yMax = CustomDisplayJAI.this.getHeight() - viewport.getHeight();
if ( x < 0 ) x = 0;
if ( x > xMax ) x = xMax;
if ( y < 0 ) y = 0;
if ( y > yMax ) y = yMax;
viewport.setViewPosition( new Point(x,y) );
}
}
}
public void mousePressed( MouseEvent e ) {
if ( this.drawMode == DRAW_ON ) {
if ( e.getButton() == MouseEvent.BUTTON1 ) {
if ( raster != null ) {
this.drawing = true;
this.xLast = e.getX();
this.yLast = e.getY();
Graphics2D g2d = this.raster.createGraphics();
// Set line width and marker (Alpha is 1.0,
// because colorMap handles transparency)
g2d.setStroke( this.stroke );
g2d.setColor( new Color(
this.raster.getColorModel().getColorSpace(),
this.color,
1.0f ) );
Point pe = new Point( e.getX(),e.getY() );
Point pl = new Point( this.xLast,this.yLast );
AffineTransform inverse = null;
try{ inverse = this.transform.createInverse(); }
catch( NoninvertibleTransformException ex ) { /* This never happens.*/ }
inverse.transform( pe,pe );
inverse.transform( pl,pl );
// Draw the line
g2d.draw( new Line2D.Double( pe, pl ) );
// Update
createColorMarkerImage();
// Repaint the component
repaint();
}
}
} else {
this.setCursor( Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR ) );
xLast = e.getX();
yLast = e.getY();
}
}
public void mouseReleased( MouseEvent e ) {
if ( this.drawMode == DRAW_ON ) {
if ( e.getButton() == MouseEvent.BUTTON1 ) this.drawing = false;
}
else this.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
}
public void mouseWheelMoved( MouseWheelEvent e ) {
if ( this.navigateMode == ZOOM_MODE ) {
Container c = CustomDisplayJAI.this.getParent();
if ( c instanceof JViewport ) {
if ( e.getWheelRotation() < 0 ) this.zoom( CustomDisplayJAI.ZOOM_IN );
else CustomDisplayJAI.this.zoom( CustomDisplayJAI.ZOOM_OUT );
CustomDisplayJAI.this.revalidate();
e.consume();
}
} else {
if ( this.defaultMouseWheelListener != null )
this.defaultMouseWheelListener.mouseWheelMoved(e);
}
}
}