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"
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"
16 #include "ui/gfx/color_utils.h"
18 namespace color_utils
{
21 // RGBA KMean Constants
22 const uint32_t kNumberOfClusters
= 4;
23 const int kNumberOfIterations
= 50;
25 const HSL kDefaultLowerHSLBound
= {-1, -1, 0.15};
26 const HSL kDefaultUpperHSLBound
= {-1, -1, 0.85};
28 // Background Color Modification Constants
29 const SkColor kDefaultBgColor
= SK_ColorWHITE
;
31 // Support class to hold information about each cluster of pixel data in
32 // the KMean algorithm. While this class does not contain all of the points
33 // that exist in the cluster, it keeps track of the aggregate sum so it can
34 // compute the new center appropriately.
42 centroid
[0] = centroid
[1] = centroid
[2] = 0;
43 aggregate
[0] = aggregate
[1] = aggregate
[2] = 0;
48 inline void SetCentroid(uint8_t r
, uint8_t g
, uint8_t b
) {
54 inline void GetCentroid(uint8_t* r
, uint8_t* g
, uint8_t* b
) {
60 inline bool IsAtCentroid(uint8_t r
, uint8_t g
, uint8_t b
) {
61 return r
== centroid
[0] && g
== centroid
[1] && b
== centroid
[2];
64 // Recomputes the centroid of the cluster based on the aggregate data. The
65 // number of points used to calculate this center is stored for weighting
66 // purposes. The aggregate and counter are then cleared to be ready for the
68 inline void RecomputeCentroid() {
70 centroid
[0] = aggregate
[0] / counter
;
71 centroid
[1] = aggregate
[1] / counter
;
72 centroid
[2] = aggregate
[2] / counter
;
74 aggregate
[0] = aggregate
[1] = aggregate
[2] = 0;
80 inline void AddPoint(uint8_t r
, uint8_t g
, uint8_t b
) {
87 // Just returns the distance^2. Since we are comparing relative distances
88 // there is no need to perform the expensive sqrt() operation.
89 inline uint32_t GetDistanceSqr(uint8_t r
, uint8_t g
, uint8_t b
) {
90 return (r
- centroid
[0]) * (r
- centroid
[0]) +
91 (g
- centroid
[1]) * (g
- centroid
[1]) +
92 (b
- centroid
[2]) * (b
- centroid
[2]);
95 // In order to determine if we have hit convergence or not we need to see
96 // if the centroid of the cluster has moved. This determines whether or
97 // not the centroid is the same as the aggregate sum of points that will be
98 // used to generate the next centroid.
99 inline bool CompareCentroidWithAggregate() {
103 return aggregate
[0] / counter
== centroid
[0] &&
104 aggregate
[1] / counter
== centroid
[1] &&
105 aggregate
[2] / counter
== centroid
[2];
108 // Returns the previous counter, which is used to determine the weight
109 // of the cluster for sorting.
110 inline uint32_t GetWeight() const {
114 static bool SortKMeanClusterByWeight(const KMeanCluster
& a
,
115 const KMeanCluster
& b
) {
116 return a
.GetWeight() > b
.GetWeight();
122 // Holds the sum of all the points that make up this cluster. Used to
123 // generate the next centroid as well as to check for convergence.
124 uint32_t aggregate
[3];
127 // The weight of the cluster, determined by how many points were used
128 // to generate the previous centroid.
132 // Un-premultiplies each pixel in |bitmap| into an output |buffer|. Requires
133 // approximately 10 microseconds for a 16x16 icon on an Intel Core i5.
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 *out
++ = SkUnPreMultiply::PMColorToColor(*in
++);
145 KMeanImageSampler::KMeanImageSampler() {
148 KMeanImageSampler::~KMeanImageSampler() {
151 GridSampler::GridSampler() : calls_(0) {
154 GridSampler::~GridSampler() {
157 int GridSampler::GetSample(int width
, int height
) {
158 // Hand-drawn bitmaps often have special outlines or feathering at the edges.
159 // Start our sampling inset from the top and left edges. For example, a 10x10
160 // image with 4 clusters would be sampled like this:
173 (calls_
/ kNumberOfClusters
) * ((width
- 2 * kPadX
) / kNumberOfClusters
);
175 (calls_
% kNumberOfClusters
) * ((height
- 2 * kPadY
) / kNumberOfClusters
);
176 int index
= x
+ (y
* width
);
178 return index
% (width
* height
);
181 SkColor
FindClosestColor(const uint8_t* image
,
185 uint8_t in_r
= SkColorGetR(color
);
186 uint8_t in_g
= SkColorGetG(color
);
187 uint8_t in_b
= SkColorGetB(color
);
188 // Search using distance-squared to avoid expensive sqrt() operations.
189 int best_distance_squared
= kint32max
;
190 SkColor best_color
= color
;
191 const uint8_t* byte
= image
;
192 for (int i
= 0; i
< width
* height
; ++i
) {
193 uint8_t b
= *(byte
++);
194 uint8_t g
= *(byte
++);
195 uint8_t r
= *(byte
++);
196 uint8_t a
= *(byte
++);
197 // Ignore fully transparent pixels.
200 int distance_squared
=
201 (in_b
- b
) * (in_b
- b
) +
202 (in_g
- g
) * (in_g
- g
) +
203 (in_r
- r
) * (in_r
- r
);
204 if (distance_squared
< best_distance_squared
) {
205 best_distance_squared
= distance_squared
;
206 best_color
= SkColorSetRGB(r
, g
, b
);
212 // For a 16x16 icon on an Intel Core i5 this function takes approximately
214 // TODO(port): This code assumes the CPU architecture is little-endian.
215 SkColor
CalculateKMeanColorOfBuffer(uint8_t* decoded_data
,
218 const HSL
& lower_bound
,
219 const HSL
& upper_bound
,
220 KMeanImageSampler
* sampler
) {
221 SkColor color
= kDefaultBgColor
;
222 if (img_width
> 0 && img_height
> 0) {
223 std::vector
<KMeanCluster
> clusters
;
224 clusters
.resize(kNumberOfClusters
, KMeanCluster());
226 // Pick a starting point for each cluster
227 std::vector
<KMeanCluster
>::iterator cluster
= clusters
.begin();
228 while (cluster
!= clusters
.end()) {
229 // Try up to 10 times to find a unique color. If no unique color can be
230 // found, destroy this cluster.
231 bool color_unique
= false;
232 for (int i
= 0; i
< 10; ++i
) {
233 int pixel_pos
= sampler
->GetSample(img_width
, img_height
) %
234 (img_width
* img_height
);
236 uint8_t b
= decoded_data
[pixel_pos
* 4];
237 uint8_t g
= decoded_data
[pixel_pos
* 4 + 1];
238 uint8_t r
= decoded_data
[pixel_pos
* 4 + 2];
239 uint8_t a
= decoded_data
[pixel_pos
* 4 + 3];
240 // Skip fully transparent pixels as they usually contain black in their
241 // RGB channels but do not contribute to the visual image.
245 // Loop through the previous clusters and check to see if we have seen
246 // this color before.
248 for (std::vector
<KMeanCluster
>::iterator
249 cluster_check
= clusters
.begin();
250 cluster_check
!= cluster
; ++cluster_check
) {
251 if (cluster_check
->IsAtCentroid(r
, g
, b
)) {
252 color_unique
= false;
257 // If we have a unique color set the center of the cluster to
260 cluster
->SetCentroid(r
, g
, b
);
265 // If we don't have a unique color erase this cluster.
267 cluster
= clusters
.erase(cluster
);
269 // Have to increment the iterator here, otherwise the increment in the
270 // for loop will skip a cluster due to the erase if the color wasn't
276 // If all pixels in the image are transparent we will have no clusters.
277 if (clusters
.empty())
280 bool convergence
= false;
281 for (int iteration
= 0;
282 iteration
< kNumberOfIterations
&& !convergence
;
285 // Loop through each pixel so we can place it in the appropriate cluster.
286 uint8_t* pixel
= decoded_data
;
287 uint8_t* decoded_data_end
= decoded_data
+ (img_width
* img_height
* 4);
288 while (pixel
< decoded_data_end
) {
289 uint8_t b
= *(pixel
++);
290 uint8_t g
= *(pixel
++);
291 uint8_t r
= *(pixel
++);
292 uint8_t a
= *(pixel
++);
293 // Skip transparent pixels, see above.
297 uint32_t distance_sqr_to_closest_cluster
= UINT_MAX
;
298 std::vector
<KMeanCluster
>::iterator closest_cluster
= clusters
.begin();
300 // Figure out which cluster this color is closest to in RGB space.
301 for (std::vector
<KMeanCluster
>::iterator cluster
= clusters
.begin();
302 cluster
!= clusters
.end(); ++cluster
) {
303 uint32_t distance_sqr
= cluster
->GetDistanceSqr(r
, g
, b
);
305 if (distance_sqr
< distance_sqr_to_closest_cluster
) {
306 distance_sqr_to_closest_cluster
= distance_sqr
;
307 closest_cluster
= cluster
;
311 closest_cluster
->AddPoint(r
, g
, b
);
314 // Calculate the new cluster centers and see if we've converged or not.
316 for (std::vector
<KMeanCluster
>::iterator cluster
= clusters
.begin();
317 cluster
!= clusters
.end(); ++cluster
) {
318 convergence
&= cluster
->CompareCentroidWithAggregate();
320 cluster
->RecomputeCentroid();
324 // Sort the clusters by population so we can tell what the most popular
326 std::sort(clusters
.begin(), clusters
.end(),
327 KMeanCluster::SortKMeanClusterByWeight
);
329 // Loop through the clusters to figure out which cluster has an appropriate
330 // color. Skip any that are too bright/dark and go in order of weight.
331 for (std::vector
<KMeanCluster
>::iterator cluster
= clusters
.begin();
332 cluster
!= clusters
.end(); ++cluster
) {
334 cluster
->GetCentroid(&r
, &g
, &b
);
336 SkColor current_color
= SkColorSetARGB(SK_AlphaOPAQUE
, r
, g
, b
);
338 SkColorToHSL(current_color
, &hsl
);
339 if (IsWithinHSLRange(hsl
, lower_bound
, upper_bound
)) {
340 // If we found a valid color just set it and break. We don't want to
341 // check the other ones.
342 color
= current_color
;
344 } else if (cluster
== clusters
.begin()) {
345 // We haven't found a valid color, but we are at the first color so
346 // set the color anyway to make sure we at least have a value here.
347 color
= current_color
;
352 // Find a color that actually appears in the image (the K-mean cluster center
353 // will not usually be a color that appears in the image).
354 return FindClosestColor(decoded_data
, img_width
, img_height
, color
);
357 SkColor
CalculateKMeanColorOfPNG(scoped_refptr
<base::RefCountedMemory
> png
,
358 const HSL
& lower_bound
,
359 const HSL
& upper_bound
,
360 KMeanImageSampler
* sampler
) {
363 std::vector
<uint8_t> decoded_data
;
364 SkColor color
= kDefaultBgColor
;
366 if (png
.get() && png
->size() &&
367 gfx::PNGCodec::Decode(png
->front(),
369 gfx::PNGCodec::FORMAT_BGRA
,
373 return CalculateKMeanColorOfBuffer(&decoded_data
[0],
383 SkColor
CalculateKMeanColorOfPNG(scoped_refptr
<base::RefCountedMemory
> png
) {
385 return CalculateKMeanColorOfPNG(
386 png
, kDefaultLowerHSLBound
, kDefaultUpperHSLBound
, &sampler
);
389 SkColor
CalculateKMeanColorOfBitmap(const SkBitmap
& bitmap
,
390 const HSL
& lower_bound
,
391 const HSL
& upper_bound
,
392 KMeanImageSampler
* sampler
) {
393 // SkBitmap uses pre-multiplied alpha but the KMean clustering function
394 // above uses non-pre-multiplied alpha. Transform the bitmap before we
395 // analyze it because the function reads each pixel multiple times.
396 int pixel_count
= bitmap
.width() * bitmap
.height();
397 scoped_ptr
<uint32_t[]> image(new uint32_t[pixel_count
]);
398 UnPreMultiply(bitmap
, image
.get(), pixel_count
);
400 return CalculateKMeanColorOfBuffer(reinterpret_cast<uint8_t*>(image
.get()),
408 SkColor
CalculateKMeanColorOfBitmap(const SkBitmap
& bitmap
) {
410 return CalculateKMeanColorOfBitmap(
411 bitmap
, kDefaultLowerHSLBound
, kDefaultUpperHSLBound
, &sampler
);
414 gfx::Matrix3F
ComputeColorCovariance(const SkBitmap
& bitmap
) {
415 // First need basic stats to normalize each channel separately.
416 SkAutoLockPixels
bitmap_lock(bitmap
);
417 gfx::Matrix3F covariance
= gfx::Matrix3F::Zeros();
418 if (!bitmap
.getPixels())
421 // Assume ARGB_8888 format.
422 DCHECK(bitmap
.colorType() == kN32_SkColorType
);
434 for (int y
= 0; y
< bitmap
.height(); ++y
) {
435 SkPMColor
* current_color
= static_cast<uint32_t*>(bitmap
.getAddr32(0, y
));
436 for (int x
= 0; x
< bitmap
.width(); ++x
, ++current_color
) {
437 SkColor c
= SkUnPreMultiply::PMColorToColor(*current_color
);
438 SkColor r
= SkColorGetR(c
);
439 SkColor g
= SkColorGetG(c
);
440 SkColor b
= SkColorGetB(c
);
454 // Covariance (not normalized) is E(X*X.t) - m * m.t and this is how it
455 // is calculated below.
456 // Each row below represents a row of the matrix describing (co)variances
457 // of R, G and B channels with (R, G, B)
458 int pixel_n
= bitmap
.width() * bitmap
.height();
460 (static_cast<double>(rr_sum
) / pixel_n
-
461 static_cast<double>(r_sum
* r_sum
) / pixel_n
/ pixel_n
),
462 (static_cast<double>(rg_sum
) / pixel_n
-
463 static_cast<double>(r_sum
* g_sum
) / pixel_n
/ pixel_n
),
464 (static_cast<double>(rb_sum
) / pixel_n
-
465 static_cast<double>(r_sum
* b_sum
) / pixel_n
/ pixel_n
),
466 (static_cast<double>(rg_sum
) / pixel_n
-
467 static_cast<double>(r_sum
* g_sum
) / pixel_n
/ pixel_n
),
468 (static_cast<double>(gg_sum
) / pixel_n
-
469 static_cast<double>(g_sum
* g_sum
) / pixel_n
/ pixel_n
),
470 (static_cast<double>(gb_sum
) / pixel_n
-
471 static_cast<double>(g_sum
* b_sum
) / pixel_n
/ pixel_n
),
472 (static_cast<double>(rb_sum
) / pixel_n
-
473 static_cast<double>(r_sum
* b_sum
) / pixel_n
/ pixel_n
),
474 (static_cast<double>(gb_sum
) / pixel_n
-
475 static_cast<double>(g_sum
* b_sum
) / pixel_n
/ pixel_n
),
476 (static_cast<double>(bb_sum
) / pixel_n
-
477 static_cast<double>(b_sum
* b_sum
) / pixel_n
/ pixel_n
));
481 bool ApplyColorReduction(const SkBitmap
& source_bitmap
,
482 const gfx::Vector3dF
& color_transform
,
484 SkBitmap
* target_bitmap
) {
485 DCHECK(target_bitmap
);
486 SkAutoLockPixels
source_lock(source_bitmap
);
487 SkAutoLockPixels
target_lock(*target_bitmap
);
489 DCHECK(source_bitmap
.getPixels());
490 DCHECK(target_bitmap
->getPixels());
491 DCHECK_EQ(kN32_SkColorType
, source_bitmap
.colorType());
492 DCHECK_EQ(kAlpha_8_SkColorType
, target_bitmap
->colorType());
493 DCHECK_EQ(source_bitmap
.height(), target_bitmap
->height());
494 DCHECK_EQ(source_bitmap
.width(), target_bitmap
->width());
495 DCHECK(!source_bitmap
.empty());
497 // Elements of color_transform are explicitly off-loaded to local values for
498 // efficiency reasons. Note that in practice images may correspond to entire
501 float tr
= color_transform
.x();
502 float tg
= color_transform
.y();
503 float tb
= color_transform
.z();
506 // We will figure out min/max in a preprocessing step and adjust
507 // actual_transform as required.
508 float max_val
= std::numeric_limits
<float>::min();
509 float min_val
= std::numeric_limits
<float>::max();
510 for (int y
= 0; y
< source_bitmap
.height(); ++y
) {
511 const SkPMColor
* source_color_row
= static_cast<SkPMColor
*>(
512 source_bitmap
.getAddr32(0, y
));
513 for (int x
= 0; x
< source_bitmap
.width(); ++x
) {
514 SkColor c
= SkUnPreMultiply::PMColorToColor(source_color_row
[x
]);
515 float r
= SkColorGetR(c
);
516 float g
= SkColorGetG(c
);
517 float b
= SkColorGetB(c
);
518 float gray_level
= tr
* r
+ tg
* g
+ tb
* b
;
519 max_val
= std::max(max_val
, gray_level
);
520 min_val
= std::min(min_val
, gray_level
);
524 // Adjust the transform so that the result is scaling.
527 if (max_val
> min_val
)
528 scale
= 255.0 / (max_val
- min_val
);
535 for (int y
= 0; y
< source_bitmap
.height(); ++y
) {
536 const SkPMColor
* source_color_row
= static_cast<SkPMColor
*>(
537 source_bitmap
.getAddr32(0, y
));
538 uint8_t* target_color_row
= target_bitmap
->getAddr8(0, y
);
539 for (int x
= 0; x
< source_bitmap
.width(); ++x
) {
540 SkColor c
= SkUnPreMultiply::PMColorToColor(source_color_row
[x
]);
541 float r
= SkColorGetR(c
);
542 float g
= SkColorGetG(c
);
543 float b
= SkColorGetB(c
);
545 float gl
= t0
+ tr
* r
+ tg
* g
+ tb
* b
;
550 target_color_row
[x
] = static_cast<uint8_t>(gl
);
557 bool ComputePrincipalComponentImage(const SkBitmap
& source_bitmap
,
558 SkBitmap
* target_bitmap
) {
559 if (!target_bitmap
) {
564 gfx::Matrix3F covariance
= ComputeColorCovariance(source_bitmap
);
565 gfx::Matrix3F eigenvectors
= gfx::Matrix3F::Zeros();
566 gfx::Vector3dF eigenvals
= covariance
.SolveEigenproblem(&eigenvectors
);
567 gfx::Vector3dF principal
= eigenvectors
.get_column(0);
568 if (eigenvals
== gfx::Vector3dF() || principal
== gfx::Vector3dF())
569 return false; // This may happen for some edge cases.
570 return ApplyColorReduction(source_bitmap
, principal
, true, target_bitmap
);