1d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)// found in the LICENSE file.
4d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
5d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "chrome/browser/thumbnails/simple_thumbnail_crop.h"
6d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
7d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "base/metrics/histogram.h"
8d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "content/public/browser/browser_thread.h"
9d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "skia/ext/platform_canvas.h"
10d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "ui/gfx/color_utils.h"
11d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "ui/gfx/image/image_skia.h"
12d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "ui/gfx/screen.h"
13d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "ui/gfx/scrollbar_size.h"
14d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)#include "ui/gfx/size_conversions.h"
15f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)#include "ui/gfx/skbitmap_operations.h"
16f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)
17d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)namespace {
18d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)static const char kThumbnailHistogramName[] = "Thumbnail.ComputeMS";
19d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)}
20d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
21d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)namespace thumbnails {
22d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
23d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)SimpleThumbnailCrop::SimpleThumbnailCrop(const gfx::Size& target_size)
247242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    : target_size_(target_size) {
255d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)  DCHECK(!target_size.IsEmpty());
267242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci}
27d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
28d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)ClipResult SimpleThumbnailCrop::GetCanvasCopyInfo(
29d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    const gfx::Size& source_size,
30d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    ui::ScaleFactor scale_factor,
31d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    gfx::Rect* clipping_rect,
32d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    gfx::Size* target_size) const {
33d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  DCHECK(!source_size.IsEmpty());
34d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  ClipResult clip_result = thumbnails::CLIP_RESULT_NOT_CLIPPED;
35d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  *clipping_rect = GetClippingRect(source_size, target_size_, &clip_result);
36d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  *target_size = GetCopySizeForThumbnail(scale_factor, target_size_);
37d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  return clip_result;
38d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)}
39d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
40d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)void SimpleThumbnailCrop::ProcessBitmap(
41d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    scoped_refptr<ThumbnailingContext> context,
42d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    const ConsumerCallback& callback,
43d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    const SkBitmap& bitmap) {
44d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
45d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  if (bitmap.isNull() || bitmap.empty())
46d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)    return;
47d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
48d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  SkBitmap thumbnail = CreateThumbnail(
49d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)      bitmap,
50d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)      ComputeTargetSizeAtMaximumScale(target_size_),
51d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)      &context->clip_result);
52d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
53d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  context->score.boring_score = CalculateBoringScore(thumbnail);
54d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)  context->score.good_clipping =
55d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)      (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
56d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)       context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE ||
57       context->clip_result == CLIP_RESULT_NOT_CLIPPED);
58
59  callback.Run(*context.get(), thumbnail);
60}
61
62double SimpleThumbnailCrop::CalculateBoringScore(const SkBitmap& bitmap) {
63  if (bitmap.isNull() || bitmap.empty())
64    return 1.0;
65  int histogram[256] = {0};
66  color_utils::BuildLumaHistogram(bitmap, histogram);
67
68  int color_count = *std::max_element(histogram, histogram + 256);
69  int pixel_count = bitmap.width() * bitmap.height();
70  return static_cast<double>(color_count) / pixel_count;
71}
72
73SkBitmap SimpleThumbnailCrop::GetClippedBitmap(const SkBitmap& bitmap,
74                                               int desired_width,
75                                               int desired_height,
76                                               ClipResult* clip_result) {
77  gfx::Rect clipping_rect =
78      GetClippingRect(gfx::Size(bitmap.width(), bitmap.height()),
79                      gfx::Size(desired_width, desired_height),
80                      clip_result);
81  SkIRect src_rect = { clipping_rect.x(), clipping_rect.y(),
82                       clipping_rect.right(), clipping_rect.bottom() };
83  SkBitmap clipped_bitmap;
84  bitmap.extractSubset(&clipped_bitmap, src_rect);
85  return clipped_bitmap;
86}
87
88// Returns the size used by RenderWidgetHost::CopyFromBackingStore.
89//
90// The size is calculated in such a way that the copied size in pixel becomes
91// equal to (f * kThumbnailWidth, f * kThumbnailHeight), where f is the scale
92// of ui::SCALE_FACTOR_200P. Since RenderWidgetHost::CopyFromBackingStore takes
93// the size in DIP, we need to adjust the size based on |view|'s device scale
94// factor in order to copy the pixels with the size above.
95//
96// The copied size was chosen for the following reasons.
97//
98// 1. When the scale factor of the primary monitor is ui::SCALE_FACTOR_200P, the
99// generated thumbnail size is (f * kThumbnailWidth, f * kThumbnailHeight).
100// In order to avoid degrading the image quality by magnification, the size
101// of the copied pixels should be equal to or larger than this thumbnail size.
102//
103// 2. RenderWidgetHost::CopyFromBackingStore can be costly especially when
104// it is necessary to read back the web contents image data from GPU. As the
105// cost is roughly propotional to the number of the copied pixels, the size of
106// the copied pixels should be as small as possible.
107//
108// When the scale factor of the primary monitor is ui::SCALE_FACTOR_100P,
109// we still copy the pixels with the same size as ui::SCALE_FACTOR_200P (2.0f)
110// because the resampling method used in RenderWidgetHost::CopyFromBackingStore
111// is not good enough for the resampled image to be used directly for the
112// thumbnail (http://crbug.com/141235). We assume this is not an issue in case of
113// ui::SCALE_FACTOR_200P because the high resolution thumbnail on high density
114// display alleviates the aliasing.
115// TODO(mazda): Copy the pixels with the smaller size in the case of
116// ui::SCALE_FACTOR_100P once the resampling method has been improved.
117// static
118gfx::Size SimpleThumbnailCrop::GetCopySizeForThumbnail(
119    ui::ScaleFactor scale_factor,
120    const gfx::Size& thumbnail_size) {
121  gfx::Size copy_size(thumbnail_size);
122  switch (scale_factor) {
123    case ui::SCALE_FACTOR_100P:
124      copy_size = gfx::ToFlooredSize(gfx::ScaleSize(copy_size, 2.0f));
125      break;
126    case ui::SCALE_FACTOR_200P:
127      // Use the size as-is.
128      break;
129    default:
130      DLOG(WARNING) << "Unsupported scale factor. Use the same copy size as "
131                    << "ui::SCALE_FACTOR_100P";
132      copy_size = gfx::ToFlooredSize(gfx::ScaleSize(
133          copy_size, gfx::ImageSkia::GetMaxSupportedScale()));
134      break;
135  }
136  return copy_size;
137}
138
139gfx::Rect SimpleThumbnailCrop::GetClippingRect(const gfx::Size& source_size,
140                                               const gfx::Size& desired_size,
141                                               ClipResult* clip_result) {
142  DCHECK(clip_result);
143
144  float desired_aspect =
145      static_cast<float>(desired_size.width()) / desired_size.height();
146
147  // Get the clipping rect so that we can preserve the aspect ratio while
148  // filling the destination.
149  gfx::Rect clipping_rect;
150  if (source_size.width() < desired_size.width() ||
151      source_size.height() < desired_size.height()) {
152    // Source image is smaller: we clip the part of source image within the
153    // dest rect, and then stretch it to fill the dest rect. We don't respect
154    // the aspect ratio in this case.
155    clipping_rect = gfx::Rect(desired_size);
156    *clip_result = thumbnails::CLIP_RESULT_SOURCE_IS_SMALLER;
157  } else {
158    float src_aspect =
159        static_cast<float>(source_size.width()) / source_size.height();
160    if (src_aspect > desired_aspect) {
161      // Wider than tall, clip horizontally: we center the smaller
162      // thumbnail in the wider screen.
163      int new_width = static_cast<int>(source_size.height() * desired_aspect);
164      int x_offset = (source_size.width() - new_width) / 2;
165      clipping_rect.SetRect(x_offset, 0, new_width, source_size.height());
166      *clip_result = (src_aspect >= ThumbnailScore::kTooWideAspectRatio) ?
167          thumbnails::CLIP_RESULT_MUCH_WIDER_THAN_TALL :
168          thumbnails::CLIP_RESULT_WIDER_THAN_TALL;
169    } else if (src_aspect < desired_aspect) {
170      clipping_rect =
171          gfx::Rect(source_size.width(), source_size.width() / desired_aspect);
172      *clip_result = thumbnails::CLIP_RESULT_TALLER_THAN_WIDE;
173    } else {
174      clipping_rect = gfx::Rect(source_size);
175      *clip_result = thumbnails::CLIP_RESULT_NOT_CLIPPED;
176    }
177  }
178  return clipping_rect;
179}
180
181// static
182gfx::Size SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(
183    const gfx::Size& given_size) {
184  // TODO(mazda|oshima): Update thumbnail when the max scale factor changes.
185  // crbug.com/159157.
186  float max_scale_factor = gfx::ImageSkia::GetMaxSupportedScale();
187  return gfx::ToFlooredSize(gfx::ScaleSize(given_size, max_scale_factor));
188}
189
190SimpleThumbnailCrop::~SimpleThumbnailCrop() {
191}
192
193// Creates a downsampled thumbnail from the given bitmap.
194// store. The returned bitmap will be isNull if there was an error creating it.
195SkBitmap SimpleThumbnailCrop::CreateThumbnail(const SkBitmap& bitmap,
196                                              const gfx::Size& desired_size,
197                                              ClipResult* clip_result) {
198  base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
199
200  SkBitmap clipped_bitmap;
201  if (*clip_result == thumbnails::CLIP_RESULT_UNPROCESSED) {
202    // Clip the pixels that will commonly hold a scrollbar, which looks bad in
203    // thumbnails.
204    int scrollbar_size = gfx::scrollbar_size();
205    SkIRect scrollbarless_rect =
206        { 0, 0,
207          std::max(1, bitmap.width() - scrollbar_size),
208          std::max(1, bitmap.height() - scrollbar_size) };
209    SkBitmap bmp;
210    bitmap.extractSubset(&bmp, scrollbarless_rect);
211
212    clipped_bitmap = GetClippedBitmap(
213        bmp, desired_size.width(), desired_size.height(), clip_result);
214  } else {
215    clipped_bitmap = bitmap;
216  }
217
218  // Need to resize it to the size we want, so downsample until it's
219  // close, and let the caller make it the exact size if desired.
220  SkBitmap result = SkBitmapOperations::DownsampleByTwoUntilSize(
221      clipped_bitmap, desired_size.width(), desired_size.height());
222#if !defined(USE_AURA)
223  // This is a bit subtle. SkBitmaps are refcounted, but the magic
224  // ones in PlatformCanvas can't be assigned to SkBitmap with proper
225  // refcounting.  If the bitmap doesn't change, then the downsampler
226  // will return the input bitmap, which will be the reference to the
227  // weird PlatformCanvas one insetad of a regular one. To get a
228  // regular refcounted bitmap, we need to copy it.
229  //
230  // On Aura, the PlatformCanvas is platform-independent and does not have
231  // any native platform resources that can't be refounted, so this issue does
232  // not occur.
233  //
234  // Note that GetClippedBitmap() does extractSubset() but it won't copy
235  // the pixels, hence we check result size == clipped_bitmap size here.
236  if (clipped_bitmap.width() == result.width() &&
237      clipped_bitmap.height() == result.height())
238    clipped_bitmap.copyTo(&result, kN32_SkColorType);
239#endif
240
241  LOCAL_HISTOGRAM_TIMES(kThumbnailHistogramName,
242                        base::TimeTicks::Now() - begin_compute_thumbnail);
243  return result;
244}
245
246} // namespace thumbnails
247