| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_ |
| #define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_ |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "base/containers/flat_set.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/optional.h" |
| #include "base/sequence_checker.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| namespace base { |
| class TimeTicks; |
| } // namespace base |
| |
| // Stores compressed thumbnail data for a tab and can vend that data as an |
| // uncompressed image to observers. |
| class ThumbnailImage : public base::RefCounted<ThumbnailImage> { |
| public: |
| // Smart pointer to reference-counted compressed image data; in this case |
| // JPEG format. |
| using CompressedThumbnailData = |
| scoped_refptr<base::RefCountedData<std::vector<uint8_t>>>; |
| |
| class Subscription { |
| public: |
| Subscription() = delete; |
| ~Subscription(); |
| |
| using UncompressedImageCallback = |
| base::RepeatingCallback<void(gfx::ImageSkia)>; |
| using CompressedImageCallback = |
| base::RepeatingCallback<void(CompressedThumbnailData)>; |
| |
| // Set callbacks to receive image data. Subscribers are not allowed |
| // to unsubscribe (by destroying |this|) from the callback. If |
| // necessary, post a task to destroy it soon after. |
| |
| void SetUncompressedImageCallback(UncompressedImageCallback callback) { |
| uncompressed_image_callback_ = std::move(callback); |
| } |
| |
| void SetCompressedImageCallback(CompressedImageCallback callback) { |
| compressed_image_callback_ = std::move(callback); |
| } |
| |
| // Provides a desired aspect ratio and minimum size that the observer will |
| // accept. If not specified, or if available thumbnail data is smaller in |
| // either dimension than this value, OnThumbnailImageAvailable will be |
| // called with an uncropped image. If this value is specified, and the |
| // available image is larger, the image passed to OnThumbnailImageAvailable |
| // will be cropped to the same aspect ratio (but otherwise unchanged, |
| // including scale). |
| // |
| // OnCompressedThumbnailDataAvailable is not affected by this value. |
| // |
| // This method is used to ensure that except for very small thumbnails, the |
| // image passed to OnThumbnailImageAvailable fits the needs of the observer |
| // for display purposes, without the observer having to further crop the |
| // image. The default is unspecified. |
| void SetSizeHint(const base::Optional<gfx::Size>& size_hint) { |
| size_hint_ = size_hint; |
| } |
| |
| private: |
| friend class ThumbnailImage; |
| |
| explicit Subscription(scoped_refptr<ThumbnailImage> thumbnail); |
| |
| scoped_refptr<ThumbnailImage> thumbnail_; |
| base::Optional<gfx::Size> size_hint_; |
| |
| UncompressedImageCallback uncompressed_image_callback_; |
| CompressedImageCallback compressed_image_callback_; |
| }; |
| |
| // Represents the endpoint |
| class Delegate { |
| public: |
| // Called whenever the thumbnail starts or stops being observed. |
| // Because updating the thumbnail could be an expensive operation, it's |
| // useful to track when there are no observers. Default behavior is no-op. |
| virtual void ThumbnailImageBeingObservedChanged(bool is_being_observed) = 0; |
| |
| protected: |
| virtual ~Delegate(); |
| |
| private: |
| friend class ThumbnailImage; |
| ThumbnailImage* thumbnail_ = nullptr; |
| }; |
| |
| explicit ThumbnailImage(Delegate* delegate); |
| |
| bool has_data() const { return data_.get(); } |
| |
| // Subscribe to thumbnail updates. See |Subscription| to set a |
| // callback and conigure additional options. |
| // |
| // Even if a callback is not set, the subscription influences |
| // thumbnail capture. It should be destroyed when updates are not |
| // needed. It is designed to be stored in base::Optional, created and |
| // destroyed as needed. |
| std::unique_ptr<Subscription> Subscribe(); |
| |
| // Sets the SkBitmap data and notifies observers with the resulting image. |
| void AssignSkBitmap(SkBitmap bitmap); |
| |
| // Clears the currently set |data_|, for when the current thumbnail is no |
| // longer valid to display. |
| void ClearData(); |
| |
| // Requests that a thumbnail image be made available to observers. Does not |
| // guarantee that Observer::OnThumbnailImageAvailable() will be called, or how |
| // long it will take, though in most cases it should happen very quickly. |
| void RequestThumbnailImage(); |
| |
| // Similar to RequestThumbnailImage() but requests only the compressed JPEG |
| // data. Users should listen for a call to |
| // Observer::OnCompressedThumbnailDataAvailable(). |
| void RequestCompressedThumbnailData(); |
| |
| // Returns the size of the compressed data backing this thumbnail. |
| // This size can be 0. Additionally, since this data is refcounted, |
| // it's possible this returns 0 even if the data is still allocated. A |
| // client can hold a reference to it after |this| drops its reference. |
| size_t GetCompressedDataSizeInBytes() const; |
| |
| void set_async_operation_finished_callback_for_testing( |
| base::RepeatingClosure callback) { |
| async_operation_finished_callback_ = std::move(callback); |
| } |
| |
| private: |
| friend class Delegate; |
| friend class ThumbnailImageTest; |
| friend class base::RefCounted<ThumbnailImage>; |
| |
| virtual ~ThumbnailImage(); |
| |
| void AssignJPEGData(base::TimeTicks assign_sk_bitmap_time, |
| std::vector<uint8_t> data); |
| bool ConvertJPEGDataToImageSkiaAndNotifyObservers(); |
| void NotifyUncompressedDataObservers(gfx::ImageSkia image); |
| void NotifyCompressedDataObservers(CompressedThumbnailData data); |
| |
| static std::vector<uint8_t> CompressBitmap(SkBitmap bitmap); |
| static gfx::ImageSkia UncompressImage(CompressedThumbnailData compressed); |
| |
| // Crops and returns a preview from a thumbnail of an entire web page. Uses |
| // logic appropriate for fixed-aspect previews (e.g. hover cards). |
| static gfx::ImageSkia CropPreviewImage(const gfx::ImageSkia& source_image, |
| const gfx::Size& minimum_size); |
| |
| void HandleSubscriptionDestroyed(Subscription* subscription); |
| |
| Delegate* delegate_; |
| |
| // This is a scoped_refptr to immutable data. Once set, the wrapped |
| // data must not be modified; it is referenced by other threads. |
| // |data_| itself can be changed as this does not affect references to |
| // the old data. |
| CompressedThumbnailData data_; |
| |
| // Subscriptions are inserted on |Subscribe()| calls and removed when |
| // they are destroyed via callback. The order of subscriber |
| // notification doesn't matter, so don't maintain any ordering. Since |
| // the number of subscribers for a given thumbnail is expected to be |
| // small, doing a linear search to remove a subscriber is fine. |
| std::vector<Subscription*> subscribers_; |
| |
| // Called when an asynchronous operation (such as encoding image data upon |
| // assignment or decoding image data for observers) finishes or fails. |
| // Intended for unit tests that want to wait for internal operations following |
| // AssignSkBitmap() or RequestThumbnailImage() calls. |
| base::RepeatingClosure async_operation_finished_callback_; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| base::WeakPtrFactory<ThumbnailImage> weak_ptr_factory_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(ThumbnailImage); |
| }; |
| |
| #endif // CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_ |