/******************************************************************************* * Copyright (c) 2010 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ package org.csstudio.swt.widgets.datadefinition; import java.util.Arrays; import java.util.LinkedHashMap; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.RGB; /**Color Map data type. * @author Xihui Chen * */ public class ColorMap { public enum PredefinedColorMap{ None("None", new double[0], new RGB[0]), GrayScale("GrayScale", new double[]{0,1}, new RGB[]{new RGB(0,0,0), new RGB(255,255,255)}), JET("JET", new double[]{0, 0.111, 0.365, 0.619, 0.873, 1}, new RGB[]{new RGB(0,0,143), new RGB(0,0,255), new RGB(0,255,255), new RGB(255,255,0), new RGB(255,0,0), new RGB(128,0,0)}), ColorSpectrum("ColorSpectrum", new double[]{0, 0.126, 0.251, 0.375, 0.5, 0.625, 0.749, 0.874,1}, new RGB[]{new RGB(0,0,0), new RGB(255,0,255), new RGB(0,0,255), new RGB(0,255,255), new RGB(0,255,0), new RGB(255,255,0), new RGB(255,128,0), new RGB(255,0,0), new RGB(255,255,255)}), Hot("Hot", new double[]{0, 0.365, 0.746, 1}, new RGB[]{new RGB(11,0,0), new RGB(255,0,0), new RGB(255,255,0), new RGB(255,255,255)}), Cool("Cool", new double[]{0,1}, new RGB[]{new RGB(0,255,255), new RGB(255,0,255)}), Shaded("Shaded", new double[]{0, 0.5, 1}, new RGB[]{new RGB(0,0,0), new RGB(255,0,0), new RGB(255,255,255)}); String name; double[] values; RGB[] colors; private PredefinedColorMap(String name, double[] values, RGB[] colors){ this.name = name; this.values = values; this.colors = colors; } public LinkedHashMap<Double, RGB> getMap() { LinkedHashMap<Double, RGB> map = new LinkedHashMap<Double, RGB>(); for(int i=0; i<values.length; i++){ map.put(values[i], colors[i]); } return map; } public static String[] getStringValues(){ String[] result = new String[values().length]; int i =0; for(PredefinedColorMap m : values()) result[i++] = m.name; return result; } public static int toIndex(PredefinedColorMap p){ return Arrays.asList(values()).indexOf(p); } public static PredefinedColorMap fromIndex(int index){ return Arrays.asList(values()).get(index); } @Override public String toString() { return name; } } private LinkedHashMap<Double, RGB> colorMap; private PredefinedColorMap predefinedColorMap; private boolean autoScale; private boolean interpolate; private RGB[] colorsLookupTable; private int[] pixelLookupTable; private PaletteData palette = new PaletteData(0xff, 0xff00, 0xff0000); private double colorMapMin; private double colorMapMax; public ColorMap() { colorMap = new LinkedHashMap<Double, RGB>(); setAutoScale(true); setInterpolate(true); predefinedColorMap = PredefinedColorMap.None; } public ColorMap(PredefinedColorMap predefinedColorMap, boolean autoScale, boolean interpolate){ setAutoScale(autoScale); setInterpolate(interpolate); setPredefinedColorMap(predefinedColorMap); } /** * @return the map which back up the ColorMap */ public LinkedHashMap<Double, RGB> getMap() { return colorMap; } /**Set a new map. * @param colorMap the new map. */ public void setColorMap(LinkedHashMap<Double, RGB> colorMap) { this.colorMap = colorMap; this.predefinedColorMap = PredefinedColorMap.None; colorsLookupTable = null; } /** * @param autoScale the autoScale to set */ public void setAutoScale(boolean autoScale) { this.autoScale = autoScale; colorsLookupTable = null; } /** * @return the autoScale */ public boolean isAutoScale() { return autoScale; } /** * @param interpolate the interpolate to set */ public void setInterpolate(boolean interpolate) { this.interpolate = interpolate; } /** * @return the interpolate */ public boolean isInterpolate() { return interpolate; } /** * @param predefinedColorMap the predefinedColorMap to set */ public void setPredefinedColorMap(PredefinedColorMap predefinedColorMap) { this.predefinedColorMap = predefinedColorMap; if(predefinedColorMap != PredefinedColorMap.None) colorMap = predefinedColorMap.getMap(); colorsLookupTable = null; } /** * @return the predefinedColorMap */ public PredefinedColorMap getPredefinedColorMap() { return predefinedColorMap; } @Override public String toString() { if(predefinedColorMap != null && predefinedColorMap != PredefinedColorMap.None) return predefinedColorMap.toString(); else return "Customized"; } /**Calculate the image data from source data based on the color map. * @param dataArray the source data * @param dataWidth number of columns of dataArray; This will be the width of image data. * @param dataHeight number of rows of dataArray; This will be the height of image data. * @param max the upper limit of the data in dataArray * @param min the lower limit of the data in dataArray * @param imageData the imageData to be filled. null if a new instance should be created. * @param shrink true if area size of image data is smaller than dataWidth*dataHeight. If this is true, it will use * the nearest neighbor iamge scaling algorithm as described at http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/. * @return the image data. null if dataWidth or dataHeight is less than 1. */ public ImageData drawImage(IPrimaryArrayWrapper dataArray, int dataWidth, int dataHeight, double max, double min, ImageData imageData, boolean shrink){ if(dataWidth <1 || dataHeight < 1 || dataWidth *dataHeight > dataArray.getSize()|| dataWidth * dataHeight < 0) return null; if(imageData == null) imageData = new ImageData(dataWidth,dataHeight, 24, palette); if(colorsLookupTable == null) getColorsLookupTable(); if(!autoScale){ min = colorMapMin; max = colorMapMax; } if(shrink){ int height = imageData.height; int width = imageData.width; // EDIT: added +1 to account for an early rounding problem int x_ratio = (int)((dataWidth<<16)/width) +1; int y_ratio = (int)((dataHeight<<16)/height) +1; //int x_ratio = (int)((w1<<16)/w2) ; //int y_ratio = (int)((h1<<16)/h2) ; int x2, y2 ; for (int i=0;i<height;i++) { for (int j=0;j<width;j++) { x2 = ((j*x_ratio)>>16) ; y2 = ((i*y_ratio)>>16) ; int index = (int) ((dataArray.get(y2 * dataWidth + x2) - min) / (max - min) * 255); if (index < 0) index = 0; else if (index > 255) index = 255; int pixel = pixelLookupTable[index]; imageData.setPixel(j, i, pixel); } } }else{ for (int y = 0; y < dataHeight; y++) { for (int x = 0; x < dataWidth; x++) { // the index of the value in the color table array int index = (int) ((dataArray.get(y * dataWidth + x) - min) / (max - min) * 255); if (index < 0) index = 0; else if (index > 255) index = 255; int pixel = pixelLookupTable[index]; imageData.setPixel(x, y, pixel); } } } return imageData; } /**Calculate the image data from source data based on the color map. * @param dataArray the source data * @param dataWidth number of columns of dataArray; This will be the width of image data. * @param dataHeight number of rows of dataArray; This will be the height of image data. * @param max the upper limit of the data in dataArray * @param min the lower limit of the data in dataArray * @param imageData the imageData to be filled. null if a new instance should be created. * @return the image data. null if dataWidth or dataHeight is less than 1. */ public ImageData drawImage(double[] dataArray, int dataWidth, int dataHeight, double max, double min){ return drawImage(new DoubleArrayWrapper(dataArray), dataWidth, dataHeight, max, min, null, false); } /** * @param colorTupleArray * @param keyArray * @param value the value which has been scaled or not based on the autoScale flag. * @param min * @param max * @return */ public RGB getValueRGB(ColorTuple[] colorTupleArray, double[] keyArray, double value){ int insertPoint = Arrays.binarySearch(keyArray, value); if(insertPoint >= 0) return colorTupleArray[insertPoint].rgb; else{ insertPoint = -insertPoint -1; if(insertPoint == 0) return colorTupleArray[0].rgb; if(insertPoint == colorTupleArray.length) return colorTupleArray[colorTupleArray.length -1].rgb; return getInterpolateRGB(colorTupleArray[insertPoint-1], colorTupleArray[insertPoint], value); } } private RGB getInterpolateRGB(ColorTuple start, ColorTuple end, double value){ if(interpolate){ double f = (value - start.value)/(end.value - start.value); int r =(int) ((end.rgb.red - start.rgb.red)*f + start.rgb.red); int g =(int) ((end.rgb.green - start.rgb.green)*f + start.rgb.green); int b =(int) ((end.rgb.blue - start.rgb.blue)*f + start.rgb.blue); return new RGB(r,g,b); }else return start.rgb; } /**Get a colors lookup table from 0 to 255. This only works for autoScale is true; * @return the colorsLookupTable a array of 256 colors corresponding to the value from min to max */ public RGB[] getColorsLookupTable() { if(colorsLookupTable == null){ //convert map to array to simplify the calculation ColorTuple[] colorTupleArray = new ColorTuple[colorMap.size()]; int i=0; for(Double k : colorMap.keySet()){ colorTupleArray[i++]=new ColorTuple(k, colorMap.get(k)); } //sort the array Arrays.sort(colorTupleArray); colorMapMin = colorTupleArray[0].value; colorMapMax = colorTupleArray[colorTupleArray.length-1].value; if(autoScale) for(ColorTuple t : colorTupleArray){ t.value = (t.value - colorMapMin)/(colorMapMax-colorMapMin); } double[] keyArray = new double[colorTupleArray.length]; for(int j = 0; j<colorTupleArray.length; j++) keyArray[j] = colorTupleArray[j].value; colorsLookupTable = new RGB[256]; pixelLookupTable = new int[256]; for(int k=0; k<256; k++){ colorsLookupTable[k] = getValueRGB(colorTupleArray, keyArray, autoScale? k/255.0 : colorMapMin + k*(colorMapMax-colorMapMin)/255.0); pixelLookupTable[k] = palette.getPixel(colorsLookupTable[k]); } } return colorsLookupTable; } public PaletteData getPalette() { return palette; } }