Decode PPI face payloads

This commit is contained in:
2026-06-01 13:35:03 +02:00
parent 10e5d5b5ae
commit 88507df90e
13 changed files with 349 additions and 15 deletions

View File

@@ -0,0 +1,67 @@
#include "assets/image_pixels.h"
#include "test_harness.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
using pp::assets::decode_png_rgba8;
using pp::foundation::StatusCode;
namespace {
constexpr std::array<std::byte, 68> transparent_png_1x1 {
std::byte { 0x89 }, std::byte { 0x50 }, std::byte { 0x4e }, std::byte { 0x47 },
std::byte { 0x0d }, std::byte { 0x0a }, std::byte { 0x1a }, std::byte { 0x0a },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x0d },
std::byte { 0x49 }, std::byte { 0x48 }, std::byte { 0x44 }, std::byte { 0x52 },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x01 },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x01 },
std::byte { 0x08 }, std::byte { 0x06 }, std::byte { 0x00 }, std::byte { 0x00 },
std::byte { 0x00 }, std::byte { 0x1f }, std::byte { 0x15 }, std::byte { 0xc4 },
std::byte { 0x89 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 },
std::byte { 0x0b }, std::byte { 0x49 }, std::byte { 0x44 }, std::byte { 0x41 },
std::byte { 0x54 }, std::byte { 0x78 }, std::byte { 0x9c }, std::byte { 0x63 },
std::byte { 0x60 }, std::byte { 0x00 }, std::byte { 0x02 }, std::byte { 0x00 },
std::byte { 0x00 }, std::byte { 0x05 }, std::byte { 0x00 }, std::byte { 0x01 },
std::byte { 0x7a }, std::byte { 0x5e }, std::byte { 0xab }, std::byte { 0x3f },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 },
std::byte { 0x49 }, std::byte { 0x45 }, std::byte { 0x4e }, std::byte { 0x44 },
std::byte { 0xae }, std::byte { 0x42 }, std::byte { 0x60 }, std::byte { 0x82 },
};
void decodes_png_to_rgba8_pixels(pp::tests::Harness& h)
{
const auto image = decode_png_rgba8(transparent_png_1x1);
PP_EXPECT(h, image.ok());
PP_EXPECT(h, image.value().width == 1U);
PP_EXPECT(h, image.value().height == 1U);
PP_EXPECT(h, image.value().pixels.size() == 4U);
PP_EXPECT(h, image.value().pixels[0] == 0U);
PP_EXPECT(h, image.value().pixels[1] == 0U);
PP_EXPECT(h, image.value().pixels[2] == 0U);
PP_EXPECT(h, image.value().pixels[3] == 0U);
}
void rejects_corrupt_png_payload(pp::tests::Harness& h)
{
auto corrupt = transparent_png_1x1;
corrupt[0] = std::byte { 0x00 };
const auto image = decode_png_rgba8(corrupt);
PP_EXPECT(h, !image.ok());
PP_EXPECT(h, image.status().code == StatusCode::invalid_argument);
}
}
int main()
{
pp::tests::Harness harness;
harness.run("decodes_png_to_rgba8_pixels", decodes_png_to_rgba8_pixels);
harness.run("rejects_corrupt_png_payload", rejects_corrupt_png_payload);
return harness.finish();
}

View File

@@ -9,6 +9,7 @@
#include <vector>
using pp::assets::parse_ppi_header;
using pp::assets::decode_ppi_project_images;
using pp::assets::parse_ppi_project_index;
using pp::assets::parse_ppi_project_summary;
using pp::assets::parse_ppi_project_layout;
@@ -119,6 +120,29 @@ std::vector<std::byte> png_ihdr_payload(
return bytes;
}
std::vector<std::byte> transparent_png_1x1()
{
return {
std::byte { 0x89 }, std::byte { 0x50 }, std::byte { 0x4e }, std::byte { 0x47 },
std::byte { 0x0d }, std::byte { 0x0a }, std::byte { 0x1a }, std::byte { 0x0a },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x0d },
std::byte { 0x49 }, std::byte { 0x48 }, std::byte { 0x44 }, std::byte { 0x52 },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x01 },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x01 },
std::byte { 0x08 }, std::byte { 0x06 }, std::byte { 0x00 }, std::byte { 0x00 },
std::byte { 0x00 }, std::byte { 0x1f }, std::byte { 0x15 }, std::byte { 0xc4 },
std::byte { 0x89 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 },
std::byte { 0x0b }, std::byte { 0x49 }, std::byte { 0x44 }, std::byte { 0x41 },
std::byte { 0x54 }, std::byte { 0x78 }, std::byte { 0x9c }, std::byte { 0x63 },
std::byte { 0x60 }, std::byte { 0x00 }, std::byte { 0x02 }, std::byte { 0x00 },
std::byte { 0x00 }, std::byte { 0x05 }, std::byte { 0x00 }, std::byte { 0x01 },
std::byte { 0x7a }, std::byte { 0x5e }, std::byte { 0xab }, std::byte { 0x3f },
std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 }, std::byte { 0x00 },
std::byte { 0x49 }, std::byte { 0x45 }, std::byte { 0x4e }, std::byte { 0x44 },
std::byte { 0xae }, std::byte { 0x42 }, std::byte { 0x60 }, std::byte { 0x82 },
};
}
std::vector<std::byte> minimal_project()
{
auto bytes = valid_header();
@@ -127,7 +151,10 @@ std::vector<std::byte> minimal_project()
return bytes;
}
std::vector<std::byte> project_with_single_face_payload(std::vector<std::byte> payload)
std::vector<std::byte> project_with_single_face_payload(
std::vector<std::byte> payload,
std::uint32_t dirty_width = 8,
std::uint32_t dirty_height = 4)
{
auto bytes = valid_header();
bytes.resize(ppi_header_size + (128U * 128U * 4U), std::byte { 0 });
@@ -149,8 +176,8 @@ std::vector<std::byte> project_with_single_face_payload(std::vector<std::byte> p
append_u32(bytes, 1);
append_u32(bytes, 2);
append_u32(bytes, 3);
append_u32(bytes, 10);
append_u32(bytes, 7);
append_u32(bytes, 2 + dirty_width);
append_u32(bytes, 3 + dirty_height);
append_u32(bytes, static_cast<std::uint32_t>(payload.size()));
bytes.insert(bytes.end(), payload.begin(), payload.end());
@@ -289,6 +316,31 @@ void validates_dirty_face_png_payload_metadata(pp::tests::Harness& h)
PP_EXPECT(h, summary.value().body.compressed_face_bytes == 33U);
}
void decodes_dirty_face_png_payloads(pp::tests::Harness& h)
{
const auto project = project_with_single_face_payload(transparent_png_1x1(), 1, 1);
const auto decoded = decode_ppi_project_images(project);
PP_EXPECT(h, decoded.ok());
PP_EXPECT(h, decoded.value().faces.size() == 1U);
PP_EXPECT(h, decoded.value().faces[0].layer_index == 0U);
PP_EXPECT(h, decoded.value().faces[0].frame_index == 0U);
PP_EXPECT(h, decoded.value().faces[0].face_index == 0U);
PP_EXPECT(h, decoded.value().faces[0].image.width == 1U);
PP_EXPECT(h, decoded.value().faces[0].image.height == 1U);
PP_EXPECT(h, decoded.value().faces[0].image.pixels.size() == 4U);
PP_EXPECT(h, decoded.value().faces[0].image.pixels[3] == 0U);
}
void rejects_metadata_only_payload_when_decoding_pixels(pp::tests::Harness& h)
{
const auto project = project_with_single_face_payload(png_ihdr_payload(8, 4));
const auto decoded = decode_ppi_project_images(project);
PP_EXPECT(h, !decoded.ok());
PP_EXPECT(h, decoded.status().code == StatusCode::invalid_argument);
}
void rejects_invalid_dirty_face_png_payloads(pp::tests::Harness& h)
{
auto mismatched_dimensions = project_with_single_face_payload(png_ihdr_payload(7, 4));
@@ -345,6 +397,8 @@ int main()
harness.run("parses_minimal_project_body_summary", parses_minimal_project_body_summary);
harness.run("indexes_project_layers_frames_and_faces", indexes_project_layers_frames_and_faces);
harness.run("validates_dirty_face_png_payload_metadata", validates_dirty_face_png_payload_metadata);
harness.run("decodes_dirty_face_png_payloads", decodes_dirty_face_png_payloads);
harness.run("rejects_metadata_only_payload_when_decoding_pixels", rejects_metadata_only_payload_when_decoding_pixels);
harness.run("rejects_invalid_dirty_face_png_payloads", rejects_invalid_dirty_face_png_payloads);
harness.run("rejects_invalid_project_body_summaries", rejects_invalid_project_body_summaries);
return harness.finish();