blob: b1a7fce7e2d53b9b6d8d98d34eef33f5b2ff548e [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/image_predictors.h"
#include <stdlib.h>
#include <algorithm>
#include "core/fxcrt/fx_2d_size.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span_util.h"
#include "core/fxcrt/stl_util.h"
#include "core/fxcrt/zip.h"
#include "core/fxge/calculate_pitch.h"
namespace {
uint8_t PaethPredictor(uint8_t a, uint8_t b, uint8_t c) {
int p = static_cast<int>(a) + b - c;
int pa = abs(p - a);
int pb = abs(p - b);
int pc = abs(p - c);
if (pa <= pb && pa <= pc) {
return a;
}
return pb <= pc ? b : c;
}
} // namespace
namespace fxcodec {
PredictorType GetPredictor(int predictor) {
if (predictor >= 10) {
return PredictorType::kPng;
}
if (predictor == 2) {
return PredictorType::kTiff;
}
return PredictorType::kNone;
}
// Fixed-size pixel views for the Sub filter's per-channel fast paths. The
// channel names are deliberately positional: PNG predictors operate on raw
// bytes and never care about color order.
struct Pixel3 {
uint8_t c0;
uint8_t c1;
uint8_t c2;
};
struct Pixel4 {
uint8_t c0;
uint8_t c1;
uint8_t c2;
uint8_t c3;
};
void PngPredictLine(pdfium::span<uint8_t> dest_span,
pdfium::span<const uint8_t> src_span,
pdfium::span<const uint8_t> last_span,
size_t row_size,
uint32_t bytes_per_pixel) {
const uint8_t tag = src_span.front();
// `row_size` is a count, not an end index, so this is exactly `row_size`
// bytes after the tag byte. PngPredictor() (below) pre-clamps the
// `row_size` it passes for a stream's last, possibly truncated, row, and
// this CHECKs rather than clamps, so `row_size` and `src_span.size()`
// are equal on every path that reaches the code below. If this is ever
// relaxed from a CHECK to a clamp, `row_size` stops being a valid loop
// bound and every use below needs the post-clamp span size instead.
src_span = src_span.subspan(1u, row_size);
// Size the row views once up front, then hoist the two special cases out
// of the loops: the first `bytes_per_pixel` bytes of a row, where the left
// and upper-left neighbors are zero, and the first row of the image, where
// up and upper-left are zero.
dest_span = dest_span.first(row_size);
if (!last_span.empty()) {
last_span = last_span.first(row_size);
}
const size_t bpp = bytes_per_pixel;
const size_t lead = std::min(bpp, row_size);
switch (tag) {
case 1: {
// Sub: left neighbor only, so the first row needs no special casing.
// The lead bytes have a zero left neighbor.
fxcrt::Copy(src_span.first(lead), dest_span);
// A Sub-filtered row's channels form independent additive chains; the
// generic loop below re-reads dest_span[i - bpp], a value it stored `bpp`
// iterations earlier, which serializes the loop on store-to-load
// forwarding. For the common 3- and 4-byte pixel sizes, walking the
// row as pixel structs and carrying the running channel values in a
// local removes that dependency (and, via fxcrt::Zip(), all
// per-element bounds checks).
if (bpp == 3 && row_size >= 3) {
auto src_px = fxcrt::reinterpret_span<const Pixel3>(src_span);
auto dest_px = fxcrt::reinterpret_span<Pixel3>(dest_span);
Pixel3 carry = dest_px.front();
for (auto [s, d] :
fxcrt::Zip(src_px.subspan(1u), dest_px.subspan(1u))) {
carry.c0 += s.c0;
carry.c1 += s.c1;
carry.c2 += s.c2;
d = carry;
}
for (size_t i = row_size - row_size % 3; i < row_size; ++i) {
dest_span[i] = src_span[i] + dest_span[i - 3];
}
break;
}
if (bpp == 4 && row_size >= 4) {
auto src_px = fxcrt::reinterpret_span<const Pixel4>(src_span);
auto dest_px = fxcrt::reinterpret_span<Pixel4>(dest_span);
Pixel4 carry = dest_px.front();
for (auto [s, d] :
fxcrt::Zip(src_px.subspan(1u), dest_px.subspan(1u))) {
carry.c0 += s.c0;
carry.c1 += s.c1;
carry.c2 += s.c2;
carry.c3 += s.c3;
d = carry;
}
for (size_t i = row_size - row_size % 4; i < row_size; ++i) {
dest_span[i] = src_span[i] + dest_span[i - 4];
}
break;
}
for (size_t i = lead; i < row_size; ++i) {
dest_span[i] = src_span[i] + dest_span[i - bpp];
}
break;
}
case 2: {
// Up: previous-row neighbor only, zero on the first row.
if (last_span.empty()) {
fxcrt::Copy(src_span, dest_span);
break;
}
for (auto [s, u, d] : fxcrt::Zip(src_span, last_span, dest_span)) {
d = s + u;
}
break;
}
case 3: {
// Average: (left + up) / 2, with the zero cases hoisted.
if (last_span.empty()) {
fxcrt::Copy(src_span.first(lead), dest_span);
for (size_t i = lead; i < row_size; ++i) {
dest_span[i] = src_span[i] + dest_span[i - bpp] / 2;
}
break;
}
for (auto [s, u, d] :
fxcrt::Zip(src_span.first(lead), last_span, dest_span)) {
d = s + u / 2;
}
for (size_t i = lead; i < row_size; ++i) {
dest_span[i] = src_span[i] + (last_span[i] + dest_span[i - bpp]) / 2;
}
break;
}
case 4: {
// Paeth. With up == upper_left == 0 (first row) the predictor always
// selects the left neighbor, so the first row reduces to Sub; with
// left == upper_left == 0 (lead bytes) it always selects up.
if (last_span.empty()) {
fxcrt::Copy(src_span.first(lead), dest_span);
for (size_t i = lead; i < row_size; ++i) {
dest_span[i] = src_span[i] + dest_span[i - bpp];
}
break;
}
for (auto [s, u, d] :
fxcrt::Zip(src_span.first(lead), last_span, dest_span)) {
d = s + u;
}
for (size_t i = lead; i < row_size; ++i) {
dest_span[i] =
src_span[i] + PaethPredictor(dest_span[i - bpp], last_span[i],
last_span[i - bpp]);
}
break;
}
default: {
fxcrt::Copy(src_span, dest_span);
break;
}
}
}
std::optional<DataVector<uint8_t>> PngPredictor(
int colors,
int bits_per_component,
int columns,
pdfium::span<const uint8_t> src_span) {
const uint32_t row_size =
fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0);
if (row_size == 0) {
return std::nullopt;
}
const uint32_t src_row_size = row_size + 1;
if (src_row_size == 0) {
// Avoid divide by 0.
return std::nullopt;
}
const size_t row_count = (src_span.size() + row_size) / src_row_size;
if (row_count == 0) {
return std::nullopt;
}
const uint32_t last_row_size = src_span.size() % src_row_size;
size_t dest_size = Fx2DSizeOrDie(row_size, row_count);
if (last_row_size) {
dest_size -= src_row_size - last_row_size;
}
DataVector<uint8_t> dest_buf(dest_size);
pdfium::span<const uint8_t> remaining_src_span = src_span;
pdfium::span<uint8_t> remaining_dest_span = pdfium::span(dest_buf);
pdfium::span<uint8_t> prev_dest_span;
const uint32_t bytes_per_pixel = (colors * bits_per_component + 7) / 8;
for (size_t row = 0; row < row_count; row++) {
const size_t remaining_row_size =
std::min<size_t>(row_size, remaining_src_span.size() - 1);
PngPredictLine(remaining_dest_span, remaining_src_span, prev_dest_span,
remaining_row_size, bytes_per_pixel);
remaining_src_span = remaining_src_span.subspan(remaining_row_size + 1);
prev_dest_span = remaining_dest_span;
remaining_dest_span = remaining_dest_span.subspan(remaining_row_size);
}
return dest_buf;
}
void TiffPredictLine(pdfium::span<uint8_t> dest_span,
int bits_per_component,
int colors,
int columns) {
if (bits_per_component == 1) {
int row_bits = std::min(bits_per_component * colors * columns,
pdfium::checked_cast<int>(dest_span.size() * 8));
int index_pre = 0;
int col_pre = 0;
for (int i = 1; i < row_bits; i++) {
int col = i % 8;
int index = i / 8;
if (((dest_span[index] >> (7 - col)) & 1) ^
((dest_span[index_pre] >> (7 - col_pre)) & 1)) {
dest_span[index] |= 1 << (7 - col);
} else {
dest_span[index] &= ~(1 << (7 - col));
}
index_pre = index;
col_pre = col;
}
return;
}
int bytes_per_pixel = bits_per_component * colors / 8;
if (bits_per_component == 16) {
for (size_t i = bytes_per_pixel; i + 1 < dest_span.size(); i += 2) {
uint16_t pixel = (dest_span[i - bytes_per_pixel] << 8) |
dest_span[i - bytes_per_pixel + 1];
pixel += (dest_span[i] << 8) | dest_span[i + 1];
dest_span[i] = pixel >> 8;
dest_span[i + 1] = (uint8_t)pixel;
}
} else {
for (size_t i = bytes_per_pixel; i < dest_span.size(); i++) {
dest_span[i] += dest_span[i - bytes_per_pixel];
}
}
}
bool TiffPredictor(int colors,
int bits_per_component,
int columns,
pdfium::span<uint8_t> data_span) {
const uint32_t row_size =
fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0);
if (row_size == 0) {
return false;
}
while (!data_span.empty()) {
auto row_span =
data_span.first(std::min<size_t>(row_size, data_span.size()));
TiffPredictLine(row_span, bits_per_component, colors, columns);
data_span = data_span.subspan(row_span.size());
}
return true;
}
} // namespace fxcodec