Export equirectangular JPEGs through paint renderer

This commit is contained in:
2026-06-05 21:22:06 +02:00
parent 875a0127d9
commit bd416f8473
11 changed files with 604 additions and 96 deletions

View File

@@ -271,6 +271,75 @@ void append_rgba8_bytes(std::vector<std::uint8_t>& bytes, std::span<const pp::pa
}
}
struct EquirectangularProjectionResult {
pp::renderer::Extent2D face_extent {};
pp::renderer::Extent2D equirectangular_extent {};
std::vector<pp::paint::Rgba> pixels;
std::size_t face_payload_count = 0;
std::size_t composited_layer_face_count = 0;
};
pp::foundation::Result<EquirectangularProjectionResult> project_document_frame_equirectangular(
const DocumentFrameCompositeResult& composite)
{
const auto face_pixel_count = expected_pixel_count(composite.extent);
if (!face_pixel_count) {
return pp::foundation::Result<EquirectangularProjectionResult>::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<EquirectangularProjectionResult>::failure(
pp::foundation::Status::invalid_argument("document equirectangular export requires complete cube faces"));
}
}
const auto output_width = static_cast<std::uint64_t>(composite.extent.width) * 4U;
const auto output_height = static_cast<std::uint64_t>(composite.extent.height) * 2U;
if (output_width > std::numeric_limits<std::uint32_t>::max()
|| output_height > std::numeric_limits<std::uint32_t>::max()) {
return pp::foundation::Result<EquirectangularProjectionResult>::failure(
pp::foundation::Status::out_of_range("document equirectangular extent exceeds uint32"));
}
EquirectangularProjectionResult result;
result.face_extent = composite.extent;
result.equirectangular_extent = pp::renderer::Extent2D {
.width = static_cast<std::uint32_t>(output_width),
.height = static_cast<std::uint32_t>(output_height),
};
result.face_payload_count = composite.face_payload_count;
result.composited_layer_face_count = composite.composited_layer_face_count;
const auto output_pixel_count = expected_pixel_count(result.equirectangular_extent);
if (!output_pixel_count) {
return pp::foundation::Result<EquirectangularProjectionResult>::failure(output_pixel_count.status());
}
constexpr auto pi = 3.14159265358979323846F;
constexpr auto two_pi = 6.28318530717958647692F;
result.pixels.assign(output_pixel_count.value(), pp::paint::Rgba {});
for (std::uint32_t y = 0; y < result.equirectangular_extent.height; ++y) {
const auto v = (static_cast<float>(y) + 0.5F) / static_cast<float>(result.equirectangular_extent.height);
const auto angle_y = (1.0F - v) * pi;
const auto sin_y = std::sin(angle_y);
const auto cos_y = std::cos(angle_y);
for (std::uint32_t x = 0; x < result.equirectangular_extent.width; ++x) {
const auto u = (static_cast<float>(x) + 0.5F) / static_cast<float>(result.equirectangular_extent.width);
const auto angle_x = (1.25F - u) * two_pi;
const auto sample = panopainter_cube_face_sample(
sin_y * std::cos(angle_x),
cos_y,
sin_y * std::sin(angle_x));
result.pixels[static_cast<std::size_t>(y) * result.equirectangular_extent.width + x] =
sample_face_nearest(composite.faces[sample.face_index], sample.s, sample.t);
}
}
return pp::foundation::Result<EquirectangularProjectionResult>::success(std::move(result));
}
}
pp::foundation::Status composite_layer(
@@ -602,63 +671,19 @@ pp::foundation::Result<DocumentFrameFacePngExportResult> export_document_frame_f
pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>
export_document_frame_equirectangular_png(const DocumentFrameCompositeResult& composite)
{
const auto face_pixel_count = expected_pixel_count(composite.extent);
if (!face_pixel_count) {
return pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>::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<DocumentFrameEquirectangularPngExportResult>::failure(
pp::foundation::Status::invalid_argument("document equirectangular export requires complete cube faces"));
}
}
const auto output_width = static_cast<std::uint64_t>(composite.extent.width) * 4U;
const auto output_height = static_cast<std::uint64_t>(composite.extent.height) * 2U;
if (output_width > std::numeric_limits<std::uint32_t>::max()
|| output_height > std::numeric_limits<std::uint32_t>::max()) {
return pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>::failure(
pp::foundation::Status::out_of_range("document equirectangular extent exceeds uint32"));
auto projection = project_document_frame_equirectangular(composite);
if (!projection) {
return pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>::failure(projection.status());
}
DocumentFrameEquirectangularPngExportResult result;
result.face_extent = composite.extent;
result.equirectangular_extent = pp::renderer::Extent2D {
.width = static_cast<std::uint32_t>(output_width),
.height = static_cast<std::uint32_t>(output_height),
};
result.face_payload_count = composite.face_payload_count;
result.composited_layer_face_count = composite.composited_layer_face_count;
const auto output_pixel_count = expected_pixel_count(result.equirectangular_extent);
if (!output_pixel_count) {
return pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>::failure(output_pixel_count.status());
}
constexpr auto pi = 3.14159265358979323846F;
constexpr auto two_pi = 6.28318530717958647692F;
std::vector<pp::paint::Rgba> output(output_pixel_count.value());
for (std::uint32_t y = 0; y < result.equirectangular_extent.height; ++y) {
const auto v = (static_cast<float>(y) + 0.5F) / static_cast<float>(result.equirectangular_extent.height);
const auto angle_y = (1.0F - v) * pi;
const auto sin_y = std::sin(angle_y);
const auto cos_y = std::cos(angle_y);
for (std::uint32_t x = 0; x < result.equirectangular_extent.width; ++x) {
const auto u = (static_cast<float>(x) + 0.5F) / static_cast<float>(result.equirectangular_extent.width);
const auto angle_x = (1.25F - u) * two_pi;
const auto sample = panopainter_cube_face_sample(
sin_y * std::cos(angle_x),
cos_y,
sin_y * std::sin(angle_x));
output[static_cast<std::size_t>(y) * result.equirectangular_extent.width + x] =
sample_face_nearest(composite.faces[sample.face_index], sample.s, sample.t);
}
}
result.face_extent = projection.value().face_extent;
result.equirectangular_extent = projection.value().equirectangular_extent;
result.face_payload_count = projection.value().face_payload_count;
result.composited_layer_face_count = projection.value().composited_layer_face_count;
std::vector<std::uint8_t> rgba8;
append_rgba8_bytes(rgba8, output);
append_rgba8_bytes(rgba8, projection.value().pixels);
auto encoded = pp::assets::encode_png_rgba8(
result.equirectangular_extent.width,
result.equirectangular_extent.height,
@@ -683,6 +708,53 @@ export_document_frame_equirectangular_png(DocumentFrameCompositeRequest request)
return export_document_frame_equirectangular_png(composite.value());
}
pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>
export_document_frame_equirectangular_jpeg(const DocumentFrameCompositeResult& composite, int quality)
{
auto projection = project_document_frame_equirectangular(composite);
if (!projection) {
return pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>::failure(projection.status());
}
DocumentFrameEquirectangularJpegExportResult result;
result.face_extent = projection.value().face_extent;
result.equirectangular_extent = projection.value().equirectangular_extent;
result.face_payload_count = projection.value().face_payload_count;
result.composited_layer_face_count = projection.value().composited_layer_face_count;
std::vector<std::uint8_t> rgba8;
append_rgba8_bytes(rgba8, projection.value().pixels);
auto encoded = pp::assets::encode_jpeg_rgba8(
result.equirectangular_extent.width,
result.equirectangular_extent.height,
rgba8,
quality);
if (!encoded) {
return pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>::failure(encoded.status());
}
auto with_xmp = pp::assets::inject_gpano_xmp_into_jpeg(encoded.value());
if (!with_xmp) {
return pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>::failure(with_xmp.status());
}
result.encoded_bytes = static_cast<std::uint64_t>(with_xmp.value().size());
result.jpeg = std::move(with_xmp.value());
result.xmp_injected = true;
return pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>::success(std::move(result));
}
pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>
export_document_frame_equirectangular_jpeg(DocumentFrameCompositeRequest request, int quality)
{
auto composite = composite_document_frame(request);
if (!composite) {
return pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>::failure(composite.status());
}
return export_document_frame_equirectangular_jpeg(composite.value(), quality);
}
pp::foundation::Result<DocumentDepthExportRenderPlan> plan_document_depth_export_render(
DocumentDepthExportRenderPlanRequest request) noexcept
{

View File

@@ -156,6 +156,16 @@ struct DocumentFrameEquirectangularPngExportResult {
std::size_t composited_layer_face_count = 0;
};
struct DocumentFrameEquirectangularJpegExportResult {
pp::renderer::Extent2D face_extent {};
pp::renderer::Extent2D equirectangular_extent {};
std::vector<std::byte> jpeg;
std::uint64_t encoded_bytes = 0;
std::size_t face_payload_count = 0;
std::size_t composited_layer_face_count = 0;
bool xmp_injected = false;
};
struct DocumentDepthExportRenderPlanRequest {
const pp::document::CanvasDocument* document = nullptr;
std::size_t frame_index = 0;
@@ -255,6 +265,16 @@ export_document_frame_equirectangular_png(const DocumentFrameCompositeResult& co
[[nodiscard]] pp::foundation::Result<DocumentFrameEquirectangularPngExportResult>
export_document_frame_equirectangular_png(DocumentFrameCompositeRequest request);
[[nodiscard]] pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>
export_document_frame_equirectangular_jpeg(
const DocumentFrameCompositeResult& composite,
int quality = 100);
[[nodiscard]] pp::foundation::Result<DocumentFrameEquirectangularJpegExportResult>
export_document_frame_equirectangular_jpeg(
DocumentFrameCompositeRequest request,
int quality = 100);
[[nodiscard]] pp::foundation::Result<DocumentDepthExportRenderPlan> plan_document_depth_export_render(
DocumentDepthExportRenderPlanRequest request) noexcept;