From 9d33c45d84cd18c2cb404ff665ba5141e4cd4ae4 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 21 May 2025 01:28:26 -0500 Subject: [PATCH] Adding new spherical projection effect, to flatten out 360/180 videos into the viewport. --- src/CMakeLists.txt | 1 + src/EffectInfo.cpp | 5 + src/effects/SphericalProjection.cpp | 227 ++++++++++++++++++++++++++++ src/effects/SphericalProjection.h | 66 ++++++++ 4 files changed, 299 insertions(+) create mode 100644 src/effects/SphericalProjection.cpp create mode 100644 src/effects/SphericalProjection.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6713d5a9..a35469d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,6 +120,7 @@ set(EFFECTS_SOURCES effects/Pixelate.cpp effects/Saturation.cpp effects/Shift.cpp + effects/SphericalProjection.cpp effects/Wave.cpp audio_effects/STFT.cpp audio_effects/Noise.cpp diff --git a/src/EffectInfo.cpp b/src/EffectInfo.cpp index 94221aed..55016a19 100644 --- a/src/EffectInfo.cpp +++ b/src/EffectInfo.cpp @@ -12,6 +12,7 @@ #include "EffectInfo.h" #include "Effects.h" +#include "effects/SphericalProjection.h" using namespace openshot; @@ -67,6 +68,9 @@ EffectBase* EffectInfo::CreateEffect(std::string effect_type) { else if (effect_type == "Shift") return new Shift(); + else if (effect_type == "SphericalProjection") + return new SphericalProjection(); + else if (effect_type == "Wave") return new Wave(); @@ -135,6 +139,7 @@ Json::Value EffectInfo::JsonValue() { root.append(Pixelate().JsonInfo()); root.append(Saturation().JsonInfo()); root.append(Shift().JsonInfo()); + root.append(SphericalProjection().JsonInfo()); root.append(Wave().JsonInfo()); /* Audio */ root.append(Noise().JsonInfo()); diff --git a/src/effects/SphericalProjection.cpp b/src/effects/SphericalProjection.cpp new file mode 100644 index 00000000..5cf218cb --- /dev/null +++ b/src/effects/SphericalProjection.cpp @@ -0,0 +1,227 @@ +/** + * @file + * @brief Source file for SphericalProjection effect class + * @author Jonathan Thomas + * + * @ref License + */ + +// Copyright (c) 2008-2025 OpenShot Studios, LLC +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "SphericalProjection.h" +#include "Exceptions.h" + +#include +#include + +#ifdef _OPENMP +#include +#endif + +using namespace openshot; + +SphericalProjection::SphericalProjection() + : yaw(0.0), pitch(0.0), roll(0.0), fov(90.0), + projection_mode(0), interpolation(0) +{ + init_effect_details(); +} + +SphericalProjection::SphericalProjection(Keyframe new_yaw, + Keyframe new_pitch, + Keyframe new_roll, + Keyframe new_fov) + : yaw(new_yaw), pitch(new_pitch), roll(new_roll), fov(new_fov), + projection_mode(0), interpolation(0) +{ + init_effect_details(); +} + +void SphericalProjection::init_effect_details() +{ + InitEffectInfo(); + info.class_name = "SphericalProjection"; + info.name = "Spherical Projection"; + info.description = "Flatten 360° video with yaw/pitch/roll, sphere or hemisphere mode, nearest or bilinear"; + info.has_audio = false; + info.has_video = true; + + // Keyframes auto-registered +} + +std::shared_ptr SphericalProjection::GetFrame( + std::shared_ptr frame, + int64_t frame_number) +{ + auto img = frame->GetImage(); + if (img->format() != QImage::Format_ARGB32) + *img = img->convertToFormat(QImage::Format_ARGB32); + + int W = img->width(), H = img->height(); + int bpl = img->bytesPerLine(); + uchar *src = img->bits(); + + QImage output(W, H, QImage::Format_ARGB32); + output.fill(Qt::black); + uchar *dst = output.bits(); + int dst_bpl = output.bytesPerLine(); + + // read keyframes & convert + double yaw_r = yaw.GetValue(frame_number) * M_PI/180.0; + double pitch_r = pitch.GetValue(frame_number) * M_PI/180.0; + double roll_r = roll.GetValue(frame_number) * M_PI/180.0; + double fov_r = fov.GetValue(frame_number) * M_PI/180.0; + + // rotation matrix R = Ry * Rx * Rz + double sy=sin(yaw_r), cy=cos(yaw_r), + sp=sin(pitch_r),cp=cos(pitch_r), + sr=sin(roll_r), cr=cos(roll_r); + double r00=cy*cr+sy*sp*sr, r01=-cy*sr+sy*sp*cr, r02=sy*cp; + double r10=cp*sr, r11=cp*cr, r12=-sp; + double r20=-sy*cr+cy*sp*sr, r21=sy*sr+cy*sp*cr, r22=cy*cp; + + // fov scalars + double hx = tan(fov_r/2.0); + double vy = hx * (double(H)/W); + +#ifdef _OPENMP +#pragma omp parallel for schedule(static) +#endif + for(int yy=0; yy + * + * @ref License + */ + +// Copyright (c) 2008-2025 OpenShot Studios, LLC +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef OPENSHOT_SPHERICAL_PROJECTION_EFFECT_H +#define OPENSHOT_SPHERICAL_PROJECTION_EFFECT_H + +#include "../EffectBase.h" +#include "../Frame.h" +#include "../Json.h" +#include "../KeyFrame.h" + +#include +#include + +namespace openshot +{ + + /** + * @brief Projects a 360° frame through a pinhole camera. + * You can choose full sphere or hemisphere, and nearest-neighbor or bilinear sampling. + */ + class SphericalProjection : public EffectBase + { + private: + void init_effect_details(); + + public: + Keyframe yaw; ///< Yaw around up-axis (degrees) + Keyframe pitch; ///< Pitch around right-axis (degrees) + Keyframe roll; ///< Roll around forward-axis (degrees) + Keyframe fov; ///< Field-of-view (horizontal degrees) + + int projection_mode; ///< 0 = full sphere, 1 = hemisphere + int interpolation; ///< 0 = nearest, 1 = bilinear + + SphericalProjection(); + SphericalProjection(Keyframe new_yaw, + Keyframe new_pitch, + Keyframe new_roll, + Keyframe new_fov); + + std::shared_ptr GetFrame(int64_t frame_number) override + { return GetFrame(std::make_shared(), frame_number); } + + std::shared_ptr GetFrame(std::shared_ptr frame, + int64_t frame_number) override; + + std::string Json() const override; + void SetJson(const std::string value) override; + Json::Value JsonValue() const override; + void SetJsonValue(const Json::Value root) override; + std::string PropertiesJSON(int64_t requested_frame) const override; + }; + +} // namespace openshot + +#endif // OPENSHOT_SPHERICAL_PROJECTION_EFFECT_H