package fr.unistra.pelican.gui;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.ArrayList;
import javax.media.jai.*;
import javax.swing.*;
import javax.swing.event.*;
import fr.unistra.pelican.BooleanImage;
import fr.unistra.pelican.ByteImage;
import fr.unistra.pelican.Image;
import fr.unistra.pelican.algorithms.conversion.*;
import fr.unistra.pelican.util.History;
/**
* The Draw2D class allows the user to draw markers with or without a background image.
*
* @author Florent Sollier, Jonathan Weber, Regis Witz (rewrited + customized it)
*
*/
public class Draw2D extends JPanel {
///////////////
// CONSTANTS //
///////////////
public static final boolean TRANSPARENCY_PRESENT_BY_DEFAULT = false;
public static final boolean ADD_LABELS_PRESENT_BY_DEFAULT = true;
public static final double ZOOM_IN_FACTOR = 1.1;
public static final double ZOOM_OUT_FACTOR = 0.9;
////////////
// FIELDS //
////////////
// GUI fields
private JDialog frame;
/** Contains the labels list. */
private JComboBox labelsBox;
/** Contains the current label color. */
private JPanel colorPanel;
/** Add Labels to {#labelsBox}. */
private JButton addLabelsButton;
/** Allows to change the stroke size of the brush. */
private JSpinner brushSpinner;
/** Just the label that goes with {#transparencySlider}. */
private JLabel transparencyLabel;
/** Allows to change the transparency of the marker image. */
private JSlider transparencySlider;
/** Wheter or not {#transparencyLabel} and {#transparencySlider} will be added to this. */
private boolean transparencyEnabled;
/** Wheter or not {#addLabelsButton} will be added to this. */
private boolean addLabelsEnabled;
private JScrollPane scroll;
private CustomDisplayJAI display;
/** Undoes the last label painting. */
private JButton undoButton;
/** Undoes the last label painting. */
private JButton redoButton;
/** Begins a new marker image. */
private JButton resetButton;
/** Confirms marker drawing's end. */
private JButton okButton;
// other fields
/** The "background" image that must be processed in this. */
public Image inputImage;
/** The user marked image. */
public ByteImage output;
/** The background image converted in BufferedImage.
* Necessary for displaying the pic in the GUI.
*/
private BufferedImage bimg;
/** The markers image.
* Always equal to the marker image that was passed in argument at creation.
* And : yes, that means "always equal to <tt>null</tt>" if nothing was passed.
*/
public ByteImage markersImage;
/** In general, this is just equal to {@link #labelsBox} size.
* But we need this field to numerotate the next label when it is created. */
private int nbLabels;
/**
* true if resetting
*/
private boolean reset = false;
/** Use to save the marker image after each drawing in case of an undo. */
private History<WritableRenderedImage> markersHistory;
public Image results;
public Image rescold;
public BooleanImage frontiers;
//////////////////
// CONSTRUCTORS //
//////////////////
/** Secondary constructor.
* @param inputImage An {@link fr.unistra.pelican.Image} to set as background.
*/
public Draw2D( Image inputImage ) {
this( inputImage,null );
}
/** Secondary constructor.
* @param inputImage An {@link fr.unistra.pelican.Image} to set as background.
* @param title The frame's title.
*/
public Draw2D( Image inputImage,
String title ) {
this( inputImage,title, null );
}
/** Secondary constructor.
* @param inputImage An {@link fr.unistra.pelican.Image} to set as background.
* @param title The frame's title.
* @param transparencyEnabled
* @param addLabelsEnabled
*/
public Draw2D( Image inputImage,
String title,
boolean transparencyEnabled,
boolean addLabelsEnabled ) {
this( inputImage,title, null, transparencyEnabled, addLabelsEnabled );
}
/** Secondary constructor.
* @param inputImage An {@link fr.unistra.pelican.Image} to set as background.
* @param title The frame's title.
* @param markersImage
*/
public Draw2D( Image inputImage,
String title,
ByteImage markersImage ) {
this( inputImage,title, markersImage,
TRANSPARENCY_PRESENT_BY_DEFAULT,
ADD_LABELS_PRESENT_BY_DEFAULT
);
}
/** Primary constructor.
* @param inputImage An {@link fr.unistra.pelican.Image} to set as background.
* @param title The frame's title.
* @param markersImage
* @param transparencyEnabled
* @param addLabelsEnabled
*/
public Draw2D( Image inputImage,
String title,
ByteImage markersImage,
boolean transparencyEnabled,
boolean addLabelsEnabled ) {
this.inputImage = inputImage;
this.markersImage = markersImage;
this.transparencyEnabled = transparencyEnabled;
this.addLabelsEnabled = addLabelsEnabled;
this.bimg =
fr.unistra.pelican.util.Tools.pelican2Buffered( this.inputImage );
this.nbLabels = 2;
this.frame = new JDialog();
this.frame.setTitle( title );
this.frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
frame.setModal(true);
this.guiInitialization();
this.frame.setVisible(true);
}
/////////////
// METHODS //
/////////////
/** Initialisation of the Draw2D GUI. */
private void guiInitialization() {
this.setLayout( new BorderLayout() );
// the "north" panel
JPanel npanel = new JPanel();
npanel.setLayout( new GridBagLayout() );
// the "images" panel
JPanel ipanel = new JPanel();
ipanel.setLayout( new GridBagLayout() );
// the "south" panel
JPanel spanel = new JPanel();
spanel.setLayout( new GridBagLayout() );
// these three will listen to the events thrown by the components
// that will be created later in this method
// ( definitions for the internal classes [a|c|m]Listener
// can be found at the end of this file )
aListener alistener = new aListener();
cListener clistener = new cListener();
kListener klistener = new kListener();
mListener mlistener = new mListener();
// one: create the components which will be put on the "north" panel
this.labelsBox = new JComboBox();
this.labelsBox.setBackground(SystemColor.control);
this.labelsBox.setOpaque(false);
this.labelsBox.addItem("Eraser ");
this.labelsBox.addItem("Label 1 ");
this.labelsBox.addItem("Label 2 ");
this.labelsBox.setSelectedIndex(1);
this.labelsBox.addActionListener( alistener );
int[][] colorMap = lutInitialization();
this.colorPanel = new JPanel();
this.colorPanel.setBorder( BorderFactory.createLineBorder( Color.black ) );
this.colorPanel.setMinimumSize( new Dimension( 20,20 ) );
this.colorPanel.setPreferredSize( new Dimension( 20,20 ) );
this.colorPanel.setBackground( new Color(
colorMap[ this.labelsBox.getSelectedIndex() ][0],
colorMap[ this.labelsBox.getSelectedIndex() ][1],
colorMap[ this.labelsBox.getSelectedIndex() ][2] ) );
this.addLabelsButton = new JButton( "+" );
this.addLabelsButton.addActionListener( alistener );
JLabel brushThicknessLabel = new JLabel( "Thickness: " );
brushThicknessLabel.setBackground( SystemColor.control );
this.brushSpinner = new JSpinner();
this.brushSpinner.setModel( new SpinnerNumberModel(
CustomDisplayJAI.DEFAULT_BRUSH_SIZE,
CustomDisplayJAI.MIN_BRUSH_SIZE,
CustomDisplayJAI.MAX_BRUSH_SIZE,
1 ) );
this.brushSpinner.setMaximumSize( new Dimension( 32767,32767 ) );
this.brushSpinner.setMinimumSize( new Dimension( 40,18 ) );
this.brushSpinner.setPreferredSize( new Dimension( 40,18 ) );
this.brushSpinner.addChangeListener( clistener );
// this will be a waste if this.transparencyEnabled = false
this.transparencyLabel = new JLabel( "Transparency:" );
this.transparencySlider = new JSlider();
this.transparencySlider.setExtent( 0 );
this.transparencySlider.setMaximum( 255 );
this.transparencySlider.setPaintLabels( false );
this.transparencySlider.setPaintTicks( false );
this.transparencySlider.setPaintTrack( true );
this.transparencySlider.setBackground(SystemColor.control);
this.transparencySlider.setMaximumSize( new Dimension( 32767,24 ) );
this.transparencySlider.setValue( 255 );
this.transparencySlider.addChangeListener( clistener );
// two: create the components which will be put on the "south" panel
this.undoButton = new JButton( "Undo" );
this.undoButton.addActionListener( alistener );
this.undoButton.setEnabled( false );
this.redoButton = new JButton( "Redo" );
this.redoButton.addActionListener( alistener );
this.redoButton.setEnabled( false );
this.resetButton = new JButton( "Reset" );
this.resetButton.addActionListener( alistener );
this.resetButton.setEnabled( false );
this.okButton = new JButton( "Done" );
this.okButton.addActionListener( alistener );
this.okButton.setEnabled( true );
// three: create the component which contains this.display
// and will be placed on the central panel
this.display = new CustomDisplayJAI();
this.display.colorMap = colorMap;
this.display.set( this.bimg,this.markersImage );
MouseListener[] ml = this.display.getMouseListeners();
this.display.removeMouseListener( ml[0] );
this.display.addMouseListener( mlistener );
this.markersHistory = new History<WritableRenderedImage>();
// save first image for undo
int widthTI = this.display.raster.getWidth();
int heightTI = this.display.raster.getHeight();
WritableRenderedImage copy =
fr.unistra.pelican.util.Tools.createGrayImage((ByteImage) null, widthTI,heightTI );
copy.setData( this.display.raster.copyData() );
this.markersHistory.add( copy );
// Component which contains the MarkerDisplayJAI instance (display)
this.scroll = new JScrollPane( this.display );
// scroll.setPreferredSize( new Dimension( 300,250 ) );
scroll.setViewportBorder( BorderFactory.createLineBorder( Color.black ) );
MouseWheelListener[] listeners = scroll.getMouseWheelListeners();
MouseWheelListener defaultMouseWheelListener = listeners[0];
scroll.removeMouseWheelListener( defaultMouseWheelListener );
this.display.defaultMouseWheelListener = defaultMouseWheelListener;
// four: put all that GUI stuff in the panels
GridBagConstraints nconstraints = new GridBagConstraints();
nconstraints.fill = GridBagConstraints.HORIZONTAL;
nconstraints.weightx = 1.0;
nconstraints.gridx = 0;
nconstraints.gridy = 0;
nconstraints.gridwidth = 1;
npanel.add( this.labelsBox, nconstraints );
nconstraints.weightx = .25;
nconstraints.gridx ++;
npanel.add( this.colorPanel, nconstraints );
if ( this.addLabelsEnabled ) {
nconstraints.gridx ++;
npanel.add( this.addLabelsButton, nconstraints );
}
nconstraints.weightx = 1.0;
nconstraints.gridx ++;
npanel.add( Box.createRigidArea( new Dimension( 10,0 ) ) );
nconstraints.gridx ++;
npanel.add( brushThicknessLabel, nconstraints );
nconstraints.weightx = .25;
nconstraints.gridx ++;
npanel.add( this.brushSpinner, nconstraints );
nconstraints.weightx = 1.0;
nconstraints.gridx ++;
if ( this.transparencyEnabled ) {
npanel.add( Box.createRigidArea( new Dimension( 10,0 ) ) );
nconstraints.gridx++;
npanel.add( this.transparencyLabel, nconstraints );
nconstraints.weightx = 2.0;
nconstraints.gridx++;
npanel.add( this.transparencySlider, nconstraints );
}
GridBagConstraints sconstraints = new GridBagConstraints();
sconstraints.fill = GridBagConstraints.HORIZONTAL;
sconstraints.weightx = 1.0;
sconstraints.gridx = 0;
sconstraints.gridy = 0;
sconstraints.gridwidth = 1;
spanel.add( this.undoButton, sconstraints );
sconstraints.gridx ++;
spanel.add( this.redoButton, sconstraints );
sconstraints.gridx ++;
spanel.add( this.resetButton, sconstraints );
sconstraints.gridx ++;
spanel.add( Box.createRigidArea( new Dimension( 10,0 ) ) );
sconstraints.gridx ++;
spanel.add( this.okButton, sconstraints );
sconstraints.gridx ++;
GridBagConstraints iconstraints = new GridBagConstraints();
iconstraints.fill = GridBagConstraints.BOTH;
iconstraints.gridx = 0;
iconstraints.gridy = 0;
iconstraints.gridwidth = 1;
iconstraints.gridheight = 1;
// ipanel.add( this.scroll, iconstraints );
iconstraints.gridx ++;
this.add( npanel, BorderLayout.NORTH);
this.add( this.scroll, BorderLayout.CENTER );
this.add( spanel, BorderLayout.SOUTH);
// five : don't forget to manage the markers image possibly passed at creation
if( this.markersImage != null ) {
for( int i = 0 ; i < this.markersImage.maximumByte()-1 ; i++ ) {
this.nbLabels ++;
// add a label to the combobox
this.labelsBox.addItem( "Label " + this.nbLabels );
this.labelsBox.setSelectedItem( "Label " + this.nbLabels );
// set the current color
Color color = new Color(
(int) this.display.colorMap[ labelsBox.getSelectedIndex() ][ 0 ],
(int) this.display.colorMap[ labelsBox.getSelectedIndex() ][ 1 ],
(int) this.display.colorMap[ labelsBox.getSelectedIndex() ][ 2 ] );
this.colorPanel.setBackground( color );
}
okButton.setEnabled(true);
resetButton.setEnabled(true);
}
// six: finalize. you know that.
MouseListener[] mlis = this.display.getMouseListeners();
MouseMotionListener[] mmlis = this.display.getMouseMotionListeners();
MouseWheelListener[] mwlis = this.display.getMouseWheelListeners();
// this.display.removeMouseListener( mlis[0] );
// this.display.removeMouseMotionListener( mmlis[0] );
this.display.removeMouseWheelListener( mwlis[0] );
Component[] allthegui = { this, frame, npanel, ipanel, spanel, display, scroll,
undoButton, redoButton, resetButton, okButton, labelsBox,
colorPanel, addLabelsButton, brushThicknessLabel, brushSpinner,
transparencyLabel, transparencySlider
};
KeyListener[] klis;
for ( int c = 0 ; c < allthegui.length ; c++ ) {
klis = allthegui[c].getKeyListeners();
allthegui[c].addKeyListener( klistener );
// allthegui[c].addMouseListener( mlis[0] );
// allthegui[c].addMouseMotionListener( mmlis[0] );
allthegui[c].addMouseWheelListener( mwlis[0] );
}
this.setOpaque( true );
this.frame.setContentPane( this );
this.frame.pack();
}
/** Fill colorMap with 256 different colors. */
private int[][] lutInitialization() {
int[][] colorMap = new int[257][4];
// a list of numbers corresponding to different hues
final ArrayList<Integer> colorList = new ArrayList<Integer>();
int indice = 0;
colorList.add(128);
int newElement;
while ( colorList.size() < 16 ) {
newElement = colorList.get(indice) / 2;
if (!((newElement < 1) || (newElement > 256))) {
colorList.add(newElement);
newElement = newElement + 128;
if (!((newElement < 1) || (newElement > 256))) {
colorList.add(newElement);
}
}
indice++;
}
final double[] saturationList = { 1, 0.75, 0.5, 0.25 };
final double[] luminanceList = { 1, 0.75, 0.5, 0.25 };
// Set the first table element to be transparent
colorMap[0][0] = 127;
colorMap[0][1] = 127;
colorMap[0][2] = 127;
colorMap[0][3] = 0;
int x = 1;
// Set all the other colors (opaque)
for ( int j = 0 ; j < 4; j++ ) {
for ( int k = 0 ; k < 4; k++ ) {
for ( int i = 0; i < 16; i++ ) {
int[] tmp = HSYToRGB.convert(
(double) ( colorList.get(i)/256.0 ),saturationList[j], luminanceList[k] );
colorMap[x][0] = tmp[0];
colorMap[x][1] = tmp[1];
colorMap[x][2] = tmp[2];
colorMap[x][3] = 255;
x++;
}
}
}
return colorMap;
}
/** Get an estimation (upper bound) of the number of labels.
* @return The number of item currently in {@link #labelBox}.
*/
public int labels(){
return labelsBox.getItemCount();
}
//////////////////////
// EVENT MANAGEMENT //
// INTERNAL CLASSES //
//////////////////////
private class aListener implements ActionListener {
public void actionPerformed( ActionEvent e ) {
Object source = e.getSource();
if ( source == labelsBox ) {
if ( !reset ) {
display.color[0] = ( ( (float)(labelsBox.getSelectedIndex())) / 256f );
// set the current color to labelColor
Color color = new Color(
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 0 ],
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 1 ],
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 2 ] );
colorPanel.setBackground(color);
}
} else
if ( source == colorPanel ) {
// nothing to do here, chum.
} else
if ( source == addLabelsButton ) {
// Update plusButtonIndex
nbLabels ++;
// add a label to the combobox
labelsBox.addItem( "Label " + nbLabels ) ;
labelsBox.setSelectedItem( "Label " + nbLabels );
// set the current color to labelColor
Color color = new Color(
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 0 ],
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 1 ],
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 2 ] );
colorPanel.setBackground( color );
display.color[0] = (float) ( ( (float)(labelsBox.getSelectedIndex())) / 256f );
} else
if ( source == undoButton ) {
// re-display the last marker image saved in history
WritableRenderedImage copy = markersHistory.rewind();
display.raster.setData( copy.getData() );
redoButton.setEnabled( true );
if ( !markersHistory.canRewind() ) {
undoButton.setEnabled( false );
// and display.tiledImage is empty, so ...
okButton.setEnabled( false );
}
display.createColorMarkerImage();
display.repaint();
} else
if ( source == redoButton ) {
WritableRenderedImage copy = markersHistory.forward();
display.raster.setData( copy.getData() );
undoButton.setEnabled( true );
if ( !markersHistory.canForward() ) redoButton.setEnabled( false );
display.createColorMarkerImage();
display.repaint();
} else
if ( source == resetButton ) {
reset = true;
WritableRenderedImage copy = markersHistory.genesis();
undoButton.setEnabled( false );
if ( markersHistory.canForward() ) redoButton.setEnabled( true );
display.raster.setData( copy.getData() );
display.createColorMarkerImage();
display.repaint();
// Reset variables
nbLabels = 1;
display.color[0] = (1f / 256f);
// Remove all items and re-add the first ones
// labelsBox.removeAllItems();
// labelsBox.addItem("Eraser ");
// labelsBox.addItem("Label 1 ");
// labelsBox.addItem("Label 2 ");
// labelsBox.setSelectedIndex(1);
Color color = new Color(
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 0 ],
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 1 ],
(int) display.colorMap[ labelsBox.getSelectedIndex() ][ 2 ] );
// colorPanel.setBackground(color);
reset = false;
} else
if ( source == okButton ) {
// Transform the marker image as a bufferedImage for the transformation
BufferedImage bi = ((PlanarImage) display.raster).getAsBufferedImage();
// raster gets the value of each pixel from im
Raster raster = bi.getData();
// Save the features of bi
int type = bi.getType();
int height = raster.getHeight();
int width = raster.getWidth();
// Set the band number to 1, we are looking for a greyscale image without alpha
int band = 1;
// Instanciates output with the correct width, height and number of band
output = new ByteImage( width, height, 1, 1, band );
// Transfers each byte from raster to output
for ( int i = 0 ; i < width ; i++ )
for ( int j = 0 ; j < height ; j++ )
output.setPixelXYBByte( i,j,0, (byte) raster.getSample( i,j,0 ) );
// Set the color parameter
output.setColor( false );
// set the type parameter
output.type = type;
frame.dispose();
}
}
}
private class cListener implements ChangeListener {
public void stateChanged( ChangeEvent e ) {
Object source = e.getSource();
if ( source == brushSpinner ) {
if ( brushSpinner.getValue() instanceof Number ) {
int brushSize = (Integer) brushSpinner.getValue();
display.stroke = new BasicStroke( brushSize );
}
} else
if ( source == transparencySlider ) {
display.rasterTransparency = transparencySlider.getValue();
// Set the colorMap transparency for all but the first element
for ( int i = 1 ; i < 256 ; i++ )
display.colorMap[i][3] = (byte) display.rasterTransparency;
// refresh
display.createColorMarkerImage();
repaint();
}
}
}
private class kListener extends KeyAdapter {
public void keyPressed( KeyEvent e ) {
if ( e.getKeyCode() == KeyEvent.VK_SHIFT ) {
display.horizontalScrollEnabled = !display.horizontalScrollEnabled;
display.revalidate();
} else
if ( e.getKeyCode() == KeyEvent.VK_CONTROL )
display.setNavigateMode( CustomDisplayJAI.ZOOM_MODE );
else
if ( e.getKeyCode() == KeyEvent.VK_ALT )
display.drawEnabled( CustomDisplayJAI.DRAW_OFF );
}
public void keyReleased( KeyEvent e ) {
if ( e.getKeyCode() == KeyEvent.VK_SHIFT ) {
display.horizontalScrollEnabled = !display.horizontalScrollEnabled;
display.revalidate();
} else
if ( e.getKeyCode() == KeyEvent.VK_CONTROL )
display.setNavigateMode( CustomDisplayJAI.DEFAULT_MODE );
else
if ( e.getKeyCode() == KeyEvent.VK_ALT )
display.drawEnabled( CustomDisplayJAI.DRAW_ON );
}
}
private class mListener implements MouseListener {
public void mouseClicked( MouseEvent e ) { display.mouseClicked(e); }
public void mouseEntered( MouseEvent e ) { display.mouseEntered(e); }
public void mouseExited( MouseEvent e ) { display.mouseExited(e); }
public void mousePressed( MouseEvent e ) { display.mousePressed(e); }
/** Invoked when the mouse button has been released on a component.
* @param e The event to process.
*/
public void mouseReleased( MouseEvent e ) {
// save current image for undo
int widthTI = display.raster.getWidth();
int heightTI = display.raster.getHeight();
WritableRenderedImage copy =
fr.unistra.pelican.util.Tools.createGrayImage((ByteImage) null, widthTI,heightTI );
copy.setData( display.raster.copyData() );
markersHistory.add( copy );
// Enable buttons
undoButton.setEnabled( true );
redoButton.setEnabled( false );
resetButton.setEnabled( true );
okButton.setEnabled( true );
// display.mouseReleased(e);
}
}
////////////////////
// ANNOYING STUFF //
////////////////////
public static final long serialVersionUID = 1L;
/* This gets rid of the annoying exception for not using native acceleration :
* "Could not find mediaLib accelerator wrapper classes. Continuing in pure Java mode."
*/ static { System.setProperty( "com.sun.media.jai.disableMediaLib", "true" ); }
}