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 android.app; 18 19 import android.annotation.FloatRange; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.graphics.Bitmap; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.os.SystemProperties; 31 import android.os.Trace; 32 import android.util.Log; 33 import android.util.MathUtils; 34 import android.util.Size; 35 36 import com.android.internal.graphics.ColorUtils; 37 import com.android.internal.graphics.cam.Cam; 38 import com.android.internal.graphics.palette.CelebiQuantizer; 39 import com.android.internal.graphics.palette.Palette; 40 import com.android.internal.graphics.palette.VariationalKMeansQuantizer; 41 import com.android.internal.util.ContrastColorUtil; 42 43 import java.io.FileOutputStream; 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.Set; 53 54 /** 55 * Provides information about the colors of a wallpaper. 56 * <p> 57 * Exposes the 3 most visually representative colors of a wallpaper. Can be either 58 * {@link WallpaperColors#getPrimaryColor()}, {@link WallpaperColors#getSecondaryColor()} 59 * or {@link WallpaperColors#getTertiaryColor()}. 60 */ 61 public final class WallpaperColors implements Parcelable { 62 /** 63 * @hide 64 */ 65 @IntDef(prefix = "HINT_", value = {HINT_SUPPORTS_DARK_TEXT, HINT_SUPPORTS_DARK_THEME}, 66 flag = true) 67 @Retention(RetentionPolicy.SOURCE) 68 public @interface ColorsHints {} 69 70 private static final boolean DEBUG_DARK_PIXELS = false; 71 72 /** 73 * Specifies that dark text is preferred over the current wallpaper for best presentation. 74 * <p> 75 * eg. A launcher may set its text color to black if this flag is specified. 76 */ 77 public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0; 78 79 /** 80 * Specifies that dark theme is preferred over the current wallpaper for best presentation. 81 * <p> 82 * eg. A launcher may set its drawer color to black if this flag is specified. 83 */ 84 public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1; 85 86 /** 87 * Specifies that this object was generated by extracting colors from a bitmap. 88 * @hide 89 */ 90 public static final int HINT_FROM_BITMAP = 1 << 2; 91 92 // Maximum size that a bitmap can have to keep our calculations valid 93 private static final int MAX_BITMAP_SIZE = 112; 94 95 // Even though we have a maximum size, we'll mainly match bitmap sizes 96 // using the area instead. This way our comparisons are aspect ratio independent. 97 private static final int MAX_WALLPAPER_EXTRACTION_AREA = MAX_BITMAP_SIZE * MAX_BITMAP_SIZE; 98 99 // When extracting the main colors, only consider colors 100 // present in at least MIN_COLOR_OCCURRENCE of the image 101 private static final float MIN_COLOR_OCCURRENCE = 0.05f; 102 103 // Decides when dark theme is optimal for this wallpaper 104 private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f; 105 // Minimum mean luminosity that an image needs to have to support dark text 106 private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = SystemProperties.getInt( 107 "persist.wallpapercolors.threshold", 70) / 100f; 108 // We also check if the image has dark pixels in it, 109 // to avoid bright images with some dark spots. 110 private static final float DARK_PIXEL_CONTRAST = 5.5f; 111 private static final float MAX_DARK_AREA = SystemProperties.getInt( 112 "persist.wallpapercolors.max_dark_area", 5) / 100f; 113 114 private final List<Color> mMainColors; 115 private final Map<Integer, Integer> mAllColors; 116 private int mColorHints; 117 WallpaperColors(Parcel parcel)118 public WallpaperColors(Parcel parcel) { 119 mMainColors = new ArrayList<>(); 120 mAllColors = new HashMap<>(); 121 int count = parcel.readInt(); 122 for (int i = 0; i < count; i++) { 123 final int colorInt = parcel.readInt(); 124 Color color = Color.valueOf(colorInt); 125 mMainColors.add(color); 126 } 127 count = parcel.readInt(); 128 for (int i = 0; i < count; i++) { 129 final int colorInt = parcel.readInt(); 130 final int population = parcel.readInt(); 131 mAllColors.put(colorInt, population); 132 } 133 mColorHints = parcel.readInt(); 134 } 135 136 /** 137 * Constructs {@link WallpaperColors} from a drawable. 138 * <p> 139 * Main colors will be extracted from the drawable. 140 * 141 * @param drawable Source where to extract from. 142 */ fromDrawable(Drawable drawable)143 public static WallpaperColors fromDrawable(Drawable drawable) { 144 if (drawable == null) { 145 throw new IllegalArgumentException("Drawable cannot be null"); 146 } 147 148 Trace.beginSection("WallpaperColors#fromDrawable"); 149 Rect initialBounds = drawable.copyBounds(); 150 int width = drawable.getIntrinsicWidth(); 151 int height = drawable.getIntrinsicHeight(); 152 153 // Some drawables do not have intrinsic dimensions 154 if (width <= 0 || height <= 0) { 155 width = MAX_BITMAP_SIZE; 156 height = MAX_BITMAP_SIZE; 157 } 158 159 Size optimalSize = calculateOptimalSize(width, height); 160 Bitmap bitmap = Bitmap.createBitmap(optimalSize.getWidth(), optimalSize.getHeight(), 161 Bitmap.Config.ARGB_8888); 162 final Canvas bmpCanvas = new Canvas(bitmap); 163 drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); 164 drawable.draw(bmpCanvas); 165 166 final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap); 167 bitmap.recycle(); 168 169 drawable.setBounds(initialBounds); 170 Trace.endSection(); 171 return colors; 172 } 173 174 /** 175 * Constructs {@link WallpaperColors} from a bitmap. 176 * <p> 177 * Main colors will be extracted from the bitmap. 178 * 179 * @param bitmap Source where to extract from. 180 */ fromBitmap(@onNull Bitmap bitmap)181 public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) { 182 if (bitmap == null) { 183 throw new IllegalArgumentException("Bitmap can't be null"); 184 } 185 return fromBitmap(bitmap, 0f /* dimAmount */); 186 } 187 188 /** 189 * Constructs {@link WallpaperColors} from a bitmap with dimming applied. 190 * <p> 191 * Main colors will be extracted from the bitmap with dimming taken into account when 192 * calculating dark hints. 193 * 194 * @param bitmap Source where to extract from. 195 * @param dimAmount Wallpaper dim amount 196 * @hide 197 */ fromBitmap(@onNull Bitmap bitmap, @FloatRange (from = 0f, to = 1f) float dimAmount)198 public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap, 199 @FloatRange (from = 0f, to = 1f) float dimAmount) { 200 Objects.requireNonNull(bitmap, "Bitmap can't be null"); 201 Trace.beginSection("WallpaperColors#fromBitmap"); 202 final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); 203 boolean shouldRecycle = false; 204 if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) { 205 shouldRecycle = true; 206 Size optimalSize = calculateOptimalSize(bitmap.getWidth(), bitmap.getHeight()); 207 bitmap = Bitmap.createScaledBitmap(bitmap, optimalSize.getWidth(), 208 optimalSize.getHeight(), false /* filter */); 209 } 210 211 final Palette palette; 212 if (ActivityManager.isLowRamDeviceStatic()) { 213 palette = Palette 214 .from(bitmap, new VariationalKMeansQuantizer()) 215 .maximumColorCount(5) 216 .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) 217 .generate(); 218 } else { 219 // in any case, always use between 5 and 128 clusters 220 int minClusters = 5; 221 int maxClusters = 128; 222 223 // if the bitmap is very small, use bitmapArea/16 clusters instead of 128 224 int minPixelsPerCluster = 16; 225 int numberOfColors = Math.max(minClusters, 226 Math.min(maxClusters, bitmapArea / minPixelsPerCluster)); 227 palette = Palette 228 .from(bitmap, new CelebiQuantizer()) 229 .maximumColorCount(numberOfColors) 230 .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) 231 .generate(); 232 } 233 // Remove insignificant colors and sort swatches by population 234 final ArrayList<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches()); 235 swatches.sort((a, b) -> b.getPopulation() - a.getPopulation()); 236 237 final int swatchesSize = swatches.size(); 238 239 final Map<Integer, Integer> populationByColor = new HashMap<>(); 240 for (int i = 0; i < swatchesSize; i++) { 241 Palette.Swatch swatch = swatches.get(i); 242 int colorInt = swatch.getInt(); 243 populationByColor.put(colorInt, swatch.getPopulation()); 244 245 } 246 247 int hints = calculateDarkHints(bitmap, dimAmount); 248 249 if (shouldRecycle) { 250 bitmap.recycle(); 251 } 252 253 Trace.endSection(); 254 return new WallpaperColors(populationByColor, HINT_FROM_BITMAP | hints); 255 } 256 257 /** 258 * Constructs a new object from three colors. 259 * 260 * @param primaryColor Primary color. 261 * @param secondaryColor Secondary color. 262 * @param tertiaryColor Tertiary color. 263 * @see WallpaperColors#fromBitmap(Bitmap) 264 * @see WallpaperColors#fromDrawable(Drawable) 265 */ WallpaperColors(@onNull Color primaryColor, @Nullable Color secondaryColor, @Nullable Color tertiaryColor)266 public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, 267 @Nullable Color tertiaryColor) { 268 this(primaryColor, secondaryColor, tertiaryColor, 0); 269 270 // Calculate dark theme support based on primary color. 271 final float[] tmpHsl = new float[3]; 272 ColorUtils.colorToHSL(primaryColor.toArgb(), tmpHsl); 273 final float luminance = tmpHsl[2]; 274 if (luminance < DARK_THEME_MEAN_LUMINANCE) { 275 mColorHints |= HINT_SUPPORTS_DARK_THEME; 276 } 277 } 278 279 /** 280 * Constructs a new object from three colors, where hints can be specified. 281 * 282 * @param primaryColor Primary color. 283 * @param secondaryColor Secondary color. 284 * @param tertiaryColor Tertiary color. 285 * @param colorHints A combination of color hints. 286 * @see WallpaperColors#fromBitmap(Bitmap) 287 * @see WallpaperColors#fromDrawable(Drawable) 288 */ WallpaperColors(@onNull Color primaryColor, @Nullable Color secondaryColor, @Nullable Color tertiaryColor, @ColorsHints int colorHints)289 public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, 290 @Nullable Color tertiaryColor, @ColorsHints int colorHints) { 291 292 if (primaryColor == null) { 293 throw new IllegalArgumentException("Primary color should never be null."); 294 } 295 296 mMainColors = new ArrayList<>(3); 297 mAllColors = new HashMap<>(); 298 299 mMainColors.add(primaryColor); 300 mAllColors.put(primaryColor.toArgb(), 0); 301 if (secondaryColor != null) { 302 mMainColors.add(secondaryColor); 303 mAllColors.put(secondaryColor.toArgb(), 0); 304 } 305 if (tertiaryColor != null) { 306 if (secondaryColor == null) { 307 throw new IllegalArgumentException("tertiaryColor can't be specified when " 308 + "secondaryColor is null"); 309 } 310 mMainColors.add(tertiaryColor); 311 mAllColors.put(tertiaryColor.toArgb(), 0); 312 } 313 mColorHints = colorHints; 314 } 315 316 /** 317 * Constructs a new object from a set of colors, where hints can be specified. 318 * 319 * @param colorToPopulation Map with keys of colors, and value representing the number of 320 * occurrences of color in the wallpaper. 321 * @param colorHints A combination of color hints. 322 * @hide 323 * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT 324 * @see WallpaperColors#fromBitmap(Bitmap) 325 * @see WallpaperColors#fromDrawable(Drawable) 326 */ WallpaperColors(@onNull Map<Integer, Integer> colorToPopulation, @ColorsHints int colorHints)327 public WallpaperColors(@NonNull Map<Integer, Integer> colorToPopulation, 328 @ColorsHints int colorHints) { 329 mAllColors = colorToPopulation; 330 331 final Map<Integer, Cam> colorToCam = new HashMap<>(); 332 for (int color : colorToPopulation.keySet()) { 333 colorToCam.put(color, Cam.fromInt(color)); 334 } 335 final double[] hueProportions = hueProportions(colorToCam, colorToPopulation); 336 final Map<Integer, Double> colorToHueProportion = colorToHueProportion( 337 colorToPopulation.keySet(), colorToCam, hueProportions); 338 339 final Map<Integer, Double> colorToScore = new HashMap<>(); 340 for (Map.Entry<Integer, Double> mapEntry : colorToHueProportion.entrySet()) { 341 int color = mapEntry.getKey(); 342 double proportion = mapEntry.getValue(); 343 double score = score(colorToCam.get(color), proportion); 344 colorToScore.put(color, score); 345 } 346 ArrayList<Map.Entry<Integer, Double>> mapEntries = new ArrayList(colorToScore.entrySet()); 347 mapEntries.sort((a, b) -> b.getValue().compareTo(a.getValue())); 348 349 List<Integer> colorsByScoreDescending = new ArrayList<>(); 350 for (Map.Entry<Integer, Double> colorToScoreEntry : mapEntries) { 351 colorsByScoreDescending.add(colorToScoreEntry.getKey()); 352 } 353 354 List<Integer> mainColorInts = new ArrayList<>(); 355 findSeedColorLoop: 356 for (int color : colorsByScoreDescending) { 357 Cam cam = colorToCam.get(color); 358 for (int otherColor : mainColorInts) { 359 Cam otherCam = colorToCam.get(otherColor); 360 if (hueDiff(cam, otherCam) < 15) { 361 continue findSeedColorLoop; 362 } 363 } 364 mainColorInts.add(color); 365 } 366 List<Color> mainColors = new ArrayList<>(); 367 for (int colorInt : mainColorInts) { 368 mainColors.add(Color.valueOf(colorInt)); 369 } 370 mMainColors = mainColors; 371 mColorHints = colorHints; 372 } 373 hueDiff(Cam a, Cam b)374 private static double hueDiff(Cam a, Cam b) { 375 return (180f - Math.abs(Math.abs(a.getHue() - b.getHue()) - 180f)); 376 } 377 score(Cam cam, double proportion)378 private static double score(Cam cam, double proportion) { 379 return cam.getChroma() + (proportion * 100); 380 } 381 colorToHueProportion(Set<Integer> colors, Map<Integer, Cam> colorToCam, double[] hueProportions)382 private static Map<Integer, Double> colorToHueProportion(Set<Integer> colors, 383 Map<Integer, Cam> colorToCam, double[] hueProportions) { 384 Map<Integer, Double> colorToHueProportion = new HashMap<>(); 385 for (int color : colors) { 386 final int hue = wrapDegrees(Math.round(colorToCam.get(color).getHue())); 387 double proportion = 0.0; 388 for (int i = hue - 15; i < hue + 15; i++) { 389 proportion += hueProportions[wrapDegrees(i)]; 390 } 391 colorToHueProportion.put(color, proportion); 392 } 393 return colorToHueProportion; 394 } 395 wrapDegrees(int degrees)396 private static int wrapDegrees(int degrees) { 397 if (degrees < 0) { 398 return (degrees % 360) + 360; 399 } else if (degrees >= 360) { 400 return degrees % 360; 401 } else { 402 return degrees; 403 } 404 } 405 hueProportions(@onNull Map<Integer, Cam> colorToCam, Map<Integer, Integer> colorToPopulation)406 private static double[] hueProportions(@NonNull Map<Integer, Cam> colorToCam, 407 Map<Integer, Integer> colorToPopulation) { 408 final double[] proportions = new double[360]; 409 410 double totalPopulation = 0; 411 for (Map.Entry<Integer, Integer> entry : colorToPopulation.entrySet()) { 412 totalPopulation += entry.getValue(); 413 } 414 415 for (Map.Entry<Integer, Integer> entry : colorToPopulation.entrySet()) { 416 final int color = (int) entry.getKey(); 417 final int population = colorToPopulation.get(color); 418 final Cam cam = colorToCam.get(color); 419 final int hue = wrapDegrees(Math.round(cam.getHue())); 420 proportions[hue] = proportions[hue] + ((double) population / totalPopulation); 421 } 422 423 return proportions; 424 } 425 426 public static final @android.annotation.NonNull Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() { 427 @Override 428 public WallpaperColors createFromParcel(Parcel in) { 429 return new WallpaperColors(in); 430 } 431 432 @Override 433 public WallpaperColors[] newArray(int size) { 434 return new WallpaperColors[size]; 435 } 436 }; 437 438 @Override describeContents()439 public int describeContents() { 440 return 0; 441 } 442 443 @Override writeToParcel(Parcel dest, int flags)444 public void writeToParcel(Parcel dest, int flags) { 445 List<Color> mainColors = getMainColors(); 446 int count = mainColors.size(); 447 dest.writeInt(count); 448 for (int i = 0; i < count; i++) { 449 Color color = mainColors.get(i); 450 dest.writeInt(color.toArgb()); 451 } 452 count = mAllColors.size(); 453 dest.writeInt(count); 454 for (Map.Entry<Integer, Integer> colorEntry : mAllColors.entrySet()) { 455 if (colorEntry.getKey() != null) { 456 dest.writeInt(colorEntry.getKey()); 457 Integer population = colorEntry.getValue(); 458 int populationInt = (population != null) ? population : 0; 459 dest.writeInt(populationInt); 460 } 461 } 462 dest.writeInt(mColorHints); 463 } 464 465 /** 466 * Gets the most visually representative color of the wallpaper. 467 * "Visually representative" means easily noticeable in the image, 468 * probably happening at high frequency. 469 *fromBitmap 470 * @return A color. 471 */ getPrimaryColor()472 public @NonNull Color getPrimaryColor() { 473 return mMainColors.get(0); 474 } 475 476 /** 477 * Gets the second most preeminent color of the wallpaper. Can be null. 478 * 479 * @return A color, may be null. 480 */ getSecondaryColor()481 public @Nullable Color getSecondaryColor() { 482 return mMainColors.size() < 2 ? null : mMainColors.get(1); 483 } 484 485 /** 486 * Gets the third most preeminent color of the wallpaper. Can be null. 487 * 488 * @return A color, may be null. 489 */ getTertiaryColor()490 public @Nullable Color getTertiaryColor() { 491 return mMainColors.size() < 3 ? null : mMainColors.get(2); 492 } 493 494 /** 495 * List of most preeminent colors, sorted by importance. 496 * 497 * @return List of colors. 498 * @hide 499 */ getMainColors()500 public @NonNull List<Color> getMainColors() { 501 return Collections.unmodifiableList(mMainColors); 502 } 503 504 /** 505 * Map of all colors. Key is rgb integer, value is importance of color. 506 * 507 * @return List of colors. 508 * @hide 509 */ getAllColors()510 public @NonNull Map<Integer, Integer> getAllColors() { 511 return Collections.unmodifiableMap(mAllColors); 512 } 513 514 515 @Override equals(@ullable Object o)516 public boolean equals(@Nullable Object o) { 517 if (o == null || getClass() != o.getClass()) { 518 return false; 519 } 520 521 WallpaperColors other = (WallpaperColors) o; 522 return mMainColors.equals(other.mMainColors) 523 && mAllColors.equals(other.mAllColors) 524 && mColorHints == other.mColorHints; 525 } 526 527 @Override hashCode()528 public int hashCode() { 529 return (31 * mMainColors.hashCode() * mAllColors.hashCode()) + mColorHints; 530 } 531 532 /** 533 * Returns the color hints for this instance. 534 * @return The color hints. 535 */ getColorHints()536 public @ColorsHints int getColorHints() { 537 return mColorHints; 538 } 539 540 /** 541 * Checks if image is bright and clean enough to support light text. 542 * 543 * @param source What to read. 544 * @param dimAmount How much wallpaper dim amount was applied. 545 * @return Whether image supports dark text or not. 546 */ calculateDarkHints(Bitmap source, float dimAmount)547 private static int calculateDarkHints(Bitmap source, float dimAmount) { 548 if (source == null) { 549 return 0; 550 } 551 552 Trace.beginSection("WallpaperColors#calculateDarkHints"); 553 dimAmount = MathUtils.saturate(dimAmount); 554 int[] pixels = new int[source.getWidth() * source.getHeight()]; 555 double totalLuminance = 0; 556 final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA); 557 int darkPixels = 0; 558 source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */, 559 source.getWidth(), source.getHeight()); 560 561 // Create a new black layer with dimAmount as the alpha to be accounted for when computing 562 // the luminance. 563 int dimmingLayerAlpha = (int) (255 * dimAmount); 564 int blackTransparent = ColorUtils.setAlphaComponent(Color.BLACK, dimmingLayerAlpha); 565 566 // This bitmap was already resized to fit the maximum allowed area. 567 // Let's just loop through the pixels, no sweat! 568 float[] tmpHsl = new float[3]; 569 for (int i = 0; i < pixels.length; i++) { 570 int pixelColor = pixels[i]; 571 ColorUtils.colorToHSL(pixelColor, tmpHsl); 572 final int alpha = Color.alpha(pixelColor); 573 574 // Apply composite colors where the foreground is a black layer with an alpha value of 575 // the dim amount and the background is the wallpaper pixel color. 576 int compositeColors = ColorUtils.compositeColors(blackTransparent, pixelColor); 577 578 // Calculate the adjusted luminance of the dimmed wallpaper pixel color. 579 double adjustedLuminance = ColorUtils.calculateLuminance(compositeColors); 580 581 // Make sure we don't have a dark pixel mass that will 582 // make text illegible. 583 final boolean satisfiesTextContrast = ContrastColorUtil 584 .calculateContrast(pixelColor, Color.BLACK) > DARK_PIXEL_CONTRAST; 585 if (!satisfiesTextContrast && alpha != 0) { 586 darkPixels++; 587 if (DEBUG_DARK_PIXELS) { 588 pixels[i] = Color.RED; 589 } 590 } 591 totalLuminance += adjustedLuminance; 592 } 593 594 int hints = 0; 595 double meanLuminance = totalLuminance / pixels.length; 596 if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels <= maxDarkPixels) { 597 hints |= HINT_SUPPORTS_DARK_TEXT; 598 } 599 if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) { 600 hints |= HINT_SUPPORTS_DARK_THEME; 601 } 602 603 if (DEBUG_DARK_PIXELS) { 604 try (FileOutputStream out = new FileOutputStream("/data/pixels.png")) { 605 source.setPixels(pixels, 0, source.getWidth(), 0, 0, source.getWidth(), 606 source.getHeight()); 607 source.compress(Bitmap.CompressFormat.PNG, 100, out); 608 } catch (Exception e) { 609 e.printStackTrace(); 610 } 611 Log.d("WallpaperColors", "l: " + meanLuminance + ", d: " + darkPixels + 612 " maxD: " + maxDarkPixels + " numPixels: " + pixels.length); 613 } 614 615 Trace.endSection(); 616 return hints; 617 } 618 calculateOptimalSize(int width, int height)619 private static Size calculateOptimalSize(int width, int height) { 620 // Calculate how big the bitmap needs to be. 621 // This avoids unnecessary processing and allocation inside Palette. 622 final int requestedArea = width * height; 623 double scale = 1; 624 if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { 625 scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea); 626 } 627 int newWidth = (int) (width * scale); 628 int newHeight = (int) (height * scale); 629 // Dealing with edge cases of the drawable being too wide or too tall. 630 // Width or height would end up being 0, in this case we'll set it to 1. 631 if (newWidth == 0) { 632 newWidth = 1; 633 } 634 if (newHeight == 0) { 635 newHeight = 1; 636 } 637 638 return new Size(newWidth, newHeight); 639 } 640 641 @Override toString()642 public String toString() { 643 final StringBuilder colors = new StringBuilder(); 644 for (int i = 0; i < mMainColors.size(); i++) { 645 colors.append(Integer.toHexString(mMainColors.get(i).toArgb())).append(" "); 646 } 647 return "[WallpaperColors: " + colors.toString() + "h: " + mColorHints + "]"; 648 } 649 } 650