Index: ui/gfx/color_analysis.cc |
diff --git a/ui/gfx/color_analysis.cc b/ui/gfx/color_analysis.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b781108a2f4500412d626f3108930a798452c4c6 |
--- /dev/null |
+++ b/ui/gfx/color_analysis.cc |
@@ -0,0 +1,324 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ui/gfx/color_analysis.h" |
+ |
+#include <algorithm> |
+#include <vector> |
+ |
+#include "ui/gfx/codec/png_codec.h" |
+ |
+namespace { |
+ |
+// RGBA KMean Constants |
+const uint32_t kNumberOfClusters = 4; |
+const int kNumberOfIterations = 50; |
+const uint32_t kMaxBrightness = 600; |
+const uint32_t kMinDarkness = 100; |
+ |
+// Background Color Modification Constants |
+const SkColor kDefaultBgColor = SK_ColorWHITE; |
+ |
+// Support class to hold information about each cluster of pixel data in |
+// the KMean algorithm. While this class does not contain all of the points |
+// that exist in the cluster, it keeps track of the aggregate sum so it can |
+// compute the new center appropriately. |
+class KMeanCluster { |
+ public: |
+ KMeanCluster() { |
+ Reset(); |
+ } |
+ |
+ void Reset() { |
+ centroid[0] = centroid[1] = centroid[2] = 0; |
+ aggregate[0] = aggregate[1] = aggregate[2] = 0; |
+ counter = 0; |
+ weight = 0; |
+ } |
+ |
+ inline void SetCentroid(uint8_t r, uint8_t g, uint8_t b) { |
+ centroid[0] = r; |
+ centroid[1] = g; |
+ centroid[2] = b; |
+ } |
+ |
+ inline void GetCentroid(uint8_t* r, uint8_t* g, uint8_t* b) { |
+ *r = centroid[0]; |
+ *g = centroid[1]; |
+ *b = centroid[2]; |
+ } |
+ |
+ inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) { |
+ return r == centroid[0] && g == centroid[1] && b == centroid[2]; |
+ } |
+ |
+ // Recomputes the centroid of the cluster based on the aggregate data. The |
+ // number of points used to calculate this center is stored for weighting |
+ // purposes. The aggregate and counter are then cleared to be ready for the |
+ // next iteration. |
+ inline void RecomputeCentroid() { |
+ if (counter > 0) { |
+ centroid[0] = aggregate[0] / counter; |
+ centroid[1] = aggregate[1] / counter; |
+ centroid[2] = aggregate[2] / counter; |
+ |
+ aggregate[0] = aggregate[1] = aggregate[2] = 0; |
+ weight = counter; |
+ counter = 0; |
+ } |
+ } |
+ |
+ inline void AddPoint(uint8_t r, uint8_t g, uint8_t b) { |
+ aggregate[0] += r; |
+ aggregate[1] += g; |
+ aggregate[2] += b; |
+ ++counter; |
+ } |
+ |
+ // Just returns the distance^2. Since we are comparing relative distances |
+ // there is no need to perform the expensive sqrt() operation. |
+ inline uint32_t GetDistanceSqr(uint8_t r, uint8_t g, uint8_t b) { |
+ return (r - centroid[0]) * (r - centroid[0]) + |
+ (g - centroid[1]) * (g - centroid[1]) + |
+ (b - centroid[2]) * (b - centroid[2]); |
+ } |
+ |
+ // In order to determine if we have hit convergence or not we need to see |
+ // if the centroid of the cluster has moved. This determines whether or |
+ // not the centroid is the same as the aggregate sum of points that will be |
+ // used to generate the next centroid. |
+ inline bool CompareCentroidWithAggregate() { |
+ if (counter == 0) |
+ return false; |
+ |
+ return aggregate[0] / counter == centroid[0] && |
+ aggregate[1] / counter == centroid[1] && |
+ aggregate[2] / counter == centroid[2]; |
+ } |
+ |
+ // Returns the previous counter, which is used to determine the weight |
+ // of the cluster for sorting. |
+ inline uint32_t GetWeight() { |
+ return weight; |
+ } |
+ |
+ static bool SortKMeanClusterByWeight(KMeanCluster a, KMeanCluster b) { |
+ return a.GetWeight() > b.GetWeight(); |
+ } |
+ |
+ private: |
+ uint8_t centroid[3]; |
+ |
+ // Holds the sum of all the points that make up this cluster. Used to |
+ // generate the next centroid as well as to check for convergence. |
+ uint32_t aggregate[3]; |
+ uint32_t counter; |
+ |
+ // The weight of the cluster, determined by how many points were used |
+ // to generate the previous centroid. |
+ uint32_t weight; |
+}; |
+ |
+} // namespace |
+ |
+namespace color_utils { |
+ |
+KMeanImageSampler::KMeanImageSampler() { |
+} |
+ |
+KMeanImageSampler::~KMeanImageSampler() { |
+} |
+ |
+RandomSampler::RandomSampler() { |
+} |
+ |
+RandomSampler::~RandomSampler() { |
+} |
+ |
+int RandomSampler::GetSample(int width, int height) { |
+ return rand(); |
+} |
+ |
+GridSampler::GridSampler() : calls_(0) { |
+} |
+ |
+GridSampler::~GridSampler() { |
+} |
+ |
+int GridSampler::GetSample(int width, int height) { |
+ calls_++; |
+ // We may keep getting called after we've gone of the edge of the grid; in |
+ // this case we offset future return values by the number of times we've gone |
+ // off the grid. |
+ return (width * height * calls_ / kNumberOfClusters) % (width * height) + |
+ calls_ / kNumberOfClusters; |
+} |
+ |
+SkColor CalculateRecommendedBgColorForPNG( |
+ scoped_refptr<RefCountedMemory> png) { |
+ RandomSampler sampler; |
+ return CalculateRecommendedBgColorForPNG(png, sampler); |
+} |
+ |
+SkColor CalculateKMeanColorOfPNG(scoped_refptr<RefCountedMemory> png, |
+ uint32_t darkness_limit, |
+ uint32_t brightness_limit) { |
+ RandomSampler sampler; |
+ return CalculateKMeanColorOfPNG(png, darkness_limit, brightness_limit, |
+ sampler); |
+} |
+ |
+SkColor CalculateRecommendedBgColorForPNG( |
+ scoped_refptr<RefCountedMemory> png, |
+ KMeanImageSampler& sampler) { |
+ return CalculateKMeanColorOfPNG(png, |
+ kMinDarkness, |
+ kMaxBrightness, |
+ sampler); |
+} |
+ |
+SkColor CalculateKMeanColorOfPNG(scoped_refptr<RefCountedMemory> png, |
+ uint32_t darkness_limit, |
+ uint32_t brightness_limit, |
+ KMeanImageSampler& sampler) { |
+ int img_width, img_height; |
+ std::vector<uint8_t> decoded_data; |
+ SkColor color = kDefaultBgColor; |
+ |
+ if (png.get() && |
+ png->size() && |
+ gfx::PNGCodec::Decode(png->front(), |
+ png->size(), |
+ gfx::PNGCodec::FORMAT_BGRA, |
+ &decoded_data, |
+ &img_width, |
+ &img_height)) { |
+ std::vector<KMeanCluster> clusters; |
+ clusters.resize(kNumberOfClusters, KMeanCluster()); |
+ |
+ // Pick a starting point for each cluster |
+ std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
+ while (cluster != clusters.end()) { |
+ // Try up to 10 times to find a unique color. If no unique color can be |
+ // found, destroy this cluster. |
+ bool color_unique = false; |
+ for (int i = 0; i < 10; ++i) { |
+ int pixel_pos = sampler.GetSample(img_width, img_height) % |
+ (img_width * img_height); |
+ |
+ uint8_t b = decoded_data[pixel_pos * 4]; |
+ uint8_t g = decoded_data[pixel_pos * 4 + 1]; |
+ uint8_t r = decoded_data[pixel_pos * 4 + 2]; |
+ |
+ // Loop through the previous clusters and check to see if we have seen |
+ // this color before. |
+ color_unique = true; |
+ for (std::vector<KMeanCluster>::iterator |
+ cluster_check = clusters.begin(); |
+ cluster_check != cluster; ++cluster_check) { |
+ if (cluster_check->IsAtCentroid(r, g, b)) { |
+ color_unique = false; |
+ break; |
+ } |
+ } |
+ |
+ // If we have a unique color set the center of the cluster to |
+ // that color. |
+ if (color_unique) { |
+ cluster->SetCentroid(r, g, b); |
+ break; |
+ } |
+ } |
+ |
+ // If we don't have a unique color erase this cluster. |
+ if (!color_unique) { |
+ cluster = clusters.erase(cluster); |
+ } else { |
+ // Have to increment the iterator here, otherwise the increment in the |
+ // for loop will skip a cluster due to the erase if the color wasn't |
+ // unique. |
+ ++cluster; |
+ } |
+ } |
+ |
+ bool convergence = false; |
+ for (int iteration = 0; |
+ iteration < kNumberOfIterations && !convergence && !clusters.empty(); |
+ ++iteration) { |
+ |
+ // Loop through each pixel so we can place it in the appropriate cluster. |
+ std::vector<uint8_t>::iterator pixel = decoded_data.begin(); |
+ while (pixel != decoded_data.end()) { |
+ uint8_t b = *(pixel++); |
+ if (pixel == decoded_data.end()) |
+ continue; |
+ uint8_t g = *(pixel++); |
+ if (pixel == decoded_data.end()) |
+ continue; |
+ uint8_t r = *(pixel++); |
+ if (pixel == decoded_data.end()) |
+ continue; |
+ ++pixel; // Ignore the alpha channel. |
+ |
+ uint32_t distance_sqr_to_closest_cluster = UINT_MAX; |
+ std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin(); |
+ |
+ // Figure out which cluster this color is closest to in RGB space. |
+ for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
+ cluster != clusters.end(); ++cluster) { |
+ uint32_t distance_sqr = cluster->GetDistanceSqr(r, g, b); |
+ |
+ if (distance_sqr < distance_sqr_to_closest_cluster) { |
+ distance_sqr_to_closest_cluster = distance_sqr; |
+ closest_cluster = cluster; |
+ } |
+ } |
+ |
+ closest_cluster->AddPoint(r, g, b); |
+ } |
+ |
+ // Calculate the new cluster centers and see if we've converged or not. |
+ convergence = true; |
+ for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
+ cluster != clusters.end(); ++cluster) { |
+ convergence &= cluster->CompareCentroidWithAggregate(); |
+ |
+ cluster->RecomputeCentroid(); |
+ } |
+ } |
+ |
+ // Sort the clusters by population so we can tell what the most popular |
+ // color is. |
+ std::sort(clusters.begin(), clusters.end(), |
+ KMeanCluster::SortKMeanClusterByWeight); |
+ |
+ // Loop through the clusters to figure out which cluster has an appropriate |
+ // color. Skip any that are too bright/dark and go in order of weight. |
+ for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); |
+ cluster != clusters.end(); ++cluster) { |
+ uint8_t r, g, b; |
+ cluster->GetCentroid(&r, &g, &b); |
+ // Sum the RGB components to determine if the color is too bright or too |
+ // dark. |
+ // TODO (dtrainor): Look into using HSV here instead. This approximation |
+ // might be fine though. |
+ uint32_t summed_color = r + g + b; |
+ |
+ if (summed_color < brightness_limit && summed_color > darkness_limit) { |
+ // If we found a valid color just set it and break. We don't want to |
+ // check the other ones. |
+ color = SkColorSetARGB(0xFF, r, g, b); |
+ break; |
+ } else if (cluster == clusters.begin()) { |
+ // We haven't found a valid color, but we are at the first color so |
+ // set the color anyway to make sure we at least have a value here. |
+ color = SkColorSetARGB(0xFF, r, g, b); |
+ } |
+ } |
+ } |
+ |
+ return color; |
+} |
+ |
+} // color_utils |