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