blob: ae7fd91df6d00967498df803819b6d8a7b9bc2f6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/common/serial_utils.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "build/build_config.h"
#include "components/paint_preview/common/file_stream.h"
#include "skia/ext/font_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/codec/SkBmpDecoder.h"
#include "third_party/skia/include/codec/SkGifDecoder.h"
#include "third_party/skia/include/codec/SkJpegDecoder.h"
#include "third_party/skia/include/codec/SkPngDecoder.h"
#include "third_party/skia/include/codec/SkWebpDecoder.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/SkData.h"
#include "third_party/skia/include/core/SkFontStyle.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace paint_preview {
TEST(PaintPreviewSerialUtils, TestMakeEmptyPicture) {
sk_sp<SkPicture> pic = MakeEmptyPicture();
ASSERT_NE(pic, nullptr);
SkSerialProcs default_procs;
auto data = pic->serialize(&default_procs);
ASSERT_NE(data, nullptr);
EXPECT_GE(data->size(), 0U);
}
TEST(PaintPreviewSerialUtils, TestTransformedPictureProcs) {
auto pic = MakeEmptyPicture();
uint32_t content_id = pic->uniqueID();
const base::UnguessableToken kFrameGuid = base::UnguessableToken::Create();
PictureSerializationContext picture_ctx;
EXPECT_TRUE(picture_ctx.content_id_to_embedding_token
.insert(std::make_pair(content_id, kFrameGuid))
.second);
auto new_clip = SkRect::MakeXYWH(10, 20, 30, 40);
EXPECT_TRUE(picture_ctx.content_id_to_transformed_clip
.insert(std::make_pair(content_id, new_clip))
.second);
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
DeserializationContext deserial_ctx;
SkDeserialProcs deserial_procs = MakeDeserialProcs(&deserial_ctx);
EXPECT_EQ(deserial_procs.fPictureCtx, &deserial_ctx);
// Check that serializing then deserialize the picture works produces a
// correct clip rect.
sk_sp<SkData> serial_pic_data =
serial_procs.fPictureProc(pic.get(), serial_procs.fPictureCtx);
sk_sp<SkPicture> deserial_pic = deserial_procs.fPictureProc(
serial_pic_data->data(), serial_pic_data->size(),
deserial_procs.fPictureCtx);
EXPECT_TRUE(deserial_ctx.count(content_id));
EXPECT_EQ(deserial_ctx[content_id].x(), new_clip.x());
EXPECT_EQ(deserial_ctx[content_id].y(), new_clip.y());
EXPECT_EQ(deserial_ctx[content_id].width(), new_clip.width());
EXPECT_EQ(deserial_ctx[content_id].height(), new_clip.height());
}
TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) {
PictureSerializationContext picture_ctx;
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
auto pic = MakeEmptyPicture();
EXPECT_EQ(serial_procs.fPictureProc(pic.get(), serial_procs.fPictureCtx),
nullptr);
}
// Skip this on Android as we only have system fonts in this test and Android
// doesn't serialize those.
#if !BUILDFLAG(IS_ANDROID)
TEST(PaintPreviewSerialUtils, TestSerialTypeface) {
PictureSerializationContext picture_ctx;
auto typeface = skia::DefaultTypeface();
TypefaceUsageMap usage_map;
std::unique_ptr<GlyphUsage> usage =
std::make_unique<SparseGlyphUsage>(typeface->countGlyphs());
usage->Set(0);
usage->Set('a');
usage->Set('b');
EXPECT_TRUE(
usage_map.insert(std::make_pair(typeface->uniqueID(), std::move(usage)))
.second);
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
auto final_data =
serial_procs.fTypefaceProc(typeface.get(), serial_procs.fTypefaceCtx);
ASSERT_TRUE(final_data);
EXPECT_GT(typeface_ctx.finished.count(typeface->uniqueID()), 0U);
auto original_data = typeface->serialize();
ASSERT_NE(original_data->size(), final_data->size());
}
#endif
#if BUILDFLAG(IS_ANDROID)
TEST(PaintPreviewSerialUtils, TestSerialAndroidSystemTypeface) {
PictureSerializationContext picture_ctx;
// This is a system font serialization of the data will be skipped.
auto typeface = skia::MakeTypefaceFromName("sans-serif", SkFontStyle::Bold());
TypefaceUsageMap usage_map;
std::unique_ptr<GlyphUsage> usage =
std::make_unique<SparseGlyphUsage>(typeface->countGlyphs());
usage->Set(0);
usage->Set('a');
usage->Set('b');
EXPECT_TRUE(
usage_map.insert(std::make_pair(typeface->uniqueID(), std::move(usage)))
.second);
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
auto final_data =
serial_procs.fTypefaceProc(typeface.get(), serial_procs.fTypefaceCtx);
ASSERT_TRUE(final_data);
EXPECT_GT(typeface_ctx.finished.count(typeface->uniqueID()), 0U);
auto original_data = typeface->serialize();
ASSERT_EQ(original_data->size(), final_data->size());
ASSERT_EQ(
0, memcmp(original_data->data(), final_data->data(), final_data->size()));
}
#endif
TEST(PaintPreviewSerialUtils, TestSerialNoTypefaceInMap) {
PictureSerializationContext picture_ctx;
auto typeface = skia::DefaultTypeface();
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
EXPECT_NE(
serial_procs.fTypefaceProc(typeface.get(), serial_procs.fTypefaceCtx),
nullptr);
EXPECT_GT(typeface_ctx.finished.count(typeface->uniqueID()), 0U);
// Still serialize font metadata for lookup if the font is serialized again.
EXPECT_NE(
serial_procs.fTypefaceProc(typeface.get(), serial_procs.fTypefaceCtx),
nullptr);
}
TEST(PaintPreviewSerialUtils, TestImageContextLimitBudget) {
SkBitmap bitmap1;
bitmap1.allocN32Pixels(1, 19);
SkCanvas canvas1(bitmap1);
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(SK_ColorRED);
canvas1.drawRect(SkRect::MakeWH(1, 4), paint);
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(40, 40));
canvas->drawImage(bitmap1.asImage(), 0, 0);
canvas->drawImage(bitmap1.asImage(), 0, 0);
canvas->drawImage(bitmap1.asImage(), 0, 0);
auto pic = recorder.finishRecordingAsPicture();
PictureSerializationContext picture_ctx;
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
ictx.remaining_image_size = 200;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
sk_sp<SkData> data = pic->serialize(&serial_procs);
EXPECT_NE(data, nullptr);
EXPECT_TRUE(ictx.memory_budget_exceeded);
SkDeserialProcs deserial_procs;
size_t deserialized_images = 0;
deserial_procs.fImageCtx = &deserialized_images;
deserial_procs.fImageProc = [](const void* data, size_t length,
void* ctx) -> sk_sp<SkImage> {
if (length > 0U) {
size_t* images = reinterpret_cast<size_t*>(ctx);
*images += 1;
}
return nullptr;
};
SkPicture::MakeFromData(data.get(), &deserial_procs);
EXPECT_EQ(deserialized_images, 2U);
}
TEST(PaintPreviewSerialUtils, TestImageContextLimitSize) {
SkBitmap bitmap1;
bitmap1.allocN32Pixels(1, 19);
SkCanvas canvas1(bitmap1);
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(SK_ColorRED);
canvas1.drawRect(SkRect::MakeWH(1, 4), paint);
SkBitmap bitmap2;
bitmap2.allocN32Pixels(20, 20);
SkCanvas canvas2(bitmap2);
canvas2.drawRect(SkRect::MakeWH(20, 5), paint);
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(40, 40));
canvas->drawImage(bitmap1.asImage(), 0, 0);
canvas->drawImage(bitmap2.asImage(), 0, 0);
auto pic = recorder.finishRecordingAsPicture();
PictureSerializationContext picture_ctx;
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
ictx.max_decoded_image_size_bytes = 200;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
sk_sp<SkData> data = pic->serialize(&serial_procs);
EXPECT_NE(data, nullptr);
EXPECT_FALSE(ictx.memory_budget_exceeded);
SkDeserialProcs deserial_procs;
size_t deserialized_images = 0;
deserial_procs.fImageCtx = &deserialized_images;
deserial_procs.fImageProc = [](const void* data, size_t length,
void* ctx) -> sk_sp<SkImage> {
if (length > 0U) {
size_t* images = reinterpret_cast<size_t*>(ctx);
*images += 1;
}
return nullptr;
};
SkPicture::MakeFromData(data.get(), &deserial_procs);
EXPECT_EQ(deserialized_images, 1U);
}
namespace {
struct DeserialImageContext {
size_t image_count = 0;
SkDeserialImageProc deserial_image_proc = nullptr;
};
static void TrySerialAndDeserial(sk_sp<SkData> image_data) {
sk_sp<SkImage> image = SkImages::DeferredFromEncodedData(image_data);
ASSERT_TRUE(image);
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(100, 100));
canvas->drawImage(image, 0, 0);
sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
// Serialize using production `SkSerialProcs` from `MakeSerialProcs` where
// `fImageProc` is `SerializeImage`.
PictureSerializationContext picture_ctx;
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
ImageSerializationContext ictx;
SkSerialProcs serial_procs =
MakeSerialProcs(&picture_ctx, &typeface_ctx, &ictx);
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fImageCtx, &ictx);
sk_sp<SkData> data = pic->serialize(&serial_procs);
EXPECT_NE(data, nullptr);
EXPECT_FALSE(ictx.memory_budget_exceeded);
// Deserialize using production `SkDeserialProcs` from `MakeDeserialProcs`.
DeserializationContext deserial_ctx;
SkDeserialProcs deserial_procs = MakeDeserialProcs(&deserial_ctx);
EXPECT_EQ(deserial_procs.fPictureCtx, &deserial_ctx);
EXPECT_NE(deserial_procs.fImageProc, nullptr);
// Spy on the operation by taking `fImageProc` (`DeserializeImage`) from the
// production procs and wrapping it as part of the `DeserialImageContext`.
// This allows end-to-end validation of its behavior.
DeserialImageContext deserial_image_ctx;
deserial_image_ctx.deserial_image_proc = deserial_procs.fImageProc;
deserial_procs.fImageCtx = &deserial_image_ctx;
deserial_procs.fImageProc = [](const void* data, size_t length,
void* ctx) -> sk_sp<SkImage> {
if (length == 0U) {
return nullptr;
}
DeserialImageContext* deserial_image_ctx =
reinterpret_cast<DeserialImageContext*>(ctx);
deserial_image_ctx->image_count += 1;
sk_sp<SkImage> image =
(*(deserial_image_ctx->deserial_image_proc))(data, length, nullptr);
EXPECT_NE(image, nullptr) << "Invalid decoded image.";
return image;
};
SkPicture::MakeFromData(data.get(), &deserial_procs);
EXPECT_EQ(deserial_image_ctx.image_count, 1U);
}
} // namespace
TEST(PaintPreviewSerialUtils, TestImageContextEncodeAndDecodePng) {
base::FilePath path;
CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
FileRStream stream(base::File(
path.AppendASCII("components/test/data/paint_preview/test.png"),
base::File::FLAG_OPEN | base::File::FLAG_READ));
SkCodecs::Register(SkPngDecoder::Decoder());
TrySerialAndDeserial(SkData::MakeFromStream(&stream, stream.length()));
}
TEST(PaintPreviewSerialUtils, TestImageContextEncodeAndDecodeJpeg) {
base::FilePath path;
ASSERT_TRUE(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
FileRStream stream(base::File(
path.AppendASCII("components/test/data/paint_preview/test.jpg"),
base::File::FLAG_OPEN | base::File::FLAG_READ));
SkCodecs::Register(SkJpegDecoder::Decoder());
TrySerialAndDeserial(SkData::MakeFromStream(&stream, stream.length()));
}
TEST(PaintPreviewSerialUtils, TestImageContextEncodeAndDecodeWebp) {
base::FilePath path;
ASSERT_TRUE(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
FileRStream stream(base::File(
path.AppendASCII("components/test/data/paint_preview/test.webp"),
base::File::FLAG_OPEN | base::File::FLAG_READ));
SkCodecs::Register(SkWebpDecoder::Decoder());
TrySerialAndDeserial(SkData::MakeFromStream(&stream, stream.length()));
}
TEST(PaintPreviewSerialUtils, TestImageContextEncodeAndDecodeGif) {
base::FilePath path;
ASSERT_TRUE(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
FileRStream stream(base::File(
path.AppendASCII("components/test/data/paint_preview/test.gif"),
base::File::FLAG_OPEN | base::File::FLAG_READ));
SkCodecs::Register(SkGifDecoder::Decoder());
TrySerialAndDeserial(SkData::MakeFromStream(&stream, stream.length()));
}
TEST(PaintPreviewSerialUtils, TestImageContextEncodeAndDecodeBmp) {
base::FilePath path;
ASSERT_TRUE(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
FileRStream stream(base::File(
path.AppendASCII("components/test/data/paint_preview/test.bmp"),
base::File::FLAG_OPEN | base::File::FLAG_READ));
SkCodecs::Register(SkBmpDecoder::Decoder());
TrySerialAndDeserial(SkData::MakeFromStream(&stream, stream.length()));
}
} // namespace paint_preview