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 (equivalent to the popularity of that | |
154 // color). | |
155 struct WeightedColor { | |
156 SkColor color; | |
157 int weight; | |
158 }; | |
159 | |
160 // A |ColorBox| represents a region in a color space (an ordered set of colors). | |
161 // It is a range in the ordered set, with a low index and a high index. The | |
162 // diversity (volume) of the box is computed by looking at the range of color | |
163 // values it spans, where r, g, and b components are considered separately. | |
164 class ColorBox { | |
165 public: | |
166 explicit ColorBox(std::vector<SkColor>* color_space) | |
167 : ColorBox(color_space, gfx::Range(0, color_space->size() - 1)) {} | |
168 ColorBox(const ColorBox& other) = default; | |
169 ColorBox& operator=(const ColorBox& other) = default; | |
170 ~ColorBox() {} | |
171 | |
172 // Can't split if there's only one color in the box. | |
173 bool CanSplit() { return !color_range_.is_empty(); } | |
sadrul
2017/02/10 19:36:44
const
Evan Stade
2017/02/13 19:01:13
Done.
| |
174 | |
175 // Splits |this| in two and returns the other half. | |
176 ColorBox Split() { | |
sadrul
2017/02/10 19:36:44
What happens if Split() is called when this has ju
Evan Stade
2017/02/13 19:01:13
It's an error to do that. You'd hit the dcheck in
| |
177 // Calculate which component has the largest range... | |
178 int r_dimension = max_r_ - min_r_; | |
179 int g_dimension = max_g_ - min_g_; | |
180 int b_dimension = max_b_ - min_b_; | |
181 int long_dimension = std::max({r_dimension, g_dimension, b_dimension}); | |
182 | |
183 // ... and sort along that axis. Split at the median of that axis. | |
184 std::function<bool(SkColor, SkColor)> sort_function = []( | |
185 SkColor a, SkColor b) { return SkColorGetR(a) < SkColorGetR(b); }; | |
sadrul
2017/02/10 19:36:44
https://chromium-cpp.appspot.com/ claims this is b
Evan Stade
2017/02/13 19:01:13
well that's pretty annoying as std::sort doesn't t
| |
186 int median = (min_r_ + max_r_) / 2; | |
187 if (g_dimension == long_dimension) { | |
188 sort_function = [](SkColor a, SkColor b) { | |
189 return SkColorGetG(a) < SkColorGetG(b); | |
190 }; | |
191 median = (min_g_ + max_g_) / 2; | |
192 } else if (b_dimension == long_dimension) { | |
193 sort_function = [](SkColor a, SkColor b) { | |
194 return SkColorGetB(a) < SkColorGetB(b); | |
195 }; | |
196 median = (min_b_ + max_b_) / 2; | |
197 } | |
198 | |
199 // Just the portion of |color_space_| that's covered by this box should be | |
200 // sorted. | |
201 std::sort(color_space_->begin() + color_range_.start(), | |
202 color_space_->begin() + color_range_.end(), sort_function); | |
203 | |
204 // Split at the first color value that's not less than the median. | |
205 uint32_t split_point = color_range_.end(); | |
206 for (auto i = color_range_.start() + 1; i < color_range_.end(); ++i) { | |
sadrul
2017/02/10 19:36:44
Just use uint32_t here?
Evan Stade
2017/02/13 19:01:13
Done.
| |
207 if (!sort_function(color_space_->at(i), | |
208 SkColorSetRGB(median, median, median))) { | |
209 split_point = i; | |
210 break; | |
211 } | |
212 } | |
213 | |
214 // Break off half and return it. | |
215 gfx::Range other_range = color_range_; | |
216 other_range.set_end(split_point - 1); | |
217 ColorBox other_box(color_space_, other_range); | |
218 | |
219 // Keep the other half in |this| and recalculate our color bounds. | |
220 color_range_.set_start(split_point); | |
221 RecomputeBounds(); | |
222 return other_box; | |
223 } | |
224 | |
225 // Returns the average color of this box, weighted by its popularity in | |
226 // |color_counts|. | |
227 WeightedColor GetWeightedAverageColor( | |
228 const std::map<SkColor, int>& color_counts) const { | |
229 int sum_r = 0; | |
230 int sum_g = 0; | |
231 int sum_b = 0; | |
232 int total_count_in_box = 0; | |
233 | |
234 for (auto i = color_range_.start(); i <= color_range_.end(); ++i) { | |
sadrul
2017/02/10 19:36:44
uint32_t
Evan Stade
2017/02/13 19:01:13
Done.
| |
235 const SkColor color = color_space_->at(i); | |
236 auto color_count_iter = color_counts.find(color); | |
237 DCHECK(color_count_iter != color_counts.end()); | |
238 const int color_count = color_count_iter->second; | |
239 | |
240 total_count_in_box += color_count; | |
241 sum_r += color_count * SkColorGetR(color); | |
242 sum_g += color_count * SkColorGetG(color); | |
243 sum_b += color_count * SkColorGetB(color); | |
244 } | |
245 | |
246 WeightedColor weighted_color; | |
247 weighted_color.weight = total_count_in_box; | |
248 weighted_color.color = SkColorSetRGB( | |
249 std::round(static_cast<float>(sum_r) / total_count_in_box), | |
250 std::round(static_cast<float>(sum_g) / total_count_in_box), | |
251 std::round(static_cast<float>(sum_b) / total_count_in_box)); | |
252 return weighted_color; | |
253 } | |
254 | |
255 // Comparisons are done by volume. | |
256 bool operator<(const ColorBox& other) const { | |
257 return volume_ < other.volume_; | |
258 } | |
259 | |
260 private: | |
261 ColorBox(std::vector<SkColor>* color_space, const gfx::Range& color_range) | |
262 : color_space_(color_space), color_range_(color_range) { | |
263 RecomputeBounds(); | |
264 } | |
265 | |
266 void RecomputeBounds() { | |
267 DCHECK(!color_range_.is_reversed()); | |
268 DCHECK_LT(color_range_.end(), color_space_->size()); | |
269 | |
270 min_r_ = 0xFF; | |
271 min_g_ = 0xFF; | |
272 min_b_ = 0xFF; | |
273 max_r_ = 0; | |
274 max_g_ = 0; | |
275 max_b_ = 0; | |
276 | |
277 for (auto i = color_range_.start(); i < color_range_.end(); ++i) { | |
sadrul
2017/02/10 19:36:44
uint32_t
Evan Stade
2017/02/13 19:01:13
Done.
| |
278 SkColor color = color_space_->at(i); | |
279 min_r_ = std::min<uint8_t>(SkColorGetR(color), min_r_); | |
280 min_g_ = std::min<uint8_t>(SkColorGetG(color), min_g_); | |
281 min_b_ = std::min<uint8_t>(SkColorGetB(color), min_b_); | |
282 max_r_ = std::max<uint8_t>(SkColorGetR(color), max_r_); | |
283 max_g_ = std::max<uint8_t>(SkColorGetG(color), max_g_); | |
284 max_b_ = std::max<uint8_t>(SkColorGetB(color), max_b_); | |
285 } | |
286 | |
287 volume_ = | |
288 (max_r_ - min_r_ + 1) * (max_g_ - min_g_ + 1) * (max_b_ - min_b_ + 1); | |
289 } | |
290 | |
291 // The set of colors of which this box captures a subset. This vector is not | |
292 // owned but may be modified during the split operation. | |
293 std::vector<SkColor>* color_space_; | |
294 | |
295 // The range of indexes into |color_space_| that are part of this box. | |
296 gfx::Range color_range_; | |
297 | |
298 // Cached min and max color component values for the colors in this box. | |
299 uint8_t min_r_ = 0; | |
300 uint8_t min_g_ = 0; | |
301 uint8_t min_b_ = 0; | |
302 uint8_t max_r_ = 0; | |
303 uint8_t max_g_ = 0; | |
304 uint8_t max_b_ = 0; | |
305 | |
306 // Cached volume value, which is the product of the range of each color | |
307 // component. | |
308 int volume_ = 0; | |
309 }; | |
310 | |
311 // Some color values should be ignored for the purposes of determining prominent | |
312 // colors. | |
313 bool IsInterestingColor(SkColor color) { | |
314 float average_channel_value = | |
315 (SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color)) / 3.0f; | |
316 // If a color is too close to white or black, ignore it. | |
317 if (average_channel_value >= 237 || average_channel_value <= 22) | |
318 return false; | |
319 | |
320 // Also rule out skin tones. | |
321 HSL hsl; | |
322 SkColorToHSL(color, &hsl); | |
323 return !(hsl.h >= 0.028f && hsl.h <= 0.10f && hsl.s <= 0.82f); | |
324 } | |
325 | |
326 // This algorithm is a port of Android's Palette API. Compare to package | |
327 // android.support.v7.graphics and see that code for additional high-level | |
328 // explanation of this algorithm. There are some minor differences: | |
329 // * This code doesn't exclude the same color from being used for | |
330 // different color profiles. | |
331 // * This code doesn't try to heuristically derive missing colors from | |
332 // existing colors. | |
333 SkColor CalculateProminentColorOfBuffer(uint8_t* decoded_data, | |
334 int img_width, | |
335 int img_height, | |
336 const HSL& lower_bound, | |
337 const HSL& upper_bound, | |
338 const HSL& goal) { | |
339 DCHECK_GT(img_width, 0); | |
340 DCHECK_GT(img_height, 0); | |
341 std::map<SkColor, int> color_counts; | |
342 | |
343 // First extract all colors. | |
344 for (int i = 0; i < img_width * img_height; ++i) { | |
345 // TODO(port): This code assumes the CPU architecture is little-endian. | |
346 uint8_t b = decoded_data[i * 4]; | |
347 uint8_t g = decoded_data[i * 4 + 1]; | |
348 uint8_t r = decoded_data[i * 4 + 2]; | |
349 uint8_t a = decoded_data[i * 4 + 3]; | |
350 if (a == SK_AlphaTRANSPARENT) | |
351 continue; | |
352 | |
353 SkColor pixel = SkColorSetRGB(r, g, b); | |
354 color_counts[pixel]++; | |
355 } | |
356 | |
357 // Now throw out some uninteresting colors. | |
358 std::vector<SkColor> interesting_colors; | |
359 for (auto color_count : color_counts) { | |
360 SkColor color = color_count.first; | |
361 if (IsInterestingColor(color)) | |
362 interesting_colors.push_back(color); | |
363 } | |
364 | |
365 if (interesting_colors.empty()) | |
366 return SK_ColorTRANSPARENT; | |
367 | |
368 // Group the colors into "boxes" and repeatedly split the most voluminous box. | |
369 // We stop the process when a box can no longer be split (there's only one | |
370 // color in it) or when the number of color boxes reaches 12. As per the | |
371 // Android docs, | |
372 // | |
373 // For landscapes, good values are in the range 12-16. For images which | |
374 // are largely made up of people's faces then this value should be increased | |
375 // to 24-32. | |
376 const int kMaxColors = 12; | |
377 // Boxes are sorted by volume with the most voluminous at the front of the PQ. | |
378 std::priority_queue<ColorBox> boxes; | |
379 boxes.emplace(&interesting_colors); | |
380 while (boxes.size() < kMaxColors) { | |
381 auto box = boxes.top(); | |
382 if (!box.CanSplit()) | |
383 break; | |
384 boxes.pop(); | |
385 boxes.push(box.Split()); | |
386 boxes.push(box); | |
387 } | |
388 | |
389 // Now extract a single color to represent each box. This is the average color | |
390 // in the box, weighted by the frequency of that color in the source image. | |
391 std::vector<WeightedColor> box_colors; | |
392 int max_weight = 0; | |
393 while (!boxes.empty()) { | |
394 box_colors.push_back(boxes.top().GetWeightedAverageColor(color_counts)); | |
395 boxes.pop(); | |
396 max_weight = std::max(max_weight, box_colors.back().weight); | |
397 } | |
398 | |
399 // Given these box average colors, find the best one for the desired color | |
400 // profile. "Best" in this case means the color which fits in the provided | |
401 // bounds and comes closest to |goal|. It's possible that no color will fit in | |
402 // the provided bounds, in which case we'll return an empty color. | |
403 double best_suitability = 0; | |
404 SkColor best_color = SK_ColorTRANSPARENT; | |
405 for (const auto& box_color : box_colors) { | |
406 HSL hsl; | |
407 SkColorToHSL(box_color.color, &hsl); | |
408 if (!IsWithinHSLRange(hsl, lower_bound, upper_bound)) | |
409 continue; | |
410 | |
411 double suitability = | |
412 (1 - std::abs(hsl.s - goal.s)) * 3 + | |
413 (1 - std::abs(hsl.l - goal.l)) * 6.5 + | |
414 (box_color.weight / static_cast<float>(max_weight)) * 0.5; | |
415 if (suitability > best_suitability) { | |
416 best_suitability = suitability; | |
417 best_color = box_color.color; | |
418 } | |
419 } | |
420 | |
421 return best_color; | |
422 } | |
423 | |
146 } // namespace | 424 } // namespace |
147 | 425 |
148 KMeanImageSampler::KMeanImageSampler() { | 426 KMeanImageSampler::KMeanImageSampler() { |
149 } | 427 } |
150 | 428 |
151 KMeanImageSampler::~KMeanImageSampler() { | 429 KMeanImageSampler::~KMeanImageSampler() { |
152 } | 430 } |
153 | 431 |
154 GridSampler::GridSampler() : calls_(0) { | 432 GridSampler::GridSampler() : calls_(0) { |
155 } | 433 } |
(...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
394 const HSL& upper_bound, | 672 const HSL& upper_bound, |
395 KMeanImageSampler* sampler) { | 673 KMeanImageSampler* sampler) { |
396 // SkBitmap uses pre-multiplied alpha but the KMean clustering function | 674 // SkBitmap uses pre-multiplied alpha but the KMean clustering function |
397 // above uses non-pre-multiplied alpha. Transform the bitmap before we | 675 // above uses non-pre-multiplied alpha. Transform the bitmap before we |
398 // analyze it because the function reads each pixel multiple times. | 676 // analyze it because the function reads each pixel multiple times. |
399 int pixel_count = bitmap.width() * bitmap.height(); | 677 int pixel_count = bitmap.width() * bitmap.height(); |
400 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); | 678 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); |
401 UnPreMultiply(bitmap, image.get(), pixel_count); | 679 UnPreMultiply(bitmap, image.get(), pixel_count); |
402 | 680 |
403 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), | 681 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), |
404 bitmap.width(), | 682 bitmap.width(), bitmap.height(), |
405 bitmap.height(), | 683 lower_bound, upper_bound, sampler); |
406 lower_bound, | |
407 upper_bound, | |
408 sampler); | |
409 } | 684 } |
410 | 685 |
411 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { | 686 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { |
412 GridSampler sampler; | 687 GridSampler sampler; |
413 return CalculateKMeanColorOfBitmap( | 688 return CalculateKMeanColorOfBitmap( |
414 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); | 689 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); |
415 } | 690 } |
416 | 691 |
692 SkColor CalculateProminentColorOfBitmap(const SkBitmap& bitmap, | |
693 int color_profile) { | |
694 // SkBitmap uses pre-multiplied alpha but the prominent color algorithm | |
695 // above uses non-pre-multiplied alpha. Transform the bitmap before we | |
696 // analyze it because the function reads each pixel multiple times. | |
697 int pixel_count = bitmap.width() * bitmap.height(); | |
698 if (pixel_count == 0) | |
699 return SK_ColorTRANSPARENT; | |
700 | |
701 std::unique_ptr<uint32_t[]> image(new uint32_t[pixel_count]); | |
702 UnPreMultiply(bitmap, image.get(), pixel_count); | |
703 | |
704 // The hue is not relevant to our bounds or goal colors. | |
705 HSL lower_bound = { | |
706 -1, | |
707 }; | |
708 HSL upper_bound = { | |
709 -1, | |
710 }; | |
711 HSL goal = { | |
712 -1, | |
713 }; | |
714 | |
715 // Set the luma. | |
716 if (color_profile & LIGHT) { | |
717 lower_bound.l = 0.55f; | |
718 upper_bound.l = 1; | |
719 goal.l = 0.74f; | |
720 } else if (color_profile & NORMAL) { | |
721 lower_bound.l = 0.3f; | |
722 upper_bound.l = 0.7f; | |
723 goal.l = 0.5f; | |
724 } else { | |
725 DCHECK(color_profile & DARK); | |
726 lower_bound.l = 0; | |
727 upper_bound.l = 0.45f; | |
728 goal.l = 0.26f; | |
729 } | |
730 | |
731 // Set the saturation. | |
732 if (color_profile & VIBRANT) { | |
733 lower_bound.s = 0.35f; | |
734 upper_bound.s = 1; | |
735 goal.s = 1; | |
736 } else { | |
737 DCHECK(color_profile & MUTED); | |
738 lower_bound.s = 0; | |
739 upper_bound.s = 0.4f; | |
740 goal.s = 0.3f; | |
741 } | |
742 | |
743 return CalculateProminentColorOfBuffer( | |
744 reinterpret_cast<uint8_t*>(image.get()), bitmap.width(), bitmap.height(), | |
745 lower_bound, upper_bound, goal); | |
746 } | |
747 | |
417 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { | 748 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { |
418 // First need basic stats to normalize each channel separately. | 749 // First need basic stats to normalize each channel separately. |
419 SkAutoLockPixels bitmap_lock(bitmap); | 750 SkAutoLockPixels bitmap_lock(bitmap); |
420 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); | 751 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); |
421 if (!bitmap.getPixels()) | 752 if (!bitmap.getPixels()) |
422 return covariance; | 753 return covariance; |
423 | 754 |
424 // Assume ARGB_8888 format. | 755 // Assume ARGB_8888 format. |
425 DCHECK(bitmap.colorType() == kN32_SkColorType); | 756 DCHECK(bitmap.colorType() == kN32_SkColorType); |
426 | 757 |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
576 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); | 907 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); |
577 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); | 908 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); |
578 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); | 909 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); |
579 gfx::Vector3dF principal = eigenvectors.get_column(0); | 910 gfx::Vector3dF principal = eigenvectors.get_column(0); |
580 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) | 911 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) |
581 return false; // This may happen for some edge cases. | 912 return false; // This may happen for some edge cases. |
582 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); | 913 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); |
583 } | 914 } |
584 | 915 |
585 } // color_utils | 916 } // color_utils |
OLD | NEW |