Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: ui/gfx/color_analysis.cc

Issue 2943333003: Extracting more than one wallpaper prominent color (Closed)
Patch Set: UMA Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/gfx/color_analysis.h" 5 #include "ui/gfx/color_analysis.h"
6 6
7 #include <limits.h> 7 #include <limits.h>
8 #include <stdint.h> 8 #include <stdint.h>
9 9
10 #include <algorithm> 10 #include <algorithm>
(...skipping 338 matching lines...) Expand 10 before | Expand all | Expand 10 after
349 return !(hsl.h >= 0.028f && hsl.h <= 0.10f && hsl.s <= 0.82f); 349 return !(hsl.h >= 0.028f && hsl.h <= 0.10f && hsl.s <= 0.82f);
350 } 350 }
351 351
352 // This algorithm is a port of Android's Palette API. Compare to package 352 // This algorithm is a port of Android's Palette API. Compare to package
353 // android.support.v7.graphics and see that code for additional high-level 353 // android.support.v7.graphics and see that code for additional high-level
354 // explanation of this algorithm. There are some minor differences: 354 // explanation of this algorithm. There are some minor differences:
355 // * This code doesn't exclude the same color from being used for 355 // * This code doesn't exclude the same color from being used for
356 // different color profiles. 356 // different color profiles.
357 // * This code doesn't try to heuristically derive missing colors from 357 // * This code doesn't try to heuristically derive missing colors from
358 // existing colors. 358 // existing colors.
359 SkColor CalculateProminentColor(const SkBitmap& bitmap, 359 std::vector<SkColor> CalculateProminentColors(
360 const HSL& lower_bound, 360 const SkBitmap& bitmap,
361 const HSL& upper_bound, 361 const std::vector<HSL>& lower_bounds,
362 const HSL& goal) { 362 const std::vector<HSL>& upper_bounds,
363 const std::vector<HSL>& goals) {
363 DCHECK(!bitmap.empty()); 364 DCHECK(!bitmap.empty());
364 DCHECK(!bitmap.isNull()); 365 DCHECK(!bitmap.isNull());
365 366
366 const uint32_t* pixels = static_cast<uint32_t*>(bitmap.getPixels()); 367 const uint32_t* pixels = static_cast<uint32_t*>(bitmap.getPixels());
367 const int pixel_count = bitmap.width() * bitmap.height(); 368 const int pixel_count = bitmap.width() * bitmap.height();
368 369
369 // For better performance, only consider at most 10k pixels (evenly 370 // For better performance, only consider at most 10k pixels (evenly
370 // distributed throughout the image). This has a very minor impact on the 371 // distributed throughout the image). This has a very minor impact on the
371 // outcome but improves runtime substantially for large images. 10,007 is a 372 // outcome but improves runtime substantially for large images. 10,007 is a
372 // prime number to reduce the chance of picking an unrepresentative sample. 373 // prime number to reduce the chance of picking an unrepresentative sample.
(...skipping 15 matching lines...) Expand all
388 // Now throw out some uninteresting colors. 389 // Now throw out some uninteresting colors.
389 std::vector<SkColor> interesting_colors; 390 std::vector<SkColor> interesting_colors;
390 interesting_colors.reserve(color_counts.size()); 391 interesting_colors.reserve(color_counts.size());
391 for (auto color_count : color_counts) { 392 for (auto color_count : color_counts) {
392 SkColor color = color_count.first; 393 SkColor color = color_count.first;
393 if (IsInterestingColor(color)) 394 if (IsInterestingColor(color))
394 interesting_colors.push_back(color); 395 interesting_colors.push_back(color);
395 } 396 }
396 397
397 if (interesting_colors.empty()) 398 if (interesting_colors.empty())
398 return SK_ColorTRANSPARENT; 399 return std::vector<SkColor>(goals.size(), SK_ColorTRANSPARENT);
Evan Stade 2017/06/20 23:07:53 nit: maybe put the best_colors initialization abov
Qiang(Joe) Xu 2017/06/21 01:10:52 Done.
399 400
400 // Group the colors into "boxes" and repeatedly split the most voluminous box. 401 // Group the colors into "boxes" and repeatedly split the most voluminous box.
401 // We stop the process when a box can no longer be split (there's only one 402 // We stop the process when a box can no longer be split (there's only one
402 // color in it) or when the number of color boxes reaches 12. As per the 403 // color in it) or when the number of color boxes reaches 12. As per the
403 // Android docs, 404 // Android docs,
404 // 405 //
405 // For landscapes, good values are in the range 12-16. For images which 406 // For landscapes, good values are in the range 12-16. For images which
406 // are largely made up of people's faces then this value should be increased 407 // are largely made up of people's faces then this value should be increased
407 // to 24-32. 408 // to 24-32.
408 const int kMaxColors = 12; 409 const int kMaxColors = 12;
(...skipping 14 matching lines...) Expand all
423 // Now extract a single color to represent each box. This is the average color 424 // Now extract a single color to represent each box. This is the average color
424 // in the box, weighted by the frequency of that color in the source image. 425 // in the box, weighted by the frequency of that color in the source image.
425 std::vector<WeightedColor> box_colors; 426 std::vector<WeightedColor> box_colors;
426 uint64_t max_weight = 0; 427 uint64_t max_weight = 0;
427 while (!boxes.empty()) { 428 while (!boxes.empty()) {
428 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); 429 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts));
429 boxes.pop(); 430 boxes.pop();
430 max_weight = std::max(max_weight, box_colors.back().weight); 431 max_weight = std::max(max_weight, box_colors.back().weight);
431 } 432 }
432 433
433 // Given these box average colors, find the best one for the desired color 434 // Given these box average colors, find the best one for each desired color
434 // profile. "Best" in this case means the color which fits in the provided 435 // profile. "Best" in this case means the color which fits in the provided
435 // bounds and comes closest to |goal|. It's possible that no color will fit in 436 // bounds and comes closest to |goal|. It's possible that no color will fit in
436 // the provided bounds, in which case we'll return an empty color. 437 // the provided bounds, in which case we'll return an empty color.
437 double best_suitability = 0; 438 std::vector<SkColor> best_colors(goals.size(), SK_ColorTRANSPARENT);
438 SkColor best_color = SK_ColorTRANSPARENT; 439 for (size_t i = 0; i < goals.size(); ++i) {
Evan Stade 2017/06/20 23:07:53 nit: I think this loop would be cleaner (and you'd
Qiang(Joe) Xu 2017/06/21 01:10:52 Done.
439 for (const auto& box_color : box_colors) { 440 double best_suitability = 0;
440 HSL hsl; 441 for (const auto& box_color : box_colors) {
441 SkColorToHSL(box_color.color, &hsl); 442 HSL hsl;
442 if (!IsWithinHSLRange(hsl, lower_bound, upper_bound)) 443 SkColorToHSL(box_color.color, &hsl);
443 continue; 444 if (!IsWithinHSLRange(hsl, lower_bounds[i], upper_bounds[i]))
445 continue;
444 446
445 double suitability = 447 double suitability =
446 (1 - std::abs(hsl.s - goal.s)) * 3 + 448 (1 - std::abs(hsl.s - goals[i].s)) * 3 +
447 (1 - std::abs(hsl.l - goal.l)) * 6.5 + 449 (1 - std::abs(hsl.l - goals[i].l)) * 6.5 +
448 (box_color.weight / static_cast<float>(max_weight)) * 0.5; 450 (box_color.weight / static_cast<float>(max_weight)) * 0.5;
449 if (suitability > best_suitability) { 451 if (suitability > best_suitability) {
450 best_suitability = suitability; 452 best_suitability = suitability;
451 best_color = box_color.color; 453 best_colors[i] = box_color.color;
454 }
452 } 455 }
453 } 456 }
454 457
455 return best_color; 458 return best_colors;
456 } 459 }
457 460
458 } // namespace 461 } // namespace
459 462
460 KMeanImageSampler::KMeanImageSampler() { 463 KMeanImageSampler::KMeanImageSampler() {
461 } 464 }
462 465
463 KMeanImageSampler::~KMeanImageSampler() { 466 KMeanImageSampler::~KMeanImageSampler() {
464 } 467 }
465 468
(...skipping 250 matching lines...) Expand 10 before | Expand all | Expand 10 after
716 bitmap.width(), bitmap.height(), 719 bitmap.width(), bitmap.height(),
717 lower_bound, upper_bound, sampler); 720 lower_bound, upper_bound, sampler);
718 } 721 }
719 722
720 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { 723 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) {
721 GridSampler sampler; 724 GridSampler sampler;
722 return CalculateKMeanColorOfBitmap( 725 return CalculateKMeanColorOfBitmap(
723 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); 726 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler);
724 } 727 }
725 728
726 SkColor CalculateProminentColorOfBitmap(const SkBitmap& bitmap, 729 std::vector<SkColor> CalculateProminentColorsOfBitmap(
727 LumaRange luma, 730 const SkBitmap& bitmap,
728 SaturationRange saturation) { 731 const ColorProfiles& color_profiles) {
732 size_t size = color_profiles.size();
729 if (bitmap.empty() || bitmap.isNull()) 733 if (bitmap.empty() || bitmap.isNull())
730 return SK_ColorTRANSPARENT; 734 return std::vector<SkColor>(size, SK_ColorTRANSPARENT);
731 735
732 // The hue is not relevant to our bounds or goal colors. 736 // The hue is not relevant to our bounds or goal colors.
733 HSL lower_bound = { 737 std::vector<HSL> lower_bounds(size, {-1});
danakj 2017/06/20 23:22:03 3 mallocs per call to this function is not awesome
Qiang(Joe) Xu 2017/06/21 01:10:52 Done.
734 -1, 738 std::vector<HSL> upper_bounds(size, {-1});
735 }; 739 std::vector<HSL> goals(size, {-1});
736 HSL upper_bound = { 740 for (size_t i = 0; i < size; ++i) {
737 -1, 741 switch (color_profiles[i].luma) {
738 }; 742 case LumaRange::LIGHT:
739 HSL goal = { 743 lower_bounds[i].l = 0.55f;
740 -1, 744 upper_bounds[i].l = 1;
741 }; 745 goals[i].l = 0.74f;
746 break;
747 case LumaRange::NORMAL:
748 lower_bounds[i].l = 0.3f;
749 upper_bounds[i].l = 0.7f;
750 goals[i].l = 0.5f;
751 break;
752 case LumaRange::DARK:
753 lower_bounds[i].l = 0;
754 upper_bounds[i].l = 0.45f;
755 goals[i].l = 0.26f;
756 break;
757 }
742 758
743 switch (luma) { 759 switch (color_profiles[i].saturation) {
744 case LumaRange::LIGHT: 760 case SaturationRange::VIBRANT:
745 lower_bound.l = 0.55f; 761 lower_bounds[i].s = 0.35f;
746 upper_bound.l = 1; 762 upper_bounds[i].s = 1;
747 goal.l = 0.74f; 763 goals[i].s = 1;
748 break; 764 break;
749 case LumaRange::NORMAL: 765 case SaturationRange::MUTED:
750 lower_bound.l = 0.3f; 766 lower_bounds[i].s = 0;
751 upper_bound.l = 0.7f; 767 upper_bounds[i].s = 0.4f;
752 goal.l = 0.5f; 768 goals[i].s = 0.3f;
753 break; 769 break;
754 case LumaRange::DARK: 770 }
755 lower_bound.l = 0;
756 upper_bound.l = 0.45f;
757 goal.l = 0.26f;
758 break;
759 } 771 }
760 772
761 switch (saturation) { 773 return CalculateProminentColors(bitmap, lower_bounds, upper_bounds, goals);
762 case SaturationRange::VIBRANT:
763 lower_bound.s = 0.35f;
764 upper_bound.s = 1;
765 goal.s = 1;
766 break;
767 case SaturationRange::MUTED:
768 lower_bound.s = 0;
769 upper_bound.s = 0.4f;
770 goal.s = 0.3f;
771 break;
772 }
773
774 return CalculateProminentColor(bitmap, lower_bound, upper_bound, goal);
775 } 774 }
776 775
777 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { 776 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) {
778 // First need basic stats to normalize each channel separately. 777 // First need basic stats to normalize each channel separately.
779 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); 778 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros();
780 if (!bitmap.getPixels()) 779 if (!bitmap.getPixels())
781 return covariance; 780 return covariance;
782 781
783 // Assume ARGB_8888 format. 782 // Assume ARGB_8888 format.
784 DCHECK(bitmap.colorType() == kN32_SkColorType); 783 DCHECK(bitmap.colorType() == kN32_SkColorType);
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
932 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); 931 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap);
933 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); 932 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros();
934 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); 933 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors);
935 gfx::Vector3dF principal = eigenvectors.get_column(0); 934 gfx::Vector3dF principal = eigenvectors.get_column(0);
936 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) 935 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF())
937 return false; // This may happen for some edge cases. 936 return false; // This may happen for some edge cases.
938 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); 937 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap);
939 } 938 }
940 939
941 } // color_utils 940 } // color_utils
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698