blob: 64cb274447c97ec60258e4431f835616baa7e3f2 [file]
// 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/libjpeg_jpeg_context.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcodec/fx_codec.h"
#include "core/fxcodec/progressive_decoder_context_delegate.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/fx_memory.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/span.h"
#include "core/fxge/dib/cfx_dibbase.h"
#include "core/fxge/dib/cfx_dibitmap.h"
#include "core/fxge/dib/fx_dib.h"
namespace fxcodec {
namespace {
void JpegLoadAttribute(const jpeg_decompress_struct& info,
CFX_DIBAttribute* attribute) {
CHECK(attribute);
attribute->x_dpi_ = info.X_density;
attribute->y_dpi_ = info.Y_density;
attribute->dpi_unit_ =
static_cast<CFX_DIBAttribute::ResUnit>(info.density_unit);
}
void RGB2BGR(pdfium::span<uint8_t> buffer) {
for (size_t i = 0; i + 2 < buffer.size(); i += 3) {
std::swap(buffer[i], buffer[i + 2]);
}
}
ProgressiveDecoderContextDelegate::Format GetCodecFormat(int num_components) {
switch (num_components) {
case 1:
return ProgressiveDecoderContextDelegate::Format::k8bppGray;
case 3:
return ProgressiveDecoderContextDelegate::Format::kRgb;
case 4:
return ProgressiveDecoderContextDelegate::Format::kCmyk;
default:
return ProgressiveDecoderContextDelegate::Format::kInvalid;
}
}
} // namespace
LibjpegJpegContext::LibjpegJpegContext(
ProgressiveDecoderContextDelegate* delegate)
: delegate_(delegate) {
common_.cinfo.client_data = &common_;
common_.cinfo.err = &common_.error_mgr;
common_.error_mgr.error_exit = jpeg_common_error_fatal;
common_.error_mgr.emit_message = jpeg_common_error_do_nothing_int;
common_.error_mgr.output_message = jpeg_common_error_do_nothing;
common_.error_mgr.format_message = jpeg_common_error_do_nothing_char;
common_.error_mgr.reset_error_mgr = jpeg_common_error_do_nothing;
common_.source_mgr.init_source = jpeg_common_src_do_nothing;
common_.source_mgr.term_source = jpeg_common_src_do_nothing;
common_.source_mgr.skip_input_data = jpeg_common_src_skip_data_or_record;
common_.source_mgr.fill_input_buffer = jpeg_common_src_fill_buffer;
common_.source_mgr.resync_to_restart = jpeg_common_src_resync;
create_ok_ = jpeg_common_create_decompress(&common_);
if (create_ok_) {
common_.cinfo.src = &common_.source_mgr;
common_.skip_size = 0;
}
}
LibjpegJpegContext::~LibjpegJpegContext() {
if (create_ok_) {
jpeg_destroy_decompress(&common_.cinfo);
}
}
FX_FILESIZE LibjpegJpegContext::GetAvailInput() const {
return static_cast<FX_FILESIZE>(common_.source_mgr.bytes_in_buffer);
}
void LibjpegJpegContext::Input(RetainPtr<CFX_CodecMemory> codec_memory) {
codec_memory_ = codec_memory;
pdfium::span<uint8_t> src_buf = codec_memory->GetUnconsumedSpan();
if (common_.skip_size) {
if (common_.skip_size > src_buf.size()) {
common_.source_mgr.bytes_in_buffer = 0;
common_.skip_size -= src_buf.size();
return;
}
src_buf = src_buf.subspan(common_.skip_size);
common_.skip_size = 0;
}
common_.source_mgr.next_input_byte = src_buf.data();
common_.source_mgr.bytes_in_buffer = src_buf.size();
}
void LibjpegJpegContext::SyncCodecMemory() {
codec_memory_->Seek(codec_memory_->GetSize() -
common_.source_mgr.bytes_in_buffer);
}
FXCODEC_STATUS LibjpegJpegContext::StartDecode() {
decode_row_ = 0;
while (!StartScanline()) {
FXCODEC_STATUS error_status = FXCODEC_STATUS::kError;
SyncCodecMemory();
if (!delegate_->ReadMoreData(std::nullopt, &error_status)) {
return error_status;
}
}
FX_SAFE_SIZE_T line_size = common_.cinfo.image_width;
line_size *= common_.cinfo.num_components;
line_buf_.resize(FxAlignToBoundary<4>(line_size).ValueOrDie());
ProgressiveDecoderContextDelegate::Format src_format =
GetCodecFormat(common_.cinfo.num_components);
if (!delegate_->PrepareScanlineResampling(
common_.cinfo.image_width, common_.cinfo.image_height, src_format)) {
return FXCODEC_STATUS::kError;
}
return FXCODEC_STATUS::kDecodeToBeContinued;
}
FXCODEC_STATUS LibjpegJpegContext::ContinueDecode() {
const bool is_rgb = common_.cinfo.num_components == 3;
while (true) {
int err_code = ReadScanline(line_buf_.data());
if (err_code == LibjpegJpegContext::kFatal) {
return FXCODEC_STATUS::kError;
}
if (err_code != LibjpegJpegContext::kOk) {
FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished;
SyncCodecMemory();
if (delegate_->ReadMoreData(std::nullopt, &error_status)) {
continue;
}
return error_status;
}
if (is_rgb) {
RGB2BGR(pdfium::span(line_buf_).first(
static_cast<size_t>(common_.cinfo.image_width) * 3));
}
delegate_->ResampleScanline(decode_row_++, line_buf_);
if (decode_row_ >= static_cast<int>(common_.cinfo.image_height)) {
return FXCODEC_STATUS::kDecodeFinished;
}
}
}
int LibjpegJpegContext::ReadHeader(int* width,
int* height,
int* nComps,
CFX_DIBAttribute* attribute) {
DCHECK(create_ok_);
if (setjmp(common_.jmpbuf)) {
return kFatal;
}
int ret = jpeg_read_header(&common_.cinfo, true);
if (ret == JPEG_SUSPENDED) {
return kNeedsMoreInput;
}
if (ret != JPEG_HEADER_OK) {
return kError;
}
*width = common_.cinfo.image_width;
*height = common_.cinfo.image_height;
*nComps = common_.cinfo.num_components;
JpegLoadAttribute(common_.cinfo, attribute);
return kOk;
}
bool LibjpegJpegContext::StartScanline() {
common_.cinfo.scale_denom = 1;
return !!jpeg_common_start_decompress(&common_);
}
int LibjpegJpegContext::ReadScanline(uint8_t* dest_buf) {
int nlines = jpeg_common_read_scanlines(&common_, &dest_buf, 1);
if (nlines == -1) {
return kFatal;
}
return nlines == 1 ? kOk : kError;
}
} // namespace fxcodec