96c93a030a0d5be484d9c2a3a3902224a9acd03e
[platform/framework/web/crosswalk.git] / src / chrome / browser / thumbnails / content_analysis.cc
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(kAlpha_8_SkColorType, input_bitmap->colorType());
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.allocPixels(input_bitmap->info().makeWH(image_size.width(),
244                                                        image_size.height()));
245
246   SkBitmap intermediate2;
247   intermediate2.allocPixels(input_bitmap->info().makeWH(image_size.width(),
248                                                         image_size.height()));
249
250   if (kernel_sigma <= kSigmaThresholdForRecursive) {
251     // For small kernels classic implementation is faster.
252     skia::ConvolutionFilter1D smoothing_filter;
253     skia::SetUpGaussianConvolutionKernel(
254         &smoothing_filter, kernel_sigma, false);
255     skia::SingleChannelConvolveX1D(
256         input_bitmap->getAddr8(0, 0),
257         static_cast<int>(input_bitmap->rowBytes()),
258         0, input_bitmap->bytesPerPixel(),
259         smoothing_filter,
260         image_size,
261         intermediate.getAddr8(0, 0),
262         static_cast<int>(intermediate.rowBytes()),
263         0, intermediate.bytesPerPixel(), false);
264     skia::SingleChannelConvolveY1D(
265         intermediate.getAddr8(0, 0),
266         static_cast<int>(intermediate.rowBytes()),
267         0, intermediate.bytesPerPixel(),
268         smoothing_filter,
269         image_size,
270         input_bitmap->getAddr8(0, 0),
271         static_cast<int>(input_bitmap->rowBytes()),
272         0, input_bitmap->bytesPerPixel(), false);
273
274     skia::ConvolutionFilter1D gradient_filter;
275     skia::SetUpGaussianConvolutionKernel(&gradient_filter, kernel_sigma, true);
276     skia::SingleChannelConvolveX1D(
277         input_bitmap->getAddr8(0, 0),
278         static_cast<int>(input_bitmap->rowBytes()),
279         0, input_bitmap->bytesPerPixel(),
280         gradient_filter,
281         image_size,
282         intermediate.getAddr8(0, 0),
283         static_cast<int>(intermediate.rowBytes()),
284         0, intermediate.bytesPerPixel(), true);
285     skia::SingleChannelConvolveY1D(
286         input_bitmap->getAddr8(0, 0),
287         static_cast<int>(input_bitmap->rowBytes()),
288         0, input_bitmap->bytesPerPixel(),
289         gradient_filter,
290         image_size,
291         intermediate2.getAddr8(0, 0),
292         static_cast<int>(intermediate2.rowBytes()),
293         0, intermediate2.bytesPerPixel(), true);
294   } else {
295     // For larger sigma values use the recursive filter.
296     skia::RecursiveFilter smoothing_filter(kernel_sigma,
297                                            skia::RecursiveFilter::FUNCTION);
298     skia::SingleChannelRecursiveGaussianX(
299         input_bitmap->getAddr8(0, 0),
300         static_cast<int>(input_bitmap->rowBytes()),
301         0, input_bitmap->bytesPerPixel(),
302         smoothing_filter,
303         image_size,
304         intermediate.getAddr8(0, 0),
305         static_cast<int>(intermediate.rowBytes()),
306         0, intermediate.bytesPerPixel(), false);
307     unsigned char smoothed_max = skia::SingleChannelRecursiveGaussianY(
308         intermediate.getAddr8(0, 0),
309         static_cast<int>(intermediate.rowBytes()),
310         0, intermediate.bytesPerPixel(),
311         smoothing_filter,
312         image_size,
313         input_bitmap->getAddr8(0, 0),
314         static_cast<int>(input_bitmap->rowBytes()),
315         0, input_bitmap->bytesPerPixel(), false);
316     if (smoothed_max < 127) {
317       int bit_shift = 8 - static_cast<int>(
318           std::log10(static_cast<float>(smoothed_max)) / std::log10(2.0f));
319       for (int r = 0; r < image_size.height(); ++r) {
320         uint8* row = input_bitmap->getAddr8(0, r);
321         for (int c = 0; c < image_size.width(); ++c, ++row) {
322           *row <<= bit_shift;
323         }
324       }
325     }
326
327     skia::RecursiveFilter gradient_filter(
328         kernel_sigma, skia::RecursiveFilter::FIRST_DERIVATIVE);
329     skia::SingleChannelRecursiveGaussianX(
330         input_bitmap->getAddr8(0, 0),
331         static_cast<int>(input_bitmap->rowBytes()),
332         0, input_bitmap->bytesPerPixel(),
333         gradient_filter,
334         image_size,
335         intermediate.getAddr8(0, 0),
336         static_cast<int>(intermediate.rowBytes()),
337         0, intermediate.bytesPerPixel(), true);
338     skia::SingleChannelRecursiveGaussianY(
339         input_bitmap->getAddr8(0, 0),
340         static_cast<int>(input_bitmap->rowBytes()),
341         0, input_bitmap->bytesPerPixel(),
342         gradient_filter,
343         image_size,
344         intermediate2.getAddr8(0, 0),
345         static_cast<int>(intermediate2.rowBytes()),
346         0, intermediate2.bytesPerPixel(), true);
347   }
348
349   unsigned grad_max = 0;
350   for (int r = 0; r < image_size.height(); ++r) {
351     const uint8* grad_x_row = intermediate.getAddr8(0, r);
352     const uint8* grad_y_row = intermediate2.getAddr8(0, r);
353     for (int c = 0; c < image_size.width(); ++c) {
354       unsigned grad_x = grad_x_row[c];
355       unsigned grad_y = grad_y_row[c];
356       grad_max = std::max(grad_max, grad_x * grad_x + grad_y * grad_y);
357     }
358   }
359
360   int bit_shift = 0;
361   if (grad_max > 255)
362     bit_shift = static_cast<int>(
363         std::log10(static_cast<float>(grad_max)) / std::log10(2.0f)) - 7;
364   for (int r = 0; r < image_size.height(); ++r) {
365     const uint8* grad_x_row = intermediate.getAddr8(0, r);
366     const uint8* grad_y_row = intermediate2.getAddr8(0, r);
367     uint8* target_row = input_bitmap->getAddr8(0, r);
368     for (int c = 0; c < image_size.width(); ++c) {
369       unsigned grad_x = grad_x_row[c];
370       unsigned grad_y = grad_y_row[c];
371       target_row[c] = (grad_x * grad_x + grad_y * grad_y) >> bit_shift;
372     }
373   }
374 }
375
376 void ExtractImageProfileInformation(const SkBitmap& input_bitmap,
377                                     const gfx::Rect& area,
378                                     const gfx::Size& target_size,
379                                     bool apply_log,
380                                     std::vector<float>* rows,
381                                     std::vector<float>* columns) {
382   SkAutoLockPixels source_lock(input_bitmap);
383   DCHECK(rows);
384   DCHECK(columns);
385   DCHECK(input_bitmap.getPixels());
386   DCHECK_EQ(kAlpha_8_SkColorType, input_bitmap.colorType());
387   DCHECK_GE(area.x(), 0);
388   DCHECK_GE(area.y(), 0);
389   DCHECK_LE(area.right(), input_bitmap.width());
390   DCHECK_LE(area.bottom(), input_bitmap.height());
391
392   // Make sure rows and columns are allocated and initialized to 0.
393   rows->clear();
394   columns->clear();
395   rows->resize(area.height(), 0);
396   columns->resize(area.width(), 0);
397
398   for (int r = 0; r < area.height(); ++r) {
399     // Points to the first byte of the row in the rectangle.
400     const uint8* image_row = input_bitmap.getAddr8(area.x(), r + area.y());
401     unsigned row_sum = 0;
402     for (int c = 0; c < area.width(); ++c, ++image_row) {
403       row_sum += *image_row;
404       (*columns)[c] += *image_row;
405     }
406     (*rows)[r] = row_sum;
407   }
408
409   if (apply_log) {
410     // Generally for processing we will need to take logarithm of this data.
411     // The option not to apply it is left principally as a test seam.
412     std::vector<float>::iterator it;
413     for (it = columns->begin(); it < columns->end(); ++it)
414       *it = std::log(1.0f + *it);
415
416     for (it = rows->begin(); it < rows->end(); ++it)
417       *it = std::log(1.0f + *it);
418   }
419
420   if (!target_size.IsEmpty()) {
421     // If the target size is given, profiles should be further processed through
422     // morphological closing. The idea is to close valleys smaller than what
423     // can be seen after scaling down to avoid deforming noticable features
424     // when profiles are used.
425     // Morphological closing is defined as dilation followed by errosion. In
426     // normal-speak: sliding-window maximum followed by minimum.
427     int column_window_size = 1 + 2 *
428         static_cast<int>(0.5f * area.width() / target_size.width() + 0.5f);
429     int row_window_size = 1 + 2 *
430         static_cast<int>(0.5f * area.height() / target_size.height() + 0.5f);
431
432     // Dilate and erode each profile with the given window size.
433     if (column_window_size >= 3) {
434       SlidingWindowMinMax(columns->begin(),
435                           columns->end(),
436                           columns->begin(),
437                           column_window_size,
438                           std::greater<float>());
439       SlidingWindowMinMax(columns->begin(),
440                           columns->end(),
441                           columns->begin(),
442                           column_window_size,
443                           std::less<float>());
444     }
445
446     if (row_window_size >= 3) {
447       SlidingWindowMinMax(rows->begin(),
448                           rows->end(),
449                           rows->begin(),
450                           row_window_size,
451                           std::greater<float>());
452       SlidingWindowMinMax(rows->begin(),
453                           rows->end(),
454                           rows->begin(),
455                           row_window_size,
456                           std::less<float>());
457     }
458   }
459 }
460
461 float AutoSegmentPeaks(const std::vector<float>& input) {
462   // This is a thresholding operation based on Otsu's method.
463   std::vector<int> histogram;
464   std::pair<float, float> minmax;
465   if (!ComputeScaledHistogram(input, &histogram, &minmax))
466     return minmax.first;
467
468   // max_index refers to the bin *after* which we need to split. The sought
469   // threshold is the centre of this bin, scaled back to the original range.
470   size_t max_index = FindOtsuThresholdingIndex(histogram);
471   return (minmax.second - minmax.first) * (max_index + 0.5f) / 255.0f +
472       minmax.first;
473 }
474
475 gfx::Size AdjustClippingSizeToAspectRatio(const gfx::Size& target_size,
476                                           const gfx::Size& image_size,
477                                           const gfx::Size& computed_size) {
478   DCHECK_GT(target_size.width(), 0);
479   DCHECK_GT(target_size.height(), 0);
480   // If the computed thumbnail would be too wide or to tall, we shall attempt
481   // to fix it. Generally the idea is to re-add content to the part which has
482   // been more aggressively shrunk unless there is nothing to add there or if
483   // adding there won't fix anything. Should that be the case,  we will
484   // (reluctantly) take away more from the other dimension.
485   float desired_aspect =
486       static_cast<float>(target_size.width()) / target_size.height();
487   int computed_width = std::max(computed_size.width(), target_size.width());
488   int computed_height = std::max(computed_size.height(), target_size.height());
489   float computed_aspect = static_cast<float>(computed_width) / computed_height;
490   float aspect_change_delta = std::abs(computed_aspect - desired_aspect);
491   float prev_aspect_change_delta = 1000.0f;
492   const float kAspectChangeEps = 0.01f;
493   const float kLargeEffect = 2.0f;
494
495   while ((prev_aspect_change_delta - aspect_change_delta > kAspectChangeEps) &&
496          (computed_aspect / desired_aspect > kAspectRatioToleranceFactor ||
497           desired_aspect / computed_aspect > kAspectRatioToleranceFactor)) {
498     int new_computed_width = computed_width;
499     int new_computed_height = computed_height;
500     float row_dimension_shrink =
501         static_cast<float>(image_size.height()) / computed_height;
502     float column_dimension_shrink =
503         static_cast<float>(image_size.width()) / computed_width;
504
505     if (computed_aspect / desired_aspect > kAspectRatioToleranceFactor) {
506       // Too wide.
507       if (row_dimension_shrink > column_dimension_shrink) {
508         // Bring the computed_height to the least of:
509         // (1) image height (2) the number of lines that would
510         // make up the desired aspect or (3) number of lines we would get
511         // at the same 'aggressivity' level as width or.
512         new_computed_height = std::min(
513             static_cast<int>(image_size.height()),
514             static_cast<int>(computed_width / desired_aspect + 0.5f));
515         new_computed_height = std::min(
516             new_computed_height,
517             static_cast<int>(
518                 image_size.height() / column_dimension_shrink + 0.5f));
519       } else if (row_dimension_shrink >= kLargeEffect ||
520                  new_computed_width <= target_size.width()) {
521         // Even though rows were resized less, we will generally rather add than
522         // remove (or there is nothing to remove in x already).
523         new_computed_height = std::min(
524             static_cast<int>(image_size.height()),
525             static_cast<int>(computed_width / desired_aspect + 0.5f));
526       } else {
527         // Rows were already shrunk less aggressively. This means there is
528         // simply no room left too expand. Cut columns to get the desired
529         // aspect ratio.
530         new_computed_width = desired_aspect * computed_height + 0.5f;
531       }
532     } else {
533       // Too tall.
534       if (column_dimension_shrink > row_dimension_shrink) {
535         // Columns were shrunk more aggressively. Try to relax the same way as
536         // above.
537         new_computed_width = std::min(
538             static_cast<int>(image_size.width()),
539             static_cast<int>(desired_aspect * computed_height + 0.5f));
540         new_computed_width = std::min(
541             new_computed_width,
542             static_cast<int>(
543                 image_size.width() / row_dimension_shrink  + 0.5f));
544       } else if (column_dimension_shrink  >= kLargeEffect ||
545                  new_computed_height <= target_size.height()) {
546         new_computed_width = std::min(
547             static_cast<int>(image_size.width()),
548             static_cast<int>(desired_aspect * computed_height + 0.5f));
549       } else {
550         new_computed_height = computed_width / desired_aspect + 0.5f;
551       }
552     }
553
554     new_computed_width = std::max(new_computed_width, target_size.width());
555     new_computed_height = std::max(new_computed_height, target_size.height());
556
557     // Update loop control variables.
558     float new_computed_aspect =
559         static_cast<float>(new_computed_width) / new_computed_height;
560
561     if (std::abs(new_computed_aspect - desired_aspect) >
562         std::abs(computed_aspect - desired_aspect)) {
563       // Do not take inferior results.
564       break;
565     }
566
567     computed_width = new_computed_width;
568     computed_height = new_computed_height;
569     computed_aspect = new_computed_aspect;
570     prev_aspect_change_delta = aspect_change_delta;
571     aspect_change_delta = std::abs(new_computed_aspect - desired_aspect);
572   }
573
574   return gfx::Size(computed_width, computed_height);
575 }
576
577 void ConstrainedProfileSegmentation(const std::vector<float>& row_profile,
578                                     const std::vector<float>& column_profile,
579                                     const gfx::Size& target_size,
580                                     std::vector<bool>* included_rows,
581                                     std::vector<bool>* included_columns) {
582   DCHECK(included_rows);
583   DCHECK(included_columns);
584
585   std::vector<int> histogram_rows;
586   std::pair<float, float> minmax_rows;
587   bool rows_well_behaved = ComputeScaledHistogram(
588       row_profile, &histogram_rows, &minmax_rows);
589
590   float row_threshold = minmax_rows.first;
591   size_t clip_index_rows = 0;
592
593   if (rows_well_behaved) {
594     clip_index_rows = FindOtsuThresholdingIndex(histogram_rows);
595     row_threshold = (minmax_rows.second - minmax_rows.first) *
596         (clip_index_rows + 0.5f) / 255.0f + minmax_rows.first;
597   }
598
599   std::vector<int> histogram_columns;
600   std::pair<float, float> minmax_columns;
601   bool columns_well_behaved = ComputeScaledHistogram(column_profile,
602                                                      &histogram_columns,
603                                                      &minmax_columns);
604   float column_threshold = minmax_columns.first;
605   size_t clip_index_columns = 0;
606
607   if (columns_well_behaved) {
608     clip_index_columns = FindOtsuThresholdingIndex(histogram_columns);
609     column_threshold = (minmax_columns.second - minmax_columns.first) *
610         (clip_index_columns + 0.5f) / 255.0f + minmax_columns.first;
611   }
612
613   int auto_segmented_width = count_if(
614       column_profile.begin(), column_profile.end(),
615       std::bind2nd(std::greater<float>(), column_threshold));
616   int auto_segmented_height = count_if(
617       row_profile.begin(), row_profile.end(),
618       std::bind2nd(std::greater<float>(), row_threshold));
619
620   gfx::Size computed_size = AdjustClippingSizeToAspectRatio(
621       target_size,
622       gfx::Size(column_profile.size(), row_profile.size()),
623       gfx::Size(auto_segmented_width, auto_segmented_height));
624
625   // Apply thresholding.
626   if (rows_well_behaved) {
627     ConstrainedProfileThresholding(row_profile,
628                                    histogram_rows,
629                                    clip_index_rows,
630                                    row_threshold,
631                                    minmax_rows,
632                                    auto_segmented_height,
633                                    computed_size.height(),
634                                    included_rows);
635   } else {
636     // This is essentially an error condition, invoked when no segmentation was
637     // possible. This will result in applying a very low threshold and likely
638     // in producing a thumbnail which should get rejected.
639     included_rows->resize(row_profile.size());
640     for (size_t i = 0; i < row_profile.size(); ++i)
641       (*included_rows)[i] = row_profile[i] > row_threshold;
642   }
643
644   if (columns_well_behaved) {
645     ConstrainedProfileThresholding(column_profile,
646                                    histogram_columns,
647                                    clip_index_columns,
648                                    column_threshold,
649                                    minmax_columns,
650                                    auto_segmented_width,
651                                    computed_size.width(),
652                                    included_columns);
653   } else {
654     included_columns->resize(column_profile.size());
655     for (size_t i = 0; i < column_profile.size(); ++i)
656       (*included_columns)[i] = column_profile[i] > column_threshold;
657   }
658 }
659
660 SkBitmap ComputeDecimatedImage(const SkBitmap& bitmap,
661                                const std::vector<bool>& rows,
662                                const std::vector<bool>& columns) {
663   SkAutoLockPixels source_lock(bitmap);
664   DCHECK(bitmap.getPixels());
665   DCHECK_GT(bitmap.bytesPerPixel(), 0);
666   DCHECK_EQ(bitmap.width(), static_cast<int>(columns.size()));
667   DCHECK_EQ(bitmap.height(), static_cast<int>(rows.size()));
668
669   unsigned target_row_count = std::count(rows.begin(), rows.end(), true);
670   unsigned target_column_count = std::count(
671       columns.begin(), columns.end(), true);
672
673   if (target_row_count == 0 || target_column_count == 0)
674     return SkBitmap();  // Not quite an error, so no DCHECK. Just return empty.
675
676   if (target_row_count == rows.size() && target_column_count == columns.size())
677     return SkBitmap();  // Equivalent of the situation above (empty target).
678
679   // Allocate the target image.
680   SkBitmap target;
681   target.allocPixels(bitmap.info().makeWH(target_column_count,
682                                           target_row_count));
683
684   int target_row = 0;
685   for (int r = 0; r < bitmap.height(); ++r) {
686     if (!rows[r])
687       continue;  // We can just skip this one.
688     uint8* src_row =
689         static_cast<uint8*>(bitmap.getPixels()) + r * bitmap.rowBytes();
690     uint8* insertion_target = static_cast<uint8*>(target.getPixels()) +
691         target_row * target.rowBytes();
692     int left_copy_pixel = -1;
693     for (int c = 0; c < bitmap.width(); ++c) {
694       if (left_copy_pixel < 0 && columns[c]) {
695         left_copy_pixel = c;  // Next time we will start copying from here.
696       } else if (left_copy_pixel >= 0 && !columns[c]) {
697         // This closes a fragment we want to copy. We do it now.
698         size_t bytes_to_copy = (c - left_copy_pixel) * bitmap.bytesPerPixel();
699         memcpy(insertion_target,
700                src_row + left_copy_pixel * bitmap.bytesPerPixel(),
701                bytes_to_copy);
702         left_copy_pixel = -1;
703         insertion_target += bytes_to_copy;
704       }
705     }
706     // We can still have the tail end to process here.
707     if (left_copy_pixel >= 0) {
708       size_t bytes_to_copy =
709           (bitmap.width() - left_copy_pixel) * bitmap.bytesPerPixel();
710       memcpy(insertion_target,
711              src_row + left_copy_pixel * bitmap.bytesPerPixel(),
712              bytes_to_copy);
713     }
714     target_row++;
715   }
716
717   return target;
718 }
719
720 SkBitmap CreateRetargetedThumbnailImage(
721     const SkBitmap& source_bitmap,
722     const gfx::Size& target_size,
723     float kernel_sigma) {
724   // First thing we need for this method is to color-reduce the source_bitmap.
725   SkBitmap reduced_color;
726   reduced_color.allocPixels(SkImageInfo::MakeA8(source_bitmap.width(),
727                                                 source_bitmap.height()));
728
729   if (!color_utils::ComputePrincipalComponentImage(source_bitmap,
730                                                    &reduced_color)) {
731     // CCIR601 luminance conversion vector.
732     gfx::Vector3dF transform(0.299f, 0.587f, 0.114f);
733     if (!color_utils::ApplyColorReduction(
734             source_bitmap, transform, true, &reduced_color)) {
735       DLOG(WARNING) << "Failed to compute luminance image from a screenshot. "
736                     << "Cannot compute retargeted thumbnail.";
737       return SkBitmap();
738     }
739     DLOG(WARNING) << "Could not compute principal color image for a thumbnail. "
740                   << "Using luminance instead.";
741   }
742
743   // Turn 'color-reduced' image into the 'energy' image.
744   ApplyGaussianGradientMagnitudeFilter(&reduced_color, kernel_sigma);
745
746   // Extract vertical and horizontal projection of image features.
747   std::vector<float> row_profile;
748   std::vector<float> column_profile;
749   ExtractImageProfileInformation(reduced_color,
750                                  gfx::Rect(reduced_color.width(),
751                                            reduced_color.height()),
752                                  target_size,
753                                  true,
754                                  &row_profile,
755                                  &column_profile);
756
757   std::vector<bool> included_rows, included_columns;
758   ConstrainedProfileSegmentation(row_profile,
759                                  column_profile,
760                                  target_size,
761                                  &included_rows,
762                                  &included_columns);
763
764   // Use the original image and computed inclusion vectors to create a resized
765   // image.
766   return ComputeDecimatedImage(source_bitmap, included_rows, included_columns);
767 }
768
769 }  // thumbnailing_utils