diff options
19 files changed, 588 insertions, 42 deletions
diff --git a/chrome/browser/thumbnails/content_analysis.cc b/chrome/browser/thumbnails/content_analysis.cc index 2300555..3b354cc 100644 --- a/chrome/browser/thumbnails/content_analysis.cc +++ b/chrome/browser/thumbnails/content_analysis.cc @@ -72,7 +72,7 @@ void ApplyGaussianGradientMagnitudeFilter(SkBitmap* input_bitmap, float kernel_sigma) { // The purpose of this function is to highlight salient // (attention-attracting?) features of the image for use in image - // retargetting. + // retargeting. SkAutoLockPixels source_lock(*input_bitmap); DCHECK(input_bitmap); DCHECK(input_bitmap->getPixels()); @@ -424,7 +424,7 @@ SkBitmap ComputeDecimatedImage(const SkBitmap& bitmap, return target; } -SkBitmap CreateRetargettedThumbnailImage( +SkBitmap CreateRetargetedThumbnailImage( const SkBitmap& source_bitmap, const gfx::Size& target_size, float kernel_sigma) { @@ -441,7 +441,7 @@ SkBitmap CreateRetargettedThumbnailImage( if (!color_utils::ApplyColorReduction( source_bitmap, transform, true, &reduced_color)) { DLOG(WARNING) << "Failed to compute luminance image from a screenshot. " - << "Cannot compute retargetted thumbnail."; + << "Cannot compute retargeted thumbnail."; return SkBitmap(); } DLOG(WARNING) << "Could not compute principal color image for a thumbnail. " diff --git a/chrome/browser/thumbnails/content_analysis.h b/chrome/browser/thumbnails/content_analysis.h index e65ff02..0eaadaa 100644 --- a/chrome/browser/thumbnails/content_analysis.h +++ b/chrome/browser/thumbnails/content_analysis.h @@ -57,9 +57,9 @@ SkBitmap ComputeDecimatedImage(const SkBitmap& bitmap, // |kernel_sigma| defines the degree of image smoothing in gradient computation. // For a natural-sized (not shrunk) screenshot at 96 DPI and regular font size // 5.0 was determined to be a good value. -SkBitmap CreateRetargettedThumbnailImage(const SkBitmap& source_bitmap, - const gfx::Size& target_size, - float kernel_sigma); +SkBitmap CreateRetargetedThumbnailImage(const SkBitmap& source_bitmap, + const gfx::Size& target_size, + float kernel_sigma); } // namespace thumbnailing_utils diff --git a/chrome/browser/thumbnails/content_analysis_unittest.cc b/chrome/browser/thumbnails/content_analysis_unittest.cc index 96fcd64..a36b375 100644 --- a/chrome/browser/thumbnails/content_analysis_unittest.cc +++ b/chrome/browser/thumbnails/content_analysis_unittest.cc @@ -389,7 +389,7 @@ TEST_F(ThumbnailContentAnalysisTest, ComputeDecimatedImage) { gfx::Point(0, 0))); } -TEST_F(ThumbnailContentAnalysisTest, CreateRetargettedThumbnailImage) { +TEST_F(ThumbnailContentAnalysisTest, CreateRetargetedThumbnailImage) { gfx::Size image_size(1200, 1300); gfx::Canvas canvas(image_size, ui::SCALE_FACTOR_100P, true); @@ -475,7 +475,7 @@ TEST_F(ThumbnailContentAnalysisTest, CreateRetargettedThumbnailImage) { SkBitmap source = skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); - SkBitmap result = CreateRetargettedThumbnailImage( + SkBitmap result = CreateRetargetedThumbnailImage( source, gfx::Size(424, 264), 2.5); EXPECT_FALSE(result.empty()); 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 0000000..3b11e61 --- /dev/null +++ b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc @@ -0,0 +1,237 @@ +// 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, 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 diff --git a/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h new file mode 100644 index 0000000..43d981b --- /dev/null +++ b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef CHROME_BROWSER_THUMBNAILS_CONTENT_BASED_THUMBNAILING_ALGORITHM_H_ +#define CHROME_BROWSER_THUMBNAILS_CONTENT_BASED_THUMBNAILING_ALGORITHM_H_ + +#include "chrome/browser/thumbnails/thumbnailing_algorithm.h" + +namespace thumbnails { + +// Encapsulates a method of creating a thumbnail from a captured tab shot which +// attempts to preserve only relevant fragments of the original image. +// The algorithm detects areas of high activity at low resolution and discards +// rows and columns which do not intersect with these areas. +class ContentBasedThumbnailingAlgorithm : public ThumbnailingAlgorithm { + public: + explicit ContentBasedThumbnailingAlgorithm(const gfx::Size& target_size); + + virtual ClipResult GetCanvasCopyInfo(const gfx::Size& source_size, + ui::ScaleFactor scale_factor, + gfx::Rect* clipping_rect, + gfx::Size* target_size) const OVERRIDE; + + virtual void ProcessBitmap(scoped_refptr<ThumbnailingContext> context, + const ConsumerCallback& callback, + const SkBitmap& bitmap) OVERRIDE; + + // Prepares (clips to size, copies etc.) the bitmap passed to ProcessBitmap. + // Always returns a bitmap that can be properly refcounted. + // Extracted and exposed as a test seam. + static SkBitmap PrepareSourceBitmap(const SkBitmap& received_bitmap, + const gfx::Size& thumbnail_size, + ThumbnailingContext* context); + + // The function processes |source_bitmap| into a thumbnail of |thumbnail_size| + // and passes the result into |callback| (on UI thread). |context| describes + // how the thumbnail is being created. + static void CreateRetargetedThumbnail( + const SkBitmap& source_bitmap, + const gfx::Size& thumbnail_size, + scoped_refptr<ThumbnailingContext> context, + const ConsumerCallback& callback); + + protected: + virtual ~ContentBasedThumbnailingAlgorithm(); + + private: + static gfx::Rect GetClippingRect(const gfx::Size& source_size, + const gfx::Size& thumbnail_size, + gfx::Size* target_size, + ClipResult* clip_result); + + const gfx::Size target_size_; + + DISALLOW_COPY_AND_ASSIGN(ContentBasedThumbnailingAlgorithm); +}; + +} // namespace thumbnails + +#endif // CHROME_BROWSER_THUMBNAILS_CONTENT_BASED_THUMBNAILING_ALGORITHM_H_ diff --git a/chrome/browser/thumbnails/content_based_thumbnailing_algorithm_unittest.cc b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm_unittest.cc new file mode 100644 index 0000000..38b0209 --- /dev/null +++ b/chrome/browser/thumbnails/content_based_thumbnailing_algorithm_unittest.cc @@ -0,0 +1,168 @@ +// 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 "base/message_loop.h" +#include "chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h" +#include "chrome/browser/thumbnails/simple_thumbnail_crop.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/scrollbar_size.h" + +namespace thumbnails { + +typedef testing::Test ContentBasedThumbnailingAlgorithmTest; + +class ConsumerCallbackCatcher { + public: + ConsumerCallbackCatcher() + : called_back_(false), clip_result_(CLIP_RESULT_UNPROCESSED) { + } + + void UiThreadCallback(const ThumbnailingContext& context, + const SkBitmap& bitmap) { + called_back_ = true; + captured_bitmap_ = bitmap; + clip_result_ = context.clip_result; + score_ = context.score; + } + + bool called_back() const { + return called_back_; + } + + const SkBitmap& captured_bitmap() const { + return captured_bitmap_; + } + + ClipResult clip_result() const { + return clip_result_; + } + + const ThumbnailScore& score() const { + return score_; + } + + private: + SkBitmap captured_bitmap_; + bool called_back_; + ClipResult clip_result_; + ThumbnailScore score_; + + DISALLOW_COPY_AND_ASSIGN(ConsumerCallbackCatcher); +}; + +TEST_F(ContentBasedThumbnailingAlgorithmTest, GetCanvasCopyInfo) { + // We will want to use the entirety of the image as the source. Usually, + // an image in its original size should be requested, except for reakky large + // canvas. In that case, image will be shrunk but wit aspect ratio preserved. + const gfx::Size thumbnail_size(312, 165); + scoped_refptr<ThumbnailingAlgorithm> algorithm( + new ContentBasedThumbnailingAlgorithm(thumbnail_size)); + + gfx::Rect clipping_rect; + gfx::Size target_size; + gfx::Size source_size(1000, 600); + + ClipResult clip_result = algorithm->GetCanvasCopyInfo( + source_size, ui::SCALE_FACTOR_100P, &clipping_rect, &target_size); + EXPECT_EQ(CLIP_RESULT_SOURCE_SAME_AS_TARGET, clip_result); + EXPECT_EQ(source_size.ToString(), clipping_rect.size().ToString()); + EXPECT_EQ(gfx::Point(0, 0).ToString(), clipping_rect.origin().ToString()); + EXPECT_EQ(source_size, target_size); + + source_size.SetSize(6000, 3000); + clip_result = algorithm->GetCanvasCopyInfo( + source_size, ui::SCALE_FACTOR_100P, &clipping_rect, &target_size); + EXPECT_EQ(CLIP_RESULT_NOT_CLIPPED, clip_result); + EXPECT_EQ(source_size.ToString(), clipping_rect.size().ToString()); + EXPECT_EQ(gfx::Point(0, 0).ToString(), clipping_rect.origin().ToString()); + EXPECT_LT(target_size.width(), source_size.width()); + EXPECT_LT(target_size.height(), source_size.height()); + EXPECT_NEAR(static_cast<float>(target_size.width()) / target_size.height(), + static_cast<float>(source_size.width()) / source_size.height(), + 0.1f); + source_size.SetSize(300, 200); + clip_result = algorithm->GetCanvasCopyInfo( + source_size, ui::SCALE_FACTOR_100P, &clipping_rect, &target_size); + EXPECT_EQ(CLIP_RESULT_SOURCE_IS_SMALLER, clip_result); + EXPECT_EQ(clipping_rect.size().ToString(), + SimpleThumbnailCrop::GetCopySizeForThumbnail( + ui::SCALE_FACTOR_100P, thumbnail_size).ToString()); + EXPECT_EQ(gfx::Point(0, 0).ToString(), clipping_rect.origin().ToString()); +} + +TEST_F(ContentBasedThumbnailingAlgorithmTest, PrepareSourceBitmap) { + const gfx::Size thumbnail_size(312, 165); + const gfx::Size copy_size(400, 200); + scoped_refptr<ThumbnailingContext> context( + ThumbnailingContext::CreateThumbnailingContextForTest()); + context->requested_copy_size = copy_size; + + // This calls for exercising two distinct paths: with prior clipping and + // without. + SkBitmap source; + source.setConfig(SkBitmap::kARGB_8888_Config, 800, 600); + source.allocPixels(); + source.eraseRGB(50, 150, 200); + SkBitmap result = ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap( + source, thumbnail_size, context); + EXPECT_EQ(CLIP_RESULT_SOURCE_SAME_AS_TARGET, context->clip_result); + EXPECT_GE(result.width(), copy_size.width()); + EXPECT_GE(result.height(), copy_size.height()); + EXPECT_LT(result.width(), source.width()); + EXPECT_LT(result.height(), source.height()); + // The check below is a bit of a side effect: since the image was clipped + // by scrollbar_size, it cannot be shrunk and thus what we get below is + // true. + EXPECT_NEAR(result.width(), source.width(), gfx::scrollbar_size()); + EXPECT_NEAR(result.height(), source.height(), gfx::scrollbar_size()); + + result = ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap( + source, thumbnail_size, context); + EXPECT_EQ(CLIP_RESULT_SOURCE_SAME_AS_TARGET, context->clip_result); + EXPECT_GE(result.width(), copy_size.width()); + EXPECT_GE(result.height(), copy_size.height()); + EXPECT_LT(result.width(), source.width()); + EXPECT_LT(result.height(), source.height()); +} + +TEST_F(ContentBasedThumbnailingAlgorithmTest, CreateRetargetedThumbnail) { + // This tests the invocation of the main thumbnail-making apparatus. + // The actual content is not really of concern here, just check the plumbing. + const gfx::Size image_size(1200, 800); + gfx::Canvas canvas(image_size, ui::SCALE_FACTOR_100P, true); + + // The image consists of vertical non-overlapping stripes 150 pixels wide. + canvas.FillRect(gfx::Rect(200, 200, 800, 400), SkColorSetRGB(255, 255, 255)); + SkBitmap source = + skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); + + ConsumerCallbackCatcher catcher; + const gfx::Size thumbnail_size(432, 284); + scoped_refptr<ThumbnailingContext> context( + ThumbnailingContext::CreateThumbnailingContextForTest()); + context->requested_copy_size = image_size; + context->clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET; + + base::MessageLoopForUI message_loop; + content::TestBrowserThread ui_thread(content::BrowserThread::UI, + &message_loop); + ContentBasedThumbnailingAlgorithm::CreateRetargetedThumbnail( + source, + thumbnail_size, + context, + base::Bind(&ConsumerCallbackCatcher::UiThreadCallback, + base::Unretained(&catcher))); + message_loop.RunUntilIdle(); + ASSERT_TRUE(catcher.called_back()); + EXPECT_TRUE(catcher.score().good_clipping); + EXPECT_FALSE(catcher.captured_bitmap().empty()); + EXPECT_LT(catcher.captured_bitmap().width(), source.width()); + EXPECT_LT(catcher.captured_bitmap().height(), source.height()); +} + +} // namespace thumbnails diff --git a/chrome/browser/thumbnails/simple_thumbnail_crop.cc b/chrome/browser/thumbnails/simple_thumbnail_crop.cc index f010e84..1a0ce5f 100644 --- a/chrome/browser/thumbnails/simple_thumbnail_crop.cc +++ b/chrome/browser/thumbnails/simple_thumbnail_crop.cc @@ -36,16 +36,18 @@ ClipResult SimpleThumbnailCrop::GetCanvasCopyInfo( return clip_result; } -void SimpleThumbnailCrop::ProcessBitmap(ThumbnailingContext* context, - const ConsumerCallback& callback, - const SkBitmap& bitmap) { +void SimpleThumbnailCrop::ProcessBitmap( + scoped_refptr<ThumbnailingContext> context, + const ConsumerCallback& callback, + const SkBitmap& bitmap) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); if (bitmap.isNull() || bitmap.empty()) return; - SkBitmap thumbnail = CreateThumbnail(bitmap, - GetThumbnailSizeInPixel(), - &context->clip_result); + SkBitmap thumbnail = CreateThumbnail( + bitmap, + ComputeTargetSizeAtMaximumScale(target_size_), + &context->clip_result); context->score.boring_score = CalculateBoringScore(thumbnail); context->score.good_clipping = @@ -134,20 +136,6 @@ gfx::Size SimpleThumbnailCrop::GetCopySizeForThumbnail( return copy_size; } -SimpleThumbnailCrop::~SimpleThumbnailCrop() { -} - -// Returns the size of the thumbnail stored in the database in pixel. -gfx::Size SimpleThumbnailCrop::GetThumbnailSizeInPixel() const { - // Determine the resolution of the thumbnail based on the maximum scale - // factor. - // TODO(mazda|oshima): Update thumbnail when the max scale factor changes. - // crbug.com/159157. - float max_scale_factor = - ui::GetScaleFactorScale(ui::GetMaxScaleFactor()); - return gfx::ToFlooredSize(gfx::ScaleSize(target_size_, max_scale_factor)); -} - gfx::Rect SimpleThumbnailCrop::GetClippingRect(const gfx::Size& source_size, const gfx::Size& desired_size, ClipResult* clip_result) { @@ -190,6 +178,19 @@ gfx::Rect SimpleThumbnailCrop::GetClippingRect(const gfx::Size& source_size, return clipping_rect; } +// static +gfx::Size SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale( + const gfx::Size& given_size) { + // TODO(mazda|oshima): Update thumbnail when the max scale factor changes. + // crbug.com/159157. + float max_scale_factor = + ui::GetScaleFactorScale(ui::GetMaxScaleFactor()); + return gfx::ToFlooredSize(gfx::ScaleSize(given_size, max_scale_factor)); +} + +SimpleThumbnailCrop::~SimpleThumbnailCrop() { +} + // Creates a downsampled thumbnail from the given bitmap. // store. The returned bitmap will be isNull if there was an error creating it. SkBitmap SimpleThumbnailCrop::CreateThumbnail(const SkBitmap& bitmap, diff --git a/chrome/browser/thumbnails/simple_thumbnail_crop.h b/chrome/browser/thumbnails/simple_thumbnail_crop.h index 472eda7..0095fc1 100644 --- a/chrome/browser/thumbnails/simple_thumbnail_crop.h +++ b/chrome/browser/thumbnails/simple_thumbnail_crop.h @@ -23,7 +23,7 @@ class SimpleThumbnailCrop : public ThumbnailingAlgorithm { gfx::Rect* clipping_rect, gfx::Size* target_size) const OVERRIDE; - virtual void ProcessBitmap(ThumbnailingContext* context, + virtual void ProcessBitmap(scoped_refptr<ThumbnailingContext> context, const ConsumerCallback& callback, const SkBitmap& bitmap) OVERRIDE; @@ -46,15 +46,20 @@ class SimpleThumbnailCrop : public ThumbnailingAlgorithm { thumbnails::ClipResult* clip_result); static gfx::Size GetCopySizeForThumbnail(ui::ScaleFactor scale_factor, const gfx::Size& thumbnail_size); + static gfx::Rect GetClippingRect(const gfx::Size& source_size, + const gfx::Size& desired_size, + ClipResult* clip_result); + + // Computes the size of a thumbnail that should be stored in the database from + // |given_size| (expected to be the thumbnail size we would normally want to + // see). The returned size is expressed in pixels and is determined by + // bumping the resolution up to the maximum scale factor. + static gfx::Size ComputeTargetSizeAtMaximumScale(const gfx::Size& given_size); protected: virtual ~SimpleThumbnailCrop(); private: - gfx::Size GetThumbnailSizeInPixel() const; - static gfx::Rect GetClippingRect(const gfx::Size& source_size, - const gfx::Size& desired_size, - ClipResult* clip_result); static SkBitmap CreateThumbnail(const SkBitmap& bitmap, const gfx::Size& desired_size, ClipResult* clip_result); diff --git a/chrome/browser/thumbnails/simple_thumbnail_crop_unittest.cc b/chrome/browser/thumbnails/simple_thumbnail_crop_unittest.cc index 54d0bcd..4da6cd5 100644 --- a/chrome/browser/thumbnails/simple_thumbnail_crop_unittest.cc +++ b/chrome/browser/thumbnails/simple_thumbnail_crop_unittest.cc @@ -196,3 +196,37 @@ TEST_F(SimpleThumbnailCropTest, GetCanvasCopyInfo) { EXPECT_EQ(thumbnails::CLIP_RESULT_SOURCE_IS_SMALLER, clip_result); EXPECT_EQ(thumbnail_size, target_size_result); } + +TEST_F(SimpleThumbnailCropTest, GetClippingRect) { + const gfx::Size desired_size(300, 200); + thumbnails::ClipResult clip_result; + // Try out 'microsource'. + gfx::Rect clip_rect = SimpleThumbnailCrop::GetClippingRect( + gfx::Size(300, 199), desired_size, &clip_result); + EXPECT_EQ(thumbnails::CLIP_RESULT_SOURCE_IS_SMALLER, clip_result); + EXPECT_EQ(gfx::Point(0, 0).ToString(), clip_rect.origin().ToString()); + EXPECT_EQ(desired_size.ToString(), clip_rect.size().ToString()); + + // Portrait source. + clip_rect = SimpleThumbnailCrop::GetClippingRect( + gfx::Size(500, 1200), desired_size, &clip_result); + EXPECT_EQ(thumbnails::CLIP_RESULT_TALLER_THAN_WIDE, clip_result); + EXPECT_EQ(gfx::Point(0, 0).ToString(), clip_rect.origin().ToString()); + EXPECT_EQ(500, clip_rect.width()); + EXPECT_GE(1200, clip_rect.height()); + + clip_rect = SimpleThumbnailCrop::GetClippingRect( + gfx::Size(2000, 800), desired_size, &clip_result); + EXPECT_TRUE(clip_result == thumbnails::CLIP_RESULT_WIDER_THAN_TALL || + clip_result == thumbnails::CLIP_RESULT_MUCH_WIDER_THAN_TALL); + EXPECT_EQ(0, clip_rect.y()); + EXPECT_LT(0, clip_rect.x()); + EXPECT_GE(2000, clip_rect.width()); + EXPECT_EQ(800, clip_rect.height()); + + clip_rect = SimpleThumbnailCrop::GetClippingRect( + gfx::Size(900, 600), desired_size, &clip_result); + EXPECT_EQ(thumbnails::CLIP_RESULT_NOT_CLIPPED, clip_result); + EXPECT_EQ(gfx::Point(0, 0).ToString(), clip_rect.origin().ToString()); + EXPECT_EQ(gfx::Size(900, 600).ToString(), clip_rect.size().ToString()); +} diff --git a/chrome/browser/thumbnails/thumbnail_service_impl.cc b/chrome/browser/thumbnails/thumbnail_service_impl.cc index 21f722792..ab3ef62 100644 --- a/chrome/browser/thumbnails/thumbnail_service_impl.cc +++ b/chrome/browser/thumbnails/thumbnail_service_impl.cc @@ -4,10 +4,14 @@ #include "chrome/browser/thumbnails/thumbnail_service_impl.h" +#include "base/command_line.h" #include "base/memory/ref_counted_memory.h" #include "chrome/browser/history/history_service.h" +#include "chrome/browser/search/search.h" +#include "chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h" #include "chrome/browser/thumbnails/simple_thumbnail_crop.h" #include "chrome/browser/thumbnails/thumbnailing_context.h" +#include "chrome/common/chrome_switches.h" namespace { @@ -15,12 +19,22 @@ namespace { const int kThumbnailWidth = 212; const int kThumbnailHeight = 132; +// True if thumbnail retargeting feature is enabled (Finch/flags). +bool IsThumbnailRetargetingEnabled() { + if (!chrome::IsInstantExtendedAPIEnabled()) + return false; + + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableThumbnailRetargeting); +} + } namespace thumbnails { ThumbnailServiceImpl::ThumbnailServiceImpl(Profile* profile) - : top_sites_(profile->GetTopSites()) { + : top_sites_(profile->GetTopSites()), + use_thumbnail_retargeting_(IsThumbnailRetargetingEnabled()){ } ThumbnailServiceImpl::~ThumbnailServiceImpl() { @@ -47,7 +61,10 @@ bool ThumbnailServiceImpl::GetPageThumbnail( ThumbnailingAlgorithm* ThumbnailServiceImpl::GetThumbnailingAlgorithm() const { - return new SimpleThumbnailCrop(gfx::Size(kThumbnailWidth, kThumbnailHeight)); + const gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight); + if (use_thumbnail_retargeting_) + return new ContentBasedThumbnailingAlgorithm(thumbnail_size); + return new SimpleThumbnailCrop(thumbnail_size); } bool ThumbnailServiceImpl::ShouldAcquirePageThumbnail(const GURL& url) { @@ -84,4 +101,4 @@ void ThumbnailServiceImpl::ShutdownOnUIThread() { top_sites_ = NULL; } -} +} // namespace thumbnails diff --git a/chrome/browser/thumbnails/thumbnail_service_impl.h b/chrome/browser/thumbnails/thumbnail_service_impl.h index 7d01d00..4a05a8a 100644 --- a/chrome/browser/thumbnails/thumbnail_service_impl.h +++ b/chrome/browser/thumbnails/thumbnail_service_impl.h @@ -38,6 +38,7 @@ class ThumbnailServiceImpl : public ThumbnailService { virtual ~ThumbnailServiceImpl(); scoped_refptr<history::TopSites> top_sites_; + bool use_thumbnail_retargeting_; DISALLOW_COPY_AND_ASSIGN(ThumbnailServiceImpl); }; diff --git a/chrome/browser/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/thumbnails/thumbnail_tab_helper.cc index 7060408..4910100 100644 --- a/chrome/browser/thumbnails/thumbnail_tab_helper.cc +++ b/chrome/browser/thumbnails/thumbnail_tab_helper.cc @@ -62,8 +62,8 @@ void UpdateThumbnail(const ThumbnailingContext& context, << context.score.ToString(); } -void ProcessCapturedBitmap(ThumbnailingContext* context, - ThumbnailingAlgorithm* algorithm, +void ProcessCapturedBitmap(scoped_refptr<ThumbnailingContext> context, + scoped_refptr<ThumbnailingAlgorithm> algorithm, bool succeeded, const SkBitmap& bitmap) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); @@ -115,11 +115,11 @@ void AsyncProcessThumbnail(content::WebContents* web_contents, copy_rect.size(), ui::GetScaleFactorForNativeView(view->GetNativeView()), ©_rect, - ©_size); + &context->requested_copy_size); render_widget_host->CopyFromBackingStore( copy_rect, - copy_size, + context->requested_copy_size, base::Bind(&ProcessCapturedBitmap, context, algorithm)); } @@ -241,4 +241,3 @@ void ThumbnailTabHelper::WidgetHidden(RenderWidgetHost* widget) { return; UpdateThumbnailIfNecessary(web_contents()); } - diff --git a/chrome/browser/thumbnails/thumbnailing_algorithm.h b/chrome/browser/thumbnails/thumbnailing_algorithm.h index d59d2cf..3211af2 100644 --- a/chrome/browser/thumbnails/thumbnailing_algorithm.h +++ b/chrome/browser/thumbnails/thumbnailing_algorithm.h @@ -41,7 +41,7 @@ class ThumbnailingAlgorithm // information on the source, including if and how it was clipped. // The function shall invoke |callback| once done, passing in fully populated // |context| along with resulting thumbnail bitmap. - virtual void ProcessBitmap(ThumbnailingContext* context, + virtual void ProcessBitmap(scoped_refptr<ThumbnailingContext> context, const ConsumerCallback& callback, const SkBitmap& bitmap) = 0; diff --git a/chrome/browser/thumbnails/thumbnailing_context.cc b/chrome/browser/thumbnails/thumbnailing_context.cc index 637df19..2457ebb 100644 --- a/chrome/browser/thumbnails/thumbnailing_context.cc +++ b/chrome/browser/thumbnails/thumbnailing_context.cc @@ -19,6 +19,10 @@ ThumbnailingContext::ThumbnailingContext(content::WebContents* web_contents, score.load_completed = !web_contents->IsLoading() && !load_interrupted; } +ThumbnailingContext::ThumbnailingContext() + : clip_result(CLIP_RESULT_UNPROCESSED) { +} + ThumbnailingContext::~ThumbnailingContext() { } diff --git a/chrome/browser/thumbnails/thumbnailing_context.h b/chrome/browser/thumbnails/thumbnailing_context.h index 4173560..877a2ea 100644 --- a/chrome/browser/thumbnails/thumbnailing_context.h +++ b/chrome/browser/thumbnails/thumbnailing_context.h @@ -10,6 +10,7 @@ #include "chrome/browser/thumbnails/thumbnail_service.h" #include "chrome/common/thumbnail_score.h" #include "content/public/browser/web_contents.h" +#include "ui/gfx/size.h" namespace thumbnails { @@ -28,6 +29,8 @@ enum ClipResult { CLIP_RESULT_TALLER_THAN_WIDE, // The source and destination aspect ratios are identical. CLIP_RESULT_NOT_CLIPPED, + // The source and destination are identical. + CLIP_RESULT_SOURCE_SAME_AS_TARGET, }; // Holds the information needed for processing a thumbnail. @@ -36,13 +39,21 @@ struct ThumbnailingContext : base::RefCountedThreadSafe<ThumbnailingContext> { ThumbnailService* receiving_service, bool load_interrupted); + // Create an instance for use with unit tests. + static ThumbnailingContext* CreateThumbnailingContextForTest() { + return new ThumbnailingContext(); + } + scoped_refptr<ThumbnailService> service; GURL url; ClipResult clip_result; + gfx::Size requested_copy_size; ThumbnailScore score; private: + ThumbnailingContext(); ~ThumbnailingContext(); + friend class base::RefCountedThreadSafe<ThumbnailingContext>; }; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 1b1e9e3..2426d39 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2231,6 +2231,8 @@ 'browser/three_d_api_observer.h', 'browser/thumbnails/content_analysis.cc', 'browser/thumbnails/content_analysis.h', + 'browser/thumbnails/content_based_thumbnailing_algorithm.cc', + 'browser/thumbnails/content_based_thumbnailing_algorithm.h', 'browser/thumbnails/simple_thumbnail_crop.cc', 'browser/thumbnails/simple_thumbnail_crop.h', 'browser/thumbnails/render_widget_snapshot_taker.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 13cb1d4..ee5adc7 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1266,6 +1266,7 @@ 'browser/themes/theme_service_unittest.cc', 'browser/themes/theme_syncable_service_unittest.cc', 'browser/thumbnails/content_analysis_unittest.cc', + 'browser/thumbnails/content_based_thumbnailing_algorithm_unittest.cc', 'browser/thumbnails/render_widget_snapshot_taker_unittest.cc', 'browser/thumbnails/simple_thumbnail_crop_unittest.cc', 'browser/thumbnails/thumbnail_service_unittest.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 969fda9..492424d 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -683,6 +683,10 @@ const char kEnableSyncSyncedNotifications[] = // Enables context menu for selecting groups of tabs. const char kEnableTabGroupsContextMenu[] = "enable-tab-groups-context-menu"; +// Enables fanciful thumbnail processing. Used with NTP for +// instant-extended-api, where thumbnails are generally smaller. +const char kEnableThumbnailRetargeting[] = "enable-thumbnail-retargeting"; + // Enables Chrome Translate for "alpha languages", that may have less-reliable // translation quality than supported languages. const char kEnableTranslateAlphaLanguages[] = diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 73d11ba..5ab084c 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -193,6 +193,7 @@ extern const char kEnableSuggestionsTabPage[]; extern const char kEnableSyncFavicons[]; extern const char kEnableSyncSyncedNotifications[]; extern const char kEnableTabGroupsContextMenu[]; +extern const char kEnableThumbnailRetargeting[]; extern const char kEnableTranslateAlphaLanguages[]; extern const char kEnableUnrestrictedSSL3Fallback[]; extern const char kEnableUserAlternateProtocolPorts[]; |