blob: 6579d735d04f4d9274a00672eb8bc7a7d6bf7961 [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/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