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