Add document face compositor bridge

This commit is contained in:
2026-06-05 17:09:17 +02:00
parent ee46a6497f
commit d4dad133ea
8 changed files with 375 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
#include "paint_renderer/compositor.h"
#include <limits>
#include <utility>
namespace pp::paint_renderer {
@@ -93,6 +94,52 @@ namespace {
return pp::foundation::Result<std::size_t>::success(static_cast<std::size_t>(count));
}
[[nodiscard]] pp::paint::Rgba rgba8_pixel(std::span<const std::uint8_t> bytes) noexcept
{
constexpr auto inv = 1.0F / 255.0F;
return pp::paint::Rgba {
.r = static_cast<float>(bytes[0]) * inv,
.g = static_cast<float>(bytes[1]) * inv,
.b = static_cast<float>(bytes[2]) * inv,
.a = static_cast<float>(bytes[3]) * inv,
};
}
[[nodiscard]] pp::foundation::Status composite_face_payload(
std::span<pp::paint::Rgba> destination,
pp::renderer::Extent2D extent,
const pp::document::LayerFacePixels& payload,
const pp::document::Layer& layer) noexcept
{
if (payload.x > extent.width || payload.width > extent.width - payload.x
|| payload.y > extent.height || payload.height > extent.height - payload.y) {
return pp::foundation::Status::out_of_range("document face payload rectangle is outside the render extent");
}
const auto payload_pixel_count = static_cast<std::uint64_t>(payload.width)
* static_cast<std::uint64_t>(payload.height);
if (payload_pixel_count > static_cast<std::uint64_t>(std::numeric_limits<std::size_t>::max() / 4U)
|| payload.rgba8.size() != static_cast<std::size_t>(payload_pixel_count) * 4U) {
return pp::foundation::Status::invalid_argument("document face payload byte size does not match dimensions");
}
for (std::uint32_t y = 0; y < payload.height; ++y) {
for (std::uint32_t x = 0; x < payload.width; ++x) {
const auto payload_index = (static_cast<std::size_t>(y) * payload.width + x) * 4U;
const auto destination_index = static_cast<std::size_t>(payload.y + y) * extent.width
+ static_cast<std::size_t>(payload.x + x);
auto stroke = rgba8_pixel(std::span<const std::uint8_t>(&payload.rgba8[payload_index], 4U));
stroke.a *= layer.opacity;
destination[destination_index] = pp::paint::blend_pixels(
destination[destination_index],
stroke,
layer.blend_mode);
}
}
return pp::foundation::Status::success();
}
[[nodiscard]] StrokeCompositePath composite_path_from_feedback(pp::renderer::PaintFeedbackPath path) noexcept
{
switch (path) {
@@ -176,6 +223,71 @@ pp::foundation::Status composite_layer(
return pp::foundation::Status::success();
}
pp::foundation::Result<DocumentFaceCompositeResult> composite_document_face(
DocumentFaceCompositeRequest request)
{
if (request.document == nullptr) {
return pp::foundation::Result<DocumentFaceCompositeResult>::failure(
pp::foundation::Status::invalid_argument("document composite request requires a document"));
}
if (request.face_index >= pp::document::cube_face_count) {
return pp::foundation::Result<DocumentFaceCompositeResult>::failure(
pp::foundation::Status::out_of_range("document composite face index is outside the cube"));
}
if (request.frame_index >= request.document->frames().size()) {
return pp::foundation::Result<DocumentFaceCompositeResult>::failure(
pp::foundation::Status::out_of_range("document composite frame index is outside the document"));
}
const pp::renderer::Extent2D extent {
.width = request.document->width(),
.height = request.document->height(),
};
const auto pixel_count = expected_pixel_count(extent);
if (!pixel_count) {
return pp::foundation::Result<DocumentFaceCompositeResult>::failure(pixel_count.status());
}
DocumentFaceCompositeResult result;
result.extent = extent;
result.pixels.assign(pixel_count.value(), request.clear_color);
result.visited_layer_count = request.document->layers().size();
for (const auto& layer : request.document->layers()) {
if (!layer.visible || layer.opacity == 0.0F || request.frame_index >= layer.frames.size()) {
continue;
}
bool composited_layer = false;
const auto& frame = layer.frames[request.frame_index];
for (const auto& payload : frame.face_pixels) {
if (payload.face_index != request.face_index) {
continue;
}
const auto status = composite_face_payload(
result.pixels,
extent,
payload,
layer);
if (!status.ok()) {
return pp::foundation::Result<DocumentFaceCompositeResult>::failure(status);
}
composited_layer = true;
++result.face_payload_count;
}
if (composited_layer) {
++result.composited_layer_count;
}
}
return pp::foundation::Result<DocumentFaceCompositeResult>::success(std::move(result));
}
bool stroke_composite_requires_feedback(
pp::paint::BlendMode layer_blend_mode,
pp::paint::StrokeBlendMode stroke_blend_mode,

View File

@@ -1,11 +1,14 @@
#pragma once
#include "document/document.h"
#include "foundation/result.h"
#include "paint/blend.h"
#include "renderer_api/renderer_api.h"
#include <cstddef>
#include <cstdint>
#include <span>
#include <vector>
namespace pp::paint_renderer {
@@ -83,11 +86,29 @@ struct CanvasStrokeFeedbackPlan {
bool compatibility_fallback = false;
};
struct DocumentFaceCompositeRequest {
const pp::document::CanvasDocument* document = nullptr;
std::size_t frame_index = 0;
std::uint32_t face_index = 0;
pp::paint::Rgba clear_color {};
};
struct DocumentFaceCompositeResult {
pp::renderer::Extent2D extent {};
std::vector<pp::paint::Rgba> pixels;
std::size_t visited_layer_count = 0;
std::size_t composited_layer_count = 0;
std::size_t face_payload_count = 0;
};
[[nodiscard]] pp::foundation::Status composite_layer(
std::span<pp::paint::Rgba> destination,
pp::renderer::Extent2D extent,
LayerCompositeView layer) noexcept;
[[nodiscard]] pp::foundation::Result<DocumentFaceCompositeResult> composite_document_face(
DocumentFaceCompositeRequest request);
[[nodiscard]] bool stroke_composite_requires_feedback(
pp::paint::BlendMode layer_blend_mode,
pp::paint::StrokeBlendMode stroke_blend_mode,