Load PPI metadata into documents

This commit is contained in:
2026-06-01 13:00:14 +02:00
parent 7319cb9aa9
commit c16cab87bd
8 changed files with 388 additions and 26 deletions

View File

@@ -106,6 +106,7 @@ void print_help()
<< " create-document --width N --height N [--layers N] [--frames N] [--frame-duration-ms N]\n"
<< " inspect-image --path FILE\n"
<< " inspect-project --path FILE\n"
<< " load-project --path FILE\n"
<< " parse-layout --path FILE\n"
<< " simulate-stroke --x1 N --y1 N --x2 N --y2 N [--spacing N]\n"
<< " simulate-stroke-script --path FILE\n"
@@ -404,6 +405,134 @@ int inspect_project(int argc, char** argv)
return 0;
}
pp::foundation::Result<pp::paint::BlendMode> ppi_layer_blend_mode(std::uint32_t blend_mode) noexcept
{
switch (blend_mode) {
case 0:
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::normal);
case 1:
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::multiply);
case 2:
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::screen);
case 3:
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::color_dodge);
case 4:
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::overlay);
default:
return pp::foundation::Result<pp::paint::BlendMode>::failure(
pp::foundation::Status::invalid_argument("PPI layer blend mode is not supported by pp_document"));
}
}
pp::foundation::Result<pp::document::CanvasDocument> document_from_ppi_index(
const pp::assets::PpiProjectIndex& project)
{
if (project.body.layers.empty()) {
return pp::foundation::Result<pp::document::CanvasDocument>::failure(
pp::foundation::Status::invalid_argument("PPI project has no layers"));
}
const auto& reference_frames = project.body.layers.front().frames;
if (reference_frames.empty()) {
return pp::foundation::Result<pp::document::CanvasDocument>::failure(
pp::foundation::Status::invalid_argument("PPI project has no frames"));
}
std::vector<pp::document::AnimationFrame> frames;
frames.reserve(reference_frames.size());
for (const auto& frame : reference_frames) {
frames.push_back(pp::document::AnimationFrame { .duration_ms = frame.duration_ms });
}
std::vector<pp::document::DocumentLayerConfig> layers;
layers.reserve(project.body.layers.size());
for (const auto& layer : project.body.layers) {
if (layer.frames.size() != reference_frames.size()) {
return pp::foundation::Result<pp::document::CanvasDocument>::failure(
pp::foundation::Status::invalid_argument("PPI per-layer frame counts are not representable yet"));
}
for (std::size_t frame_index = 0; frame_index < layer.frames.size(); ++frame_index) {
if (layer.frames[frame_index].duration_ms != reference_frames[frame_index].duration_ms) {
return pp::foundation::Result<pp::document::CanvasDocument>::failure(
pp::foundation::Status::invalid_argument("PPI per-layer frame durations are not representable yet"));
}
}
const auto blend_mode = ppi_layer_blend_mode(layer.blend_mode);
if (!blend_mode) {
return pp::foundation::Result<pp::document::CanvasDocument>::failure(blend_mode.status());
}
layers.push_back(pp::document::DocumentLayerConfig {
.name = layer.name,
.visible = layer.visible,
.alpha_locked = layer.alpha_locked,
.opacity = layer.opacity,
.blend_mode = blend_mode.value(),
});
}
return pp::document::CanvasDocument::create_from_snapshot(pp::document::DocumentSnapshotConfig {
.width = project.body.summary.width,
.height = project.body.summary.height,
.layers = layers,
.frames = frames,
});
}
int load_project(int argc, char** argv)
{
InspectProjectArgs args;
const auto status = parse_inspect_project_args(argc, argv, args);
if (!status.ok()) {
print_error("load-project", status.message);
return 2;
}
std::ifstream stream(args.path, std::ios::binary);
if (!stream) {
print_error("load-project", "project file could not be opened");
return 2;
}
const std::string chars {
std::istreambuf_iterator<char>(stream),
std::istreambuf_iterator<char>()
};
const auto* data = reinterpret_cast<const std::byte*>(chars.data());
const auto project = pp::assets::parse_ppi_project_index(std::span<const std::byte>(data, chars.size()));
if (!project) {
print_error("load-project", project.status().message);
return 2;
}
const auto document_result = document_from_ppi_index(project.value());
if (!document_result) {
print_error("load-project", document_result.status().message);
return 2;
}
const auto& document = document_result.value();
std::cout << "{\"ok\":true,\"command\":\"load-project\""
<< ",\"source\":\"ppi\""
<< ",\"pixelDataLoaded\":false"
<< ",\"document\":{\"width\":" << document.width()
<< ",\"height\":" << document.height()
<< ",\"layers\":" << document.layers().size()
<< ",\"frames\":" << document.frames().size()
<< ",\"animationDurationMs\":" << document.animation_duration_ms()
<< ",\"layerNames\":[";
for (std::size_t layer_index = 0; layer_index < document.layers().size(); ++layer_index) {
if (layer_index != 0U) {
std::cout << ",";
}
std::cout << "\"" << json_escape(document.layers()[layer_index].name) << "\"";
}
std::cout << "]}}\n";
return 0;
}
pp::foundation::Status parse_simulate_stroke_args(int argc, char** argv, SimulateStrokeArgs& args)
{
for (int i = 2; i < argc; ++i) {
@@ -635,6 +764,10 @@ int main(int argc, char** argv)
return inspect_project(argc, argv);
}
if (command == "load-project") {
return load_project(argc, argv);
}
if (command == "simulate-stroke") {
return simulate_stroke(argc, argv);
}