blob: b8434d92e5abf55fe53c0832d299ab401e2d374f [file] [log] [blame]
// Copyright 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.get(), 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.get());
if (bitmap.isNull() || bitmap.empty())
return;
gfx::Size target_thumbnail_size =
SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_);
SkBitmap source_bitmap =
PrepareSourceBitmap(bitmap, target_thumbnail_size, context.get());
// 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.get(), 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, kPMColor_SkColorType);
}
#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