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