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 const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS"; |
| 21 const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS"; |
| 22 const float kScoreBoostFromSuccessfulRetargeting = 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(&CreateRetargetedThumbnail, |
| 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 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)); |
| 116 |
| 117 gfx::Rect clipping_rect = GetClippingRect( |
| 118 scrollbarless, |
| 119 thumbnail_size, |
| 120 &resize_target, |
| 121 &context->clip_result); |
| 122 |
| 123 received_bitmap.extractSubset(&clipped_bitmap, |
| 124 gfx::RectToSkIRect(clipping_rect)); |
| 125 } else { |
| 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; |
| 132 } |
| 133 |
| 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. |
| 141 // |
| 142 // With Aura, this does not happen since PlatformCanvas is platform |
| 143 // idependent. |
| 144 if (clipped_bitmap.width() == result_bitmap.width() && |
| 145 clipped_bitmap.height() == result_bitmap.height()) { |
| 146 clipped_bitmap.copyTo(&result_bitmap, SkBitmap::kARGB_8888_Config); |
| 147 } |
| 148 #endif |
| 149 |
| 150 return result_bitmap; |
| 151 } |
| 152 |
| 153 // static |
| 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(); |
| 160 float kernel_sigma = |
| 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."; |
| 171 |
| 172 ClipResult clip_result; |
| 173 gfx::Rect clipping_rect = SimpleThumbnailCrop::GetClippingRect( |
| 174 gfx::Size(source_bitmap.width(), source_bitmap.height()), |
| 175 thumbnail_size, |
| 176 &clip_result); |
| 177 source_bitmap.extractSubset(&thumbnail, gfx::RectToSkIRect(clipping_rect)); |
| 178 thumbnail = SkBitmapOperations::DownsampleByTwoUntilSize( |
| 179 thumbnail, thumbnail_size.width(), thumbnail_size.height()); |
| 180 } |
| 181 |
| 182 HISTOGRAM_TIMES( |
| 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( |
| 196 BrowserThread::UI, |
| 197 FROM_HERE, |
| 198 base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail)); |
| 199 } |
| 200 |
| 201 ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() { |
| 202 } |
| 203 |
| 204 // static |
| 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 |
| 214 // thumbnail size). |
| 215 DCHECK(target_size); |
| 216 DCHECK(clip_result); |
| 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; |
| 228 } else { |
| 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; |
| 232 } |
| 233 |
| 234 return clipping_rect; |
| 235 } |
| 236 |
| 237 } // namespace thumbnails |
OLD | NEW |