Preserve per-layer document timelines

This commit is contained in:
2026-06-01 13:05:14 +02:00
parent c16cab87bd
commit 10e5d5b5ae
8 changed files with 182 additions and 35 deletions

View File

@@ -33,6 +33,15 @@ namespace {
return "Layer " + std::to_string(index + 1U);
}
[[nodiscard]] std::uint64_t frame_duration_sum(std::span<const AnimationFrame> frames) noexcept
{
std::uint64_t duration = 0;
for (const auto& frame : frames) {
duration += frame.duration_ms;
}
return duration;
}
[[nodiscard]] pp::foundation::Status validate_layer_name(std::string_view name) noexcept
{
if (name.empty()) {
@@ -108,11 +117,14 @@ pp::foundation::Result<CanvasDocument> CanvasDocument::create(DocumentConfig con
CanvasDocument document;
document.width_ = config.width;
document.height_ = config.height;
document.frames_.push_back(AnimationFrame {});
document.layers_.reserve(config.layer_count);
for (std::uint32_t i = 0; i < config.layer_count; ++i) {
document.layers_.push_back(Layer { .name = default_layer_name(i) });
document.layers_.push_back(Layer {
.name = default_layer_name(i),
.frames = document.frames_,
});
}
document.frames_.push_back(AnimationFrame {});
return pp::foundation::Result<CanvasDocument>::success(document);
}
@@ -158,13 +170,33 @@ pp::foundation::Result<CanvasDocument> CanvasDocument::create_from_snapshot(Docu
return pp::foundation::Result<CanvasDocument>::failure(blend_status);
}
const auto layer_frames = layer_config.frames.empty() ? config.frames : layer_config.frames;
if (layer_frames.empty()) {
return pp::foundation::Result<CanvasDocument>::failure(
pp::foundation::Status::invalid_argument("document layer must contain at least one frame"));
}
if (layer_frames.size() > max_frame_count) {
return pp::foundation::Result<CanvasDocument>::failure(
pp::foundation::Status::out_of_range("document layer frame count exceeds the configured limit"));
}
for (const auto& frame_config : layer_frames) {
const auto duration_status = validate_frame_duration(frame_config.duration_ms);
if (!duration_status.ok()) {
return pp::foundation::Result<CanvasDocument>::failure(duration_status);
}
}
document.layers_.push_back(Layer {
.name = std::string(layer_config.name),
.visible = layer_config.visible,
.alpha_locked = layer_config.alpha_locked,
.opacity = layer_config.opacity,
.blend_mode = layer_config.blend_mode,
.frames = {},
});
document.layers_.back().frames.assign(layer_frames.begin(), layer_frames.end());
}
document.frames_.reserve(config.frames.size());
@@ -202,13 +234,23 @@ std::size_t CanvasDocument::active_frame_index() const noexcept
std::uint64_t CanvasDocument::animation_duration_ms() const noexcept
{
std::uint64_t duration = 0;
for (const auto& frame : frames_) {
duration += frame.duration_ms;
std::uint64_t duration = frame_duration_sum(frames_);
for (const auto& layer : layers_) {
duration = std::max(duration, frame_duration_sum(layer.frames));
}
return duration;
}
pp::foundation::Result<std::uint64_t> CanvasDocument::layer_animation_duration_ms(std::size_t index) const noexcept
{
const auto index_status = validate_layer_index(index, layers_.size());
if (!index_status.ok()) {
return pp::foundation::Result<std::uint64_t>::failure(index_status);
}
return pp::foundation::Result<std::uint64_t>::success(frame_duration_sum(layers_[index].frames));
}
std::span<const Layer> CanvasDocument::layers() const noexcept
{
return layers_;
@@ -236,6 +278,7 @@ pp::foundation::Result<std::size_t> CanvasDocument::add_layer(std::string_view n
}
layer.name = std::string(name);
}
layer.frames = frames_;
layers_.push_back(layer);
active_layer_index_ = layers_.size() - 1U;
return pp::foundation::Result<std::size_t>::success(active_layer_index_);
@@ -381,6 +424,9 @@ pp::foundation::Result<std::size_t> CanvasDocument::add_frame(std::uint32_t dura
}
frames_.push_back(AnimationFrame { .duration_ms = duration_ms });
for (auto& layer : layers_) {
layer.frames.push_back(AnimationFrame { .duration_ms = duration_ms });
}
active_frame_index_ = frames_.size() - 1U;
return pp::foundation::Result<std::size_t>::success(active_frame_index_);
}
@@ -400,6 +446,13 @@ pp::foundation::Result<std::size_t> CanvasDocument::duplicate_frame(std::size_t
const auto insert_at = index + 1U;
frames_.insert(frames_.begin() + static_cast<std::ptrdiff_t>(insert_at), frames_[index]);
for (auto& layer : layers_) {
if (index < layer.frames.size()) {
layer.frames.insert(
layer.frames.begin() + static_cast<std::ptrdiff_t>(insert_at),
layer.frames[index]);
}
}
active_frame_index_ = insert_at;
return pp::foundation::Result<std::size_t>::success(active_frame_index_);
}
@@ -416,6 +469,11 @@ pp::foundation::Status CanvasDocument::remove_frame(std::size_t index)
}
frames_.erase(frames_.begin() + static_cast<std::ptrdiff_t>(index));
for (auto& layer : layers_) {
if (index < layer.frames.size() && layer.frames.size() > 1U) {
layer.frames.erase(layer.frames.begin() + static_cast<std::ptrdiff_t>(index));
}
}
if (active_frame_index_ >= frames_.size()) {
active_frame_index_ = frames_.size() - 1U;
} else if (active_frame_index_ > index) {
@@ -438,6 +496,13 @@ pp::foundation::Status CanvasDocument::move_frame(std::size_t from, std::size_t
const auto frame = frames_[from];
frames_.erase(frames_.begin() + static_cast<std::ptrdiff_t>(from));
frames_.insert(frames_.begin() + static_cast<std::ptrdiff_t>(to), frame);
for (auto& layer : layers_) {
if (from < layer.frames.size() && to < layer.frames.size()) {
const auto layer_frame = layer.frames[from];
layer.frames.erase(layer.frames.begin() + static_cast<std::ptrdiff_t>(from));
layer.frames.insert(layer.frames.begin() + static_cast<std::ptrdiff_t>(to), layer_frame);
}
}
if (active_frame_index_ == from) {
active_frame_index_ = to;
@@ -463,6 +528,11 @@ pp::foundation::Status CanvasDocument::set_frame_duration(std::size_t index, std
}
frames_[index].duration_ms = duration_ms;
for (auto& layer : layers_) {
if (index < layer.frames.size()) {
layer.frames[index].duration_ms = duration_ms;
}
}
return pp::foundation::Status::success();
}

View File

@@ -25,16 +25,17 @@ struct DocumentConfig {
std::uint32_t layer_count = 1;
};
struct AnimationFrame {
std::uint32_t duration_ms = 100;
};
struct Layer {
std::string name;
bool visible = true;
bool alpha_locked = false;
float opacity = 1.0F;
pp::paint::BlendMode blend_mode = pp::paint::BlendMode::normal;
};
struct AnimationFrame {
std::uint32_t duration_ms = 100;
std::vector<AnimationFrame> frames;
};
struct DocumentLayerConfig {
@@ -43,6 +44,7 @@ struct DocumentLayerConfig {
bool alpha_locked = false;
float opacity = 1.0F;
pp::paint::BlendMode blend_mode = pp::paint::BlendMode::normal;
std::span<const AnimationFrame> frames;
};
struct DocumentSnapshotConfig {
@@ -62,6 +64,7 @@ public:
[[nodiscard]] std::size_t active_layer_index() const noexcept;
[[nodiscard]] std::size_t active_frame_index() const noexcept;
[[nodiscard]] std::uint64_t animation_duration_ms() const noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> layer_animation_duration_ms(std::size_t index) const noexcept;
[[nodiscard]] std::span<const Layer> layers() const noexcept;
[[nodiscard]] std::span<const AnimationFrame> frames() const noexcept;