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> |
| 11 #include <cmath> |
11 #include <limits> | 12 #include <limits> |
| 13 #include <map> |
12 #include <memory> | 14 #include <memory> |
| 15 #include <queue> |
13 #include <vector> | 16 #include <vector> |
14 | 17 |
15 #include "base/logging.h" | 18 #include "base/logging.h" |
16 #include "third_party/skia/include/core/SkBitmap.h" | 19 #include "third_party/skia/include/core/SkBitmap.h" |
17 #include "third_party/skia/include/core/SkUnPreMultiply.h" | 20 #include "third_party/skia/include/core/SkUnPreMultiply.h" |
18 #include "ui/gfx/codec/png_codec.h" | 21 #include "ui/gfx/codec/png_codec.h" |
| 22 #include "ui/gfx/color_palette.h" |
19 #include "ui/gfx/color_utils.h" | 23 #include "ui/gfx/color_utils.h" |
| 24 #include "ui/gfx/range/range.h" |
20 | 25 |
21 namespace color_utils { | 26 namespace color_utils { |
22 namespace { | 27 namespace { |
23 | 28 |
24 // RGBA KMean Constants | 29 // RGBA KMean Constants |
25 const uint32_t kNumberOfClusters = 4; | 30 const uint32_t kNumberOfClusters = 4; |
26 const int kNumberOfIterations = 50; | 31 const int kNumberOfIterations = 50; |
27 | 32 |
28 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; | 33 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; |
29 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; | 34 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 // approximately 10 microseconds for a 16x16 icon on an Intel Core i5. | 141 // approximately 10 microseconds for a 16x16 icon on an Intel Core i5. |
137 void UnPreMultiply(const SkBitmap& bitmap, uint32_t* buffer, int buffer_size) { | 142 void UnPreMultiply(const SkBitmap& bitmap, uint32_t* buffer, int buffer_size) { |
138 SkAutoLockPixels auto_lock(bitmap); | 143 SkAutoLockPixels auto_lock(bitmap); |
139 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); | 144 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); |
140 uint32_t* out = buffer; | 145 uint32_t* out = buffer; |
141 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); | 146 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); |
142 for (int i = 0; i < pixel_count; ++i) | 147 for (int i = 0; i < pixel_count; ++i) |
143 *out++ = SkUnPreMultiply::PMColorToColor(*in++); | 148 *out++ = SkUnPreMultiply::PMColorToColor(*in++); |
144 } | 149 } |
145 | 150 |
| 151 // Prominent color utilities --------------------------------------------------- |
| 152 |
| 153 // A color value with an associated weight. |
| 154 struct WeightedColor { |
| 155 WeightedColor(SkColor color, uint64_t weight) |
| 156 : color(color), weight(weight) {} |
| 157 |
| 158 SkColor color; |
| 159 |
| 160 // The weight correlates to a count, so it should be 1 or greater. |
| 161 uint64_t weight; |
| 162 }; |
| 163 |
| 164 // A |ColorBox| represents a 3-dimensional region in a color space (an ordered |
| 165 // set of colors). It is a range in the ordered set, with a low index and a high |
| 166 // index. The diversity (volume) of the box is computed by looking at the range |
| 167 // of color values it spans, where r, g, and b components are considered |
| 168 // separately. |
| 169 class ColorBox { |
| 170 public: |
| 171 explicit ColorBox(std::vector<SkColor>* color_space) |
| 172 : ColorBox(color_space, gfx::Range(0, color_space->size())) {} |
| 173 ColorBox(const ColorBox& other) = default; |
| 174 ColorBox& operator=(const ColorBox& other) = default; |
| 175 ~ColorBox() {} |
| 176 |
| 177 // Can't split if there's only one color in the box. |
| 178 bool CanSplit() const { return color_range_.length() > 1; } |
| 179 |
| 180 // Splits |this| in two and returns the other half. |
| 181 ColorBox Split() { |
| 182 // Calculate which component has the largest range... |
| 183 const uint8_t r_dimension = max_r_ - min_r_; |
| 184 const uint8_t g_dimension = max_g_ - min_g_; |
| 185 const uint8_t b_dimension = max_b_ - min_b_; |
| 186 const uint8_t long_dimension = |
| 187 std::max({r_dimension, g_dimension, b_dimension}); |
| 188 const enum { |
| 189 RED, |
| 190 GREEN, |
| 191 BLUE, |
| 192 } channel = long_dimension == r_dimension |
| 193 ? RED |
| 194 : long_dimension == g_dimension ? GREEN : BLUE; |
| 195 |
| 196 // ... and sort along that axis. |
| 197 auto sort_function = [channel](SkColor a, SkColor b) { |
| 198 switch (channel) { |
| 199 case RED: |
| 200 return SkColorGetR(a) < SkColorGetR(b); |
| 201 case GREEN: |
| 202 return SkColorGetG(a) < SkColorGetG(b); |
| 203 case BLUE: |
| 204 return SkColorGetB(a) < SkColorGetB(b); |
| 205 } |
| 206 NOTREACHED(); |
| 207 return SkColorGetB(a) < SkColorGetB(b); |
| 208 }; |
| 209 // Just the portion of |color_space_| that's covered by this box should be |
| 210 // sorted. |
| 211 std::sort(color_space_->begin() + color_range_.start(), |
| 212 color_space_->begin() + color_range_.end(), sort_function); |
| 213 |
| 214 // Split at the first color value that's not less than the midpoint (mean of |
| 215 // the start and values). |
| 216 uint32_t split_point = color_range_.end() - 1; |
| 217 for (uint32_t i = color_range_.start() + 1; i < color_range_.end() - 1; |
| 218 ++i) { |
| 219 bool past_midpoint = false; |
| 220 switch (channel) { |
| 221 case RED: |
| 222 past_midpoint = |
| 223 static_cast<uint8_t>(SkColorGetR((*color_space_)[i])) > |
| 224 (min_r_ + max_r_) / 2; |
| 225 break; |
| 226 case GREEN: |
| 227 past_midpoint = |
| 228 static_cast<uint8_t>(SkColorGetG((*color_space_)[i])) > |
| 229 (min_g_ + max_g_) / 2; |
| 230 break; |
| 231 case BLUE: |
| 232 past_midpoint = |
| 233 static_cast<uint8_t>(SkColorGetB((*color_space_)[i])) > |
| 234 (min_b_ + max_b_) / 2; |
| 235 break; |
| 236 } |
| 237 if (past_midpoint) { |
| 238 split_point = i; |
| 239 break; |
| 240 } |
| 241 } |
| 242 |
| 243 // Break off half and return it. |
| 244 gfx::Range other_range = color_range_; |
| 245 other_range.set_end(split_point); |
| 246 ColorBox other_box(color_space_, other_range); |
| 247 |
| 248 // Keep the other half in |this| and recalculate our color bounds. |
| 249 color_range_.set_start(split_point); |
| 250 RecomputeBounds(); |
| 251 return other_box; |
| 252 } |
| 253 |
| 254 // Returns the average color of this box, weighted by its popularity in |
| 255 // |color_counts|. |
| 256 WeightedColor GetWeightedAverageColor( |
| 257 const std::map<SkColor, int>& color_counts) const { |
| 258 uint64_t sum_r = 0; |
| 259 uint64_t sum_g = 0; |
| 260 uint64_t sum_b = 0; |
| 261 uint64_t total_count_in_box = 0; |
| 262 |
| 263 for (uint32_t i = color_range_.start(); i < color_range_.end(); ++i) { |
| 264 const SkColor color = (*color_space_)[i]; |
| 265 const auto color_count_iter = color_counts.find(color); |
| 266 DCHECK(color_count_iter != color_counts.end()); |
| 267 const int color_count = color_count_iter->second; |
| 268 |
| 269 total_count_in_box += color_count; |
| 270 sum_r += color_count * SkColorGetR(color); |
| 271 sum_g += color_count * SkColorGetG(color); |
| 272 sum_b += color_count * SkColorGetB(color); |
| 273 } |
| 274 |
| 275 return WeightedColor( |
| 276 SkColorSetRGB( |
| 277 std::round(static_cast<double>(sum_r) / total_count_in_box), |
| 278 std::round(static_cast<double>(sum_g) / total_count_in_box), |
| 279 std::round(static_cast<double>(sum_b) / total_count_in_box)), |
| 280 total_count_in_box); |
| 281 } |
| 282 |
| 283 static bool CompareByVolume(const ColorBox& a, const ColorBox& b) { |
| 284 return a.volume_ < b.volume_; |
| 285 } |
| 286 |
| 287 private: |
| 288 ColorBox(std::vector<SkColor>* color_space, const gfx::Range& color_range) |
| 289 : color_space_(color_space), color_range_(color_range) { |
| 290 RecomputeBounds(); |
| 291 } |
| 292 |
| 293 void RecomputeBounds() { |
| 294 DCHECK(!color_range_.is_reversed()); |
| 295 DCHECK(!color_range_.is_empty()); |
| 296 DCHECK_LE(color_range_.end(), color_space_->size()); |
| 297 |
| 298 min_r_ = 0xFF; |
| 299 min_g_ = 0xFF; |
| 300 min_b_ = 0xFF; |
| 301 max_r_ = 0; |
| 302 max_g_ = 0; |
| 303 max_b_ = 0; |
| 304 |
| 305 for (uint32_t i = color_range_.start(); i < color_range_.end(); ++i) { |
| 306 SkColor color = (*color_space_)[i]; |
| 307 min_r_ = std::min<uint8_t>(SkColorGetR(color), min_r_); |
| 308 min_g_ = std::min<uint8_t>(SkColorGetG(color), min_g_); |
| 309 min_b_ = std::min<uint8_t>(SkColorGetB(color), min_b_); |
| 310 max_r_ = std::max<uint8_t>(SkColorGetR(color), max_r_); |
| 311 max_g_ = std::max<uint8_t>(SkColorGetG(color), max_g_); |
| 312 max_b_ = std::max<uint8_t>(SkColorGetB(color), max_b_); |
| 313 } |
| 314 |
| 315 volume_ = |
| 316 (max_r_ - min_r_ + 1) * (max_g_ - min_g_ + 1) * (max_b_ - min_b_ + 1); |
| 317 } |
| 318 |
| 319 // The set of colors of which this box captures a subset. This vector is not |
| 320 // owned but may be modified during the split operation. |
| 321 std::vector<SkColor>* color_space_; |
| 322 |
| 323 // The range of indexes into |color_space_| that are part of this box. |
| 324 gfx::Range color_range_; |
| 325 |
| 326 // Cached min and max color component values for the colors in this box. |
| 327 uint8_t min_r_ = 0; |
| 328 uint8_t min_g_ = 0; |
| 329 uint8_t min_b_ = 0; |
| 330 uint8_t max_r_ = 0; |
| 331 uint8_t max_g_ = 0; |
| 332 uint8_t max_b_ = 0; |
| 333 |
| 334 // Cached volume value, which is the product of the range of each color |
| 335 // component. |
| 336 int volume_ = 0; |
| 337 }; |
| 338 |
| 339 // Some color values should be ignored for the purposes of determining prominent |
| 340 // colors. |
| 341 bool IsInterestingColor(SkColor color) { |
| 342 const float average_channel_value = |
| 343 (SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3.0f; |
| 344 // If a color is too close to white or black, ignore it. |
| 345 if (average_channel_value >= 237 || average_channel_value <= 22) |
| 346 return false; |
| 347 |
| 348 HSL hsl; |
| 349 SkColorToHSL(color, &hsl); |
| 350 return !(hsl.h >= 0.028f && hsl.h <= 0.10f && hsl.s <= 0.82f); |
| 351 } |
| 352 |
| 353 // This algorithm is a port of Android's Palette API. Compare to package |
| 354 // android.support.v7.graphics and see that code for additional high-level |
| 355 // explanation of this algorithm. There are some minor differences: |
| 356 // * This code doesn't exclude the same color from being used for |
| 357 // different color profiles. |
| 358 // * This code doesn't try to heuristically derive missing colors from |
| 359 // existing colors. |
| 360 SkColor CalculateProminentColor(const SkBitmap& bitmap, |
| 361 const HSL& lower_bound, |
| 362 const HSL& upper_bound, |
| 363 const HSL& goal) { |
| 364 DCHECK(!bitmap.empty()); |
| 365 DCHECK(!bitmap.isNull()); |
| 366 |
| 367 SkAutoLockPixels auto_lock(bitmap); |
| 368 const uint32_t* pixels = static_cast<uint32_t*>(bitmap.getPixels()); |
| 369 int pixel_count = bitmap.width() * bitmap.height(); |
| 370 std::map<SkColor, int> color_counts; |
| 371 |
| 372 // First extract all colors into counts. |
| 373 for (int i = 0; i < pixel_count; ++i) { |
| 374 // SkBitmap uses pre-multiplied alpha but the prominent color algorithm |
| 375 // needs non-pre-multiplied alpha. |
| 376 const SkColor pixel = SkUnPreMultiply::PMColorToColor(pixels[i]); |
| 377 if (SkColorGetA(pixel) == SK_AlphaTRANSPARENT) |
| 378 continue; |
| 379 |
| 380 color_counts[pixel]++; |
| 381 } |
| 382 |
| 383 // Now throw out some uninteresting colors. |
| 384 std::vector<SkColor> interesting_colors; |
| 385 interesting_colors.reserve(color_counts.size()); |
| 386 for (auto color_count : color_counts) { |
| 387 SkColor color = color_count.first; |
| 388 if (IsInterestingColor(color)) |
| 389 interesting_colors.push_back(color); |
| 390 } |
| 391 |
| 392 if (interesting_colors.empty()) |
| 393 return SK_ColorTRANSPARENT; |
| 394 |
| 395 // Group the colors into "boxes" and repeatedly split the most voluminous box. |
| 396 // We stop the process when a box can no longer be split (there's only one |
| 397 // color in it) or when the number of color boxes reaches 12. As per the |
| 398 // Android docs, |
| 399 // |
| 400 // For landscapes, good values are in the range 12-16. For images which |
| 401 // are largely made up of people's faces then this value should be increased |
| 402 // to 24-32. |
| 403 const int kMaxColors = 12; |
| 404 // Boxes are sorted by volume with the most voluminous at the front of the PQ. |
| 405 std::priority_queue<ColorBox, std::vector<ColorBox>, |
| 406 bool (*)(const ColorBox&, const ColorBox&)> |
| 407 boxes(&ColorBox::CompareByVolume); |
| 408 boxes.emplace(&interesting_colors); |
| 409 while (boxes.size() < kMaxColors) { |
| 410 auto box = boxes.top(); |
| 411 if (!box.CanSplit()) |
| 412 break; |
| 413 boxes.pop(); |
| 414 boxes.push(box.Split()); |
| 415 boxes.push(box); |
| 416 } |
| 417 |
| 418 // Now extract a single color to represent each box. This is the average color |
| 419 // in the box, weighted by the frequency of that color in the source image. |
| 420 std::vector<WeightedColor> box_colors; |
| 421 uint64_t max_weight = 0; |
| 422 while (!boxes.empty()) { |
| 423 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); |
| 424 boxes.pop(); |
| 425 max_weight = std::max(max_weight, box_colors.back().weight); |
| 426 } |
| 427 |
| 428 // Given these box average colors, find the best one for the desired color |
| 429 // profile. "Best" in this case means the color which fits in the provided |
| 430 // bounds and comes closest to |goal|. It's possible that no color will fit in |
| 431 // the provided bounds, in which case we'll return an empty color. |
| 432 double best_suitability = 0; |
| 433 SkColor best_color = SK_ColorTRANSPARENT; |
| 434 for (const auto& box_color : box_colors) { |
| 435 HSL hsl; |
| 436 SkColorToHSL(box_color.color, &hsl); |
| 437 if (!IsWithinHSLRange(hsl, lower_bound, upper_bound)) |
| 438 continue; |
| 439 |
| 440 double suitability = |
| 441 (1 - std::abs(hsl.s - goal.s)) * 3 + |
| 442 (1 - std::abs(hsl.l - goal.l)) * 6.5 + |
| 443 (box_color.weight / static_cast<float>(max_weight)) * 0.5; |
| 444 if (suitability > best_suitability) { |
| 445 best_suitability = suitability; |
| 446 best_color = box_color.color; |
| 447 } |
| 448 } |
| 449 |
| 450 return best_color; |
| 451 } |
| 452 |
146 } // namespace | 453 } // namespace |
147 | 454 |
148 KMeanImageSampler::KMeanImageSampler() { | 455 KMeanImageSampler::KMeanImageSampler() { |
149 } | 456 } |
150 | 457 |
151 KMeanImageSampler::~KMeanImageSampler() { | 458 KMeanImageSampler::~KMeanImageSampler() { |
152 } | 459 } |
153 | 460 |
154 GridSampler::GridSampler() : calls_(0) { | 461 GridSampler::GridSampler() : calls_(0) { |
155 } | 462 } |
(...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
394 const HSL& upper_bound, | 701 const HSL& upper_bound, |
395 KMeanImageSampler* sampler) { | 702 KMeanImageSampler* sampler) { |
396 // SkBitmap uses pre-multiplied alpha but the KMean clustering function | 703 // SkBitmap uses pre-multiplied alpha but the KMean clustering function |
397 // above uses non-pre-multiplied alpha. Transform the bitmap before we | 704 // above uses non-pre-multiplied alpha. Transform the bitmap before we |
398 // analyze it because the function reads each pixel multiple times. | 705 // analyze it because the function reads each pixel multiple times. |
399 int pixel_count = bitmap.width() * bitmap.height(); | 706 int pixel_count = bitmap.width() * bitmap.height(); |
400 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); | 707 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); |
401 UnPreMultiply(bitmap, image.get(), pixel_count); | 708 UnPreMultiply(bitmap, image.get(), pixel_count); |
402 | 709 |
403 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), | 710 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), |
404 bitmap.width(), | 711 bitmap.width(), bitmap.height(), |
405 bitmap.height(), | 712 lower_bound, upper_bound, sampler); |
406 lower_bound, | |
407 upper_bound, | |
408 sampler); | |
409 } | 713 } |
410 | 714 |
411 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { | 715 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { |
412 GridSampler sampler; | 716 GridSampler sampler; |
413 return CalculateKMeanColorOfBitmap( | 717 return CalculateKMeanColorOfBitmap( |
414 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); | 718 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); |
415 } | 719 } |
416 | 720 |
| 721 SkColor CalculateProminentColorOfBitmap(const SkBitmap& bitmap, |
| 722 LumaRange luma, |
| 723 SaturationRange saturation) { |
| 724 if (bitmap.empty() || bitmap.isNull()) |
| 725 return SK_ColorTRANSPARENT; |
| 726 |
| 727 // The hue is not relevant to our bounds or goal colors. |
| 728 HSL lower_bound = { |
| 729 -1, |
| 730 }; |
| 731 HSL upper_bound = { |
| 732 -1, |
| 733 }; |
| 734 HSL goal = { |
| 735 -1, |
| 736 }; |
| 737 |
| 738 switch (luma) { |
| 739 case LumaRange::LIGHT: |
| 740 lower_bound.l = 0.55f; |
| 741 upper_bound.l = 1; |
| 742 goal.l = 0.74f; |
| 743 break; |
| 744 case LumaRange::NORMAL: |
| 745 lower_bound.l = 0.3f; |
| 746 upper_bound.l = 0.7f; |
| 747 goal.l = 0.5f; |
| 748 break; |
| 749 case LumaRange::DARK: |
| 750 lower_bound.l = 0; |
| 751 upper_bound.l = 0.45f; |
| 752 goal.l = 0.26f; |
| 753 break; |
| 754 } |
| 755 |
| 756 switch (saturation) { |
| 757 case SaturationRange::VIBRANT: |
| 758 lower_bound.s = 0.35f; |
| 759 upper_bound.s = 1; |
| 760 goal.s = 1; |
| 761 break; |
| 762 case SaturationRange::MUTED: |
| 763 lower_bound.s = 0; |
| 764 upper_bound.s = 0.4f; |
| 765 goal.s = 0.3f; |
| 766 break; |
| 767 } |
| 768 |
| 769 return CalculateProminentColor(bitmap, lower_bound, upper_bound, goal); |
| 770 } |
| 771 |
417 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { | 772 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { |
418 // First need basic stats to normalize each channel separately. | 773 // First need basic stats to normalize each channel separately. |
419 SkAutoLockPixels bitmap_lock(bitmap); | 774 SkAutoLockPixels bitmap_lock(bitmap); |
420 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); | 775 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); |
421 if (!bitmap.getPixels()) | 776 if (!bitmap.getPixels()) |
422 return covariance; | 777 return covariance; |
423 | 778 |
424 // Assume ARGB_8888 format. | 779 // Assume ARGB_8888 format. |
425 DCHECK(bitmap.colorType() == kN32_SkColorType); | 780 DCHECK(bitmap.colorType() == kN32_SkColorType); |
426 | 781 |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
576 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); | 931 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); |
577 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); | 932 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); |
578 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); | 933 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); |
579 gfx::Vector3dF principal = eigenvectors.get_column(0); | 934 gfx::Vector3dF principal = eigenvectors.get_column(0); |
580 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) | 935 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) |
581 return false; // This may happen for some edge cases. | 936 return false; // This may happen for some edge cases. |
582 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); | 937 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); |
583 } | 938 } |
584 | 939 |
585 } // color_utils | 940 } // color_utils |
OLD | NEW |