summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/thumbnails/content_analysis.cc6
-rw-r--r--chrome/browser/thumbnails/content_analysis.h6
-rw-r--r--chrome/browser/thumbnails/content_analysis_unittest.cc4
-rw-r--r--chrome/browser/thumbnails/content_based_thumbnailing_algorithm.cc237
-rw-r--r--chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h61
-rw-r--r--chrome/browser/thumbnails/content_based_thumbnailing_algorithm_unittest.cc168
-rw-r--r--chrome/browser/thumbnails/simple_thumbnail_crop.cc41
-rw-r--r--chrome/browser/thumbnails/simple_thumbnail_crop.h15
-rw-r--r--chrome/browser/thumbnails/simple_thumbnail_crop_unittest.cc34
-rw-r--r--chrome/browser/thumbnails/thumbnail_service_impl.cc23
-rw-r--r--chrome/browser/thumbnails/thumbnail_service_impl.h1
-rw-r--r--chrome/browser/thumbnails/thumbnail_tab_helper.cc9
-rw-r--r--chrome/browser/thumbnails/thumbnailing_algorithm.h2
-rw-r--r--chrome/browser/thumbnails/thumbnailing_context.cc4
-rw-r--r--chrome/browser/thumbnails/thumbnailing_context.h11
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
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()),
&copy_rect,
- &copy_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[];