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 ); } }