| /* |
| * 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(); |
| } |