/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 1999-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.image;
import java.util.Objects;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.plaf.ComponentUI;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.Map;
import java.util.Locale;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.SampleDimension;
import org.geotoolkit.display.axis.Graduation;
import org.geotoolkit.internal.coverage.CoverageUtilities;
/**
* A color ramp with a graduation. The colors can be specified with a {@link SampleDimension},
* an array of {@link Color}s or an {@link IndexColorModel} object, and the graduation is
* specified with a {@link Graduation} object. The resulting {@code ColorRamp} object
* is usually painted together with a remote sensing image.
*
* <p> </p>
* <p align="center"><img src="doc-files/ColorRamp.png"></p>
* <p> </p>
*
* @author Martin Desruisseaux (MPO, IRD)
* @version 3.16
*
* @since 1.1
* @module
*/
@SuppressWarnings("serial")
public class ColorRamp extends JComponent {
/**
* The object to use for painting the color ramp.
*/
@SuppressWarnings("serial")
private final class Painter extends org.geotoolkit.internal.swing.ColorRamp {
/** Tells the painter that we want to be informed about color changes. */
@Override protected boolean reportColorChanges() {
return true;
}
/** Allows user to override the {@code createGraduation} method. */
@Override protected Graduation createGraduation(final Graduation reuse,
final SampleDimension band, final double minimum, final double maximum)
{
return ColorRamp.this.createGraduation(reuse, band, minimum, maximum);
}
/** Uses the widget locale for formatting error messages and graduation labels. */
@Override public Locale getLocale() {
return ColorRamp.this.getLocale();
}
}
/**
* The object on which to delegate the paint operations.
*/
private final Painter painter;
/**
* The {@link ComponentUI} object for computing preferred
* size, drawn the component and handle some events.
*/
private final UI ui;
/**
* Constructs an initially empty color ramp. Colors can be
* set using one of the {@code setColors(...)} methods.
*/
public ColorRamp() {
ui = new UI();
setOpaque(true);
setUI(ui);
painter = new Painter();
}
/**
* Constructs a color ramp for the specified coverage.
*
* @param coverage The coverage for which to create a color ramp.
*/
public ColorRamp(final Coverage coverage) {
this();
setColors(coverage);
}
/**
* Returns the graduation to paint over colors. If the graduation is
* not yet defined, then this method returns {@code null}.
*
* @return The graduation to draw.
*/
public Graduation getGraduation() {
return painter.getGraduation();
}
/**
* Sets the graduation to paint on top of the color bar. The graduation can be set also
* by a call to {@link #setColors(SampleDimension)} and {@link #setColors(Coverage)}.
* This method will fire a property change event with the {@code "graduation"} name.
* <p>
* The graduation minimum and maximum values should be both inclusive.
*
* @param graduation The new graduation, or {@code null} if none.
* @return {@code true} if this object changed as a result of this call.
*/
public boolean setGraduation(final Graduation graduation) {
final Graduation oldGraduation = painter.setGraduation(graduation);
final boolean changed = !Objects.equals(graduation, oldGraduation);
if (changed) {
if (oldGraduation != null) {
oldGraduation.removePropertyChangeListener(ui);
}
if (graduation != null) {
graduation.addPropertyChangeListener(ui);
}
repaint();
firePropertyChange("graduation", oldGraduation, graduation);
}
return changed;
}
/**
* Returns the colors painted by this {@code ColorRamp}.
*
* @return The colors (never {@code null}).
*/
public Color[] getColors() {
return painter.getColors();
}
/**
* Sets the colors to paint. If the new colors are different than the old ones, then this
* method fires a {@linkplain PropertyChangeEvent property change event} named {@code "colors"}
* with values of type {@code Color[]}.
*
* @param colors The colors to paint, or {@code null} if none.
* @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
*
* @see #setColors(Coverage)
* @see #setColors(SampleDimension)
* @see #setColors(IndexColorModel)
* @see #getColors()
* @see #getGraduation()
*/
public boolean setColors(final Color... colors) {
return fireColorChange(painter.setColors(colors));
}
/**
* Sets the colors to paint as an array of ARGB values. This method performs the same
* work than {@link #setColors(Color[])}, but is more efficient if the colors were
* already available as an array of ARGB values.
* <p>
* If the new colors are different than the old ones, then this method fires a
* {@linkplain PropertyChangeEvent property change event} named {@code "colors"}
* with values of type {@code Color[]} - not {@code int[]}.
*
* @param colors The colors to paint, or {@code null} if none.
* @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
*
* @since 3.09
*/
public boolean setColors(final int... colors) {
return fireColorChange(painter.setColors(colors));
}
/**
* Fires a property change event if the colors changed.
*
* @param changes The old and new colors, or {@code null} if there is no change.
* @return Whatever that were a change.
*/
private boolean fireColorChange(final Color[][] changes) {
if (changes == null) {
return false;
}
repaint();
firePropertyChange("colors", changes[0], changes[1]);
return true;
}
/**
* Sets the colors to paint from an {@link IndexColorModel}. The default implementation
* fetches the ARGB values from the index color model and invokes {@link #setColors(int[])}.
*
* @param model The colors to paint, or {@code null} if none.
* @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
*
* @see #setColors(Coverage)
* @see #setColors(SampleDimension)
* @see #setColors(Color[])
* @see #getColors()
* @see #getGraduation()
*/
public boolean setColors(final IndexColorModel model) {
int[] colors = null;
if (model != null) {
colors = new int[model.getMapSize()];
model.getRGBs(colors);
}
return setColors(colors);
}
/**
* Sets the graduation and the colors from a sample dimension.
* The default implementation fetches the palette and the minimum and maximum values
* from the supplied band, and then invokes {@link #setColors(Color[]) setColors} and
* {@link #setGraduation setGraduation}.
*
* @param band The sample dimension, or {@code null} if none.
* @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
*
* @see #setColors(Coverage)
* @see #setColors(SampleDimension)
* @see #setColors(IndexColorModel)
* @see #setColors(Color[])
* @see #getColors()
* @see #getGraduation()
*/
public boolean setColors(final SampleDimension band) {
final Map.Entry<Graduation,Color[]> entry = painter.getColors(band);
return setGraduation(entry.getKey()) | setColors(entry.getValue()); // Realy |, not ||
}
/**
* Sets the graduation and the colors from a coverage. The default implementation
* fetches the visible sample dimension from the specified coverage, and then invokes
* {@link #setColors(Color[]) setColors} and {@link #setGraduation setGraduation}.
*
* @param coverage The coverage, or {@code null}.
* @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
*
* @see #setColors(IndexColorModel)
* @see #setColors(SampleDimension)
* @see #getColors()
* @see #getGraduation()
*/
public boolean setColors(final Coverage coverage) {
SampleDimension band = null;
if (coverage != null) {
band = coverage.getSampleDimension(CoverageUtilities.getVisibleBand(band));
}
return setColors(band);
}
/**
* Returns {@code true} if the colors are interpolated at rendering time. If {@code false},
* only the colors given to the {@code setColors(...)} method will be used: the color ramp
* will be painted using rectangles of uniform colors. If {@code true}, then a linear
* interpolation will be used between every colors given to the {@code setColors(...)}
* method.
*
* @return Whatever the colors will be interpolated at rendering time.
*
* @since 3.16
*/
public boolean isInterpolationEnabled() {
return painter.interpolationEnabled;
}
/**
* Sets whatever the colors should be interpolated at rendering time.
* If this method is never invoked, then the default value is {@code true}.
*
* @param enabled Whatever the colors will be interpolated at rendering time.
*
* @since 3.16
*/
public void setInterpolationEnabled(final boolean enabled) {
final boolean old = painter.interpolationEnabled;
painter.interpolationEnabled = enabled;
if (old != enabled) {
firePropertyChange("interpolationEnabled", old, enabled);
repaint();
}
}
/**
* Returns the component's orientation (horizontal or vertical). It should be one of the
* following constants: {@link SwingConstants#HORIZONTAL} or {@link SwingConstants#VERTICAL}.
*
* @return The component orientation.
*/
public int getOrientation() {
return painter.getOrientation();
}
/**
* Sets the component's orientation (horizontal or vertical).
*
* @param orient {@link SwingConstants#HORIZONTAL} or {@link SwingConstants#VERTICAL}.
*/
public void setOrientation(final int orient) {
final int old = painter.setOrientation(orient);
if (old != orient) {
firePropertyChange("orientation", old, orient);
repaint();
}
}
/**
* Tests if graduation labels are paint on top of the
* colors ramp. Default value is {@code true}.
*
* @return {@code true} if graduation labels are drawn.
*/
public boolean isLabelVisibles() {
return painter.labelVisibles;
}
/**
* Sets whatever the graduation labels should be painted on top of the colors ramp.
*
* @param visible {@code true} if graduation labels should be drawn.
*/
public void setLabelVisibles(final boolean visible) {
final boolean old = painter.labelVisibles;
painter.labelVisibles = visible;
if (old != visible) {
firePropertyChange("labelVisibles", old, visible);
repaint();
}
}
/**
* Sets the label colors. A {@code null} value reset the automatic color.
*
* @param color The new label color, or {@code null} for the default.
*
* @see #getForeground
*/
@Override
public void setForeground(final Color color) {
super.setForeground(color);
painter.autoForeground = (color == null);
}
/**
* Paints the color ramp. This method doesn't need to restore
* {@link Graphics2D} to its initial state once finished.
*
* @param graphics The graphic context in which to paint.
* @param bounds The bounding box where to paint the color ramp.
* @return Bounding box of graduation labels (NOT taking in account the color ramp
* behind them), or {@code null} if no label has been painted.
*/
final Rectangle2D paint(final Graphics2D graphics, final Rectangle bounds) {
return painter.paint(graphics, bounds, getFont(), getForeground());
}
/**
* Returns a graduation for the specified sample dimension, minimum and maximum values. If
* the supplied {@code reuse} object is non-null and is of the appropriate class, then this
* method can return {@code reuse} without creating a new graduation object. Otherwise this
* method must returns a graduation of the appropriate class, usually
* {@link org.geotoolkit.display.axis.NumberGraduation} or
* {@link org.geotoolkit.display.axis.LogarithmicNumberGraduation}.
* <p>
* In every cases, this method must set graduations
* {@linkplain org.geotoolkit.display.axis.AbstractGraduation#setMinimum minimum},
* {@linkplain org.geotoolkit.display.axis.AbstractGraduation#setMaximum maximum} and
* {@linkplain org.geotoolkit.display.axis.AbstractGraduation#setUnit unit}
* according the values given in arguments.
*
* @param reuse The graduation to reuse if possible.
* @param band The sample dimension to create graduation for.
* @param minimum The minimal geophysics value to appear in the graduation.
* @param maximum The maximal geophysics value to appear in the graduation.
* @return A graduation for the supplied sample dimension.
*/
protected Graduation createGraduation(final Graduation reuse, final SampleDimension band,
final double minimum, final double maximum)
{
return Painter.createDefaultGraduation(reuse, band.getSampleToGeophysics(),
minimum, maximum, band.getUnits(), getLocale());
}
/**
* Returns an image representation for this color ramp. The image size will be this
* {@linkplain #getSize() widget size}.
*
* @return The color ramp as a buffered image.
*
* @since 2.4
*/
public BufferedImage toImage() {
return painter.toImage(getWidth(), getHeight(), getFont(), getForeground());
}
/**
* Returns a string representation for this color ramp.
*/
@Override
public String toString() {
return painter.toString(getClass());
}
/**
* Notifies this component that it now has a parent component. This method
* is invoked by <i>Swing</i> and shouldn't be directly used.
*/
@Override
public void addNotify() {
super.addNotify();
final Graduation graduation = painter.getGraduation();
if (graduation != null) {
graduation.removePropertyChangeListener(ui); // Avoid duplication
graduation.addPropertyChangeListener(ui);
}
}
/**
* Notifies this component that it no longer has a parent component.
* This method is invoked by <i>Swing</i> and shouldn't be directly used.
*/
@Override
public void removeNotify() {
final Graduation graduation = painter.getGraduation();
if (graduation != null) {
graduation.removePropertyChangeListener(ui);
}
super.removeNotify();
}
/**
* Returns the size of a vertical or horizontal rectangle, depending
* on the orientation of this widget.
*/
final Dimension getSize(final int size) {
switch (getOrientation()) {
case SwingConstants.HORIZONTAL: return new Dimension(size, 16);
case SwingConstants.VERTICAL: return new Dimension(16, size);
default: throw new AssertionError(this);
}
}
/**
* Classe ayant la charge de dessiner la rampe de couleurs, ainsi que
* de calculer l'espace qu'elle occupe. Cette classe peut aussi réagir
* à certains événements.
*
* @author Martin Desruisseaux (MPO, IRD)
* @version 3.00
*
* @since 2.3
* @module
*/
private final class UI extends ComponentUI implements PropertyChangeListener {
/**
* Returns the minimal {@link ColorRamp} size.
*/
@Override
public Dimension getMinimumSize(final JComponent c) {
return ((ColorRamp) c).getSize(2*Painter.MARGIN);
}
/**
* Returns the prefered {@link ColorRamp} size.
*/
@Override
public Dimension getPreferredSize(final JComponent c) {
return ((ColorRamp) c).getSize(256);
}
/**
* Dessine la rampe de couleurs vers le graphique spécifié. Cette méthode a
* l'avantage d'être appelée automatiquement par <i>Swing</i> avec une copie
* d'un objet {@link Graphics}, ce qui nous évite d'avoir à le remettre dans
* son état initial lorsqu'on a terminé le traçage de la rampe de couleurs.
* On n'a pas cet avantage lorsque l'on ne fait que redéfinir
* {@link JComponent#paintComponent}.
*/
@Override
public void paint(final Graphics graphics, final JComponent component) {
final ColorRamp ramp = (ColorRamp) component;
if (ramp.painter.hasColors()) {
final Rectangle bounds = ramp.getBounds();
bounds.x = 0;
bounds.y = 0;
ramp.paint((Graphics2D) graphics, bounds);
}
}
/**
* Méthode appelée automatiquement chaque fois qu'une propriété de l'axe a changée.
*/
@Override
public void propertyChange(final PropertyChangeEvent event) {
repaint();
}
}
}