Disable flaky test ExtensionActionContextMenuTest.RunInspectPopup
[chromium-blink-merge.git] / ui / gfx / color_analysis.cc
blobf8987cfb546defe1e1ef5b6a2785f00673d8140e
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.
5 #include "ui/gfx/color_analysis.h"
7 #include <algorithm>
8 #include <limits>
9 #include <vector>
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"
17 namespace {
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;
25 // Background Color Modification Constants
26 const SkColor kDefaultBgColor = SK_ColorWHITE;
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();
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;
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;
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];
57 inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) {
58 return r == centroid[0] && g == centroid[1] && b == centroid[2];
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;
71 aggregate[0] = aggregate[1] = aggregate[2] = 0;
72 weight = counter;
73 counter = 0;
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;
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]);
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;
100 return aggregate[0] / counter == centroid[0] &&
101 aggregate[1] / counter == centroid[1] &&
102 aggregate[2] / counter == centroid[2];
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;
111 static bool SortKMeanClusterByWeight(const KMeanCluster& a,
112 const KMeanCluster& b) {
113 return a.GetWeight() > b.GetWeight();
116 private:
117 uint8_t centroid[3];
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;
124 // The weight of the cluster, determined by how many points were used
125 // to generate the previous centroid.
126 uint32_t weight;
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++);
140 } // namespace
142 namespace color_utils {
144 KMeanImageSampler::KMeanImageSampler() {
147 KMeanImageSampler::~KMeanImageSampler() {
150 GridSampler::GridSampler() : calls_(0) {
153 GridSampler::~GridSampler() {
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);
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);
208 return best_color;
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());
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);
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;
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;
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;
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;
275 // If all pixels in the image are transparent we will have no clusters.
276 if (clusters.empty())
277 return color;
279 bool convergence = false;
280 for (int iteration = 0;
281 iteration < kNumberOfIterations && !convergence;
282 ++iteration) {
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;
296 uint32_t distance_sqr_to_closest_cluster = UINT_MAX;
297 std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin();
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);
304 if (distance_sqr < distance_sqr_to_closest_cluster) {
305 distance_sqr_to_closest_cluster = distance_sqr;
306 closest_cluster = cluster;
310 closest_cluster->AddPoint(r, g, b);
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();
319 cluster->RecomputeCentroid();
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);
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;
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);
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);
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;
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);
382 return color;
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);
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;
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;
411 // Assume ARGB_8888 format.
412 DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config);
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;
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);
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;
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;
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);
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());
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();
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);
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;
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);
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);
544 return true;
547 bool ComputePrincipalComponentImage(const SkBitmap& source_bitmap,
548 SkBitmap* target_bitmap) {
549 if (!target_bitmap) {
550 NOTREACHED();
551 return false;
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);
563 } // color_utils