DefaultGenerator.java revision f78a300c82748e29a3890c8f17a13726aacf33be
1/* 2 * Copyright 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.graphics; 18 19import android.support.v4.graphics.ColorUtils; 20import android.support.v7.graphics.Palette.Swatch; 21 22import java.util.List; 23 24class DefaultGenerator extends Palette.Generator { 25 26 private static final float TARGET_DARK_LUMA = 0.26f; 27 private static final float MAX_DARK_LUMA = 0.45f; 28 29 private static final float MIN_LIGHT_LUMA = 0.55f; 30 private static final float TARGET_LIGHT_LUMA = 0.74f; 31 32 private static final float MIN_NORMAL_LUMA = 0.3f; 33 private static final float TARGET_NORMAL_LUMA = 0.5f; 34 private static final float MAX_NORMAL_LUMA = 0.7f; 35 36 private static final float TARGET_MUTED_SATURATION = 0.3f; 37 private static final float MAX_MUTED_SATURATION = 0.4f; 38 39 private static final float TARGET_VIBRANT_SATURATION = 1f; 40 private static final float MIN_VIBRANT_SATURATION = 0.35f; 41 42 private static final float WEIGHT_SATURATION = 3f; 43 private static final float WEIGHT_LUMA = 6f; 44 private static final float WEIGHT_POPULATION = 1f; 45 46 private List<Swatch> mSwatches; 47 48 private int mHighestPopulation; 49 50 private Swatch mVibrantSwatch; 51 private Swatch mMutedSwatch; 52 private Swatch mDarkVibrantSwatch; 53 private Swatch mDarkMutedSwatch; 54 private Swatch mLightVibrantSwatch; 55 private Swatch mLightMutedSwatch; 56 57 @Override 58 public void generate(final List<Swatch> swatches) { 59 mSwatches = swatches; 60 61 mHighestPopulation = findMaxPopulation(); 62 63 generateVariationColors(); 64 65 // Now try and generate any missing colors 66 generateEmptySwatches(); 67 } 68 69 @Override 70 public Swatch getVibrantSwatch() { 71 return mVibrantSwatch; 72 } 73 74 @Override 75 public Swatch getLightVibrantSwatch() { 76 return mLightVibrantSwatch; 77 } 78 79 @Override 80 public Swatch getDarkVibrantSwatch() { 81 return mDarkVibrantSwatch; 82 } 83 84 @Override 85 public Swatch getMutedSwatch() { 86 return mMutedSwatch; 87 } 88 89 @Override 90 public Swatch getLightMutedSwatch() { 91 return mLightMutedSwatch; 92 } 93 94 @Override 95 public Swatch getDarkMutedSwatch() { 96 return mDarkMutedSwatch; 97 } 98 99 private void generateVariationColors() { 100 mVibrantSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, 101 TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); 102 103 mLightVibrantSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f, 104 TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); 105 106 mDarkVibrantSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA, 107 TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); 108 109 mMutedSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, 110 TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); 111 112 mLightMutedSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f, 113 TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); 114 115 mDarkMutedSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA, 116 TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); 117 } 118 119 /** 120 * Try and generate any missing swatches from the swatches we did find. 121 */ 122 private void generateEmptySwatches() { 123 if (mVibrantSwatch == null) { 124 // If we do not have a vibrant color... 125 if (mDarkVibrantSwatch != null) { 126 // ...but we do have a dark vibrant, generate the value by modifying the luma 127 final float[] newHsl = copyHslValues(mDarkVibrantSwatch); 128 newHsl[2] = TARGET_NORMAL_LUMA; 129 mVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0); 130 } 131 } 132 133 if (mDarkVibrantSwatch == null) { 134 // If we do not have a dark vibrant color... 135 if (mVibrantSwatch != null) { 136 // ...but we do have a vibrant, generate the value by modifying the luma 137 final float[] newHsl = copyHslValues(mVibrantSwatch); 138 newHsl[2] = TARGET_DARK_LUMA; 139 mDarkVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0); 140 } 141 } 142 } 143 144 /** 145 * Find the {@link Palette.Swatch} with the highest population value and return the population. 146 */ 147 private int findMaxPopulation() { 148 int population = 0; 149 for (Swatch swatch : mSwatches) { 150 population = Math.max(population, swatch.getPopulation()); 151 } 152 return population; 153 } 154 155 private Swatch findColorVariation(float targetLuma, float minLuma, float maxLuma, 156 float targetSaturation, float minSaturation, float maxSaturation) { 157 Swatch max = null; 158 float maxValue = 0f; 159 160 for (Swatch swatch : mSwatches) { 161 final float sat = swatch.getHsl()[1]; 162 final float luma = swatch.getHsl()[2]; 163 164 if (sat >= minSaturation && sat <= maxSaturation && 165 luma >= minLuma && luma <= maxLuma && 166 !isAlreadySelected(swatch)) { 167 float value = createComparisonValue(sat, targetSaturation, luma, targetLuma, 168 swatch.getPopulation(), mHighestPopulation); 169 if (max == null || value > maxValue) { 170 max = swatch; 171 maxValue = value; 172 } 173 } 174 } 175 176 return max; 177 } 178 179 /** 180 * @return true if we have already selected {@code swatch} 181 */ 182 private boolean isAlreadySelected(Swatch swatch) { 183 return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch || 184 mLightVibrantSwatch == swatch || mMutedSwatch == swatch || 185 mDarkMutedSwatch == swatch || mLightMutedSwatch == swatch; 186 } 187 188 private static float createComparisonValue(float saturation, float targetSaturation, 189 float luma, float targetLuma, 190 int population, int maxPopulation) { 191 return createComparisonValue(saturation, targetSaturation, WEIGHT_SATURATION, 192 luma, targetLuma, WEIGHT_LUMA, 193 population, maxPopulation, WEIGHT_POPULATION); 194 } 195 196 private static float createComparisonValue( 197 float saturation, float targetSaturation, float saturationWeight, 198 float luma, float targetLuma, float lumaWeight, 199 int population, int maxPopulation, float populationWeight) { 200 return weightedMean( 201 invertDiff(saturation, targetSaturation), saturationWeight, 202 invertDiff(luma, targetLuma), lumaWeight, 203 population / (float) maxPopulation, populationWeight 204 ); 205 } 206 207 /** 208 * Copy a {@link Swatch}'s HSL values into a new float[]. 209 */ 210 private static float[] copyHslValues(Swatch color) { 211 final float[] newHsl = new float[3]; 212 System.arraycopy(color.getHsl(), 0, newHsl, 0, 3); 213 return newHsl; 214 } 215 216 /** 217 * Returns a value in the range 0-1. 1 is returned when {@code value} equals the 218 * {@code targetValue} and then decreases as the absolute difference between {@code value} and 219 * {@code targetValue} increases. 220 * 221 * @param value the item's value 222 * @param targetValue the value which we desire 223 */ 224 private static float invertDiff(float value, float targetValue) { 225 return 1f - Math.abs(value - targetValue); 226 } 227 228 private static float weightedMean(float... values) { 229 float sum = 0f; 230 float sumWeight = 0f; 231 232 for (int i = 0; i < values.length; i += 2) { 233 float value = values[i]; 234 float weight = values[i + 1]; 235 236 sum += (value * weight); 237 sumWeight += weight; 238 } 239 240 return sum / sumWeight; 241 } 242} 243