blob: 2589d06f85a4fa84822b06c2f112fc2ac2122785 [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/jpeg/jpegmodule.h"
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "core/fxcodec/scanlinedecoder.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/span_util.h"
#include "core/fxge/dib/fx_dib.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/utils/file_util.h"
#include "testing/utils/path_service.h"
namespace fxcodec {
namespace {
std::vector<uint8_t> LoadMonaLisa() {
std::string file_path = PathService::GetTestFilePath("mona_lisa.jpg");
return GetFileContents(file_path.c_str());
}
} // namespace
TEST(JpegModuleTest, LoadInfoInvalid) {
EXPECT_FALSE(JpegModule::LoadInfo({}).has_value());
std::vector<uint8_t> invalid = {0x00, 0x01, 0x02, 0x03};
EXPECT_FALSE(JpegModule::LoadInfo(invalid).has_value());
}
TEST(JpegModuleTest, LoadInfoValid) {
std::vector<uint8_t> jpeg_data = LoadMonaLisa();
ASSERT_FALSE(jpeg_data.empty());
std::optional<JpegModule::ImageInfo> info = JpegModule::LoadInfo(jpeg_data);
ASSERT_TRUE(info.has_value());
EXPECT_EQ(120u, info->width);
EXPECT_EQ(120u, info->height);
EXPECT_EQ(3, info->num_components);
EXPECT_EQ(8, info->bits_per_components);
}
TEST(JpegModuleTest, CreateDecoderInvalid) {
EXPECT_FALSE(JpegModule::CreateDecoder({}, 120, 120, 3, true, 1, false));
std::vector<uint8_t> invalid = {0x00, 0x01, 0x02, 0x03};
EXPECT_FALSE(JpegModule::CreateDecoder(invalid, 120, 120, 3, true, 1, false));
}
TEST(JpegModuleTest, CreateDecoderValid) {
std::vector<uint8_t> jpeg_data = LoadMonaLisa();
ASSERT_FALSE(jpeg_data.empty());
std::unique_ptr<ScanlineDecoder> decoder =
JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, false);
ASSERT_TRUE(decoder);
EXPECT_EQ(120, decoder->GetWidth());
EXPECT_EQ(120, decoder->GetHeight());
EXPECT_EQ(3, decoder->CountComps());
EXPECT_EQ(8, decoder->GetBPC());
for (int line = 0; line < 120; ++line) {
pdfium::span<const uint8_t> scanline = decoder->GetScanline(line);
EXPECT_FALSE(scanline.empty()) << "line " << line;
EXPECT_GE(scanline.size(), 360u) << "line " << line;
}
// Out of range scanline line index.
EXPECT_TRUE(decoder->GetScanline(120).empty());
// Rewind and re-read line 0.
pdfium::span<const uint8_t> scanline0 = decoder->GetScanline(0);
EXPECT_FALSE(scanline0.empty());
EXPECT_GE(scanline0.size(), 360u);
}
TEST(JpegModuleTest, CreateDecoderDownscaled) {
std::vector<uint8_t> jpeg_data = LoadMonaLisa();
ASSERT_FALSE(jpeg_data.empty());
// 1/2 scale (scale_denom = 2).
std::unique_ptr<ScanlineDecoder> decoder2 =
JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 2, false);
ASSERT_TRUE(decoder2);
EXPECT_EQ(60, decoder2->GetWidth());
EXPECT_EQ(60, decoder2->GetHeight());
for (int line = 0; line < 60; ++line) {
pdfium::span<const uint8_t> scanline = decoder2->GetScanline(line);
EXPECT_FALSE(scanline.empty()) << "line " << line;
EXPECT_GE(scanline.size(), 180u) << "line " << line;
}
// 1/4 scale (scale_denom = 4).
std::unique_ptr<ScanlineDecoder> decoder4 =
JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 4, false);
ASSERT_TRUE(decoder4);
EXPECT_EQ(30, decoder4->GetWidth());
EXPECT_EQ(30, decoder4->GetHeight());
for (int line = 0; line < 30; ++line) {
pdfium::span<const uint8_t> scanline = decoder4->GetScanline(line);
EXPECT_FALSE(scanline.empty()) << "line " << line;
EXPECT_GE(scanline.size(), 90u) << "line " << line;
}
// 1/8 scale (scale_denom = 8).
std::unique_ptr<ScanlineDecoder> decoder8 =
JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 8, false);
ASSERT_TRUE(decoder8);
EXPECT_EQ(15, decoder8->GetWidth());
EXPECT_EQ(15, decoder8->GetHeight());
for (int line = 0; line < 15; ++line) {
pdfium::span<const uint8_t> scanline = decoder8->GetScanline(line);
EXPECT_FALSE(scanline.empty()) << "line " << line;
EXPECT_GE(scanline.size(), 45u) << "line " << line;
}
}
TEST(JpegModuleTest, CreateDecoderBgrOutput) {
std::vector<uint8_t> jpeg_data = LoadMonaLisa();
ASSERT_FALSE(jpeg_data.empty());
std::unique_ptr<ScanlineDecoder> rgb_decoder =
JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, false);
ASSERT_TRUE(rgb_decoder);
EXPECT_FALSE(rgb_decoder->ScanlinesAreBgr());
std::unique_ptr<ScanlineDecoder> bgr_decoder =
JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, true);
ASSERT_TRUE(bgr_decoder);
if (bgr_decoder->ScanlinesAreBgr()) {
// libjpeg honored the request (not the case for e.g. the Skia decoder):
// every scanline must hold exactly the RGB decode's samples with the
// red and blue positions exchanged.
EXPECT_EQ(3, bgr_decoder->CountComps());
EXPECT_EQ(8, bgr_decoder->GetBPC());
for (int line = 0; line < 120; ++line) {
pdfium::span<const uint8_t> rgb_bytes = rgb_decoder->GetScanline(line);
pdfium::span<const uint8_t> bgr_bytes = bgr_decoder->GetScanline(line);
ASSERT_EQ(360u, rgb_bytes.size()) << "line " << line;
ASSERT_EQ(360u, bgr_bytes.size()) << "line " << line;
// Reading one scanline as R,G,B structs and the other as B,G,R structs
// makes the same-samples-swapped-positions property a field-by-field
// comparison.
auto rgb =
fxcrt::reinterpret_span<const FX_RGB_STRUCT<uint8_t>>(rgb_bytes);
auto bgr =
fxcrt::reinterpret_span<const FX_BGR_STRUCT<uint8_t>>(bgr_bytes);
for (size_t i = 0; i < 120; ++i) {
ASSERT_EQ(rgb[i].red, bgr[i].red) << "line " << line << " px " << i;
ASSERT_EQ(rgb[i].green, bgr[i].green) << "line " << line << " px " << i;
ASSERT_EQ(rgb[i].blue, bgr[i].blue) << "line " << line << " px " << i;
}
}
}
}
} // namespace fxcodec