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.
5 #include "chrome/browser/thumbnails/simple_thumbnail_crop.h"
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"
18 static const char kThumbnailHistogramName
[] = "Thumbnail.ComputeMS";
21 namespace thumbnails
{
23 SimpleThumbnailCrop::SimpleThumbnailCrop(const gfx::Size
& target_size
)
24 : target_size_(target_size
) {
25 DCHECK(!target_size
.IsEmpty());
28 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_
);
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())
48 SkBitmap thumbnail
= CreateThumbnail(
50 ComputeTargetSizeAtMaximumScale(target_size_
),
51 &context
->clip_result
);
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
);
59 callback
.Run(*context
.get(), thumbnail
);
62 double SimpleThumbnailCrop::CalculateBoringScore(const SkBitmap
& bitmap
) {
63 if (bitmap
.isNull() || bitmap
.empty())
65 int histogram
[256] = {0};
66 color_utils::BuildLumaHistogram(bitmap
, histogram
);
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
;
73 SkBitmap
SimpleThumbnailCrop::GetClippedBitmap(const SkBitmap
& bitmap
,
76 ClipResult
* clip_result
) {
77 gfx::Rect clipping_rect
=
78 GetClippingRect(gfx::Size(bitmap
.width(), bitmap
.height()),
79 gfx::Size(desired_width
, desired_height
),
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
;
88 // Returns the size used by RenderWidgetHost::CopyFromBackingStore.
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.
96 // The copied size was chosen for the following reasons.
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.
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.
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.
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
));
126 case ui::SCALE_FACTOR_200P
:
127 // Use the size as-is.
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()));
139 gfx::Rect
SimpleThumbnailCrop::GetClippingRect(const gfx::Size
& source_size
,
140 const gfx::Size
& desired_size
,
141 ClipResult
* clip_result
) {
144 float desired_aspect
=
145 static_cast<float>(desired_size
.width()) / desired_size
.height();
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
;
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
) {
171 gfx::Rect(source_size
.width(), source_size
.width() / desired_aspect
);
172 *clip_result
= thumbnails::CLIP_RESULT_TALLER_THAN_WIDE
;
174 clipping_rect
= gfx::Rect(source_size
);
175 *clip_result
= thumbnails::CLIP_RESULT_NOT_CLIPPED
;
178 return clipping_rect
;
182 gfx::Size
SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(
183 const gfx::Size
& given_size
) {
184 // TODO(mazda|oshima): Update thumbnail when the max scale factor changes.
186 float max_scale_factor
= gfx::ImageSkia::GetMaxSupportedScale();
187 return gfx::ToFlooredSize(gfx::ScaleSize(given_size
, max_scale_factor
));
190 SimpleThumbnailCrop::~SimpleThumbnailCrop() {
193 // Creates a downsampled thumbnail from the given bitmap.
194 // store. The returned bitmap will be isNull if there was an error creating it.
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();
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
204 int scrollbar_size
= gfx::scrollbar_size();
205 SkIRect scrollbarless_rect
=
207 std::max(1, bitmap
.width() - scrollbar_size
),
208 std::max(1, bitmap
.height() - scrollbar_size
) };
210 bitmap
.extractSubset(&bmp
, scrollbarless_rect
);
212 clipped_bitmap
= GetClippedBitmap(
213 bmp
, desired_size
.width(), desired_size
.height(), clip_result
);
215 clipped_bitmap
= bitmap
;
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.
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
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
);
241 HISTOGRAM_TIMES(kThumbnailHistogramName
,
242 base::TimeTicks::Now() - begin_compute_thumbnail
);
246 } // namespace thumbnails