Decode PPI face payloads
This commit is contained in:
94
src/assets/image_pixels.cpp
Normal file
94
src/assets/image_pixels.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "assets/image_pixels.h"
|
||||
|
||||
#include "assets/image_metadata.h"
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#define STB_IMAGE_STATIC
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
|
||||
namespace pp::assets {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> rgba_byte_size(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height) noexcept
|
||||
{
|
||||
const auto pixels = static_cast<std::uint64_t>(width) * static_cast<std::uint64_t>(height);
|
||||
constexpr auto channels = 4ULL;
|
||||
if (pixels > std::numeric_limits<std::uint64_t>::max() / channels) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("RGBA byte size overflows"));
|
||||
}
|
||||
|
||||
const auto bytes = pixels * channels;
|
||||
if (bytes > static_cast<std::uint64_t>(std::numeric_limits<std::size_t>::max())) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("RGBA byte size exceeds addressable memory"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::size_t>::success(static_cast<std::size_t>(bytes));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<Rgba8Image> decode_png_rgba8(std::span<const std::byte> bytes)
|
||||
{
|
||||
const auto metadata = parse_png_metadata(bytes);
|
||||
if (!metadata) {
|
||||
return pp::foundation::Result<Rgba8Image>::failure(metadata.status());
|
||||
}
|
||||
|
||||
if (bytes.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
||||
return pp::foundation::Result<Rgba8Image>::failure(
|
||||
pp::foundation::Status::out_of_range("PNG payload is too large for the decoder"));
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int source_components = 0;
|
||||
auto* decoded = stbi_load_from_memory(
|
||||
reinterpret_cast<const stbi_uc*>(bytes.data()),
|
||||
static_cast<int>(bytes.size()),
|
||||
&width,
|
||||
&height,
|
||||
&source_components,
|
||||
4);
|
||||
if (decoded == nullptr) {
|
||||
return pp::foundation::Result<Rgba8Image>::failure(
|
||||
pp::foundation::Status::invalid_argument("PNG payload could not be decoded"));
|
||||
}
|
||||
|
||||
const auto cleanup = [decoded]() noexcept {
|
||||
stbi_image_free(decoded);
|
||||
};
|
||||
|
||||
if (width <= 0 || height <= 0
|
||||
|| static_cast<std::uint32_t>(width) != metadata.value().width
|
||||
|| static_cast<std::uint32_t>(height) != metadata.value().height) {
|
||||
cleanup();
|
||||
return pp::foundation::Result<Rgba8Image>::failure(
|
||||
pp::foundation::Status::invalid_argument("decoded PNG dimensions are inconsistent"));
|
||||
}
|
||||
|
||||
const auto byte_count = rgba_byte_size(metadata.value().width, metadata.value().height);
|
||||
if (!byte_count) {
|
||||
cleanup();
|
||||
return pp::foundation::Result<Rgba8Image>::failure(byte_count.status());
|
||||
}
|
||||
|
||||
Rgba8Image image {
|
||||
.width = metadata.value().width,
|
||||
.height = metadata.value().height,
|
||||
.pixels = {},
|
||||
};
|
||||
image.pixels.assign(decoded, decoded + byte_count.value());
|
||||
cleanup();
|
||||
|
||||
return pp::foundation::Result<Rgba8Image>::success(std::move(image));
|
||||
}
|
||||
|
||||
}
|
||||
21
src/assets/image_pixels.h
Normal file
21
src/assets/image_pixels.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::assets {
|
||||
|
||||
struct Rgba8Image {
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::vector<std::uint8_t> pixels;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<Rgba8Image> decode_png_rgba8(
|
||||
std::span<const std::byte> bytes);
|
||||
|
||||
}
|
||||
@@ -561,4 +561,59 @@ pp::foundation::Result<PpiProjectIndex> parse_ppi_project_index(std::span<const
|
||||
});
|
||||
}
|
||||
|
||||
pp::foundation::Result<PpiDecodedProjectImages> decode_ppi_project_images(std::span<const std::byte> bytes)
|
||||
{
|
||||
auto project = parse_ppi_project_index(bytes);
|
||||
if (!project) {
|
||||
return pp::foundation::Result<PpiDecodedProjectImages>::failure(project.status());
|
||||
}
|
||||
|
||||
PpiDecodedProjectImages decoded {
|
||||
.project = project.value(),
|
||||
.faces = {},
|
||||
};
|
||||
decoded.faces.reserve(decoded.project.body.summary.rgba_face_payload_count);
|
||||
|
||||
const auto body = bytes.subspan(decoded.project.layout.body_offset, decoded.project.layout.body_bytes);
|
||||
for (std::size_t layer_index = 0; layer_index < decoded.project.body.layers.size(); ++layer_index) {
|
||||
const auto& layer = decoded.project.body.layers[layer_index];
|
||||
for (std::size_t frame_index = 0; frame_index < layer.frames.size(); ++frame_index) {
|
||||
const auto& frame = layer.frames[frame_index];
|
||||
for (std::size_t face_index = 0; face_index < frame.faces.size(); ++face_index) {
|
||||
const auto& face = frame.faces[face_index];
|
||||
if (!face.has_data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (face.body_payload_offset > body.size()
|
||||
|| face.payload_bytes > body.size() - face.body_payload_offset) {
|
||||
return pp::foundation::Result<PpiDecodedProjectImages>::failure(
|
||||
pp::foundation::Status::out_of_range("PPI face payload range is outside the body"));
|
||||
}
|
||||
|
||||
const auto image = decode_png_rgba8(
|
||||
body.subspan(face.body_payload_offset, face.payload_bytes));
|
||||
if (!image) {
|
||||
return pp::foundation::Result<PpiDecodedProjectImages>::failure(image.status());
|
||||
}
|
||||
|
||||
if (image.value().width != face.png_width || image.value().height != face.png_height) {
|
||||
return pp::foundation::Result<PpiDecodedProjectImages>::failure(
|
||||
pp::foundation::Status::invalid_argument("decoded PPI face payload dimensions changed"));
|
||||
}
|
||||
|
||||
decoded.faces.push_back(PpiDecodedFacePayload {
|
||||
.layer_index = static_cast<std::uint32_t>(layer_index),
|
||||
.frame_index = static_cast<std::uint32_t>(frame_index),
|
||||
.face_index = static_cast<std::uint32_t>(face_index),
|
||||
.descriptor = face,
|
||||
.image = image.value(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Result<PpiDecodedProjectImages>::success(std::move(decoded));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "assets/image_pixels.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <array>
|
||||
@@ -104,6 +105,19 @@ struct PpiProjectIndex {
|
||||
PpiBodyIndex body;
|
||||
};
|
||||
|
||||
struct PpiDecodedFacePayload {
|
||||
std::uint32_t layer_index = 0;
|
||||
std::uint32_t frame_index = 0;
|
||||
std::uint32_t face_index = 0;
|
||||
PpiFacePayloadSummary descriptor;
|
||||
Rgba8Image image;
|
||||
};
|
||||
|
||||
struct PpiDecodedProjectImages {
|
||||
PpiProjectIndex project;
|
||||
std::vector<PpiDecodedFacePayload> faces;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<PpiHeaderInfo> parse_ppi_header(
|
||||
std::span<const std::byte> bytes) noexcept;
|
||||
|
||||
@@ -126,4 +140,7 @@ struct PpiProjectIndex {
|
||||
[[nodiscard]] pp::foundation::Result<PpiProjectIndex> parse_ppi_project_index(
|
||||
std::span<const std::byte> bytes);
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<PpiDecodedProjectImages> decode_ppi_project_images(
|
||||
std::span<const std::byte> bytes);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user