blob: dca7fb17566abd8769723a64edbe1e9e6164b948 [file]
// Copyright 2017 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fxcodec/gif/cfx_gifcontext.h"
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <array>
#include <iterator>
#include <optional>
#include <utility>
#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcrt/byteorder.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/to_underlying.h"
namespace fxcodec {
namespace {
constexpr std::array<const int32_t, 4> kGifInterlaceStep = {{8, 8, 4, 2}};
std::optional<GifSignature> GifSignatureFromByte(uint8_t signature) {
switch (signature) {
case fxcrt::to_underlying(GifSignature::kExtension):
return GifSignature::kExtension;
case fxcrt::to_underlying(GifSignature::kImage):
return GifSignature::kImage;
case fxcrt::to_underlying(GifSignature::kTrailer):
return GifSignature::kTrailer;
default:
return std::nullopt;
}
}
} // namespace
CFX_GifContext::CFX_GifContext(Delegate* delegate) : delegate_(delegate) {}
CFX_GifContext::~CFX_GifContext() = default;
ProgressiveDecoderContext::Status CFX_GifContext::ReadHeader(
int* width,
int* height,
pdfium::span<CFX_GifPalette>* pal_pp,
int* bg_index) {
ProgressiveDecoderContext::Status ret = ReadHeader();
if (ret != ProgressiveDecoderContext::Status::kSuccess) {
return ret;
}
*width = width_;
*height = height_;
*pal_pp = global_palette_;
*bg_index = bc_index_;
return ProgressiveDecoderContext::Status::kSuccess;
}
void CFX_GifContext::ReadScanline(int32_t row_num,
pdfium::span<uint8_t> row_buf) {
delegate_->GifReadScanline(row_num, row_buf);
}
bool CFX_GifContext::GetRecordPosition(uint32_t cur_pos,
int32_t left,
int32_t top,
int32_t width,
int32_t height,
pdfium::span<CFX_GifPalette> pal,
int32_t trans_index) {
return delegate_->GifInputRecordPositionBuf(
cur_pos, FX_RECT(left, top, left + width, top + height), pal,
trans_index);
}
ProgressiveDecoderContext::Status CFX_GifContext::ReadHeader() {
ProgressiveDecoderContext::Status status = ReadGifSignature();
if (status != ProgressiveDecoderContext::Status::kSuccess) {
return status;
}
return ReadLogicalScreenDescriptor();
}
ProgressiveDecoderContext::Status CFX_GifContext::GetFrame() {
ProgressiveDecoderContext::Status ret =
ProgressiveDecoderContext::Status::kSuccess;
while (true) {
switch (decode_status_) {
case GifDecoderStatus::kTail:
return ProgressiveDecoderContext::Status::kSuccess;
case GifDecoderStatus::kSig: {
uint8_t signature;
if (!ReadAllOrNone(pdfium::byte_span_from_ref(signature))) {
return ProgressiveDecoderContext::Status::kContinue;
}
std::optional<GifSignature> gif_signature =
GifSignatureFromByte(signature);
if (!gif_signature.has_value()) {
if (!input_buffer_->IsEOF()) {
// The Gif File has non_standard Tag!
SaveDecodingStatus(GifDecoderStatus::kSig);
continue;
}
// The Gif File Doesn't have Trailer Tag!
return ProgressiveDecoderContext::Status::kSuccess;
}
switch (gif_signature.value()) {
case GifSignature::kExtension:
SaveDecodingStatus(GifDecoderStatus::kExt);
continue;
case GifSignature::kImage:
SaveDecodingStatus(GifDecoderStatus::kImgInfo);
continue;
case GifSignature::kTrailer:
SaveDecodingStatus(GifDecoderStatus::kTail);
return ProgressiveDecoderContext::Status::kSuccess;
}
}
case GifDecoderStatus::kExt: {
uint8_t extension;
if (!ReadAllOrNone(pdfium::byte_span_from_ref(extension))) {
return ProgressiveDecoderContext::Status::kContinue;
}
switch (extension) {
case fxcrt::to_underlying(GifExtensionLabel::kComment):
SaveDecodingStatus(GifDecoderStatus::kExtCe);
continue;
case fxcrt::to_underlying(GifExtensionLabel::kGraphicControl):
SaveDecodingStatus(GifDecoderStatus::kExtGce);
continue;
case fxcrt::to_underlying(GifExtensionLabel::kPlainText):
SaveDecodingStatus(GifDecoderStatus::kExtPte);
continue;
default: {
GifDecoderStatus status = GifDecoderStatus::kExtUne;
if (extension ==
fxcrt::to_underlying(GifExtensionLabel::kPlainText)) {
status = GifDecoderStatus::kExtPte;
}
SaveDecodingStatus(status);
continue;
}
}
}
case GifDecoderStatus::kImgInfo: {
ret = DecodeImageInfo();
if (ret != ProgressiveDecoderContext::Status::kSuccess) {
return ret;
}
continue;
}
case GifDecoderStatus::kImgData: {
uint8_t img_data_size;
size_t read_marker = input_buffer_->GetPosition();
if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
return ProgressiveDecoderContext::Status::kContinue;
}
while (img_data_size != kGifSubBlockTerminator) {
if (!input_buffer_->Seek(input_buffer_->GetPosition() +
img_data_size)) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
// This saving of the scan state on partial reads is why
// ScanForTerminalMarker() cannot be used here.
SaveDecodingStatus(GifDecoderStatus::kImgData);
read_marker = input_buffer_->GetPosition();
if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
return ProgressiveDecoderContext::Status::kContinue;
}
}
SaveDecodingStatus(GifDecoderStatus::kSig);
continue;
}
default: {
ret = DecodeExtension();
if (ret != ProgressiveDecoderContext::Status::kSuccess) {
return ret;
}
break;
}
}
}
}
ProgressiveDecoderContext::Status CFX_GifContext::DecodeImage(
size_t frame_index) {
if (frame_index >= images_.size()) {
return ProgressiveDecoderContext::Status::kError;
}
CFX_GifImage* gif_image = images_[frame_index].get();
if (gif_image->image_info.height == 0) {
return ProgressiveDecoderContext::Status::kError;
}
uint32_t gif_img_row_bytes = gif_image->image_info.width;
if (gif_img_row_bytes == 0) {
return ProgressiveDecoderContext::Status::kError;
}
if (decode_status_ == GifDecoderStatus::kTail) {
gif_image->row_buffer.resize(gif_img_row_bytes);
CFX_GifGraphicControlExtension* gif_img_gce = gif_image->image_GCE.get();
pdfium::span<CFX_GifPalette> pLocalPalette = gif_image->local_palettes;
if (!gif_img_gce) {
bool bRes = GetRecordPosition(
gif_image->data_pos, gif_image->image_info.left,
gif_image->image_info.top, gif_image->image_info.width,
gif_image->image_info.height, pLocalPalette, -1);
if (!bRes) {
gif_image->row_buffer.clear();
return ProgressiveDecoderContext::Status::kError;
}
} else {
bool bRes = GetRecordPosition(
gif_image->data_pos, gif_image->image_info.left,
gif_image->image_info.top, gif_image->image_info.width,
gif_image->image_info.height, pLocalPalette,
gif_image->image_GCE->gce_flags.transparency
? static_cast<int32_t>(gif_image->image_GCE->trans_index)
: -1);
if (!bRes) {
gif_image->row_buffer.clear();
return ProgressiveDecoderContext::Status::kError;
}
}
if (gif_image->code_exp > kGifMaxLzwExp) {
gif_image->row_buffer.clear();
return ProgressiveDecoderContext::Status::kError;
}
img_row_offset_ = 0;
img_row_avail_size_ = 0;
img_pass_num_ = 0;
gif_image->row_num = 0;
SaveDecodingStatus(GifDecoderStatus::kImgData);
}
uint8_t img_data_size;
DataVector<uint8_t> img_data;
size_t read_marker = input_buffer_->GetPosition();
// TODO(crbug.com/42270805): This logic can be simplified a lot, but it
// probably makes more sense to switch to a different GIF decoder altogether.
if (decode_status_ == GifDecoderStatus::kImgData) {
if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
return ProgressiveDecoderContext::Status::kContinue;
}
if (img_data_size != kGifSubBlockTerminator) {
img_data.resize(img_data_size);
if (!ReadAllOrNone(img_data)) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
if (!lzw_decompressor_) {
lzw_decompressor_ = LZWDecompressor::Create(GetPaletteExp(gif_image),
gif_image->code_exp);
if (!lzw_decompressor_) {
DecodingFailureAtTailCleanup(gif_image);
return ProgressiveDecoderContext::Status::kError;
}
}
lzw_decompressor_->SetSource(img_data);
SaveDecodingStatus(GifDecoderStatus::kImgData);
img_row_offset_ += img_row_avail_size_;
img_row_avail_size_ = gif_img_row_bytes - img_row_offset_;
auto img_row_span = pdfium::span(gif_image->row_buffer)
.subspan(img_row_offset_, img_row_avail_size_);
LZWDecompressor::Status ret = UNSAFE_TODO(
lzw_decompressor_->Decode(img_row_span.data(), &img_row_avail_size_));
if (ret == LZWDecompressor::Status::kError) {
DecodingFailureAtTailCleanup(gif_image);
return ProgressiveDecoderContext::Status::kError;
}
while (ret != LZWDecompressor::Status::kError) {
if (ret == LZWDecompressor::Status::kSuccess) {
ReadScanline(gif_image->row_num, gif_image->row_buffer);
gif_image->row_buffer.clear();
SaveDecodingStatus(GifDecoderStatus::kTail);
return ProgressiveDecoderContext::Status::kSuccess;
}
if (ret == LZWDecompressor::Status::kUnfinished) {
read_marker = input_buffer_->GetPosition();
if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
return ProgressiveDecoderContext::Status::kContinue;
}
if (img_data_size != kGifSubBlockTerminator) {
img_data.resize(img_data_size);
if (!ReadAllOrNone(img_data)) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
lzw_decompressor_->SetSource(img_data);
SaveDecodingStatus(GifDecoderStatus::kImgData);
img_row_offset_ += img_row_avail_size_;
img_row_avail_size_ = gif_img_row_bytes - img_row_offset_;
img_row_span = pdfium::span(gif_image->row_buffer)
.subspan(img_row_offset_, img_row_avail_size_);
ret = UNSAFE_TODO(lzw_decompressor_->Decode(img_row_span.data(),
&img_row_avail_size_));
}
}
if (ret == LZWDecompressor::Status::kInsufficientDestSize) {
if (gif_image->image_info.local_flags.interlace) {
ReadScanline(gif_image->row_num, gif_image->row_buffer);
gif_image->row_num += kGifInterlaceStep[img_pass_num_];
if (gif_image->row_num >=
static_cast<int32_t>(gif_image->image_info.height)) {
img_pass_num_++;
if (img_pass_num_ == std::size(kGifInterlaceStep)) {
DecodingFailureAtTailCleanup(gif_image);
return ProgressiveDecoderContext::Status::kError;
}
gif_image->row_num = kGifInterlaceStep[img_pass_num_] / 2;
}
} else {
ReadScanline(gif_image->row_num++, gif_image->row_buffer);
}
img_row_offset_ = 0;
img_row_avail_size_ = gif_img_row_bytes;
img_row_span = pdfium::span(gif_image->row_buffer)
.subspan(img_row_offset_, img_row_avail_size_);
ret = UNSAFE_TODO(lzw_decompressor_->Decode(img_row_span.data(),
&img_row_avail_size_));
}
if (ret == LZWDecompressor::Status::kError) {
DecodingFailureAtTailCleanup(gif_image);
return ProgressiveDecoderContext::Status::kError;
}
}
}
SaveDecodingStatus(GifDecoderStatus::kTail);
}
return ProgressiveDecoderContext::Status::kError;
}
void CFX_GifContext::SetInputBuffer(RetainPtr<CFX_CodecMemory> codec_memory) {
input_buffer_ = std::move(codec_memory);
}
FX_FILESIZE CFX_GifContext::GetAvailInput() const {
if (!input_buffer_) {
return 0;
}
return input_buffer_->GetSize() - input_buffer_->GetPosition();
}
void CFX_GifContext::Input(RetainPtr<CFX_CodecMemory> codec_memory) {
SetInputBuffer(std::move(codec_memory));
}
bool CFX_GifContext::ReadAllOrNone(pdfium::span<uint8_t> dest) {
if (!input_buffer_ || dest.empty()) {
return false;
}
size_t read_marker = input_buffer_->GetPosition();
size_t read = input_buffer_->ReadBlock(dest);
if (read < dest.size()) {
input_buffer_->Seek(read_marker);
return false;
}
return true;
}
ProgressiveDecoderContext::Status CFX_GifContext::ReadGifSignature() {
CFX_GifHeader header;
if (!ReadAllOrNone(pdfium::byte_span_from_ref(header))) {
return ProgressiveDecoderContext::Status::kContinue;
}
if (UNSAFE_TODO(strncmp(header.signature, kGifSignature87, 6)) != 0 &&
UNSAFE_TODO(strncmp(header.signature, kGifSignature89, 6)) != 0) {
return ProgressiveDecoderContext::Status::kError;
}
return ProgressiveDecoderContext::Status::kSuccess;
}
ProgressiveDecoderContext::Status
CFX_GifContext::ReadLogicalScreenDescriptor() {
CFX_GifLocalScreenDescriptor lsd;
size_t read_marker = input_buffer_->GetPosition();
if (!ReadAllOrNone(pdfium::byte_span_from_ref(lsd))) {
return ProgressiveDecoderContext::Status::kContinue;
}
if (lsd.global_flags.global_pal) {
uint32_t palette_count = unsigned(2 << lsd.global_flags.pal_bits);
if (lsd.bc_index >= palette_count) {
return ProgressiveDecoderContext::Status::kError;
}
bc_index_ = lsd.bc_index;
std::vector<CFX_GifPalette> palette(palette_count);
if (!ReadAllOrNone(pdfium::as_writable_byte_span(palette))) {
// Roll back the read for the LSD
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
global_palette_exp_ = lsd.global_flags.pal_bits;
global_sort_flag_ = lsd.global_flags.sort_flag;
global_color_resolution_ = lsd.global_flags.color_resolution;
std::swap(global_palette_, palette);
}
width_ = fxcrt::FromLE16(lsd.width);
height_ = fxcrt::FromLE16(lsd.height);
return ProgressiveDecoderContext::Status::kSuccess;
}
void CFX_GifContext::SaveDecodingStatus(GifDecoderStatus status) {
decode_status_ = status;
}
ProgressiveDecoderContext::Status CFX_GifContext::DecodeExtension() {
size_t read_marker = input_buffer_->GetPosition();
switch (decode_status_) {
case GifDecoderStatus::kExtCe: {
if (!ScanForTerminalMarker()) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
break;
}
case GifDecoderStatus::kExtPte: {
CFX_GifPlainTextExtension gif_pte;
if (!ReadAllOrNone(pdfium::byte_span_from_ref(gif_pte))) {
return ProgressiveDecoderContext::Status::kContinue;
}
graphic_control_extension_ = nullptr;
if (!ScanForTerminalMarker()) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
break;
}
case GifDecoderStatus::kExtGce: {
CFX_GifGraphicControlExtension gif_gce;
if (!ReadAllOrNone(pdfium::byte_span_from_ref(gif_gce))) {
return ProgressiveDecoderContext::Status::kContinue;
}
if (!graphic_control_extension_.get()) {
graphic_control_extension_ =
std::make_unique<CFX_GifGraphicControlExtension>();
}
graphic_control_extension_->block_size = gif_gce.block_size;
graphic_control_extension_->gce_flags = gif_gce.gce_flags;
graphic_control_extension_->delay_time =
fxcrt::FromLE16(gif_gce.delay_time);
graphic_control_extension_->trans_index = gif_gce.trans_index;
break;
}
default: {
if (decode_status_ == GifDecoderStatus::kExtPte) {
graphic_control_extension_ = nullptr;
}
if (!ScanForTerminalMarker()) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
}
}
SaveDecodingStatus(GifDecoderStatus::kSig);
return ProgressiveDecoderContext::Status::kSuccess;
}
ProgressiveDecoderContext::Status CFX_GifContext::DecodeImageInfo() {
if (width_ <= 0 || height_ <= 0) {
return ProgressiveDecoderContext::Status::kError;
}
size_t read_marker = input_buffer_->GetPosition();
CFX_GifImageInfo img_info;
if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_info))) {
return ProgressiveDecoderContext::Status::kContinue;
}
auto gif_image = std::make_unique<CFX_GifImage>();
gif_image->image_info.left = fxcrt::FromLE16(img_info.left);
gif_image->image_info.top = fxcrt::FromLE16(img_info.top);
gif_image->image_info.width = fxcrt::FromLE16(img_info.width);
gif_image->image_info.height = fxcrt::FromLE16(img_info.height);
gif_image->image_info.local_flags = img_info.local_flags;
if (gif_image->image_info.left + gif_image->image_info.width > width_ ||
gif_image->image_info.top + gif_image->image_info.height > height_) {
return ProgressiveDecoderContext::Status::kError;
}
CFX_GifLocalFlags* gif_img_info_lf = &img_info.local_flags;
if (gif_img_info_lf->local_pal) {
gif_image->local_palette_exp = gif_img_info_lf->pal_bits;
uint32_t loc_pal_count = unsigned(2 << gif_img_info_lf->pal_bits);
std::vector<CFX_GifPalette> loc_pal(loc_pal_count);
if (!ReadAllOrNone(pdfium::as_writable_byte_span(loc_pal))) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
gif_image->local_palettes = std::move(loc_pal);
}
uint8_t code_size;
if (!ReadAllOrNone(pdfium::span_from_ref(code_size))) {
input_buffer_->Seek(read_marker);
return ProgressiveDecoderContext::Status::kContinue;
}
gif_image->code_exp = code_size;
gif_image->data_pos = delegate_->GifCurrentPosition();
gif_image->image_GCE = nullptr;
if (graphic_control_extension_.get()) {
if (graphic_control_extension_->gce_flags.transparency) {
// Need to test that the color that is going to be transparent is actually
// in the palette being used.
if (graphic_control_extension_->trans_index >=
(2 << GetPaletteExp(gif_image.get()))) {
return ProgressiveDecoderContext::Status::kError;
}
}
gif_image->image_GCE = std::move(graphic_control_extension_);
graphic_control_extension_ = nullptr;
}
images_.push_back(std::move(gif_image));
SaveDecodingStatus(GifDecoderStatus::kImgData);
return ProgressiveDecoderContext::Status::kSuccess;
}
void CFX_GifContext::DecodingFailureAtTailCleanup(CFX_GifImage* gif_image) {
gif_image->row_buffer.clear();
SaveDecodingStatus(GifDecoderStatus::kTail);
}
bool CFX_GifContext::ScanForTerminalMarker() {
uint8_t data_size;
if (!ReadAllOrNone(pdfium::span_from_ref(data_size))) {
return false;
}
while (data_size != kGifSubBlockTerminator) {
if (!input_buffer_->Seek(input_buffer_->GetPosition() + data_size) ||
!ReadAllOrNone(pdfium::span_from_ref(data_size))) {
return false;
}
}
return true;
}
uint8_t CFX_GifContext::GetPaletteExp(CFX_GifImage* gif_image) const {
return !gif_image->local_palettes.empty() ? gif_image->local_palette_exp
: global_palette_exp_;
}
} // namespace fxcodec