Home | History | Annotate | Download | only in thumbnails
      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
    711