OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/tab_contents/thumbnail_generator.h" | 5 #include "chrome/browser/tab_contents/thumbnail_generator.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <map> | 8 #include <map> |
9 | 9 |
10 #include "base/metrics/histogram.h" | 10 #include "base/metrics/histogram.h" |
11 #include "base/scoped_ptr.h" | 11 #include "base/scoped_ptr.h" |
12 #include "base/time.h" | 12 #include "base/time.h" |
13 #include "build/build_config.h" | 13 #include "build/build_config.h" |
| 14 #include "chrome/browser/browser_process.h" |
| 15 #include "chrome/browser/history/top_sites.h" |
| 16 #include "chrome/browser/profiles/profile.h" |
14 #include "chrome/browser/renderer_host/backing_store.h" | 17 #include "chrome/browser/renderer_host/backing_store.h" |
15 #include "chrome/browser/renderer_host/render_process_host.h" | 18 #include "chrome/browser/renderer_host/render_process_host.h" |
16 #include "chrome/browser/renderer_host/render_view_host.h" | 19 #include "chrome/browser/renderer_host/render_view_host.h" |
17 #include "chrome/browser/tab_contents/tab_contents.h" | 20 #include "chrome/browser/tab_contents/tab_contents.h" |
18 #include "chrome/common/notification_service.h" | 21 #include "chrome/common/notification_service.h" |
19 #include "chrome/common/property_bag.h" | 22 #include "chrome/common/property_bag.h" |
| 23 #include "chrome/common/thumbnail_score.h" |
| 24 #include "gfx/color_utils.h" |
20 #include "gfx/rect.h" | 25 #include "gfx/rect.h" |
21 #include "gfx/skbitmap_operations.h" | 26 #include "gfx/skbitmap_operations.h" |
| 27 #include "googleurl/src/gurl.h" |
| 28 #include "skia/ext/bitmap_platform_device.h" |
| 29 #include "skia/ext/image_operations.h" |
22 #include "skia/ext/platform_canvas.h" | 30 #include "skia/ext/platform_canvas.h" |
23 #include "third_party/skia/include/core/SkBitmap.h" | 31 #include "third_party/skia/include/core/SkBitmap.h" |
24 | 32 |
25 #if defined(OS_WIN) | 33 #if defined(OS_WIN) |
26 #include "app/win/win_util.h" | 34 #include "app/win/win_util.h" |
27 #endif | 35 #endif |
28 | 36 |
29 // Overview | 37 // Overview |
30 // -------- | 38 // -------- |
31 // This class provides current thumbnails for tabs. The simplest operation is | 39 // This class provides current thumbnails for tabs. The simplest operation is |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 if (wt) | 100 if (wt) |
93 return wt; | 101 return wt; |
94 | 102 |
95 GetThumbnailAccessor()->SetProperty(host->property_bag(), | 103 GetThumbnailAccessor()->SetProperty(host->property_bag(), |
96 WidgetThumbnail()); | 104 WidgetThumbnail()); |
97 return GetThumbnailAccessor()->GetProperty(host->property_bag()); | 105 return GetThumbnailAccessor()->GetProperty(host->property_bag()); |
98 } | 106 } |
99 | 107 |
100 // Creates a downsampled thumbnail for the given backing store. The returned | 108 // Creates a downsampled thumbnail for the given backing store. The returned |
101 // bitmap will be isNull if there was an error creating it. | 109 // bitmap will be isNull if there was an error creating it. |
102 SkBitmap GetBitmapForBackingStore(BackingStore* backing_store, | 110 SkBitmap GetBitmapForBackingStore( |
103 int desired_width, | 111 BackingStore* backing_store, |
104 int desired_height) { | 112 int desired_width, |
| 113 int desired_height, |
| 114 int options, |
| 115 ThumbnailGenerator::ClipResult* clip_result) { |
105 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now(); | 116 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now(); |
106 | 117 |
107 SkBitmap result; | 118 SkBitmap result; |
108 | 119 |
109 // Get the bitmap as a Skia object so we can resample it. This is a large | 120 // Get the bitmap as a Skia object so we can resample it. This is a large |
110 // allocation and we can tolerate failure here, so give up if the allocation | 121 // allocation and we can tolerate failure here, so give up if the allocation |
111 // fails. | 122 // fails. |
112 skia::PlatformCanvas temp_canvas; | 123 skia::PlatformCanvas temp_canvas; |
113 if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()), | 124 if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()), |
114 &temp_canvas)) | 125 &temp_canvas)) |
115 return result; | 126 return result; |
116 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false); | 127 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false); |
117 | 128 |
118 // Need to resize it to the size we want, so downsample until it's | 129 // Check if a clipped thumbnail is requested. |
119 // close, and let the caller make it the exact size if desired. | 130 if (options & ThumbnailGenerator::kClippedThumbnail) { |
120 result = SkBitmapOperations::DownsampleByTwoUntilSize( | 131 SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap( |
121 bmp, desired_width, desired_height); | 132 bmp, desired_width, desired_height, clip_result); |
122 | 133 |
123 // This is a bit subtle. SkBitmaps are refcounted, but the magic | 134 // Need to resize it to the size we want, so downsample until it's |
124 // ones in PlatformCanvas can't be assigned to SkBitmap with proper | 135 // close, and let the caller make it the exact size if desired. |
125 // refcounting. If the bitmap doesn't change, then the downsampler | 136 result = SkBitmapOperations::DownsampleByTwoUntilSize( |
126 // will return the input bitmap, which will be the reference to the | 137 clipped_bitmap, desired_width, desired_height); |
127 // weird PlatformCanvas one insetad of a regular one. To get a | 138 } else { |
128 // regular refcounted bitmap, we need to copy it. | 139 // Need to resize it to the size we want, so downsample until it's |
129 if (bmp.width() == result.width() && | 140 // close, and let the caller make it the exact size if desired. |
130 bmp.height() == result.height()) | 141 result = SkBitmapOperations::DownsampleByTwoUntilSize( |
131 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config); | 142 bmp, desired_width, desired_height); |
| 143 |
| 144 // This is a bit subtle. SkBitmaps are refcounted, but the magic |
| 145 // ones in PlatformCanvas can't be assigned to SkBitmap with proper |
| 146 // refcounting. If the bitmap doesn't change, then the downsampler |
| 147 // will return the input bitmap, which will be the reference to the |
| 148 // weird PlatformCanvas one insetad of a regular one. To get a |
| 149 // regular refcounted bitmap, we need to copy it. |
| 150 if (bmp.width() == result.width() && |
| 151 bmp.height() == result.height()) |
| 152 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config); |
| 153 } |
132 | 154 |
133 HISTOGRAM_TIMES(kThumbnailHistogramName, | 155 HISTOGRAM_TIMES(kThumbnailHistogramName, |
134 base::TimeTicks::Now() - begin_compute_thumbnail); | 156 base::TimeTicks::Now() - begin_compute_thumbnail); |
135 return result; | 157 return result; |
136 } | 158 } |
137 | 159 |
138 } // namespace | 160 } // namespace |
139 | 161 |
140 struct ThumbnailGenerator::AsyncRequestInfo { | 162 struct ThumbnailGenerator::AsyncRequestInfo { |
141 scoped_ptr<ThumbnailReadyCallback> callback; | 163 scoped_ptr<ThumbnailReadyCallback> callback; |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
216 ThumbnailReadyCallback* callback, | 238 ThumbnailReadyCallback* callback, |
217 gfx::Size page_size, | 239 gfx::Size page_size, |
218 gfx::Size desired_size) { | 240 gfx::Size desired_size) { |
219 if (prefer_backing_store) { | 241 if (prefer_backing_store) { |
220 BackingStore* backing_store = renderer->GetBackingStore(false); | 242 BackingStore* backing_store = renderer->GetBackingStore(false); |
221 if (backing_store) { | 243 if (backing_store) { |
222 // We were able to find a non-null backing store for this renderer, so | 244 // We were able to find a non-null backing store for this renderer, so |
223 // we'll go with it. | 245 // we'll go with it. |
224 SkBitmap first_try = GetBitmapForBackingStore(backing_store, | 246 SkBitmap first_try = GetBitmapForBackingStore(backing_store, |
225 desired_size.width(), | 247 desired_size.width(), |
226 desired_size.height()); | 248 desired_size.height(), |
| 249 kNoOptions, |
| 250 NULL); |
227 callback->Run(first_try); | 251 callback->Run(first_try); |
228 | 252 |
229 delete callback; | 253 delete callback; |
230 return; | 254 return; |
231 } | 255 } |
232 // Now, if the backing store didn't exist, we will still try and | 256 // Now, if the backing store didn't exist, we will still try and |
233 // render asynchronously. | 257 // render asynchronously. |
234 } | 258 } |
235 | 259 |
236 // We are going to render the thumbnail asynchronously now, so keep | 260 // We are going to render the thumbnail asynchronously now, so keep |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 NOTREACHED() << "Callback already registered?"; | 292 NOTREACHED() << "Callback already registered?"; |
269 return; | 293 return; |
270 } | 294 } |
271 | 295 |
272 renderer->PaintAtSize( | 296 renderer->PaintAtSize( |
273 renderer_dib_handle, sequence_num, page_size, desired_size); | 297 renderer_dib_handle, sequence_num, page_size, desired_size); |
274 } | 298 } |
275 | 299 |
276 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer( | 300 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer( |
277 RenderWidgetHost* renderer) const { | 301 RenderWidgetHost* renderer) const { |
| 302 return GetThumbnailForRendererWithOptions(renderer, kNoOptions, NULL); |
| 303 } |
| 304 |
| 305 SkBitmap ThumbnailGenerator::GetThumbnailForRendererWithOptions( |
| 306 RenderWidgetHost* renderer, |
| 307 int options, |
| 308 ClipResult* clip_result) const { |
278 WidgetThumbnail* wt = GetDataForHost(renderer); | 309 WidgetThumbnail* wt = GetDataForHost(renderer); |
279 | 310 |
280 BackingStore* backing_store = renderer->GetBackingStore(false); | 311 BackingStore* backing_store = renderer->GetBackingStore(false); |
281 if (!backing_store) { | 312 if (!backing_store) { |
282 // When we have no backing store, there's no choice in what to use. We | 313 // When we have no backing store, there's no choice in what to use. We |
283 // have to return either the existing thumbnail or the empty one if there | 314 // have to return either the existing thumbnail or the empty one if there |
284 // isn't a saved one. | 315 // isn't a saved one. |
285 return wt->thumbnail; | 316 return wt->thumbnail; |
286 } | 317 } |
287 | 318 |
288 // Now that we have a backing store, we have a choice to use it to make | 319 // Now that we have a backing store, we have a choice to use it to make |
289 // a new thumbnail, or use a previously stashed one if we have it. | 320 // a new thumbnail, or use a previously stashed one if we have it. |
290 // | 321 // |
291 // Return the previously-computed one if we have it and it hasn't expired. | 322 // Return the previously-computed one if we have it and it hasn't expired. |
292 if (!wt->thumbnail.isNull() && | 323 if (!wt->thumbnail.isNull() && |
293 (no_timeout_ || | 324 (no_timeout_ || |
294 base::TimeTicks::Now() - | 325 base::TimeTicks::Now() - |
295 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown)) | 326 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown)) |
296 return wt->thumbnail; | 327 return wt->thumbnail; |
297 | 328 |
298 // Save this thumbnail in case we need to use it again soon. It will be | 329 // Save this thumbnail in case we need to use it again soon. It will be |
299 // invalidated on the next paint. | 330 // invalidated on the next paint. |
300 wt->thumbnail = GetBitmapForBackingStore(backing_store, | 331 wt->thumbnail = GetBitmapForBackingStore(backing_store, |
301 kThumbnailWidth, | 332 kThumbnailWidth, |
302 kThumbnailHeight); | 333 kThumbnailHeight, |
| 334 options, |
| 335 clip_result); |
303 return wt->thumbnail; | 336 return wt->thumbnail; |
304 } | 337 } |
305 | 338 |
306 void ThumbnailGenerator::WidgetWillDestroyBackingStore( | 339 void ThumbnailGenerator::WidgetWillDestroyBackingStore( |
307 RenderWidgetHost* widget, | 340 RenderWidgetHost* widget, |
308 BackingStore* backing_store) { | 341 BackingStore* backing_store) { |
309 // Since the backing store is going away, we need to save it as a thumbnail. | 342 // Since the backing store is going away, we need to save it as a thumbnail. |
310 WidgetThumbnail* wt = GetDataForHost(widget); | 343 WidgetThumbnail* wt = GetDataForHost(widget); |
311 | 344 |
312 // If there is already a thumbnail on the RWH that's visible, it means that | 345 // If there is already a thumbnail on the RWH that's visible, it means that |
313 // not enough time has elapsed since being shown, and we can ignore generating | 346 // not enough time has elapsed since being shown, and we can ignore generating |
314 // a new one. | 347 // a new one. |
315 if (!wt->thumbnail.isNull()) | 348 if (!wt->thumbnail.isNull()) |
316 return; | 349 return; |
317 | 350 |
318 // Save a scaled-down image of the page in case we're asked for the thumbnail | 351 // Save a scaled-down image of the page in case we're asked for the thumbnail |
319 // when there is no RenderViewHost. If this fails, we don't want to overwrite | 352 // when there is no RenderViewHost. If this fails, we don't want to overwrite |
320 // an existing thumbnail. | 353 // an existing thumbnail. |
321 SkBitmap new_thumbnail = GetBitmapForBackingStore(backing_store, | 354 SkBitmap new_thumbnail = GetBitmapForBackingStore(backing_store, |
322 kThumbnailWidth, | 355 kThumbnailWidth, |
323 kThumbnailHeight); | 356 kThumbnailHeight, |
| 357 kNoOptions, |
| 358 NULL); |
324 if (!new_thumbnail.isNull()) | 359 if (!new_thumbnail.isNull()) |
325 wt->thumbnail = new_thumbnail; | 360 wt->thumbnail = new_thumbnail; |
326 } | 361 } |
327 | 362 |
328 void ThumbnailGenerator::WidgetDidUpdateBackingStore(RenderWidgetHost* widget) { | 363 void ThumbnailGenerator::WidgetDidUpdateBackingStore(RenderWidgetHost* widget) { |
329 // Notify interested parties that they might want to update their | 364 // Notify interested parties that they might want to update their |
330 // snapshots. | 365 // snapshots. |
331 NotificationService::current()->Notify( | 366 NotificationService::current()->Notify( |
332 NotificationType::THUMBNAIL_GENERATOR_SNAPSHOT_CHANGED, | 367 NotificationType::THUMBNAIL_GENERATOR_SNAPSHOT_CHANGED, |
333 Source<ThumbnailGenerator>(this), | 368 Source<ThumbnailGenerator>(this), |
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
527 &ThumbnailGenerator::ShownDelayHandler); | 562 &ThumbnailGenerator::ShownDelayHandler); |
528 } | 563 } |
529 } | 564 } |
530 | 565 |
531 void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) { | 566 void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) { |
532 std::vector<RenderWidgetHost*>::iterator found = | 567 std::vector<RenderWidgetHost*>::iterator found = |
533 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget); | 568 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget); |
534 if (found != shown_hosts_.end()) | 569 if (found != shown_hosts_.end()) |
535 shown_hosts_.erase(found); | 570 shown_hosts_.erase(found); |
536 } | 571 } |
| 572 |
| 573 double ThumbnailGenerator::CalculateBoringScore(SkBitmap* bitmap) { |
| 574 if (bitmap->isNull() || bitmap->empty()) |
| 575 return 1.0; |
| 576 int histogram[256] = {0}; |
| 577 color_utils::BuildLumaHistogram(bitmap, histogram); |
| 578 |
| 579 int color_count = *std::max_element(histogram, histogram + 256); |
| 580 int pixel_count = bitmap->width() * bitmap->height(); |
| 581 return static_cast<double>(color_count) / pixel_count; |
| 582 } |
| 583 |
| 584 SkBitmap ThumbnailGenerator::GetClippedBitmap(const SkBitmap& bitmap, |
| 585 int desired_width, |
| 586 int desired_height, |
| 587 ClipResult* clip_result) { |
| 588 const SkRect dest_rect = { 0, 0, |
| 589 SkIntToScalar(desired_width), |
| 590 SkIntToScalar(desired_height) }; |
| 591 const float dest_aspect = dest_rect.width() / dest_rect.height(); |
| 592 |
| 593 // Get the src rect so that we can preserve the aspect ratio while filling |
| 594 // the destination. |
| 595 SkIRect src_rect; |
| 596 if (bitmap.width() < dest_rect.width() || |
| 597 bitmap.height() < dest_rect.height()) { |
| 598 // Source image is smaller: we clip the part of source image within the |
| 599 // dest rect, and then stretch it to fill the dest rect. We don't respect |
| 600 // the aspect ratio in this case. |
| 601 src_rect.set(0, 0, static_cast<S16CPU>(dest_rect.width()), |
| 602 static_cast<S16CPU>(dest_rect.height())); |
| 603 if (clip_result) |
| 604 *clip_result = ThumbnailGenerator::kSourceIsSmaller; |
| 605 } else { |
| 606 const float src_aspect = |
| 607 static_cast<float>(bitmap.width()) / bitmap.height(); |
| 608 if (src_aspect > dest_aspect) { |
| 609 // Wider than tall, clip horizontally: we center the smaller |
| 610 // thumbnail in the wider screen. |
| 611 S16CPU new_width = static_cast<S16CPU>(bitmap.height() * dest_aspect); |
| 612 S16CPU x_offset = (bitmap.width() - new_width) / 2; |
| 613 src_rect.set(x_offset, 0, new_width + x_offset, bitmap.height()); |
| 614 if (clip_result) |
| 615 *clip_result = ThumbnailGenerator::kWiderThanTall; |
| 616 } else if (src_aspect < dest_aspect) { |
| 617 src_rect.set(0, 0, bitmap.width(), |
| 618 static_cast<S16CPU>(bitmap.width() / dest_aspect)); |
| 619 if (clip_result) |
| 620 *clip_result = ThumbnailGenerator::kTallerThanWide; |
| 621 } else { |
| 622 src_rect.set(0, 0, bitmap.width(), bitmap.height()); |
| 623 if (clip_result) |
| 624 *clip_result = ThumbnailGenerator::kNotClipped; |
| 625 } |
| 626 } |
| 627 |
| 628 SkBitmap clipped_bitmap; |
| 629 bitmap.extractSubset(&clipped_bitmap, src_rect); |
| 630 return clipped_bitmap; |
| 631 } |
| 632 |
| 633 void ThumbnailGenerator::UpdateThumbnailIfNecessary( |
| 634 TabContents* tab_contents, const GURL& url) { |
| 635 if (tab_contents->profile()->IsOffTheRecord() || |
| 636 (url.SchemeIs("chrome") && url.host() == "newtab")) |
| 637 return; |
| 638 // TODO(satorux): Add more conditions here to avoid unnecessary |
| 639 // thumbnail generation. |
| 640 |
| 641 ThumbnailGenerator* generator = g_browser_process->GetThumbnailGenerator(); |
| 642 const int options = ThumbnailGenerator::kClippedThumbnail; |
| 643 ThumbnailGenerator::ClipResult clip_result = ThumbnailGenerator::kNotClipped; |
| 644 SkBitmap thumbnail = generator->GetThumbnailForRendererWithOptions( |
| 645 tab_contents->render_view_host(), options, &clip_result); |
| 646 // Failed to generate a thumbnail. Maybe the tab is in the background? |
| 647 if (thumbnail.isNull()) |
| 648 return; |
| 649 |
| 650 // Compute the thumbnail score. |
| 651 ThumbnailScore score; |
| 652 score.at_top = |
| 653 (tab_contents->render_view_host()->last_scroll_offset().height() == 0); |
| 654 score.boring_score = ThumbnailGenerator::CalculateBoringScore(&thumbnail); |
| 655 score.good_clipping = |
| 656 (clip_result == ThumbnailGenerator::kTallerThanWide || |
| 657 clip_result == ThumbnailGenerator::kNotClipped); |
| 658 |
| 659 history::TopSites* top_sites = tab_contents->profile()->GetTopSites(); |
| 660 top_sites->SetPageThumbnail(url, thumbnail, score); |
| 661 VLOG(1) << "Thumbnail taken for " << url |
| 662 << ", at_top: " << score.at_top |
| 663 << ", boring_score: " << score.boring_score |
| 664 << ", good_clipping: " << score.good_clipping; |
| 665 } |
OLD | NEW |