/* * The MIT License (MIT) * * Copyright (c) 2007-2015 Broad Institute * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.broad.igv.renderer; //~--- non-JDK imports -------------------------------------------------------- import org.broad.igv.ui.UIConstants; import org.broad.igv.ui.color.ColorUtilities; import java.awt.*; import java.awt.image.BufferedImage; /** * @author jrobinso */ public class ContinuousColorScale extends AbstractColorScale { /** * String to use to identify this clas when serializing. We could use * the class name, but that would invalidate serialized instances if the * name was ever changed. */ public static String serializedClassName = "ContinuousColorScale"; private boolean useDoubleGradient; private double negEnd; private double posEnd; private double negStart; private double posStart; private Color minColor; private Color midColor = Color.white; private Color maxColor; private Color[] colors; private boolean defaultCS = false; /** * Constructs ... * * @param string */ public ContinuousColorScale(String string) { String[] tokens = string.split(";"); if (tokens.length == 5) { this.negEnd = Double.parseDouble(tokens[1]); this.posEnd = Double.parseDouble(tokens[2]); this.minColor = ColorUtilities.stringToColor(tokens[3]); this.maxColor = ColorUtilities.stringToColor(tokens[4]); this.useDoubleGradient = false; } else if (tokens.length == 8) { this.negStart = Double.parseDouble(tokens[1]); this.negEnd = Double.parseDouble(tokens[2]); this.posStart = Double.parseDouble(tokens[3]); this.posEnd = Double.parseDouble(tokens[4]); this.minColor = ColorUtilities.stringToColor(tokens[5]); this.midColor = ColorUtilities.stringToColor(tokens[6]); this.maxColor = ColorUtilities.stringToColor(tokens[7]); this.useDoubleGradient = true; } else { throw new RuntimeException("Illegal ColorScale: " + string); } initColors(); } /** * Constructs ... * * @param min * @param max * @param minColor * @param maxColor */ public ContinuousColorScale(double min, double max, Color minColor, Color maxColor) { this.negEnd = min; this.posEnd = max; this.negStart = Math.max(0, min); this.posStart = this.negStart; this.minColor = minColor; this.maxColor = maxColor; this.useDoubleGradient = false; initColors(); } /** * Constructs ... * * @param min * @param mid * @param max * @param minColor * @param midColor * @param maxColor */ public ContinuousColorScale(double min, double mid, double max, Color minColor, Color midColor, Color maxColor) { this.negEnd = min; this.posEnd = max; this.negStart = mid; this.posStart = mid; this.minColor = minColor; this.midColor = midColor; this.maxColor = maxColor; this.useDoubleGradient = true; } /** * Constructs ... * * @param negStart * @param negEnd * @param posStart * @param posEnd * @param minColor * @param midColor * @param maxColor */ public ContinuousColorScale(double negStart, double negEnd, double posStart, double posEnd, Color minColor, Color midColor, Color maxColor) { this.negEnd = negEnd; this.posEnd = posEnd; this.negStart = negStart; this.posStart = posStart; this.minColor = minColor; this.midColor = midColor; this.maxColor = maxColor; this.useDoubleGradient = true; } public ContinuousColorScale(ContinuousColorScale otherScale) { this.negEnd = otherScale.negEnd; this.posEnd = otherScale.posEnd; this.negStart = otherScale.negStart; this.posStart = otherScale.posStart; this.minColor = otherScale.minColor; this.midColor = otherScale.midColor; this.maxColor = otherScale.maxColor; this.useDoubleGradient = true; } public void setDefault(boolean defaultCS) { this.defaultCS = defaultCS; } public boolean isDefault() { return defaultCS; } public void setNegEnd(double negEnd) { this.negEnd = negEnd; } public void setPosEnd(double posEnd) { this.posEnd = posEnd; colors = null; } public void setNegStart(double negStart) { this.negStart = negStart; colors = null; } public void setPosStart(double posStart) { this.posStart = posStart; colors = null; } public void setMinColor(Color minColor) { this.minColor = minColor; colors = null; } public void setMidColor(Color midColor) { this.midColor = midColor; colors = null; } public void setMaxColor(Color maxColor) { this.maxColor = maxColor; colors = null; } /** * Create a string form of this object. * * @return */ public String asString() { StringBuffer buf = new StringBuffer(); buf.append(serializedClassName + ";"); if (useDoubleGradient) { buf.append(String.valueOf(negStart) + ";"); buf.append(String.valueOf(negEnd) + ";"); buf.append(String.valueOf(posStart) + ";"); buf.append(String.valueOf(posEnd) + ";"); buf.append(ColorUtilities.colorToString(minColor) + ";"); buf.append(ColorUtilities.colorToString(midColor) + ";"); buf.append(ColorUtilities.colorToString(maxColor)); } else { buf.append(String.valueOf(negEnd) + ";"); buf.append(String.valueOf(posEnd) + ";"); buf.append(ColorUtilities.colorToString(minColor) + ";"); buf.append(ColorUtilities.colorToString(maxColor)); } return buf.toString(); } private double delta; private void initColors() { colors = new Color[251]; delta = (posEnd - negEnd) / colors.length; if (isUseDoubleGradient()) { ColorGradient csPos = new ColorGradient(posStart, posEnd, midColor, maxColor); ColorGradient csNeg = new ColorGradient(negEnd, negStart, minColor, midColor); for (int i = 0; i < colors.length; i++) { double x = getMinimum() + i * delta; if ((x > negStart) && (x < posStart)) { colors[i] = midColor; } else if (x <= negStart) { colors[i] = csNeg.getColor(x); } else { colors[i] = csPos.getColor(x); } } } else { ColorGradient cs = new ColorGradient(negEnd, posEnd, minColor, maxColor); for (int i = 0; i < colors.length; i++) { double x = getMinimum() + i * delta; colors[i] = cs.getColor(x); } } } /** * Method description * * @param val * @return */ @Override public Color getColor(float val) { if (colors == null) { initColors(); } if(Float.isNaN(val)) { return UIConstants.NO_DATA_COLOR; } // See if we are in the midrange. TO deal with floating point roundoffissues expand the range // by a small amount. if ((val >= 1.0001 * negStart) && (val <= 1.0001 * posStart)) { return midColor; } else { //double f = (val - getMinimum()) / (getMaximum() - getMinimum()); int index = (int) Math.round((val - negEnd) / delta); index = Math.max(0, Math.min(index, colors.length - 1)); return colors[index]; } } /** * Method description * * @return */ public double getNegStart() { return negStart; } /** * Method description * * @return */ public double getPosStart() { return posStart; } /** * Method description * * @return */ public double getMinimum() { return negEnd; } /** * Method description * * @return */ public double getMaximum() { return posEnd; } public double getBaseline() { return (negStart + posStart) / 2; } /** * Method description * * @return */ public Color getMinColor() { return minColor; } /** * Method description * * @return */ public Color getMidColor() { return midColor; } /** * Method description * * @return */ public Color getMaxColor() { return maxColor; } /** * Method description * * @return */ public boolean isUseDoubleGradient() { return useDoubleGradient; } static class ColorGradient { boolean useDoubleGradient = true; double min, max, mid; BufferedImage posImage, negImage; /** * Constructs ... * * @param min * @param mid * @param max * @param negColor * @param neutralColor * @param posColor */ public ColorGradient(double min, double mid, double max, Color negColor, Color neutralColor, Color posColor) { this.useDoubleGradient = true; this.min = min; this.max = max; this.mid = mid; this.posImage = createGradientImage(neutralColor, posColor); this.negImage = createGradientImage(negColor, neutralColor); } /** * Constructs ... * * @param min * @param max * @param neutralColor * @param posColor */ public ColorGradient(double min, double max, Color neutralColor, Color posColor) { this.useDoubleGradient = false; this.min = min; this.max = max; posImage = createGradientImage(neutralColor, posColor); } /** * Creates a gradient image given specified <CODE>Color</CODE>(s) * * @param color1 <CODE>Color</CODE> to display at left side of gradient * @param color2 <CODE>Color</CODE> to display at right side of gradient * @return returns a gradient image */ private BufferedImage createGradientImage(Color color1, Color color2) { BufferedImage image = (BufferedImage) java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice().getDefaultConfiguration() .createCompatibleImage(256, 1); Graphics2D graphics = image.createGraphics(); GradientPaint gp = new GradientPaint(0, 0, color1, 255, 0, color2); graphics.setPaint(gp); graphics.drawRect(0, 0, 255, 1); graphics.dispose(); return image; } /** * Method description * * @param value * @return */ public Color getColor(double value) { int rgb; if (useDoubleGradient) { double maximum = (value < mid) ? this.min : this.max; int colorIndex = (int) (255 * (value - mid) / (maximum - mid)); colorIndex = (colorIndex > 255) ? 255 : colorIndex; rgb = (value < mid) ? negImage.getRGB(255 - colorIndex, 0) : posImage.getRGB(colorIndex, 0); } else { double span = this.max - this.min; int colorIndex = 0; if (value <= min) { colorIndex = 0; } else if (value >= max) { colorIndex = 255; } else { colorIndex = (int) (((value - this.min) / span) * 255); } rgb = posImage.getRGB(colorIndex, 0); } return new Color(rgb); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ContinuousColorScale that = (ContinuousColorScale) o; if (Double.compare(that.negEnd, negEnd) != 0) return false; if (Double.compare(that.negStart, negStart) != 0) return false; if (Double.compare(that.posEnd, posEnd) != 0) return false; if (Double.compare(that.posStart, posStart) != 0) return false; if (useDoubleGradient != that.useDoubleGradient) return false; if (!maxColor.equals(that.maxColor)) return false; if (!midColor.equals(that.midColor)) return false; if (!minColor.equals(that.minColor)) return false; if (!noDataColor.equals(that.noDataColor)) return false; return true; } @Override public int hashCode() { int result; long temp; result = (useDoubleGradient ? 1 : 0); temp = negEnd != +0.0d ? Double.doubleToLongBits(negEnd) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = posEnd != +0.0d ? Double.doubleToLongBits(posEnd) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = negStart != +0.0d ? Double.doubleToLongBits(negStart) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = posStart != +0.0d ? Double.doubleToLongBits(posStart) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + minColor.hashCode(); result = 31 * result + midColor.hashCode(); result = 31 * result + maxColor.hashCode(); result = 31 * result + noDataColor.hashCode(); return result; } }