OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 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 <vector> |
| 9 |
| 10 #include "ui/gfx/codec/png_codec.h" |
| 11 |
| 12 namespace { |
| 13 |
| 14 // RGBA KMean Constants |
| 15 const uint32_t kNumberOfClusters = 4; |
| 16 const int kNumberOfIterations = 50; |
| 17 const uint32_t kMaxBrightness = 600; |
| 18 const uint32_t kMinDarkness = 100; |
| 19 |
| 20 // Background Color Modification Constants |
| 21 const SkColor kDefaultBgColor = SK_ColorWHITE; |
| 22 |
| 23 // Support class to hold information about each cluster of pixel data in |
| 24 // the KMean algorithm. While this class does not contain all of the points |
| 25 // that exist in the cluster, it keeps track of the aggregate sum so it can |
| 26 // compute the new center appropriately. |
| 27 class KMeanCluster { |
| 28 public: |
| 29 KMeanCluster() { |
| 30 Reset(); |
| 31 } |
| 32 |
| 33 void Reset() { |
| 34 centroid[0] = centroid[1] = centroid[2] = 0; |
| 35 aggregate[0] = aggregate[1] = aggregate[2] = 0; |
| 36 counter = 0; |
| 37 weight = 0; |
| 38 } |
| 39 |
| 40 inline void SetCentroid(uint8_t r, uint8_t g, uint8_t b) { |
| 41 centroid[0] = r; |
| 42 centroid[1] = g; |
| 43 centroid[2] = b; |
| 44 } |
| 45 |
| 46 inline void GetCentroid(uint8_t* r, uint8_t* g, uint8_t* b) { |
| 47 *r = centroid[0]; |
| 48 *g = centroid[1]; |
| 49 *b = centroid[2]; |
| 50 } |
| 51 |
| 52 inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) { |
| 53 return r == centroid[0] && g == centroid[1] && b == centroid[2]; |
| 54 } |
| 55 |
| 56 // Recomputes the centroid of the cluster based on the aggregate data. The |
| 57 // number of points used to calculate this center is stored for weighting |
| 58 // purposes. The aggregate and counter are then cleared to be ready for the |
| 59 // next iteration. |
| 60 inline void RecomputeCentroid() { |
| 61 if (counter > 0) { |
| 62 centroid[0] = aggregate[0] / counter; |
| 63 centroid[1] = aggregate[1] / counter; |
| 64 centroid[2] = aggregate[2] / counter; |
| 65 |
| 66 aggregate[0] = aggregate[1] = aggregate[2] = 0; |
| 67 weight = counter; |
| 68 counter = 0; |
| 69 } |
| 70 } |
| 71 |
| 72 inline void AddPoint(uint8_t r, uint8_t g, uint8_t b) { |
| 73 aggregate[0] += r; |
| 74 aggregate[1] += g; |
| 75 aggregate[2] += b; |
| 76 ++counter; |
| 77 } |
| 78 |
| 79 // Just returns the distance^2. Since we are comparing relative distances |
| 80 // there is no need to perform the expensive sqrt() operation. |
| 81 inline uint32_t GetDistanceSqr(uint8_t r, uint8_t g, uint8_t b) { |
| 82 return (r - centroid[0]) * (r - centroid[0]) + |
| 83 (g - centroid[1]) * (g - centroid[1]) + |
| 84 (b - centroid[2]) * (b - centroid[2]); |
| 85 } |
| 86 |
| 87 // In order to determine if we have hit convergence or not we need to see |
| 88 // if the centroid of the cluster has moved. This determines whether or |
| 89 // not the centroid is the same as the aggregate sum of points that will be |
| 90 // used to generate the next centroid. |
| 91 inline bool CompareCentroidWithAggregate() { |
| 92 if (counter == 0) |
| 93 return false; |
| 94 |
| 95 return aggregate[0] / counter == centroid[0] && |
| 96 aggregate[1] / counter == centroid[1] && |
| 97 aggregate[2] / counter == centroid[2]; |
| 98 } |
| 99 |
| 100 // Returns the previous counter, which is used to determine the weight |
| 101 // of the cluster for sorting. |
| 102 inline uint32_t GetWeight() { |
| 103 return weight; |
| 104 } |
| 105 |
| 106 static bool SortKMeanClusterByWeight(KMeanCluster a, KMeanCluster b) { |
| 107 return a.GetWeight() > b.GetWeight(); |
| 108 } |
| 109 |
| 110 private: |
| 111 uint8_t centroid[3]; |
| 112 |
| 113 // Holds the sum of all the points that make up this cluster. Used to |
| 114 // generate the next centroid as well as to check for convergence. |
| 115 uint32_t aggregate[3]; |
| 116 uint32_t counter; |
| 117 |
| 118 // The weight of the cluster, determined by how many points were used |
| 119 // to generate the previous centroid. |
| 120 uint32_t weight; |
| 121 }; |
| 122 |
| 123 } // namespace |
| 124 |
| 125 namespace color_utils { |
| 126 |
| 127 KMeanImageSampler::KMeanImageSampler() { |
| 128 } |
| 129 |
| 130 KMeanImageSampler::~KMeanImageSampler() { |
| 131 } |
| 132 |
| 133 RandomSampler::RandomSampler() { |
| 134 } |
| 135 |
| 136 RandomSampler::~RandomSampler() { |
| 137 } |
| 138 |
| 139 int RandomSampler::GetSample(int width, int height) { |
| 140 return rand(); |
| 141 } |
| 142 |
| 143 GridSampler::GridSampler() : calls_(0) { |
| 144 } |
| 145 |
| 146 GridSampler::~GridSampler() { |
| 147 } |
| 148 |
| 149 int GridSampler::GetSample(int width, int height) { |
| 150 calls_++; |
| 151 // We may keep getting called after we've gone of the edge of the grid; in |
| 152 // this case we offset future return values by the number of times we've gone |
| 153 // off the grid. |
| 154 return (width * height * calls_ / kNumberOfClusters) % (width * height) + |
| 155 calls_ / kNumberOfClusters; |
| 156 } |
| 157 |
| 158 SkColor CalculateRecommendedBgColorForPNG( |
| 159 scoped_refptr<RefCountedMemory> png) { |
| 160 RandomSampler sampler; |
| 161 return CalculateRecommendedBgColorForPNG(png, sampler); |
| 162 } |
| 163 |
| 164 SkColor CalculateKMeanColorOfPNG(scoped_refptr<RefCountedMemory> png, |
| 165 uint32_t darkness_limit, |
| 166 uint32_t brightness_limit) { |
| 167 RandomSampler sampler; |
| 168 return CalculateKMeanColorOfPNG(png, darkness_limit, brightness_limit, |
| 169 sampler); |
| 170 } |
| 171 |
| 172 SkColor CalculateRecommendedBgColorForPNG( |
| 173 scoped_refptr<RefCountedMemory> png, |
| 174 KMeanImageSampler& sampler) { |
| 175 return CalculateKMeanColorOfPNG(png, |
| 176 kMinDarkness, |
| 177 kMaxBrightness, |
| 178 sampler); |
| 179 } |
| 180 |
| 181 SkColor CalculateKMeanColorOfPNG(scoped_refptr<RefCountedMemory> png, |
| 182 uint32_t darkness_limit, |
| 183 uint32_t brightness_limit, |
| 184 KMeanImageSampler& sampler) { |
| 185 int img_width, img_height; |
| 186 std::vector<uint8_t> decoded_data; |
| 187 SkColor color = kDefaultBgColor; |
| 188 |
| 189 if (png.get() && |
| 190 png->size() && |
| 191 gfx::PNGCodec::Decode(png->front(), |
| 192 png->size(), |
| 193 gfx::PNGCodec::FORMAT_BGRA, |
| 194 &decoded_data, |
| 195 &img_width, |
| 196 &img_height)) { |
| 197 std::vector<KMeanCluster> clusters; |
| 198 clusters.resize(kNumberOfClusters, KMeanCluster()); |
| 199 |
| 200 // Pick a starting point for each cluster |
| 201 std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
| 202 while (cluster != clusters.end()) { |
| 203 // Try up to 10 times to find a unique color. If no unique color can be |
| 204 // found, destroy this cluster. |
| 205 bool color_unique = false; |
| 206 for (int i = 0; i < 10; ++i) { |
| 207 int pixel_pos = sampler.GetSample(img_width, img_height) % |
| 208 (img_width * img_height); |
| 209 |
| 210 uint8_t b = decoded_data[pixel_pos * 4]; |
| 211 uint8_t g = decoded_data[pixel_pos * 4 + 1]; |
| 212 uint8_t r = decoded_data[pixel_pos * 4 + 2]; |
| 213 |
| 214 // Loop through the previous clusters and check to see if we have seen |
| 215 // this color before. |
| 216 color_unique = true; |
| 217 for (std::vector<KMeanCluster>::iterator |
| 218 cluster_check = clusters.begin(); |
| 219 cluster_check != cluster; ++cluster_check) { |
| 220 if (cluster_check->IsAtCentroid(r, g, b)) { |
| 221 color_unique = false; |
| 222 break; |
| 223 } |
| 224 } |
| 225 |
| 226 // If we have a unique color set the center of the cluster to |
| 227 // that color. |
| 228 if (color_unique) { |
| 229 cluster->SetCentroid(r, g, b); |
| 230 break; |
| 231 } |
| 232 } |
| 233 |
| 234 // If we don't have a unique color erase this cluster. |
| 235 if (!color_unique) { |
| 236 cluster = clusters.erase(cluster); |
| 237 } else { |
| 238 // Have to increment the iterator here, otherwise the increment in the |
| 239 // for loop will skip a cluster due to the erase if the color wasn't |
| 240 // unique. |
| 241 ++cluster; |
| 242 } |
| 243 } |
| 244 |
| 245 bool convergence = false; |
| 246 for (int iteration = 0; |
| 247 iteration < kNumberOfIterations && !convergence && !clusters.empty(); |
| 248 ++iteration) { |
| 249 |
| 250 // Loop through each pixel so we can place it in the appropriate cluster. |
| 251 std::vector<uint8_t>::iterator pixel = decoded_data.begin(); |
| 252 while (pixel != decoded_data.end()) { |
| 253 uint8_t b = *(pixel++); |
| 254 if (pixel == decoded_data.end()) |
| 255 continue; |
| 256 uint8_t g = *(pixel++); |
| 257 if (pixel == decoded_data.end()) |
| 258 continue; |
| 259 uint8_t r = *(pixel++); |
| 260 if (pixel == decoded_data.end()) |
| 261 continue; |
| 262 ++pixel; // Ignore the alpha channel. |
| 263 |
| 264 uint32_t distance_sqr_to_closest_cluster = UINT_MAX; |
| 265 std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin(); |
| 266 |
| 267 // Figure out which cluster this color is closest to in RGB space. |
| 268 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
| 269 cluster != clusters.end(); ++cluster) { |
| 270 uint32_t distance_sqr = cluster->GetDistanceSqr(r, g, b); |
| 271 |
| 272 if (distance_sqr < distance_sqr_to_closest_cluster) { |
| 273 distance_sqr_to_closest_cluster = distance_sqr; |
| 274 closest_cluster = cluster; |
| 275 } |
| 276 } |
| 277 |
| 278 closest_cluster->AddPoint(r, g, b); |
| 279 } |
| 280 |
| 281 // Calculate the new cluster centers and see if we've converged or not. |
| 282 convergence = true; |
| 283 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
| 284 cluster != clusters.end(); ++cluster) { |
| 285 convergence &= cluster->CompareCentroidWithAggregate(); |
| 286 |
| 287 cluster->RecomputeCentroid(); |
| 288 } |
| 289 } |
| 290 |
| 291 // Sort the clusters by population so we can tell what the most popular |
| 292 // color is. |
| 293 std::sort(clusters.begin(), clusters.end(), |
| 294 KMeanCluster::SortKMeanClusterByWeight); |
| 295 |
| 296 // Loop through the clusters to figure out which cluster has an appropriate |
| 297 // color. Skip any that are too bright/dark and go in order of weight. |
| 298 for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
| 299 cluster != clusters.end(); ++cluster) { |
| 300 uint8_t r, g, b; |
| 301 cluster->GetCentroid(&r, &g, &b); |
| 302 // Sum the RGB components to determine if the color is too bright or too |
| 303 // dark. |
| 304 // TODO (dtrainor): Look into using HSV here instead. This approximation |
| 305 // might be fine though. |
| 306 uint32_t summed_color = r + g + b; |
| 307 |
| 308 if (summed_color < brightness_limit && summed_color > darkness_limit) { |
| 309 // If we found a valid color just set it and break. We don't want to |
| 310 // check the other ones. |
| 311 color = SkColorSetARGB(0xFF, r, g, b); |
| 312 break; |
| 313 } else if (cluster == clusters.begin()) { |
| 314 // We haven't found a valid color, but we are at the first color so |
| 315 // set the color anyway to make sure we at least have a value here. |
| 316 color = SkColorSetARGB(0xFF, r, g, b); |
| 317 } |
| 318 } |
| 319 } |
| 320 |
| 321 return color; |
| 322 } |
| 323 |
| 324 } // color_utils |
OLD | NEW |