Add targeted PPI payload automation

This commit is contained in:
2026-06-02 11:00:29 +02:00
parent ddca24779e
commit 1bc90d88b4
8 changed files with 203 additions and 26 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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;
}