24 : yaw(0.0), pitch(0.0), roll(0.0), fov(90.0), in_fov(180.0),
25 projection_mode(0), invert(0), input_model(INPUT_EQUIRECT), interpolation(3)
27 init_effect_details();
32 : yaw(new_yaw), pitch(new_pitch), roll(new_roll), fov(new_fov),
33 in_fov(180.0), projection_mode(0), invert(0),
34 input_model(INPUT_EQUIRECT), interpolation(3)
36 init_effect_details();
39 void SphericalProjection::init_effect_details()
45 "Flatten and reproject 360° or fisheye inputs into a rectilinear view with yaw, pitch, roll, and FOV. Supports Equirect and multiple fisheye lens models.";
51 inline double cubic_interp(
double p0,
double p1,
double p2,
double p3,
54 double a0 = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3;
55 double a1 = p0 - 2.5 * p1 + 2.0 * p2 - 0.5 * p3;
56 double a2 = -0.5 * p0 + 0.5 * p2;
58 return ((a0 * t + a1) * t + a2) * t + a3;
62 std::shared_ptr<openshot::Frame>
64 int64_t frame_number) {
65 auto img = frame->GetImage();
66 if (img->format() != QImage::Format_ARGB32)
67 *img = img->convertToFormat(QImage::Format_ARGB32);
69 int W = img->width(), H = img->height();
70 int bpl = img->bytesPerLine();
71 uchar *src = img->bits();
73 QImage output(W, H, QImage::Format_ARGB32);
74 output.fill(Qt::black);
75 uchar *dst = output.bits();
76 int dst_bpl = output.bytesPerLine();
79 const double DEG = M_PI / 180.0;
92 double sy = sin(yaw_r), cy = cos(yaw_r);
93 double sp = sin(pitch_r),cp = cos(pitch_r);
104 double r21 = cy * sp;
105 double r22 = cy * cp;
108 double roll_sign = (r22 >= 0.0) ? 1.0 : -1.0;
111 double hx = tan(out_fov_r * 0.5);
112 double vy = hx * double(H) / W;
114 auto q = [](
double a) {
return std::llround(a * 1e6); };
115 bool recompute = uv_map.empty() || W != cached_width || H != cached_height ||
116 q(yaw_r) != q(cached_yaw) ||
117 q(pitch_r) != q(cached_pitch) ||
118 q(roll_r) != q(cached_roll) ||
119 q(in_fov_r) != q(cached_in_fov) ||
120 q(out_fov_r) != q(cached_out_fov) ||
126 uv_map.resize(W * H * 2);
128 #pragma omp parallel for schedule(static)
129 for (
int yy = 0; yy < H; yy++) {
130 double ndc_y = (2.0 * (yy + 0.5) / H - 1.0) * vy;
132 for (
int xx = 0; xx < W; xx++) {
133 double uf = -1.0, vf = -1.0;
135 const bool out_is_rect =
140 double cx = (xx + 0.5) - W * 0.5;
141 double cy_dn = (yy + 0.5) - H * 0.5;
142 double R = 0.5 * std::min(W, H);
146 double ry_up = -cy_dn / R;
147 double cR = cos(roll_r), sR = sin(roll_r) * roll_sign;
148 double rxr = cR * rx + sR * ry_up;
149 double ryr = -sR * rx + cR * ry_up;
151 double r_norm = std::sqrt(rxr * rxr + ryr * ryr);
153 double theta_max = out_fov_r * 0.5;
158 theta = r_norm * theta_max;
162 theta = 2.0 * std::asin(std::clamp(r_norm * std::sin(theta_max * 0.5), -1.0, 1.0));
166 theta = 2.0 * std::atan(r_norm * std::tan(theta_max * 0.5));
170 theta = std::asin(std::clamp(r_norm * std::sin(theta_max), -1.0, 1.0));
173 theta = r_norm * theta_max;
178 double phi = std::atan2(ryr, rxr);
181 double vx = std::sin(theta) * std::cos(phi);
182 double vy2= std::sin(theta) * std::sin(phi);
183 double vz = -std::cos(theta);
186 double dx = r00 * vx + r01 * vy2 + r02 * vz;
187 double dy = r10 * vx + r11 * vy2 + r12 * vz;
188 double dz = r20 * vx + r21 * vy2 + r22 * vz;
190 project_input(dx, dy, dz, in_fov_r, W, H, uf, vf);
197 double ndc_x = (2.0 * (xx + 0.5) / W - 1.0) * hx;
201 double sy_up = -ndc_y;
202 double cR = cos(roll_r), sR = sin(roll_r) * roll_sign;
203 double rx = cR * sx + sR * sy_up;
204 double ry = -sR * sx + cR * sy_up;
207 double vx = rx, vy2 = ry, vz = -1.0;
208 double inv_len = 1.0 / std::sqrt(vx*vx + vy2*vy2 + vz*vz);
209 vx *= inv_len; vy2 *= inv_len; vz *= inv_len;
212 double dx = r00 * vx + r01 * vy2 + r02 * vz;
213 double dy = r10 * vx + r11 * vy2 + r12 * vz;
214 double dz = r20 * vx + r21 * vy2 + r22 * vz;
216 project_input(dx, dy, dz, in_fov_r, W, H, uf, vf);
219 int idx = 2 * (yy * W + xx);
220 uv_map[idx] = (float)uf;
221 uv_map[idx + 1] = (float)vf;
228 cached_pitch = pitch_r;
229 cached_roll = roll_r;
230 cached_in_fov = in_fov_r;
231 cached_out_fov= out_fov_r;
244 double ppd_src = W / coverage_r;
245 double ppd_out = W / out_fov_r;
246 double ratio = ppd_out / ppd_src;
253 std::vector<QImage> mipmaps;
255 mipmaps.push_back(*img);
256 for (
int level = 1; level < 4; ++level) {
257 const QImage &prev = mipmaps[level - 1];
258 if (prev.width() <= 1 || prev.height() <= 1)
break;
259 int w = prev.width() / 2, h = prev.height() / 2;
260 QImage next(w, h, QImage::Format_ARGB32);
261 uchar *nb = next.bits();
int nbpl = next.bytesPerLine();
262 const uchar *pb = prev.bits();
int pbpl = prev.bytesPerLine();
263 for (
int y = 0; y < h; y++) {
264 for (
int x = 0; x < w; x++) {
265 for (
int c = 0; c < 4; c++) {
266 int p00 = pb[(2*y) * pbpl + (2*x) * 4 + c];
267 int p10 = pb[(2*y) * pbpl + (2*x+1) * 4 + c];
268 int p01 = pb[(2*y+1) * pbpl + (2*x) * 4 + c];
269 int p11 = pb[(2*y+1) * pbpl + (2*x+1) * 4 + c];
270 nb[y * nbpl + x * 4 + c] = (p00 + p10 + p01 + p11) / 4;
274 mipmaps.push_back(next);
278 #pragma omp parallel for schedule(static)
279 for (
int yy = 0; yy < H; yy++) {
280 uchar *dst_row = dst + yy * dst_bpl;
281 for (
int xx = 0; xx < W; xx++) {
282 int idx = 2 * (yy * W + xx);
283 double uf = uv_map[idx];
284 double vf = uv_map[idx + 1];
285 uchar *d = dst_row + xx * 4;
288 uf = std::fmod(std::fmod(uf, W) + W, W);
289 vf = std::clamp(vf, 0.0, (
double)H - 1);
291 uf = std::clamp(uf, 0.0, (
double)W - 1);
292 vf = std::clamp(vf, 0.0, (
double)H - 1);
293 }
else if (uf < 0 || uf >= W || vf < 0 || vf >= H) {
294 d[0] = d[1] = d[2] = 0; d[3] = 0;
299 int x0 = std::clamp(
int(std::floor(uf)), 0, W - 1);
300 int y0 = std::clamp(
int(std::floor(vf)), 0, H - 1);
301 uchar *s = src + y0 * bpl + x0 * 4;
302 d[0]=s[0]; d[1]=s[1]; d[2]=s[2]; d[3]=s[3];
304 int x0 = std::clamp(
int(std::floor(uf)), 0, W - 1);
305 int y0 = std::clamp(
int(std::floor(vf)), 0, H - 1);
306 int x1 = std::clamp(x0 + 1, 0, W - 1);
307 int y1 = std::clamp(y0 + 1, 0, H - 1);
308 double dxr = uf - x0, dyr = vf - y0;
309 uchar *p00 = src + y0 * bpl + x0 * 4;
310 uchar *p10 = src + y0 * bpl + x1 * 4;
311 uchar *p01 = src + y1 * bpl + x0 * 4;
312 uchar *p11 = src + y1 * bpl + x1 * 4;
313 for (
int c = 0; c < 4; c++) {
314 double v0 = p00[c] * (1 - dxr) + p10[c] * dxr;
315 double v1 = p01[c] * (1 - dxr) + p11[c] * dxr;
316 d[c] = uchar(v0 * (1 - dyr) + v1 * dyr + 0.5);
319 int x1 = std::clamp(
int(std::floor(uf)), 0, W - 1);
320 int y1 = std::clamp(
int(std::floor(vf)), 0, H - 1);
321 double tx = uf - x1, ty = vf - y1;
322 for (
int c = 0; c < 4; c++) {
324 for (
int j = -1; j <= 2; j++) {
325 int y = std::clamp(y1 + j, 0, H - 1);
327 for (
int i = -1; i <= 2; i++) {
328 int x = std::clamp(x1 + i, 0, W - 1);
329 row[i + 1] = src[y * bpl + x * 4 + c];
331 col[j + 1] = cubic_interp(row[0], row[1], row[2], row[3], tx);
333 double val = cubic_interp(col[0], col[1], col[2], col[3], ty);
334 d[c] = uchar(std::clamp(val, 0.0, 255.0) + 0.5);
337 double uf_dx = 0.0, vf_dx = 0.0, uf_dy = 0.0, vf_dy = 0.0;
338 if (xx + 1 < W) { uf_dx = uv_map[idx + 2] - uf; vf_dx = uv_map[idx + 3] - vf; }
339 if (yy + 1 < H) { uf_dy = uv_map[idx + 2 * W] - uf; vf_dy = uv_map[idx + 2 * W + 1] - vf; }
340 double scale_x = std::sqrt(uf_dx*uf_dx + vf_dx*vf_dx);
341 double scale_y = std::sqrt(uf_dy*uf_dy + vf_dy*vf_dy);
342 double scale = std::max(scale_x, scale_y);
345 level = std::min<int>(std::floor(std::log2(scale)), (
int)mipmaps.size() - 1);
346 const QImage &lvl = mipmaps[level];
347 int Wl = lvl.width(), Hl = lvl.height();
348 int bpl_l = lvl.bytesPerLine();
349 const uchar *srcl = lvl.bits();
350 double uf_l = uf / (1 << level);
351 double vf_l = vf / (1 << level);
352 int x0 = std::clamp(
int(std::floor(uf_l)), 0, Wl - 1);
353 int y0 = std::clamp(
int(std::floor(vf_l)), 0, Hl - 1);
354 int x1 = std::clamp(x0 + 1, 0, Wl - 1);
355 int y1 = std::clamp(y0 + 1, 0, Hl - 1);
356 double dxr = uf_l - x0, dyr = vf_l - y0;
357 const uchar *p00 = srcl + y0 * bpl_l + x0 * 4;
358 const uchar *p10 = srcl + y0 * bpl_l + x1 * 4;
359 const uchar *p01 = srcl + y1 * bpl_l + x0 * 4;
360 const uchar *p11 = srcl + y1 * bpl_l + x1 * 4;
361 for (
int c = 0; c < 4; c++) {
362 double v0 = p00[c] * (1 - dxr) + p10[c] * dxr;
363 double v1 = p01[c] * (1 - dxr) + p11[c] * dxr;
364 d[c] = uchar(v0 * (1 - dyr) + v1 * dyr + 0.5);
374 void SphericalProjection::project_input(
double dx,
double dy,
double dz,
375 double in_fov_r,
int W,
int H,
376 double &uf,
double &vf)
const {
379 double lon = std::atan2(dx, -dz);
380 double lat = std::asin(std::clamp(dy, -1.0, 1.0));
383 lon = std::clamp(lon, -M_PI / 2.0, M_PI / 2.0);
387 uf = ((lon + lon_offset) / horiz_span) * W;
390 vf = (M_PI / 2.0 - lat) / M_PI * H;
396 const double ax = 0.0, ay = 0.0;
400 double cos_t = std::clamp(dx * ax + dy * ay + dz * az, -1.0, 1.0);
401 double theta = std::acos(cos_t);
402 double tmax = std::max(1e-6, in_fov_r * 0.5);
407 case INPUT_FEQ_EQUISOLID: r_norm = std::sin(theta*0.5) / std::max(1e-12, std::sin(tmax*0.5));
break;
410 default: r_norm = theta / tmax;
break;
414 double phi = std::atan2(dy, dx);
416 double R = 0.5 * std::min(W, H);
417 double rpx = r_norm * R;
418 uf = W * 0.5 + rpx * std::cos(phi);
419 vf = H * 0.5 - rpx * std::sin(phi);
452 throw InvalidJSON(
"Invalid JSON for SphericalProjection");
466 if (!root[
"projection_mode"].isNull())
469 if (!root[
"invert"].isNull())
470 invert = root[
"invert"].asInt();
472 if (!root[
"input_model"].isNull())
475 if (!root[
"interpolation"].isNull())
530 return root.toStyledString();