OLD | NEW |
---|---|
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 331 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
342 (SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3.0f; | 342 (SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3.0f; |
343 // If a color is too close to white or black, ignore it. | 343 // If a color is too close to white or black, ignore it. |
344 if (average_channel_value >= 237 || average_channel_value <= 22) | 344 if (average_channel_value >= 237 || average_channel_value <= 22) |
345 return false; | 345 return false; |
346 | 346 |
347 HSL hsl; | 347 HSL hsl; |
348 SkColorToHSL(color, &hsl); | 348 SkColorToHSL(color, &hsl); |
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 // Used to group lower_bound, upper_bound, goal HSL color together for prominent | |
353 // color calculation. | |
354 struct ColorBracket { | |
355 HSL lower_bound = {-1}; | |
356 HSL upper_bound = {-1}; | |
357 HSL goal = {-1}; | |
358 }; | |
359 | |
352 // This algorithm is a port of Android's Palette API. Compare to package | 360 // 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 | 361 // android.support.v7.graphics and see that code for additional high-level |
354 // explanation of this algorithm. There are some minor differences: | 362 // explanation of this algorithm. There are some minor differences: |
355 // * This code doesn't exclude the same color from being used for | 363 // * This code doesn't exclude the same color from being used for |
356 // different color profiles. | 364 // different color profiles. |
357 // * This code doesn't try to heuristically derive missing colors from | 365 // * This code doesn't try to heuristically derive missing colors from |
358 // existing colors. | 366 // existing colors. |
359 SkColor CalculateProminentColor(const SkBitmap& bitmap, | 367 std::vector<SkColor> |
360 const HSL& lower_bound, | 368 CalculateProminentColors(const SkBitmap& bitmap, |
361 const HSL& upper_bound, | 369 const std::vector<ColorBracket>& color_brackets) { |
362 const HSL& goal) { | |
363 DCHECK(!bitmap.empty()); | 370 DCHECK(!bitmap.empty()); |
364 DCHECK(!bitmap.isNull()); | 371 DCHECK(!bitmap.isNull()); |
365 | 372 |
366 const uint32_t* pixels = static_cast<uint32_t*>(bitmap.getPixels()); | 373 const uint32_t* pixels = static_cast<uint32_t*>(bitmap.getPixels()); |
367 const int pixel_count = bitmap.width() * bitmap.height(); | 374 const int pixel_count = bitmap.width() * bitmap.height(); |
368 | 375 |
369 // For better performance, only consider at most 10k pixels (evenly | 376 // For better performance, only consider at most 10k pixels (evenly |
370 // distributed throughout the image). This has a very minor impact on the | 377 // 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 | 378 // outcome but improves runtime substantially for large images. 10,007 is a |
372 // prime number to reduce the chance of picking an unrepresentative sample. | 379 // prime number to reduce the chance of picking an unrepresentative sample. |
(...skipping 14 matching lines...) Expand all Loading... | |
387 | 394 |
388 // Now throw out some uninteresting colors. | 395 // Now throw out some uninteresting colors. |
389 std::vector<SkColor> interesting_colors; | 396 std::vector<SkColor> interesting_colors; |
390 interesting_colors.reserve(color_counts.size()); | 397 interesting_colors.reserve(color_counts.size()); |
391 for (auto color_count : color_counts) { | 398 for (auto color_count : color_counts) { |
392 SkColor color = color_count.first; | 399 SkColor color = color_count.first; |
393 if (IsInterestingColor(color)) | 400 if (IsInterestingColor(color)) |
394 interesting_colors.push_back(color); | 401 interesting_colors.push_back(color); |
395 } | 402 } |
396 | 403 |
404 std::vector<SkColor> best_colors(color_brackets.size(), SK_ColorTRANSPARENT); | |
397 if (interesting_colors.empty()) | 405 if (interesting_colors.empty()) |
398 return SK_ColorTRANSPARENT; | 406 return best_colors; |
399 | 407 |
400 // Group the colors into "boxes" and repeatedly split the most voluminous box. | 408 // 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 | 409 // 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 | 410 // color in it) or when the number of color boxes reaches 12. As per the |
403 // Android docs, | 411 // Android docs, |
404 // | 412 // |
405 // For landscapes, good values are in the range 12-16. For images which | 413 // 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 | 414 // are largely made up of people's faces then this value should be increased |
407 // to 24-32. | 415 // to 24-32. |
408 const int kMaxColors = 12; | 416 const int kMaxColors = 12; |
(...skipping 14 matching lines...) Expand all Loading... | |
423 // Now extract a single color to represent each box. This is the average color | 431 // 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. | 432 // in the box, weighted by the frequency of that color in the source image. |
425 std::vector<WeightedColor> box_colors; | 433 std::vector<WeightedColor> box_colors; |
426 uint64_t max_weight = 0; | 434 uint64_t max_weight = 0; |
427 while (!boxes.empty()) { | 435 while (!boxes.empty()) { |
428 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); | 436 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); |
429 boxes.pop(); | 437 boxes.pop(); |
430 max_weight = std::max(max_weight, box_colors.back().weight); | 438 max_weight = std::max(max_weight, box_colors.back().weight); |
431 } | 439 } |
432 | 440 |
433 // Given these box average colors, find the best one for the desired color | 441 // 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 | 442 // 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 | 443 // 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. | 444 // the provided bounds, in which case we'll return an empty color. |
437 double best_suitability = 0; | 445 for (size_t i = 0; i < color_brackets.size(); ++i) { |
438 SkColor best_color = SK_ColorTRANSPARENT; | 446 double best_suitability = 0; |
439 for (const auto& box_color : box_colors) { | 447 for (const auto& box_color : box_colors) { |
440 HSL hsl; | 448 HSL hsl; |
441 SkColorToHSL(box_color.color, &hsl); | 449 SkColorToHSL(box_color.color, &hsl); |
442 if (!IsWithinHSLRange(hsl, lower_bound, upper_bound)) | 450 if (!IsWithinHSLRange(hsl, color_brackets[i].lower_bound, |
443 continue; | 451 color_brackets[i].upper_bound)) { |
452 continue; | |
453 } | |
444 | 454 |
445 double suitability = | 455 double suitability = |
446 (1 - std::abs(hsl.s - goal.s)) * 3 + | 456 (1 - std::abs(hsl.s - color_brackets[i].goal.s)) * 3 + |
447 (1 - std::abs(hsl.l - goal.l)) * 6.5 + | 457 (1 - std::abs(hsl.l - color_brackets[i].goal.l)) * 6.5 + |
448 (box_color.weight / static_cast<float>(max_weight)) * 0.5; | 458 (box_color.weight / static_cast<float>(max_weight)) * 0.5; |
449 if (suitability > best_suitability) { | 459 if (suitability > best_suitability) { |
450 best_suitability = suitability; | 460 best_suitability = suitability; |
451 best_color = box_color.color; | 461 best_colors[i] = box_color.color; |
462 } | |
452 } | 463 } |
453 } | 464 } |
454 | 465 |
455 return best_color; | 466 return best_colors; |
456 } | 467 } |
457 | 468 |
458 } // namespace | 469 } // namespace |
459 | 470 |
460 KMeanImageSampler::KMeanImageSampler() { | 471 KMeanImageSampler::KMeanImageSampler() { |
461 } | 472 } |
462 | 473 |
463 KMeanImageSampler::~KMeanImageSampler() { | 474 KMeanImageSampler::~KMeanImageSampler() { |
464 } | 475 } |
465 | 476 |
(...skipping 250 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
716 bitmap.width(), bitmap.height(), | 727 bitmap.width(), bitmap.height(), |
717 lower_bound, upper_bound, sampler); | 728 lower_bound, upper_bound, sampler); |
718 } | 729 } |
719 | 730 |
720 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { | 731 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { |
721 GridSampler sampler; | 732 GridSampler sampler; |
722 return CalculateKMeanColorOfBitmap( | 733 return CalculateKMeanColorOfBitmap( |
723 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); | 734 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); |
724 } | 735 } |
725 | 736 |
726 SkColor CalculateProminentColorOfBitmap(const SkBitmap& bitmap, | 737 std::vector<SkColor> CalculateProminentColorsOfBitmap( |
727 LumaRange luma, | 738 const SkBitmap& bitmap, |
728 SaturationRange saturation) { | 739 const std::vector<ColorProfile>& color_profiles) { |
740 size_t size = color_profiles.size(); | |
729 if (bitmap.empty() || bitmap.isNull()) | 741 if (bitmap.empty() || bitmap.isNull()) |
bruthig
2017/06/21 15:54:17
This should probably return early if color_profile
Qiang(Joe) Xu
2017/06/21 22:35:27
Done.
| |
730 return SK_ColorTRANSPARENT; | 742 return std::vector<SkColor>(size, SK_ColorTRANSPARENT); |
731 | 743 |
732 // The hue is not relevant to our bounds or goal colors. | 744 // The hue is not relevant to our bounds or goal colors. |
733 HSL lower_bound = { | 745 std::vector<ColorBracket> color_brackets(size); |
734 -1, | 746 for (size_t i = 0; i < size; ++i) { |
735 }; | 747 switch (color_profiles[i].luma) { |
736 HSL upper_bound = { | 748 case LumaRange::LIGHT: |
737 -1, | 749 color_brackets[i].lower_bound.l = 0.55f; |
738 }; | 750 color_brackets[i].upper_bound.l = 1; |
739 HSL goal = { | 751 color_brackets[i].goal.l = 0.74f; |
740 -1, | 752 break; |
741 }; | 753 case LumaRange::NORMAL: |
754 color_brackets[i].lower_bound.l = 0.3f; | |
755 color_brackets[i].upper_bound.l = 0.7f; | |
756 color_brackets[i].goal.l = 0.5f; | |
757 break; | |
758 case LumaRange::DARK: | |
759 color_brackets[i].lower_bound.l = 0; | |
760 color_brackets[i].upper_bound.l = 0.45f; | |
761 color_brackets[i].goal.l = 0.26f; | |
762 break; | |
763 } | |
742 | 764 |
743 switch (luma) { | 765 switch (color_profiles[i].saturation) { |
744 case LumaRange::LIGHT: | 766 case SaturationRange::VIBRANT: |
745 lower_bound.l = 0.55f; | 767 color_brackets[i].lower_bound.s = 0.35f; |
746 upper_bound.l = 1; | 768 color_brackets[i].upper_bound.s = 1; |
747 goal.l = 0.74f; | 769 color_brackets[i].goal.s = 1; |
748 break; | 770 break; |
749 case LumaRange::NORMAL: | 771 case SaturationRange::MUTED: |
750 lower_bound.l = 0.3f; | 772 color_brackets[i].lower_bound.s = 0; |
751 upper_bound.l = 0.7f; | 773 color_brackets[i].upper_bound.s = 0.4f; |
752 goal.l = 0.5f; | 774 color_brackets[i].goal.s = 0.3f; |
753 break; | 775 break; |
754 case LumaRange::DARK: | 776 } |
755 lower_bound.l = 0; | |
756 upper_bound.l = 0.45f; | |
757 goal.l = 0.26f; | |
758 break; | |
759 } | 777 } |
760 | 778 |
761 switch (saturation) { | 779 return CalculateProminentColors(bitmap, color_brackets); |
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 } | 780 } |
776 | 781 |
777 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { | 782 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { |
778 // First need basic stats to normalize each channel separately. | 783 // First need basic stats to normalize each channel separately. |
779 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); | 784 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); |
780 if (!bitmap.getPixels()) | 785 if (!bitmap.getPixels()) |
781 return covariance; | 786 return covariance; |
782 | 787 |
783 // Assume ARGB_8888 format. | 788 // Assume ARGB_8888 format. |
784 DCHECK(bitmap.colorType() == kN32_SkColorType); | 789 DCHECK(bitmap.colorType() == kN32_SkColorType); |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
932 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); | 937 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); |
933 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); | 938 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); |
934 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); | 939 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); |
935 gfx::Vector3dF principal = eigenvectors.get_column(0); | 940 gfx::Vector3dF principal = eigenvectors.get_column(0); |
936 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) | 941 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) |
937 return false; // This may happen for some edge cases. | 942 return false; // This may happen for some edge cases. |
938 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); | 943 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); |
939 } | 944 } |
940 | 945 |
941 } // color_utils | 946 } // color_utils |
OLD | NEW |