OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/bind.h" | 10 #include "base/bind.h" |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
58 // service generates. | 58 // service generates. |
59 | 59 |
60 using content::RenderViewHost; | 60 using content::RenderViewHost; |
61 using content::RenderWidgetHost; | 61 using content::RenderWidgetHost; |
62 using content::WebContents; | 62 using content::WebContents; |
63 | 63 |
64 namespace { | 64 namespace { |
65 | 65 |
66 static const int kThumbnailWidth = 212; | 66 static const int kThumbnailWidth = 212; |
67 static const int kThumbnailHeight = 132; | 67 static const int kThumbnailHeight = 132; |
68 static const int kScrollbarThickness = 15; | |
brettw
2012/08/05 05:38:39
I wonder, can we get the actual value from the the
mazda
2012/08/06 17:22:16
I found a function to get scrollbar size in ui/gfx
| |
68 | 69 |
69 // This factor determines the number of pixels to be copied by | 70 // This factor determines the number of pixels to be copied by |
70 // RenderWidgetHost::CopyFromBackingStore for generating thumbnail. | 71 // RenderWidgetHost::CopyFromBackingStore for generating thumbnail. |
71 // Smaller scale is good for performance, but too small scale causes aliasing | 72 // Smaller scale is good for performance, but too small scale causes aliasing |
72 // because the resampling method is not good enough to retain the image quality. | 73 // because the resampling method is not good enough to retain the image quality. |
73 // TODO(mazda): the Improve resampling method and use a smaller scale | 74 // TODO(mazda): the Improve resampling method and use a smaller scale |
74 // (http://crbug.com/118571). | 75 // (http://crbug.com/118571). |
75 static const double kThumbnailCopyScale = 2.0; | 76 static const double kThumbnailCopyScale = 2.0; |
76 | 77 |
77 static const char kThumbnailHistogramName[] = "Thumbnail.ComputeMS"; | 78 static const char kThumbnailHistogramName[] = "Thumbnail.ComputeMS"; |
78 | 79 |
79 // Calculates the size used by RenderWidgetHost::CopyFromBackingStore. | 80 // Calculates the size used by RenderWidgetHost::CopyFromBackingStore. |
80 // The result is computed as the minimum size that satisfies the following | 81 // The result is computed as the minimum size that satisfies the following |
81 // conditions. | 82 // conditions. |
82 // result.width : result.height == view_size.width : view_size.height | 83 // result.width : result.height == view_size.width : view_size.height |
83 // result.width >= kThumbnailCopyScale * desired_size.width | 84 // result.width >= kThumbnailCopyScale * desired_size.width |
84 // result.height >= kThumbnailCopyScale * desired_size.height | 85 // result.height >= kThumbnailCopyScale * desired_size.height |
85 gfx::Size GetCopySizeForThumbnail(const gfx::Size& view_size, | 86 gfx::Size GetCopySizeForThumbnail(const gfx::Size& view_size, |
86 const gfx::Size& desired_size) { | 87 const gfx::Size& desired_size) { |
87 const double scale = kThumbnailCopyScale * | 88 const double scale = kThumbnailCopyScale * |
88 std::max(static_cast<double>(desired_size.width()) / view_size.width(), | 89 std::max(static_cast<double>(desired_size.width()) / view_size.width(), |
89 static_cast<double>(desired_size.height()) / view_size.height()); | 90 static_cast<double>(desired_size.height()) / view_size.height()); |
90 return gfx::Size(static_cast<int>(scale * view_size.width()), | 91 return gfx::Size(static_cast<int>(scale * view_size.width()), |
91 static_cast<int>(scale * view_size.height())); | 92 static_cast<int>(scale * view_size.height())); |
92 } | 93 } |
93 | 94 |
95 // Returns the clipping rectangle that is used for creating a thumbnail with | |
96 // the size of |desired_size| from the bitmap with the size of |source_size|. | |
97 // The type of clipping that needs to be done is assigned to |clip_result|. | |
98 gfx::Rect GetClippingRect(const gfx::Size& source_size, | |
99 const gfx::Size& desired_size, | |
100 ThumbnailGenerator::ClipResult* clip_result) { | |
101 DCHECK(clip_result); | |
102 | |
103 const float desired_aspect = | |
brettw
2012/08/05 05:38:39
Optional: IMO const in this case just adds noise (
mazda
2012/08/06 17:22:16
Removed const.
| |
104 static_cast<float>(desired_size.width()) / desired_size.height(); | |
105 | |
106 // Get the clipping rect so that we can preserve the aspect ratio while | |
107 // filling the destination. | |
108 gfx::Rect clipping_rect; | |
109 if (source_size.width() < desired_size.width() || | |
110 source_size.height() < desired_size.height()) { | |
111 // Source image is smaller: we clip the part of source image within the | |
112 // dest rect, and then stretch it to fill the dest rect. We don't respect | |
113 // the aspect ratio in this case. | |
114 clipping_rect = gfx::Rect(desired_size); | |
115 *clip_result = ThumbnailGenerator::kSourceIsSmaller; | |
116 } else { | |
117 const float src_aspect = | |
118 static_cast<float>(source_size.width()) / source_size.height(); | |
119 if (src_aspect > desired_aspect) { | |
120 // Wider than tall, clip horizontally: we center the smaller | |
121 // thumbnail in the wider screen. | |
122 int new_width = static_cast<int>(source_size.height() * desired_aspect); | |
123 int x_offset = (source_size.width() - new_width) / 2; | |
124 clipping_rect.SetRect(x_offset, 0, new_width, source_size.height()); | |
125 *clip_result = (src_aspect >= ThumbnailScore::kTooWideAspectRatio) ? | |
126 ThumbnailGenerator::kTooWiderThanTall : | |
127 ThumbnailGenerator::kWiderThanTall; | |
128 } else if (src_aspect < desired_aspect) { | |
129 clipping_rect = | |
130 gfx::Rect(source_size.width(), source_size.width() / desired_aspect); | |
131 *clip_result = ThumbnailGenerator::kTallerThanWide; | |
132 } else { | |
133 clipping_rect = gfx::Rect(source_size); | |
134 *clip_result = ThumbnailGenerator::kNotClipped; | |
135 } | |
136 } | |
137 return clipping_rect; | |
138 } | |
139 | |
94 // Creates a downsampled thumbnail from the given bitmap. | 140 // Creates a downsampled thumbnail from the given bitmap. |
95 // store. The returned bitmap will be isNull if there was an error creating it. | 141 // store. The returned bitmap will be isNull if there was an error creating it. |
96 SkBitmap CreateThumbnail( | 142 SkBitmap CreateThumbnail( |
97 const SkBitmap& bmp_with_scrollbars, | 143 const SkBitmap& bitmap, |
98 int desired_width, | 144 int desired_width, |
99 int desired_height, | 145 int desired_height, |
100 ThumbnailGenerator::ClipResult* clip_result) { | 146 ThumbnailGenerator::ClipResult* clip_result) { |
101 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now(); | 147 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now(); |
102 | 148 |
103 // Clip the edgemost 15 pixels as that will commonly hold a scrollbar, which | 149 SkBitmap clipped_bitmap; |
104 // looks bad in thumbnails. | 150 if (*clip_result == ThumbnailGenerator::kUnprocessed) { |
105 SkIRect scrollbarless_rect = | 151 // Clip the edgemost 15 pixels as that will commonly hold a scrollbar, which |
brettw
2012/08/05 05:38:39
I'd remove the 15 pixel comment here since the act
mazda
2012/08/06 17:22:16
Removed the magic number (15) from the comment.
| |
106 { 0, 0, | 152 // looks bad in thumbnails. |
107 std::max(1, bmp_with_scrollbars.width() - 15), | 153 SkIRect scrollbarless_rect = |
108 std::max(1, bmp_with_scrollbars.height() - 15) }; | 154 { 0, 0, |
109 SkBitmap bmp; | 155 std::max(1, bitmap.width() - kScrollbarThickness), |
110 bmp_with_scrollbars.extractSubset(&bmp, scrollbarless_rect); | 156 std::max(1, bitmap.height() - kScrollbarThickness) }; |
157 SkBitmap bmp; | |
158 bitmap.extractSubset(&bmp, scrollbarless_rect); | |
111 | 159 |
112 SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap( | 160 clipped_bitmap = ThumbnailGenerator::GetClippedBitmap( |
113 bmp, desired_width, desired_height, clip_result); | 161 bmp, desired_width, desired_height, clip_result); |
162 } else { | |
163 clipped_bitmap = bitmap; | |
164 } | |
114 | 165 |
115 // Need to resize it to the size we want, so downsample until it's | 166 // Need to resize it to the size we want, so downsample until it's |
116 // close, and let the caller make it the exact size if desired. | 167 // close, and let the caller make it the exact size if desired. |
117 SkBitmap result = SkBitmapOperations::DownsampleByTwoUntilSize( | 168 SkBitmap result = SkBitmapOperations::DownsampleByTwoUntilSize( |
118 clipped_bitmap, desired_width, desired_height); | 169 clipped_bitmap, desired_width, desired_height); |
119 #if !defined(USE_AURA) | 170 #if !defined(USE_AURA) |
120 // This is a bit subtle. SkBitmaps are refcounted, but the magic | 171 // This is a bit subtle. SkBitmaps are refcounted, but the magic |
121 // ones in PlatformCanvas can't be assigned to SkBitmap with proper | 172 // ones in PlatformCanvas can't be assigned to SkBitmap with proper |
122 // refcounting. If the bitmap doesn't change, then the downsampler | 173 // refcounting. If the bitmap doesn't change, then the downsampler |
123 // will return the input bitmap, which will be the reference to the | 174 // will return the input bitmap, which will be the reference to the |
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
369 | 420 |
370 int color_count = *std::max_element(histogram, histogram + 256); | 421 int color_count = *std::max_element(histogram, histogram + 256); |
371 int pixel_count = bitmap.width() * bitmap.height(); | 422 int pixel_count = bitmap.width() * bitmap.height(); |
372 return static_cast<double>(color_count) / pixel_count; | 423 return static_cast<double>(color_count) / pixel_count; |
373 } | 424 } |
374 | 425 |
375 SkBitmap ThumbnailGenerator::GetClippedBitmap(const SkBitmap& bitmap, | 426 SkBitmap ThumbnailGenerator::GetClippedBitmap(const SkBitmap& bitmap, |
376 int desired_width, | 427 int desired_width, |
377 int desired_height, | 428 int desired_height, |
378 ClipResult* clip_result) { | 429 ClipResult* clip_result) { |
379 const SkRect dest_rect = { 0, 0, | 430 gfx::Rect clipping_rect = |
380 SkIntToScalar(desired_width), | 431 GetClippingRect(gfx::Size(bitmap.width(), bitmap.height()), |
381 SkIntToScalar(desired_height) }; | 432 gfx::Size(desired_width, desired_height), |
382 const float dest_aspect = dest_rect.width() / dest_rect.height(); | 433 clip_result); |
383 | 434 SkIRect src_rect = { clipping_rect.x(), clipping_rect.y(), |
384 // Get the src rect so that we can preserve the aspect ratio while filling | 435 clipping_rect.right(), clipping_rect.bottom() }; |
385 // the destination. | |
386 SkIRect src_rect; | |
387 if (bitmap.width() < dest_rect.width() || | |
388 bitmap.height() < dest_rect.height()) { | |
389 // Source image is smaller: we clip the part of source image within the | |
390 // dest rect, and then stretch it to fill the dest rect. We don't respect | |
391 // the aspect ratio in this case. | |
392 src_rect.set(0, 0, static_cast<S16CPU>(dest_rect.width()), | |
393 static_cast<S16CPU>(dest_rect.height())); | |
394 if (clip_result) | |
395 *clip_result = ThumbnailGenerator::kSourceIsSmaller; | |
396 } else { | |
397 const float src_aspect = | |
398 static_cast<float>(bitmap.width()) / bitmap.height(); | |
399 if (src_aspect > dest_aspect) { | |
400 // Wider than tall, clip horizontally: we center the smaller | |
401 // thumbnail in the wider screen. | |
402 S16CPU new_width = static_cast<S16CPU>(bitmap.height() * dest_aspect); | |
403 S16CPU x_offset = (bitmap.width() - new_width) / 2; | |
404 src_rect.set(x_offset, 0, new_width + x_offset, bitmap.height()); | |
405 if (clip_result) { | |
406 *clip_result = (src_aspect >= ThumbnailScore::kTooWideAspectRatio) ? | |
407 ThumbnailGenerator::kTooWiderThanTall : | |
408 ThumbnailGenerator::kWiderThanTall; | |
409 } | |
410 } else if (src_aspect < dest_aspect) { | |
411 src_rect.set(0, 0, bitmap.width(), | |
412 static_cast<S16CPU>(bitmap.width() / dest_aspect)); | |
413 if (clip_result) | |
414 *clip_result = ThumbnailGenerator::kTallerThanWide; | |
415 } else { | |
416 src_rect.set(0, 0, bitmap.width(), bitmap.height()); | |
417 if (clip_result) | |
418 *clip_result = ThumbnailGenerator::kNotClipped; | |
419 } | |
420 } | |
421 | |
422 SkBitmap clipped_bitmap; | 436 SkBitmap clipped_bitmap; |
423 bitmap.extractSubset(&clipped_bitmap, src_rect); | 437 bitmap.extractSubset(&clipped_bitmap, src_rect); |
424 return clipped_bitmap; | 438 return clipped_bitmap; |
425 } | 439 } |
426 | 440 |
427 void ThumbnailGenerator::UpdateThumbnailIfNecessary( | 441 void ThumbnailGenerator::UpdateThumbnailIfNecessary( |
428 WebContents* web_contents) { | 442 WebContents* web_contents) { |
429 // Skip if a pending entry exists. WidgetHidden can be called while navigaing | 443 // Skip if a pending entry exists. WidgetHidden can be called while navigaing |
430 // pages and this is not a timing when thumbnails should be generated. | 444 // pages and this is not a timing when thumbnails should be generated. |
431 if (web_contents->GetController().GetPendingEntry()) | 445 if (web_contents->GetController().GetPendingEntry()) |
432 return; | 446 return; |
433 const GURL& url = web_contents->GetURL(); | 447 const GURL& url = web_contents->GetURL(); |
434 Profile* profile = | 448 Profile* profile = |
435 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 449 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
436 history::TopSites* top_sites = profile->GetTopSites(); | 450 history::TopSites* top_sites = profile->GetTopSites(); |
437 // Skip if we don't need to update the thumbnail. | 451 // Skip if we don't need to update the thumbnail. |
438 if (!ShouldUpdateThumbnail(profile, top_sites, url)) | 452 if (!ShouldUpdateThumbnail(profile, top_sites, url)) |
439 return; | 453 return; |
440 | 454 |
441 AsyncUpdateThumbnail(web_contents); | 455 AsyncUpdateThumbnail(web_contents); |
442 } | 456 } |
443 | 457 |
444 void ThumbnailGenerator::UpdateThumbnail( | 458 void ThumbnailGenerator::UpdateThumbnail( |
445 WebContents* web_contents, const SkBitmap& thumbnail, | 459 WebContents* web_contents, const SkBitmap& thumbnail, |
446 const ThumbnailGenerator::ClipResult& clip_result) { | 460 const ClipResult& clip_result) { |
447 | 461 |
448 Profile* profile = | 462 Profile* profile = |
449 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 463 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
450 history::TopSites* top_sites = profile->GetTopSites(); | 464 history::TopSites* top_sites = profile->GetTopSites(); |
451 if (!top_sites) | 465 if (!top_sites) |
452 return; | 466 return; |
453 | 467 |
454 // Compute the thumbnail score. | 468 // Compute the thumbnail score. |
455 ThumbnailScore score; | 469 ThumbnailScore score; |
456 score.at_top = | 470 score.at_top = |
(...skipping 22 matching lines...) Expand all Loading... | |
479 #if defined(OS_WIN) | 493 #if defined(OS_WIN) |
480 // On Windows XP, neither the backing store nor the compositing surface is | 494 // On Windows XP, neither the backing store nor the compositing surface is |
481 // available in the browser when accelerated compositing is active, so ask | 495 // available in the browser when accelerated compositing is active, so ask |
482 // the renderer to send a snapshot for creating the thumbnail. | 496 // the renderer to send a snapshot for creating the thumbnail. |
483 if (base::win::GetVersion() < base::win::VERSION_VISTA) { | 497 if (base::win::GetVersion() < base::win::VERSION_VISTA) { |
484 gfx::Size view_size = | 498 gfx::Size view_size = |
485 render_widget_host->GetView()->GetViewBounds().size(); | 499 render_widget_host->GetView()->GetViewBounds().size(); |
486 AskForSnapshot(render_widget_host, | 500 AskForSnapshot(render_widget_host, |
487 base::Bind(&ThumbnailGenerator::UpdateThumbnailWithBitmap, | 501 base::Bind(&ThumbnailGenerator::UpdateThumbnailWithBitmap, |
488 weak_factory_.GetWeakPtr(), | 502 weak_factory_.GetWeakPtr(), |
489 web_contents), | 503 web_contents, |
504 ThumbnailGenerator::kUnprocessed), | |
490 view_size, | 505 view_size, |
491 view_size); | 506 view_size); |
492 } | 507 } |
493 #endif | 508 #endif |
494 return; | 509 return; |
495 } | 510 } |
496 | 511 |
512 gfx::Rect copy_rect = gfx::Rect(view->GetViewBounds().size()); | |
513 // Clip the edgemost 15 pixels as that will commonly hold a scrollbar, which | |
brettw
2012/08/05 05:38:39
Ditto w/ "15 pixels."
mazda
2012/08/06 17:22:16
Done.
| |
514 // looks bad in thumbnails. | |
515 copy_rect.Inset(0, 0, kScrollbarThickness, kScrollbarThickness); | |
516 ClipResult clip_result = ThumbnailGenerator::kUnprocessed; | |
517 copy_rect = GetClippingRect(copy_rect.size(), | |
518 gfx::Size(kThumbnailWidth, kThumbnailHeight), | |
519 &clip_result); | |
497 gfx::Size copy_size = | 520 gfx::Size copy_size = |
498 GetCopySizeForThumbnail(view->GetViewBounds().size(), | 521 gfx::Size(kThumbnailWidth, kThumbnailHeight).Scale(kThumbnailCopyScale); |
499 gfx::Size(kThumbnailWidth, kThumbnailHeight)); | |
500 skia::PlatformCanvas* temp_canvas = new skia::PlatformCanvas; | 522 skia::PlatformCanvas* temp_canvas = new skia::PlatformCanvas; |
501 render_widget_host->CopyFromBackingStore( | 523 render_widget_host->CopyFromBackingStore( |
502 gfx::Rect(), | 524 copy_rect, |
503 copy_size, | 525 copy_size, |
504 base::Bind(&ThumbnailGenerator::UpdateThumbnailWithCanvas, | 526 base::Bind(&ThumbnailGenerator::UpdateThumbnailWithCanvas, |
505 weak_factory_.GetWeakPtr(), | 527 weak_factory_.GetWeakPtr(), |
506 web_contents, | 528 web_contents, |
529 clip_result, | |
507 base::Owned(temp_canvas)), | 530 base::Owned(temp_canvas)), |
508 temp_canvas); | 531 temp_canvas); |
509 } | 532 } |
510 | 533 |
511 void ThumbnailGenerator::UpdateThumbnailWithBitmap( | 534 void ThumbnailGenerator::UpdateThumbnailWithBitmap( |
512 WebContents* web_contents, | 535 WebContents* web_contents, |
536 ClipResult clip_result, | |
513 const SkBitmap& bitmap) { | 537 const SkBitmap& bitmap) { |
514 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 538 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
515 if (bitmap.isNull() || bitmap.empty()) | 539 if (bitmap.isNull() || bitmap.empty()) |
516 return; | 540 return; |
517 | 541 |
518 ClipResult clip_result; | |
519 SkBitmap thumbnail = CreateThumbnail(bitmap, | 542 SkBitmap thumbnail = CreateThumbnail(bitmap, |
520 kThumbnailWidth, | 543 kThumbnailWidth, |
521 kThumbnailHeight, | 544 kThumbnailHeight, |
522 &clip_result); | 545 &clip_result); |
523 UpdateThumbnail(web_contents, thumbnail, clip_result); | 546 UpdateThumbnail(web_contents, thumbnail, clip_result); |
524 } | 547 } |
525 | 548 |
526 void ThumbnailGenerator::UpdateThumbnailWithCanvas( | 549 void ThumbnailGenerator::UpdateThumbnailWithCanvas( |
527 WebContents* web_contents, | 550 WebContents* web_contents, |
551 ClipResult clip_result, | |
528 skia::PlatformCanvas* temp_canvas, | 552 skia::PlatformCanvas* temp_canvas, |
529 bool succeeded) { | 553 bool succeeded) { |
530 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 554 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
531 if (!succeeded) | 555 if (!succeeded) |
532 return; | 556 return; |
533 | 557 |
534 SkBitmap bmp_with_scrollbars = | 558 SkBitmap bitmap = skia::GetTopDevice(*temp_canvas)->accessBitmap(false); |
535 skia::GetTopDevice(*temp_canvas)->accessBitmap(false); | 559 UpdateThumbnailWithBitmap(web_contents, clip_result, bitmap); |
536 UpdateThumbnailWithBitmap(web_contents, bmp_with_scrollbars); | |
537 } | 560 } |
538 | 561 |
539 bool ThumbnailGenerator::ShouldUpdateThumbnail(Profile* profile, | 562 bool ThumbnailGenerator::ShouldUpdateThumbnail(Profile* profile, |
540 history::TopSites* top_sites, | 563 history::TopSites* top_sites, |
541 const GURL& url) { | 564 const GURL& url) { |
542 if (!profile || !top_sites) | 565 if (!profile || !top_sites) |
543 return false; | 566 return false; |
544 // Skip if it's in the incognito mode. | 567 // Skip if it's in the incognito mode. |
545 if (profile->IsOffTheRecord()) | 568 if (profile->IsOffTheRecord()) |
546 return false; | 569 return false; |
(...skipping 21 matching lines...) Expand all Loading... | |
568 void ThumbnailGenerator::DidStartLoading( | 591 void ThumbnailGenerator::DidStartLoading( |
569 content::RenderViewHost* render_view_host) { | 592 content::RenderViewHost* render_view_host) { |
570 load_interrupted_ = false; | 593 load_interrupted_ = false; |
571 } | 594 } |
572 | 595 |
573 void ThumbnailGenerator::StopNavigation() { | 596 void ThumbnailGenerator::StopNavigation() { |
574 // This function gets called when the page loading is interrupted by the | 597 // This function gets called when the page loading is interrupted by the |
575 // stop button. | 598 // stop button. |
576 load_interrupted_ = true; | 599 load_interrupted_ = true; |
577 } | 600 } |
OLD | NEW |