Export equirectangular JPEGs through paint renderer
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user