From ba5c3069e187fb9e5f07b104e68e2af174552bd7 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Fri, 5 Jun 2026 18:24:58 +0200 Subject: [PATCH] Export captured canvas snapshots through document writer --- docs/modernization/build-inventory.md | 15 ++++++++---- docs/modernization/capability-map.md | 2 +- docs/modernization/debt.md | 9 ++++++-- docs/modernization/roadmap.md | 18 ++++++++++++--- src/app_core/document_canvas.h | 27 ++++++++++++++++++++++ tests/CMakeLists.txt | 4 ++-- tests/app_core/document_canvas_tests.cpp | 29 ++++++++++++++++++++++++ tools/pano_cli/main.cpp | 24 ++++++++++++++++++++ 8 files changed, 115 insertions(+), 13 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 27f5153..12c4b49 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -265,9 +265,12 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p state toward `pp_document::CanvasDocument`, including dimensions, active layer/frame, layer visibility/opacity/alpha/blend metadata, frame durations, captured RGBA8 face payloads, and remaining renderer payload-readback counts, - plus the save-readiness report now consumed before retained live saves, and - is covered by `pano_cli_plan_canvas_document_snapshot_smoke` plus the - payload-bearing snapshot smoke. + plus the save-readiness report now consumed before retained live saves. For + payload-complete or metadata-only snapshots, the same app-core boundary now + exports through the pure `pp_document` PPI writer and reports generated byte + counts plus decoded dirty-face counts in `ppiExport` JSON. It is covered by + `pano_cli_plan_canvas_document_snapshot_smoke` plus the payload-bearing + snapshot smoke. - `pano_cli save-document-project` writes that pure document export to a PPI file and is covered by `pano_cli_save_document_project_roundtrip_smoke`, which inspects and loads the generated file. @@ -1094,8 +1097,10 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p cleanup. Existing Save, Save As, Save Version, and save-before-workflow prepare and log a payload-bearing canvas document snapshot report before delegating to retained `Canvas::project_save`. Retained legacy UI/canvas - execution and actual save serialization remain tracked by `DEBT-0040`, - `DEBT-0041`, and `DEBT-0042`. + execution and actual live save serialization remain tracked by `DEBT-0040`, + `DEBT-0041`, and `DEBT-0042`; the pure snapshot-to-PPI export handoff is + already validated in `pp_app_core_document_canvas_tests` and + `pano_cli_plan_canvas_document_snapshot_payload_smoke`. - `src/legacy_document_export_services.*` is the current app-shell bridge between `pp_app_core` document export execution plans and live equirectangular, layers, animation-frame, depth, and cube-face export calls. It preserves diff --git a/docs/modernization/capability-map.md b/docs/modernization/capability-map.md index ba74f1e..b31fba6 100644 --- a/docs/modernization/capability-map.md +++ b/docs/modernization/capability-map.md @@ -11,7 +11,7 @@ and validation command. | Capability | Current Area | Target Owner | Required Tests | | --- | --- | --- | --- | -| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture, live-canvas-to-`pp_document` snapshot projection with captured RGBA8 payloads, pending renderer-readback counts, and save-readiness reporting before retained live saves | +| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture, live-canvas-to-`pp_document` snapshot projection with captured RGBA8 payloads, pending renderer-readback counts, save-readiness reporting before retained live saves, and pure PPI export from payload-complete snapshots | | Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, open-action plan tests, CLI route/action smoke, app open smoke | | Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/new-document resolution/overwrite/version-target decision tests, CLI session, new-document, document-file, and document-version smoke, app close/open/save/new/browse smoke | | Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior | diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 6265ada..3f6955d 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -479,6 +479,11 @@ agent or engineer to remove them without reconstructing context from chat. report before delegating to retained `Canvas::project_save`. The retained writer still owns PPI serialization, progress/threading, and compatibility quirks, so pure writer replacement remains open. +- 2026-06-05: DEBT-0010/DEBT-0013 were narrowed again. `pp_app_core` now + exports payload-complete or metadata-only canvas document snapshots through + the pure `pp_document` PPI writer and rejects snapshots that still require + renderer readback; `pano_cli plan-canvas-document-snapshot` reports + `ppiExport` readiness, byte counts, and decoded dirty-face counts. ## Open Debt @@ -492,10 +497,10 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, and root CMake now exposes a focused `panopainter_platform_build_vcpkg_ui_core` target for the vcpkg-backed `pp_ui_core`/tinyxml2 boundary, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_platform_build_vcpkg_ui_core` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions | | DEBT-0008 | Open | Modernization | `windows-msvc-default` and `windows-msvc-vcpkg-headless` explicitly select Visual Studio 18 2026 for local validation, but non-VS2026 CMake executables on PATH may not know that generator | The local machine has VS 2026, but using an older CMake can still default to Ninja or reject the VS 2026 generator | `cmake --preset windows-msvc-default`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug` | The repo automation invokes or locates a CMake executable that supports `Visual Studio 18 2026`, and VS 2026 generator validation is the normal Windows path without manual tool selection | | DEBT-0009 | Open | Modernization | Android root CMake validation currently builds headless targets only, while retained standard/Quest/Focus package CMake paths now have a refreshed CMake 3.10/C++23 baseline outside root CMake; automation queries `sdkmanager`, installs newer or missing SDK Manager NDK/CMake packages, selects the resulting pair before configure, and reports update decisions; root CMake exposes non-default platform-build and retained native package validation targets | Platform app entrypoints still live in legacy Gradle/CMake projects and need Phase 6 alignment | `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64`; `cmake --build --preset android-x64`; `cmake --build --preset android-quest-arm64`; `cmake --build --preset android-focus-arm64`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_platform_build_android_assets`; `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard`; `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages quest,focus -ConfigureOnly`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly -AndroidNativeChecks -PackageKinds android-standard-apk,android-quest-apk,android-focus-apk`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_native_package_smoke` | Android standard, Quest, and Focus/Wave package targets consume shared component targets and have package smoke commands | -| 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, snapshot-embedded face-payload validation, renderer-free alpha8 selection-mask storage, PPI import/export helpers, stroke-script-to-face-payload CLI automation, `pp_paint_renderer` document face/frame compositors, renderer-neutral six-face texture upload, OpenGL command-planner validation through CLI render automation, live Canvas snapshot projection through `pp_app_core`/`legacy_document_canvas_services`, captured RGBA8 payload attachment to `pp_document`, and live Save/Save As/Save Version/save-before-workflow snapshot-readiness reporting before retained save execution, but export/action-command adoption, pure save-writer replacement, and renderer-owned cube-face readback ownership are not yet wired | 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`; `pano_cli simulate-document-render --width 64 --height 32`; `pano_cli plan-canvas-document-snapshot --width 64 --height 32`; `pano_cli plan-canvas-document-snapshot --captured-face-payloads-per-layer 1`; `pp_document_tests`; `pp_document_ppi_import_tests`; `pp_document_ppi_export_tests`; `pp_paint_renderer_compositor_tests`; `pp_app_core_document_canvas_tests`; `pano_cli_simulate_document_edits_smoke`; `pano_cli_simulate_document_export_smoke`; `pano_cli_simulate_document_render_smoke`; `pano_cli_plan_canvas_document_snapshot_smoke`; `pano_cli_plan_canvas_document_snapshot_payload_smoke`; `pano_cli_save_document_project_roundtrip_smoke`; `pano_cli_apply_stroke_script_roundtrip_smoke`; `pano_cli_apply_stroke_script_rejects_tiny_canvas` | Legacy document behavior is represented by `pp_document`/`pp_paint_renderer` tests and the app consumes it through a boundary/facade | +| 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, snapshot-embedded face-payload validation, renderer-free alpha8 selection-mask storage, PPI import/export helpers, stroke-script-to-face-payload CLI automation, `pp_paint_renderer` document face/frame compositors, renderer-neutral six-face texture upload, OpenGL command-planner validation through CLI render automation, live Canvas snapshot projection through `pp_app_core`/`legacy_document_canvas_services`, captured RGBA8 payload attachment to `pp_document`, live Save/Save As/Save Version/save-before-workflow snapshot-readiness reporting before retained save execution, and pure app-core PPI export for payload-complete canvas snapshots, but export/action-command adoption, live save-writer replacement, and renderer-owned cube-face readback ownership are not yet wired | 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`; `pano_cli simulate-document-render --width 64 --height 32`; `pano_cli plan-canvas-document-snapshot --width 64 --height 32`; `pano_cli plan-canvas-document-snapshot --captured-face-payloads-per-layer 1`; `pp_document_tests`; `pp_document_ppi_import_tests`; `pp_document_ppi_export_tests`; `pp_paint_renderer_compositor_tests`; `pp_app_core_document_canvas_tests`; `pano_cli_simulate_document_edits_smoke`; `pano_cli_simulate_document_export_smoke`; `pano_cli_simulate_document_render_smoke`; `pano_cli_plan_canvas_document_snapshot_smoke`; `pano_cli_plan_canvas_document_snapshot_payload_smoke`; `pano_cli_save_document_project_roundtrip_smoke`; `pano_cli_apply_stroke_script_roundtrip_smoke`; `pano_cli_apply_stroke_script_rejects_tiny_canvas` | Legacy document behavior is represented by `pp_document`/`pp_paint_renderer` tests and the app consumes it through a boundary/facade | | DEBT-0011 | Open | Modernization | `package-smoke` validates the Windows CMake app artifact and launch-folder DLL payload, and reports a structured package readiness matrix for Windows AppX, Android standard/Quest/Focus APKs, Apple bundles, Linux app output, and WebGL output; the Windows app smoke passes the configure-time CMake executable so VS 2026 generator validation does not depend on `cmake` from PATH, retained Android package native CMake paths, and retained Linux/WebGL CMake baseline metadata are reachable from package validation and root CMake package-readiness targets, but Windows AppX/APK/Linux/Apple/WebGL package outputs are still `blocked` because root CMake package targets do not exist yet | 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`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_windows_app_package_smoke`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly -AndroidNativeChecks -PackageKinds android-standard-apk,android-quest-apk,android-focus-apk`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_native_package_smoke`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_linux_webgl_package_readiness`; `python scripts/dev/check_package_smoke_readiness.py`; `bash -n scripts/automation/package-smoke.sh` | Package-smoke builds and validates Windows AppX, Android APK variants, Linux app, 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`, `pp_document`, `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 explicit layer opacity/blend/alpha-lock/visibility metadata, per-layer frame durations, 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, pure document-to-PPI export, CLI document export automation, file-writing document export automation, stroke-script-generated document payload export, decoded pixel attachment to `pp_document`, and live save-path snapshot-readiness reporting, but full legacy PPI round-trip parity and pure live save writer replacement are 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`; `pp_document_ppi_export_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`; `pano_cli_simulate_document_export_smoke`; `pano_cli_save_document_project_roundtrip_smoke`; `pano_cli_apply_stroke_script_roundtrip_smoke`; `pano_cli_apply_stroke_script_rejects_tiny_canvas` | 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`, `pp_document`, `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 explicit layer opacity/blend/alpha-lock/visibility metadata, per-layer frame durations, 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, pure document-to-PPI export, CLI document export automation, file-writing document export automation, stroke-script-generated document payload export, decoded pixel attachment to `pp_document`, live save-path snapshot-readiness reporting, and app-core canvas-snapshot-to-PPI export automation, but full legacy PPI round-trip parity and pure live save writer replacement are 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`; `pp_document_ppi_export_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`; `pano_cli_simulate_document_export_smoke`; `pano_cli_save_document_project_roundtrip_smoke`; `pano_cli_apply_stroke_script_roundtrip_smoke`; `pano_cli_apply_stroke_script_rejects_tiny_canvas` | 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 | | DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, `App::show_cursor`/`App::hide_cursor` dispatch through `PlatformServices` without platform guards, and Windows live execution uses injected `WindowsPlatformServices`, but macOS cursor execution still reaches the retained fallback adapter | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by injected `pp_platform_*` services for every supported platform | | DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`, and Windows live execution uses injected `WindowsPlatformServices`, but Apple/Android clipboard execution still reaches retained fallback adapter branches from `App::clipboard_get_text` and `App::clipboard_set_text` | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by injected `pp_platform_*` services for every supported platform | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 93cdec9..b844f7e 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -671,8 +671,11 @@ now builds the same metadata snapshot from live `Canvas` state and has an opt-in dirty-face payload snapshot path backed by retained `Layer::snapshot()` readback. Live Save, Save As, Save Version, and save-before-workflow paths now prepare and log a payload-completeness report from that snapshot before -delegating to retained `Canvas::project_save`; pure PPI writer replacement, -export adoption, and renderer-owned readback remain under +delegating to retained `Canvas::project_save`; the app-core snapshot boundary +also has a tested pure PPI export helper, and +`pano_cli plan-canvas-document-snapshot` runs that helper for payload-complete +snapshots and reports generated byte/dirty-face summaries. Live save writer +replacement, export adoption, and renderer-owned readback remain under `DEBT-0010`/`DEBT-0013`/`DEBT-0036`. `pano_cli plan-image-import` exposes app-core planning for File > Import image route decisions, including wide equirectangular images, legacy vertical cube @@ -2213,7 +2216,9 @@ Results: retained `Canvas::project_save` writer, keeping behavior stable while moving the app path onto the document/canvas boundary. - `pano_cli plan-canvas-document-snapshot` now emits the same save-readiness - report (`payloadComplete` and `canExportPpi`) used by the live save bridge. + report (`payloadComplete` and `canExportPpi`) used by the live save bridge, + and payload-complete snapshots now run the pure `pp_document` PPI exporter + and decoded-project summary before emitting `ppiExport` JSON. - `pp_app_core_document_import_tests` passed, covering wide equirectangular, legacy vertical cube strip, regular transform-placement, and invalid-dimension import route decisions, equirectangular service dispatch, transform import @@ -2485,6 +2490,13 @@ Results: Version, and save-before-workflow now log payload completeness and PPI readiness from `pp_app_core` while legacy `Canvas::project_save` still owns the actual file write, progress/threading behavior, and compatibility quirks. +- `pp_app_core` now exposes + `export_document_canvas_save_snapshot_to_ppi`, which refuses snapshots that + still need renderer payload readback and exports payload-complete or + metadata-only snapshots through the pure `pp_document` PPI writer. The + document-canvas tests decode the generated bytes, and + `pano_cli plan-canvas-document-snapshot` reports `ppiExport` readiness, + byte count, and dirty-face count for agent automation. - Snapshot creation now rejects invalid embedded RGBA8 face payloads before document export or history can persist malformed state. - Package-smoke wrappers validate the Windows CMake app executable/runtime diff --git a/src/app_core/document_canvas.h b/src/app_core/document_canvas.h index 60c170d..8557b25 100644 --- a/src/app_core/document_canvas.h +++ b/src/app_core/document_canvas.h @@ -1,6 +1,7 @@ #pragma once #include "document/document.h" +#include "document/ppi_export.h" #include "foundation/result.h" #include @@ -77,6 +78,11 @@ struct DocumentCanvasSaveSnapshotReport { bool can_export_ppi = false; }; +struct DocumentCanvasPpiExportResult { + DocumentCanvasSaveSnapshotReport report; + std::vector bytes; +}; + class DocumentCanvasClearServices { public: virtual ~DocumentCanvasClearServices() = default; @@ -242,6 +248,27 @@ public: }; } +[[nodiscard]] inline pp::foundation::Result +export_document_canvas_save_snapshot_to_ppi(const DocumentCanvasSnapshotResult& snapshot) +{ + const auto report = make_document_canvas_save_snapshot_report(snapshot); + if (!report.can_export_ppi) { + return pp::foundation::Result::failure( + pp::foundation::Status::invalid_argument( + "canvas document snapshot still requires renderer payload readback")); + } + + auto bytes = pp::document::export_ppi_project_document(snapshot.document); + if (!bytes) { + return pp::foundation::Result::failure(bytes.status()); + } + + return pp::foundation::Result::success(DocumentCanvasPpiExportResult { + .report = report, + .bytes = std::move(bytes.value()), + }); +} + [[nodiscard]] inline pp::foundation::Result plan_document_canvas_clear( bool has_canvas, float r = 0.0F, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3e66130..a05eb46 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1474,13 +1474,13 @@ if(TARGET pano_cli) COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 3 --frames 2 --current-layer 2 --current-frame 1 --hidden-layer 0 --alpha-locked-layer 2 --opacity 0.5 --blend-mode 4 --pending-face-payloads-per-layer 6) set_tests_properties(pano_cli_plan_canvas_document_snapshot_smoke PROPERTIES LABELS "app;document;integration;desktop-fast" - PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"width\":128.*\"height\":64.*\"layers\":3.*\"frames\":2.*\"activeLayer\":2.*\"activeFrame\":1.*\"activeLayerName\":\"Layer 3\".*\"activeLayerOpacity\":0.5.*\"activeLayerBlend\":\"overlay\".*\"activeLayerAlphaLocked\":true.*\"pendingFacePayloads\":18.*\"metadataOnly\":true.*\"requiresRendererPayloadReadback\":true.*\"documentFacePayloads\":0.*\"saveReport\":\\{\"payloadComplete\":false,\"canExportPpi\":false\\}") + PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"width\":128.*\"height\":64.*\"layers\":3.*\"frames\":2.*\"activeLayer\":2.*\"activeFrame\":1.*\"activeLayerName\":\"Layer 3\".*\"activeLayerOpacity\":0.5.*\"activeLayerBlend\":\"overlay\".*\"activeLayerAlphaLocked\":true.*\"pendingFacePayloads\":18.*\"metadataOnly\":true.*\"requiresRendererPayloadReadback\":true.*\"documentFacePayloads\":0.*\"saveReport\":\\{\"payloadComplete\":false,\"canExportPpi\":false\\}.*\"ppiExport\":\\{\"ready\":false,\"bytes\":0,\"dirtyFaces\":0\\}") add_test(NAME pano_cli_plan_canvas_document_snapshot_payload_smoke COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 2 --frames 2 --current-layer 1 --current-frame 1 --pending-face-payloads-per-layer 2 --captured-face-payloads-per-layer 2) set_tests_properties(pano_cli_plan_canvas_document_snapshot_payload_smoke PROPERTIES LABELS "app;document;integration;desktop-fast" - PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"layers\":2.*\"frames\":2.*\"activeLayer\":1.*\"activeFrame\":1.*\"pendingFacePayloads\":4.*\"capturedFacePayloads\":4.*\"metadataOnly\":false.*\"requiresRendererPayloadReadback\":false.*\"documentFacePayloads\":4.*\"saveReport\":\\{\"payloadComplete\":true,\"canExportPpi\":true\\}") + PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"layers\":2.*\"frames\":2.*\"activeLayer\":1.*\"activeFrame\":1.*\"pendingFacePayloads\":4.*\"capturedFacePayloads\":4.*\"metadataOnly\":false.*\"requiresRendererPayloadReadback\":false.*\"documentFacePayloads\":4.*\"saveReport\":\\{\"payloadComplete\":true,\"canExportPpi\":true\\}.*\"ppiExport\":\\{\"ready\":true,\"bytes\":[1-9][0-9]*,\"dirtyFaces\":4\\}") add_test(NAME pano_cli_plan_canvas_document_snapshot_no_canvas COMMAND pano_cli plan-canvas-document-snapshot --no-canvas) diff --git a/tests/app_core/document_canvas_tests.cpp b/tests/app_core/document_canvas_tests.cpp index b9b9c26..bc6f73a 100644 --- a/tests/app_core/document_canvas_tests.cpp +++ b/tests/app_core/document_canvas_tests.cpp @@ -1,4 +1,5 @@ #include "app_core/document_canvas.h" +#include "assets/ppi_header.h" #include "test_harness.h" #include @@ -89,6 +90,10 @@ void snapshot_plan_projects_canvas_metadata(pp::tests::Harness& harness) PP_EXPECT(harness, value.document.layers()[1].blend_mode == pp::paint::BlendMode::overlay); PP_EXPECT(harness, value.document.layers()[1].frames.size() == 1U); PP_EXPECT(harness, value.document.face_pixel_payload_count() == 0U); + + const auto export_result = pp::app::export_document_canvas_save_snapshot_to_ppi(value); + PP_EXPECT(harness, !export_result); + PP_EXPECT(harness, export_result.status().code == pp::foundation::StatusCode::invalid_argument); } void snapshot_plan_defaults_empty_names_and_frames(pp::tests::Harness& harness) @@ -117,6 +122,16 @@ void snapshot_plan_defaults_empty_names_and_frames(pp::tests::Harness& harness) PP_EXPECT(harness, !result.value().requires_renderer_payload_readback); PP_EXPECT(harness, result.value().document.layers()[0].name == "Layer 1"); PP_EXPECT(harness, result.value().document.frames()[0].duration_ms == 100U); + + const auto export_result = pp::app::export_document_canvas_save_snapshot_to_ppi(result.value()); + PP_EXPECT(harness, export_result); + if (export_result) { + const auto decoded = pp::assets::decode_ppi_project_images(export_result.value().bytes); + PP_EXPECT(harness, decoded); + if (decoded) { + PP_EXPECT(harness, decoded.value().project.body.summary.dirty_face_count == 0U); + } + } } void snapshot_plan_attaches_captured_face_payloads(pp::tests::Harness& harness) @@ -177,6 +192,20 @@ void snapshot_plan_attaches_captured_face_payloads(pp::tests::Harness& harness) PP_EXPECT(harness, report.captured_face_payloads == 1U); PP_EXPECT(harness, report.payload_complete); PP_EXPECT(harness, report.can_export_ppi); + + const auto export_result = pp::app::export_document_canvas_save_snapshot_to_ppi(result.value()); + PP_EXPECT(harness, export_result); + if (export_result) { + PP_EXPECT(harness, export_result.value().report.can_export_ppi); + PP_EXPECT(harness, export_result.value().bytes.size() > 0U); + + const auto decoded = pp::assets::decode_ppi_project_images(export_result.value().bytes); + PP_EXPECT(harness, decoded); + if (decoded) { + PP_EXPECT(harness, decoded.value().project.body.summary.dirty_face_count == 1U); + PP_EXPECT(harness, decoded.value().project.body.summary.rgba_face_payload_count == 1U); + } + } } void snapshot_plan_rejects_invalid_canvas_state(pp::tests::Harness& harness) diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 187452e..f684cb1 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -5995,6 +5995,27 @@ int plan_canvas_document_snapshot(int argc, char** argv) const auto& document = value.document; const auto& active_layer = document.layers()[document.active_layer_index()]; const auto save_report = pp::app::make_document_canvas_save_snapshot_report(value); + bool ppi_export_ready = false; + std::size_t ppi_export_bytes = 0; + std::uint32_t ppi_export_dirty_faces = 0; + if (save_report.can_export_ppi) { + const auto exported = pp::app::export_document_canvas_save_snapshot_to_ppi(value); + if (!exported) { + print_error("plan-canvas-document-snapshot", exported.status().message); + return 2; + } + + const auto decoded = pp::assets::decode_ppi_project_images(exported.value().bytes); + if (!decoded) { + print_error("plan-canvas-document-snapshot", decoded.status().message); + return 2; + } + + ppi_export_ready = true; + ppi_export_bytes = exported.value().bytes.size(); + ppi_export_dirty_faces = decoded.value().project.body.summary.dirty_face_count; + } + std::cout << "{\"ok\":true,\"command\":\"plan-canvas-document-snapshot\"" << ",\"state\":{\"hasCanvas\":" << json_bool(args.has_canvas) << ",\"width\":" << args.width @@ -6021,6 +6042,9 @@ int plan_canvas_document_snapshot(int argc, char** argv) << ",\"documentFacePayloads\":" << document.face_pixel_payload_count() << ",\"saveReport\":{\"payloadComplete\":" << json_bool(save_report.payload_complete) << ",\"canExportPpi\":" << json_bool(save_report.can_export_ppi) + << "},\"ppiExport\":{\"ready\":" << json_bool(ppi_export_ready) + << ",\"bytes\":" << ppi_export_bytes + << ",\"dirtyFaces\":" << ppi_export_dirty_faces << "}" << "}}\n"; return 0;