1 /*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 *
7 * This test confirms that a multi skp can be serialize and deserailzied without error.
8 */
9
10 #include "include/core/SkDocument.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkPicture.h"
13 #include "include/core/SkString.h"
14 #include "include/core/SkSurface.h"
15 #include "include/core/SkTextBlob.h"
16 #include "src/core/SkRecord.h"
17 #include "src/core/SkRecorder.h"
18 #include "src/utils/SkMultiPictureDocument.h"
19 #include "tests/Test.h"
20 #include "tools/SkSharingProc.h"
21
22 namespace {
23
24 class RecordVisitor {
25 // An SkRecord visitor that remembers the name of the last visited command.
26 public:
27 SkString name;
28
RecordVisitor()29 explicit RecordVisitor() {}
30
31 template <typename T>
operator ()(const T & command)32 void operator()(const T& command) {
33 name = SkString(NameOf(command));
34 }
35
lastCommandName()36 SkString lastCommandName() {
37 return name;
38 }
39 private:
40 template <typename T>
NameOf(const T &)41 static const char* NameOf(const T&) {
42 #define CASE(U) case SkRecords::U##_Type: return #U;
43 switch (T::kType) { SK_RECORD_TYPES(CASE) }
44 #undef CASE
45 return "Unknown T";
46 }
47 };
48 } // namespace
49
50 // Compare record tested with record expected. Assert op sequence is the same (comparing types)
51 // frame_num is only used for error message.
compareRecords(const SkRecord & tested,const SkRecord & expected,int frame_num,skiatest::Reporter * reporter)52 static void compareRecords(const SkRecord& tested, const SkRecord& expected,
53 int frame_num, skiatest::Reporter* reporter) {
54 REPORTER_ASSERT(reporter, tested.count() == expected.count(),
55 "Found %d commands in frame %d, expected %d", tested.count(), frame_num, expected.count());
56
57 RecordVisitor rv;
58 for (int i = 0; i < tested.count(); i++) {
59 tested.visit(i, rv);
60 const SkString testCommandName = rv.lastCommandName();
61 expected.visit(i, rv);
62 const SkString expectedCommandName = rv.lastCommandName();
63 REPORTER_ASSERT(reporter, testCommandName == expectedCommandName,
64 "Unexpected command type '%s' in frame %d, op %d. Expected '%s'",
65 testCommandName.c_str(), frame_num, i, expectedCommandName.c_str());
66 }
67 }
68
draw_something(SkCanvas * canvas,int seed,sk_sp<SkImage> image)69 static void draw_something(SkCanvas* canvas, int seed, sk_sp<SkImage> image) {
70 canvas->drawColor(SK_ColorWHITE);
71
72 SkPaint paint;
73 paint.setStyle(SkPaint::kStroke_Style);
74 paint.setStrokeWidth(seed);
75 paint.setColor(SK_ColorRED);
76
77 SkRect rect = SkRect::MakeXYWH(50+seed, 50+seed, 4*seed, 60);
78 canvas->drawRect(rect, paint);
79
80 SkRRect oval;
81 oval.setOval(rect);
82 oval.offset(40, 60+seed);
83 paint.setColor(SK_ColorBLUE);
84 canvas->drawRRect(oval, paint);
85
86 paint.setColor(SK_ColorCYAN);
87 canvas->drawCircle(180, 50, 5*seed, paint);
88
89 rect.offset(80, 0);
90 paint.setColor(SK_ColorYELLOW);
91 canvas->drawRoundRect(rect, 10, 10, paint);
92
93 SkPath path;
94 path.cubicTo(768, 0, -512, 256, 256, 256);
95 paint.setColor(SK_ColorGREEN);
96 canvas->drawPath(path, paint);
97
98 canvas->drawImage(image, 128-seed, 128, &paint);
99
100 if (seed % 2 == 0) {
101 SkRect rect2 = SkRect::MakeXYWH(0, 0, 40, 60);
102 canvas->drawImageRect(image, rect2, &paint);
103 }
104
105 SkPaint paint2;
106 auto text = SkTextBlob::MakeFromString(
107 SkStringPrintf("Frame %d", seed).c_str(), SkFont(nullptr, 2+seed));
108 canvas->drawTextBlob(text.get(), 50, 25, paint2);
109 }
110
111 // Test serialization and deserialization of multi skp.
DEF_TEST(Serialize_and_deserialize_multi_skp,reporter)112 DEF_TEST(Serialize_and_deserialize_multi_skp, reporter) {
113 // Create the stream we will serialize into.
114 SkDynamicMemoryWStream stream;
115
116 // Create the image sharing proc.
117 SkSharingSerialContext ctx;
118 SkSerialProcs procs;
119 procs.fImageProc = SkSharingSerialContext::serializeImage;
120 procs.fImageCtx = &ctx;
121
122 // Create the mulit picture document used for recording frames.
123 sk_sp<SkDocument> multipic = SkMakeMultiPictureDocument(&stream, &procs);
124
125 static const int NUM_FRAMES = 12;
126 static const int WIDTH = 256;
127 static const int HEIGHT = 256;
128
129 // Make an image to be used in a later step.
130 auto surface(SkSurface::MakeRasterN32Premul(100, 100));
131 surface->getCanvas()->clear(SK_ColorGREEN);
132 sk_sp<SkImage> image(surface->makeImageSnapshot());
133 REPORTER_ASSERT(reporter, image);
134
135 // Create frames, recording them to multipic.
136 SkRecord expectedRecords[NUM_FRAMES];
137 for (int i=0; i<NUM_FRAMES; i++) {
138 SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
139 draw_something(pictureCanvas, i, image);
140 multipic->endPage();
141 // Also record the same commands to separate SkRecords for later comparison
142 SkRecorder canvas(&expectedRecords[i], WIDTH, HEIGHT);
143 draw_something(&canvas, i, image);
144 }
145 // Finalize
146 multipic->close();
147
148 // Confirm written data is at least as large as the magic word
149 std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream();
150 REPORTER_ASSERT(reporter, writtenStream->getLength() > 24,
151 "Written data length too short (%d)", writtenStream->getLength());
152 SkDebugf("Multi Frame file size = %d\n", writtenStream->getLength());
153
154 // Set up deserialization
155 SkSharingDeserialContext deserialContext;
156 SkDeserialProcs dprocs;
157 dprocs.fImageProc = SkSharingDeserialContext::deserializeImage;
158 dprocs.fImageCtx = &deserialContext;
159
160 // Confirm data is a MultiPictureDocument
161 int frame_count = SkMultiPictureDocumentReadPageCount(writtenStream.get());
162 REPORTER_ASSERT(reporter, frame_count == NUM_FRAMES,
163 "Expected %d frames, got %d. \n 0 frames may indicate the written file was not a "
164 "MultiPictureDocument.", NUM_FRAMES, frame_count);
165
166 // Deserailize
167 std::vector<SkDocumentPage> frames(frame_count);
168 REPORTER_ASSERT(reporter,
169 SkMultiPictureDocumentRead(writtenStream.get(), frames.data(), frame_count, &dprocs),
170 "Failed while reading MultiPictureDocument");
171
172 // Examine each frame.
173 SkRecorder resultRecorder(nullptr, 1, 1);
174 int i=0;
175 for (const auto& frame : frames) {
176 SkRect bounds = frame.fPicture->cullRect();
177 REPORTER_ASSERT(reporter, bounds.width() == WIDTH,
178 "Page width: expected (%d) got (%d)", WIDTH, bounds.width());
179 REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
180 "Page height: expected (%d) got (%d)", HEIGHT, bounds.height());
181 // confirm contents of picture match what we drew.
182 // There are several ways of doing this, an ideal comparison would not break in the same
183 // way at the same time as the code under test (no serialization), and would involve only
184 // minimal transformation of frame.fPicture, minimizing the chance that a detected fault lies
185 // in the test itself. The comparions also would not be an overly sensitive change detector,
186 // so that it doesn't break every time someone submits code (no golden file)
187
188 // Extract the SkRecord from the deserialized picture using playback (instead of a mess of
189 // friend classes to grab the private record inside frame.fPicture
190 SkRecord record;
191 // This picture mode is necessary so that we record the command contents of frame.fPicture
192 // not just a 'DrawPicture' command.
193 resultRecorder.reset(&record, bounds, SkRecorder::Playback_DrawPictureMode, nullptr);
194 frame.fPicture->playback(&resultRecorder);
195 // Compare the record to the expected one
196 compareRecords(record, expectedRecords[i], i, reporter);
197 i++;
198 }
199 }
200