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/scrollbar_size.h"
14 #include "ui/gfx/size_conversions.h"
15 #include "ui/gfx/skbitmap_operations.h"
16 #include "ui/gfx/skia_util.h"
20 const char kThumbnailHistogramName
[] = "Thumbnail.RetargetMS";
21 const char kFailureHistogramName
[] = "Thumbnail.FailedRetargetMS";
22 const float kScoreBoostFromSuccessfulRetargeting
= 1.1f
;
24 void CallbackInvocationAdapter(
25 const thumbnails::ThumbnailingAlgorithm::ConsumerCallback
& callback
,
26 scoped_refptr
<thumbnails::ThumbnailingContext
> context
,
27 const SkBitmap
& source_bitmap
) {
28 callback
.Run(*context
.get(), source_bitmap
);
33 namespace thumbnails
{
35 using content::BrowserThread
;
37 ContentBasedThumbnailingAlgorithm::ContentBasedThumbnailingAlgorithm(
38 const gfx::Size
& target_size
)
39 : target_size_(target_size
) {
40 DCHECK(!target_size
.IsEmpty());
43 ClipResult
ContentBasedThumbnailingAlgorithm::GetCanvasCopyInfo(
44 const gfx::Size
& source_size
,
45 ui::ScaleFactor scale_factor
,
46 gfx::Rect
* clipping_rect
,
47 gfx::Size
* target_size
) const {
48 DCHECK(!source_size
.IsEmpty());
49 gfx::Size target_thumbnail_size
=
50 SimpleThumbnailCrop::GetCopySizeForThumbnail(scale_factor
, target_size_
);
52 ClipResult clipping_method
= thumbnails::CLIP_RESULT_NOT_CLIPPED
;
53 *clipping_rect
= GetClippingRect(
54 source_size
, target_thumbnail_size
, target_size
, &clipping_method
);
55 return clipping_method
;
58 void ContentBasedThumbnailingAlgorithm::ProcessBitmap(
59 scoped_refptr
<ThumbnailingContext
> context
,
60 const ConsumerCallback
& callback
,
61 const SkBitmap
& bitmap
) {
62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
63 DCHECK(context
.get());
64 if (bitmap
.isNull() || bitmap
.empty())
67 gfx::Size target_thumbnail_size
=
68 SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_
);
70 SkBitmap source_bitmap
=
71 PrepareSourceBitmap(bitmap
, target_thumbnail_size
, context
.get());
73 // If the source is same (or smaller) than the target, just return it as
74 // the final result. Otherwise, send the shrinking task to the blocking
76 if (source_bitmap
.width() <= target_thumbnail_size
.width() ||
77 source_bitmap
.height() <= target_thumbnail_size
.height()) {
78 context
->score
.boring_score
=
79 SimpleThumbnailCrop::CalculateBoringScore(source_bitmap
);
80 context
->score
.good_clipping
=
81 (context
->clip_result
== CLIP_RESULT_WIDER_THAN_TALL
||
82 context
->clip_result
== CLIP_RESULT_TALLER_THAN_WIDE
||
83 context
->clip_result
== CLIP_RESULT_NOT_CLIPPED
||
84 context
->clip_result
== CLIP_RESULT_SOURCE_SAME_AS_TARGET
);
86 callback
.Run(*context
.get(), source_bitmap
);
90 if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
92 base::Bind(&CreateRetargetedThumbnail
,
94 target_thumbnail_size
,
97 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)) {
98 LOG(WARNING
) << "PostSequencedWorkerTask failed. The thumbnail for "
99 << context
->url
<< " will not be created.";
104 SkBitmap
ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap(
105 const SkBitmap
& received_bitmap
,
106 const gfx::Size
& thumbnail_size
,
107 ThumbnailingContext
* context
) {
108 gfx::Size resize_target
;
109 SkBitmap clipped_bitmap
;
110 if (context
->clip_result
== CLIP_RESULT_UNPROCESSED
) {
111 // This case will require extracting a fragment from the retrieved bitmap.
112 int scrollbar_size
= gfx::scrollbar_size();
113 gfx::Size
scrollbarless(
114 std::max(1, received_bitmap
.width() - scrollbar_size
),
115 std::max(1, received_bitmap
.height() - scrollbar_size
));
117 gfx::Rect clipping_rect
= GetClippingRect(
121 &context
->clip_result
);
123 received_bitmap
.extractSubset(&clipped_bitmap
,
124 gfx::RectToSkIRect(clipping_rect
));
126 // This means that the source bitmap has been requested and at least
127 // clipped. Upstream code in same cases seems opportunistic and it may
128 // not perform actual resizing if copying with resize is not supported.
129 // In this case we will resize to the orignally requested copy size.
130 resize_target
= context
->requested_copy_size
;
131 clipped_bitmap
= received_bitmap
;
134 SkBitmap result_bitmap
= SkBitmapOperations::DownsampleByTwoUntilSize(
135 clipped_bitmap
, resize_target
.width(), resize_target
.height());
136 #if !defined(USE_AURA)
137 // If the bitmap has not been indeed resized, it has to be copied. In that
138 // case resampler simply returns a reference to the original bitmap, sitting
139 // in PlatformCanvas. One does not simply assign these 'magic' bitmaps to
140 // SkBitmap. They cannot be refcounted.
142 // With Aura, this does not happen since PlatformCanvas is platform
144 if (clipped_bitmap
.width() == result_bitmap
.width() &&
145 clipped_bitmap
.height() == result_bitmap
.height()) {
146 clipped_bitmap
.copyTo(&result_bitmap
, kN32_SkColorType
);
150 return result_bitmap
;
154 void ContentBasedThumbnailingAlgorithm::CreateRetargetedThumbnail(
155 const SkBitmap
& source_bitmap
,
156 const gfx::Size
& thumbnail_size
,
157 scoped_refptr
<ThumbnailingContext
> context
,
158 const ConsumerCallback
& callback
) {
159 base::TimeTicks begin_compute_thumbnail
= base::TimeTicks::Now();
161 context
->clip_result
== CLIP_RESULT_SOURCE_SAME_AS_TARGET
? 5.0f
: 2.5f
;
162 SkBitmap thumbnail
= thumbnailing_utils::CreateRetargetedThumbnailImage(
163 source_bitmap
, thumbnail_size
, kernel_sigma
);
164 bool processing_failed
= thumbnail
.empty();
165 if (processing_failed
) {
166 // Log and apply the method very much like in SimpleThumbnailCrop (except
167 // that some clipping and copying is not required).
168 LOG(WARNING
) << "CreateRetargetedThumbnailImage failed. "
169 << "The thumbnail for " << context
->url
170 << " will be created the old-fashioned way.";
172 ClipResult clip_result
;
173 gfx::Rect clipping_rect
= SimpleThumbnailCrop::GetClippingRect(
174 gfx::Size(source_bitmap
.width(), source_bitmap
.height()),
177 source_bitmap
.extractSubset(&thumbnail
, gfx::RectToSkIRect(clipping_rect
));
178 thumbnail
= SkBitmapOperations::DownsampleByTwoUntilSize(
179 thumbnail
, thumbnail_size
.width(), thumbnail_size
.height());
183 processing_failed
? kFailureHistogramName
: kThumbnailHistogramName
,
184 base::TimeTicks::Now() - begin_compute_thumbnail
);
185 context
->score
.boring_score
=
186 SimpleThumbnailCrop::CalculateBoringScore(source_bitmap
);
187 if (!processing_failed
)
188 context
->score
.boring_score
*= kScoreBoostFromSuccessfulRetargeting
;
189 context
->score
.good_clipping
=
190 (context
->clip_result
== CLIP_RESULT_WIDER_THAN_TALL
||
191 context
->clip_result
== CLIP_RESULT_TALLER_THAN_WIDE
||
192 context
->clip_result
== CLIP_RESULT_NOT_CLIPPED
||
193 context
->clip_result
== CLIP_RESULT_SOURCE_SAME_AS_TARGET
);
194 // Post the result (the bitmap) back to the callback.
195 BrowserThread::PostTask(
198 base::Bind(&CallbackInvocationAdapter
, callback
, context
, thumbnail
));
201 ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() {
205 gfx::Rect
ContentBasedThumbnailingAlgorithm::GetClippingRect(
206 const gfx::Size
& source_size
,
207 const gfx::Size
& thumbnail_size
,
208 gfx::Size
* target_size
,
209 ClipResult
* clip_result
) {
210 // Compute and return the clipping rectagle of the source image and the
211 // size of the target bitmap which will be used for the further processing.
212 // This function in 'general case' is trivial (don't clip, halve the source)
213 // but it is needed for handling edge cases (source smaller than the target
217 gfx::Rect clipping_rect
;
218 if (source_size
.width() < thumbnail_size
.width() ||
219 source_size
.height() < thumbnail_size
.height()) {
220 clipping_rect
= gfx::Rect(thumbnail_size
);
221 *target_size
= thumbnail_size
;
222 *clip_result
= CLIP_RESULT_SOURCE_IS_SMALLER
;
223 } else if (source_size
.width() < thumbnail_size
.width() * 4 ||
224 source_size
.height() < thumbnail_size
.height() * 4) {
225 clipping_rect
= gfx::Rect(source_size
);
226 *target_size
= source_size
;
227 *clip_result
= CLIP_RESULT_SOURCE_SAME_AS_TARGET
;
229 clipping_rect
= gfx::Rect(source_size
);
230 target_size
->SetSize(source_size
.width() / 2, source_size
.height() / 2);
231 *clip_result
= CLIP_RESULT_NOT_CLIPPED
;
234 return clipping_rect
;
237 } // namespace thumbnails