Attach captured canvas payloads to document snapshots

This commit is contained in:
2026-06-05 18:03:33 +02:00
parent d0412e3bf9
commit f4f6eb903e
10 changed files with 298 additions and 23 deletions

View File

@@ -26,6 +26,16 @@ struct DocumentCanvasClearPlan {
bool no_op = true;
};
struct DocumentCanvasFacePayloadInput {
std::uint32_t frame_index = 0;
std::uint32_t face_index = 0;
std::uint32_t x = 0;
std::uint32_t y = 0;
std::uint32_t width = 0;
std::uint32_t height = 0;
std::span<const std::uint8_t> rgba8;
};
struct DocumentCanvasLayerSnapshotInput {
std::string_view name;
bool visible = true;
@@ -34,6 +44,7 @@ struct DocumentCanvasLayerSnapshotInput {
int blend_mode = 0;
std::span<const std::uint32_t> frame_durations_ms;
std::size_t pending_face_payloads = 0;
std::span<const DocumentCanvasFacePayloadInput> captured_face_payloads;
};
struct DocumentCanvasSnapshotInput {
@@ -50,7 +61,8 @@ struct DocumentCanvasSnapshotResult {
std::size_t layer_count = 0;
std::size_t frame_count = 0;
std::size_t pending_face_payloads = 0;
bool metadata_only = true;
std::size_t captured_face_payloads = 0;
bool metadata_only = false;
bool requires_renderer_payload_readback = false;
};
@@ -87,9 +99,11 @@ public:
std::size_t frame_count = 1U;
std::size_t pending_face_payloads = 0U;
std::size_t captured_face_payloads = 0U;
for (const auto& layer : input.layers) {
frame_count = std::max(frame_count, layer.frame_durations_ms.size());
pending_face_payloads += layer.pending_face_payloads;
captured_face_payloads += layer.captured_face_payloads.size();
}
if (input.active_layer_index >= input.layers.size()) {
@@ -161,6 +175,26 @@ public:
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(document.status());
}
for (std::size_t layer_index = 0; layer_index < input.layers.size(); ++layer_index) {
for (const auto& payload : input.layers[layer_index].captured_face_payloads) {
pp::document::LayerFacePixels pixels {
.face_index = payload.face_index,
.x = payload.x,
.y = payload.y,
.width = payload.width,
.height = payload.height,
.rgba8 = std::vector<std::uint8_t>(payload.rgba8.begin(), payload.rgba8.end()),
};
const auto payload_status = document.value().set_layer_frame_face_pixels(
layer_index,
payload.frame_index,
std::move(pixels));
if (!payload_status.ok()) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(payload_status);
}
}
}
auto active_status = document.value().set_active_layer(input.active_layer_index);
if (!active_status.ok()) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(active_status);
@@ -176,8 +210,9 @@ public:
.layer_count = input.layers.size(),
.frame_count = frame_count,
.pending_face_payloads = pending_face_payloads,
.metadata_only = true,
.requires_renderer_payload_readback = pending_face_payloads > 0U,
.captured_face_payloads = captured_face_payloads,
.metadata_only = captured_face_payloads == 0U,
.requires_renderer_payload_readback = pending_face_payloads > captured_face_payloads,
});
}

View File

@@ -7,6 +7,7 @@
#include "legacy_history_services.h"
#include <algorithm>
#include <cstring>
#include <cstdint>
#include <span>
#include <string>
@@ -79,6 +80,55 @@ private:
return static_cast<std::size_t>(frame_count) * pp::document::cube_face_count;
}
[[nodiscard]] std::uint32_t legacy_nonnegative_u32(float value) noexcept
{
if (value <= 0.0F) {
return 0U;
}
return static_cast<std::uint32_t>(value);
}
struct LegacyLayerPayloadStorage {
std::vector<std::vector<std::uint8_t>> bytes;
std::vector<pp::app::DocumentCanvasFacePayloadInput> payloads;
};
void append_legacy_layer_payloads(
Layer& layer,
int frame_index,
LegacyLayerPayloadStorage& storage)
{
auto snapshot = layer.snapshot(frame_index);
for (std::uint32_t face_index = 0; face_index < pp::document::cube_face_count; ++face_index) {
if (!snapshot.m_dirty_face[face_index] || snapshot.image[face_index] == nullptr) {
continue;
}
const auto& box = snapshot.m_dirty_box[face_index];
const auto x = legacy_nonnegative_u32(box.x);
const auto y = legacy_nonnegative_u32(box.y);
const auto width = legacy_nonnegative_u32(box.z - box.x);
const auto height = legacy_nonnegative_u32(box.w - box.y);
if (width == 0U || height == 0U) {
continue;
}
const auto byte_count = static_cast<std::size_t>(width) * static_cast<std::size_t>(height)
* pp::document::rgba8_components;
storage.bytes.push_back(std::vector<std::uint8_t>(byte_count));
std::memcpy(storage.bytes.back().data(), snapshot.image[face_index].get(), byte_count);
storage.payloads.push_back(pp::app::DocumentCanvasFacePayloadInput {
.frame_index = static_cast<std::uint32_t>(std::max(frame_index, 0)),
.face_index = face_index,
.x = x,
.y = y,
.width = width,
.height = height,
.rgba8 = std::span<const std::uint8_t>(storage.bytes.back().data(), storage.bytes.back().size()),
});
}
}
} // namespace
bool legacy_document_canvas_available(const App& app) noexcept
@@ -141,6 +191,67 @@ pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult> capture_legacy_can
return capture_legacy_canvas_document_snapshot(*app.canvas->m_canvas);
}
pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult> capture_legacy_canvas_document_payload_snapshot(
Canvas& canvas)
{
std::vector<std::string> layer_names;
std::vector<std::vector<std::uint32_t>> layer_frame_durations;
std::vector<LegacyLayerPayloadStorage> layer_payload_storage;
std::vector<pp::app::DocumentCanvasLayerSnapshotInput> layers;
layer_names.reserve(canvas.m_layers.size());
layer_frame_durations.reserve(canvas.m_layers.size());
layer_payload_storage.reserve(canvas.m_layers.size());
layers.reserve(canvas.m_layers.size());
for (const auto& legacy_layer : canvas.m_layers) {
if (!legacy_layer) {
continue;
}
layer_names.push_back(legacy_layer->m_name);
auto& durations = layer_frame_durations.emplace_back();
auto& payload_storage = layer_payload_storage.emplace_back();
const auto frame_count = legacy_layer->frames_count();
durations.reserve(static_cast<std::size_t>(std::max(frame_count, 0)));
for (int frame_index = 0; frame_index < frame_count; ++frame_index) {
durations.push_back(legacy_u32_or_zero(legacy_layer->frame_duration(frame_index)));
append_legacy_layer_payloads(*legacy_layer, frame_index, payload_storage);
}
layers.push_back(pp::app::DocumentCanvasLayerSnapshotInput {
.name = layer_names.back(),
.visible = legacy_layer->m_visible,
.alpha_locked = legacy_layer->m_alpha_locked,
.opacity = legacy_layer->m_opacity,
.blend_mode = legacy_layer->m_blend_mode,
.frame_durations_ms = std::span<const std::uint32_t>(durations),
.pending_face_payloads = payload_storage.payloads.size(),
.captured_face_payloads = std::span<const pp::app::DocumentCanvasFacePayloadInput>(
payload_storage.payloads),
});
}
return pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = legacy_u32_or_zero(canvas.m_width),
.height = legacy_u32_or_zero(canvas.m_height),
.active_layer_index = static_cast<std::size_t>(std::max(canvas.m_current_layer_idx, 0)),
.active_frame_index = static_cast<std::size_t>(std::max(canvas.m_anim_frame, 0)),
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
});
}
pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult> capture_legacy_canvas_document_payload_snapshot(
App& app)
{
if (!legacy_document_canvas_available(app)) {
return pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult>::failure(
pp::foundation::Status::invalid_argument("legacy document canvas payload snapshot requires a canvas"));
}
return capture_legacy_canvas_document_payload_snapshot(*app.canvas->m_canvas);
}
pp::foundation::Status execute_legacy_document_canvas_clear_plan(
App& app,
const pp::app::DocumentCanvasClearPlan& plan)

View File

@@ -14,6 +14,10 @@ namespace pp::panopainter {
capture_legacy_canvas_document_snapshot(const Canvas& canvas);
[[nodiscard]] pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult>
capture_legacy_canvas_document_snapshot(const App& app);
[[nodiscard]] pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult>
capture_legacy_canvas_document_payload_snapshot(Canvas& canvas);
[[nodiscard]] pp::foundation::Result<pp::app::DocumentCanvasSnapshotResult>
capture_legacy_canvas_document_payload_snapshot(App& app);
[[nodiscard]] pp::foundation::Status execute_legacy_document_canvas_clear_plan(
App& app,
const pp::app::DocumentCanvasClearPlan& plan);