blob: 2b46ac79e8ba64960d938b703ef728a7fb2e09b6 [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 <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