color_analysis_unittest.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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.
4
5#include "ui/gfx/color_analysis.h"
6
7#include <vector>
8
9#include "testing/gtest/include/gtest/gtest.h"
10#include "third_party/skia/include/core/SkBitmap.h"
11#include "third_party/skia/include/core/SkColor.h"
12#include "ui/gfx/canvas.h"
13#include "ui/gfx/color_utils.h"
14#include "ui/gfx/image/image.h"
15#include "ui/gfx/rect.h"
16
17namespace color_utils {
18
19const unsigned char k1x1White[] = {
20  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
21  0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
22  0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
23  0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
24  0xde, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
25  0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
26  0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
27  0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
28  0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
29  0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x11, 0x15,
30  0x16, 0x1b, 0xaa, 0x58, 0x38, 0x76, 0x00, 0x00,
31  0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
32  0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
33  0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
34  0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
35  0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x0c, 0x49,
36  0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff,
37  0xff, 0x3f, 0x00, 0x05, 0xfe, 0x02, 0xfe, 0xdc,
38  0xcc, 0x59, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x49,
39  0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
40};
41
42const unsigned char k1x3BlueWhite[] = {
43  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
44  0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
45  0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
46  0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2,
47  0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
48  0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
49  0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
50  0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
51  0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
52  0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01,
53  0x0a, 0x2c, 0xfd, 0x08, 0x64, 0x66, 0x00, 0x00,
54  0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
55  0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
56  0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
57  0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
58  0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49,
59  0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff,
60  0xff, 0x3f, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03,
61  0xc3, 0x7f, 0x00, 0x1e, 0xfd, 0x03, 0xff, 0xde,
62  0x72, 0x58, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x49,
63  0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
64};
65
66const unsigned char k1x3BlueRed[] = {
67  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
68  0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
69  0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
70  0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2,
71  0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
72  0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
73  0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
74  0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
75  0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
76  0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01,
77  0x07, 0x09, 0x03, 0xa2, 0xce, 0x6c, 0x00, 0x00,
78  0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
79  0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
80  0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
81  0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
82  0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49,
83  0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf,
84  0xc0, 0xc0, 0xc4, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
85  0xf0, 0x1f, 0x00, 0x0c, 0x10, 0x02, 0x01, 0x2c,
86  0x8f, 0x8b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x49,
87  0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
88};
89
90const HSL kDefaultLowerBound = {-1, -1, 0.15};
91const HSL kDefaultUpperBound = {-1, -1, 0.85};
92
93// Creates a 1-dimensional png of the pixel colors found in |colors|.
94scoped_refptr<base::RefCountedMemory> CreateTestPNG(
95    const std::vector<SkColor>& colors) {
96  SkBitmap bitmap;
97  bitmap.setConfig(SkBitmap::kARGB_8888_Config, colors.size(), 1);
98  bitmap.allocPixels();
99
100  SkAutoLockPixels lock(bitmap);
101  for (size_t i = 0; i < colors.size(); ++i) {
102    bitmap.eraseArea(SkIRect::MakeXYWH(i, 0, 1, 1), colors[i]);
103  }
104  return gfx::Image::CreateFrom1xBitmap(bitmap).As1xPNGBytes();
105}
106
107class MockKMeanImageSampler : public KMeanImageSampler {
108 public:
109  MockKMeanImageSampler() : current_result_index_(0) {
110  }
111
112  explicit MockKMeanImageSampler(const std::vector<int>& samples)
113      : prebaked_sample_results_(samples),
114        current_result_index_(0) {
115  }
116
117  virtual ~MockKMeanImageSampler() {
118  }
119
120  void AddSample(int sample) {
121    prebaked_sample_results_.push_back(sample);
122  }
123
124  virtual int GetSample(int width, int height) OVERRIDE {
125    if (current_result_index_ >= prebaked_sample_results_.size()) {
126      current_result_index_ = 0;
127    }
128
129    if (prebaked_sample_results_.empty()) {
130      return 0;
131    }
132
133    return prebaked_sample_results_[current_result_index_++];
134  }
135
136 protected:
137  std::vector<int> prebaked_sample_results_;
138  size_t current_result_index_;
139};
140
141// Return true if a color channel is approximately equal to an expected value.
142bool ChannelApproximatelyEqual(int expected, uint8_t channel) {
143  return (abs(expected - static_cast<int>(channel)) <= 1);
144}
145
146// Compute minimal and maximal graylevel (or alphalevel) of the input |bitmap|.
147// |bitmap| has to be allocated and configured to kA8_Config.
148void Calculate8bitBitmapMinMax(const SkBitmap& bitmap,
149                               uint8_t* min_gl,
150                               uint8_t* max_gl) {
151  SkAutoLockPixels bitmap_lock(bitmap);
152  DCHECK(bitmap.getPixels());
153  DCHECK(bitmap.config() == SkBitmap::kA8_Config);
154  DCHECK(min_gl);
155  DCHECK(max_gl);
156  *min_gl = std::numeric_limits<uint8_t>::max();
157  *max_gl = std::numeric_limits<uint8_t>::min();
158  for (int y = 0; y < bitmap.height(); ++y) {
159    uint8_t* current_color = bitmap.getAddr8(0, y);
160    for (int x = 0; x < bitmap.width(); ++x, ++current_color) {
161      *min_gl = std::min(*min_gl, *current_color);
162      *max_gl = std::max(*max_gl, *current_color);
163    }
164  }
165}
166
167class ColorAnalysisTest : public testing::Test {
168};
169
170TEST_F(ColorAnalysisTest, CalculatePNGKMeanAllWhite) {
171  MockKMeanImageSampler test_sampler;
172  test_sampler.AddSample(0);
173
174  scoped_refptr<base::RefCountedBytes> png(
175      new base::RefCountedBytes(
176          std::vector<unsigned char>(
177              k1x1White,
178              k1x1White + sizeof(k1x1White) / sizeof(unsigned char))));
179
180  SkColor color = CalculateKMeanColorOfPNG(
181      png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler);
182
183  EXPECT_EQ(color, SK_ColorWHITE);
184}
185
186TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreWhiteLightness) {
187  MockKMeanImageSampler test_sampler;
188  test_sampler.AddSample(0);
189  test_sampler.AddSample(1);
190  test_sampler.AddSample(2);
191
192  scoped_refptr<base::RefCountedBytes> png(
193     new base::RefCountedBytes(
194         std::vector<unsigned char>(
195             k1x3BlueWhite,
196             k1x3BlueWhite + sizeof(k1x3BlueWhite) / sizeof(unsigned char))));
197
198  SkColor color = CalculateKMeanColorOfPNG(
199      png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler);
200
201  EXPECT_EQ(SkColorSetARGB(0xFF, 0x00, 0x00, 0xFF), color);
202}
203
204TEST_F(ColorAnalysisTest, CalculatePNGKMeanPickMostCommon) {
205  MockKMeanImageSampler test_sampler;
206  test_sampler.AddSample(0);
207  test_sampler.AddSample(1);
208  test_sampler.AddSample(2);
209
210  scoped_refptr<base::RefCountedBytes> png(
211     new base::RefCountedBytes(
212         std::vector<unsigned char>(
213             k1x3BlueRed,
214             k1x3BlueRed + sizeof(k1x3BlueRed) / sizeof(unsigned char))));
215
216  SkColor color = CalculateKMeanColorOfPNG(
217      png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler);
218
219  EXPECT_EQ(SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00), color);
220}
221
222TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreRedHue) {
223  MockKMeanImageSampler test_sampler;
224  test_sampler.AddSample(0);
225  test_sampler.AddSample(1);
226  test_sampler.AddSample(2);
227
228  std::vector<SkColor> colors(4, SK_ColorRED);
229  colors[1] = SK_ColorBLUE;
230
231  scoped_refptr<base::RefCountedMemory> png = CreateTestPNG(colors);
232
233  HSL lower = {0.2, -1, 0.15};
234  HSL upper = {0.8, -1, 0.85};
235  SkColor color = CalculateKMeanColorOfPNG(
236      png, lower, upper, &test_sampler);
237
238  EXPECT_EQ(SK_ColorBLUE, color);
239}
240
241TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreGreySaturation) {
242  MockKMeanImageSampler test_sampler;
243  test_sampler.AddSample(0);
244  test_sampler.AddSample(1);
245  test_sampler.AddSample(2);
246
247  std::vector<SkColor> colors(4, SK_ColorGRAY);
248  colors[1] = SK_ColorBLUE;
249
250  scoped_refptr<base::RefCountedMemory> png = CreateTestPNG(colors);
251  HSL lower = {-1, 0.3, -1};
252  HSL upper = {-1, 1, -1};
253  SkColor color = CalculateKMeanColorOfPNG(
254      png, lower, upper, &test_sampler);
255
256  EXPECT_EQ(SK_ColorBLUE, color);
257}
258
259TEST_F(ColorAnalysisTest, GridSampler) {
260  GridSampler sampler;
261  const int kWidth = 16;
262  const int kHeight = 16;
263  // Sample starts at 1,1.
264  EXPECT_EQ(1 + 1 * kWidth, sampler.GetSample(kWidth, kHeight));
265  EXPECT_EQ(1 + 4 * kWidth, sampler.GetSample(kWidth, kHeight));
266  EXPECT_EQ(1 + 7 * kWidth, sampler.GetSample(kWidth, kHeight));
267  EXPECT_EQ(1 + 10 * kWidth, sampler.GetSample(kWidth, kHeight));
268  // Step over by 3.
269  EXPECT_EQ(4 + 1 * kWidth, sampler.GetSample(kWidth, kHeight));
270  EXPECT_EQ(4 + 4 * kWidth, sampler.GetSample(kWidth, kHeight));
271  EXPECT_EQ(4 + 7 * kWidth, sampler.GetSample(kWidth, kHeight));
272  EXPECT_EQ(4 + 10 * kWidth, sampler.GetSample(kWidth, kHeight));
273}
274
275TEST_F(ColorAnalysisTest, FindClosestColor) {
276  // Empty image returns input color.
277  SkColor color = FindClosestColor(NULL, 0, 0, SK_ColorRED);
278  EXPECT_EQ(SK_ColorRED, color);
279
280  // Single color image returns that color.
281  SkBitmap bitmap;
282  bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
283  bitmap.allocPixels();
284  bitmap.eraseColor(SK_ColorWHITE);
285  color = FindClosestColor(static_cast<uint8_t*>(bitmap.getPixels()),
286                           bitmap.width(),
287                           bitmap.height(),
288                           SK_ColorRED);
289  EXPECT_EQ(SK_ColorWHITE, color);
290
291  // Write a black pixel into the image. A dark grey input pixel should match
292  // the black one in the image.
293  uint32_t* pixel = bitmap.getAddr32(0, 0);
294  *pixel = SK_ColorBLACK;
295  color = FindClosestColor(static_cast<uint8_t*>(bitmap.getPixels()),
296                           bitmap.width(),
297                           bitmap.height(),
298                           SK_ColorDKGRAY);
299  EXPECT_EQ(SK_ColorBLACK, color);
300}
301
302TEST_F(ColorAnalysisTest, CalculateKMeanColorOfBitmap) {
303  // Create a 16x16 bitmap to represent a favicon.
304  SkBitmap bitmap;
305  bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
306  bitmap.allocPixels();
307  bitmap.eraseARGB(255, 100, 150, 200);
308
309  SkColor color = CalculateKMeanColorOfBitmap(bitmap);
310  EXPECT_EQ(255u, SkColorGetA(color));
311  // Color values are not exactly equal due to reversal of premultiplied alpha.
312  EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color)));
313  EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color)));
314  EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color)));
315
316  // Test a bitmap with an alpha channel.
317  bitmap.eraseARGB(128, 100, 150, 200);
318  color = CalculateKMeanColorOfBitmap(bitmap);
319
320  // Alpha channel should be ignored for dominant color calculation.
321  EXPECT_EQ(255u, SkColorGetA(color));
322  EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color)));
323  EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color)));
324  EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color)));
325}
326
327TEST_F(ColorAnalysisTest, ComputeColorCovarianceTrivial) {
328  SkBitmap bitmap;
329  bitmap.setConfig(SkBitmap::kARGB_8888_Config, 100, 200);
330
331  EXPECT_EQ(gfx::Matrix3F::Zeros(), ComputeColorCovariance(bitmap));
332  bitmap.allocPixels();
333  bitmap.eraseARGB(255, 50, 150, 200);
334  gfx::Matrix3F covariance = ComputeColorCovariance(bitmap);
335  // The answer should be all zeros.
336  EXPECT_TRUE(covariance == gfx::Matrix3F::Zeros());
337}
338
339TEST_F(ColorAnalysisTest, ComputeColorCovarianceWithCanvas) {
340  gfx::Canvas canvas(gfx::Size(250, 200), 1.0f, true);
341  // The image consists of vertical stripes, with color bands set to 100
342  // in overlapping stripes 150 pixels wide.
343  canvas.FillRect(gfx::Rect(0, 0, 50, 200), SkColorSetRGB(100, 0, 0));
344  canvas.FillRect(gfx::Rect(50, 0, 50, 200), SkColorSetRGB(100, 100, 0));
345  canvas.FillRect(gfx::Rect(100, 0, 50, 200), SkColorSetRGB(100, 100, 100));
346  canvas.FillRect(gfx::Rect(150, 0, 50, 200), SkColorSetRGB(0, 100, 100));
347  canvas.FillRect(gfx::Rect(200, 0, 50, 200), SkColorSetRGB(0, 0, 100));
348
349  SkBitmap bitmap =
350      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
351  gfx::Matrix3F covariance = ComputeColorCovariance(bitmap);
352
353  gfx::Matrix3F expected_covariance = gfx::Matrix3F::Zeros();
354  expected_covariance.set(2400, 400, -1600,
355                          400, 2400, 400,
356                          -1600, 400, 2400);
357  EXPECT_EQ(expected_covariance, covariance);
358}
359
360TEST_F(ColorAnalysisTest, ApplyColorReductionSingleColor) {
361  // The test runs color reduction on a single-colot image, where results are
362  // bound to be uninteresting. This is an important edge case, though.
363  SkBitmap source, result;
364  source.setConfig(SkBitmap::kARGB_8888_Config, 300, 200);
365  result.setConfig(SkBitmap::kA8_Config, 300, 200);
366
367  source.allocPixels();
368  result.allocPixels();
369  source.eraseARGB(255, 50, 150, 200);
370
371  gfx::Vector3dF transform(1.0f, .5f, 0.1f);
372  // This transform, if not scaled, should result in GL=145.
373  EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
374
375  uint8_t min_gl = 0;
376  uint8_t max_gl = 0;
377  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
378  EXPECT_EQ(145, min_gl);
379  EXPECT_EQ(145, max_gl);
380
381  // Now scan requesting rescale. Expect all 0.
382  EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
383  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
384  EXPECT_EQ(0, min_gl);
385  EXPECT_EQ(0, max_gl);
386
387  // Test cliping to upper limit.
388  transform.set_z(1.1f);
389  EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
390  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
391  EXPECT_EQ(0xFF, min_gl);
392  EXPECT_EQ(0xFF, max_gl);
393
394  // Test cliping to upper limit.
395  transform.Scale(-1.0f);
396  EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
397  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
398  EXPECT_EQ(0x0, min_gl);
399  EXPECT_EQ(0x0, max_gl);
400}
401
402TEST_F(ColorAnalysisTest, ApplyColorReductionBlackAndWhite) {
403  // Check with images with multiple colors. This is really different only when
404  // the result is scaled.
405  gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true);
406
407  // The image consists of vertical non-overlapping stripes 150 pixels wide.
408  canvas.FillRect(gfx::Rect(0, 0, 150, 200), SkColorSetRGB(0, 0, 0));
409  canvas.FillRect(gfx::Rect(150, 0, 150, 200), SkColorSetRGB(255, 255, 255));
410  SkBitmap source =
411      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
412  SkBitmap result;
413  result.setConfig(SkBitmap::kA8_Config, 300, 200);
414  result.allocPixels();
415
416  gfx::Vector3dF transform(1.0f, 0.5f, 0.1f);
417  EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
418  uint8_t min_gl = 0;
419  uint8_t max_gl = 0;
420  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
421
422  EXPECT_EQ(0, min_gl);
423  EXPECT_EQ(255, max_gl);
424  EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0)));
425  EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199)));
426
427  // Reverse test.
428  transform.Scale(-1.0f);
429  EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
430  min_gl = 0;
431  max_gl = 0;
432  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
433
434  EXPECT_EQ(0, min_gl);
435  EXPECT_EQ(255, max_gl);
436  EXPECT_EQ(max_gl, SkColorGetA(result.getColor(0, 0)));
437  EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
438}
439
440TEST_F(ColorAnalysisTest, ApplyColorReductionMultiColor) {
441  // Check with images with multiple colors. This is really different only when
442  // the result is scaled.
443  gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true);
444
445  // The image consists of vertical non-overlapping stripes 100 pixels wide.
446  canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(100, 0, 0));
447  canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(0, 255, 0));
448  canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(0, 0, 128));
449  SkBitmap source =
450      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
451  SkBitmap result;
452  result.setConfig(SkBitmap::kA8_Config, 300, 200);
453  result.allocPixels();
454
455  gfx::Vector3dF transform(1.0f, 0.5f, 0.1f);
456  EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
457  uint8_t min_gl = 0;
458  uint8_t max_gl = 0;
459  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
460  EXPECT_EQ(12, min_gl);
461  EXPECT_EQ(127, max_gl);
462  EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
463  EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0)));
464  EXPECT_EQ(100U, SkColorGetA(result.getColor(0, 0)));
465
466  EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
467  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
468  EXPECT_EQ(0, min_gl);
469  EXPECT_EQ(255, max_gl);
470  EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
471  EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0)));
472  EXPECT_EQ(193U, SkColorGetA(result.getColor(0, 0)));
473}
474
475TEST_F(ColorAnalysisTest, ComputePrincipalComponentImageNotComputable) {
476  SkBitmap source, result;
477  source.setConfig(SkBitmap::kARGB_8888_Config, 300, 200);
478  result.setConfig(SkBitmap::kA8_Config, 300, 200);
479
480  source.allocPixels();
481  result.allocPixels();
482  source.eraseARGB(255, 50, 150, 200);
483
484  // This computation should fail since all colors always vary together.
485  EXPECT_FALSE(ComputePrincipalComponentImage(source, &result));
486}
487
488TEST_F(ColorAnalysisTest, ComputePrincipalComponentImage) {
489  gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true);
490
491  // The image consists of vertical non-overlapping stripes 100 pixels wide.
492  canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(10, 10, 10));
493  canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(100, 100, 100));
494  canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(255, 255, 255));
495  SkBitmap source =
496      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
497  SkBitmap result;
498  result.setConfig(SkBitmap::kA8_Config, 300, 200);
499  result.allocPixels();
500
501  // This computation should fail since all colors always vary together.
502  EXPECT_TRUE(ComputePrincipalComponentImage(source, &result));
503
504  uint8_t min_gl = 0;
505  uint8_t max_gl = 0;
506  Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
507
508  EXPECT_EQ(0, min_gl);
509  EXPECT_EQ(255, max_gl);
510  EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0)));
511  EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199)));
512  EXPECT_EQ(93U, SkColorGetA(result.getColor(150, 0)));
513}
514
515}  // namespace color_utils
516