| // Copyright 2026 The PDFium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "core/fxcodec/jpeg/rust_jpeg_context.h" | |
| #include <utility> | |
| #include "core/fxcodec/cfx_codec_memory.h" | |
| #include "core/fxcodec/jpeg/rust_jpeg_ffi.rs.h" | |
| #include "core/fxcodec/progressive_decoder_context_delegate.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/check_op.h" | |
| #include "core/fxcrt/data_vector.h" | |
| #include "core/fxcrt/fx_safe_types.h" | |
| #include "core/fxcrt/span.h" | |
| #include "core/fxge/dib/cfx_dibitmap.h" | |
| namespace fxcodec { | |
| RustJpegContext::RustJpegContext(ProgressiveDecoderContextDelegate* delegate) | |
| : delegate_(delegate) {} | |
| RustJpegContext::~RustJpegContext() = default; | |
| bool RustJpegContext::ReadHeader() { | |
| if (state_ == State::kGotHeader || state_ == State::kFinishedDecoding) { | |
| return true; | |
| } | |
| if (state_ == State::kError || !codec_memory_) { | |
| return false; | |
| } | |
| CHECK_EQ(state_, State::kNoDecoder); | |
| pdfium::span<const uint8_t> src_span = codec_memory_->GetBufferSpan(); | |
| rust_jpeg::JpegHeaderInfo header_info{}; | |
| rust::Slice<const uint8_t> src_slice(src_span); | |
| if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { | |
| // More data may be needed to parse the header during progressive decoding, | |
| // so do not transition to `State::kError` here. | |
| return false; | |
| } | |
| switch (header_info.num_components) { | |
| case 1: | |
| src_format_ = ProgressiveDecoderContextDelegate::Format::k8bppGray; | |
| break; | |
| case 3: | |
| src_format_ = ProgressiveDecoderContextDelegate::Format::kRgb; | |
| break; | |
| case 4: | |
| src_format_ = ProgressiveDecoderContextDelegate::Format::kCmyk; | |
| break; | |
| default: | |
| state_ = State::kError; | |
| return false; | |
| } | |
| width_ = header_info.width; | |
| height_ = header_info.height; | |
| num_components_ = header_info.num_components; | |
| x_density_ = header_info.x_density; | |
| y_density_ = header_info.y_density; | |
| density_unit_ = header_info.density_unit; | |
| state_ = State::kGotHeader; | |
| return true; | |
| } | |
| bool RustJpegContext::ProcessData() { | |
| if (state_ == State::kFinishedDecoding) { | |
| return true; | |
| } | |
| if (state_ == State::kError) { | |
| return false; | |
| } | |
| if (state_ == State::kNoDecoder && !ReadHeader()) { | |
| return false; | |
| } | |
| CHECK_EQ(state_, State::kGotHeader); | |
| if (!delegate_->PrepareScanlineResampling(width_, height_, src_format_)) { | |
| state_ = State::kError; | |
| return false; | |
| } | |
| pdfium::span<const uint8_t> src_span = codec_memory_->GetBufferSpan(); | |
| rust::Slice<const uint8_t> src_slice(src_span); | |
| FX_SAFE_SIZE_T row_bytes = width_; | |
| row_bytes *= num_components_; | |
| row_bytes += 3; | |
| row_bytes /= 4; | |
| row_bytes *= 4; | |
| if (!row_bytes.IsValid()) { | |
| state_ = State::kError; | |
| return false; | |
| } | |
| FX_SAFE_SIZE_T total_bytes = row_bytes; | |
| total_bytes *= height_; | |
| if (!total_bytes.IsValid()) { | |
| state_ = State::kError; | |
| return false; | |
| } | |
| DataVector<uint8_t> decoded_buf(total_bytes.ValueOrDie()); | |
| rust::Slice<uint8_t> out_slice(decoded_buf); | |
| if (!rust_jpeg::decode_jpeg_to_buf(src_slice, out_slice, | |
| row_bytes.ValueOrDie(), | |
| /*scale_denom=*/1)) { | |
| state_ = State::kError; | |
| return false; | |
| } | |
| size_t stride = row_bytes.ValueOrDie(); | |
| for (uint32_t y = 0; y < height_; ++y) { | |
| pdfium::span<const uint8_t> scanline = | |
| pdfium::span(decoded_buf).subspan(y * stride, stride); | |
| delegate_->ResampleScanline(y, scanline); | |
| } | |
| state_ = State::kFinishedDecoding; | |
| return true; | |
| } | |
| void RustJpegContext::Input(RetainPtr<CFX_CodecMemory> codec_memory) { | |
| codec_memory_ = std::move(codec_memory); | |
| } | |
| FXCODEC_STATUS RustJpegContext::StartDecode(RetainPtr<CFX_DIBitmap> bitmap) { | |
| bitmap_ = std::move(bitmap); | |
| FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeToBeContinued; | |
| if (!delegate_->ReadMoreData(0, &status)) { | |
| return status; | |
| } | |
| return FXCODEC_STATUS::kDecodeToBeContinued; | |
| } | |
| FXCODEC_STATUS RustJpegContext::ContinueDecode() { | |
| FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeFinished; | |
| while (delegate_->ReadMoreData(std::nullopt, &status)) { | |
| } | |
| if (status == FXCODEC_STATUS::kError || !ProcessData()) { | |
| state_ = State::kError; | |
| status = FXCODEC_STATUS::kError; | |
| } | |
| bitmap_ = nullptr; | |
| return state_ == State::kFinishedDecoding ? FXCODEC_STATUS::kDecodeFinished | |
| : status; | |
| } | |
| } // namespace fxcodec |