Home | History | Annotate | Download | only in gfx
      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:
     34   KMeanCluster() {
     35     Reset();
     36   }
     37 
     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 
     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 
     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 
     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.
     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 
     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.
     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.
     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.
    107   inline uint32_t GetWeight() const {
    108     return weight;
    109   }
    110 
    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.
    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 
    144 KMeanImageSampler::KMeanImageSampler() {
    145 }
    146 
    147 KMeanImageSampler::~KMeanImageSampler() {
    148 }
    149 
    150 GridSampler::GridSampler() : calls_(0) {
    151 }
    152 
    153 GridSampler::~GridSampler() {
    154 }
    155 
    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 
    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.
    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 
    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 
    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 
    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 
    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 
    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