Add document layer metadata tests
This commit is contained in:
@@ -312,8 +312,8 @@ PPI header recognition, and a pure typed settings document model, with
|
||||
corrupt/truncated/unsupported and key/value limit tests.
|
||||
`pp_paint` has started with CPU reference math for the five current shader
|
||||
blend modes plus deterministic stroke spacing/interpolation. `pp_document` has
|
||||
started with a pure canvas/layer/frame model and layer/frame/undo-redo history
|
||||
invariant tests. `pp_renderer_api` has started with renderer-neutral
|
||||
started with a pure canvas/layer/frame model, layer metadata operations, and
|
||||
layer/frame/undo-redo history invariant tests. `pp_renderer_api` has started with renderer-neutral
|
||||
texture/readback descriptors and validation tests. `pp_paint_renderer` has
|
||||
started with deterministic CPU layer compositing over renderer extents using
|
||||
the paint blend reference. `pp_ui_core` has started with XML-layout-facing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "document/document.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::document {
|
||||
|
||||
@@ -32,6 +33,28 @@ namespace {
|
||||
return "Layer " + std::to_string(index + 1U);
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_layer_name(std::string_view name) noexcept
|
||||
{
|
||||
if (name.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("layer name must not be empty");
|
||||
}
|
||||
|
||||
if (name.size() > max_layer_name_length) {
|
||||
return pp::foundation::Status::out_of_range("layer name length exceeds the configured limit");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_layer_index(std::size_t index, std::size_t layer_count) noexcept
|
||||
{
|
||||
if (index >= layer_count) {
|
||||
return pp::foundation::Status::out_of_range("layer index is outside the document");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<CanvasDocument> CanvasDocument::create(DocumentConfig config)
|
||||
@@ -91,7 +114,15 @@ pp::foundation::Result<std::size_t> CanvasDocument::add_layer(std::string_view n
|
||||
}
|
||||
|
||||
Layer layer;
|
||||
layer.name = name.empty() ? default_layer_name(layers_.size()) : std::string(name);
|
||||
if (name.empty()) {
|
||||
layer.name = default_layer_name(layers_.size());
|
||||
} else {
|
||||
const auto name_status = validate_layer_name(name);
|
||||
if (!name_status.ok()) {
|
||||
return pp::foundation::Result<std::size_t>::failure(name_status);
|
||||
}
|
||||
layer.name = std::string(name);
|
||||
}
|
||||
layers_.push_back(layer);
|
||||
active_layer_index_ = layers_.size() - 1U;
|
||||
return pp::foundation::Result<std::size_t>::success(active_layer_index_);
|
||||
@@ -144,14 +175,77 @@ pp::foundation::Status CanvasDocument::move_layer(std::size_t from, std::size_t
|
||||
|
||||
pp::foundation::Status CanvasDocument::set_active_layer(std::size_t index) noexcept
|
||||
{
|
||||
if (index >= layers_.size()) {
|
||||
return pp::foundation::Status::out_of_range("layer index is outside the document");
|
||||
const auto index_status = validate_layer_index(index, layers_.size());
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
|
||||
active_layer_index_ = index;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::rename_layer(std::size_t index, std::string_view name)
|
||||
{
|
||||
const auto index_status = validate_layer_index(index, layers_.size());
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
|
||||
const auto name_status = validate_layer_name(name);
|
||||
if (!name_status.ok()) {
|
||||
return name_status;
|
||||
}
|
||||
|
||||
layers_[index].name = std::string(name);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::set_layer_visible(std::size_t index, bool visible) noexcept
|
||||
{
|
||||
const auto index_status = validate_layer_index(index, layers_.size());
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
|
||||
layers_[index].visible = visible;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::set_layer_opacity(std::size_t index, float opacity) noexcept
|
||||
{
|
||||
const auto index_status = validate_layer_index(index, layers_.size());
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
|
||||
if (!std::isfinite(opacity) || opacity < 0.0F || opacity > 1.0F) {
|
||||
return pp::foundation::Status::out_of_range("layer opacity must be finite and within 0..1");
|
||||
}
|
||||
|
||||
layers_[index].opacity = opacity;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status CanvasDocument::set_layer_blend_mode(std::size_t index, pp::paint::BlendMode blend_mode) noexcept
|
||||
{
|
||||
const auto index_status = validate_layer_index(index, layers_.size());
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
|
||||
switch (blend_mode) {
|
||||
case pp::paint::BlendMode::normal:
|
||||
case pp::paint::BlendMode::multiply:
|
||||
case pp::paint::BlendMode::screen:
|
||||
case pp::paint::BlendMode::color_dodge:
|
||||
case pp::paint::BlendMode::overlay:
|
||||
layers_[index].blend_mode = blend_mode;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("layer blend mode is not supported");
|
||||
}
|
||||
|
||||
pp::foundation::Result<std::size_t> CanvasDocument::add_frame(std::uint32_t duration_ms)
|
||||
{
|
||||
if (frames_.size() >= max_frame_count) {
|
||||
|
||||
@@ -17,6 +17,7 @@ constexpr std::uint32_t max_frame_count = 100000;
|
||||
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;
|
||||
|
||||
struct DocumentConfig {
|
||||
std::uint32_t width = 0;
|
||||
@@ -50,6 +51,10 @@ public:
|
||||
[[nodiscard]] pp::foundation::Status remove_layer(std::size_t index);
|
||||
[[nodiscard]] pp::foundation::Status move_layer(std::size_t from, std::size_t to);
|
||||
[[nodiscard]] pp::foundation::Status set_active_layer(std::size_t index) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status rename_layer(std::size_t index, std::string_view name);
|
||||
[[nodiscard]] pp::foundation::Status set_layer_visible(std::size_t index, bool visible) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status set_layer_opacity(std::size_t index, float opacity) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status set_layer_blend_mode(std::size_t index, pp::paint::BlendMode blend_mode) noexcept;
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> add_frame(std::uint32_t duration_ms);
|
||||
[[nodiscard]] pp::foundation::Result<std::size_t> duplicate_frame(std::size_t index);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "document/document.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
|
||||
using pp::paint::BlendMode;
|
||||
using pp::document::CanvasDocument;
|
||||
using pp::document::DocumentHistory;
|
||||
using pp::document::DocumentConfig;
|
||||
@@ -10,6 +12,7 @@ using pp::document::max_document_history_entries;
|
||||
using pp::document::max_canvas_dimension;
|
||||
using pp::document::max_frame_count;
|
||||
using pp::document::max_layer_count;
|
||||
using pp::document::max_layer_name_length;
|
||||
using pp::foundation::StatusCode;
|
||||
|
||||
namespace {
|
||||
@@ -93,6 +96,64 @@ void moves_layers_and_preserves_active_layer_identity(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, bad_move.code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
void updates_layer_metadata(pp::tests::Harness& h)
|
||||
{
|
||||
auto document_result = CanvasDocument::create(
|
||||
DocumentConfig { .width = 64, .height = 64, .layer_count = 2 });
|
||||
PP_EXPECT(h, document_result.ok());
|
||||
auto document = document_result.value();
|
||||
|
||||
PP_EXPECT(h, document.rename_layer(1, "Ink").ok());
|
||||
PP_EXPECT(h, document.set_layer_visible(1, false).ok());
|
||||
PP_EXPECT(h, document.set_layer_opacity(1, 0.25F).ok());
|
||||
PP_EXPECT(h, document.set_layer_blend_mode(1, BlendMode::multiply).ok());
|
||||
|
||||
PP_EXPECT(h, document.layers()[1].name == std::string_view("Ink"));
|
||||
PP_EXPECT(h, !document.layers()[1].visible);
|
||||
PP_EXPECT(h, std::fabs(document.layers()[1].opacity - 0.25F) < 0.0001F);
|
||||
PP_EXPECT(h, document.layers()[1].blend_mode == BlendMode::multiply);
|
||||
}
|
||||
|
||||
void rejects_invalid_layer_metadata(pp::tests::Harness& h)
|
||||
{
|
||||
auto document_result = CanvasDocument::create(
|
||||
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
||||
PP_EXPECT(h, document_result.ok());
|
||||
auto document = document_result.value();
|
||||
|
||||
const auto empty_name = document.rename_layer(0, "");
|
||||
const auto long_name = document.rename_layer(0, std::string(max_layer_name_length + 1U, 'x'));
|
||||
const auto missing_name = document.rename_layer(4, "Missing");
|
||||
const auto bad_opacity_low = document.set_layer_opacity(0, -0.1F);
|
||||
const auto bad_opacity_high = document.set_layer_opacity(0, 1.1F);
|
||||
const auto bad_opacity_nan = document.set_layer_opacity(0, std::nanf(""));
|
||||
const auto missing_visible = document.set_layer_visible(2, true);
|
||||
const auto missing_blend = document.set_layer_blend_mode(2, BlendMode::normal);
|
||||
const auto bad_blend = document.set_layer_blend_mode(0, static_cast<BlendMode>(255));
|
||||
const auto bad_add_layer = document.add_layer(std::string(max_layer_name_length + 1U, 'x'));
|
||||
|
||||
PP_EXPECT(h, !empty_name.ok());
|
||||
PP_EXPECT(h, empty_name.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !long_name.ok());
|
||||
PP_EXPECT(h, long_name.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !missing_name.ok());
|
||||
PP_EXPECT(h, missing_name.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !bad_opacity_low.ok());
|
||||
PP_EXPECT(h, bad_opacity_low.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !bad_opacity_high.ok());
|
||||
PP_EXPECT(h, bad_opacity_high.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !bad_opacity_nan.ok());
|
||||
PP_EXPECT(h, bad_opacity_nan.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !missing_visible.ok());
|
||||
PP_EXPECT(h, missing_visible.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !missing_blend.ok());
|
||||
PP_EXPECT(h, missing_blend.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !bad_blend.ok());
|
||||
PP_EXPECT(h, bad_blend.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !bad_add_layer.ok());
|
||||
PP_EXPECT(h, bad_add_layer.status().code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
void manages_animation_frames_and_duration(pp::tests::Harness& h)
|
||||
{
|
||||
auto document_result = CanvasDocument::create(
|
||||
@@ -267,6 +328,8 @@ int main()
|
||||
harness.run("rejects_invalid_document_configs", rejects_invalid_document_configs);
|
||||
harness.run("manages_layer_add_remove_and_active_index", manages_layer_add_remove_and_active_index);
|
||||
harness.run("moves_layers_and_preserves_active_layer_identity", moves_layers_and_preserves_active_layer_identity);
|
||||
harness.run("updates_layer_metadata", updates_layer_metadata);
|
||||
harness.run("rejects_invalid_layer_metadata", rejects_invalid_layer_metadata);
|
||||
harness.run("manages_animation_frames_and_duration", manages_animation_frames_and_duration);
|
||||
harness.run("rejects_invalid_animation_frame_operations", rejects_invalid_animation_frame_operations);
|
||||
harness.run("records_document_history_and_restores_snapshots", records_document_history_and_restores_snapshots);
|
||||
|
||||
Reference in New Issue
Block a user