| // 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) { | |
| #if defined(PDF_ENABLE_RUST_JPEG) | |
| return RustJpegScanlineDecoder::Create(src_span, width, height, nComps, | |
| ColorTransform, scale_denom); | |
| #else | |
| return LibjpegScanlineDecoder::Create(src_span, width, height, nComps, | |
| ColorTransform, scale_denom); | |
| #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 |