/*
* org.openmicroscopy.shoola.util.ui.colourpicker.HSVWheel
*
*------------------------------------------------------------------------------
* Copyright (C) 2006 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.ui.colourpicker;
//Java imports
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.event.ChangeListener;
//Third-party libraries
//Application-internal dependencies
import org.openmicroscopy.shoola.util.image.geom.Factory;
import org.openmicroscopy.shoola.util.math.geom2D.PlanePoint;
/**
* Creates the HSVColour wheel and provides methods for manipulating it's
* settings: Changing alpha and value(HSV). The wheel is created from a lookup
* table generated in {@link #buildLUT()} which also creates reverse lookup
* tables used to determine the position on the wheel based on Hue, Saturation
* values.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* <small>
* (<b>Internal version:</b> $Revision: $ $Date: $)
* </small>
* @since OME2.2
*/
class HSVWheel
extends JPanel
{
/** The default stroke. */
private static final Stroke LINE = new BasicStroke(1.0f);
/** The default color. */
private static final Color LINE_COLOR = Color.BLACK;
/**
* The colour wheel is created as an bufferedImage which is created
* in createColourWheel.
*/
private BufferedImage img;
/** Diameter of ColourWheel. */
private float wheelwidth;
/** Radius of the wheel. */
private float radius;
/** Position of the puck on ColourWheel. */
private PlanePoint puck;
/** Colour of the puck border on the wheel. */
private Color puckColour;
/** Colour of the puck fill on the wheel. */
private Color puckfillColour;
/** Lookup table used to construct colourwheel. */
private int[][][] lut;
/**
* listener to the control, this will be the UI element which contains
* HSVWheel. The listeners will be notified when the user has change the
* puck position.
*/
private List<ChangeListener> listeners;
/** Reference to the Control. */
private RGBControl control;
/** Mouse listener used to get mouse down and drag events. */
private HSVWheelListener mouselistener;
/**
* Builds the colour wheel graphic based on LUT, construct in a
* BufferedImage to improve speed.
*/
private void createColourWheelFromLUT()
{
int sz = (int) radius;
float szsz = sz*sz;
float value = control.getValue();
for (int x = sz ; x > -sz ; x--)
for (int y = sz ; y > -sz ; y--)
if (x*x+y*y < szsz) {
img.setRGB(sz+x, sz+y, Factory.makeARGB((
(int) (control.getAlpha()*255)),
(int) (lut[x+sz][y+sz][0]*value),
(int) (lut[x+sz][y+sz][1]*value),
(int) (lut[x+sz][y+sz][2]*value)));
}
}
/**
* Builds the lookup table for the colourwheel. Although the WheelUI
* changes all parts of the HSV vector, the wheel only changes the H, S
* components. We can then create a colour lookup table which will set these
* It is much fast to render the wheel using this.
*/
private void buildComponents()
{
int sz = (int) radius;
float f, p, q, t;
float s, fsz, xd, yd, sd;
float angle;
int hi;
float szsz = sz*sz;
float value = 1;
fsz = sz;
for (int x = sz ; x > -sz ; x--)
{
for (int y = sz ; y > -sz ; y--)
{
if (x*x+y*y < szsz)
{
s = (float) Math.sqrt(x*x+y*y);
xd = x/fsz;
yd = y/fsz;
sd = (s/fsz);
if (sd != 0)
angle = (float) Math.toDegrees(Math.acos(xd/sd));
else angle = 90;
if (yd < 0) angle = 360-angle;
hi = ((int) angle/60)%6;
switch (hi) {
case 0:
f = ((angle/60.0f)-hi);
p = (value)*(1-sd);
t = (value)*(1-(1-f)*sd);
lut[x+sz][y+sz][0] = (int) (value*255.0f);
lut[x+sz][y+sz][1] = (int) (t*255.0f);
lut[x+sz][y+sz][2] = (int) (p*255.0f);
break;
case 1:
f = ((angle/60.0f)-hi);
p = (value)*(1-sd);
q = (value)*(1-f*sd);
lut[x+sz][y+sz][0] = (int) (q*255.0f);
lut[x+sz][y+sz][1] = (int) (value*255.0f);
lut[x+sz][y+sz][2] = (int) (p*255.0f);
break;
case 2:
f = ((angle/60.0f)-hi);
p = (value)*(1-sd);
t = (value)*(1-(1-f)*sd);
lut[x+sz][y+sz][0] = (int) (p*255.0f);
lut[x+sz][y+sz][1] = (int) (value*255.0f);
lut[x+sz][y+sz][2] = (int) (t*255.0f);
break;
case 3:
f = ((angle/60.0f)-hi);
p = (value)*(1-sd);
q = (value)*(1-f*sd);
lut[x+sz][y+sz][0] = (int) (p*255.0f);
lut[x+sz][y+sz][1] = (int) (q*255.0f);
lut[x+sz][y+sz][2] = (int) (value*255.0f);
break;
case 4:
f = ((angle/60.0f)-hi);
p = (value)*(1-sd);
t = (value)*(1-(1-f)*sd);
lut[x+sz][y+sz][0] = (int) (t*255.0f);
lut[x+sz][y+sz][1] = (int) (p*255.0f);
lut[x+sz][y+sz][2] = (int) (value*255.0f);
break;
case 5:
f = ((angle/60.0f)-hi);
p = (value)*(1-sd);
q = (value)*(1-f*sd);
lut[x+sz][y+sz][0] = (int) (value*255.0f);
lut[x+sz][y+sz][1] = (int) (p*255.0f);
lut[x+sz][y+sz][2] = (int) (q*255.0f);
break;
}
}
}
}
}
/**
* Renders the graphics colourwheel.
*
* @param g The graphics context.
*/
private void render(Graphics2D g)
{
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Ellipse2D.Float ellipse = new Ellipse2D.Float(1f, 1f, wheelwidth-2,
wheelwidth-2);
Color c = getBackground();
g.setColor(LINE_COLOR);
g.draw(ellipse);
g.setColor(c);
g.drawImage(img, 0, 0, (int) wheelwidth, (int) wheelwidth, null);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
if (puck == null) return;
g.setStroke(LINE);
g.setPaint(puckfillColour);
g.fillRect((int) puck.x1-2, (int) puck.x2-2, 4, 4);
g.setPaint(puckColour);
g.drawRect((int) puck.x1-2, (int) puck.x2-2, 4, 4);
}
/**
* Builds the lookup table of the colourwheel, althought the WheelUI
* changes all parts of the HSV vector, the wheel only changes the H, S
* components. We can then create a colour lookup table which will set them.
*/
private void buildLUT()
{
lut = new int[(int) wheelwidth][(int) wheelwidth][3];
buildComponents();
}
/**
* Fires a Changed event to all listeners stating the HSVWheel has changed.
*/
private void fireChangeEvent()
{
for (int i = 0 ; i < listeners.size(); i++)
listeners.get(i).stateChanged(new ColourChangedEvent(this));
}
/**
* Sets the wheel width to the panel size, the puck position to match the
* colour specified in the model and creates LUT and colourwheel image.
* Called when the panel changes size.
*/
private void changePanelSize()
{
wheelwidth = this.getWidth() < this.getHeight() ?
this.getWidth() : this.getHeight();
radius = wheelwidth/2;
puckColour = Color.black;
puckfillColour = Color.white;
img = new BufferedImage((int) wheelwidth, (int) wheelwidth,
BufferedImage.TYPE_INT_ARGB);
buildLUT();
createColourWheelFromLUT();
findPuck();
}
/**
* Constructs the HSVWheel and reference to the control c.
*
* @param c Reference to the control. Mustn't be <code>null</code>.
*/
HSVWheel(RGBControl c)
{
if (c == null) throw new NullPointerException("No control.");
control = c;
radius = 1f;
puck = new PlanePoint(radius, radius);
mouselistener = new HSVWheelListener(this);
this.addMouseListener(mouselistener);
this.addMouseMotionListener(mouselistener);
listeners = new ArrayList<ChangeListener>();
}
/**
* Adds listener to the components who will be notified when
* the state changes in HSVWheel.
*
* @param listener The listener to add.
*/
void addListener(ChangeListener listener) { listeners.add(listener); }
/**
* Gets the Hue of the colour determined by the position of the puck.
*
* @return Returns the value of the hue from the Hue Saturation
* look up table (HSlut).
*/
float getHue()
{
double x = puck.x1-radius;
double y = puck.x2-radius;
double s = Math.sqrt(x*x+y*y);
double xd = x/radius;
double yd = y/radius;
double sd = (s/radius);
double angle;
/* Check for achromatic colours */
if (x == 0 && y == 0) return 0;
if (sd != 0) angle = (float) Math.toDegrees(Math.acos(xd/sd));
else angle = 90;
if (yd < 0) angle = 360-angle;
return (float) angle/360.0f;
}
/**
* Gets the Saturation of the colour determined by the position of the puck.
*
* @return Returns the value of the saturation from the Hue Saturation
* look up table.
*/
float getSaturation()
{
double x = puck.x1-radius;
double y = puck.x2-radius;
double s = Math.sqrt(x*x+y*y);
return (float) s/radius;
}
/**
* Returns <code>true</code> if the wheel has been picked in the
* JPanel component, <code>false</code> otherwise.
*
* @param x The position(x coord) of the mouse relative to the panel.
* @param y The position(y coord) of the mouse relative to the panel.
* @return See above.
*/
boolean picked(int x, int y)
{
float dx = (x-radius);
float dx2 = dx*dx;
float dy = (y-radius);
float dy2 = dy*dy;
float dist = dx2+dy2;
float r2 = radius*radius;
if (dist<=r2)
{
puck = new PlanePoint(x,y);
return true;
}
return false;
}
/**
* Method called on mousedown, checks to see if either the colourwheel,
* has been picked, if so set puck to the new x, y position and post
* statechanged event.
*
* @param x The mouse x position.
* @param y The mouse x position.
*/
void mouseDown(int x, int y) { if (picked(x, y)) fireChangeEvent(); }
/**
* Method called on mousedrag, checks to see if either the colourwheel,
* has been picked, if so set puck to the new x, y position and post
* statechanged event.
*
* @param x The mouse x position.
* @param y The mouse x position.
*/
void mouseDrag(int x, int y) { if (picked(x, y)) fireChangeEvent(); }
/**
* Finds the position of the puck on the wheel based on the current colour
* selected by the model.
*/
void findPuck()
{
float h = (control.getHue()*360.0f);
float s = (control.getSaturation());
double x = s*Math.cos(Math.toRadians(h));
double y = s*Math.sin(Math.toRadians(h));
if (s != 0)
{
puck.x1 = x*radius+radius;
puck.x2 = y*radius+radius;
}
else
{
puck.x1 = radius;
puck.x2 = radius;
}
}
/** Refreshes the display. */
void refresh() { createColourWheelFromLUT(); }
/**
* Overridden, calls the {@link #changePanelSize()} method, to create
* a new LUT and wheel to the size of the window.
* @see java.awt.Component#setBounds(int, int, int, int)
*/
public void setBounds(int x, int y, int h, int w)
{
super.setBounds(x, y, w, h);
changePanelSize();
}
/**
* Overridden, calls the {@link #changePanelSize()} method, to create
* a new LUT and wheel to the size of the window.
* @see java.awt.Component#setBounds(java.awt.Rectangle)
*/
public void setBounds(Rectangle r)
{
super.setBounds(r);
changePanelSize();
}
/**
* Overridden to render the color wheel.
* @see javax.swing.JComponent#paintComponent(Graphics)
*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
render((Graphics2D) g);
}
}