From 33f62685c41458028de91336ac5207c9bfb8d718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Babi=C4=8D?= Date: Wed, 25 Mar 2015 21:47:52 +0100 Subject: [PATCH] preparing for color palette --- .classpath | 2 +- src/neural/BattlefieldParameterEvaluator.java | 6 +- src/neural/ColorMap.java | 104 ++++ src/neural/Palette.java | 487 ++++++++++++++++++ 4 files changed, 597 insertions(+), 2 deletions(-) create mode 100644 src/neural/ColorMap.java create mode 100644 src/neural/Palette.java diff --git a/.classpath b/.classpath index b492a72..e1c6d65 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,6 @@ - + diff --git a/src/neural/BattlefieldParameterEvaluator.java b/src/neural/BattlefieldParameterEvaluator.java index cbeda4c..ca87faa 100644 --- a/src/neural/BattlefieldParameterEvaluator.java +++ b/src/neural/BattlefieldParameterEvaluator.java @@ -22,6 +22,8 @@ import robocode.BattleResults; import robocode.control.*; import robocode.control.events.*; +import neural.Palette; + public class BattlefieldParameterEvaluator { // Minimum allowable battlefield size is 400 final static int MINBATTLEFIELDSIZE = 400; @@ -153,6 +155,7 @@ public class BattlefieldParameterEvaluator { BasicMLData input = new BasicMLData(MyTestData[NdxCooling + NdxBattleSize * NUMCOOLINGRATES]); final MLData output = network.compute(input.clone()); double MyResult = output.getData()[0]; + // 2 * fix introduced to shift the top gradient more down to se whole ColorMap spectrum MyValue = ClipColor(MyResult * 2); MyColor = new Color((float) MyValue, (float) MyValue, (float) MyValue); OutputRGBint[NdxCooling + NdxBattleSize * NUMCOOLINGRATES] = MyColor.getRGB(); @@ -162,7 +165,8 @@ public class BattlefieldParameterEvaluator { // Plot the training samples for (int NdxSample = 0; NdxSample < NUMSAMPLES; NdxSample++) { // MyValue = ClipColor(FinalScore1[NdxSample] / BATTLE_MAX_SCORE); - MyValue = ClipColor(2 * FinalScore1[NdxSample] / 250); + // 2 * fix introduced to shift the top gradient more down to se whole ColorMap spectrum + MyValue = ClipColor(2 * FinalScore1[NdxSample] / BATTLE_MAX_SCORE); MyColor = new Color((float) MyValue, (float) MyValue, (float) MyValue); int MyPixelIndex = (int) (Math.round(NUMCOOLINGRATES * ((GunCoolingRate[NdxSample] / MAXGUNCOOLINGRATE) - 0.1) / 0.9) + Math .round(NUMBATTLEFIELDSIZES * ((BattlefieldSize[NdxSample] / MAXBATTLEFIELDSIZE) - 0.1) / 0.9) diff --git a/src/neural/ColorMap.java b/src/neural/ColorMap.java new file mode 100644 index 0000000..2899938 --- /dev/null +++ b/src/neural/ColorMap.java @@ -0,0 +1,104 @@ +/** + * + */ +package neural; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Random; + +import javax.imageio.ImageIO; + +public class ColorMap { + + /** + * @param data + * @param pixelsWidth + * @param pixelsHeight + * @return + * @throws Exception + * @see https://josetips.wordpress.com/category/java/ + */ + public static BufferedImage generateImage(double[][] data, int pixelsWidth, int pixelsHeight) throws Exception { + + double x = 0, y = 0; + BufferedImage bi = new BufferedImage(pixelsWidth, pixelsHeight, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = (Graphics2D) bi.getGraphics(); + + int w = data[0].length; + int l = data.length; + double Dy = (double) pixelsHeight / (double) l; + double Dx = (double) pixelsWidth / (double) w; + int height = (int) Math.ceil(Dy); + int width = (int) Math.ceil(Dx); + int gap = (int) Math.ceil(width / 4); + + double max = -Double.MAX_VALUE; + double min = Double.MAX_VALUE; + for (int i = 0; i < l; i++) { + for (int j = 0; j < w; j++) { + max = data[i][j]; + if (data[i][j] < min) { + min = data[i][j]; + } + } + } + + float fcolor; + float saturation = (float) 0.9; + float bright = (float) 0.7; + + g.setColor(Color.WHITE); + g.fillRect(0, 0, pixelsWidth, pixelsHeight); + for (int i = 0; i < l; i++) { + for (int j = 0; j < w; j++) { + fcolor = (float) ((data[i][j] - min) / (max - min)); + float hue = fcolor; + g.setColor(Color.getHSBColor((float) (hue * 0.7), saturation, bright)); + g.fillRect((int) x, (int) y, width - gap, height); + x += Dx; + } + y += Dy; + x = 0; + } + + File f = new File("color.png"); + try { + ImageIO.write(bi, "png", f); + } + catch (IOException e) { + e.printStackTrace(); + } + g.dispose(); + + return bi; + } + + public static void main(String[] args) { + double[][] data; + int pixelsWidth = 300; + int pixelsHeight = 400; + Random r = new Random(); + + data = new double[pixelsWidth][]; + for (int ndx = 0; ndx < pixelsWidth; ndx++) { + data[ndx] = new double[pixelsHeight]; + for (int ndx2 = 0; ndx2 < pixelsHeight; ndx2++) { +// data[ndx][ndx2] = r.nextDouble(); + data[ndx][ndx2] = ndx2; + } + + } + + try { + generateImage(data, pixelsWidth, pixelsHeight); + } + catch (Exception e) { + e.printStackTrace(); + } + + } +} diff --git a/src/neural/Palette.java b/src/neural/Palette.java new file mode 100644 index 0000000..850f3d2 --- /dev/null +++ b/src/neural/Palette.java @@ -0,0 +1,487 @@ +//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 divisionPoints; // First element is always 0; last value is always 1 + private ArrayList divisionPointColors; // Size is divisionPoints.size() + 1 + + private final ChangeEvent changeEvent = new ChangeEvent(this); + private ArrayList changeListeners; + + + +// public static void main(String[] args) { +// +// Palette palette = new Palette(); +// palette.split(0.25); +// palette.split(0.5); +// palette.split(0.75); +// 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(); + divisionPointColors = new ArrayList(); + 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 divisionPoints, ArrayList 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(); + that.divisionPoints.addAll(this.divisionPoints); + that.divisionPointColors = new ArrayList(); + 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(); + this.divisionPoints.addAll(that.divisionPoints); + this.divisionPointColors = new ArrayList(); + 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(); + 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); + } +} \ No newline at end of file