Home | History | Annotate | Download | only in thumbnails
      1 // Copyright (c) 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/simple_thumbnail_crop.h"
      6 
      7 #include "base/metrics/histogram.h"
      8 #include "content/public/browser/browser_thread.h"
      9 #include "skia/ext/platform_canvas.h"
     10 #include "ui/gfx/color_utils.h"
     11 #include "ui/gfx/screen.h"
     12 #include "ui/gfx/scrollbar_size.h"
     13 #include "ui/gfx/size_conversions.h"
     14 #include "ui/gfx/skbitmap_operations.h"
     15 
     16 namespace {
     17 static const char kThumbnailHistogramName[] = "Thumbnail.ComputeMS";
     18 }
     19 
     20 namespace thumbnails {
     21 
     22 SimpleThumbnailCrop::SimpleThumbnailCrop(const gfx::Size& target_size)
     23     : target_size_(target_size) {
     24   DCHECK(!target_size.IsEmpty());
     25 }
     26 
     27 ClipResult SimpleThumbnailCrop::GetCanvasCopyInfo(
     28     const gfx::Size& source_size,
     29     ui::ScaleFactor scale_factor,
     30     gfx::Rect* clipping_rect,
     31     gfx::Size* target_size) const {
     32   DCHECK(!source_size.IsEmpty());
     33   ClipResult clip_result = thumbnails::CLIP_RESULT_NOT_CLIPPED;
     34   *clipping_rect = GetClippingRect(source_size, target_size_, &clip_result);
     35   *target_size = GetCopySizeForThumbnail(scale_factor, target_size_);
     36   return clip_result;
     37 }
     38 
     39 void SimpleThumbnailCrop::ProcessBitmap(
     40     scoped_refptr<ThumbnailingContext> context,
     41     const ConsumerCallback& callback,
     42     const SkBitmap& bitmap) {
     43   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     44   if (bitmap.isNull() || bitmap.empty())
     45     return;
     46 
     47   SkBitmap thumbnail = CreateThumbnail(
     48       bitmap,
     49       ComputeTargetSizeAtMaximumScale(target_size_),
     50       &context->clip_result);
     51 
     52   context->score.boring_score = CalculateBoringScore(thumbnail);
     53   context->score.good_clipping =
     54       (context->clip_result == CLIP_RESULT_WIDER_THAN_TALL ||
     55        context->clip_result == CLIP_RESULT_TALLER_THAN_WIDE ||
     56        context->clip_result == CLIP_RESULT_NOT_CLIPPED);
     57 
     58   callback.Run(*context.get(), thumbnail);
     59 }
     60 
     61 double SimpleThumbnailCrop::CalculateBoringScore(const SkBitmap& bitmap) {
     62   if (bitmap.isNull() || bitmap.empty())
     63     return 1.0;
     64   int histogram[256] = {0};
     65   color_utils::BuildLumaHistogram(bitmap, histogram);
     66 
     67   int color_count = *std::max_element(histogram, histogram + 256);
     68   int pixel_count = bitmap.width() * bitmap.height();
     69   return static_cast<double>(color_count) / pixel_count;
     70 }
     71 
     72 SkBitmap SimpleThumbnailCrop::GetClippedBitmap(const SkBitmap& bitmap,
     73                                                int desired_width,
     74                                                int desired_height,
     75                                                ClipResult* clip_result) {
     76   gfx::Rect clipping_rect =
     77       GetClippingRect(gfx::Size(bitmap.width(), bitmap.height()),
     78                       gfx::Size(desired_width, desired_height),
     79                       clip_result);
     80   SkIRect src_rect = { clipping_rect.x(), clipping_rect.y(),
     81                        clipping_rect.right(), clipping_rect.bottom() };
     82   SkBitmap clipped_bitmap;
     83   bitmap.extractSubset(&clipped_bitmap, src_rect);
     84   return clipped_bitmap;
     85 }
     86 
     87 // Returns the size used by RenderWidgetHost::CopyFromBackingStore.
     88 //
     89 // The size is calculated in such a way that the copied size in pixel becomes
     90 // equal to (f * kThumbnailWidth, f * kThumbnailHeight), where f is the scale
     91 // of ui::SCALE_FACTOR_200P. Since RenderWidgetHost::CopyFromBackingStore takes
     92 // the size in DIP, we need to adjust the size based on |view|'s device scale
     93 // factor in order to copy the pixels with the size above.
     94 //
     95 // The copied size was chosen for the following reasons.
     96 //
     97 // 1. When the scale factor of the primary monitor is ui::SCALE_FACTOR_200P, the
     98 // generated thumbnail size is (f * kThumbnailWidth, f * kThumbnailHeight).
     99 // In order to avoid degrading the image quality by magnification, the size
    100 // of the copied pixels should be equal to or larger than this thumbnail size.
    101 //
    102 // 2. RenderWidgetHost::CopyFromBackingStore can be costly especially when
    103 // it is necessary to read back the web contents image data from GPU. As the
    104 // cost is roughly propotional to the number of the copied pixels, the size of
    105 // the copied pixels should be as small as possible.
    106 //
    107 // When the scale factor of the primary monitor is ui::SCALE_FACTOR_100P,
    108 // we still copy the pixels with the same size as ui::SCALE_FACTOR_200P because
    109 // the resampling method used in RenderWidgetHost::CopyFromBackingStore is not
    110 // good enough for the resampled image to be used directly for the thumbnail
    111 // (http://crbug.com/141235). We assume this is not an issue in case of
    112 // ui::SCALE_FACTOR_200P because the high resolution thumbnail on high density
    113 // display alleviates the aliasing.
    114 // TODO(mazda): Copy the pixels with the smaller size in the case of
    115 // ui::SCALE_FACTOR_100P once the resampling method has been improved.
    116 // static
    117 gfx::Size SimpleThumbnailCrop::GetCopySizeForThumbnail(
    118     ui::ScaleFactor scale_factor,
    119     const gfx::Size& thumbnail_size) {
    120   gfx::Size copy_size(thumbnail_size);
    121   switch (scale_factor) {
    122     case ui::SCALE_FACTOR_100P:
    123       copy_size = gfx::ToFlooredSize(gfx::ScaleSize(
    124           copy_size, ui::GetScaleFactorScale(ui::SCALE_FACTOR_200P)));
    125       break;
    126     case ui::SCALE_FACTOR_200P:
    127       // Use the size as-is.
    128       break;
    129     default:
    130       DLOG(WARNING) << "Unsupported scale factor. Use the same copy size as "
    131                     << "ui::SCALE_FACTOR_100P";
    132       copy_size = gfx::ToFlooredSize(gfx::ScaleSize(
    133           copy_size, ui::GetMaxScaleFactor()));
    134       break;
    135   }
    136   return copy_size;
    137 }
    138 
    139 gfx::Rect SimpleThumbnailCrop::GetClippingRect(const gfx::Size& source_size,
    140                                                const gfx::Size& desired_size,
    141                                                ClipResult* clip_result) {
    142   DCHECK(clip_result);
    143 
    144   float desired_aspect =
    145       static_cast<float>(desired_size.width()) / desired_size.height();
    146 
    147   // Get the clipping rect so that we can preserve the aspect ratio while
    148   // filling the destination.
    149   gfx::Rect clipping_rect;
    150   if (source_size.width() < desired_size.width() ||
    151       source_size.height() < desired_size.height()) {
    152     // Source image is smaller: we clip the part of source image within the
    153     // dest rect, and then stretch it to fill the dest rect. We don't respect
    154     // the aspect ratio in this case.
    155     clipping_rect = gfx::Rect(desired_size);
    156     *clip_result = thumbnails::CLIP_RESULT_SOURCE_IS_SMALLER;
    157   } else {
    158     float src_aspect =
    159         static_cast<float>(source_size.width()) / source_size.height();
    160     if (src_aspect > desired_aspect) {
    161       // Wider than tall, clip horizontally: we center the smaller
    162       // thumbnail in the wider screen.
    163       int new_width = static_cast<int>(source_size.height() * desired_aspect);
    164       int x_offset = (source_size.width() - new_width) / 2;
    165       clipping_rect.SetRect(x_offset, 0, new_width, source_size.height());
    166       *clip_result = (src_aspect >= ThumbnailScore::kTooWideAspectRatio) ?
    167           thumbnails::CLIP_RESULT_MUCH_WIDER_THAN_TALL :
    168           thumbnails::CLIP_RESULT_WIDER_THAN_TALL;
    169     } else if (src_aspect < desired_aspect) {
    170       clipping_rect =
    171           gfx::Rect(source_size.width(), source_size.width() / desired_aspect);
    172       *clip_result = thumbnails::CLIP_RESULT_TALLER_THAN_WIDE;
    173     } else {
    174       clipping_rect = gfx::Rect(source_size);
    175       *clip_result = thumbnails::CLIP_RESULT_NOT_CLIPPED;
    176     }
    177   }
    178   return clipping_rect;
    179 }
    180 
    181 // static
    182 gfx::Size SimpleThumbnailCrop::ComputeTargetSizeAtMaximumScale(
    183     const gfx::Size& given_size) {
    184   // TODO(mazda|oshima): Update thumbnail when the max scale factor changes.
    185   // crbug.com/159157.
    186   float max_scale_factor =
    187       ui::GetScaleFactorScale(ui::GetMaxScaleFactor());
    188   return gfx::ToFlooredSize(gfx::ScaleSize(given_size, max_scale_factor));
    189 }
    190 
    191 SimpleThumbnailCrop::~SimpleThumbnailCrop() {
    192 }
    193 
    194 // Creates a downsampled thumbnail from the given bitmap.
    195 // store. The returned bitmap will be isNull if there was an error creating it.
    196 SkBitmap SimpleThumbnailCrop::CreateThumbnail(const SkBitmap& bitmap,
    197                                               const gfx::Size& desired_size,
    198                                               ClipResult* clip_result) {
    199   base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
    200 
    201   SkBitmap clipped_bitmap;
    202   if (*clip_result == thumbnails::CLIP_RESULT_UNPROCESSED) {
    203     // Clip the pixels that will commonly hold a scrollbar, which looks bad in
    204     // thumbnails.
    205     int scrollbar_size = gfx::scrollbar_size();
    206     SkIRect scrollbarless_rect =
    207         { 0, 0,
    208           std::max(1, bitmap.width() - scrollbar_size),
    209           std::max(1, bitmap.height() - scrollbar_size) };
    210     SkBitmap bmp;
    211     bitmap.extractSubset(&bmp, scrollbarless_rect);
    212 
    213     clipped_bitmap = GetClippedBitmap(
    214         bmp, desired_size.width(), desired_size.height(), clip_result);
    215   } else {
    216     clipped_bitmap = bitmap;
    217   }
    218 
    219   // Need to resize it to the size we want, so downsample until it's
    220   // close, and let the caller make it the exact size if desired.
    221   SkBitmap result = SkBitmapOperations::DownsampleByTwoUntilSize(
    222       clipped_bitmap, desired_size.width(), desired_size.height());
    223 #if !defined(USE_AURA)
    224   // This is a bit subtle. SkBitmaps are refcounted, but the magic
    225   // ones in PlatformCanvas can't be assigned to SkBitmap with proper
    226   // refcounting.  If the bitmap doesn't change, then the downsampler
    227   // will return the input bitmap, which will be the reference to the
    228   // weird PlatformCanvas one insetad of a regular one. To get a
    229   // regular refcounted bitmap, we need to copy it.
    230   //
    231   // On Aura, the PlatformCanvas is platform-independent and does not have
    232   // any native platform resources that can't be refounted, so this issue does
    233   // not occur.
    234   //
    235   // Note that GetClippedBitmap() does extractSubset() but it won't copy
    236   // the pixels, hence we check result size == clipped_bitmap size here.
    237   if (clipped_bitmap.width() == result.width() &&
    238       clipped_bitmap.height() == result.height())
    239     clipped_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config);
    240 #endif
    241 
    242   HISTOGRAM_TIMES(kThumbnailHistogramName,
    243                   base::TimeTicks::Now() - begin_compute_thumbnail);
    244   return result;
    245 }
    246 
    247 } // namespace thumbnails
    248