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 <algorithm> | 7 #include <algorithm> |
8 #include <limits> | 8 #include <limits> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
13 #include "third_party/skia/include/core/SkBitmap.h" | 13 #include "third_party/skia/include/core/SkBitmap.h" |
14 #include "third_party/skia/include/core/SkUnPreMultiply.h" | 14 #include "third_party/skia/include/core/SkUnPreMultiply.h" |
15 #include "ui/gfx/codec/png_codec.h" | 15 #include "ui/gfx/codec/png_codec.h" |
| 16 #include "ui/gfx/color_utils.h" |
16 | 17 |
| 18 namespace color_utils { |
17 namespace { | 19 namespace { |
18 | 20 |
19 // RGBA KMean Constants | 21 // RGBA KMean Constants |
20 const uint32_t kNumberOfClusters = 4; | 22 const uint32_t kNumberOfClusters = 4; |
21 const int kNumberOfIterations = 50; | 23 const int kNumberOfIterations = 50; |
22 const uint32_t kMaxBrightness = 665; | 24 |
23 const uint32_t kMinDarkness = 100; | 25 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; |
| 26 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; |
24 | 27 |
25 // Background Color Modification Constants | 28 // Background Color Modification Constants |
26 const SkColor kDefaultBgColor = SK_ColorWHITE; | 29 const SkColor kDefaultBgColor = SK_ColorWHITE; |
27 | 30 |
28 // Support class to hold information about each cluster of pixel data in | 31 // Support class to hold information about each cluster of pixel data in |
29 // the KMean algorithm. While this class does not contain all of the points | 32 // the KMean algorithm. While this class does not contain all of the points |
30 // that exist in the cluster, it keeps track of the aggregate sum so it can | 33 // that exist in the cluster, it keeps track of the aggregate sum so it can |
31 // compute the new center appropriately. | 34 // compute the new center appropriately. |
32 class KMeanCluster { | 35 class KMeanCluster { |
33 public: | 36 public: |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
132 SkAutoLockPixels auto_lock(bitmap); | 135 SkAutoLockPixels auto_lock(bitmap); |
133 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); | 136 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); |
134 uint32_t* out = buffer; | 137 uint32_t* out = buffer; |
135 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); | 138 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); |
136 for (int i = 0; i < pixel_count; ++i) | 139 for (int i = 0; i < pixel_count; ++i) |
137 *out++ = SkUnPreMultiply::PMColorToColor(*in++); | 140 *out++ = SkUnPreMultiply::PMColorToColor(*in++); |
138 } | 141 } |
139 | 142 |
140 } // namespace | 143 } // namespace |
141 | 144 |
142 namespace color_utils { | |
143 | |
144 KMeanImageSampler::KMeanImageSampler() { | 145 KMeanImageSampler::KMeanImageSampler() { |
145 } | 146 } |
146 | 147 |
147 KMeanImageSampler::~KMeanImageSampler() { | 148 KMeanImageSampler::~KMeanImageSampler() { |
148 } | 149 } |
149 | 150 |
150 GridSampler::GridSampler() : calls_(0) { | 151 GridSampler::GridSampler() : calls_(0) { |
151 } | 152 } |
152 | 153 |
153 GridSampler::~GridSampler() { | 154 GridSampler::~GridSampler() { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
207 } | 208 } |
208 return best_color; | 209 return best_color; |
209 } | 210 } |
210 | 211 |
211 // For a 16x16 icon on an Intel Core i5 this function takes approximately | 212 // For a 16x16 icon on an Intel Core i5 this function takes approximately |
212 // 0.5 ms to run. | 213 // 0.5 ms to run. |
213 // TODO(port): This code assumes the CPU architecture is little-endian. | 214 // TODO(port): This code assumes the CPU architecture is little-endian. |
214 SkColor CalculateKMeanColorOfBuffer(uint8_t* decoded_data, | 215 SkColor CalculateKMeanColorOfBuffer(uint8_t* decoded_data, |
215 int img_width, | 216 int img_width, |
216 int img_height, | 217 int img_height, |
217 uint32_t darkness_limit, | 218 const HSL& lower_bound, |
218 uint32_t brightness_limit, | 219 const HSL& upper_bound, |
219 KMeanImageSampler* sampler) { | 220 KMeanImageSampler* sampler) { |
220 SkColor color = kDefaultBgColor; | 221 SkColor color = kDefaultBgColor; |
221 if (img_width > 0 && img_height > 0) { | 222 if (img_width > 0 && img_height > 0) { |
222 std::vector<KMeanCluster> clusters; | 223 std::vector<KMeanCluster> clusters; |
223 clusters.resize(kNumberOfClusters, KMeanCluster()); | 224 clusters.resize(kNumberOfClusters, KMeanCluster()); |
224 | 225 |
225 // Pick a starting point for each cluster | 226 // Pick a starting point for each cluster |
226 std::vector<KMeanCluster>::iterator cluster = clusters.begin(); | 227 std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
227 while (cluster != clusters.end()) { | 228 while (cluster != clusters.end()) { |
228 // Try up to 10 times to find a unique color. If no unique color can be | 229 // Try up to 10 times to find a unique color. If no unique color can be |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
324 // color is. | 325 // color is. |
325 std::sort(clusters.begin(), clusters.end(), | 326 std::sort(clusters.begin(), clusters.end(), |
326 KMeanCluster::SortKMeanClusterByWeight); | 327 KMeanCluster::SortKMeanClusterByWeight); |
327 | 328 |
328 // Loop through the clusters to figure out which cluster has an appropriate | 329 // Loop through the clusters to figure out which cluster has an appropriate |
329 // color. Skip any that are too bright/dark and go in order of weight. | 330 // color. Skip any that are too bright/dark and go in order of weight. |
330 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); | 331 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
331 cluster != clusters.end(); ++cluster) { | 332 cluster != clusters.end(); ++cluster) { |
332 uint8_t r, g, b; | 333 uint8_t r, g, b; |
333 cluster->GetCentroid(&r, &g, &b); | 334 cluster->GetCentroid(&r, &g, &b); |
334 // Sum the RGB components to determine if the color is too bright or too | |
335 // dark. | |
336 // TODO (dtrainor): Look into using HSV here instead. This approximation | |
337 // might be fine though. | |
338 uint32_t summed_color = r + g + b; | |
339 | 335 |
340 if (summed_color < brightness_limit && summed_color > darkness_limit) { | 336 SkColor current_color = SkColorSetARGB(SK_AlphaOPAQUE, r, g, b); |
| 337 HSL hsl; |
| 338 SkColorToHSL(current_color, &hsl); |
| 339 if (IsWithinHSLRange(hsl, lower_bound, upper_bound)) { |
341 // If we found a valid color just set it and break. We don't want to | 340 // If we found a valid color just set it and break. We don't want to |
342 // check the other ones. | 341 // check the other ones. |
343 color = SkColorSetARGB(0xFF, r, g, b); | 342 color = current_color; |
344 break; | 343 break; |
345 } else if (cluster == clusters.begin()) { | 344 } else if (cluster == clusters.begin()) { |
346 // We haven't found a valid color, but we are at the first color so | 345 // We haven't found a valid color, but we are at the first color so |
347 // set the color anyway to make sure we at least have a value here. | 346 // set the color anyway to make sure we at least have a value here. |
348 color = SkColorSetARGB(0xFF, r, g, b); | 347 color = current_color; |
349 } | 348 } |
350 } | 349 } |
351 } | 350 } |
352 | 351 |
353 // Find a color that actually appears in the image (the K-mean cluster center | 352 // Find a color that actually appears in the image (the K-mean cluster center |
354 // will not usually be a color that appears in the image). | 353 // will not usually be a color that appears in the image). |
355 return FindClosestColor(decoded_data, img_width, img_height, color); | 354 return FindClosestColor(decoded_data, img_width, img_height, color); |
356 } | 355 } |
357 | 356 |
358 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png, | 357 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png, |
359 uint32_t darkness_limit, | 358 const HSL& lower_bound, |
360 uint32_t brightness_limit, | 359 const HSL& upper_bound, |
361 KMeanImageSampler* sampler) { | 360 KMeanImageSampler* sampler) { |
362 int img_width = 0; | 361 int img_width = 0; |
363 int img_height = 0; | 362 int img_height = 0; |
364 std::vector<uint8_t> decoded_data; | 363 std::vector<uint8_t> decoded_data; |
365 SkColor color = kDefaultBgColor; | 364 SkColor color = kDefaultBgColor; |
366 | 365 |
367 if (png.get() && | 366 if (png.get() && png->size() && |
368 png->size() && | |
369 gfx::PNGCodec::Decode(png->front(), | 367 gfx::PNGCodec::Decode(png->front(), |
370 png->size(), | 368 png->size(), |
371 gfx::PNGCodec::FORMAT_BGRA, | 369 gfx::PNGCodec::FORMAT_BGRA, |
372 &decoded_data, | 370 &decoded_data, |
373 &img_width, | 371 &img_width, |
374 &img_height)) { | 372 &img_height)) { |
375 return CalculateKMeanColorOfBuffer(&decoded_data[0], | 373 return CalculateKMeanColorOfBuffer(&decoded_data[0], |
376 img_width, | 374 img_width, |
377 img_height, | 375 img_height, |
378 darkness_limit, | 376 lower_bound, |
379 brightness_limit, | 377 upper_bound, |
380 sampler); | 378 sampler); |
381 } | 379 } |
382 return color; | 380 return color; |
383 } | 381 } |
384 | 382 |
385 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png) { | 383 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png) { |
386 GridSampler sampler; | 384 GridSampler sampler; |
387 return CalculateKMeanColorOfPNG(png, kMinDarkness, kMaxBrightness, &sampler); | 385 return CalculateKMeanColorOfPNG( |
| 386 png, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); |
388 } | 387 } |
389 | 388 |
390 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap, | 389 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap, |
391 uint32_t darkness_limit, | 390 const HSL& lower_bound, |
392 uint32_t brightness_limit, | 391 const HSL& upper_bound, |
393 KMeanImageSampler* sampler) { | 392 KMeanImageSampler* sampler) { |
394 // SkBitmap uses pre-multiplied alpha but the KMean clustering function | 393 // SkBitmap uses pre-multiplied alpha but the KMean clustering function |
395 // above uses non-pre-multiplied alpha. Transform the bitmap before we | 394 // above uses non-pre-multiplied alpha. Transform the bitmap before we |
396 // analyze it because the function reads each pixel multiple times. | 395 // analyze it because the function reads each pixel multiple times. |
397 int pixel_count = bitmap.width() * bitmap.height(); | 396 int pixel_count = bitmap.width() * bitmap.height(); |
398 scoped_ptr<uint32_t[]> image(new uint32_t[pixel_count]); | 397 scoped_ptr<uint32_t[]> image(new uint32_t[pixel_count]); |
399 UnPreMultiply(bitmap, image.get(), pixel_count); | 398 UnPreMultiply(bitmap, image.get(), pixel_count); |
400 | 399 |
401 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), | 400 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), |
402 bitmap.width(), | 401 bitmap.width(), |
403 bitmap.height(), | 402 bitmap.height(), |
404 darkness_limit, | 403 lower_bound, |
405 brightness_limit, | 404 upper_bound, |
406 sampler); | 405 sampler); |
407 } | 406 } |
408 | 407 |
409 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { | 408 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { |
410 GridSampler sampler; | 409 GridSampler sampler; |
411 return CalculateKMeanColorOfBitmap( | 410 return CalculateKMeanColorOfBitmap( |
412 bitmap, kMinDarkness, kMaxBrightness, &sampler); | 411 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); |
413 } | 412 } |
414 | 413 |
415 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { | 414 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { |
416 // First need basic stats to normalize each channel separately. | 415 // First need basic stats to normalize each channel separately. |
417 SkAutoLockPixels bitmap_lock(bitmap); | 416 SkAutoLockPixels bitmap_lock(bitmap); |
418 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); | 417 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); |
419 if (!bitmap.getPixels()) | 418 if (!bitmap.getPixels()) |
420 return covariance; | 419 return covariance; |
421 | 420 |
422 // Assume ARGB_8888 format. | 421 // Assume ARGB_8888 format. |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
565 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); | 564 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); |
566 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); | 565 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); |
567 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); | 566 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); |
568 gfx::Vector3dF principal = eigenvectors.get_column(0); | 567 gfx::Vector3dF principal = eigenvectors.get_column(0); |
569 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) | 568 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) |
570 return false; // This may happen for some edge cases. | 569 return false; // This may happen for some edge cases. |
571 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); | 570 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); |
572 } | 571 } |
573 | 572 |
574 } // color_utils | 573 } // color_utils |
OLD | NEW |