diff options
author | skuhne <skuhne@chromium.org> | 2014-09-25 10:12:20 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-25 17:12:52 +0000 |
commit | 7144431a072fad80fc82ee87e7216011c534ef3b (patch) | |
tree | 0597db5d005fb64e83bf43a4576161703ee04bed | |
parent | 5627b3628bdbafc79c2a39f906c4c9adbcaaf8c5 (diff) | |
download | chromium_src-7144431a072fad80fc82ee87e7216011c534ef3b.zip chromium_src-7144431a072fad80fc82ee87e7216011c534ef3b.tar.gz chromium_src-7144431a072fad80fc82ee87e7216011c534ef3b.tar.bz2 |
Creating PNG images from web content to be used by OverviewMode navigation
This patch reads back content from the GPU in quarter res and stores it (in memory) as PNG.
The OverviewMode can then grab an image through an activity to present it accordingly.
Since we do not have browser test framework for Athena, there is no unit test yet.
I took however issue 412474 on to mitigate that and I will use this test as the gunieapig test.
In the meantime sadrul@chromium.org can start using this for his overviewmode presentation development.
BUG=408837
TEST=Visual: Added Logging statements to see that the code gets properly executed.
Review URL: https://codereview.chromium.org/591693002
Cr-Commit-Position: refs/heads/master@{#296729}
-rw-r--r-- | athena/activity/public/activity_view_model.h | 9 | ||||
-rw-r--r-- | athena/content/app_activity.cc | 6 | ||||
-rw-r--r-- | athena/content/app_activity.h | 5 | ||||
-rw-r--r-- | athena/content/app_activity_proxy.cc | 2 | ||||
-rw-r--r-- | athena/content/app_activity_proxy.h | 3 | ||||
-rw-r--r-- | athena/content/app_activity_registry.h | 1 | ||||
-rw-r--r-- | athena/content/content_proxy.cc | 165 | ||||
-rw-r--r-- | athena/content/content_proxy.h | 79 | ||||
-rw-r--r-- | athena/test/sample_activity.cc | 1 | ||||
-rw-r--r-- | athena/test/sample_activity.h | 5 |
10 files changed, 169 insertions, 107 deletions
diff --git a/athena/activity/public/activity_view_model.h b/athena/activity/public/activity_view_model.h index 7412af2..2459021 100644 --- a/athena/activity/public/activity_view_model.h +++ b/athena/activity/public/activity_view_model.h @@ -56,9 +56,12 @@ class ATHENA_EXPORT ActivityViewModel { // Returns an image which can be used to represent the activity in e.g. the // overview mode. The returned image can have no size if either a view exists - // or the activity has not yet been loaded. In that case - // GetRepresentativeColor() should be used to clear the preview area. - // Note: We intentionally do not use a layer / view for this. + // or the activity has not yet been loaded or ever been presented. In that + // case GetRepresentativeColor() should be used to clear the preview area. + // Note that since the image gets created upon request, and the + // ActivityViewModel will hold no reference to the returned image data. As + // such it is advisable to hold on to the image as long as needed instead of + // calling this function frequently since it will cause time to generate. virtual gfx::ImageSkia GetOverviewModeImage() = 0; // Prepares the contents view for overview. diff --git a/athena/content/app_activity.cc b/athena/content/app_activity.cc index 885edba..8768508 100644 --- a/athena/content/app_activity.cc +++ b/athena/content/app_activity.cc @@ -29,9 +29,11 @@ AppActivity::AppActivity(extensions::AppWindow* app_window, Observe(app_window->web_contents()); } -scoped_ptr<ContentProxy> AppActivity::GetContentProxy(aura::Window* window) { +scoped_ptr<ContentProxy> AppActivity::GetContentProxy() { + // Note: After this call, the content is still valid because the contents + // destruction will destroy this |AppActivity| object. if (content_proxy_.get()) - content_proxy_->Reparent(window); + content_proxy_->OnPreContentDestroyed(); return content_proxy_.Pass(); } diff --git a/athena/content/app_activity.h b/athena/content/app_activity.h index 6406a62..cc1a0c8 100644 --- a/athena/content/app_activity.h +++ b/athena/content/app_activity.h @@ -10,7 +10,6 @@ #include "athena/content/app_activity_proxy.h" #include "base/memory/scoped_ptr.h" #include "content/public/browser/web_contents_observer.h" -#include "ui/gfx/image/image_skia.h" namespace extensions { class AppWindow; @@ -33,8 +32,8 @@ class AppActivity : public Activity, public: AppActivity(extensions::AppWindow* app_window, views::WebView* web_view); - // Gets the content proxy so that the AppProxy can take it over. - scoped_ptr<ContentProxy> GetContentProxy(aura::Window* window); + // Gets the content proxy so that the AppActivityProxy can take it over. + scoped_ptr<ContentProxy> GetContentProxy(); // Activity: virtual athena::ActivityViewModel* GetActivityViewModel() OVERRIDE; diff --git a/athena/content/app_activity_proxy.cc b/athena/content/app_activity_proxy.cc index 87d98ae..143dd88 100644 --- a/athena/content/app_activity_proxy.cc +++ b/athena/content/app_activity_proxy.cc @@ -64,7 +64,7 @@ content::WebContents* AppActivityProxy::GetWebContents() { void AppActivityProxy::Init() { DCHECK(replaced_activity_); // Get the content proxy to present the content. - content_proxy_ = replaced_activity_->GetContentProxy(GetWindow()); + content_proxy_ = replaced_activity_->GetContentProxy(); WindowListProvider* window_list_provider = WindowManager::Get()->GetWindowListProvider(); window_list_provider->StackWindowBehindTo(GetWindow(), diff --git a/athena/content/app_activity_proxy.h b/athena/content/app_activity_proxy.h index 68db1bb..9f3bfbe 100644 --- a/athena/content/app_activity_proxy.h +++ b/athena/content/app_activity_proxy.h @@ -11,7 +11,6 @@ #include "athena/activity/public/activity_view_model.h" #include "athena/content/content_proxy.h" #include "base/memory/scoped_ptr.h" -#include "ui/gfx/image/image_skia.h" namespace athena { @@ -19,7 +18,7 @@ class AppActivity; class AppActivityRegistry; // This activity object is a proxy placeholder for the application while it is -// unloaded. When selected it will launch the applciation again and destroy +// unloaded. When selected it will launch the application again and destroy // itself indirectly. class AppActivityProxy : public Activity, public ActivityViewModel { diff --git a/athena/content/app_activity_registry.h b/athena/content/app_activity_registry.h index a0f5dbb..cce8071 100644 --- a/athena/content/app_activity_registry.h +++ b/athena/content/app_activity_registry.h @@ -9,6 +9,7 @@ #include "athena/activity/public/activity_view_model.h" #include "athena/content/app_activity_proxy.h" +#include "ui/gfx/image/image_skia.h" namespace aura { class Window; diff --git a/athena/content/content_proxy.cc b/athena/content/content_proxy.cc index a63aa35..7dce0e9 100644 --- a/athena/content/content_proxy.cc +++ b/athena/content/content_proxy.cc @@ -6,32 +6,74 @@ #include "athena/activity/public/activity.h" #include "athena/activity/public/activity_view_model.h" +#include "base/bind.h" +#include "base/threading/worker_pool.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "ui/aura/window.h" -#include "ui/views/background.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_png_rep.h" #include "ui/views/controls/webview/webview.h" #include "ui/views/widget/widget.h" namespace athena { +// Encodes an A8 SkBitmap to grayscale PNG in a worker thread. +class ProxyImageData : public base::RefCountedThreadSafe<ProxyImageData> { + public: + ProxyImageData() { + } + + void EncodeImage(const SkBitmap& bitmap, base::Closure callback) { + if (!base::WorkerPool::PostTaskAndReply(FROM_HERE, + base::Bind(&ProxyImageData::EncodeOnWorker, + this, + bitmap), + callback, + true)) { + // When coming here, the resulting image will be empty. + DCHECK(false) << "Cannot start bitmap encode task."; + callback.Run(); + } + } + + scoped_refptr<base::RefCountedBytes> data() const { return data_; } + + private: + friend class base::RefCountedThreadSafe<ProxyImageData>; + virtual ~ProxyImageData() { + } + + void EncodeOnWorker(const SkBitmap& bitmap) { + DCHECK_EQ(bitmap.colorType(), kAlpha_8_SkColorType); + // Encode the A8 bitmap to grayscale PNG treating alpha as color intensity. + std::vector<unsigned char> data; + if (gfx::PNGCodec::EncodeA8SkBitmap(bitmap, &data)) + data_ = new base::RefCountedBytes(data); + } + + scoped_refptr<base::RefCountedBytes> data_; + + DISALLOW_COPY_AND_ASSIGN(ProxyImageData); +}; + ContentProxy::ContentProxy(views::WebView* web_view, Activity* activity) : web_view_(web_view), - window_(activity->GetWindow()), - background_color_( - activity->GetActivityViewModel()->GetRepresentativeColor()), - content_loaded_(true) { + content_visible_(true), + content_loaded_(true), + content_creation_called_(false), + proxy_content_to_image_factory_(this) { + // Note: The content will be hidden once the image got created. CreateProxyContent(); - HideOriginalContent(); } ContentProxy::~ContentProxy() { // If we still have a connection to the original Activity, we make it visible // again. ShowOriginalContent(); - // At this point we should either have no view - or the view should not be - // shown by its parent anymore. - DCHECK(!proxy_content_.get() || !proxy_content_->parent()); - proxy_content_.reset(); } void ContentProxy::ContentWillUnload() { @@ -39,39 +81,33 @@ void ContentProxy::ContentWillUnload() { } gfx::ImageSkia ContentProxy::GetContentImage() { - // For the time being we keep this here and return an empty image. - return image_; + // While we compress to PNG, we use the original read back. + if (!raw_image_.isNull() || !png_data_.get()) + return raw_image_; + + // Otherwise we convert the PNG. + std::vector<gfx::ImagePNGRep> image_reps; + image_reps.push_back(gfx::ImagePNGRep(png_data_, 0.0f)); + return *(gfx::Image(image_reps).ToImageSkia()); } void ContentProxy::EvictContent() { - HideProxyContent(); - CreateSolidProxyContent(); - ShowProxyContent(); + raw_image_ = gfx::ImageSkia(); + png_data_->Release(); } -void ContentProxy::Reparent(aura::Window* new_parent_window) { - if (new_parent_window == window_) - return; - +void ContentProxy::OnPreContentDestroyed() { // Since we are breaking now the connection to the old content, we make the // content visible again before we continue. // Note: Since the owning window is invisible, it does not matter that we // make the web content visible if the window gets destroyed shortly after. ShowOriginalContent(); - // Transfer the |proxy_content_| to the passed window. - window_ = new_parent_window; web_view_ = NULL; - - // Move the view to the new window and show it there. - HideOriginalContent(); } void ContentProxy::ShowOriginalContent() { - // Hide our content. - HideProxyContent(); - - if (web_view_) { + if (web_view_ && !content_visible_) { // Show the original |web_view_| again. web_view_->SetFastResize(false); // If the content is loaded, we ask it to relayout itself since the @@ -81,52 +117,73 @@ void ContentProxy::ShowOriginalContent() { web_view_->Layout(); web_view_->GetWebContents()->GetNativeView()->Show(); web_view_->SetVisible(true); + content_visible_ = true; } } void ContentProxy::HideOriginalContent() { - if (web_view_) { + if (web_view_ && content_visible_) { // Hide the |web_view_|. // TODO(skuhne): We might consider removing the view from the window while // it's hidden - it should work the same way as show/hide and does not have - // any window re-ordering effect. + // any window re-ordering effect. Furthermore we want possibly to suppress + // any resizing of content (not only fast resize) here to avoid jank on + // rotation. web_view_->GetWebContents()->GetNativeView()->Hide(); web_view_->SetVisible(false); // Don't allow the content to get resized with window size changes. web_view_->SetFastResize(true); + content_visible_ = false; } - - // Show our replacement content. - ShowProxyContent(); } void ContentProxy::CreateProxyContent() { - // For the time being we create only a solid color here. - // TODO(skuhne): Replace with copying the drawn content into |proxy_content_| - // instead. - CreateSolidProxyContent(); -} + DCHECK(!content_creation_called_); + content_creation_called_ = true; + // Unit tests might not have a |web_view_|. + if (!web_view_) + return; -void ContentProxy::CreateSolidProxyContent() { - proxy_content_.reset(new views::View()); - proxy_content_->set_owned_by_client(); - proxy_content_->set_background( - views::Background::CreateSolidBackground(background_color_)); + content::RenderViewHost* host = + web_view_->GetWebContents()->GetRenderViewHost(); + DCHECK(host && host->GetView()); + gfx::Size source = host->GetView()->GetViewBounds().size(); + gfx::Size target = gfx::Size(source.width() / 2, source.height() / 2); + host->CopyFromBackingStore( + gfx::Rect(), + target, + base::Bind(&ContentProxy::OnContentImageRead, + proxy_content_to_image_factory_.GetWeakPtr()), + kAlpha_8_SkColorType); } -void ContentProxy::ShowProxyContent() { - views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window_); - DCHECK(widget); - views::View* client_view = widget->client_view(); - // Place the view in front of all others. - client_view->AddChildView(proxy_content_.get()); - proxy_content_->SetSize(client_view->bounds().size()); +void ContentProxy::OnContentImageRead(bool success, const SkBitmap& bitmap) { + // Now we can hide the content. Note that after hiding we are freeing memory + // and if something goes wrong we will end up with an empty page. + HideOriginalContent(); + + if (!success || bitmap.empty() || bitmap.isNull()) + return; + + // While we are encoding the image, we keep the current image as reference + // to have something for the overview mode to grab. Once we have the encoded + // PNG, we will get rid of this. + raw_image_ = gfx::ImageSkia::CreateFrom1xBitmap(bitmap); + + scoped_refptr<ProxyImageData> png_image = new ProxyImageData(); + png_image->EncodeImage( + bitmap, + base::Bind(&ContentProxy::OnContentImageEncodeComplete, + proxy_content_to_image_factory_.GetWeakPtr(), + png_image)); } -void ContentProxy::HideProxyContent() { - views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window_); - views::View* client_view = widget->client_view(); - client_view->RemoveChildView(proxy_content_.get()); +void ContentProxy::OnContentImageEncodeComplete( + scoped_refptr<ProxyImageData> image) { + png_data_ = image->data(); + + // From now on we decode the image as needed to save memory. + raw_image_ = gfx::ImageSkia(); } } // namespace athena diff --git a/athena/content/content_proxy.h b/athena/content/content_proxy.h index d42c0e1..e460338 100644 --- a/athena/content/content_proxy.h +++ b/athena/content/content_proxy.h @@ -6,35 +6,27 @@ #define ATHENA_CONTENT_CONTENT_PROXY_H_ #include "base/macros.h" +#include "base/memory/ref_counted_memory.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" #include "ui/gfx/image/image_skia.h" -namespace aura { -class Window; -} - namespace views { -class View; class WebView; } -typedef unsigned int SkColor; - namespace athena { class Activity; -class AppActivity; -class AppActivityProxy; -class WebActivity; +class ProxyImageData; // This object creates and holds proxy content which gets shown instead of the // actual web content, generated from the passed |web_view|. // The created |proxy_content_| will be destroyed with the destruction of this // object. -// Calling EvictContent() will release the rendered content and replaces it with -// a solid color to save memory. -// Calling Reparent() will transfer the |proxy_content_| to an ActivityProxy, -// allowing the destruction of the original activity / content. +// Calling EvictContent() will release the rendered content. +// When ContentGetsDestroyed() gets called, the old view will be made visible +// and then the link to the |web_view_| will get severed. class ContentProxy { public: // Creates the object by creating a sized down |web_view| layer and making it @@ -60,11 +52,9 @@ class ContentProxy { // will be returned. gfx::ImageSkia GetContentImage(); - // Transfer the owned |proxy_content_| to the |new_parent_window|. - // Once called, the |web_view_| will be made visible again and the connection - // to it will be removed since the old activity might go away. + // The content is about to get destroyed by its creator. // Note: This function should only be used by AppActivity. - void Reparent(aura::Window* new_parent_window); + void OnPreContentDestroyed(); private: // Make the original (web)content visible. This call should only be paired @@ -75,40 +65,47 @@ class ContentProxy { // MakeVisible. void HideOriginalContent(); - // Creates proxy content from |web_view_|. If there is no |web_view_|, - // a solid |proxy_content_| with |background_color_| will be created. + // Creates proxy content from |web_view_|. void CreateProxyContent(); - // Creates a solid |proxy_content_| with |background_color_|. - void CreateSolidProxyContent(); + // Creates an image from the current content. + bool CreateContentImage(); - // Show the |proxy_content_| in the current |window_|. - void ShowProxyContent(); + // Called once the content was read back. + void OnContentImageRead(bool success, const SkBitmap& bitmap); - // Removes the |proxy_content_| from the window again. - void HideProxyContent(); + // Called once the image content has been converted to PNG. + void OnContentImageEncodeComplete(scoped_refptr<ProxyImageData> image); - // The web view which is associated with this object. It will be NULL after - // the object got called with Reparent(), since the Activity which owns it - // will be destroyed shortly after. + // The web view which was passed on creation and is associated with this + // object. It will be shown when OnPreContentDestroyed() gets called and then + // set to NULL. The ownership remains with the creator. views::WebView* web_view_; - // The window which shows our |proxy_content_|, - aura::Window* window_; + // While we are doing our PNG encode, we keep the read back image to have + // something which we can pass back to the overview mode. (It would make no + // sense to the user to see that more recent windows get painted later than + // older ones). + gfx::ImageSkia raw_image_; - // The background color to use when evicted. - SkColor background_color_; + // True if the content is visible. + bool content_visible_; - // If we have an image (e.g. from session restore) it is stored here. - gfx::ImageSkia image_; - - // True if the content is loaded. + // True if the content is loaded and needs a re-layout when it gets shown + // again. bool content_loaded_; - // The content representation which which will be presented to the user. It - // will either contain a shrunken down image of the |web_view| content or a - // solid |background_color_|. - scoped_ptr<views::View> proxy_content_; + // True if a content creation was kicked off once. This ensures that the + // function is never called twice. + bool content_creation_called_; + + // The PNG image data. + scoped_refptr<base::RefCountedBytes> png_data_; + + // Creating an encoded image from the content will be asynchronous. Use a + // weakptr for the callback to make sure that the read back / encoding image + // completion callback does not trigger on a destroyed ContentProxy. + base::WeakPtrFactory<ContentProxy> proxy_content_to_image_factory_; DISALLOW_COPY_AND_ASSIGN(ContentProxy); }; diff --git a/athena/test/sample_activity.cc b/athena/test/sample_activity.cc index 0ea3f2a..754a2eb 100644 --- a/athena/test/sample_activity.cc +++ b/athena/test/sample_activity.cc @@ -4,6 +4,7 @@ #include "athena/test/sample_activity.h" +#include "ui/gfx/image/image_skia.h" #include "ui/views/background.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" diff --git a/athena/test/sample_activity.h b/athena/test/sample_activity.h index 5f7b4e0..6bde146 100644 --- a/athena/test/sample_activity.h +++ b/athena/test/sample_activity.h @@ -7,7 +7,10 @@ #include "athena/activity/public/activity.h" #include "athena/activity/public/activity_view_model.h" -#include "ui/gfx/image/image_skia.h" + +namespace gfx { +class ImageSkia; +} namespace athena { namespace test { |