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 "chrome/browser/thumbnails/content_analysis.h"
6
7#include <algorithm>
8#include <cmath>
9#include <cstdlib>
10#include <functional>
11#include <limits>
12#include <numeric>
13#include <vector>
14
15#include "base/memory/scoped_ptr.h"
16#include "testing/gtest/include/gtest/gtest.h"
17#include "third_party/skia/include/core/SkBitmap.h"
18#include "third_party/skia/include/core/SkColor.h"
19#include "ui/gfx/canvas.h"
20#include "ui/gfx/color_analysis.h"
21#include "ui/gfx/color_utils.h"
22#include "ui/gfx/image/image.h"
23#include "ui/gfx/rect.h"
24#include "ui/gfx/size.h"
25
26namespace {
27
28#ifndef M_PI
29#define M_PI 3.14159265358979323846
30#endif
31
32unsigned long ImagePixelSum(const SkBitmap& bitmap, const gfx::Rect& rect) {
33  // Get the sum of pixel values in the rectangle. Applicable only to
34  // monochrome bitmaps.
35  DCHECK_EQ(SkBitmap::kA8_Config, bitmap.config());
36  unsigned long total = 0;
37  for (int r = rect.y(); r < rect.bottom(); ++r) {
38    const uint8* row_data = static_cast<const uint8*>(
39        bitmap.getPixels()) + r * bitmap.rowBytes();
40    for (int c = rect.x(); c < rect.right(); ++c)
41      total += row_data[c];
42  }
43
44  return total;
45}
46
47bool CompareImageFragments(const SkBitmap& bitmap_left,
48                           const SkBitmap& bitmap_right,
49                           const gfx::Size& comparison_area,
50                           const gfx::Point& origin_left,
51                           const gfx::Point& origin_right) {
52  SkAutoLockPixels left_lock(bitmap_left);
53  SkAutoLockPixels right_lock(bitmap_right);
54  for (int r = 0; r < comparison_area.height(); ++r) {
55    for (int c = 0; c < comparison_area.width(); ++c) {
56      SkColor color_left = bitmap_left.getColor(origin_left.x() + c,
57                                                origin_left.y() + r);
58      SkColor color_right = bitmap_right.getColor(origin_right.x() + c,
59                                                  origin_right.y() + r);
60      if (color_left != color_right)
61        return false;
62    }
63  }
64
65  return true;
66}
67
68float AspectDifference(const gfx::Size& reference, const gfx::Size& candidate) {
69  return std::abs(static_cast<float>(candidate.width()) / candidate.height() -
70                  static_cast<float>(reference.width()) / reference.height());
71}
72
73}  // namespace
74
75namespace thumbnailing_utils {
76
77class ThumbnailContentAnalysisTest : public testing::Test {
78};
79
80TEST_F(ThumbnailContentAnalysisTest, ApplyGradientMagnitudeOnImpulse) {
81  gfx::Canvas canvas(gfx::Size(800, 600), 1.0f, true);
82
83  // The image consists of a point spike on uniform (non-zero) background.
84  canvas.FillRect(gfx::Rect(0, 0, 800, 600), SkColorSetRGB(10, 10, 10));
85  canvas.FillRect(gfx::Rect(400, 300, 1, 1), SkColorSetRGB(255, 255, 255));
86
87  SkBitmap source =
88      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
89
90  SkBitmap reduced_color;
91  reduced_color.setConfig(
92      SkBitmap::kA8_Config, source.width(), source.height());
93  reduced_color.allocPixels();
94
95  gfx::Vector3dF transform(0.299f, 0.587f, 0.114f);
96  EXPECT_TRUE(color_utils::ApplyColorReduction(
97      source, transform, true, &reduced_color));
98
99  float sigma = 2.5f;
100  ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma);
101
102  // Expect everything to be within 8 * sigma.
103  int tail_length = static_cast<int>(8.0f * sigma + 0.5f);
104  gfx::Rect echo_rect(399 - tail_length, 299 - tail_length,
105                      2 * tail_length + 1, 2 * tail_length + 1);
106  unsigned long data_sum = ImagePixelSum(reduced_color, echo_rect);
107  unsigned long all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600));
108  EXPECT_GT(data_sum, 0U);
109  EXPECT_EQ(data_sum, all_sum);
110
111  sigma = 5.0f;
112  ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma);
113
114  // Expect everything to be within 8 * sigma.
115  tail_length = static_cast<int>(8.0f * sigma + 0.5f);
116  echo_rect = gfx::Rect(399 - tail_length, 299 - tail_length,
117                        2 * tail_length + 1, 2 * tail_length + 1);
118  data_sum = ImagePixelSum(reduced_color, echo_rect);
119  all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600));
120  EXPECT_GT(data_sum, 0U);
121  EXPECT_EQ(data_sum, all_sum);
122}
123
124TEST_F(ThumbnailContentAnalysisTest, ApplyGradientMagnitudeOnFrame) {
125  gfx::Canvas canvas(gfx::Size(800, 600), 1.0f, true);
126
127  // The image consists of a single white block in the centre.
128  gfx::Rect draw_rect(300, 200, 200, 200);
129  canvas.FillRect(gfx::Rect(0, 0, 800, 600), SkColorSetRGB(0, 0, 0));
130  canvas.DrawRect(draw_rect, SkColorSetRGB(255, 255, 255));
131
132  SkBitmap source =
133      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
134
135  SkBitmap reduced_color;
136  reduced_color.setConfig(
137      SkBitmap::kA8_Config, source.width(), source.height());
138  reduced_color.allocPixels();
139
140  gfx::Vector3dF transform(0.299f, 0.587f, 0.114f);
141  EXPECT_TRUE(color_utils::ApplyColorReduction(
142      source, transform, true, &reduced_color));
143
144  float sigma = 2.5f;
145  ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma);
146
147  int tail_length = static_cast<int>(8.0f * sigma + 0.5f);
148  gfx::Rect outer_rect(draw_rect.x() - tail_length,
149                       draw_rect.y() - tail_length,
150                       draw_rect.width() + 2 * tail_length,
151                       draw_rect.height() + 2 * tail_length);
152  gfx::Rect inner_rect(draw_rect.x() + tail_length,
153                       draw_rect.y() + tail_length,
154                       draw_rect.width() - 2 * tail_length,
155                       draw_rect.height() - 2 * tail_length);
156  unsigned long data_sum = ImagePixelSum(reduced_color, outer_rect);
157  unsigned long all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600));
158  EXPECT_GT(data_sum, 0U);
159  EXPECT_EQ(data_sum, all_sum);
160  EXPECT_EQ(ImagePixelSum(reduced_color, inner_rect), 0U);
161}
162
163TEST_F(ThumbnailContentAnalysisTest, ExtractImageProfileInformation) {
164  gfx::Canvas canvas(gfx::Size(800, 600), 1.0f, true);
165
166  // The image consists of a white frame drawn in the centre.
167  gfx::Rect draw_rect(100, 100, 200, 100);
168  gfx::Rect image_rect(0, 0, 800, 600);
169  canvas.FillRect(image_rect, SkColorSetRGB(0, 0, 0));
170  canvas.DrawRect(draw_rect, SkColorSetRGB(255, 255, 255));
171
172  SkBitmap source =
173      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
174  SkBitmap reduced_color;
175  reduced_color.setConfig(
176      SkBitmap::kA8_Config, source.width(), source.height());
177  reduced_color.allocPixels();
178
179  gfx::Vector3dF transform(1, 0, 0);
180  EXPECT_TRUE(color_utils::ApplyColorReduction(
181      source, transform, true, &reduced_color));
182  std::vector<float> column_profile;
183  std::vector<float> row_profile;
184  ExtractImageProfileInformation(reduced_color,
185                                 image_rect,
186                                 gfx::Size(),
187                                 false,
188                                 &row_profile,
189                                 &column_profile);
190  EXPECT_EQ(0, std::accumulate(column_profile.begin(),
191                               column_profile.begin() + draw_rect.x() - 1,
192                               0));
193  EXPECT_EQ(column_profile[draw_rect.x()], 255U * (draw_rect.height() + 1));
194  EXPECT_EQ(2 * 255 * (draw_rect.width() - 2),
195            std::accumulate(column_profile.begin() + draw_rect.x() + 1,
196                            column_profile.begin() + draw_rect.right() - 1,
197                            0));
198
199  EXPECT_EQ(0, std::accumulate(row_profile.begin(),
200                               row_profile.begin() + draw_rect.y() - 1,
201                               0));
202  EXPECT_EQ(row_profile[draw_rect.y()], 255U * (draw_rect.width() + 1));
203  EXPECT_EQ(2 * 255 * (draw_rect.height() - 2),
204            std::accumulate(row_profile.begin() + draw_rect.y() + 1,
205                            row_profile.begin() + draw_rect.bottom() - 1,
206                            0));
207
208  gfx::Rect test_rect(150, 80, 400, 100);
209  ExtractImageProfileInformation(reduced_color,
210                                 test_rect,
211                                 gfx::Size(),
212                                 false,
213                                 &row_profile,
214                                 &column_profile);
215
216  // Some overlap with the drawn rectagle. If you work it out on a piece of
217  // paper, sums should be as follows.
218  EXPECT_EQ(255 * (test_rect.bottom() - draw_rect.y()) +
219            255 * (draw_rect.right() - test_rect.x()),
220            std::accumulate(row_profile.begin(), row_profile.end(), 0));
221  EXPECT_EQ(255 * (test_rect.bottom() - draw_rect.y()) +
222            255 * (draw_rect.right() - test_rect.x()),
223            std::accumulate(column_profile.begin(), column_profile.end(), 0));
224}
225
226TEST_F(ThumbnailContentAnalysisTest,
227       ExtractImageProfileInformationWithClosing) {
228  gfx::Canvas canvas(gfx::Size(800, 600), 1.0f, true);
229
230  // The image consists of a two white frames drawn side by side, with a
231  // single-pixel vertical gap in between.
232  gfx::Rect image_rect(0, 0, 800, 600);
233  canvas.FillRect(image_rect, SkColorSetRGB(0, 0, 0));
234  canvas.DrawRect(gfx::Rect(300, 250, 99, 100), SkColorSetRGB(255, 255, 255));
235  canvas.DrawRect(gfx::Rect(401, 250, 99, 100), SkColorSetRGB(255, 255, 255));
236
237  SkBitmap source =
238      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
239  SkBitmap reduced_color;
240  reduced_color.setConfig(
241      SkBitmap::kA8_Config, source.width(), source.height());
242  reduced_color.allocPixels();
243
244  gfx::Vector3dF transform(1, 0, 0);
245  EXPECT_TRUE(color_utils::ApplyColorReduction(
246      source, transform, true, &reduced_color));
247  std::vector<float> column_profile;
248  std::vector<float> row_profile;
249
250  ExtractImageProfileInformation(reduced_color,
251                                 image_rect,
252                                 gfx::Size(),
253                                 true,
254                                 &row_profile,
255                                 &column_profile);
256  // Column profiles should have two spikes in the middle, with a single
257  // 0-valued value between them.
258  EXPECT_GT(column_profile[398], 0.0f);
259  EXPECT_GT(column_profile[399], column_profile[398]);
260  EXPECT_GT(column_profile[402], 0.0f);
261  EXPECT_GT(column_profile[401], column_profile[402]);
262  EXPECT_EQ(column_profile[401], column_profile[399]);
263  EXPECT_EQ(column_profile[402], column_profile[398]);
264  EXPECT_EQ(column_profile[400], 0.0f);
265  EXPECT_EQ(column_profile[299], 0.0f);
266  EXPECT_EQ(column_profile[502], 0.0f);
267
268  // Now the same with closing applied. The space in the middle will be closed.
269  ExtractImageProfileInformation(reduced_color,
270                                 image_rect,
271                                 gfx::Size(200, 100),
272                                 true,
273                                 &row_profile,
274                                 &column_profile);
275  EXPECT_GT(column_profile[398], 0);
276  EXPECT_GT(column_profile[400], 0);
277  EXPECT_GT(column_profile[402], 0);
278  EXPECT_EQ(column_profile[299], 0);
279  EXPECT_EQ(column_profile[502], 0);
280  EXPECT_EQ(column_profile[399], column_profile[401]);
281  EXPECT_EQ(column_profile[398], column_profile[402]);
282}
283
284TEST_F(ThumbnailContentAnalysisTest, AdjustClippingSizeToAspectRatio) {
285  // The test will exercise several relations of sizes. Basic invariants
286  // checked in each case: each dimension in adjusted_size ougth not be greater
287  // than the source image and not lesser than requested target. Aspect ratio
288  // of adjusted_size should never be worse than that of computed_size.
289  gfx::Size target_size(212, 100);
290  gfx::Size image_size(1000, 2000);
291  gfx::Size computed_size(420, 200);
292
293  gfx::Size adjusted_size = AdjustClippingSizeToAspectRatio(
294      target_size, image_size, computed_size);
295
296  EXPECT_LE(adjusted_size.width(), image_size.width());
297  EXPECT_LE(adjusted_size.height(), image_size.height());
298  EXPECT_GE(adjusted_size.width(), target_size.width());
299  EXPECT_GE(adjusted_size.height(), target_size.height());
300  EXPECT_LE(AspectDifference(target_size, adjusted_size),
301            AspectDifference(target_size, computed_size));
302  // This case is special (and trivial): no change expected.
303  EXPECT_EQ(computed_size, adjusted_size);
304
305  // Computed size is too tall. Adjusted size has to add rows.
306  computed_size.SetSize(600, 150);
307  adjusted_size = AdjustClippingSizeToAspectRatio(
308      target_size, image_size, computed_size);
309  // Invariant check.
310  EXPECT_LE(adjusted_size.width(), image_size.width());
311  EXPECT_LE(adjusted_size.height(), image_size.height());
312  EXPECT_GE(adjusted_size.width(), target_size.width());
313  EXPECT_GE(adjusted_size.height(), target_size.height());
314  EXPECT_LE(AspectDifference(target_size, adjusted_size),
315            AspectDifference(target_size, computed_size));
316  // Specific to this case.
317  EXPECT_EQ(computed_size.width(), adjusted_size.width());
318  EXPECT_LE(computed_size.height(), adjusted_size.height());
319  EXPECT_NEAR(
320      static_cast<float>(target_size.width()) / target_size.height(),
321      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
322      0.02f);
323
324  // Computed size is too wide. Adjusted size has to add columns.
325  computed_size.SetSize(200, 400);
326  adjusted_size = AdjustClippingSizeToAspectRatio(
327      target_size, image_size, computed_size);
328  // Invariant check.
329  EXPECT_LE(adjusted_size.width(), image_size.width());
330  EXPECT_LE(adjusted_size.height(), image_size.height());
331  EXPECT_GE(adjusted_size.width(), target_size.width());
332  EXPECT_GE(adjusted_size.height(), target_size.height());
333  EXPECT_LE(AspectDifference(target_size, adjusted_size),
334            AspectDifference(target_size, computed_size));
335  EXPECT_NEAR(
336      static_cast<float>(target_size.width()) / target_size.height(),
337      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
338      0.02f);
339
340  target_size.SetSize(416, 205);
341  image_size.SetSize(1200, 1200);
342  computed_size.SetSize(900, 300);
343  adjusted_size = AdjustClippingSizeToAspectRatio(
344      target_size, image_size, computed_size);
345  // Invariant check.
346  EXPECT_LE(adjusted_size.width(), image_size.width());
347  EXPECT_LE(adjusted_size.height(), image_size.height());
348  EXPECT_GE(adjusted_size.width(), target_size.width());
349  EXPECT_GE(adjusted_size.height(), target_size.height());
350  EXPECT_LE(AspectDifference(target_size, adjusted_size),
351            AspectDifference(target_size, computed_size));
352  // Specific to this case.
353  EXPECT_EQ(computed_size.width(), adjusted_size.width());
354  EXPECT_LE(computed_size.height(), adjusted_size.height());
355  EXPECT_NEAR(
356      static_cast<float>(target_size.width()) / target_size.height(),
357      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
358      0.02f);
359
360  target_size.SetSize(416, 205);
361  image_size.SetSize(1200, 1200);
362  computed_size.SetSize(300, 300);
363  adjusted_size = AdjustClippingSizeToAspectRatio(
364      target_size, image_size, computed_size);
365  // Invariant check.
366  EXPECT_LE(adjusted_size.width(), image_size.width());
367  EXPECT_LE(adjusted_size.height(), image_size.height());
368  EXPECT_GE(adjusted_size.width(), target_size.width());
369  EXPECT_GE(adjusted_size.height(), target_size.height());
370  EXPECT_LE(AspectDifference(target_size, adjusted_size),
371            AspectDifference(target_size, computed_size));
372  // Specific to this case.
373  EXPECT_EQ(computed_size.height(), adjusted_size.height());
374  EXPECT_LE(computed_size.width(), adjusted_size.width());
375  EXPECT_NEAR(
376      static_cast<float>(target_size.width()) / target_size.height(),
377      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
378      0.02f);
379
380  computed_size.SetSize(200, 300);
381  adjusted_size = AdjustClippingSizeToAspectRatio(
382      target_size, image_size, computed_size);
383  // Invariant check.
384  EXPECT_LE(adjusted_size.width(), image_size.width());
385  EXPECT_LE(adjusted_size.height(), image_size.height());
386  EXPECT_GE(adjusted_size.width(), target_size.width());
387  EXPECT_GE(adjusted_size.height(), target_size.height());
388  EXPECT_LE(AspectDifference(target_size, adjusted_size),
389            AspectDifference(target_size, computed_size));
390  // Specific to this case.
391  EXPECT_EQ(computed_size.height(), adjusted_size.height());
392  EXPECT_LE(computed_size.width(), adjusted_size.width());
393  EXPECT_NEAR(
394      static_cast<float>(target_size.width()) / target_size.height(),
395      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
396      0.02f);
397
398  target_size.SetSize(416, 205);
399  image_size.SetSize(1400, 600);
400  computed_size.SetSize(300, 300);
401  adjusted_size = AdjustClippingSizeToAspectRatio(
402      target_size, image_size, computed_size);
403  // Invariant check.
404  EXPECT_LE(adjusted_size.width(), image_size.width());
405  EXPECT_LE(adjusted_size.height(), image_size.height());
406  EXPECT_GE(adjusted_size.width(), target_size.width());
407  EXPECT_GE(adjusted_size.height(), target_size.height());
408  EXPECT_LE(AspectDifference(target_size, adjusted_size),
409            AspectDifference(target_size, computed_size));
410  // Specific to this case.
411  EXPECT_EQ(computed_size.height(), adjusted_size.height());
412  EXPECT_LE(computed_size.width(), adjusted_size.width());
413  EXPECT_NEAR(
414      static_cast<float>(target_size.width()) / target_size.height(),
415      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
416      0.02f);
417
418  computed_size.SetSize(900, 300);
419  adjusted_size = AdjustClippingSizeToAspectRatio(
420      target_size, image_size, computed_size);
421  // Invariant check.
422  EXPECT_LE(adjusted_size.width(), image_size.width());
423  EXPECT_LE(adjusted_size.height(), image_size.height());
424  EXPECT_GE(adjusted_size.width(), target_size.width());
425  EXPECT_GE(adjusted_size.height(), target_size.height());
426  EXPECT_LE(AspectDifference(target_size, adjusted_size),
427            AspectDifference(target_size, computed_size));
428  // Specific to this case.
429  EXPECT_LE(computed_size.height(), adjusted_size.height());
430  EXPECT_NEAR(
431      static_cast<float>(target_size.width()) / target_size.height(),
432      static_cast<float>(adjusted_size.width()) / adjusted_size.height(),
433      0.02f);
434}
435
436TEST_F(ThumbnailContentAnalysisTest, AutoSegmentPeaks) {
437  std::vector<float> profile_info;
438
439  EXPECT_EQ(AutoSegmentPeaks(profile_info), std::numeric_limits<float>::max());
440  profile_info.resize(1000, 1.0f);
441  EXPECT_EQ(AutoSegmentPeaks(profile_info), 1.0f);
442  std::srand(42);
443  std::generate(profile_info.begin(), profile_info.end(), std::rand);
444  float threshold = AutoSegmentPeaks(profile_info);
445  EXPECT_GT(threshold, 0);  // Not much to expect.
446
447  // There should be roughly 50% above and below the threshold.
448  // Random is not really random thanks to srand, so we can sort-of compare.
449  int above_count = std::count_if(
450      profile_info.begin(),
451      profile_info.end(),
452      std::bind2nd(std::greater<float>(), threshold));
453  EXPECT_GT(above_count, 450);  // Not much to expect.
454  EXPECT_LT(above_count, 550);
455
456  for (unsigned i = 0; i < profile_info.size(); ++i) {
457    float y = std::sin(M_PI * i / 250.0f);
458    profile_info[i] = y > 0 ? y : 0;
459  }
460  threshold = AutoSegmentPeaks(profile_info);
461
462  above_count = std::count_if(
463      profile_info.begin(),
464      profile_info.end(),
465      std::bind2nd(std::greater<float>(), threshold));
466  EXPECT_LT(above_count, 500);  // Negative y expected to fall below threshold.
467
468  // Expect two peaks around between 0 and 250 and 500 and 750.
469  std::vector<bool> thresholded_values(profile_info.size(), false);
470  std::transform(profile_info.begin(),
471                 profile_info.end(),
472                 thresholded_values.begin(),
473                 std::bind2nd(std::greater<float>(), threshold));
474  EXPECT_TRUE(thresholded_values[125]);
475  EXPECT_TRUE(thresholded_values[625]);
476  int transitions = 0;
477  for (unsigned i = 1; i < thresholded_values.size(); ++i) {
478    if (thresholded_values[i] != thresholded_values[i-1])
479      transitions++;
480  }
481  EXPECT_EQ(transitions, 4);  // We have two contiguous peaks. Good going!
482}
483
484TEST_F(ThumbnailContentAnalysisTest, ConstrainedProfileSegmentation) {
485  const size_t kRowCount = 800;
486  const size_t kColumnCount = 1400;
487  const gfx::Size target_size(300, 150);
488  std::vector<float> rows_profile(kRowCount);
489  std::vector<float> columns_profile(kColumnCount);
490
491  std::srand(42);
492  std::generate(rows_profile.begin(), rows_profile.end(), std::rand);
493  std::generate(columns_profile.begin(), columns_profile.end(), std::rand);
494
495  // Bring noise level to 0-1.
496  std::transform(rows_profile.begin(),
497                 rows_profile.end(),
498                 rows_profile.begin(),
499                 std::bind2nd(std::divides<float>(), RAND_MAX));
500  std::transform(columns_profile.begin(),
501                 columns_profile.end(),
502                 columns_profile.begin(),
503                 std::bind2nd(std::divides<float>(), RAND_MAX));
504
505  // Set up values to 0-1.
506  std::transform(rows_profile.begin(),
507                 rows_profile.end(),
508                 rows_profile.begin(),
509                 std::bind2nd(std::plus<float>(), 1.0f));
510  std::transform(columns_profile.begin(),
511                 columns_profile.end(),
512                 columns_profile.begin(),
513                 std::bind2nd(std::plus<float>(), 1.0f));
514
515  std::transform(rows_profile.begin() + 300,
516                 rows_profile.begin() + 450,
517                 rows_profile.begin() + 300,
518                 std::bind2nd(std::plus<float>(), 8.0f));
519  std::transform(columns_profile.begin() + 400,
520                 columns_profile.begin() + 1000,
521                 columns_profile.begin() + 400,
522                 std::bind2nd(std::plus<float>(), 10.0f));
523
524  // Make sure that threshold falls somewhere reasonable.
525  float row_threshold = AutoSegmentPeaks(rows_profile);
526  EXPECT_GT(row_threshold, 1.0f);
527  EXPECT_LT(row_threshold, 9.0f);
528
529  int row_above_count = std::count_if(
530      rows_profile.begin(),
531      rows_profile.end(),
532      std::bind2nd(std::greater<float>(), row_threshold));
533  EXPECT_EQ(row_above_count, 150);
534
535  float column_threshold = AutoSegmentPeaks(columns_profile);
536  EXPECT_GT(column_threshold, 1.0f);
537  EXPECT_LT(column_threshold, 11.0f);
538
539  int column_above_count = std::count_if(
540      columns_profile.begin(),
541      columns_profile.end(),
542      std::bind2nd(std::greater<float>(), column_threshold));
543  EXPECT_EQ(column_above_count, 600);
544
545
546  std::vector<bool> rows_guide;
547  std::vector<bool> columns_guide;
548  ConstrainedProfileSegmentation(
549      rows_profile, columns_profile, target_size, &rows_guide, &columns_guide);
550
551  int row_count = std::count(rows_guide.begin(), rows_guide.end(), true);
552  int column_count = std::count(
553      columns_guide.begin(), columns_guide.end(), true);
554  float expected_aspect =
555      static_cast<float>(target_size.width()) / target_size.height();
556  float actual_aspect = static_cast<float>(column_count) / row_count;
557  EXPECT_GE(1.05f, expected_aspect / actual_aspect);
558  EXPECT_GE(1.05f, actual_aspect / expected_aspect);
559}
560
561TEST_F(ThumbnailContentAnalysisTest, ComputeDecimatedImage) {
562  gfx::Size image_size(1600, 1200);
563  gfx::Canvas canvas(image_size, 1.0f, true);
564
565  // Make some content we will later want to keep.
566  canvas.FillRect(gfx::Rect(100, 200, 100, 100), SkColorSetRGB(125, 0, 0));
567  canvas.FillRect(gfx::Rect(300, 200, 100, 100), SkColorSetRGB(0, 200, 0));
568  canvas.FillRect(gfx::Rect(500, 200, 100, 100), SkColorSetRGB(0, 0, 225));
569  canvas.FillRect(gfx::Rect(100, 400, 600, 100), SkColorSetRGB(125, 200, 225));
570
571  std::vector<bool> rows(image_size.height(), false);
572  std::fill_n(rows.begin() + 200, 100, true);
573  std::fill_n(rows.begin() + 400, 100, true);
574
575  std::vector<bool> columns(image_size.width(), false);
576  std::fill_n(columns.begin() + 100, 100, true);
577  std::fill_n(columns.begin() + 300, 100, true);
578  std::fill_n(columns.begin() + 500, 100, true);
579
580  SkBitmap source =
581      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
582  SkBitmap result = ComputeDecimatedImage(source, rows, columns);
583  EXPECT_FALSE(result.empty());
584  EXPECT_EQ(300, result.width());
585  EXPECT_EQ(200, result.height());
586
587  // The call should have removed all empty spaces.
588  ASSERT_TRUE(CompareImageFragments(source,
589                                    result,
590                                    gfx::Size(100, 100),
591                                    gfx::Point(100, 200),
592                                    gfx::Point(0, 0)));
593  ASSERT_TRUE(CompareImageFragments(source,
594                                    result,
595                                    gfx::Size(100, 100),
596                                    gfx::Point(300, 200),
597                                    gfx::Point(100, 0)));
598  ASSERT_TRUE(CompareImageFragments(source,
599                                    result,
600                                    gfx::Size(100, 100),
601                                    gfx::Point(500, 200),
602                                    gfx::Point(200, 0)));
603  ASSERT_TRUE(CompareImageFragments(source,
604                                    result,
605                                    gfx::Size(100, 100),
606                                    gfx::Point(100, 400),
607                                    gfx::Point(0, 100)));
608}
609
610TEST_F(ThumbnailContentAnalysisTest, CreateRetargetedThumbnailImage) {
611  gfx::Size image_size(1200, 1300);
612  gfx::Canvas canvas(image_size, 1.0f, true);
613
614  // The following will create a 'fake image' consisting of color blocks placed
615  // on a neutral background. The entire layout is supposed to mimic a
616  // screenshot of a web page.
617  // The tested function is supposed to locate the interesing areas in the
618  // middle.
619  const int margin_horizontal = 60;
620  const int margin_vertical = 20;
621  canvas.FillRect(gfx::Rect(image_size), SkColorSetRGB(200, 210, 210));
622  const gfx::Rect header_rect(margin_horizontal,
623                              margin_vertical,
624                              image_size.width() - 2 * margin_horizontal,
625                              100);
626  const gfx::Rect footer_rect(margin_horizontal,
627                              image_size.height() - margin_vertical - 100,
628                              image_size.width() - 2 * margin_horizontal,
629                              100);
630  const gfx::Rect body_rect(margin_horizontal,
631                            header_rect.bottom() + margin_vertical,
632                            image_size.width() - 2 * margin_horizontal,
633                            footer_rect.y() - header_rect.bottom() -
634                            2 * margin_vertical);
635  canvas.FillRect(header_rect, SkColorSetRGB(200, 40, 10));
636  canvas.FillRect(footer_rect, SkColorSetRGB(10, 40, 180));
637  canvas.FillRect(body_rect, SkColorSetRGB(150, 180, 40));
638
639  // 'Fine print' at the bottom.
640  const int fine_print = 8;
641  const SkColor print_color = SkColorSetRGB(45, 30, 30);
642  for (int y = footer_rect.y() + fine_print;
643       y < footer_rect.bottom() - fine_print;
644       y += 2 * fine_print) {
645    for (int x = footer_rect.x() + fine_print;
646         x < footer_rect.right() - fine_print;
647         x += 2 * fine_print) {
648      canvas.DrawRect(gfx::Rect(x, y, fine_print, fine_print),  print_color);
649    }
650  }
651
652  // Blocky content at the top.
653  const int block_size = header_rect.height() - margin_vertical;
654  for (int x = header_rect.x() + margin_horizontal;
655       x < header_rect.right() - block_size;
656       x += block_size + margin_horizontal) {
657    const int half_block = block_size / 2 - 5;
658    const SkColor block_color = SkColorSetRGB(255, 255, 255);
659    const int y = header_rect.y() + margin_vertical / 2;
660    int second_col = x + half_block + 10;
661    int second_row = y + half_block + 10;
662    canvas.FillRect(gfx::Rect(x, y, half_block, block_size), block_color);
663    canvas.FillRect(gfx::Rect(second_col,  y, half_block, half_block),
664                    block_color);
665    canvas.FillRect(gfx::Rect(second_col, second_row, half_block, half_block),
666                    block_color);
667  }
668
669  // Now the main body. Mostly text with some 'pictures'.
670  for (int y = body_rect.y() + fine_print;
671       y < body_rect.bottom() - fine_print;
672       y += 2 * fine_print) {
673    for (int x = body_rect.x() + fine_print;
674         x < body_rect.right() - fine_print;
675         x += 2 * fine_print) {
676      canvas.DrawRect(gfx::Rect(x, y, fine_print, fine_print),  print_color);
677    }
678  }
679
680  for (int line = 0; line < 3; ++line) {
681    int alignment = line % 2;
682    const int y = body_rect.y() +
683        body_rect.height() / 3 * line + margin_vertical;
684    const int x = body_rect.x() +
685        alignment * body_rect.width() / 2 + margin_vertical;
686    gfx::Rect pict_rect(x, y,
687                        body_rect.width() / 2 - 2 * margin_vertical,
688                        body_rect.height() / 3 - 2 * margin_vertical);
689    canvas.FillRect(pict_rect, SkColorSetRGB(255, 255, 255));
690    canvas.DrawRect(pict_rect, SkColorSetRGB(0, 0, 0));
691  }
692
693  SkBitmap source =
694      skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
695
696  SkBitmap result = CreateRetargetedThumbnailImage(
697      source, gfx::Size(424, 264), 2.5);
698  EXPECT_FALSE(result.empty());
699
700  // Given the nature of computation We can't really assert much here about the
701  // image itself. We know it should have been computed, should be smaller than
702  // the original and it must not be zero.
703  EXPECT_LT(result.width(), image_size.width());
704  EXPECT_LT(result.height(), image_size.height());
705
706  int histogram[256] = {};
707  color_utils::BuildLumaHistogram(result, histogram);
708  int non_zero_color_count = std::count_if(
709      histogram, histogram + 256, std::bind2nd(std::greater<int>(), 0));
710  EXPECT_GT(non_zero_color_count, 4);
711
712}
713
714}  // namespace thumbnailing_utils
715