blob: d6ba287e15f62ea00097e91acdb12a8e847cdc54 [file]
/*
* Copyright 2025 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/capture/SkCapture.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkData.h"
#include "include/core/SkFourByteTag.h"
#include "include/core/SkImage.h"
#include "include/core/SkPicture.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSerialProcs.h"
#include "include/core/SkStream.h"
#include "include/private/SkLog.h"
#include "include/private/SkTArray.h"
#include "src/capture/SkCaptureManager.h"
constexpr SkFourByteTag kMagic1 = SkSetFourByteTag('s','k','i','a');
constexpr SkFourByteTag kMagic2 = SkSetFourByteTag('c','a','p','t');
sk_sp<SkCapture> SkCapture::MakeFromData(sk_sp<const SkData> data) {
if (!data) {
return nullptr;
}
// 1. Setup Stream
SkMemoryStream stream(data->data(), data->size());
// 2. Read and Validate Magic Number
uint32_t magic1;
uint32_t magic2;
if (!stream.readU32(&magic1) || !stream.readU32(&magic2) ||
magic1 != kMagic1 || magic2 != kMagic2) {
SKIA_LOG_E("Invalid magic number for SkCapture.");
return nullptr;
}
// 3. Read and Validate Version
uint32_t version;
if (!stream.readU32(&version) || version != kVersion) {
SKIA_LOG_E("Unsupported SkCapture version: %u.", version);
return nullptr;
}
// 4. Read Asset and RecordingCapture Counts
uint32_t assetCount;
uint32_t recordingCaptureCount;
if (!stream.readU32(&assetCount) || !stream.readU32(&recordingCaptureCount)) {
SKIA_LOG_E("Failed to read asset or recording capture counts.");
return nullptr;
}
auto capture = sk_make_sp<SkCapture>();
capture->fMetadata = {version, assetCount, recordingCaptureCount};
// 5. Loop and Deserialize Each Asset (SkPicture)
for (uint32_t i = 0; i < assetCount; ++i) {
uint32_t pictureDataSize;
if (!stream.readU32(&pictureDataSize)) {
SKIA_LOG_E("Failed to read picture data size for asset %u.", i);
return nullptr;
}
sk_sp<SkData> pictureData = SkData::MakeUninitialized(pictureDataSize);
if (!pictureData || stream.read(pictureData->writable_data(),
pictureDataSize) != pictureDataSize) {
SKIA_LOG_E("Failed to read picture data for asset %u or allocation failed.", i);
return nullptr;
}
SkDeserialProcs procs;
procs.fImageDataProc = SkCapture::deserializeImageProc;
sk_sp<SkPicture> picture = SkPicture::MakeFromData(pictureData.get(), &procs);
if (!picture) {
SKIA_LOG_E("Failed to deserialize SkPicture for asset %u.", i);
return nullptr;
}
capture->fAssets.emplace_back(std::move(picture));
}
// 6. Loop and Deserialize Each RecordingCapture
for (uint32_t i = 0; i < recordingCaptureCount; ++i) {
RecordingCapture rec;
uint32_t taskCount;
if (!stream.readU32(&taskCount)) {
SKIA_LOG_E("Failed to read task count for recording capture %u.", i);
return nullptr;
}
for (uint32_t j = 0; j < taskCount; ++j) {
uint32_t assetIdx;
if (!stream.readU32(&assetIdx)) {
SKIA_LOG_E("Failed to read task %u for recording capture %u.", j, i);
return nullptr;
}
if (assetIdx >= static_cast<uint32_t>(capture->fAssets.size())) {
SKIA_LOG_E("Out-of-bounds asset index %u parsed for task %u inside recording %u.",
assetIdx, j, i);
return nullptr;
}
rec.fDrawTasks.push_back({assetIdx});
}
capture->fTimeline.push_back(std::move(rec));
}
SKIA_LOG_I("Successfully read %d assets and %d recording captures into SkCapture.",
(int)capture->fAssets.size(), (int)capture->fTimeline.size());
return capture;
}
sk_sp<SkCapture> SkCapture::MakeEmpty() {
auto capture = sk_make_sp<SkCapture>();
capture->fMetadata = {SkCapture::kVersion, 0, 0};
return capture;
}
void SkCapture::addAsset(sk_sp<SkPicture> picture) {
if (picture) {
fAssets.push_back(std::move(picture));
fMetadata.numAssets = fAssets.size();
}
}
void SkCapture::addRecordingCapture(RecordingCapture rec) {
fTimeline.push_back(std::move(rec));
fMetadata.numRecordingCaptures = fTimeline.size();
}
sk_sp<SkPicture> SkCapture::getAsset(int i) const {
if (i >= 0 && i < fAssets.size()) {
return fAssets[i];
}
return nullptr;
}
const SkCapture::RecordingCapture* SkCapture::getRecordingCapture(int i) const {
if (i >= 0 && i < fTimeline.size()) {
return &fTimeline[i];
}
return nullptr;
}
SkCapture::Metadata SkCapture::getMetadata() const {
return fMetadata;
}
sk_sp<SkData> SkCapture::serializeCapture() {
SkDynamicMemoryWStream stream;
stream.write32(kMagic1);
stream.write32(kMagic2);
stream.write32(SkCapture::kVersion);
// Number of assets and recording captures
stream.write32(fAssets.size());
stream.write32(fTimeline.size());
// 1. Assets Section: Serialized Pictures
for (const auto& picture : fAssets) {
SkDynamicMemoryWStream pictureStream;
SkSerialProcs procs;
procs.fImageProc = SkCapture::serializeImageProc;
picture->serialize(&pictureStream, &procs);
sk_sp<SkData> pictureData = pictureStream.detachAsData();
// Write size and then data
stream.write32(pictureData->size());
stream.write(pictureData->data(), pictureData->size());
}
// 2. Timeline Section: RecordingCaptures
for (const auto& rec : fTimeline) {
// Write DrawTasks count
stream.write32(rec.fDrawTasks.size());
for (const auto& task : rec.fDrawTasks) {
stream.write32(task.fAssetIndex);
}
}
auto data = stream.detachAsData();
SKIA_LOG_I("Wrote %d assets and %d recording captures to SkData block.",
(int)fAssets.size(), (int)fTimeline.size());
return data;
}
// TODO(b/412351769): When serializing our SkPictures, the images that are drawn through drawImage
// and similar functions will also need to be serialized. Instead of naively encoding all images as
// PNGs, we want the images that refer to content created from an SkSurface to point to its
// corresponding SkPicture. This means that we need to create a context for the proc to track these
// contentIDs.
SkSerialReturnType SkCapture::serializeImageProc(SkImage* img, void* ctx) {
const int contentID = -1; // TODO: replace with real content ID.
return SkData::MakeWithCopy(&contentID, sizeof(int));
}
sk_sp<SkImage> SkCapture::deserializeImageProc(sk_sp<SkData>, std::optional<SkAlphaType>, void*) {
// TODO: set up the SkCapture context and inspect it to grab SkPictures and pass them as images.
SkBitmap b;
b.allocN32Pixels(5, 5);
SkCanvas canvas(b);
canvas.drawColor(SK_ColorMAGENTA);
return b.asImage();
}