blob: 28830c0661f4c5ce163aa83c22c95b7c188b6d27 [file] [log] [blame]
Nathaniel Nifong0426c382019-06-21 11:09:19 -04001/*
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 *
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -04007 * This test confirms that a MultiPictureDocument can be serialized and deserailzied without error.
8 * And that the pictures within it are re-created accurately
Nathaniel Nifong0426c382019-06-21 11:09:19 -04009 */
10
11#include "include/core/SkDocument.h"
Nathaniel Nifong0426c382019-06-21 11:09:19 -040012#include "include/core/SkFont.h"
Mike Klein52337de2019-07-25 09:00:52 -050013#include "include/core/SkPicture.h"
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -040014#include "include/core/SkPictureRecorder.h"
Mike Klein52337de2019-07-25 09:00:52 -050015#include "include/core/SkString.h"
16#include "include/core/SkSurface.h"
17#include "include/core/SkTextBlob.h"
Nathaniel Nifong0426c382019-06-21 11:09:19 -040018#include "src/core/SkRecord.h"
19#include "src/core/SkRecorder.h"
20#include "src/utils/SkMultiPictureDocument.h"
Nathaniel Nifong0426c382019-06-21 11:09:19 -040021#include "tests/Test.h"
Mike Klein52337de2019-07-25 09:00:52 -050022#include "tools/SkSharingProc.h"
Nathaniel Nifong0426c382019-06-21 11:09:19 -040023
24namespace {
25
26class RecordVisitor {
27// An SkRecord visitor that remembers the name of the last visited command.
28public:
29 SkString name;
30
31 explicit RecordVisitor() {}
32
33 template <typename T>
34 void operator()(const T& command) {
35 name = SkString(NameOf(command));
36 }
37
38 SkString lastCommandName() {
39 return name;
40 }
41private:
42 template <typename T>
43 static const char* NameOf(const T&) {
44 #define CASE(U) case SkRecords::U##_Type: return #U;
45 switch (T::kType) { SK_RECORD_TYPES(CASE) }
46 #undef CASE
47 return "Unknown T";
48 }
49};
50} // namespace
51
52// Compare record tested with record expected. Assert op sequence is the same (comparing types)
53// frame_num is only used for error message.
54static void compareRecords(const SkRecord& tested, const SkRecord& expected,
55 int frame_num, skiatest::Reporter* reporter) {
56 REPORTER_ASSERT(reporter, tested.count() == expected.count(),
57 "Found %d commands in frame %d, expected %d", tested.count(), frame_num, expected.count());
58
59 RecordVisitor rv;
60 for (int i = 0; i < tested.count(); i++) {
61 tested.visit(i, rv);
62 const SkString testCommandName = rv.lastCommandName();
63 expected.visit(i, rv);
64 const SkString expectedCommandName = rv.lastCommandName();
65 REPORTER_ASSERT(reporter, testCommandName == expectedCommandName,
66 "Unexpected command type '%s' in frame %d, op %d. Expected '%s'",
67 testCommandName.c_str(), frame_num, i, expectedCommandName.c_str());
68 }
69}
70
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -040071// Covers rects, ovals, paths, images, text
72static void draw_basic(SkCanvas* canvas, int seed, sk_sp<SkImage> image) {
Nathaniel Nifong0426c382019-06-21 11:09:19 -040073 canvas->drawColor(SK_ColorWHITE);
74
75 SkPaint paint;
76 paint.setStyle(SkPaint::kStroke_Style);
77 paint.setStrokeWidth(seed);
78 paint.setColor(SK_ColorRED);
79
80 SkRect rect = SkRect::MakeXYWH(50+seed, 50+seed, 4*seed, 60);
81 canvas->drawRect(rect, paint);
82
83 SkRRect oval;
84 oval.setOval(rect);
85 oval.offset(40, 60+seed);
86 paint.setColor(SK_ColorBLUE);
87 canvas->drawRRect(oval, paint);
88
89 paint.setColor(SK_ColorCYAN);
90 canvas->drawCircle(180, 50, 5*seed, paint);
91
92 rect.offset(80, 0);
93 paint.setColor(SK_ColorYELLOW);
94 canvas->drawRoundRect(rect, 10, 10, paint);
95
96 SkPath path;
97 path.cubicTo(768, 0, -512, 256, 256, 256);
98 paint.setColor(SK_ColorGREEN);
99 canvas->drawPath(path, paint);
100
101 canvas->drawImage(image, 128-seed, 128, &paint);
102
103 if (seed % 2 == 0) {
104 SkRect rect2 = SkRect::MakeXYWH(0, 0, 40, 60);
105 canvas->drawImageRect(image, rect2, &paint);
106 }
107
108 SkPaint paint2;
109 auto text = SkTextBlob::MakeFromString(
110 SkStringPrintf("Frame %d", seed).c_str(), SkFont(nullptr, 2+seed));
111 canvas->drawTextBlob(text.get(), 50, 25, paint2);
112}
113
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -0400114// Covers all of the above and drawing nested sub-pictures.
115static void draw_advanced(SkCanvas* canvas, int seed, sk_sp<SkImage> image, sk_sp<SkPicture> sub) {
116 draw_basic(canvas, seed, image);
117
118 // Use subpicture twice in different places
119 canvas->drawPicture(sub);
120 canvas->save();
121 canvas->translate(seed, seed);
122 canvas->drawPicture(sub);
123 canvas->restore();
124}
125
126// Test serialization and deserialization of multi picture document
Nathaniel Nifong0426c382019-06-21 11:09:19 -0400127DEF_TEST(Serialize_and_deserialize_multi_skp, reporter) {
128 // Create the stream we will serialize into.
129 SkDynamicMemoryWStream stream;
130
131 // Create the image sharing proc.
132 SkSharingSerialContext ctx;
133 SkSerialProcs procs;
134 procs.fImageProc = SkSharingSerialContext::serializeImage;
135 procs.fImageCtx = &ctx;
136
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -0400137 // Create the multi picture document used for recording frames.
Nathaniel Nifong0426c382019-06-21 11:09:19 -0400138 sk_sp<SkDocument> multipic = SkMakeMultiPictureDocument(&stream, &procs);
139
140 static const int NUM_FRAMES = 12;
141 static const int WIDTH = 256;
142 static const int HEIGHT = 256;
143
144 // Make an image to be used in a later step.
145 auto surface(SkSurface::MakeRasterN32Premul(100, 100));
146 surface->getCanvas()->clear(SK_ColorGREEN);
147 sk_sp<SkImage> image(surface->makeImageSnapshot());
148 REPORTER_ASSERT(reporter, image);
149
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -0400150 // Make a subpicture to be used in a later step
151 SkPictureRecorder pr;
152 SkCanvas* subCanvas = pr.beginRecording(100, 100);
153 draw_basic(subCanvas, 42, image);
154 sk_sp<SkPicture> sub = pr.finishRecordingAsPicture();
155
Nathaniel Nifong0426c382019-06-21 11:09:19 -0400156 // Create frames, recording them to multipic.
157 SkRecord expectedRecords[NUM_FRAMES];
158 for (int i=0; i<NUM_FRAMES; i++) {
159 SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -0400160 draw_advanced(pictureCanvas, i, image, sub);
Nathaniel Nifong0426c382019-06-21 11:09:19 -0400161 multipic->endPage();
162 // Also record the same commands to separate SkRecords for later comparison
163 SkRecorder canvas(&expectedRecords[i], WIDTH, HEIGHT);
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -0400164 draw_advanced(&canvas, i, image, sub);
Nathaniel Nifong0426c382019-06-21 11:09:19 -0400165 }
166 // Finalize
167 multipic->close();
168
169 // Confirm written data is at least as large as the magic word
170 std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream();
171 REPORTER_ASSERT(reporter, writtenStream->getLength() > 24,
172 "Written data length too short (%d)", writtenStream->getLength());
173 SkDebugf("Multi Frame file size = %d\n", writtenStream->getLength());
174
175 // Set up deserialization
176 SkSharingDeserialContext deserialContext;
177 SkDeserialProcs dprocs;
178 dprocs.fImageProc = SkSharingDeserialContext::deserializeImage;
179 dprocs.fImageCtx = &deserialContext;
180
181 // Confirm data is a MultiPictureDocument
182 int frame_count = SkMultiPictureDocumentReadPageCount(writtenStream.get());
183 REPORTER_ASSERT(reporter, frame_count == NUM_FRAMES,
184 "Expected %d frames, got %d. \n 0 frames may indicate the written file was not a "
185 "MultiPictureDocument.", NUM_FRAMES, frame_count);
186
187 // Deserailize
188 std::vector<SkDocumentPage> frames(frame_count);
189 REPORTER_ASSERT(reporter,
190 SkMultiPictureDocumentRead(writtenStream.get(), frames.data(), frame_count, &dprocs),
191 "Failed while reading MultiPictureDocument");
192
193 // Examine each frame.
194 SkRecorder resultRecorder(nullptr, 1, 1);
195 int i=0;
196 for (const auto& frame : frames) {
197 SkRect bounds = frame.fPicture->cullRect();
198 REPORTER_ASSERT(reporter, bounds.width() == WIDTH,
199 "Page width: expected (%d) got (%d)", WIDTH, bounds.width());
200 REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
201 "Page height: expected (%d) got (%d)", HEIGHT, bounds.height());
202 // confirm contents of picture match what we drew.
203 // There are several ways of doing this, an ideal comparison would not break in the same
204 // way at the same time as the code under test (no serialization), and would involve only
205 // minimal transformation of frame.fPicture, minimizing the chance that a detected fault lies
206 // in the test itself. The comparions also would not be an overly sensitive change detector,
207 // so that it doesn't break every time someone submits code (no golden file)
208
209 // Extract the SkRecord from the deserialized picture using playback (instead of a mess of
210 // friend classes to grab the private record inside frame.fPicture
211 SkRecord record;
212 // This picture mode is necessary so that we record the command contents of frame.fPicture
Nathaniel Nifong7ee3f932019-09-25 14:52:44 -0400213 // not just a 'DrawPicture' command, but don't record pictures within it. We want to assert
214 // that the code under test reffed them like it should have.
215 resultRecorder.reset(&record, bounds, SkRecorder::PlaybackTop_DrawPictureMode, nullptr);
Nathaniel Nifong0426c382019-06-21 11:09:19 -0400216 frame.fPicture->playback(&resultRecorder);
217 // Compare the record to the expected one
218 compareRecords(record, expectedRecords[i], i, reporter);
219 i++;
220 }
221}