Project legacy canvas metadata into documents

This commit is contained in:
2026-06-05 17:54:45 +02:00
parent a9ef2c598c
commit d0412e3bf9
11 changed files with 580 additions and 4 deletions

View File

@@ -1,8 +1,17 @@
#pragma once
#include "document/document.h"
#include "foundation/result.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace pp::app {
@@ -17,6 +26,34 @@ struct DocumentCanvasClearPlan {
bool no_op = true;
};
struct DocumentCanvasLayerSnapshotInput {
std::string_view name;
bool visible = true;
bool alpha_locked = false;
float opacity = 1.0F;
int blend_mode = 0;
std::span<const std::uint32_t> frame_durations_ms;
std::size_t pending_face_payloads = 0;
};
struct DocumentCanvasSnapshotInput {
bool has_canvas = true;
std::uint32_t width = 0;
std::uint32_t height = 0;
std::size_t active_layer_index = 0;
std::size_t active_frame_index = 0;
std::span<const DocumentCanvasLayerSnapshotInput> layers;
};
struct DocumentCanvasSnapshotResult {
pp::document::CanvasDocument document;
std::size_t layer_count = 0;
std::size_t frame_count = 0;
std::size_t pending_face_payloads = 0;
bool metadata_only = true;
bool requires_renderer_payload_readback = false;
};
class DocumentCanvasClearServices {
public:
virtual ~DocumentCanvasClearServices() = default;
@@ -35,6 +72,115 @@ public:
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasSnapshotResult> plan_document_canvas_snapshot(
DocumentCanvasSnapshotInput input)
{
if (!input.has_canvas) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
pp::foundation::Status::invalid_argument("document canvas snapshot requires a canvas"));
}
if (input.layers.empty()) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
pp::foundation::Status::invalid_argument("document canvas snapshot requires at least one layer"));
}
std::size_t frame_count = 1U;
std::size_t pending_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;
}
if (input.active_layer_index >= input.layers.size()) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
pp::foundation::Status::out_of_range("active canvas layer is outside the document snapshot"));
}
if (input.active_frame_index >= frame_count) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
pp::foundation::Status::out_of_range("active canvas frame is outside the document snapshot"));
}
std::vector<pp::document::AnimationFrame> root_frames;
root_frames.reserve(frame_count);
for (std::size_t frame_index = 0; frame_index < frame_count; ++frame_index) {
std::uint32_t duration_ms = 100U;
for (const auto& layer : input.layers) {
if (frame_index < layer.frame_durations_ms.size()) {
duration_ms = layer.frame_durations_ms[frame_index];
break;
}
}
root_frames.push_back(pp::document::AnimationFrame { .duration_ms = duration_ms });
}
std::vector<std::string> layer_names;
std::vector<std::vector<pp::document::AnimationFrame>> layer_frames;
std::vector<pp::document::DocumentLayerConfig> layer_configs;
layer_names.reserve(input.layers.size());
layer_frames.reserve(input.layers.size());
layer_configs.reserve(input.layers.size());
for (std::size_t layer_index = 0; layer_index < input.layers.size(); ++layer_index) {
const auto& layer = input.layers[layer_index];
if (layer.name.empty()) {
layer_names.push_back("Layer " + std::to_string(layer_index + 1U));
} else {
layer_names.push_back(std::string(layer.name));
}
layer_frames.push_back({});
auto& frames = layer_frames.back();
frames.reserve(layer.frame_durations_ms.empty() ? root_frames.size() : layer.frame_durations_ms.size());
if (layer.frame_durations_ms.empty()) {
frames = root_frames;
} else {
for (const auto duration_ms : layer.frame_durations_ms) {
frames.push_back(pp::document::AnimationFrame { .duration_ms = duration_ms });
}
}
layer_configs.push_back(pp::document::DocumentLayerConfig {
.name = layer_names.back(),
.visible = layer.visible,
.alpha_locked = layer.alpha_locked,
.opacity = layer.opacity,
.blend_mode = static_cast<pp::paint::BlendMode>(layer.blend_mode),
.frames = std::span<const pp::document::AnimationFrame>(frames),
});
}
auto document = pp::document::CanvasDocument::create_from_snapshot(pp::document::DocumentSnapshotConfig {
.width = input.width,
.height = input.height,
.layers = std::span<const pp::document::DocumentLayerConfig>(layer_configs),
.frames = std::span<const pp::document::AnimationFrame>(root_frames),
});
if (!document) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(document.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);
}
active_status = document.value().set_active_frame(input.active_frame_index);
if (!active_status.ok()) {
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(active_status);
}
return pp::foundation::Result<DocumentCanvasSnapshotResult>::success(DocumentCanvasSnapshotResult {
.document = std::move(document.value()),
.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,
});
}
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasClearPlan> plan_document_canvas_clear(
bool has_canvas,
float r = 0.0F,