Chromium Code Reviews| 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 |