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