Integrate dialog export and Apple service teams

This commit is contained in:
2026-06-12 20:18:20 +02:00
parent 90f5fb29a6
commit 46fb8efec4
21 changed files with 1271 additions and 122 deletions

View File

@@ -10,6 +10,12 @@
namespace pp::paint_renderer {
pp::foundation::Result<DocumentFrameCompositeResult> composite_document_layer_frame(
const pp::document::CanvasDocument& document,
std::size_t layer_index,
std::size_t frame_index,
pp::paint::Rgba clear_color);
namespace {
[[nodiscard]] bool is_valid_blend_mode(pp::paint::BlendMode mode) noexcept
@@ -122,6 +128,8 @@ struct CubeFaceSample {
float t = 0.0F;
};
constexpr float document_depth_export_default_fov_degrees = 85.0F;
[[nodiscard]] CubeFaceSample panopainter_cube_face_sample(float x, float y, float z) noexcept
{
const auto ax = std::fabs(x);
@@ -279,6 +287,56 @@ struct EquirectangularProjectionResult {
std::size_t composited_layer_face_count = 0;
};
struct PerspectiveProjectionMap {
pp::renderer::Extent2D output_extent {};
std::vector<CubeFaceSample> samples;
};
pp::foundation::Result<PerspectiveProjectionMap> make_perspective_projection_map(
pp::renderer::Extent2D output_extent,
float vertical_fov_degrees)
{
if (!std::isfinite(vertical_fov_degrees)) {
return pp::foundation::Result<PerspectiveProjectionMap>::failure(
pp::foundation::Status::invalid_argument("document depth export field of view must be finite"));
}
if (vertical_fov_degrees <= 0.0F || vertical_fov_degrees >= 180.0F) {
return pp::foundation::Result<PerspectiveProjectionMap>::failure(
pp::foundation::Status::out_of_range("document depth export field of view must be between 0 and 180"));
}
const auto output_pixel_count = expected_pixel_count(output_extent);
if (!output_pixel_count) {
return pp::foundation::Result<PerspectiveProjectionMap>::failure(output_pixel_count.status());
}
PerspectiveProjectionMap map;
map.output_extent = output_extent;
map.samples.reserve(output_pixel_count.value());
constexpr auto pi = 3.14159265358979323846F;
const auto tan_half_fov = std::tan(vertical_fov_degrees * pi / 360.0F);
const auto aspect = static_cast<float>(output_extent.width) / static_cast<float>(output_extent.height);
for (std::uint32_t y = 0; y < output_extent.height; ++y) {
const auto ny = 1.0F
- ((static_cast<float>(y) + 0.5F) / static_cast<float>(output_extent.height)) * 2.0F;
for (std::uint32_t x = 0; x < output_extent.width; ++x) {
const auto nx = ((static_cast<float>(x) + 0.5F) / static_cast<float>(output_extent.width)) * 2.0F
- 1.0F;
const auto dir_x = nx * aspect * tan_half_fov;
const auto dir_y = ny * tan_half_fov;
constexpr auto dir_z = -1.0F;
const auto inv_length = 1.0F / std::sqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z);
map.samples.push_back(panopainter_cube_face_sample(
dir_x * inv_length,
dir_y * inv_length,
dir_z * inv_length));
}
}
return pp::foundation::Result<PerspectiveProjectionMap>::success(std::move(map));
}
pp::foundation::Result<EquirectangularProjectionResult> project_document_frame_equirectangular(
const DocumentFrameCompositeResult& composite)
{
@@ -340,6 +398,87 @@ pp::foundation::Result<EquirectangularProjectionResult> project_document_frame_e
return pp::foundation::Result<EquirectangularProjectionResult>::success(std::move(result));
}
pp::foundation::Result<std::vector<pp::paint::Rgba>> project_document_frame_perspective(
const DocumentFrameCompositeResult& composite,
const PerspectiveProjectionMap& projection)
{
const auto face_pixel_count = expected_pixel_count(composite.extent);
if (!face_pixel_count) {
return pp::foundation::Result<std::vector<pp::paint::Rgba>>::failure(face_pixel_count.status());
}
for (const auto& face : composite.faces) {
if (face.extent.width != composite.extent.width || face.extent.height != composite.extent.height
|| face.pixels.size() != face_pixel_count.value()) {
return pp::foundation::Result<std::vector<pp::paint::Rgba>>::failure(
pp::foundation::Status::invalid_argument("document depth export requires complete cube faces"));
}
}
std::vector<pp::paint::Rgba> pixels;
pixels.reserve(projection.samples.size());
for (const auto& sample : projection.samples) {
pixels.push_back(sample_face_nearest(composite.faces[sample.face_index], sample.s, sample.t));
}
return pp::foundation::Result<std::vector<pp::paint::Rgba>>::success(std::move(pixels));
}
float sample_face_alpha_nearest(
const DocumentFaceCompositeResult& face,
float s,
float t) noexcept
{
return sample_face_nearest(face, s, t).a;
}
pp::foundation::Result<std::vector<pp::paint::Rgba>> project_document_depth_perspective(
const pp::document::CanvasDocument& document,
std::size_t frame_index,
const PerspectiveProjectionMap& projection)
{
std::vector<pp::paint::Rgba> pixels(
projection.samples.size(),
pp::paint::Rgba {
.r = 0.0F,
.g = 0.0F,
.b = 0.0F,
.a = 1.0F,
});
const auto layer_count = document.layers().size();
for (std::size_t layer_index = 0; layer_index < layer_count; ++layer_index) {
const auto& layer = document.layers()[layer_index];
if (!layer.visible || layer.opacity == 0.0F || frame_index >= layer.frames.size()) {
continue;
}
auto composite = composite_document_layer_frame(document, layer_index, frame_index, {});
if (!composite) {
return pp::foundation::Result<std::vector<pp::paint::Rgba>>::failure(composite.status());
}
const auto gray = static_cast<float>(layer_index + 1U) / static_cast<float>(layer_count + 1U);
const pp::paint::Rgba layer_color {
.r = gray,
.g = gray,
.b = gray,
.a = 1.0F,
};
for (std::size_t pixel_index = 0; pixel_index < projection.samples.size(); ++pixel_index) {
const auto& sample = projection.samples[pixel_index];
const auto alpha = sample_face_alpha_nearest(
composite.value().faces[sample.face_index],
sample.s,
sample.t);
if (alpha > 0.01F) {
pixels[pixel_index] = layer_color;
}
}
}
return pp::foundation::Result<std::vector<pp::paint::Rgba>>::success(std::move(pixels));
}
}
pp::foundation::Status composite_layer(
@@ -806,6 +945,79 @@ pp::foundation::Result<DocumentDepthExportRenderPlan> plan_document_depth_export
return pp::foundation::Result<DocumentDepthExportRenderPlan>::success(plan);
}
pp::foundation::Result<DocumentDepthPngExportResult> export_document_depth_pngs(
DocumentDepthExportRenderPlanRequest request)
{
auto plan = plan_document_depth_export_render(request);
if (!plan) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(plan.status());
}
auto projection = make_perspective_projection_map(
plan.value().output_extent,
document_depth_export_default_fov_degrees);
if (!projection) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(projection.status());
}
auto image_composite = composite_document_frame(DocumentFrameCompositeRequest {
.document = request.document,
.frame_index = request.frame_index,
.clear_color = pp::paint::Rgba {
.r = 0.0F,
.g = 0.0F,
.b = 0.0F,
.a = 1.0F,
},
});
if (!image_composite) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(image_composite.status());
}
auto image_pixels = project_document_frame_perspective(image_composite.value(), projection.value());
if (!image_pixels) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(image_pixels.status());
}
auto depth_pixels = project_document_depth_perspective(*request.document, request.frame_index, projection.value());
if (!depth_pixels) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(depth_pixels.status());
}
std::vector<std::uint8_t> rgba8;
append_rgba8_bytes(rgba8, image_pixels.value());
auto image_png = pp::assets::encode_png_rgba8(
plan.value().output_extent.width,
plan.value().output_extent.height,
rgba8);
if (!image_png) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(image_png.status());
}
append_rgba8_bytes(rgba8, depth_pixels.value());
auto depth_png = pp::assets::encode_png_rgba8(
plan.value().output_extent.width,
plan.value().output_extent.height,
rgba8);
if (!depth_png) {
return pp::foundation::Result<DocumentDepthPngExportResult>::failure(depth_png.status());
}
DocumentDepthPngExportResult result;
result.output_extent = plan.value().output_extent;
result.image_encoded_bytes = static_cast<std::uint64_t>(image_png.value().size());
result.depth_encoded_bytes = static_cast<std::uint64_t>(depth_png.value().size());
result.merged_face_draw_count = plan.value().merged_face_draw_count;
result.layer_depth_draw_count = plan.value().layer_depth_draw_count;
result.visited_layer_count = plan.value().visited_layer_count;
result.visible_layer_count = plan.value().visible_layer_count;
result.face_payload_count = plan.value().face_payload_count;
result.uses_perspective_camera = plan.value().uses_perspective_camera;
result.image_png = std::move(image_png.value());
result.depth_png = std::move(depth_png.value());
return pp::foundation::Result<DocumentDepthPngExportResult>::success(std::move(result));
}
pp::foundation::Result<DocumentLayerEquirectangularPngExportResult>
export_document_layers_equirectangular_pngs(DocumentLayerEquirectangularPngExportRequest request)
{

View File

@@ -189,6 +189,23 @@ struct DocumentDepthExportRenderPlan {
bool requires_renderer_readback = true;
};
struct DocumentDepthPngExportResult {
pp::renderer::Extent2D output_extent {
.width = 1024,
.height = 1024,
};
std::vector<std::byte> image_png;
std::vector<std::byte> depth_png;
std::uint64_t image_encoded_bytes = 0;
std::uint64_t depth_encoded_bytes = 0;
std::size_t merged_face_draw_count = 0;
std::size_t layer_depth_draw_count = 0;
std::size_t visited_layer_count = 0;
std::size_t visible_layer_count = 0;
std::size_t face_payload_count = 0;
bool uses_perspective_camera = true;
};
struct DocumentLayerEquirectangularPngExportRequest {
const pp::document::CanvasDocument* document = nullptr;
std::size_t frame_index = 0;
@@ -278,6 +295,9 @@ export_document_frame_equirectangular_jpeg(
[[nodiscard]] pp::foundation::Result<DocumentDepthExportRenderPlan> plan_document_depth_export_render(
DocumentDepthExportRenderPlanRequest request) noexcept;
[[nodiscard]] pp::foundation::Result<DocumentDepthPngExportResult> export_document_depth_pngs(
DocumentDepthExportRenderPlanRequest request);
[[nodiscard]] pp::foundation::Result<DocumentLayerEquirectangularPngExportResult>
export_document_layers_equirectangular_pngs(DocumentLayerEquirectangularPngExportRequest request);