| // 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 <stddef.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| #include <vector> |
| |
| #include "core/fxcrt/span.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace fxcodec { |
| namespace { |
| |
| uint8_t ReferencePaethPredictor(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; |
| } |
| |
| void ReferencePngPredictLine(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(); |
| src_span = src_span.subspan(1u, row_size); |
| 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; |
| for (size_t i = 0; i < row_size; ++i) { |
| const uint8_t left = i >= bpp ? dest_span[i - bpp] : 0; |
| const uint8_t up = last_span.empty() ? 0 : last_span[i]; |
| const uint8_t upper_left = |
| (i >= bpp && !last_span.empty()) ? last_span[i - bpp] : 0; |
| switch (tag) { |
| case 1: |
| dest_span[i] = src_span[i] + left; |
| break; |
| case 2: |
| dest_span[i] = src_span[i] + up; |
| break; |
| case 3: |
| dest_span[i] = src_span[i] + (up + left) / 2; |
| break; |
| case 4: |
| dest_span[i] = |
| src_span[i] + ReferencePaethPredictor(left, up, upper_left); |
| break; |
| default: |
| dest_span[i] = src_span[i]; |
| break; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| TEST(ImagePredictorsTest, PngPredictLineAllTagsAndWidths) { |
| for (uint8_t tag = 0; tag <= 5; ++tag) { |
| for (uint32_t bpp : {1u, 2u, 3u, 4u, 6u}) { |
| for (size_t row_size : |
| {1u, 2u, 3u, 4u, 5u, 7u, 8u, 12u, 16u, 31u, 64u, 129u}) { |
| std::vector<uint8_t> src(row_size + 1); |
| std::vector<uint8_t> last(row_size); |
| src[0] = tag; |
| for (size_t i = 0; i < row_size; ++i) { |
| src[i + 1] = |
| static_cast<uint8_t>((i * 73 + tag * 19 + bpp * 41) & 0xFF); |
| last[i] = |
| static_cast<uint8_t>((i * 151 + tag * 31 + bpp * 17) & 0xFF); |
| } |
| |
| // First row (empty last_span). |
| std::vector<uint8_t> dst_ref_first(row_size, 0); |
| std::vector<uint8_t> dst_opt_first(row_size, 0); |
| ReferencePngPredictLine(dst_ref_first, src, {}, row_size, bpp); |
| PngPredictLine(dst_opt_first, src, {}, row_size, bpp); |
| EXPECT_EQ(dst_ref_first, dst_opt_first) |
| << "First row mismatch for tag=" << static_cast<int>(tag) |
| << " bpp=" << bpp << " row_size=" << row_size; |
| |
| // Subsequent row (non-empty last_span). |
| std::vector<uint8_t> dst_ref(row_size, 0); |
| std::vector<uint8_t> dst_opt(row_size, 0); |
| ReferencePngPredictLine(dst_ref, src, last, row_size, bpp); |
| PngPredictLine(dst_opt, src, last, row_size, bpp); |
| EXPECT_EQ(dst_ref, dst_opt) |
| << "Row mismatch for tag=" << static_cast<int>(tag) |
| << " bpp=" << bpp << " row_size=" << row_size; |
| } |
| } |
| } |
| } |
| |
| } // namespace fxcodec |