| // 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 "testing/utils/pixel_diff_util.h" | |
| #include <stdint.h> | |
| #include <vector> | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaExactMatch) { | |
| EXPECT_EQ(0, MaxPixelPerChannelDelta(0x00000000, 0x00000000)); | |
| EXPECT_EQ(0, MaxPixelPerChannelDelta(0xffffffff, 0xffffffff)); | |
| EXPECT_EQ(0, MaxPixelPerChannelDelta(0x12345678, 0x12345678)); | |
| } | |
| TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaSingleChannel) { | |
| EXPECT_EQ(5, MaxPixelPerChannelDelta(0x00000005, 0x00000000)); | |
| EXPECT_EQ(10, MaxPixelPerChannelDelta(0x00000a00, 0x00000000)); | |
| EXPECT_EQ(15, MaxPixelPerChannelDelta(0x000f0000, 0x00000000)); | |
| EXPECT_EQ(20, MaxPixelPerChannelDelta(0x14000000, 0x00000000)); | |
| EXPECT_EQ(255, MaxPixelPerChannelDelta(0x000000ff, 0x00000000)); | |
| } | |
| TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaMultipleChannels) { | |
| EXPECT_EQ(8, MaxPixelPerChannelDelta(0x01020304, 0x09020304)); | |
| EXPECT_EQ(7, MaxPixelPerChannelDelta(0x01020304, 0x01090304)); | |
| EXPECT_EQ(6, MaxPixelPerChannelDelta(0x01020304, 0x01020904)); | |
| EXPECT_EQ(5, MaxPixelPerChannelDelta(0x01020304, 0x01020309)); | |
| } | |
| TEST(PixelDiffUtilTest, PixelSquaredError) { | |
| EXPECT_EQ(0u, PixelSquaredError(0x00000000, 0x00000000)); | |
| EXPECT_EQ(0u, PixelSquaredError(0xffffffff, 0xffffffff)); | |
| // Differences in color channels (bytes 0, 1, 2). | |
| EXPECT_EQ(4u, PixelSquaredError(0x00000002, 0x00000000)); | |
| EXPECT_EQ(9u, PixelSquaredError(0x00000300, 0x00000000)); | |
| EXPECT_EQ(16u, PixelSquaredError(0x00040000, 0x00000000)); | |
| EXPECT_EQ(29u, PixelSquaredError(0x00040302, 0x00000000)); | |
| // Alpha (byte 3) is ignored for color MSE calculation. | |
| EXPECT_EQ(0u, PixelSquaredError(0xff000000, 0x00000000)); | |
| } | |
| TEST(PixelDiffUtilTest, FuzzyThresholds) { | |
| EXPECT_EQ(3, kMaxFuzzyPixelDelta); | |
| EXPECT_DOUBLE_EQ(0.05, kMaxFuzzyMeanSquaredError); | |
| EXPECT_EQ(8, kMaxFuzzyWindowSize); | |
| EXPECT_DOUBLE_EQ(15.0, kMaxFuzzyWindowMeanSquaredError); | |
| } | |
| TEST(PixelDiffUtilTest, CalculateMaxWindowMSEExactMatch) { | |
| std::vector<uint32_t> img(64, 0xffffffff); | |
| EXPECT_DOUBLE_EQ(0.0, CalculateMaxWindowMSE(img, 8, img, 8, 8, 8, 8)); | |
| } | |
| TEST(PixelDiffUtilTest, CalculateMaxWindowMSEUniformDelta) { | |
| // Uniform delta of 1 on red channel across entire image. | |
| std::vector<uint32_t> img1(64, 0x00000000); | |
| std::vector<uint32_t> img2(64, 0x00000001); | |
| // Squared error = 1 on 1 channel per pixel -> local MSE = 1 / 3 = 0.3333... | |
| EXPECT_NEAR(1.0 / 3.0, CalculateMaxWindowMSE(img1, 8, img2, 8, 8, 8, 4), | |
| 1e-6); | |
| } | |
| TEST(PixelDiffUtilTest, CalculateMaxWindowMSELocalizedDefect) { | |
| // 16x16 white image with a 4x4 black box defect at (4, 4). | |
| std::vector<uint32_t> baseline(16 * 16, 0x00ffffff); | |
| std::vector<uint32_t> actual = baseline; | |
| for (int y = 4; y < 8; ++y) { | |
| for (int x = 4; x < 8; ++x) { | |
| actual[y * 16 + x] = 0x00000000; | |
| } | |
| } | |
| // Window size = 8. | |
| // The window from (4, 4) to (12, 12) captures all 16 defect pixels. | |
| // Defect error = 16 pixels * (3 * 255^2) = 3121200. | |
| // Window MSE = 3121200 / (3 * 64) = 16256.25. | |
| EXPECT_DOUBLE_EQ(16256.25, | |
| CalculateMaxWindowMSE(baseline, 16, actual, 16, 16, 16, 8)); | |
| } | |
| TEST(PixelDiffUtilTest, CalculateMaxWindowMSEChromaEdge) { | |
| // 8x8 image with an 8-pixel line of delta = 10 on all 3 channels. | |
| std::vector<uint32_t> baseline(8 * 8, 0x00000000); | |
| std::vector<uint32_t> actual = baseline; | |
| for (int x = 0; x < 8; ++x) { | |
| actual[x] = 0x000a0a0a; // r=10, g=10, b=10 | |
| } | |
| // 8 pixels * 3 channels * 10^2 = 2400. | |
| // Window MSE = 2400 / (3 * 64) = 12.5. | |
| EXPECT_DOUBLE_EQ(12.5, | |
| CalculateMaxWindowMSE(baseline, 8, actual, 8, 8, 8, 8)); | |
| } | |
| TEST(PixelDiffUtilTest, CalculateMaxWindowMSEWithStrides) { | |
| // 8x8 image within a buffer with stride = 12. | |
| constexpr int kWidth = 8; | |
| constexpr int kHeight = 8; | |
| constexpr size_t kStride = 12; | |
| std::vector<uint32_t> baseline(kStride * kHeight, 0x00000000); | |
| std::vector<uint32_t> actual = baseline; | |
| // Uniform delta of 1 on red channel inside the 8x8 region. | |
| for (int y = 0; y < kHeight; ++y) { | |
| for (int x = 0; x < kWidth; ++x) { | |
| actual[y * kStride + x] = 0x00000001; | |
| } | |
| } | |
| // Padding pixels outside 8x8 should not affect calculation. | |
| for (int y = 0; y < kHeight; ++y) { | |
| for (size_t x = kWidth; x < kStride; ++x) { | |
| actual[y * kStride + x] = 0x00ffffff; | |
| } | |
| } | |
| EXPECT_NEAR(1.0 / 3.0, | |
| CalculateMaxWindowMSE(baseline, kStride, actual, kStride, kWidth, | |
| kHeight, 4), | |
| 1e-6); | |
| } | |
| TEST(PixelDiffUtilTest, CalculateMaxWindowMSEInvalidInputs) { | |
| std::vector<uint32_t> img(16, 0); | |
| EXPECT_DOUBLE_EQ(0.0, CalculateMaxWindowMSE(img, 0, img, 0, 0, 0, 8)); | |
| EXPECT_DOUBLE_EQ(0.0, CalculateMaxWindowMSE(img, 4, img, 4, 4, 4, 0)); | |
| } | |
| TEST(PixelDiffUtilTest, CalculatePixelsDifferentExactMatch) { | |
| std::vector<uint32_t> img(64, 0xffffffff); | |
| EXPECT_EQ(0, | |
| CalculatePixelsDifferent(img, 8, img, 8, 8, 8, kExactDiffOptions)); | |
| } | |
| TEST(PixelDiffUtilTest, CalculatePixelsDifferentTolerance) { | |
| std::vector<uint32_t> baseline(64, 0x00000000); | |
| std::vector<uint32_t> actual = baseline; | |
| actual[0] = 0x00020202; // delta 2 on all channels | |
| // Exact match fails. | |
| EXPECT_EQ(1, CalculatePixelsDifferent(baseline, 8, actual, 8, 8, 8, | |
| kExactDiffOptions)); | |
| // Tolerating delta 2 passes. | |
| EXPECT_EQ(0, CalculatePixelsDifferent( | |
| baseline, 8, actual, 8, 8, 8, | |
| DiffOptions{.max_pixel_per_channel_delta = 2})); | |
| // Tolerating delta 2 but strict MSE (0.0001) fails. | |
| EXPECT_EQ(1, CalculatePixelsDifferent( | |
| baseline, 8, actual, 8, 8, 8, | |
| DiffOptions{.max_pixel_per_channel_delta = 2, | |
| .max_mean_squared_error = 0.0001})); | |
| // Window MSE failure triggers. | |
| EXPECT_EQ(1, CalculatePixelsDifferent( | |
| baseline, 8, actual, 8, 8, 8, | |
| DiffOptions{.max_pixel_per_channel_delta = 2, | |
| .max_mean_squared_error = 1.0, | |
| .window_size = 8, | |
| .max_window_mean_squared_error = 0.01})); | |
| // Under exact diff, all 64 pixels differ. | |
| std::vector<uint32_t> img1(64, 0x00000000); | |
| std::vector<uint32_t> img2(64, 0x00000001); // delta 1 on red | |
| EXPECT_EQ( | |
| 64, CalculatePixelsDifferent(img1, 8, img2, 8, 8, 8, kExactDiffOptions)); | |
| // Under fuzzy diff (tolerates delta <= 3 and MSE <= 0.05). | |
| // Here MSE = (64 * 1) / (3 * 64) = 0.333 > 0.05, so MSE fails and returns > | |
| // 0. | |
| EXPECT_GT(CalculatePixelsDifferent(img1, 8, img2, 8, 8, 8, kFuzzyDiffOptions), | |
| 0); | |
| // With a relaxed MSE threshold of 0.5, it should report 0 differences. | |
| DiffOptions relaxed_options = { | |
| .max_pixel_per_channel_delta = 1, | |
| .max_mean_squared_error = 0.5, | |
| }; | |
| EXPECT_EQ(0, | |
| CalculatePixelsDifferent(img1, 8, img2, 8, 8, 8, relaxed_options)); | |
| } | |
| TEST(PixelDiffUtilTest, DiffOptionsAggregateAndDefaults) { | |
| static_assert(std::is_aggregate_v<DiffOptions>); | |
| constexpr DiffOptions default_options; | |
| EXPECT_EQ(0, default_options.max_pixel_per_channel_delta); | |
| EXPECT_DOUBLE_EQ(0.0, default_options.max_mean_squared_error); | |
| EXPECT_EQ(0, default_options.window_size); | |
| EXPECT_DOUBLE_EQ(0.0, default_options.max_window_mean_squared_error); | |
| EXPECT_EQ(0, kExactDiffOptions.max_pixel_per_channel_delta); | |
| EXPECT_DOUBLE_EQ(0.0, kExactDiffOptions.max_mean_squared_error); | |
| EXPECT_EQ(0, kExactDiffOptions.window_size); | |
| EXPECT_DOUBLE_EQ(0.0, kExactDiffOptions.max_window_mean_squared_error); | |
| EXPECT_EQ(kMaxFuzzyPixelDelta, kFuzzyDiffOptions.max_pixel_per_channel_delta); | |
| EXPECT_DOUBLE_EQ(kMaxFuzzyMeanSquaredError, | |
| kFuzzyDiffOptions.max_mean_squared_error); | |
| EXPECT_EQ(kMaxFuzzyWindowSize, kFuzzyDiffOptions.window_size); | |
| EXPECT_DOUBLE_EQ(kMaxFuzzyWindowMeanSquaredError, | |
| kFuzzyDiffOptions.max_window_mean_squared_error); | |
| // Verification with options struct. | |
| std::vector<uint32_t> baseline(64, 0x00000000); | |
| std::vector<uint32_t> actual = baseline; | |
| actual[0] = 0x00000002; | |
| EXPECT_EQ(1, CalculatePixelsDifferent(baseline, 8, actual, 8, 8, 8, | |
| kExactDiffOptions)); | |
| EXPECT_EQ(0, CalculatePixelsDifferent(baseline, 8, actual, 8, 8, 8, | |
| kFuzzyDiffOptions)); | |
| } |