package spike.palette.paul.com.palettespike;
/*
* Copyright 2014 Chris Banes
*
* 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.
*/
import android.graphics.Bitmap;
import android.graphics.Color;
import java.util.Arrays;
import java.util.Comparator;
import spike.palette.paul.com.palettespike.MedianCutQuantizer.ColorNode;
public class DominantColorCalculator {
private static final String LOG_TAG = DominantColorCalculator.class.getSimpleName();
private static final int NUM_COLORS = 10;
private static final int PRIMARY_TEXT_MIN_CONTRAST = 135;
private static final int SECONDARY_MIN_DIFF_HUE_PRIMARY = 120;
private static final int TERTIARY_MIN_CONTRAST_PRIMARY = 20;
private static final int TERTIARY_MIN_CONTRAST_SECONDARY = 90;
private final MedianCutQuantizer.ColorNode[] mPalette;
private final MedianCutQuantizer.ColorNode[] mWeightedPalette;
private ColorScheme mColorScheme;
public DominantColorCalculator(Bitmap bitmap) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final int[] rgbPixels = new int[width * height];
bitmap.getPixels(rgbPixels, 0, width, 0, 0, width, height);
final MedianCutQuantizer mcq = new MedianCutQuantizer(rgbPixels, NUM_COLORS);
mPalette = mcq.getQuantizedColors();
mWeightedPalette = weight(mPalette);
findColors();
}
public ColorScheme getColorScheme() {
return mColorScheme;
}
private void findColors() {
final ColorNode primaryAccentColor = findPrimaryAccentColor();
final ColorNode secondaryAccentColor = findSecondaryAccentColor(primaryAccentColor);
final int tertiaryAccentColor = findTertiaryAccentColor(
primaryAccentColor, secondaryAccentColor);
final int primaryTextColor = findPrimaryTextColor(primaryAccentColor);
final int secondaryTextColor = findSecondaryTextColor(primaryAccentColor);
mColorScheme = new ColorScheme(
primaryAccentColor.getRgb(),
secondaryAccentColor.getRgb(),
tertiaryAccentColor,
primaryTextColor,
secondaryTextColor);
}
/**
* @return the first color from our weighted palette.
*/
private ColorNode findPrimaryAccentColor() {
return mWeightedPalette[0];
}
/**
* @return the next color in the weighted palette which ideally has enough difference in hue.
*/
private ColorNode findSecondaryAccentColor(final ColorNode primary) {
final float primaryHue = primary.getHsv()[0];
// Find the first color which has sufficient difference in hue from the primary
for (ColorNode candidate : mWeightedPalette) {
final float candidateHue = candidate.getHsv()[0];
// Calculate the difference in hue, if it's over the threshold return it
if (Math.abs(primaryHue - candidateHue) >= SECONDARY_MIN_DIFF_HUE_PRIMARY) {
return candidate;
}
}
// If we get here, just return the second weighted color
return mWeightedPalette[1];
}
/**
* @return the first color from our weighted palette which has sufficient contrast from the
* primary and secondary colors.
*/
private int findTertiaryAccentColor(final ColorNode primary, final ColorNode secondary) {
// Find the first color which has sufficient contrast from both the primary & secondary
for (ColorNode color : mWeightedPalette) {
if (ColorUtils.calculateContrast(color, primary) >= TERTIARY_MIN_CONTRAST_PRIMARY
&& ColorUtils.calculateContrast(color, secondary) >= TERTIARY_MIN_CONTRAST_SECONDARY) {
return color.getRgb();
}
}
// We couldn't find a colour. In that case use the primary colour, modifying it's brightness
// by 45%
return ColorUtils.changeBrightness(secondary.getRgb(), 0.45f);
}
/**
* @return the first color which has sufficient contrast from the primary colors.
*/
private int findPrimaryTextColor(final ColorNode primary) {
// Try and find a colour with sufficient contrast from the primary colour
for (ColorNode color : mPalette) {
if (ColorUtils.calculateContrast(color, primary) >= PRIMARY_TEXT_MIN_CONTRAST) {
return color.getRgb();
}
}
// We haven't found a colour, so return black/white depending on the primary colour's
// brightness
return ColorUtils.calculateYiqLuma(primary.getRgb()) >= 128 ? Color.BLACK : Color.WHITE;
}
/**
* @return return black/white depending on the primary colour's brightness
*/
private int findSecondaryTextColor(final ColorNode primary) {
return ColorUtils.calculateYiqLuma(primary.getRgb()) >= 128 ? Color.BLACK : Color.WHITE;
}
private static ColorNode[] weight(ColorNode[] palette) {
final MedianCutQuantizer.ColorNode[] copy = Arrays.copyOf(palette, palette.length);
final float maxCount = palette[0].getCount();
Arrays.sort(copy, new Comparator<ColorNode>() {
@Override
public int compare(ColorNode lhs, ColorNode rhs) {
final float lhsWeight = calculateWeight(lhs, maxCount);
final float rhsWeight = calculateWeight(rhs, maxCount);
if (lhsWeight < rhsWeight) {
return 1;
} else if (lhsWeight > rhsWeight) {
return -1;
}
return 0;
}
});
return copy;
}
private static float calculateWeight(ColorNode node, final float maxCount) {
return FloatUtils.weightedAverage(
ColorUtils.calculateColorfulness(node), 2f,
(node.getCount() / maxCount), 1f
);
}
}