blob: 7f48d946a9489207dce7429be255ea7a5b7f1f3c [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/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));
}