Add document selection mask automation
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace pp::document {
|
||||
@@ -107,37 +108,70 @@ namespace {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> rgba8_byte_size(
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> byte_size(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height) noexcept
|
||||
std::uint32_t height,
|
||||
std::uint32_t components,
|
||||
const char* dimensions_overflow_message,
|
||||
const char* byte_size_overflow_message,
|
||||
const char* payload_limit_message,
|
||||
const char* addressable_memory_message) 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"));
|
||||
pp::foundation::Status::out_of_range(dimensions_overflow_message));
|
||||
}
|
||||
|
||||
const auto pixels = width64 * height64;
|
||||
if (pixels > std::numeric_limits<std::uint64_t>::max() / rgba8_components) {
|
||||
if (pixels > std::numeric_limits<std::uint64_t>::max() / components) {
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("face pixel byte size overflows"));
|
||||
pp::foundation::Status::out_of_range(byte_size_overflow_message));
|
||||
}
|
||||
|
||||
const auto bytes = pixels * rgba8_components;
|
||||
const auto bytes = pixels * 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"));
|
||||
pp::foundation::Status::out_of_range(payload_limit_message));
|
||||
}
|
||||
|
||||
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"));
|
||||
pp::foundation::Status::out_of_range(addressable_memory_message));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<std::size_t>::success(static_cast<std::size_t>(bytes));
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> rgba8_byte_size(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height) noexcept
|
||||
{
|
||||
return byte_size(
|
||||
width,
|
||||
height,
|
||||
rgba8_components,
|
||||
"face pixel dimensions overflow",
|
||||
"face pixel byte size overflows",
|
||||
"face pixel payload exceeds the configured limit",
|
||||
"face pixel payload exceeds addressable memory");
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> alpha8_byte_size(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height) noexcept
|
||||
{
|
||||
return byte_size(
|
||||
width,
|
||||
height,
|
||||
alpha8_components,
|
||||
"selection mask dimensions overflow",
|
||||
"selection mask byte size overflows",
|
||||
"selection mask payload exceeds the configured limit",
|
||||
"selection mask payload exceeds addressable memory");
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_face_pixels(
|
||||
LayerFacePixels pixels,
|
||||
std::uint32_t document_width,
|
||||
@@ -168,6 +202,36 @@ namespace {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_selection_mask(
|
||||
SelectionMask mask,
|
||||
std::uint32_t document_width,
|
||||
std::uint32_t document_height) noexcept
|
||||
{
|
||||
if (mask.face_index >= cube_face_count) {
|
||||
return pp::foundation::Status::out_of_range("selection mask cube face index is outside the document");
|
||||
}
|
||||
|
||||
if (mask.width == 0 || mask.height == 0) {
|
||||
return pp::foundation::Status::invalid_argument("selection mask dimensions must be greater than zero");
|
||||
}
|
||||
|
||||
if (mask.x > document_width || mask.width > document_width - mask.x
|
||||
|| mask.y > document_height || mask.height > document_height - mask.y) {
|
||||
return pp::foundation::Status::out_of_range("selection mask rectangle is outside the document");
|
||||
}
|
||||
|
||||
const auto expected_bytes = alpha8_byte_size(mask.width, mask.height);
|
||||
if (!expected_bytes) {
|
||||
return expected_bytes.status();
|
||||
}
|
||||
|
||||
if (mask.alpha8.size() != expected_bytes.value()) {
|
||||
return pp::foundation::Status::invalid_argument("selection mask byte size does not match dimensions");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<CanvasDocument> CanvasDocument::create(DocumentConfig config)
|
||||
@@ -272,6 +336,14 @@ pp::foundation::Result<CanvasDocument> CanvasDocument::create_from_snapshot(Docu
|
||||
document.frames_.push_back(frame_config);
|
||||
}
|
||||
|
||||
for (const auto& mask : config.selection_masks) {
|
||||
const auto mask_status = validate_selection_mask(mask, document.width_, document.height_);
|
||||
if (!mask_status.ok()) {
|
||||
return pp::foundation::Result<CanvasDocument>::failure(mask_status);
|
||||
}
|
||||
document.selection_masks_.push_back(mask);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasDocument>::success(document);
|
||||
}
|
||||
|
||||
@@ -325,6 +397,11 @@ std::size_t CanvasDocument::face_pixel_payload_count() const noexcept
|
||||
return count;
|
||||
}
|
||||
|
||||
std::size_t CanvasDocument::selection_mask_payload_count() const noexcept
|
||||
{
|
||||
return selection_masks_.size();
|
||||
}
|
||||
|
||||
std::span<const Layer> CanvasDocument::layers() const noexcept
|
||||
{
|
||||
return layers_;
|
||||
@@ -335,6 +412,11 @@ std::span<const AnimationFrame> CanvasDocument::frames() const noexcept
|
||||
return frames_;
|
||||
}
|
||||
|
||||
std::span<const SelectionMask> CanvasDocument::selection_masks() const noexcept
|
||||
{
|
||||
return selection_masks_;
|
||||
}
|
||||
|
||||
pp::foundation::Result<std::size_t> CanvasDocument::add_layer(std::string_view name)
|
||||
{
|
||||
if (layers_.size() >= max_layer_count) {
|
||||
@@ -656,6 +738,47 @@ pp::foundation::Status CanvasDocument::set_layer_frame_face_pixels(
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::set_selection_mask(SelectionMask mask)
|
||||
{
|
||||
const auto mask_status = validate_selection_mask(mask, width_, height_);
|
||||
if (!mask_status.ok()) {
|
||||
return mask_status;
|
||||
}
|
||||
|
||||
const auto existing = std::find_if(
|
||||
selection_masks_.begin(),
|
||||
selection_masks_.end(),
|
||||
[face_index = mask.face_index](const SelectionMask& candidate) {
|
||||
return candidate.face_index == face_index;
|
||||
});
|
||||
if (existing == selection_masks_.end()) {
|
||||
selection_masks_.push_back(std::move(mask));
|
||||
} else {
|
||||
*existing = std::move(mask);
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::clear_selection_mask(std::uint32_t face_index) noexcept
|
||||
{
|
||||
if (face_index >= cube_face_count) {
|
||||
return pp::foundation::Status::out_of_range("selection mask cube face index is outside the document");
|
||||
}
|
||||
|
||||
const auto existing = std::find_if(
|
||||
selection_masks_.begin(),
|
||||
selection_masks_.end(),
|
||||
[face_index](const SelectionMask& candidate) {
|
||||
return candidate.face_index == face_index;
|
||||
});
|
||||
if (existing == selection_masks_.end()) {
|
||||
return pp::foundation::Status::out_of_range("selection mask face is not present");
|
||||
}
|
||||
|
||||
selection_masks_.erase(existing);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Result<DocumentHistory> DocumentHistory::create(
|
||||
CanvasDocument initial_document,
|
||||
std::size_t max_entries)
|
||||
|
||||
@@ -20,6 +20,7 @@ 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::uint32_t alpha8_components = 1;
|
||||
constexpr std::uint64_t max_face_pixel_payload_bytes = 1024ULL * 1024ULL * 1024ULL;
|
||||
|
||||
struct DocumentConfig {
|
||||
@@ -37,6 +38,15 @@ struct LayerFacePixels {
|
||||
std::vector<std::uint8_t> rgba8;
|
||||
};
|
||||
|
||||
struct SelectionMask {
|
||||
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> alpha8;
|
||||
};
|
||||
|
||||
struct AnimationFrame {
|
||||
std::uint32_t duration_ms = 100;
|
||||
std::vector<LayerFacePixels> face_pixels;
|
||||
@@ -65,6 +75,7 @@ struct DocumentSnapshotConfig {
|
||||
std::uint32_t height = 0;
|
||||
std::span<const DocumentLayerConfig> layers;
|
||||
std::span<const AnimationFrame> frames;
|
||||
std::span<const SelectionMask> selection_masks;
|
||||
};
|
||||
|
||||
class CanvasDocument {
|
||||
@@ -79,8 +90,10 @@ public:
|
||||
[[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::size_t selection_mask_payload_count() const noexcept;
|
||||
[[nodiscard]] std::span<const Layer> layers() const noexcept;
|
||||
[[nodiscard]] std::span<const AnimationFrame> frames() const noexcept;
|
||||
[[nodiscard]] std::span<const SelectionMask> selection_masks() const noexcept;
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> add_layer(std::string_view name);
|
||||
[[nodiscard]] pp::foundation::Status remove_layer(std::size_t index);
|
||||
@@ -102,6 +115,8 @@ public:
|
||||
std::size_t layer_index,
|
||||
std::size_t frame_index,
|
||||
LayerFacePixels pixels);
|
||||
[[nodiscard]] pp::foundation::Status set_selection_mask(SelectionMask mask);
|
||||
[[nodiscard]] pp::foundation::Status clear_selection_mask(std::uint32_t face_index) noexcept;
|
||||
|
||||
private:
|
||||
std::uint32_t width_ = 0;
|
||||
@@ -110,6 +125,7 @@ private:
|
||||
std::size_t active_frame_index_ = 0;
|
||||
std::vector<Layer> layers_;
|
||||
std::vector<AnimationFrame> frames_;
|
||||
std::vector<SelectionMask> selection_masks_;
|
||||
};
|
||||
|
||||
class DocumentHistory {
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace {
|
||||
.height = project.body.summary.height,
|
||||
.layers = layers,
|
||||
.frames = frames,
|
||||
.selection_masks = {},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user