blob: 100b9195b6db68305486b5a11909a96870cde576 [file] [log] [blame]
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobalt/loader/image/animated_webp_image.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/loader/image/image_decoder.h"
#include "cobalt/render_tree/brush.h"
#include "cobalt/render_tree/composition_node.h"
#include "cobalt/render_tree/image_node.h"
#include "cobalt/render_tree/node.h"
#include "cobalt/render_tree/rect_node.h"
#include "nb/memory_scope.h"
#include "starboard/memory.h"
namespace cobalt {
namespace loader {
namespace image {
namespace {
const int kLoopInfinite = 0;
const int kMinimumDelayInMilliseconds = 10;
} // namespace
AnimatedWebPImage::AnimatedWebPImage(
const math::Size& size, bool is_opaque,
render_tree::PixelFormat pixel_format,
render_tree::ResourceProvider* resource_provider)
: size_(size),
is_opaque_(is_opaque),
pixel_format_(pixel_format),
demux_(NULL),
demux_state_(WEBP_DEMUX_PARSING_HEADER),
received_first_frame_(false),
is_playing_(false),
frame_count_(0),
loop_count_(kLoopInfinite),
current_frame_index_(0),
next_frame_index_(0),
should_dispose_previous_frame_to_background_(false),
resource_provider_(resource_provider),
frame_provider_(new FrameProvider()) {
TRACE_EVENT0("cobalt::loader::image",
"AnimatedWebPImage::AnimatedWebPImage()");
}
scoped_refptr<const AnimatedImage::FrameProvider>
AnimatedWebPImage::GetFrameProvider() {
TRACE_EVENT0("cobalt::loader::image",
"AnimatedWebPImage::GetFrameProvider()");
return frame_provider_;
}
void AnimatedWebPImage::Play(
const scoped_refptr<base::MessageLoopProxy>& message_loop) {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::Play()");
base::AutoLock lock(lock_);
if (is_playing_) {
return;
}
is_playing_ = true;
message_loop_ = message_loop;
if (received_first_frame_) {
PlayInternal();
}
}
void AnimatedWebPImage::Stop() {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::Stop()");
base::AutoLock lock(lock_);
if (is_playing_) {
message_loop_->PostTask(
FROM_HERE,
base::Bind(&AnimatedWebPImage::StopInternal, base::Unretained(this)));
}
}
void AnimatedWebPImage::AppendChunk(const uint8* data, size_t size) {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::AppendChunk()");
TRACK_MEMORY_SCOPE("Rendering");
base::AutoLock lock(lock_);
data_buffer_.insert(data_buffer_.end(), data, data + size);
WebPData webp_data = {&data_buffer_[0], data_buffer_.size()};
WebPDemuxDelete(demux_);
demux_ = WebPDemuxPartial(&webp_data, &demux_state_);
DCHECK_GT(demux_state_, WEBP_DEMUX_PARSING_HEADER);
// Update frame count.
int new_frame_count = WebPDemuxGetI(demux_, WEBP_FF_FRAME_COUNT);
if (new_frame_count > 0 && frame_count_ == 0) {
// We've just received the first frame.
received_first_frame_ = true;
loop_count_ = WebPDemuxGetI(demux_, WEBP_FF_LOOP_COUNT);
// The default background color of the canvas in [Blue, Green, Red, Alpha]
// byte order. It is read in little endian order as an 32bit int.
uint32_t background_color = WebPDemuxGetI(demux_, WEBP_FF_BACKGROUND_COLOR);
background_color_ =
render_tree::ColorRGBA((background_color >> 16 & 0xff) / 255.0f,
(background_color >> 8 & 0xff) / 255.0f,
(background_color & 0xff) / 255.0f,
(background_color >> 24 & 0xff) / 255.0f);
if (is_playing_) {
PlayInternal();
}
}
frame_count_ = new_frame_count;
}
AnimatedWebPImage::~AnimatedWebPImage() {
TRACE_EVENT0("cobalt::loader::image",
"AnimatedWebPImage::~AnimatedWebPImage()");
Stop();
bool is_playing = false;
{
base::AutoLock lock(lock_);
is_playing = is_playing_;
}
if (is_playing) {
message_loop_->WaitForFence();
}
WebPDemuxDelete(demux_);
}
void AnimatedWebPImage::StopInternal() {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::StopInternal()");
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock lock(lock_);
if (!decode_closure_.callback().is_null()) {
is_playing_ = false;
decode_closure_.Cancel();
}
}
void AnimatedWebPImage::PlayInternal() {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::PlayInternal()");
current_frame_time_ = base::TimeTicks::Now();
message_loop_->PostTask(
FROM_HERE,
base::Bind(&AnimatedWebPImage::DecodeFrames, base::Unretained(this)));
}
void AnimatedWebPImage::DecodeFrames() {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::DecodeFrames()");
TRACK_MEMORY_SCOPE("Rendering");
DCHECK(is_playing_ && received_first_frame_);
DCHECK(message_loop_->BelongsToCurrentThread());
base::AutoLock lock(lock_);
if (decode_closure_.callback().is_null()) {
decode_closure_.Reset(
base::Bind(&AnimatedWebPImage::DecodeFrames, base::Unretained(this)));
}
UpdateTimelineInfo();
// Decode the frames from current frame to next frame and blend the results.
for (int frame_index = current_frame_index_ + 1;
frame_index <= next_frame_index_; ++frame_index) {
if (!DecodeOneFrame(frame_index)) {
break;
}
}
current_frame_index_ = next_frame_index_;
// Set up the next time to call the decode callback.
if (is_playing_) {
base::TimeDelta delay = next_frame_time_ - base::TimeTicks::Now();
const base::TimeDelta min_delay =
base::TimeDelta::FromMilliseconds(kMinimumDelayInMilliseconds);
if (delay < min_delay) {
delay = min_delay;
}
message_loop_->PostDelayedTask(FROM_HERE, decode_closure_.callback(),
delay);
}
}
namespace {
void RecordImage(scoped_refptr<render_tree::Image>* image_pointer,
const scoped_refptr<loader::image::Image>& image) {
image::StaticImage* static_image =
base::polymorphic_downcast<loader::image::StaticImage*>(image.get());
DCHECK(static_image);
*image_pointer = static_image->image();
}
} // namespace
bool AnimatedWebPImage::DecodeOneFrame(int frame_index) {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::DecodeOneFrame()");
TRACK_MEMORY_SCOPE("Rendering");
DCHECK(message_loop_->BelongsToCurrentThread());
lock_.AssertAcquired();
WebPIterator webp_iterator;
scoped_refptr<render_tree::Image> next_frame_image;
// Decode the current frame.
{
TRACE_EVENT0("cobalt::loader::image", "Decoding");
WebPDemuxGetFrame(demux_, frame_index, &webp_iterator);
if (!webp_iterator.complete) {
return false;
}
ImageDecoder image_decoder(
resource_provider_, base::Bind(&RecordImage, &next_frame_image),
ImageDecoder::ErrorCallback(), ImageDecoder::kImageTypeWebP);
image_decoder.DecodeChunk(
reinterpret_cast<const char*>(webp_iterator.fragment.bytes),
webp_iterator.fragment.size);
image_decoder.Finish();
if (!next_frame_image) {
LOG(ERROR) << "Failed to decode WebP image frame.";
return false;
}
}
// Alpha blend the current frame on top of the buffer.
{
TRACE_EVENT0("cobalt::loader::image", "Blending");
render_tree::CompositionNode::Builder builder;
// Add the current canvas or, if there is not one, a background color
// rectangle;
if (current_canvas_) {
builder.AddChild(new render_tree::ImageNode(current_canvas_));
} else {
scoped_ptr<render_tree::Brush> brush(
new render_tree::SolidColorBrush(background_color_));
builder.AddChild(
new render_tree::RectNode(math::RectF(size_), brush.Pass()));
}
// Dispose previous frame by adding a solid rectangle.
if (should_dispose_previous_frame_to_background_) {
scoped_ptr<render_tree::Brush> brush(
new render_tree::SolidColorBrush(background_color_));
builder.AddChild(
new render_tree::RectNode(previous_frame_rect_, brush.Pass()));
}
// Add the current frame.
builder.AddChild(new render_tree::ImageNode(
next_frame_image,
math::Vector2dF(webp_iterator.x_offset, webp_iterator.y_offset)));
scoped_refptr<render_tree::Node> root =
new render_tree::CompositionNode(builder);
current_canvas_ = resource_provider_->DrawOffscreenImage(root);
frame_provider_->SetFrame(current_canvas_);
}
if (webp_iterator.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
should_dispose_previous_frame_to_background_ = true;
previous_frame_rect_ =
math::RectF(webp_iterator.x_offset, webp_iterator.y_offset,
webp_iterator.width, webp_iterator.height);
} else if (webp_iterator.dispose_method == WEBP_MUX_DISPOSE_NONE) {
should_dispose_previous_frame_to_background_ = false;
} else {
NOTREACHED();
}
WebPDemuxReleaseIterator(&webp_iterator);
return true;
}
void AnimatedWebPImage::UpdateTimelineInfo() {
TRACE_EVENT0("cobalt::loader::image",
"AnimatedWebPImage::UpdateTimelineInfo()");
TRACK_MEMORY_SCOPE("Rendering");
DCHECK(message_loop_->BelongsToCurrentThread());
lock_.AssertAcquired();
base::TimeTicks current_time = base::TimeTicks::Now();
next_frame_index_ = current_frame_index_ ? current_frame_index_ : 1;
while (true) {
// Decode frames, until a frame such that the duration covers the current
// time, i.e. the next frame should be displayed in the future.
WebPIterator webp_iterator;
WebPDemuxGetFrame(demux_, next_frame_index_, &webp_iterator);
next_frame_time_ = current_frame_time_ + base::TimeDelta::FromMilliseconds(
webp_iterator.duration);
WebPDemuxReleaseIterator(&webp_iterator);
if (current_time < next_frame_time_) {
break;
}
current_frame_time_ = next_frame_time_;
if (next_frame_index_ < frame_count_) {
next_frame_index_++;
} else {
DCHECK_EQ(next_frame_index_, frame_count_);
// If the WebP image hasn't been fully fetched, or we've reached the end
// of the last loop, then stop on the current frame.
if (demux_state_ == WEBP_DEMUX_PARSED_HEADER || loop_count_ == 1) {
break;
}
next_frame_index_ = 1;
current_frame_index_ = 0;
if (loop_count_ != kLoopInfinite) {
loop_count_--;
}
}
}
}
scoped_ptr<render_tree::ImageData> AnimatedWebPImage::AllocateImageData(
const math::Size& size) {
TRACE_EVENT0("cobalt::loader::image",
"AnimatedWebPImage::AllocateImageData()");
TRACK_MEMORY_SCOPE("Rendering");
scoped_ptr<render_tree::ImageData> image_data =
resource_provider_->AllocateImageData(
size, pixel_format_, render_tree::kAlphaFormatPremultiplied);
DCHECK(image_data) << "Failed to allocate image.";
return image_data.Pass();
}
} // namespace image
} // namespace loader
} // namespace cobalt