Index PPI project layers

This commit is contained in:
2026-06-01 12:53:48 +02:00
parent 677d0b33a8
commit 7319cb9aa9
8 changed files with 317 additions and 47 deletions

View File

@@ -5,6 +5,7 @@
#include <bit>
#include <limits>
#include <utility>
namespace pp::assets {
@@ -78,26 +79,28 @@ namespace {
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status validate_face_png_payload(
[[nodiscard]] pp::foundation::Result<ImageMetadata> validate_face_png_payload(
std::span<const std::byte> payload,
std::uint32_t width,
std::uint32_t height) noexcept
{
const auto metadata = parse_png_metadata(payload);
if (!metadata) {
return metadata.status();
return pp::foundation::Result<ImageMetadata>::failure(metadata.status());
}
if (metadata.value().width != width || metadata.value().height != height) {
return pp::foundation::Status::invalid_argument("PPI face PNG dimensions do not match the dirty box");
return pp::foundation::Result<ImageMetadata>::failure(
pp::foundation::Status::invalid_argument("PPI face PNG dimensions do not match the dirty box"));
}
if (metadata.value().bit_depth != 8U || metadata.value().components != 4U
|| metadata.value().color_type != ImageColorType::rgba) {
return pp::foundation::Status::invalid_argument("PPI face PNG payload must be 8-bit RGBA");
return pp::foundation::Result<ImageMetadata>::failure(
pp::foundation::Status::invalid_argument("PPI face PNG payload must be 8-bit RGBA"));
}
return pp::foundation::Status::success();
return pp::foundation::Result<ImageMetadata>::success(metadata.value());
}
}
@@ -221,10 +224,18 @@ pp::foundation::Result<PpiProjectLayout> parse_ppi_project_layout(std::span<cons
});
}
pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
namespace {
pp::foundation::Result<PpiBodySummary> parse_ppi_body_impl(
PpiHeaderInfo header,
std::span<const std::byte> body) noexcept
std::span<const std::byte> body,
PpiBodyIndex* index) noexcept
{
if (index != nullptr) {
index->summary = {};
index->layers.clear();
}
pp::foundation::ByteReader reader(body);
const auto width = read_positive_i32(reader, "PPI canvas width is outside the supported range");
const auto height = read_positive_i32(reader, "PPI canvas height is outside the supported range");
@@ -251,6 +262,12 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
.declared_frame_count = 1,
};
std::vector<bool> seen_orders;
if (index != nullptr) {
index->layers.resize(summary.layer_count);
seen_orders.assign(summary.layer_count, false);
}
if (header.document_version.minor >= 3U) {
const auto declared_frames = read_positive_i32(reader, "PPI declared frame count is outside the supported range");
if (!declared_frames) {
@@ -278,6 +295,14 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
pp::foundation::Status::out_of_range("PPI layer order is outside the layer list"));
}
if (index != nullptr) {
if (seen_orders[order.value()]) {
return pp::foundation::Result<PpiBodySummary>::failure(
pp::foundation::Status::invalid_argument("PPI layer order is duplicated"));
}
seen_orders[order.value()] = true;
}
if (opacity.value() < 0.0F || opacity.value() > 1.0F) {
return pp::foundation::Result<PpiBodySummary>::failure(
pp::foundation::Status::out_of_range("PPI layer opacity is outside the supported range"));
@@ -288,9 +313,19 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
pp::foundation::Status::out_of_range("PPI layer name exceeds the configured limit"));
}
const auto name_status = skip_bytes(reader, name_length.value());
if (!name_status.ok()) {
return pp::foundation::Result<PpiBodySummary>::failure(name_status);
const auto name_bytes = reader.read_bytes(name_length.value());
if (!name_bytes) {
return pp::foundation::Result<PpiBodySummary>::failure(name_bytes.status());
}
PpiLayerSummary layer_summary;
if (index != nullptr) {
layer_summary.stored_order = order.value();
layer_summary.opacity = opacity.value();
layer_summary.name.reserve(name_bytes.value().size());
for (const auto byte : name_bytes.value()) {
layer_summary.name.push_back(static_cast<char>(std::to_integer<unsigned char>(byte)));
}
}
if (header.document_version.minor >= 2U) {
@@ -306,6 +341,12 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
return pp::foundation::Result<PpiBodySummary>::failure(
pp::foundation::Status::invalid_argument("PPI layer boolean field is invalid"));
}
if (index != nullptr) {
layer_summary.blend_mode = blend_mode.value();
layer_summary.alpha_locked = alpha_locked.value() != 0U;
layer_summary.visible = visible.value() != 0U;
}
}
std::uint32_t layer_frames = 1;
@@ -328,6 +369,10 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
}
summary.total_layer_frames += layer_frames;
if (index != nullptr) {
layer_summary.frames.resize(layer_frames);
}
for (std::uint32_t frame_index = 0; frame_index < layer_frames; ++frame_index) {
if (header.document_version.minor >= 3U) {
const auto duration = read_positive_i32(reader, "PPI frame duration is outside the supported range");
@@ -339,6 +384,10 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
return pp::foundation::Result<PpiBodySummary>::failure(
pp::foundation::Status::invalid_argument("PPI frame duration must be greater than zero"));
}
if (index != nullptr) {
layer_summary.frames[frame_index].duration_ms = duration.value();
}
}
for (std::uint32_t face = 0; face < 6U; ++face) {
@@ -384,22 +433,40 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
return pp::foundation::Result<PpiBodySummary>::failure(byte_status);
}
const auto payload_offset = reader.position();
const auto payload = reader.read_bytes(data_size.value());
if (!payload) {
return pp::foundation::Result<PpiBodySummary>::failure(payload.status());
}
const auto png_status = validate_face_png_payload(
const auto png_metadata = validate_face_png_payload(
payload.value(),
x1.value() - x0.value(),
y1.value() - y0.value());
if (!png_status.ok()) {
return pp::foundation::Result<PpiBodySummary>::failure(png_status);
if (!png_metadata) {
return pp::foundation::Result<PpiBodySummary>::failure(png_metadata.status());
}
++summary.rgba_face_payload_count;
if (index != nullptr) {
layer_summary.frames[frame_index].faces[face] = PpiFacePayloadSummary {
.has_data = true,
.x0 = x0.value(),
.y0 = y0.value(),
.x1 = x1.value(),
.y1 = y1.value(),
.body_payload_offset = static_cast<std::uint32_t>(payload_offset),
.payload_bytes = data_size.value(),
.png_width = png_metadata.value().width,
.png_height = png_metadata.value().height,
};
}
}
}
if (index != nullptr) {
index->layers[order.value()] = std::move(layer_summary);
}
}
if (header.document_version.minor >= 3U && summary.total_layer_frames != summary.declared_frame_count) {
@@ -425,9 +492,35 @@ pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
pp::foundation::Status::invalid_argument("PPI body has trailing bytes"));
}
if (index != nullptr) {
index->summary = summary;
}
return pp::foundation::Result<PpiBodySummary>::success(summary);
}
}
pp::foundation::Result<PpiBodySummary> parse_ppi_body_summary(
PpiHeaderInfo header,
std::span<const std::byte> body) noexcept
{
return parse_ppi_body_impl(header, body, nullptr);
}
pp::foundation::Result<PpiBodyIndex> parse_ppi_body_index(
PpiHeaderInfo header,
std::span<const std::byte> body)
{
PpiBodyIndex index;
const auto summary = parse_ppi_body_impl(header, body, &index);
if (!summary) {
return pp::foundation::Result<PpiBodyIndex>::failure(summary.status());
}
return pp::foundation::Result<PpiBodyIndex>::success(std::move(index));
}
pp::foundation::Result<PpiProjectSummary> parse_ppi_project_summary(std::span<const std::byte> bytes) noexcept
{
const auto layout = parse_ppi_project_layout(bytes);
@@ -448,4 +541,24 @@ pp::foundation::Result<PpiProjectSummary> parse_ppi_project_summary(std::span<co
});
}
pp::foundation::Result<PpiProjectIndex> parse_ppi_project_index(std::span<const std::byte> bytes)
{
const auto layout = parse_ppi_project_layout(bytes);
if (!layout) {
return pp::foundation::Result<PpiProjectIndex>::failure(layout.status());
}
const auto body = parse_ppi_body_index(
layout.value().header,
bytes.subspan(layout.value().body_offset, layout.value().body_bytes));
if (!body) {
return pp::foundation::Result<PpiProjectIndex>::failure(body.status());
}
return pp::foundation::Result<PpiProjectIndex>::success(PpiProjectIndex {
.layout = layout.value(),
.body = body.value(),
});
}
}