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/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
{
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.
43 centroid_
[0] = centroid_
[1] = centroid_
[2] = 0;
44 aggregate_
[0] = aggregate_
[1] = aggregate_
[2] = 0;
49 inline void SetCentroid(uint8_t r
, uint8_t g
, uint8_t b
) {
55 inline void GetCentroid(uint8_t* r
, uint8_t* g
, uint8_t* b
) {
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
69 inline void RecomputeCentroid() {
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;
81 inline void AddPoint(uint8_t r
, uint8_t g
, uint8_t b
) {
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() {
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 {
115 static bool SortKMeanClusterByWeight(const KMeanCluster
& a
,
116 const KMeanCluster
& b
) {
117 return a
.GetWeight() > b
.GetWeight();
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];
128 // The weight of the cluster, determined by how many points were used
129 // to generate the previous centroid.
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
++);
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:
178 (calls_
/ kNumberOfClusters
) * ((width
- 2 * kPadX
) / kNumberOfClusters
);
180 (calls_
% kNumberOfClusters
) * ((height
- 2 * kPadY
) / kNumberOfClusters
);
181 int index
= x
+ (y
* width
);
183 return index
% (width
* height
);
186 SkColor
FindClosestColor(const uint8_t* image
,
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.
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
);
217 // For a 16x16 icon on an Intel Core i5 this function takes approximately
219 // TODO(port): This code assumes the CPU architecture is little-endian.
220 SkColor
CalculateKMeanColorOfBuffer(uint8_t* decoded_data
,
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.
250 // Loop through the previous clusters and check to see if we have seen
251 // this color before.
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;
262 // If we have a unique color set the center of the cluster to
265 cluster
->SetCentroid(r
, g
, b
);
270 // If we don't have a unique color erase this cluster.
272 cluster
= clusters
.erase(cluster
);
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
281 // If all pixels in the image are transparent we will have no clusters.
282 if (clusters
.empty())
285 bool convergence
= false;
286 for (int iteration
= 0;
287 iteration
< kNumberOfIterations
&& !convergence
;
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.
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.
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
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
) {
339 cluster
->GetCentroid(&r
, &g
, &b
);
341 SkColor current_color
= SkColorSetARGB(SK_AlphaOPAQUE
, r
, g
, b
);
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
;
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
) {
368 std::vector
<uint8_t> decoded_data
;
369 SkColor color
= kDefaultBgColor
;
371 if (png
.get() && png
->size() &&
372 gfx::PNGCodec::Decode(png
->front(),
374 gfx::PNGCodec::FORMAT_BGRA
,
378 return CalculateKMeanColorOfBuffer(&decoded_data
[0],
388 SkColor
CalculateKMeanColorOfPNG(scoped_refptr
<base::RefCountedMemory
> png
) {
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()),
413 SkColor
CalculateKMeanColorOfBitmap(const SkBitmap
& bitmap
) {
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())
426 // Assume ARGB_8888 format.
427 DCHECK(bitmap
.colorType() == kN32_SkColorType
);
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
) {
443 int alpha
= SkGetPackedA32(*current_color
);
444 if (alpha
!= 0 && alpha
!= 255)
445 c
= SkUnPreMultiply::PMColorToColor(*current_color
);
449 SkColor r
= SkColorGetR(c
);
450 SkColor g
= SkColorGetG(c
);
451 SkColor b
= SkColorGetB(c
);
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();
472 static_cast<double>(rr_sum
) / pixel_n
-
473 static_cast<double>(r_sum
* r_sum
) / pixel_n
/ pixel_n
),
475 static_cast<double>(rg_sum
) / pixel_n
-
476 static_cast<double>(r_sum
* g_sum
) / pixel_n
/ pixel_n
),
478 static_cast<double>(rb_sum
) / pixel_n
-
479 static_cast<double>(r_sum
* b_sum
) / pixel_n
/ pixel_n
),
481 static_cast<double>(rg_sum
) / pixel_n
-
482 static_cast<double>(r_sum
* g_sum
) / pixel_n
/ pixel_n
),
484 static_cast<double>(gg_sum
) / pixel_n
-
485 static_cast<double>(g_sum
* g_sum
) / pixel_n
/ pixel_n
),
487 static_cast<double>(gb_sum
) / pixel_n
-
488 static_cast<double>(g_sum
* b_sum
) / pixel_n
/ pixel_n
),
490 static_cast<double>(rb_sum
) / pixel_n
-
491 static_cast<double>(r_sum
* b_sum
) / pixel_n
/ pixel_n
),
493 static_cast<double>(gb_sum
) / pixel_n
-
494 static_cast<double>(g_sum
* b_sum
) / pixel_n
/ pixel_n
),
496 static_cast<double>(bb_sum
) / pixel_n
-
497 static_cast<double>(b_sum
* b_sum
) / pixel_n
/ pixel_n
));
501 bool ApplyColorReduction(const SkBitmap
& source_bitmap
,
502 const gfx::Vector3dF
& color_transform
,
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
521 float tr
= color_transform
.x();
522 float tg
= color_transform
.y();
523 float tb
= color_transform
.z();
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
) {
535 int alpha
= SkGetPackedA32(source_color_row
[x
]);
536 if (alpha
!= 0 && alpha
!= 255)
537 c
= SkUnPreMultiply::PMColorToColor(source_color_row
[x
]);
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.
553 if (max_val
> min_val
)
554 scale
= 255.0f
/ (max_val
- min_val
);
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
) {
567 int alpha
= SkGetPackedA32(source_color_row
[x
]);
568 if (alpha
!= 0 && alpha
!= 255)
569 c
= SkUnPreMultiply::PMColorToColor(source_color_row
[x
]);
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
;
582 target_color_row
[x
] = static_cast<uint8_t>(gl
);
589 bool ComputePrincipalComponentImage(const SkBitmap
& source_bitmap
,
590 SkBitmap
* target_bitmap
) {
591 if (!target_bitmap
) {
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
);