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 3 // found in the LICENSE file. 4 5 #include "ui/gfx/color_analysis.h" 6 7 #include <algorithm> 8 #include <limits> 9 #include <vector> 10 11 #include "base/logging.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "third_party/skia/include/core/SkBitmap.h" 14 #include "third_party/skia/include/core/SkUnPreMultiply.h" 15 #include "ui/gfx/codec/png_codec.h" 16 #include "ui/gfx/color_utils.h" 17 18 namespace color_utils { 19 namespace { 20 21 // RGBA KMean Constants 22 const uint32_t kNumberOfClusters = 4; 23 const int kNumberOfIterations = 50; 24 25 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15}; 26 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85}; 27 28 // Background Color Modification Constants 29 const SkColor kDefaultBgColor = SK_ColorWHITE; 30 31 // Support class to hold information about each cluster of pixel data in 32 // the KMean algorithm. While this class does not contain all of the points 33 // that exist in the cluster, it keeps track of the aggregate sum so it can 34 // compute the new center appropriately. 35 class KMeanCluster { 36 public: 37 KMeanCluster() { 38 Reset(); 39 } 40 41 void Reset() { 42 centroid[0] = centroid[1] = centroid[2] = 0; 43 aggregate[0] = aggregate[1] = aggregate[2] = 0; 44 counter = 0; 45 weight = 0; 46 } 47 48 inline void SetCentroid(uint8_t r, uint8_t g, uint8_t b) { 49 centroid[0] = r; 50 centroid[1] = g; 51 centroid[2] = b; 52 } 53 54 inline void GetCentroid(uint8_t* r, uint8_t* g, uint8_t* b) { 55 *r = centroid[0]; 56 *g = centroid[1]; 57 *b = centroid[2]; 58 } 59 60 inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) { 61 return r == centroid[0] && g == centroid[1] && b == centroid[2]; 62 } 63 64 // Recomputes the centroid of the cluster based on the aggregate data. The 65 // number of points used to calculate this center is stored for weighting 66 // purposes. The aggregate and counter are then cleared to be ready for the 67 // next iteration. 68 inline void RecomputeCentroid() { 69 if (counter > 0) { 70 centroid[0] = aggregate[0] / counter; 71 centroid[1] = aggregate[1] / counter; 72 centroid[2] = aggregate[2] / counter; 73 74 aggregate[0] = aggregate[1] = aggregate[2] = 0; 75 weight = counter; 76 counter = 0; 77 } 78 } 79 80 inline void AddPoint(uint8_t r, uint8_t g, uint8_t b) { 81 aggregate[0] += r; 82 aggregate[1] += g; 83 aggregate[2] += b; 84 ++counter; 85 } 86 87 // Just returns the distance^2. Since we are comparing relative distances 88 // there is no need to perform the expensive sqrt() operation. 89 inline uint32_t GetDistanceSqr(uint8_t r, uint8_t g, uint8_t b) { 90 return (r - centroid[0]) * (r - centroid[0]) + 91 (g - centroid[1]) * (g - centroid[1]) + 92 (b - centroid[2]) * (b - centroid[2]); 93 } 94 95 // In order to determine if we have hit convergence or not we need to see 96 // if the centroid of the cluster has moved. This determines whether or 97 // not the centroid is the same as the aggregate sum of points that will be 98 // used to generate the next centroid. 99 inline bool CompareCentroidWithAggregate() { 100 if (counter == 0) 101 return false; 102 103 return aggregate[0] / counter == centroid[0] && 104 aggregate[1] / counter == centroid[1] && 105 aggregate[2] / counter == centroid[2]; 106 } 107 108 // Returns the previous counter, which is used to determine the weight 109 // of the cluster for sorting. 110 inline uint32_t GetWeight() const { 111 return weight; 112 } 113 114 static bool SortKMeanClusterByWeight(const KMeanCluster& a, 115 const KMeanCluster& b) { 116 return a.GetWeight() > b.GetWeight(); 117 } 118 119 private: 120 uint8_t centroid[3]; 121 122 // Holds the sum of all the points that make up this cluster. Used to 123 // generate the next centroid as well as to check for convergence. 124 uint32_t aggregate[3]; 125 uint32_t counter; 126 127 // The weight of the cluster, determined by how many points were used 128 // to generate the previous centroid. 129 uint32_t weight; 130 }; 131 132 // Un-premultiplies each pixel in |bitmap| into an output |buffer|. Requires 133 // approximately 10 microseconds for a 16x16 icon on an Intel Core i5. 134 void UnPreMultiply(const SkBitmap& bitmap, uint32_t* buffer, int buffer_size) { 135 SkAutoLockPixels auto_lock(bitmap); 136 uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels()); 137 uint32_t* out = buffer; 138 int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size); 139 for (int i = 0; i < pixel_count; ++i) 140 *out++ = SkUnPreMultiply::PMColorToColor(*in++); 141 } 142 143 } // namespace 144 145 KMeanImageSampler::KMeanImageSampler() { 146 } 147 148 KMeanImageSampler::~KMeanImageSampler() { 149 } 150 151 GridSampler::GridSampler() : calls_(0) { 152 } 153 154 GridSampler::~GridSampler() { 155 } 156 157 int GridSampler::GetSample(int width, int height) { 158 // Hand-drawn bitmaps often have special outlines or feathering at the edges. 159 // Start our sampling inset from the top and left edges. For example, a 10x10 160 // image with 4 clusters would be sampled like this: 161 // .......... 162 // .0.4.8.... 163 // .......... 164 // .1.5.9.... 165 // .......... 166 // .2.6...... 167 // .......... 168 // .3.7...... 169 // .......... 170 const int kPadX = 1; 171 const int kPadY = 1; 172 int x = kPadX + 173 (calls_ / kNumberOfClusters) * ((width - 2 * kPadX) / kNumberOfClusters); 174 int y = kPadY + 175 (calls_ % kNumberOfClusters) * ((height - 2 * kPadY) / kNumberOfClusters); 176 int index = x + (y * width); 177 ++calls_; 178 return index % (width * height); 179 } 180 181 SkColor FindClosestColor(const uint8_t* image, 182 int width, 183 int height, 184 SkColor color) { 185 uint8_t in_r = SkColorGetR(color); 186 uint8_t in_g = SkColorGetG(color); 187 uint8_t in_b = SkColorGetB(color); 188 // Search using distance-squared to avoid expensive sqrt() operations. 189 int best_distance_squared = kint32max; 190 SkColor best_color = color; 191 const uint8_t* byte = image; 192 for (int i = 0; i < width * height; ++i) { 193 uint8_t b = *(byte++); 194 uint8_t g = *(byte++); 195 uint8_t r = *(byte++); 196 uint8_t a = *(byte++); 197 // Ignore fully transparent pixels. 198 if (a == 0) 199 continue; 200 int distance_squared = 201 (in_b - b) * (in_b - b) + 202 (in_g - g) * (in_g - g) + 203 (in_r - r) * (in_r - r); 204 if (distance_squared < best_distance_squared) { 205 best_distance_squared = distance_squared; 206 best_color = SkColorSetRGB(r, g, b); 207 } 208 } 209 return best_color; 210 } 211 212 // For a 16x16 icon on an Intel Core i5 this function takes approximately 213 // 0.5 ms to run. 214 // TODO(port): This code assumes the CPU architecture is little-endian. 215 SkColor CalculateKMeanColorOfBuffer(uint8_t* decoded_data, 216 int img_width, 217 int img_height, 218 const HSL& lower_bound, 219 const HSL& upper_bound, 220 KMeanImageSampler* sampler) { 221 SkColor color = kDefaultBgColor; 222 if (img_width > 0 && img_height > 0) { 223 std::vector<KMeanCluster> clusters; 224 clusters.resize(kNumberOfClusters, KMeanCluster()); 225 226 // Pick a starting point for each cluster 227 std::vector<KMeanCluster>::iterator cluster = clusters.begin(); 228 while (cluster != clusters.end()) { 229 // Try up to 10 times to find a unique color. If no unique color can be 230 // found, destroy this cluster. 231 bool color_unique = false; 232 for (int i = 0; i < 10; ++i) { 233 int pixel_pos = sampler->GetSample(img_width, img_height) % 234 (img_width * img_height); 235 236 uint8_t b = decoded_data[pixel_pos * 4]; 237 uint8_t g = decoded_data[pixel_pos * 4 + 1]; 238 uint8_t r = decoded_data[pixel_pos * 4 + 2]; 239 uint8_t a = decoded_data[pixel_pos * 4 + 3]; 240 // Skip fully transparent pixels as they usually contain black in their 241 // RGB channels but do not contribute to the visual image. 242 if (a == 0) 243 continue; 244 245 // Loop through the previous clusters and check to see if we have seen 246 // this color before. 247 color_unique = true; 248 for (std::vector<KMeanCluster>::iterator 249 cluster_check = clusters.begin(); 250 cluster_check != cluster; ++cluster_check) { 251 if (cluster_check->IsAtCentroid(r, g, b)) { 252 color_unique = false; 253 break; 254 } 255 } 256 257 // If we have a unique color set the center of the cluster to 258 // that color. 259 if (color_unique) { 260 cluster->SetCentroid(r, g, b); 261 break; 262 } 263 } 264 265 // If we don't have a unique color erase this cluster. 266 if (!color_unique) { 267 cluster = clusters.erase(cluster); 268 } else { 269 // Have to increment the iterator here, otherwise the increment in the 270 // for loop will skip a cluster due to the erase if the color wasn't 271 // unique. 272 ++cluster; 273 } 274 } 275 276 // If all pixels in the image are transparent we will have no clusters. 277 if (clusters.empty()) 278 return color; 279 280 bool convergence = false; 281 for (int iteration = 0; 282 iteration < kNumberOfIterations && !convergence; 283 ++iteration) { 284 285 // Loop through each pixel so we can place it in the appropriate cluster. 286 uint8_t* pixel = decoded_data; 287 uint8_t* decoded_data_end = decoded_data + (img_width * img_height * 4); 288 while (pixel < decoded_data_end) { 289 uint8_t b = *(pixel++); 290 uint8_t g = *(pixel++); 291 uint8_t r = *(pixel++); 292 uint8_t a = *(pixel++); 293 // Skip transparent pixels, see above. 294 if (a == 0) 295 continue; 296 297 uint32_t distance_sqr_to_closest_cluster = UINT_MAX; 298 std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin(); 299 300 // Figure out which cluster this color is closest to in RGB space. 301 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); 302 cluster != clusters.end(); ++cluster) { 303 uint32_t distance_sqr = cluster->GetDistanceSqr(r, g, b); 304 305 if (distance_sqr < distance_sqr_to_closest_cluster) { 306 distance_sqr_to_closest_cluster = distance_sqr; 307 closest_cluster = cluster; 308 } 309 } 310 311 closest_cluster->AddPoint(r, g, b); 312 } 313 314 // Calculate the new cluster centers and see if we've converged or not. 315 convergence = true; 316 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); 317 cluster != clusters.end(); ++cluster) { 318 convergence &= cluster->CompareCentroidWithAggregate(); 319 320 cluster->RecomputeCentroid(); 321 } 322 } 323 324 // Sort the clusters by population so we can tell what the most popular 325 // color is. 326 std::sort(clusters.begin(), clusters.end(), 327 KMeanCluster::SortKMeanClusterByWeight); 328 329 // Loop through the clusters to figure out which cluster has an appropriate 330 // color. Skip any that are too bright/dark and go in order of weight. 331 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); 332 cluster != clusters.end(); ++cluster) { 333 uint8_t r, g, b; 334 cluster->GetCentroid(&r, &g, &b); 335 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)) { 340 // If we found a valid color just set it and break. We don't want to 341 // check the other ones. 342 color = current_color; 343 break; 344 } else if (cluster == clusters.begin()) { 345 // We haven't found a valid color, but we are at the first color so 346 // set the color anyway to make sure we at least have a value here. 347 color = current_color; 348 } 349 } 350 } 351 352 // Find a color that actually appears in the image (the K-mean cluster center 353 // will not usually be a color that appears in the image). 354 return FindClosestColor(decoded_data, img_width, img_height, color); 355 } 356 357 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png, 358 const HSL& lower_bound, 359 const HSL& upper_bound, 360 KMeanImageSampler* sampler) { 361 int img_width = 0; 362 int img_height = 0; 363 std::vector<uint8_t> decoded_data; 364 SkColor color = kDefaultBgColor; 365 366 if (png.get() && png->size() && 367 gfx::PNGCodec::Decode(png->front(), 368 png->size(), 369 gfx::PNGCodec::FORMAT_BGRA, 370 &decoded_data, 371 &img_width, 372 &img_height)) { 373 return CalculateKMeanColorOfBuffer(&decoded_data[0], 374 img_width, 375 img_height, 376 lower_bound, 377 upper_bound, 378 sampler); 379 } 380 return color; 381 } 382 383 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png) { 384 GridSampler sampler; 385 return CalculateKMeanColorOfPNG( 386 png, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); 387 } 388 389 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap, 390 const HSL& lower_bound, 391 const HSL& upper_bound, 392 KMeanImageSampler* sampler) { 393 // SkBitmap uses pre-multiplied alpha but the KMean clustering function 394 // above uses non-pre-multiplied alpha. Transform the bitmap before we 395 // analyze it because the function reads each pixel multiple times. 396 int pixel_count = bitmap.width() * bitmap.height(); 397 scoped_ptr<uint32_t[]> image(new uint32_t[pixel_count]); 398 UnPreMultiply(bitmap, image.get(), pixel_count); 399 400 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()), 401 bitmap.width(), 402 bitmap.height(), 403 lower_bound, 404 upper_bound, 405 sampler); 406 } 407 408 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) { 409 GridSampler sampler; 410 return CalculateKMeanColorOfBitmap( 411 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler); 412 } 413 414 gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) { 415 // First need basic stats to normalize each channel separately. 416 SkAutoLockPixels bitmap_lock(bitmap); 417 gfx::Matrix3F covariance = gfx::Matrix3F::Zeros(); 418 if (!bitmap.getPixels()) 419 return covariance; 420 421 // Assume ARGB_8888 format. 422 DCHECK(bitmap.colorType() == kPMColor_SkColorType); 423 424 int64_t r_sum = 0; 425 int64_t g_sum = 0; 426 int64_t b_sum = 0; 427 int64_t rr_sum = 0; 428 int64_t gg_sum = 0; 429 int64_t bb_sum = 0; 430 int64_t rg_sum = 0; 431 int64_t rb_sum = 0; 432 int64_t gb_sum = 0; 433 434 for (int y = 0; y < bitmap.height(); ++y) { 435 SkPMColor* current_color = static_cast<uint32_t*>(bitmap.getAddr32(0, y)); 436 for (int x = 0; x < bitmap.width(); ++x, ++current_color) { 437 SkColor c = SkUnPreMultiply::PMColorToColor(*current_color); 438 SkColor r = SkColorGetR(c); 439 SkColor g = SkColorGetG(c); 440 SkColor b = SkColorGetB(c); 441 442 r_sum += r; 443 g_sum += g; 444 b_sum += b; 445 rr_sum += r * r; 446 gg_sum += g * g; 447 bb_sum += b * b; 448 rg_sum += r * g; 449 rb_sum += r * b; 450 gb_sum += g * b; 451 } 452 } 453 454 // Covariance (not normalized) is E(X*X.t) - m * m.t and this is how it 455 // is calculated below. 456 // Each row below represents a row of the matrix describing (co)variances 457 // of R, G and B channels with (R, G, B) 458 int pixel_n = bitmap.width() * bitmap.height(); 459 covariance.set( 460 (static_cast<double>(rr_sum) / pixel_n - 461 static_cast<double>(r_sum * r_sum) / pixel_n / pixel_n), 462 (static_cast<double>(rg_sum) / pixel_n - 463 static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n), 464 (static_cast<double>(rb_sum) / pixel_n - 465 static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n), 466 (static_cast<double>(rg_sum) / pixel_n - 467 static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n), 468 (static_cast<double>(gg_sum) / pixel_n - 469 static_cast<double>(g_sum * g_sum) / pixel_n / pixel_n), 470 (static_cast<double>(gb_sum) / pixel_n - 471 static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n), 472 (static_cast<double>(rb_sum) / pixel_n - 473 static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n), 474 (static_cast<double>(gb_sum) / pixel_n - 475 static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n), 476 (static_cast<double>(bb_sum) / pixel_n - 477 static_cast<double>(b_sum * b_sum) / pixel_n / pixel_n)); 478 return covariance; 479 } 480 481 bool ApplyColorReduction(const SkBitmap& source_bitmap, 482 const gfx::Vector3dF& color_transform, 483 bool fit_to_range, 484 SkBitmap* target_bitmap) { 485 DCHECK(target_bitmap); 486 SkAutoLockPixels source_lock(source_bitmap); 487 SkAutoLockPixels target_lock(*target_bitmap); 488 489 DCHECK(source_bitmap.getPixels()); 490 DCHECK(target_bitmap->getPixels()); 491 DCHECK_EQ(kPMColor_SkColorType, source_bitmap.colorType()); 492 DCHECK_EQ(kAlpha_8_SkColorType, target_bitmap->colorType()); 493 DCHECK_EQ(source_bitmap.height(), target_bitmap->height()); 494 DCHECK_EQ(source_bitmap.width(), target_bitmap->width()); 495 DCHECK(!source_bitmap.empty()); 496 497 // Elements of color_transform are explicitly off-loaded to local values for 498 // efficiency reasons. Note that in practice images may correspond to entire 499 // tab captures. 500 float t0 = 0.0; 501 float tr = color_transform.x(); 502 float tg = color_transform.y(); 503 float tb = color_transform.z(); 504 505 if (fit_to_range) { 506 // We will figure out min/max in a preprocessing step and adjust 507 // actual_transform as required. 508 float max_val = std::numeric_limits<float>::min(); 509 float min_val = std::numeric_limits<float>::max(); 510 for (int y = 0; y < source_bitmap.height(); ++y) { 511 const SkPMColor* source_color_row = static_cast<SkPMColor*>( 512 source_bitmap.getAddr32(0, y)); 513 for (int x = 0; x < source_bitmap.width(); ++x) { 514 SkColor c = SkUnPreMultiply::PMColorToColor(source_color_row[x]); 515 float r = SkColorGetR(c); 516 float g = SkColorGetG(c); 517 float b = SkColorGetB(c); 518 float gray_level = tr * r + tg * g + tb * b; 519 max_val = std::max(max_val, gray_level); 520 min_val = std::min(min_val, gray_level); 521 } 522 } 523 524 // Adjust the transform so that the result is scaling. 525 float scale = 0.0; 526 t0 = -min_val; 527 if (max_val > min_val) 528 scale = 255.0 / (max_val - min_val); 529 t0 *= scale; 530 tr *= scale; 531 tg *= scale; 532 tb *= scale; 533 } 534 535 for (int y = 0; y < source_bitmap.height(); ++y) { 536 const SkPMColor* source_color_row = static_cast<SkPMColor*>( 537 source_bitmap.getAddr32(0, y)); 538 uint8_t* target_color_row = target_bitmap->getAddr8(0, y); 539 for (int x = 0; x < source_bitmap.width(); ++x) { 540 SkColor c = SkUnPreMultiply::PMColorToColor(source_color_row[x]); 541 float r = SkColorGetR(c); 542 float g = SkColorGetG(c); 543 float b = SkColorGetB(c); 544 545 float gl = t0 + tr * r + tg * g + tb * b; 546 if (gl < 0) 547 gl = 0; 548 if (gl > 0xFF) 549 gl = 0xFF; 550 target_color_row[x] = static_cast<uint8_t>(gl); 551 } 552 } 553 554 return true; 555 } 556 557 bool ComputePrincipalComponentImage(const SkBitmap& source_bitmap, 558 SkBitmap* target_bitmap) { 559 if (!target_bitmap) { 560 NOTREACHED(); 561 return false; 562 } 563 564 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap); 565 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros(); 566 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors); 567 gfx::Vector3dF principal = eigenvectors.get_column(0); 568 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF()) 569 return false; // This may happen for some edge cases. 570 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap); 571 } 572 573 } // color_utils 574