| /* |
| * 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 <string> |
| |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/loader/image/image_decoder.h" |
| #include "cobalt/render_tree/brush.h" |
| #include "cobalt/render_tree/clear_rect_node.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::ResourceProvider* resource_provider) |
| : size_(size), |
| is_opaque_(is_opaque), |
| 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), |
| should_dispose_previous_frame_to_background_(false), |
| resource_provider_(resource_provider), |
| frame_provider_(new FrameProvider()) { |
| TRACE_EVENT0("cobalt::loader::image", |
| "AnimatedWebPImage::AnimatedWebPImage()"); |
| } |
| |
| scoped_refptr<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], |
| // from most significant byte to least significant byte. |
| uint32_t background_color = WebPDemuxGetI(demux_, WEBP_FF_BACKGROUND_COLOR); |
| background_color_ = |
| render_tree::ColorRGBA((background_color >> 8 & 0xff) / 255.0f, |
| (background_color >> 16 & 0xff) / 255.0f, |
| (background_color >> 24 & 0xff) / 255.0f, |
| (background_color >> 0 & 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))); |
| } |
| |
| if (AdvanceFrame()) { |
| // Decode the frames from current frame to next frame and blend the results. |
| DecodeOneFrame(current_frame_index_); |
| } |
| |
| // Set up the next time to call the decode callback. |
| if (is_playing_) { |
| const base::TimeDelta min_delay = |
| base::TimeDelta::FromMilliseconds(kMinimumDelayInMilliseconds); |
| base::TimeDelta delay; |
| if (next_frame_time_) { |
| delay = *next_frame_time_ - base::TimeTicks::Now(); |
| if (delay < min_delay) { |
| delay = min_delay; |
| } |
| } else { |
| 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(); |
| } |
| |
| void DecodeError(const std::string& error) { |
| LOG(ERROR) << error; |
| } |
| |
| } // 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), |
| base::Bind(&DecodeError), 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 { |
| builder.AddChild(new render_tree::ClearRectNode(math::RectF(size_), |
| background_color_)); |
| } |
| // Dispose previous frame by adding a solid rectangle. |
| if (should_dispose_previous_frame_to_background_) { |
| builder.AddChild(new render_tree::ClearRectNode(previous_frame_rect_, |
| background_color_)); |
| } |
| |
| // Add the current frame. |
| if (webp_iterator.blend_method == WEBP_MUX_NO_BLEND) { |
| // If blending is disabled, first clear the image region to transparent |
| // before rendering. |
| builder.AddChild(new render_tree::ClearRectNode( |
| math::RectF( |
| math::PointF(webp_iterator.x_offset, webp_iterator.y_offset), |
| next_frame_image->GetSize()), |
| render_tree::ColorRGBA(0, 0, 0, 0))); |
| } |
| |
| 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; |
| } |
| |
| bool AnimatedWebPImage::AdvanceFrame() { |
| TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::AdvanceFrame()"); |
| TRACK_MEMORY_SCOPE("Rendering"); |
| DCHECK(message_loop_->BelongsToCurrentThread()); |
| lock_.AssertAcquired(); |
| |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| |
| // If the WebP image hasn't been fully fetched, then stop on the current |
| // frame. |
| if (demux_state_ == WEBP_DEMUX_PARSED_HEADER) { |
| return false; |
| } |
| |
| // If we're done playing the animation, do nothing. |
| if (LoopingFinished()) { |
| return false; |
| } |
| |
| // If it's still not time to advance to the next frame, do nothing. |
| if (next_frame_time_ && current_time < *next_frame_time_) { |
| return false; |
| } |
| |
| // Always wait for a consumer to consume the previous frame before moving |
| // forward with decoding the next frame. |
| if (!frame_provider_->FrameConsumed()) { |
| return false; |
| } |
| |
| if (next_frame_time_) { |
| current_frame_time_ = *next_frame_time_; |
| } else { |
| current_frame_time_ = current_time; |
| } |
| |
| ++current_frame_index_; |
| if (current_frame_index_ == frame_count_) { |
| // Check if we have finished looping, and if so return indicating that there |
| // is no additional frame available. |
| if (LoopingFinished()) { |
| next_frame_time_ = base::nullopt; |
| return false; |
| } |
| |
| // Loop around to the beginning |
| current_frame_index_ = 0; |
| if (loop_count_ != kLoopInfinite) { |
| loop_count_--; |
| } |
| } |
| |
| // Update the time in the future at which point we should switch to the |
| // frame after the new current frame. |
| next_frame_time_ = |
| current_frame_time_ + GetFrameDuration(current_frame_index_); |
| if (next_frame_time_ < current_time) { |
| // Don't let the animation fall back for more than a frame. |
| next_frame_time_ = current_time; |
| } |
| |
| return true; |
| } |
| |
| base::TimeDelta AnimatedWebPImage::GetFrameDuration(int frame_index) { |
| lock_.AssertAcquired(); |
| WebPIterator webp_iterator; |
| WebPDemuxGetFrame(demux_, frame_index, &webp_iterator); |
| base::TimeDelta frame_duration = |
| base::TimeDelta::FromMilliseconds(webp_iterator.duration); |
| WebPDemuxReleaseIterator(&webp_iterator); |
| return frame_duration; |
| } |
| |
| bool AnimatedWebPImage::LoopingFinished() const { |
| return loop_count_ == 1 && current_frame_index_ == frame_count_; |
| } |
| |
| } // namespace image |
| } // namespace loader |
| } // namespace cobalt |