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

@@ -240,7 +240,7 @@ if(TARGET pano_cli)
COMMAND pano_cli load-project --path "${CMAKE_CURRENT_SOURCE_DIR}/data/projects/minimal-project.ppi")
set_tests_properties(pano_cli_load_project_metadata_smoke PROPERTIES
LABELS "assets;document;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"load-project\".*\"pixelDataLoaded\":false.*\"document\":\\{\"width\":64,\"height\":32,\"layers\":1,\"frames\":1,\"animationDurationMs\":100,\"layerNames\":\\[\"Ink\"\\]")
PASS_REGULAR_EXPRESSION "\"command\":\"load-project\".*\"pixelDataLoaded\":false.*\"document\":\\{\"width\":64,\"height\":32,\"layers\":1,\"frames\":1,\"animationDurationMs\":100,\"layerNames\":\\[\"Ink\"\\],\"layerFrameCounts\":\\[1\\],\"layerDurationsMs\":\\[100\\]")
add_test(NAME pano_cli_parse_layout_smoke
COMMAND pano_cli parse-layout --path "${CMAKE_CURRENT_SOURCE_DIR}/data/layouts/simple-layout.xml")

View File

@@ -34,6 +34,8 @@ void creates_document_with_default_layers(pp::tests::Harness& h)
PP_EXPECT(h, document.value().active_layer_index() == 0U);
PP_EXPECT(h, document.value().frames().size() == 1U);
PP_EXPECT(h, document.value().frames()[0].duration_ms == 100U);
PP_EXPECT(h, document.value().layers()[0].frames.size() == 1U);
PP_EXPECT(h, document.value().layers()[0].frames[0].duration_ms == 100U);
PP_EXPECT(h, document.value().animation_duration_ms() == 100U);
PP_EXPECT(h, document.value().active_frame_index() == 0U);
}
@@ -172,6 +174,7 @@ void creates_document_from_snapshot_metadata(pp::tests::Harness& h)
.alpha_locked = true,
.opacity = 0.5F,
.blend_mode = BlendMode::screen,
.frames = {},
},
{
.name = "Glaze",
@@ -179,6 +182,7 @@ void creates_document_from_snapshot_metadata(pp::tests::Harness& h)
.alpha_locked = false,
.opacity = 0.75F,
.blend_mode = BlendMode::overlay,
.frames = {},
},
};
const AnimationFrame frames[] {
@@ -199,16 +203,61 @@ void creates_document_from_snapshot_metadata(pp::tests::Harness& h)
PP_EXPECT(h, !document_result.value().layers()[0].visible);
PP_EXPECT(h, document_result.value().layers()[0].alpha_locked);
PP_EXPECT(h, document_result.value().layers()[0].blend_mode == BlendMode::screen);
PP_EXPECT(h, document_result.value().layers()[0].frames.size() == 2U);
PP_EXPECT(h, document_result.value().layers()[0].frames[1].duration_ms == 250U);
PP_EXPECT(h, document_result.value().frames().size() == 2U);
PP_EXPECT(h, document_result.value().animation_duration_ms() == 350U);
}
void preserves_per_layer_snapshot_timelines(pp::tests::Harness& h)
{
const AnimationFrame project_frames[] {
{ .duration_ms = 100 },
};
const AnimationFrame short_layer_frames[] {
{ .duration_ms = 100 },
{ .duration_ms = 150 },
};
const AnimationFrame long_layer_frames[] {
{ .duration_ms = 500 },
};
const DocumentLayerConfig layers[] {
{
.name = "Short",
.frames = short_layer_frames,
},
{
.name = "Long",
.frames = long_layer_frames,
},
};
const auto document_result = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
.width = 128,
.height = 64,
.layers = layers,
.frames = project_frames,
});
PP_EXPECT(h, document_result.ok());
PP_EXPECT(h, document_result.value().frames().size() == 1U);
PP_EXPECT(h, document_result.value().layers()[0].frames.size() == 2U);
PP_EXPECT(h, document_result.value().layers()[1].frames.size() == 1U);
PP_EXPECT(h, document_result.value().layers()[0].frames[1].duration_ms == 150U);
PP_EXPECT(h, document_result.value().layers()[1].frames[0].duration_ms == 500U);
PP_EXPECT(h, document_result.value().animation_duration_ms() == 500U);
const auto layer_duration = document_result.value().layer_animation_duration_ms(0);
PP_EXPECT(h, layer_duration.ok());
PP_EXPECT(h, layer_duration.value() == 250U);
}
void rejects_invalid_snapshot_metadata(pp::tests::Harness& h)
{
const DocumentLayerConfig layers[] { { .name = "Ink" } };
const DocumentLayerConfig layers[] { { .name = "Ink", .frames = {} } };
const AnimationFrame frames[] { { .duration_ms = 100 } };
const AnimationFrame bad_frames[] { { .duration_ms = 0 } };
const DocumentLayerConfig bad_layers[] { { .name = "" } };
const DocumentLayerConfig bad_layers[] { { .name = "", .frames = {} } };
const DocumentLayerConfig bad_layer_frames[] { { .name = "Ink", .frames = bad_frames } };
const auto no_layers = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
.width = 64,
@@ -234,6 +283,12 @@ void rejects_invalid_snapshot_metadata(pp::tests::Harness& h)
.layers = bad_layers,
.frames = frames,
});
const auto bad_layer_frame = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
.width = 64,
.height = 64,
.layers = bad_layer_frames,
.frames = frames,
});
PP_EXPECT(h, !no_layers.ok());
PP_EXPECT(h, no_layers.status().code == StatusCode::invalid_argument);
@@ -243,6 +298,8 @@ void rejects_invalid_snapshot_metadata(pp::tests::Harness& h)
PP_EXPECT(h, bad_frame.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_layer.ok());
PP_EXPECT(h, bad_layer.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !bad_layer_frame.ok());
PP_EXPECT(h, bad_layer_frame.status().code == StatusCode::invalid_argument);
}
void manages_animation_frames_and_duration(pp::tests::Harness& h)
@@ -257,6 +314,7 @@ void manages_animation_frames_and_duration(pp::tests::Harness& h)
PP_EXPECT(h, added.value() == 1U);
PP_EXPECT(h, document.active_frame_index() == 1U);
PP_EXPECT(h, document.frames()[1].duration_ms == 250U);
PP_EXPECT(h, document.layers()[0].frames[1].duration_ms == 250U);
const auto duplicated = document.duplicate_frame(1);
PP_EXPECT(h, duplicated.ok());
@@ -264,6 +322,7 @@ void manages_animation_frames_and_duration(pp::tests::Harness& h)
PP_EXPECT(h, document.frames()[2].duration_ms == 250U);
PP_EXPECT(h, document.set_frame_duration(2, 333).ok());
PP_EXPECT(h, document.frames()[2].duration_ms == 333U);
PP_EXPECT(h, document.layers()[0].frames[2].duration_ms == 333U);
PP_EXPECT(h, document.animation_duration_ms() == 683U);
PP_EXPECT(h, document.remove_frame(1).ok());
@@ -457,6 +516,7 @@ int main()
harness.run("updates_layer_metadata", updates_layer_metadata);
harness.run("rejects_invalid_layer_metadata", rejects_invalid_layer_metadata);
harness.run("creates_document_from_snapshot_metadata", creates_document_from_snapshot_metadata);
harness.run("preserves_per_layer_snapshot_timelines", preserves_per_layer_snapshot_timelines);
harness.run("rejects_invalid_snapshot_metadata", rejects_invalid_snapshot_metadata);
harness.run("manages_animation_frames_and_duration", manages_animation_frames_and_duration);
harness.run("moves_frames_and_preserves_active_frame_identity", moves_frames_and_preserves_active_frame_identity);