Commit 868f1e24 authored by Jonathan Metzman's avatar Jonathan Metzman Committed by Commit Bot

[libFuzzer][LPM] Write experimental proto fuzzer for skia filters.

TBR=vitalybuka@chromium.org

Bug: 539572
Change-Id: I1e7dbad47b8f5b4debfd4ab071ce946d07c0d93f
Reviewed-on: https://chromium-review.googlesource.com/885085Reviewed-by: default avatarJonathan Metzman <metzman@chromium.org>
Reviewed-by: default avatarMartin Barbella <mbarbella@chromium.org>
Commit-Queue: Jonathan Metzman <metzman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533308}
parent e152ac2a
......@@ -524,3 +524,22 @@ fuzzer_test("v8_fully_instrumented_fuzzer") {
dict = "dicts/generated/javascript.dict"
libfuzzer_options = [ "only_ascii=1" ]
}
if (!is_win) {
fuzzer_test("skia_image_filter_proto_fuzzer") {
sources = [
"../proto/skia_image_filter_proto_converter.cc",
"../proto/skia_image_filter_proto_converter.h",
"skia_image_filter_proto_fuzzer.cc",
]
deps = [
"//base",
"//base/test:test_support",
"//skia",
"//testing/libfuzzer/proto:skia_image_filter_converter",
"//testing/libfuzzer/proto:skia_image_filter_proto",
"//third_party/libprotobuf-mutator",
]
}
}
// Copyright 2018 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.
// Takes an Input protobuf message from libprotobuf-mutator, converts it to an
// actual skia image filter and then applies it to a canvas for the purpose of
// fuzzing skia. This should uncover bugs that could be used by a compromised
// renderer to exploit the browser process.
#include <stdlib.h>
#include <iostream>
#include <string>
#include "testing/libfuzzer/proto/skia_image_filter_proto_converter.h"
#include "base/process/memory.h"
#include "base/test/test_discardable_memory_allocator.h"
#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageFilter.h"
protobuf_mutator::protobuf::LogSilencer log_silencer;
using skia_image_filter_proto_converter::Input;
using skia_image_filter_proto_converter::Converter;
static const int kBitmapSize = 24;
struct Environment {
base::TestDiscardableMemoryAllocator* discardable_memory_allocator;
Environment() {
base::EnableTerminationOnOutOfMemory();
discardable_memory_allocator = new base::TestDiscardableMemoryAllocator();
base::DiscardableMemoryAllocator::SetInstance(discardable_memory_allocator);
}
};
DEFINE_PROTO_FUZZER(const Input& input) {
static Environment environment = Environment();
ALLOW_UNUSED_LOCAL(environment);
static Converter converter = Converter();
std::string ipc_filter_message = converter.Convert(input);
// Allow the flattened skia filter to be retrieved easily.
if (getenv("LPM_DUMP_NATIVE_INPUT")) {
// Don't write a newline since it will make the output invalid (so that it
// cannot be fed to filter_fuzz_stub) Flush instead.
std::cout << ipc_filter_message << std::flush;
}
sk_sp<SkImageFilter> flattenable = SkImageFilter::Deserialize(
ipc_filter_message.c_str(), ipc_filter_message.size());
if (!flattenable)
return;
SkBitmap bitmap;
bitmap.allocN32Pixels(kBitmapSize, kBitmapSize);
SkCanvas canvas(bitmap);
canvas.clear(0x00000000);
SkPaint paint;
paint.setImageFilter(flattenable);
canvas.save();
canvas.clipRect(SkRect::MakeXYWH(0, 0, SkIntToScalar(kBitmapSize),
SkIntToScalar(kBitmapSize)));
canvas.drawBitmap(bitmap, 0, 0, &paint);
canvas.restore();
}
......@@ -35,3 +35,33 @@ source_set("json_proto_converter") {
":json_proto",
]
}
if (!is_win) {
static_library("skia_image_filter_converter") {
sources = [
"skia_image_filter_proto_converter.cc",
"skia_image_filter_proto_converter.h",
]
deps = [
":skia_image_filter_proto",
"//base",
"//skia",
"//third_party/libprotobuf-mutator",
]
defines = [ "AVOID_MISBEHAVIOR=1" ]
testonly = true
# Can't disable instrumentation because of container-overflow false
# positives.
# Assertion failures in skia are uninteresting. Don't use debug builds on
# CF.
}
proto_library("skia_image_filter_proto") {
sources = [
"skia_image_filter.proto",
]
}
}
// Copyright 2018 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.
// Contains the input used by filter_proto_converter to create a valid flattened
// skia image filter. These messages were made using CreateProc and flatten
// methods in mind so that a valid flattenable is produced. Many of the enums
// here are copied from skia. Most of the messages are straightforward, but
// there are a few things worth noting here. First is that many fields can be
// written to the flattenable output as-is (such as the fields x,y and z in
// Point3). And sometimes entire messages such as Point3 are written
// as-is. These Message and fields can be what we call "autovisited". For fields
// it means that they can be handled by the WriteFields method in
// filter_proto_converter.cc. For messages it means they can be handled by
// WriteFields and the generic Visit function (void Converter::Visit(const
// Message& msg)) and do not need their own specific code. In this file, we will
// put a comment "Autovisit:" before fields or messages that can be autovisited.
// A second thing to know is the parent and child pattern we use here. In many
// cases we need to specify one type of some kind of skia flattenable, like one
// ColorFilter. To do this we create a message $CONCEPT + "Child" (eg
// ColorFilterChild) that contains oneof these subtypes. In many cases these
// subtypes (or children) will share things in common with each other. To avoid
// re-implementing the same thing dozens of times, we give these children a
// field called parent, that when Visited handles this common functionality. One
// important exception to this general pattern is the LightParent message which
// contains its own children, rather than the usual practice (which is the other
// way around). That was done because the order in which the common
// functionality must be done is different than in other cases (here it must
// happen after the child functionality is handled, not before as in the other
// cases). Finally, the last thing to know is because protobuf doesn't have a
// good way to specify an array of a certain length so this functionality is
// implemented by defining a message containing required fields representing
// each element in the array (eg OverdrawColorFilter).
syntax = "proto2";
package skia_image_filter_proto_converter;
// Used for testcases.
message Input {
required ImageFilterChild image_filter = 1;
required uint64 rng_seed = 2;
}
// No content when flattened.
message LumaColorFilter {}
message OverdrawColorFilter {
// Autovisit:
required uint32 val1 = 1;
required uint32 val2 = 2;
required uint32 val3 = 3;
required uint32 val4 = 4;
required uint32 val5 = 5;
required uint32 val6 = 6;
}
message ColorFilterChild {
oneof children {
ModeColorFilter mode_color_filter = 1;
ColorMatrixFilterRowMajor255 color_matrix_filter_row_major_255 = 2;
ComposeColorFilter compose_color_filter = 3;
SRGBGammaColorFilter srgb_gamma_color_filter = 4;
HighContrast_Filter high_contrast__filter = 5;
LumaColorFilter luma_color_filter = 6;
OverdrawColorFilter overdraw_color_filter = 7;
Table_ColorFilter table__color_filter = 8;
ToSRGBColorFilter to_srgb_color_filter = 9;
}
}
message TransferFn {
required Named named = 1;
required float a = 2;
required float b = 3;
required float c = 4;
required float d = 5;
required float e = 6;
required float f = 7;
required float g = 8;
required ThreeByFour three_by_four = 9;
}
enum Named {
kSRGB_Named = 0;
kAdobeRGB_Named = 1;
kSRGBLinear_Named = 2;
kSRGB_NonLinearBlending_Named = 3;
}
message ColorSpace_XYZ {
enum GammaNamed {
kLinear_SkGammaNamed = 0;
kSRGB_SkGammaNamed = 1;
k2Dot2Curve_SkGammaNamed = 2;
}
required GammaNamed gamma_named = 1;
required ThreeByFour three_by_four = 2;
}
message ColorSpaceNamed {
enum ColorSpaceNamedEnum {
kAdobeRGB_Named = 1;
kSRGBLinear_Named = 2;
}
required ColorSpaceNamedEnum named = 1;
required GammaNamed gamma_named = 2;
}
message ColorSpaceChild {
oneof data {
ICC icc = 1;
TransferFn transfer_fn = 2;
ColorSpace_XYZ color_space__xyz = 3;
}
required ColorSpaceNamed named = 4;
}
message ToSRGBColorFilter {
required ColorSpaceChild color_space = 1;
}
message ColorTable {
required float val1 = 1;
required float val2 = 2;
required float val3 = 3;
required float val4 = 4;
required float val5 = 5;
required float val6 = 6;
required float val7 = 7;
required float val8 = 8;
required float val9 = 9;
required float val10 = 10;
required float val11 = 11;
required float val12 = 12;
required float val13 = 13;
required float val14 = 14;
required float val15 = 15;
required float val16 = 16;
required float val17 = 17;
required float val18 = 18;
required float val19 = 19;
required float val20 = 20;
required float val21 = 21;
required float val22 = 22;
required float val23 = 23;
required float val24 = 24;
required float val25 = 25;
required float val26 = 26;
required float val27 = 27;
required float val28 = 28;
required float val29 = 29;
required float val30 = 30;
required float val31 = 31;
required float val32 = 32;
required float val33 = 33;
required float val34 = 34;
required float val35 = 35;
required float val36 = 36;
required float val37 = 37;
required float val38 = 38;
required float val39 = 39;
required float val40 = 40;
required float val41 = 41;
required float val42 = 42;
required float val43 = 43;
required float val44 = 44;
required float val45 = 45;
required float val46 = 46;
required float val47 = 47;
required float val48 = 48;
required float val49 = 49;
required float val50 = 50;
required float val51 = 51;
required float val52 = 52;
required float val53 = 53;
required float val54 = 54;
required float val55 = 55;
required float val56 = 56;
required float val57 = 57;
required float val58 = 58;
required float val59 = 59;
required float val60 = 60;
required float val61 = 61;
required float val62 = 62;
required float val63 = 63;
required float val64 = 64;
}
message Table_ColorFilter {
optional ColorTable table_a = 1;
optional ColorTable table_r = 2;
optional ColorTable table_g = 3;
optional ColorTable table_b = 4;
}
// See SkHighContrastFilter.cpp
message HighContrast_Filter {
enum InvertStyle {
kNoInvert = 0;
kInvertBrightness = 1;
kInvertLightness = 2;
}
required bool grayscale = 1;
required InvertStyle invert_style = 2;
// Autovisit up to here
required float contrast = 3;
}
// Autovisit:
message SRGBGammaColorFilter {
enum Direction {
kLinearToSRGB = 0;
kSRGBToLinear = 1;
}
required Direction direction = 1;
}
message ComposeColorFilter {
required ColorFilterChild outer = 1;
required ColorFilterChild inner = 2;
}
message ColorFilterMatrix {
// Autovisit:
required float val1 = 1;
required float val2 = 2;
required float val3 = 3;
required float val4 = 4;
required float val5 = 5;
required float val6 = 6;
required float val7 = 7;
required float val8 = 8;
required float val9 = 9;
required float val10 = 10;
required float val11 = 11;
required float val12 = 12;
required float val13 = 13;
required float val14 = 14;
required float val15 = 15;
required float val16 = 16;
required float val17 = 17;
required float val18 = 18;
required float val19 = 19;
required float val20 = 20;
}
// See SkColorMatrixFilterRowMajor255.cpp (https://goo.gl/qwF8DK)
message ColorMatrixFilterRowMajor255 {
// Autovisit:
required ColorFilterMatrix color_filter_matrix = 1;
}
message ModeColorFilter {
required uint32 color = 1;
required BlendMode mode = 2;
}
message Rectangle {
required float left = 1;
required float top = 2;
required float right = 3;
required float bottom = 4;
}
message IRect {
required int32 left = 1;
required int32 top = 2;
required int32 right = 3;
required int32 bottom = 4;
}
message CropRectangle {
required Rectangle rectangle = 1;
required uint32 flags = 2;
}
message PictureInfo {
// TODO(metzman): Figure out how to keep this up to date.
enum Version {
V0 = 56;
V1 = 57;
V2 = 58;
V3 = 59;
kRemoveHeaderFlags_Version = 60;
V4 = 61;
}
required Version version = 1;
required Rectangle rectangle = 2;
required uint32 flags = 3;
}
message PictureData {
// SkPictureData.cpp (https://goo.gl/hDnKjz)
repeated PictureTagChild tags = 1;
required ReaderPictureTag reader_tag = 2;
}
enum BlendMode {
kClear = 0;
kSrc = 1;
kDst = 2;
// TODO(metzman): Uncomment this when bug 786133 is fixed.
// kSrcOver = 3;
kDstOver = 4;
kSrcIn = 5;
kDstIn = 6;
kSrcOut = 7;
kDstOut = 8;
kSrcATop = 9;
kDstATop = 10;
kXor = 11;
kPlus = 12;
kModulate = 13;
kScreenAndLastCoeffMode = 14;
kOverlay = 15;
kDarken = 16;
kLighten = 17;
kColorDodge = 18;
kColorBurn = 19;
kHardLight = 20;
kSoftLight = 21;
kDifference = 22;
kExclusion = 23;
kLastSeparableModeAndMultiply = 24;
kHue = 25;
kSaturation = 26;
kColor = 27;
kLuminosity = 28;
}
message Paint {
required float text_size = 1;
required float text_scale_x = 2;
required float text_skew_x = 3;
required float stroke_width = 4;
required float stroke_miter = 5;
required uint32 color = 6;
// Autovisit up to here
enum TextEncoding {
kUTF8_TextEncoding = 0;
kUTF16_TextEncoding = 1;
kUTF32_TextEncoding = 2;
kGlyphID_TextEncoding = 3;
}
enum Style {
kFill_Style = 0;
kStroke_Style = 1;
kStrokeAndFill_Style = 2;
}
enum StrokeCap {
kButt_Cap = 0;
kRound_Cap = 1;
kSquare_Cap = 2;
}
enum StrokeJoin {
kMiter_Join = 0;
kRound_Join = 1;
kBevel_Join = 2;
}
required StrokeCap stroke_cap = 7;
required StrokeJoin stroke_join = 8;
required Style style = 9;
required TextEncoding text_encoding = 10;
required BlendMode blend_mode = 11;
optional PaintEffects effects = 12;
enum PaintFlags {
kAntiAlias_Flag = 0x01;
kDither_Flag = 0x04;
kFakeBoldText_Flag = 0x20;
kLinearText_Flag = 0x40;
kSubpixelText_Flag = 0x80;
kDevKernText_Flag = 0x100;
kLCDRenderText_Flag = 0x200;
kEmbeddedBitmapText_Flag = 0x400;
kAutoHinting_Flag = 0x800;
kVerticalText_Flag = 0x1000;
kGenA8FromLCD_Flag = 0x2000;
kAllFlags = 0xFFFF;
}
enum Hinting {
kNo_Hinting = 0;
kSlight_Hinting = 1;
kNormal_Hinting = 2;
kFull_Hinting = 3;
}
enum Align {
kLeft_Align = 0;
kCenter_Align = 1;
kRight_Align = 2;
}
// Stuff that gets packed into flags.
required PaintFlags flags = 13;
required Hinting hinting = 14;
required Align align = 15;
required FilterQuality filter_quality = 16;
}
message Point {
required float x = 1;
required float y = 2;
}
message PathEffectChild {
oneof children {
PairPathEffect pair_path_effect = 1;
Path2DPathEffect path_2d_path_effect = 2;
Line2DPathEffect line_2d_path_effect = 3;
CornerPathEffect corner_path_effect = 4;
DashImpl dash_impl = 5;
DiscretePathEffect discrete_path_effect = 6;
Path1DPathEffect path_1d_path_effect = 7;
}
}
// Autovisit:
message CornerPathEffect {
// 0 is a very bad choice for radius, so make field optional with a default of
// 1.
optional float radius = 1 [default = 1];
}
message Path2DPathEffect {
required Matrix matrix = 1;
required Path path = 2;
}
message Line2DPathEffect {
required Matrix matrix = 1;
required float width = 2;
}
message DashImpl {
required float phase = 1;
required float interval_1 = 2;
required float interval_2 = 3;
repeated float intervals = 4;
}
// Autovisit:
message DiscretePathEffect {
required float seg_length = 1;
required float perterb = 2;
required uint32 seed_assist = 3;
}
message Path1DPathEffect {
enum Style {
kTranslate_Style = 0;
kRotate_Style = 1;
kMorph_Style = 2;
}
required float advance = 1;
required Path path = 2;
required float initial_offset = 3;
required Style style = 4;
}
message Path {
enum Convexity {
kUnknown_Convexity = 0;
kConvex_Convexity = 1;
kConcave_Convexity = 2;
}
enum FirstDirection {
kCW_FirstDirection = 0;
kCCW_FirstDirection = 1;
kUnknown_FirstDirection = 2;
}
enum SerializationVersion {
kPathPrivFirstDirection_Version = 1;
kPathPrivLastMoveToIndex_Version = 2;
kPathPrivTypeEnumVersion = 3;
}
required Convexity convexity = 1;
required uint32 fill_type = 2; // Should be 8 bytes
required FirstDirection first_direction = 3;
required bool is_volatile = 4;
required SerializationVersion serialized_version = 5;
required int32 last_move_to_index = 6;
required PathRef path_ref = 7;
}
message ValidVerb {
enum Value {
kMove_Verb = 0;
kLine_Verb = 1;
kQuad_Verb = 2;
kConic_Verb = 3;
kCubic_Verb = 4;
kClose_Verb = 5;
// We don't actually want kDone_Verb.
}
required Value value = 1;
required Point point1 = 2;
required Point point2 = 3;
required Point point3 = 4;
required float conic_weight = 5;
}
message PathRef {
repeated ValidVerb verbs = 1;
required bool is_finite = 2;
required uint32 segment_mask = 3;
required ValidVerb first_verb = 4;
}
message PairPathEffect {
enum Type {
SUM = 1;
COMPOSE = 2;
}
required Type type = 1;
required PathEffectChild path_effect_1 = 2;
required PathEffectChild path_effect_2 = 3;
}
message ShaderChild {
oneof children {
ColorShader color_shader = 1;
Color4Shader color_4_shader = 2;
ColorFilterShader color_filter_shader = 3;
ComposeShader compose_shader = 4;
EmptyShader empty_shader = 5;
ImageShader image_shader = 6;
PictureShader picture_shader = 7;
PerlinNoiseShaderImpl perlin_noise_shader_impl = 8;
LocalMatrixShader local_matrix_shader = 9;
LinearGradient linear_gradient = 10;
RadialGradient radial_gradient = 11;
SweepGradient sweep_gradient = 12;
TwoPointConicalGradient two_point_conical_gradient = 13;
}
}
message TwoPointConicalGradient {
required GradientParent parent = 1;
// Autovisit:
required Point center1 = 2;
required Point center2 = 3;
required float radius1 = 4;
required float radius2 = 5;
}
message SweepGradient {
required GradientParent parent = 1;
// Autovisit:
required Point center = 2;
// TODO(metzman): Handle case when buffer.fVersion >=
// kTileInfoInSweepGradient_Version or fVersion != 0.
required float bias = 3;
required float scale = 4;
}
message RadialGradient {
required GradientParent parent = 1;
// Autovisit:
required Point center = 2;
required float radius = 3;
}
message Color4f {
// Autovisit:
required float r = 1;
required float g = 2;
required float b = 3;
required float a = 4;
}
// Note that this cannot be named "Descriptor" since that name is used by
// protobuf's reflection methods
message GradientDescriptor {
optional ColorSpaceChild color_space = 1;
optional float pos = 2;
optional Matrix local_matrix = 3;
required TileMode tile_mode = 4;
required uint32 grad_flags = 5; // <= UINT8_MAX
repeated Color4f colors = 6;
}
// Contained by children
message GradientParent {
required GradientDescriptor gradient_descriptor = 1;
}
message LinearGradient {
required GradientParent parent = 1;
// Autovisit:
required Point start = 2;
required Point end = 3;
}
message LocalMatrixShader {
required Matrix matrix = 1;
required ShaderChild proxy_shader = 2;
}
// Autovisit:
message PerlinNoiseShaderImpl {
enum Type {
kFractalNoise_Type = 0;
kTurbulence_Type = 1;
kImprovedNoise_Type = 2;
}
required Type type = 1;
required float base_frequency_x = 2;
required float base_frequency_y = 3;
required int32 octaves = 4;
required float seed = 5;
required int32 height = 6;
required int32 width = 7;
}
message PictureShader {
required Matrix matrix = 1;
// Autovisit:
required TileMode tmx = 2;
required TileMode tmy = 3;
required Rectangle rect = 4;
}
enum TileMode {
kClamp_TileMode = 0;
kRepeat_TileMode = 1;
kMirror_TileMode = 2;
}
// Autovisit:
message ImageShader {
required TileMode tile_mode_x = 1;
required TileMode tile_mode_y = 2;
required Matrix matrix = 3;
required Image image = 4;
}
message ImageInfo {
enum AlphaType {
kUnknown_SkAlphaType = 0;
kOpaque_SkAlphaType = 1;
kPremul_SkAlphaType = 2;
kUnpremul_SkAlphaType = 3;
}
enum ColorType {
kUnknown_Stored_SkColorType = 0;
kAlpha_8_Stored_SkColorType = 1;
kRGB_565_Stored_SkColorType = 2;
kARGB_4444_Stored_SkColorType = 3;
kRGBA_8888_Stored_SkColorType = 4;
kBGRA_8888_Stored_SkColorType = 5;
kIndex_8_Stored_SkColorType_DEPRECATED = 6;
kGray_8_Stored_SkColorType = 7;
kRGBA_F16_Stored_SkColorType = 8;
}
required int32 width = 1;
required int32 height = 2;
required AlphaType alpha_type = 3;
required ColorType color_type = 4;
required ColorSpaceChild color_space = 5;
}
message ImageData {
repeated uint32 data = 1;
}
// TODO(metzman): Finish implementing using ImageInfo.
message Image {
// Must be non-negative.
required int32 width = 1;
required int32 height = 2;
required ImageData data = 3;
// Must be nonnegative.
required int32 origin_x = 4;
required int32 origin_y = 5;
}
// Autovisit:
message EmptyShader {}
message ComposeShader {
required ShaderChild dst = 1;
required ShaderChild src = 2;
// Autovisit:
required BlendMode mode = 3;
required float lerp_t = 4;
}
message ColorFilterShader {
required ShaderChild shader = 1;
required ColorFilterChild filter = 2;
}
message Color4Shader {
required uint32 color = 1;
}
// Autovisit:
message ColorShader {
required uint32 color = 1;
}
message LooperChild {
required LayerDrawLooper layer_draw_looper = 1;
}
message LayerDrawLooper {
repeated LayerInfo layer_infos = 1;
}
message LayerInfo {
required int32 paint_bits = 1;
required BlendMode color_mode = 2;
required Point point = 3;
required bool post_translate = 4;
// Autovisit up to here
required Paint paint = 5;
}
message MaskFilterChild {
oneof children {
BlurMaskFilter blur_mask_filter_impl = 1;
EmbossMaskFilter emboss_mask_filter = 2;
RRectsGaussianEdgeMaskFilterImpl r_rects_gaussian_edge_mask_filter_impl = 3;
}
}
message RRectsGaussianEdgeMaskFilterImpl {
required Rectangle rect_1 = 1;
required float x_rad_1 = 2;
required float y_rad_1 = 3;
required Rectangle rect_2 = 4;
required float x_rad_2 = 5;
required float y_rad_2 = 6;
required float radius = 7;
}
message EmbossMaskFilterLight {
required float direction_x = 1;
required float direction_y = 2;
required float direction_z = 3;
required uint32 ambient = 4;
required uint32 specular = 5;
}
message EmbossMaskFilter {
required EmbossMaskFilterLight light = 1;
required float blur_sigma = 2;
}
enum BlurStyle {
kNormal_SkBlurStyle = 0;
kSolid_SkBlurStyle = 1;
kOuter_SkBlurStyle = 2;
kInner_SkBlurStyle = 3;
}
// Copied from https://goo.gl/Yy5Euw
enum BlurFlags {
kNone_BlurFlag = 0x00;
kIgnoreTransform_BlurFlag = 0x01;
kHighQuality_BlurFlag = 0x02;
kAll_BlurFlag = 0x03;
}
message BlurMaskFilter {
required float sigma = 1;
required BlurStyle style = 2;
required BlurFlags flags = 3;
required Rectangle occluder = 4;
}
message PaintEffects {
optional PathEffectChild path_effect = 1;
optional ShaderChild shader = 2;
optional MaskFilterChild mask_filter = 3;
optional ColorFilterChild color_filter = 4;
optional LooperChild looper = 5;
optional ImageFilterChild image_filter = 6;
}
message RecordingData {
repeated Paint paints = 1;
}
message PaintImageFilter {
required ImageFilterParent image_filter_parent = 1;
required Paint paint = 2;
}
message PictureTagChild {
oneof children {
PaintPictureTag paint = 1;
PathPictureTag path = 2;
Image image = 3;
Vertices vertices = 4;
TextBlob text_blob = 5;
}
}
message TextBlob {
required Rectangle bounds = 1;
enum GlyphPositioning {
kDefault_Positioning = 0;
kHorizontal_Positioning = 1;
kFull_Positioning = 2;
}
required GlyphPositioning glyph_positioning = 2;
required bool extended = 3;
required Point offset = 4;
required Paint paint = 5;
required GlyphAndPosAndCluster glyph_pos_cluster_1 = 6;
required GlyphAndPosAndCluster glyph_pos_cluster_2 = 7;
repeated GlyphAndPosAndCluster glyph_pos_clusters = 8;
repeated uint32 text = 9;
}
message GlyphAndPosAndCluster {
required uint32 glyph = 1;
required float position_1 = 2;
required float position_2 = 3;
required uint32 cluster = 4;
}
message Vertices {
enum VertexMode {
kTriangles_VertexMode = 0;
kTriangleStrip_VertexMode = 1;
kTriangleFan_VertexMode = 2;
}
required VertexMode mode = 1;
required bool has_texs = 2;
required bool has_colors = 3;
repeated VertexTexColor vertex_text_colors = 4;
repeated uint32 indices = 5;
}
message VertexTexColor {
required Point vertex = 1;
required Point tex = 2;
required Point color = 3;
}
message ReaderPictureTag {
required uint32 first_bytes = 1;
repeated uint32 later_bytes = 2;
}
message PaintPictureTag {
required Paint paint = 1;
}
message PathPictureTag {
required Path path = 1;
}
message Picture {
required PictureInfo info = 1;
optional PictureData data = 2;
}
// Copied with comments from skia.
// Enums in C++ that don't have set values start at 0.
enum FilterQuality {
// fastest but lowest quality, typically nearest-neighbor
kNone_SkFilterQuality = 0;
kLow_SkFilterQuality = 1; // typically bilerp
kMedium_SkFilterQuality = 2; // typically bilerp + mipmaps for down-scaling
// slowest but highest quality, typically bicubic or better
kHigh_SkFilterQuality = 3;
}
message PictureImageFilter {
enum PictureResolution {
kDeviceSpace_PictureResolution = 0;
kLocalSpace_PictureResolution = 1;
}
optional Picture picture = 1;
required Rectangle crop_rectangle = 2;
required PictureResolution resolution = 3;
}
message Matrix {
required float val1 = 1;
required float val2 = 2;
required float val3 = 3;
required float val4 = 4;
required float val5 = 5;
required float val6 = 6;
required float val7 = 7;
required float val8 = 8;
required float val9 = 9;
}
message MatrixImageFilter {
required ImageFilterParent image_filter_parent = 1;
required Matrix transform = 2;
required FilterQuality filter_quality = 3;
}
message ImageFilterChild {
oneof children {
PaintImageFilter paint_image_filter = 1;
MatrixImageFilter matrix_image_filter = 2;
SpecularLightingImageFilter specular_lighting_image_filter = 3;
ArithmeticImageFilter arithmetic_image_filter = 4;
AlphaThresholdFilterImpl alpha_threshold_filter_impl = 5;
BlurImageFilterImpl blur_image_filter_impl = 6;
ColorFilterImageFilter color_filter_image_filter = 7;
ComposeImageFilter compose_image_filter = 8;
DisplacementMapEffect displacement_map_effect = 9;
DropShadowImageFilter drop_shadow_image_filter = 10;
LocalMatrixImageFilter local_matrix_image_filter = 11;
MagnifierImageFilter magnifier_image_filter = 13;
MatrixConvolutionImageFilter matrix_convolution_image_filter = 14;
MergeImageFilter merge_image_filter = 15;
DilateImageFilter dilate_image_filter = 16;
ErodeImageFilter erode_image_filter = 17;
OffsetImageFilter offset_image_filter = 18;
PictureImageFilter picture_image_filter = 19;
TileImageFilter tile_image_filter = 20;
XfermodeImageFilter_Base xfermode_image_filter__base = 21;
XfermodeImageFilter xfermode_image_filter = 22;
DiffuseLightingImageFilter diffuse_lighting_image_filter = 23;
ImageSource image_source = 24;
}
}
message DiffuseLightingImageFilter {
required ImageFilterParent parent = 1;
required LightParent light = 2;
required float surface_scale = 3;
required float kd = 4;
}
message XfermodeImageFilter {
required ImageFilterParent parent = 1;
required BlendMode mode = 2;
}
message XfermodeImageFilter_Base {
required ImageFilterParent parent = 1;
required BlendMode mode = 2;
}
message TileImageFilter {
required ImageFilterParent parent = 1;
required Rectangle src = 2;
required Rectangle dst = 3;
}
message OffsetImageFilter {
required ImageFilterParent parent = 1;
required Point offset = 2;
}
message ErodeImageFilter {
required ImageFilterParent parent = 1;
required int32 width = 2;
required int32 height = 3;
}
message DilateImageFilter {
required ImageFilterParent parent = 1;
required int32 width = 2;
required int32 height = 3;
}
message MergeImageFilter {
required ImageFilterParent parent = 1;
}
message MatrixConvolutionImageFilter {
required ImageFilterParent parent = 1;
required int32 width = 2;
required int32 height = 3;
// Since we can't specify a field of repeated bytes that is width*height, use
// a kernel_seed to seed a RNG to get the number of bytes we need.
required int64 kernel_seed = 4;
required float gain = 5;
required float bias = 6;
required int32 offset_x = 7;
required int32 offset_y = 8;
required TileMode tile_mode = 9;
required bool convolve_alpha = 10;
}
message MagnifierImageFilter {
required ImageFilterParent parent = 1;
required Rectangle src = 2;
required float inset = 3;
}
message LocalMatrixImageFilter {
required ImageFilterParent parent = 1;
required Matrix matrix = 2;
}
message ImageSource {
required FilterQuality filter_quality = 1;
required Rectangle src = 2;
required Rectangle dst = 3;
// / Autovisit
required Image image = 4;
}
message DropShadowImageFilter {
enum ShadowMode {
kDrawShadowAndForeground_ShadowMode = 0;
kDrawShadowOnly_ShadowMode = 1;
kDrawShadowOnly_ShadowMod = 2;
}
required ImageFilterParent parent = 1;
// Autovisit:
required float dx = 2;
required float dy = 3;
required float sigma_x = 4;
required float sigma_y = 5;
required uint32 color = 6;
required ShadowMode shadow_mode = 7;
}
message DisplacementMapEffect {
enum ChannelSelectorType {
kUnknown_ChannelSelectorType = 0;
kR_ChannelSelectorType = 1;
kG_ChannelSelectorType = 2;
kB_ChannelSelectorType = 3;
kA_ChannelSelectorTyp = 4;
}
required ImageFilterParent parent = 1;
// Autovisit:
required ChannelSelectorType xsel = 2;
required ChannelSelectorType ysel = 3;
required float scale = 4;
}
message ComposeImageFilter {
required ImageFilterParent parent = 1;
}
message ColorFilterImageFilter {
required ImageFilterParent parent = 1;
required ColorFilterChild color_filter = 2;
}
message BlurImageFilterImpl {
required ImageFilterParent parent = 1;
required float sigma_x = 2;
required float sigma_y = 3;
required TileMode mode = 4;
}
message AlphaThresholdFilterImpl {
required ImageFilterParent parent = 1;
required float inner = 2;
required float outer = 3;
required Region rgn = 4;
}
message Region {
required IRect bounds = 1;
// TODO(metzman): Properly implement complex regions.
}
message RegionComplex {
required int32 y_span_count = 1;
required int32 interval_count = 2;
repeated int32 run_seed = 3;
}
message ArithmeticImageFilter {
required ImageFilterParent parent = 1;
// Ignored see SkXfermodeImageFilter_Base::LegacyArithmeticCreateProc.
// Converter will write a mode even without a corresponding field.
// required BlendMode mode = 2;
// Autovisit:
required float val1 = 2;
required float val2 = 3;
required float val3 = 4;
required float val4 = 5;
required bool enforce_pm_color = 6;
}
// Contained by children
message ImageFilterParent {
required ImageFilterChild default_input = 1;
repeated ImageFilterChild inputs = 2;
required CropRectangle crop_rectangle = 3;
}
// Autovisit:
message Point3 {
required float x = 1;
required float y = 2;
required float z = 3;
}
// Contains children
message LightParent {
required Point3 color = 1;
required LightChild light_child = 2;
}
// Autovisit:
message DistantLight {
required Point3 direction = 1;
}
// Autovisit:
message PointLight {
required Point3 location = 1;
}
// See SkLightingImageFilter.cpp
// Autovisit:
message SpotLight {
required Point3 location = 1;
required Point3 target = 2;
required float specular_exponent = 3;
required float cos_outer_cone_angle = 4;
required float cos_inner_cone_angle = 5;
required float cone_scale = 6;
required Point3 s = 7;
}
message LightChild {
oneof children {
PointLight point_light = 1;
SpotLight spot_light = 2;
}
required DistantLight distant_light = 3;
}
message SpecularLightingImageFilter {
required ImageFilterParent image_filter_parent = 1;
required LightParent light = 2;
required float surface_scale = 3;
required float ks = 4;
required float shininess = 5;
}
enum GammaNamed {
kLinear_SkGammaNamed = 0;
kSRGB_SkGammaNamed = 1;
k2Dot2Curve_SkGammaNamed = 2;
kNonStandard_SkGammaNamed = 3;
}
message ThreeByFour {
required float val1 = 1;
required float val2 = 2;
required float val3 = 3;
required float val4 = 4;
required float val5 = 5;
required float val6 = 6;
required float val7 = 7;
required float val8 = 8;
required float val9 = 9;
required float val10 = 10;
required float val11 = 11;
required float val12 = 12;
}
enum ICCTag {
kTAG_rXYZ = 0;
kTAG_gXYZ = 1;
kTAG_bXYZ = 2;
kTAG_rTRC = 3;
kTAG_gTRC = 4;
kTAG_bTRC = 5;
kTAG_kTRC = 6;
kTAG_A2B0 = 7;
kTAG_CurveType = 8;
kTAG_ParaCurveType = 9;
kTAG_TextType = 10;
}
// This contains a lot of commented out fields since they are in the actual
// struct this message represents, but are unused. We don't define them and
// WriteIgnoredFields is used to write them rather than wasting LPM's time
// setting them. However, we leave them here commented out for reference, and
// we don't use their numbers in case we use them in the future.
message ICC {
enum Profile {
Display_Profile = 0;
Input_Profile = 1;
Output_Profile = 2;
ColorSpace_Profile = 3;
}
enum InputColorSpace {
RGB_ColorSpace = 0;
CMYK_ColorSpace = 1;
Gray_ColorSpace = 2;
}
enum PCS {
kXYZ_PCSSpace = 0;
kLAB_PCSSpace = 1;
}
enum RenderingIntent {
kPerceptual = 0;
kRelative = 1;
kSaturation = 2;
kAbsolute = 3;
}
required Named named = 34;
// required uint32 size = 1; // Always 132.
// required uint32 cmm_type_ignored = 2;
required uint32 version = 3;
required Profile profile_class = 4;
required InputColorSpace input_color_space = 5;
required PCS pcs = 6;
// required uint32 datetime_ignored_1 = 7;
// required uint32 datetime_ignored_2 = 8;
// required uint32 datetime_ignored_3 = 9;
// Always SkSetFourByteTag('a', 'c', 's', 'p')
// required uint32 signature = 10;
// required uint32 platform_target_ignored = 11;
// required uint32 flags_ignored = 12;
// required uint32 device_manufacturer_ignored = 13;
// required uint32 device_model_ignored = 14;
// required uint32 device_attributes_ignored_1 = 15;
// required uint32 device_attributes_ignored_2 = 16;
required RenderingIntent rendering_intent = 17;
required int32 illuminant_x = 18;
required int32 illuminant_y = 19;
required int32 illuminant_z = 20;
// required uint32 creator_ignored = 21;
// required uint32 profileid_ignored_1 = 22;
// required uint32 profileid_ignored_2 = 23;
// required uint32 profileid_ignored_3 = 24;
// required uint32 profileid_ignored_4 = 25;
// required uint32 reserved_ignored_1 = 26;
// required uint32 reserved_ignored_2 = 27;
// required uint32 reserved_ignored_3 = 28;
// required uint32 reserved_ignored_4 = 29;
// required uint32 reserved_ignored_5 = 30;
// required uint32 reserved_ignored_6 = 31;
// required uint32 reserved_ignored_7 = 32;
// We'll use colorspaces instead
required ICCColorSpace color_space = 33;
// repeated Tag tags = 33;
}
message ICCColorSpace {
oneof color_space {
ICCXYZ xyz = 1;
ICCGray gray = 2;
}
// Default.
required ICCA2B0 a2b0 = 3;
}
message ICCXYZ {}
message ICCGray {}
message ICCA2B0 {
oneof type {
ICCA2B0Lut8 lut8 = 1;
ICCA2B0Lut16 lut16 = 2;
}
// Default.
required ICCA2B0AToB atob = 3;
}
enum Ignored { VALUE = 0; }
enum UInt8 {
VAL0 = 0;
VAL1 = 1;
VAL2 = 2;
VAL3 = 3;
VAL4 = 4;
VAL5 = 5;
VAL6 = 6;
VAL7 = 7;
VAL8 = 8;
VAL9 = 9;
VAL10 = 10;
VAL11 = 11;
VAL12 = 12;
VAL13 = 13;
VAL14 = 14;
VAL15 = 15;
VAL16 = 16;
VAL17 = 17;
VAL18 = 18;
VAL19 = 19;
VAL20 = 20;
VAL21 = 21;
VAL22 = 22;
VAL23 = 23;
VAL24 = 24;
VAL25 = 25;
VAL26 = 26;
VAL27 = 27;
VAL28 = 28;
VAL29 = 29;
VAL30 = 30;
VAL31 = 31;
VAL32 = 32;
VAL33 = 33;
VAL34 = 34;
VAL35 = 35;
VAL36 = 36;
VAL37 = 37;
VAL38 = 38;
VAL39 = 39;
VAL40 = 40;
VAL41 = 41;
VAL42 = 42;
VAL43 = 43;
VAL44 = 44;
VAL45 = 45;
VAL46 = 46;
VAL47 = 47;
VAL48 = 48;
VAL49 = 49;
VAL50 = 50;
VAL51 = 51;
VAL52 = 52;
VAL53 = 53;
VAL54 = 54;
VAL55 = 55;
VAL56 = 56;
VAL57 = 57;
VAL58 = 58;
VAL59 = 59;
VAL60 = 60;
VAL61 = 61;
VAL62 = 62;
VAL63 = 63;
VAL64 = 64;
VAL65 = 65;
VAL66 = 66;
VAL67 = 67;
VAL68 = 68;
VAL69 = 69;
VAL70 = 70;
VAL71 = 71;
VAL72 = 72;
VAL73 = 73;
VAL74 = 74;
VAL75 = 75;
VAL76 = 76;
VAL77 = 77;
VAL78 = 78;
VAL79 = 79;
VAL80 = 80;
VAL81 = 81;
VAL82 = 82;
VAL83 = 83;
VAL84 = 84;
VAL85 = 85;
VAL86 = 86;
VAL87 = 87;
VAL88 = 88;
VAL89 = 89;
VAL90 = 90;
VAL91 = 91;
VAL92 = 92;
VAL93 = 93;
VAL94 = 94;
VAL95 = 95;
VAL96 = 96;
VAL97 = 97;
VAL98 = 98;
VAL99 = 99;
VAL100 = 100;
VAL101 = 101;
VAL102 = 102;
VAL103 = 103;
VAL104 = 104;
VAL105 = 105;
VAL106 = 106;
VAL107 = 107;
VAL108 = 108;
VAL109 = 109;
VAL110 = 110;
VAL111 = 111;
VAL112 = 112;
VAL113 = 113;
VAL114 = 114;
VAL115 = 115;
VAL116 = 116;
VAL117 = 117;
VAL118 = 118;
VAL119 = 119;
VAL120 = 120;
VAL121 = 121;
VAL122 = 122;
VAL123 = 123;
VAL124 = 124;
VAL125 = 125;
VAL126 = 126;
VAL127 = 127;
VAL128 = 128;
VAL129 = 129;
VAL130 = 130;
VAL131 = 131;
VAL132 = 132;
VAL133 = 133;
VAL134 = 134;
VAL135 = 135;
VAL136 = 136;
VAL137 = 137;
VAL138 = 138;
VAL139 = 139;
VAL140 = 140;
VAL141 = 141;
VAL142 = 142;
VAL143 = 143;
VAL144 = 144;
VAL145 = 145;
VAL146 = 146;
VAL147 = 147;
VAL148 = 148;
VAL149 = 149;
VAL150 = 150;
VAL151 = 151;
VAL152 = 152;
VAL153 = 153;
VAL154 = 154;
VAL155 = 155;
VAL156 = 156;
VAL157 = 157;
VAL158 = 158;
VAL159 = 159;
VAL160 = 160;
VAL161 = 161;
VAL162 = 162;
VAL163 = 163;
VAL164 = 164;
VAL165 = 165;
VAL166 = 166;
VAL167 = 167;
VAL168 = 168;
VAL169 = 169;
VAL170 = 170;
VAL171 = 171;
VAL172 = 172;
VAL173 = 173;
VAL174 = 174;
VAL175 = 175;
VAL176 = 176;
VAL177 = 177;
VAL178 = 178;
VAL179 = 179;
VAL180 = 180;
VAL181 = 181;
VAL182 = 182;
VAL183 = 183;
VAL184 = 184;
VAL185 = 185;
VAL186 = 186;
VAL187 = 187;
VAL188 = 188;
VAL189 = 189;
VAL190 = 190;
VAL191 = 191;
VAL192 = 192;
VAL193 = 193;
VAL194 = 194;
VAL195 = 195;
VAL196 = 196;
VAL197 = 197;
VAL198 = 198;
VAL199 = 199;
VAL200 = 200;
VAL201 = 201;
VAL202 = 202;
VAL203 = 203;
VAL204 = 204;
VAL205 = 205;
VAL206 = 206;
VAL207 = 207;
VAL208 = 208;
VAL209 = 209;
VAL210 = 210;
VAL211 = 211;
VAL212 = 212;
VAL213 = 213;
VAL214 = 214;
VAL215 = 215;
VAL216 = 216;
VAL217 = 217;
VAL218 = 218;
VAL219 = 219;
VAL220 = 220;
VAL221 = 221;
VAL222 = 222;
VAL223 = 223;
VAL224 = 224;
VAL225 = 225;
VAL226 = 226;
VAL227 = 227;
VAL228 = 228;
VAL229 = 229;
VAL230 = 230;
VAL231 = 231;
VAL232 = 232;
VAL233 = 233;
VAL234 = 234;
VAL235 = 235;
VAL236 = 236;
VAL237 = 237;
VAL238 = 238;
VAL239 = 239;
VAL240 = 240;
VAL241 = 241;
VAL242 = 242;
VAL243 = 243;
VAL244 = 244;
VAL245 = 245;
VAL246 = 246;
VAL247 = 247;
VAL248 = 248;
VAL249 = 249;
VAL250 = 250;
VAL251 = 251;
VAL252 = 252;
VAL253 = 253;
VAL254 = 254;
VAL255 = 255;
}
enum InputChannels {
ONE = 1;
TWO = 2;
THREE = 3;
}
enum OutputChannels {
// Can't be named THREE or else it will conflict with THREE in InputChannels.
// It doesn't matter, since we only use the numeric value on the converter
// side.
_THREE = 3;
}
message ICCA2B0AToB {
required InputChannels input_channels = 1;
required OutputChannels output_channels = 2; // Must be 3
}
message ICCA2B0Lut16 {
required ICCA2B0Lut8 lut8 = 1;
// TODO(metzman): allow these to be specified rather than generated.
// required uint32 in_table_entries = 2; // uint16_t
// required uint32 out_table_entries = 3; // uint16_t
required uint64 in_table_seed = 4;
required uint64 out_table_seed = 5;
}
message ICCA2B0Lut8 {
required Ignored ignored_byte_4 = 1;
required Ignored ignored_byte_5 = 2;
required Ignored ignored_byte_6 = 3;
required Ignored ignored_byte_7 = 4;
// Needs to agree with output_channels
required OutputChannels input_channels = 5;
required OutputChannels output_channels = 6; // Must be 3
required UInt8 clut_grid_points = 7;
required Ignored ignored_byte_11 = 8;
required Matrix matrix = 9;
required OneChannelGammas input_gammas_1 = 10;
required OneChannelGammas input_gammas_2 = 11;
required OneChannelGammas input_gammas_3 = 12;
required uint64 clut_bytes_seed = 13;
required OutputGammas output_gammas = 14;
}
message OneChannelGammas {
required int32 bytes_0_3 = 1;
required int32 bytes_4_7 = 2;
required int32 bytes_8_11 = 3;
required int32 bytes_12_15 = 4;
required int32 bytes_16_19 = 5;
required int32 bytes_20_23 = 6;
required int32 bytes_24_27 = 7;
required int32 bytes_28_31 = 8;
required int32 bytes_32_35 = 9;
required int32 bytes_36_39 = 10;
required int32 bytes_40_43 = 11;
required int32 bytes_44_47 = 12;
required int32 bytes_48_51 = 13;
required int32 bytes_52_55 = 14;
required int32 bytes_56_59 = 15;
required int32 bytes_60_63 = 16;
required int32 bytes_64_67 = 17;
required int32 bytes_68_71 = 18;
required int32 bytes_72_75 = 19;
required int32 bytes_76_79 = 20;
required int32 bytes_80_83 = 21;
required int32 bytes_84_87 = 22;
required int32 bytes_88_91 = 23;
required int32 bytes_92_95 = 24;
required int32 bytes_96_99 = 25;
required int32 bytes_100_103 = 26;
required int32 bytes_104_107 = 27;
required int32 bytes_108_111 = 28;
required int32 bytes_112_115 = 29;
required int32 bytes_116_119 = 30;
required int32 bytes_120_123 = 31;
required int32 bytes_124_127 = 32;
required int32 bytes_128_131 = 33;
required int32 bytes_132_135 = 34;
required int32 bytes_136_139 = 35;
required int32 bytes_140_143 = 36;
required int32 bytes_144_147 = 37;
required int32 bytes_148_151 = 38;
required int32 bytes_152_155 = 39;
required int32 bytes_156_159 = 40;
required int32 bytes_160_163 = 41;
required int32 bytes_164_167 = 42;
required int32 bytes_168_171 = 43;
required int32 bytes_172_175 = 44;
required int32 bytes_176_179 = 45;
required int32 bytes_180_183 = 46;
required int32 bytes_184_187 = 47;
required int32 bytes_188_191 = 48;
required int32 bytes_192_195 = 49;
required int32 bytes_196_199 = 50;
required int32 bytes_200_203 = 51;
required int32 bytes_204_207 = 52;
required int32 bytes_208_211 = 53;
required int32 bytes_212_215 = 54;
required int32 bytes_216_219 = 55;
required int32 bytes_220_223 = 56;
required int32 bytes_224_227 = 57;
required int32 bytes_228_231 = 58;
required int32 bytes_232_235 = 59;
required int32 bytes_236_239 = 60;
required int32 bytes_240_243 = 61;
required int32 bytes_244_247 = 62;
required int32 bytes_248_251 = 63;
required int32 bytes_252_255 = 64;
}
// Since output gammas are 3 times the size of input gammas, make
message OutputGammas {
required OneChannelGammas bytes_0_255 = 1;
required OneChannelGammas bytes_255_511 = 2;
required OneChannelGammas bytes_512_768 = 3;
}
// Copyright 2018 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.
// Converts an Input protobuf Message to a string that can be successfully read
// by SkImageFilter::Deserialize and used as an image filter. The string
// is essentially a valid flattened skia image filter. Note: We will sometimes
// not use the exact values given to us by LPM in cases where those particular
// values cause issues with OOMs and timeouts. Other times, we may write a value
// that isn't exactly the same as the one given to us by LPM, since we may want
// to write invalid values that the proto definition forbids (eg a number that
// is not in enum). Also note that the skia unflattening code is necessary to
// apply the output of the converter to a canvas, but it isn't the main target
// of the fuzzer. This means that we will generally try to produce output that
// can be applied to a canvas, even if we will consequently be unable to produce
// outputs that allow us to reach paths in the unflattening code (in particular,
// code that handles invalid input). We make this tradeoff because being applied
// to a canvas makes an image filter more likely to cause bugs than if it were
// just deserialized. Thus, increasing the chance that a filter is applied is
// more important than hitting all paths in unflattening, particularly if those
// paths return nullptr because they've detected an invalid filter. The mutated
// enum values are a case where we knowingly generate output that may not be
// unflattened successfully, which is why we mutate enums relatively
// infrequently.
// Note that since this is a work in progress and skia serialization is a
// moving target, not everything is finished. Many of these parts of the code
// are #defined out if DEVELOPMENT is not defined.
#include "testing/libfuzzer/proto/skia_image_filter_proto_converter.h"
#include <ctype.h>
#include <stdlib.h>
#include <algorithm>
#include <cmath>
#include <limits>
#include <random>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#include "base/logging.h"
#include "third_party/protobuf/src/google/protobuf/descriptor.h"
#include "third_party/protobuf/src/google/protobuf/message.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkRect.h"
using google::protobuf::Descriptor;
using google::protobuf::EnumDescriptor;
using google::protobuf::EnumValueDescriptor;
using google::protobuf::FieldDescriptor;
using google::protobuf::Message;
using google::protobuf::Reflection;
namespace skia_image_filter_proto_converter {
// Visit the skia flattenable that is stored on the oneof FIELD field of MSG if
// not flattenable_visited and MSG.has_FIELD. Sets flattenable_visited to true
// if the MSG.FIELD() is visited. Note that `bool flattenable_visited` must be
// defined false in the same context that this macro is used, before it can be
// used.
#define VISIT_ONEOF_FLATTENABLE(MSG, FIELD) \
if (MSG.has_##FIELD() && !IsBlacklisted(#FIELD)) { \
CHECK(!flattenable_visited); \
if (PreVisitFlattenable(FieldToFlattenableName(#FIELD))) { \
Visit(MSG.FIELD()); \
PostVisitFlattenable(); \
} \
flattenable_visited = true; \
}
// Visit FIELD if FIELD is set or if no other field on message was visited
// (this should be used at the end of a series of calls to
// VISIT_ONEOF_FLATTENABLE).
// Note FIELD should not be a message that contains itself by default.
// This is used for messages like ImageFilterChild where we must visit one of
// the fields in a oneof. Even though protobuf doesn't mandate that one of these
// be set, we can still visit one of them if they are not set and protobuf will
// return the default values for each field on that message.
#define VISIT_DEFAULT_FLATTENABLE(MSG, FIELD) \
VISIT_ONEOF_FLATTENABLE(MSG, FIELD); \
if (!flattenable_visited) { \
flattenable_visited = true; \
if (PreVisitFlattenable(FieldToFlattenableName(#FIELD))) { \
Visit(MSG.FIELD()); \
PostVisitFlattenable(); \
} \
}
// Visit FIELD if it is set on MSG, or write a NULL to indicate it is not
// present.
#define VISIT_OPT_OR_NULL(MSG, FIELD) \
if (MSG.has_##FIELD()) { \
Visit(MSG.FIELD()); \
} else { \
WriteNum(0); \
}
// Call VisitPictureTag on picture_tag.FIELD() if it is set.
#define VISIT_OPT_TAG(FIELD, TAG) \
if (picture_tag.has_##FIELD()) { \
VisitPictureTag(picture_tag.FIELD(), TAG); \
}
// Copied from third_party/skia/include/core/SkTypes.h:SkSetFourByteTag.
#define SET_FOUR_BYTE_TAG(A, B, C, D) \
(((A) << 24) | ((B) << 16) | ((C) << 8) | (D))
// The following enums and constants are copied from various parts of the skia
// codebase.
enum FlatFlags {
kHasTypeface_FlatFlag = 0x1,
kHasEffects_FlatFlag = 0x2,
kFlatFlagMask = 0x3,
};
enum LightType {
kDistant_LightType,
kPoint_LightType,
kSpot_LightType,
};
// Copied from SkVertices.cpp.
enum VerticesConstants {
kMode_Mask = 0x0FF,
kHasTexs_Mask = 0x100,
kHasColors_Mask = 0x200,
};
// Copied from SerializationOffsets in SkPath.h. Named PathSerializationOffsets
// to avoid conflicting with PathRefSerializationOffsets. Both enums were named
// SerializationOffsets in skia.
enum PathSerializationOffsets {
kType_SerializationShift = 28,
kDirection_SerializationShift = 26,
kIsVolatile_SerializationShift = 25,
kConvexity_SerializationShift = 16,
kFillType_SerializationShift = 8,
};
// Copied from SerializationOffsets in SkPathRef.h. Named
// PathRefSerializationOffsets to avoid conflicting with
// PathSerializationOffsets. Both enums were named SerializationOffsets in skia.
enum PathRefSerializationOffsets {
kLegacyRRectOrOvalStartIdx_SerializationShift = 28,
kLegacyRRectOrOvalIsCCW_SerializationShift = 27,
kLegacyIsRRect_SerializationShift = 26,
kIsFinite_SerializationShift = 25,
kLegacyIsOval_SerializationShift = 24,
kSegmentMask_SerializationShift = 0
};
const uint32_t Converter::kPictEofTag = SET_FOUR_BYTE_TAG('e', 'o', 'f', ' ');
const uint32_t Converter::kProfileLookupTable[] = {
SET_FOUR_BYTE_TAG('m', 'n', 't', 'r'),
SET_FOUR_BYTE_TAG('s', 'c', 'n', 'r'),
SET_FOUR_BYTE_TAG('p', 'r', 't', 'r'),
SET_FOUR_BYTE_TAG('s', 'p', 'a', 'c'),
};
const uint32_t Converter::kInputColorSpaceLookupTable[] = {
SET_FOUR_BYTE_TAG('R', 'G', 'B', ' '),
SET_FOUR_BYTE_TAG('C', 'M', 'Y', 'K'),
SET_FOUR_BYTE_TAG('G', 'R', 'A', 'Y'),
};
const uint32_t Converter::kPCSLookupTable[] = {
SET_FOUR_BYTE_TAG('X', 'Y', 'Z', ' '),
SET_FOUR_BYTE_TAG('L', 'a', 'b', ' '),
};
const uint32_t Converter::kTagLookupTable[] = {
SET_FOUR_BYTE_TAG('r', 'X', 'Y', 'Z'),
SET_FOUR_BYTE_TAG('g', 'X', 'Y', 'Z'),
SET_FOUR_BYTE_TAG('b', 'X', 'Y', 'Z'),
SET_FOUR_BYTE_TAG('r', 'T', 'R', 'C'),
SET_FOUR_BYTE_TAG('g', 'T', 'R', 'C'),
SET_FOUR_BYTE_TAG('b', 'T', 'R', 'C'),
SET_FOUR_BYTE_TAG('k', 'T', 'R', 'C'),
SET_FOUR_BYTE_TAG('A', '2', 'B', '0'),
SET_FOUR_BYTE_TAG('c', 'u', 'r', 'v'),
SET_FOUR_BYTE_TAG('p', 'a', 'r', 'a'),
SET_FOUR_BYTE_TAG('m', 'l', 'u', 'c'),
};
const char Converter::kSkPictReaderTag[] = {'r', 'e', 'a', 'd'};
const char Converter::kPictureMagicString[] = {'s', 'k', 'i', 'a',
'p', 'i', 'c', 't'};
const uint8_t Converter::kCountNibBits[] = {0, 1, 1, 2, 1, 2, 2, 3,
1, 2, 2, 3, 2, 3, 3, 4};
// The rest of the Converter attributes are not copied from skia.
const int Converter::kFlattenableDepthLimit = 4;
const int Converter::kColorTableBufferLength = 256;
uint8_t Converter::kColorTableBuffer[kColorTableBufferLength];
const int Converter::kNumBound = 50;
const uint8_t Converter::kMutateEnumDenominator = 40;
// Does not include SkSumPathEffect, SkComposePathEffect or SkRegion
// since they don't use the VISIT FLATTENABLE macros.
const string_map_t Converter::kFieldToFlattenableName = {
{"path_1d_path_effect", "SkPath1DPathEffect"},
{"path_2d_path_effect", "SkPath2DPathEffect"},
{"alpha_threshold_filter_impl", "SkAlphaThresholdFilterImpl"},
{"arithmetic_image_filter", "SkArithmeticImageFilter"},
{"blur_image_filter_impl", "SkBlurImageFilterImpl"},
{"blur_mask_filter_impl", "SkBlurMaskFilterImpl"},
{"color_4_shader", "SkColor4Shader"},
{"color_filter_image_filter", "SkColorFilterImageFilter"},
{"color_filter_shader", "SkColorFilterShader"},
{"color_matrix_filter_row_major_255", "SkColorMatrixFilterRowMajor255"},
{"color_shader", "SkColorShader"},
{"compose_color_filter", "SkComposeColorFilter"},
{"compose_image_filter", "SkComposeImageFilter"},
{"compose_shader", "SkComposeShader"},
{"corner_path_effect", "SkCornerPathEffect"},
{"dash_impl", "SkDashImpl"},
{"diffuse_lighting_image_filter", "SkDiffuseLightingImageFilter"},
{"dilate_image_filter", "SkDilateImageFilter"},
{"discrete_path_effect", "SkDiscretePathEffect"},
{"displacement_map_effect", "SkDisplacementMapEffect"},
{"drop_shadow_image_filter", "SkDropShadowImageFilter"},
{"emboss_mask_filter", "SkEmbossMaskFilter"},
{"empty_shader", "SkEmptyShader"},
{"image_shader", "SkImageShader"},
{"image_source", "SkImageSource"},
{"line_2d_path_effect", "SkLine2DPathEffect"},
{"linear_gradient", "SkLinearGradient"},
{"local_matrix_image_filter", "SkLocalMatrixImageFilter"},
{"local_matrix_shader", "SkLocalMatrixShader"},
{"luma_color_filter", "SkLumaColorFilter"},
{"magnifier_image_filter", "SkMagnifierImageFilter"},
{"matrix_convolution_image_filter", "SkMatrixConvolutionImageFilter"},
{"matrix_image_filter", "SkMatrixImageFilter"},
{"merge_image_filter", "SkMergeImageFilter"},
{"mode_color_filter", "SkModeColorFilter"},
{"offset_image_filter", "SkOffsetImageFilter"},
{"overdraw_color_filter", "SkOverdrawColorFilter"},
{"paint_image_filter", "SkPaintImageFilter"},
{"picture_image_filter", "SkPictureImageFilter"},
{"picture_shader", "SkPictureShader"},
{"radial_gradient", "SkRadialGradient"},
{"r_rects_gaussian_edge_mask_filter_impl",
"SkRRectsGaussianEdgeMaskFilterImpl"},
{"specular_lighting_image_filter", "SkSpecularLightingImageFilter"},
{"sweep_gradient", "SkSweepGradient"},
{"tile_image_filter", "SkTileImageFilter"},
{"two_point_conical_gradient", "SkTwoPointConicalGradient"},
{"xfermode_image_filter", "SkXfermodeImageFilter"},
{"xfermode_image_filter__base", "SkXfermodeImageFilter_Base"},
{"srgb_gamma_color_filter", "SkSRGBGammaColorFilter"},
{"high_contrast__filter", "SkHighContrast_Filter"},
{"table__color_filter", "SkTable_ColorFilter"},
{"to_srgb_color_filter", "SkToSRGBColorFilter"},
{"layer_draw_looper", "SkLayerDrawLooper"},
{"perlin_noise_shader_impl", "SkPerlinNoiseShaderImpl"},
{"erode_image_filter", "SkErodeImageFilter"},
};
const std::set<std::string> Converter::kMisbehavedFlattenableBlacklist = {
"matrix_image_filter", // Causes OOMs.
"discrete_path_effect", // Causes timeouts.
"path_1d_path_effect", // Causes timeouts.
};
// We don't care about default values of attributes because Reset() sets them to
// correct values and is called by Convert(), the only important public
// function.
Converter::Converter() {
CHECK_GT(kMutateEnumDenominator, 2);
}
Converter::~Converter() {}
Converter::Converter(const Converter& other) {}
std::string Converter::FieldToFlattenableName(
const std::string& field_name) const {
CHECK(kFieldToFlattenableName.find(field_name) !=
kFieldToFlattenableName.end());
return kFieldToFlattenableName.at(field_name);
}
void Converter::Reset() {
output_.clear();
bound_positive_ = false;
dont_mutate_enum_ = true;
pair_path_effect_depth_ = 0;
flattenable_depth_ = 0;
stroke_style_used_ = false;
in_compose_color_filter_ = false;
// In production we don't need attributes used by ICC code since it is not
// built for production code.
#ifdef DEVELOPMENT
tag_offset_ = 0;
icc_base_ = 0;
#endif // DEVELOPMENT
}
std::string Converter::Convert(const Input& input) {
Reset();
rand_gen_ = std::mt19937(input.rng_seed());
enum_mutator_chance_distribution_ =
std::uniform_int_distribution<>(2, kMutateEnumDenominator);
// This will recursively call Visit on each proto flattenable until all of
// them are converted to strings and stored in output_.
Visit(input.image_filter());
CheckAlignment();
return std::string(&output_[0], output_.size());
}
void Converter::Visit(const CropRectangle& crop_rectangle) {
Visit(crop_rectangle.rectangle());
WriteNum(BoundNum(crop_rectangle.flags()));
}
void Converter::Visit(const Rectangle& rectangle) {
WriteRectangle(GetValidRectangle(rectangle.left(), rectangle.top(),
rectangle.right(), rectangle.bottom()));
}
std::tuple<float, float, float, float>
Converter::GetValidRectangle(float left, float top, float right, float bottom) {
bool initial = bound_positive_;
bound_positive_ = true;
left = BoundFloat(left);
top = BoundFloat(top);
right = BoundFloat(right);
bottom = BoundFloat(bottom);
if (right < left)
right = left;
if (bottom < top)
bottom = top;
// Inspired by SkValidationUtils.h:SkIsValidRect
CHECK_LE(left, right);
CHECK_LE(top, bottom);
CHECK(IsFinite(right - left));
CHECK(IsFinite(bottom - top));
bound_positive_ = initial;
return std::make_tuple(left, top, right, bottom);
}
std::tuple<int32_t, int32_t, int32_t, int32_t> Converter::GetValidIRect(
int32_t left,
int32_t top,
int32_t right,
int32_t bottom) {
auto float_rectangle = GetValidRectangle(left, top, right, bottom);
return std::make_tuple(static_cast<int32_t>(std::get<0>(float_rectangle)),
static_cast<int32_t>(std::get<1>(float_rectangle)),
static_cast<int32_t>(std::get<2>(float_rectangle)),
static_cast<int32_t>(std::get<3>(float_rectangle)));
}
template <typename T>
void Converter::WriteRectangle(std::tuple<T, T, T, T> rectangle) {
WriteNum(std::get<0>(rectangle));
WriteNum(std::get<1>(rectangle));
WriteNum(std::get<2>(rectangle));
WriteNum(std::get<3>(rectangle));
}
void Converter::Visit(const LightChild& light_child) {
if (light_child.has_point_light())
Visit(light_child.point_light());
else if (light_child.has_spot_light())
Visit(light_child.spot_light());
else
Visit(light_child.distant_light());
}
void Converter::Visit(const LightParent& light_parent) {
if (light_parent.light_child().has_point_light())
WriteNum(kPoint_LightType);
else if (light_parent.light_child().has_spot_light())
WriteNum(kSpot_LightType);
else // Assume we have distant light
WriteNum(kDistant_LightType);
Visit(light_parent.color());
Visit(light_parent.light_child());
}
void Converter::Visit(const ImageFilterChild& image_filter_child) {
bool flattenable_visited = false;
VISIT_ONEOF_FLATTENABLE(image_filter_child, specular_lighting_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, matrix_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, arithmetic_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, alpha_threshold_filter_impl);
VISIT_ONEOF_FLATTENABLE(image_filter_child, blur_image_filter_impl);
VISIT_ONEOF_FLATTENABLE(image_filter_child, color_filter_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, compose_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, displacement_map_effect);
VISIT_ONEOF_FLATTENABLE(image_filter_child, drop_shadow_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, local_matrix_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, magnifier_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, matrix_convolution_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, merge_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, dilate_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, erode_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, offset_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, picture_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, tile_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, xfermode_image_filter__base);
VISIT_ONEOF_FLATTENABLE(image_filter_child, xfermode_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, diffuse_lighting_image_filter);
VISIT_ONEOF_FLATTENABLE(image_filter_child, image_source);
VISIT_DEFAULT_FLATTENABLE(image_filter_child, paint_image_filter);
}
void Converter::Visit(
const DiffuseLightingImageFilter& diffuse_lighting_image_filter) {
Visit(diffuse_lighting_image_filter.parent(), 1);
Visit(diffuse_lighting_image_filter.light());
WriteNum(diffuse_lighting_image_filter.surface_scale());
// Can't be negative, see:
// https://www.w3.org/TR/SVG/filters.html#feDiffuseLightingElement
const float kd = fabs(BoundFloat(diffuse_lighting_image_filter.kd()));
WriteNum(kd);
}
void Converter::Visit(const XfermodeImageFilter& xfermode_image_filter) {
Visit(xfermode_image_filter.parent(), 2);
WriteNum(xfermode_image_filter.mode());
}
void Converter::Visit(
const XfermodeImageFilter_Base& xfermode_image_filter__base) {
Visit(xfermode_image_filter__base.parent(), 2);
WriteNum(xfermode_image_filter__base.mode());
}
void Converter::Visit(const TileImageFilter& tile_image_filter) {
Visit(tile_image_filter.parent(), 1);
Visit(tile_image_filter.src());
Visit(tile_image_filter.dst());
}
void Converter::Visit(const OffsetImageFilter& offset_image_filter) {
Visit(offset_image_filter.parent(), 1);
Visit(offset_image_filter.offset());
}
void Converter::Visit(const HighContrast_Filter& high_contrast__filter) {
WriteFields(high_contrast__filter, 1, 2);
// Use contrast as a seed.
WriteNum(GetRandomFloat(high_contrast__filter.contrast(), -1.0, 1.0));
}
void Converter::Visit(const MergeImageFilter& merge_image_filter) {
Visit(merge_image_filter.parent(), merge_image_filter.parent().inputs_size());
}
void Converter::Visit(const ErodeImageFilter& erode_image_filter) {
Visit(erode_image_filter.parent(), 1);
bool initial = bound_positive_;
bound_positive_ = true;
WriteFields(erode_image_filter, 2);
bound_positive_ = initial;
}
template <typename T>
T Converter::BoundNum(T num, int upper_bound) const {
if (bound_positive_)
num = Abs(num);
if (num >= 0) {
return num % upper_bound;
} else {
// Don't let negative numbers be too negative.
return num % -upper_bound;
}
}
template <typename T>
T Converter::BoundNum(T num) {
return BoundNum(num, kNumBound);
}
float Converter::BoundFloat(float num) {
return BoundFloat(num, kNumBound);
}
float Converter::BoundFloat(float num, const float num_bound) {
// Don't allow nans infs, they can cause OOMs.
if (!IsFinite(num))
num = GetRandomFloat(&rand_gen_);
float result;
if (num >= 0)
result = fmod(num, num_bound);
else if (bound_positive_)
result = fmod(fabsf(num), num_bound);
else
// Bound negative numbers.
result = fmod(num, -num_bound);
if (!IsFinite(result))
return BoundFloat(num);
return result;
}
void Converter::Visit(const DilateImageFilter& dilate_image_filter) {
Visit(dilate_image_filter.parent(), 1);
// Make sure WriteFields writes positive values for width and height.
// Save the value of bound_positive_ and restore it after WriteFields
// returns.
bool initial_bound_positive = bound_positive_;
bound_positive_ = true;
WriteFields(dilate_image_filter, 2);
bound_positive_ = initial_bound_positive;
}
void Converter::Visit(
const MatrixConvolutionImageFilter& matrix_convolution_image_filter) {
Visit(matrix_convolution_image_filter.parent(), 1);
// Avoid timeouts from having to generate too many random numbers.
// TODO(metzman): actually calculate the limit based on this bound (eg 31 x 1
// probably doesn't need to be bounded).
const int upper_bound = 30;
// Use 2 instead of 1 to avoid FPEs in BoundNum.
int32_t width = std::max(
2, BoundNum(Abs(matrix_convolution_image_filter.width()), upper_bound));
WriteNum(width);
int32_t height = std::max(
2, BoundNum(Abs(matrix_convolution_image_filter.height()), upper_bound));
WriteNum(height);
std::mt19937 rand_gen(matrix_convolution_image_filter.kernel_seed());
const uint32_t kernel_size = width * height;
WriteNum(kernel_size);
// Use rand_gen to ensure we have a large enough kernel.
for (uint32_t kernel_counter = 0; kernel_counter < kernel_size;
kernel_counter++) {
float kernel_element = GetRandomFloat(&rand_gen);
WriteNum(kernel_element);
}
WriteFields(matrix_convolution_image_filter, 5, 6);
const uint32_t offset_x =
std::max(0, matrix_convolution_image_filter.offset_x());
const uint32_t offset_y =
std::max(0, matrix_convolution_image_filter.offset_y());
WriteNum(BoundNum(offset_x, width - 1));
WriteNum(BoundNum(offset_y, height - 1));
WriteFields(matrix_convolution_image_filter, 9);
}
void Converter::Visit(const MagnifierImageFilter& magnifier_image_filter) {
Visit(magnifier_image_filter.parent(), 1);
Visit(magnifier_image_filter.src());
const float inset = fabs(BoundFloat(magnifier_image_filter.inset()));
CHECK(IsFinite(inset));
WriteNum(inset);
}
void Converter::Visit(const LocalMatrixImageFilter& local_matrix_image_filter) {
// TODO(metzman): Make it so that deserialization always succeeds by ensuring
// the type isn't kAffine_Mask or KPerspectiveMask (see constructor for
// SkLocalMatrixImageFilter).
Visit(local_matrix_image_filter.parent(), 1);
Visit(local_matrix_image_filter.matrix(), true);
}
void Converter::Visit(const ImageSource& image_source) {
WriteNum(image_source.filter_quality());
auto src_rect = GetValidRectangle(
image_source.src().left(), image_source.src().top(),
image_source.src().right(), image_source.src().bottom());
// See SkImageSource::Make for why we mandate width and height be at least
// .01. This is such a small difference that we won't bother bounding again.
float left = std::get<0>(src_rect);
float* right = &std::get<2>(src_rect);
if ((*right - left) <= 0.0f)
*right += .01;
float top = std::get<1>(src_rect);
float* bottom = &std::get<3>(src_rect);
if ((*bottom - top) <= 0.0f)
*bottom += .01;
WriteRectangle(src_rect);
Visit(image_source.dst());
Visit(image_source.image());
}
void Converter::Visit(const DropShadowImageFilter& drop_shadow_image_filter) {
Visit(drop_shadow_image_filter.parent(), 1);
WriteFields(drop_shadow_image_filter, 2);
}
void Converter::Visit(const DisplacementMapEffect& displacement_map_effect) {
Visit(displacement_map_effect.parent(), 2);
bool initial = dont_mutate_enum_;
dont_mutate_enum_ = true;
WriteFields(displacement_map_effect, 2);
dont_mutate_enum_ = initial;
}
void Converter::Visit(const ComposeImageFilter& compose_image_filter) {
Visit(compose_image_filter.parent(), 2);
}
void Converter::Visit(const ColorFilterImageFilter& color_filter_image_filter) {
Visit(color_filter_image_filter.parent(), 1);
Visit(color_filter_image_filter.color_filter());
}
void Converter::Visit(const BlurImageFilterImpl& blur_image_filter_impl) {
Visit(blur_image_filter_impl.parent(), 1);
WriteFields(blur_image_filter_impl, 2);
}
void Converter::Visit(
const AlphaThresholdFilterImpl& alpha_threshold_filter_impl) {
Visit(alpha_threshold_filter_impl.parent(), 1);
WriteFields(alpha_threshold_filter_impl, 2, 3);
Visit(alpha_threshold_filter_impl.rgn());
}
std::tuple<int32_t, int32_t, int32_t, int32_t> Converter::WriteNonEmptyIRect(
const IRect& irect) {
// Make sure bounds do not specify an empty rectangle.
// See SkRect.h:202
auto rectangle =
GetValidIRect(irect.left(), irect.top(), irect.right(), irect.bottom());
// Ensure top and right are greater than left and top.
if (irect.left() >= irect.right() || irect.top() >= irect.bottom()) {
std::get<2>(rectangle) = std::get<0>(rectangle) + 1;
std::get<3>(rectangle) = std::get<1>(rectangle) + 1;
}
WriteRectangle(rectangle);
return rectangle;
}
void Converter::Visit(const Region& region) {
// Write simple region.
WriteNum(0);
WriteNonEmptyIRect(region.bounds());
// Complex regions are not finished.
#ifdef DEVELOPMENT
enum { kRunTypeSentinel = 0x7FFFFFFF };
auto rectangle = WriteNonEmptyIRect(region.bounds());
const int32_t bound_left = std::get<0>(rectangle);
const int32_t bound_top = std::get<1>(rectangle);
const int32_t bound_right = std::get<2>(rectangle);
const int32_t bound_bottom = std::get<3>(rectangle);
const int32_t y_span_count =
BoundNum(std::max(1, Abs(region.y_span_count())));
const int32_t interval_count = BoundNum(std::max(1, Abs(region.interval_())));
WriteNum(run_count);
WriteNum(y_span_count);
WriteNum(interval_count);
// See SkRegion::validate_run.
// Really this is two less, but we will write the two sentinels
ourselves const int32_t run_count = 3 * y_span_count + 2 * interval_count;
CHECK(run_count >= 7);
WriteNum(run_count + 2);
// Write runs.
// Write top.
Write(bound_top);
WriteNum(kRunTypeSentinel);
WriteNum(kRunTypeSentinel);
#endif // DEVELOPMENT
}
void Converter::Visit(const PictureInfo& picture_info) {
WriteArray(kPictureMagicString, sizeof(kPictureMagicString));
WriteNum(picture_info.version());
Visit(picture_info.rectangle());
if (picture_info.version() < PictureInfo::kRemoveHeaderFlags_Version)
WriteNum(picture_info.flags());
}
void Converter::Visit(const ImageFilterParent& image_filter,
const int num_inputs_required) {
CHECK_GE(num_inputs_required, 0);
if (!num_inputs_required) {
WriteNum(0);
} else {
WriteNum(num_inputs_required);
WriteBool(true);
Visit(image_filter.default_input());
int num_inputs = 1;
for (const auto& input : image_filter.inputs()) {
if (num_inputs++ >= num_inputs_required)
break;
WriteBool(true);
Visit(input);
}
for (; num_inputs < num_inputs_required; num_inputs++) {
// Copy default_input until we have enough.
WriteBool(true);
Visit(image_filter.default_input());
}
}
Visit(image_filter.crop_rectangle());
}
void Converter::Visit(const ArithmeticImageFilter& arithmetic_image_filter) {
Visit(arithmetic_image_filter.parent(), 2);
// This is field is ignored, but write kSrcOver (3) as the flattening code
// does.
// TODO(metzman): change to enum value (SkBlendMode::kSrcOver) when it
// is uncommented, for now just write, its value: 3.
WriteNum(3);
WriteFields(arithmetic_image_filter, 2);
}
void Converter::Visit(
const SpecularLightingImageFilter& specular_lighting_image_filter) {
Visit(specular_lighting_image_filter.image_filter_parent(), 1);
Visit(specular_lighting_image_filter.light());
WriteNum(BoundFloat(specular_lighting_image_filter.surface_scale()) * 255);
WriteNum(fabs(BoundFloat(specular_lighting_image_filter.ks())));
WriteNum(BoundFloat(specular_lighting_image_filter.shininess()));
}
void Converter::RecordSize() {
// Reserve space to overwrite when we are done writing whatever size we are
// recording.
WriteNum(0);
start_sizes_.push_back(output_.size());
}
size_t Converter::PopStartSize() {
CHECK_GT(start_sizes_.size(), static_cast<size_t>(0));
const size_t back = start_sizes_.back();
start_sizes_.pop_back();
return back;
}
template <typename T>
void Converter::WriteNum(const T num) {
CHECK_LE(sizeof(T), static_cast<size_t>(4));
char num_arr[sizeof(T)];
memcpy(num_arr, &num, sizeof(T));
for (size_t idx = 0; idx < sizeof(T); idx++)
output_.push_back(num_arr[idx]);
}
void Converter::WriteNum(const uint64_t num) {
CHECK_LE(num, UINT32_MAX);
WriteNum(static_cast<uint32_t>(num));
}
void Converter::InsertSize(const size_t size, const uint32_t position) {
char size_arr[sizeof(uint32_t)];
memcpy(size_arr, &size, sizeof(uint32_t));
for (size_t idx = 0; idx < sizeof(uint32_t); idx++) {
const size_t output__idx = position + idx - sizeof(uint32_t);
CHECK_LT(output__idx, output_.size());
output_[output__idx] = size_arr[idx];
}
}
void Converter::WriteBytesWritten() {
const size_t start_size = PopStartSize();
CHECK_LT(start_size, std::numeric_limits<uint32_t>::max());
const size_t end_size = output_.size();
CHECK_LE(start_size, end_size);
const size_t bytes_written = end_size - start_size;
CHECK_LT(bytes_written, std::numeric_limits<uint32_t>::max());
InsertSize(bytes_written, start_size);
}
void Converter::WriteString(const std::string str) {
WriteNum(str.size());
const char* c_str = str.c_str();
for (size_t idx = 0; idx < str.size(); idx++)
output_.push_back(c_str[idx]);
output_.push_back('\0'); // Add trailing NULL.
Pad(str.size() + 1);
}
void Converter::WriteArray(
const google::protobuf::RepeatedField<uint32_t>& repeated_field,
const size_t size) {
WriteNum(size * sizeof(uint32_t)); // Array size.
for (uint32_t element : repeated_field)
WriteNum(element);
// Padding is not a concern because uint32_ts are 4 bytes.
}
void Converter::WriteArray(const char* arr, const size_t size) {
WriteNum(size);
for (size_t idx = 0; idx < size; idx++)
output_.push_back(arr[idx]);
for (unsigned idx = 0; idx < size % 4; idx++)
output_.push_back('\0');
}
void Converter::WriteBool(const bool bool_val) {
// bools are usually written as 32 bit integers in skia flattening.
WriteNum(static_cast<uint32_t>(bool_val));
}
void Converter::WriteNum(const char (&num_arr)[4]) {
for (size_t idx = 0; idx < 4; idx++)
output_.push_back(num_arr[idx]);
}
void Converter::Visit(const PictureShader& picture_shader) {
// PictureShader cannot be autovisited because matrix cannot be.
Visit(picture_shader.matrix());
WriteFields(picture_shader, 2, 3);
Visit(picture_shader.rect());
WriteBool(false);
}
void Converter::Visit(const Message& msg) {
WriteFields(msg);
}
// Visit the Message elements of repeated_field, using the type-specific Visit
// methods (thanks to templating).
template <class T>
void Converter::Visit(
const google::protobuf::RepeatedPtrField<T>& repeated_field) {
for (const T& single_field : repeated_field)
Visit(single_field);
}
void Converter::Visit(const PictureImageFilter& picture_image_filter) {
WriteBool(picture_image_filter.has_picture());
if (picture_image_filter.has_picture())
Visit(picture_image_filter.picture());
// Allow 0x0 rectangles to sometimes be written even though it will mess up
// make_localspace_filter.
Visit(picture_image_filter.crop_rectangle());
if (picture_image_filter.has_picture()) {
if (picture_image_filter.picture().info().version() <
PictureInfo::kRemoveHeaderFlags_Version)
WriteNum(picture_image_filter.resolution());
}
}
void Converter::Visit(const PictureData& picture_data) {
for (auto& tag : picture_data.tags()) {
Visit(tag);
}
Visit(picture_data.reader_tag());
WriteNum(kPictEofTag);
}
void Converter::VisitPictureTag(const PaintPictureTag& paint_picture_tag,
uint32_t tag) {
WriteNum(tag);
WriteNum(1); // Size.
Visit(paint_picture_tag.paint());
}
void Converter::VisitPictureTag(const PathPictureTag& path_picture_tag,
uint32_t tag) {
WriteNum(tag);
WriteNum(1); // Size.
WriteNum(1); // Count.
Visit(path_picture_tag.path());
}
template <class T>
void Converter::VisitPictureTag(const T& picture_tag_child, uint32_t tag) {
WriteNum(tag);
WriteNum(1);
Visit(picture_tag_child);
}
void Converter::Visit(const ReaderPictureTag& reader) {
WriteNum(SET_FOUR_BYTE_TAG('r', 'e', 'a', 'd'));
const uint32_t size = sizeof(uint32_t) * (1 + reader.later_bytes_size());
WriteNum(size);
WriteNum(size);
WriteNum(reader.first_bytes());
for (auto bytes : reader.later_bytes())
WriteNum(bytes);
}
// Copied from SkPaint.cpp.
static uint32_t pack_4(unsigned a, unsigned b, unsigned c, unsigned d) {
CHECK_EQ(a, (uint8_t)a);
CHECK_EQ(b, (uint8_t)b);
CHECK_EQ(c, (uint8_t)c);
CHECK_EQ(d, (uint8_t)d);
return (a << 24) | (b << 16) | (c << 8) | d;
}
// Copied from SkPaint.cpp.
static uint32_t pack_paint_flags(unsigned flags,
unsigned hint,
unsigned align,
unsigned filter,
unsigned flatFlags) {
// left-align the fields of "known" size, and right-align the last (flatFlags)
// so it can easily add more bits in the future.
return (flags << 16) | (hint << 14) | (align << 12) | (filter << 10) |
flatFlags;
}
bool Converter::IsFinite(float num) const {
// If num is inf, -inf, nan or -nan then num*0 will be nan.
return !std::isnan(num * 0);
}
void Converter::Visit(const Paint& paint) {
WriteFields(paint, 1, 6);
uint8_t flat_flags = 0;
if (paint.has_effects())
flat_flags |= kHasEffects_FlatFlag;
WriteNum(pack_paint_flags(paint.flags(), paint.hinting(), paint.align(),
paint.filter_quality(), flat_flags));
int style = paint.style();
Paint::StrokeCap stroke_cap = paint.stroke_cap();
if (stroke_style_used_) {
style = Paint::kFill_Style;
} else if (style == Paint::kStrokeAndFill_Style ||
style == Paint::kStroke_Style) {
stroke_style_used_ = true;
// Avoid timeouts.
stroke_cap = Paint::kButt_Cap;
}
uint32_t tmp =
pack_4(stroke_cap, paint.stroke_join(),
(style << 4) | paint.text_encoding(), paint.blend_mode());
WriteNum(tmp); // See https://goo.gl/nYJfTy
if (paint.has_effects())
Visit(paint.effects());
}
void Converter::Visit(const PaintEffects& paint_effects) {
// There should be a NULL written for every paint_effects field that is not
// set.
VISIT_OPT_OR_NULL(paint_effects, path_effect);
VISIT_OPT_OR_NULL(paint_effects, shader);
VISIT_OPT_OR_NULL(paint_effects, mask_filter);
VISIT_OPT_OR_NULL(paint_effects, color_filter);
WriteNum(0); // Write ignored number where rasterizer used to be.
VISIT_OPT_OR_NULL(paint_effects, looper);
VISIT_OPT_OR_NULL(paint_effects, image_filter);
}
void Converter::Visit(const ColorFilterChild& color_filter_child) {
bool flattenable_visited = false;
VISIT_ONEOF_FLATTENABLE(color_filter_child,
color_matrix_filter_row_major_255);
if (!in_compose_color_filter_)
VISIT_ONEOF_FLATTENABLE(color_filter_child, compose_color_filter);
VISIT_ONEOF_FLATTENABLE(color_filter_child, srgb_gamma_color_filter);
VISIT_ONEOF_FLATTENABLE(color_filter_child, high_contrast__filter);
VISIT_ONEOF_FLATTENABLE(color_filter_child, luma_color_filter);
VISIT_ONEOF_FLATTENABLE(color_filter_child, overdraw_color_filter);
VISIT_ONEOF_FLATTENABLE(color_filter_child, table__color_filter);
VISIT_ONEOF_FLATTENABLE(color_filter_child, to_srgb_color_filter);
VISIT_DEFAULT_FLATTENABLE(color_filter_child, mode_color_filter);
}
void Converter::Visit(const Color4f& color_4f) {
WriteFields(color_4f);
}
void Converter::Visit(const GradientDescriptor& gradient_descriptor) {
// See SkGradientShaderBase::Descriptor::flatten in SkGradientShader.cpp.
enum GradientSerializationFlags {
// Bits 29:31 used for various boolean flags
kHasPosition_GSF = 0x80000000,
kHasLocalMatrix_GSF = 0x40000000,
kHasColorSpace_GSF = 0x20000000,
// Bits 12:28 unused
// Bits 8:11 for fTileMode
kTileModeShift_GSF = 8,
kTileModeMask_GSF = 0xF,
// Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80)
kGradFlagsShift_GSF = 0,
kGradFlagsMask_GSF = 0xFF,
};
uint32_t flags = 0;
if (gradient_descriptor.has_pos())
flags |= kHasPosition_GSF;
if (gradient_descriptor.has_local_matrix())
flags |= kHasLocalMatrix_GSF;
if (gradient_descriptor.has_color_space())
flags |= kHasColorSpace_GSF;
flags |= (gradient_descriptor.tile_mode() << kTileModeShift_GSF);
uint32_t grad_flags =
(gradient_descriptor.grad_flags() % (kGradFlagsMask_GSF + 1));
CHECK_LE(grad_flags, kGradFlagsMask_GSF);
WriteNum(flags);
const uint32_t count = gradient_descriptor.colors_size();
WriteNum(count);
for (auto& color : gradient_descriptor.colors())
Visit(color);
Visit(gradient_descriptor.color_space());
WriteNum(count);
for (uint32_t counter = 0; counter < count; counter++)
WriteNum(gradient_descriptor.pos());
Visit(gradient_descriptor.local_matrix());
}
void Converter::Visit(const GradientParent& gradient_parent) {
Visit(gradient_parent.gradient_descriptor());
}
void Converter::Visit(const ToSRGBColorFilter& to_srgb_color_filter) {
Visit(to_srgb_color_filter.color_space());
}
void Converter::Visit(const LooperChild& looper) {
if (PreVisitFlattenable("SkLayerDrawLooper")) {
Visit(looper.layer_draw_looper());
PostVisitFlattenable();
}
}
// Copied from SkPackBits.cpp.
static uint8_t* flush_diff8(uint8_t* dst, const uint8_t* src, size_t count) {
while (count > 0) {
size_t n = count > 128 ? 128 : count;
*dst++ = (uint8_t)(n + 127);
memcpy(dst, src, n);
src += n;
dst += n;
count -= n;
}
return dst;
}
// Copied from SkPackBits.cpp.
static uint8_t* flush_same8(uint8_t dst[], uint8_t value, size_t count) {
while (count > 0) {
size_t n = count > 128 ? 128 : count;
*dst++ = (uint8_t)(n - 1);
*dst++ = (uint8_t)value;
count -= n;
}
return dst;
}
// Copied from SkPackBits.cpp.
static size_t compute_max_size8(size_t srcSize) {
// Worst case is the number of 8bit values + 1 byte per (up to) 128 entries.
return ((srcSize + 127) >> 7) + srcSize;
}
// Copied from SkPackBits.cpp.
static size_t pack8(const uint8_t* src,
size_t srcSize,
uint8_t* dst,
size_t dstSize) {
if (dstSize < compute_max_size8(srcSize)) {
return 0;
}
uint8_t* const origDst = dst;
const uint8_t* stop = src + srcSize;
for (intptr_t count = stop - src; count > 0; count = stop - src) {
if (1 == count) {
*dst++ = 0;
*dst++ = *src;
break;
}
unsigned value = *src;
const uint8_t* s = src + 1;
if (*s == value) { // accumulate same values...
do {
s++;
if (s == stop) {
break;
}
} while (*s == value);
dst = flush_same8(dst, value, (size_t)(s - src));
} else { // accumulate diff values...
do {
if (++s == stop) {
goto FLUSH_DIFF;
}
// only stop if we hit 3 in a row,
// otherwise we get bigger than compuatemax
} while (*s != s[-1] || s[-1] != s[-2]);
s -= 2; // back up so we don't grab the "same" values that follow
FLUSH_DIFF:
dst = flush_diff8(dst, src, (size_t)(s - src));
}
src = s;
}
return dst - origDst;
}
const uint8_t* Converter::ColorTableToArray(const ColorTable& color_table) {
float* dst = reinterpret_cast<float*>(kColorTableBuffer);
const int array_size = 64;
// Now write the 256 fields.
const Descriptor* descriptor = color_table.GetDescriptor();
CHECK(descriptor);
const Reflection* reflection = color_table.GetReflection();
CHECK(reflection);
for (int field_num = 1; field_num <= array_size; field_num++, dst++) {
const FieldDescriptor* field_descriptor =
descriptor->FindFieldByNumber(field_num);
CHECK(field_descriptor);
*dst = BoundFloat(reflection->GetFloat(color_table, field_descriptor));
}
return kColorTableBuffer;
}
void Converter::Visit(const Table_ColorFilter& table__color_filter) {
// See SkTable_ColorFilter::SkTable_ColorFilter
enum {
kA_Flag = 1 << 0,
kR_Flag = 1 << 1,
kG_Flag = 1 << 2,
kB_Flag = 1 << 3,
};
unsigned flags = 0;
uint8_t f_storage[4 * kColorTableBufferLength];
uint8_t* dst = f_storage;
if (table__color_filter.has_table_a()) {
memcpy(dst, ColorTableToArray(table__color_filter.table_a()),
kColorTableBufferLength);
dst += kColorTableBufferLength;
flags |= kA_Flag;
}
if (table__color_filter.has_table_r()) {
memcpy(dst, ColorTableToArray(table__color_filter.table_r()),
kColorTableBufferLength);
dst += kColorTableBufferLength;
flags |= kR_Flag;
}
if (table__color_filter.has_table_g()) {
memcpy(dst, ColorTableToArray(table__color_filter.table_g()),
kColorTableBufferLength);
dst += kColorTableBufferLength;
flags |= kG_Flag;
}
if (table__color_filter.has_table_b()) {
memcpy(dst, ColorTableToArray(table__color_filter.table_b()),
kColorTableBufferLength);
dst += kColorTableBufferLength;
flags |= kB_Flag;
}
uint8_t storage[5 * kColorTableBufferLength];
const int count = kCountNibBits[flags & 0xF];
const size_t size = pack8(f_storage, count * kColorTableBufferLength, storage,
sizeof(storage));
CHECK_LE(flags, UINT32_MAX);
const uint32_t flags_32 = (uint32_t)flags;
WriteNum(flags_32);
WriteNum((uint32_t)size);
for (size_t idx = 0; idx < size; idx++)
output_.push_back(storage[idx]);
Pad(output_.size());
}
void Converter::Visit(const ComposeColorFilter& compose_color_filter) {
CHECK(!in_compose_color_filter_);
in_compose_color_filter_ = true;
Visit(compose_color_filter.outer());
Visit(compose_color_filter.inner());
in_compose_color_filter_ = false;
}
void Converter::Visit(const OverdrawColorFilter& overdraw_color_filter) {
// This is written as a byte array (length-in-bytes followed by data).
const uint32_t num_fields = 6;
const uint32_t arr_size = num_fields * sizeof(uint32_t);
WriteNum(arr_size);
WriteFields(overdraw_color_filter);
}
void Converter::Visit(
const ColorMatrixFilterRowMajor255& color_matrix_filter_row_major_255) {
Visit(color_matrix_filter_row_major_255.color_filter_matrix());
}
void Converter::Visit(const ColorFilterMatrix& color_filter_matrix) {
static const int kColorFilterMatrixNumFields = 20;
WriteNum(kColorFilterMatrixNumFields);
WriteFields(color_filter_matrix);
}
void Converter::Visit(const LayerDrawLooper& layer_draw_looper) {
WriteNum(layer_draw_looper.layer_infos_size());
for (auto& layer_info : layer_draw_looper.layer_infos()) {
Visit(layer_info);
#ifdef AVOID_MISBEHAVIOR
break; // Only write 1 to avoid timeouts.
#endif
}
}
void Converter::Visit(const LayerInfo& layer_info) {
WriteNum(0);
// Don't mutate these enum values or else a crash will be caused
bool initial = dont_mutate_enum_;
dont_mutate_enum_ = true;
WriteFields(layer_info, 1, 4);
dont_mutate_enum_ = initial;
Visit(layer_info.paint());
}
void Converter::Visit(const PairPathEffect& pair) {
// Don't allow nesting of PairPathEffects for performance reasons
if (pair_path_effect_depth_ >= 1)
return;
if (flattenable_depth_ > kFlattenableDepthLimit)
return;
pair_path_effect_depth_ += 1;
flattenable_depth_ += 1;
std::string name;
if (pair.type() == PairPathEffect::SUM)
name = "SkSumPathEffect";
else
name = "SkComposePathEffect";
WriteString(name);
RecordSize();
Visit(pair.path_effect_1());
Visit(pair.path_effect_2());
WriteBytesWritten(); // Flattenable size.
CheckAlignment();
pair_path_effect_depth_ -= 1;
flattenable_depth_ -= 1;
}
// See SkPathRef::writeToBuffer
void Converter::Visit(const PathRef& path_ref) {
// Bound segment_mask to avoid timeouts and for proper behavior.
const int32_t packed =
(((path_ref.is_finite() & 1) << kIsFinite_SerializationShift) |
(ToUInt8(path_ref.segment_mask()) << kSegmentMask_SerializationShift));
WriteNum(packed);
WriteNum(0);
std::vector<SkPoint> points;
if (path_ref.verbs_size()) {
WriteNum(path_ref.verbs_size() + 1);
uint32_t num_points = 1; // The last move will add 1 point.
uint32_t num_conics = 0;
for (auto& verb : path_ref.verbs()) {
switch (verb.value()) {
case ValidVerb::kMove_Verb:
case ValidVerb::kLine_Verb:
num_points += 1;
break;
case ValidVerb::kConic_Verb:
num_conics += 1;
// fall-through
case ValidVerb::kQuad_Verb:
num_points += 2;
break;
case ValidVerb::kCubic_Verb:
num_points += 3;
break;
case ValidVerb::kClose_Verb:
break;
default:
NOTREACHED();
}
}
WriteNum(num_points);
WriteNum(num_conics);
} else {
WriteNum(0);
WriteNum(0);
WriteNum(0);
}
for (auto& verb : path_ref.verbs()) {
const uint8_t value = verb.value();
WriteNum(value);
}
// Verbs must start (they are written backwards) with kMove_Verb (0).
if (path_ref.verbs_size()) {
uint8_t value = ValidVerb::kMove_Verb;
WriteNum(value);
}
// Write points
for (auto& verb : path_ref.verbs()) {
switch (verb.value()) {
case ValidVerb::kMove_Verb:
case ValidVerb::kLine_Verb: {
Visit(verb.point1());
AppendAsSkPoint(points, verb.point1());
break;
}
case ValidVerb::kConic_Verb:
case ValidVerb::kQuad_Verb: {
Visit(verb.point1());
Visit(verb.point2());
AppendAsSkPoint(points, verb.point1());
AppendAsSkPoint(points, verb.point2());
break;
}
case ValidVerb::kCubic_Verb:
Visit(verb.point1());
Visit(verb.point2());
Visit(verb.point3());
AppendAsSkPoint(points, verb.point1());
AppendAsSkPoint(points, verb.point2());
AppendAsSkPoint(points, verb.point3());
break;
default:
break;
}
}
// Write point of the Move Verb we put at the end.
if (path_ref.verbs_size()) {
Visit(path_ref.first_verb().point1());
AppendAsSkPoint(points, path_ref.first_verb().point1());
}
// Write conic weights.
for (auto& verb : path_ref.verbs()) {
if (verb.value() == ValidVerb::kConic_Verb)
WriteNum(verb.conic_weight());
}
SkRect skrect;
skrect.setBoundsCheck(&points[0], points.size());
WriteNum(skrect.fLeft);
WriteNum(skrect.fTop);
WriteNum(skrect.fRight);
WriteNum(skrect.fBottom);
}
void Converter::AppendAsSkPoint(std::vector<SkPoint>& sk_points,
const Point& proto_point) const {
SkPoint sk_point;
sk_point.fX = proto_point.x();
sk_point.fY = proto_point.y();
sk_points.push_back(sk_point);
}
void Converter::Visit(const Path& path) {
enum SerializationVersions {
kPathPrivFirstDirection_Version = 1,
kPathPrivLastMoveToIndex_Version = 2,
kPathPrivTypeEnumVersion = 3,
kCurrent_Version = 3
};
enum FirstDirection {
kCW_FirstDirection,
kCCW_FirstDirection,
kUnknown_FirstDirection,
};
int32_t packed = (path.convexity() << kConvexity_SerializationShift) |
(path.fill_type() << kFillType_SerializationShift) |
(path.first_direction() << kDirection_SerializationShift) |
(path.is_volatile() << kIsVolatile_SerializationShift) |
kCurrent_Version;
// TODO(metzman): Allow writing as RRect.
WriteNum(packed);
WriteNum(path.last_move_to_index());
Visit(path.path_ref());
Pad(output_.size());
CheckAlignment();
}
void Converter::Visit(const RRectsGaussianEdgeMaskFilterImpl&
r_rects_gaussian_edge_mask_filter_impl) {
Visit(r_rects_gaussian_edge_mask_filter_impl.rect_1());
WriteFields(r_rects_gaussian_edge_mask_filter_impl, 2, 3);
Visit(r_rects_gaussian_edge_mask_filter_impl.rect_2());
WriteFields(r_rects_gaussian_edge_mask_filter_impl, 5);
}
void Converter::Visit(const BlurMaskFilter& blur_mask_filter) {
// Sigma must be a finite number <= 0.
float sigma = fabs(BoundFloat(blur_mask_filter.sigma()));
sigma = 1 ? sigma == 0 : sigma;
WriteNum(sigma);
const bool old_value = dont_mutate_enum_;
dont_mutate_enum_ = true;
WriteFields(blur_mask_filter, 2, 3);
dont_mutate_enum_ = old_value;
Visit(blur_mask_filter.occluder());
}
void Converter::CheckAlignment() const {
CHECK_EQ(output_.size() % 4, static_cast<size_t>(0));
}
void Converter::Visit(const ShaderChild& shader) {
bool flattenable_visited = false;
VISIT_ONEOF_FLATTENABLE(shader, color_4_shader);
VISIT_ONEOF_FLATTENABLE(shader, color_filter_shader);
VISIT_ONEOF_FLATTENABLE(shader, image_shader);
VISIT_ONEOF_FLATTENABLE(shader, compose_shader);
VISIT_ONEOF_FLATTENABLE(shader, empty_shader);
VISIT_ONEOF_FLATTENABLE(shader, picture_shader);
VISIT_ONEOF_FLATTENABLE(shader, perlin_noise_shader_impl);
VISIT_ONEOF_FLATTENABLE(shader, local_matrix_shader);
VISIT_ONEOF_FLATTENABLE(shader, linear_gradient);
VISIT_ONEOF_FLATTENABLE(shader, radial_gradient);
VISIT_ONEOF_FLATTENABLE(shader, sweep_gradient);
VISIT_ONEOF_FLATTENABLE(shader, two_point_conical_gradient);
VISIT_DEFAULT_FLATTENABLE(shader, color_shader);
}
void Converter::Visit(
const TwoPointConicalGradient& two_point_conical_gradient) {
Visit(two_point_conical_gradient.parent());
WriteFields(two_point_conical_gradient, 2, 5);
}
void Converter::Visit(const LinearGradient& linear_gradient) {
Visit(linear_gradient.parent());
WriteFields(linear_gradient, 2, 3);
}
void Converter::Visit(const SweepGradient& sweep_gradient) {
Visit(sweep_gradient.parent());
WriteFields(sweep_gradient, 2, 4);
}
void Converter::Visit(const RadialGradient& radial_gradient) {
Visit(radial_gradient.parent());
WriteFields(radial_gradient, 2, 3);
}
// Don't compile unfinished (dead) code in production.
#ifdef DEVELOPMENT
// ICC handling code is unfinished.
// TODO(metzman): Finish implementing ICC.
// Copied from https://goo.gl/j78F6Z
static constexpr uint32_t kTAG_lut8Type = SET_FOUR_BYTE_TAG('m', 'f', 't', '1');
static constexpr uint32_t kTAG_lut16Type =
SET_FOUR_BYTE_TAG('m', 'f', 't', '2');
void Converter::Visit(const ICC& icc) {
icc_base_ = output_.size();
const uint32_t header_size = sizeof(uint8_t) * 4;
uint32_t tag_count = 0;
uint32_t tags_size = 0;
if (icc.color_space().has_a2b0()) {
if (icc.color_space().a2b0().has_lut8()) {
tags_size =
GetLut8Size(icc.color_space().a2b0().lut8()) + kICCTagTableEntrySize;
} else if (icc.color_space().a2b0().has_lut16()) {
tags_size = GetLut16Size(icc.color_space().a2b0().lut16()) +
kICCTagTableEntrySize;
} else {
NOTREACHED();
}
tag_count = 1;
} else {
NOTREACHED();
}
const uint32_t profile_size = sizeof(float) * 33 + tags_size;
const uint32_t size = profile_size + sizeof(profile_size) + header_size;
WriteNum(size);
// Header.
WriteColorSpaceVersion();
WriteNum(ToUInt8(icc.named()));
WriteNum(ToUInt8(GammaNamed::kNonStandard_SkGammaNamed));
WriteNum(kICC_Flag);
WriteNum(profile_size);
WriteBigEndian(profile_size);
WriteIgnoredFields(1);
uint32_t version = icc.version() % 5;
version <<= 24;
WriteBigEndian(version);
WriteBigEndian(kProfileLookupTable[icc.profile_class()]);
WriteBigEndian(kInputColorSpaceLookupTable[icc.input_color_space()]);
WriteBigEndian(kPCSLookupTable[icc.pcs()]);
WriteIgnoredFields(3);
WriteBigEndian(SET_FOUR_BYTE_TAG('a', 'c', 's', 'p'));
WriteIgnoredFields(6);
WriteBigEndian(icc.rendering_intent());
WriteBigEndian(BoundIlluminant(icc.illuminant_x(), 0.96420f));
WriteBigEndian(BoundIlluminant(icc.illuminant_y(), 1.00000f));
WriteBigEndian(BoundIlluminant(icc.illuminant_z(), 0.82491f));
WriteIgnoredFields(12);
Visit(icc.color_space());
const unsigned new_size = output_.size();
CHECK_EQ(static_cast<size_t>(new_size - icc_base_), size + sizeof(size));
}
void Converter::WriteTagSize(const char (&tag)[4], const size_t size) {
WriteNum(tag);
WriteNum(size);
}
// Writes num as a big endian number.
template <typename T>
void Converter::WriteBigEndian(const T num) {
CHECK_LE(sizeof(T), static_cast<size_t>(4));
uint8_t num_arr[sizeof(T)];
memcpy(num_arr, &num, sizeof(T));
uint8_t tmp1 = num_arr[0];
uint8_t tmp2 = num_arr[3];
num_arr[3] = tmp1;
num_arr[0] = tmp2;
tmp1 = num_arr[1];
tmp2 = num_arr[2];
num_arr[2] = tmp1;
num_arr[1] = tmp2;
for (size_t idx = 0; idx < sizeof(uint32_t); idx++)
output_.push_back(num_arr[idx]);
}
void Converter::Visit(const ICCColorSpace& icc_color_space) {
if (icc_color_space.has_xyz())
Visit(icc_color_space.xyz());
else if (icc_color_space.has_gray())
Visit(icc_color_space.gray());
else
Visit(icc_color_space.a2b0());
}
void Converter::Visit(const ICCXYZ& icc_xyz) {}
void Converter::Visit(const ICCGray& icc_gray) {}
void Converter::Visit(const ICCA2B0& icc_a2b0) {
if (icc_a2b0.has_lut8())
Visit(icc_a2b0.lut8());
else if (icc_a2b0.has_lut16())
Visit(icc_a2b0.lut16());
else
Visit(icc_a2b0.atob());
}
void Converter::Visit(const ICCA2B0AToB& icc_a2b0_atob) {}
uint8_t Converter::GetClutGridPoints(const ICCA2B0Lut8& icc_a2b0_lut8) {
uint8_t clut_grid_points = icc_a2b0_lut8.clut_grid_points();
return clut_grid_points ? clut_grid_points > 1 : 2;
}
uint32_t Converter::GetLut8Size(const ICCA2B0Lut8& icc_a2b0_lut8) {
const uint32_t num_entries =
GetClutGridPoints(icc_a2b0_lut8) * icc_a2b0_lut8.output_channels();
const uint32_t clut_bytes = kLut8Precision * num_entries * 4;
const uint32_t gammas_size =
kOneChannelGammasSize * (3 + icc_a2b0_lut8.input_channels());
return kLut8InputSize + gammas_size + clut_bytes;
}
uint32_t Converter::GetLut16Size(const ICCA2B0Lut16& icc_a2b0_lut16) {
return 48;
}
void Converter::Visit(const ICCA2B0Lut8& icc_a2b0_lut8) {
// Write Header.
WriteA2B0TagCommon();
// Write length.
WriteBigEndian(GetLut8Size(icc_a2b0_lut8));
// Specify type.
WriteBigEndian(kTAG_lut8Type); // Bytes 0-3.
WriteLut8(icc_a2b0_lut8);
Visit(icc_a2b0_lut8.input_gammas_1());
if (icc_a2b0_lut8.input_channels() == 2) {
Visit(icc_a2b0_lut8.input_gammas_2());
} else if (icc_a2b0_lut8.input_channels() == 3) {
Visit(icc_a2b0_lut8.input_gammas_2());
Visit(icc_a2b0_lut8.input_gammas_3());
}
std::mt19937 gen(icc_a2b0_lut8.clut_bytes_seed());
const uint32_t clut_bytes = GetClutGridPoints(icc_a2b0_lut8) *
icc_a2b0_lut8.output_channels() * kLut8Precision *
4;
for (uint32_t i = 0; i < clut_bytes; i++)
WriteUInt8(static_cast<uint8_t>(gen()));
Visit(icc_a2b0_lut8.output_gammas());
}
// Write the parts of a lut8 used by a lut16.
void Converter::WriteLut8(const ICCA2B0Lut8& icc_a2b0_lut8) {
// Bytes 4-7 are ignored.
WriteUInt8(icc_a2b0_lut8.ignored_byte_4());
WriteUInt8(icc_a2b0_lut8.ignored_byte_5());
WriteUInt8(icc_a2b0_lut8.ignored_byte_6());
WriteUInt8(icc_a2b0_lut8.ignored_byte_7());
WriteUInt8(icc_a2b0_lut8.input_channels()); // Byte 8.
WriteUInt8(icc_a2b0_lut8.output_channels()); // Byte 9.
WriteUInt8(GetClutGridPoints(icc_a2b0_lut8)); // Byte 10.
WriteUInt8(icc_a2b0_lut8.ignored_byte_11());
Visit(icc_a2b0_lut8.matrix());
}
void Converter::WriteA2B0TagCommon() {
WriteBigEndian(1); // ICC Tag Count
WriteBigEndian(kTagLookupTable[ICCTag::kTAG_A2B0]);
WriteBigEndian(GetCurrentICCOffset() - 4); // Offset.
}
void Converter::WriteIgnoredFields(const int num_fields) {
CHECK_GE(num_fields, 1);
for (int counter = 0; counter < num_fields; counter++)
WriteNum(0);
}
int32_t Converter::BoundIlluminant(float illuminant, const float num) const {
while (fabs(illuminant) >= 1) {
illuminant /= 10;
}
const float result = num + 0.01f * illuminant;
CHECK_LT(fabs(num - result), .01f);
// 1.52587890625e-5f is a hardcoded value from SkFixed.h.
return round(result / 1.52587890625e-5f);
}
uint32_t Converter::GetCurrentICCOffset() {
return output_.size() - icc_base_;
}
void Converter::Visit(const ICCA2B0Lut16& icc_a2b0_lut16) {
// Write Tag Header
WriteA2B0TagCommon();
WriteBigEndian(GetLut16Size(icc_a2b0_lut16));
WriteBigEndian(kTAG_lut16Type); // Bytes 0-3.
WriteLut8(icc_a2b0_lut16.lut8());
uint16_t in_entries =
icc_a2b0_lut16.in_table_entries() % (kMaxLut16GammaEntries + 1);
in_entries = in_entries ? in_entries >= 1 : 2;
uint16_t out_entries =
icc_a2b0_lut16.out_table_entries() % (kMaxLut16GammaEntries + 1);
out_entries = out_entries ? out_entries >= 1 : 2;
WriteUInt16(static_cast<uint16_t>(in_entries));
WriteUInt16(static_cast<uint16_t>(out_entries));
}
void Converter::WriteTagHeader(const uint32_t tag, const uint32_t len) {
WriteBigEndian(kTagLookupTable[tag]);
WriteBigEndian(tag_offset_);
WriteBigEndian(len);
tag_offset_ += 12;
}
// ImageInfo related code.
// Copied from SkImageInfo.h
static int SkColorTypeBytesPerPixel(uint8_t ct) {
static const uint8_t gSize[] = {
0, // Unknown
1, // Alpha_8
2, // RGB_565
2, // ARGB_4444
4, // RGBA_8888
4, // BGRA_8888
1, // kGray_8
8, // kRGBA_F16
};
return gSize[ct];
}
size_t Converter::ComputeMinByteSize(int32_t width,
int32_t height,
ImageInfo::AlphaType alpha_type) const {
width = Abs(width);
height = Abs(height);
if (!height)
return 0;
uint32_t bytes_per_pixel = SkColorTypeBytesPerPixel(alpha_type);
uint64_t bytes_per_row_64 = width * bytes_per_pixel;
CHECK(bytes_per_row_64 <= INT32_MAX);
int32_t bytes_per_row = bytes_per_row_64;
size_t num_bytes = (height - 1) * bytes_per_row + bytes_per_pixel * width;
return num_bytes;
}
std::tuple<int32_t, int32_t, int32_t> Converter::GetNumPixelBytes(
const ImageInfo& image_info,
int32_t width,
int32_t height) {
// Returns a value for pixel bytes that is divisible by four by modifying
// image_info.width() as needed until the computed min byte size is divisible
// by four.
size_t num_bytes_64 =
ComputeMinByteSize(width, height, image_info.alpha_type());
CHECK(num_bytes_64 <= INT32_MAX);
int32_t num_bytes = num_bytes_64;
bool subtract = (num_bytes >= 5);
while (num_bytes % 4) {
if (subtract)
width -= 1;
else
width += 1;
num_bytes_64 = ComputeMinByteSize(width, height, image_info.alpha_type());
CHECK(num_bytes_64 <= INT32_MAX);
num_bytes = num_bytes_64;
}
return std::make_tuple(num_bytes, width, height);
}
void Converter::Visit(const ImageInfo& image_info,
const int32_t width,
const int32_t height) {
WriteNum(width);
WriteNum(height);
uint32_t packed = (image_info.alpha_type() << 8) | image_info.color_type();
WriteNum(packed);
Visit(image_info.color_space());
}
#endif // DEVELOPMENT
void Converter::Visit(const ColorSpaceChild& color_space) {
// ICC code is not finished.
#ifdef DEVELOPMENT
if (color_space.has_icc())
Visit(color_space.icc());
else if (color_space.has_transfer_fn())
#else
if (color_space.has_transfer_fn())
#endif // DEVELOPMENT
Visit(color_space.transfer_fn());
else if (color_space.has_color_space__xyz())
Visit(color_space.color_space__xyz());
else
Visit(color_space.named());
}
template <typename T>
void Converter::WriteUInt8(T num) {
CHECK_LT(num, 256);
output_.push_back(static_cast<uint8_t>(num));
}
void Converter::WriteUInt16(uint16_t num) {
char num_arr[2];
memcpy(num_arr, &num, 2);
for (size_t idx = 0; idx < 2; idx++)
output_.push_back(num_arr[idx]);
}
void Converter::Visit(const TransferFn& transfer_fn) {
const size_t size_64 =
(12 * sizeof(float) + 7 * sizeof(float) + 4 * sizeof(uint8_t));
CHECK_LT(size_64, UINT32_MAX);
WriteNum((uint32_t)size_64);
// Header
WriteColorSpaceVersion();
WriteNum(ToUInt8(transfer_fn.named()));
WriteNum(ToUInt8(GammaNamed::kNonStandard_SkGammaNamed));
WriteNum(ToUInt8(kTransferFn_Flag));
WriteFields(transfer_fn, 2);
}
void Converter::WriteColorSpaceVersion() {
// See SkColorSpace::writeToMemory for why this always writes k0_Version.
// TODO(metzman): Figure out how to keep this up to date.
WriteNum(k0_Version);
}
void Converter::Visit(const ColorSpace_XYZ& color_space__xyz) {
const uint32_t size = 12 * sizeof(float) + sizeof(uint8_t) * 4;
WriteNum(size);
// Header
WriteColorSpaceVersion();
WriteNum(ToUInt8(Named::kSRGB_Named));
WriteNum(ToUInt8(color_space__xyz.gamma_named()));
// See SkColorSpace.cpp:Deserialize (around here: https://goo.gl/R9xQ2B)
WriteNum(ToUInt8(kMatrix_Flag));
Visit(color_space__xyz.three_by_four());
}
void Converter::Visit(const ColorSpaceNamed& color_space_named) {
const uint32_t size = sizeof(uint8_t) * 4;
WriteNum(size);
// Header
WriteColorSpaceVersion();
WriteNum(ToUInt8(color_space_named.named()));
WriteNum(ToUInt8(color_space_named.gamma_named()));
WriteNum(ToUInt8(0));
}
void Converter::Visit(const ImageData& image_data) {
WriteNum(-4 * image_data.data_size());
for (uint32_t element : image_data.data())
WriteNum(element);
}
void Converter::Visit(const Image& image) {
// Width and height must be greater than 0.
WriteNum(std::max(1, BoundNum(Abs(image.width()))));
WriteNum(std::max(1, BoundNum(Abs(image.height()))));
Visit(image.data());
if (image.data().data_size()) {
// origin_x and origin_y need to be positive.
WriteNum(Abs(image.origin_x()));
WriteNum(Abs(image.origin_y()));
}
}
void Converter::Visit(const ImageShader& image_shader) {
WriteFields(image_shader, 1, 3);
Visit(image_shader.image());
}
void Converter::Visit(const ColorFilterShader& color_filter_shader) {
Visit(color_filter_shader.shader());
Visit(color_filter_shader.filter());
}
void Converter::Visit(const ComposeShader& compose_shader) {
if (flattenable_depth_ > kFlattenableDepthLimit)
return;
flattenable_depth_ += 1;
Visit(compose_shader.dst());
Visit(compose_shader.src());
WriteFields(compose_shader, 3, 4);
flattenable_depth_ -= 1;
}
void Converter::Visit(const LocalMatrixShader& local_matrix_shader) {
Visit(local_matrix_shader.matrix());
Visit(local_matrix_shader.proxy_shader());
}
void Converter::Visit(const Color4Shader& color_4_shader) {
WriteNum(color_4_shader.color());
// TODO(metzman): Implement ColorSpaces when skia does. See
// https://goo.gl/c6YAq7
WriteBool(false);
}
void Converter::Pad(const size_t write_size) {
if (write_size % 4 == 0)
return;
for (size_t padding_count = 0; (padding_count + write_size) % 4 != 0;
padding_count++)
output_.push_back('\0');
}
void Converter::Visit(const Path1DPathEffect& path_1d_path_effect) {
WriteNum(path_1d_path_effect.advance());
if (path_1d_path_effect.advance()) {
Visit(path_1d_path_effect.path());
WriteFields(path_1d_path_effect, 3, 4);
}
}
bool Converter::PreVisitFlattenable(const std::string& name) {
if (flattenable_depth_ > kFlattenableDepthLimit)
return false;
flattenable_depth_ += 1;
WriteString(name);
RecordSize();
return true;
}
void Converter::PostVisitFlattenable() {
WriteBytesWritten(); // Flattenable size.
CheckAlignment();
flattenable_depth_ -= 1;
}
void Converter::Visit(const DashImpl& dash_impl) {
WriteNum(BoundFloat(dash_impl.phase()));
int num_left = dash_impl.intervals_size();
int size = dash_impl.intervals_size() + 2;
if (size % 2) {
num_left = num_left - 1;
size = size - 1;
}
WriteNum(size);
WriteNum(fabs(BoundFloat(dash_impl.interval_1())));
WriteNum(fabs(BoundFloat(dash_impl.interval_2())));
for (int idx = 0; idx < num_left; idx++)
WriteNum(fabs(BoundFloat(dash_impl.intervals().Get(idx))));
}
void Converter::Visit(const Path2DPathEffect& path_2d_path_effect) {
Visit(path_2d_path_effect.matrix());
Visit(path_2d_path_effect.path());
}
void Converter::Visit(const PathEffectChild& path_effect) {
bool flattenable_visited = false;
// Visit(pair_path_effect) implements the functionality of
// VisitFlattenable by writing the correct names itself.
if (path_effect.has_pair_path_effect()) {
Visit(path_effect.pair_path_effect());
flattenable_visited = true;
}
VISIT_ONEOF_FLATTENABLE(path_effect, path_2d_path_effect);
VISIT_ONEOF_FLATTENABLE(path_effect, line_2d_path_effect);
VISIT_ONEOF_FLATTENABLE(path_effect, corner_path_effect);
VISIT_ONEOF_FLATTENABLE(path_effect, discrete_path_effect);
VISIT_ONEOF_FLATTENABLE(path_effect, path_1d_path_effect);
VISIT_DEFAULT_FLATTENABLE(path_effect, dash_impl);
}
void Converter::Visit(const DiscretePathEffect& discrete_path_effect) {
// Don't write seg_length because it causes too many timeouts.
// See SkScalar.h for why this value is picked
const float SK_ScalarNotNearlyZero = 1.0 / (1 << 11);
WriteNum(SK_ScalarNotNearlyZero);
// Found in testing to be a good value that is unlikely to cause timeouts.
float perterb = discrete_path_effect.perterb();
// Do this to avoid timeouts.
if (perterb < 1)
perterb += 1;
WriteNum(perterb);
WriteNum(discrete_path_effect.seed_assist());
}
void Converter::Visit(const MaskFilterChild& mask_filter) {
bool flattenable_visited = false;
VISIT_ONEOF_FLATTENABLE(mask_filter, emboss_mask_filter);
VISIT_ONEOF_FLATTENABLE(mask_filter, r_rects_gaussian_edge_mask_filter_impl);
VISIT_DEFAULT_FLATTENABLE(mask_filter, blur_mask_filter_impl);
}
template <typename T>
uint8_t Converter::ToUInt8(const T input_num) const {
return input_num % (UINT8_MAX + 1);
}
void Converter::Visit(const EmbossMaskFilterLight& emboss_mask_filter_light) {
// This is written as a byte array, so first write its size, direction_* are
// floats, fPad is uint16_t and ambient and specular are uint8_ts.
const uint32_t byte_array_size =
(3 * sizeof(float) + sizeof(uint16_t) + (2 * sizeof(uint8_t)));
WriteNum(byte_array_size);
WriteFields(emboss_mask_filter_light, 1, 3);
const uint16_t pad = 0;
WriteNum(pad); // fPad = 0;
WriteNum(ToUInt8(emboss_mask_filter_light.ambient()));
WriteNum(ToUInt8(emboss_mask_filter_light.specular()));
}
void Converter::Visit(const EmbossMaskFilter& emboss_mask_filter) {
Visit(emboss_mask_filter.light());
WriteNum(emboss_mask_filter.blur_sigma());
}
void Converter::Visit(const RecordingData& recording_data) {
WriteNum(kSkPictReaderTag);
Visit(recording_data.paints());
}
void Converter::Visit(const PictureTagChild& picture_tag) {
VISIT_OPT_TAG(paint, SET_FOUR_BYTE_TAG('p', 'n', 't', ' '));
VISIT_OPT_TAG(path, SET_FOUR_BYTE_TAG('p', 't', 'h', ' '));
VISIT_OPT_TAG(image, SET_FOUR_BYTE_TAG('i', 'm', 'a', 'g'));
VISIT_OPT_TAG(vertices, SET_FOUR_BYTE_TAG('v', 'e', 'r', 't'));
VISIT_OPT_TAG(text_blob, SET_FOUR_BYTE_TAG('b', 'l', 'o', 'b'));
}
void Converter::Visit(const Picture& picture) {
Visit(picture.info());
WriteNum(1);
Visit(picture.data());
}
void Converter::Visit(const Matrix& matrix, bool is_local) {
// Avoid OOMs by making sure that matrix fields aren't tiny fractions.
WriteMatrixField(matrix.val1());
WriteMatrixField(matrix.val2());
WriteMatrixField(matrix.val3());
WriteMatrixField(matrix.val4());
WriteMatrixField(matrix.val5());
WriteMatrixField(matrix.val6());
// See SkLocalMatrixImageFilter.cpp:20
if (is_local)
WriteNum(0.0f);
else
WriteMatrixField(matrix.val7());
if (is_local)
WriteNum(0.0f);
else
WriteMatrixField(matrix.val8());
if (is_local)
WriteNum(1.0f);
else
WriteMatrixField(matrix.val9());
}
void Converter::WriteMatrixField(float field_value) {
// Don't let the field values be tiny fractions.
field_value = BoundFloat(field_value);
while ((field_value > 0 && field_value < 1e-5) ||
(field_value < 0 && field_value > -1e-5))
field_value /= 10.0;
WriteNum(field_value);
}
void Converter::Visit(const MatrixImageFilter& matrix_image_filter) {
Visit(matrix_image_filter.image_filter_parent(), 1);
Visit(matrix_image_filter.transform());
WriteNum(matrix_image_filter.filter_quality());
}
void Converter::Visit(const PaintImageFilter& paint_image_filter) {
Visit(paint_image_filter.image_filter_parent(), 0);
Visit(paint_image_filter.paint());
}
float Converter::GetRandomFloat(std::mt19937* gen_ptr) {
CHECK(gen_ptr);
std::mt19937 gen = *gen_ptr;
const float positive_random_float = gen();
const bool is_negative = gen() % 2 == 1;
if (is_negative)
return -positive_random_float;
return positive_random_float;
}
float Converter::GetRandomFloat(float seed, float min, float max) {
std::mt19937 gen(seed);
auto next_after_max = std::nextafter(max, std::numeric_limits<float>::max());
std::uniform_real_distribution<> distribution(min, next_after_max);
float result = distribution(gen);
CHECK_LE(result, 1.0);
CHECK_GE(result, -1.0);
return result;
}
void Converter::WriteFields(const Message& msg,
const unsigned start,
const unsigned end) {
// Do basic validation on start and end. If end == 0, then write all
// fields left in msg (after start).
CHECK_GE(start, static_cast<unsigned>(1));
CHECK_GE(end, static_cast<unsigned>(0));
CHECK(start <= end || end == 0);
const Descriptor* descriptor = msg.GetDescriptor();
CHECK(descriptor);
const Reflection* reflection = msg.GetReflection();
CHECK(reflection);
int field_count = descriptor->field_count();
CHECK_LE(end, static_cast<unsigned>(field_count));
const bool write_until_last = end == 0;
const unsigned last_field_to_write = write_until_last ? field_count : end;
for (auto field_num = start; field_num <= last_field_to_write; field_num++) {
const FieldDescriptor* field_descriptor =
descriptor->FindFieldByNumber(field_num);
CHECK(field_descriptor);
const auto& tp = field_descriptor->cpp_type();
if (field_descriptor->is_repeated()) {
switch (tp) {
case FieldDescriptor::CPPTYPE_UINT32: {
const size_t num_elements =
reflection->FieldSize(msg, field_descriptor);
for (size_t idx = 0; idx < num_elements; idx++) {
WriteNum(reflection->GetRepeatedUInt32(msg, field_descriptor, idx));
}
break;
}
case FieldDescriptor::CPPTYPE_FLOAT: {
const size_t num_elements =
reflection->FieldSize(msg, field_descriptor);
for (size_t idx = 0; idx < num_elements; idx++) {
WriteNum(reflection->GetRepeatedFloat(msg, field_descriptor, idx));
}
break;
}
case FieldDescriptor::CPPTYPE_MESSAGE: {
Visit(reflection->GetRepeatedPtrField<google::protobuf::Message>(
msg, field_descriptor));
break;
}
default: { NOTREACHED(); }
}
continue;
// Skip field if it is optional and it is unset.
} else if (!field_descriptor->is_required() &&
!reflection->HasField(msg, field_descriptor)) {
continue;
}
// Field is either required or it is optional but is set, so write it:
switch (tp) {
case FieldDescriptor::CPPTYPE_INT32:
WriteNum(BoundNum(reflection->GetInt32(msg, field_descriptor)));
break;
case FieldDescriptor::CPPTYPE_UINT32:
WriteNum(BoundNum(reflection->GetUInt32(msg, field_descriptor)));
break;
case FieldDescriptor::CPPTYPE_FLOAT:
WriteNum(BoundFloat(reflection->GetFloat(msg, field_descriptor)));
break;
case FieldDescriptor::CPPTYPE_BOOL:
WriteBool(reflection->GetBool(msg, field_descriptor));
break;
case FieldDescriptor::CPPTYPE_ENUM:
WriteEnum(msg, reflection, field_descriptor);
break;
case FieldDescriptor::CPPTYPE_STRING:
WriteString(reflection->GetString(msg, field_descriptor));
break;
case FieldDescriptor::CPPTYPE_MESSAGE:
Visit(reflection->GetMessage(msg, field_descriptor));
break;
default:
NOTREACHED();
}
}
CHECK(!write_until_last ||
!descriptor->FindFieldByNumber(last_field_to_write + 1));
}
void Converter::WriteEnum(const Message& msg,
const Reflection* reflection,
const FieldDescriptor* field_descriptor) {
enum MutationState {
MORE = 1,
LESS = 2,
};
const int value = reflection->GetEnumValue(msg, field_descriptor);
if (dont_mutate_enum_) {
WriteNum(value);
return;
}
const int should_mutate = enum_mutator_chance_distribution_(rand_gen_);
if (should_mutate != MORE && should_mutate != LESS) {
// Don't mutate, just write it.
WriteNum(value);
return;
}
const EnumDescriptor* enum_descriptor = field_descriptor->enum_type();
CHECK(enum_descriptor);
const EnumValueDescriptor* min_value_descriptor = enum_descriptor->value(0);
CHECK(min_value_descriptor);
const int min_value = min_value_descriptor->number();
const int num_values = enum_descriptor->value_count();
const EnumValueDescriptor* max_value_descriptor =
enum_descriptor->value(num_values - 1);
CHECK(max_value_descriptor);
const int max_value = max_value_descriptor->number();
// If we are trying to write less than the min value, but it is 0, just write
// than the max instead.
if (should_mutate == LESS && min_value != 0) {
std::uniform_int_distribution<> value_distribution(-min_value,
min_value - 1);
const int new_value = value_distribution(rand_gen_);
CHECK_EQ(enum_descriptor->FindValueByNumber(new_value), nullptr);
WriteNum(new_value);
// Don't also write an enum that is larger than it is supposed to be.
return;
}
const int distribution_lower_bound = max_value + 1;
CHECK_GT(distribution_lower_bound, max_value);
const int distribution_upper_bound = 2 * max_value;
CHECK_GE(distribution_upper_bound, distribution_lower_bound);
std::uniform_int_distribution<> value_distribution(distribution_lower_bound,
distribution_upper_bound);
const int new_value = value_distribution(rand_gen_);
CHECK_EQ(enum_descriptor->FindValueByNumber(new_value), nullptr);
WriteNum(new_value);
}
int Converter::Abs(const int val) const {
if (val == INT_MIN)
return abs(val + 1);
return abs(val);
}
void Converter::Visit(const Vertices& vertices) {
// Note that the size is only needed when this is deserialized as part of a
// picture image filter. Since this the only way our fuzzer can deserialize
// Vertices, we always write the size.
RecordSize();
int32_t packed = vertices.mode() | kMode_Mask;
packed = packed ? !vertices.has_texs() : packed | kHasTexs_Mask;
packed = packed ? !vertices.has_colors() : packed | kHasColors_Mask;
WriteNum(packed);
WriteNum(vertices.vertex_text_colors_size());
WriteNum(vertices.indices_size());
for (auto vertex_text_color : vertices.vertex_text_colors())
Visit(vertex_text_color.vertex());
if (vertices.has_texs()) {
for (auto vertex_text_color : vertices.vertex_text_colors())
Visit(vertex_text_color.tex());
}
if (vertices.has_colors()) {
for (auto vertex_text_color : vertices.vertex_text_colors())
Visit(vertex_text_color.color());
}
WriteBytesWritten();
}
void Converter::Visit(const TextBlob& text_blob) {
Visit(text_blob.bounds());
int num_glyphs = 2 + text_blob.glyph_pos_clusters_size();
if (num_glyphs % 2 != 0)
num_glyphs--;
CHECK_EQ(num_glyphs % 2, 0);
WriteNum(num_glyphs);
WriteUInt8(text_blob.glyph_positioning());
WriteUInt8(text_blob.extended());
WriteUInt16(0); // padding
if (text_blob.extended())
WriteNum(Abs(text_blob.text_size()));
Visit(text_blob.offset());
Paint paint;
paint.CopyFrom(text_blob.paint());
paint.set_text_encoding(Paint::kGlyphID_TextEncoding);
paint.set_text_size(text_blob.text_size());
Visit(paint);
// Byte array size.
WriteNum(sizeof(uint16_t) * num_glyphs);
WriteUInt16(text_blob.glyph_pos_cluster_1().glyph());
WriteUInt16(text_blob.glyph_pos_cluster_2().glyph());
// Ensure 4-byte alignment doesn't get messed up by writing an odd number of
// glyphs.
int idx = 2;
for (auto& glyph_pos_cluster : text_blob.glyph_pos_clusters()) {
if (idx++ == num_glyphs)
break;
WriteUInt16(glyph_pos_cluster.glyph());
}
WriteNum(sizeof(float) * num_glyphs * text_blob.glyph_positioning());
idx = 2;
if (text_blob.glyph_positioning() == TextBlob::kHorizontal_Positioning) {
WriteNum(text_blob.glyph_pos_cluster_1().position_1());
WriteNum(text_blob.glyph_pos_cluster_2().position_1());
} else if (text_blob.glyph_positioning() == TextBlob::kFull_Positioning) {
WriteNum(text_blob.glyph_pos_cluster_1().position_1());
WriteNum(text_blob.glyph_pos_cluster_1().position_2());
WriteNum(text_blob.glyph_pos_cluster_2().position_1());
WriteNum(text_blob.glyph_pos_cluster_2().position_2());
}
for (auto& glyph_pos_cluster : text_blob.glyph_pos_clusters()) {
if (idx++ == num_glyphs)
break;
if (text_blob.glyph_positioning() == TextBlob::kHorizontal_Positioning) {
WriteNum(glyph_pos_cluster.position_1());
} else if (text_blob.glyph_positioning() == TextBlob::kFull_Positioning) {
WriteNum(glyph_pos_cluster.position_1());
WriteNum(glyph_pos_cluster.position_2());
}
}
if (text_blob.extended()) {
// Write clusters.
WriteNum(text_blob.glyph_pos_cluster_1().cluster());
WriteNum(text_blob.glyph_pos_cluster_2().cluster());
WriteNum(sizeof(uint32_t) * num_glyphs);
idx = 2;
for (auto& glyph_pos_cluster : text_blob.glyph_pos_clusters()) {
if (idx++ == num_glyphs)
break;
WriteNum(glyph_pos_cluster.cluster());
}
WriteArray(text_blob.text(), text_blob.text_size());
}
// No more glyphs.
WriteNum(0);
}
bool Converter::IsBlacklisted(const std::string& field_name) const {
#ifndef AVOID_MISBEHAVIOR
// Don't blacklist misbehaving flattenables.
return false;
#else
return kMisbehavedFlattenableBlacklist.find(field_name) !=
kMisbehavedFlattenableBlacklist.end();
#endif // AVOID_MISBEHAVIOR
}
}; // namespace skia_image_filter_proto_converter
// Copyright 2018 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.
#ifndef TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
#define TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
#include <random>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#include "third_party/skia/include/core/SkPoint.h"
#include "testing/libfuzzer/proto/skia_image_filter.pb.h"
using google::protobuf::FieldDescriptor;
using google::protobuf::Message;
using google::protobuf::Reflection;
typedef std::unordered_map<std::string, std::string> string_map_t;
namespace skia_image_filter_proto_converter {
// Takes an Input proto as input and converts it to a string that will usually
// be deserialized as a skia image filter.
class Converter {
public:
Converter();
Converter(const Converter&);
~Converter();
// Provides the public interface for this class's functionality by converting
// Input to a string representing a serialized image filter.
std::string Convert(const Input&);
private:
// These constexprs are copied from skia.
static constexpr uint8_t kICC_Flag = 1 << 1;
static constexpr size_t kICCTagTableEntrySize = 12;
static constexpr uint32_t kMatrix_Flag = 1 << 0;
static constexpr uint8_t k0_Version = 0;
static constexpr uint8_t kTransferFn_Flag = 1 << 3;
static constexpr size_t kLut8InputSize = 48;
static constexpr size_t kOneChannelGammasSize = 256;
static constexpr size_t kMaxLut16GammaEntries = 4096;
static constexpr uint8_t kLut8Precision = 1;
static const uint32_t kPictEofTag;
static const uint32_t kProfileLookupTable[];
static const uint32_t kInputColorSpaceLookupTable[];
static const uint32_t kPCSLookupTable[];
static const uint32_t kTagLookupTable[];
static const char kPictureMagicString[];
static const char kSkPictReaderTag[];
static const uint8_t kCountNibBits[];
// The size of kColorTableBuffer.
static const int kColorTableBufferLength;
// Used to bound flattenable_depth_.
static const int kFlattenableDepthLimit;
// Used to bound numeric fields.
static const int kNumBound;
// Used by ColorTableToArray to store a ColorTable Message as an array.
static uint8_t kColorTableBuffer[];
// There will be a 1/kMutateEnumDenominator chance that WriteEnum
// writes an invalid enum value instead of the one given to us by LPM.
// This must be greater than 1.
static const uint8_t kMutateEnumDenominator;
// Mapping of field names to types.
static const string_map_t kFieldToFlattenableName;
// Used by IsBlacklisted to determine which skia flattenable should not be
// serialized.
static const std::set<std::string> kMisbehavedFlattenableBlacklist;
// Probably the most important attribute, a char vector that contains
// serialized skia flattenable written by the Visit functions. The contents of
// output_ is returned by Convert().
std::vector<char> output_;
// Stores the size of output_ when a skia flattenable is being written (since
// they need to store their size).
std::vector<size_t> start_sizes_;
// Keep a record of whether we used kStrokeAndFill_Style or kStroke_Style
// already since using them multiple times increases the risk of OOMs and
// timeouts.
bool stroke_style_used_;
// Used to keep track of how nested are the skia flattenables we are writing.
// We use this to limit nesting to avoid OOMs and timeouts.
int flattenable_depth_;
// Nesting PairPathEffects is particularly likely to cause OOMs and timeouts
// so limit this even more than other skia flattenables.
int pair_path_effect_depth_;
// Don't allow ComposeColorFilters to contain themselves or else LPM will go
// crazy and nest them to the point that it causes OOMs and timeouts (these
// filters are more likely than other skia flattenables to cause these
// problems).
bool in_compose_color_filter_;
// Used to generate random numbers (for replacing NaNs, and mutating enum
// values).
std::mt19937 rand_gen_;
// A distribution from 2-kMutateEnumDenominator that will be used
// to generate a random value that we will use to decide if an enum value
// should be written as-is or if it should be mutated.
// The reason why there is a 2/kMutateEnumDenominator chance rather than a
// 1/kMutateEnumDenominator chance is because we treat making an enum value
// too small as a separate case from making it too big.
std::uniform_int_distribution<> enum_mutator_chance_distribution_;
// Prevents WriteEnum from writing an invalid enum value instead of the one it
// was given.
bool dont_mutate_enum_;
// BoundNum and BoundFloat will only return positive numbers when this is
// true.
bool bound_positive_;
// In production we don't need attributes used by ICC code since it is not
// built for production code.
#ifdef DEVELOPMENT
uint32_t icc_base_;
int tag_offset_;
#endif // DEVELOPMENT
// Reset any state used by the flattenable so that is can be used to convert
// another proto input.
void Reset();
void Visit(const PictureImageFilter&);
void Visit(const Picture&);
void Visit(const PictureTagChild&);
// The generic Visit function. The compiler will allow this to be called on
// any proto message, though this won't result in a correct conversion.
// However some very simple messages have contents that can pretty much be
// written as they are defined by the fields of msg (eg "DistantLight"). This
// method is intended for those messages and will properly convert
// those. Note that this method is viral in that any fields on msg that
// contain other messages will only be written using this method and
// WriteFields. For example, "DistantLight" has a field "direction" that
// contains a "Point3" message. It is OK to call this on "DistantLight"
// messages because it is OK to call this on "Point3". In essence, it
// is only correct to call this method on msg if it is correct to call this
// method on any fields (or fields of fields etc.) of msg that are also
// Messages. See the file comment on skia_image_filter.proto for an
// explanation of how this and WriteFields are used for autovisit.
void Visit(const Message& msg);
void Visit(const PictureData&);
void Visit(const RecordingData&);
void Visit(const LightParent&);
void Visit(const ImageFilterChild&);
void Visit(const ImageFilterParent&, const int num_inputs_required);
void Visit(const MatrixImageFilter&);
void Visit(const Matrix&, bool is_local = false);
void Visit(const SpecularLightingImageFilter&);
void Visit(const PaintImageFilter&);
void Visit(const Paint&);
void Visit(const PaintEffects&);
void Visit(const PathEffectChild&);
void Visit(const LooperChild&);
void Visit(const LayerDrawLooper&);
void Visit(const LayerInfo&);
void Visit(const ColorFilterChild&);
void Visit(const ComposeColorFilter&);
void Visit(const OverdrawColorFilter&);
void Visit(const ToSRGBColorFilter&);
void Visit(const ColorFilterMatrix&);
void Visit(const ColorMatrixFilterRowMajor255&);
void Visit(const MergeImageFilter&);
void Visit(const XfermodeImageFilter&);
void Visit(const DiffuseLightingImageFilter&);
void Visit(const XfermodeImageFilter_Base&);
void Visit(const TileImageFilter&);
void Visit(const OffsetImageFilter&);
void Visit(const ErodeImageFilter&);
void Visit(const DilateImageFilter&);
void Visit(const DiscretePathEffect&);
void Visit(const MatrixConvolutionImageFilter&);
void Visit(const MagnifierImageFilter&);
void Visit(const LocalMatrixImageFilter&);
void Visit(const ImageSource&);
void Visit(const Path&);
void Visit(const PathRef&);
void Visit(const DropShadowImageFilter&);
void Visit(const DisplacementMapEffect&);
void Visit(const ComposeImageFilter&);
void Visit(const ColorFilterImageFilter&);
void Visit(const BlurImageFilterImpl&);
void Visit(const AlphaThresholdFilterImpl&);
void Visit(const Region&);
void Visit(const Path1DPathEffect&);
// Writes the correct PairPathEffect skia flattenable (SkSumPathEffect or
// SkComposePathEffect) depending on pair.type(). Note that it writes the
// entire skia flattenable (including name and size) unlike most Visit
// functions and thus should not be be preceded by a call to
// PreVisitFlattenable, nor should it be followed by a call to
// PostVisitFlattenable.
void Visit(const PairPathEffect&);
void Visit(const ShaderChild&);
void Visit(const Color4Shader&);
void Visit(const GradientDescriptor&);
void Visit(const GradientParent&);
void Visit(const TwoPointConicalGradient&);
void Visit(const LinearGradient&);
void Visit(const SweepGradient&);
void Visit(const RadialGradient&);
void Visit(const PictureShader&);
void Visit(const LocalMatrixShader&);
void Visit(const ComposeShader&);
void Visit(const ColorFilterShader&);
void Visit(const ImageShader&);
void Visit(const Color4f&);
void Visit(const Image&);
void Visit(const ImageData&);
void Visit(const ColorSpaceChild&);
void Visit(const ColorSpace_XYZ&);
void Visit(const ColorSpaceNamed&);
void Visit(const TransferFn&);
void Visit(const MaskFilterChild&);
void Visit(const Table_ColorFilter&);
void Visit(const EmbossMaskFilter&);
void Visit(const EmbossMaskFilterLight&);
void Visit(const DashImpl&);
void Visit(const Path2DPathEffect&);
void Visit(const ArithmeticImageFilter&);
void Visit(const LightChild&);
void Visit(const CropRectangle&);
void Visit(const Rectangle&);
void Visit(const RRectsGaussianEdgeMaskFilterImpl&);
void Visit(const PictureInfo&);
void Visit(const BlurMaskFilter&);
void Visit(const HighContrast_Filter&);
void Visit(const ReaderPictureTag&);
void Visit(const Vertices&);
void Visit(const TextBlob&);
template <class T>
void Visit(const google::protobuf::RepeatedPtrField<T>& repeated_field);
void VisitPictureTag(const PathPictureTag& path_picture_tag, uint32_t tag);
void VisitPictureTag(const PaintPictureTag& paint_picture_tag, uint32_t tag);
template <class T>
void VisitPictureTag(const T& picture_tag_child, uint32_t tag);
// Returns false if there is too much nesting (determined by
// kFlattenableDepthLimit and flattenable_depth_). Writes name and reserves a
// space to write the size of the flattenable. Also increments
// flattenable_depth_.
bool PreVisitFlattenable(const std::string& name);
// Writes the size of the flattenable to the reserved space, ensures that
// output_ is four byte aligned and then decrements flattenable_depth_.
void PostVisitFlattenable();
std::tuple<int32_t, int32_t, int32_t, int32_t> WriteNonEmptyIRect(
const IRect& irect);
void WriteColorSpaceVersion();
// Write a string in the proper serialized format, padding if necessary.
void WriteString(std::string str);
// Get the size of a skia flattenable that was just written and insert it at
// the proper location. Every call to this method should have a corresponding
// call to RecordSize.
void WriteBytesWritten();
// Reserves space to write the size of what we are serializing and records
// info so that WriteBytesWritten can determine the size. Every call to this
// method should have a corresponding call to WriteBytesWritten that it is
// followed by.
void RecordSize();
// Write size to position in output_.
void InsertSize(const size_t size, const uint32_t position);
// Pops off the end of start_sizes_.
size_t PopStartSize();
// Pad the write_size bytes that were written with zeroes so that the
// write_size + number of padding bytes is divisible by four.
void Pad(const size_t write_size);
// Write size elements of RepeatedField of uint32_ts repeated_field as an
// array.
void WriteArray(
const google::protobuf::RepeatedField<uint32_t>& repeated_field,
const size_t size);
// Write size bytes of arr as an array and pad if necessary.
void WriteArray(const char* arr, const size_t size);
void WriteBool(const bool bool_val);
// Write the fields of msg starting with the field whose number is start and
// ending with the field whose number is end. If end is 0 then all fields
// until the last one will be written. start defaults to 1 and end defaults to
// 0. Note that not all possible (eg repeated bools) fields are supported,
// consult the code to determine if the field is supported (an error will be
// thrown if a field is unsupported). Note that WriteFields is viral in that
// if msg contains a field containing another Message, let's say msg2, then
// WriteFields(msg2) will be called (assuming that fields is in the range of
// msgs we are writing). If there is a method defined Visit(const Message2&
// msg2), it will not be called because there is no simple way to determine
// the type of msg2 using protobuf's reflection API. WriteFields will bound
// any numeric fields before writing them to avoid OOMs and timeouts. See the
// file comment on skia_image_filter.proto for an explanation of how this and
// the generic Visit function are used for autovisit. Note that this may write
// invalid enum values instead of the ones provided if dont_mutate_enum_ is
// true. Note that this method may not work if the max field of msg has number
// that is different than the number of fields. For example, if msg does not
// have a field with field number 1, but has fields with field numbers 2 and
// 3, calling WriteFields(msg, 2) will not write field 3.
void WriteFields(const Message& msg,
const unsigned start = 1,
const unsigned end = 0);
// Given the name of a proto field, field_name returns the name of the
// flattenable skia flattenable object it represents.
std::string FieldToFlattenableName(const std::string& field_name) const;
void CheckAlignment() const;
// Append our proto Message proto_point to sk_points as an SkPoint.
void AppendAsSkPoint(std::vector<SkPoint>& sk_points,
const Point& proto_point) const;
template <typename T>
void WriteUInt8(T num);
void WriteUInt16(uint16_t num);
void WriteNum(const uint64_t num);
void WriteNum(const char (&num_arr)[4]);
// Write num as a number. Assumes num is four bytes or less.
template <class T>
void WriteNum(T num);
// Write the enum value described by field descriptor and stored on message,
// or write an invalid enum value with some probability if dont_mutate_enums_
// is false.
void WriteEnum(const Message& msg,
const Reflection* reflection,
const FieldDescriptor* field_descriptor);
// Bound a num using num_bound so that it won't cause OOMs or timeouts.
template <typename T>
T BoundNum(T num, int upper_bound) const;
// kNumBound cant be a default parameter to BoundNum(T num, int upper_bound)
// so this function exists instead.
template <typename T>
T BoundNum(T num);
// kNumBound cant be a default parameter to BoundFloat(T num, int upper_bound)
// so this function exists instead.
float BoundFloat(float num);
// Bound a float num using kNumBound so that it won't cause OOMs or
// timeouts.
float BoundFloat(float num, const float num_bound);
// Convert input_num to a uint8_t.
template <typename T>
uint8_t ToUInt8(const T input_num) const;
float GetRandomFloat(std::mt19937* gen_ptr);
float GetRandomFloat(float seed, float min, float max);
// Write a sane value of field_value, which should be the value of a field of
// a matrix.
void WriteMatrixField(float field_value);
// Saturating wrapper for stdlib.h's abs, which has undefined behavior when
// given INT_MIN. This returns abs(INT_MIN+1) if val is INT_MIN.
int Abs(const int val) const;
// Writes the representation of a rectangle returned by GetValidRectangle.
template <typename T>
void WriteRectangle(std::tuple<T, T, T, T> rectangle);
// Bound the points making up a rectangle so that the returned tuple is a
// valid rectangle.
std::tuple<float, float, float, float> GetValidRectangle(float left,
float top,
float right,
float bottom);
std::tuple<int32_t, int32_t, int32_t, int32_t> GetValidIRect(int32_t left,
int32_t top,
int32_t right,
int32_t bottom);
bool IsFinite(float num) const;
bool IsBlacklisted(const std::string& field_name) const;
// Converts color_table from our proto Message format to a 256-byte array.
// Note that this function modifies kColorTableBuffer.
const uint8_t* ColorTableToArray(const ColorTable& color_table);
#ifdef DEVELOPMENT
// ICC related functions
void Visit(const ICC&);
void Visit(const ICCColorSpace&);
void Visit(const ICCXYZ&);
void Visit(const ICCGray&);
void Visit(const ICCA2B0&);
void Visit(const ICCA2B0AToB&);
void Visit(const ICCA2B0Lut8&);
void Visit(const ICCA2B0Lut16&);
uint8_t GetClutGridPoints(const ICCA2B0Lut8&);
uint32_t GetCurrentICCOffset();
uint32_t GetLut8Size(const ICCA2B0Lut8&);
uint32_t GetLut16Size(const ICCA2B0Lut16&);
void WriteLut8(const ICCA2B0Lut8&);
void WriteA2B0TagCommon();
void WriteTagSize(const char (&tag)[4], const size_t size);
template <typename T>
void WriteBigEndian(const T num);
void WriteTagHeader(const uint32_t tag, const uint32_t len);
// Write num_fields zeroes to fill the space used by ignored or reserved
// fields in an ICC ColorSpace.
void WriteIgnoredFields(const int num_fields);
// Bound illuminant using num to avoid OOMs and timeouts.
int32_t BoundIlluminant(float illuminant, const float num) const;
// ImageInfo related functions
void Visit(const ImageInfo&, const int32_t width, const int32_t height);
std::tuple<int32_t, int32_t, int32_t>
GetNumPixelBytes(const ImageInfo& image_info, int32_t width, int32_t height);
size_t ComputeMinByteSize(int32_t width,
int32_t height,
ImageInfo::AlphaType alpha_type) const;
#endif // DEVELOPMENT
};
}; // namespace skia_image_filter_proto_converter
#endif // TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
......@@ -26,7 +26,10 @@ source_set("libprotobuf-mutator") {
"src/src/text_format.cc",
"src/src/utf8_fix.cc",
]
deps = [
# Allow users of LPM to use protobuf reflection and other features from
# protobuf_full.
public_deps = [
"//third_party/protobuf:protobuf_full",
]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment