/* * 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); } }