Add targeted PPI payload automation
This commit is contained in:
@@ -122,9 +122,11 @@ Known local toolchain state:
|
||||
`pano_cli_load_project_metadata_smoke`.
|
||||
- `pano_cli save-project` writes generated multi-layer, multi-frame PPI files
|
||||
with configurable layer opacity, blend mode, alpha lock, and visibility
|
||||
through `pp_assets` and is covered by `pano_cli_save_project_roundtrip_smoke`
|
||||
through `pp_assets`; test dirty-face payloads can target explicit generated
|
||||
layer/frame slots. It is covered by `pano_cli_save_project_roundtrip_smoke`
|
||||
and `pano_cli_save_project_payload_roundtrip_smoke`, which reload generated
|
||||
metadata-only and dirty-face-payload projects through `pano_cli load-project`.
|
||||
metadata-only and targeted dirty-face-payload projects through
|
||||
`pano_cli load-project`.
|
||||
- `pano_cli create-document` supports `--frames` and `--frame-duration-ms` and
|
||||
is covered by `pano_cli_create_animation_document_smoke`.
|
||||
- `pano_cli simulate-document-edits` exercises pure document layer/frame edit
|
||||
@@ -287,9 +289,10 @@ Known local toolchain state:
|
||||
automation and is covered by `pano_cli_export_image_roundtrip_smoke`; full
|
||||
legacy canvas export remains a future CLI task.
|
||||
- `pano_cli save-project` exposes generated multi-layer, multi-frame PPI
|
||||
writing with layer metadata through JSON automation and is covered by
|
||||
metadata-only and dirty-face-payload round-trip smoke tests; full legacy
|
||||
canvas save parity remains tracked by DEBT-0013.
|
||||
writing with layer metadata and targeted dirty-face layer/frame payloads
|
||||
through JSON automation and is covered by metadata-only and
|
||||
dirty-face-payload round-trip smoke tests; full legacy canvas save parity
|
||||
remains tracked by DEBT-0013.
|
||||
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
|
||||
through the vcpkg preset; default and Android validation still use the
|
||||
retained vendored fallback tracked by DEBT-0012.
|
||||
|
||||
@@ -31,7 +31,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0010 | Open | Modernization | `pp_document` is a pure layer/frame/document/undo-history model with alpha-lock metadata, snapshot construction, per-layer frame metadata, renderer-free RGBA8 face payload storage, and renderer-free alpha8 selection-mask storage, but it is not yet wired to legacy `Canvas`, save, or legacy action commands | Keep extraction incremental while preserving app behavior | `ctest --preset desktop-fast --build-config Debug`; `pano_cli create-document --width 64 --height 32 --layers 2`; `pano_cli load-project --path tests\data\projects\minimal-project.ppi`; `pp_document_ppi_import_tests`; `pano_cli_simulate_document_edits_smoke` | Legacy document behavior is represented by `pp_document` tests and the app consumes it through a boundary/facade |
|
||||
| DEBT-0011 | Open | Modernization | `package-smoke` validates the Windows CMake app artifact only, not AppX/APK/Apple/WebGL package outputs | Platform package targets are not migrated to root CMake yet | `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Package-smoke covers Windows AppX, Android APK variants, Apple bundles, and WebGL output where local toolchains are present |
|
||||
| DEBT-0012 | Open | Modernization | `pp_ui_core` uses vcpkg tinyxml2 on `windows-msvc-vcpkg-headless`, but retains `pp_vendor_tinyxml2` for default and unproven platform presets | Mobile/AppX/Apple triplets and app packaging still need validation before removing the vendored fallback | `ctest --preset desktop-fast-vcpkg --build-config Debug`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64` | All supported presets consume vcpkg tinyxml2 or document a permanent vendored exception |
|
||||
| DEBT-0013 | Open | Modernization | `pp_assets`, `pano_cli inspect-project`, `pano_cli load-project`, and `pano_cli save-project` validate the fixed PPI header, thumbnail/body byte layout, generated multi-layer/multi-frame PPI writing with layer opacity/blend/alpha-lock/visibility metadata, metadata-only and dirty-face-payload save/load round-trips, layer/frame index, dirty-face descriptors, dirty-face PNG payload metadata, asset-level RGBA PNG payload decoding, and decoded pixel attachment to `pp_document`, but full legacy PPI round-trip parity is not yet extracted | Full PPI save parity requires staged extraction of legacy `Canvas` serialization and image/layer payload handling | `ctest --preset desktop-fast --build-config Debug`; `pp_assets_image_pixels_tests`; `pp_assets_ppi_header_tests`; `pp_document_ppi_import_tests`; `pano_cli_inspect_project_layout_smoke`; `pano_cli_load_project_metadata_smoke`; `pano_cli_save_project_roundtrip_smoke`; `pano_cli_save_project_payload_roundtrip_smoke` | Full PPI load/save fixtures cover thumbnails, decoded layer face payloads attached to documents, frames, corrupt payloads, dirty-face payload saving, arbitrary legacy canvas payload/layout combinations, and legacy app round-trip compatibility |
|
||||
| DEBT-0013 | Open | Modernization | `pp_assets`, `pano_cli inspect-project`, `pano_cli load-project`, and `pano_cli save-project` validate the fixed PPI header, thumbnail/body byte layout, generated multi-layer/multi-frame PPI writing with layer opacity/blend/alpha-lock/visibility metadata, metadata-only and targeted dirty-face-payload save/load round-trips, layer/frame index, dirty-face descriptors, dirty-face PNG payload metadata, asset-level RGBA PNG payload decoding, and decoded pixel attachment to `pp_document`, but full legacy PPI round-trip parity is not yet extracted | Full PPI save parity requires staged extraction of legacy `Canvas` serialization and image/layer payload handling | `ctest --preset desktop-fast --build-config Debug`; `pp_assets_image_pixels_tests`; `pp_assets_ppi_header_tests`; `pp_document_ppi_import_tests`; `pano_cli_inspect_project_layout_smoke`; `pano_cli_load_project_metadata_smoke`; `pano_cli_save_project_roundtrip_smoke`; `pano_cli_save_project_payload_roundtrip_smoke` | Full PPI load/save fixtures cover thumbnails, decoded layer face payloads attached to documents, frames, corrupt payloads, dirty-face payload saving, arbitrary legacy canvas payload/layout combinations, and legacy app round-trip compatibility |
|
||||
| DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
@@ -347,7 +347,8 @@ payloads are present.
|
||||
`pano_cli save-project` writes generated multi-layer, multi-frame PPI files
|
||||
with layer opacity, blend mode, alpha lock, and visibility metadata through the
|
||||
extracted `pp_assets` writer and round-trips metadata-only and test
|
||||
dirty-face-payload variants through `load-project`.
|
||||
dirty-face-payload variants through `load-project`; dirty-face payloads can be
|
||||
targeted to explicit generated layer/frame slots for animation coverage.
|
||||
`pano_cli create-document` can create simple animation documents with explicit
|
||||
frame count/duration. `pano_cli simulate-document-edits` exercises pure
|
||||
layer metadata, frame reordering, active-index preservation, tiny face-payload
|
||||
@@ -680,7 +681,8 @@ Results:
|
||||
payload rejection.
|
||||
- `pp_assets_ppi_header_tests` passed, including PPI thumbnail/body layout,
|
||||
body summary validation, layer/frame indexing, dirty-face PNG payload
|
||||
metadata validation, and decoded dirty-face payload coverage.
|
||||
metadata validation, targeted layer/frame dirty-face writing, and decoded
|
||||
dirty-face payload coverage.
|
||||
- `pp_assets_settings_document_tests` passed.
|
||||
- `pp_paint_brush_tests` passed.
|
||||
- `pp_paint_blend_tests` passed.
|
||||
@@ -725,8 +727,9 @@ Results:
|
||||
`pp_assets` PPI writer can save a generated multi-frame PPI and reload it
|
||||
through `pano_cli load-project`.
|
||||
- `pano_cli_save_project_payload_roundtrip_smoke` passed and proves the
|
||||
`pp_assets` PPI writer can save a compressed RGBA PNG dirty-face payload and
|
||||
reload it as decoded `pp_document` face-pixel data.
|
||||
`pp_assets` PPI writer can save a compressed RGBA PNG dirty-face payload to
|
||||
an explicit layer/frame slot, inspect the serialized descriptor, and reload
|
||||
it as decoded `pp_document` face-pixel data.
|
||||
- `pano_cli_parse_layout_smoke` passed.
|
||||
- `pano_cli_simulate_stroke_smoke` passed and reports deterministic stroke
|
||||
sample counts/distances.
|
||||
@@ -794,9 +797,10 @@ Results:
|
||||
and has a save/import round-trip smoke test. Full legacy canvas export
|
||||
remains a future `pano_cli` task.
|
||||
- `pano_cli save-project` exposes generated multi-layer, multi-frame PPI
|
||||
writing with layer metadata through JSON automation and is covered by
|
||||
metadata-only and dirty-face-payload save/load round-trip smoke tests. Full
|
||||
legacy canvas save parity remains tracked by DEBT-0013.
|
||||
writing with layer metadata and targeted dirty-face layer/frame payloads
|
||||
through JSON automation and is covered by metadata-only and
|
||||
dirty-face-payload save/load round-trip smoke tests. Full legacy canvas save
|
||||
parity remains tracked by DEBT-0013.
|
||||
- PowerShell package-smoke wrapper validates the Windows CMake app executable
|
||||
and runtime `data/` copy.
|
||||
- Android arm64 configured with NDK 29.0.14206865 through the platform-build
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "assets/image_metadata.h"
|
||||
#include "foundation/binary_stream.h"
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
@@ -705,19 +706,33 @@ pp::foundation::Result<std::vector<std::byte>> create_minimal_ppi_project(PpiMin
|
||||
}
|
||||
}
|
||||
|
||||
bool seen_faces[6] {};
|
||||
std::vector<std::array<bool, 6>> seen_faces(
|
||||
static_cast<std::size_t>(config.layer_count) * static_cast<std::size_t>(config.frame_count));
|
||||
std::uint64_t total_payload_bytes = 0;
|
||||
for (const auto& face : config.dirty_faces) {
|
||||
if (face.layer_index >= config.layer_count) {
|
||||
return pp::foundation::Result<std::vector<std::byte>>::failure(
|
||||
pp::foundation::Status::out_of_range("PPI dirty face layer index is outside the layer list"));
|
||||
}
|
||||
|
||||
if (face.frame_index >= config.frame_count) {
|
||||
return pp::foundation::Result<std::vector<std::byte>>::failure(
|
||||
pp::foundation::Status::out_of_range("PPI dirty face frame index is outside the frame list"));
|
||||
}
|
||||
|
||||
if (face.face_index >= 6U) {
|
||||
return pp::foundation::Result<std::vector<std::byte>>::failure(
|
||||
pp::foundation::Status::out_of_range("PPI dirty face index is outside the cube face list"));
|
||||
}
|
||||
|
||||
if (seen_faces[face.face_index]) {
|
||||
const auto slot_index =
|
||||
static_cast<std::size_t>(face.layer_index) * static_cast<std::size_t>(config.frame_count)
|
||||
+ static_cast<std::size_t>(face.frame_index);
|
||||
if (seen_faces[slot_index][face.face_index]) {
|
||||
return pp::foundation::Result<std::vector<std::byte>>::failure(
|
||||
pp::foundation::Status::invalid_argument("PPI dirty face index is duplicated"));
|
||||
pp::foundation::Status::invalid_argument("PPI dirty face slot is duplicated"));
|
||||
}
|
||||
seen_faces[face.face_index] = true;
|
||||
seen_faces[slot_index][face.face_index] = true;
|
||||
|
||||
if (face.width == 0 || face.height == 0) {
|
||||
return pp::foundation::Result<std::vector<std::byte>>::failure(
|
||||
@@ -790,12 +805,11 @@ pp::foundation::Result<std::vector<std::byte>> create_minimal_ppi_project(PpiMin
|
||||
append_u32(bytes, config.frame_duration_ms);
|
||||
for (std::uint32_t face = 0; face < 6U; ++face) {
|
||||
const PpiDirtyFacePayloadConfig* dirty_face = nullptr;
|
||||
if (layer == 0U && frame == 0U) {
|
||||
for (const auto& candidate : config.dirty_faces) {
|
||||
if (candidate.face_index == face) {
|
||||
dirty_face = &candidate;
|
||||
break;
|
||||
}
|
||||
for (const auto& candidate : config.dirty_faces) {
|
||||
if (candidate.layer_index == layer && candidate.frame_index == frame
|
||||
&& candidate.face_index == face) {
|
||||
dirty_face = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ struct PpiDecodedProjectImages {
|
||||
};
|
||||
|
||||
struct PpiDirtyFacePayloadConfig {
|
||||
std::uint32_t layer_index = 0;
|
||||
std::uint32_t frame_index = 0;
|
||||
std::uint32_t face_index = 0;
|
||||
std::uint32_t x = 0;
|
||||
std::uint32_t y = 0;
|
||||
|
||||
@@ -486,6 +486,8 @@ void creates_minimal_project_with_dirty_face_payload(pp::tests::Harness& h)
|
||||
const auto png_payload = transparent_png_1x1();
|
||||
const pp::assets::PpiDirtyFacePayloadConfig dirty_faces[] {
|
||||
{
|
||||
.layer_index = 0,
|
||||
.frame_index = 0,
|
||||
.face_index = 2,
|
||||
.x = 4,
|
||||
.y = 5,
|
||||
@@ -520,11 +522,64 @@ void creates_minimal_project_with_dirty_face_payload(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, decoded.value().faces[0].image.pixels.size() == 4U);
|
||||
}
|
||||
|
||||
void creates_minimal_project_with_targeted_dirty_face_payloads(pp::tests::Harness& h)
|
||||
{
|
||||
const auto png_payload = transparent_png_1x1();
|
||||
const pp::assets::PpiDirtyFacePayloadConfig dirty_faces[] {
|
||||
{
|
||||
.layer_index = 0,
|
||||
.frame_index = 1,
|
||||
.face_index = 2,
|
||||
.x = 4,
|
||||
.y = 5,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.png_rgba8 = std::span<const std::byte>(png_payload.data(), png_payload.size()),
|
||||
},
|
||||
{
|
||||
.layer_index = 1,
|
||||
.frame_index = 0,
|
||||
.face_index = 2,
|
||||
.x = 6,
|
||||
.y = 7,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.png_rgba8 = std::span<const std::byte>(png_payload.data(), png_payload.size()),
|
||||
},
|
||||
};
|
||||
const auto project = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig {
|
||||
.width = 256,
|
||||
.height = 128,
|
||||
.layer_name = "Payload",
|
||||
.layer_metadata = {},
|
||||
.layer_count = 2,
|
||||
.frame_count = 2,
|
||||
.frame_duration_ms = 333,
|
||||
.dirty_faces = std::span<const pp::assets::PpiDirtyFacePayloadConfig>(dirty_faces, 2),
|
||||
});
|
||||
|
||||
PP_EXPECT(h, project.ok());
|
||||
const auto decoded = decode_ppi_project_images(project.value());
|
||||
PP_EXPECT(h, decoded.ok());
|
||||
PP_EXPECT(h, decoded.value().project.body.summary.dirty_face_count == 2U);
|
||||
PP_EXPECT(h, decoded.value().faces.size() == 2U);
|
||||
PP_EXPECT(h, decoded.value().faces[0].layer_index == 0U);
|
||||
PP_EXPECT(h, decoded.value().faces[0].frame_index == 1U);
|
||||
PP_EXPECT(h, decoded.value().faces[0].face_index == 2U);
|
||||
PP_EXPECT(h, decoded.value().faces[0].descriptor.x0 == 4U);
|
||||
PP_EXPECT(h, decoded.value().faces[1].layer_index == 1U);
|
||||
PP_EXPECT(h, decoded.value().faces[1].frame_index == 0U);
|
||||
PP_EXPECT(h, decoded.value().faces[1].face_index == 2U);
|
||||
PP_EXPECT(h, decoded.value().faces[1].descriptor.x0 == 6U);
|
||||
}
|
||||
|
||||
void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
|
||||
{
|
||||
const auto png_payload = transparent_png_1x1();
|
||||
const pp::assets::PpiDirtyFacePayloadConfig duplicate_faces[] {
|
||||
{
|
||||
.layer_index = 0,
|
||||
.frame_index = 0,
|
||||
.face_index = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
@@ -533,6 +588,8 @@ void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
|
||||
.png_rgba8 = std::span<const std::byte>(png_payload.data(), png_payload.size()),
|
||||
},
|
||||
{
|
||||
.layer_index = 0,
|
||||
.frame_index = 0,
|
||||
.face_index = 0,
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
@@ -541,6 +598,30 @@ void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
|
||||
.png_rgba8 = std::span<const std::byte>(png_payload.data(), png_payload.size()),
|
||||
},
|
||||
};
|
||||
const pp::assets::PpiDirtyFacePayloadConfig bad_layer_faces[] {
|
||||
{
|
||||
.layer_index = 1,
|
||||
.frame_index = 0,
|
||||
.face_index = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.png_rgba8 = std::span<const std::byte>(png_payload.data(), png_payload.size()),
|
||||
},
|
||||
};
|
||||
const pp::assets::PpiDirtyFacePayloadConfig bad_frame_faces[] {
|
||||
{
|
||||
.layer_index = 0,
|
||||
.frame_index = 1,
|
||||
.face_index = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.png_rgba8 = std::span<const std::byte>(png_payload.data(), png_payload.size()),
|
||||
},
|
||||
};
|
||||
const auto no_size = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig {
|
||||
.width = 0,
|
||||
.height = 128,
|
||||
@@ -625,6 +706,26 @@ void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
|
||||
.frame_duration_ms = 100,
|
||||
.dirty_faces = std::span<const pp::assets::PpiDirtyFacePayloadConfig>(duplicate_faces, 2),
|
||||
});
|
||||
const auto bad_layer_dirty_face = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig {
|
||||
.width = 128,
|
||||
.height = 128,
|
||||
.layer_name = "Ink",
|
||||
.layer_metadata = {},
|
||||
.layer_count = 1,
|
||||
.frame_count = 1,
|
||||
.frame_duration_ms = 100,
|
||||
.dirty_faces = std::span<const pp::assets::PpiDirtyFacePayloadConfig>(bad_layer_faces, 1),
|
||||
});
|
||||
const auto bad_frame_dirty_face = create_minimal_ppi_project(pp::assets::PpiMinimalProjectConfig {
|
||||
.width = 128,
|
||||
.height = 128,
|
||||
.layer_name = "Ink",
|
||||
.layer_metadata = {},
|
||||
.layer_count = 1,
|
||||
.frame_count = 1,
|
||||
.frame_duration_ms = 100,
|
||||
.dirty_faces = std::span<const pp::assets::PpiDirtyFacePayloadConfig>(bad_frame_faces, 1),
|
||||
});
|
||||
|
||||
PP_EXPECT(h, !no_size.ok());
|
||||
PP_EXPECT(h, no_size.status().code == StatusCode::invalid_argument);
|
||||
@@ -642,6 +743,10 @@ void rejects_invalid_minimal_project_writer_inputs(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, bad_blend_mode.status().code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !duplicate_dirty_face.ok());
|
||||
PP_EXPECT(h, duplicate_dirty_face.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !bad_layer_dirty_face.ok());
|
||||
PP_EXPECT(h, bad_layer_dirty_face.status().code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !bad_frame_dirty_face.ok());
|
||||
PP_EXPECT(h, bad_frame_dirty_face.status().code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -665,6 +770,7 @@ int main()
|
||||
harness.run("creates_minimal_project_with_multiple_layers", creates_minimal_project_with_multiple_layers);
|
||||
harness.run("creates_minimal_project_with_multiple_frames", creates_minimal_project_with_multiple_frames);
|
||||
harness.run("creates_minimal_project_with_dirty_face_payload", creates_minimal_project_with_dirty_face_payload);
|
||||
harness.run("creates_minimal_project_with_targeted_dirty_face_payloads", creates_minimal_project_with_targeted_dirty_face_payloads);
|
||||
harness.run("rejects_invalid_minimal_project_writer_inputs", rejects_invalid_minimal_project_writer_inputs);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ execute_process(
|
||||
--frames 2
|
||||
--frame-duration-ms 321
|
||||
--include-test-face-payload
|
||||
--payload-layer 1
|
||||
--payload-frame 1
|
||||
RESULT_VARIABLE save_result
|
||||
OUTPUT_VARIABLE save_output
|
||||
ERROR_VARIABLE save_error)
|
||||
@@ -32,7 +34,14 @@ string(FIND "${save_output}" "\"command\":\"save-project\"" save_command_index)
|
||||
string(FIND "${save_output}" "\"layers\":2" save_layers_index)
|
||||
string(FIND "${save_output}" "\"frames\":2" save_frames_index)
|
||||
string(FIND "${save_output}" "\"facePayloads\":1" save_payload_index)
|
||||
if(save_command_index LESS 0 OR save_layers_index LESS 0 OR save_frames_index LESS 0 OR save_payload_index LESS 0)
|
||||
string(FIND "${save_output}" "\"payloadLayer\":1" save_payload_layer_index)
|
||||
string(FIND "${save_output}" "\"payloadFrame\":1" save_payload_frame_index)
|
||||
if(save_command_index LESS 0
|
||||
OR save_layers_index LESS 0
|
||||
OR save_frames_index LESS 0
|
||||
OR save_payload_index LESS 0
|
||||
OR save_payload_layer_index LESS 0
|
||||
OR save_payload_frame_index LESS 0)
|
||||
message(FATAL_ERROR "save-project payload output did not contain expected summary: ${save_output}")
|
||||
endif()
|
||||
|
||||
@@ -40,6 +49,21 @@ if(NOT EXISTS "${OUTPUT_PATH}")
|
||||
message(FATAL_ERROR "save-project did not create ${OUTPUT_PATH}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${PANO_CLI}" inspect-project --path "${OUTPUT_PATH}"
|
||||
RESULT_VARIABLE inspect_result
|
||||
OUTPUT_VARIABLE inspect_output
|
||||
ERROR_VARIABLE inspect_error)
|
||||
|
||||
if(NOT inspect_result EQUAL 0)
|
||||
message(FATAL_ERROR "inspect-project failed after payload save-project: ${inspect_output}${inspect_error}")
|
||||
endif()
|
||||
|
||||
string(REGEX MATCH "\"index\":1,\"storedOrder\":1,\"name\":\"Payload 2\".*\"index\":1,\"durationMs\":321,\"dirtyFaces\":\\[\\{\"face\":0" inspect_targeted_payload "${inspect_output}")
|
||||
if(NOT inspect_targeted_payload)
|
||||
message(FATAL_ERROR "inspect-project output did not show the payload on layer 1 frame 1: ${inspect_output}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${PANO_CLI}" load-project --path "${OUTPUT_PATH}"
|
||||
RESULT_VARIABLE load_result
|
||||
|
||||
@@ -46,6 +46,8 @@ struct SaveProjectArgs {
|
||||
std::uint32_t frames = 1;
|
||||
std::uint32_t frame_duration_ms = 100;
|
||||
bool include_test_face_payload = false;
|
||||
std::uint32_t payload_layer = 0;
|
||||
std::uint32_t payload_frame = 0;
|
||||
};
|
||||
|
||||
struct InspectImageArgs {
|
||||
@@ -206,7 +208,7 @@ void print_help()
|
||||
<< " load-project --path FILE\n"
|
||||
<< " parse-layout --path FILE\n"
|
||||
<< " record-render [--width N] [--height N]\n"
|
||||
<< " save-project --path FILE --width N --height N [--layer-name NAME] [--layer-opacity N] [--blend-mode N] [--alpha-locked] [--hidden] [--layers N] [--frames N] [--frame-duration-ms N] [--include-test-face-payload]\n"
|
||||
<< " save-project --path FILE --width N --height N [--layer-name NAME] [--layer-opacity N] [--blend-mode N] [--alpha-locked] [--hidden] [--layers N] [--frames N] [--frame-duration-ms N] [--include-test-face-payload] [--payload-layer N] [--payload-frame N]\n"
|
||||
<< " simulate-document-edits [--width N] [--height N]\n"
|
||||
<< " simulate-document-history [--width N] [--height N] [--history N]\n"
|
||||
<< " simulate-image-import [--width N] [--height N]\n"
|
||||
@@ -341,7 +343,8 @@ pp::foundation::Status parse_save_project_args(int argc, char** argv, SaveProjec
|
||||
} else if (key == "--hidden") {
|
||||
args.visible = false;
|
||||
} else if (key == "--width" || key == "--height" || key == "--layers" || key == "--frames"
|
||||
|| key == "--blend-mode" || key == "--frame-duration-ms") {
|
||||
|| key == "--blend-mode" || key == "--frame-duration-ms" || key == "--payload-layer"
|
||||
|| key == "--payload-frame") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
@@ -361,6 +364,10 @@ pp::foundation::Status parse_save_project_args(int argc, char** argv, SaveProjec
|
||||
args.frames = value.value();
|
||||
} else if (key == "--blend-mode") {
|
||||
args.blend_mode = value.value();
|
||||
} else if (key == "--payload-layer") {
|
||||
args.payload_layer = value.value();
|
||||
} else if (key == "--payload-frame") {
|
||||
args.payload_frame = value.value();
|
||||
} else {
|
||||
args.frame_duration_ms = value.value();
|
||||
}
|
||||
@@ -403,6 +410,19 @@ pp::foundation::Status parse_save_project_args(int argc, char** argv, SaveProjec
|
||||
return pp::foundation::Status::invalid_argument("frame duration must be greater than zero");
|
||||
}
|
||||
|
||||
if (args.include_test_face_payload && args.payload_layer >= args.layers) {
|
||||
return pp::foundation::Status::out_of_range("payload layer must be inside the generated layer list");
|
||||
}
|
||||
|
||||
if (args.include_test_face_payload && args.payload_frame >= args.frames) {
|
||||
return pp::foundation::Status::out_of_range("payload frame must be inside the generated frame list");
|
||||
}
|
||||
|
||||
if (!args.include_test_face_payload && (args.payload_layer != 0 || args.payload_frame != 0)) {
|
||||
return pp::foundation::Status::invalid_argument(
|
||||
"payload layer/frame options require --include-test-face-payload");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
@@ -418,6 +438,8 @@ int save_project(int argc, char** argv)
|
||||
const auto test_payload = transparent_png_1x1_bytes();
|
||||
const pp::assets::PpiDirtyFacePayloadConfig dirty_faces[] {
|
||||
{
|
||||
.layer_index = args.payload_layer,
|
||||
.frame_index = args.payload_frame,
|
||||
.face_index = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
@@ -476,6 +498,8 @@ int save_project(int argc, char** argv)
|
||||
<< ",\"visible\":" << (args.visible ? "true" : "false")
|
||||
<< ",\"frameDurationMs\":" << args.frame_duration_ms
|
||||
<< ",\"facePayloads\":" << (args.include_test_face_payload ? 1 : 0)
|
||||
<< ",\"payloadLayer\":" << args.payload_layer
|
||||
<< ",\"payloadFrame\":" << args.payload_frame
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user