Reland "Deserialize MultiPictureDocument based SKP files (with image sharing proc) in wasm debugger."
This is a reland of 7635013ad16a60cfa5ff93af28c5fab8927c92ce
Original change's description:
> Deserialize MultiPictureDocument based SKP files (with image sharing proc) in wasm debugger.
>
> Change-Id: I73affae3cd05a2aa6ac1c75c8e049d352bbf3a85
> Bug: 9176
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/217135
> Commit-Queue: Nathaniel Nifong <nifong@google.com>
> Reviewed-by: Derek Sollenberger <djsollen@google.com>
> Reviewed-by: Kevin Lubick <kjlubick@google.com>
Bug: 9176
Change-Id: Ifef1ff45ac0013ba3015f88c7ecd75527b28b604
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222505
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 2897286..14b9cf6 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1057,6 +1057,7 @@
public_configs = [ ":skia_public" ]
sources = [
+ "tools/SkSharingProc.cpp",
"tools/UrlDataManager.cpp",
"tools/debugger/DebugCanvas.cpp",
"tools/debugger/DrawCommand.cpp",
@@ -1597,6 +1598,7 @@
"tools/LsanSuppressions.cpp",
"tools/ProcStats.cpp",
"tools/Resources.cpp",
+ "tools/SkSharingProc.cpp",
"tools/ToolUtils.cpp",
"tools/UrlDataManager.cpp",
"tools/debugger/DebugCanvas.cpp",
diff --git a/experimental/wasm-skp-debugger/debugger_bindings.cpp b/experimental/wasm-skp-debugger/debugger_bindings.cpp
index b4fb35d..19084c4 100644
--- a/experimental/wasm-skp-debugger/debugger_bindings.cpp
+++ b/experimental/wasm-skp-debugger/debugger_bindings.cpp
@@ -8,6 +8,8 @@
#include "include/core/SkPicture.h"
#include "include/core/SkSurface.h"
#include "src/utils/SkJSONWriter.h"
+#include "src/utils/SkMultiPictureDocument.h"
+#include "tools/SkSharingProc.h"
#include "tools/UrlDataManager.h"
#include "tools/debugger/DebugCanvas.h"
@@ -26,6 +28,10 @@
using JSColor = int32_t;
+// file signature for SkMultiPictureDocument
+// TODO(nifong): make public and include from SkMultiPictureDocument.h
+static constexpr char kMultiMagic[] = "Skia Multi-Picture Doc\n\n";
+
struct SimpleImageInfo {
int width;
int height;
@@ -52,45 +58,61 @@
* pointer we're expecting.
*/
void loadSkp(uintptr_t cptr, int length) {
- const auto* data = reinterpret_cast<const uint8_t*>(cptr);
- // note overloaded = operator that actually does a move
- fPicture = SkPicture::MakeFromData(data, length);
- if (!fPicture) {
- SkDebugf("Unable to parse SKP file.\n");
- return;
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(cptr);
+ char magic[8];
+ // Both traditional and multi-frame skp files have a magic word
+ SkMemoryStream stream(data, length);
+ SkDebugf("make stream at %p, with %d bytes\n",data, length);
+ // Why -1? I think it's got to do with using a constexpr, just a guess.
+ const size_t magicsize = sizeof(kMultiMagic) - 1;
+ if (memcmp(data, kMultiMagic, magicsize) == 0) {
+ SkDebugf("Try reading as a multi-frame skp\n");
+ loadMultiFrame(&stream);
+ } else {
+ SkDebugf("Try reading as single-frame skp\n");
+ frames.push_back(loadSingleFrame(&stream));
}
- SkDebugf("Parsed SKP file.\n");
- // Make debug canvas using bounds from SkPicture
- fBounds = fPicture->cullRect().roundOut();
- fDebugCanvas.reset(new DebugCanvas(fBounds));
- SkDebugf("DebugCanvas created.\n");
-
- // Only draw picture to the debug canvas once.
- fDebugCanvas->drawPicture(fPicture);
- SkDebugf("Added picture with %d commands.\n", fDebugCanvas->getSize());
}
/* drawTo asks the debug canvas to draw from the beginning of the picture
* to the given command and flush the canvas.
*/
void drawTo(SkSurface* surface, int32_t index) {
- fDebugCanvas->drawTo(surface->getCanvas(), index);
+ int cmdlen = frames[fp]->getSize();
+ if (cmdlen == 0) {
+ SkDebugf("Zero commands to execute");
+ return;
+ }
+ if (index >= cmdlen) {
+ SkDebugf("Constrained command index (%d) within this frame's length (%d)\n", index, cmdlen);
+ index = cmdlen-1;
+ }
+ frames[fp]->drawTo(surface->getCanvas(), index);
surface->getCanvas()->flush();
}
const SkIRect& getBounds() { return fBounds; }
- void setOverdrawVis(bool on) { fDebugCanvas->setOverdrawViz(on); }
+ void setOverdrawVis(bool on) {
+ frames[fp]->setOverdrawViz(on);
+ }
void setGpuOpBounds(bool on) {
- fDebugCanvas->setDrawGpuOpBounds(on);
+ frames[fp]->setDrawGpuOpBounds(on);
}
void setClipVizColor(JSColor color) {
- fDebugCanvas->setClipVizColor(SkColor(color));
+ frames[fp]->setClipVizColor(SkColor(color));
}
- int getSize() const { return fDebugCanvas->getSize(); }
- void deleteCommand(int index) { fDebugCanvas->deleteDrawCommandAt(index); }
+ void deleteCommand(int index) {
+ frames[fp]->deleteDrawCommandAt(index);
+ }
void setCommandVisibility(int index, bool visible) {
- fDebugCanvas->toggleCommand(index, visible);
+ frames[fp]->toggleCommand(index, visible);
+ }
+ int getSize() const {
+ return frames[fp]->getSize();
+ }
+ int getFrameCount() const {
+ return frames.size();
}
// Return the command list in JSON representation as a string
@@ -101,7 +123,7 @@
// this will be prepended to any links that are created in the json command list.
UrlDataManager udm(SkString("/"));
writer.beginObject(); // root
- fDebugCanvas->toJSON(writer, udm, getSize(), surface->getCanvas());
+ frames[fp]->toJSON(writer, udm, getSize(), surface->getCanvas());
writer.endObject(); // root
writer.flush();
auto skdata = stream.detachAsData();
@@ -113,8 +135,8 @@
// Gets the clip and matrix of the last command drawn
std::string lastCommandInfo() {
- SkMatrix vm = fDebugCanvas->getCurrentMatrix();
- SkIRect clip = fDebugCanvas->getCurrentClip();
+ SkMatrix vm = frames[fp]->getCurrentMatrix();
+ SkIRect clip = frames[fp]->getCurrentClip();
SkDynamicMemoryWStream stream;
SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
@@ -135,10 +157,81 @@
return std::string(data_view);
}
+ void changeFrame(int index) {
+ fp = index;
+ }
+
private:
- std::unique_ptr<DebugCanvas> fDebugCanvas;
- sk_sp<SkPicture> fPicture;
- SkIRect fBounds;
+
+ // Loads a single frame (traditional) skp file from the provided data stream and returns
+ // a newly allocated DebugCanvas initialized with the SkPicture that was in the file.
+ std::unique_ptr<DebugCanvas> loadSingleFrame(SkMemoryStream* stream) {
+ // note overloaded = operator that actually does a move
+ sk_sp<SkPicture> picture = SkPicture::MakeFromStream(stream);
+ if (!picture) {
+ SkDebugf("Unable to deserialze frame.\n");
+ return nullptr;
+ }
+ SkDebugf("Parsed SKP file.\n");
+ // Make debug canvas using bounds from SkPicture
+ fBounds = picture->cullRect().roundOut();
+ std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds);
+ SkDebugf("DebugCanvas created.\n");
+
+ // Only draw picture to the debug canvas once.
+ debugDanvas->drawPicture(picture);
+ SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize());
+ return debugDanvas;
+ }
+
+ void loadMultiFrame(SkMemoryStream* stream) {
+
+ // Attempt to deserialize with an image sharing serial proc.
+ auto deserialContext = std::make_unique<SkSharingDeserialContext>();
+ SkDeserialProcs procs;
+ procs.fImageProc = SkSharingDeserialContext::deserializeImage;
+ procs.fImageCtx = deserialContext.get();
+
+ int page_count = SkMultiPictureDocumentReadPageCount(stream);
+ if (!page_count) {
+ SkDebugf("Not a MultiPictureDocument");
+ return;
+ }
+ SkDebugf("Expecting %d frames\n", page_count);
+
+ std::vector<SkDocumentPage> pages(page_count);
+ if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) {
+ SkDebugf("Reading frames from MultiPictureDocument failed");
+ return;
+ }
+
+ for (const auto& page : pages) {
+ // Make debug canvas using bounds from SkPicture
+ fBounds = page.fPicture->cullRect().roundOut();
+ std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds);
+ // Only draw picture to the debug canvas once.
+ debugDanvas->drawPicture(page.fPicture);
+ SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize());
+
+ if (debugDanvas->getSize() <=0 ){
+ SkDebugf("Skipped corrupted frame, had %d commands \n", debugDanvas->getSize());
+ continue;
+ }
+ debugDanvas->setOverdrawViz(false);
+ debugDanvas->setDrawGpuOpBounds(false);
+ debugDanvas->setClipVizColor(SK_ColorTRANSPARENT);
+ frames.push_back(std::move(debugDanvas));
+ }
+ }
+
+ // A vector of DebugCanvas, each one initialized to a frame of the animation.
+ std::vector<std::unique_ptr<DebugCanvas>> frames;
+ // The index of the current frame (into the vector above)
+ int fp = 0;
+ // The width and height of the animation. (in practice the bounds of the last loaded frame)
+ SkIRect fBounds;
+ // SKP version of loaded file.
+ uint32_t fFileVersion;
};
#if SK_SUPPORT_GPU
@@ -218,7 +311,9 @@
.function("setCommandVisibility", &SkpDebugPlayer::setCommandVisibility)
.function("setGpuOpBounds", &SkpDebugPlayer::setGpuOpBounds)
.function("jsonCommandList", &SkpDebugPlayer::jsonCommandList, allow_raw_pointers())
- .function("lastCommandInfo", &SkpDebugPlayer::lastCommandInfo);
+ .function("lastCommandInfo", &SkpDebugPlayer::lastCommandInfo)
+ .function("changeFrame", &SkpDebugPlayer::changeFrame)
+ .function("getFrameCount", &SkpDebugPlayer::getFrameCount);
// Structs used as arguments or returns to the functions above
value_object<SkIRect>("SkIRect")
diff --git a/gn/tests.gni b/gn/tests.gni
index 734c1e4..ac67998 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -152,6 +152,7 @@
"$_tests/MessageBusTest.cpp",
"$_tests/MetaDataTest.cpp",
"$_tests/MipMapTest.cpp",
+ "$_tests/MultiSkpTest.cpp",
"$_tests/NonlinearBlendingTest.cpp",
"$_tests/OctoBoundsTest.cpp",
"$_tests/OSPathTest.cpp",
diff --git a/public.bzl b/public.bzl
index f3f56af..3df6fc9 100644
--- a/public.bzl
+++ b/public.bzl
@@ -453,6 +453,7 @@
"experimental/svg/model/*.h",
"gm/*.cpp",
"gm/*.h",
+ "src/utils/SkMultiPictureDocument.cpp",
"src/xml/*.cpp",
"tests/*.cpp",
"tests/*.h",
@@ -499,6 +500,7 @@
"tools/gpu/**/*.h",
"tools/random_parse_path.cpp",
"tools/random_parse_path.h",
+ "tools/SkSharingProc.cpp",
"tools/ToolUtils.cpp",
"tools/ToolUtils.h",
"tools/timer/*.cpp",
diff --git a/src/utils/SkMultiPictureDocument.cpp b/src/utils/SkMultiPictureDocument.cpp
index b87f967..25be716 100644
--- a/src/utils/SkMultiPictureDocument.cpp
+++ b/src/utils/SkMultiPictureDocument.cpp
@@ -80,7 +80,8 @@
SkCanvas* c = fPictureRecorder.beginRecording(SkRect::MakeSize(bigsize));
for (const sk_sp<SkPicture>& page : fPages) {
c->drawPicture(page);
- c->drawAnnotation(SkRect::MakeEmpty(), kEndPage, nullptr);
+ // Annotations must include some data.
+ c->drawAnnotation(SkRect::MakeEmpty(), kEndPage, SkData::MakeWithCString("X"));
}
sk_sp<SkPicture> p = fPictureRecorder.finishRecordingAsPicture();
p->serialize(wStream, &fProcs);
@@ -195,7 +196,8 @@
// PagerCanvas::onDrawAnnotation().
picture->playback(&canvas);
if (canvas.fIndex != dstArrayCount) {
- SkDEBUGF("Malformed SkMultiPictureDocument\n");
+ SkDEBUGF("Malformed SkMultiPictureDocument: canvas.fIndex=%d dstArrayCount=%d\n",
+ canvas.fIndex, dstArrayCount);
}
return true;
}
diff --git a/tests/MultiSkpTest.cpp b/tests/MultiSkpTest.cpp
new file mode 100644
index 0000000..8e3ea3b
--- /dev/null
+++ b/tests/MultiSkpTest.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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 multi skp can be serialize and deserailzied without error.
+ */
+
+#include "include/core/SkDocument.h"
+#include "include/core/SkPicture.h"
+#include "include/core/SkSurface.h"
+#include "include/core/SkString.h"
+#include "include/core/SkTextBlob.h"
+#include "include/core/SkFont.h"
+#include "src/core/SkRecord.h"
+#include "src/core/SkRecorder.h"
+#include "src/utils/SkMultiPictureDocument.h"
+#include "tools/SkSharingProc.h"
+#include "tests/Test.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());
+ }
+}
+
+static void draw_something(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);
+}
+
+// Test serialization and deserialization of multi skp.
+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 mulit 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);
+
+ // 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_something(pictureCanvas, i, image);
+ multipic->endPage();
+ // Also record the same commands to separate SkRecords for later comparison
+ SkRecorder canvas(&expectedRecords[i], WIDTH, HEIGHT);
+ draw_something(&canvas, i, image);
+ }
+ // 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 (%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);
+ 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, 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.
+ resultRecorder.reset(&record, bounds, SkRecorder::Playback_DrawPictureMode, nullptr);
+ frame.fPicture->playback(&resultRecorder);
+ // Compare the record to the expected one
+ compareRecords(record, expectedRecords[i], i, reporter);
+ i++;
+ }
+}
diff --git a/tools/SkSharingProc.cpp b/tools/SkSharingProc.cpp
new file mode 100644
index 0000000..803f766
--- /dev/null
+++ b/tools/SkSharingProc.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "tools/SkSharingProc.h"
+
+#include "include/core/SkData.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkSerialProcs.h"
+
+sk_sp<SkData> SkSharingSerialContext::serializeImage(SkImage* img, void* ctx) {
+ SkSharingSerialContext* context = reinterpret_cast<SkSharingSerialContext*>(ctx);
+ uint32_t id = img->uniqueID(); // get this process's id for the image. these are not hashes.
+ // find out if we have already serialized this, and if so, what its in-file id is.
+ auto iter = context->fImageMap.find(id);
+ if (iter == context->fImageMap.end()) {
+ // When not present, add its id to the map and return its usual serialized form.
+ context->fImageMap[id] = context->fImageMap.size();
+ return img->encodeToData();
+ }
+ uint32_t fid = context->fImageMap[id];
+ // if present, return only the in-file id we registered the first time we serialized it.
+ return SkData::MakeWithCopy(&fid, sizeof(fid));
+}
+
+sk_sp<SkImage> SkSharingDeserialContext::deserializeImage(
+ const void* data, size_t length, void* ctx) {
+ SkSharingDeserialContext* context = reinterpret_cast<SkSharingDeserialContext*>(ctx);
+ uint32_t fid;
+ // If the data is an image fid, look up an already deserialized image from our map
+ if (length == sizeof(fid)) {
+ memcpy(&fid, data, sizeof(fid));
+ if (fid >= context->fImages.size()) {
+ SkDebugf("We do not have the data for image %d.\n", fid);
+ return nullptr;
+ }
+ return context->fImages[fid];
+ }
+ // Otherwise, the data is an image, deserialise it, store it in our map at its fid.
+ // TODO(nifong): make DeserialProcs accept sk_sp<SkData> so we don't have to copy this.
+ sk_sp<SkData> dataView = SkData::MakeWithCopy(data, length);
+ const sk_sp<SkImage> image = SkImage::MakeFromEncoded(std::move(dataView));
+ context->fImages.push_back(image);
+ return image;
+}
diff --git a/tools/SkSharingProc.h b/tools/SkSharingProc.h
new file mode 100644
index 0000000..12a7b80
--- /dev/null
+++ b/tools/SkSharingProc.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSharingProc_DEFINED
+#define SkSharingProc_DEFINED
+
+#include <unordered_map>
+#include <vector>
+
+#include "include/core/SkData.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkSerialProcs.h"
+
+struct SkSharingSerialContext {
+ // A map from the ids from SkImage::uniqueID() to ids used within the file
+ std::unordered_map<uint32_t, uint32_t> fImageMap;
+
+ // A serial proc that shares images between subpictures
+ // To use this, create an instance of SkSerialProcs and populate it this way.
+ // The client must retain ownership of the context.
+ // auto ctx = std::make_unique<SkSharingSerialContext>()
+ // SkSerialProcs procs;
+ // procs.fImageProc = SkSharingSerialContext::serializeImage;
+ // procs.fImageCtx = ctx.get();
+ static sk_sp<SkData> serializeImage(SkImage* img, void* ctx);
+};
+
+struct SkSharingDeserialContext {
+ // a list of unique images in the order they were encountered in the file
+ // Subsequent occurrences of an image refer to it by it's index in this list.
+ std::vector<sk_sp<SkImage>> fImages;
+
+ // A deserial proc that can interpret id's in place of images as references to previous images.
+ // Can also deserialize a SKP where all images are inlined (it's backwards compatible)
+ static sk_sp<SkImage> deserializeImage(const void* data, size_t length, void* ctx);
+};
+
+#endif