Commit 7dbdfb95 authored by Jacob Dufault's avatar Jacob Dufault Committed by Commit Bot

cros: Show animated avatars in views-based lock.

This adds support for parsing APNG images by hooking into the
data_decoder service. webkit::WebImage API is extended to support
fetching the relevant animation data from blink's PNG decoder.

Bug: 750064, 719015
Change-Id: I7fb38643dce5d6e16eaaee2b6f74253ba70af20a
Reviewed-on: https://chromium-review.googlesource.com/679459
Commit-Queue: Jacob Dufault <jdufault@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#506768}
parent ab056206
......@@ -219,6 +219,11 @@ component("ash") {
"login/lock_screen_apps_focus_observer.h",
"login/lock_screen_controller.cc",
"login/lock_screen_controller.h",
"login/ui/animated_rounded_image_view.cc",
"login/ui/animated_rounded_image_view.h",
"login/ui/animation_frame.h",
"login/ui/image_parser.cc",
"login/ui/image_parser.h",
"login/ui/layout_util.cc",
"login/ui/layout_util.h",
"login/ui/lock_contents_view.cc",
......@@ -937,6 +942,7 @@ component("ash") {
"//gpu/command_buffer/client:gles2_interface",
"//media",
"//net",
"//services/data_decoder/public/cpp",
"//services/preferences/public/cpp",
"//services/service_manager/public/cpp",
"//services/ui/public/cpp",
......
include_rules = [
"+chromeos/cryptohome",
"+services/data_decoder/public/cpp/decode_image.h",
]
// Copyright 2017 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.
#include "ash/login/ui/animated_rounded_image_view.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/skia_util.h"
namespace ash {
AnimatedRoundedImageView::AnimatedRoundedImageView(int corner_radius)
: corner_radius_(corner_radius) {}
AnimatedRoundedImageView::~AnimatedRoundedImageView() {}
void AnimatedRoundedImageView::SetAnimation(const AnimationFrames& animation,
const gfx::Size& size) {
image_size_ = size;
frames_.clear();
frames_.reserve(animation.size());
for (AnimationFrame frame : animation) {
// Try to get the best image quality for the animation.
frame.image = gfx::ImageSkiaOperations::CreateResizedImage(
frame.image, skia::ImageOperations::RESIZE_BEST, size);
DCHECK(frame.image.bitmap()->isImmutable());
frames_.emplace_back(frame);
}
StartOrStopAnimation();
}
void AnimatedRoundedImageView::SetImage(const gfx::ImageSkia& image,
const gfx::Size& size) {
AnimationFrame frame;
frame.image = image;
SetAnimation({frame}, size);
}
void AnimatedRoundedImageView::SetAnimationEnabled(bool enabled) {
should_animate_ = enabled;
StartOrStopAnimation();
}
gfx::Size AnimatedRoundedImageView::CalculatePreferredSize() const {
return gfx::Size(image_size_.width() + GetInsets().width(),
image_size_.height() + GetInsets().height());
}
void AnimatedRoundedImageView::OnPaint(gfx::Canvas* canvas) {
if (frames_.empty())
return;
View::OnPaint(canvas);
gfx::Rect image_bounds(GetContentsBounds());
image_bounds.ClampToCenteredSize(GetPreferredSize());
const SkScalar kRadius[8] = {
SkIntToScalar(corner_radius_), SkIntToScalar(corner_radius_),
SkIntToScalar(corner_radius_), SkIntToScalar(corner_radius_),
SkIntToScalar(corner_radius_), SkIntToScalar(corner_radius_),
SkIntToScalar(corner_radius_), SkIntToScalar(corner_radius_)};
SkPath path;
path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius);
cc::PaintFlags flags;
flags.setAntiAlias(true);
canvas->DrawImageInPath(frames_[active_frame_].image, image_bounds.x(),
image_bounds.y(), path, flags);
}
void AnimatedRoundedImageView::StartOrStopAnimation() {
// If animation is disabled or if there are less than 2 frames, show a static
// image.
if (!should_animate_ || frames_.size() < 2) {
active_frame_ = 0;
update_frame_timer_.Stop();
SchedulePaint();
return;
}
// Start animation.
active_frame_ = -1;
UpdateAnimationFrame();
}
void AnimatedRoundedImageView::UpdateAnimationFrame() {
DCHECK(!frames_.empty());
// Note: |active_frame_| may be invalid.
active_frame_ = (active_frame_ + 1) % frames_.size();
SchedulePaint();
// Schedule next frame update.
update_frame_timer_.Start(
FROM_HERE, frames_[active_frame_].duration,
base::BindRepeating(&AnimatedRoundedImageView::UpdateAnimationFrame,
base::Unretained(this)));
}
} // namespace ash
// Copyright 2017 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 ASH_LOGIN_UI_ANIMATED_ROUNDED_IMAGE_VIEW_H_
#define ASH_LOGIN_UI_ANIMATED_ROUNDED_IMAGE_VIEW_H_
#include <vector>
#include "ash/login/ui/animation_frame.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/view.h"
namespace ash {
// A custom image view with rounded edges.
class AnimatedRoundedImageView : public views::View {
public:
// Constructs a new rounded image view with rounded corners of radius
// |corner_radius|.
explicit AnimatedRoundedImageView(int corner_radius);
~AnimatedRoundedImageView() override;
// Show an animation.
void SetAnimation(const AnimationFrames& animation, const gfx::Size& size);
// Show a static image.
void SetImage(const gfx::ImageSkia& image, const gfx::Size& size);
// Start or stop animation.
void SetAnimationEnabled(bool enabled);
// Overridden from views::View.
gfx::Size CalculatePreferredSize() const override;
void OnPaint(gfx::Canvas* canvas) override;
private:
void StartOrStopAnimation();
void UpdateAnimationFrame();
// If true and there are multiple frames, the animation will play. If false,
// only the first frame in the animation will be shown.
bool should_animate_ = false;
// Currently displayed animation frame.
int active_frame_ = 0;
AnimationFrames frames_;
gfx::Size image_size_;
const int corner_radius_;
base::OneShotTimer update_frame_timer_;
DISALLOW_COPY_AND_ASSIGN(AnimatedRoundedImageView);
};
} // namespace ash
#endif // ASH_LOGIN_UI_ANIMATED_ROUNDED_IMAGE_VIEW_H_
// Copyright 2017 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 ASH_LOGIN_UI_ANIMATION_FRAME_H_
#define ASH_LOGIN_UI_ANIMATION_FRAME_H_
#include <vector>
#include "base/time/time.h"
#include "ui/gfx/image/image_skia.h"
namespace ash {
// A frame of an animation, which contains an image and the length that image
// should be display.
struct AnimationFrame {
gfx::ImageSkia image;
base::TimeDelta duration;
};
using AnimationFrames = std::vector<AnimationFrame>;
} // namespace ash
#endif // ASH_LOGIN_UI_ANIMATION_FRAME_H_
\ No newline at end of file
// Copyright 2017 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.
#include "ash/login/ui/image_parser.h"
#include <utility>
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ipc/ipc_channel.h"
#include "services/data_decoder/public/cpp/decode_image.h"
namespace ash {
namespace {
const int64_t kMaxImageSizeInBytes =
static_cast<int64_t>(IPC::Channel::kMaximumMessageSize);
void ConvertToAnimationFrame(
OnDecoded callback,
std::vector<data_decoder::mojom::AnimationFramePtr> mojo_frames) {
AnimationFrames animation;
for (auto& mojo_frame : mojo_frames) {
AnimationFrame frame;
frame.image = gfx::ImageSkia::CreateFrom1xBitmap(mojo_frame->bitmap);
frame.duration = mojo_frame->duration;
animation.push_back(frame);
}
std::move(callback).Run(std::move(animation));
}
} // namespace
void DecodeAnimation(const std::vector<uint8_t>& image_data,
OnDecoded on_decoded) {
// There are two reasons to decode the image in a sandboxed process:
// - image_data may have come from the user, so it cannot be trusted.
// - PNGCodec::Decode uses libpng which does not support APNG. blink::WebImage
// also goes through libpng, but APNG support is handled specifically by
// blink's PNGImageReader.cpp.
data_decoder::DecodeAnimation(
Shell::Get()->shell_delegate()->GetShellConnector(), image_data,
true /*shrink_to_fit*/, kMaxImageSizeInBytes,
base::Bind(&ConvertToAnimationFrame, Passed(&on_decoded)));
}
} // namespace ash
\ No newline at end of file
// Copyright 2017 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 ASH_LOGIN_UI_IMAGE_PARSER_H_
#define ASH_LOGIN_UI_IMAGE_PARSER_H_
#include <vector>
#include "ash/ash_export.h"
#include "ash/login/ui/animation_frame.h"
#include "base/callback_forward.h"
namespace ash {
using OnDecoded = base::OnceCallback<void(AnimationFrames animation)>;
// Do an async animation decode; |on_decoded| is run on the calling thread when
// the decode has finished.
//
// This uses blink's image decoder and supports APNG.
ASH_EXPORT void DecodeAnimation(const std::vector<uint8_t>& image_data,
OnDecoded on_decoded);
} // namespace ash
#endif // ASH_LOGIN_UI_IMAGE_PARSER_H_
\ No newline at end of file
......@@ -5,6 +5,8 @@
#include "ash/login/ui/login_user_view.h"
#include "ash/ash_constants.h"
#include "ash/login/ui/animated_rounded_image_view.h"
#include "ash/login/ui/image_parser.h"
#include "ash/login/ui/login_bubble.h"
#include "ash/login/ui/login_constants.h"
#include "ash/login/ui/non_accessible_view.h"
......@@ -12,6 +14,7 @@
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/user/rounded_image_view.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer_animation_sequence.h"
......@@ -85,25 +88,53 @@ views::View* MakePreferredSizeView(gfx::Size size) {
class LoginUserView::UserImage : public NonAccessibleView {
public:
UserImage(int size)
: NonAccessibleView(kLoginUserImageClassName), size_(size) {
: NonAccessibleView(kLoginUserImageClassName),
size_(size),
weak_factory_(this) {
SetLayoutManager(new views::FillLayout());
// TODO(jdufault): We need to render a black border. We will probably have
// to add support directly to RoundedImageView, since the existing
// to add support directly to AnimatedRoundedImageView, since the existing
// views::Border renders based on bounds (ie, a rectangle).
image_ = new tray::RoundedImageView(size_ / 2);
image_ = new AnimatedRoundedImageView(size_ / 2);
AddChildView(image_);
}
~UserImage() override = default;
void UpdateForUser(const mojom::LoginUserInfoPtr& user) {
image_->SetImage(user->basic_user_info->avatar, gfx::Size(size_, size_));
// Set the initial image from |avatar| since we already have it available.
// Then, decode the bytes via blink's PNG decoder and play any animated
// frames if they are available.
if (!user->basic_user_info->avatar.isNull())
image_->SetImage(user->basic_user_info->avatar, gfx::Size(size_, size_));
// Decode the avatar using blink, as blink's PNG decoder supports APNG,
// which is the format used for the animated avators.
if (!user->basic_user_info->avatar_bytes.empty()) {
DecodeAnimation(user->basic_user_info->avatar_bytes,
base::Bind(&LoginUserView::UserImage::OnImageDecoded,
weak_factory_.GetWeakPtr()));
}
}
void SetAnimationEnabled(bool enable) { image_->SetAnimationEnabled(enable); }
private:
tray::RoundedImageView* image_ = nullptr;
void OnImageDecoded(AnimationFrames animation) {
// If there is only a single frame to display, show the existing avatar.
if (animation.size() <= 1) {
LOG_IF(ERROR, animation.empty()) << "Decoding user avatar failed";
return;
}
image_->SetAnimation(animation, gfx::Size(size_, size_));
}
AnimatedRoundedImageView* image_ = nullptr;
int size_;
base::WeakPtrFactory<UserImage> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(UserImage);
};
......@@ -416,6 +447,9 @@ void LoginUserView::UpdateOpacity() {
auto user_dropdown_settings = build_settings(user_dropdown_);
user_dropdown_->layer()->SetOpacity(target_opacity);
}
// Animate avatar only if we are opaque.
user_image_->SetAnimationEnabled(is_opaque_);
}
void LoginUserView::SetLargeLayout() {
......
......@@ -44,6 +44,10 @@ struct UserInfo {
string display_name;
string display_email;
gfx.mojom.ImageSkia avatar;
// The raw bytes for the avatar. Useful if the avatar is animated.
// TODO(crbug.com/770373): Use a shared buffer (mojo.Blob), as this may be
// large enough to congest IPC.
array<uint8> avatar_bytes;
// True if this user has a newly created profile (first time login on the
// device)
bool is_new_profile;
......
......@@ -26,6 +26,7 @@
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/ui/views/user_board_view.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/default_user_image/default_user_images.h"
#include "chrome/browser/chromeos/login/users/multi_profile_user_controller.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
......@@ -96,8 +97,8 @@ void AddPublicSessionDetailsToUserDictionaryEntry(
std::vector<std::string> kEmptyRecommendedLocales;
const std::vector<std::string>& recommended_locales =
public_session_recommended_locales ?
*public_session_recommended_locales : kEmptyRecommendedLocales;
public_session_recommended_locales ? *public_session_recommended_locales
: kEmptyRecommendedLocales;
// Construct the list of available locales. This list consists of the
// recommended locales, followed by all others.
......@@ -106,10 +107,9 @@ void AddPublicSessionDetailsToUserDictionaryEntry(
// Select the the first recommended locale that is actually available or the
// current UI locale if none of them are available.
const std::string selected_locale = FindMostRelevantLocale(
recommended_locales,
*available_locales.get(),
g_browser_process->GetApplicationLocale());
const std::string selected_locale =
FindMostRelevantLocale(recommended_locales, *available_locales.get(),
g_browser_process->GetApplicationLocale());
// Set |kKeyInitialLocales| to the list of available locales.
user_dict->Set(kKeyInitialLocales, std::move(available_locales));
......@@ -453,12 +453,36 @@ void UserSelectionScreen::FillUserMojoStruct(
user_info->basic_user_info->display_name =
base::UTF16ToUTF8(user->GetDisplayName());
user_info->basic_user_info->display_email = user->display_email();
user_info->basic_user_info->avatar = user->GetImage();
if (user_info->basic_user_info->avatar.isNull()) {
if (!user->GetImage().isNull()) {
user_info->basic_user_info->avatar = user->GetImage();
} else {
user_info->basic_user_info->avatar =
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_LOGIN_DEFAULT_USER);
}
// TODO(jdufault): Unify image handling between this code and
// user_image_source::GetUserImageInternal.
auto load_image_from_resource = [&](int resource_id) {
auto& rb = ui::ResourceBundle::GetSharedInstance();
base::StringPiece avatar =
rb.GetRawDataResourceForScale(resource_id, rb.GetMaxScaleFactor());
user_info->basic_user_info->avatar_bytes.assign(avatar.begin(),
avatar.end());
};
if (user->has_image_bytes()) {
user_info->basic_user_info->avatar_bytes.assign(
user->image_bytes()->front(),
user->image_bytes()->front() + user->image_bytes()->size());
} else if (user->HasDefaultImage()) {
int resource_id = chromeos::default_user_image::kDefaultImageResourceIDs
[user->image_index()];
load_image_from_resource(resource_id);
} else if (user->image_is_stub()) {
load_image_from_resource(IDR_LOGIN_DEFAULT_USER);
}
user_info->auth_type = auth_type;
user_info->is_signed_in = user->is_logged_in();
user_info->is_device_owner = is_owner;
......@@ -527,9 +551,7 @@ void UserSelectionScreen::OnPasswordClearTimerExpired() {
void UserSelectionScreen::OnUserActivity(const ui::Event* event) {
if (!password_clear_timer_.IsRunning()) {
password_clear_timer_.Start(
FROM_HERE,
base::TimeDelta::FromSeconds(kPasswordClearTimeoutSec),
this,
FROM_HERE, base::TimeDelta::FromSeconds(kPasswordClearTimeoutSec), this,
&UserSelectionScreen::OnPasswordClearTimerExpired);
}
password_clear_timer_.Reset();
......@@ -546,15 +568,13 @@ const user_manager::UserList UserSelectionScreen::PrepareUserListForSending(
size_t non_owner_count = 0;
for (user_manager::UserList::const_iterator it = users.begin();
it != users.end();
++it) {
it != users.end(); ++it) {
bool is_owner = ((*it)->GetAccountId() == owner);
bool is_public_account =
((*it)->GetType() == user_manager::USER_TYPE_PUBLIC_ACCOUNT);
if ((is_public_account && !is_signin_to_add) || is_owner ||
(!is_public_account && non_owner_count < max_non_owner_users)) {
if (!is_owner)
++non_owner_count;
if (is_owner && users_to_send.size() > kMaxUsers) {
......
......@@ -27,6 +27,35 @@ namespace {
int64_t kPadding = 64;
void ResizeImage(SkBitmap* decoded_image,
bool shrink_to_fit,
int64_t max_size_in_bytes) {
// When serialized, the space taken up by a skia::mojom::Bitmap excluding
// the pixel data payload should be:
// sizeof(skia::mojom::Bitmap::Data_) + pixel data array header (8 bytes)
// Use a bigger number in case we need padding at the end.
int64_t struct_size = sizeof(skia::mojom::Bitmap::Data_) + kPadding;
int64_t image_size = decoded_image->computeSize64();
int halves = 0;
while (struct_size + (image_size >> 2 * halves) > max_size_in_bytes)
halves++;
if (halves) {
// If the decoded image is too large, either discard it or shrink it.
//
// TODO(rockot): Also support exposing the bytes via shared memory for
// larger images. https://crbug.com/416916.
if (shrink_to_fit) {
// Shrinking by halves prevents quality loss and should never
// overshrink on displays smaller than 3600x2400.
*decoded_image = skia::ImageOperations::Resize(
*decoded_image, skia::ImageOperations::RESIZE_LANCZOS3,
decoded_image->width() >> halves, decoded_image->height() >> halves);
} else {
decoded_image->reset();
}
}
}
} // namespace
ImageDecoderImpl::ImageDecoderImpl(
......@@ -76,34 +105,44 @@ void ImageDecoderImpl::DecodeImage(const std::vector<uint8_t>& encoded_data,
.GetSkBitmap();
}
if (!decoded_image.isNull()) {
// When serialized, the space taken up by a skia::mojom::Bitmap excluding
// the pixel data payload should be:
// sizeof(skia::mojom::Bitmap::Data_) + pixel data array header (8 bytes)
// Use a bigger number in case we need padding at the end.
int64_t struct_size = sizeof(skia::mojom::Bitmap::Data_) + kPadding;
int64_t image_size = decoded_image.computeSize64();
int halves = 0;
while (struct_size + (image_size >> 2 * halves) > max_size_in_bytes)
halves++;
if (halves) {
// If the decoded image is too large, either discard it or shrink it.
//
// TODO(rockot): Also support exposing the bytes via shared memory for
// larger images. https://crbug.com/416916.
if (shrink_to_fit) {
// Shrinking by halves prevents quality loss and should never overshrink
// on displays smaller than 3600x2400.
decoded_image = skia::ImageOperations::Resize(
decoded_image, skia::ImageOperations::RESIZE_LANCZOS3,
decoded_image.width() >> halves, decoded_image.height() >> halves);
} else {
decoded_image.reset();
}
if (!decoded_image.isNull())
ResizeImage(&decoded_image, shrink_to_fit, max_size_in_bytes);
std::move(callback).Run(decoded_image);
}
void ImageDecoderImpl::DecodeAnimation(const std::vector<uint8_t>& encoded_data,
bool shrink_to_fit,
int64_t max_size_in_bytes,
DecodeAnimationCallback callback) {
if (encoded_data.size() == 0) {
std::move(callback).Run(std::vector<mojom::AnimationFramePtr>());
return;
}
auto frames = blink::WebImage::AnimationFromData(blink::WebData(
reinterpret_cast<const char*>(encoded_data.data()), encoded_data.size()));
int64_t max_frame_size_in_bytes = max_size_in_bytes / frames.size();
std::vector<mojom::AnimationFramePtr> decoded_images;
for (const blink::WebImage::AnimationFrame& frame : frames) {
auto image_frame = mojom::AnimationFrame::New();
image_frame->bitmap = frame.bitmap;
image_frame->duration = frame.duration;
ResizeImage(&image_frame->bitmap, shrink_to_fit, max_frame_size_in_bytes);
// Resizing reset the frame because it was too large. Clear out any
// previously decoded frames so we do not return a partially decoded image.
if (image_frame->bitmap.isNull()) {
decoded_images.clear();
break;
}
decoded_images.push_back(std::move(image_frame));
}
std::move(callback).Run(decoded_image);
std::move(callback).Run(std::move(decoded_images));
}
} // namespace data_decoder
......@@ -27,6 +27,10 @@ class ImageDecoderImpl : public mojom::ImageDecoder {
int64_t max_size_in_bytes,
const gfx::Size& desired_image_frame_size,
DecodeImageCallback callback) override;
void DecodeAnimation(const std::vector<uint8_t>& encoded_data,
bool shrink_to_fit,
int64_t max_size_in_bytes,
DecodeAnimationCallback callback) override;
private:
const std::unique_ptr<service_manager::ServiceContextRef> service_ref_;
......
......@@ -4,6 +4,9 @@
#include "services/data_decoder/public/cpp/decode_image.h"
#include <utility>
#include "base/bind_helpers.h"
#include "services/data_decoder/public/interfaces/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/skia/include/core/SkBitmap.h"
......@@ -21,6 +24,12 @@ void OnDecodeImage(mojom::ImageDecoderPtr decoder,
std::move(callback).Run(bitmap);
}
void OnDecodeImages(mojom::ImageDecoderPtr decoder,
mojom::ImageDecoder::DecodeAnimationCallback callback,
std::vector<mojom::AnimationFramePtr> bitmaps) {
std::move(callback).Run(std::move(bitmaps));
}
} // namespace
void DecodeImage(service_manager::Connector* connector,
......@@ -44,4 +53,24 @@ void DecodeImage(service_manager::Connector* connector,
base::BindOnce(&OnDecodeImage, std::move(decoder), std::move(call_once)));
}
void DecodeAnimation(service_manager::Connector* connector,
const std::vector<uint8_t>& encoded_bytes,
bool shrink_to_fit,
uint64_t max_size_in_bytes,
mojom::ImageDecoder::DecodeAnimationCallback callback) {
mojom::ImageDecoderPtr decoder;
connector->BindInterface(mojom::kServiceName, &decoder);
// |call_once| runs |callback| on its first invocation.
auto call_once = base::AdaptCallbackForRepeating(std::move(callback));
decoder.set_connection_error_handler(base::Bind(
call_once, base::Passed(std::vector<mojom::AnimationFramePtr>())));
mojom::ImageDecoder* raw_decoder = decoder.get();
raw_decoder->DecodeAnimation(
encoded_bytes, shrink_to_fit, max_size_in_bytes,
base::BindOnce(&OnDecodeImages, std::move(decoder),
std::move(call_once)));
}
} // namespace data_decoder
......@@ -40,6 +40,15 @@ void DecodeImage(service_manager::Connector* connector,
const gfx::Size& desired_image_frame_size,
mojom::ImageDecoder::DecodeImageCallback callback);
// Helper function to decode an animation via the data_decoder service. Any
// image with multiple frames is considered an animation, so long as the frames
// are all the same size.
void DecodeAnimation(service_manager::Connector* connector,
const std::vector<uint8_t>& encoded_bytes,
bool shrink_to_fit,
uint64_t max_size_in_bytes,
mojom::ImageDecoder::DecodeAnimationCallback callback);
} // namespace data_decoder
#endif // SERVICES_DATA_DECODER_PUBLIC_CPP_DECODE_H_
......@@ -11,6 +11,7 @@ mojom("interfaces") {
public_deps = [
":constants",
"//mojo/common:common_custom_types",
"//skia/public/interfaces",
"//ui/gfx/geometry/mojo",
]
......
......@@ -4,6 +4,7 @@
module data_decoder.mojom;
import "mojo/common/time.mojom";
import "skia/public/interfaces/bitmap.mojom";
import "ui/gfx/geometry/mojo/geometry.mojom";
......@@ -13,6 +14,11 @@ enum ImageCodec {
ROBUST_PNG,
};
struct AnimationFrame {
skia.mojom.Bitmap bitmap;
mojo.common.mojom.TimeDelta duration;
};
interface ImageDecoder {
// Decodes image data to a raw skia bitmap.
//
......@@ -26,4 +32,18 @@ interface ImageDecoder {
DecodeImage(array<uint8> encoded_data, ImageCodec codec, bool shrink_to_fit,
int64 max_size_in_bytes, gfx.mojom.Size desired_image_frame_size)
=> (skia.mojom.Bitmap? decoded_image);
// Decodes the image in |encoded_data|. This will return all frames in the
// image and assumes it is an animation (instead of say, a multi-sized image).
//
// If the total size of the decoded image data in bytes exceeds
// |max_size_in_bytes| and |shrink_to_fit| is true, the image is halved
// successively until its total size no longer exceeds |max_size_in_bytes|.
//
// If the total size of the decoded image data in bytes exceeds
// |max_size_in_bytes| and |shrink_to_fit| is false, this is treated as a
// decoding failure and the |decoded_image| response is null.
DecodeAnimation(array<uint8> encoded_data, bool shrink_to_fit,
int64 max_size_in_bytes)
=> (array<AnimationFrame> decoded_image);
};
......@@ -110,6 +110,49 @@ WebVector<WebImage> WebImage::FramesFromData(const WebData& data) {
return frames;
}
WebVector<WebImage::AnimationFrame> WebImage::AnimationFromData(
const WebData& data) {
std::unique_ptr<ImageDecoder> decoder(ImageDecoder::Create(
data, true, ImageDecoder::kAlphaPremultiplied, ColorBehavior::Ignore()));
if (!decoder || !decoder->IsSizeAvailable() || decoder->FrameCount() == 0)
return WebVector<WebImage::AnimationFrame>();
const size_t frame_count = decoder->FrameCount();
IntSize last_size = decoder->FrameSizeAtIndex(0);
Vector<WebImage::AnimationFrame> frames;
frames.ReserveCapacity(frame_count);
for (size_t i = 0; i < frame_count; ++i) {
// If frame size changes, this is most likely not an animation and is
// instead an image with multiple versions at different resolutions. If
// that's the case, return only the first frame (or no frames if we failed
// decoding the first one).
if (last_size != decoder->FrameSizeAtIndex(i)) {
frames.resize(frames.IsEmpty() ? 0 : 1);
return frames;
}
last_size = decoder->FrameSizeAtIndex(i);
ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i);
SkBitmap bitmap = frame->Bitmap();
if (bitmap.isNull() || frame->GetStatus() != ImageFrame::kFrameComplete)
continue;
// Make the bitmap a deep copy, otherwise the next loop iteration will
// replace the contents of the previous frame. DecodeFrameBufferAtIndex
// reuses the same underlying pixel buffer.
bitmap.setImmutable();
AnimationFrame output;
output.bitmap = bitmap;
output.duration = frame->Duration();
frames.push_back(output);
}
return frames;
}
void WebImage::Reset() {
bitmap_.reset();
}
......
......@@ -34,6 +34,7 @@
#include "WebCommon.h"
#include "WebVector.h"
#include "base/time/time.h"
#include "third_party/skia/include/core/SkBitmap.h"
#if INSIDE_BLINK
......@@ -49,6 +50,13 @@ struct WebSize;
// A container for an ARGB bitmap.
class WebImage {
public:
// An image with a duration associated. An animation is a sequence of
// AnimationFrames played in succession.
struct AnimationFrame {
SkBitmap bitmap;
base::TimeDelta duration;
};
~WebImage() { Reset(); }
WebImage() { Init(); }
......@@ -73,6 +81,10 @@ class WebImage {
BLINK_PLATFORM_EXPORT static WebVector<WebImage> FramesFromData(
const WebData&);
// Returns a list of all animation frames in the image.
BLINK_PLATFORM_EXPORT static WebVector<AnimationFrame> AnimationFromData(
const WebData&);
BLINK_PLATFORM_EXPORT void Reset();
BLINK_PLATFORM_EXPORT void Assign(const WebImage&);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment