-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PickerGifView with basic gif showing functionality.
This is for showing gifs in the new ChromeOS picker feature. Various details are still TBD (e.g. how many gifs can be shown, whether to always animate or only animate on hover, resolution requirements), so just add some basic functionality to experiment with for now. Gifs are planned to be fetched from Google's tenor API: https://developers.google.com/tenor Then, the gifs are decoded out of process using the data_decoder service, via ash::image_util API. The decoded frames are stored in PickerGifView, which handles frame updates. Note that the gifs are downscaled when they are decoded if needed to reduce the total size of all frames to `kMaxImageSizeInBytes`: https://source.chromium.org/chromium/chromium/src/+/main:ash/public/cpp/image_util.cc;l=156;drc=4597c83760e5b831ec153cba625e571d0a4ec6f9 Bug: b:316817118, b:316936723 Change-Id: Ia1bed51d6a640bb4aa1dbfc5ee1d823b35fa9564 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5124918 Reviewed-by: Scott Violet <sky@chromium.org> Reviewed-by: Theodore Olsauskas-Warren <sauski@google.com> Commit-Queue: Michelle Chen <michellegc@google.com> Reviewed-by: Darren Shen <shend@chromium.org> Cr-Commit-Position: refs/heads/main@{#1243889}
- Loading branch information
Michelle
authored and
Chromium LUCI CQ
committed
Jan 8, 2024
1 parent
e4a5da6
commit 21f7c3f
Showing
14 changed files
with
393 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Copyright 2024 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "ash/picker/views/picker_gif_view.h" | ||
|
||
#include "ash/public/cpp/image_util.h" | ||
#include "base/functional/bind.h" | ||
#include "base/functional/callback.h" | ||
#include "base/time/time.h" | ||
#include "base/timer/timer.h" | ||
#include "ui/base/metadata/metadata_impl_macros.h" | ||
#include "ui/base/models/image_model.h" | ||
#include "ui/chromeos/styles/cros_tokens_color_mappings.h" | ||
#include "ui/gfx/geometry/size.h" | ||
#include "ui/gfx/geometry/skia_conversions.h" | ||
#include "ui/gfx/image/image_skia_operations.h" | ||
#include "ui/views/background.h" | ||
#include "ui/views/controls/image_view.h" | ||
|
||
namespace ash { | ||
|
||
namespace { | ||
|
||
constexpr int kPickerGifCornerRadius = 8; | ||
|
||
// We use a duration of 100ms for frames that specify a duration of <= 10ms. | ||
// This is to follow the behavior of blink (see http://webkit.org/b/36082 for | ||
// more information). | ||
constexpr base::TimeDelta kShortFrameDurationThreshold = base::Milliseconds(10); | ||
constexpr base::TimeDelta kAdjustedDurationForShortFrames = | ||
base::Milliseconds(100); | ||
|
||
} // namespace | ||
|
||
PickerGifView::PickerGifView(FramesFetcher frames_fetcher, | ||
const gfx::Size& image_size) | ||
: image_size_(image_size) { | ||
// Show a placeholder rect while the gif loads. | ||
SetBackground(views::CreateThemedRoundedRectBackground( | ||
cros_tokens::kCrosSysAppBaseShaded, kPickerGifCornerRadius)); | ||
SetImage( | ||
ui::ImageModel::FromImageSkia(image_util::CreateEmptyImage(image_size))); | ||
|
||
std::move(frames_fetcher) | ||
.Run(base::BindOnce(&PickerGifView::OnFramesFetched, | ||
weak_factory_.GetWeakPtr())); | ||
} | ||
|
||
PickerGifView::~PickerGifView() = default; | ||
|
||
void PickerGifView::OnBoundsChanged(const gfx::Rect& previous_bounds) { | ||
views::ImageView::OnBoundsChanged(previous_bounds); | ||
|
||
SkPath path; | ||
path.addRoundRect(gfx::RectToSkRect(GetImageBounds()), | ||
SkIntToScalar(kPickerGifCornerRadius), | ||
SkIntToScalar(kPickerGifCornerRadius)); | ||
SetClipPath(path); | ||
} | ||
|
||
void PickerGifView::UpdateFrame() { | ||
CHECK(next_frame_index_ < frames_.size()); | ||
SetImage(ui::ImageModel::FromImageSkia(frames_[next_frame_index_].image)); | ||
|
||
// Schedule next frame update. | ||
update_frame_timer_.Start( | ||
FROM_HERE, frames_[next_frame_index_].duration, | ||
base::BindOnce(&PickerGifView::UpdateFrame, weak_factory_.GetWeakPtr())); | ||
next_frame_index_ = (next_frame_index_ + 1) % frames_.size(); | ||
} | ||
|
||
void PickerGifView::OnFramesFetched( | ||
std::vector<image_util::AnimationFrame> frames) { | ||
if (frames.empty()) { | ||
// TODO: b/316936723 - Handle frames being empty. | ||
return; | ||
} | ||
|
||
frames_.reserve(frames.size()); | ||
for (auto& frame : frames) { | ||
frame.image = gfx::ImageSkiaOperations::CreateResizedImage( | ||
frame.image, skia::ImageOperations::RESIZE_BEST, image_size_); | ||
if (frame.duration <= kShortFrameDurationThreshold) { | ||
frame.duration = kAdjustedDurationForShortFrames; | ||
} | ||
frames_.push_back(std::move(frame)); | ||
} | ||
|
||
// Start gif from the first frame. | ||
next_frame_index_ = 0; | ||
UpdateFrame(); | ||
} | ||
|
||
BEGIN_METADATA(PickerGifView, views::ImageView) | ||
END_METADATA | ||
|
||
} // namespace ash |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Copyright 2024 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef ASH_PICKER_VIEWS_PICKER_GIF_VIEW_H_ | ||
#define ASH_PICKER_VIEWS_PICKER_GIF_VIEW_H_ | ||
|
||
#include <optional> | ||
#include <vector> | ||
|
||
#include "ash/ash_export.h" | ||
#include "base/functional/callback_forward.h" | ||
#include "base/timer/timer.h" | ||
#include "ui/base/metadata/metadata_header_macros.h" | ||
#include "ui/gfx/geometry/size.h" | ||
#include "ui/views/controls/image_view.h" | ||
#include "ui/views/view.h" | ||
|
||
namespace ash { | ||
|
||
namespace image_util { | ||
struct AnimationFrame; | ||
} // namespace image_util | ||
|
||
class ASH_EXPORT PickerGifView : public views::ImageView { | ||
METADATA_HEADER(PickerGifView, views::View) | ||
|
||
public: | ||
using FramesFetchedCallback = | ||
base::OnceCallback<void(std::vector<image_util::AnimationFrame>)>; | ||
using FramesFetcher = base::OnceCallback<void(FramesFetchedCallback)>; | ||
|
||
PickerGifView(FramesFetcher frames_fetcher, const gfx::Size& image_size); | ||
PickerGifView(const PickerGifView&) = delete; | ||
PickerGifView& operator=(const PickerGifView&) = delete; | ||
~PickerGifView() override; | ||
|
||
// views::ImageViewBase: | ||
void OnBoundsChanged(const gfx::Rect& previous_bounds) override; | ||
|
||
private: | ||
void UpdateFrame(); | ||
void OnFramesFetched(std::vector<image_util::AnimationFrame> frames); | ||
|
||
gfx::Size image_size_; | ||
|
||
// The decoded gif frames. | ||
std::vector<image_util::AnimationFrame> frames_; | ||
|
||
// Timer to call `UpdateFrame` when the next frame should be shown. | ||
base::OneShotTimer update_frame_timer_; | ||
|
||
// Index of the frame to show on the next call to `UpdateFrame`. | ||
size_t next_frame_index_ = 0; | ||
|
||
base::WeakPtrFactory<PickerGifView> weak_factory_{this}; | ||
}; | ||
|
||
} // namespace ash | ||
|
||
#endif // ASH_PICKER_VIEWS_PICKER_GIF_VIEW_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Copyright 2024 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "ash/picker/views/picker_gif_view.h" | ||
|
||
#include <utility> | ||
#include <vector> | ||
|
||
#include "ash/public/cpp/image_util.h" | ||
#include "base/test/task_environment.h" | ||
#include "base/time/time.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
#include "ui/gfx/geometry/size.h" | ||
#include "ui/gfx/image/image_skia.h" | ||
|
||
namespace ash { | ||
namespace { | ||
|
||
constexpr gfx::Size kImageSize(100, 100); | ||
|
||
image_util::AnimationFrame CreateGifFrame(base::TimeDelta duration) { | ||
return {.image = image_util::CreateEmptyImage(kImageSize), | ||
.duration = duration}; | ||
} | ||
|
||
void FetchGifFrames(std::vector<image_util::AnimationFrame> frames, | ||
PickerGifView::FramesFetchedCallback callback) { | ||
std::move(callback).Run(frames); | ||
} | ||
|
||
gfx::ImageSkia GetImage(const PickerGifView& gif_view) { | ||
return gif_view.GetImageModel().GetImage().AsImageSkia(); | ||
} | ||
|
||
TEST(PickerGifViewTest, ImageSize) { | ||
base::test::SingleThreadTaskEnvironment task_environment; | ||
|
||
constexpr gfx::Size kPreferredImageSize(200, 300); | ||
const std::vector<image_util::AnimationFrame> frames = { | ||
CreateGifFrame(base::Milliseconds(30)), | ||
CreateGifFrame(base::Milliseconds(40))}; | ||
PickerGifView gif_view(base::BindOnce(&FetchGifFrames, frames), | ||
kPreferredImageSize); | ||
|
||
EXPECT_EQ(gif_view.GetImageModel().Size(), kPreferredImageSize); | ||
EXPECT_EQ(gif_view.GetPreferredSize(), kPreferredImageSize); | ||
} | ||
|
||
TEST(PickerGifViewTest, FrameDurations) { | ||
base::test::SingleThreadTaskEnvironment task_environment( | ||
base::test::TaskEnvironment::TimeSource::MOCK_TIME); | ||
|
||
const std::vector<image_util::AnimationFrame> frames = { | ||
CreateGifFrame(base::Milliseconds(30)), | ||
CreateGifFrame(base::Milliseconds(40)), | ||
CreateGifFrame(base::Milliseconds(50))}; | ||
PickerGifView gif_view(base::BindOnce(&FetchGifFrames, frames), kImageSize); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[0].image)); | ||
|
||
task_environment.FastForwardBy(frames[0].duration); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[1].image)); | ||
|
||
task_environment.FastForwardBy(frames[1].duration); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[2].image)); | ||
|
||
task_environment.FastForwardBy(frames[2].duration); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[0].image)); | ||
} | ||
|
||
TEST(PickerGifViewTest, AdjustsShortFrameDurations) { | ||
base::test::SingleThreadTaskEnvironment task_environment( | ||
base::test::TaskEnvironment::TimeSource::MOCK_TIME); | ||
|
||
const std::vector<image_util::AnimationFrame> frames = { | ||
CreateGifFrame(base::Milliseconds(0)), | ||
CreateGifFrame(base::Milliseconds(30))}; | ||
PickerGifView gif_view(base::BindOnce(&FetchGifFrames, frames), kImageSize); | ||
|
||
// We use a duration of 100ms for frames that specify a duration of <= 10ms | ||
// (to follow the behavior of blink). | ||
task_environment.FastForwardBy(base::Milliseconds(20)); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[0].image)); | ||
|
||
task_environment.FastForwardBy(base::Milliseconds(20)); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[0].image)); | ||
|
||
task_environment.FastForwardBy(base::Milliseconds(60)); | ||
EXPECT_TRUE(GetImage(gif_view).BackedBySameObjectAs(frames[1].image)); | ||
} | ||
|
||
} // namespace | ||
} // namespace ash |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.