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