| Index: chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc
|
| diff --git a/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7bce10e0b5c7d8ff9a6572da50a0aef7eb9c0f59
|
| --- /dev/null
|
| +++ b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc
|
| @@ -0,0 +1,237 @@
|
| +// 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/content_based_thumbnailing_algorithm.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 {
|
| +
|
| +const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS";
|
| +const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS";
|
| +const float kScoreBoostFromSuccessfulRetargeting = 1.1f;
|
| +
|
| +void CallbackInvocationAdapter(
|
| + const thumbnails::ThumbnailingAlgorithm::ConsumerCallback& callback,
|
| + scoped_refptr<thumbnails::ThumbnailingContext> context,
|
| + const SkBitmap& source_bitmap) {
|
| + callback.Run(*context, source_bitmap);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace thumbnails {
|
| +
|
| +using content::BrowserThread;
|
| +
|
| +ContentBasedThumbnailingAlgorithm::ContentBasedThumbnailingAlgorithm(
|
| + const gfx::Size& target_size)
|
| + : target_size_(target_size) {
|
| + DCHECK(!target_size.IsEmpty());
|
| +}
|
| +
|
| +ClipResult ContentBasedThumbnailingAlgorithm::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 ContentBasedThumbnailingAlgorithm::ProcessBitmap(
|
| + scoped_refptr<ThumbnailingContext> context,
|
| + const ConsumerCallback& callback,
|
| + const SkBitmap& bitmap) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + DCHECK(context);
|
| + if (bitmap.isNull() || bitmap.empty())
|
| + return;
|
| +
|
| + gfx::Size target_thumbnail_size =
|
| + SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_);
|
| +
|
| + 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(&CreateRetargetedThumbnail,
|
| + 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.";
|
| + }
|
| +}
|
| +
|
| +// static
|
| +SkBitmap ContentBasedThumbnailingAlgorithm::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 = 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 ContentBasedThumbnailingAlgorithm::CreateRetargetedThumbnail(
|
| + 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::CreateRetargetedThumbnailImage(
|
| + 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) << "CreateRetargetedThumbnailImage failed. "
|
| + << "The thumbnail for " << context->url
|
| + << " will be created the old-fashioned way.";
|
| +
|
| + 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 *= kScoreBoostFromSuccessfulRetargeting;
|
| + 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));
|
| +}
|
| +
|
| +ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() {
|
| +}
|
| +
|
| +// static
|
| +gfx::Rect ContentBasedThumbnailingAlgorithm::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;
|
| +}
|
| +
|
| +} // namespace thumbnails
|
|
|