Home | History | Annotate | Download | only in thumbnails
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/thumbnails/content_based_thumbnailing_algorithm.h"
      6 
      7 #include "base/metrics/histogram.h"
      8 #include "base/threading/sequenced_worker_pool.h"
      9 #include "chrome/browser/thumbnails/content_analysis.h"
     10 #include "chrome/browser/thumbnails/simple_thumbnail_crop.h"
     11 #include "content/public/browser/browser_thread.h"
     12 #include "third_party/skia/include/core/SkBitmap.h"
     13 #include "ui/gfx/scrollbar_size.h"
     14 #include "ui/gfx/size_conversions.h"
     15 #include "ui/gfx/skbitmap_operations.h"
     16 #include "ui/gfx/skia_util.h"
     17 
     18 namespace {
     19 
     20 const char kThumbnailHistogramName[] = "Thumbnail.RetargetMS";
     21 const char kFailureHistogramName[] = "Thumbnail.FailedRetargetMS";
     22 const float kScoreBoostFromSuccessfulRetargeting = 1.1f;
     23 
     24 void CallbackInvocationAdapter(
     25     const thumbnails::ThumbnailingAlgorithm::ConsumerCallback& callback,
     26     scoped_refptr<thumbnails::ThumbnailingContext> context,
     27     const SkBitmap& source_bitmap) {
     28   callback.Run(*context.get(), source_bitmap);
     29 }
     30 
     31 }  // namespace
     32 
     33 namespace thumbnails {
     34 
     35 using content::BrowserThread;
     36 
     37 ContentBasedThumbnailingAlgorithm::ContentBasedThumbnailingAlgorithm(
     38     const gfx::Size& target_size)
     39     : target_size_(target_size) {
     40   DCHECK(!target_size.IsEmpty());
     41 }
     42 
     43 ClipResult ContentBasedThumbnailingAlgorithm::GetCanvasCopyInfo(
     44     const gfx::Size& source_size,
     45     ui::ScaleFactor scale_factor,
     46     gfx::Rect* clipping_rect,
     47     gfx::Size* target_size) const {
     48   DCHECK(!source_size.IsEmpty());
     49   gfx::Size target_thumbnail_size =
     50       SimpleThumbnailCrop::GetCopySizeForThumbnail(scale_factor, target_size_);
     51 
     52   ClipResult clipping_method = thumbnails::CLIP_RESULT_NOT_CLIPPED;
     53   *clipping_rect = GetClippingRect(
     54       source_size, target_thumbnail_size, target_size, &clipping_method);
     55   return clipping_method;
     56 }
     57 
     58 void ContentBasedThumbnailingAlgorithm::ProcessBitmap(
     59     scoped_refptr<ThumbnailingContext> context,
     60     const ConsumerCallback& callback,
     61     const SkBitmap& bitmap) {
     62   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     63   DCHECK(context.get());
     64   if (bitmap.isNull() || bitmap.empty())
     65     return;
     66 
     67   gfx::Size target_thumbnail_size =
     68       SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(target_size_);
     69 
     70   SkBitmap source_bitmap =
     71       PrepareSourceBitmap(bitmap, target_thumbnail_size, context.get());
     72 
     73   // If the source is same (or smaller) than the target, just return it as
     74   // the final result. Otherwise, send the shrinking task to the blocking
     75   // thread pool.
     76   if (source_bitmap.width() <= target_thumbnail_size.width() ||
     77       source_bitmap.height() <= target_thumbnail_size.height()) {
     78     context->score.boring_score =
     79         SimpleThumbnailCrop::CalculateBoringScore(source_bitmap);
     80     context->score.good_clipping =
     81         (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
     82          context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE ||
     83          context->clip_result == CLIP_RESULT_NOT_CLIPPED ||
     84          context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET);
     85 
     86     callback.Run(*context.get(), source_bitmap);
     87     return;
     88   }
     89 
     90   if (!BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
     91           FROM_HERE,
     92           base::Bind(&CreateRetargetedThumbnail,
     93                      source_bitmap,
     94                      target_thumbnail_size,
     95                      context,
     96                      callback),
     97           base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)) {
     98     LOG(WARNING) << "PostSequencedWorkerTask failed. The thumbnail for "
     99                  << context->url << " will not be created.";
    100   }
    101 }
    102 
    103 // static
    104 SkBitmap ContentBasedThumbnailingAlgorithm::PrepareSourceBitmap(
    105     const SkBitmap& received_bitmap,
    106     const gfx::Size& thumbnail_size,
    107     ThumbnailingContext* context) {
    108   gfx::Size resize_target;
    109   SkBitmap clipped_bitmap;
    110   if (context->clip_result == CLIP_RESULT_UNPROCESSED) {
    111     // This case will require extracting a fragment from the retrieved bitmap.
    112     int scrollbar_size = gfx::scrollbar_size();
    113     gfx::Size scrollbarless(
    114         std::max(1, received_bitmap.width() - scrollbar_size),
    115         std::max(1, received_bitmap.height() - scrollbar_size));
    116 
    117     gfx::Rect clipping_rect = GetClippingRect(
    118         scrollbarless,
    119         thumbnail_size,
    120         &resize_target,
    121         &context->clip_result);
    122 
    123     received_bitmap.extractSubset(&clipped_bitmap,
    124                                   gfx::RectToSkIRect(clipping_rect));
    125   } else {
    126     // This means that the source bitmap has been requested and at least
    127     // clipped. Upstream code in same cases seems opportunistic and it may
    128     // not perform actual resizing if copying with resize is not supported.
    129     // In this case we will resize to the orignally requested copy size.
    130     resize_target = context->requested_copy_size;
    131     clipped_bitmap = received_bitmap;
    132   }
    133 
    134   SkBitmap result_bitmap = SkBitmapOperations::DownsampleByTwoUntilSize(
    135       clipped_bitmap, resize_target.width(), resize_target.height());
    136 #if !defined(USE_AURA)
    137   // If the bitmap has not been indeed resized, it has to be copied. In that
    138   // case resampler simply returns a reference to the original bitmap, sitting
    139   // in PlatformCanvas. One does not simply assign these 'magic' bitmaps to
    140   // SkBitmap. They cannot be refcounted.
    141   //
    142   // With Aura, this does not happen since PlatformCanvas is platform
    143   // idependent.
    144   if (clipped_bitmap.width() == result_bitmap.width() &&
    145       clipped_bitmap.height() == result_bitmap.height()) {
    146     clipped_bitmap.copyTo(&result_bitmap, kN32_SkColorType);
    147   }
    148 #endif
    149 
    150   return result_bitmap;
    151 }
    152 
    153 // static
    154 void ContentBasedThumbnailingAlgorithm::CreateRetargetedThumbnail(
    155     const SkBitmap& source_bitmap,
    156     const gfx::Size& thumbnail_size,
    157     scoped_refptr<ThumbnailingContext> context,
    158     const ConsumerCallback& callback) {
    159   base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
    160   float kernel_sigma =
    161       context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET ? 5.0f : 2.5f;
    162   SkBitmap thumbnail = thumbnailing_utils::CreateRetargetedThumbnailImage(
    163       source_bitmap, thumbnail_size, kernel_sigma);
    164   bool processing_failed = thumbnail.empty();
    165   if (processing_failed) {
    166     // Log and apply the method very much like in SimpleThumbnailCrop (except
    167     // that some clipping and copying is not required).
    168     LOG(WARNING) << "CreateRetargetedThumbnailImage failed. "
    169                  << "The thumbnail for " << context->url
    170                  << " will be created the old-fashioned way.";
    171 
    172     ClipResult clip_result;
    173     gfx::Rect clipping_rect = SimpleThumbnailCrop::GetClippingRect(
    174         gfx::Size(source_bitmap.width(), source_bitmap.height()),
    175         thumbnail_size,
    176         &clip_result);
    177     source_bitmap.extractSubset(&thumbnail, gfx::RectToSkIRect(clipping_rect));
    178     thumbnail = SkBitmapOperations::DownsampleByTwoUntilSize(
    179         thumbnail, thumbnail_size.width(), thumbnail_size.height());
    180   }
    181 
    182   if (processing_failed) {
    183     LOCAL_HISTOGRAM_TIMES(kFailureHistogramName,
    184                           base::TimeTicks::Now() - begin_compute_thumbnail);
    185   } else {
    186     LOCAL_HISTOGRAM_TIMES(kThumbnailHistogramName,
    187                           base::TimeTicks::Now() - begin_compute_thumbnail);
    188   }
    189   context->score.boring_score =
    190         SimpleThumbnailCrop::CalculateBoringScore(source_bitmap);
    191   if (!processing_failed)
    192     context->score.boring_score *= kScoreBoostFromSuccessfulRetargeting;
    193   context->score.good_clipping =
    194       (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
    195        context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE ||
    196        context->clip_result == CLIP_RESULT_NOT_CLIPPED ||
    197        context->clip_result == CLIP_RESULT_SOURCE_SAME_AS_TARGET);
    198   // Post the result (the bitmap) back to the callback.
    199   BrowserThread::PostTask(
    200       BrowserThread::UI,
    201       FROM_HERE,
    202       base::Bind(&CallbackInvocationAdapter, callback, context, thumbnail));
    203 }
    204 
    205 ContentBasedThumbnailingAlgorithm::~ContentBasedThumbnailingAlgorithm() {
    206 }
    207 
    208 //  static
    209 gfx::Rect ContentBasedThumbnailingAlgorithm::GetClippingRect(
    210     const gfx::Size& source_size,
    211     const gfx::Size& thumbnail_size,
    212     gfx::Size* target_size,
    213     ClipResult* clip_result) {
    214   // Compute and return the clipping rectagle of the source image and the
    215   // size of the target bitmap which will be used for the further processing.
    216   // This function in 'general case' is trivial (don't clip, halve the source)
    217   // but it is needed for handling edge cases (source smaller than the target
    218   // thumbnail size).
    219   DCHECK(target_size);
    220   DCHECK(clip_result);
    221   gfx::Rect clipping_rect;
    222   if (source_size.width() < thumbnail_size.width() ||
    223       source_size.height() < thumbnail_size.height()) {
    224     clipping_rect = gfx::Rect(thumbnail_size);
    225     *target_size = thumbnail_size;
    226     *clip_result = CLIP_RESULT_SOURCE_IS_SMALLER;
    227   } else if (source_size.width() < thumbnail_size.width() * 4 ||
    228              source_size.height() < thumbnail_size.height() * 4) {
    229     clipping_rect = gfx::Rect(source_size);
    230     *target_size = source_size;
    231     *clip_result = CLIP_RESULT_SOURCE_SAME_AS_TARGET;
    232   } else {
    233     clipping_rect = gfx::Rect(source_size);
    234     target_size->SetSize(source_size.width() / 2, source_size.height() / 2);
    235     *clip_result = CLIP_RESULT_NOT_CLIPPED;
    236   }
    237 
    238   return clipping_rect;
    239 }
    240 
    241 }  // namespace thumbnails
    242