// 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 #include #include #include "build/build_config.h" #include "core/fxcodec/cfx_codec_memory.h" #include "core/fxcodec/jpeg/cjpegcontext.h" #include "core/fxcodec/jpeg/jpeg_progressive_decoder.h" #include "core/fxcodec/progressive_decoder_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" #ifdef PDF_ENABLE_XFA_BMP #if defined(PDF_ENABLE_RUST_BMP) #include "core/fxcodec/bmp/skia_bmp_context.h" #else #include "core/fxcodec/bmp/cfx_bmpcontext.h" #endif #endif // PDF_ENABLE_XFA_BMP #ifdef PDF_ENABLE_XFA_GIF #include "core/fxcodec/gif/cfx_gifcontext.h" #endif // PDF_ENABLE_XFA_GIF #ifdef PDF_ENABLE_XFA_PNG #include "core/fxcodec/png/png_decoder_delegate.h" // TODO(https://crbug.com/444045690): Remove `pdf_enable_rust_png` from the // condition below once this build mode has been tested and stabilized. // (Chromium already sets `pdf_use_skia_override = true` so having an extra // condition avoids affecting the Chromium behavior.) #if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) #include "core/fxcodec/png/skia_png_context.h" #include "core/fxcodec/png/skia_png_decoder.h" #else #include "core/fxcodec/png/cpngcontext.h" #include "core/fxcodec/png/libpng_png_decoder.h" #endif #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_TIFF #include "core/fxcodec/tiff/tiff_decoder.h" #endif // PDF_ENABLE_XFA_TIFF namespace fxcodec { namespace { constexpr size_t kBlockSize = 4096; std::unique_ptr CreateDecoderContext( FXCODEC_IMAGE_TYPE type, ProgressiveDecoder* delegate) { switch (type) { #ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: #if defined(PDF_ENABLE_RUST_BMP) return std::make_unique(delegate); #else return std::make_unique(delegate); #endif #endif // PDF_ENABLE_XFA_BMP #ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: return std::make_unique(delegate); #endif // PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_JPG: { auto context = std::make_unique(); if (!context->create_ok_) { return nullptr; } return context; } #ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: #if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) return std::make_unique(delegate); #else { auto context = std::make_unique(delegate); if (!context->png_ || !context->info_) { return nullptr; } return context; } #endif #endif // PDF_ENABLE_XFA_PNG default: return nullptr; } } #ifdef PDF_ENABLE_XFA_PNG #if BUILDFLAG(IS_APPLE) const double kPngGamma = 1.7; #else const double kPngGamma = 2.2; #endif // BUILDFLAG(IS_APPLE) #endif // PDF_ENABLE_XFA_PNG void RGB2BGR(uint8_t* buffer, int width = 1) { if (buffer && width > 0) { uint8_t temp; int i = 0; int j = 0; UNSAFE_TODO({ for (; i < width; i++, j += 3) { temp = buffer[j]; buffer[j] = buffer[j + 2]; buffer[j + 2] = temp; } }); } } } // namespace ProgressiveDecoder::ProgressiveDecoder() = default; ProgressiveDecoder::~ProgressiveDecoder() = default; #ifdef PDF_ENABLE_XFA_PNG bool ProgressiveDecoder::PngReadHeader(int width, int height, double* gamma) { if (!device_bitmap_) { got_png_metadata_ = true; src_width_ = width; src_height_ = height; // PNG decoder always decodes into BGRA. src_bits_per_component_ = 8; src_components_count_ = 4; src_format_ = FXCodec_Argb; return false; } CHECK_EQ(width, src_width_); CHECK_EQ(height, src_height_); CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); *gamma = kPngGamma; return true; } pdfium::span ProgressiveDecoder::PngAskScanlineBuf(int line) { CHECK_GE(line, 0); CHECK_LT(line, src_height_); CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); CHECK_EQ(src_format_, FXCodec_Argb); return device_bitmap_->GetWritableScanline(line); } pdfium::span ProgressiveDecoder::PngAskImageBuf() { CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); CHECK_EQ(src_format_, FXCodec_Argb); return device_bitmap_->GetWritableBuffer(); } void ProgressiveDecoder::PngFinishedDecoding() { status_ = FXCODEC_STATUS::kDecodeFinished; } #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_GIF uint32_t ProgressiveDecoder::GifCurrentPosition() const { uint32_t remain_size = pdfium::checked_cast(context_->GetAvailInput()); return offset_ - remain_size; } bool ProgressiveDecoder::GifInputRecordPositionBuf( uint32_t rcd_pos, const FX_RECT& img_rc, pdfium::span pal_span, int32_t trans_index) { offset_ = rcd_pos; FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; codec_memory_->Seek(codec_memory_->GetSize()); if (!GifReadMoreData(&error_status)) { return false; } if (pal_span.empty()) { pal_span = gif_palette_; } if (pal_span.empty()) { return false; } src_palette_.resize(pal_span.size()); for (size_t i = 0; i < pal_span.size(); i++) { src_palette_[i] = ArgbEncode(0xff, pal_span[i].r, pal_span[i].g, pal_span[i].b); } gif_trans_index_ = trans_index; gif_frame_rect_ = img_rc; int32_t pal_index = gif_bg_index_; RetainPtr pDevice = device_bitmap_; if (trans_index >= static_cast(pal_span.size())) { trans_index = -1; } if (trans_index != -1) { src_palette_[trans_index] &= 0x00ffffff; if (pDevice->IsAlphaFormat()) { pal_index = trans_index; } } if (pal_index >= static_cast(pal_span.size())) { return false; } int startX = 0; int startY = 0; int sizeX = src_width_; int sizeY = src_height_; const int bytes_per_pixel = pDevice->GetBPP() / 8; FX_ARGB argb = src_palette_[pal_index]; for (int row = 0; row < sizeY; row++) { pdfium::span scan_span = pDevice->GetWritableScanline(row + startY) .subspan(static_cast(startX * bytes_per_pixel)); switch (trans_method_) { case TransformMethod::k8BppRgbToRgbNoAlpha: { uint8_t* pScanline = scan_span.data(); UNSAFE_TODO({ for (int col = 0; col < sizeX; col++) { *pScanline++ = FXARGB_B(argb); *pScanline++ = FXARGB_G(argb); *pScanline++ = FXARGB_R(argb); pScanline += bytes_per_pixel - 3; } }); break; } case TransformMethod::k8BppRgbToArgb: { for (int col = 0; col < sizeX; col++) { FXARGB_SetDIB(scan_span.first<4u>(), argb); scan_span = scan_span.subspan<4u>(); } break; } default: break; } } return true; } void ProgressiveDecoder::GifReadScanline(int32_t row_num, pdfium::span row_buf) { RetainPtr pDIBitmap = device_bitmap_; const size_t img_width = static_cast(gif_frame_rect_.Width()); const pdfium::span row_span = row_buf.first(img_width); if (!pDIBitmap->IsAlphaFormat()) { for (auto& byte_ref : row_span) { if (byte_ref == gif_trans_index_) { byte_ref = gif_bg_index_; } } } int32_t pal_index = gif_bg_index_; if (gif_trans_index_ != -1 && device_bitmap_->IsAlphaFormat()) { pal_index = gif_trans_index_; } const int32_t left = gif_frame_rect_.left; const pdfium::span decode_span = decode_buf_; std::ranges::fill(decode_span.first(static_cast(src_width_)), pal_index); fxcrt::Copy(row_span, decode_span.subspan(static_cast(left))); int32_t line = row_num + gif_frame_rect_.top; if (line < 0 || line >= src_height_) { return; } ResampleScanline(pDIBitmap, line, decode_span, src_format_); } #endif // PDF_ENABLE_XFA_GIF #ifdef PDF_ENABLE_XFA_BMP bool ProgressiveDecoder::BmpInputImagePositionBuf(uint32_t rcd_pos) { offset_ = rcd_pos; FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; return BmpReadMoreData(context_.get(), &error_status); } void ProgressiveDecoder::BmpReadScanline(uint32_t row_num, pdfium::span row_buf) { RetainPtr pDIBitmap = device_bitmap_; DCHECK(pDIBitmap); int scanline_size = GetScanlineSize(); fxcrt::Copy(row_buf.first(static_cast(scanline_size)), decode_buf_); if (row_num >= static_cast(src_height_)) { return; } ResampleScanline(pDIBitmap, row_num, decode_buf_, src_format_); } bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( CFX_DIBAttribute* pAttribute) { std::unique_ptr pBmcontext = CreateDecoderContext(FXCODEC_IMAGE_BMP, this); pBmcontext->Input(codec_memory_); #if defined(PDF_ENABLE_RUST_BMP) auto* ctx = static_cast(pBmcontext.get()); #else auto* ctx = static_cast(pBmcontext.get()); #endif pdfium::span palette; ProgressiveDecoderContext::Status read_result = ctx->ReadHeader(&src_width_, &src_height_, &bmp_is_top_bottom_, &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_, &bmp_is_top_bottom_, &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_ = FXCodec_8bppRgb; format = FXDIB_Format::k8bppRgb; break; case 3: src_format_ = FXCodec_Rgb; format = FXDIB_Format::kBgr; break; case 4: src_format_ = FXCodec_Rgb32; 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 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( 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 (!ReadMoreData(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(0); 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(0); } device_bitmap_ = nullptr; file_ = nullptr; status_ = read_res == ProgressiveDecoderContext::Status::kSuccess ? FXCODEC_STATUS::kDecodeFinished : FXCODEC_STATUS::kError; return status_; } #endif // PDF_ENABLE_XFA_BMP #ifdef PDF_ENABLE_XFA_GIF 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 (!ReadMoreData(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(context_.get()); ProgressiveDecoderContext::Status readResult = ctx->ReadHeader(&src_width_, &src_height_, &gif_palette_, &gif_bg_index_); 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_, &gif_palette_, &gif_bg_index_); } 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_ = FXCodec_8bppRgb; 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); frame_cur_ = 0; status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; } FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() { ProgressiveDecoderContext::Status readRes = context_->DecodeImage(frame_cur_); while (readRes == ProgressiveDecoderContext::Status::kContinue) { FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; if (!GifReadMoreData(&error_status)) { device_bitmap_ = nullptr; file_ = nullptr; status_ = error_status; return status_; } readRes = context_->DecodeImage(frame_cur_); } if (readRes == ProgressiveDecoderContext::Status::kSuccess) { device_bitmap_ = nullptr; file_ = nullptr; status_ = FXCODEC_STATUS::kDecodeFinished; return status_; } device_bitmap_ = nullptr; file_ = nullptr; status_ = FXCODEC_STATUS::kError; return status_; } #endif // PDF_ENABLE_XFA_GIF bool ProgressiveDecoder::JpegReadMoreData(FXCODEC_STATUS* err_status) { FX_SAFE_SIZE_T avail_input = context_->GetAvailInput(); if (!avail_input.IsValid()) { return false; } if (!ReadMoreData(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_); while (1) { int read_result = JpegProgressiveDecoder::ReadHeader( context_.get(), &src_width_, &src_height_, &src_components_count_, pAttribute); switch (read_result) { case JpegProgressiveDecoder::kFatal: case JpegProgressiveDecoder::kError: status_ = FXCODEC_STATUS::kError; return false; case JpegProgressiveDecoder::kOk: src_bits_per_component_ = 8; return true; case JpegProgressiveDecoder::kNeedsMoreInput: { FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; if (!JpegReadMoreData(&error_status)) { status_ = error_status; return false; } break; } default: NOTREACHED(); } } } FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { while (!JpegProgressiveDecoder::StartScanline(context_.get())) { // Maybe it needs more data. FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; if (!JpegReadMoreData(&error_status)) { device_bitmap_ = nullptr; file_ = nullptr; status_ = error_status; return status_; } } decode_buf_.resize(GetScanlineSize()); FXDIB_ResampleOptions options; options.bInterpolateBilinear = true; weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, src_width_, options); switch (src_components_count_) { case 1: src_format_ = FXCodec_8bppGray; break; case 3: src_format_ = FXCodec_Rgb; break; case 4: src_format_ = FXCodec_Cmyk; break; } SetTransMethod(); status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; } FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { while (true) { int err_code = JpegProgressiveDecoder::ReadScanline(context_.get(), decode_buf_.data()); if (err_code == JpegProgressiveDecoder::kFatal) { context_.reset(); status_ = FXCODEC_STATUS::kError; return FXCODEC_STATUS::kError; } if (err_code != JpegProgressiveDecoder::kOk) { // Maybe it needs more data. FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; if (JpegReadMoreData(&error_status)) { continue; } device_bitmap_ = nullptr; file_ = nullptr; status_ = error_status; return status_; } if (src_format_ == FXCodec_Rgb) { RGB2BGR(UNSAFE_TODO(decode_buf_.data()), src_width_); } if (src_row_ >= src_height_) { device_bitmap_ = nullptr; file_ = nullptr; status_ = FXCODEC_STATUS::kDecodeFinished; return status_; } Resample(device_bitmap_, src_row_, decode_buf_.data(), src_format_); src_row_++; } } #ifdef PDF_ENABLE_XFA_PNG bool ProgressiveDecoder::PngReadMoreData() { size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); if (!ReadMoreData(unconsumed_bytes, &status_)) { return false; } return PngDecoder::ContinueDecode(context_.get(), codec_memory_); } bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this); if (!context_) { status_ = FXCODEC_STATUS::kError; return false; } // Keep feeding more input into the decoder until either the decoder 1) fails, // or 2) calls `PngReadHeader` to indicate that it `got_png_metadata_`. if (PngDecoder::ContinueDecode(context_.get(), codec_memory_)) { while (!got_png_metadata_ && PngReadMoreData()) { } } // Return `got_png_metadata_` and ignore any failures that the decoder may // have reported. (In particular ignore the failure that `PngReadHeader` // reports when there is no `device_bitmap_` - e.g. during image type // detection.) context_.reset(); return got_png_metadata_; } FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this); if (!context_) { device_bitmap_ = nullptr; file_ = nullptr; status_ = FXCODEC_STATUS::kError; return status_; } // No need to resample/transform pixels when decoding PNGs, because 1) // `device_bitmap_` for PNGs is always kBgra and 2) the decoder always outputs // `FXCodec_Argb` (the same format). In other words, PNG code path doesn't // need to use an intermediate `decode_buf_` to transform the pixels via // `SetTransMethod` and `ResampleScanline`. CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); CHECK_EQ(src_format_, FXCodec_Argb); // Discard old/stale data from `codec_memory_` and restart reading the `file_` // from `offset_` 0. codec_memory_->Seek(codec_memory_->GetSize()); offset_ = 0; status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; } FXCODEC_STATUS ProgressiveDecoder::PngContinueDecode() { while (status_ == FXCODEC_STATUS::kDecodeToBeContinued) { if (!PngReadMoreData()) { status_ = FXCODEC_STATUS::kError; break; } } context_.reset(); device_bitmap_ = nullptr; file_ = nullptr; CHECK(status_ == FXCODEC_STATUS::kDecodeFinished || status_ == FXCODEC_STATUS::kError); return status_; } #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_TIFF bool ProgressiveDecoder::TiffDetectImageTypeFromFile( CFX_DIBAttribute* pAttribute) { context_ = TiffDecoder::CreateDecoder(file_); if (!context_) { status_ = FXCODEC_STATUS::kError; return false; } int32_t dummy_bpc; bool ret = TiffDecoder::LoadFrameInfo(context_.get(), 0, &src_width_, &src_height_, &src_components_count_, &dummy_bpc, pAttribute); src_components_count_ = 4; if (!ret) { context_.reset(); status_ = FXCODEC_STATUS::kError; return false; } 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); status_ = TiffDecoder::Decode(context_.get(), std::move(device_bitmap_)) ? FXCODEC_STATUS::kDecodeFinished : FXCODEC_STATUS::kError; file_ = nullptr; return status_; } #endif // PDF_ENABLE_XFA_TIFF bool ProgressiveDecoder::DetectImageType(FXCODEC_IMAGE_TYPE imageType, CFX_DIBAttribute* pAttribute) { #ifdef PDF_ENABLE_XFA_TIFF if (imageType == FXCODEC_IMAGE_TIFF) { return TiffDetectImageTypeFromFile(pAttribute); } #endif // PDF_ENABLE_XFA_TIFF size_t size = pdfium::checked_cast( std::min(file_->GetSize(), kBlockSize)); codec_memory_ = pdfium::MakeRetain(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); } #ifdef PDF_ENABLE_XFA_BMP if (imageType == FXCODEC_IMAGE_BMP) { return BmpDetectImageTypeInBuffer(pAttribute); } #endif // PDF_ENABLE_XFA_BMP #ifdef PDF_ENABLE_XFA_GIF if (imageType == FXCODEC_IMAGE_GIF) { return GifDetectImageTypeInBuffer(); } #endif // PDF_ENABLE_XFA_GIF #ifdef PDF_ENABLE_XFA_PNG if (imageType == FXCODEC_IMAGE_PNG) { return PngDetectImageTypeInBuffer(); } #endif // PDF_ENABLE_XFA_PNG status_ = FXCODEC_STATUS::kError; return false; } bool ProgressiveDecoder::ReadMoreData(size_t unconsumed_bytes, FXCODEC_STATUS* err_status) { // Check for EOF. if (offset_ >= static_cast(file_->GetSize())) { return false; } // Try to get whatever remains. uint32_t bytes_to_fetch_from_file = pdfium::checked_cast(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(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( std::min(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 pFile, FXCODEC_IMAGE_TYPE imageType, CFX_DIBAttribute* pAttribute, bool bSkipImageTypeCheck) { DCHECK(pAttribute); 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(pFile); 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 (imageType != FXCODEC_IMAGE_UNKNOWN && DetectImageType(imageType, pAttribute)) { image_type_ = imageType; status_ = FXCODEC_STATUS::kFrameReady; return status_; } // If we got here then the image data does not match the requested decoder. // If we're skipping the type check then bail out at this point and return // the failed status. if (bSkipImageTypeCheck) { return status_; } for (int type = FXCODEC_IMAGE_UNKNOWN + 1; type < FXCODEC_IMAGE_MAX; type++) { if (DetectImageType(static_cast(type), pAttribute)) { image_type_ = static_cast(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 FXCodec_Invalid: trans_method_ = TransformMethod::kInvalid; break; case FXCodec_8bppGray: trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha; break; case FXCodec_8bppRgb: trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; break; case FXCodec_Rgb: case FXCodec_Rgb32: case FXCodec_Argb: trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; break; case FXCodec_Cmyk: trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; break; } break; } case FXDIB_Format::kBgrx: case FXDIB_Format::kBgra: { switch (src_format_) { case FXCodec_Invalid: trans_method_ = TransformMethod::kInvalid; break; case FXCodec_8bppGray: trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha; break; case FXCodec_8bppRgb: if (device_bitmap_->GetFormat() == FXDIB_Format::kBgra) { trans_method_ = TransformMethod::k8BppRgbToArgb; } else { trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; } break; case FXCodec_Rgb: case FXCodec_Rgb32: trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; break; case FXCodec_Cmyk: trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; break; case FXCodec_Argb: 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& pDeviceBitmap, int dest_line, pdfium::span src_span, FXCodec_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 = (src_format & 0xff) / 8; 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::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 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& pDeviceBitmap, int32_t src_line, uint8_t* src_scan, FXCodec_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: #ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: #endif // PDF_ENABLE_XFA_BMP return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; #ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: #endif // PDF_ENABLE_XFA_TIFF default: // TODO(crbug.com/355630556): Consider adding support for // `FXDIB_Format::kBgraPremul` return FXDIB_Format::kBgra; } } std::pair ProgressiveDecoder::GetFrames() { if (!(status_ == FXCODEC_STATUS::kFrameReady || status_ == FXCODEC_STATUS::kFrameToBeContinued)) { return {FXCODEC_STATUS::kError, 0}; } switch (image_type_) { #ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: #endif // PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_JPG: #ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: #endif // PDF_ENABLE_XFA_TIFF frame_number_ = 1; status_ = FXCODEC_STATUS::kDecodeReady; return {status_, 1}; #ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: { auto* ctx = static_cast(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}; } } #endif // PDF_ENABLE_XFA_GIF default: return {FXCODEC_STATUS::kError, 0}; } } FXCODEC_STATUS ProgressiveDecoder::StartDecode(RetainPtr 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; } frame_cur_ = 0; device_bitmap_ = std::move(bitmap); switch (image_type_) { #ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: return BmpStartDecode(); #endif // PDF_ENABLE_XFA_BMP #ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: return GifStartDecode(); #endif // PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_JPG: return JpegStartDecode(); #ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: return PngStartDecode(); #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; #endif // PDF_ENABLE_XFA_TIFF 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(); #ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: return BmpContinueDecode(); #endif // PDF_ENABLE_XFA_BMP #ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: return GifContinueDecode(); #endif // PDF_ENABLE_XFA_GIF #ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: return PngContinueDecode(); #endif // PDF_ENABLE_XFA_PNG #ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: return TiffContinueDecode(); #endif // PDF_ENABLE_XFA_TIFF 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