/******************************************************************************* * Copyright 2013 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.common.color; import java.awt.Color; import au.gov.ga.earthsci.common.color.ColorType.Channel; import au.gov.ga.earthsci.worldwind.common.util.Validate; /** * A utility class that can sample a {@link ColorMap} and return the result in a * number of formats useful in different applications. * * @author James Navin (james.navin@ga.gov.au) */ public class ColorMapSampler { private ColorMapSampler() { } /** * Sample the given color map and return an array of sampled {@link Color} * objects. * <p/> * The map will be sampled in the range {@code [minValue, maxValue]} using * {@code numSamples} samples. * <p/> * Equivalent to the call: * * <pre> * sample(map, numSamples, minValue, maxValue, new Color[numSamples], 0); * </pre> * * @param map * The map to sample from * @param numSamples * The number of samples to take from the map * @param minValue * The minimum data value to use when sampling * @param maxValue * The maximum data value to use when sampling * * @return an array of {@link Color} objects sampled from the map. The * result will have length {@code numSamples}. */ @SuppressWarnings("nls") public static Color[] sample(ColorMap map, int numSamples, double minValue, double maxValue) { Validate.isTrue(numSamples >= 0, "numSamples must be a non-negative integer."); return sample(map, numSamples, minValue, maxValue, new Color[numSamples], 0); } /** * Sample the given color map and populate the given target array with * {@link Color} objects. * <p/> * The map will be sampled in the range {@code [minValue, maxValue]} using * {@code numSamples} samples. * * @param map * The map to sample from. Must be non-null. * @param numSamples * The number of samples to take from the map. Must be a non * @param minValue * The minimum data value to use when sampling * @param maxValue * The maximum data value to use when sampling * @param target * The array to put samples into. Must have * {@code length >= numSamples + offset}. * @param offset * The offset into the array at which sample should be added * * @return The target array */ @SuppressWarnings("nls") public static Color[] sample(final ColorMap map, final int numSamples, double minValue, double maxValue, final Color[] target, final int offset) { Validate.notNull(map, "A ColorMap is required"); Validate.isTrue(numSamples >= 0, "numSamples must be a non-negative integer."); Validate.notNull(target, "A target array is required"); Validate.isTrue(offset >= 0, "Offset must be a non-negative integer"); Validate.isTrue(target.length >= numSamples + offset, "The target array is not large enough to contain the desired number of samples. Got " + target.length + ", need " + (numSamples + offset)); minValue = Math.min(minValue, maxValue); maxValue = Math.max(minValue, maxValue); // Special case - take single samples as the minValue if (numSamples == 1) { target[offset] = map.getColor(minValue, minValue, maxValue); return target; } // General case - for more than 1 sample, sample along the interval [minValue, maxValue] for (int i = 0; i < numSamples; i++) { double sampleValue = ((double) i / (numSamples - 1)) * (maxValue - minValue) + minValue; Color color = map.getColor(sampleValue, minValue, maxValue); target[offset + i] = color; } return target; } /** * Sample the given color map and return an array of sampled colors as float * values per channel, controlled by the provided {@link ColorType}. * <p/> * The map will be sampled in the range {@code [minValue, maxValue]} using * {@code numSamples} samples. * <p/> * Equivalent to the call: * * <pre> * sample(map, numSamples, minValue, maxValue, new float[numSamples * type.getNumComponents()], 0); * </pre> * * @param map * The map to sample from * @param numSamples * The number of samples to take from the map * @param minValue * The minimum data value to use when sampling * @param maxValue * The maximum data value to use when sampling * @param type * The type of color value to store in the float array * * @return an array of float color values sampled from the map. The result * will have length {@code numSamples * type.getNumComponents()}. */ @SuppressWarnings("nls") public static float[] sample(ColorMap map, int numSamples, double minValue, double maxValue, ColorType type) { Validate.isTrue(numSamples >= 0, "numSamples must be a non-negative integer."); Validate.notNull(type, "A color type must be provided"); return sample(map, numSamples, minValue, maxValue, new float[numSamples * type.getNumComponents()], 0, type); } /** * Sample the given color map and populate the given target array with float * values for each color channel, set as per the provided {@link ColorType}. * <p/> * The map will be sampled in the range {@code [minValue, maxValue]} using * {@code numSamples} samples. * * @param map * The map to sample from. Must be non-null. * @param numSamples * The number of samples to take from the map. Must be a non * @param minValue * The minimum data value to use when sampling * @param maxValue * The maximum data value to use when sampling * @param target * The array to put samples into. Must have * {@code length >= (numSamples * type.getNumComponents()) + offset} * . * @param offset * The offset into the array at which sample should be added * @param type * The color type (and pattern) to use when writing color * channels into the target array * * @return The target array */ @SuppressWarnings("nls") public static float[] sample(final ColorMap map, final int numSamples, double minValue, double maxValue, final float[] target, final int offset, ColorType type) { Validate.notNull(map, "A ColorMap is required"); Validate.isTrue(numSamples >= 0, "numSamples must be a non-negative integer."); Validate.notNull(target, "A target array is required"); Validate.notNull(type, "A target color type is required"); Validate.isTrue(offset >= 0, "Offset must be a non-negative integer"); Validate.isTrue(target.length >= (numSamples * type.getNumComponents()) + offset, "The target array is not large enough to contain the desired number of samples. Got " + target.length + ", need " + ((numSamples * type.getNumComponents()) + offset)); minValue = Math.min(minValue, maxValue); maxValue = Math.max(minValue, maxValue); // Special case - take single samples as the minValue if (numSamples == 1) { Color c = map.getColor(minValue, minValue, maxValue); toFloats(c, target, null, offset, type); return target; } // General case - for more than 1 sample, sample along the interval [minValue, maxValue] float[] rgbTemp = new float[4]; for (int i = 0; i < numSamples; i++) { double sampleValue = ((double) i / (numSamples - 1)) * (maxValue - minValue) + minValue; Color color = map.getColor(sampleValue, minValue, maxValue); toFloats(color, target, rgbTemp, offset + (i * type.getNumComponents()), type); } return target; } private static void toFloats(Color c, float[] target, float[] rgbTemp, int offset, ColorType type) { float[] rgb = c.getRGBColorComponents(rgbTemp); if (type.hasChannel(Channel.RED)) { target[offset + type.getChannelIndex(Channel.RED)] = rgb[0]; } if (type.hasChannel(Channel.GREEN)) { target[offset + type.getChannelIndex(Channel.GREEN)] = rgb[1]; } if (type.hasChannel(Channel.BLUE)) { target[offset + type.getChannelIndex(Channel.BLUE)] = rgb[2]; } if (type.hasChannel(Channel.ALPHA)) { target[offset + type.getChannelIndex(Channel.ALPHA)] = (c.getAlpha() / 255.0f); } } }