Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / thumbnails / content_analysis_unittest.cc
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
26 namespace {
27
28 #ifndef M_PI
29 #define M_PI 3.14159265358979323846
30 #endif
31
32 unsigned 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
47 bool 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
68 float 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
75 namespace thumbnailing_utils {
76
77 class ThumbnailContentAnalysisTest : public testing::Test {
78 };
79
80 TEST_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
123 TEST_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
161 TEST_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
223 TEST_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
280 TEST_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
432 TEST_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
480 TEST_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
557 TEST_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
606 TEST_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