| // Copyright 2016 The PDFium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // TODO(tsepez) this requires a lot more testing. | |
| #include "core/fxcodec/jbig2/jbig2_image.h" | |
| #include <stdint.h> | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace { | |
| const int32_t kWidthPixels = 80; | |
| const int32_t kWidthBytes = 10; | |
| const int32_t kStrideBytes = kWidthBytes + 2; // For testing stride != width. | |
| const int32_t kHeightLines = 20; | |
| const int32_t kLargerHeightLines = 100; | |
| const int32_t kTooLargeHeightLines = 40000000; | |
| void CheckImageEq(CJBig2_Image* img1, CJBig2_Image* img2, int line) { | |
| EXPECT_EQ(img1->width(), img2->width()); | |
| EXPECT_EQ(img1->height(), img2->height()); | |
| for (int32_t y = 0; y < img1->height(); ++y) { | |
| pdfium::span<const uint8_t> img1_line = img1->GetLine(y); | |
| pdfium::span<const uint8_t> img2_line = img2->GetLine(y); | |
| for (int32_t x = 0; x < img1->width(); ++x) { | |
| EXPECT_EQ(img1->GetPixel(x, img1_line), img2->GetPixel(x, img2_line)) | |
| << " at " << x << " " << y << " actual line " << line; | |
| } | |
| } | |
| } | |
| } // namespace | |
| TEST(fxcodec, EmptyImage) { | |
| CJBig2_Image empty(0, 0); | |
| EXPECT_EQ(empty.width(), 0); | |
| EXPECT_EQ(empty.height(), 0); | |
| // Out-of-bounds GetLine() returns empty. | |
| pdfium::span<uint8_t> line0 = empty.GetLine(0); | |
| pdfium::span<uint8_t> line1 = empty.GetLine(1); | |
| EXPECT_TRUE(line0.empty()); | |
| EXPECT_TRUE(line1.empty()); | |
| // Out-of-bounds SetPixel() is silent no-op. | |
| empty.SetPixel(0, line0, true); | |
| empty.SetPixel(1, line1, true); | |
| // Out-of-bounds GetPixel returns 0. | |
| EXPECT_EQ(empty.GetPixel(0, line0), 0); | |
| EXPECT_EQ(empty.GetPixel(1, line1), 0); | |
| } | |
| TEST(fxcodec, JBig2ImageCreate) { | |
| CJBig2_Image img(kWidthPixels, kHeightLines); | |
| EXPECT_EQ(kWidthPixels, img.width()); | |
| EXPECT_EQ(kHeightLines, img.height()); | |
| pdfium::span<uint8_t> line0 = img.GetLine(0); | |
| EXPECT_EQ(0, img.GetPixel(0, line0)); | |
| ASSERT_EQ(static_cast<size_t>(kStrideBytes), line0.size()); | |
| EXPECT_EQ(0, line0[0]); | |
| pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1); | |
| ASSERT_EQ(static_cast<size_t>(kStrideBytes), line_last.size()); | |
| EXPECT_EQ(0, line_last[kWidthBytes - 1]); | |
| img.SetPixel(0, line0, true); | |
| img.SetPixel(kWidthPixels - 1, line_last, true); | |
| EXPECT_EQ(1, img.GetPixel(0, line0)); | |
| EXPECT_EQ(1, img.GetPixel(kWidthPixels - 1, line_last)); | |
| EXPECT_EQ(0x80, line0[0]); | |
| EXPECT_EQ(0x01, line_last[kWidthBytes - 1]); | |
| // Out-of-bounds SetPixel() is silent no-op. | |
| pdfium::span<uint8_t> line1 = img.GetLine(1); | |
| img.SetPixel(-1, line1, true); | |
| pdfium::span<uint8_t> line_oob = img.GetLine(kHeightLines); | |
| img.SetPixel(kWidthPixels, line_oob, true); | |
| // Out-of-bounds GetPixel returns 0. | |
| EXPECT_EQ(0, img.GetPixel(-1, img.GetLine(-1))); | |
| EXPECT_EQ(0, img.GetPixel(kWidthPixels, img.GetLine(kHeightLines))); | |
| // Out-of-bounds GetLine() returns empty. | |
| EXPECT_TRUE(img.GetLine(-1).empty()); | |
| EXPECT_TRUE(img.GetLine(kHeightLines).empty()); | |
| } | |
| TEST(fxcodec, JBig2ImageCreateTooBig) { | |
| CJBig2_Image img(kWidthPixels, kTooLargeHeightLines); | |
| EXPECT_EQ(0, img.width()); | |
| EXPECT_EQ(0, img.height()); | |
| EXPECT_FALSE(img.has_data()); | |
| EXPECT_TRUE(img.span().empty()); | |
| } | |
| TEST(fxcodec, JBig2ImageCreateExternal) { | |
| uint8_t buf[kHeightLines * kStrideBytes]; | |
| CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, buf); | |
| pdfium::span<uint8_t> line0 = img.GetLine(0); | |
| pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1); | |
| img.SetPixel(0, line0, true); | |
| img.SetPixel(kWidthPixels - 1, line_last, false); | |
| EXPECT_EQ(kWidthPixels, img.width()); | |
| EXPECT_EQ(kHeightLines, img.height()); | |
| EXPECT_TRUE(img.GetPixel(0, line0)); | |
| EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last)); | |
| } | |
| TEST(fxcodec, JBig2ImageCreateExternalTooBig) { | |
| uint8_t buf[kHeightLines * kStrideBytes]; | |
| CJBig2_Image img(kWidthPixels, kTooLargeHeightLines, kStrideBytes, buf); | |
| EXPECT_EQ(0, img.width()); | |
| EXPECT_EQ(0, img.height()); | |
| EXPECT_FALSE(img.has_data()); | |
| EXPECT_TRUE(img.span().empty()); | |
| } | |
| TEST(fxcodec, JBig2ImageCreateExternalSizeButNoData) { | |
| CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, {}); | |
| EXPECT_EQ(0, img.width()); | |
| EXPECT_EQ(0, img.height()); | |
| EXPECT_FALSE(img.has_data()); | |
| EXPECT_TRUE(img.span().empty()); | |
| } | |
| TEST(fxcodec, JBig2ImageCreateExternalBadStride) { | |
| uint8_t buf[kHeightLines * kStrideBytes]; | |
| CJBig2_Image img(kWidthPixels, kTooLargeHeightLines, kStrideBytes - 1, buf); | |
| EXPECT_EQ(0, img.width()); | |
| EXPECT_EQ(0, img.height()); | |
| EXPECT_FALSE(img.has_data()); | |
| EXPECT_TRUE(img.span().empty()); | |
| } | |
| TEST(fxcodec, JBig2ImageExpand) { | |
| CJBig2_Image img(kWidthPixels, kHeightLines); | |
| pdfium::span<uint8_t> line0 = img.GetLine(0); | |
| pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1); | |
| img.SetPixel(0, line0, true); | |
| img.SetPixel(kWidthPixels - 1, line_last, false); | |
| img.Expand(kLargerHeightLines, true); | |
| EXPECT_EQ(kWidthPixels, img.width()); | |
| EXPECT_EQ(kLargerHeightLines, img.height()); | |
| line0 = img.GetLine(0); | |
| line_last = img.GetLine(kHeightLines - 1); | |
| EXPECT_TRUE(img.GetPixel(0, line0)); | |
| EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last)); | |
| EXPECT_TRUE( | |
| img.GetPixel(kWidthPixels - 1, img.GetLine(kLargerHeightLines - 1))); | |
| } | |
| TEST(fxcodec, JBig2ImageExpandTooBig) { | |
| CJBig2_Image img(kWidthPixels, kHeightLines); | |
| pdfium::span<uint8_t> line0 = img.GetLine(0); | |
| pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1); | |
| img.SetPixel(0, line0, true); | |
| img.SetPixel(kWidthPixels - 1, line_last, false); | |
| img.Expand(kTooLargeHeightLines, true); | |
| EXPECT_EQ(kWidthPixels, img.width()); | |
| EXPECT_EQ(kHeightLines, img.height()); | |
| line0 = img.GetLine(0); | |
| line_last = img.GetLine(kHeightLines - 1); | |
| EXPECT_TRUE(img.GetPixel(0, line0)); | |
| EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last)); | |
| } | |
| TEST(fxcodec, JBig2ImageExpandExternal) { | |
| uint8_t buf[kHeightLines * kStrideBytes]; | |
| CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, buf); | |
| pdfium::span<uint8_t> line0 = img.GetLine(0); | |
| pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1); | |
| img.SetPixel(0, line0, true); | |
| img.SetPixel(kWidthPixels - 1, line_last, false); | |
| img.Expand(kLargerHeightLines, true); | |
| EXPECT_EQ(kWidthPixels, img.width()); | |
| EXPECT_EQ(kLargerHeightLines, img.height()); | |
| line0 = img.GetLine(0); | |
| line_last = img.GetLine(kHeightLines - 1); | |
| EXPECT_TRUE(img.GetPixel(0, line0)); | |
| EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last)); | |
| EXPECT_TRUE( | |
| img.GetPixel(kWidthPixels - 1, img.GetLine(kLargerHeightLines - 1))); | |
| } | |
| TEST(fxcodec, JBig2ImageExpandExternalTooBig) { | |
| uint8_t buf[kHeightLines * kStrideBytes]; | |
| CJBig2_Image img(kWidthPixels, kHeightLines, kStrideBytes, buf); | |
| pdfium::span<uint8_t> line0 = img.GetLine(0); | |
| pdfium::span<uint8_t> line_last = img.GetLine(kHeightLines - 1); | |
| img.SetPixel(0, line0, true); | |
| img.SetPixel(kWidthPixels - 1, line_last, false); | |
| img.Expand(kTooLargeHeightLines, true); | |
| EXPECT_EQ(kWidthPixels, img.width()); | |
| EXPECT_EQ(kHeightLines, img.height()); | |
| line0 = img.GetLine(0); | |
| line_last = img.GetLine(kHeightLines - 1); | |
| EXPECT_TRUE(img.GetPixel(0, line0)); | |
| EXPECT_FALSE(img.GetPixel(kWidthPixels - 1, line_last)); | |
| } | |
| TEST(fxcodec, JBig2EmptyImage) { | |
| auto empty = std::make_unique<CJBig2_Image>(0, 0); | |
| // Empty subimage. | |
| auto sub1 = empty->SubImage(0, 0, 0, 0); | |
| EXPECT_EQ(sub1->width(), 0); | |
| EXPECT_EQ(sub1->height(), 0); | |
| // Larger dimensions are zero-padded. | |
| auto sub2 = empty->SubImage(0, 0, 1, 1); | |
| EXPECT_EQ(1, sub2->width()); | |
| EXPECT_EQ(1, sub2->height()); | |
| EXPECT_EQ(0, sub2->GetPixel(0, sub2->GetLine(0))); | |
| // Bad dimensions give an empty image. | |
| sub2 = empty->SubImage(0, 0, -1, -1); | |
| EXPECT_EQ(sub2->width(), 0); | |
| EXPECT_EQ(sub2->height(), 0); | |
| // Bad offsets zero pad the image. | |
| auto sub3 = empty->SubImage(-1, -1, 2, 2); | |
| EXPECT_EQ(sub3->width(), 2); | |
| EXPECT_EQ(sub3->height(), 2); | |
| // Bad dimensions and bad offsets give an empty image. | |
| sub3 = empty->SubImage(-1, -1, -100, -100); | |
| EXPECT_EQ(sub3->width(), 0); | |
| EXPECT_EQ(sub3->height(), 0); | |
| } | |
| TEST(fxcodec, JBig2SubImage) { | |
| // clang-format off | |
| // 1-px wide rectangle in image. | |
| uint8_t pattern[40] = { | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| }; | |
| // 1-px wide rectangle in image, offset 2 in x. | |
| uint8_t pattern20[40] = { | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| }; | |
| // 1-px wide rectangle in image, offset 2 in x and y, padded. | |
| uint8_t pattern22[40] = { | |
| 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| }; | |
| // 1-px wide rectangle in image, offset 16 in x, 1 in y, padded. | |
| uint8_t pattern161[40] = { | |
| 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| }; | |
| // clang-format on | |
| // Image size a nice clean power of two. | |
| auto img32 = std::make_unique<CJBig2_Image>(32, 5, 8, pattern); | |
| // Image size not a nice clean value. | |
| auto img37 = std::make_unique<CJBig2_Image>(37, 5, 8, pattern); | |
| // Expected results to check against. | |
| auto expected20 = std::make_unique<CJBig2_Image>(30, 5, 8, pattern20); | |
| auto expected22 = std::make_unique<CJBig2_Image>(30, 5, 8, pattern22); | |
| auto expected161 = std::make_unique<CJBig2_Image>(25, 5, 8, pattern161); | |
| auto expected_zeros = std::make_unique<CJBig2_Image>(32, 5); | |
| // Empty subimage. | |
| auto sub = img32->SubImage(0, 0, 0, 0); | |
| EXPECT_EQ(sub->width(), 0); | |
| EXPECT_EQ(sub->height(), 0); | |
| // Full sub-image. | |
| sub = img32->SubImage(0, 0, 32, 5); | |
| EXPECT_EQ(sub->width(), 32); | |
| EXPECT_EQ(sub->height(), 5); | |
| CheckImageEq(img32.get(), sub.get(), __LINE__); | |
| sub = img37->SubImage(0, 0, 32, 5); | |
| EXPECT_EQ(sub->width(), 32); | |
| EXPECT_EQ(sub->height(), 5); | |
| CheckImageEq(img32.get(), sub.get(), __LINE__); | |
| // Actual bit manipulations. | |
| sub = img32->SubImage(2, 0, 30, 5); | |
| CheckImageEq(expected20.get(), sub.get(), __LINE__); | |
| sub = img37->SubImage(2, 2, 30, 5); | |
| CheckImageEq(expected22.get(), sub.get(), __LINE__); | |
| // Fast path. | |
| sub = img37->SubImage(16, 1, 25, 5); | |
| CheckImageEq(expected161.get(), sub.get(), __LINE__); | |
| // Aligned Sub-image including cruft in stride beyond width. | |
| sub = img37->SubImage(32, 0, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image waaaaay beyond width. | |
| sub = img37->SubImage(2000, 0, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image waaaaay beyond height. | |
| sub = img37->SubImage(0, 2000, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image with negative x offset. | |
| sub = img37->SubImage(-1, 0, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image with negative y offset. | |
| sub = img37->SubImage(0, -1, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image with negative width. | |
| sub = img37->SubImage(-1, 0, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image with negative height. | |
| sub = img37->SubImage(0, -1, 32, 5); | |
| CheckImageEq(expected_zeros.get(), sub.get(), __LINE__); | |
| // Sub-image wider than original. | |
| sub = img37->SubImage(0, 0, 128, 5); | |
| EXPECT_EQ(128, sub->width()); | |
| EXPECT_EQ(5, sub->height()); | |
| // Sub-image higher than original. | |
| sub = img37->SubImage(0, 0, 32, 40); | |
| EXPECT_EQ(32, sub->width()); | |
| EXPECT_EQ(40, sub->height()); | |
| } | |
| TEST(fxcodec, JBig2CopyLine) { | |
| // clang-format off | |
| // Horizontal line in image. | |
| uint8_t pattern[24] = { | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| }; | |
| uint8_t expected_pattern[24] = { | |
| 0x00, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| }; | |
| // clang-format on | |
| auto img = std::make_unique<CJBig2_Image>(37, 3, 8, pattern); | |
| auto expected = std::make_unique<CJBig2_Image>(37, 3, 8, expected_pattern); | |
| // Shuffle. | |
| img->CopyLine(img->GetLine(2), img->GetLine(1)); | |
| img->CopyLine(img->GetLine(1), img->GetLine(0)); | |
| img->CopyLine(img->GetLine(0), img->GetLine(2)); | |
| // Clear top line via invalid |from| offset. | |
| img->CopyLine(img->GetLine(2), img->GetLine(3)); | |
| // Copies with invalid |to|s don't mess with things. | |
| img->CopyLine(img->GetLine(-1), img->GetLine(0)); | |
| img->CopyLine(img->GetLine(4), img->GetLine(0)); | |
| img->CopyLine(img->GetLine(-1), img->GetLine(-1)); | |
| img->CopyLine(img->GetLine(4), img->GetLine(4)); | |
| CheckImageEq(expected.get(), img.get(), __LINE__); | |
| } |