1 /*
2 * This file defines SkpDebugPlayer, a class which loads a SKP or MSKP file and draws it
3 * to an SkSurface with annotation, and detailed playback controls. It holds as many DebugCanvases
4 * as there are frames in the file.
5 *
6 * It also defines emscripten bindings for SkpDebugPlayer and other classes necessary to us it.
7 *
8 * Copyright 2019 Google LLC
9 *
10 * Use of this source code is governed by a BSD-style license that can be
11 * found in the LICENSE file.
12 */
13
14 #include "include/codec/SkCodec.h"
15 #include "include/core/SkImage.h"
16 #include "include/core/SkPicture.h"
17 #include "include/core/SkString.h"
18 #include "include/core/SkSurface.h"
19 #include "include/docs/SkMultiPictureDocument.h"
20 #include "include/encode/SkPngEncoder.h"
21 #include "src/base/SkBase64.h"
22 #include "src/core/SkPicturePriv.h"
23 #include "src/ports/SkTypeface_FreeType.h"
24 #include "src/utils/SkJSONWriter.h"
25 #include "tools/SkSharingProc.h"
26 #include "tools/UrlDataManager.h"
27 #include "tools/debugger/DebugCanvas.h"
28 #include "tools/debugger/DebugLayerManager.h"
29 #include "tools/debugger/DrawCommand.h"
30
31 #include <memory>
32 #include <string>
33 #include <string_view>
34 #include <vector>
35 #include <map>
36 #include <emscripten.h>
37 #include <emscripten/bind.h>
38
39 #ifdef CK_ENABLE_WEBGL
40 #include "include/gpu/ganesh/GrBackendSurface.h"
41 #include "include/gpu/ganesh/GrDirectContext.h"
42 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
43 #include "include/gpu/ganesh/gl/GrGLInterface.h"
44 #include "include/gpu/ganesh/gl/GrGLTypes.h"
45
46 #include <GL/gl.h>
47 #include <emscripten/html5.h>
48 #endif
49
50 #include "modules/canvaskit/WasmCommon.h"
51
52 // file signature for SkMultiPictureDocument
53 // TODO(nifong): make public and include from SkMultiPictureDocument.h
54 static constexpr char kMultiMagic[] = "Skia Multi-Picture Doc\n\n";
55
MinVersion()56 uint32_t MinVersion() { return SkPicturePriv::kMin_Version; }
57
58 struct ImageInfoNoColorspace {
59 int width;
60 int height;
61 SkColorType colorType;
62 SkAlphaType alphaType;
63 };
64
65 // TODO(kjlubick) Should this handle colorspace
toImageInfoNoColorspace(const SkImageInfo & ii)66 ImageInfoNoColorspace toImageInfoNoColorspace(const SkImageInfo& ii) {
67 return (ImageInfoNoColorspace){ii.width(), ii.height(), ii.colorType(), ii.alphaType()};
68 }
69
deserializeImage(sk_sp<SkData> data,std::optional<SkAlphaType>,void *)70 static sk_sp<SkImage> deserializeImage(sk_sp<SkData> data, std::optional<SkAlphaType>, void*) {
71 std::unique_ptr<SkCodec> codec = DecodeImageData(std::move(data));
72 if (!codec) {
73 SkDebugf("Could not decode an image\n");
74 return nullptr;
75 }
76 sk_sp<SkImage> img = std::get<0>(codec->getImage());
77 if (!img) {
78 SkDebugf("Could not make an image from a codec\n");
79 return nullptr;
80 }
81 return img;
82 }
83
register_typeface()84 static void register_typeface() {
85 static SkOnce once;
86 once([] {
87 SkTypeface::Register(SkTypeface_FreeType::FactoryId,
88 SkTypeface_FreeType::MakeFromStream );
89 });
90 }
91
92 class SkpDebugPlayer {
93 public:
SkpDebugPlayer()94 SkpDebugPlayer() :
95 udm(UrlDataManager(SkString("/data"))){}
96
97 /* loadSkp deserializes a skp file that has been copied into the shared WASM memory.
98 * cptr - a pointer to the data to deserialize.
99 * length - length of the data in bytes.
100 * The caller must allocate the memory with M._malloc where M is the wasm module in javascript
101 * and copy the data into M.buffer at the pointer returned by malloc.
102 *
103 * uintptr_t is used here because emscripten will not allow binding of functions with pointers
104 * to primitive types. We can instead pass a number and cast it to whatever kind of
105 * pointer we're expecting.
106 *
107 * Returns an error string which is populated in the case that the file cannot be read.
108 */
loadSkp(uintptr_t cptr,int length)109 std::string loadSkp(uintptr_t cptr, int length) {
110 const uint8_t* data = reinterpret_cast<const uint8_t*>(cptr);
111 // Both traditional and multi-frame skp files have a magic word
112 SkMemoryStream stream(data, length);
113 SkDebugf("make stream at %p, with %d bytes\n",data, length);
114 const bool isMulti = memcmp(data, kMultiMagic, sizeof(kMultiMagic) - 1) == 0;
115
116
117 if (isMulti) {
118 SkDebugf("Try reading as a multi-frame skp\n");
119 const auto& error = loadMultiFrame(&stream);
120 if (!error.empty()) { return error; }
121 } else {
122 SkDebugf("Try reading as single-frame skp\n");
123 // TODO(nifong): Rely on SkPicture's return errors once it provides some.
124 std::unique_ptr<DebugCanvas> canvas = loadSingleFrame(&stream);
125 if (!canvas) {
126 return "Error loading single frame";
127 }
128 frames.push_back(std::move(canvas));
129 }
130 return "";
131 }
132
133 /* drawTo asks the debug canvas to draw from the beginning of the picture
134 * to the given command and flush the canvas.
135 */
drawTo(SkSurface * surface,int32_t index)136 void drawTo(SkSurface* surface, int32_t index) {
137 // Set the command within the frame or layer event being drawn.
138 if (fInspectedLayer >= 0) {
139 fLayerManager->setCommand(fInspectedLayer, fp, index);
140 } else {
141 index = constrainFrameCommand(index);
142 }
143
144 auto* canvas = surface->getCanvas();
145 canvas->clear(SK_ColorTRANSPARENT);
146 if (fInspectedLayer >= 0) {
147 // when it's a layer event we're viewing, we use the layer manager to render it.
148 fLayerManager->drawLayerEventTo(surface, fInspectedLayer, fp);
149 } else {
150 // otherwise, its a frame at the top level.
151 frames[fp]->drawTo(surface->getCanvas(), index);
152 }
153 #ifdef CK_ENABLE_WEBGL
154 skgpu::ganesh::Flush(surface);
155 #endif
156 }
157
158 // Draws to the end of the current frame.
draw(SkSurface * surface)159 void draw(SkSurface* surface) {
160 auto* canvas = surface->getCanvas();
161 canvas->clear(SK_ColorTRANSPARENT);
162 frames[fp]->draw(surface->getCanvas());
163 #ifdef CK_ENABLE_WEBGL
164 skgpu::ganesh::Flush(surface);
165 #endif
166 }
167
168 // Gets the bounds for the given frame
169 // (or layer update, assuming there is one at that frame for fInspectedLayer)
getBoundsForFrame(int32_t frame)170 const SkIRect getBoundsForFrame(int32_t frame) {
171 if (fInspectedLayer < 0) {
172 return fBoundsArray[frame];
173 }
174 auto summary = fLayerManager->event(fInspectedLayer, fp);
175 return SkIRect::MakeWH(summary.layerWidth, summary.layerHeight);
176 }
177
178 // Gets the bounds for the current frame
getBounds()179 const SkIRect getBounds() {
180 return getBoundsForFrame(fp);
181 }
182
183 // returns the debugcanvas of the current frame, or the current draw event when inspecting
184 // a layer.
visibleCanvas()185 DebugCanvas* visibleCanvas() {
186 if (fInspectedLayer >=0) {
187 return fLayerManager->getEventDebugCanvas(fInspectedLayer, fp);
188 } else {
189 return frames[fp].get();
190 }
191 }
192
193 // The following three operations apply to every debugcanvas because they are overdraw features.
194 // There is only one toggle for them on the app, they are global settings.
195 // However, there's not a simple way to make the debugcanvases pull settings from a central
196 // location so we set it on all of them at once.
setOverdrawVis(bool on)197 void setOverdrawVis(bool on) {
198 for (size_t i=0; i < frames.size(); i++) {
199 frames[i]->setOverdrawViz(on);
200 }
201 fLayerManager->setOverdrawViz(on);
202 }
setGpuOpBounds(bool on)203 void setGpuOpBounds(bool on) {
204 for (size_t i=0; i < frames.size(); i++) {
205 frames[i]->setDrawGpuOpBounds(on);
206 }
207 fLayerManager->setDrawGpuOpBounds(on);
208 }
setClipVizColor(JSColor color)209 void setClipVizColor(JSColor color) {
210 for (size_t i=0; i < frames.size(); i++) {
211 frames[i]->setClipVizColor(SkColor(color));
212 }
213 fLayerManager->setClipVizColor(SkColor(color));
214 }
setAndroidClipViz(bool on)215 void setAndroidClipViz(bool on) {
216 for (size_t i=0; i < frames.size(); i++) {
217 frames[i]->setAndroidClipViz(on);
218 }
219 // doesn't matter in layers
220 }
setOriginVisible(bool on)221 void setOriginVisible(bool on) {
222 for (size_t i=0; i < frames.size(); i++) {
223 frames[i]->setOriginVisible(on);
224 }
225 }
226 // The two operations below only apply to the current frame, because they concern the command
227 // list, which is unique to each frame.
deleteCommand(int index)228 void deleteCommand(int index) {
229 visibleCanvas()->deleteDrawCommandAt(index);
230 }
setCommandVisibility(int index,bool visible)231 void setCommandVisibility(int index, bool visible) {
232 visibleCanvas()->toggleCommand(index, visible);
233 }
getSize() const234 int getSize() const {
235 if (fInspectedLayer >=0) {
236 return fLayerManager->event(fInspectedLayer, fp).commandCount;
237 } else {
238 return frames[fp]->getSize();
239 }
240 }
getFrameCount() const241 int getFrameCount() const {
242 return frames.size();
243 }
244
245 // Return the command list in JSON representation as a string
jsonCommandList(sk_sp<SkSurface> surface)246 std::string jsonCommandList(sk_sp<SkSurface> surface) {
247 SkDynamicMemoryWStream stream;
248 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
249 writer.beginObject(); // root
250 visibleCanvas()->toJSON(writer, udm, surface->getCanvas());
251 writer.endObject(); // root
252 writer.flush();
253 auto skdata = stream.detachAsData();
254 // Convert skdata to string_view, which accepts a length
255 std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
256 // and string_view to string, which emscripten understands.
257 return std::string(data_view);
258 }
259
260 // Gets the clip and matrix of the last command drawn
lastCommandInfo()261 std::string lastCommandInfo() {
262 SkM44 vm = visibleCanvas()->getCurrentMatrix();
263 SkIRect clip = visibleCanvas()->getCurrentClip();
264
265 SkDynamicMemoryWStream stream;
266 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
267 writer.beginObject(); // root
268
269 writer.appendName("ViewMatrix");
270 DrawCommand::MakeJsonMatrix44(writer, vm);
271 writer.appendName("ClipRect");
272 DrawCommand::MakeJsonIRect(writer, clip);
273
274 writer.endObject(); // root
275 writer.flush();
276 auto skdata = stream.detachAsData();
277 // Convert skdata to string_view, which accepts a length
278 std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
279 // and string_view to string, which emscripten understands.
280 return std::string(data_view);
281 }
282
changeFrame(int index)283 void changeFrame(int index) {
284 fp = index;
285 }
286
287 // Return the png file at the requested index in
288 // the skp file's vector of shared images. this is the set of images referred to by the
289 // filenames like "\\1" in DrawImage commands.
290 // Return type is the PNG data as a base64 encoded string with prepended URI.
getImageResource(int index)291 std::string getImageResource(int index) {
292 sk_sp<SkData> pngData = SkPngEncoder::Encode(nullptr, fImages[index].get(), {});
293 size_t len = SkBase64::EncodedSize(pngData->size());
294 SkString dst;
295 dst.resize(len);
296 SkBase64::Encode(pngData->data(), pngData->size(), dst.data());
297 dst.prepend("data:image/png;base64,");
298 return std::string(dst.c_str());
299 }
300
getImageCount()301 int getImageCount() {
302 return fImages.size();
303 }
304
305 // Get the image info of one of the resource images.
getImageInfo(int index)306 ImageInfoNoColorspace getImageInfo(int index) {
307 return toImageInfoNoColorspace(fImages[index]->imageInfo());
308 }
309
310 // return data on which commands each image is used in.
311 // (frame, -1) returns info for the given frame,
312 // (frame, nodeid) return info for a layer update
313 // { imageid: [commandid, commandid, ...], ... }
imageUseInfo(int framenumber,int nodeid)314 JSObject imageUseInfo(int framenumber, int nodeid) {
315 JSObject result = emscripten::val::object();
316 DebugCanvas* debugCanvas = frames[framenumber].get();
317 if (nodeid >= 0) {
318 debugCanvas = fLayerManager->getEventDebugCanvas(nodeid, framenumber);
319 }
320 const auto& map = debugCanvas->getImageIdToCommandMap(udm);
321 for (auto it = map.begin(); it != map.end(); ++it) {
322 JSArray list = emscripten::val::array();
323 for (const int commandId : it->second) {
324 list.call<void>("push", commandId);
325 }
326 result.set(std::to_string(it->first), list);
327 }
328 return result;
329 }
330
331 // Return information on every layer (offscreeen buffer) that is available for drawing at
332 // the current frame.
getLayerSummariesJs()333 JSArray getLayerSummariesJs() {
334 JSArray result = emscripten::val::array();
335 for (auto summary : fLayerManager->summarizeLayers(fp)) {
336 result.call<void>("push", summary);
337 }
338 return result;
339 }
340
getLayerKeys()341 JSArray getLayerKeys() {
342 JSArray result = emscripten::val::array();
343 for (auto key : fLayerManager->getKeys()) {
344 JSObject item = emscripten::val::object();
345 item.set("frame", key.frame);
346 item.set("nodeId", key.nodeId);
347 result.call<void>("push", item);
348 }
349 return result;
350 }
351
352 // When set to a valid layer index, causes this class to playback the layer draw event at nodeId
353 // on frame fp. No validation of nodeId or fp is performed, this must be valid values obtained
354 // from either fLayerManager.listNodesForFrame or fLayerManager.summarizeEvents
355 // Set to -1 to return to viewing the top level animation
setInspectedLayer(int nodeId)356 void setInspectedLayer(int nodeId) {
357 fInspectedLayer = nodeId;
358 }
359
360 // Finds a command that left the given pixel in it's current state.
361 // Note that this method may fail to find the absolute last command that leaves a pixel
362 // the given color, but there is probably only one candidate in most cases, and the log(n)
363 // makes it worth it.
findCommandByPixel(SkSurface * surface,int x,int y,int commandIndex)364 int findCommandByPixel(SkSurface* surface, int x, int y, int commandIndex) {
365 // What color is the pixel now?
366 SkColor finalColor = evaluateCommandColor(surface, commandIndex, x, y);
367
368 int lowerBound = 0;
369 int upperBound = commandIndex;
370
371 while (upperBound - lowerBound > 1) {
372 int command = (upperBound - lowerBound) / 2 + lowerBound;
373 auto c = evaluateCommandColor(surface, command, x, y);
374 if (c == finalColor) {
375 upperBound = command;
376 } else {
377 lowerBound = command;
378 }
379 }
380 // clean up after side effects
381 drawTo(surface, commandIndex);
382 return upperBound;
383 }
384
385 private:
386
387 // Helper for findCommandByPixel.
388 // Has side effect of flushing to surface.
389 // TODO(nifong) eliminate side effect.
evaluateCommandColor(SkSurface * surface,int command,int x,int y)390 SkColor evaluateCommandColor(SkSurface* surface, int command, int x, int y) {
391 drawTo(surface, command);
392
393 SkColor c;
394 SkImageInfo info = SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
395 SkPixmap pixmap(info, &c, 4);
396 surface->readPixels(pixmap, x, y);
397 return c;
398 }
399
400 // Loads a single frame (traditional) skp file from the provided data stream and returns
401 // a newly allocated DebugCanvas initialized with the SkPicture that was in the file.
loadSingleFrame(SkMemoryStream * stream)402 std::unique_ptr<DebugCanvas> loadSingleFrame(SkMemoryStream* stream) {
403 register_typeface();
404 SkDeserialProcs procs;
405 procs.fImageDataProc = deserializeImage;
406 // note overloaded = operator that actually does a move
407 sk_sp<SkPicture> picture = SkPicture::MakeFromStream(stream, &procs);
408 if (!picture) {
409 SkDebugf("Unable to deserialze frame.\n");
410 return nullptr;
411 }
412 SkDebugf("Parsed SKP file.\n");
413 // Make debug canvas using bounds from SkPicture
414 fBoundsArray.push_back(picture->cullRect().roundOut());
415 std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBoundsArray.back());
416
417 // Only draw picture to the debug canvas once.
418 debugCanvas->drawPicture(picture);
419 return debugCanvas;
420 }
421
loadMultiFrame(SkMemoryStream * stream)422 std::string loadMultiFrame(SkMemoryStream* stream) {
423 register_typeface();
424 // Attempt to deserialize with an image sharing serial proc.
425 auto deserialContext = std::make_unique<SkSharingDeserialContext>();
426 SkDeserialProcs procs;
427 procs.fImageProc = SkSharingDeserialContext::deserializeImage;
428 procs.fImageCtx = deserialContext.get();
429
430 int page_count = SkMultiPictureDocument::ReadPageCount(stream);
431 if (!page_count) {
432 // MSKP's have a version separate from the SKP subpictures they contain.
433 return "Not a MultiPictureDocument, MultiPictureDocument file version too old, or MultiPictureDocument contained 0 frames.";
434 }
435 SkDebugf("Expecting %d frames\n", page_count);
436
437 std::vector<SkDocumentPage> pages(page_count);
438 if (!SkMultiPictureDocument::Read(stream, pages.data(), page_count, &procs)) {
439 return "Reading frames from MultiPictureDocument failed";
440 }
441
442 fLayerManager = std::make_unique<DebugLayerManager>();
443
444 int i = 0;
445 for (const auto& page : pages) {
446 // Make debug canvas using bounds from SkPicture
447 fBoundsArray.push_back(page.fPicture->cullRect().roundOut());
448 std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBoundsArray.back());
449 debugCanvas->setLayerManagerAndFrame(fLayerManager.get(), i);
450
451 // Only draw picture to the debug canvas once.
452 debugCanvas->drawPicture(page.fPicture);
453
454 if (debugCanvas->getSize() <=0 ){
455 SkDebugf("Skipped corrupted frame, had %d commands \n", debugCanvas->getSize());
456 continue;
457 }
458 // If you don't set these, they're undefined.
459 debugCanvas->setOverdrawViz(false);
460 debugCanvas->setDrawGpuOpBounds(false);
461 debugCanvas->setClipVizColor(SK_ColorTRANSPARENT);
462 debugCanvas->setAndroidClipViz(false);
463 frames.push_back(std::move(debugCanvas));
464 i++;
465 }
466 fImages = deserialContext->fImages;
467
468 udm.indexImages(fImages);
469 return "";
470 }
471
472 // constrains the draw command index to the frame's command list length.
constrainFrameCommand(int index)473 int constrainFrameCommand(int index) {
474 int cmdlen = frames[fp]->getSize();
475 if (index >= cmdlen) {
476 return cmdlen-1;
477 }
478 return index;
479 }
480
481 // A vector of DebugCanvas, each one initialized to a frame of the animation.
482 std::vector<std::unique_ptr<DebugCanvas>> frames;
483 // The index of the current frame (into the vector above)
484 int fp = 0;
485 // The width and height of every frame.
486 // frame sizes are known to change in Android Skia RenderEngine because it interleves pictures from different applications.
487 std::vector<SkIRect> fBoundsArray;
488 // image resources from a loaded file
489 std::vector<sk_sp<SkImage>> fImages;
490
491 // The URLDataManager here is a cache that accepts encoded data (pngs) and puts
492 // numbers on them. We have our own collection of images (fImages) that was populated by the
493 // SkSharingDeserialContext when mskp files are loaded which it can use for IDing images
494 // without having to serialize them.
495 UrlDataManager udm;
496
497 // A structure holding the picture information needed to draw any layers used in an mskp file
498 // individual frames hold a pointer to it, store draw events, and request images from it.
499 // it is stateful and is set to the current frame at all times.
500 std::unique_ptr<DebugLayerManager> fLayerManager;
501
502 // The node id of a layer being inspected, if any.
503 // -1 means we are viewing the top level animation, not a layer.
504 // the exact draw event being inspected depends also on the selected frame `fp`.
505 int fInspectedLayer = -1;
506 };
507
508 using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module)509 EMSCRIPTEN_BINDINGS(my_module) {
510
511 function("MinVersion", &MinVersion);
512
513 // The main class that the JavaScript in index.html uses
514 class_<SkpDebugPlayer>("SkpDebugPlayer")
515 .constructor<>()
516 .function("changeFrame", &SkpDebugPlayer::changeFrame)
517 .function("deleteCommand", &SkpDebugPlayer::deleteCommand)
518 .function("draw", &SkpDebugPlayer::draw, allow_raw_pointers())
519 .function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers())
520 .function("findCommandByPixel", &SkpDebugPlayer::findCommandByPixel, allow_raw_pointers())
521 .function("getBounds", &SkpDebugPlayer::getBounds)
522 .function("getBoundsForFrame", &SkpDebugPlayer::getBoundsForFrame)
523 .function("getFrameCount", &SkpDebugPlayer::getFrameCount)
524 .function("getImageResource", &SkpDebugPlayer::getImageResource)
525 .function("getImageCount", &SkpDebugPlayer::getImageCount)
526 .function("getImageInfo", &SkpDebugPlayer::getImageInfo)
527 .function("getLayerKeys", &SkpDebugPlayer::getLayerKeys)
528 .function("getLayerSummariesJs", &SkpDebugPlayer::getLayerSummariesJs)
529 .function("getSize", &SkpDebugPlayer::getSize)
530 .function("imageUseInfo", &SkpDebugPlayer::imageUseInfo)
531 .function("imageUseInfoForFrameJs", optional_override([](SkpDebugPlayer& self, const int frame)->JSObject {
532 // -1 as a node id is used throughout the application to mean no layer inspected.
533 return self.imageUseInfo(frame, -1);
534 }))
535 .function("jsonCommandList", &SkpDebugPlayer::jsonCommandList, allow_raw_pointers())
536 .function("lastCommandInfo", &SkpDebugPlayer::lastCommandInfo)
537 .function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers())
538 .function("setClipVizColor", &SkpDebugPlayer::setClipVizColor)
539 .function("setCommandVisibility", &SkpDebugPlayer::setCommandVisibility)
540 .function("setGpuOpBounds", &SkpDebugPlayer::setGpuOpBounds)
541 .function("setInspectedLayer", &SkpDebugPlayer::setInspectedLayer)
542 .function("setOriginVisible", &SkpDebugPlayer::setOriginVisible)
543 .function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis)
544 .function("setAndroidClipViz", &SkpDebugPlayer::setAndroidClipViz);
545
546 // Structs used as arguments or returns to the functions above
547 // TODO(kjlubick) handle this rect like the ones in CanvasKit
548 value_object<SkIRect>("SkIRect")
549 .field("fLeft", &SkIRect::fLeft)
550 .field("fTop", &SkIRect::fTop)
551 .field("fRight", &SkIRect::fRight)
552 .field("fBottom", &SkIRect::fBottom);
553 // emscripten provided the following convenience function for binding vector<T>
554 // https://emscripten.org/docs/api_reference/bind.h.html#_CPPv415register_vectorPKc
555 register_vector<DebugLayerManager::LayerSummary>("VectorLayerSummary");
556 value_object<DebugLayerManager::LayerSummary>("LayerSummary")
557 .field("nodeId", &DebugLayerManager::LayerSummary::nodeId)
558 .field("frameOfLastUpdate", &DebugLayerManager::LayerSummary::frameOfLastUpdate)
559 .field("fullRedraw", &DebugLayerManager::LayerSummary::fullRedraw)
560 .field("layerWidth", &DebugLayerManager::LayerSummary::layerWidth)
561 .field("layerHeight", &DebugLayerManager::LayerSummary::layerHeight);
562
563 value_object<ImageInfoNoColorspace>("ImageInfoNoColorspace")
564 .field("width", &ImageInfoNoColorspace::width)
565 .field("height", &ImageInfoNoColorspace::height)
566 .field("colorType", &ImageInfoNoColorspace::colorType)
567 .field("alphaType", &ImageInfoNoColorspace::alphaType);
568 }
569