Add image export roundtrip automation
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user