Chromium Code Reviews| Index: chrome/browser/thumbnails/advanced_thumbnail_crop.cc | 
| diff --git a/chrome/browser/thumbnails/advanced_thumbnail_crop.cc b/chrome/browser/thumbnails/advanced_thumbnail_crop.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..af25377a69900de1c2b5603cfe93e5387c2d8dec | 
| --- /dev/null | 
| +++ b/chrome/browser/thumbnails/advanced_thumbnail_crop.cc | 
| @@ -0,0 +1,238 @@ | 
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "chrome/browser/thumbnails/advanced_thumbnail_crop.h" | 
| + | 
| +#include "base/metrics/histogram.h" | 
| +#include "base/threading/sequenced_worker_pool.h" | 
| +#include "chrome/browser/thumbnails/content_analysis.h" | 
| +#include "chrome/browser/thumbnails/simple_thumbnail_crop.h" | 
| +#include "content/public/browser/browser_thread.h" | 
| +#include "third_party/skia/include/core/SkBitmap.h" | 
| +#include "ui/gfx/scrollbar_size.h" | 
| +#include "ui/gfx/size_conversions.h" | 
| +#include "ui/gfx/skbitmap_operations.h" | 
| +#include "ui/gfx/skia_util.h" | 
| + | 
| +namespace { | 
| 
 
mazda
2013/05/23 19:13:57
nit: add one empty line
 
motek.
2013/05/27 16:54:54
Done.
 
 | 
| +static const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS"; | 
| +static const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS"; | 
| + | 
| +void CallbackInvocationAdapter( | 
| + const thumbnails::ThumbnailingAlgorithm::ConsumerCallback& callback, | 
| + scoped_refptr<thumbnails::ThumbnailingContext> context, | 
| + const SkBitmap& source_bitmap) { | 
| + callback.Run(*context, source_bitmap); | 
| +} | 
| + | 
| +} | 
| 
 
mazda
2013/05/23 19:13:57
}  // namespace
 
motek.
2013/05/27 16:54:54
Done.
 
 | 
| + | 
| +namespace thumbnails { | 
| + | 
| +using content::BrowserThread; | 
| + | 
| +AdvancedThumbnailCrop::AdvancedThumbnailCrop(const gfx::Size& target_size) | 
| + : target_size_(target_size) { | 
| + DCHECK(!target_size.IsEmpty()); | 
| +} | 
| + | 
| +ClipResult AdvancedThumbnailCrop::GetCanvasCopyInfo( | 
| + const gfx::Size& source_size, | 
| + ui::ScaleFactor scale_factor, | 
| + gfx::Rect* clipping_rect, | 
| + gfx::Size* target_size) const { | 
| + DCHECK(!source_size.IsEmpty()); | 
| + gfx::Size target_thumbnail_size = | 
| + SimpleThumbnailCrop::GetCopySizeForThumbnail(scale_factor, target_size_); | 
| + | 
| + ClipResult clipping_method = thumbnails::CLIP_RESULT_NOT_CLIPPED; | 
| + *clipping_rect = GetClippingRect( | 
| + source_size, target_thumbnail_size, target_size, &clipping_method); | 
| + return clipping_method; | 
| +} | 
| + | 
| +void AdvancedThumbnailCrop::ProcessBitmap( | 
| + scoped_refptr<ThumbnailingContext> context, | 
| + const ConsumerCallback& callback, | 
| + const SkBitmap& bitmap) { | 
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 
| + DCHECK(context); | 
| + if (bitmap.isNull() || bitmap.empty()) | 
| + return; | 
| + | 
| + float max_scale_factor = | 
| + ui::GetScaleFactorScale(ui::GetMaxScaleFactor()); | 
| + gfx::Size target_thumbnail_size = gfx::ToFlooredSize( | 
| + 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
 
 | 
| + | 
| + SkBitmap source_bitmap = PrepareSourceBitmap( | 
| + bitmap, target_thumbnail_size, context); | 
| + | 
| + // If the source is same (or smaller) than the target, just return it as | 
| + // the final result. Otherwise, send the shrinking task to the blocking | 
| + // thread pool. | 
| + if (source_bitmap.width() <= target_thumbnail_size.width() || | 
| + source_bitmap.height() <= target_thumbnail_size.height()) { | 
| + context->score.boring_score = | 
| + SimpleThumbnailCrop::CalculateBoringScore(source_bitmap); | 
| + context->score.good_clipping = | 
| + (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL || | 
| + context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE || | 
| + context->clip_result == CLIP_RESULT_NOT_CLIPPED || | 
| + context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET); | 
| + | 
| + callback.Run(*context, source_bitmap); | 
| + return; | 
| + } | 
| + | 
| + if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior( | 
| + FROM_HERE, | 
| + base::Bind(&CreateRetargettedThumbnail, | 
| + source_bitmap, | 
| + target_thumbnail_size, | 
| + context, | 
| + callback), | 
| + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)) { | 
| + LOG(WARNING) << "PostSequencedWorkerTask failed. " << | 
| + "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.
 
 | 
| + } | 
| +} | 
| + | 
| +// static | 
| +SkBitmap AdvancedThumbnailCrop::PrepareSourceBitmap( | 
| + const SkBitmap& received_bitmap, | 
| + const gfx::Size& thumbnail_size, | 
| + ThumbnailingContext* context) { | 
| + | 
| + gfx::Size resize_target; | 
| + SkBitmap clipped_bitmap; | 
| + if (context->clip_result == CLIP_RESULT_UNPROCESSED) { | 
| + // This case will require extracting a fragment from the retrieved bitmap. | 
| + int scrollbar_size = gfx::scrollbar_size(); | 
| + gfx::Size scrollbarless( | 
| + std::max(1, received_bitmap.width() - scrollbar_size), | 
| + std::max(1, received_bitmap.height() - scrollbar_size)); | 
| + | 
| + gfx::Rect clipping_rect = AdvancedThumbnailCrop::GetClippingRect( | 
| + scrollbarless, | 
| + thumbnail_size, | 
| + &resize_target, | 
| + &context->clip_result); | 
| + | 
| + received_bitmap.extractSubset(&clipped_bitmap, | 
| + gfx::RectToSkIRect(clipping_rect)); | 
| + } else { | 
| + // This means that the source bitmap has been requested and at least | 
| + // clipped. Upstream code in same cases seems opportunistic and it may | 
| + // not perform actual resizing if copying with resize is not supported. | 
| + // In this case we will resize to the orignally requested copy size. | 
| + resize_target = context->requested_copy_size; | 
| + clipped_bitmap = received_bitmap; | 
| + } | 
| + | 
| + SkBitmap result_bitmap = SkBitmapOperations::DownsampleByTwoUntilSize( | 
| + clipped_bitmap, resize_target.width(), resize_target.height()); | 
| +#if !defined(USE_AURA) | 
| + // If the bitmap has not been indeed resized, it has to be copied. In that | 
| + // case resampler simply returns a reference to the original bitmap, sitting | 
| + // in PlatformCanvas. One does not simply assign these 'magic' bitmaps to | 
| + // SkBitmap. They cannot be refcounted. | 
| + // | 
| + // With Aura, this does not happen since PlatformCanvas is platform | 
| + // idependent. | 
| + if (clipped_bitmap.width() == result_bitmap.width() && | 
| + clipped_bitmap.height() == result_bitmap.height()) { | 
| + clipped_bitmap.copyTo(&result_bitmap, SkBitmap::kARGB_8888_Config); | 
| + } | 
| +#endif | 
| + | 
| + return result_bitmap; | 
| +} | 
| + | 
| +// static | 
| +void AdvancedThumbnailCrop::CreateRetargettedThumbnail( | 
| + const SkBitmap& source_bitmap, | 
| + const gfx::Size& thumbnail_size, | 
| + scoped_refptr<ThumbnailingContext> context, | 
| + const ConsumerCallback& callback) { | 
| + base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now(); | 
| + float kernel_sigma = | 
| + context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET ? 5.0f : 2.5f; | 
| + SkBitmap thumbnail = thumbnailing_utils::CreateRetargettedThumbnailImage( | 
| + source_bitmap, thumbnail_size, kernel_sigma); | 
| + bool processing_failed = thumbnail.empty(); | 
| + if (processing_failed) { | 
| + // Log and apply the method very much like in SimpleThumbnailCrop (except | 
| + // that some clipping and copying is not required). | 
| + LOG(WARNING) << "CreateRetargettedThumbnailImage failed. " << | 
| + "The thumbnail for " << context->url << | 
| + " 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.
 
 | 
| + ClipResult clip_result; | 
| + gfx::Rect clipping_rect = SimpleThumbnailCrop::GetClippingRect( | 
| + gfx::Size(source_bitmap.width(), source_bitmap.height()), | 
| + thumbnail_size, | 
| + &clip_result); | 
| + source_bitmap.extractSubset(&thumbnail, gfx::RectToSkIRect(clipping_rect)); | 
| + thumbnail = SkBitmapOperations::DownsampleByTwoUntilSize( | 
| + thumbnail, thumbnail_size.width(), thumbnail_size.height()); | 
| + } | 
| + | 
| + HISTOGRAM_TIMES( | 
| + processing_failed ? kFailureHistogramName : kThumbnailHistogramName, | 
| + base::TimeTicks::Now() - begin_compute_thumbnail); | 
| + context->score.boring_score = | 
| + SimpleThumbnailCrop::CalculateBoringScore(source_bitmap); | 
| + if (!processing_failed) | 
| + 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.
 
 | 
| + context->score.good_clipping = | 
| + (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL || | 
| + context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE || | 
| + context->clip_result == CLIP_RESULT_NOT_CLIPPED || | 
| + context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET); | 
| + // Post the result (the bitmap) back to the callback. | 
| + BrowserThread::PostTask( | 
| + BrowserThread::UI, | 
| + FROM_HERE, | 
| + base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail)); | 
| +} | 
| + | 
| +AdvancedThumbnailCrop::~AdvancedThumbnailCrop() { | 
| +} | 
| + | 
| +// static | 
| +gfx::Rect AdvancedThumbnailCrop::GetClippingRect( | 
| + const gfx::Size& source_size, | 
| + const gfx::Size& thumbnail_size, | 
| + gfx::Size* target_size, | 
| + ClipResult* clip_result) { | 
| + // Compute and return the clipping rectagle of the source image and the | 
| + // size of the target bitmap which will be used for the further processing. | 
| + // This function in 'general case' is trivial (don't clip, halve the source) | 
| + // but it is needed for handling edge cases (source smaller than the target | 
| + // thumbnail size). | 
| + DCHECK(target_size); | 
| + DCHECK(clip_result); | 
| + gfx::Rect clipping_rect; | 
| + if (source_size.width() < thumbnail_size.width() || | 
| + source_size.height() < thumbnail_size.height()) { | 
| + clipping_rect = gfx::Rect(thumbnail_size); | 
| + *target_size = thumbnail_size; | 
| + *clip_result = CLIP_RESULT_SOURCE_IS_SMALLER; | 
| + } else if (source_size.width() < thumbnail_size.width() * 4 || | 
| + source_size.height() < thumbnail_size.height() * 4) { | 
| + clipping_rect = gfx::Rect(source_size); | 
| + *target_size = source_size; | 
| + *clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET; | 
| + } else { | 
| + clipping_rect = gfx::Rect(source_size); | 
| + target_size->SetSize(source_size.width() / 2, source_size.height() / 2); | 
| + *clip_result = CLIP_RESULT_NOT_CLIPPED; | 
| + } | 
| + | 
| + return clipping_rect; | 
| +} | 
| + | 
| + | 
| 
 
mazda
2013/05/23 19:13:57
nit: delete two empty lines
 
motek.
2013/05/27 16:54:54
Done.
 
 | 
| + | 
| +} // namespace thumbnails |