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