| // Copyright 2019 Google LLC |
| // |
| // 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 |
| // |
| // https://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. |
| // ----------------------------------------------------------------------------- |
| // |
| // Functions related to animation decoding. |
| // |
| // Author: Yannis Guyon (yguyon@google.com) |
| |
| #include <cassert> |
| |
| #include "src/common/color_precision.h" |
| #include "src/common/header_enc_dec.h" |
| #include "src/dec/wp2_dec_i.h" |
| #include "src/utils/ans_utils.h" |
| #include "src/utils/data_source.h" |
| #include "src/wp2/base.h" |
| #include "src/wp2/decode.h" |
| #include "src/wp2/format_constants.h" |
| |
| namespace WP2 { |
| |
| //------------------------------------------------------------------------------ |
| |
| WP2Status DecodeANMF(const DecoderConfig& config, DataSource* const data_source, |
| const BitstreamFeatures& features, uint32_t frame_index, |
| AnimationFrame* const frame) { |
| if (features.is_animation) { |
| const size_t num_read_bytes_before = data_source->GetNumReadBytes(); |
| BitUnpacker dec(data_source, "anmf_chunk"); |
| const uint32_t tag = dec.ReadBits(kANMFTagNumBits, "anmf_tag"); |
| frame->dispose = (tag == kANMFTagDispose); |
| frame->blend = (dec.ReadBits(1, "blend") == 1); |
| frame->is_last = (dec.ReadBits(1, "is_last") == 1); |
| // TODO(yguyon): Force read 'duration_ms>0' if kMaxNumPreframes || is_last |
| frame->duration_ms = dec.ReadVarUInt(0, kMaxFrameDurationMs, "duration"); |
| frame->window = |
| dec.ReadRect(features.raw_width, features.raw_height, "window"); |
| const WP2Status status = |
| dec.Pad() ? dec.GetStatus() : WP2_STATUS_BITSTREAM_ERROR; |
| if (status != WP2_STATUS_OK) { |
| // The data consumption must be atomic for incr dec glimpses. |
| data_source->UnmarkAllReadBytes(); |
| data_source->MarkNumBytesAsRead(num_read_bytes_before); |
| } |
| WP2_CHECK_STATUS(status); |
| |
| WP2_CHECK_OK((tag == kANMFTagDispose) || (tag == kANMFTagFrame), |
| WP2_STATUS_BITSTREAM_ERROR); |
| assert(frame->window.x + frame->window.width <= features.raw_width); |
| assert(frame->window.y + frame->window.height <= features.raw_height); |
| if (frame_index == 0) { // The first frame MUST dispose. |
| WP2_CHECK_OK(frame->dispose, WP2_STATUS_BITSTREAM_ERROR); |
| } else { |
| WP2_CHECK_OK(frame_index < kMaxNumFrames, WP2_STATUS_BITSTREAM_ERROR); |
| if (frame->duration_ms == 0) { |
| WP2_CHECK_OK(!frame->is_last, WP2_STATUS_BITSTREAM_ERROR); |
| } |
| } |
| WP2_CHECK_REDUCED_STATUS( |
| RegisterBitTrace(config, |
| data_source->GetNumReadBytes() - num_read_bytes_before, "ANMF")); |
| } else { |
| frame->dispose = true; |
| frame->blend = false; |
| frame->duration_ms = kMaxFrameDurationMs; |
| frame->window = {0, 0, features.raw_width, features.raw_height}; |
| frame->is_last = true; |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Fills the area outside of 'frame.window' with 'features.background_color' if |
| // 'frame.dispose' is true. The destination is the non-null buffer among |
| // 'rgb_output' and 'yuv_output'. |
| static WP2Status FillBorders(const BitstreamFeatures& features, |
| const AnimationFrame& frame, |
| ArgbBuffer* const rgb_output, |
| const CSPTransform* const csp_transform, |
| YUVPlane* const yuv_output) { |
| if (!frame.dispose) return WP2_STATUS_OK; |
| |
| uint32_t width, height; |
| if (rgb_output != nullptr) { |
| WP2_CHECK_OK(yuv_output == nullptr, WP2_STATUS_INVALID_PARAMETER); |
| width = rgb_output->width(); |
| height = rgb_output->height(); |
| } else { |
| WP2_CHECK_OK(yuv_output != nullptr, WP2_STATUS_INVALID_PARAMETER); |
| width = yuv_output->GetWidth(); |
| height = yuv_output->GetHeight(); |
| } |
| |
| const Rectangle& window = frame.window; |
| assert(window.x + window.width <= width); |
| assert(window.y + window.height <= height); |
| Rectangle top, bot, lft, rgt; |
| Rectangle(0, 0, width, height).Exclude(window, &top, &bot, &lft, &rgt); |
| |
| if (rgb_output != nullptr) { |
| if (WP2Formatbpc(rgb_output->format()) <= 8) { |
| const Argb32b color = ToArgb32b(features.background_color); |
| |
| rgb_output->Fill(top, color); |
| rgb_output->Fill(bot, color); |
| rgb_output->Fill(lft, color); |
| rgb_output->Fill(rgt, color); |
| } else { |
| rgb_output->Fill(top, features.background_color); |
| rgb_output->Fill(bot, features.background_color); |
| rgb_output->Fill(lft, features.background_color); |
| rgb_output->Fill(rgt, features.background_color); |
| } |
| } else { |
| WP2_CHECK_OK(csp_transform != nullptr, WP2_STATUS_INVALID_PARAMETER); |
| const Ayuv38b color = |
| csp_transform->ToYuv(ToArgb32b(features.background_color)); |
| |
| yuv_output->Fill(top, color); |
| yuv_output->Fill(bot, color); |
| yuv_output->Fill(lft, color); |
| yuv_output->Fill(rgt, color); |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status FillBorders(const BitstreamFeatures& features, |
| const AnimationFrame& frame, ArgbBuffer* const output) { |
| return FillBorders(features, frame, output, |
| /*csp_transform=*/nullptr, /*yuv_output=*/nullptr); |
| } |
| |
| WP2Status FillBorders(const BitstreamFeatures& features, |
| const AnimationFrame& frame, |
| const CSPTransform& csp_transform, |
| YUVPlane* const output) { |
| return FillBorders(features, frame, /*rgb_output=*/nullptr, |
| /*csp_transform=*/&csp_transform, /*yuv_output=*/output); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| } // namespace WP2 |