
For certain PDFs, pdfium_test has no problem rendering them, while the PDF Viewer does. Looking at the differences, when a page has transparencies, pdfium_test fills the bitmap it renders into with pixel value 0, while the PDF Viewer fills them with 0xFFFFFFFF. Simply switching the PDF Viewer to match pdfium_test's fill color results in weird smeared rendering, as this has been attempted several times without success. The key to fixing this is ask the PaintManager to draw the page's white background first into the actual rendering surface, before drawing the bitmap with the contents. Then everything renders more correctly, so update PaintManagerTest to match the new behavior. The remaining issue is premultiplied alpha handling. It turns out the bitmap PDFium draws into had always been premultiplied in the PDF Viewer, which is not right, considering PDFium assumes the bitmap has straight alpha. However, this did not matter, because all the pixels had 0xFF as the alpha value, so the bitmap was effectively opaque. Now that the bitmap actually has alpha, the wrong bitmap type causes color differences due to straight vs. premultiplied alpha confusion. Fix this by marking the bitmap as un-premultiplied. Considering some of this PDF Viewer code has been behaving the same way for 10+ years, and the last change caused this bug, play it safe this time with a kill switch. Preserve the old behavior in a fallback path, while enabling the code path with the fix by default. At the same time, use pdfium_test's drawing logic in PDFiumPage::GenerateThumbnail() to fix the same issue with thumbnails. Update tests with the new expectations. Bug: 40216952 Change-Id: Ieadf2e76b9253351b74c5b7ccf21e74d3c4e3c05 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5940452 Reviewed-by: Andy Phan <andyphan@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Cr-Commit-Position: refs/heads/main@{#1370910}
340 lines
12 KiB
C++
340 lines
12 KiB
C++
// Copyright 2022 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "pdf/paint_manager.h"
|
|
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include "base/files/file_path.h"
|
|
#include "base/run_loop.h"
|
|
#include "cc/test/pixel_comparator.h"
|
|
#include "cc/test/pixel_test_utils.h"
|
|
#include "pdf/paint_ready_rect.h"
|
|
#include "pdf/test/test_helpers.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/skia/include/core/SkBitmap.h"
|
|
#include "third_party/skia/include/core/SkCanvas.h"
|
|
#include "third_party/skia/include/core/SkColor.h"
|
|
#include "third_party/skia/include/core/SkImage.h"
|
|
#include "third_party/skia/include/core/SkRect.h"
|
|
#include "third_party/skia/include/core/SkRefCnt.h"
|
|
#include "third_party/skia/include/core/SkSurface.h"
|
|
#include "ui/gfx/geometry/rect.h"
|
|
#include "ui/gfx/geometry/size.h"
|
|
#include "ui/gfx/geometry/skia_conversions.h"
|
|
|
|
namespace chrome_pdf {
|
|
|
|
namespace {
|
|
|
|
using ::testing::_;
|
|
using ::testing::NiceMock;
|
|
|
|
base::FilePath GetTestDataFilePath(std::string_view filename) {
|
|
return base::FilePath(FILE_PATH_LITERAL("paint_manager"))
|
|
.AppendASCII(filename);
|
|
}
|
|
|
|
class FakeClient : public PaintManager::Client {
|
|
public:
|
|
MOCK_METHOD(void, InvalidatePluginContainer, (), (override));
|
|
MOCK_METHOD(void,
|
|
OnPaint,
|
|
(const std::vector<gfx::Rect>& paint_rects,
|
|
std::vector<PaintReadyRect>& ready,
|
|
std::vector<gfx::Rect>& pending),
|
|
(override));
|
|
MOCK_METHOD(void, UpdateSnapshot, (sk_sp<SkImage> snapshot), (override));
|
|
MOCK_METHOD(void, UpdateScale, (float scale), (override));
|
|
MOCK_METHOD(void,
|
|
UpdateLayerTransform,
|
|
(float scale, const gfx::Vector2dF& translate),
|
|
(override));
|
|
};
|
|
|
|
class PaintManagerTest : public testing::Test {
|
|
protected:
|
|
void WaitForOnPaint() {
|
|
base::RunLoop run_loop;
|
|
EXPECT_CALL(client_, OnPaint).WillOnce([&run_loop] { run_loop.Quit(); });
|
|
run_loop.Run();
|
|
}
|
|
|
|
sk_sp<SkImage> WaitForFlush(
|
|
const std::vector<gfx::Rect>& expected_paint_rects,
|
|
std::vector<PaintReadyRect> fake_ready,
|
|
std::vector<gfx::Rect> fake_pending) {
|
|
EXPECT_CALL(client_, OnPaint(expected_paint_rects, _, _))
|
|
.WillOnce([&fake_ready, &fake_pending](
|
|
const std::vector<gfx::Rect>& paint_rects,
|
|
std::vector<PaintReadyRect>& ready,
|
|
std::vector<gfx::Rect>& pending) {
|
|
ready = std::move(fake_ready);
|
|
pending = std::move(fake_pending);
|
|
});
|
|
|
|
sk_sp<SkImage> saved_snapshot;
|
|
base::RunLoop run_loop;
|
|
EXPECT_CALL(client_, UpdateSnapshot)
|
|
.WillOnce([&saved_snapshot, &run_loop](sk_sp<SkImage> snapshot) {
|
|
saved_snapshot = std::move(snapshot);
|
|
run_loop.Quit();
|
|
});
|
|
run_loop.Run();
|
|
|
|
return saved_snapshot;
|
|
}
|
|
|
|
// Generates the expectations for use with TestPaintImage().
|
|
//
|
|
// - `plugin_size`: The expected image size.
|
|
// - `paint_rect`: Expected to be painted white.
|
|
// - `overlapped_rect`: Expected to be painted red.
|
|
// May paint over `paint_rect`.
|
|
SkBitmap GeneratePaintImageExpectation(const gfx::Size& plugin_size,
|
|
const gfx::Rect& paint_rect,
|
|
const gfx::Rect& overlapped_rect) {
|
|
sk_sp<SkSurface> surface =
|
|
CreateSkiaSurfaceForTesting(plugin_size, SK_ColorMAGENTA);
|
|
SkCanvas* canvas = surface->getCanvas();
|
|
canvas->clipIRect(gfx::RectToSkIRect(paint_rect));
|
|
canvas->clear(SK_ColorWHITE);
|
|
canvas->clipIRect(gfx::RectToSkIRect(overlapped_rect));
|
|
canvas->clear(SK_ColorRED);
|
|
|
|
SkBitmap bitmap;
|
|
EXPECT_TRUE(surface->makeImageSnapshot()->asLegacyBitmap(&bitmap));
|
|
return bitmap;
|
|
}
|
|
|
|
void TestPaintImage(const gfx::Size& plugin_size,
|
|
const gfx::Size& source_size,
|
|
const gfx::Rect& paint_rect,
|
|
const gfx::Rect& overlapped_rect) {
|
|
// Paint `paint_rect` from `source_size` image over a magenta background.
|
|
paint_manager_.SetSize(plugin_size, 1.0f);
|
|
sk_sp<SkImage> snapshot = WaitForFlush(
|
|
/*expected_paint_rects=*/{{gfx::Rect(plugin_size)}},
|
|
/*fake_ready=*/
|
|
{
|
|
{gfx::Rect(plugin_size),
|
|
CreateSkiaImageForTesting(plugin_size, SK_ColorMAGENTA)},
|
|
{paint_rect, CreateSkiaImageForTesting(source_size, SK_ColorRED)},
|
|
},
|
|
/*fake_pending=*/{});
|
|
ASSERT_TRUE(snapshot);
|
|
|
|
// Check if `snapshot` matches `expected_bitmap`.
|
|
snapshot = snapshot->makeSubset(
|
|
static_cast<GrDirectContext*>(nullptr),
|
|
SkIRect::MakeWH(plugin_size.width(), plugin_size.height()));
|
|
ASSERT_TRUE(snapshot);
|
|
|
|
SkBitmap snapshot_bitmap;
|
|
ASSERT_TRUE(snapshot->asLegacyBitmap(&snapshot_bitmap));
|
|
|
|
SkBitmap expected_bitmap =
|
|
GeneratePaintImageExpectation(plugin_size, paint_rect, overlapped_rect);
|
|
EXPECT_TRUE(cc::MatchesBitmap(snapshot_bitmap, expected_bitmap,
|
|
cc::ExactPixelComparator()));
|
|
}
|
|
|
|
void TestScroll(const gfx::Vector2d& scroll_amount,
|
|
const gfx::Rect& expected_paint_rect,
|
|
std::string_view expected_png) {
|
|
// Paint non-uniform initial image.
|
|
gfx::Size plugin_size = paint_manager_.GetEffectiveSize();
|
|
ASSERT_GE(plugin_size.width(), 4);
|
|
ASSERT_GE(plugin_size.height(), 4);
|
|
|
|
sk_sp<SkSurface> initial_surface =
|
|
CreateSkiaSurfaceForTesting(plugin_size, SK_ColorRED);
|
|
initial_surface->getCanvas()->clipIRect(SkIRect::MakeLTRB(
|
|
1, 1, plugin_size.width() - 1, plugin_size.height() - 2));
|
|
initial_surface->getCanvas()->clear(SK_ColorGREEN);
|
|
|
|
paint_manager_.Invalidate();
|
|
ASSERT_TRUE(WaitForFlush(
|
|
/*expected_paint_rects=*/{gfx::Rect(plugin_size)},
|
|
/*fake_ready=*/
|
|
{{gfx::Rect(plugin_size), initial_surface->makeImageSnapshot()}},
|
|
/*fake_pending=*/{}));
|
|
|
|
// Scroll by `scroll_amount`, painting `expected_paint_rect` magenta.
|
|
paint_manager_.ScrollRect(gfx::Rect(plugin_size), scroll_amount);
|
|
sk_sp<SkImage> snapshot = WaitForFlush(
|
|
/*expected_paint_rects=*/{expected_paint_rect},
|
|
/*fake_ready=*/
|
|
{{expected_paint_rect,
|
|
CreateSkiaImageForTesting(plugin_size, SK_ColorMAGENTA)}},
|
|
/*fake_pending=*/{});
|
|
ASSERT_TRUE(snapshot);
|
|
|
|
// Compare snapshot to `expected_png`.
|
|
snapshot = snapshot->makeSubset(
|
|
static_cast<GrDirectContext*>(nullptr),
|
|
SkIRect::MakeWH(plugin_size.width(), plugin_size.height()));
|
|
ASSERT_TRUE(snapshot);
|
|
|
|
EXPECT_TRUE(
|
|
MatchesPngFile(snapshot.get(), GetTestDataFilePath(expected_png)));
|
|
}
|
|
|
|
NiceMock<FakeClient> client_;
|
|
PaintManager paint_manager_{&client_};
|
|
};
|
|
|
|
TEST_F(PaintManagerTest, GetNewContextSizeWhenGrowingBelowMaximum) {
|
|
EXPECT_EQ(gfx::Size(450, 350),
|
|
PaintManager::GetNewContextSize({450, 350}, {450, 349}));
|
|
EXPECT_EQ(gfx::Size(450, 350),
|
|
PaintManager::GetNewContextSize({450, 350}, {449, 350}));
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, GetNewContextSizeWhenGrowingAboveMaximum) {
|
|
EXPECT_EQ(gfx::Size(501, 400),
|
|
PaintManager::GetNewContextSize({450, 350}, {451, 350}));
|
|
EXPECT_EQ(gfx::Size(500, 401),
|
|
PaintManager::GetNewContextSize({450, 350}, {450, 351}));
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, GetNewContextSizeWhenShrinkingAboveMinimum) {
|
|
EXPECT_EQ(gfx::Size(450, 350),
|
|
PaintManager::GetNewContextSize({450, 350}, {350, 251}));
|
|
EXPECT_EQ(gfx::Size(450, 350),
|
|
PaintManager::GetNewContextSize({450, 350}, {351, 250}));
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, GetNewContextSizeWhenShrinkingBelowMinimum) {
|
|
EXPECT_EQ(gfx::Size(399, 300),
|
|
PaintManager::GetNewContextSize({450, 350}, {349, 250}));
|
|
EXPECT_EQ(gfx::Size(400, 299),
|
|
PaintManager::GetNewContextSize({450, 350}, {350, 249}));
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, Create) {
|
|
EXPECT_EQ(gfx::Size(0, 0), paint_manager_.GetEffectiveSize());
|
|
EXPECT_EQ(1.0f, paint_manager_.GetEffectiveDeviceScale());
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, SetSizeWithoutPaint) {
|
|
EXPECT_CALL(client_, InvalidatePluginContainer).Times(0);
|
|
paint_manager_.SetSize({400, 300}, 2.0f);
|
|
|
|
EXPECT_EQ(gfx::Size(400, 300), paint_manager_.GetEffectiveSize());
|
|
EXPECT_EQ(2.0f, paint_manager_.GetEffectiveDeviceScale());
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, SetSizeWithPaint) {
|
|
paint_manager_.SetSize({400, 300}, 2.0f);
|
|
|
|
EXPECT_CALL(client_, InvalidatePluginContainer);
|
|
EXPECT_CALL(client_, UpdateScale(0.5f));
|
|
WaitForOnPaint();
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, SetTransformWithoutSurface) {
|
|
EXPECT_CALL(client_, UpdateLayerTransform).Times(0);
|
|
paint_manager_.SetTransform(0.25f, {150, 50}, {-4, 8},
|
|
/*schedule_flush=*/true);
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, SetTransformWithSurface) {
|
|
paint_manager_.SetSize({400, 300}, 2.0f);
|
|
WaitForOnPaint();
|
|
|
|
EXPECT_CALL(client_,
|
|
UpdateLayerTransform(0.25f, gfx::Vector2dF(116.5f, 29.5f)));
|
|
paint_manager_.SetTransform(0.25f, {150, 50}, {-4, 8},
|
|
/*schedule_flush=*/true);
|
|
WaitForOnPaint();
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, ClearTransform) {
|
|
paint_manager_.SetSize({400, 300}, 2.0f);
|
|
WaitForOnPaint();
|
|
|
|
EXPECT_CALL(client_, UpdateLayerTransform(1.0f, gfx::Vector2dF()));
|
|
paint_manager_.ClearTransform();
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, DoPaintFirst) {
|
|
paint_manager_.SetSize({400, 300}, 2.0f);
|
|
|
|
sk_sp<SkImage> snapshot =
|
|
WaitForFlush(/*expected_paint_rects=*/{{0, 0, 400, 300}},
|
|
/*fake_ready=*/
|
|
{{{25, 50, 200, 100},
|
|
CreateSkiaImageForTesting({200, 100}, SK_ColorGRAY)}},
|
|
/*fake_pending=*/{});
|
|
|
|
EXPECT_TRUE(MatchesPngFile(snapshot.get(),
|
|
GetTestDataFilePath("do_paint_first.png")));
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, PaintImage) {
|
|
// Painted area is within the plugin area and the source image.
|
|
TestPaintImage(/*plugin_size=*/{20, 20}, /*source_size=*/{15, 15},
|
|
/*paint_rect=*/{0, 0, 10, 10},
|
|
/*overlapped_rect=*/{0, 0, 10, 10});
|
|
|
|
// Painted area straddles the plugin area and the source image.
|
|
TestPaintImage(/*plugin_size=*/{50, 30}, /*source_size=*/{30, 50},
|
|
/*paint_rect=*/{10, 10, 30, 30},
|
|
/*overlapped_rect=*/{10, 10, 20, 20});
|
|
|
|
// Painted area is outside the plugin area.
|
|
TestPaintImage(/*plugin_size=*/{10, 10}, /*source_size=*/{30, 30},
|
|
/*paint_rect=*/{10, 10, 10, 10},
|
|
/*overlapped_rect=*/{0, 0, 0, 0});
|
|
|
|
// Painted area is outside the source image.
|
|
TestPaintImage(/*plugin_size=*/{15, 15}, /*source_size=*/{5, 5},
|
|
/*paint_rect=*/{10, 10, 5, 5},
|
|
/*overlapped_rect=*/{0, 0, 0, 0});
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, Scroll) {
|
|
paint_manager_.SetSize({4, 5}, 1.0f);
|
|
|
|
TestScroll(/*scroll_amount=*/{1, 0}, /*expected_paint_rect=*/{0, 0, 1, 5},
|
|
"scroll_right.png");
|
|
TestScroll(/*scroll_amount=*/{-2, 0}, /*expected_paint_rect=*/{2, 0, 2, 5},
|
|
"scroll_left.png");
|
|
TestScroll(/*scroll_amount=*/{0, 3}, /*expected_paint_rect=*/{0, 0, 4, 3},
|
|
"scroll_down.png");
|
|
TestScroll(/*scroll_amount=*/{0, -3}, /*expected_paint_rect=*/{0, 2, 4, 3},
|
|
"scroll_up.png");
|
|
}
|
|
|
|
TEST_F(PaintManagerTest, ScrollIgnored) {
|
|
paint_manager_.SetSize({4, 5}, 1.0f);
|
|
|
|
// Scroll to the edge of the plugin area.
|
|
TestScroll(/*scroll_amount=*/{4, 0}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
TestScroll(/*scroll_amount=*/{-4, 0}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
TestScroll(/*scroll_amount=*/{0, 5}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
TestScroll(/*scroll_amount=*/{0, -5}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
|
|
// Scroll outside of the plugin area.
|
|
TestScroll(/*scroll_amount=*/{5, 0}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
TestScroll(/*scroll_amount=*/{-7, 0}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
TestScroll(/*scroll_amount=*/{0, 8}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
TestScroll(/*scroll_amount=*/{0, -9}, /*expected_paint_rect=*/{0, 0, 4, 5},
|
|
"scroll_ignored.png");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace chrome_pdf
|