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/advanced_thumbnail_crop.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 { | |
|
mazda
2013/05/23 19:13:57
nit: add one empty line
motek.
2013/05/27 16:54:54
Done.
| |
| 19 static const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS"; | |
| 20 static const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS"; | |
| 21 | |
| 22 void CallbackInvocationAdapter( | |
| 23 const thumbnails::ThumbnailingAlgorithm::ConsumerCallback& callback, | |
| 24 scoped_refptr<thumbnails::ThumbnailingContext> context, | |
| 25 const SkBitmap& source_bitmap) { | |
| 26 callback.Run(*context, source_bitmap); | |
| 27 } | |
| 28 | |
| 29 } | |
|
mazda
2013/05/23 19:13:57
} // namespace
motek.
2013/05/27 16:54:54
Done.
| |
| 30 | |
| 31 namespace thumbnails { | |
| 32 | |
| 33 using content::BrowserThread; | |
| 34 | |
| 35 AdvancedThumbnailCrop::AdvancedThumbnailCrop(const gfx::Size& target_size) | |
| 36 : target_size_(target_size) { | |
| 37 DCHECK(!target_size.IsEmpty()); | |
| 38 } | |
| 39 | |
| 40 ClipResult AdvancedThumbnailCrop::GetCanvasCopyInfo( | |
| 41 const gfx::Size& source_size, | |
| 42 ui::ScaleFactor scale_factor, | |
| 43 gfx::Rect* clipping_rect, | |
| 44 gfx::Size* target_size) const { | |
| 45 DCHECK(!source_size.IsEmpty()); | |
| 46 gfx::Size target_thumbnail_size = | |
| 47 SimpleThumbnailCrop::GetCopySizeForThumbnail(scale_factor, target_size_); | |
| 48 | |
| 49 ClipResult clipping_method = thumbnails::CLIP_RESULT_NOT_CLIPPED; | |
| 50 *clipping_rect = GetClippingRect( | |
| 51 source_size, target_thumbnail_size, target_size, &clipping_method); | |
| 52 return clipping_method; | |
| 53 } | |
| 54 | |
| 55 void AdvancedThumbnailCrop::ProcessBitmap( | |
| 56 scoped_refptr<ThumbnailingContext> context, | |
| 57 const ConsumerCallback& callback, | |
| 58 const SkBitmap& bitmap) { | |
| 59 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 60 DCHECK(context); | |
| 61 if (bitmap.isNull() || bitmap.empty()) | |
| 62 return; | |
| 63 | |
| 64 float max_scale_factor = | |
| 65 ui::GetScaleFactorScale(ui::GetMaxScaleFactor()); | |
| 66 gfx::Size target_thumbnail_size = gfx::ToFlooredSize( | |
| 67 gfx::ScaleSize(target_size_, max_scale_factor)); | |
|
mazda
2013/05/23 19:13:57
Could you share this logic of how the scale factor
motek.
2013/05/27 16:54:54
I did it by applying some refactoring. Effectively
| |
| 68 | |
| 69 SkBitmap source_bitmap = PrepareSourceBitmap( | |
| 70 bitmap, target_thumbnail_size, context); | |
| 71 | |
| 72 // If the source is same (or smaller) than the target, just return it as | |
| 73 // the final result. Otherwise, send the shrinking task to the blocking | |
| 74 // thread pool. | |
| 75 if (source_bitmap.width() <= target_thumbnail_size.width() || | |
| 76 source_bitmap.height() <= target_thumbnail_size.height()) { | |
| 77 context->score.boring_score = | |
| 78 SimpleThumbnailCrop::CalculateBoringScore(source_bitmap); | |
| 79 context->score.good_clipping = | |
| 80 (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL || | |
| 81 context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE || | |
| 82 context->clip_result == CLIP_RESULT_NOT_CLIPPED || | |
| 83 context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET); | |
| 84 | |
| 85 callback.Run(*context, source_bitmap); | |
| 86 return; | |
| 87 } | |
| 88 | |
| 89 if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior( | |
| 90 FROM_HERE, | |
| 91 base::Bind(&CreateRetargettedThumbnail, | |
| 92 source_bitmap, | |
| 93 target_thumbnail_size, | |
| 94 context, | |
| 95 callback), | |
| 96 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)) { | |
| 97 LOG(WARNING) << "PostSequencedWorkerTask failed. " << | |
| 98 "The thumbnail for " << context->url << " will not be created."; | |
|
mazda
2013/05/23 19:13:57
Move "<<" from the previous line and align it with
motek.
2013/05/27 16:54:54
Done.
| |
| 99 } | |
| 100 } | |
| 101 | |
| 102 // static | |
| 103 SkBitmap AdvancedThumbnailCrop::PrepareSourceBitmap( | |
| 104 const SkBitmap& received_bitmap, | |
| 105 const gfx::Size& thumbnail_size, | |
| 106 ThumbnailingContext* context) { | |
| 107 | |
| 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 = AdvancedThumbnailCrop::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 AdvancedThumbnailCrop::CreateRetargettedThumbnail( | |
| 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::CreateRetargettedThumbnailImage( | |
| 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) << "CreateRetargettedThumbnailImage failed. " << | |
| 169 "The thumbnail for " << context->url << | |
| 170 " will be created the old-fashioned way."; | |
|
mazda
2013/05/23 19:13:57
LOG(WARNING) << "CreateRetargettedThumbnailImage f
motek.
2013/05/27 16:54:54
Done.
| |
| 171 ClipResult clip_result; | |
| 172 gfx::Rect clipping_rect = SimpleThumbnailCrop::GetClippingRect( | |
| 173 gfx::Size(source_bitmap.width(), source_bitmap.height()), | |
| 174 thumbnail_size, | |
| 175 &clip_result); | |
| 176 source_bitmap.extractSubset(&thumbnail, gfx::RectToSkIRect(clipping_rect)); | |
| 177 thumbnail = SkBitmapOperations::DownsampleByTwoUntilSize( | |
| 178 thumbnail, thumbnail_size.width(), thumbnail_size.height()); | |
| 179 } | |
| 180 | |
| 181 HISTOGRAM_TIMES( | |
| 182 processing_failed ? kFailureHistogramName : kThumbnailHistogramName, | |
| 183 base::TimeTicks::Now() - begin_compute_thumbnail); | |
| 184 context->score.boring_score = | |
| 185 SimpleThumbnailCrop::CalculateBoringScore(source_bitmap); | |
| 186 if (!processing_failed) | |
| 187 context->score.boring_score *= 1.1f; // A bit of a boost for retargetted. | |
|
mazda
2013/05/23 19:13:57
Please make a constant variable for 1.1f.
motek.
2013/05/27 16:54:54
Done.
| |
| 188 context->score.good_clipping = | |
| 189 (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL || | |
| 190 context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE || | |
| 191 context->clip_result == CLIP_RESULT_NOT_CLIPPED || | |
| 192 context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET); | |
| 193 // Post the result (the bitmap) back to the callback. | |
| 194 BrowserThread::PostTask( | |
| 195 BrowserThread::UI, | |
| 196 FROM_HERE, | |
| 197 base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail)); | |
| 198 } | |
| 199 | |
| 200 AdvancedThumbnailCrop::~AdvancedThumbnailCrop() { | |
| 201 } | |
| 202 | |
| 203 // static | |
| 204 gfx::Rect AdvancedThumbnailCrop::GetClippingRect( | |
| 205 const gfx::Size& source_size, | |
| 206 const gfx::Size& thumbnail_size, | |
| 207 gfx::Size* target_size, | |
| 208 ClipResult* clip_result) { | |
| 209 // Compute and return the clipping rectagle of the source image and the | |
| 210 // size of the target bitmap which will be used for the further processing. | |
| 211 // This function in 'general case' is trivial (don't clip, halve the source) | |
| 212 // but it is needed for handling edge cases (source smaller than the target | |
| 213 // thumbnail size). | |
| 214 DCHECK(target_size); | |
| 215 DCHECK(clip_result); | |
| 216 gfx::Rect clipping_rect; | |
| 217 if (source_size.width() < thumbnail_size.width() || | |
| 218 source_size.height() < thumbnail_size.height()) { | |
| 219 clipping_rect = gfx::Rect(thumbnail_size); | |
| 220 *target_size = thumbnail_size; | |
| 221 *clip_result = CLIP_RESULT_SOURCE_IS_SMALLER; | |
| 222 } else if (source_size.width() < thumbnail_size.width() * 4 || | |
| 223 source_size.height() < thumbnail_size.height() * 4) { | |
| 224 clipping_rect = gfx::Rect(source_size); | |
| 225 *target_size = source_size; | |
| 226 *clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET; | |
| 227 } else { | |
| 228 clipping_rect = gfx::Rect(source_size); | |
| 229 target_size->SetSize(source_size.width() / 2, source_size.height() / 2); | |
| 230 *clip_result = CLIP_RESULT_NOT_CLIPPED; | |
| 231 } | |
| 232 | |
| 233 return clipping_rect; | |
| 234 } | |
| 235 | |
| 236 | |
|
mazda
2013/05/23 19:13:57
nit: delete two empty lines
motek.
2013/05/27 16:54:54
Done.
| |
| 237 | |
| 238 } // namespace thumbnails | |
| OLD | NEW |