1146 lines
39 KiB
C++
1146 lines
39 KiB
C++
#pragma once
|
|
|
|
#include "app_core/app_dialog.h"
|
|
#include "app_core/document_canvas.h"
|
|
#include "document/document.h"
|
|
#include "foundation/result.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
namespace pp::app {
|
|
|
|
struct DocumentExportFileTarget {
|
|
std::string path;
|
|
std::string suggested_name;
|
|
};
|
|
|
|
struct DocumentExportCollectionTarget {
|
|
std::string directory;
|
|
std::string stem_path;
|
|
};
|
|
|
|
struct DocumentExportStemTarget {
|
|
std::string stem_path;
|
|
};
|
|
|
|
struct DocumentCubeFaceExportFileTarget {
|
|
std::string face_name;
|
|
std::string path;
|
|
};
|
|
|
|
struct DocumentCubeFaceExportTarget {
|
|
std::array<DocumentCubeFaceExportFileTarget, pp::document::cube_face_count> faces;
|
|
std::size_t face_count = 0;
|
|
};
|
|
|
|
struct DocumentDepthExportTarget {
|
|
std::string image_path;
|
|
std::string depth_path;
|
|
};
|
|
|
|
struct DocumentCubeFaceExportPayload {
|
|
std::span<const std::byte> bytes;
|
|
};
|
|
|
|
struct DocumentDepthExportPayload {
|
|
std::span<const std::byte> image_bytes;
|
|
std::span<const std::byte> depth_bytes;
|
|
};
|
|
|
|
struct DocumentExportFilePayload {
|
|
std::span<const std::byte> bytes;
|
|
};
|
|
|
|
struct DocumentExportCollectionPngPayload {
|
|
std::string path_suffix;
|
|
std::span<const std::byte> bytes;
|
|
};
|
|
|
|
struct DocumentExportSuggestedName {
|
|
std::string name;
|
|
};
|
|
|
|
enum class DocumentExportCollectionDestination {
|
|
work_directory_collection,
|
|
picked_directory_stem,
|
|
};
|
|
|
|
enum class DocumentExportStartDecision {
|
|
start_now,
|
|
show_license_disabled,
|
|
unavailable_no_canvas,
|
|
};
|
|
|
|
enum class DocumentExportMenuKind {
|
|
jpeg,
|
|
png,
|
|
layers,
|
|
cube_faces,
|
|
depth,
|
|
animation_frames,
|
|
animation_mp4,
|
|
timelapse,
|
|
};
|
|
|
|
enum class DocumentExportMenuAction {
|
|
show_jpeg_dialog,
|
|
show_png_dialog,
|
|
show_layers_dialog,
|
|
show_cube_faces_dialog,
|
|
show_depth_dialog,
|
|
show_animation_frames_dialog,
|
|
show_animation_mp4_dialog,
|
|
show_timelapse_dialog,
|
|
show_license_disabled,
|
|
unavailable_no_canvas,
|
|
};
|
|
|
|
enum class DocumentExportCollectionKind {
|
|
layers,
|
|
animation_frames,
|
|
};
|
|
|
|
enum class DocumentVideoExportKind {
|
|
animation_mp4,
|
|
timelapse,
|
|
};
|
|
|
|
enum class DocumentExportSuccessKind {
|
|
equirectangular,
|
|
layers,
|
|
animation_frames,
|
|
depth,
|
|
cube_faces,
|
|
animation_mp4,
|
|
timelapse,
|
|
};
|
|
|
|
enum class DocumentExportSuccessDestination {
|
|
suppressed,
|
|
photos,
|
|
pictures_panopainter,
|
|
files_panopainter,
|
|
work_directory,
|
|
path,
|
|
generic_success,
|
|
};
|
|
|
|
enum class DocumentExportExecutionKind {
|
|
equirectangular_file,
|
|
layers_collection,
|
|
layers_stem,
|
|
animation_frames_collection,
|
|
animation_frames_stem,
|
|
depth,
|
|
cube_faces,
|
|
animation_mp4,
|
|
timelapse,
|
|
};
|
|
|
|
enum class DocumentExportSnapshotRouteAction {
|
|
use_document_snapshot_writer,
|
|
use_legacy_export,
|
|
};
|
|
|
|
struct DocumentExportMenuPlan {
|
|
DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg;
|
|
DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog;
|
|
bool opens_dialog = true;
|
|
};
|
|
|
|
struct DocumentExportCollectionTargetPlan {
|
|
DocumentExportCollectionKind kind = DocumentExportCollectionKind::layers;
|
|
DocumentExportCollectionDestination destination = DocumentExportCollectionDestination::picked_directory_stem;
|
|
std::string_view suffix;
|
|
};
|
|
|
|
struct DocumentExportSuccessDialogPlan {
|
|
DocumentExportSuccessKind kind = DocumentExportSuccessKind::equirectangular;
|
|
DocumentExportSuccessDestination destination = DocumentExportSuccessDestination::suppressed;
|
|
AppMessageDialogPlan dialog;
|
|
bool show_dialog = false;
|
|
};
|
|
|
|
struct DocumentExportSnapshotRoutePlan {
|
|
DocumentExportExecutionKind kind = DocumentExportExecutionKind::equirectangular_file;
|
|
DocumentExportSnapshotRouteAction action = DocumentExportSnapshotRouteAction::use_legacy_export;
|
|
bool payload_complete = false;
|
|
bool target_supported = false;
|
|
bool platform_supported = false;
|
|
bool uses_document_snapshot_writer = false;
|
|
std::string_view fallback_reason;
|
|
};
|
|
|
|
class DocumentExportMenuServices {
|
|
public:
|
|
virtual ~DocumentExportMenuServices() = default;
|
|
|
|
virtual void show_jpeg_dialog() = 0;
|
|
virtual void show_png_dialog() = 0;
|
|
virtual void show_layers_dialog() = 0;
|
|
virtual void show_cube_faces_dialog() = 0;
|
|
virtual void show_depth_dialog() = 0;
|
|
virtual void show_animation_frames_dialog() = 0;
|
|
virtual void show_animation_mp4_dialog() = 0;
|
|
virtual void show_timelapse_dialog() = 0;
|
|
virtual void show_license_disabled() = 0;
|
|
};
|
|
|
|
class DocumentExportServices {
|
|
public:
|
|
virtual ~DocumentExportServices() = default;
|
|
|
|
virtual bool create_directory(std::string_view directory) = 0;
|
|
virtual void export_equirectangular(const DocumentExportFileTarget& target) = 0;
|
|
virtual void export_layers_to_stem(const DocumentExportStemTarget& target) = 0;
|
|
virtual void export_layers_to_collection(const DocumentExportCollectionTarget& target) = 0;
|
|
virtual void export_animation_frames_to_stem(const DocumentExportStemTarget& target) = 0;
|
|
virtual void export_animation_frames_to_collection(const DocumentExportCollectionTarget& target) = 0;
|
|
virtual void export_depth(std::string_view document_name) = 0;
|
|
virtual void export_cube_faces(std::string_view document_name) = 0;
|
|
};
|
|
|
|
class DocumentVideoExportServices {
|
|
public:
|
|
virtual ~DocumentVideoExportServices() = default;
|
|
|
|
virtual void export_animation_mp4(std::string_view path) = 0;
|
|
virtual void export_timelapse_mp4(std::string_view path) = 0;
|
|
virtual void show_animation_export_success(std::string_view path) = 0;
|
|
virtual void show_timelapse_export_success(std::string_view path) = 0;
|
|
};
|
|
|
|
class DocumentCubeFaceExportWriteServices {
|
|
public:
|
|
virtual ~DocumentCubeFaceExportWriteServices() = default;
|
|
|
|
virtual pp::foundation::Status write_binary_file(
|
|
std::string_view path,
|
|
std::span<const std::byte> bytes) = 0;
|
|
virtual void publish_exported_image(std::string_view path) = 0;
|
|
};
|
|
|
|
class DocumentDepthExportWriteServices {
|
|
public:
|
|
virtual ~DocumentDepthExportWriteServices() = default;
|
|
|
|
virtual pp::foundation::Status write_binary_file(
|
|
std::string_view path,
|
|
std::span<const std::byte> bytes) = 0;
|
|
virtual void publish_exported_image(std::string_view path) = 0;
|
|
};
|
|
|
|
class DocumentExportFileWriteServices {
|
|
public:
|
|
virtual ~DocumentExportFileWriteServices() = default;
|
|
|
|
virtual pp::foundation::Status write_binary_file(
|
|
std::string_view path,
|
|
std::span<const std::byte> bytes) = 0;
|
|
virtual void publish_exported_image(std::string_view path) = 0;
|
|
};
|
|
|
|
class DocumentExportCollectionWriteServices {
|
|
public:
|
|
virtual ~DocumentExportCollectionWriteServices() = default;
|
|
|
|
virtual pp::foundation::Status write_binary_file(
|
|
std::string_view path,
|
|
std::span<const std::byte> bytes) = 0;
|
|
virtual void publish_exported_image(std::string_view path) = 0;
|
|
};
|
|
|
|
[[nodiscard]] constexpr DocumentExportStartDecision plan_document_export_start(
|
|
bool requires_license,
|
|
bool license_valid,
|
|
bool has_canvas) noexcept
|
|
{
|
|
if (requires_license && !license_valid) {
|
|
return DocumentExportStartDecision::show_license_disabled;
|
|
}
|
|
|
|
return has_canvas
|
|
? DocumentExportStartDecision::start_now
|
|
: DocumentExportStartDecision::unavailable_no_canvas;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool document_export_menu_requires_license(
|
|
DocumentExportMenuKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportMenuKind::animation_mp4:
|
|
case DocumentExportMenuKind::timelapse:
|
|
return true;
|
|
case DocumentExportMenuKind::jpeg:
|
|
case DocumentExportMenuKind::png:
|
|
case DocumentExportMenuKind::layers:
|
|
case DocumentExportMenuKind::cube_faces:
|
|
case DocumentExportMenuKind::depth:
|
|
case DocumentExportMenuKind::animation_frames:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportMenuAction document_export_menu_dialog_action(
|
|
DocumentExportMenuKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportMenuKind::jpeg:
|
|
return DocumentExportMenuAction::show_jpeg_dialog;
|
|
case DocumentExportMenuKind::png:
|
|
return DocumentExportMenuAction::show_png_dialog;
|
|
case DocumentExportMenuKind::layers:
|
|
return DocumentExportMenuAction::show_layers_dialog;
|
|
case DocumentExportMenuKind::cube_faces:
|
|
return DocumentExportMenuAction::show_cube_faces_dialog;
|
|
case DocumentExportMenuKind::depth:
|
|
return DocumentExportMenuAction::show_depth_dialog;
|
|
case DocumentExportMenuKind::animation_frames:
|
|
return DocumentExportMenuAction::show_animation_frames_dialog;
|
|
case DocumentExportMenuKind::animation_mp4:
|
|
return DocumentExportMenuAction::show_animation_mp4_dialog;
|
|
case DocumentExportMenuKind::timelapse:
|
|
return DocumentExportMenuAction::show_timelapse_dialog;
|
|
}
|
|
|
|
return DocumentExportMenuAction::show_jpeg_dialog;
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_equirectangular_platform_destination() noexcept
|
|
{
|
|
#if defined(__IOS__)
|
|
return DocumentExportSuccessDestination::photos;
|
|
#elif defined(__OSX__)
|
|
return DocumentExportSuccessDestination::pictures_panopainter;
|
|
#elif defined(_WIN32)
|
|
return DocumentExportSuccessDestination::work_directory;
|
|
#else
|
|
return DocumentExportSuccessDestination::suppressed;
|
|
#endif
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_media_platform_destination() noexcept
|
|
{
|
|
#if defined(__IOS__)
|
|
return DocumentExportSuccessDestination::files_panopainter;
|
|
#elif defined(__OSX__)
|
|
return DocumentExportSuccessDestination::pictures_panopainter;
|
|
#elif defined(_WIN32)
|
|
return DocumentExportSuccessDestination::work_directory;
|
|
#else
|
|
return DocumentExportSuccessDestination::suppressed;
|
|
#endif
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportMenuPlan plan_document_export_menu_action(
|
|
DocumentExportMenuKind kind,
|
|
bool has_canvas,
|
|
bool license_valid) noexcept
|
|
{
|
|
DocumentExportMenuPlan plan;
|
|
plan.kind = kind;
|
|
plan.action = document_export_menu_dialog_action(kind);
|
|
|
|
const auto start = plan_document_export_start(
|
|
document_export_menu_requires_license(kind),
|
|
license_valid,
|
|
has_canvas);
|
|
if (start == DocumentExportStartDecision::show_license_disabled) {
|
|
plan.action = DocumentExportMenuAction::show_license_disabled;
|
|
plan.opens_dialog = false;
|
|
} else if (start == DocumentExportStartDecision::unavailable_no_canvas) {
|
|
plan.action = DocumentExportMenuAction::unavailable_no_canvas;
|
|
plan.opens_dialog = false;
|
|
}
|
|
|
|
return plan;
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::string_view document_export_collection_suffix(
|
|
DocumentExportCollectionKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportCollectionKind::layers:
|
|
return "_layers";
|
|
case DocumentExportCollectionKind::animation_frames:
|
|
return "_frames";
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportSuccessKind document_export_collection_success_kind(
|
|
DocumentExportCollectionKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportCollectionKind::layers:
|
|
return DocumentExportSuccessKind::layers;
|
|
case DocumentExportCollectionKind::animation_frames:
|
|
return DocumentExportSuccessKind::animation_frames;
|
|
}
|
|
|
|
return DocumentExportSuccessKind::layers;
|
|
}
|
|
|
|
[[nodiscard]] constexpr const char* document_export_failure_dialog_title(
|
|
DocumentExportSuccessKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportSuccessKind::equirectangular:
|
|
return "Export Equirectangular";
|
|
case DocumentExportSuccessKind::layers:
|
|
case DocumentExportSuccessKind::animation_frames:
|
|
return "Export Layers";
|
|
case DocumentExportSuccessKind::depth:
|
|
return "Export 3D View + Depth";
|
|
case DocumentExportSuccessKind::cube_faces:
|
|
return "Export Cube Faces";
|
|
case DocumentExportSuccessKind::animation_mp4:
|
|
return "Export Animation";
|
|
case DocumentExportSuccessKind::timelapse:
|
|
return "Export Timelapse";
|
|
}
|
|
|
|
return "Export";
|
|
}
|
|
|
|
[[nodiscard]] constexpr const char* document_export_execution_log_message(
|
|
DocumentExportExecutionKind kind) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportExecutionKind::equirectangular_file:
|
|
return "Document export file action failed";
|
|
case DocumentExportExecutionKind::layers_collection:
|
|
return "Document layer collection export failed";
|
|
case DocumentExportExecutionKind::layers_stem:
|
|
return "Document layer stem export failed";
|
|
case DocumentExportExecutionKind::animation_frames_collection:
|
|
return "Document animation frame collection export failed";
|
|
case DocumentExportExecutionKind::animation_frames_stem:
|
|
return "Document animation frame stem export failed";
|
|
case DocumentExportExecutionKind::depth:
|
|
return "Document depth export failed";
|
|
case DocumentExportExecutionKind::cube_faces:
|
|
return "Document cube-face export failed";
|
|
case DocumentExportExecutionKind::animation_mp4:
|
|
return "Document animation export failed";
|
|
case DocumentExportExecutionKind::timelapse:
|
|
return "Document timelapse export failed";
|
|
}
|
|
|
|
return "Document export failed";
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool ascii_iequals(std::string_view left, std::string_view right) noexcept
|
|
{
|
|
if (left.size() != right.size()) {
|
|
return false;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < left.size(); ++i) {
|
|
auto lhs = left[i];
|
|
if (lhs >= 'A' && lhs <= 'Z') {
|
|
lhs = static_cast<char>(lhs - 'A' + 'a');
|
|
}
|
|
auto rhs = right[i];
|
|
if (rhs >= 'A' && rhs <= 'Z') {
|
|
rhs = static_cast<char>(rhs - 'A' + 'a');
|
|
}
|
|
if (lhs != rhs) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool document_export_path_has_extension(
|
|
std::string_view path,
|
|
std::string_view extension) noexcept
|
|
{
|
|
return path.size() >= extension.size()
|
|
&& ascii_iequals(path.substr(path.size() - extension.size()), extension);
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool document_export_path_is_png_target(std::string_view path) noexcept
|
|
{
|
|
return document_export_path_has_extension(path, ".png");
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool document_export_path_is_jpeg_target(std::string_view path) noexcept
|
|
{
|
|
return document_export_path_has_extension(path, ".jpg")
|
|
|| document_export_path_has_extension(path, ".jpeg");
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool document_export_snapshot_target_supported(
|
|
DocumentExportExecutionKind kind,
|
|
std::string_view target_path = {}) noexcept
|
|
{
|
|
switch (kind) {
|
|
case DocumentExportExecutionKind::equirectangular_file:
|
|
return document_export_path_is_png_target(target_path)
|
|
|| document_export_path_is_jpeg_target(target_path);
|
|
case DocumentExportExecutionKind::layers_collection:
|
|
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::animation_mp4:
|
|
case DocumentExportExecutionKind::timelapse:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool document_export_snapshot_platform_supported() noexcept
|
|
{
|
|
#if __WEB__
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route(
|
|
DocumentExportExecutionKind kind,
|
|
DocumentCanvasSaveSnapshotReport report,
|
|
bool target_supported,
|
|
bool platform_supported) noexcept
|
|
{
|
|
DocumentExportSnapshotRoutePlan plan;
|
|
plan.kind = kind;
|
|
plan.payload_complete = report.payload_complete;
|
|
plan.target_supported = target_supported;
|
|
plan.platform_supported = platform_supported;
|
|
|
|
if (!platform_supported) {
|
|
plan.fallback_reason = "document snapshot export is disabled on this platform";
|
|
return plan;
|
|
}
|
|
|
|
if (!target_supported) {
|
|
plan.fallback_reason = "document snapshot export does not support this target";
|
|
return plan;
|
|
}
|
|
|
|
if (!report.payload_complete) {
|
|
plan.fallback_reason = "document snapshot still requires renderer payload readback";
|
|
return plan;
|
|
}
|
|
|
|
plan.action = DocumentExportSnapshotRouteAction::use_document_snapshot_writer;
|
|
plan.uses_document_snapshot_writer = true;
|
|
return plan;
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route_for_target(
|
|
DocumentExportExecutionKind kind,
|
|
DocumentCanvasSaveSnapshotReport report,
|
|
std::string_view target_path,
|
|
bool platform_supported) noexcept
|
|
{
|
|
return plan_document_export_snapshot_route(
|
|
kind,
|
|
report,
|
|
document_export_snapshot_target_supported(kind, target_path),
|
|
platform_supported);
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route_for_current_platform(
|
|
DocumentExportExecutionKind kind,
|
|
DocumentCanvasSaveSnapshotReport report,
|
|
std::string_view target_path = {}) noexcept
|
|
{
|
|
return plan_document_export_snapshot_route_for_target(
|
|
kind,
|
|
report,
|
|
target_path,
|
|
document_export_snapshot_platform_supported());
|
|
}
|
|
|
|
[[nodiscard]] constexpr DocumentExportCollectionTargetPlan plan_document_export_collection_target(
|
|
DocumentExportCollectionKind kind,
|
|
bool use_work_directory_collection) noexcept
|
|
{
|
|
return {
|
|
kind,
|
|
use_work_directory_collection
|
|
? DocumentExportCollectionDestination::work_directory_collection
|
|
: DocumentExportCollectionDestination::picked_directory_stem,
|
|
document_export_collection_suffix(kind),
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] inline AppMessageDialogPlan plan_document_export_failure_dialog(
|
|
DocumentExportSuccessKind kind,
|
|
std::string_view status_message)
|
|
{
|
|
return plan_app_message_dialog(
|
|
document_export_failure_dialog_title(kind),
|
|
status_message,
|
|
false);
|
|
}
|
|
|
|
[[nodiscard]] inline AppMessageDialogPlan plan_document_export_license_disabled_dialog()
|
|
{
|
|
return plan_app_message_dialog(
|
|
"License",
|
|
"This function is disabled in demo mode.",
|
|
false);
|
|
}
|
|
|
|
[[nodiscard]] inline DocumentExportSuccessDialogPlan plan_document_export_success_dialog(
|
|
DocumentExportSuccessKind kind,
|
|
DocumentExportSuccessDestination destination,
|
|
std::string_view detail = {})
|
|
{
|
|
DocumentExportSuccessDialogPlan plan;
|
|
plan.kind = kind;
|
|
plan.destination = destination;
|
|
if (destination == DocumentExportSuccessDestination::suppressed) {
|
|
return plan;
|
|
}
|
|
|
|
std::string message;
|
|
switch (kind) {
|
|
case DocumentExportSuccessKind::equirectangular:
|
|
plan.dialog.title = "Export Equirectangular";
|
|
switch (destination) {
|
|
case DocumentExportSuccessDestination::photos:
|
|
message = "Image exported to Photos";
|
|
break;
|
|
case DocumentExportSuccessDestination::pictures_panopainter:
|
|
message = "Image exported to Pictures/PanoPainter folder";
|
|
break;
|
|
case DocumentExportSuccessDestination::work_directory:
|
|
message = "Image exported to ";
|
|
message += detail;
|
|
break;
|
|
case DocumentExportSuccessDestination::suppressed:
|
|
case DocumentExportSuccessDestination::files_panopainter:
|
|
case DocumentExportSuccessDestination::path:
|
|
case DocumentExportSuccessDestination::generic_success:
|
|
break;
|
|
}
|
|
break;
|
|
case DocumentExportSuccessKind::layers:
|
|
case DocumentExportSuccessKind::animation_frames:
|
|
plan.dialog.title = "Export Layers";
|
|
if (destination == DocumentExportSuccessDestination::files_panopainter) {
|
|
message = "Image layers exported to Files/PanoPainter";
|
|
} else if (destination == DocumentExportSuccessDestination::path) {
|
|
message = "Layers exported to: ";
|
|
message += detail;
|
|
}
|
|
break;
|
|
case DocumentExportSuccessKind::depth:
|
|
plan.dialog.title = "Export 3D View + Depth";
|
|
if (destination == DocumentExportSuccessDestination::files_panopainter) {
|
|
message = "Image and depth exported to Files/PanoPainter";
|
|
} else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
|
|
message = "Image and depth exported to Pictures/PanoPainter folder";
|
|
} else if (destination == DocumentExportSuccessDestination::work_directory) {
|
|
message = "Image and depth exported to ";
|
|
message += detail;
|
|
}
|
|
break;
|
|
case DocumentExportSuccessKind::cube_faces:
|
|
plan.dialog.title = "Export Cube Faces";
|
|
if (destination == DocumentExportSuccessDestination::files_panopainter) {
|
|
message = "Image and depth exported to Files/PanoPainter";
|
|
} else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
|
|
message = "Image and depth exported to Pictures/PanoPainter folder";
|
|
} else if (destination == DocumentExportSuccessDestination::work_directory) {
|
|
message = "Image and depth exported to ";
|
|
message += detail;
|
|
}
|
|
break;
|
|
case DocumentExportSuccessKind::animation_mp4:
|
|
plan.dialog.title = "Export Animation";
|
|
if (destination == DocumentExportSuccessDestination::path) {
|
|
message = "Animation exported to: ";
|
|
message += detail;
|
|
} else if (destination == DocumentExportSuccessDestination::generic_success) {
|
|
message = "Animation exported successfully.";
|
|
}
|
|
break;
|
|
case DocumentExportSuccessKind::timelapse:
|
|
plan.dialog.title = "Export Timelapse";
|
|
if (destination == DocumentExportSuccessDestination::path) {
|
|
message = "Timelapse exported to: ";
|
|
message += detail;
|
|
} else if (destination == DocumentExportSuccessDestination::generic_success) {
|
|
message = "Timelapse exported successfully.";
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!plan.dialog.title.empty() && !message.empty()) {
|
|
plan.dialog.message = std::move(message);
|
|
plan.show_dialog = true;
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<DocumentExportFileTarget> make_document_export_file_target(
|
|
std::string_view work_directory,
|
|
std::string_view document_name,
|
|
std::string_view extension)
|
|
{
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Result<DocumentExportFileTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
|
}
|
|
|
|
if (extension.empty()) {
|
|
return pp::foundation::Result<DocumentExportFileTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("extension must not be empty"));
|
|
}
|
|
|
|
DocumentExportFileTarget target;
|
|
target.suggested_name.reserve(document_name.size() + extension.size());
|
|
target.suggested_name += document_name;
|
|
target.suggested_name += extension;
|
|
target.path.reserve(work_directory.size() + target.suggested_name.size() + 1);
|
|
target.path += work_directory;
|
|
target.path += "/";
|
|
target.path += target.suggested_name;
|
|
return pp::foundation::Result<DocumentExportFileTarget>::success(std::move(target));
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<DocumentExportCollectionTarget> make_document_export_collection_target(
|
|
std::string_view work_directory,
|
|
std::string_view document_name,
|
|
std::string_view suffix)
|
|
{
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Result<DocumentExportCollectionTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
|
}
|
|
|
|
DocumentExportCollectionTarget target;
|
|
target.directory.reserve(work_directory.size() + document_name.size() + suffix.size() + 1);
|
|
target.directory += work_directory;
|
|
target.directory += "/";
|
|
target.directory += document_name;
|
|
target.directory += suffix;
|
|
target.stem_path.reserve(target.directory.size() + document_name.size() + 1);
|
|
target.stem_path += target.directory;
|
|
target.stem_path += "/";
|
|
target.stem_path += document_name;
|
|
return pp::foundation::Result<DocumentExportCollectionTarget>::success(std::move(target));
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<DocumentExportStemTarget> make_document_export_stem_target(
|
|
std::string_view directory,
|
|
std::string_view document_name)
|
|
{
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Result<DocumentExportStemTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
|
}
|
|
|
|
DocumentExportStemTarget target;
|
|
target.stem_path.reserve(directory.size() + document_name.size() + 1);
|
|
target.stem_path += directory;
|
|
target.stem_path += "/";
|
|
target.stem_path += document_name;
|
|
return pp::foundation::Result<DocumentExportStemTarget>::success(std::move(target));
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::array<std::string_view, pp::document::cube_face_count>
|
|
document_cube_face_export_names() noexcept
|
|
{
|
|
return {
|
|
"front",
|
|
"right",
|
|
"back",
|
|
"left",
|
|
"top",
|
|
"bottom",
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<DocumentCubeFaceExportTarget> make_document_cube_face_export_target(
|
|
std::string_view work_directory,
|
|
std::string_view document_name)
|
|
{
|
|
if (work_directory.empty()) {
|
|
return pp::foundation::Result<DocumentCubeFaceExportTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("work directory must not be empty"));
|
|
}
|
|
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Result<DocumentCubeFaceExportTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
|
}
|
|
|
|
DocumentCubeFaceExportTarget target;
|
|
const auto face_names = document_cube_face_export_names();
|
|
target.face_count = face_names.size();
|
|
for (std::size_t face_index = 0; face_index < face_names.size(); ++face_index) {
|
|
auto& face = target.faces[face_index];
|
|
face.face_name = face_names[face_index];
|
|
face.path.reserve(work_directory.size() + document_name.size() + face_names[face_index].size() + 6);
|
|
face.path += work_directory;
|
|
face.path += "/";
|
|
face.path += document_name;
|
|
face.path += "-";
|
|
face.path += face_names[face_index];
|
|
face.path += ".png";
|
|
}
|
|
|
|
return pp::foundation::Result<DocumentCubeFaceExportTarget>::success(std::move(target));
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<DocumentDepthExportTarget> make_document_depth_export_target(
|
|
std::string_view work_directory,
|
|
std::string_view document_name)
|
|
{
|
|
if (work_directory.empty()) {
|
|
return pp::foundation::Result<DocumentDepthExportTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("work directory must not be empty"));
|
|
}
|
|
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Result<DocumentDepthExportTarget>::failure(
|
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
|
}
|
|
|
|
DocumentDepthExportTarget target;
|
|
target.image_path.reserve(work_directory.size() + document_name.size() + 5U);
|
|
target.image_path += work_directory;
|
|
target.image_path += "/";
|
|
target.image_path += document_name;
|
|
target.image_path += ".png";
|
|
target.depth_path.reserve(work_directory.size() + document_name.size() + 11U);
|
|
target.depth_path += work_directory;
|
|
target.depth_path += "/";
|
|
target.depth_path += document_name;
|
|
target.depth_path += "_depth.png";
|
|
return pp::foundation::Result<DocumentDepthExportTarget>::success(std::move(target));
|
|
}
|
|
|
|
[[nodiscard]] inline std::string document_export_two_digit_index(std::size_t index)
|
|
{
|
|
auto value = std::to_string(index);
|
|
if (value.size() < 2U) {
|
|
value.insert(value.begin(), '0');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
[[nodiscard]] inline std::string make_document_layer_export_path_suffix(
|
|
std::size_t layer_index,
|
|
std::string_view layer_name)
|
|
{
|
|
std::string suffix;
|
|
const auto index = document_export_two_digit_index(layer_index);
|
|
suffix.reserve(10U + index.size() + layer_name.size());
|
|
suffix += "-layer";
|
|
suffix += index;
|
|
suffix += "-";
|
|
suffix += layer_name;
|
|
suffix += ".png";
|
|
return suffix;
|
|
}
|
|
|
|
[[nodiscard]] inline std::string make_document_animation_frame_export_path_suffix(std::size_t frame_index)
|
|
{
|
|
std::string suffix;
|
|
const auto index = document_export_two_digit_index(frame_index);
|
|
suffix.reserve(5U + index.size());
|
|
suffix += "-";
|
|
suffix += index;
|
|
suffix += ".png";
|
|
return suffix;
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Result<DocumentExportSuggestedName> make_document_export_suggested_name(
|
|
std::string_view document_name,
|
|
std::string_view suffix)
|
|
{
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Result<DocumentExportSuggestedName>::failure(
|
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
|
}
|
|
|
|
DocumentExportSuggestedName target;
|
|
target.name.reserve(document_name.size() + suffix.size());
|
|
target.name += document_name;
|
|
target.name += suffix;
|
|
return pp::foundation::Result<DocumentExportSuggestedName>::success(std::move(target));
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_cube_face_export_write(
|
|
const DocumentCubeFaceExportTarget& target,
|
|
std::span<const DocumentCubeFaceExportPayload> face_payloads,
|
|
DocumentCubeFaceExportWriteServices& services)
|
|
{
|
|
if (target.face_count != pp::document::cube_face_count) {
|
|
return pp::foundation::Status::invalid_argument("cube face export target must contain all cube faces");
|
|
}
|
|
|
|
if (face_payloads.size() != target.face_count) {
|
|
return pp::foundation::Status::invalid_argument("cube face export payload count must match target face count");
|
|
}
|
|
|
|
for (std::size_t face_index = 0; face_index < target.face_count; ++face_index) {
|
|
const auto& face = target.faces[face_index];
|
|
if (face.path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("cube face export path must not be empty");
|
|
}
|
|
if (face_payloads[face_index].bytes.empty()) {
|
|
return pp::foundation::Status::invalid_argument("cube face export payload must not be empty");
|
|
}
|
|
|
|
const auto write_status = services.write_binary_file(face.path, face_payloads[face_index].bytes);
|
|
if (!write_status.ok()) {
|
|
return write_status;
|
|
}
|
|
services.publish_exported_image(face.path);
|
|
}
|
|
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_depth_export_write(
|
|
const DocumentDepthExportTarget& target,
|
|
DocumentDepthExportPayload payload,
|
|
DocumentDepthExportWriteServices& services)
|
|
{
|
|
if (target.image_path.empty() || target.depth_path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("depth export target requires image and depth paths");
|
|
}
|
|
if (payload.image_bytes.empty() || payload.depth_bytes.empty()) {
|
|
return pp::foundation::Status::invalid_argument("depth export payload requires image and depth bytes");
|
|
}
|
|
|
|
const auto image_status = services.write_binary_file(target.image_path, payload.image_bytes);
|
|
if (!image_status.ok()) {
|
|
return image_status;
|
|
}
|
|
services.publish_exported_image(target.image_path);
|
|
|
|
const auto depth_status = services.write_binary_file(target.depth_path, payload.depth_bytes);
|
|
if (!depth_status.ok()) {
|
|
return depth_status;
|
|
}
|
|
services.publish_exported_image(target.depth_path);
|
|
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_file_write(
|
|
const DocumentExportFileTarget& target,
|
|
DocumentExportFilePayload payload,
|
|
DocumentExportFileWriteServices& services)
|
|
{
|
|
if (target.path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export file target requires a path");
|
|
}
|
|
if (payload.bytes.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export file payload must not be empty");
|
|
}
|
|
|
|
const auto write_status = services.write_binary_file(target.path, payload.bytes);
|
|
if (!write_status.ok()) {
|
|
return write_status;
|
|
}
|
|
services.publish_exported_image(target.path);
|
|
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_collection_write(
|
|
const DocumentExportCollectionTarget& target,
|
|
std::span<const DocumentExportCollectionPngPayload> payloads,
|
|
DocumentExportCollectionWriteServices& services)
|
|
{
|
|
if (target.stem_path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export collection target requires a stem path");
|
|
}
|
|
|
|
if (payloads.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export collection payloads must not be empty");
|
|
}
|
|
|
|
for (const auto& payload : payloads) {
|
|
if (payload.path_suffix.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export collection payload suffix must not be empty");
|
|
}
|
|
if (payload.bytes.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export collection payload must not be empty");
|
|
}
|
|
|
|
std::string path;
|
|
path.reserve(target.stem_path.size() + payload.path_suffix.size());
|
|
path += target.stem_path;
|
|
path += payload.path_suffix;
|
|
const auto write_status = services.write_binary_file(path, payload.bytes);
|
|
if (!write_status.ok()) {
|
|
return write_status;
|
|
}
|
|
services.publish_exported_image(path);
|
|
}
|
|
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_file(
|
|
const DocumentExportFileTarget& target,
|
|
DocumentExportServices& services)
|
|
{
|
|
if (target.path.empty() || target.suggested_name.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export file target requires a path and suggested name");
|
|
}
|
|
|
|
services.export_equirectangular(target);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_stem(
|
|
DocumentExportCollectionKind kind,
|
|
const DocumentExportStemTarget& target,
|
|
DocumentExportServices& services)
|
|
{
|
|
if (target.stem_path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export stem target requires a stem path");
|
|
}
|
|
|
|
switch (kind) {
|
|
case DocumentExportCollectionKind::layers:
|
|
services.export_layers_to_stem(target);
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportCollectionKind::animation_frames:
|
|
services.export_animation_frames_to_stem(target);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
return pp::foundation::Status::invalid_argument("unknown document export collection kind");
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_collection(
|
|
DocumentExportCollectionKind kind,
|
|
const DocumentExportCollectionTarget& target,
|
|
DocumentExportServices& services)
|
|
{
|
|
if (target.directory.empty() || target.stem_path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("export collection target requires a directory and stem path");
|
|
}
|
|
|
|
if (!services.create_directory(target.directory)) {
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
switch (kind) {
|
|
case DocumentExportCollectionKind::layers:
|
|
services.export_layers_to_collection(target);
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportCollectionKind::animation_frames:
|
|
services.export_animation_frames_to_collection(target);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
return pp::foundation::Status::invalid_argument("unknown document export collection kind");
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_depth(
|
|
std::string_view document_name,
|
|
DocumentExportServices& services)
|
|
{
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Status::invalid_argument("document name must not be empty");
|
|
}
|
|
|
|
services.export_depth(document_name);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_cube_faces(
|
|
std::string_view document_name,
|
|
DocumentExportServices& services)
|
|
{
|
|
if (document_name.empty()) {
|
|
return pp::foundation::Status::invalid_argument("document name must not be empty");
|
|
}
|
|
|
|
services.export_cube_faces(document_name);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_video_export(
|
|
DocumentVideoExportKind kind,
|
|
std::string_view path,
|
|
DocumentVideoExportServices& services)
|
|
{
|
|
if (path.empty()) {
|
|
return pp::foundation::Status::invalid_argument("video export path must not be empty");
|
|
}
|
|
|
|
switch (kind) {
|
|
case DocumentVideoExportKind::animation_mp4:
|
|
services.export_animation_mp4(path);
|
|
services.show_animation_export_success(path);
|
|
return pp::foundation::Status::success();
|
|
case DocumentVideoExportKind::timelapse:
|
|
services.export_timelapse_mp4(path);
|
|
services.show_timelapse_export_success(path);
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
return pp::foundation::Status::invalid_argument("unknown document video export kind");
|
|
}
|
|
|
|
[[nodiscard]] inline pp::foundation::Status execute_document_export_menu_plan(
|
|
const DocumentExportMenuPlan& plan,
|
|
DocumentExportMenuServices& services)
|
|
{
|
|
switch (plan.action) {
|
|
case DocumentExportMenuAction::show_jpeg_dialog:
|
|
services.show_jpeg_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_png_dialog:
|
|
services.show_png_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_layers_dialog:
|
|
services.show_layers_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_cube_faces_dialog:
|
|
services.show_cube_faces_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_depth_dialog:
|
|
services.show_depth_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_animation_frames_dialog:
|
|
services.show_animation_frames_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_animation_mp4_dialog:
|
|
services.show_animation_mp4_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_timelapse_dialog:
|
|
services.show_timelapse_dialog();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::show_license_disabled:
|
|
services.show_license_disabled();
|
|
return pp::foundation::Status::success();
|
|
case DocumentExportMenuAction::unavailable_no_canvas:
|
|
return pp::foundation::Status::success();
|
|
}
|
|
|
|
return pp::foundation::Status::invalid_argument("unknown document export menu action");
|
|
}
|
|
|
|
}
|