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

@@ -2,6 +2,7 @@
#include "foundation/result.h"
#include <memory>
#include <string>
#include <string_view>
@@ -34,6 +35,41 @@ struct AppInputDialogPlan {
std::string ok_caption = "Ok";
};
class AppDialog {
public:
virtual ~AppDialog() = default;
[[nodiscard]] virtual AppDialogKind kind() const noexcept = 0;
};
class AppProgressDialog : public AppDialog {
public:
~AppProgressDialog() override = default;
};
class AppMessageDialog : public AppDialog {
public:
~AppMessageDialog() override = default;
};
class AppInputDialog : public AppDialog {
public:
~AppInputDialog() override = default;
};
class AppDialogFactory {
public:
virtual ~AppDialogFactory() = default;
[[nodiscard]] virtual std::shared_ptr<AppProgressDialog> show_progress_dialog(
const AppProgressDialogPlan& plan) = 0;
[[nodiscard]] virtual std::shared_ptr<AppMessageDialog> show_message_dialog(
const AppMessageDialogPlan& plan) = 0;
[[nodiscard]] virtual std::shared_ptr<AppInputDialog> show_input_dialog(
const AppInputDialogPlan& plan) = 0;
};
[[nodiscard]] inline AppProgressDialogPlan plan_app_progress_dialog(
std::string_view title,
int total) noexcept

View File

@@ -492,9 +492,9 @@ public:
case DocumentExportExecutionKind::layers_stem:
case DocumentExportExecutionKind::animation_frames_collection:
case DocumentExportExecutionKind::animation_frames_stem:
case DocumentExportExecutionKind::depth:
case DocumentExportExecutionKind::cube_faces:
return true;
case DocumentExportExecutionKind::depth:
case DocumentExportExecutionKind::animation_mp4:
case DocumentExportExecutionKind::timelapse:
return false;

View File

@@ -126,13 +126,15 @@ void start_document_export_collection(
std::shared_ptr<NodeProgressBar> App::show_progress(const std::string& title, int total /*= 0*/)
{
const auto plan = pp::app::plan_app_progress_dialog(title, total);
return pp::panopainter::create_legacy_app_progress_dialog(*this, plan);
const auto dialogs = pp::panopainter::make_legacy_app_dialog_factory(*this);
return pp::panopainter::legacy_progress_dialog_node(dialogs->show_progress_dialog(plan));
}
std::shared_ptr<NodeMessageBox> App::message_box(const std::string &title, const std::string& text, bool cancel_button)
{
const auto plan = pp::app::plan_app_message_dialog(title, text, cancel_button);
return pp::panopainter::create_legacy_app_message_dialog(*this, plan);
const auto dialogs = pp::panopainter::make_legacy_app_dialog_factory(*this);
return pp::panopainter::legacy_message_dialog_node(dialogs->show_message_dialog(plan));
}
std::shared_ptr<NodeInputBox> App::input_box(const std::string& title,
@@ -143,7 +145,8 @@ std::shared_ptr<NodeInputBox> App::input_box(const std::string& title,
LOG("input dialog skipped: %s", plan_result.status().message);
return nullptr;
}
return pp::panopainter::create_legacy_app_input_dialog(*this, plan_result.value());
const auto dialogs = pp::panopainter::make_legacy_app_dialog_factory(*this);
return pp::panopainter::legacy_input_dialog_node(dialogs->show_input_dialog(plan_result.value()));
}
void App::dialog_usermanual()

View File

@@ -8,46 +8,150 @@
#include "node_progress_bar.h"
namespace pp::panopainter {
namespace {
class LegacyAppProgressDialog final : public pp::app::AppProgressDialog {
public:
explicit LegacyAppProgressDialog(std::shared_ptr<NodeProgressBar> node) noexcept
: node_(std::move(node))
{
}
[[nodiscard]] pp::app::AppDialogKind kind() const noexcept override
{
return pp::app::AppDialogKind::progress;
}
[[nodiscard]] std::shared_ptr<NodeProgressBar> node() const noexcept
{
return node_;
}
private:
std::shared_ptr<NodeProgressBar> node_;
};
class LegacyAppMessageDialog final : public pp::app::AppMessageDialog {
public:
explicit LegacyAppMessageDialog(std::shared_ptr<NodeMessageBox> node) noexcept
: node_(std::move(node))
{
}
[[nodiscard]] pp::app::AppDialogKind kind() const noexcept override
{
return pp::app::AppDialogKind::message;
}
[[nodiscard]] std::shared_ptr<NodeMessageBox> node() const noexcept
{
return node_;
}
private:
std::shared_ptr<NodeMessageBox> node_;
};
class LegacyAppInputDialog final : public pp::app::AppInputDialog {
public:
explicit LegacyAppInputDialog(std::shared_ptr<NodeInputBox> node) noexcept
: node_(std::move(node))
{
}
[[nodiscard]] pp::app::AppDialogKind kind() const noexcept override
{
return pp::app::AppDialogKind::input;
}
[[nodiscard]] std::shared_ptr<NodeInputBox> node() const noexcept
{
return node_;
}
private:
std::shared_ptr<NodeInputBox> node_;
};
class LegacyAppDialogFactory final : public pp::app::AppDialogFactory {
public:
explicit LegacyAppDialogFactory(App& app) noexcept
: app_(app)
{
}
[[nodiscard]] std::shared_ptr<pp::app::AppProgressDialog> show_progress_dialog(
const pp::app::AppProgressDialogPlan& plan) override
{
return std::make_shared<LegacyAppProgressDialog>(
create_legacy_progress_dialog_overlay(app_, plan));
}
[[nodiscard]] std::shared_ptr<pp::app::AppMessageDialog> show_message_dialog(
const pp::app::AppMessageDialogPlan& plan) override
{
return std::make_shared<LegacyAppMessageDialog>(
create_legacy_message_dialog_overlay(app_, plan));
}
[[nodiscard]] std::shared_ptr<pp::app::AppInputDialog> show_input_dialog(
const pp::app::AppInputDialogPlan& plan) override
{
return std::make_shared<LegacyAppInputDialog>(
create_legacy_input_dialog_overlay(app_, plan));
}
private:
App& app_;
};
} // namespace
std::unique_ptr<pp::app::AppDialogFactory> make_legacy_app_dialog_factory(App& app)
{
return std::make_unique<LegacyAppDialogFactory>(app);
}
std::shared_ptr<NodeProgressBar> create_legacy_app_progress_dialog(
App& app,
const pp::app::AppProgressDialogPlan& plan)
{
auto progress = make_legacy_overlay_node<NodeProgressBar>(app);
progress->m_progress->SetWidthP(plan.progress_fraction);
progress->m_title->set_text(plan.title.c_str());
progress->m_total = plan.total;
progress->m_count = plan.count;
(void)attach_legacy_overlay_node(app, progress);
return progress;
return legacy_progress_dialog_node(make_legacy_app_dialog_factory(app)->show_progress_dialog(plan));
}
std::shared_ptr<NodeMessageBox> create_legacy_app_message_dialog(
App& app,
const pp::app::AppMessageDialogPlan& plan)
{
auto message = make_legacy_overlay_node<NodeMessageBox>(app);
message->m_title->set_text(plan.title.c_str());
message->m_message->set_text(plan.message.c_str());
message->btn_ok->m_text->set_text(plan.ok_caption.c_str());
if (plan.show_cancel)
message->btn_cancel->m_text->set_text(plan.cancel_caption.c_str());
else
close_legacy_dialog_node(*message->btn_cancel);
(void)attach_legacy_overlay_node(app, message);
return message;
return legacy_message_dialog_node(make_legacy_app_dialog_factory(app)->show_message_dialog(plan));
}
std::shared_ptr<NodeInputBox> create_legacy_app_input_dialog(
App& app,
const pp::app::AppInputDialogPlan& plan)
{
auto input = make_legacy_overlay_node<NodeInputBox>(app);
input->m_title->set_text(plan.title.c_str());
input->m_field_name->set_text(plan.field_name.c_str());
input->btn_ok->m_text->set_text(plan.ok_caption.c_str());
(void)attach_legacy_overlay_node(app, input);
return input;
return legacy_input_dialog_node(make_legacy_app_dialog_factory(app)->show_input_dialog(plan));
}
std::shared_ptr<NodeProgressBar> legacy_progress_dialog_node(
const std::shared_ptr<pp::app::AppProgressDialog>& dialog) noexcept
{
auto legacy = std::dynamic_pointer_cast<LegacyAppProgressDialog>(dialog);
return legacy ? legacy->node() : nullptr;
}
std::shared_ptr<NodeMessageBox> legacy_message_dialog_node(
const std::shared_ptr<pp::app::AppMessageDialog>& dialog) noexcept
{
auto legacy = std::dynamic_pointer_cast<LegacyAppMessageDialog>(dialog);
return legacy ? legacy->node() : nullptr;
}
std::shared_ptr<NodeInputBox> legacy_input_dialog_node(
const std::shared_ptr<pp::app::AppInputDialog>& dialog) noexcept
{
auto legacy = std::dynamic_pointer_cast<LegacyAppInputDialog>(dialog);
return legacy ? legacy->node() : nullptr;
}
} // namespace pp::panopainter

View File

@@ -11,16 +11,27 @@ class NodeProgressBar;
namespace pp::panopainter {
std::shared_ptr<NodeProgressBar> create_legacy_app_progress_dialog(
[[nodiscard]] std::unique_ptr<pp::app::AppDialogFactory> make_legacy_app_dialog_factory(App& app);
[[nodiscard]] std::shared_ptr<NodeProgressBar> create_legacy_app_progress_dialog(
App& app,
const pp::app::AppProgressDialogPlan& plan);
std::shared_ptr<NodeMessageBox> create_legacy_app_message_dialog(
[[nodiscard]] std::shared_ptr<NodeMessageBox> create_legacy_app_message_dialog(
App& app,
const pp::app::AppMessageDialogPlan& plan);
std::shared_ptr<NodeInputBox> create_legacy_app_input_dialog(
[[nodiscard]] std::shared_ptr<NodeInputBox> create_legacy_app_input_dialog(
App& app,
const pp::app::AppInputDialogPlan& plan);
[[nodiscard]] std::shared_ptr<NodeProgressBar> legacy_progress_dialog_node(
const std::shared_ptr<pp::app::AppProgressDialog>& dialog) noexcept;
[[nodiscard]] std::shared_ptr<NodeMessageBox> legacy_message_dialog_node(
const std::shared_ptr<pp::app::AppMessageDialog>& dialog) noexcept;
[[nodiscard]] std::shared_ptr<NodeInputBox> legacy_input_dialog_node(
const std::shared_ptr<pp::app::AppInputDialog>& dialog) noexcept;
} // namespace pp::panopainter

View File

@@ -330,6 +330,42 @@ pp::foundation::Status export_equirectangular_from_document_snapshot(
services);
}
pp::foundation::Status export_depth_from_document_snapshot(
App& app,
const pp::app::DocumentDepthExportTarget& target,
const LegacyDocumentExportSnapshotReports& reports)
{
auto exported = pp::paint_renderer::export_document_depth_pngs(
pp::paint_renderer::DocumentDepthExportRenderPlanRequest {
.document = &reports.snapshot.document,
.frame_index = reports.snapshot.document.active_frame_index(),
});
if (!exported) {
return exported.status();
}
LOG(
"export-depth document export PNG writer: %ux%u imageBytes=%llu depthBytes=%llu mergedFaceDraws=%zu layerDepthDraws=%zu visitedLayers=%zu visibleLayers=%zu facePayloads=%zu",
exported.value().output_extent.width,
exported.value().output_extent.height,
static_cast<unsigned long long>(exported.value().image_encoded_bytes),
static_cast<unsigned long long>(exported.value().depth_encoded_bytes),
exported.value().merged_face_draw_count,
exported.value().layer_depth_draw_count,
exported.value().visited_layer_count,
exported.value().visible_layer_count,
exported.value().face_payload_count);
LegacyExportWriteServices services(app);
return pp::app::execute_document_depth_export_write(
target,
pp::app::DocumentDepthExportPayload {
.image_bytes = std::span<const std::byte>(exported.value().image_png),
.depth_bytes = std::span<const std::byte>(exported.value().depth_png),
},
services);
}
class LegacyDocumentExportServices final : public pp::app::DocumentExportServices {
public:
explicit LegacyDocumentExportServices(App& app) noexcept
@@ -565,15 +601,29 @@ public:
const auto prepared = prepare_legacy_document_export_snapshot(app_, "export-depth");
if (prepared) {
const auto report = pp::app::make_document_canvas_save_snapshot_report(prepared.value().snapshot);
const auto route = pp::app::plan_document_export_snapshot_route_for_current_platform(
pp::app::DocumentExportExecutionKind::depth,
report);
if (!route.uses_document_snapshot_writer) {
LOG(
"export-depth document export writer retained legacy export: %.*s",
static_cast<int>(route.fallback_reason.size()),
route.fallback_reason.data());
if (should_use_document_snapshot_writer(
"export-depth",
pp::app::DocumentExportExecutionKind::depth,
prepared.value(),
{})) {
if (target) {
const auto exported = export_depth_from_document_snapshot(app_, target.value(), prepared.value());
if (exported.ok()) {
show_export_success_dialog(
app_,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::depth,
pp::app::document_export_media_platform_destination(),
app_.work_path));
return;
}
LOG("export-depth document export writer retained legacy export after failure: %s", exported.message);
} else {
LOG(
"export-depth document export writer retained legacy export after target failure: %s",
target.status().message);
}
}
#if !__WEB__
const auto plan = pp::paint_renderer::plan_document_depth_export_render(

View File

@@ -3,7 +3,10 @@
#include "app.h"
#include "node.h"
#include "node_input_box.h"
#include "node_message_box.h"
#include "node_popup_menu.h"
#include "node_progress_bar.h"
namespace pp::panopainter {
@@ -148,4 +151,45 @@ pp::foundation::Result<std::shared_ptr<NodePopupMenu>> add_legacy_popup_menu(
return pp::foundation::Result<std::shared_ptr<NodePopupMenu>>::success(popup);
}
std::shared_ptr<NodeProgressBar> create_legacy_progress_dialog_overlay(
App& app,
const pp::app::AppProgressDialogPlan& plan)
{
auto progress = make_legacy_overlay_node<NodeProgressBar>(app);
progress->m_progress->SetWidthP(plan.progress_fraction);
progress->m_title->set_text(plan.title.c_str());
progress->m_total = plan.total;
progress->m_count = plan.count;
(void)attach_legacy_overlay_node(app, progress);
return progress;
}
std::shared_ptr<NodeMessageBox> create_legacy_message_dialog_overlay(
App& app,
const pp::app::AppMessageDialogPlan& plan)
{
auto message = make_legacy_overlay_node<NodeMessageBox>(app);
message->m_title->set_text(plan.title.c_str());
message->m_message->set_text(plan.message.c_str());
message->btn_ok->m_text->set_text(plan.ok_caption.c_str());
if (plan.show_cancel)
message->btn_cancel->m_text->set_text(plan.cancel_caption.c_str());
else
close_legacy_dialog_node(*message->btn_cancel);
(void)attach_legacy_overlay_node(app, message);
return message;
}
std::shared_ptr<NodeInputBox> create_legacy_input_dialog_overlay(
App& app,
const pp::app::AppInputDialogPlan& plan)
{
auto input = make_legacy_overlay_node<NodeInputBox>(app);
input->m_title->set_text(plan.title.c_str());
input->m_field_name->set_text(plan.field_name.c_str());
input->btn_ok->m_text->set_text(plan.ok_caption.c_str());
(void)attach_legacy_overlay_node(app, input);
return input;
}
} // namespace pp::panopainter

View File

@@ -1,5 +1,6 @@
#pragma once
#include "app_core/app_dialog.h"
#include "foundation/result.h"
#include "node.h"
@@ -7,7 +8,10 @@
#include <memory>
class App;
class NodeInputBox;
class NodeMessageBox;
class NodePopupMenu;
class NodeProgressBar;
namespace pp::panopainter {
@@ -40,6 +44,18 @@ void close_legacy_popup_panel(
float y,
float rtl_anchor_width) noexcept;
[[nodiscard]] std::shared_ptr<NodeProgressBar> create_legacy_progress_dialog_overlay(
App& app,
const pp::app::AppProgressDialogPlan& plan);
[[nodiscard]] std::shared_ptr<NodeMessageBox> create_legacy_message_dialog_overlay(
App& app,
const pp::app::AppMessageDialogPlan& plan);
[[nodiscard]] std::shared_ptr<NodeInputBox> create_legacy_input_dialog_overlay(
App& app,
const pp::app::AppInputDialogPlan& plan);
template <class T>
std::shared_ptr<T> make_legacy_overlay_node(App& app)
{

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);

View File

@@ -0,0 +1,128 @@
#include "platform_apple/apple_platform_services.h"
#include "app_core/document_platform_io.h"
#include "platform_api/platform_policy.h"
#include <array>
#include <utility>
namespace pp::platform::apple {
namespace {
[[nodiscard]] std::vector<std::string> apple_image_file_types()
{
static constexpr std::array<std::string_view, 5> kFileTypes = {
"png",
"PNG",
"jpg",
"JPG",
"jpeg",
};
return { kFileTypes.begin(), kFileTypes.end() };
}
void invoke_picked_path_if_selected(
const std::string& path,
const PickedPathCallback& callback)
{
if (pp::app::plan_picked_path(path) == pp::app::PickedPathAction::invoke_callback)
callback(path);
}
}
AppleDocumentPlatformServices::AppleDocumentPlatformServices(
PlatformFamily family,
AppleDocumentPickerBridge bridge)
: family_(family)
, bridge_(std::move(bridge))
{
}
std::vector<std::string> AppleDocumentPlatformServices::document_browse_roots(
std::string_view work_path,
std::string_view data_path) const
{
return platform_document_browse_roots(family_, work_path, data_path);
}
void AppleDocumentPlatformServices::pick_image(PickedPathCallback callback) const
{
if (family_ == PlatformFamily::ios)
{
if (bridge_.pick_image)
bridge_.pick_image(std::move(callback));
return;
}
if (family_ == PlatformFamily::macos && bridge_.pick_file)
{
bridge_.pick_file(
apple_image_file_types(),
[callback = std::move(callback)](std::string path) {
invoke_picked_path_if_selected(path, callback);
});
}
}
void AppleDocumentPlatformServices::pick_file(
std::vector<std::string> file_types,
PickedPathCallback callback) const
{
if (!bridge_.pick_file)
return;
if (family_ == PlatformFamily::ios)
{
bridge_.pick_file(std::move(file_types), std::move(callback));
return;
}
if (family_ == PlatformFamily::macos)
{
bridge_.pick_file(
std::move(file_types),
[callback = std::move(callback)](std::string path) {
invoke_picked_path_if_selected(path, callback);
});
}
}
void AppleDocumentPlatformServices::pick_save_file(
std::vector<std::string> file_types,
PickedPathCallback callback) const
{
if (family_ == PlatformFamily::macos && bridge_.pick_save_file)
{
bridge_.pick_save_file(
std::move(file_types),
[callback = std::move(callback)](std::string path) {
invoke_picked_path_if_selected(path, callback);
});
}
}
void AppleDocumentPlatformServices::pick_directory(PickedPathCallback callback) const
{
if (family_ == PlatformFamily::macos && bridge_.pick_directory)
{
bridge_.pick_directory([callback = std::move(callback)](std::string path) {
invoke_picked_path_if_selected(path, callback);
});
}
}
bool AppleDocumentPlatformServices::supports_working_directory_picker() const
{
return platform_supports_working_directory_picker(family_);
}
std::string AppleDocumentPlatformServices::format_working_directory_path(std::string_view path) const
{
if (family_ == PlatformFamily::macos && bridge_.format_working_directory_path)
return bridge_.format_working_directory_path(path);
return std::string(path);
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include "platform_api/platform_policy.h"
#include "platform_api/platform_services.h"
#include <functional>
#include <string>
#include <string_view>
#include <vector>
namespace pp::platform::apple {
struct AppleDocumentPickerBridge {
std::function<void(PickedPathCallback callback)> pick_image;
std::function<void(std::vector<std::string> file_types, PickedPathCallback callback)> pick_file;
std::function<void(std::vector<std::string> file_types, PickedPathCallback callback)> pick_save_file;
std::function<void(PickedPathCallback callback)> pick_directory;
std::function<std::string(std::string_view path)> format_working_directory_path;
};
class AppleDocumentPlatformServices {
public:
explicit AppleDocumentPlatformServices(
PlatformFamily family,
AppleDocumentPickerBridge bridge = {});
[[nodiscard]] std::vector<std::string> document_browse_roots(
std::string_view work_path,
std::string_view data_path) const;
void pick_image(PickedPathCallback callback) const;
void pick_file(std::vector<std::string> file_types, PickedPathCallback callback) const;
void pick_save_file(std::vector<std::string> file_types, PickedPathCallback callback) const;
void pick_directory(PickedPathCallback callback) const;
[[nodiscard]] bool supports_working_directory_picker() const;
[[nodiscard]] std::string format_working_directory_path(std::string_view path) const;
private:
PlatformFamily family_;
AppleDocumentPickerBridge bridge_;
};
}

View File

@@ -2,9 +2,9 @@
#include "platform_legacy/legacy_platform_services.h"
#include "app.h"
#include "app_core/document_platform_io.h"
#include "legacy_ui_gl_dispatch.h"
#include "log.h"
#include "platform_apple/apple_platform_services.h"
#include "platform_api/network_tls_policy.h"
#include "platform_api/platform_policy.h"
#include "renderer_gl/opengl_capabilities.h"
@@ -52,14 +52,6 @@ void webgl_sync();
namespace {
void invoke_picked_path_if_selected(
const std::string& path,
const std::function<void(std::string path)>& callback)
{
if (pp::app::plan_picked_path(path) == pp::app::PickedPathAction::invoke_callback)
callback(path);
}
#ifdef __WEB__
class RetainedWebPlatformServices final : public pp::platform::WebPlatformServices {
public:
@@ -102,6 +94,79 @@ public:
#endif
}
#if defined(__IOS__) || defined(__OSX__)
[[nodiscard]] NSMutableArray<NSString*>* apple_file_types_array(const std::vector<std::string>& file_types)
{
NSMutableArray<NSString*>* types = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& type : file_types)
{
[types addObject:[NSString stringWithCString:type.c_str() encoding:NSUTF8StringEncoding]];
}
return types;
}
[[nodiscard]] pp::platform::apple::AppleDocumentPlatformServices& active_apple_document_platform_services()
{
#ifdef __IOS__
static pp::platform::apple::AppleDocumentPlatformServices services(
pp::platform::PlatformFamily::ios,
[] {
pp::platform::apple::AppleDocumentPickerBridge bridge;
bridge.pick_image = [](pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view pick_photo:callback];
});
};
bridge.pick_file = [](
std::vector<std::string> file_types,
pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view pick_file:apple_file_types_array(file_types) then:callback];
});
};
return bridge;
}());
return services;
#else
static pp::platform::apple::AppleDocumentPlatformServices services(
pp::platform::PlatformFamily::macos,
[] {
pp::platform::apple::AppleDocumentPickerBridge bridge;
bridge.pick_file = [](
std::vector<std::string> file_types,
pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{
const std::string path = [App::I->osx_view pick_file:apple_file_types_array(file_types)];
callback(path);
});
};
bridge.pick_save_file = [](
std::vector<std::string> file_types,
pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{
const std::string path = [App::I->osx_view pick_file_save:apple_file_types_array(file_types)];
callback(path);
});
};
bridge.pick_directory = [](pp::platform::PickedPathCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{
const std::string path = [App::I->osx_view pick_dir];
callback(path);
});
};
bridge.format_working_directory_path = [](std::string_view path) {
char path_buffer[4096] = {};
if (realpath(std::string(path).c_str(), path_buffer))
return std::string(path_buffer);
return std::string(path);
};
return bridge;
}());
return services;
#endif
}
#endif
// DEBT-0017: fallback for platforms that do not inject PlatformServices yet.
class LegacyPlatformServices final : public pp::platform::PlatformServices {
public:
@@ -376,10 +441,14 @@ public:
std::string_view work_path,
std::string_view data_path) override
{
#if defined(__IOS__) || defined(__OSX__)
return active_apple_document_platform_services().document_browse_roots(work_path, data_path);
#else
return pp::platform::platform_document_browse_roots(
pp::platform::current_platform_family(),
work_path,
data_path);
#endif
}
void save_ui_state() override
@@ -414,15 +483,9 @@ public:
void pick_image(pp::platform::PickedPathCallback callback) override
{
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
[App::I->ios_view pick_photo:callback];
});
active_apple_document_platform_services().pick_image(std::move(callback));
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil];
std::string path = [App::I->osx_view pick_file:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
active_apple_document_platform_services().pick_image(std::move(callback));
#elif __ANDROID__
android_pick_file(callback);
#elif __LINUX__
@@ -438,20 +501,9 @@ public:
void pick_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{
#ifdef __IOS__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& t : file_types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
[App::I->ios_view pick_file:fileTypes then:callback];
});
active_apple_document_platform_services().pick_file(std::move(file_types), std::move(callback));
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& t : file_types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
std::string path = [App::I->osx_view pick_file:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
active_apple_document_platform_services().pick_file(std::move(file_types), std::move(callback));
#elif __ANDROID__
android_pick_file(callback);
#elif __LINUX__
@@ -468,13 +520,7 @@ public:
void pick_save_file(std::vector<std::string> file_types, pp::platform::PickedPathCallback callback) override
{
#if __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:file_types.size()];
for (const auto& t : file_types)
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
std::string path = [App::I->osx_view pick_file_save:fileTypes];
invoke_picked_path_if_selected(path, callback);
});
active_apple_document_platform_services().pick_save_file(std::move(file_types), std::move(callback));
#elif __ANDROID__
android_pick_file_save(callback);
#else
@@ -488,10 +534,7 @@ public:
#ifdef __IOS__
(void)callback;
#elif __OSX__
dispatch_async(dispatch_get_main_queue(), ^{
std::string path = [App::I->osx_view pick_dir];
invoke_picked_path_if_selected(path, callback);
});
active_apple_document_platform_services().pick_directory(std::move(callback));
#elif __ANDROID__
(void)callback;
#else
@@ -501,16 +544,18 @@ public:
[[nodiscard]] bool supports_working_directory_picker() override
{
#if defined(__IOS__) || defined(__OSX__)
return active_apple_document_platform_services().supports_working_directory_picker();
#else
return pp::platform::platform_supports_working_directory_picker(
pp::platform::current_platform_family());
#endif
}
[[nodiscard]] std::string format_working_directory_path(std::string_view path) override
{
#if defined(__OSX__)
char path_buffer[4096] = {};
if (realpath(std::string(path).c_str(), path_buffer))
return path_buffer;
#if defined(__IOS__) || defined(__OSX__)
return active_apple_document_platform_services().format_working_directory_path(path);
#endif
return std::string(path);
}