Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1450)

Side by Side Diff: chrome/browser/tab_contents/thumbnail_generator.cc

Issue 6246007: Generate thumbnails in the browser process. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix the function name... Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « chrome/browser/tab_contents/thumbnail_generator.h ('k') | chrome/browser/tab_contents/thumbnail_generator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698