• 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 #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:
KMeanCluster()37   KMeanCluster() {
38     Reset();
39   }
40 
Reset()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 
SetCentroid(uint8_t r,uint8_t g,uint8_t b)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 
GetCentroid(uint8_t * r,uint8_t * g,uint8_t * b)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 
IsAtCentroid(uint8_t r,uint8_t g,uint8_t b)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.
RecomputeCentroid()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 
AddPoint(uint8_t r,uint8_t g,uint8_t b)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.
GetDistanceSqr(uint8_t r,uint8_t g,uint8_t b)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.
CompareCentroidWithAggregate()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.
GetWeight() const110   inline uint32_t GetWeight() const {
111     return weight;
112   }
113 
SortKMeanClusterByWeight(const KMeanCluster & a,const KMeanCluster & b)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.
UnPreMultiply(const SkBitmap & bitmap,uint32_t * buffer,int buffer_size)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 
KMeanImageSampler()145 KMeanImageSampler::KMeanImageSampler() {
146 }
147 
~KMeanImageSampler()148 KMeanImageSampler::~KMeanImageSampler() {
149 }
150 
GridSampler()151 GridSampler::GridSampler() : calls_(0) {
152 }
153 
~GridSampler()154 GridSampler::~GridSampler() {
155 }
156 
GetSample(int width,int height)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 
FindClosestColor(const uint8_t * image,int width,int height,SkColor color)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.
CalculateKMeanColorOfBuffer(uint8_t * decoded_data,int img_width,int img_height,const HSL & lower_bound,const HSL & upper_bound,KMeanImageSampler * sampler)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 
CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png,const HSL & lower_bound,const HSL & upper_bound,KMeanImageSampler * sampler)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 
CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png)383 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png) {
384   GridSampler sampler;
385   return CalculateKMeanColorOfPNG(
386       png, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler);
387 }
388 
CalculateKMeanColorOfBitmap(const SkBitmap & bitmap,const HSL & lower_bound,const HSL & upper_bound,KMeanImageSampler * sampler)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 
CalculateKMeanColorOfBitmap(const SkBitmap & bitmap)408 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) {
409   GridSampler sampler;
410   return CalculateKMeanColorOfBitmap(
411       bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler);
412 }
413 
ComputeColorCovariance(const SkBitmap & bitmap)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 
ApplyColorReduction(const SkBitmap & source_bitmap,const gfx::Vector3dF & color_transform,bool fit_to_range,SkBitmap * target_bitmap)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 
ComputePrincipalComponentImage(const SkBitmap & source_bitmap,SkBitmap * target_bitmap)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