summaryrefslogtreecommitdiffstats
path: root/ui/gfx/color_analysis.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ui/gfx/color_analysis.cc')
-rw-r--r--ui/gfx/color_analysis.cc324
1 files changed, 324 insertions, 0 deletions
diff --git a/ui/gfx/color_analysis.cc b/ui/gfx/color_analysis.cc
new file mode 100644
index 0000000..b781108
--- /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