blob: c266a220132ccd58b187c769a5dc7aef92fb066a [file]
// Copyright 2023 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_pageimagecache.h"
#include <memory>
#include <string>
#include <utility>
#include "core/fpdfapi/page/cpdf_docpagedata.h"
#include "core/fpdfapi/page/cpdf_image.h"
#include "core/fpdfapi/page/cpdf_imageobject.h"
#include "core/fpdfapi/page/cpdf_page.h"
#include "core/fpdfapi/page/cpdf_pagemodule.h"
#include "core/fpdfapi/page/test_with_page_module.h"
#include "core/fpdfapi/parser/cpdf_parser.h"
#include "core/fpdfapi/render/cpdf_docrenderdata.h"
#include "core/fxcrt/cfx_fileaccess_stream.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/utils/path_service.h"
namespace pdfium {
using CPDFPageImageCacheTest = TestWithPageModule;
TEST_F(CPDFPageImageCacheTest, RenderBug1924) {
// If you render a page with a JPEG2000 image as a thumbnail (small picture)
// first, the image that gets cached has a low resolution. If you afterwards
// render it full-size, you should get a larger image - the image cache will
// be regenerate.
{
std::string file_path = PathService::GetTestFilePath("jpx_lzw.pdf");
ASSERT_FALSE(file_path.empty());
auto document =
std::make_unique<CPDF_Document>(std::make_unique<CPDF_DocRenderData>(),
std::make_unique<CPDF_DocPageData>());
ASSERT_EQ(document->LoadDoc(
CFX_FileAccessStream::CreateFromFilename(file_path.c_str()),
nullptr),
CPDF_Parser::SUCCESS);
RetainPtr<CPDF_Dictionary> page_dict =
document->GetMutablePageDictionary(0);
ASSERT_TRUE(page_dict);
auto page =
pdfium::MakeRetain<CPDF_Page>(document.get(), std::move(page_dict));
page->AddPageImageCache();
page->ParseContent();
CPDF_PageImageCache* page_image_cache = page->GetPageImageCache();
ASSERT_TRUE(page_image_cache);
CPDF_PageObject* page_obj = page->GetPageObjectByIndex(0);
ASSERT_TRUE(page_obj);
CPDF_ImageObject* image = page_obj->AsImage();
ASSERT_TRUE(image);
// Render with small scale.
bool should_continue = page_image_cache->StartGetCachedBitmap(
image->GetImage(), nullptr, page->GetMutablePageResources(), true,
CPDF_ColorSpace::Family::kICCBased, false, {50, 50});
while (should_continue) {
should_continue = page_image_cache->Continue(nullptr);
}
RetainPtr<CFX_DIBBase> bitmap_small = page_image_cache->DetachCurBitmap();
// And render with large scale.
should_continue = page_image_cache->StartGetCachedBitmap(
image->GetImage(), nullptr, page->GetMutablePageResources(), true,
CPDF_ColorSpace::Family::kICCBased, false, {100, 100});
while (should_continue) {
should_continue = page_image_cache->Continue(nullptr);
}
RetainPtr<CFX_DIBBase> bitmap_large = page_image_cache->DetachCurBitmap();
ASSERT_GT(bitmap_large->GetWidth(), bitmap_small->GetWidth());
ASSERT_GT(bitmap_large->GetHeight(), bitmap_small->GetHeight());
ASSERT_TRUE(page->AsPDFPage());
page->AsPDFPage()->ClearView();
}
}
TEST_F(CPDFPageImageCacheTest, RenderReducedDctThenFullSize) {
// A DCTDecode (JPEG) image drawn small is decoded at reduced resolution via
// libjpeg's scale_denom (like the JPEG2000 path above). If it is then drawn
// larger, the cache must re-decode at a higher resolution rather than
// upscaling the cached low-resolution bitmap. The image is 400x400 and a
// multiple of the MCU size, so reduced decoding is allowed.
std::string file_path = PathService::GetTestFilePath("jpeg_reduced_size.pdf");
ASSERT_FALSE(file_path.empty());
auto document =
std::make_unique<CPDF_Document>(std::make_unique<CPDF_DocRenderData>(),
std::make_unique<CPDF_DocPageData>());
ASSERT_EQ(
document->LoadDoc(
CFX_FileAccessStream::CreateFromFilename(file_path.c_str()), nullptr),
CPDF_Parser::SUCCESS);
RetainPtr<CPDF_Dictionary> page_dict = document->GetMutablePageDictionary(0);
ASSERT_TRUE(page_dict);
auto page =
pdfium::MakeRetain<CPDF_Page>(document.get(), std::move(page_dict));
page->AddPageImageCache();
page->ParseContent();
CPDF_PageImageCache* page_image_cache = page->GetPageImageCache();
ASSERT_TRUE(page_image_cache);
CPDF_PageObject* page_obj = page->GetPageObjectByIndex(0);
ASSERT_TRUE(page_obj);
CPDF_ImageObject* image = page_obj->AsImage();
ASSERT_TRUE(image);
// Render with small scale: 400/50 -> 1/8, so a 50x50 bitmap.
bool should_continue = page_image_cache->StartGetCachedBitmap(
image->GetImage(), nullptr, page->GetMutablePageResources(), true,
CPDF_ColorSpace::Family::kUnknown, false, {50, 50});
while (should_continue) {
should_continue = page_image_cache->Continue(nullptr);
}
RetainPtr<CFX_DIBBase> bitmap_small = page_image_cache->DetachCurBitmap();
ASSERT_TRUE(bitmap_small);
EXPECT_EQ(bitmap_small->GetWidth(), 50);
EXPECT_EQ(bitmap_small->GetHeight(), 50);
// Render with large scale: 400/100 -> 1/4, so a 100x100 bitmap. The cached
// 50x50 bitmap is too small, so this forces a higher-resolution re-decode.
should_continue = page_image_cache->StartGetCachedBitmap(
image->GetImage(), nullptr, page->GetMutablePageResources(), true,
CPDF_ColorSpace::Family::kUnknown, false, {100, 100});
while (should_continue) {
should_continue = page_image_cache->Continue(nullptr);
}
RetainPtr<CFX_DIBBase> bitmap_large = page_image_cache->DetachCurBitmap();
ASSERT_TRUE(bitmap_large);
EXPECT_EQ(bitmap_large->GetWidth(), 100);
EXPECT_EQ(bitmap_large->GetHeight(), 100);
ASSERT_TRUE(page->AsPDFPage());
page->AsPDFPage()->ClearView();
}
TEST_F(CPDFPageImageCacheTest, RenderReducedDctWithSMask) {
// The base image (DCTDecode) reduces when drawn small, but its soft mask is
// always loaded at full resolution (StartLoadMaskDIB passes a zero max size).
// The base and mask therefore have different dimensions; this exercises that
// path and confirms both are produced without a dimension-mismatch failure.
std::string file_path =
PathService::GetTestFilePath("jpeg_reduced_size_with_smask.pdf");
ASSERT_FALSE(file_path.empty());
auto document =
std::make_unique<CPDF_Document>(std::make_unique<CPDF_DocRenderData>(),
std::make_unique<CPDF_DocPageData>());
ASSERT_EQ(
document->LoadDoc(
CFX_FileAccessStream::CreateFromFilename(file_path.c_str()), nullptr),
CPDF_Parser::SUCCESS);
RetainPtr<CPDF_Dictionary> page_dict = document->GetMutablePageDictionary(0);
ASSERT_TRUE(page_dict);
auto page =
pdfium::MakeRetain<CPDF_Page>(document.get(), std::move(page_dict));
page->AddPageImageCache();
page->ParseContent();
CPDF_PageImageCache* page_image_cache = page->GetPageImageCache();
ASSERT_TRUE(page_image_cache);
CPDF_PageObject* page_obj = page->GetPageObjectByIndex(0);
ASSERT_TRUE(page_obj);
CPDF_ImageObject* image = page_obj->AsImage();
ASSERT_TRUE(image);
// Draw small so the 400x400 base is decoded at reduced resolution.
bool should_continue = page_image_cache->StartGetCachedBitmap(
image->GetImage(), nullptr, page->GetMutablePageResources(), true,
CPDF_ColorSpace::Family::kUnknown, /*bLoadMask=*/true, {50, 50});
while (should_continue) {
should_continue = page_image_cache->Continue(nullptr);
}
RetainPtr<CFX_DIBBase> bitmap = page_image_cache->DetachCurBitmap();
RetainPtr<CFX_DIBBase> mask = page_image_cache->DetachCurMask();
ASSERT_TRUE(bitmap);
ASSERT_TRUE(mask);
// The base is reduced; the mask is loaded at full resolution. The compositor
// scales each to the destination independently, so the differing sizes are
// expected and handled.
EXPECT_EQ(bitmap->GetWidth(), 50);
EXPECT_EQ(bitmap->GetHeight(), 50);
EXPECT_EQ(mask->GetWidth(), 400);
EXPECT_EQ(mask->GetHeight(), 400);
ASSERT_TRUE(page->AsPDFPage());
page->AsPDFPage()->ClearView();
}
TEST_F(CPDFPageImageCacheTest, NonMcuAlignedDctIsNotReduced) {
// Reduced-size decoding is only safe when the image dimensions are a multiple
// of the MCU size (otherwise padded edge blocks would contaminate the
// result). This 408x408 image is 4:2:0, so its MCU is 16x16 and 408 is not a
// multiple of it (408 % 16 == 8), even though 408 % 8 == 0. Drawing it small
// must therefore still decode it at full resolution rather than reducing it.
std::string file_path =
PathService::GetTestFilePath("jpeg_unaligned_no_reduce.pdf");
ASSERT_FALSE(file_path.empty());
auto document =
std::make_unique<CPDF_Document>(std::make_unique<CPDF_DocRenderData>(),
std::make_unique<CPDF_DocPageData>());
ASSERT_EQ(
document->LoadDoc(
CFX_FileAccessStream::CreateFromFilename(file_path.c_str()), nullptr),
CPDF_Parser::SUCCESS);
RetainPtr<CPDF_Dictionary> page_dict = document->GetMutablePageDictionary(0);
ASSERT_TRUE(page_dict);
auto page =
pdfium::MakeRetain<CPDF_Page>(document.get(), std::move(page_dict));
page->AddPageImageCache();
page->ParseContent();
CPDF_PageImageCache* page_image_cache = page->GetPageImageCache();
ASSERT_TRUE(page_image_cache);
CPDF_PageObject* page_obj = page->GetPageObjectByIndex(0);
ASSERT_TRUE(page_obj);
CPDF_ImageObject* image = page_obj->AsImage();
ASSERT_TRUE(image);
// Draw small: an MCU-aligned image would be reduced here, but this one must
// not be, so the decoded bitmap stays at the full 408x408.
bool should_continue = page_image_cache->StartGetCachedBitmap(
image->GetImage(), nullptr, page->GetMutablePageResources(), true,
CPDF_ColorSpace::Family::kUnknown, false, {50, 50});
while (should_continue) {
should_continue = page_image_cache->Continue(nullptr);
}
RetainPtr<CFX_DIBBase> bitmap = page_image_cache->DetachCurBitmap();
ASSERT_TRUE(bitmap);
EXPECT_EQ(bitmap->GetWidth(), 408);
EXPECT_EQ(bitmap->GetHeight(), 408);
ASSERT_TRUE(page->AsPDFPage());
page->AsPDFPage()->ClearView();
}
} // namespace pdfium