blob: 5d422ed557ab55112ef5cf341a667f669c5dbe49 [file] [log] [blame]
* Copyright 2013 Google Inc.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
* This test confirms that a MultiPictureDocument can be serialized and deserailzied without error.
* And that the pictures within it are re-created accurately
#include "include/core/SkDocument.h"
#include "include/core/SkFont.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "src/core/SkRecord.h"
#include "src/core/SkRecorder.h"
#include "src/utils/SkMultiPictureDocument.h"
#include "tests/Test.h"
#include "tools/SkSharingProc.h"
namespace {
class RecordVisitor {
// An SkRecord visitor that remembers the name of the last visited command.
SkString name;
explicit RecordVisitor() {}
template <typename T>
void operator()(const T& command) {
name = SkString(NameOf(command));
SkString lastCommandName() {
return name;
template <typename T>
static const char* NameOf(const T&) {
#define CASE(U) case SkRecords::U##_Type: return #U;
switch (T::kType) { SK_RECORD_TYPES(CASE) }
#undef CASE
return "Unknown T";
} // namespace
// Compare record tested with record expected. Assert op sequence is the same (comparing types)
// frame_num is only used for error message.
static void compareRecords(const SkRecord& tested, const SkRecord& expected,
int frame_num, skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, tested.count() == expected.count(),
"Found %d commands in frame %d, expected %d", tested.count(), frame_num, expected.count());
RecordVisitor rv;
for (int i = 0; i < tested.count(); i++) {
tested.visit(i, rv);
const SkString testCommandName = rv.lastCommandName();
expected.visit(i, rv);
const SkString expectedCommandName = rv.lastCommandName();
REPORTER_ASSERT(reporter, testCommandName == expectedCommandName,
"Unexpected command type '%s' in frame %d, op %d. Expected '%s'",
testCommandName.c_str(), frame_num, i, expectedCommandName.c_str());
// Covers rects, ovals, paths, images, text
static void draw_basic(SkCanvas* canvas, int seed, sk_sp<SkImage> image) {
SkPaint paint;
SkRect rect = SkRect::MakeXYWH(50+seed, 50+seed, 4*seed, 60);
canvas->drawRect(rect, paint);
SkRRect oval;
oval.offset(40, 60+seed);
canvas->drawRRect(oval, paint);
canvas->drawCircle(180, 50, 5*seed, paint);
rect.offset(80, 0);
canvas->drawRoundRect(rect, 10, 10, paint);
SkPath path;
path.cubicTo(768, 0, -512, 256, 256, 256);
canvas->drawPath(path, paint);
canvas->drawImage(image, 128-seed, 128, &paint);
if (seed % 2 == 0) {
SkRect rect2 = SkRect::MakeXYWH(0, 0, 40, 60);
canvas->drawImageRect(image, rect2, &paint);
SkPaint paint2;
auto text = SkTextBlob::MakeFromString(
SkStringPrintf("Frame %d", seed).c_str(), SkFont(nullptr, 2+seed));
canvas->drawTextBlob(text.get(), 50, 25, paint2);
// Covers all of the above and drawing nested sub-pictures.
static void draw_advanced(SkCanvas* canvas, int seed, sk_sp<SkImage> image, sk_sp<SkPicture> sub) {
draw_basic(canvas, seed, image);
// Use subpicture twice in different places
canvas->translate(seed, seed);
// Test serialization and deserialization of multi picture document
DEF_TEST(Serialize_and_deserialize_multi_skp, reporter) {
// Create the stream we will serialize into.
SkDynamicMemoryWStream stream;
// Create the image sharing proc.
SkSharingSerialContext ctx;
SkSerialProcs procs;
procs.fImageProc = SkSharingSerialContext::serializeImage;
procs.fImageCtx = &ctx;
// Create the multi picture document used for recording frames.
sk_sp<SkDocument> multipic = SkMakeMultiPictureDocument(&stream, &procs);
static const int NUM_FRAMES = 12;
static const int WIDTH = 256;
static const int HEIGHT = 256;
// Make an image to be used in a later step.
auto surface(SkSurface::MakeRasterN32Premul(100, 100));
sk_sp<SkImage> image(surface->makeImageSnapshot());
REPORTER_ASSERT(reporter, image);
// Make a subpicture to be used in a later step
SkPictureRecorder pr;
SkCanvas* subCanvas = pr.beginRecording(100, 100);
draw_basic(subCanvas, 42, image);
sk_sp<SkPicture> sub = pr.finishRecordingAsPicture();
// Create frames, recording them to multipic.
SkRecord expectedRecords[NUM_FRAMES];
for (int i=0; i<NUM_FRAMES; i++) {
SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
draw_advanced(pictureCanvas, i, image, sub);
// Also record the same commands to separate SkRecords for later comparison
SkRecorder canvas(&expectedRecords[i], WIDTH, HEIGHT);
draw_advanced(&canvas, i, image, sub);
// Finalize
// Confirm written data is at least as large as the magic word
std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream();
REPORTER_ASSERT(reporter, writtenStream->getLength() > 24,
"Written data length too short (%d)", writtenStream->getLength());
// SkDebugf("Multi Frame file size = %d\n", writtenStream->getLength());
// Set up deserialization
SkSharingDeserialContext deserialContext;
SkDeserialProcs dprocs;
dprocs.fImageProc = SkSharingDeserialContext::deserializeImage;
dprocs.fImageCtx = &deserialContext;
// Confirm data is a MultiPictureDocument
int frame_count = SkMultiPictureDocumentReadPageCount(writtenStream.get());
REPORTER_ASSERT(reporter, frame_count == NUM_FRAMES,
"Expected %d frames, got %d. \n 0 frames may indicate the written file was not a "
"MultiPictureDocument.", NUM_FRAMES, frame_count);
// Deserailize
std::vector<SkDocumentPage> frames(frame_count);
SkMultiPictureDocumentRead(writtenStream.get(),, frame_count, &dprocs),
"Failed while reading MultiPictureDocument");
// Examine each frame.
SkRecorder resultRecorder(nullptr, 1, 1);
int i=0;
for (const auto& frame : frames) {
SkRect bounds = frame.fPicture->cullRect();
REPORTER_ASSERT(reporter, bounds.width() == WIDTH,
"Page width: expected (%d) got (%d)", WIDTH, bounds.width());
REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
"Page height: expected (%d) got (%d)", HEIGHT, bounds.height());
// confirm contents of picture match what we drew.
// There are several ways of doing this, an ideal comparison would not break in the same
// way at the same time as the code under test (no serialization), and would involve only
// minimal transformation of frame.fPicture, minimizing the chance that a detected fault lies
// in the test itself. The comparions also would not be an overly sensitive change detector,
// so that it doesn't break every time someone submits code (no golden file)
// Extract the SkRecord from the deserialized picture using playback (instead of a mess of
// friend classes to grab the private record inside frame.fPicture
SkRecord record;
// This picture mode is necessary so that we record the command contents of frame.fPicture
// not just a 'DrawPicture' command, but don't record pictures within it. We want to assert
// that the code under test reffed them like it should have.
resultRecorder.reset(&record, bounds, SkRecorder::PlaybackTop_DrawPictureMode, nullptr);
// Compare the record to the expected one
compareRecords(record, expectedRecords[i], i, reporter);