blob: 82239252a7eaaba7327e56d737fbda51b38a7a8e [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.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fxcodec/jpeg/jpegmodule.h"
#include <memory>
#include <optional>
#include "build/build_config.h"
#include "core/fxcodec/scanlinedecoder.h"
#include "core/fxcrt/span.h"
#if defined(PDF_ENABLE_RUST_JPEG)
#include "core/fxcodec/jpeg/rust_jpeg_scanline_decoder.h"
#else
#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "core/fxcodec/jpeg/jpeg_common.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_memory.h"
#include "core/fxcrt/fx_memory_wrappers.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxge/dib/cfx_dibbase.h"
#include "core/fxge/dib/fx_dib.h"
#endif // BUILDFLAG(IS_WIN)
namespace fxcodec {
#if BUILDFLAG(IS_WIN)
namespace {
struct ScopedJpegCompressCommon {
~ScopedJpegCompressCommon() {
if (created) {
jpeg_common_destroy_compress(&common);
}
}
JpegCompressCommon common = {};
bool created = false;
};
} // namespace
#endif // BUILDFLAG(IS_WIN)
// static
std::unique_ptr<ScanlineDecoder> JpegModule::CreateDecoder(
pdfium::span<const uint8_t> src_span,
uint32_t width,
uint32_t height,
int nComps,
bool ColorTransform,
uint32_t scale_denom,
bool prefer_bgr_output) {
#if defined(PDF_ENABLE_RUST_JPEG)
// RustJpegScanlineDecoder does not implement `prefer_bgr_output`; it never
// reports ScanlinesAreBgr(), so callers translate as usual.
return RustJpegScanlineDecoder::Create(src_span, width, height, nComps,
ColorTransform, scale_denom);
#else
return LibjpegScanlineDecoder::Create(src_span, width, height, nComps,
ColorTransform, scale_denom,
prefer_bgr_output);
#endif
}
// static
std::optional<JpegModule::ImageInfo> JpegModule::LoadInfo(
pdfium::span<const uint8_t> src_span) {
#if defined(PDF_ENABLE_RUST_JPEG)
return RustJpegScanlineDecoder::LoadInfo(src_span);
#else
return LibjpegScanlineDecoder::LoadInfo(src_span);
#endif
}
#if BUILDFLAG(IS_WIN)
bool JpegModule::JpegEncode(const RetainPtr<const CFX_DIBBase>& pSource,
uint8_t** dest_buf,
size_t* dest_size) {
const int bytes_per_pixel = pSource->GetBPP() / 8;
uint32_t nComponents = bytes_per_pixel >= 3 ? 3 : 1;
uint32_t pitch = pSource->GetPitch();
uint32_t width = pdfium::checked_cast<uint32_t>(pSource->GetWidth());
uint32_t height = pdfium::checked_cast<uint32_t>(pSource->GetHeight());
FX_SAFE_UINT32 safe_buf_len = width;
safe_buf_len *= height;
safe_buf_len *= nComponents;
safe_buf_len += 1024;
if (!safe_buf_len.IsValid()) {
return false;
}
static constexpr uint32_t kMinTryBufLen = 1024;
uint32_t dest_buf_length = safe_buf_len.ValueOrDie();
std::unique_ptr<uint8_t, FxFreeDeleter> local_dest;
for (; dest_buf_length >= kMinTryBufLen; dest_buf_length >>= 1) {
local_dest.reset(FX_TryAlloc(uint8_t, dest_buf_length));
if (local_dest) {
break;
}
}
if (!local_dest) {
return false;
}
ScopedJpegCompressCommon compress_common;
compress_common.created =
jpeg_common_create_compress(&compress_common.common);
if (!compress_common.created) {
return false;
}
compress_common.common.dest_mgr.next_output_byte = local_dest.get();
compress_common.common.dest_mgr.free_in_buffer = dest_buf_length;
compress_common.common.cinfo.image_width = width;
compress_common.common.cinfo.image_height = height;
compress_common.common.cinfo.input_components = nComponents;
if (nComponents == 1) {
compress_common.common.cinfo.in_color_space = JCS_GRAYSCALE;
} else if (nComponents == 3) {
compress_common.common.cinfo.in_color_space = JCS_RGB;
} else {
compress_common.common.cinfo.in_color_space = JCS_CMYK;
}
std::unique_ptr<uint8_t, FxFreeDeleter> line_buf;
if (nComponents > 1) {
line_buf.reset(FX_Alloc2D(uint8_t, width, nComponents));
}
if (!jpeg_common_set_defaults(&compress_common.common) ||
!jpeg_common_start_compress(&compress_common.common, TRUE)) {
return false;
}
JSAMPROW row_pointer;
JDIMENSION row;
while (compress_common.common.cinfo.next_scanline <
compress_common.common.cinfo.image_height) {
pdfium::span<const uint8_t> src_scan =
pSource->GetScanline(compress_common.common.cinfo.next_scanline);
if (nComponents > 1) {
uint8_t* dest_scan = line_buf.get();
if (nComponents == 3) {
UNSAFE_TODO({
for (uint32_t i = 0; i < width; i++) {
ReverseCopy3Bytes(dest_scan, src_scan.data());
dest_scan += 3;
src_scan = src_scan.subspan(static_cast<size_t>(bytes_per_pixel));
}
});
} else {
UNSAFE_TODO({
for (uint32_t i = 0; i < pitch; i++) {
*dest_scan++ = ~src_scan.front();
src_scan = src_scan.subspan<1u>();
}
});
}
row_pointer = line_buf.get();
} else {
row_pointer = const_cast<uint8_t*>(src_scan.data());
}
row = compress_common.common.cinfo.next_scanline;
if (jpeg_common_write_scanlines(&compress_common.common, &row_pointer, 1) <
0) {
return false;
}
UNSAFE_TODO({
if (compress_common.common.cinfo.next_scanline == row) {
static constexpr size_t kJpegBlockSize = 1048576;
local_dest.reset(FX_Realloc(uint8_t, local_dest.release(),
dest_buf_length + kJpegBlockSize));
compress_common.common.dest_mgr.next_output_byte =
local_dest.get() + dest_buf_length -
compress_common.common.dest_mgr.free_in_buffer;
dest_buf_length += kJpegBlockSize;
compress_common.common.dest_mgr.free_in_buffer += kJpegBlockSize;
}
});
}
if (!jpeg_common_finish_compress(&compress_common.common)) {
return false;
}
*dest_size =
dest_buf_length -
static_cast<size_t>(compress_common.common.dest_mgr.free_in_buffer);
*dest_buf = local_dest.release();
return true;
}
#endif // BUILDFLAG(IS_WIN)
} // namespace fxcodec