Add helper to calculate sizes of PDF thumbnails
Add a method to calculate the size of a thumbnail image in device pixels using the dimensions of the PDF page and the intended device to pixel ratio. Thumbnail images are raw RGBA bitmaps with their sizes capped at a maximum of 255KiB; their pixel dimensions are also bound to that limit. However, the aspect ratio of thumbnails will always be as close as possible to that of its corresponding PDF page. Bug: 652400 Change-Id: I1866f52e1eb1835d921e5653c76fc98d68d77f9c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2378387 Commit-Queue: Daniel Hosseinian <dhoss@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: K. Moon <kmoon@chromium.org> Cr-Commit-Position: refs/heads/master@{#804272}
This commit is contained in:

committed by
Commit Bot

parent
e230cadcb4
commit
3766873879
@ -311,6 +311,7 @@ if (enable_pdf) {
|
||||
"ppapi_migration/geometry_conversions_unittest.cc",
|
||||
"range_set_unittest.cc",
|
||||
"test/run_all_unittests.cc",
|
||||
"thumbnail_unittest.cc",
|
||||
]
|
||||
|
||||
configs += [
|
||||
|
@ -4,9 +4,13 @@
|
||||
|
||||
#include "pdf/thumbnail.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/check_op.h"
|
||||
#include "base/numerics/ranges.h"
|
||||
#include "base/numerics/safe_math.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "third_party/skia/include/core/SkImageInfo.h"
|
||||
#include "ui/gfx/geometry/size.h"
|
||||
@ -18,6 +22,73 @@ namespace {
|
||||
constexpr float kMinDevicePixelRatio = 0.25;
|
||||
constexpr float kMaxDevicePixelRatio = 2;
|
||||
|
||||
constexpr int kImageColorChannels = 4;
|
||||
|
||||
// TODO(crbug.com/702993): Reevaluate the thumbnail size cap when the PDF
|
||||
// component migrates off of PPAPI.
|
||||
// The maximum thumbnail area is essentially arbitrary, but the value was chosen
|
||||
// considering the fact that when sending array buffers through PPAPI, if the
|
||||
// size of the data is over 256KiB, it gets sent using shared memory instead of
|
||||
// IPC. Thumbnail sizes are capped at 255KiB to avoid the 256KiB threshold for
|
||||
// images and their metadata, such as size.
|
||||
constexpr int kMaxThumbnailPixels = 255 * 1024 / kImageColorChannels;
|
||||
|
||||
// Maximum CSS dimensions are set to match UX specifications.
|
||||
constexpr int kMaxWidthPortraitPx = 108;
|
||||
constexpr int kMaxWidthLandscapePx = 140;
|
||||
|
||||
// PDF page size limits in default user space units, as defined by PDF 1.7 Annex
|
||||
// C.2, "Architectural limits".
|
||||
constexpr int kPdfPageMinDimension = 3;
|
||||
constexpr int kPdfPageMaxDimension = 14400;
|
||||
constexpr int kPdfMaxAspectRatio = kPdfPageMaxDimension / kPdfPageMinDimension;
|
||||
|
||||
// Limit the proportions within PDF limits to handle pathological PDF pages.
|
||||
gfx::Size LimitAspectRatio(gfx::Size page_size) {
|
||||
// Bump up any lengths of 0 to 1.
|
||||
page_size.SetToMax(gfx::Size(1, 1));
|
||||
|
||||
if (page_size.height() / page_size.width() > kPdfMaxAspectRatio)
|
||||
return gfx::Size(kPdfPageMinDimension, kPdfPageMaxDimension);
|
||||
if (page_size.width() / page_size.height() > kPdfMaxAspectRatio)
|
||||
return gfx::Size(kPdfPageMaxDimension, kPdfPageMinDimension);
|
||||
|
||||
return page_size;
|
||||
}
|
||||
|
||||
// Calculate the size of a thumbnail image in device pixels using |page_size| in
|
||||
// any units and |device_pixel_ratio|.
|
||||
gfx::Size CalculateBestFitSize(const gfx::Size& page_size,
|
||||
float device_pixel_ratio) {
|
||||
gfx::Size safe_page_size = LimitAspectRatio(page_size);
|
||||
|
||||
// Return the larger of the unrotated and rotated sizes to over-sample the PDF
|
||||
// page so that the thumbnail looks good in different orientations.
|
||||
float scale_portrait =
|
||||
static_cast<float>(kMaxWidthPortraitPx) /
|
||||
std::min(safe_page_size.width(), safe_page_size.height());
|
||||
float scale_landscape =
|
||||
static_cast<float>(kMaxWidthLandscapePx) /
|
||||
std::max(safe_page_size.width(), safe_page_size.height());
|
||||
float scale = std::max(scale_portrait, scale_landscape) * device_pixel_ratio;
|
||||
|
||||
// Using gfx::ScaleToFlooredSize() is fine because `scale` will not yield an
|
||||
// empty size unless `device_pixel_ratio` is very small (close to 0).
|
||||
// However, `device_pixel_ratio` support is limited to between 0.25 and 2.
|
||||
gfx::Size scaled_size = gfx::ScaleToFlooredSize(safe_page_size, scale);
|
||||
if (scaled_size.GetCheckedArea().ValueOrDefault(kMaxThumbnailPixels + 1) >
|
||||
kMaxThumbnailPixels) {
|
||||
// Recalculate `scale` to accommodate pixel size limit such that:
|
||||
// (scale * safe_page_size.width()) * (scale * safe_page_size.height()) ==
|
||||
// kMaxThumbnailPixels;
|
||||
scale = std::sqrt(static_cast<float>(kMaxThumbnailPixels) /
|
||||
safe_page_size.width() / safe_page_size.height());
|
||||
return gfx::ScaleToFlooredSize(safe_page_size, scale);
|
||||
}
|
||||
|
||||
return scaled_size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Thumbnail::Thumbnail() = default;
|
||||
@ -28,8 +99,8 @@ Thumbnail::Thumbnail(const gfx::Size& page_size, float device_pixel_ratio) {
|
||||
device_pixel_ratio_ = base::ClampToRange(
|
||||
device_pixel_ratio, kMinDevicePixelRatio, kMaxDevicePixelRatio);
|
||||
|
||||
// TODO(dhoss): Add conversion from page size to thumbnail size.
|
||||
const gfx::Size thumbnail_size_device_pixels = page_size;
|
||||
const gfx::Size thumbnail_size_device_pixels =
|
||||
CalculateBestFitSize(page_size, device_pixel_ratio_);
|
||||
|
||||
// Note that <canvas> can only hold data in RGBA format. It is the
|
||||
// responsibility of the thumbnail's renderer to fill `bitmap_` with RGBA
|
||||
|
83
pdf/thumbnail_unittest.cc
Normal file
83
pdf/thumbnail_unittest.cc
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "pdf/thumbnail.h"
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "ui/gfx/geometry/size.h"
|
||||
|
||||
namespace chrome_pdf {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kDeviceToPixelLow = 1;
|
||||
constexpr float kDeviceToPixelHigh = 2;
|
||||
|
||||
struct BestFitSizeParams {
|
||||
float device_pixel_ratio;
|
||||
gfx::Size page_size;
|
||||
gfx::Size expected_thumbnail_size;
|
||||
};
|
||||
|
||||
void TestBestFitSize(const BestFitSizeParams& params) {
|
||||
Thumbnail thumbnail(params.page_size, params.device_pixel_ratio);
|
||||
EXPECT_EQ(gfx::Size(thumbnail.bitmap().width(), thumbnail.bitmap().height()),
|
||||
params.expected_thumbnail_size)
|
||||
<< "Failed for page of size of " << params.page_size.ToString()
|
||||
<< " and device to pixel ratio of " << params.device_pixel_ratio;
|
||||
}
|
||||
|
||||
TEST(ThumbnailTest, CalculateBestFitSizeNormal) {
|
||||
static constexpr BestFitSizeParams kBestFitSizeTestParams[] = {
|
||||
{kDeviceToPixelLow, {612, 792}, {108, 140}}, // ANSI Letter
|
||||
{kDeviceToPixelLow, {595, 842}, {108, 152}}, // ISO 216 A4
|
||||
{kDeviceToPixelLow, {200, 200}, {140, 140}}, // Square
|
||||
{kDeviceToPixelLow, {1000, 200}, {540, 108}}, // Wide
|
||||
{kDeviceToPixelLow, {200, 1000}, {108, 540}}, // Tall
|
||||
{kDeviceToPixelLow, {1500, 50}, {1399, 46}}, // Super wide
|
||||
{kDeviceToPixelLow, {50, 1500}, {46, 1399}}, // Super tall
|
||||
{kDeviceToPixelHigh, {612, 792}, {216, 280}}, // ANSI Letter
|
||||
{kDeviceToPixelHigh, {595, 842}, {214, 303}}, // ISO 216 A4
|
||||
{kDeviceToPixelHigh, {200, 200}, {255, 255}}, // Square
|
||||
{kDeviceToPixelHigh, {1000, 200}, {571, 114}}, // Wide
|
||||
{kDeviceToPixelHigh, {200, 1000}, {114, 571}}, // Tall
|
||||
{kDeviceToPixelHigh, {1500, 50}, {1399, 46}}, // Super wide
|
||||
{kDeviceToPixelHigh, {50, 1500}, {46, 1399}}, // Super tall
|
||||
};
|
||||
|
||||
for (const auto& params : kBestFitSizeTestParams)
|
||||
TestBestFitSize(params);
|
||||
}
|
||||
|
||||
TEST(ThumbnailTest, CalculateBestFitSizeLargeAspectRatio) {
|
||||
static constexpr BestFitSizeParams kBestFitSizeTestParams[] = {
|
||||
{kDeviceToPixelLow, {14400, 3}, {17701, 3}}, // PDF 1.7 widest
|
||||
{kDeviceToPixelLow, {3, 14400}, {3, 17701}}, // PDF 1.7 tallest
|
||||
{kDeviceToPixelLow, {0, 0}, {140, 140}}, // Empty
|
||||
{kDeviceToPixelLow, {9999999, 1}, {17701, 3}}, // Very wide
|
||||
{kDeviceToPixelLow, {1, 9999999}, {3, 17701}}, // Very tall
|
||||
{kDeviceToPixelHigh, {14400, 3}, {17701, 3}}, // PDF 1.7 widest
|
||||
{kDeviceToPixelHigh, {3, 14400}, {3, 17701}}, // PDF 1.7 tallest
|
||||
{kDeviceToPixelHigh, {0, 0}, {255, 255}}, // Empty
|
||||
{kDeviceToPixelHigh, {9999999, 1}, {17701, 3}}, // Very wide
|
||||
{kDeviceToPixelHigh, {1, 9999999}, {3, 17701}}, // Very tall
|
||||
};
|
||||
|
||||
for (const auto& params : kBestFitSizeTestParams)
|
||||
TestBestFitSize(params);
|
||||
}
|
||||
|
||||
TEST(ThumbnailTest, CalculateBestFitSizeNoOverflow) {
|
||||
static constexpr BestFitSizeParams kBestFitSizeTestParams[] = {
|
||||
{kDeviceToPixelLow, {9999999, 9999999}, {140, 140}}, // Very large
|
||||
{kDeviceToPixelHigh, {9999999, 9999999}, {255, 255}}, // Very large
|
||||
};
|
||||
|
||||
for (const auto& params : kBestFitSizeTestParams)
|
||||
TestBestFitSize(params);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace chrome_pdf
|
Reference in New Issue
Block a user