/*
* Copyright 2014 Google Inc.
*
* 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 com.google.maps.android.heatmaps;
import android.graphics.Color;
import java.util.HashMap;
/**
* A class to generate a color map from a given array of colors and the fractions
* that the colors represent by interpolating between their HSV values.
* This color map is to be used in the HeatmapTileProvider.
*/
public class Gradient {
private class ColorInterval {
private final int color1;
private final int color2;
/**
* The period over which the color changes from color1 to color2.
* This is given as the number of elements it represents in the colorMap.
*/
private final float duration;
private ColorInterval(int color1, int color2, float duration) {
this.color1 = color1;
this.color2 = color2;
this.duration = duration;
}
}
private static final int DEFAULT_COLOR_MAP_SIZE = 1000;
/**
* Size of a color map for the heatmap
*/
public final int mColorMapSize;
/**
* The colors to be used in the gradient
*/
public int[] mColors;
/**
* The starting point for each color, given as a percentage of the maximum intensity
*/
public float[] mStartPoints;
/**
* Creates a Gradient with the given colors and starting points.
* These are given as parallel arrays.
*
* @param colors The colors to be used in the gradient
* @param startPoints The starting point for each color, given as a percentage of the maximum intensity
* This is given as an array of floats with values in the interval [0,1]
*/
public Gradient(int[] colors, float[] startPoints) {
this(colors, startPoints, DEFAULT_COLOR_MAP_SIZE);
}
/**
* Creates a Gradient with the given colors and starting points which creates a colorMap of given size.
* The colors and starting points are given as parallel arrays.
*
* @param colors The colors to be used in the gradient
* @param startPoints The starting point for each color, given as a percentage of the maximum intensity
* This is given as an array of floats with values in the interval [0,1]
* @param colorMapSize The size of the colorMap to be generated by the Gradient
*/
public Gradient(int[] colors, float[] startPoints, int colorMapSize) {
if (colors.length != startPoints.length) {
throw new IllegalArgumentException("colors and startPoints should be same length");
} else if (colors.length == 0) {
throw new IllegalArgumentException("No colors have been defined");
}
for (int i = 1; i < startPoints.length; i++) {
if (startPoints[i] <= startPoints[i - 1]) {
throw new IllegalArgumentException("startPoints should be in increasing order");
}
}
mColorMapSize = colorMapSize;
mColors = new int[colors.length];
mStartPoints = new float[startPoints.length];
System.arraycopy(colors, 0, mColors, 0, colors.length);
System.arraycopy(startPoints, 0, mStartPoints, 0, startPoints.length);
}
private HashMap<Integer, ColorInterval> generateColorIntervals() {
HashMap<Integer, ColorInterval> colorIntervals = new HashMap<Integer, ColorInterval>();
// Create first color if not already created
// The initial color is transparent by default
if (mStartPoints[0] != 0) {
int initialColor = Color.argb(
0, Color.red(mColors[0]), Color.green(mColors[0]), Color.blue(mColors[0]));
colorIntervals.put(0, new ColorInterval(initialColor, mColors[0], mColorMapSize * mStartPoints[0]));
}
// Generate color intervals
for (int i = 1; i < mColors.length; i++) {
colorIntervals.put(((int) (mColorMapSize * mStartPoints[i - 1])),
new ColorInterval(mColors[i - 1], mColors[i],
(mColorMapSize * (mStartPoints[i] - mStartPoints[i - 1]))));
}
// Extend to a final color
// If color for 100% intensity is not given, the color of highest intensity is used.
if (mStartPoints[mStartPoints.length - 1] != 1) {
int i = mStartPoints.length - 1;
colorIntervals.put(((int) (mColorMapSize * mStartPoints[i])),
new ColorInterval(mColors[i], mColors[i], mColorMapSize * (1 - mStartPoints[i])));
}
return colorIntervals;
}
/**
* Generates the color map to use with a provided gradient.
*
* @param opacity Overall opacity of entire image: every individual alpha value will be
* multiplied by this opacity.
* @return the generated color map based on the gradient
*/
int[] generateColorMap(double opacity) {
HashMap<Integer, ColorInterval> colorIntervals = generateColorIntervals();
int[] colorMap = new int[mColorMapSize];
ColorInterval interval = colorIntervals.get(0);
int start = 0;
for (int i = 0; i < mColorMapSize; i++) {
if (colorIntervals.containsKey(i)) {
interval = colorIntervals.get(i);
start = i;
}
float ratio = (i - start) / interval.duration;
colorMap[i] = interpolateColor(interval.color1, interval.color2, ratio);
}
if (opacity != 1) {
for (int i = 0; i < mColorMapSize; i++) {
int c = colorMap[i];
colorMap[i] = Color.argb((int) (Color.alpha(c) * opacity),
Color.red(c), Color.green(c), Color.blue(c));
}
}
return colorMap;
}
/**
* Helper function for creation of color map
* Interpolates between two given colors using their HSV values.
*
* @param color1 First color
* @param color2 Second color
* @param ratio Between 0 to 1. Fraction of the distance between color1 and color2
* @return Color associated with x2
*/
static int interpolateColor(int color1, int color2, float ratio) {
int alpha = (int) ((Color.alpha(color2) - Color.alpha(color1)) * ratio + Color.alpha(color1));
float[] hsv1 = new float[3];
Color.RGBToHSV(Color.red(color1), Color.green(color1), Color.blue(color1), hsv1);
float[] hsv2 = new float[3];
Color.RGBToHSV(Color.red(color2), Color.green(color2), Color.blue(color2), hsv2);
// adjust so that the shortest path on the color wheel will be taken
if (hsv1[0] - hsv2[0] > 180) {
hsv2[0] += 360;
} else if (hsv2[0] - hsv1[0] > 180) {
hsv1[0] += 360;
}
// Interpolate using calculated ratio
float[] result = new float[3];
for (int i = 0; i < 3; i++) {
result[i] = (hsv2[i] - hsv1[i]) * (ratio) + hsv1[i];
}
return Color.HSVToColor(alpha, result);
}
}