• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/simple_thumbnail_crop.h"
6 
7 #include "base/metrics/histogram.h"
8 #include "content/public/browser/browser_thread.h"
9 #include "skia/ext/platform_canvas.h"
10 #include "ui/gfx/color_utils.h"
11 #include "ui/gfx/image/image_skia.h"
12 #include "ui/gfx/screen.h"
13 #include "ui/gfx/scrollbar_size.h"
14 #include "ui/gfx/size_conversions.h"
15 #include "ui/gfx/skbitmap_operations.h"
16 
17 namespace {
18 static const char kThumbnailHistogramName[] = "Thumbnail.ComputeMS";
19 }
20 
21 namespace thumbnails {
22 
SimpleThumbnailCrop(const gfx::Size & target_size)23 SimpleThumbnailCrop::SimpleThumbnailCrop(const gfx::Size& target_size)
24     : target_size_(target_size) {
25   DCHECK(!target_size.IsEmpty());
26 }
27 
GetCanvasCopyInfo(const gfx::Size & source_size,ui::ScaleFactor scale_factor,gfx::Rect * clipping_rect,gfx::Size * target_size) const28 ClipResult SimpleThumbnailCrop::GetCanvasCopyInfo(
29     const gfx::Size& source_size,
30     ui::ScaleFactor scale_factor,
31     gfx::Rect* clipping_rect,
32     gfx::Size* target_size) const {
33   DCHECK(!source_size.IsEmpty());
34   ClipResult clip_result = thumbnails::CLIP_RESULT_NOT_CLIPPED;
35   *clipping_rect = GetClippingRect(source_size, target_size_, &clip_result);
36   *target_size = GetCopySizeForThumbnail(scale_factor, target_size_);
37   return clip_result;
38 }
39 
ProcessBitmap(scoped_refptr<ThumbnailingContext> context,const ConsumerCallback & callback,const SkBitmap & bitmap)40 void SimpleThumbnailCrop::ProcessBitmap(
41     scoped_refptr<ThumbnailingContext> context,
42     const ConsumerCallback& callback,
43     const SkBitmap& bitmap) {
44   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
45   if (bitmap.isNull() || bitmap.empty())
46     return;
47 
48   SkBitmap thumbnail = CreateThumbnail(
49       bitmap,
50       ComputeTargetSizeAtMaximumScale(target_size_),
51       &context->clip_result);
52 
53   context->score.boring_score = CalculateBoringScore(thumbnail);
54   context->score.good_clipping =
55       (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
56        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 
CalculateBoringScore(const SkBitmap & bitmap)62 double 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 
GetClippedBitmap(const SkBitmap & bitmap,int desired_width,int desired_height,ClipResult * clip_result)73 SkBitmap 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
GetCopySizeForThumbnail(ui::ScaleFactor scale_factor,const gfx::Size & thumbnail_size)118 gfx::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 
GetClippingRect(const gfx::Size & source_size,const gfx::Size & desired_size,ClipResult * clip_result)139 gfx::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
ComputeTargetSizeAtMaximumScale(const gfx::Size & given_size)182 gfx::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 
~SimpleThumbnailCrop()190 SimpleThumbnailCrop::~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.
CreateThumbnail(const SkBitmap & bitmap,const gfx::Size & desired_size,ClipResult * clip_result)195 SkBitmap 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