Home | History | Annotate | Download | only in thumbnails
      1 // Copyright (c) 2013 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 <deque>
     10 #include <functional>
     11 #include <limits>
     12 #include <numeric>
     13 #include <vector>
     14 
     15 #include "base/logging.h"
     16 #include "skia/ext/convolver.h"
     17 #include "skia/ext/recursive_gaussian_convolution.h"
     18 #include "third_party/skia/include/core/SkBitmap.h"
     19 #include "third_party/skia/include/core/SkSize.h"
     20 #include "ui/gfx/color_analysis.h"
     21 
     22 namespace {
     23 
     24 const float kSigmaThresholdForRecursive = 1.5f;
     25 const float kAspectRatioToleranceFactor = 1.02f;
     26 
     27 template<class InputIterator, class OutputIterator, class Compare>
     28 void SlidingWindowMinMax(InputIterator first,
     29                          InputIterator last,
     30                          OutputIterator output,
     31                          int window_size,
     32                          Compare cmp) {
     33   typedef std::deque<
     34       std::pair<typename std::iterator_traits<InputIterator>::value_type, int> >
     35           deque_type;
     36   deque_type slider;
     37   int front_tail_length = window_size / 2;
     38   int i = 0;
     39   DCHECK_LT(front_tail_length, last - first);
     40   // This min-max filter functions the way image filters do. The min/max we
     41   // compute is placed in the center of the window. Thus, first we need to
     42   // 'pre-load' the window with the slider with right-tail of the filter.
     43   for (; first < last && i < front_tail_length; ++i, ++first)
     44     slider.push_back(std::make_pair(*first, i));
     45 
     46   for (; first < last; ++i, ++first, ++output) {
     47     while (!slider.empty() && !cmp(slider.back().first, *first))
     48       slider.pop_back();
     49     slider.push_back(std::make_pair(*first, i));
     50 
     51     while (slider.front().second <= i - window_size)
     52       slider.pop_front();
     53     *output = slider.front().first;
     54   }
     55 
     56   // Now at the tail-end we will simply need to use whatever value is left of
     57   // the filter to compute the remaining front_tail_length taps in the output.
     58 
     59   // If input shorter than window, remainder length needs to be adjusted.
     60   front_tail_length = std::min(front_tail_length, i);
     61   for (; front_tail_length >= 0; --front_tail_length, ++i) {
     62     while (slider.front().second <= i - window_size)
     63       slider.pop_front();
     64     *output = slider.front().first;
     65   }
     66 }
     67 
     68 size_t FindOtsuThresholdingIndex(const std::vector<int>& histogram) {
     69   // Otsu's method seeks to maximize variance between two classes of pixels
     70   // correspondng to valleys and peaks of the profile.
     71   double w1 = histogram[0];  // Total weight of the first class.
     72   double t1 = 0.5 * w1;
     73   double w2 = 0.0;
     74   double t2 = 0.0;
     75   for (size_t i = 1; i < histogram.size(); ++i) {
     76     w2 += histogram[i];
     77     t2 += (0.5 + i) * histogram[i];
     78   }
     79 
     80   size_t max_index = 0;
     81   double m1 = t1 / w1;
     82   double m2 = t2 / w2;
     83   double max_variance_score = w1 * w2 * (m1 - m2) * (m1 - m2);
     84   // Iterate through all possible ways of splitting the histogram.
     85   for (size_t i = 1; i < histogram.size() - 1; i++) {
     86     double bin_volume = (0.5 + i) * histogram[i];
     87     w1 += histogram[i];
     88     w2 -= histogram[i];
     89     t2 -= bin_volume;
     90     t1 += bin_volume;
     91     m1 = t1 / w1;
     92     m2 = t2 / w2;
     93     double variance_score = w1 * w2 * (m1 - m2) * (m1 - m2);
     94     if (variance_score >= max_variance_score) {
     95       max_variance_score = variance_score;
     96       max_index = i;
     97     }
     98   }
     99 
    100   return max_index;
    101 }
    102 
    103 bool ComputeScaledHistogram(const std::vector<float>& source,
    104                             std::vector<int>* histogram,
    105                             std::pair<float, float>* minmax) {
    106   DCHECK(histogram);
    107   DCHECK(minmax);
    108   histogram->clear();
    109   histogram->resize(256);
    110   float value_min = std::numeric_limits<float>::max();
    111   float value_max = 0.0f;
    112 
    113   std::vector<float>::const_iterator it;
    114   for (it = source.begin(); it < source.end(); ++it) {
    115     value_min = std::min(value_min, *it);
    116     value_max = std::max(value_max, *it);
    117   }
    118 
    119   *minmax = std::make_pair(value_min, value_max);
    120 
    121   if (value_max - value_min <= std::numeric_limits<float>::epsilon() * 100.0f) {
    122     // Scaling won't work and there is nothing really to segment anyway.
    123     return false;
    124   }
    125 
    126   float value_span = value_max - value_min;
    127   float scale = 255.0f / value_span;
    128   for (it = source.begin(); it < source.end(); ++it) {
    129     float scaled_value = (*it - value_min) * scale;
    130     (*histogram)[static_cast<int>(scaled_value)] += 1;
    131   }
    132   return true;
    133 }
    134 
    135 void ConstrainedProfileThresholding(const std::vector<float>& profile,
    136                                     const std::vector<int>& histogram,
    137                                     int current_clip_index,
    138                                     float current_threshold,
    139                                     const std::pair<float, float>& range,
    140                                     int size_for_threshold,
    141                                     int target_size,
    142                                     std::vector<bool>* result) {
    143   DCHECK(!profile.empty());
    144   DCHECK_EQ(histogram.size(), 256U);
    145   DCHECK(result);
    146 
    147   // A subroutine performing thresholding on the |profile|.
    148   if (size_for_threshold != target_size) {
    149     // Find a cut-off point (on the histogram) closest to the desired size.
    150     int candidate_size = profile.size();
    151     int candidate_clip_index = 0;
    152     for (std::vector<int>::const_iterator it = histogram.begin();
    153          it != histogram.end(); ++it, ++candidate_clip_index) {
    154       if (std::abs(candidate_size - target_size) <
    155           std::abs(candidate_size - *it - target_size)) {
    156         break;
    157       }
    158       candidate_size -= *it;
    159     }
    160 
    161     if (std::abs(candidate_size - target_size) <
    162         std::abs(candidate_size -size_for_threshold)) {
    163       current_clip_index = candidate_clip_index;
    164       current_threshold =  (range.second - range.first) *
    165           current_clip_index / 255.0f + range.first;
    166       // Recount, rather than assume. One-offs due to rounding can be very
    167       // harmful when eroding / dilating the result.
    168       size_for_threshold = std::count_if(
    169           profile.begin(), profile.end(),
    170           std::bind2nd(std::greater<float>(), current_threshold));
    171     }
    172   }
    173 
    174   result->resize(profile.size());
    175   for (size_t i = 0; i < profile.size(); ++i)
    176     (*result)[i] = profile[i] > current_threshold;
    177 
    178   while (size_for_threshold > target_size) {
    179     // If the current size is larger than target size, erode exactly as needed.
    180     std::vector<bool>::iterator mod_it = result->begin();
    181     std::vector<bool>::const_iterator lead_it = result->begin();
    182     bool prev_value = true;
    183     for (++lead_it;
    184          lead_it < result->end() && size_for_threshold > target_size;
    185          ++lead_it, ++mod_it) {
    186       bool value = *mod_it;
    187       // If any neighbour is false, switch the element off.
    188       if (!prev_value || !*lead_it) {
    189         *mod_it = false;
    190         --size_for_threshold;
    191       }
    192       prev_value = value;
    193     }
    194 
    195     if (lead_it == result->end() && !prev_value) {
    196       *mod_it = false;
    197       --size_for_threshold;
    198     }
    199   }
    200 
    201   while (size_for_threshold < target_size) {
    202     std::vector<bool>::iterator mod_it = result->begin();
    203     std::vector<bool>::const_iterator lead_it = result->begin();
    204     bool prev_value = false;
    205     for (++lead_it;
    206          lead_it < result->end() && size_for_threshold < target_size;
    207          ++lead_it, ++mod_it) {
    208       bool value = *mod_it;
    209       // If any neighbour is false, switch the element off.
    210       if (!prev_value || !*lead_it) {
    211         *mod_it = true;
    212         ++size_for_threshold;
    213       }
    214       prev_value = value;
    215     }
    216 
    217     if (lead_it == result->end() && !prev_value) {
    218       *mod_it = true;
    219       ++size_for_threshold;
    220     }
    221   }
    222 }
    223 
    224 }  // namespace
    225 
    226 namespace thumbnailing_utils {
    227 
    228 void ApplyGaussianGradientMagnitudeFilter(SkBitmap* input_bitmap,
    229                                           float kernel_sigma) {
    230   // The purpose of this function is to highlight salient
    231   // (attention-attracting?) features of the image for use in image
    232   // retargeting.
    233   SkAutoLockPixels source_lock(*input_bitmap);
    234   DCHECK(input_bitmap);
    235   DCHECK(input_bitmap->getPixels());
    236   DCHECK_EQ(SkBitmap::kA8_Config, input_bitmap->config());
    237 
    238   // To perform computations we will need one intermediate buffer. It can
    239   // very well be just another bitmap.
    240   const SkISize image_size = SkISize::Make(input_bitmap->width(),
    241                                            input_bitmap->height());
    242   SkBitmap intermediate;
    243   intermediate.setConfig(
    244       input_bitmap->config(), image_size.width(), image_size.height());
    245   intermediate.allocPixels();
    246 
    247   SkBitmap intermediate2;
    248   intermediate2.setConfig(
    249       input_bitmap->config(), image_size.width(), image_size.height());
    250   intermediate2.allocPixels();
    251 
    252 
    253   if (kernel_sigma <= kSigmaThresholdForRecursive) {
    254     // For small kernels classic implementation is faster.
    255     skia::ConvolutionFilter1D smoothing_filter;
    256     skia::SetUpGaussianConvolutionKernel(
    257         &smoothing_filter, kernel_sigma, false);
    258     skia::SingleChannelConvolveX1D(
    259         input_bitmap->getAddr8(0, 0),
    260         static_cast<int>(input_bitmap->rowBytes()),
    261         0, input_bitmap->bytesPerPixel(),
    262         smoothing_filter,
    263         image_size,
    264         intermediate.getAddr8(0, 0),
    265         static_cast<int>(intermediate.rowBytes()),
    266         0, intermediate.bytesPerPixel(), false);
    267     skia::SingleChannelConvolveY1D(
    268         intermediate.getAddr8(0, 0),
    269         static_cast<int>(intermediate.rowBytes()),
    270         0, intermediate.bytesPerPixel(),
    271         smoothing_filter,
    272         image_size,
    273         input_bitmap->getAddr8(0, 0),
    274         static_cast<int>(input_bitmap->rowBytes()),
    275         0, input_bitmap->bytesPerPixel(), false);
    276 
    277     skia::ConvolutionFilter1D gradient_filter;
    278     skia::SetUpGaussianConvolutionKernel(&gradient_filter, kernel_sigma, true);
    279     skia::SingleChannelConvolveX1D(
    280         input_bitmap->getAddr8(0, 0),
    281         static_cast<int>(input_bitmap->rowBytes()),
    282         0, input_bitmap->bytesPerPixel(),
    283         gradient_filter,
    284         image_size,
    285         intermediate.getAddr8(0, 0),
    286         static_cast<int>(intermediate.rowBytes()),
    287         0, intermediate.bytesPerPixel(), true);
    288     skia::SingleChannelConvolveY1D(
    289         input_bitmap->getAddr8(0, 0),
    290         static_cast<int>(input_bitmap->rowBytes()),
    291         0, input_bitmap->bytesPerPixel(),
    292         gradient_filter,
    293         image_size,
    294         intermediate2.getAddr8(0, 0),
    295         static_cast<int>(intermediate2.rowBytes()),
    296         0, intermediate2.bytesPerPixel(), true);
    297   } else {
    298     // For larger sigma values use the recursive filter.
    299     skia::RecursiveFilter smoothing_filter(kernel_sigma,
    300                                            skia::RecursiveFilter::FUNCTION);
    301     skia::SingleChannelRecursiveGaussianX(
    302         input_bitmap->getAddr8(0, 0),
    303         static_cast<int>(input_bitmap->rowBytes()),
    304         0, input_bitmap->bytesPerPixel(),
    305         smoothing_filter,
    306         image_size,
    307         intermediate.getAddr8(0, 0),
    308         static_cast<int>(intermediate.rowBytes()),
    309         0, intermediate.bytesPerPixel(), false);
    310     unsigned char smoothed_max = skia::SingleChannelRecursiveGaussianY(
    311         intermediate.getAddr8(0, 0),
    312         static_cast<int>(intermediate.rowBytes()),
    313         0, intermediate.bytesPerPixel(),
    314         smoothing_filter,
    315         image_size,
    316         input_bitmap->getAddr8(0, 0),
    317         static_cast<int>(input_bitmap->rowBytes()),
    318         0, input_bitmap->bytesPerPixel(), false);
    319     if (smoothed_max < 127) {
    320       int bit_shift = 8 - static_cast<int>(
    321           std::log10(static_cast<float>(smoothed_max)) / std::log10(2.0f));
    322       for (int r = 0; r < image_size.height(); ++r) {
    323         uint8* row = input_bitmap->getAddr8(0, r);
    324         for (int c = 0; c < image_size.width(); ++c, ++row) {
    325           *row <<= bit_shift;
    326         }
    327       }
    328     }
    329 
    330     skia::RecursiveFilter gradient_filter(
    331         kernel_sigma, skia::RecursiveFilter::FIRST_DERIVATIVE);
    332     skia::SingleChannelRecursiveGaussianX(
    333         input_bitmap->getAddr8(0, 0),
    334         static_cast<int>(input_bitmap->rowBytes()),
    335         0, input_bitmap->bytesPerPixel(),
    336         gradient_filter,
    337         image_size,
    338         intermediate.getAddr8(0, 0),
    339         static_cast<int>(intermediate.rowBytes()),
    340         0, intermediate.bytesPerPixel(), true);
    341     skia::SingleChannelRecursiveGaussianY(
    342         input_bitmap->getAddr8(0, 0),
    343         static_cast<int>(input_bitmap->rowBytes()),
    344         0, input_bitmap->bytesPerPixel(),
    345         gradient_filter,
    346         image_size,
    347         intermediate2.getAddr8(0, 0),
    348         static_cast<int>(intermediate2.rowBytes()),
    349         0, intermediate2.bytesPerPixel(), true);
    350   }
    351 
    352   unsigned grad_max = 0;
    353   for (int r = 0; r < image_size.height(); ++r) {
    354     const uint8* grad_x_row = intermediate.getAddr8(0, r);
    355     const uint8* grad_y_row = intermediate2.getAddr8(0, r);
    356     for (int c = 0; c < image_size.width(); ++c) {
    357       unsigned grad_x = grad_x_row[c];
    358       unsigned grad_y = grad_y_row[c];
    359       grad_max = std::max(grad_max, grad_x * grad_x + grad_y * grad_y);
    360     }
    361   }
    362 
    363   int bit_shift = 0;
    364   if (grad_max > 255)
    365     bit_shift = static_cast<int>(
    366         std::log10(static_cast<float>(grad_max)) / std::log10(2.0f)) - 7;
    367   for (int r = 0; r < image_size.height(); ++r) {
    368     const uint8* grad_x_row = intermediate.getAddr8(0, r);
    369     const uint8* grad_y_row = intermediate2.getAddr8(0, r);
    370     uint8* target_row = input_bitmap->getAddr8(0, r);
    371     for (int c = 0; c < image_size.width(); ++c) {
    372       unsigned grad_x = grad_x_row[c];
    373       unsigned grad_y = grad_y_row[c];
    374       target_row[c] = (grad_x * grad_x + grad_y * grad_y) >> bit_shift;
    375     }
    376   }
    377 }
    378 
    379 void ExtractImageProfileInformation(const SkBitmap& input_bitmap,
    380                                     const gfx::Rect& area,
    381                                     const gfx::Size& target_size,
    382                                     bool apply_log,
    383                                     std::vector<float>* rows,
    384                                     std::vector<float>* columns) {
    385   SkAutoLockPixels source_lock(input_bitmap);
    386   DCHECK(rows);
    387   DCHECK(columns);
    388   DCHECK(input_bitmap.getPixels());
    389   DCHECK_EQ(SkBitmap::kA8_Config, input_bitmap.config());
    390   DCHECK_GE(area.x(), 0);
    391   DCHECK_GE(area.y(), 0);
    392   DCHECK_LE(area.right(), input_bitmap.width());
    393   DCHECK_LE(area.bottom(), input_bitmap.height());
    394 
    395   // Make sure rows and columns are allocated and initialized to 0.
    396   rows->clear();
    397   columns->clear();
    398   rows->resize(area.height(), 0);
    399   columns->resize(area.width(), 0);
    400 
    401   for (int r = 0; r < area.height(); ++r) {
    402     // Points to the first byte of the row in the rectangle.
    403     const uint8* image_row = input_bitmap.getAddr8(area.x(), r + area.y());
    404     unsigned row_sum = 0;
    405     for (int c = 0; c < area.width(); ++c, ++image_row) {
    406       row_sum += *image_row;
    407       (*columns)[c] += *image_row;
    408     }
    409     (*rows)[r] = row_sum;
    410   }
    411 
    412   if (apply_log) {
    413     // Generally for processing we will need to take logarithm of this data.
    414     // The option not to apply it is left principally as a test seam.
    415     std::vector<float>::iterator it;
    416     for (it = columns->begin(); it < columns->end(); ++it)
    417       *it = std::log(1.0f + *it);
    418 
    419     for (it = rows->begin(); it < rows->end(); ++it)
    420       *it = std::log(1.0f + *it);
    421   }
    422 
    423   if (!target_size.IsEmpty()) {
    424     // If the target size is given, profiles should be further processed through
    425     // morphological closing. The idea is to close valleys smaller than what
    426     // can be seen after scaling down to avoid deforming noticable features
    427     // when profiles are used.
    428     // Morphological closing is defined as dilation followed by errosion. In
    429     // normal-speak: sliding-window maximum followed by minimum.
    430     int column_window_size = 1 + 2 *
    431         static_cast<int>(0.5f * area.width() / target_size.width() + 0.5f);
    432     int row_window_size = 1 + 2 *
    433         static_cast<int>(0.5f * area.height() / target_size.height() + 0.5f);
    434 
    435     // Dilate and erode each profile with the given window size.
    436     if (column_window_size >= 3) {
    437       SlidingWindowMinMax(columns->begin(),
    438                           columns->end(),
    439                           columns->begin(),
    440                           column_window_size,
    441                           std::greater<float>());
    442       SlidingWindowMinMax(columns->begin(),
    443                           columns->end(),
    444                           columns->begin(),
    445                           column_window_size,
    446                           std::less<float>());
    447     }
    448 
    449     if (row_window_size >= 3) {
    450       SlidingWindowMinMax(rows->begin(),
    451                           rows->end(),
    452                           rows->begin(),
    453                           row_window_size,
    454                           std::greater<float>());
    455       SlidingWindowMinMax(rows->begin(),
    456                           rows->end(),
    457                           rows->begin(),
    458                           row_window_size,
    459                           std::less<float>());
    460     }
    461   }
    462 }
    463 
    464 float AutoSegmentPeaks(const std::vector<float>& input) {
    465   // This is a thresholding operation based on Otsu's method.
    466   std::vector<int> histogram;
    467   std::pair<float, float> minmax;
    468   if (!ComputeScaledHistogram(input, &histogram, &minmax))
    469     return minmax.first;
    470 
    471   // max_index refers to the bin *after* which we need to split. The sought
    472   // threshold is the centre of this bin, scaled back to the original range.
    473   size_t max_index = FindOtsuThresholdingIndex(histogram);
    474   return (minmax.second - minmax.first) * (max_index + 0.5f) / 255.0f +
    475       minmax.first;
    476 }
    477 
    478 gfx::Size AdjustClippingSizeToAspectRatio(const gfx::Size& target_size,
    479                                           const gfx::Size& image_size,
    480                                           const gfx::Size& computed_size) {
    481   DCHECK_GT(target_size.width(), 0);
    482   DCHECK_GT(target_size.height(), 0);
    483   // If the computed thumbnail would be too wide or to tall, we shall attempt
    484   // to fix it. Generally the idea is to re-add content to the part which has
    485   // been more aggressively shrunk unless there is nothing to add there or if
    486   // adding there won't fix anything. Should that be the case,  we will
    487   // (reluctantly) take away more from the other dimension.
    488   float desired_aspect =
    489       static_cast<float>(target_size.width()) / target_size.height();
    490   int computed_width = std::max(computed_size.width(), target_size.width());
    491   int computed_height = std::max(computed_size.height(), target_size.height());
    492   float computed_aspect = static_cast<float>(computed_width) / computed_height;
    493   float aspect_change_delta = std::abs(computed_aspect - desired_aspect);
    494   float prev_aspect_change_delta = 1000.0f;
    495   const float kAspectChangeEps = 0.01f;
    496   const float kLargeEffect = 2.0f;
    497 
    498   while ((prev_aspect_change_delta - aspect_change_delta > kAspectChangeEps) &&
    499          (computed_aspect / desired_aspect > kAspectRatioToleranceFactor ||
    500           desired_aspect / computed_aspect > kAspectRatioToleranceFactor)) {
    501     int new_computed_width = computed_width;
    502     int new_computed_height = computed_height;
    503     float row_dimension_shrink =
    504         static_cast<float>(image_size.height()) / computed_height;
    505     float column_dimension_shrink =
    506         static_cast<float>(image_size.width()) / computed_width;
    507 
    508     if (computed_aspect / desired_aspect > kAspectRatioToleranceFactor) {
    509       // Too wide.
    510       if (row_dimension_shrink > column_dimension_shrink) {
    511         // Bring the computed_height to the least of:
    512         // (1) image height (2) the number of lines that would
    513         // make up the desired aspect or (3) number of lines we would get
    514         // at the same 'aggressivity' level as width or.
    515         new_computed_height = std::min(
    516             static_cast<int>(image_size.height()),
    517             static_cast<int>(computed_width / desired_aspect + 0.5f));
    518         new_computed_height = std::min(
    519             new_computed_height,
    520             static_cast<int>(
    521                 image_size.height() / column_dimension_shrink + 0.5f));
    522       } else if (row_dimension_shrink >= kLargeEffect ||
    523                  new_computed_width <= target_size.width()) {
    524         // Even though rows were resized less, we will generally rather add than
    525         // remove (or there is nothing to remove in x already).
    526         new_computed_height = std::min(
    527             static_cast<int>(image_size.height()),
    528             static_cast<int>(computed_width / desired_aspect + 0.5f));
    529       } else {
    530         // Rows were already shrunk less aggressively. This means there is
    531         // simply no room left too expand. Cut columns to get the desired
    532         // aspect ratio.
    533         new_computed_width = desired_aspect * computed_height + 0.5f;
    534       }
    535     } else {
    536       // Too tall.
    537       if (column_dimension_shrink > row_dimension_shrink) {
    538         // Columns were shrunk more aggressively. Try to relax the same way as
    539         // above.
    540         new_computed_width = std::min(
    541             static_cast<int>(image_size.width()),
    542             static_cast<int>(desired_aspect * computed_height + 0.5f));
    543         new_computed_width = std::min(
    544             new_computed_width,
    545             static_cast<int>(
    546                 image_size.width() / row_dimension_shrink  + 0.5f));
    547       } else if (column_dimension_shrink  >= kLargeEffect ||
    548                  new_computed_height <= target_size.height()) {
    549         new_computed_width = std::min(
    550             static_cast<int>(image_size.width()),
    551             static_cast<int>(desired_aspect * computed_height + 0.5f));
    552       } else {
    553         new_computed_height = computed_width / desired_aspect + 0.5f;
    554       }
    555     }
    556 
    557     new_computed_width = std::max(new_computed_width, target_size.width());
    558     new_computed_height = std::max(new_computed_height, target_size.height());
    559 
    560     // Update loop control variables.
    561     float new_computed_aspect =
    562         static_cast<float>(new_computed_width) / new_computed_height;
    563 
    564     if (std::abs(new_computed_aspect - desired_aspect) >
    565         std::abs(computed_aspect - desired_aspect)) {
    566       // Do not take inferior results.
    567       break;
    568     }
    569 
    570     computed_width = new_computed_width;
    571     computed_height = new_computed_height;
    572     computed_aspect = new_computed_aspect;
    573     prev_aspect_change_delta = aspect_change_delta;
    574     aspect_change_delta = std::abs(new_computed_aspect - desired_aspect);
    575   }
    576 
    577   return gfx::Size(computed_width, computed_height);
    578 }
    579 
    580 void ConstrainedProfileSegmentation(const std::vector<float>& row_profile,
    581                                     const std::vector<float>& column_profile,
    582                                     const gfx::Size& target_size,
    583                                     std::vector<bool>* included_rows,
    584                                     std::vector<bool>* included_columns) {
    585   DCHECK(included_rows);
    586   DCHECK(included_columns);
    587 
    588   std::vector<int> histogram_rows;
    589   std::pair<float, float> minmax_rows;
    590   bool rows_well_behaved = ComputeScaledHistogram(
    591       row_profile, &histogram_rows, &minmax_rows);
    592 
    593   float row_threshold = minmax_rows.first;
    594   size_t clip_index_rows = 0;
    595 
    596   if (rows_well_behaved) {
    597     clip_index_rows = FindOtsuThresholdingIndex(histogram_rows);
    598     row_threshold = (minmax_rows.second - minmax_rows.first) *
    599         (clip_index_rows + 0.5f) / 255.0f + minmax_rows.first;
    600   }
    601 
    602   std::vector<int> histogram_columns;
    603   std::pair<float, float> minmax_columns;
    604   bool columns_well_behaved = ComputeScaledHistogram(column_profile,
    605                                                      &histogram_columns,
    606                                                      &minmax_columns);
    607   float column_threshold = minmax_columns.first;
    608   size_t clip_index_columns = 0;
    609 
    610   if (columns_well_behaved) {
    611     clip_index_columns = FindOtsuThresholdingIndex(histogram_columns);
    612     column_threshold = (minmax_columns.second - minmax_columns.first) *
    613         (clip_index_columns + 0.5f) / 255.0f + minmax_columns.first;
    614   }
    615 
    616   int auto_segmented_width = count_if(
    617       column_profile.begin(), column_profile.end(),
    618       std::bind2nd(std::greater<float>(), column_threshold));
    619   int auto_segmented_height = count_if(
    620       row_profile.begin(), row_profile.end(),
    621       std::bind2nd(std::greater<float>(), row_threshold));
    622 
    623   gfx::Size computed_size = AdjustClippingSizeToAspectRatio(
    624       target_size,
    625       gfx::Size(column_profile.size(), row_profile.size()),
    626       gfx::Size(auto_segmented_width, auto_segmented_height));
    627 
    628   // Apply thresholding.
    629   if (rows_well_behaved) {
    630     ConstrainedProfileThresholding(row_profile,
    631                                    histogram_rows,
    632                                    clip_index_rows,
    633                                    row_threshold,
    634                                    minmax_rows,
    635                                    auto_segmented_height,
    636                                    computed_size.height(),
    637                                    included_rows);
    638   } else {
    639     // This is essentially an error condition, invoked when no segmentation was
    640     // possible. This will result in applying a very low threshold and likely
    641     // in producing a thumbnail which should get rejected.
    642     included_rows->resize(row_profile.size());
    643     for (size_t i = 0; i < row_profile.size(); ++i)
    644       (*included_rows)[i] = row_profile[i] > row_threshold;
    645   }
    646 
    647   if (columns_well_behaved) {
    648     ConstrainedProfileThresholding(column_profile,
    649                                    histogram_columns,
    650                                    clip_index_columns,
    651                                    column_threshold,
    652                                    minmax_columns,
    653                                    auto_segmented_width,
    654                                    computed_size.width(),
    655                                    included_columns);
    656   } else {
    657     included_columns->resize(column_profile.size());
    658     for (size_t i = 0; i < column_profile.size(); ++i)
    659       (*included_columns)[i] = column_profile[i] > column_threshold;
    660   }
    661 }
    662 
    663 SkBitmap ComputeDecimatedImage(const SkBitmap& bitmap,
    664                                const std::vector<bool>& rows,
    665                                const std::vector<bool>& columns) {
    666   SkAutoLockPixels source_lock(bitmap);
    667   DCHECK(bitmap.getPixels());
    668   DCHECK_GT(bitmap.bytesPerPixel(), 0);
    669   DCHECK_EQ(bitmap.width(), static_cast<int>(columns.size()));
    670   DCHECK_EQ(bitmap.height(), static_cast<int>(rows.size()));
    671 
    672   unsigned target_row_count = std::count(rows.begin(), rows.end(), true);
    673   unsigned target_column_count = std::count(
    674       columns.begin(), columns.end(), true);
    675 
    676   if (target_row_count == 0 || target_column_count == 0)
    677     return SkBitmap();  // Not quite an error, so no DCHECK. Just return empty.
    678 
    679   if (target_row_count == rows.size() && target_column_count == columns.size())
    680     return SkBitmap();  // Equivalent of the situation above (empty target).
    681 
    682   // Allocate the target image.
    683   SkBitmap target;
    684   target.setConfig(bitmap.config(), target_column_count, target_row_count);
    685   target.allocPixels();
    686 
    687   int target_row = 0;
    688   for (int r = 0; r < bitmap.height(); ++r) {
    689     if (!rows[r])
    690       continue;  // We can just skip this one.
    691     uint8* src_row =
    692         static_cast<uint8*>(bitmap.getPixels()) + r * bitmap.rowBytes();
    693     uint8* insertion_target = static_cast<uint8*>(target.getPixels()) +
    694         target_row * target.rowBytes();
    695     int left_copy_pixel = -1;
    696     for (int c = 0; c < bitmap.width(); ++c) {
    697       if (left_copy_pixel < 0 && columns[c]) {
    698         left_copy_pixel = c;  // Next time we will start copying from here.
    699       } else if (left_copy_pixel >= 0 && !columns[c]) {
    700         // This closes a fragment we want to copy. We do it now.
    701         size_t bytes_to_copy = (c - left_copy_pixel) * bitmap.bytesPerPixel();
    702         memcpy(insertion_target,
    703                src_row + left_copy_pixel * bitmap.bytesPerPixel(),
    704                bytes_to_copy);
    705         left_copy_pixel = -1;
    706         insertion_target += bytes_to_copy;
    707       }
    708     }
    709     // We can still have the tail end to process here.
    710     if (left_copy_pixel >= 0) {
    711       size_t bytes_to_copy =
    712           (bitmap.width() - left_copy_pixel) * bitmap.bytesPerPixel();
    713       memcpy(insertion_target,
    714              src_row + left_copy_pixel * bitmap.bytesPerPixel(),
    715              bytes_to_copy);
    716     }
    717     target_row++;
    718   }
    719 
    720   return target;
    721 }
    722 
    723 SkBitmap CreateRetargetedThumbnailImage(
    724     const SkBitmap& source_bitmap,
    725     const gfx::Size& target_size,
    726     float kernel_sigma) {
    727   // First thing we need for this method is to color-reduce the source_bitmap.
    728   SkBitmap reduced_color;
    729   reduced_color.setConfig(
    730       SkBitmap::kA8_Config, source_bitmap.width(), source_bitmap.height());
    731   reduced_color.allocPixels();
    732 
    733   if (!color_utils::ComputePrincipalComponentImage(source_bitmap,
    734                                                    &reduced_color)) {
    735     // CCIR601 luminance conversion vector.
    736     gfx::Vector3dF transform(0.299f, 0.587f, 0.114f);
    737     if (!color_utils::ApplyColorReduction(
    738             source_bitmap, transform, true, &reduced_color)) {
    739       DLOG(WARNING) << "Failed to compute luminance image from a screenshot. "
    740                     << "Cannot compute retargeted thumbnail.";
    741       return SkBitmap();
    742     }
    743     DLOG(WARNING) << "Could not compute principal color image for a thumbnail. "
    744                   << "Using luminance instead.";
    745   }
    746 
    747   // Turn 'color-reduced' image into the 'energy' image.
    748   ApplyGaussianGradientMagnitudeFilter(&reduced_color, kernel_sigma);
    749 
    750   // Extract vertical and horizontal projection of image features.
    751   std::vector<float> row_profile;
    752   std::vector<float> column_profile;
    753   ExtractImageProfileInformation(reduced_color,
    754                                  gfx::Rect(reduced_color.width(),
    755                                            reduced_color.height()),
    756                                  target_size,
    757                                  true,
    758                                  &row_profile,
    759                                  &column_profile);
    760 
    761   std::vector<bool> included_rows, included_columns;
    762   ConstrainedProfileSegmentation(row_profile,
    763                                  column_profile,
    764                                  target_size,
    765                                  &included_rows,
    766                                  &included_columns);
    767 
    768   // Use the original image and computed inclusion vectors to create a resized
    769   // image.
    770   return ComputeDecimatedImage(source_bitmap, included_rows, included_columns);
    771 }
    772 
    773 }  // thumbnailing_utils
    774