684838f1f5
Change-Id: I3d2ee8dca1d2e962794ce8c3c391779bff357f0c Reviewed-on: https://skia-review.googlesource.com/c/skia/+/288762 Commit-Queue: Adlai Holler <adlai@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com> Auto-Submit: Adlai Holler <adlai@google.com>
222 lines
8.3 KiB
C++
222 lines
8.3 KiB
C++
/*
|
|
* 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.
|
|
public:
|
|
SkString name;
|
|
|
|
explicit RecordVisitor() {}
|
|
|
|
template <typename T>
|
|
void operator()(const T& command) {
|
|
name = SkString(NameOf(command));
|
|
}
|
|
|
|
SkString lastCommandName() {
|
|
return name;
|
|
}
|
|
private:
|
|
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) {
|
|
canvas->drawColor(SK_ColorWHITE);
|
|
|
|
SkPaint paint;
|
|
paint.setStyle(SkPaint::kStroke_Style);
|
|
paint.setStrokeWidth(seed);
|
|
paint.setColor(SK_ColorRED);
|
|
|
|
SkRect rect = SkRect::MakeXYWH(50+seed, 50+seed, 4*seed, 60);
|
|
canvas->drawRect(rect, paint);
|
|
|
|
SkRRect oval;
|
|
oval.setOval(rect);
|
|
oval.offset(40, 60+seed);
|
|
paint.setColor(SK_ColorBLUE);
|
|
canvas->drawRRect(oval, paint);
|
|
|
|
paint.setColor(SK_ColorCYAN);
|
|
canvas->drawCircle(180, 50, 5*seed, paint);
|
|
|
|
rect.offset(80, 0);
|
|
paint.setColor(SK_ColorYELLOW);
|
|
canvas->drawRoundRect(rect, 10, 10, paint);
|
|
|
|
SkPath path;
|
|
path.cubicTo(768, 0, -512, 256, 256, 256);
|
|
paint.setColor(SK_ColorGREEN);
|
|
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->drawPicture(sub);
|
|
canvas->save();
|
|
canvas->translate(seed, seed);
|
|
canvas->drawPicture(sub);
|
|
canvas->restore();
|
|
}
|
|
|
|
// 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));
|
|
surface->getCanvas()->clear(SK_ColorGREEN);
|
|
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);
|
|
multipic->endPage();
|
|
// Also record the same commands to separate SkRecords for later comparison
|
|
SkRecorder canvas(&expectedRecords[i], WIDTH, HEIGHT);
|
|
draw_advanced(&canvas, i, image, sub);
|
|
}
|
|
// Finalize
|
|
multipic->close();
|
|
|
|
// 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 (%zu)", writtenStream->getLength());
|
|
// SkDebugf("Multi Frame file size = %zu\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);
|
|
REPORTER_ASSERT(reporter,
|
|
SkMultiPictureDocumentRead(writtenStream.get(), frames.data(), 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, (int)bounds.width());
|
|
REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
|
|
"Page height: expected (%d) got (%d)", HEIGHT, (int)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);
|
|
frame.fPicture->playback(&resultRecorder);
|
|
// Compare the record to the expected one
|
|
compareRecords(record, expectedRecords[i], i, reporter);
|
|
i++;
|
|
}
|
|
}
|