| // 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_scanline_decoder.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <optional> | |
| #include <utility> | |
| #include "core/fxcodec/jpeg/rust_jpeg_ffi.rs.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/check_op.h" | |
| #include "core/fxcrt/fx_safe_types.h" | |
| #include "core/fxcrt/span_util.h" | |
| namespace fxcodec { | |
| namespace { | |
| constexpr size_t kKnownBadHeaderWithInvalidHeightByteOffsetStarts[] = {94, 163}; | |
| constexpr size_t kSofMarkerByteOffset = 5; | |
| pdfium::span<const uint8_t> JpegScanSOI(pdfium::span<const uint8_t> src_span) { | |
| if (src_span.size() < 2) { | |
| return src_span; | |
| } | |
| for (size_t offset = 0; offset + 1 < src_span.size(); ++offset) { | |
| if (src_span[offset] == 0xff && src_span[offset + 1] == 0xd8) { | |
| return src_span.subspan(offset); | |
| } | |
| } | |
| return src_span; | |
| } | |
| uint32_t ScaledJpegSize(uint32_t dim, uint32_t scale_denom) { | |
| return (dim + scale_denom - 1) / scale_denom; | |
| } | |
| bool IsSofSegment(pdfium::span<const uint8_t> src_span, size_t marker_offset) { | |
| if (src_span.size() <= marker_offset + 1) { | |
| return false; | |
| } | |
| const auto header_marker = src_span.subspan(marker_offset); | |
| return header_marker[0] == 0xff && header_marker[1] >= 0xc0 && | |
| header_marker[1] <= 0xcf; | |
| } | |
| bool HasKnownBadHeaderWithInvalidHeight(pdfium::span<const uint8_t> src_span, | |
| size_t dimension_offset, | |
| uint32_t orig_width) { | |
| if (src_span.size() <= dimension_offset + 3u) { | |
| return false; | |
| } | |
| if (dimension_offset < kSofMarkerByteOffset) { | |
| return false; | |
| } | |
| if (!IsSofSegment(src_span, dimension_offset - kSofMarkerByteOffset)) { | |
| return false; | |
| } | |
| const auto header_dimensions = src_span.subspan(dimension_offset); | |
| uint8_t expected_width_byte1 = (orig_width >> 8) & 0xff; | |
| uint8_t expected_width_byte2 = orig_width & 0xff; | |
| return header_dimensions[0] == 0xff && header_dimensions[1] == 0xff && | |
| header_dimensions[2] == expected_width_byte1 && | |
| header_dimensions[3] == expected_width_byte2; | |
| } | |
| void PatchUpKnownBadHeaderWithInvalidHeight(pdfium::span<uint8_t> src_span, | |
| size_t dimension_offset, | |
| uint32_t orig_height) { | |
| auto data = src_span.subspan(dimension_offset); | |
| data[0] = (orig_height >> 8) & 0xff; | |
| data[1] = orig_height & 0xff; | |
| } | |
| void PatchUpTrailer(pdfium::span<uint8_t> src_span) { | |
| src_span[src_span.size() - 2] = 0xff; | |
| src_span[src_span.size() - 1] = 0xd9; | |
| } | |
| } // namespace | |
| // static | |
| std::unique_ptr<ScanlineDecoder> RustJpegScanlineDecoder::Create( | |
| pdfium::span<const uint8_t> src_span, | |
| uint32_t width, | |
| uint32_t height, | |
| int num_components, | |
| bool color_transform, | |
| uint32_t scale_denom) { | |
| auto decoder = std::make_unique<RustJpegScanlineDecoder>(); | |
| if (!decoder->CreateImpl(src_span, width, height, num_components, | |
| color_transform, scale_denom)) { | |
| return nullptr; | |
| } | |
| return decoder; | |
| } | |
| RustJpegScanlineDecoder::RustJpegScanlineDecoder() = default; | |
| RustJpegScanlineDecoder::~RustJpegScanlineDecoder() = default; | |
| bool RustJpegScanlineDecoder::CreateImpl(pdfium::span<const uint8_t> src_span, | |
| uint32_t width, | |
| uint32_t height, | |
| int num_components, | |
| bool color_transform, | |
| uint32_t scale_denom) { | |
| src_span = JpegScanSOI(src_span); | |
| if (src_span.size() < 2) { | |
| return false; | |
| } | |
| // SAFETY: const_cast<> doesn't change size. | |
| pdfium::span<uint8_t> writable_src = UNSAFE_BUFFERS( | |
| pdfium::span(const_cast<uint8_t*>(src_span.data()), src_span.size())); | |
| PatchUpTrailer(writable_src); | |
| rust_jpeg::JpegHeaderInfo header_info{}; | |
| rust::Slice<const uint8_t> src_slice(src_span); | |
| if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { | |
| bool patched = false; | |
| for (size_t offset : kKnownBadHeaderWithInvalidHeightByteOffsetStarts) { | |
| if (HasKnownBadHeaderWithInvalidHeight(src_span, offset, width)) { | |
| PatchUpKnownBadHeaderWithInvalidHeight(writable_src, offset, height); | |
| patched = true; | |
| break; | |
| } | |
| } | |
| if (!patched || !rust_jpeg::read_jpeg_info(src_slice, header_info)) { | |
| return false; | |
| } | |
| } | |
| orig_width_ = header_info.width; | |
| orig_height_ = header_info.height; | |
| DCHECK(scale_denom == 1 || scale_denom == 2 || scale_denom == 4 || | |
| scale_denom == 8); | |
| comps_ = header_info.num_components; | |
| bpc_ = header_info.bits_per_component; | |
| constexpr int kDctSize = 8; | |
| const int max_h_samp = std::max<int>(1, header_info.max_h_samp); | |
| const int max_v_samp = std::max<int>(1, header_info.max_v_samp); | |
| if (orig_width_ % (max_h_samp * kDctSize) != 0 || | |
| orig_height_ % (max_v_samp * kDctSize) != 0) { | |
| scale_denom = 1; | |
| } | |
| output_width_ = ScaledJpegSize(orig_width_, scale_denom); | |
| output_height_ = ScaledJpegSize(orig_height_, scale_denom); | |
| CalcPitch(); | |
| FX_SAFE_SIZE_T buf_size = pitch_; | |
| buf_size *= output_height_; | |
| if (!buf_size.IsValid()) { | |
| return false; | |
| } | |
| decoded_image_ = DataVector<uint8_t>(buf_size.ValueOrDie()); | |
| rust::Slice<uint8_t> out_slice(decoded_image_); | |
| if (!rust_jpeg::decode_jpeg_to_buf(src_slice, out_slice, pitch_, | |
| scale_denom)) { | |
| return false; | |
| } | |
| next_row_ = 0; | |
| return true; | |
| } | |
| bool RustJpegScanlineDecoder::Rewind() { | |
| next_row_ = 0; | |
| return true; | |
| } | |
| pdfium::span<uint8_t> RustJpegScanlineDecoder::GetNextLine() { | |
| if (next_row_ >= static_cast<uint32_t>(output_height_)) { | |
| return pdfium::span<uint8_t>(); | |
| } | |
| size_t offset = next_row_ * pitch_; | |
| if (offset + pitch_ > decoded_image_.size()) { | |
| return pdfium::span<uint8_t>(); | |
| } | |
| ++next_row_; | |
| return pdfium::span(decoded_image_).subspan(offset, pitch_); | |
| } | |
| uint32_t RustJpegScanlineDecoder::GetSrcOffset() { | |
| return 0; | |
| } | |
| void RustJpegScanlineDecoder::CalcPitch() { | |
| DCHECK_GT(output_width_, 0); | |
| pitch_ = static_cast<uint32_t>(output_width_) * comps_; | |
| pitch_ += 3; | |
| pitch_ /= 4; | |
| pitch_ *= 4; | |
| } | |
| // static | |
| std::optional<JpegModule::ImageInfo> RustJpegScanlineDecoder::LoadInfo( | |
| pdfium::span<const uint8_t> src_span) { | |
| src_span = JpegScanSOI(src_span); | |
| if (src_span.size() < 2) { | |
| return std::nullopt; | |
| } | |
| rust_jpeg::JpegHeaderInfo header_info{}; | |
| rust::Slice<const uint8_t> src_slice(src_span); | |
| if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { | |
| return std::nullopt; | |
| } | |
| JpegModule::ImageInfo info; | |
| info.width = header_info.width; | |
| info.height = header_info.height; | |
| info.num_components = header_info.num_components; | |
| info.color_transform = header_info.color_transform; | |
| info.bits_per_components = header_info.bits_per_component; | |
| return info; | |
| } | |
| } // namespace fxcodec |