[Cronet] Delay StartNetLog and StopNetLog until native request context is initialized
[chromium-blink-merge.git] / ui / gfx / color_analysis.cc
blob354ca84bdabee492c8309c3d7262b2e718985eed
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/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"
19 namespace color_utils {
20 namespace {
22 // RGBA KMean Constants
23 const uint32_t kNumberOfClusters = 4;
24 const int kNumberOfIterations = 50;
26 const HSL kDefaultLowerHSLBound = {-1, -1, 0.15};
27 const HSL kDefaultUpperHSLBound = {-1, -1, 0.85};
29 // Background Color Modification Constants
30 const SkColor kDefaultBgColor = SK_ColorWHITE;
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();
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;
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;
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];
61 inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) {
62 return r == centroid_[0] && g == centroid_[1] && b == centroid_[2];
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] = static_cast<uint8_t>(aggregate_[0] / counter_);
72 centroid_[1] = static_cast<uint8_t>(aggregate_[1] / counter_);
73 centroid_[2] = static_cast<uint8_t>(aggregate_[2] / counter_);
75 aggregate_[0] = aggregate_[1] = aggregate_[2] = 0;
76 weight_ = counter_;
77 counter_ = 0;
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_;
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]);
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;
104 return aggregate_[0] / counter_ == centroid_[0] &&
105 aggregate_[1] / counter_ == centroid_[1] &&
106 aggregate_[2] / counter_ == centroid_[2];
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_;
115 static bool SortKMeanClusterByWeight(const KMeanCluster& a,
116 const KMeanCluster& b) {
117 return a.GetWeight() > b.GetWeight();
120 private:
121 uint8_t centroid_[3];
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_;
128 // The weight of the cluster, determined by how many points were used
129 // to generate the previous centroid.
130 uint32_t weight_;
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++;
148 } // namespace
150 KMeanImageSampler::KMeanImageSampler() {
153 KMeanImageSampler::~KMeanImageSampler() {
156 GridSampler::GridSampler() : calls_(0) {
159 GridSampler::~GridSampler() {
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);
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);
214 return best_color;
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());
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);
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;
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;
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;
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;
281 // If all pixels in the image are transparent we will have no clusters.
282 if (clusters.empty())
283 return color;
285 bool convergence = false;
286 for (int iteration = 0;
287 iteration < kNumberOfIterations && !convergence;
288 ++iteration) {
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;
302 uint32_t distance_sqr_to_closest_cluster = UINT_MAX;
303 std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin();
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);
310 if (distance_sqr < distance_sqr_to_closest_cluster) {
311 distance_sqr_to_closest_cluster = distance_sqr;
312 closest_cluster = cluster;
316 closest_cluster->AddPoint(r, g, b);
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();
325 cluster->RecomputeCentroid();
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);
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);
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;
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);
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;
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);
385 return color;
388 SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png) {
389 GridSampler sampler;
390 return CalculateKMeanColorOfPNG(
391 png, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler);
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);
405 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image.get()),
406 bitmap.width(),
407 bitmap.height(),
408 lower_bound,
409 upper_bound,
410 sampler);
413 SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) {
414 GridSampler sampler;
415 return CalculateKMeanColorOfBitmap(
416 bitmap, kDefaultLowerHSLBound, kDefaultUpperHSLBound, &sampler);
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;
426 // Assume ARGB_8888 format.
427 DCHECK(bitmap.colorType() == kN32_SkColorType);
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;
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;
449 SkColor r = SkColorGetR(c);
450 SkColor g = SkColorGetG(c);
451 SkColor b = SkColorGetB(c);
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;
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<float>(
472 static_cast<double>(rr_sum) / pixel_n -
473 static_cast<double>(r_sum * r_sum) / pixel_n / pixel_n),
474 static_cast<float>(
475 static_cast<double>(rg_sum) / pixel_n -
476 static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n),
477 static_cast<float>(
478 static_cast<double>(rb_sum) / pixel_n -
479 static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n),
480 static_cast<float>(
481 static_cast<double>(rg_sum) / pixel_n -
482 static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n),
483 static_cast<float>(
484 static_cast<double>(gg_sum) / pixel_n -
485 static_cast<double>(g_sum * g_sum) / pixel_n / pixel_n),
486 static_cast<float>(
487 static_cast<double>(gb_sum) / pixel_n -
488 static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n),
489 static_cast<float>(
490 static_cast<double>(rb_sum) / pixel_n -
491 static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n),
492 static_cast<float>(
493 static_cast<double>(gb_sum) / pixel_n -
494 static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n),
495 static_cast<float>(
496 static_cast<double>(bb_sum) / pixel_n -
497 static_cast<double>(b_sum * b_sum) / pixel_n / pixel_n));
498 return covariance;
501 bool ApplyColorReduction(const SkBitmap& source_bitmap,
502 const gfx::Vector3dF& color_transform,
503 bool fit_to_range,
504 SkBitmap* target_bitmap) {
505 DCHECK(target_bitmap);
506 SkAutoLockPixels source_lock(source_bitmap);
507 SkAutoLockPixels target_lock(*target_bitmap);
509 DCHECK(source_bitmap.getPixels());
510 DCHECK(target_bitmap->getPixels());
511 DCHECK_EQ(kN32_SkColorType, source_bitmap.colorType());
512 DCHECK_EQ(kAlpha_8_SkColorType, target_bitmap->colorType());
513 DCHECK_EQ(source_bitmap.height(), target_bitmap->height());
514 DCHECK_EQ(source_bitmap.width(), target_bitmap->width());
515 DCHECK(!source_bitmap.empty());
517 // Elements of color_transform are explicitly off-loaded to local values for
518 // efficiency reasons. Note that in practice images may correspond to entire
519 // tab captures.
520 float t0 = 0.0;
521 float tr = color_transform.x();
522 float tg = color_transform.y();
523 float tb = color_transform.z();
525 if (fit_to_range) {
526 // We will figure out min/max in a preprocessing step and adjust
527 // actual_transform as required.
528 float max_val = std::numeric_limits<float>::min();
529 float min_val = std::numeric_limits<float>::max();
530 for (int y = 0; y < source_bitmap.height(); ++y) {
531 const SkPMColor* source_color_row = static_cast<SkPMColor*>(
532 source_bitmap.getAddr32(0, y));
533 for (int x = 0; x < source_bitmap.width(); ++x) {
534 SkColor c;
535 int alpha = SkGetPackedA32(source_color_row[x]);
536 if (alpha != 0 && alpha != 255)
537 c = SkUnPreMultiply::PMColorToColor(source_color_row[x]);
538 else
539 c = source_color_row[x];
541 uint8_t r = SkColorGetR(c);
542 uint8_t g = SkColorGetG(c);
543 uint8_t b = SkColorGetB(c);
544 float gray_level = tr * r + tg * g + tb * b;
545 max_val = std::max(max_val, gray_level);
546 min_val = std::min(min_val, gray_level);
550 // Adjust the transform so that the result is scaling.
551 float scale = 0.0;
552 t0 = -min_val;
553 if (max_val > min_val)
554 scale = 255.0f / (max_val - min_val);
555 t0 *= scale;
556 tr *= scale;
557 tg *= scale;
558 tb *= scale;
561 for (int y = 0; y < source_bitmap.height(); ++y) {
562 const SkPMColor* source_color_row = static_cast<SkPMColor*>(
563 source_bitmap.getAddr32(0, y));
564 uint8_t* target_color_row = target_bitmap->getAddr8(0, y);
565 for (int x = 0; x < source_bitmap.width(); ++x) {
566 SkColor c;
567 int alpha = SkGetPackedA32(source_color_row[x]);
568 if (alpha != 0 && alpha != 255)
569 c = SkUnPreMultiply::PMColorToColor(source_color_row[x]);
570 else
571 c = source_color_row[x];
573 uint8_t r = SkColorGetR(c);
574 uint8_t g = SkColorGetG(c);
575 uint8_t b = SkColorGetB(c);
577 float gl = t0 + tr * r + tg * g + tb * b;
578 if (gl < 0)
579 gl = 0;
580 if (gl > 0xFF)
581 gl = 0xFF;
582 target_color_row[x] = static_cast<uint8_t>(gl);
586 return true;
589 bool ComputePrincipalComponentImage(const SkBitmap& source_bitmap,
590 SkBitmap* target_bitmap) {
591 if (!target_bitmap) {
592 NOTREACHED();
593 return false;
596 gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap);
597 gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros();
598 gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors);
599 gfx::Vector3dF principal = eigenvectors.get_column(0);
600 if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF())
601 return false; // This may happen for some edge cases.
602 return ApplyColorReduction(source_bitmap, principal, true, target_bitmap);
605 } // color_utils