| // 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/fpdfapi/page/cpdf_dib.h" | |
| #include <stdint.h> | |
| #include <utility> | |
| #include "core/fpdfapi/page/test_with_page_module.h" | |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" | |
| #include "core/fpdfapi/parser/cpdf_name.h" | |
| #include "core/fpdfapi/parser/cpdf_number.h" | |
| #include "core/fpdfapi/parser/cpdf_stream.h" | |
| #include "core/fpdfapi/parser/cpdf_test_document.h" | |
| #include "core/fxcrt/data_vector.h" | |
| #include "core/fxcrt/retain_ptr.h" | |
| #include "core/fxcrt/span.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace { | |
| using CPDFDIBTest = TestWithPageModule; | |
| // The width every test uses. Two pixels is enough to catch a wrong stride. | |
| constexpr int kWidth = 2; | |
| // Builds an uncompressed 2x1 DeviceRGB image XObject with the given sample | |
| // data and loads it as a CPDF_DIB. The DIB stores 24bpp images as kBgr, so | |
| // the scanline it returns is the default-decode translation of `data`. | |
| RetainPtr<CPDF_DIB> LoadDeviceRgbImage(CPDF_Document* doc, | |
| int bits_per_component, | |
| DataVector<uint8_t> data) { | |
| auto dict = pdfium::MakeRetain<CPDF_Dictionary>(); | |
| dict->SetNewFor<CPDF_Name>("Type", "XObject"); | |
| dict->SetNewFor<CPDF_Name>("Subtype", "Image"); | |
| dict->SetNewFor<CPDF_Number>("Width", kWidth); | |
| dict->SetNewFor<CPDF_Number>("Height", 1); | |
| dict->SetNewFor<CPDF_Number>("BitsPerComponent", bits_per_component); | |
| dict->SetNewFor<CPDF_Name>("ColorSpace", "DeviceRGB"); | |
| auto stream = | |
| pdfium::MakeRetain<CPDF_Stream>(std::move(data), std::move(dict)); | |
| auto dib = pdfium::MakeRetain<CPDF_DIB>(doc, std::move(stream)); | |
| if (!dib->Load()) { | |
| return nullptr; | |
| } | |
| return dib; | |
| } | |
| } // namespace | |
| // Fewer than 8 bits per component: each component is read out of the bit | |
| // stream and scaled to 8 bits. | |
| TEST_F(CPDFDIBTest, DefaultDecode4BitsPerComponent) { | |
| CPDF_TestDocument doc; | |
| // Two pixels of 3 components at 4 bits each: R=0x0 G=0x8 B=0xf, then | |
| // R=0xf G=0x0 B=0x8. | |
| DataVector<uint8_t> data = {0x08, 0xff, 0x08}; | |
| RetainPtr<CPDF_DIB> dib = | |
| LoadDeviceRgbImage(&doc, /*bits_per_component=*/4, std::move(data)); | |
| ASSERT_TRUE(dib); | |
| // Each 4-bit sample scaled by 255 / 15, stored blue, green, red. | |
| EXPECT_THAT(dib->GetScanline(0), | |
| testing::ElementsAre(0xff, 0x88, 0x00, 0x88, 0x00, 0xff)); | |
| } | |
| // 8 bits per component is a plain RGB-to-BGR swap. | |
| TEST_F(CPDFDIBTest, DefaultDecode8BitsPerComponent) { | |
| CPDF_TestDocument doc; | |
| DataVector<uint8_t> data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; | |
| RetainPtr<CPDF_DIB> dib = | |
| LoadDeviceRgbImage(&doc, /*bits_per_component=*/8, std::move(data)); | |
| ASSERT_TRUE(dib); | |
| EXPECT_THAT(dib->GetScanline(0), | |
| testing::ElementsAre(0x33, 0x22, 0x11, 0x66, 0x55, 0x44)); | |
| } | |
| // 16 bits per component keeps the high byte of each big-endian sample. | |
| TEST_F(CPDFDIBTest, DefaultDecode16BitsPerComponent) { | |
| CPDF_TestDocument doc; | |
| // Two pixels, samples big-endian: R=0x1122 G=0x3344 B=0x5566, then | |
| // R=0x7788 G=0x99aa B=0xbbcc. | |
| DataVector<uint8_t> data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, | |
| 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc}; | |
| RetainPtr<CPDF_DIB> dib = | |
| LoadDeviceRgbImage(&doc, /*bits_per_component=*/16, std::move(data)); | |
| ASSERT_TRUE(dib); | |
| EXPECT_THAT(dib->GetScanline(0), | |
| testing::ElementsAre(0x55, 0x33, 0x11, 0xbb, 0x99, 0x77)); | |
| } |