blob: 4ff895f99f0ff2cadb371358b01437e873a6a038 [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/trace_event/common/trace_event_common.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/paint_preview/common/subset_font.h"
#include "skia/ext/font_utils.h"
#include "third_party/skia/include/codec/SkBmpDecoder.h"
#include "third_party/skia/include/codec/SkCodec.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/SkCanvas.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkString.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "third_party/skia/include/encode/SkPngEncoder.h"
#include "third_party/skia/include/private/chromium/Slug.h"
namespace paint_preview {
namespace {
// Supported by MSVC, g++, and clang++. Ensures no gaps in packing.
#pragma pack(push, 1)
struct SerializedRectData {
uint32_t content_id;
// The size of the subframe in the local coordinates when it was drawn.
float subframe_width;
float subframe_height;
// The rect of the subframe in its parent frame's root coordinate system.
float transformed_x;
float transformed_y;
float transformed_width;
float transformed_height;
};
#pragma pack(pop)
// Serializes a SkPicture representing a subframe as a custom data placeholder.
sk_sp<SkData> SerializePictureAsRectData(SkPicture* picture, void* ctx) {
const PictureSerializationContext* context =
reinterpret_cast<PictureSerializationContext*>(ctx);
auto it = context->content_id_to_transformed_clip.find(picture->uniqueID());
// Defers picture serialization behavior to Skia.
if (it == context->content_id_to_transformed_clip.end()) {
return nullptr;
}
// This data originates from |PaintPreviewTracker|.
const SkRect& transformed_cull_rect = it->second;
SerializedRectData rect_data = {
picture->uniqueID(), picture->cullRect().width(),
picture->cullRect().height(), transformed_cull_rect.x(),
transformed_cull_rect.y(), transformed_cull_rect.width(),
transformed_cull_rect.height()};
return SkData::MakeWithCopy(&rect_data, sizeof(rect_data));
}
// De-duplicates and subsets used typefaces and discards any unused typefaces.
// If subsetting fails (or on Android) this returns data only for non-system
// fonts. This means the resulting SkPicture is not portable across devices.
sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) {
TRACE_EVENT0("paint_preview", "SerializeTypeface");
TypefaceSerializationContext* context =
reinterpret_cast<TypefaceSerializationContext*>(ctx);
if (context->finished.count(typeface->uniqueID())) {
return typeface->serialize(SkTypeface::SerializeBehavior::kDontIncludeData);
}
context->finished.insert(typeface->uniqueID());
auto usage_it = context->usage->find(typeface->uniqueID());
if (usage_it == context->usage->end()) {
return typeface->serialize(SkTypeface::SerializeBehavior::kDontIncludeData);
}
#if BUILDFLAG(IS_ANDROID)
{
SkString familyName;
typeface->getFamilyName(&familyName);
// On Android MakeFromName will return nullptr rather than falling back to
// an alternative font if a system font doesn't match. As such, we can use
// this to check if the SkTypeface is for a system font. If it is a system
// font we don't need to subset/serialize it.
if (skia::MakeTypefaceFromName(familyName.c_str(), typeface->fontStyle())) {
return typeface->serialize(
SkTypeface::SerializeBehavior::kIncludeDataIfLocal);
}
}
#endif
auto subset_data = SubsetFont(typeface, *usage_it->second);
if (!subset_data) {
return typeface->serialize(
SkTypeface::SerializeBehavior::kIncludeDataIfLocal);
}
return subset_data;
}
static sk_sp<SkTypeface> DeserializeTypeface(const void* data,
size_t length,
void* ctx) {
// TODO(bungeman,kjlubick) This should not be how the Skia deserial proc
// works.
SkStream* stream = *(reinterpret_cast<SkStream**>(const_cast<void*>(data)));
if (length < sizeof(stream)) {
return nullptr;
}
// The default implementation of SkPicture deserialization of SkTypeface
// does not use a fallback (system) font manager, but this is necessary
// on Android due to the above behavior w/r to system fonts. Thus, we
// call the underlying SkTypeface::MakeDeserialize and pass in the
// system font manager ourselves.
return SkTypeface::MakeDeserialize(stream, skia::DefaultFontMgr());
}
static bool is_supported_codec(sk_sp<SkData> data) {
CHECK(data);
return SkBmpDecoder::IsBmp(data->data(), data->size()) ||
SkGifDecoder::IsGif(data->data(), data->size()) ||
SkPngDecoder::IsPng(data->data(), data->size()) ||
SkJpegDecoder::IsJpeg(data->data(), data->size()) ||
SkWebpDecoder::IsWebp(data->data(), data->size());
}
sk_sp<SkData> SerializeImage(SkImage* image, void* ctx) {
ImageSerializationContext* context =
reinterpret_cast<ImageSerializationContext*>(ctx);
// Ignore texture backed content if any slipped through. This shouldn't occur
// now that ToSkPicture has a dedicated ImageProvider that forces software
// SkImage inputs, but this is a safeguard.
if (context->skip_texture_backed && image->isTextureBacked()) {
return SkData::MakeEmpty();
}
// If the decoded form of the image would result in it exceeding the allowable
// size then effectively delete it by providing no data.
const SkImageInfo& image_info = image->imageInfo();
if (context->max_decoded_image_size_bytes !=
std::numeric_limits<uint64_t>::max() &&
image_info.computeMinByteSize() > context->max_decoded_image_size_bytes) {
return SkData::MakeEmpty();
}
// If there already exists encoded data use it directly.
sk_sp<SkData> encoded_data = image->refEncodedData();
if (!encoded_data || !is_supported_codec(encoded_data)) {
// Use the default PNG at quality 100 as it is safe.
// TODO(crbug.com/40177283): Investigate supporting JPEG at quality 100 for
// opaque images.
encoded_data = SkPngEncoder::Encode(nullptr, image, {});
}
if (!encoded_data) {
return SkData::MakeEmpty();
}
// Ensure the encoded data fits in the size restriction if present.
// OOM Prevention: This avoids creating/keeping large serialized images
// in-memory during serialization if the size budget is already exceeded due
// to images.
if (context->remaining_image_size != std::numeric_limits<uint64_t>::max()) {
if (context->remaining_image_size < encoded_data->size()) {
context->memory_budget_exceeded = true;
return SkData::MakeEmpty();
}
context->remaining_image_size -= encoded_data->size();
}
return encoded_data;
}
sk_sp<SkImage> DeserializeImage(const void* bytes, size_t length, void*) {
// Although we usually serialize images to the PNG format, if an image was
// already encoded as a JPEG or WEBP, those bytes are written to the
// SKP as-is, so we should try to decode those as well.
sk_sp<SkData> data = SkData::MakeWithoutCopy(bytes, length);
const auto get_image = [](std::unique_ptr<SkCodec> codec) -> sk_sp<SkImage> {
if (!codec) {
return nullptr;
}
// prefer premul over unpremul (this produces better filtering in general)
SkImageInfo targetInfo =
codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
return std::get<0>(codec->getImage(targetInfo));
};
if (SkPngDecoder::IsPng(bytes, length)) {
return get_image(SkPngDecoder::Decode(data, nullptr));
}
if (SkBmpDecoder::IsBmp(bytes, length)) {
return get_image(SkBmpDecoder::Decode(data, nullptr));
}
if (SkGifDecoder::IsGif(bytes, length)) {
return get_image(SkGifDecoder::Decode(data, nullptr));
}
if (SkJpegDecoder::IsJpeg(bytes, length)) {
return get_image(SkJpegDecoder::Decode(data, nullptr));
}
return get_image(SkWebpDecoder::Decode(data, nullptr));
}
// Deserializes a clip rect for a subframe within the main SkPicture. These
// represent subframes and require special decoding as they are custom data
// rather than a valid SkPicture.
// Precondition: the version of the SkPicture should be checked prior to
// invocation to ensure deserialization will succeed.
sk_sp<SkPicture> DeserializePictureAsRectData(const void* data,
size_t length,
void* ctx) {
SerializedRectData rect_data;
if (length < sizeof(rect_data)) {
return MakeEmptyPicture();
}
memcpy(&rect_data, data, sizeof(rect_data));
auto* context = reinterpret_cast<DeserializationContext*>(ctx);
context->insert(
{rect_data.content_id,
gfx::RectF(rect_data.transformed_x, rect_data.transformed_y,
rect_data.transformed_width, rect_data.transformed_height)});
return MakeEmptyPicture();
}
// Similar to |DeserializePictureAsRectData|, but instead of writing out the
// serialized rect data to |ctx|, |ctx| is instead a
// |LoadedFramesDeserialContext*| that is looked up to return the picture
// itself. This assumes that the picture was already previously deserialized
// and recorded into |ctx|. Returns an empty picture if |ctx| does not contain
// the content ID embedded in |data|.
sk_sp<SkPicture> GetPictureFromDeserialContext(const void* data,
size_t length,
void* ctx) {
SerializedRectData rect_data;
if (length < sizeof(rect_data)) {
return MakeEmptyPicture();
}
memcpy(&rect_data, data, sizeof(rect_data));
auto* context = reinterpret_cast<LoadedFramesDeserialContext*>(ctx);
auto it = context->find(rect_data.content_id);
if (it == context->end()) {
return MakeEmptyPicture();
}
// Scroll and clip the subframe manually since the picture in |ctx| does not
// encode this information.
SkRect subframe_bounds =
SkRect::MakeWH(rect_data.subframe_width, rect_data.subframe_height);
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(subframe_bounds);
canvas->clipRect(subframe_bounds);
SkMatrix apply_scroll_offsets = SkMatrix::Translate(
-it->second.scroll_offsets.width(), -it->second.scroll_offsets.height());
canvas->drawPicture(it->second.picture, &apply_scroll_offsets, nullptr);
return recorder.finishRecordingAsPicture();
}
} // namespace
PictureSerializationContext::PictureSerializationContext() = default;
PictureSerializationContext::~PictureSerializationContext() = default;
PictureSerializationContext::PictureSerializationContext(
PictureSerializationContext&&) noexcept = default;
PictureSerializationContext& PictureSerializationContext::operator=(
PictureSerializationContext&&) noexcept = default;
TypefaceSerializationContext::TypefaceSerializationContext(
TypefaceUsageMap* usage)
: usage(usage) {}
TypefaceSerializationContext::~TypefaceSerializationContext() = default;
FrameAndScrollOffsets::FrameAndScrollOffsets() = default;
FrameAndScrollOffsets::~FrameAndScrollOffsets() = default;
FrameAndScrollOffsets::FrameAndScrollOffsets(const FrameAndScrollOffsets&) =
default;
FrameAndScrollOffsets& FrameAndScrollOffsets::operator=(
const FrameAndScrollOffsets&) = default;
sk_sp<SkPicture> MakeEmptyPicture() {
// Effectively a no-op.
SkPictureRecorder rec;
rec.beginRecording(1, 1);
return rec.finishRecordingAsPicture();
}
SkSerialProcs MakeSerialProcs(PictureSerializationContext* picture_ctx,
TypefaceSerializationContext* typeface_ctx,
ImageSerializationContext* image_ctx) {
SkSerialProcs procs;
procs.fPictureProc = SerializePictureAsRectData;
procs.fPictureCtx = picture_ctx;
procs.fTypefaceProc = SerializeTypeface;
procs.fTypefaceCtx = typeface_ctx;
image_ctx->memory_budget_exceeded = false;
procs.fImageProc = SerializeImage;
procs.fImageCtx = image_ctx;
return procs;
}
SkDeserialProcs MakeDeserialProcs(DeserializationContext* ctx) {
SkDeserialProcs procs;
procs.fPictureProc = DeserializePictureAsRectData;
procs.fPictureCtx = ctx;
procs.fImageProc = DeserializeImage;
procs.fTypefaceProc = DeserializeTypeface;
sktext::gpu::Slug::AddDeserialProcs(&procs, nullptr);
return procs;
}
SkDeserialProcs MakeDeserialProcs(LoadedFramesDeserialContext* ctx) {
SkDeserialProcs procs;
procs.fPictureProc = GetPictureFromDeserialContext;
procs.fPictureCtx = ctx;
procs.fImageProc = DeserializeImage;
procs.fTypefaceProc = DeserializeTypeface;
return procs;
}
} // namespace paint_preview