Add image export roundtrip automation

This commit is contained in:
2026-06-02 10:41:34 +02:00
parent 9d05d193a7
commit 9c6b52eb8e
8 changed files with 303 additions and 6 deletions

View File

@@ -55,6 +55,12 @@ struct ImportImageArgs {
std::uint32_t y = 0;
};
struct ExportImageArgs {
std::string path;
std::uint32_t width = 2;
std::uint32_t height = 2;
};
struct ParseLayoutArgs {
std::string path;
};
@@ -173,6 +179,7 @@ void print_help()
std::cout
<< "pano_cli commands:\n"
<< " create-document --width N --height N [--layers N] [--frames N] [--frame-duration-ms N]\n"
<< " export-image --path FILE [--width N] [--height N]\n"
<< " inspect-image --path FILE\n"
<< " import-image --path FILE [--document-width N] [--document-height N] [--face N] [--x N] [--y N]\n"
<< " inspect-project --path FILE\n"
@@ -623,6 +630,102 @@ int import_image(int argc, char** argv)
return 0;
}
pp::foundation::Status parse_export_image_args(int argc, char** argv, ExportImageArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--path") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
args.path = argv[++i];
} else if (key == "--width" || key == "--height") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
const auto value = pp::foundation::parse_u32(argv[++i]);
if (!value) {
return value.status();
}
if (key == "--width") {
args.width = value.value();
} else {
args.height = value.value();
}
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
if (args.path.empty()) {
return pp::foundation::Status::invalid_argument("path must not be empty");
}
if (args.width == 0 || args.height == 0) {
return pp::foundation::Status::invalid_argument("width and height must be greater than zero");
}
constexpr std::uint64_t max_cli_export_bytes = 64ULL * 1024ULL * 1024ULL;
if (static_cast<std::uint64_t>(args.width) > max_cli_export_bytes / 4ULL / static_cast<std::uint64_t>(args.height)) {
return pp::foundation::Status::out_of_range("export image exceeds the CLI automation size limit");
}
return pp::foundation::Status::success();
}
int export_image(int argc, char** argv)
{
ExportImageArgs args;
const auto status = parse_export_image_args(argc, argv, args);
if (!status.ok()) {
print_error("export-image", status.message);
return 2;
}
std::vector<std::uint8_t> pixels(
static_cast<std::size_t>(args.width) * static_cast<std::size_t>(args.height) * 4U);
for (std::uint32_t y = 0; y < args.height; ++y) {
for (std::uint32_t x = 0; x < args.width; ++x) {
const auto offset = (static_cast<std::size_t>(y) * args.width + x) * 4U;
pixels[offset + 0U] = static_cast<std::uint8_t>((x * 37U) & 0xffU);
pixels[offset + 1U] = static_cast<std::uint8_t>((y * 53U) & 0xffU);
pixels[offset + 2U] = static_cast<std::uint8_t>(((x + y) * 29U) & 0xffU);
pixels[offset + 3U] = 255U;
}
}
const auto encoded = pp::assets::encode_png_rgba8(args.width, args.height, pixels);
if (!encoded) {
print_error("export-image", encoded.status().message);
return 2;
}
std::ofstream stream(args.path, std::ios::binary);
if (!stream) {
print_error("export-image", "image file could not be opened for writing");
return 2;
}
const auto& bytes = encoded.value();
stream.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
if (!stream) {
print_error("export-image", "image file could not be written");
return 2;
}
std::cout << "{\"ok\":true,\"command\":\"export-image\""
<< ",\"path\":\"" << json_escape(args.path) << "\""
<< ",\"image\":{\"format\":\"png\",\"width\":" << args.width
<< ",\"height\":" << args.height
<< ",\"bytes\":" << pixels.size()
<< "},\"file\":{\"bytes\":" << bytes.size()
<< "}}\n";
return 0;
}
pp::foundation::Status parse_inspect_project_args(int argc, char** argv, InspectProjectArgs& args)
{
for (int i = 2; i < argc; ++i) {
@@ -1554,6 +1657,10 @@ int main(int argc, char** argv)
return inspect_image(argc, argv);
}
if (command == "export-image") {
return export_image(argc, argv);
}
if (command == "import-image") {
return import_image(argc, argv);
}