1 /* 2 * Copyright (C) 2017 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 17 package com.android.launcher3.uioverrides.dynamicui; 18 19 import android.content.Context; 20 import android.graphics.Color; 21 import android.support.annotation.NonNull; 22 import android.support.annotation.Nullable; 23 import android.support.v4.graphics.ColorUtils; 24 import android.util.Log; 25 import android.util.Pair; 26 import android.util.Range; 27 28 import com.android.launcher3.R; 29 import com.android.launcher3.Utilities; 30 31 import java.util.Arrays; 32 import java.util.LinkedList; 33 import java.util.List; 34 35 /** 36 * Implementation of tonal color extraction 37 **/ 38 public class ColorExtractionAlgorithm { 39 40 public static ColorExtractionAlgorithm newInstance(Context context) { 41 return Utilities.getOverrideObject(ColorExtractionAlgorithm.class, 42 context.getApplicationContext(), R.string.color_extraction_impl_class); 43 } 44 45 private static final String TAG = "Tonal"; 46 47 // Used for tonal palette fitting 48 private static final float FIT_WEIGHT_H = 1.0f; 49 private static final float FIT_WEIGHT_S = 1.0f; 50 private static final float FIT_WEIGHT_L = 10.0f; 51 52 public static final int MAIN_COLOR_LIGHT = 0xffb0b0b0; 53 public static final int SECONDARY_COLOR_LIGHT = 0xff9e9e9e; 54 public static final int MAIN_COLOR_DARK = 0xff212121; 55 public static final int SECONDARY_COLOR_DARK = 0xff000000; 56 57 // Temporary variable to avoid allocations 58 private float[] mTmpHSL = new float[3]; 59 60 public Pair<Integer, Integer> extractInto(WallpaperColorsCompat inWallpaperColors) { 61 if (inWallpaperColors == null) { 62 return applyFallback(inWallpaperColors); 63 } 64 65 final List<Integer> mainColors = getMainColors(inWallpaperColors); 66 final int mainColorsSize = mainColors.size(); 67 final boolean supportsDarkText = (inWallpaperColors.getColorHints() & 68 WallpaperColorsCompat.HINT_SUPPORTS_DARK_TEXT) != 0; 69 70 if (mainColorsSize == 0) { 71 return applyFallback(inWallpaperColors); 72 } 73 // Tonal is not really a sort, it takes a color from the extracted 74 // palette and finds a best fit amongst a collection of pre-defined 75 // palettes. The best fit is tweaked to be closer to the source color 76 // and replaces the original palette 77 78 // Get the most preeminent, non-blacklisted color. 79 Integer bestColor = 0; 80 final float[] hsl = new float[3]; 81 for (int i = 0; i < mainColorsSize; i++) { 82 final int colorValue = mainColors.get(i); 83 ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), 84 Color.blue(colorValue), hsl); 85 86 // Stop when we find a color that meets our criteria 87 if (!isBlacklisted(hsl)) { 88 bestColor = colorValue; 89 break; 90 } 91 } 92 93 // Fail if not found 94 if (bestColor == null) { 95 return applyFallback(inWallpaperColors); 96 } 97 98 int colorValue = bestColor; 99 ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue), 100 hsl); 101 102 // The Android HSL definition requires the hue to go from 0 to 360 but 103 // the Material Tonal Palette defines hues from 0 to 1. 104 hsl[0] /= 360f; 105 106 // Find the palette that contains the closest color 107 TonalPalette palette = findTonalPalette(hsl[0], hsl[1]); 108 if (palette == null) { 109 Log.w(TAG, "Could not find a tonal palette!"); 110 return applyFallback(inWallpaperColors); 111 } 112 113 // Figure out what's the main color index in the optimal palette 114 int fitIndex = bestFit(palette, hsl[0], hsl[1], hsl[2]); 115 if (fitIndex == -1) { 116 Log.w(TAG, "Could not find best fit!"); 117 return applyFallback(inWallpaperColors); 118 } 119 120 // Generate the 10 colors palette by offsetting each one of them 121 float[] h = fit(palette.h, hsl[0], fitIndex, 122 Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY); 123 float[] s = fit(palette.s, hsl[1], fitIndex, 0.0f, 1.0f); 124 float[] l = fit(palette.l, hsl[2], fitIndex, 0.0f, 1.0f); 125 126 int primaryIndex = fitIndex; 127 int mainColor = getColorInt(primaryIndex, h, s, l); 128 129 // We might want use the fallback in case the extracted color is brighter than our 130 // light fallback or darker than our dark fallback. 131 ColorUtils.colorToHSL(mainColor, mTmpHSL); 132 final float mainLuminosity = mTmpHSL[2]; 133 ColorUtils.colorToHSL(MAIN_COLOR_LIGHT, mTmpHSL); 134 final float lightLuminosity = mTmpHSL[2]; 135 if (mainLuminosity > lightLuminosity) { 136 return applyFallback(inWallpaperColors); 137 } 138 ColorUtils.colorToHSL(MAIN_COLOR_DARK, mTmpHSL); 139 final float darkLuminosity = mTmpHSL[2]; 140 if (mainLuminosity < darkLuminosity) { 141 return applyFallback(inWallpaperColors); 142 } 143 144 // Dark colors: 145 // Stops at 4th color, only lighter if dark text is supported 146 if (supportsDarkText) { 147 primaryIndex = h.length - 1; 148 } else if (fitIndex < 2) { 149 primaryIndex = 0; 150 } else { 151 primaryIndex = Math.min(fitIndex, 3); 152 } 153 int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2); 154 int secondaryColor = getColorInt(secondaryIndex, h, s, l); 155 156 return new Pair<>(mainColor, secondaryColor); 157 } 158 159 public static Pair<Integer, Integer> applyFallback(WallpaperColorsCompat inWallpaperColors) { 160 boolean light = inWallpaperColors != null 161 && (inWallpaperColors.getColorHints() 162 & WallpaperColorsCompat.HINT_SUPPORTS_DARK_TEXT)!= 0; 163 int innerColor = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK; 164 int outerColor = light ? SECONDARY_COLOR_LIGHT : SECONDARY_COLOR_DARK; 165 return new Pair<>(innerColor, outerColor); 166 } 167 168 private int getColorInt(int fitIndex, float[] h, float[] s, float[] l) { 169 mTmpHSL[0] = fract(h[fitIndex]) * 360.0f; 170 mTmpHSL[1] = s[fitIndex]; 171 mTmpHSL[2] = l[fitIndex]; 172 return ColorUtils.HSLToColor(mTmpHSL); 173 } 174 175 /** 176 * Checks if a given color exists in the blacklist 177 * @param hsl float array with 3 components (H 0..360, S 0..1 and L 0..1) 178 * @return true if color should be avoided 179 */ 180 private boolean isBlacklisted(float[] hsl) { 181 for (ColorRange badRange: BLACKLISTED_COLORS) { 182 if (badRange.containsColor(hsl[0], hsl[1], hsl[2])) { 183 return true; 184 } 185 } 186 return false; 187 } 188 189 /** 190 * Offsets all colors by a delta, clamping values that go beyond what's 191 * supported on the color space. 192 * @param data what you want to fit 193 * @param v how big should be the offset 194 * @param index which index to calculate the delta against 195 * @param min minimum accepted value (clamp) 196 * @param max maximum accepted value (clamp) 197 * @return new shifted palette 198 */ 199 private static float[] fit(float[] data, float v, int index, float min, float max) { 200 float[] fitData = new float[data.length]; 201 float delta = v - data[index]; 202 203 for (int i = 0; i < data.length; i++) { 204 fitData[i] = Utilities.boundToRange(data[i] + delta, min, max); 205 } 206 207 return fitData; 208 } 209 210 /** 211 * Finds the closest color in a palette, given another HSL color 212 * 213 * @param palette where to search 214 * @param h hue 215 * @param s saturation 216 * @param l lightness 217 * @return closest index or -1 if palette is empty. 218 */ 219 private static int bestFit(@NonNull TonalPalette palette, float h, float s, float l) { 220 int minErrorIndex = -1; 221 float minError = Float.POSITIVE_INFINITY; 222 223 for (int i = 0; i < palette.h.length; i++) { 224 float error = 225 FIT_WEIGHT_H * Math.abs(h - palette.h[i]) 226 + FIT_WEIGHT_S * Math.abs(s - palette.s[i]) 227 + FIT_WEIGHT_L * Math.abs(l - palette.l[i]); 228 if (error < minError) { 229 minError = error; 230 minErrorIndex = i; 231 } 232 } 233 234 return minErrorIndex; 235 } 236 237 @Nullable 238 private static TonalPalette findTonalPalette(float h, float s) { 239 // Fallback to a grey palette if the color is too desaturated. 240 // This avoids hue shifts. 241 if (s < 0.05f) { 242 return GREY_PALETTE; 243 } 244 245 TonalPalette best = null; 246 float error = Float.POSITIVE_INFINITY; 247 248 for (int i = 0; i < TONAL_PALETTES.length; i++) { 249 final TonalPalette candidate = TONAL_PALETTES[i]; 250 251 if (h >= candidate.minHue && h <= candidate.maxHue) { 252 best = candidate; 253 break; 254 } 255 256 if (candidate.maxHue > 1.0f && h >= 0.0f && h <= fract(candidate.maxHue)) { 257 best = candidate; 258 break; 259 } 260 261 if (candidate.minHue < 0.0f && h >= fract(candidate.minHue) && h <= 1.0f) { 262 best = candidate; 263 break; 264 } 265 266 if (h <= candidate.minHue && candidate.minHue - h < error) { 267 best = candidate; 268 error = candidate.minHue - h; 269 } else if (h >= candidate.maxHue && h - candidate.maxHue < error) { 270 best = candidate; 271 error = h - candidate.maxHue; 272 } else if (candidate.maxHue > 1.0f && h >= fract(candidate.maxHue) 273 && h - fract(candidate.maxHue) < error) { 274 best = candidate; 275 error = h - fract(candidate.maxHue); 276 } else if (candidate.minHue < 0.0f && h <= fract(candidate.minHue) 277 && fract(candidate.minHue) - h < error) { 278 best = candidate; 279 error = fract(candidate.minHue) - h; 280 } 281 } 282 283 return best; 284 } 285 286 private static float fract(float v) { 287 return v - (float) Math.floor(v); 288 } 289 290 static class TonalPalette { 291 final float[] h; 292 final float[] s; 293 final float[] l; 294 final float minHue; 295 final float maxHue; 296 297 TonalPalette(float[] h, float[] s, float[] l) { 298 if (h.length != s.length || s.length != l.length) { 299 throw new IllegalArgumentException("All arrays should have the same size. h: " 300 + Arrays.toString(h) + " s: " + Arrays.toString(s) + " l: " 301 + Arrays.toString(l)); 302 } 303 304 this.h = h; 305 this.s = s; 306 this.l = l; 307 308 float minHue = Float.POSITIVE_INFINITY; 309 float maxHue = Float.NEGATIVE_INFINITY; 310 311 for (float v : h) { 312 minHue = Math.min(v, minHue); 313 maxHue = Math.max(v, maxHue); 314 } 315 316 this.minHue = minHue; 317 this.maxHue = maxHue; 318 } 319 } 320 321 // Data definition of Material Design tonal palettes 322 // When the sort type is set to TONAL, these palettes are used to find 323 // a best fit. Each palette is defined as 22 HSL colors 324 private static final TonalPalette[] TONAL_PALETTES = { 325 new TonalPalette( 326 new float[] {1f, 1f, 0.991f, 0.991f, 0.9833333333333333f, 0f, 0f, 0f, 327 0.01134380453752181f, 0.015625000000000003f, 0.024193548387096798f, 328 0.027397260273972573f, 0.017543859649122865f}, 329 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f, 330 1f}, 331 new float[] {0.04f, 0.09f, 0.14f, 0.2f, 0.27450980392156865f, 332 0.34901960784313724f, 0.4235294117647059f, 0.5490196078431373f, 333 0.6254901960784314f, 0.6862745098039216f, 0.7568627450980392f, 334 0.8568627450980393f, 0.9254901960784314f} 335 ), 336 new TonalPalette( 337 new float[] {0.638f, 0.638f, 0.6385767790262171f, 0.6301169590643275f, 338 0.6223958333333334f, 0.6151079136690647f, 0.6065400843881856f, 339 0.5986964618249534f, 0.5910746812386157f, 0.5833333333333334f, 340 0.5748031496062993f, 0.5582010582010583f}, 341 new float[] {1f, 1f, 1f, 1f, 0.9014084507042253f, 0.8128654970760234f, 342 0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f, 1f, 1f, 343 1f}, 344 new float[] {0.05f, 0.12f, 0.17450980392156862f, 0.2235294117647059f, 345 0.2784313725490196f, 0.3352941176470588f, 0.388235294117647f, 346 0.44901960784313727f, 0.5392156862745098f, 0.6509803921568628f, 347 0.7509803921568627f, 0.8764705882352941f} 348 ), 349 new TonalPalette( 350 new float[] {0.563f, 0.569f, 0.5666f, 0.5669934640522876f, 0.5748031496062993f, 351 0.5595238095238095f, 0.5473118279569893f, 0.5393258426966292f, 352 0.5315955766192734f, 0.524031007751938f, 0.5154711673699016f, 353 0.508080808080808f, 0.5f}, 354 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f, 355 1f}, 356 new float[] {0.07f, 0.12f, 0.16f, 0.2f, 0.24901960784313726f, 357 0.27450980392156865f, 0.30392156862745096f, 0.34901960784313724f, 358 0.4137254901960784f, 0.47647058823529415f, 0.5352941176470588f, 359 0.6764705882352942f, 0.8f} 360 ), 361 new TonalPalette( 362 new float[] {0.508f, 0.511f, 0.508f, 0.508f, 0.5082304526748972f, 363 0.5069444444444444f, 0.5f, 0.5f, 0.5f, 0.48724954462659376f, 364 0.4800347222222222f, 0.4755134281200632f, 0.4724409448818897f, 365 0.4671052631578947f}, 366 new float[] {1f, 1f, 1f, 1f, 1f, 0.8888888888888887f, 0.9242424242424242f, 1f, 367 1f, 0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f}, 368 new float[] {0.04f, 0.06f, 0.08f, 0.12f, 0.1588235294117647f, 369 0.21176470588235297f, 0.25882352941176473f, 0.3f, 0.34901960784313724f, 370 0.44117647058823534f, 0.5215686274509804f, 0.5862745098039216f, 371 0.7509803921568627f, 0.8509803921568627f} 372 ), 373 new TonalPalette( 374 new float[] {0.333f, 0.333f, 0.333f, 0.3333333333333333f, 0.3333333333333333f, 375 0.34006734006734f, 0.34006734006734f, 0.34006734006734f, 376 0.34259259259259256f, 0.3475783475783476f, 0.34767025089605735f, 377 0.3467741935483871f, 0.3703703703703704f}, 378 new float[] {0.70f, 0.72f, 0.69f, 0.6703296703296703f, 0.728813559322034f, 379 0.5657142857142856f, 0.5076923076923077f, 0.3944223107569721f, 380 0.6206896551724138f, 0.8931297709923666f, 1f, 1f, 1f}, 381 new float[] {0.05f, 0.08f, 0.14f, 0.1784313725490196f, 0.23137254901960785f, 382 0.3431372549019608f, 0.38235294117647056f, 0.49215686274509807f, 383 0.6588235294117647f, 0.7431372549019608f, 0.8176470588235294f, 384 0.8784313725490196f, 0.9294117647058824f} 385 ), 386 new TonalPalette( 387 new float[] {0.161f, 0.163f, 0.163f, 0.162280701754386f, 0.15032679738562088f, 388 0.15879265091863518f, 0.16236559139784948f, 0.17443868739205526f, 389 0.17824074074074076f, 0.18674698795180725f, 0.18692449355432778f, 390 0.1946778711484594f, 0.18604651162790695f}, 391 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, 392 new float[] {0.05f, 0.08f, 0.11f, 0.14901960784313725f, 0.2f, 393 0.24901960784313726f, 0.30392156862745096f, 0.3784313725490196f, 394 0.4235294117647059f, 0.48823529411764705f, 0.6450980392156863f, 395 0.7666666666666666f, 0.8313725490196078f} 396 ), 397 new TonalPalette( 398 new float[] {0.108f, 0.105f, 0.105f, 0.105f, 0.10619469026548674f, 399 0.11924686192468618f, 0.13046448087431692f, 0.14248366013071895f, 400 0.1506024096385542f, 0.16220238095238093f, 0.16666666666666666f, 401 0.16666666666666666f, 0.162280701754386f, 0.15686274509803924f}, 402 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, 403 new float[] {0.17f, 0.22f, 0.28f, 0.35f, 0.44313725490196076f, 404 0.46862745098039216f, 0.47843137254901963f, 0.5f, 0.5117647058823529f, 405 0.5607843137254902f, 0.6509803921568628f, 0.7509803921568627f, 406 0.8509803921568627f, 0.9f} 407 ), 408 new TonalPalette( 409 new float[] {0.036f, 0.036f, 0.036f, 0.036f, 0.03561253561253561f, 410 0.05098039215686275f, 0.07516339869281045f, 0.09477124183006536f, 411 0.1150326797385621f, 0.134640522875817f, 0.14640522875816991f, 412 0.1582397003745319f, 0.15773809523809523f, 0.15359477124183002f}, 413 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, 414 new float[] {0.19f, 0.26f, 0.34f, 0.39f, 0.4588235294117647f, 0.5f, 0.5f, 0.5f, 415 0.5f, 0.5f, 0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f} 416 ), 417 new TonalPalette( 418 new float[] {0.955f, 0.961f, 0.958f, 0.9596491228070175f, 0.9593837535014005f, 419 0.9514767932489452f, 0.943859649122807f, 0.9396825396825397f, 420 0.9395424836601307f, 0.9393939393939394f, 0.9362745098039216f, 421 0.9754098360655739f, 0.9824561403508771f}, 422 new float[] {0.87f, 0.85f, 0.85f, 0.84070796460177f, 0.8206896551724138f, 423 0.7979797979797981f, 0.7661290322580644f, 0.9051724137931036f, 424 1f, 1f, 1f, 1f, 1f}, 425 new float[] {0.06f, 0.11f, 0.16f, 0.22156862745098038f, 0.2843137254901961f, 426 0.388235294117647f, 0.48627450980392156f, 0.5450980392156863f, 427 0.6f, 0.6764705882352942f, 0.8f, 0.8803921568627451f, 428 0.9254901960784314f} 429 ), 430 new TonalPalette( 431 new float[] {0.866f, 0.855f, 0.841025641025641f, 0.8333333333333334f, 432 0.8285256410256411f, 0.821522309711286f, 0.8083333333333333f, 433 0.8046594982078853f, 0.8005822416302766f, 0.7842377260981912f, 434 0.7771084337349398f, 0.7747747747747749f}, 435 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 436 0.737142857142857f, 0.6434108527131781f, 0.46835443037974644f}, 437 new float[] {0.05f, 0.08f, 0.12745098039215685f, 0.15490196078431373f, 438 0.20392156862745098f, 0.24901960784313726f, 0.3137254901960784f, 439 0.36470588235294116f, 0.44901960784313727f, 0.6568627450980392f, 440 0.7470588235294118f, 0.8450980392156863f} 441 ), 442 new TonalPalette( 443 new float[] {0.925f, 0.93f, 0.938f, 0.947f, 0.955952380952381f, 444 0.9681069958847737f, 0.9760479041916167f, 0.9873563218390804f, 0f, 0f, 445 0.009057971014492771f, 0.026748971193415648f, 446 0.041666666666666616f, 0.05303030303030304f}, 447 new float[] {1f, 1f, 1f, 1f, 1f, 0.8350515463917526f, 0.6929460580912863f, 448 0.6387665198237885f, 0.6914893617021276f, 0.7583892617449666f, 449 0.8070175438596495f, 0.9310344827586209f, 1f, 1f}, 450 new float[] {0.10f, 0.13f, 0.17f, 0.2f, 0.27450980392156865f, 451 0.3803921568627451f, 0.4725490196078432f, 0.5549019607843138f, 452 0.6313725490196078f, 0.707843137254902f, 0.7764705882352941f, 453 0.8294117647058823f, 0.9058823529411765f, 0.9568627450980391f} 454 ), 455 new TonalPalette( 456 new float[] {0.733f, 0.736f, 0.744f, 0.7514619883040936f, 0.7679738562091503f, 457 0.7802083333333333f, 0.7844311377245509f, 0.796875f, 458 0.8165618448637316f, 0.8487179487179487f, 0.8582375478927203f, 459 0.8562091503267975f, 0.8666666666666667f}, 460 new float[] {1f, 1f, 1f, 1f, 1f, 0.8163265306122449f, 0.6653386454183268f, 461 0.7547169811320753f, 0.929824561403509f, 0.9558823529411766f, 462 0.9560439560439562f, 1f, 1f}, 463 new float[] {0.07f, 0.12f, 0.17f, 0.2235294117647059f, 0.3f, 464 0.38431372549019605f, 0.492156862745098f, 0.5843137254901961f, 465 0.6647058823529411f, 0.7333333333333334f, 0.8215686274509804f, 0.9f, 466 0.9411764705882353f} 467 ), 468 new TonalPalette( 469 new float[] {0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 470 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 471 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f, 472 0.6666666666666666f, 0.6666666666666666f}, 473 new float[] {0.25f, 0.24590163934426232f, 0.17880794701986752f, 474 0.14606741573033713f, 0.13761467889908252f, 0.14893617021276592f, 475 0.16756756756756758f, 0.20312500000000017f, 0.26086956521739135f, 476 0.29999999999999966f, 0.5000000000000004f}, 477 new float[] {0.18f, 0.2392156862745098f, 0.296078431372549f, 478 0.34901960784313724f, 0.4274509803921569f, 0.5392156862745098f, 479 0.6372549019607843f, 0.7490196078431373f, 0.8196078431372549f, 480 0.8823529411764706f, 0.9372549019607843f} 481 ), 482 new TonalPalette( 483 new float[] {0.938f, 0.944f, 0.952f, 0.961f, 0.9678571428571429f, 484 0.9944812362030905f, 0f, 0f, 485 0.0047348484848484815f, 0.00316455696202532f, 0f, 486 0.9980392156862745f, 0.9814814814814816f, 0.9722222222222221f}, 487 new float[] {1f, 1f, 1f, 1f, 1f, 0.7023255813953488f, 0.6638655462184874f, 488 0.6521739130434782f, 0.7719298245614035f, 0.8315789473684211f, 489 0.6867469879518071f, 0.7264957264957265f, 0.8181818181818182f, 490 0.8181818181818189f}, 491 new float[] {0.08f, 0.13f, 0.18f, 0.23f, 0.27450980392156865f, 492 0.4215686274509804f, 493 0.4666666666666667f, 0.503921568627451f, 0.5529411764705883f, 494 0.6274509803921569f, 0.6745098039215687f, 0.7705882352941176f, 495 0.892156862745098f, 0.9568627450980391f} 496 ), 497 new TonalPalette( 498 new float[] {0.88f, 0.888f, 0.897f, 0.9052287581699346f, 0.9112021857923498f, 499 0.9270152505446624f, 0.9343137254901961f, 0.9391534391534391f, 500 0.9437984496124031f, 0.943661971830986f, 0.9438943894389439f, 501 0.9426229508196722f, 0.9444444444444444f}, 502 new float[] {1f, 1f, 1f, 1f, 0.8133333333333332f, 0.7927461139896375f, 503 0.7798165137614679f, 0.7777777777777779f, 0.8190476190476191f, 504 0.8255813953488372f, 0.8211382113821142f, 0.8133333333333336f, 505 0.8000000000000006f}, 506 new float[] {0.08f, 0.12f, 0.16f, 0.2f, 0.29411764705882354f, 507 0.3784313725490196f, 0.42745098039215684f, 0.4764705882352941f, 508 0.5882352941176471f, 0.6627450980392157f, 0.7588235294117647f, 509 0.8529411764705882f, 0.9411764705882353f} 510 ), 511 new TonalPalette( 512 new float[] {0.669f, 0.680f, 0.6884057971014492f, 0.6974789915966387f, 513 0.7079889807162534f, 0.7154471544715447f, 0.7217741935483872f, 514 0.7274143302180687f, 0.7272727272727273f, 0.7258064516129031f, 515 0.7252252252252251f, 0.7333333333333333f}, 516 new float[] {0.81f, 0.81f, 0.8214285714285715f, 0.6878612716763006f, 517 0.6080402010050251f, 0.5774647887323943f, 0.5391304347826086f, 518 0.46724890829694316f, 0.4680851063829788f, 0.462686567164179f, 519 0.45679012345678977f, 0.4545454545454551f}, 520 new float[] {0.12f, 0.16f, 0.2196078431372549f, 0.33921568627450976f, 521 0.39019607843137255f, 0.4176470588235294f, 0.45098039215686275f, 522 0.5509803921568628f, 0.6313725490196078f, 0.7372549019607844f, 523 0.8411764705882353f, 0.9352941176470588f} 524 ), 525 new TonalPalette( 526 new float[] {0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f, 527 0.6441441441441442f, 0.6432748538011696f, 0.6416666666666667f, 528 0.6402439024390243f, 0.6412429378531074f, 0.6435185185185186f, 529 0.6428571428571429f}, 530 new float[] {0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f, 531 0.5362318840579711f, 0.5f, 0.4424778761061947f, 0.44086021505376327f, 532 0.44360902255639095f, 0.4499999999999997f, 0.4375000000000006f}, 533 new float[] {0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f, 534 0.40588235294117647f, 0.44705882352941173f, 535 0.5568627450980392f, 0.6352941176470588f, 0.7392156862745098f, 536 0.8431372549019608f, 0.9372549019607843f} 537 ), 538 new TonalPalette( 539 new float[] {0.469f, 0.46732026143790845f, 0.4718614718614719f, 540 0.4793650793650794f, 0.48071625344352614f, 0.4829683698296837f, 541 0.484375f, 0.4841269841269842f, 0.48444444444444457f, 542 0.48518518518518516f, 0.4907407407407408f}, 543 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f, 544 0.41899441340782106f, 0.4128440366972478f, 0.4090909090909088f}, 545 new float[] {0.07f, 0.1f, 0.15098039215686274f, 0.20588235294117646f, 546 0.2372549019607843f, 0.26862745098039215f, 0.4f, 0.5078431372549019f, 547 0.6490196078431372f, 0.7862745098039216f, 0.9137254901960784f} 548 ), 549 new TonalPalette( 550 new float[] {0.542f, 0.5444444444444444f, 0.5555555555555556f, 551 0.5555555555555556f, 0.553763440860215f, 0.5526315789473684f, 552 0.5555555555555556f, 0.5555555555555555f, 0.5555555555555556f, 553 0.5512820512820514f, 0.5666666666666667f}, 554 new float[] {0.25f, 0.24590163934426232f, 0.19148936170212766f, 555 0.1791044776119403f, 0.18343195266272191f, 0.18446601941747576f, 556 0.1538461538461539f, 0.15625000000000003f, 0.15328467153284678f, 557 0.15662650602409653f, 0.151515151515151f}, 558 new float[] {0.05f, 0.1196078431372549f, 0.1843137254901961f, 559 0.2627450980392157f, 560 0.33137254901960783f, 0.403921568627451f, 0.5411764705882354f, 561 0.6235294117647059f, 0.7313725490196079f, 0.8372549019607843f, 562 0.9352941176470588f} 563 ), 564 new TonalPalette( 565 new float[] {0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f, 566 0.03947368421052631f, 0.04166666666666668f, 567 0.043650793650793655f, 0.04411764705882352f, 0.04166666666666652f, 568 0.04444444444444459f, 0.05555555555555529f}, 569 new float[] {0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f, 570 0.25675675675675674f, 0.2528735632183908f, 0.17500000000000002f, 571 0.15315315315315312f, 0.15189873417721522f, 572 0.15789473684210534f, 0.15789473684210542f}, 573 new float[] {0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f, 574 0.2901960784313725f, 0.3411764705882353f, 0.47058823529411764f, 575 0.5647058823529412f, 0.6901960784313725f, 0.8137254901960784f, 576 0.9254901960784314f} 577 ), 578 new TonalPalette( 579 new float[] {0.027f, 0.03f, 0.038f, 0.044f, 0.050884955752212385f, 580 0.07254901960784313f, 0.0934640522875817f, 581 0.10457516339869281f, 0.11699346405228758f, 582 0.1255813953488372f, 0.1268939393939394f, 0.12533333333333332f, 583 0.12500000000000003f, 0.12777777777777777f}, 584 new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, 585 new float[] {0.25f, 0.3f, 0.35f, 0.4f, 0.44313725490196076f, 0.5f, 0.5f, 0.5f, 586 0.5f, 0.5784313725490196f, 587 0.6549019607843137f, 0.7549019607843137f, 0.8509803921568627f, 588 0.9411764705882353f} 589 ) 590 }; 591 592 private static final TonalPalette GREY_PALETTE = new TonalPalette( 593 new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, 594 new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f}, 595 new float[]{0.08f, 0.11f, 0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f, 596 0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f, 597 0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f} 598 ); 599 600 @SuppressWarnings("WeakerAccess") 601 static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] { 602 603 // Red 604 new ColorRange( 605 new Range<>(0f, 20f) /* H */, 606 new Range<>(0.7f, 1f) /* S */, 607 new Range<>(0.21f, 0.79f)) /* L */, 608 new ColorRange( 609 new Range<>(0f, 20f), 610 new Range<>(0.3f, 0.7f), 611 new Range<>(0.355f, 0.653f)), 612 613 // Red Orange 614 new ColorRange( 615 new Range<>(20f, 40f), 616 new Range<>(0.7f, 1f), 617 new Range<>(0.28f, 0.643f)), 618 new ColorRange( 619 new Range<>(20f, 40f), 620 new Range<>(0.3f, 0.7f), 621 new Range<>(0.414f, 0.561f)), 622 new ColorRange( 623 new Range<>(20f, 40f), 624 new Range<>(0f, 3f), 625 new Range<>(0.343f, 0.584f)), 626 627 // Orange 628 new ColorRange( 629 new Range<>(40f, 60f), 630 new Range<>(0.7f, 1f), 631 new Range<>(0.173f, 0.349f)), 632 new ColorRange( 633 new Range<>(40f, 60f), 634 new Range<>(0.3f, 0.7f), 635 new Range<>(0.233f, 0.427f)), 636 new ColorRange( 637 new Range<>(40f, 60f), 638 new Range<>(0f, 0.3f), 639 new Range<>(0.231f, 0.484f)), 640 641 // Yellow 60 642 new ColorRange( 643 new Range<>(60f, 80f), 644 new Range<>(0.7f, 1f), 645 new Range<>(0.488f, 0.737f)), 646 new ColorRange( 647 new Range<>(60f, 80f), 648 new Range<>(0.3f, 0.7f), 649 new Range<>(0.673f, 0.837f)), 650 651 // Yellow Green 80 652 new ColorRange( 653 new Range<>(80f, 100f), 654 new Range<>(0.7f, 1f), 655 new Range<>(0.469f, 0.61f)), 656 657 // Yellow green 100 658 new ColorRange( 659 new Range<>(100f, 120f), 660 new Range<>(0.7f, 1f), 661 new Range<>(0.388f, 0.612f)), 662 new ColorRange( 663 new Range<>(100f, 120f), 664 new Range<>(0.3f, 0.7f), 665 new Range<>(0.424f, 0.541f)), 666 667 // Green 668 new ColorRange( 669 new Range<>(120f, 140f), 670 new Range<>(0.7f, 1f), 671 new Range<>(0.375f, 0.52f)), 672 new ColorRange( 673 new Range<>(120f, 140f), 674 new Range<>(0.3f, 0.7f), 675 new Range<>(0.435f, 0.524f)), 676 677 // Green Blue 140 678 new ColorRange( 679 new Range<>(140f, 160f), 680 new Range<>(0.7f, 1f), 681 new Range<>(0.496f, 0.641f)), 682 683 // Seafoam 684 new ColorRange( 685 new Range<>(160f, 180f), 686 new Range<>(0.7f, 1f), 687 new Range<>(0.496f, 0.567f)), 688 689 // Cyan 690 new ColorRange( 691 new Range<>(180f, 200f), 692 new Range<>(0.7f, 1f), 693 new Range<>(0.52f, 0.729f)), 694 695 // Blue 696 new ColorRange( 697 new Range<>(220f, 240f), 698 new Range<>(0.7f, 1f), 699 new Range<>(0.396f, 0.571f)), 700 new ColorRange( 701 new Range<>(220f, 240f), 702 new Range<>(0.3f, 0.7f), 703 new Range<>(0.425f, 0.551f)), 704 705 // Blue Purple 240 706 new ColorRange( 707 new Range<>(240f, 260f), 708 new Range<>(0.7f, 1f), 709 new Range<>(0.418f, 0.639f)), 710 new ColorRange( 711 new Range<>(220f, 240f), 712 new Range<>(0.3f, 0.7f), 713 new Range<>(0.441f, 0.576f)), 714 715 // Blue Purple 260 716 new ColorRange( 717 new Range<>(260f, 280f), 718 new Range<>(0.3f, 1f), // Bigger range 719 new Range<>(0.461f, 0.553f)), 720 721 // Fuchsia 722 new ColorRange( 723 new Range<>(300f, 320f), 724 new Range<>(0.7f, 1f), 725 new Range<>(0.484f, 0.588f)), 726 new ColorRange( 727 new Range<>(300f, 320f), 728 new Range<>(0.3f, 0.7f), 729 new Range<>(0.48f, 0.592f)), 730 731 // Pink 732 new ColorRange( 733 new Range<>(320f, 340f), 734 new Range<>(0.7f, 1f), 735 new Range<>(0.466f, 0.629f)), 736 737 // Soft red 738 new ColorRange( 739 new Range<>(340f, 360f), 740 new Range<>(0.7f, 1f), 741 new Range<>(0.437f, 0.596f)) 742 }; 743 744 /** 745 * Representation of an HSL color range. 746 * <ul> 747 * <li>hsl[0] is Hue [0 .. 360)</li> 748 * <li>hsl[1] is Saturation [0...1]</li> 749 * <li>hsl[2] is Lightness [0...1]</li> 750 * </ul> 751 */ 752 static class ColorRange { 753 private Range<Float> mHue; 754 private Range<Float> mSaturation; 755 private Range<Float> mLightness; 756 757 ColorRange(Range<Float> hue, Range<Float> saturation, Range<Float> lightness) { 758 mHue = hue; 759 mSaturation = saturation; 760 mLightness = lightness; 761 } 762 763 boolean containsColor(float h, float s, float l) { 764 if (!mHue.contains(h)) { 765 return false; 766 } else if (!mSaturation.contains(s)) { 767 return false; 768 } else if (!mLightness.contains(l)) { 769 return false; 770 } 771 return true; 772 } 773 774 float[] getCenter() { 775 return new float[] { 776 mHue.getLower() + (mHue.getUpper() - mHue.getLower()) / 2f, 777 mSaturation.getLower() + (mSaturation.getUpper() - mSaturation.getLower()) / 2f, 778 mLightness.getLower() + (mLightness.getUpper() - mLightness.getLower()) / 2f 779 }; 780 } 781 782 @Override 783 public String toString() { 784 return String.format("H: %s, S: %s, L %s", mHue, mSaturation, mLightness); 785 } 786 } 787 788 private static List<Integer> getMainColors(WallpaperColorsCompat wallpaperColors) { 789 LinkedList<Integer> colors = new LinkedList<>(); 790 if (wallpaperColors.getPrimaryColor() != 0) { 791 colors.add(wallpaperColors.getPrimaryColor()); 792 } 793 if (wallpaperColors.getSecondaryColor() != 0) { 794 colors.add(wallpaperColors.getSecondaryColor()); 795 } 796 if (wallpaperColors.getTertiaryColor() != 0) { 797 colors.add(wallpaperColors.getTertiaryColor()); 798 } 799 return colors; 800 } 801 } 802