You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
491 lines
18 KiB
491 lines
18 KiB
//http://math.hws.edu/xJava/MB/xMandelbrotSource-1-2/edu/hws/eck/umb/palette/Palette.java
|
|
//package edu.hws.eck.umb.palette;
|
|
package neural;
|
|
|
|
import java.awt.Color;
|
|
import java.util.ArrayList;
|
|
|
|
import javax.swing.event.ChangeEvent;
|
|
import javax.swing.event.ChangeListener;
|
|
|
|
/**
|
|
* Represents a palette, that is a sequence of colors. A palette assigns
|
|
* a color to each real number in the range 0 through 1, inclusive. The color
|
|
* is specified at several points in this range (including at least 0 and 1).
|
|
* These points are referred to as "division points."
|
|
* Between these points, the color is determined by linear interpolation. A palette
|
|
* can have color type HSB or RGB. For HSB colors, the interpolation is done in
|
|
* the HSB color space; for RGB colors, the interpolation is done in the RGB color
|
|
* space.
|
|
*/
|
|
public class Palette implements Cloneable {
|
|
|
|
/**
|
|
* The colorType for a palette in which colors are specified as Red/Green/Blue values.
|
|
*/
|
|
public final static int COLOR_TYPE_RGB = 0;
|
|
|
|
/**
|
|
* The colorType for a palette in which colors are specified as Hue/Saturation/Brightness values.
|
|
*/
|
|
public final static int COLOR_TYPE_HSB = 1;
|
|
|
|
private int colorType;
|
|
private boolean mirrorOutOfRangeComponents;
|
|
|
|
private ArrayList<Double> divisionPoints; // First element is always 0; last value is always 1
|
|
private ArrayList<float[]> divisionPointColors; // Size is divisionPoints.size() + 1
|
|
|
|
private final ChangeEvent changeEvent = new ChangeEvent(this);
|
|
private ArrayList<ChangeListener> changeListeners;
|
|
|
|
|
|
|
|
// public static void main(String[] args) {
|
|
//
|
|
// Palette palette = new Palette();
|
|
//// palette.split(0.25);
|
|
//// palette.split(0.5);
|
|
//// palette.split(0.75);
|
|
// System.out.println(palette.getColor(0.990987643));
|
|
//
|
|
// System.exit(0);
|
|
//
|
|
// }
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
* Creates a palette of HSB color type, showing a rainbow spectrum.
|
|
*/
|
|
public Palette() {
|
|
this(COLOR_TYPE_HSB);
|
|
}
|
|
|
|
/**
|
|
* Create a palette of specified color type. For HSB color type, the palette
|
|
* is a rainbow spectrum. For the RGB color type, the palette is a grayscale
|
|
* from white to black.
|
|
* @param colorType One of the constants Palette.COLOR_TYPE_HSB or Palette.COLOR_TYPE_RGB.
|
|
* @throws IllegalArgumentException if the parameter is not one of the two valid color type constants.
|
|
*/
|
|
public Palette(int colorType) {
|
|
this.colorType = colorType;
|
|
mirrorOutOfRangeComponents = true;
|
|
divisionPoints = new ArrayList<Double>();
|
|
divisionPointColors = new ArrayList<float[]>();
|
|
divisionPoints.add(0.0);
|
|
divisionPoints.add(1.0);
|
|
if (colorType == COLOR_TYPE_HSB) { // spectrum
|
|
divisionPointColors.add(new float[] {0, 1, 1});
|
|
divisionPointColors.add(new float[] {1, 1, 1});
|
|
}
|
|
else if (colorType == COLOR_TYPE_RGB){ // grayscale
|
|
divisionPointColors.add(new float[] {1, 1, 1});
|
|
divisionPointColors.add(new float[] {0, 0, 0});
|
|
}
|
|
else
|
|
throw new IllegalArgumentException("Palette color type must be TYPE_COLOR_RGB or TYPE_COLOR_HSB");
|
|
}
|
|
|
|
Palette(int colorType, boolean mirrored, ArrayList<Double> divisionPoints, ArrayList<float[]> colorComponents) {
|
|
// For use by the PaletteIO and PaletteEditDialog classes in the same package; no error checking done here.
|
|
this.colorType = colorType;
|
|
this.mirrorOutOfRangeComponents = mirrored;
|
|
this.divisionPoints = divisionPoints;
|
|
this.divisionPointColors = colorComponents;
|
|
}
|
|
|
|
/**
|
|
* Creates one of the built-in palettes used in the Mandelbrot program.
|
|
* @param paletteName The name of the palette. Must be one of "Spectrum",
|
|
* "PaleSpectrum", "Grayscale", "CyclicGrayscale", "CyclicRedCyan",
|
|
* "EarthSky", "HotCold", or "Fire".
|
|
* @throws IllegalArgumentException if the parameter is not one of the valid
|
|
* built-in palette names.
|
|
*/
|
|
public static Palette makeDefaultPalette(String paletteName) {
|
|
Palette palette;
|
|
if (paletteName.equals("Spectrum")) {
|
|
palette = new Palette();
|
|
}
|
|
else if (paletteName.equals("PaleSpectrum")) {
|
|
palette = new Palette();
|
|
palette.setDivisionPointColorComponents(0, 0, 0.5f, 1);
|
|
palette.setDivisionPointColorComponents(1, 1, 0.5f, 1);
|
|
}
|
|
else if (paletteName.equals("DarkSpectrum")) {
|
|
palette = new Palette();
|
|
palette.setDivisionPointColorComponents(0, 0, 1, 0.5f);
|
|
palette.setDivisionPointColorComponents(1, 1, 1, 0.5f);
|
|
}
|
|
else if (paletteName.equals("Grayscale")) {
|
|
palette =new Palette(Palette.COLOR_TYPE_RGB);
|
|
}
|
|
else if (paletteName.equals("CyclicGrayscale")) {
|
|
palette = new Palette(Palette.COLOR_TYPE_RGB);
|
|
palette.split(0.5);
|
|
palette.setDivisionPointColorComponents(1, 0, 0, 0);
|
|
palette.setDivisionPointColorComponents(2, 1, 1, 1);
|
|
}
|
|
else if (paletteName.equals("CyclicRedCyan")) {
|
|
palette = new Palette(Palette.COLOR_TYPE_RGB);
|
|
palette.split(0.5);
|
|
palette.setDivisionPointColorComponents(0, 1, 0, 0);
|
|
palette.setDivisionPointColorComponents(1, 0, 1, 1);
|
|
palette.setDivisionPointColorComponents(2, 1, 0, 0);
|
|
}
|
|
else if (paletteName.equals("EarthSky")) {
|
|
palette = new Palette(Palette.COLOR_TYPE_RGB);
|
|
palette.split(0.15);
|
|
palette.split(0.33);
|
|
palette.split(0.67);
|
|
palette.split(0.85);
|
|
palette.setDivisionPointColorComponents(0, 1, 1, 1);
|
|
palette.setDivisionPointColorComponents(1, 1, 0.8f, 0);
|
|
palette.setDivisionPointColorComponents(2, 0.53f, 0.12f, 0.075f);
|
|
palette.setDivisionPointColorComponents(3, 0, 0, 0.6f);
|
|
palette.setDivisionPointColorComponents(4, 0, 0.4f, 1);
|
|
palette.setDivisionPointColorComponents(5, 1, 1, 1);
|
|
}
|
|
else if (paletteName.equals("HotCold")) {
|
|
palette = new Palette(Palette.COLOR_TYPE_RGB);
|
|
palette.split(0.16);
|
|
palette.split(0.5);
|
|
palette.split(0.84);
|
|
palette.setDivisionPointColorComponents(0, 1, 1, 1);
|
|
palette.setDivisionPointColorComponents(1, 0, 0.4f, 1);
|
|
palette.setDivisionPointColorComponents(2, 0.2f, 0.2f, 0.2f);
|
|
palette.setDivisionPointColorComponents(3, 1, 0, 0.8f);
|
|
palette.setDivisionPointColorComponents(4, 1, 1, 1);
|
|
}
|
|
else if (paletteName.equals("Fire")) {
|
|
palette = new Palette(Palette.COLOR_TYPE_RGB);
|
|
palette.split(0.17);
|
|
palette.split(0.83);
|
|
palette.setDivisionPointColorComponents(0, 0, 0, 0);
|
|
palette.setDivisionPointColorComponents(1, 1, 0, 0);
|
|
palette.setDivisionPointColorComponents(2, 1, 1, 0);
|
|
palette.setDivisionPointColorComponents(3, 1, 1, 1);
|
|
}
|
|
else
|
|
throw new IllegalArgumentException("Unknown palette: " + paletteName);
|
|
return palette;
|
|
}
|
|
|
|
public boolean equals(Object obj) {
|
|
if (obj == null || !(obj instanceof Palette))
|
|
return false;
|
|
Palette that = (Palette)obj;
|
|
if (that.colorType != colorType)
|
|
return false;
|
|
if (that.mirrorOutOfRangeComponents != mirrorOutOfRangeComponents)
|
|
return false;
|
|
if (that.divisionPoints.size() != divisionPoints.size())
|
|
return false;
|
|
for (int i = 0; i < divisionPoints.size(); i++) {
|
|
if (that.divisionPoints.get(i) != divisionPoints.get(i))
|
|
return false;
|
|
float[] a = that.divisionPointColors.get(i);
|
|
float[] b = divisionPointColors.get(i);
|
|
if (a[0] != b[0] || a[1] != b[1] || a[2] != b[2])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public Palette clone() {
|
|
Palette that = new Palette(this.colorType);
|
|
that.mirrorOutOfRangeComponents = this.mirrorOutOfRangeComponents;
|
|
that.divisionPoints = new ArrayList<Double>();
|
|
that.divisionPoints.addAll(this.divisionPoints);
|
|
that.divisionPointColors = new ArrayList<float[]>();
|
|
for (float[] c : this.divisionPointColors)
|
|
that.divisionPointColors.add(c.clone());
|
|
that.changed();
|
|
return that;
|
|
}
|
|
|
|
/**
|
|
* Copies all properties from a specified palette, making this
|
|
* palette equal to the specified palette.
|
|
* @param that the palette whose properties are to be copied
|
|
*/
|
|
public void copyFrom(Palette that) {
|
|
this.colorType = that.colorType;
|
|
this.mirrorOutOfRangeComponents = that.mirrorOutOfRangeComponents;
|
|
this.divisionPoints = new ArrayList<Double>();
|
|
this.divisionPoints.addAll(that.divisionPoints);
|
|
this.divisionPointColors = new ArrayList<float[]>();
|
|
for (float[] c : that.divisionPointColors)
|
|
this.divisionPointColors.add(c.clone());
|
|
this.changed();
|
|
}
|
|
|
|
/**
|
|
* Removes a specified division point, one of the points where the color is specified explicitly.
|
|
* @param divisionPointIndex the number of the division point to be removed, which much be
|
|
* in the range 1 through ct-2, where ct is the number of division points. It is not possible
|
|
* to remove the first division point (which is 0.0) or the last division point (which is 1.0).
|
|
*/
|
|
public void join(int divisionPointIndex) {
|
|
if (divisionPointIndex <= 0 || divisionPointIndex >= divisionPoints.size() - 1)
|
|
throw new IllegalArgumentException("Division point index out of range: " + divisionPointIndex);
|
|
divisionPoints.remove(divisionPointIndex);
|
|
divisionPointColors.remove(divisionPointIndex);
|
|
changed();
|
|
}
|
|
|
|
/**
|
|
* Adds a division point to the palette. The color associated to the point is obtained by
|
|
* interpolating between the colors of the points that neighbor the new point.
|
|
* @param divisionPoint The number between 0.0 and 1.0 where the new division point is to
|
|
* be added. The value cannot be the same as an existing point.
|
|
* @return The index of the new division point, this is, its position number in the list of division points.
|
|
* @throws IllegalArgumentException if the parameter is less than 0.0 or greater than 1.0, or if
|
|
* a division point already exists at the specified value.
|
|
*/
|
|
public int split(double divisionPoint) { // Return value is index of divisionPoint/color that are inserted, or -1 if exact number already exists as a divisionPoint
|
|
if (divisionPoint <= 0 || divisionPoint >= 1 || Double.isNaN(divisionPoint))
|
|
throw new IllegalArgumentException("Division point out of range: " + divisionPoint);
|
|
int index = 0;
|
|
while (divisionPoint > divisionPoints.get(index))
|
|
index++;
|
|
if (Math.abs(divisionPoint - divisionPoints.get(index)) < 1e-15)
|
|
return -1;
|
|
float ratio = (float)( (divisionPoint - divisionPoints.get(index-1))
|
|
/ (divisionPoints.get(index) - divisionPoints.get(index-1)) );
|
|
float[] c1 = divisionPointColors.get(index-1);
|
|
float[] c2 = divisionPointColors.get(index);
|
|
float a = c1[0] + ratio*(c2[0] - c1[0]);
|
|
float b = c1[1] + ratio*(c2[1] - c1[1]);
|
|
float c = c1[2] + ratio*(c2[2] - c1[2]);
|
|
float[] color = new float[] { a, b, c };
|
|
divisionPoints.add(index, divisionPoint);
|
|
divisionPointColors.add(index, color);
|
|
changed();
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Get the color that this palette assigns to a specified number.
|
|
* @param position the number between 0.0 and 1.0, inclusive, for which the corresponding
|
|
* color is to be returned.
|
|
* @throws IllegalArgumentException if the position is outside the range 0.0 to 1.0.
|
|
*/
|
|
public Color getColor(double position) {
|
|
if (position < 0 || position > 1)
|
|
throw new IllegalArgumentException("Position " + position + " is out of range.");
|
|
int pt = 1;
|
|
while (position > divisionPoints.get(pt))
|
|
pt++;
|
|
float ratio = (float)( (position - divisionPoints.get(pt-1))
|
|
/ (divisionPoints.get(pt) - divisionPoints.get(pt-1)) );
|
|
float[] c1 = divisionPointColors.get(pt-1);
|
|
float[] c2 = divisionPointColors.get(pt);
|
|
float a = clamp1(c1[0] + ratio*(c2[0] - c1[0]));
|
|
float b = clamp2(c1[1] + ratio*(c2[1] - c1[1]));
|
|
float c = clamp2(c1[2] + ratio*(c2[2] - c1[2]));
|
|
Color color;
|
|
if (colorType == COLOR_TYPE_HSB)
|
|
color = Color.getHSBColor(a, b, c);
|
|
else
|
|
color = new Color(a, b, c);
|
|
return color;
|
|
}
|
|
|
|
/**
|
|
* Get an array of RGB color values corresponding to equally spaced points in the
|
|
* range 0.0 to 1.0.
|
|
* @param paletteLength The number of points for which colors will be returned.
|
|
* @param offset the color values are "rotated" by this amount within the array.
|
|
* That is, the color value corresponding to 0.0 is in the array at index = offset
|
|
* (or, more exactly, paletteLength % offset).
|
|
*/
|
|
public int[] makeRGBs(int paletteLength, int offset) {
|
|
int[] rgb;
|
|
rgb = new int[paletteLength];
|
|
rgb[offset % paletteLength] = getDivisionPointColor(0).getRGB();
|
|
int ct = 1;
|
|
double dx = 1.0 / (paletteLength-1);
|
|
int pt = 1;
|
|
while (ct < paletteLength-1) {
|
|
double position = dx*ct;
|
|
while (position > divisionPoints.get(pt))
|
|
pt++;
|
|
float ratio = (float)( (position - divisionPoints.get(pt-1))
|
|
/ (divisionPoints.get(pt) - divisionPoints.get(pt-1)) );
|
|
float[] c1 = divisionPointColors.get(pt-1);
|
|
float[] c2 = divisionPointColors.get(pt);
|
|
float a = clamp1(c1[0] + ratio*(c2[0] - c1[0]));
|
|
float b = clamp2(c1[1] + ratio*(c2[1] - c1[1]));
|
|
float c = clamp2(c1[2] + ratio*(c2[2] - c1[2]));
|
|
Color color;
|
|
if (colorType == COLOR_TYPE_HSB)
|
|
color = Color.getHSBColor(a, b, c);
|
|
else
|
|
color = new Color(a, b, c);
|
|
rgb[(ct + offset) % paletteLength] = color.getRGB();
|
|
ct++;
|
|
}
|
|
rgb[(offset + paletteLength-1) % paletteLength] = getDivisionPointColor(divisionPoints.size()-1).getRGB();
|
|
return rgb;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of division points in the palette, always two or more.
|
|
*/
|
|
public int getDivisionPointCount() {
|
|
return divisionPoints.size();
|
|
}
|
|
|
|
/**
|
|
* Returns a specified division points. The return value is the range 0.0 to 1.0. Divsion points
|
|
* are stored in strictly increasing order.
|
|
* @param index The index of the desired division point in the list of division points.
|
|
*/
|
|
public double getDivisionPoint(int index) {
|
|
return divisionPoints.get(index);
|
|
}
|
|
|
|
/**
|
|
* Sets the value of a specified division point. Does not apply to the first or last points,
|
|
* which always have values 0.0 and 1.0. The new value must be strictly between the positions
|
|
* the neighboring division points.
|
|
* @param index The index of the division point whose position is to be set.
|
|
* @param position The new position for the specified division point.
|
|
* @throws IllegalArgumentException if the index or position is not valid.
|
|
*/
|
|
public void setDivisionPoint(int index, double position) {
|
|
if (index <= 0 || index >= divisionPoints.size() - 1)
|
|
throw new IllegalArgumentException("Index out of legal range");
|
|
if (position <= divisionPoints.get(index-1) || position >= divisionPoints.get(index+1))
|
|
throw new IllegalArgumentException("Division point position outside of legal range.");
|
|
if (position != divisionPoints.get(index)) {
|
|
divisionPoints.set(index, position);
|
|
changed();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the color associated with a given division point.
|
|
* @param index The index of the division point in the list of points.
|
|
*/
|
|
public Color getDivisionPointColor(int index) {
|
|
float[] components = divisionPointColors.get(index);
|
|
float a = clamp1(components[0]);
|
|
float b = clamp2(components[1]);
|
|
float c = clamp2(components[2]);
|
|
if (colorType == COLOR_TYPE_RGB)
|
|
return new Color(a,b,c);
|
|
else
|
|
return Color.getHSBColor(a,b,c);
|
|
}
|
|
|
|
/**
|
|
* Returns the color components for the division point at a specified index.
|
|
*/
|
|
public float[] getDivisionPointColorComponents(int index) {
|
|
return divisionPointColors.get(index).clone();
|
|
}
|
|
|
|
/**
|
|
* Set the color components for the division point at a s specified index in the list of
|
|
* division points. These components are the color data that is stored for each division
|
|
* point and that are used for interpolation between division points. Note that when a color
|
|
* is actually computed, the component values must be in the range 0.0 to 1.0. However, the
|
|
* values specified here do NOT have to be in this range. Values given here are used for
|
|
* interpolation, and then the resulting values are transformed into the range 0.0 to 1.0
|
|
* just before the color is computed. This means that the value can effectively oscillate
|
|
* several times between two division points.
|
|
* @param index
|
|
* @param c1 The Red color component for an RGB palette, or the Hue component for an HSB palette.
|
|
* @param c2 The Green color component for an RGB palette, or the Saturation component for an HSB palette.
|
|
* @param c3 The Blue color component for an RGB palette, or the Brightness component for an HSB palette.
|
|
*/
|
|
public void setDivisionPointColorComponents(int index, float c1, float c2, float c3) {
|
|
float[] c = divisionPointColors.get(index);
|
|
if (c1 == c[0] && c2 == c[1] && c3 == c[2])
|
|
return;
|
|
c[0] = c1;
|
|
c[1] = c2;
|
|
c[2] = c3;
|
|
changed();
|
|
}
|
|
|
|
/**
|
|
* Return the color type of this palette, which is one of the constants Palette.COLOR_TYPE_RGB
|
|
* or Palette.COLOR_TYPE_HSB.
|
|
*/
|
|
public int getColorType() {
|
|
return colorType;
|
|
}
|
|
|
|
public boolean getMirrorOutOfRangeComponents() {
|
|
return mirrorOutOfRangeComponents;
|
|
}
|
|
|
|
/**
|
|
* Sets the value of the confusing mirrorOutOfRangeComponents property. This only has
|
|
* an effect when a floating-point color component value that is outside the range
|
|
* 0.0 to 1.0 has to be transformed to a value within that range. For a Hue, the whole-number
|
|
* part is simply discarded. For the other components however, the transformation depends
|
|
* on the value of the mirrorOutOfRangeComponents property. If the property is false,
|
|
* the whole-number part is discarded, but this results in a discontinuity at integer values.
|
|
* If the property is true, this discontinuity is avoided by having the value oscillate with
|
|
* period 2 instead of cycle with period 1. The default value is true, and this is never
|
|
* changed in the Mandelbrot application. So, really, you shouldn't even be reading this.
|
|
*/
|
|
public void setMirrorOutOfRangeComponents(boolean mirrorOutOfRangeComponents) {
|
|
if (this.mirrorOutOfRangeComponents == mirrorOutOfRangeComponents)
|
|
return;
|
|
this.mirrorOutOfRangeComponents = mirrorOutOfRangeComponents;
|
|
changed();
|
|
}
|
|
|
|
/**
|
|
* Add a listener that will be notified whenever the palette changes in any way.
|
|
*/
|
|
public void addChangeListener(ChangeListener listener) {
|
|
if (listener == null)
|
|
return;
|
|
if (changeListeners == null)
|
|
changeListeners = new ArrayList<ChangeListener>();
|
|
if (!changeListeners.contains(listener))
|
|
changeListeners.add(listener);
|
|
}
|
|
|
|
/**
|
|
* Remove a listener (if present in the list of listeners).
|
|
*/
|
|
public void removeChangeListener(ChangeListener listener) {
|
|
if (changeListeners == null)
|
|
return;
|
|
changeListeners.remove(listener);
|
|
if (changeListeners.size() == 0)
|
|
changeListeners = null;
|
|
}
|
|
|
|
private float clamp1(float x) {
|
|
if (colorType == COLOR_TYPE_HSB || !mirrorOutOfRangeComponents)
|
|
return x - (float)Math.floor(x);
|
|
else
|
|
return clamp2(x);
|
|
}
|
|
|
|
private float clamp2(float x) {
|
|
if (!mirrorOutOfRangeComponents)
|
|
return x - (float)Math.floor(x);
|
|
x = 2*(x/2 - (float)Math.floor(x/2));
|
|
if (x > 1)
|
|
x = 2 - x;
|
|
return x;
|
|
}
|
|
|
|
private void changed() {
|
|
if (changeListeners != null)
|
|
for (ChangeListener lst : changeListeners)
|
|
lst.stateChanged(changeEvent);
|
|
}
|
|
} |