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