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. 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.get(), 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.get()); 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 = 71 PrepareSourceBitmap(bitmap, target_thumbnail_size, context.get()); 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.get(), 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, kN32_SkColorType); 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 if (processing_failed) { 183 LOCAL_HISTOGRAM_TIMES(kFailureHistogramName, 184 base::TimeTicks::Now() - begin_compute_thumbnail); 185 } else { 186 LOCAL_HISTOGRAM_TIMES(kThumbnailHistogramName, 187 base::TimeTicks::Now() - begin_compute_thumbnail); 188 } 189 context->score.boring_score = 190 SimpleThumbnailCrop::CalculateBoringScore(source_bitmap); 191 if (!processing_failed) 192 context->score.boring_score *= kScoreBoostFromSuccessfulRetargeting; 193 context->score.good_clipping = 194 (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL || 195 context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE || 196 context->clip_result == CLIP_RESULT_NOT_CLIPPED || 197 context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET); 198 // Post the result (the bitmap) back to the callback. 199 BrowserThread::PostTask( 200 BrowserThread::UI, 201 FROM_HERE, 202 base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail)); 203 } 204 205 ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() { 206 } 207 208 // static 209 gfx::Rect ContentBasedThumbnailingAlgorithm::GetClippingRect( 210 const gfx::Size& source_size, 211 const gfx::Size& thumbnail_size, 212 gfx::Size* target_size, 213 ClipResult* clip_result) { 214 // Compute and return the clipping rectagle of the source image and the 215 // size of the target bitmap which will be used for the further processing. 216 // This function in 'general case' is trivial (don't clip, halve the source) 217 // but it is needed for handling edge cases (source smaller than the target 218 // thumbnail size). 219 DCHECK(target_size); 220 DCHECK(clip_result); 221 gfx::Rect clipping_rect; 222 if (source_size.width() < thumbnail_size.width() || 223 source_size.height() < thumbnail_size.height()) { 224 clipping_rect = gfx::Rect(thumbnail_size); 225 *target_size = thumbnail_size; 226 *clip_result = CLIP_RESULT_SOURCE_IS_SMALLER; 227 } else if (source_size.width() < thumbnail_size.width() * 4 || 228 source_size.height() < thumbnail_size.height() * 4) { 229 clipping_rect = gfx::Rect(source_size); 230 *target_size = source_size; 231 *clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET; 232 } else { 233 clipping_rect = gfx::Rect(source_size); 234 target_size->SetSize(source_size.width() / 2, source_size.height() / 2); 235 *clip_result = CLIP_RESULT_NOT_CLIPPED; 236 } 237 238 return clipping_rect; 239 } 240 241 } // namespace thumbnails 242