| // Copyright 2014 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/progressive_decoder.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include "build/build_config.h" | |
| #include "core/fxcodec/fx_codec.h" | |
| #include "core/fxcodec/gif/cfx_gifcontext.h" | |
| #include "core/fxcodec/progressive_decoder_context.h" | |
| #include "core/fxcodec/tiff/libtiff_tiff_context.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/check_op.h" | |
| #include "core/fxcrt/compiler_specific.h" | |
| #include "core/fxcrt/fx_2d_size.h" | |
| #include "core/fxcrt/fx_memcpy_wrappers.h" | |
| #include "core/fxcrt/fx_safe_types.h" | |
| #include "core/fxcrt/fx_stream.h" | |
| #include "core/fxcrt/fx_system.h" | |
| #include "core/fxcrt/notreached.h" | |
| #include "core/fxcrt/numerics/safe_conversions.h" | |
| #include "core/fxcrt/span_util.h" | |
| #include "core/fxcrt/stl_util.h" | |
| #include "core/fxge/dib/cfx_cmyk_to_srgb.h" | |
| #include "core/fxge/dib/cfx_dibitmap.h" | |
| #include "core/fxge/dib/fx_dib.h" | |
| #ifndef PDF_ENABLE_XFA | |
| #error "XFA only" | |
| #endif | |
| #if defined(PDF_ENABLE_RUST_BMP) | |
| #include "core/fxcodec/bmp/rust_bmp_context.h" | |
| #else | |
| #include "core/fxcodec/bmp/cfx_bmpcontext.h" | |
| #endif | |
| #if defined(PDF_ENABLE_RUST_JPEG) | |
| #include "core/fxcodec/jpeg/rust_jpeg_context.h" | |
| #else | |
| #include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" | |
| #endif | |
| #if defined(PDF_ENABLE_RUST_PNG) | |
| #include "core/fxcodec/png/rust_png_context.h" | |
| #else | |
| #include "core/fxcodec/png/libpng_png_context.h" | |
| #endif | |
| namespace fxcodec { | |
| namespace { | |
| constexpr size_t kBlockSize = 4096; | |
| std::unique_ptr<ProgressiveDecoderContext> CreateDecoderContext( | |
| FXCODEC_IMAGE_TYPE type, | |
| ProgressiveDecoder* delegate) { | |
| switch (type) { | |
| case FXCODEC_IMAGE_BMP: | |
| #if defined(PDF_ENABLE_RUST_BMP) | |
| return std::make_unique<RustBmpContext>(delegate); | |
| #else | |
| return std::make_unique<CFX_BmpContext>(delegate); | |
| #endif | |
| case FXCODEC_IMAGE_GIF: | |
| return std::make_unique<CFX_GifContext>(delegate); | |
| case FXCODEC_IMAGE_JPG: { | |
| #if defined(PDF_ENABLE_RUST_JPEG) | |
| return std::make_unique<RustJpegContext>(delegate); | |
| #else | |
| auto context = std::make_unique<LibjpegJpegContext>(delegate); | |
| if (!context->create_ok_) { | |
| return nullptr; | |
| } | |
| return context; | |
| #endif | |
| } | |
| case FXCODEC_IMAGE_PNG: | |
| #if defined(PDF_ENABLE_RUST_PNG) | |
| return std::make_unique<RustPngContext>(delegate); | |
| #else | |
| { | |
| auto context = std::make_unique<LibpngPngContext>(delegate); | |
| if (!context->png_ || !context->info_) { | |
| return nullptr; | |
| } | |
| return context; | |
| } | |
| #endif | |
| default: | |
| return nullptr; | |
| } | |
| } | |
| } // namespace | |
| ProgressiveDecoder::ProgressiveDecoder() = default; | |
| ProgressiveDecoder::~ProgressiveDecoder() = default; | |
| bool ProgressiveDecoder::ReadMoreData(std::optional<uint32_t> rcd_pos, | |
| FXCODEC_STATUS* err_status) { | |
| if (rcd_pos.has_value()) { | |
| offset_ = rcd_pos.value(); | |
| codec_memory_->Seek(codec_memory_->GetSize()); | |
| } | |
| size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); | |
| if (!ReadMoreDataInternal(unconsumed_bytes, err_status)) { | |
| return false; | |
| } | |
| if (context_) { | |
| context_->Input(codec_memory_); | |
| } | |
| return true; | |
| } | |
| uint32_t ProgressiveDecoder::GetCurrentInputPosition() const { | |
| uint32_t remain_size = | |
| context_ ? pdfium::checked_cast<uint32_t>(context_->GetAvailInput()) : 0; | |
| return offset_ - remain_size; | |
| } | |
| bool ProgressiveDecoder::PrepareScanlineResampling( | |
| int src_width, | |
| int src_height, | |
| Format src_format, | |
| pdfium::span<const FX_ARGB> palette, | |
| std::optional<FX_ARGB> fill_argb) { | |
| if (!device_bitmap_ || src_format == Format::kInvalid || src_width <= 0 || | |
| src_height <= 0) { | |
| return false; | |
| } | |
| src_width_ = src_width; | |
| src_height_ = src_height; | |
| src_format_ = src_format; | |
| src_components_count_ = GetCompsFromFormat(src_format); | |
| src_bits_per_component_ = 8; | |
| SetTransMethod(); | |
| decode_buf_.resize(GetScanlineSize()); | |
| FXDIB_ResampleOptions options; | |
| options.bInterpolateBilinear = true; | |
| weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, | |
| src_width_, options); | |
| if (!palette.empty()) { | |
| device_bitmap_->SetPalette(palette); | |
| src_palette_ = DataVector<FX_ARGB>(palette.begin(), palette.end()); | |
| } | |
| if (fill_argb.has_value()) { | |
| device_bitmap_->Clear(fill_argb.value()); | |
| } | |
| return true; | |
| } | |
| void ProgressiveDecoder::ResampleScanline( | |
| int line, | |
| pdfium::span<const uint8_t> src_span) { | |
| CHECK(device_bitmap_); | |
| if (line < 0 || line >= src_height_) { | |
| return; | |
| } | |
| int scanline_size = GetScanlineSize(); | |
| fxcrt::Copy(src_span.first(static_cast<size_t>(scanline_size)), decode_buf_); | |
| ResampleScanline(device_bitmap_, line, decode_buf_, src_format_); | |
| } | |
| bool ProgressiveDecoder::PrepareDirectOutput(int src_width, | |
| int src_height, | |
| Format src_format) { | |
| if (src_width <= 0 || src_height <= 0 || src_format != Format::kBgra) { | |
| return false; | |
| } | |
| src_width_ = src_width; | |
| src_height_ = src_height; | |
| src_format_ = src_format; | |
| src_bits_per_component_ = 8; | |
| src_components_count_ = 4; | |
| return true; | |
| } | |
| pdfium::span<uint8_t> ProgressiveDecoder::AskScanlineBuf(int line) { | |
| CHECK_GE(line, 0); | |
| CHECK_LT(line, src_height_); | |
| CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); | |
| CHECK_EQ(src_format_, Format::kBgra); | |
| return device_bitmap_->GetWritableScanline(line); | |
| } | |
| pdfium::span<uint8_t> ProgressiveDecoder::AskImageBuf() { | |
| CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); | |
| CHECK_EQ(src_format_, Format::kBgra); | |
| return device_bitmap_->GetWritableBuffer(); | |
| } | |
| bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( | |
| CFX_DIBAttribute* pAttribute) { | |
| std::unique_ptr<ProgressiveDecoderContext> pBmcontext = | |
| CreateDecoderContext(FXCODEC_IMAGE_BMP, this); | |
| pBmcontext->Input(codec_memory_); | |
| #if defined(PDF_ENABLE_RUST_BMP) | |
| auto* ctx = static_cast<RustBmpContext*>(pBmcontext.get()); | |
| #else | |
| auto* ctx = static_cast<CFX_BmpContext*>(pBmcontext.get()); | |
| #endif | |
| pdfium::span<const FX_ARGB> palette; | |
| ProgressiveDecoderContext::Status read_result = ctx->ReadHeader( | |
| &src_width_, &src_height_, &src_components_count_, &palette, pAttribute); | |
| while (read_result == ProgressiveDecoderContext::Status::kContinue) { | |
| FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; | |
| if (!BmpReadMoreData(pBmcontext.get(), &error_status)) { | |
| status_ = error_status; | |
| return false; | |
| } | |
| read_result = ctx->ReadHeader(&src_width_, &src_height_, | |
| &src_components_count_, &palette, pAttribute); | |
| } | |
| if (read_result != ProgressiveDecoderContext::Status::kSuccess) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| FXDIB_Format format = FXDIB_Format::kInvalid; | |
| switch (src_components_count_) { | |
| case 1: | |
| src_format_ = Format::k8bppRgb; | |
| format = FXDIB_Format::k8bppRgb; | |
| break; | |
| case 3: | |
| src_format_ = Format::kBgr; | |
| format = FXDIB_Format::kBgr; | |
| break; | |
| case 4: | |
| src_format_ = Format::kBgrx; | |
| format = FXDIB_Format::kBgrx; | |
| break; | |
| default: | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| // Set to 0 to make CalculatePitchAndSize() calculate it. | |
| static constexpr uint32_t kNoPitch = 0; | |
| std::optional<CFX_DIBitmap::PitchAndSize> needed_data = | |
| CFX_DIBitmap::CalculatePitchAndSize(src_width_, src_height_, format, | |
| kNoPitch); | |
| if (!needed_data.has_value()) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| uint32_t available_data = pdfium::checked_cast<uint32_t>( | |
| file_->GetSize() - offset_ + pBmcontext->GetAvailInput()); | |
| if (needed_data.value().size / src_components_count_ > available_data) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| src_bits_per_component_ = 8; | |
| context_ = std::move(pBmcontext); | |
| if (!palette.empty()) { | |
| src_palette_.resize(palette.size()); | |
| fxcrt::Copy(palette, src_palette_); | |
| } else { | |
| src_palette_.clear(); | |
| } | |
| return true; | |
| } | |
| bool ProgressiveDecoder::BmpReadMoreData(ProgressiveDecoderContext* bmp_context, | |
| FXCODEC_STATUS* err_status) { | |
| // TODO(lukasza): Can this just use | |
| // `codec_memory_->GetUnconsumedSpan().size()`? (IIUC this is what | |
| // `GetAvailInput` uses in the end, but I haven't investigated that this is | |
| // the same instance of `CFX_CodecMemory`.) | |
| FX_SAFE_SIZE_T avail_input = bmp_context->GetAvailInput(); | |
| if (!avail_input.IsValid()) { | |
| return false; | |
| } | |
| if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) { | |
| return false; | |
| } | |
| bmp_context->Input(codec_memory_); | |
| return true; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::BmpStartDecode() { | |
| SetTransMethod(); | |
| decode_buf_.resize(GetScanlineSize()); | |
| FXDIB_ResampleOptions options; | |
| options.bInterpolateBilinear = true; | |
| weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, | |
| src_width_, options); | |
| status_ = FXCODEC_STATUS::kDecodeToBeContinued; | |
| return status_; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::BmpContinueDecode() { | |
| ProgressiveDecoderContext::Status read_res = context_->DecodeImage(); | |
| while (read_res == ProgressiveDecoderContext::Status::kContinue) { | |
| FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; | |
| if (!BmpReadMoreData(context_.get(), &error_status)) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| status_ = error_status; | |
| return status_; | |
| } | |
| read_res = context_->DecodeImage(); | |
| } | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| status_ = read_res == ProgressiveDecoderContext::Status::kSuccess | |
| ? FXCODEC_STATUS::kDecodeFinished | |
| : FXCODEC_STATUS::kError; | |
| return status_; | |
| } | |
| bool ProgressiveDecoder::GifReadMoreData(FXCODEC_STATUS* err_status) { | |
| // TODO(lukasza): Can this just use | |
| // `codec_memory_->GetUnconsumedSpan().size()`? (IIUC this is what | |
| // `GetAvailInput` uses in the end, but I haven't investigated that this is | |
| // the same instance of `CFX_CodecMemory`.) | |
| FX_SAFE_SIZE_T avail_input = context_->GetAvailInput(); | |
| if (!avail_input.IsValid()) { | |
| return false; | |
| } | |
| if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) { | |
| return false; | |
| } | |
| context_->Input(codec_memory_); | |
| return true; | |
| } | |
| bool ProgressiveDecoder::GifDetectImageTypeInBuffer() { | |
| context_ = CreateDecoderContext(FXCODEC_IMAGE_GIF, this); | |
| context_->Input(codec_memory_); | |
| src_components_count_ = 1; | |
| auto* ctx = static_cast<CFX_GifContext*>(context_.get()); | |
| ProgressiveDecoderContext::Status readResult = | |
| ctx->ReadHeader(&src_width_, &src_height_); | |
| while (readResult == ProgressiveDecoderContext::Status::kContinue) { | |
| FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; | |
| if (!GifReadMoreData(&error_status)) { | |
| context_ = nullptr; | |
| status_ = error_status; | |
| return false; | |
| } | |
| readResult = ctx->ReadHeader(&src_width_, &src_height_); | |
| } | |
| if (readResult == ProgressiveDecoderContext::Status::kSuccess) { | |
| src_bits_per_component_ = 8; | |
| return true; | |
| } | |
| context_ = nullptr; | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::GifStartDecode() { | |
| src_format_ = Format::k8bppRgb; | |
| SetTransMethod(); | |
| decode_buf_.resize(GetScanlineSize()); | |
| FXDIB_ResampleOptions options; | |
| options.bInterpolateBilinear = true; | |
| weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, | |
| src_width_, options); | |
| auto* ctx = static_cast<CFX_GifContext*>(context_.get()); | |
| status_ = ctx->StartDecode(device_bitmap_); | |
| return status_; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() { | |
| auto* ctx = static_cast<CFX_GifContext*>(context_.get()); | |
| status_ = ctx->ContinueDecode(); // Success or error, never continue. | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| return status_; | |
| } | |
| bool ProgressiveDecoder::JpegReadMoreData(FXCODEC_STATUS* err_status) { | |
| FX_SAFE_SIZE_T avail_input = context_->GetAvailInput(); | |
| if (!avail_input.IsValid()) { | |
| return false; | |
| } | |
| if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) { | |
| return false; | |
| } | |
| context_->Input(codec_memory_); | |
| return true; | |
| } | |
| bool ProgressiveDecoder::JpegDetectImageTypeInBuffer( | |
| CFX_DIBAttribute* pAttribute) { | |
| context_ = CreateDecoderContext(FXCODEC_IMAGE_JPG, this); | |
| if (!context_) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| context_->Input(codec_memory_); | |
| #if defined(PDF_ENABLE_RUST_JPEG) | |
| auto* ctx = static_cast<RustJpegContext*>(context_.get()); | |
| src_width_ = 0; | |
| src_height_ = 0; | |
| while (!ctx->ReadHeader()) { | |
| FXCODEC_STATUS status = FXCODEC_STATUS::kError; | |
| if (!ReadMoreData(std::nullopt, &status)) { | |
| break; | |
| } | |
| } | |
| bool got_metadata = (ctx->width() > 0 && ctx->height() > 0); | |
| if (got_metadata) { | |
| src_width_ = ctx->width(); | |
| src_height_ = ctx->height(); | |
| src_components_count_ = ctx->num_components(); | |
| src_bits_per_component_ = 8; | |
| #if defined(PDF_ENABLE_XFA) | |
| if (pAttribute) { | |
| pAttribute->x_dpi_ = ctx->x_density(); | |
| pAttribute->y_dpi_ = ctx->y_density(); | |
| pAttribute->dpi_unit_ = | |
| static_cast<CFX_DIBAttribute::ResUnit>(ctx->density_unit()); | |
| } | |
| #endif | |
| } else { | |
| status_ = FXCODEC_STATUS::kError; | |
| } | |
| context_.reset(); | |
| return got_metadata; | |
| #else | |
| auto* ctx = static_cast<LibjpegJpegContext*>(context_.get()); | |
| while (1) { | |
| int read_result = ctx->ReadHeader(&src_width_, &src_height_, | |
| &src_components_count_, pAttribute); | |
| switch (read_result) { | |
| case LibjpegJpegContext::kFatal: | |
| case LibjpegJpegContext::kError: | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| case LibjpegJpegContext::kOk: | |
| src_bits_per_component_ = 8; | |
| return true; | |
| case LibjpegJpegContext::kNeedsMoreInput: { | |
| FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; | |
| if (!JpegReadMoreData(&error_status)) { | |
| status_ = error_status; | |
| return false; | |
| } | |
| break; | |
| } | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| #endif | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { | |
| #if defined(PDF_ENABLE_RUST_JPEG) | |
| context_ = CreateDecoderContext(FXCODEC_IMAGE_JPG, this); | |
| if (!context_) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| status_ = FXCODEC_STATUS::kError; | |
| return status_; | |
| } | |
| context_->Input(codec_memory_); | |
| auto* ctx = static_cast<RustJpegContext*>(context_.get()); | |
| status_ = ctx->StartDecode(device_bitmap_); | |
| return status_; | |
| #else | |
| status_ = static_cast<LibjpegJpegContext*>(context_.get())->StartDecode(); | |
| if (status_ == FXCODEC_STATUS::kError) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| context_.reset(); | |
| } | |
| return status_; | |
| #endif | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { | |
| #if defined(PDF_ENABLE_RUST_JPEG) | |
| auto* ctx = static_cast<RustJpegContext*>(context_.get()); | |
| status_ = ctx->ContinueDecode(); | |
| if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| } | |
| return status_; | |
| #else | |
| status_ = static_cast<LibjpegJpegContext*>(context_.get())->ContinueDecode(); | |
| if (status_ == FXCODEC_STATUS::kError) { | |
| context_.reset(); | |
| } | |
| if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| } | |
| return status_; | |
| #endif | |
| } | |
| bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { | |
| context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this); | |
| if (!context_) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| src_width_ = 0; | |
| src_height_ = 0; | |
| #if defined(PDF_ENABLE_RUST_PNG) | |
| context_->Input(codec_memory_); | |
| auto* ctx = static_cast<RustPngContext*>(context_.get()); | |
| FXCODEC_STATUS status = FXCODEC_STATUS::kError; | |
| while (src_width_ == 0) { | |
| if (ctx->ReadHeader()) { | |
| break; | |
| } | |
| if (ctx->has_error() || !ReadMoreData(std::nullopt, &status)) { | |
| break; | |
| } | |
| } | |
| #else | |
| auto* ctx = static_cast<LibpngPngContext*>(context_.get()); | |
| if (ctx->ReadHeader(codec_memory_)) { | |
| FXCODEC_STATUS status = FXCODEC_STATUS::kError; | |
| while (src_width_ == 0 && ReadMoreData(std::nullopt, &status)) { | |
| if (!ctx->ReadHeader(codec_memory_)) { | |
| break; | |
| } | |
| } | |
| } | |
| #endif | |
| bool got_metadata = (src_width_ > 0 && src_height_ > 0); | |
| if (!got_metadata) { | |
| status_ = FXCODEC_STATUS::kError; | |
| } | |
| context_.reset(); | |
| return got_metadata; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { | |
| context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this); | |
| if (!context_) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| status_ = FXCODEC_STATUS::kError; | |
| return status_; | |
| } | |
| context_->Input(codec_memory_); | |
| #if defined(PDF_ENABLE_RUST_PNG) | |
| auto* ctx = static_cast<RustPngContext*>(context_.get()); | |
| #else | |
| auto* ctx = static_cast<LibpngPngContext*>(context_.get()); | |
| #endif | |
| status_ = ctx->StartDecode(device_bitmap_); | |
| return status_; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::PngContinueDecode() { | |
| #if defined(PDF_ENABLE_RUST_PNG) | |
| auto* ctx = static_cast<RustPngContext*>(context_.get()); | |
| #else | |
| auto* ctx = static_cast<LibpngPngContext*>(context_.get()); | |
| #endif | |
| status_ = ctx->ContinueDecode(); | |
| if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { | |
| device_bitmap_ = nullptr; | |
| file_ = nullptr; | |
| } | |
| return status_; | |
| } | |
| bool ProgressiveDecoder::TiffDetectImageTypeFromFile( | |
| CFX_DIBAttribute* pAttribute) { | |
| auto ctx = std::make_unique<LibtiffTiffContext>(); | |
| if (!ctx->InitDecoder(file_)) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| int32_t dummy_bpc; | |
| bool ret = ctx->LoadFrameInfo(0, &src_width_, &src_height_, | |
| &src_components_count_, &dummy_bpc, pAttribute); | |
| src_components_count_ = 4; | |
| if (!ret) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| context_ = std::move(ctx); | |
| return true; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::TiffContinueDecode() { | |
| // TODO(crbug.com/355630556): Consider adding support for | |
| // `FXDIB_Format::kBgraPremul` | |
| CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); | |
| auto* ctx = static_cast<LibtiffTiffContext*>(context_.get()); | |
| status_ = ctx->Decode(std::move(device_bitmap_)) | |
| ? FXCODEC_STATUS::kDecodeFinished | |
| : FXCODEC_STATUS::kError; | |
| file_ = nullptr; | |
| return status_; | |
| } | |
| bool ProgressiveDecoder::DetectImageType(FXCODEC_IMAGE_TYPE imageType, | |
| CFX_DIBAttribute* pAttribute) { | |
| if (imageType == FXCODEC_IMAGE_TIFF) { | |
| return TiffDetectImageTypeFromFile(pAttribute); | |
| } | |
| size_t size = pdfium::checked_cast<size_t>( | |
| std::min<FX_FILESIZE>(file_->GetSize(), kBlockSize)); | |
| codec_memory_ = pdfium::MakeRetain<CFX_CodecMemory>(size); | |
| offset_ = 0; | |
| if (!file_->ReadBlockAtOffset(codec_memory_->GetBufferSpan().first(size), | |
| offset_)) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| offset_ += size; | |
| if (imageType == FXCODEC_IMAGE_JPG) { | |
| return JpegDetectImageTypeInBuffer(pAttribute); | |
| } | |
| if (imageType == FXCODEC_IMAGE_BMP) { | |
| return BmpDetectImageTypeInBuffer(pAttribute); | |
| } | |
| if (imageType == FXCODEC_IMAGE_GIF) { | |
| return GifDetectImageTypeInBuffer(); | |
| } | |
| if (imageType == FXCODEC_IMAGE_PNG) { | |
| return PngDetectImageTypeInBuffer(); | |
| } | |
| status_ = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| bool ProgressiveDecoder::ReadMoreDataInternal(size_t unconsumed_bytes, | |
| FXCODEC_STATUS* err_status) { | |
| // Check for EOF. | |
| if (offset_ >= static_cast<uint32_t>(file_->GetSize())) { | |
| return false; | |
| } | |
| // Try to get whatever remains. | |
| uint32_t bytes_to_fetch_from_file = | |
| pdfium::checked_cast<uint32_t>(file_->GetSize() - offset_); | |
| if (unconsumed_bytes == codec_memory_->GetSize()) { | |
| // Codec couldn't make any progress against the bytes in the buffer. | |
| // Increase the buffer size so that there might be enough contiguous | |
| // bytes to allow whatever operation is having difficulty to succeed. | |
| bytes_to_fetch_from_file = | |
| std::min<uint32_t>(bytes_to_fetch_from_file, kBlockSize); | |
| size_t new_size = codec_memory_->GetSize() + bytes_to_fetch_from_file; | |
| if (!codec_memory_->TryResize(new_size)) { | |
| *err_status = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| } else { | |
| // TODO(crbug.com/42270919): Simplify the `CFX_CodecMemory` API so we | |
| // don't need to do this awkward dance to free up exactly enough buffer | |
| // space for the next read. | |
| size_t already_read_bytes = codec_memory_->GetSize() - unconsumed_bytes; | |
| bytes_to_fetch_from_file = pdfium::checked_cast<uint32_t>( | |
| std::min<size_t>(bytes_to_fetch_from_file, already_read_bytes)); | |
| codec_memory_->Consume(bytes_to_fetch_from_file); | |
| codec_memory_->Seek(already_read_bytes - bytes_to_fetch_from_file); | |
| unconsumed_bytes += codec_memory_->GetPosition(); | |
| } | |
| // Append new data past the bytes not yet processed by the codec. | |
| if (!file_->ReadBlockAtOffset(codec_memory_->GetBufferSpan().subspan( | |
| unconsumed_bytes, bytes_to_fetch_from_file), | |
| offset_)) { | |
| *err_status = FXCODEC_STATUS::kError; | |
| return false; | |
| } | |
| offset_ += bytes_to_fetch_from_file; | |
| return true; | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::LoadImageInfo( | |
| RetainPtr<IFX_SeekableReadStream> file, | |
| FXCODEC_IMAGE_TYPE image_type, | |
| CFX_DIBAttribute* attribute, | |
| bool skip_fallback_type_detection) { | |
| DCHECK(attribute); | |
| switch (status_) { | |
| case FXCODEC_STATUS::kFrameReady: | |
| case FXCODEC_STATUS::kFrameToBeContinued: | |
| case FXCODEC_STATUS::kDecodeReady: | |
| case FXCODEC_STATUS::kDecodeToBeContinued: | |
| return FXCODEC_STATUS::kError; | |
| case FXCODEC_STATUS::kError: | |
| case FXCODEC_STATUS::kDecodeFinished: | |
| break; | |
| } | |
| file_ = std::move(file); | |
| if (!file_) { | |
| status_ = FXCODEC_STATUS::kError; | |
| return status_; | |
| } | |
| offset_ = 0; | |
| src_width_ = 0; | |
| src_height_ = 0; | |
| src_components_count_ = 0; | |
| src_bits_per_component_ = 0; | |
| if (image_type != FXCODEC_IMAGE_UNKNOWN && | |
| DetectImageType(image_type, attribute)) { | |
| image_type_ = image_type; | |
| status_ = FXCODEC_STATUS::kFrameReady; | |
| return status_; | |
| } | |
| // The image data does not match the requested decoder. If skipping fallback | |
| // type detection, bail out at this point and return the failed status. | |
| if (skip_fallback_type_detection) { | |
| status_ = FXCODEC_STATUS::kError; | |
| file_ = nullptr; | |
| return status_; | |
| } | |
| for (int type = FXCODEC_IMAGE_UNKNOWN + 1; type < FXCODEC_IMAGE_MAX; type++) { | |
| if (DetectImageType(static_cast<FXCODEC_IMAGE_TYPE>(type), attribute)) { | |
| image_type_ = static_cast<FXCODEC_IMAGE_TYPE>(type); | |
| status_ = FXCODEC_STATUS::kFrameReady; | |
| return status_; | |
| } | |
| } | |
| status_ = FXCODEC_STATUS::kError; | |
| file_ = nullptr; | |
| return status_; | |
| } | |
| void ProgressiveDecoder::SetTransMethod() { | |
| switch (device_bitmap_->GetFormat()) { | |
| case FXDIB_Format::kInvalid: | |
| case FXDIB_Format::k1bppMask: | |
| case FXDIB_Format::k1bppRgb: | |
| case FXDIB_Format::k8bppMask: | |
| case FXDIB_Format::k8bppRgb: | |
| NOTREACHED(); | |
| case FXDIB_Format::kBgr: { | |
| switch (src_format_) { | |
| case Format::kInvalid: | |
| trans_method_ = TransformMethod::kInvalid; | |
| break; | |
| case Format::k8bppGray: | |
| trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha; | |
| break; | |
| case Format::k8bppRgb: | |
| trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; | |
| break; | |
| case Format::kBgr: | |
| case Format::kBgrx: | |
| case Format::kBgra: | |
| trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; | |
| break; | |
| case Format::kRgb: | |
| trans_method_ = TransformMethod::kRgbToBgrMaybeAlpha; | |
| break; | |
| case Format::kCmyk: | |
| trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; | |
| break; | |
| } | |
| break; | |
| } | |
| case FXDIB_Format::kBgrx: | |
| case FXDIB_Format::kBgra: { | |
| switch (src_format_) { | |
| case Format::kInvalid: | |
| trans_method_ = TransformMethod::kInvalid; | |
| break; | |
| case Format::k8bppGray: | |
| trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha; | |
| break; | |
| case Format::k8bppRgb: | |
| if (device_bitmap_->GetFormat() == FXDIB_Format::kBgra) { | |
| trans_method_ = TransformMethod::k8BppRgbToArgb; | |
| } else { | |
| trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; | |
| } | |
| break; | |
| case Format::kBgr: | |
| case Format::kBgrx: | |
| trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; | |
| break; | |
| case Format::kRgb: | |
| trans_method_ = TransformMethod::kRgbToBgrMaybeAlpha; | |
| break; | |
| case Format::kCmyk: | |
| trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; | |
| break; | |
| case Format::kBgra: | |
| trans_method_ = TransformMethod::kArgbToArgb; | |
| break; | |
| } | |
| break; | |
| } | |
| #if defined(PDF_USE_SKIA) | |
| case FXDIB_Format::kBgraPremul: | |
| // TODO(crbug.com/355630556): Consider adding support for | |
| // `FXDIB_Format::kBgraPremul` | |
| NOTREACHED(); | |
| #endif | |
| } | |
| } | |
| void ProgressiveDecoder::ResampleScanline( | |
| const RetainPtr<CFX_DIBitmap>& pDeviceBitmap, | |
| int dest_line, | |
| pdfium::span<uint8_t> src_span, | |
| Format src_format) { | |
| uint8_t* src_scan = src_span.data(); | |
| uint8_t* dest_scan = pDeviceBitmap->GetWritableScanline(dest_line).data(); | |
| const int src_bytes_per_pixel = GetCompsFromFormat(src_format); | |
| const int dest_bytes_per_pixel = pDeviceBitmap->GetBPP() / 8; | |
| for (int dest_col = 0; dest_col < src_width_; dest_col++) { | |
| CStretchEngine::PixelWeight* pPixelWeights = | |
| weight_horz_.GetPixelWeight(dest_col); | |
| switch (trans_method_) { | |
| case TransformMethod::kInvalid: | |
| return; | |
| case TransformMethod::k8BppGrayToRgbMaybeAlpha: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_g = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| dest_g += pixel_weight * src_scan[j]; | |
| } | |
| FXSYS_memset(dest_scan, CStretchEngine::PixelFromFixed(dest_g), 3); | |
| dest_scan += dest_bytes_per_pixel; | |
| break; | |
| }); | |
| } | |
| case TransformMethod::k8BppRgbToRgbNoAlpha: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| uint32_t argb = src_palette_[src_scan[j]]; | |
| dest_r += pixel_weight * FXARGB_R(argb); | |
| dest_g += pixel_weight * FXARGB_G(argb); | |
| dest_b += pixel_weight * FXARGB_B(argb); | |
| } | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); | |
| dest_scan += dest_bytes_per_pixel - 3; | |
| break; | |
| }); | |
| } | |
| case TransformMethod::k8BppRgbToArgb: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_a = 0; | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| FX_ARGB argb = src_palette_[src_scan[j]]; | |
| dest_a += pixel_weight * FXARGB_A(argb); | |
| dest_r += pixel_weight * FXARGB_R(argb); | |
| dest_g += pixel_weight * FXARGB_G(argb); | |
| dest_b += pixel_weight * FXARGB_B(argb); | |
| } | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_a); | |
| break; | |
| }); | |
| } | |
| case TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_b = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_r = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel; | |
| dest_b += pixel_weight * (*src_pixel++); | |
| dest_g += pixel_weight * (*src_pixel++); | |
| dest_r += pixel_weight * (*src_pixel); | |
| } | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); | |
| dest_scan += dest_bytes_per_pixel - 3; | |
| break; | |
| }); | |
| } | |
| case TransformMethod::kRgbToBgrMaybeAlpha: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_b = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_r = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel; | |
| dest_r += pixel_weight * (*src_pixel++); | |
| dest_g += pixel_weight * (*src_pixel++); | |
| dest_b += pixel_weight * (*src_pixel); | |
| } | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); | |
| dest_scan += dest_bytes_per_pixel - 3; | |
| break; | |
| }); | |
| } | |
| case TransformMethod::kCmykToRgbMaybeAlpha: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_b = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_r = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel; | |
| FX_RGB_STRUCT<uint8_t> src_rgb = | |
| AdobeCmykToStandardRgb(255 - src_pixel[0], 255 - src_pixel[1], | |
| 255 - src_pixel[2], 255 - src_pixel[3]); | |
| dest_b += pixel_weight * src_rgb.blue; | |
| dest_g += pixel_weight * src_rgb.green; | |
| dest_r += pixel_weight * src_rgb.red; | |
| } | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); | |
| dest_scan += dest_bytes_per_pixel - 3; | |
| break; | |
| }); | |
| } | |
| case TransformMethod::kArgbToArgb: { | |
| UNSAFE_TODO({ | |
| uint32_t dest_alpha = 0; | |
| uint32_t dest_r = 0; | |
| uint32_t dest_g = 0; | |
| uint32_t dest_b = 0; | |
| for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; | |
| j++) { | |
| uint32_t pixel_weight = | |
| pPixelWeights->weights_[j - pPixelWeights->src_start_]; | |
| const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel; | |
| pixel_weight = pixel_weight * src_pixel[3] / 255; | |
| dest_b += pixel_weight * (*src_pixel++); | |
| dest_g += pixel_weight * (*src_pixel++); | |
| dest_r += pixel_weight * (*src_pixel); | |
| dest_alpha += pixel_weight; | |
| } | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); | |
| *dest_scan++ = CStretchEngine::PixelFromFixed(dest_alpha * 255); | |
| break; | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| void ProgressiveDecoder::Resample(const RetainPtr<CFX_DIBitmap>& pDeviceBitmap, | |
| int32_t src_line, | |
| uint8_t* src_scan, | |
| Format src_format) { | |
| if (src_line < 0 || src_line >= src_height_) { | |
| return; | |
| } | |
| ResampleScanline(pDeviceBitmap, src_line, decode_buf_, src_format); | |
| } | |
| FXDIB_Format ProgressiveDecoder::GetBitmapFormat() const { | |
| switch (image_type_) { | |
| case FXCODEC_IMAGE_JPG: | |
| case FXCODEC_IMAGE_BMP: | |
| return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; | |
| case FXCODEC_IMAGE_PNG: | |
| case FXCODEC_IMAGE_TIFF: | |
| default: | |
| // TODO(crbug.com/355630556): Consider adding support for | |
| // `FXDIB_Format::kBgraPremul` | |
| return FXDIB_Format::kBgra; | |
| } | |
| } | |
| std::pair<FXCODEC_STATUS, size_t> ProgressiveDecoder::GetFrames() { | |
| if (!(status_ == FXCODEC_STATUS::kFrameReady || | |
| status_ == FXCODEC_STATUS::kFrameToBeContinued)) { | |
| return {FXCODEC_STATUS::kError, 0}; | |
| } | |
| switch (image_type_) { | |
| case FXCODEC_IMAGE_JPG: | |
| case FXCODEC_IMAGE_BMP: | |
| case FXCODEC_IMAGE_PNG: | |
| case FXCODEC_IMAGE_TIFF: | |
| frame_number_ = 1; | |
| status_ = FXCODEC_STATUS::kDecodeReady; | |
| return {status_, 1}; | |
| case FXCODEC_IMAGE_GIF: { | |
| auto* ctx = static_cast<CFX_GifContext*>(context_.get()); | |
| while (true) { | |
| ProgressiveDecoderContext::Status readResult = ctx->GetFrame(); | |
| frame_number_ = | |
| readResult == ProgressiveDecoderContext::Status::kSuccess | |
| ? ctx->GetFrameNum() | |
| : 0; | |
| while (readResult == ProgressiveDecoderContext::Status::kContinue) { | |
| FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; | |
| if (!GifReadMoreData(&error_status)) { | |
| return {error_status, 0}; | |
| } | |
| readResult = ctx->GetFrame(); | |
| frame_number_ = | |
| readResult == ProgressiveDecoderContext::Status::kSuccess | |
| ? ctx->GetFrameNum() | |
| : 0; | |
| } | |
| if (readResult == ProgressiveDecoderContext::Status::kSuccess) { | |
| status_ = FXCODEC_STATUS::kDecodeReady; | |
| return {status_, frame_number_}; | |
| } | |
| context_ = nullptr; | |
| status_ = FXCODEC_STATUS::kError; | |
| return {status_, 0}; | |
| } | |
| } | |
| default: | |
| return {FXCODEC_STATUS::kError, 0}; | |
| } | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::StartDecode(RetainPtr<CFX_DIBitmap> bitmap) { | |
| CHECK(bitmap); | |
| CHECK_EQ(bitmap->GetWidth(), src_width_); | |
| CHECK_EQ(bitmap->GetHeight(), src_height_); | |
| CHECK_GT(src_width_, 0); | |
| CHECK_GT(src_height_, 0); | |
| const FXDIB_Format format = bitmap->GetFormat(); | |
| CHECK(format == FXDIB_Format::kBgra || format == FXDIB_Format::kBgr || | |
| format == FXDIB_Format::kBgrx); | |
| if (status_ != FXCODEC_STATUS::kDecodeReady) { | |
| return FXCODEC_STATUS::kError; | |
| } | |
| if (frame_number_ == 0) { | |
| return FXCODEC_STATUS::kError; | |
| } | |
| if (bitmap->GetWidth() > 65535 || bitmap->GetHeight() > 65535) { | |
| return FXCODEC_STATUS::kError; | |
| } | |
| device_bitmap_ = std::move(bitmap); | |
| switch (image_type_) { | |
| case FXCODEC_IMAGE_JPG: | |
| return JpegStartDecode(); | |
| case FXCODEC_IMAGE_BMP: | |
| return BmpStartDecode(); | |
| case FXCODEC_IMAGE_GIF: | |
| return GifStartDecode(); | |
| case FXCODEC_IMAGE_PNG: | |
| return PngStartDecode(); | |
| case FXCODEC_IMAGE_TIFF: | |
| status_ = FXCODEC_STATUS::kDecodeToBeContinued; | |
| return status_; | |
| default: | |
| return FXCODEC_STATUS::kError; | |
| } | |
| } | |
| FXCODEC_STATUS ProgressiveDecoder::ContinueDecode() { | |
| if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { | |
| return FXCODEC_STATUS::kError; | |
| } | |
| switch (image_type_) { | |
| case FXCODEC_IMAGE_JPG: | |
| return JpegContinueDecode(); | |
| case FXCODEC_IMAGE_BMP: | |
| return BmpContinueDecode(); | |
| case FXCODEC_IMAGE_GIF: | |
| return GifContinueDecode(); | |
| case FXCODEC_IMAGE_PNG: | |
| return PngContinueDecode(); | |
| case FXCODEC_IMAGE_TIFF: | |
| return TiffContinueDecode(); | |
| default: | |
| return FXCODEC_STATUS::kError; | |
| } | |
| } | |
| int ProgressiveDecoder::GetScanlineSize() const { | |
| // Can't be called before basic image metadata is decoded. | |
| CHECK_NE(src_components_count_, 0); | |
| CHECK_NE(src_width_, 0); | |
| FX_SAFE_INT32 result = src_width_; | |
| result *= src_components_count_; | |
| return FxAlignToBoundary<4>(result).ValueOrDie(); | |
| } | |
| } // namespace fxcodec |