OLD | NEW |
---|---|
(Empty) | |
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/content_based_thumbnailing_algorithm.h" | |
6 | |
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" | |
17 | |
18 namespace { | |
19 | |
20 static const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS"; | |
21 static const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS"; | |
22 static const float kScoreBoostFromSuccessfulRetargetting = 1.1f; | |
23 | |
24 void CallbackInvocationAdapter( | |
25 const thumbnails::ThumbnailingAlgorithm::ConsumerCallback& callback, | |
26 scoped_refptr<thumbnails::ThumbnailingContext> context, | |
27 const SkBitmap& source_bitmap) { | |
28 callback.Run(*context, source_bitmap); | |
29 } | |
30 | |
31 } // namespace | |
32 | |
33 namespace thumbnails { | |
34 | |
35 using content::BrowserThread; | |
36 | |
37 ContentBasedThumbnailingAlgorithm::ContentBasedThumbnailingAlgorithm( | |
38 const gfx::Size& target_size) | |
39 : target_size_(target_size) { | |
40 DCHECK(!target_size.IsEmpty()); | |
41 } | |
42 | |
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_); | |
51 | |
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; | |
56 } | |
57 | |
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); | |
64 if (bitmap.isNull() || bitmap.empty()) | |
65 return; | |
66 | |
67 gfx::Size target_thumbnail_size = | |
68 SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_); | |
69 | |
70 SkBitmap source_bitmap = PrepareSourceBitmap( | |
71 bitmap, target_thumbnail_size, context); | |
72 | |
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 | |
75 // thread pool. | |
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); | |
85 | |
86 callback.Run(*context, source_bitmap); | |
87 return; | |
88 } | |
89 | |
90 if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior( | |
91 FROM_HERE, | |
92 base::Bind(&CreateRetargettedThumbnail, | |
93 source_bitmap, | |
94 target_thumbnail_size, | |
95 context, | |
96 callback), | |
97 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)) { | |
98 LOG(WARNING) << "PostSequencedWorkerTask failed. The thumbnail for " | |
99 << context->url << " will not be created."; | |
100 } | |
101 } | |
102 | |
103 // static | |
104 SkBitmap ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap( | |
105 const SkBitmap& received_bitmap, | |
106 const gfx::Size& thumbnail_size, | |
107 ThumbnailingContext* context) { | |
108 | |
mazda
2013/05/29 21:50:27
nit: remove the empty line
motek.
2013/05/30 15:00:03
Done.
| |
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)); | |
117 | |
118 gfx::Rect clipping_rect = GetClippingRect( | |
119 scrollbarless, | |
120 thumbnail_size, | |
121 &resize_target, | |
122 &context->clip_result); | |
123 | |
124 received_bitmap.extractSubset(&clipped_bitmap, | |
125 gfx::RectToSkIRect(clipping_rect)); | |
126 } else { | |
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; | |
133 } | |
134 | |
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. | |
142 // | |
143 // With Aura, this does not happen since PlatformCanvas is platform | |
144 // idependent. | |
145 if (clipped_bitmap.width() == result_bitmap.width() && | |
146 clipped_bitmap.height() == result_bitmap.height()) { | |
147 clipped_bitmap.copyTo(&result_bitmap, SkBitmap::kARGB_8888_Config); | |
148 } | |
149 #endif | |
150 | |
151 return result_bitmap; | |
152 } | |
153 | |
154 // static | |
155 void ContentBasedThumbnailingAlgorithm::CreateRetargettedThumbnail( | |
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(); | |
161 float kernel_sigma = | |
162 context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET ? 5.0f : 2.5f; | |
163 SkBitmap thumbnail = thumbnailing_utils::CreateRetargettedThumbnailImage( | |
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) << "CreateRetargettedThumbnailImage failed. " | |
170 << "The thumbnail for " << context->url | |
171 << " will be created the old-fashioned way."; | |
172 | |
173 ClipResult clip_result; | |
174 gfx::Rect clipping_rect = SimpleThumbnailCrop::GetClippingRect( | |
175 gfx::Size(source_bitmap.width(), source_bitmap.height()), | |
176 thumbnail_size, | |
177 &clip_result); | |
178 source_bitmap.extractSubset(&thumbnail, gfx::RectToSkIRect(clipping_rect)); | |
179 thumbnail = SkBitmapOperations::DownsampleByTwoUntilSize( | |
180 thumbnail, thumbnail_size.width(), thumbnail_size.height()); | |
181 } | |
182 | |
183 HISTOGRAM_TIMES( | |
184 processing_failed ? kFailureHistogramName : kThumbnailHistogramName, | |
185 base::TimeTicks::Now() - begin_compute_thumbnail); | |
186 context->score.boring_score = | |
187 SimpleThumbnailCrop::CalculateBoringScore(source_bitmap); | |
188 if (!processing_failed) | |
189 context->score.boring_score *= kScoreBoostFromSuccessfulRetargetting; | |
190 context->score.good_clipping = | |
191 (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL || | |
192 context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE || | |
193 context->clip_result == CLIP_RESULT_NOT_CLIPPED || | |
194 context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET); | |
195 // Post the result (the bitmap) back to the callback. | |
196 BrowserThread::PostTask( | |
197 BrowserThread::UI, | |
198 FROM_HERE, | |
199 base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail)); | |
200 } | |
201 | |
202 ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() { | |
203 } | |
204 | |
205 // static | |
206 gfx::Rect ContentBasedThumbnailingAlgorithm::GetClippingRect( | |
207 const gfx::Size& source_size, | |
208 const gfx::Size& thumbnail_size, | |
209 gfx::Size* target_size, | |
210 ClipResult* clip_result) { | |
211 // Compute and return the clipping rectagle of the source image and the | |
212 // size of the target bitmap which will be used for the further processing. | |
213 // This function in 'general case' is trivial (don't clip, halve the source) | |
214 // but it is needed for handling edge cases (source smaller than the target | |
215 // thumbnail size). | |
216 DCHECK(target_size); | |
217 DCHECK(clip_result); | |
218 gfx::Rect clipping_rect; | |
219 if (source_size.width() < thumbnail_size.width() || | |
220 source_size.height() < thumbnail_size.height()) { | |
221 clipping_rect = gfx::Rect(thumbnail_size); | |
222 *target_size = thumbnail_size; | |
223 *clip_result = CLIP_RESULT_SOURCE_IS_SMALLER; | |
224 } else if (source_size.width() < thumbnail_size.width() * 4 || | |
225 source_size.height() < thumbnail_size.height() * 4) { | |
226 clipping_rect = gfx::Rect(source_size); | |
227 *target_size = source_size; | |
228 *clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET; | |
229 } else { | |
230 clipping_rect = gfx::Rect(source_size); | |
231 target_size->SetSize(source_size.width() / 2, source_size.height() / 2); | |
232 *clip_result = CLIP_RESULT_NOT_CLIPPED; | |
233 } | |
234 | |
235 return clipping_rect; | |
236 } | |
237 | |
238 } // namespace thumbnails | |
OLD | NEW |