| // 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 <array> |
| #include <optional> |
| #include <utility> |
| |
| #include "core/fxcrt/fx_2d_size.h" |
| #include "core/fxcrt/fx_ceil_div.h" |
| #include "core/fxcrt/fx_extension.h" |
| #include "core/fxcrt/notreached.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 fxcodec { |
| |
| namespace { |
| |
| uint8_t PaethPredictor(uint8_t a, uint8_t b, uint8_t c) { |
| const int bc = static_cast<int>(b) - c; |
| const int ac = static_cast<int>(a) - c; |
| const int pa = abs(bc); |
| const int pb = abs(ac); |
| const int pc = abs(ac + bc); |
| // Eagerly selecting between `b` and `c` avoids a short-circuit branch across |
| // `&&` and lets the compiler emit two branchless conditional selects. |
| const uint8_t bc_best = pb <= pc ? b : c; |
| return (pa <= pb && pa <= pc) ? a : bc_best; |
| } |
| |
| // Fixed-size pixel view for per-pixel register carry in Sub, Average, and Paeth |
| // predictors. PNG predictors operate on raw bytes and never care about color |
| // order. |
| template <size_t Bpp> |
| using Pixel = std::array<uint8_t, Bpp>; |
| |
| template <size_t Bpp> |
| void PredictSubRest(pdfium::span<uint8_t> dest_span, |
| pdfium::span<const uint8_t> src_span) { |
| const size_t row_size = dest_span.size(); |
| if (row_size <= Bpp) { |
| return; |
| } |
| auto src_px = fxcrt::reinterpret_span<const Pixel<Bpp>>(src_span); |
| auto dest_px = fxcrt::reinterpret_span<Pixel<Bpp>>(dest_span); |
| Pixel<Bpp> left = dest_px.front(); |
| for (auto [s, d] : fxcrt::Zip(src_px.subspan(1u), dest_px.subspan(1u))) { |
| for (size_t c = 0; c < Bpp; ++c) { |
| left[c] += s[c]; |
| } |
| d = left; |
| } |
| for (size_t i = row_size - row_size % Bpp; i < row_size; ++i) { |
| dest_span[i] = src_span[i] + dest_span[i - Bpp]; |
| } |
| } |
| |
| void PredictSubRow(pdfium::span<uint8_t> dest_span, |
| pdfium::span<const uint8_t> src_span, |
| size_t bpp, |
| size_t lead) { |
| // 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. Walking the row as pixel arrays and carrying the running |
| // channel values in a local removes that dependency (and, via fxcrt::Zip(), |
| // all per-element bounds checks). |
| switch (bpp) { |
| case 1: |
| PredictSubRest<1>(dest_span, src_span); |
| return; |
| case 2: |
| PredictSubRest<2>(dest_span, src_span); |
| return; |
| case 3: |
| PredictSubRest<3>(dest_span, src_span); |
| return; |
| case 4: |
| PredictSubRest<4>(dest_span, src_span); |
| return; |
| default: { |
| const size_t row_size = dest_span.size(); |
| for (size_t i = lead; i < row_size; ++i) { |
| dest_span[i] = src_span[i] + dest_span[i - bpp]; |
| } |
| return; |
| } |
| } |
| } |
| |
| template <size_t Bpp> |
| void PredictAverageFirstRowRest(pdfium::span<uint8_t> dest_span, |
| pdfium::span<const uint8_t> src_span) { |
| const size_t row_size = dest_span.size(); |
| if (row_size <= Bpp) { |
| return; |
| } |
| auto src_px = fxcrt::reinterpret_span<const Pixel<Bpp>>(src_span); |
| auto dest_px = fxcrt::reinterpret_span<Pixel<Bpp>>(dest_span); |
| Pixel<Bpp> left = dest_px.front(); |
| for (auto [s, d] : fxcrt::Zip(src_px.subspan(1u), dest_px.subspan(1u))) { |
| for (size_t c = 0; c < Bpp; ++c) { |
| left[c] = s[c] + left[c] / 2; |
| } |
| d = left; |
| } |
| for (size_t i = row_size - row_size % Bpp; i < row_size; ++i) { |
| dest_span[i] = src_span[i] + dest_span[i - Bpp] / 2; |
| } |
| } |
| |
| template <size_t Bpp> |
| void PredictAverageRest(pdfium::span<uint8_t> dest_span, |
| pdfium::span<const uint8_t> src_span, |
| pdfium::span<const uint8_t> last_span) { |
| const size_t row_size = dest_span.size(); |
| if (row_size <= Bpp) { |
| return; |
| } |
| auto src_px = fxcrt::reinterpret_span<const Pixel<Bpp>>(src_span); |
| auto last_px = fxcrt::reinterpret_span<const Pixel<Bpp>>(last_span); |
| auto dest_px = fxcrt::reinterpret_span<Pixel<Bpp>>(dest_span); |
| Pixel<Bpp> left = dest_px.front(); |
| for (auto [s, u, d] : fxcrt::Zip(src_px.subspan(1u), last_px.subspan(1u), |
| dest_px.subspan(1u))) { |
| for (size_t c = 0; c < Bpp; ++c) { |
| left[c] = s[c] + (u[c] + left[c]) / 2; |
| } |
| d = left; |
| } |
| for (size_t i = row_size - row_size % Bpp; i < row_size; ++i) { |
| dest_span[i] = src_span[i] + (last_span[i] + dest_span[i - Bpp]) / 2; |
| } |
| } |
| |
| template <size_t Bpp> |
| void PredictPaethRest(pdfium::span<uint8_t> dest_span, |
| pdfium::span<const uint8_t> src_span, |
| pdfium::span<const uint8_t> last_span) { |
| const size_t row_size = dest_span.size(); |
| if (row_size <= Bpp) { |
| return; |
| } |
| auto src_px = fxcrt::reinterpret_span<const Pixel<Bpp>>(src_span); |
| auto last_px = fxcrt::reinterpret_span<const Pixel<Bpp>>(last_span); |
| auto dest_px = fxcrt::reinterpret_span<Pixel<Bpp>>(dest_span); |
| Pixel<Bpp> left = dest_px.front(); |
| Pixel<Bpp> upper_left = last_px.front(); |
| for (auto [s, u, d] : fxcrt::Zip(src_px.subspan(1u), last_px.subspan(1u), |
| dest_px.subspan(1u))) { |
| for (size_t c = 0; c < Bpp; ++c) { |
| left[c] = s[c] + PaethPredictor(left[c], u[c], upper_left[c]); |
| } |
| upper_left = u; |
| d = left; |
| } |
| for (size_t i = row_size - row_size % Bpp; i < row_size; ++i) { |
| dest_span[i] = |
| src_span[i] + |
| PaethPredictor(dest_span[i - Bpp], last_span[i], last_span[i - Bpp]); |
| } |
| } |
| |
| 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 = |
| fxcrt::CeilDiv(colors * bits_per_component, 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; |
| } |
| |
| 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 |
| |
| PredictorType GetPredictor(int predictor) { |
| if (predictor >= 10) { |
| return PredictorType::kPng; |
| } |
| if (predictor == 2) { |
| return PredictorType::kTiff; |
| } |
| return PredictorType::kNone; |
| } |
| |
| const DataAndBytesConsumed ApplyPredictor(DataVector<uint8_t> decoded_buf, |
| int predictor, |
| int colors, |
| int bits_per_component, |
| int columns, |
| uint32_t bytes_consumed) { |
| PredictorType predictor_type = GetPredictor(predictor); |
| switch (predictor_type) { |
| case PredictorType::kNone: { |
| return {std::move(decoded_buf), bytes_consumed}; |
| } |
| case PredictorType::kPng: { |
| std::optional<DataVector<uint8_t>> result = |
| PngPredictor(colors, bits_per_component, columns, decoded_buf); |
| if (!result.has_value()) { |
| return {std::move(decoded_buf), FX_INVALID_OFFSET}; |
| } |
| return {std::move(result.value()), bytes_consumed}; |
| } |
| case PredictorType::kTiff: { |
| bool ret = |
| TiffPredictor(colors, bits_per_component, columns, decoded_buf); |
| return {std::move(decoded_buf), ret ? bytes_consumed : FX_INVALID_OFFSET}; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| 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. |
| PredictSubRow(dest_span, src_span, bpp, lead); |
| 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); |
| switch (bpp) { |
| case 1: |
| PredictAverageFirstRowRest<1>(dest_span, src_span); |
| break; |
| case 2: |
| PredictAverageFirstRowRest<2>(dest_span, src_span); |
| break; |
| case 3: |
| PredictAverageFirstRowRest<3>(dest_span, src_span); |
| break; |
| case 4: |
| PredictAverageFirstRowRest<4>(dest_span, src_span); |
| break; |
| default: |
| for (size_t i = lead; i < row_size; ++i) { |
| dest_span[i] = src_span[i] + dest_span[i - bpp] / 2; |
| } |
| break; |
| } |
| break; |
| } |
| for (auto [s, u, d] : |
| fxcrt::Zip(src_span.first(lead), last_span, dest_span)) { |
| d = s + u / 2; |
| } |
| switch (bpp) { |
| case 1: |
| PredictAverageRest<1>(dest_span, src_span, last_span); |
| break; |
| case 2: |
| PredictAverageRest<2>(dest_span, src_span, last_span); |
| break; |
| case 3: |
| PredictAverageRest<3>(dest_span, src_span, last_span); |
| break; |
| case 4: |
| PredictAverageRest<4>(dest_span, src_span, last_span); |
| break; |
| default: |
| for (size_t i = lead; i < row_size; ++i) { |
| dest_span[i] = |
| src_span[i] + (last_span[i] + dest_span[i - bpp]) / 2; |
| } |
| break; |
| } |
| 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()) { |
| PredictSubRow(dest_span, src_span, bpp, lead); |
| break; |
| } |
| for (auto [s, u, d] : |
| fxcrt::Zip(src_span.first(lead), last_span, dest_span)) { |
| d = s + u; |
| } |
| switch (bpp) { |
| case 1: |
| PredictPaethRest<1>(dest_span, src_span, last_span); |
| break; |
| case 2: |
| PredictPaethRest<2>(dest_span, src_span, last_span); |
| break; |
| case 3: |
| PredictPaethRest<3>(dest_span, src_span, last_span); |
| break; |
| case 4: |
| PredictPaethRest<4>(dest_span, src_span, last_span); |
| break; |
| default: |
| 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; |
| } |
| break; |
| } |
| default: { |
| fxcrt::Copy(src_span, dest_span); |
| break; |
| } |
| } |
| } |
| |
| 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]; |
| } |
| } |
| } |
| |
| } // namespace fxcodec |