370 lines
14 KiB
C++
370 lines
14 KiB
C++
#include "app_core/document_canvas.h"
|
|
#include "test_harness.h"
|
|
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <span>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
class FakeDocumentCanvasClearServices final : public pp::app::DocumentCanvasClearServices {
|
|
public:
|
|
void clear_current_canvas(float r, float g, float b, float a) override
|
|
{
|
|
clear_calls += 1;
|
|
last_r = r;
|
|
last_g = g;
|
|
last_b = b;
|
|
last_a = a;
|
|
call_order += "clear;";
|
|
}
|
|
|
|
int clear_calls = 0;
|
|
float last_r = 0.0F;
|
|
float last_g = 0.0F;
|
|
float last_b = 0.0F;
|
|
float last_a = 0.0F;
|
|
std::string call_order;
|
|
};
|
|
|
|
void snapshot_plan_projects_canvas_metadata(pp::tests::Harness& harness)
|
|
{
|
|
const std::uint32_t base_frames[] { 120U, 240U };
|
|
const std::uint32_t paint_frames[] { 180U };
|
|
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
|
|
{
|
|
.name = "Base",
|
|
.visible = false,
|
|
.alpha_locked = true,
|
|
.opacity = 0.5F,
|
|
.blend_mode = 2,
|
|
.frame_durations_ms = std::span<const std::uint32_t>(base_frames),
|
|
.pending_face_payloads = 6U,
|
|
},
|
|
{
|
|
.name = "Paint",
|
|
.visible = true,
|
|
.alpha_locked = false,
|
|
.opacity = 0.75F,
|
|
.blend_mode = 4,
|
|
.frame_durations_ms = std::span<const std::uint32_t>(paint_frames),
|
|
.pending_face_payloads = 3U,
|
|
},
|
|
};
|
|
|
|
const auto result = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 64U,
|
|
.height = 32U,
|
|
.active_layer_index = 1U,
|
|
.active_frame_index = 1U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
|
|
});
|
|
|
|
PP_EXPECT(harness, result);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
const auto& value = result.value();
|
|
PP_EXPECT(harness, value.layer_count == 2U);
|
|
PP_EXPECT(harness, value.frame_count == 2U);
|
|
PP_EXPECT(harness, value.pending_face_payloads == 9U);
|
|
PP_EXPECT(harness, value.metadata_only);
|
|
PP_EXPECT(harness, value.requires_renderer_payload_readback);
|
|
PP_EXPECT(harness, value.document.width() == 64U);
|
|
PP_EXPECT(harness, value.document.height() == 32U);
|
|
PP_EXPECT(harness, value.document.active_layer_index() == 1U);
|
|
PP_EXPECT(harness, value.document.active_frame_index() == 1U);
|
|
PP_EXPECT(harness, value.document.layers().size() == 2U);
|
|
PP_EXPECT(harness, value.document.frames().size() == 2U);
|
|
PP_EXPECT(harness, value.document.layers()[0].name == "Base");
|
|
PP_EXPECT(harness, !value.document.layers()[0].visible);
|
|
PP_EXPECT(harness, value.document.layers()[0].alpha_locked);
|
|
PP_EXPECT(harness, value.document.layers()[0].opacity == 0.5F);
|
|
PP_EXPECT(harness, value.document.layers()[0].blend_mode == pp::paint::BlendMode::screen);
|
|
PP_EXPECT(harness, value.document.layers()[0].frames[1].duration_ms == 240U);
|
|
PP_EXPECT(harness, value.document.layers()[1].name == "Paint");
|
|
PP_EXPECT(harness, value.document.layers()[1].blend_mode == pp::paint::BlendMode::overlay);
|
|
PP_EXPECT(harness, value.document.layers()[1].frames.size() == 1U);
|
|
PP_EXPECT(harness, value.document.face_pixel_payload_count() == 0U);
|
|
}
|
|
|
|
void snapshot_plan_defaults_empty_names_and_frames(pp::tests::Harness& harness)
|
|
{
|
|
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
|
|
{
|
|
.name = "",
|
|
.frame_durations_ms = {},
|
|
},
|
|
};
|
|
|
|
const auto result = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
|
|
});
|
|
|
|
PP_EXPECT(harness, result);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
PP_EXPECT(harness, result.value().frame_count == 1U);
|
|
PP_EXPECT(harness, result.value().pending_face_payloads == 0U);
|
|
PP_EXPECT(harness, !result.value().requires_renderer_payload_readback);
|
|
PP_EXPECT(harness, result.value().document.layers()[0].name == "Layer 1");
|
|
PP_EXPECT(harness, result.value().document.frames()[0].duration_ms == 100U);
|
|
}
|
|
|
|
void snapshot_plan_attaches_captured_face_payloads(pp::tests::Harness& harness)
|
|
{
|
|
const std::uint32_t frames[] { 100U };
|
|
const std::uint8_t rgba[] {
|
|
255U, 0U, 0U, 255U,
|
|
0U, 255U, 0U, 255U,
|
|
};
|
|
const pp::app::DocumentCanvasFacePayloadInput payloads[] {
|
|
{
|
|
.frame_index = 0U,
|
|
.face_index = 2U,
|
|
.x = 3U,
|
|
.y = 4U,
|
|
.width = 2U,
|
|
.height = 1U,
|
|
.rgba8 = std::span<const std::uint8_t>(rgba),
|
|
},
|
|
};
|
|
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
|
|
{
|
|
.name = "Paint",
|
|
.frame_durations_ms = std::span<const std::uint32_t>(frames),
|
|
.pending_face_payloads = 1U,
|
|
.captured_face_payloads = std::span<const pp::app::DocumentCanvasFacePayloadInput>(payloads),
|
|
},
|
|
};
|
|
|
|
const auto result = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
|
|
});
|
|
|
|
PP_EXPECT(harness, result);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
PP_EXPECT(harness, result.value().pending_face_payloads == 1U);
|
|
PP_EXPECT(harness, result.value().captured_face_payloads == 1U);
|
|
PP_EXPECT(harness, !result.value().metadata_only);
|
|
PP_EXPECT(harness, !result.value().requires_renderer_payload_readback);
|
|
PP_EXPECT(harness, result.value().document.face_pixel_payload_count() == 1U);
|
|
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].face_index == 2U);
|
|
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].x == 3U);
|
|
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].rgba8[4] == 0U);
|
|
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].rgba8[5] == 255U);
|
|
|
|
const auto report = pp::app::make_document_canvas_save_snapshot_report(result.value());
|
|
PP_EXPECT(harness, report.width == 16U);
|
|
PP_EXPECT(harness, report.height == 8U);
|
|
PP_EXPECT(harness, report.layer_count == 1U);
|
|
PP_EXPECT(harness, report.frame_count == 1U);
|
|
PP_EXPECT(harness, report.pending_face_payloads == 1U);
|
|
PP_EXPECT(harness, report.captured_face_payloads == 1U);
|
|
PP_EXPECT(harness, report.payload_complete);
|
|
PP_EXPECT(harness, report.can_export_ppi);
|
|
}
|
|
|
|
void snapshot_plan_rejects_invalid_canvas_state(pp::tests::Harness& harness)
|
|
{
|
|
const std::uint32_t frames[] { 100U };
|
|
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
|
|
{
|
|
.name = "Layer",
|
|
.frame_durations_ms = std::span<const std::uint32_t>(frames),
|
|
},
|
|
};
|
|
|
|
const auto no_canvas = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = false,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
|
|
});
|
|
const auto bad_layer = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.active_layer_index = 1U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
|
|
});
|
|
const pp::app::DocumentCanvasLayerSnapshotInput bad_blend_layers[] {
|
|
{
|
|
.name = "Layer",
|
|
.blend_mode = 64,
|
|
.frame_durations_ms = std::span<const std::uint32_t>(frames),
|
|
},
|
|
};
|
|
const auto bad_blend = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(bad_blend_layers),
|
|
});
|
|
const std::uint32_t bad_frames[] { 0U };
|
|
const pp::app::DocumentCanvasLayerSnapshotInput bad_duration_layers[] {
|
|
{
|
|
.name = "Layer",
|
|
.frame_durations_ms = std::span<const std::uint32_t>(bad_frames),
|
|
},
|
|
};
|
|
const auto bad_duration = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(bad_duration_layers),
|
|
});
|
|
const std::uint8_t bad_rgba[] { 1U, 2U, 3U };
|
|
const pp::app::DocumentCanvasFacePayloadInput bad_payloads[] {
|
|
{
|
|
.frame_index = 0U,
|
|
.face_index = 0U,
|
|
.x = 0U,
|
|
.y = 0U,
|
|
.width = 1U,
|
|
.height = 1U,
|
|
.rgba8 = std::span<const std::uint8_t>(bad_rgba),
|
|
},
|
|
};
|
|
const pp::app::DocumentCanvasLayerSnapshotInput bad_payload_layers[] {
|
|
{
|
|
.name = "Layer",
|
|
.frame_durations_ms = std::span<const std::uint32_t>(frames),
|
|
.pending_face_payloads = 1U,
|
|
.captured_face_payloads = std::span<const pp::app::DocumentCanvasFacePayloadInput>(bad_payloads),
|
|
},
|
|
};
|
|
const auto bad_payload = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
|
|
.has_canvas = true,
|
|
.width = 16U,
|
|
.height = 8U,
|
|
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(bad_payload_layers),
|
|
});
|
|
|
|
PP_EXPECT(harness, !no_canvas);
|
|
PP_EXPECT(harness, !bad_layer);
|
|
PP_EXPECT(harness, !bad_blend);
|
|
PP_EXPECT(harness, !bad_duration);
|
|
PP_EXPECT(harness, !bad_payload);
|
|
}
|
|
|
|
void clear_plan_records_legacy_canvas_effects(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_document_canvas_clear(true, 0.0F, 0.1F, 0.2F, 0.3F);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, plan.value().clears_canvas);
|
|
PP_EXPECT(harness, plan.value().records_undo);
|
|
PP_EXPECT(harness, plan.value().marks_unsaved);
|
|
PP_EXPECT(harness, !plan.value().no_op);
|
|
PP_EXPECT(harness, plan.value().r == 0.0F);
|
|
PP_EXPECT(harness, plan.value().g == 0.1F);
|
|
PP_EXPECT(harness, plan.value().b == 0.2F);
|
|
PP_EXPECT(harness, plan.value().a == 0.3F);
|
|
}
|
|
}
|
|
|
|
void clear_plan_noops_without_canvas(pp::tests::Harness& harness)
|
|
{
|
|
const auto plan = pp::app::plan_document_canvas_clear(false);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, !plan.value().clears_canvas);
|
|
PP_EXPECT(harness, !plan.value().records_undo);
|
|
PP_EXPECT(harness, !plan.value().marks_unsaved);
|
|
PP_EXPECT(harness, plan.value().no_op);
|
|
}
|
|
}
|
|
|
|
void clear_plan_rejects_bad_color_channels(pp::tests::Harness& harness)
|
|
{
|
|
PP_EXPECT(harness, !pp::app::plan_document_canvas_clear(true, -0.01F, 0.0F, 0.0F, 0.0F));
|
|
PP_EXPECT(harness, !pp::app::plan_document_canvas_clear(true, 0.0F, 1.01F, 0.0F, 0.0F));
|
|
PP_EXPECT(harness, !pp::app::plan_document_canvas_clear(
|
|
true,
|
|
0.0F,
|
|
0.0F,
|
|
std::numeric_limits<float>::infinity(),
|
|
0.0F));
|
|
}
|
|
|
|
void clear_executor_dispatches_color_to_service(pp::tests::Harness& harness)
|
|
{
|
|
FakeDocumentCanvasClearServices services;
|
|
const auto plan = pp::app::plan_document_canvas_clear(true, 0.25F, 0.5F, 0.75F, 1.0F);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, pp::app::execute_document_canvas_clear_plan(plan.value(), services).ok());
|
|
}
|
|
|
|
PP_EXPECT(harness, services.clear_calls == 1);
|
|
PP_EXPECT(harness, services.last_r == 0.25F);
|
|
PP_EXPECT(harness, services.last_g == 0.5F);
|
|
PP_EXPECT(harness, services.last_b == 0.75F);
|
|
PP_EXPECT(harness, services.last_a == 1.0F);
|
|
PP_EXPECT(harness, services.call_order == "clear;");
|
|
}
|
|
|
|
void clear_executor_preserves_noop_without_canvas(pp::tests::Harness& harness)
|
|
{
|
|
FakeDocumentCanvasClearServices services;
|
|
const auto plan = pp::app::plan_document_canvas_clear(false);
|
|
PP_EXPECT(harness, plan);
|
|
if (plan) {
|
|
PP_EXPECT(harness, pp::app::execute_document_canvas_clear_plan(plan.value(), services).ok());
|
|
}
|
|
|
|
PP_EXPECT(harness, services.clear_calls == 0);
|
|
}
|
|
|
|
void clear_executor_rejects_invalid_color(pp::tests::Harness& harness)
|
|
{
|
|
FakeDocumentCanvasClearServices services;
|
|
pp::app::DocumentCanvasClearPlan plan;
|
|
plan.clears_canvas = true;
|
|
plan.records_undo = true;
|
|
plan.marks_unsaved = true;
|
|
plan.no_op = false;
|
|
plan.r = 0.0F;
|
|
plan.g = 0.0F;
|
|
plan.b = 1.5F;
|
|
plan.a = 0.0F;
|
|
|
|
const auto status = pp::app::execute_document_canvas_clear_plan(plan, services);
|
|
PP_EXPECT(harness, !status.ok());
|
|
PP_EXPECT(harness, status.code == pp::foundation::StatusCode::out_of_range);
|
|
PP_EXPECT(harness, services.clear_calls == 0);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
pp::tests::Harness harness;
|
|
harness.run("snapshot plan projects canvas metadata", snapshot_plan_projects_canvas_metadata);
|
|
harness.run("snapshot plan defaults empty names and frames", snapshot_plan_defaults_empty_names_and_frames);
|
|
harness.run("snapshot plan attaches captured face payloads", snapshot_plan_attaches_captured_face_payloads);
|
|
harness.run("snapshot plan rejects invalid canvas state", snapshot_plan_rejects_invalid_canvas_state);
|
|
harness.run("clear plan records legacy canvas effects", clear_plan_records_legacy_canvas_effects);
|
|
harness.run("clear plan noops without canvas", clear_plan_noops_without_canvas);
|
|
harness.run("clear plan rejects bad color channels", clear_plan_rejects_bad_color_channels);
|
|
harness.run("clear executor dispatches color to service", clear_executor_dispatches_color_to_service);
|
|
harness.run("clear executor preserves noop without canvas", clear_executor_preserves_noop_without_canvas);
|
|
harness.run("clear executor rejects invalid color", clear_executor_rejects_invalid_color);
|
|
return harness.finish();
|
|
}
|