1 // Copyright 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/content_based_thumbnailing_algorithm.h"
7 #include "base/metrics/histogram.h"
8 #include "base/threading/sequenced_worker_pool.h"
9 #include "chrome/browser/thumbnails/content_analysis.h"
10 #include "chrome/browser/thumbnails/simple_thumbnail_crop.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "third_party/skia/include/core/SkBitmap.h"
13 #include "ui/gfx/color_utils.h"
14 #include "ui/gfx/geometry/size_conversions.h"
15 #include "ui/gfx/scrollbar_size.h"
16 #include "ui/gfx/skbitmap_operations.h"
17 #include "ui/gfx/skia_util.h"
21 const char kThumbnailHistogramName
[] = "Thumbnail.RetargetMS";
22 const char kFailureHistogramName
[] = "Thumbnail.FailedRetargetMS";
23 const float kScoreBoostFromSuccessfulRetargeting
= 1.1f
;
25 void CallbackInvocationAdapter(
26 const thumbnails::ThumbnailingAlgorithm::ConsumerCallback
& callback
,
27 scoped_refptr
<thumbnails::ThumbnailingContext
> context
,
28 const SkBitmap
& source_bitmap
) {
29 callback
.Run(*context
.get(), source_bitmap
);
34 namespace thumbnails
{
36 using content::BrowserThread
;
38 ContentBasedThumbnailingAlgorithm::ContentBasedThumbnailingAlgorithm(
39 const gfx::Size
& target_size
)
40 : target_size_(target_size
) {
41 DCHECK(!target_size
.IsEmpty());
44 ClipResult
ContentBasedThumbnailingAlgorithm::GetCanvasCopyInfo(
45 const gfx::Size
& source_size
,
46 ui::ScaleFactor scale_factor
,
47 gfx::Rect
* clipping_rect
,
48 gfx::Size
* copy_size
) const {
49 DCHECK(!source_size
.IsEmpty());
50 gfx::Size copy_thumbnail_size
=
51 SimpleThumbnailCrop::GetCopySizeForThumbnail(scale_factor
, target_size_
);
53 ClipResult clipping_method
= thumbnails::CLIP_RESULT_NOT_CLIPPED
;
54 *clipping_rect
= GetClippingRect(source_size
, copy_thumbnail_size
, copy_size
,
56 return clipping_method
;
59 void ContentBasedThumbnailingAlgorithm::ProcessBitmap(
60 scoped_refptr
<ThumbnailingContext
> context
,
61 const ConsumerCallback
& callback
,
62 const SkBitmap
& bitmap
) {
63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
64 DCHECK(context
.get());
65 if (bitmap
.isNull() || bitmap
.empty())
68 gfx::Size target_thumbnail_size
=
69 SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_
);
71 SkBitmap source_bitmap
=
72 PrepareSourceBitmap(bitmap
, target_thumbnail_size
, context
.get());
74 // If the source is same (or smaller) than the target, just return it as
75 // the final result. Otherwise, send the shrinking task to the blocking
77 if (source_bitmap
.width() <= target_thumbnail_size
.width() ||
78 source_bitmap
.height() <= target_thumbnail_size
.height()) {
79 context
->score
.boring_score
=
80 color_utils::CalculateBoringScore(source_bitmap
);
81 context
->score
.good_clipping
=
82 (context
->clip_result
== CLIP_RESULT_WIDER_THAN_TALL
||
83 context
->clip_result
== CLIP_RESULT_TALLER_THAN_WIDE
||
84 context
->clip_result
== CLIP_RESULT_NOT_CLIPPED
||
85 context
->clip_result
== CLIP_RESULT_SOURCE_SAME_AS_TARGET
);
87 callback
.Run(*context
.get(), source_bitmap
);
91 if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
93 base::Bind(&CreateRetargetedThumbnail
,
95 target_thumbnail_size
,
98 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)) {
99 LOG(WARNING
) << "PostSequencedWorkerTask failed. The thumbnail for "
100 << context
->url
<< " will not be created.";
105 SkBitmap
ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap(
106 const SkBitmap
& received_bitmap
,
107 const gfx::Size
& thumbnail_size
,
108 ThumbnailingContext
* context
) {
109 gfx::Size resize_target
;
110 SkBitmap clipped_bitmap
;
111 if (context
->clip_result
== CLIP_RESULT_UNPROCESSED
) {
112 // This case will require extracting a fragment from the retrieved bitmap.
113 int scrollbar_size
= gfx::scrollbar_size();
114 gfx::Size
scrollbarless(
115 std::max(1, received_bitmap
.width() - scrollbar_size
),
116 std::max(1, received_bitmap
.height() - scrollbar_size
));
118 gfx::Rect clipping_rect
= GetClippingRect(
122 &context
->clip_result
);
124 received_bitmap
.extractSubset(&clipped_bitmap
,
125 gfx::RectToSkIRect(clipping_rect
));
127 // This means that the source bitmap has been requested and at least
128 // clipped. Upstream code in same cases seems opportunistic and it may
129 // not perform actual resizing if copying with resize is not supported.
130 // In this case we will resize to the orignally requested copy size.
131 resize_target
= context
->requested_copy_size
;
132 clipped_bitmap
= received_bitmap
;
135 SkBitmap result_bitmap
= SkBitmapOperations::DownsampleByTwoUntilSize(
136 clipped_bitmap
, resize_target
.width(), resize_target
.height());
137 #if !defined(USE_AURA)
138 // If the bitmap has not been indeed resized, it has to be copied. In that
139 // case resampler simply returns a reference to the original bitmap, sitting
140 // in PlatformCanvas. One does not simply assign these 'magic' bitmaps to
141 // SkBitmap. They cannot be refcounted.
143 // With Aura, this does not happen since PlatformCanvas is platform
145 if (clipped_bitmap
.width() == result_bitmap
.width() &&
146 clipped_bitmap
.height() == result_bitmap
.height()) {
147 clipped_bitmap
.copyTo(&result_bitmap
, kN32_SkColorType
);
151 return result_bitmap
;
155 void ContentBasedThumbnailingAlgorithm::CreateRetargetedThumbnail(
156 const SkBitmap
& source_bitmap
,
157 const gfx::Size
& thumbnail_size
,
158 scoped_refptr
<ThumbnailingContext
> context
,
159 const ConsumerCallback
& callback
) {
160 base::TimeTicks begin_compute_thumbnail
= base::TimeTicks::Now();
162 context
->clip_result
== CLIP_RESULT_SOURCE_SAME_AS_TARGET
? 5.0f
: 2.5f
;
163 SkBitmap thumbnail
= thumbnailing_utils::CreateRetargetedThumbnailImage(
164 source_bitmap
, thumbnail_size
, kernel_sigma
);
165 bool processing_failed
= thumbnail
.empty();
166 if (processing_failed
) {
167 // Log and apply the method very much like in SimpleThumbnailCrop (except
168 // that some clipping and copying is not required).
169 LOG(WARNING
) << "CreateRetargetedThumbnailImage failed. "
170 << "The thumbnail for " << context
->url
171 << " will be created the old-fashioned way.";
173 ClipResult clip_result
;
174 gfx::Rect clipping_rect
= SimpleThumbnailCrop::GetClippingRect(
175 gfx::Size(source_bitmap
.width(), source_bitmap
.height()),
178 source_bitmap
.extractSubset(&thumbnail
, gfx::RectToSkIRect(clipping_rect
));
179 thumbnail
= SkBitmapOperations::DownsampleByTwoUntilSize(
180 thumbnail
, thumbnail_size
.width(), thumbnail_size
.height());
183 if (processing_failed
) {
184 LOCAL_HISTOGRAM_TIMES(kFailureHistogramName
,
185 base::TimeTicks::Now() - begin_compute_thumbnail
);
187 LOCAL_HISTOGRAM_TIMES(kThumbnailHistogramName
,
188 base::TimeTicks::Now() - begin_compute_thumbnail
);
190 context
->score
.boring_score
=
191 color_utils::CalculateBoringScore(source_bitmap
);
192 if (!processing_failed
)
193 context
->score
.boring_score
*= kScoreBoostFromSuccessfulRetargeting
;
194 context
->score
.good_clipping
=
195 (context
->clip_result
== CLIP_RESULT_WIDER_THAN_TALL
||
196 context
->clip_result
== CLIP_RESULT_TALLER_THAN_WIDE
||
197 context
->clip_result
== CLIP_RESULT_NOT_CLIPPED
||
198 context
->clip_result
== CLIP_RESULT_SOURCE_SAME_AS_TARGET
);
199 // Post the result (the bitmap) back to the callback.
200 BrowserThread::PostTask(
203 base::Bind(&CallbackInvocationAdapter
, callback
, context
, thumbnail
));
206 ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() {
210 gfx::Rect
ContentBasedThumbnailingAlgorithm::GetClippingRect(
211 const gfx::Size
& source_size
,
212 const gfx::Size
& thumbnail_size
,
213 gfx::Size
* target_size
,
214 ClipResult
* clip_result
) {
215 // Compute and return the clipping rectagle of the source image and the
216 // size of the target bitmap which will be used for the further processing.
217 // This function in 'general case' is trivial (don't clip, halve the source)
218 // but it is needed for handling edge cases (source smaller than the target
222 gfx::Rect clipping_rect
;
223 if (source_size
.width() < thumbnail_size
.width() ||
224 source_size
.height() < thumbnail_size
.height()) {
225 clipping_rect
= gfx::Rect(thumbnail_size
);
226 *target_size
= thumbnail_size
;
227 *clip_result
= CLIP_RESULT_SOURCE_IS_SMALLER
;
228 } else if (source_size
.width() < thumbnail_size
.width() * 4 ||
229 source_size
.height() < thumbnail_size
.height() * 4) {
230 clipping_rect
= gfx::Rect(source_size
);
231 *target_size
= source_size
;
232 *clip_result
= CLIP_RESULT_SOURCE_SAME_AS_TARGET
;
234 clipping_rect
= gfx::Rect(source_size
);
235 target_size
->SetSize(source_size
.width() / 2, source_size
.height() / 2);
236 *clip_result
= CLIP_RESULT_NOT_CLIPPED
;
239 return clipping_rect
;
242 } // namespace thumbnails