Attach PPI pixels to documents
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace pp::document {
|
||||
|
||||
@@ -105,6 +107,67 @@ namespace {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> rgba8_byte_size(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height) noexcept
|
||||
{
|
||||
const auto width64 = static_cast<std::uint64_t>(width);
|
||||
const auto height64 = static_cast<std::uint64_t>(height);
|
||||
if (width64 > std::numeric_limits<std::uint64_t>::max() / height64) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("face pixel dimensions overflow"));
|
||||
}
|
||||
|
||||
const auto pixels = width64 * height64;
|
||||
if (pixels > std::numeric_limits<std::uint64_t>::max() / rgba8_components) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("face pixel byte size overflows"));
|
||||
}
|
||||
|
||||
const auto bytes = pixels * rgba8_components;
|
||||
if (bytes > max_face_pixel_payload_bytes) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("face pixel payload exceeds the configured limit"));
|
||||
}
|
||||
|
||||
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("face pixel payload exceeds addressable memory"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::size_t>::success(static_cast<std::size_t>(bytes));
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_face_pixels(
|
||||
LayerFacePixels pixels,
|
||||
std::uint32_t document_width,
|
||||
std::uint32_t document_height) noexcept
|
||||
{
|
||||
if (pixels.face_index >= cube_face_count) {
|
||||
return pp::foundation::Status::out_of_range("cube face index is outside the document");
|
||||
}
|
||||
|
||||
if (pixels.width == 0 || pixels.height == 0) {
|
||||
return pp::foundation::Status::invalid_argument("face pixel dimensions must be greater than zero");
|
||||
}
|
||||
|
||||
if (pixels.x > document_width || pixels.width > document_width - pixels.x
|
||||
|| pixels.y > document_height || pixels.height > document_height - pixels.y) {
|
||||
return pp::foundation::Status::out_of_range("face pixel rectangle is outside the document");
|
||||
}
|
||||
|
||||
const auto expected_bytes = rgba8_byte_size(pixels.width, pixels.height);
|
||||
if (!expected_bytes) {
|
||||
return expected_bytes.status();
|
||||
}
|
||||
|
||||
if (pixels.rgba8.size() != expected_bytes.value()) {
|
||||
return pp::foundation::Status::invalid_argument("face pixel payload byte size does not match dimensions");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<CanvasDocument> CanvasDocument::create(DocumentConfig config)
|
||||
@@ -251,6 +314,17 @@ pp::foundation::Result<std::uint64_t> CanvasDocument::layer_animation_duration_m
|
||||
return pp::foundation::Result<std::uint64_t>::success(frame_duration_sum(layers_[index].frames));
|
||||
}
|
||||
|
||||
std::size_t CanvasDocument::face_pixel_payload_count() const noexcept
|
||||
{
|
||||
std::size_t count = 0;
|
||||
for (const auto& layer : layers_) {
|
||||
for (const auto& frame : layer.frames) {
|
||||
count += frame.face_pixels.size();
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::span<const Layer> CanvasDocument::layers() const noexcept
|
||||
{
|
||||
return layers_;
|
||||
@@ -423,9 +497,9 @@ pp::foundation::Result<std::size_t> CanvasDocument::add_frame(std::uint32_t dura
|
||||
duration_status);
|
||||
}
|
||||
|
||||
frames_.push_back(AnimationFrame { .duration_ms = duration_ms });
|
||||
frames_.push_back(AnimationFrame { .duration_ms = duration_ms, .face_pixels = {} });
|
||||
for (auto& layer : layers_) {
|
||||
layer.frames.push_back(AnimationFrame { .duration_ms = duration_ms });
|
||||
layer.frames.push_back(AnimationFrame { .duration_ms = duration_ms, .face_pixels = {} });
|
||||
}
|
||||
active_frame_index_ = frames_.size() - 1U;
|
||||
return pp::foundation::Result<std::size_t>::success(active_frame_index_);
|
||||
@@ -547,6 +621,41 @@ pp::foundation::Status CanvasDocument::set_active_frame(std::size_t index) noexc
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::set_layer_frame_face_pixels(
|
||||
std::size_t layer_index,
|
||||
std::size_t frame_index,
|
||||
LayerFacePixels pixels)
|
||||
{
|
||||
const auto layer_status = validate_layer_index(layer_index, layers_.size());
|
||||
if (!layer_status.ok()) {
|
||||
return layer_status;
|
||||
}
|
||||
|
||||
const auto frame_status = validate_frame_index(frame_index, layers_[layer_index].frames.size());
|
||||
if (!frame_status.ok()) {
|
||||
return frame_status;
|
||||
}
|
||||
|
||||
const auto pixels_status = validate_face_pixels(pixels, width_, height_);
|
||||
if (!pixels_status.ok()) {
|
||||
return pixels_status;
|
||||
}
|
||||
|
||||
auto& faces = layers_[layer_index].frames[frame_index].face_pixels;
|
||||
const auto existing = std::find_if(
|
||||
faces.begin(),
|
||||
faces.end(),
|
||||
[face_index = pixels.face_index](const LayerFacePixels& face) {
|
||||
return face.face_index == face_index;
|
||||
});
|
||||
if (existing == faces.end()) {
|
||||
faces.push_back(std::move(pixels));
|
||||
} else {
|
||||
*existing = std::move(pixels);
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Result<DocumentHistory> DocumentHistory::create(
|
||||
CanvasDocument initial_document,
|
||||
std::size_t max_entries)
|
||||
|
||||
@@ -18,6 +18,9 @@ constexpr std::uint32_t min_frame_duration_ms = 1;
|
||||
constexpr std::size_t min_document_history_entries = 2;
|
||||
constexpr std::size_t max_document_history_entries = 10000;
|
||||
constexpr std::size_t max_layer_name_length = 128;
|
||||
constexpr std::uint32_t cube_face_count = 6;
|
||||
constexpr std::uint32_t rgba8_components = 4;
|
||||
constexpr std::uint64_t max_face_pixel_payload_bytes = 1024ULL * 1024ULL * 1024ULL;
|
||||
|
||||
struct DocumentConfig {
|
||||
std::uint32_t width = 0;
|
||||
@@ -25,8 +28,18 @@ struct DocumentConfig {
|
||||
std::uint32_t layer_count = 1;
|
||||
};
|
||||
|
||||
struct LayerFacePixels {
|
||||
std::uint32_t face_index = 0;
|
||||
std::uint32_t x = 0;
|
||||
std::uint32_t y = 0;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::vector<std::uint8_t> rgba8;
|
||||
};
|
||||
|
||||
struct AnimationFrame {
|
||||
std::uint32_t duration_ms = 100;
|
||||
std::vector<LayerFacePixels> face_pixels;
|
||||
};
|
||||
|
||||
struct Layer {
|
||||
@@ -65,6 +78,7 @@ public:
|
||||
[[nodiscard]] std::size_t active_frame_index() const noexcept;
|
||||
[[nodiscard]] std::uint64_t animation_duration_ms() const noexcept;
|
||||
[[nodiscard]] pp::foundation::Result<std::uint64_t> layer_animation_duration_ms(std::size_t index) const noexcept;
|
||||
[[nodiscard]] std::size_t face_pixel_payload_count() const noexcept;
|
||||
[[nodiscard]] std::span<const Layer> layers() const noexcept;
|
||||
[[nodiscard]] std::span<const AnimationFrame> frames() const noexcept;
|
||||
|
||||
@@ -84,6 +98,10 @@ public:
|
||||
[[nodiscard]] pp::foundation::Status move_frame(std::size_t from, std::size_t to);
|
||||
[[nodiscard]] pp::foundation::Status set_frame_duration(std::size_t index, std::uint32_t duration_ms) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status set_active_frame(std::size_t index) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status set_layer_frame_face_pixels(
|
||||
std::size_t layer_index,
|
||||
std::size_t frame_index,
|
||||
LayerFacePixels pixels);
|
||||
|
||||
private:
|
||||
std::uint32_t width_ = 0;
|
||||
|
||||
116
src/document/ppi_import.cpp
Normal file
116
src/document/ppi_import.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "document/ppi_import.h"
|
||||
|
||||
#include <utility>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::document {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<pp::paint::BlendMode> ppi_layer_blend_mode(
|
||||
std::uint32_t blend_mode) noexcept
|
||||
{
|
||||
switch (blend_mode) {
|
||||
case 0:
|
||||
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::normal);
|
||||
case 1:
|
||||
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::multiply);
|
||||
case 2:
|
||||
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::screen);
|
||||
case 3:
|
||||
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::color_dodge);
|
||||
case 4:
|
||||
return pp::foundation::Result<pp::paint::BlendMode>::success(pp::paint::BlendMode::overlay);
|
||||
default:
|
||||
return pp::foundation::Result<pp::paint::BlendMode>::failure(
|
||||
pp::foundation::Status::invalid_argument("PPI layer blend mode is not supported by pp_document"));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<CanvasDocument> document_from_ppi_index(
|
||||
const pp::assets::PpiProjectIndex& project)
|
||||
{
|
||||
if (project.body.layers.empty()) {
|
||||
return pp::foundation::Result<CanvasDocument>::failure(
|
||||
pp::foundation::Status::invalid_argument("PPI project has no layers"));
|
||||
}
|
||||
|
||||
const auto& reference_frames = project.body.layers.front().frames;
|
||||
if (reference_frames.empty()) {
|
||||
return pp::foundation::Result<CanvasDocument>::failure(
|
||||
pp::foundation::Status::invalid_argument("PPI project has no frames"));
|
||||
}
|
||||
|
||||
std::vector<AnimationFrame> frames;
|
||||
frames.reserve(reference_frames.size());
|
||||
for (const auto& frame : reference_frames) {
|
||||
frames.push_back(AnimationFrame { .duration_ms = frame.duration_ms, .face_pixels = {} });
|
||||
}
|
||||
|
||||
std::vector<std::vector<AnimationFrame>> layer_frames;
|
||||
layer_frames.reserve(project.body.layers.size());
|
||||
std::vector<DocumentLayerConfig> layers;
|
||||
layers.reserve(project.body.layers.size());
|
||||
for (const auto& layer : project.body.layers) {
|
||||
const auto blend_mode = ppi_layer_blend_mode(layer.blend_mode);
|
||||
if (!blend_mode) {
|
||||
return pp::foundation::Result<CanvasDocument>::failure(blend_mode.status());
|
||||
}
|
||||
|
||||
auto& frame_list = layer_frames.emplace_back();
|
||||
frame_list.reserve(layer.frames.size());
|
||||
for (const auto& frame : layer.frames) {
|
||||
frame_list.push_back(AnimationFrame { .duration_ms = frame.duration_ms, .face_pixels = {} });
|
||||
}
|
||||
|
||||
layers.push_back(DocumentLayerConfig {
|
||||
.name = layer.name,
|
||||
.visible = layer.visible,
|
||||
.alpha_locked = layer.alpha_locked,
|
||||
.opacity = layer.opacity,
|
||||
.blend_mode = blend_mode.value(),
|
||||
.frames = std::span<const AnimationFrame>(frame_list.data(), frame_list.size()),
|
||||
});
|
||||
}
|
||||
|
||||
return CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
|
||||
.width = project.body.summary.width,
|
||||
.height = project.body.summary.height,
|
||||
.layers = layers,
|
||||
.frames = frames,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<CanvasDocument> import_ppi_project_document(
|
||||
const pp::assets::PpiDecodedProjectImages& project)
|
||||
{
|
||||
auto document = document_from_ppi_index(project.project);
|
||||
if (!document) {
|
||||
return document;
|
||||
}
|
||||
|
||||
auto value = document.value();
|
||||
for (const auto& face : project.faces) {
|
||||
const auto status = value.set_layer_frame_face_pixels(
|
||||
face.layer_index,
|
||||
face.frame_index,
|
||||
LayerFacePixels {
|
||||
.face_index = face.face_index,
|
||||
.x = face.descriptor.x0,
|
||||
.y = face.descriptor.y0,
|
||||
.width = face.image.width,
|
||||
.height = face.image.height,
|
||||
.rgba8 = face.image.pixels,
|
||||
});
|
||||
if (!status.ok()) {
|
||||
return pp::foundation::Result<CanvasDocument>::failure(status);
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasDocument>::success(std::move(value));
|
||||
}
|
||||
|
||||
}
|
||||
11
src/document/ppi_import.h
Normal file
11
src/document/ppi_import.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "assets/ppi_header.h"
|
||||
#include "document/document.h"
|
||||
|
||||
namespace pp::document {
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<CanvasDocument> import_ppi_project_document(
|
||||
const pp::assets::PpiDecodedProjectImages& project);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user