]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_v360.c
avfilter/vf_v360: improve description of output fov options
[ffmpeg] / libavfilter / vf_v360.c
1 /*
2  * Copyright (c) 2019 Eugene Lyapustin
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 /**
22  * @file
23  * 360 video conversion filter.
24  * Principle of operation:
25  *
26  * (for each pixel in output frame)
27  * 1) Calculate OpenGL-like coordinates (x, y, z) for pixel position (i, j)
28  * 2) Apply 360 operations (rotation, mirror) to (x, y, z)
29  * 3) Calculate pixel position (u, v) in input frame
30  * 4) Calculate interpolation window and weight for each pixel
31  *
32  * (for each frame)
33  * 5) Remap input frame to output frame using precalculated data
34  */
35
36 #include <math.h>
37
38 #include "libavutil/avassert.h"
39 #include "libavutil/imgutils.h"
40 #include "libavutil/pixdesc.h"
41 #include "libavutil/opt.h"
42 #include "avfilter.h"
43 #include "formats.h"
44 #include "internal.h"
45 #include "video.h"
46 #include "v360.h"
47
48 typedef struct ThreadData {
49     AVFrame *in;
50     AVFrame *out;
51 } ThreadData;
52
53 #define OFFSET(x) offsetof(V360Context, x)
54 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
55 #define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
56
57 static const AVOption v360_options[] = {
58     {     "input", "set input projection",              OFFSET(in), AV_OPT_TYPE_INT,    {.i64=EQUIRECTANGULAR}, 0,    NB_PROJECTIONS-1, FLAGS, "in" },
59     {         "e", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "in" },
60     {  "equirect", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "in" },
61     {      "c3x2", "cubemap 3x2",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_3_2},     0,                   0, FLAGS, "in" },
62     {      "c6x1", "cubemap 6x1",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_6_1},     0,                   0, FLAGS, "in" },
63     {       "eac", "equi-angular cubemap",                       0, AV_OPT_TYPE_CONST,  {.i64=EQUIANGULAR},     0,                   0, FLAGS, "in" },
64     {  "dfisheye", "dual fisheye",                               0, AV_OPT_TYPE_CONST,  {.i64=DUAL_FISHEYE},    0,                   0, FLAGS, "in" },
65     {      "flat", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "in" },
66     {"rectilinear", "regular video",                             0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "in" },
67     {  "gnomonic", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "in" },
68     {    "barrel", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "in" },
69     {        "fb", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "in" },
70     {      "c1x6", "cubemap 1x6",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_1_6},     0,                   0, FLAGS, "in" },
71     {        "sg", "stereographic",                              0, AV_OPT_TYPE_CONST,  {.i64=STEREOGRAPHIC},   0,                   0, FLAGS, "in" },
72     {  "mercator", "mercator",                                   0, AV_OPT_TYPE_CONST,  {.i64=MERCATOR},        0,                   0, FLAGS, "in" },
73     {      "ball", "ball",                                       0, AV_OPT_TYPE_CONST,  {.i64=BALL},            0,                   0, FLAGS, "in" },
74     {    "hammer", "hammer",                                     0, AV_OPT_TYPE_CONST,  {.i64=HAMMER},          0,                   0, FLAGS, "in" },
75     {"sinusoidal", "sinusoidal",                                 0, AV_OPT_TYPE_CONST,  {.i64=SINUSOIDAL},      0,                   0, FLAGS, "in" },
76     {   "fisheye", "fisheye",                                    0, AV_OPT_TYPE_CONST,  {.i64=FISHEYE},         0,                   0, FLAGS, "in" },
77     {"cylindrical", "cylindrical",                               0, AV_OPT_TYPE_CONST,  {.i64=CYLINDRICAL},     0,                   0, FLAGS, "in" },
78     {"tetrahedron", "tetrahedron",                               0, AV_OPT_TYPE_CONST,  {.i64=TETRAHEDRON},     0,                   0, FLAGS, "in" },
79     {"barrelsplit", "barrel split facebook's 360 format",        0, AV_OPT_TYPE_CONST,  {.i64=BARREL_SPLIT},    0,                   0, FLAGS, "in" },
80     {       "tsp", "truncated square pyramid",                   0, AV_OPT_TYPE_CONST,  {.i64=TSPYRAMID},       0,                   0, FLAGS, "in" },
81     { "hequirect", "half equirectangular",                       0, AV_OPT_TYPE_CONST,  {.i64=HEQUIRECTANGULAR},0,                   0, FLAGS, "in" },
82     {        "he", "half equirectangular",                       0, AV_OPT_TYPE_CONST,  {.i64=HEQUIRECTANGULAR},0,                   0, FLAGS, "in" },
83     {    "output", "set output projection",            OFFSET(out), AV_OPT_TYPE_INT,    {.i64=CUBEMAP_3_2},     0,    NB_PROJECTIONS-1, FLAGS, "out" },
84     {         "e", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "out" },
85     {  "equirect", "equirectangular",                            0, AV_OPT_TYPE_CONST,  {.i64=EQUIRECTANGULAR}, 0,                   0, FLAGS, "out" },
86     {      "c3x2", "cubemap 3x2",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_3_2},     0,                   0, FLAGS, "out" },
87     {      "c6x1", "cubemap 6x1",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_6_1},     0,                   0, FLAGS, "out" },
88     {       "eac", "equi-angular cubemap",                       0, AV_OPT_TYPE_CONST,  {.i64=EQUIANGULAR},     0,                   0, FLAGS, "out" },
89     {  "dfisheye", "dual fisheye",                               0, AV_OPT_TYPE_CONST,  {.i64=DUAL_FISHEYE},    0,                   0, FLAGS, "out" },
90     {      "flat", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
91     {"rectilinear", "regular video",                             0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
92     {  "gnomonic", "regular video",                              0, AV_OPT_TYPE_CONST,  {.i64=FLAT},            0,                   0, FLAGS, "out" },
93     {    "barrel", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "out" },
94     {        "fb", "barrel facebook's 360 format",               0, AV_OPT_TYPE_CONST,  {.i64=BARREL},          0,                   0, FLAGS, "out" },
95     {      "c1x6", "cubemap 1x6",                                0, AV_OPT_TYPE_CONST,  {.i64=CUBEMAP_1_6},     0,                   0, FLAGS, "out" },
96     {        "sg", "stereographic",                              0, AV_OPT_TYPE_CONST,  {.i64=STEREOGRAPHIC},   0,                   0, FLAGS, "out" },
97     {  "mercator", "mercator",                                   0, AV_OPT_TYPE_CONST,  {.i64=MERCATOR},        0,                   0, FLAGS, "out" },
98     {      "ball", "ball",                                       0, AV_OPT_TYPE_CONST,  {.i64=BALL},            0,                   0, FLAGS, "out" },
99     {    "hammer", "hammer",                                     0, AV_OPT_TYPE_CONST,  {.i64=HAMMER},          0,                   0, FLAGS, "out" },
100     {"sinusoidal", "sinusoidal",                                 0, AV_OPT_TYPE_CONST,  {.i64=SINUSOIDAL},      0,                   0, FLAGS, "out" },
101     {   "fisheye", "fisheye",                                    0, AV_OPT_TYPE_CONST,  {.i64=FISHEYE},         0,                   0, FLAGS, "out" },
102     {   "pannini", "pannini",                                    0, AV_OPT_TYPE_CONST,  {.i64=PANNINI},         0,                   0, FLAGS, "out" },
103     {"cylindrical", "cylindrical",                               0, AV_OPT_TYPE_CONST,  {.i64=CYLINDRICAL},     0,                   0, FLAGS, "out" },
104     {"perspective", "perspective",                               0, AV_OPT_TYPE_CONST,  {.i64=PERSPECTIVE},     0,                   0, FLAGS, "out" },
105     {"tetrahedron", "tetrahedron",                               0, AV_OPT_TYPE_CONST,  {.i64=TETRAHEDRON},     0,                   0, FLAGS, "out" },
106     {"barrelsplit", "barrel split facebook's 360 format",        0, AV_OPT_TYPE_CONST,  {.i64=BARREL_SPLIT},    0,                   0, FLAGS, "out" },
107     {       "tsp", "truncated square pyramid",                   0, AV_OPT_TYPE_CONST,  {.i64=TSPYRAMID},       0,                   0, FLAGS, "out" },
108     { "hequirect", "half equirectangular",                       0, AV_OPT_TYPE_CONST,  {.i64=HEQUIRECTANGULAR},0,                   0, FLAGS, "out" },
109     {        "he", "half equirectangular",                       0, AV_OPT_TYPE_CONST,  {.i64=HEQUIRECTANGULAR},0,                   0, FLAGS, "out" },
110     {    "interp", "set interpolation method",      OFFSET(interp), AV_OPT_TYPE_INT,    {.i64=BILINEAR},        0, NB_INTERP_METHODS-1, FLAGS, "interp" },
111     {      "near", "nearest neighbour",                          0, AV_OPT_TYPE_CONST,  {.i64=NEAREST},         0,                   0, FLAGS, "interp" },
112     {   "nearest", "nearest neighbour",                          0, AV_OPT_TYPE_CONST,  {.i64=NEAREST},         0,                   0, FLAGS, "interp" },
113     {      "line", "bilinear interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=BILINEAR},        0,                   0, FLAGS, "interp" },
114     {    "linear", "bilinear interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=BILINEAR},        0,                   0, FLAGS, "interp" },
115     { "lagrange9", "lagrange9 interpolation",                    0, AV_OPT_TYPE_CONST,  {.i64=LAGRANGE9},       0,                   0, FLAGS, "interp" },
116     {      "cube", "bicubic interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=BICUBIC},         0,                   0, FLAGS, "interp" },
117     {     "cubic", "bicubic interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=BICUBIC},         0,                   0, FLAGS, "interp" },
118     {      "lanc", "lanczos interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=LANCZOS},         0,                   0, FLAGS, "interp" },
119     {   "lanczos", "lanczos interpolation",                      0, AV_OPT_TYPE_CONST,  {.i64=LANCZOS},         0,                   0, FLAGS, "interp" },
120     {      "sp16", "spline16 interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=SPLINE16},        0,                   0, FLAGS, "interp" },
121     {  "spline16", "spline16 interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=SPLINE16},        0,                   0, FLAGS, "interp" },
122     {     "gauss", "gaussian interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=GAUSSIAN},        0,                   0, FLAGS, "interp" },
123     {  "gaussian", "gaussian interpolation",                     0, AV_OPT_TYPE_CONST,  {.i64=GAUSSIAN},        0,                   0, FLAGS, "interp" },
124     {         "w", "output width",                   OFFSET(width), AV_OPT_TYPE_INT,    {.i64=0},               0,           INT16_MAX, FLAGS, "w"},
125     {         "h", "output height",                 OFFSET(height), AV_OPT_TYPE_INT,    {.i64=0},               0,           INT16_MAX, FLAGS, "h"},
126     { "in_stereo", "input stereo format",        OFFSET(in_stereo), AV_OPT_TYPE_INT,    {.i64=STEREO_2D},       0,    NB_STEREO_FMTS-1, FLAGS, "stereo" },
127     {"out_stereo", "output stereo format",      OFFSET(out_stereo), AV_OPT_TYPE_INT,    {.i64=STEREO_2D},       0,    NB_STEREO_FMTS-1, FLAGS, "stereo" },
128     {        "2d", "2d mono",                                    0, AV_OPT_TYPE_CONST,  {.i64=STEREO_2D},       0,                   0, FLAGS, "stereo" },
129     {       "sbs", "side by side",                               0, AV_OPT_TYPE_CONST,  {.i64=STEREO_SBS},      0,                   0, FLAGS, "stereo" },
130     {        "tb", "top bottom",                                 0, AV_OPT_TYPE_CONST,  {.i64=STEREO_TB},       0,                   0, FLAGS, "stereo" },
131     { "in_forder", "input cubemap face order",   OFFSET(in_forder), AV_OPT_TYPE_STRING, {.str="rludfb"},        0,     NB_DIRECTIONS-1, FLAGS, "in_forder"},
132     {"out_forder", "output cubemap face order", OFFSET(out_forder), AV_OPT_TYPE_STRING, {.str="rludfb"},        0,     NB_DIRECTIONS-1, FLAGS, "out_forder"},
133     {   "in_frot", "input cubemap face rotation",  OFFSET(in_frot), AV_OPT_TYPE_STRING, {.str="000000"},        0,     NB_DIRECTIONS-1, FLAGS, "in_frot"},
134     {  "out_frot", "output cubemap face rotation",OFFSET(out_frot), AV_OPT_TYPE_STRING, {.str="000000"},        0,     NB_DIRECTIONS-1, FLAGS, "out_frot"},
135     {    "in_pad", "percent input cubemap pads",    OFFSET(in_pad), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,                 1.f,TFLAGS, "in_pad"},
136     {   "out_pad", "percent output cubemap pads",  OFFSET(out_pad), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,                 1.f,TFLAGS, "out_pad"},
137     {   "fin_pad", "fixed input cubemap pads",     OFFSET(fin_pad), AV_OPT_TYPE_INT,    {.i64=0},               0,                 100,TFLAGS, "fin_pad"},
138     {  "fout_pad", "fixed output cubemap pads",   OFFSET(fout_pad), AV_OPT_TYPE_INT,    {.i64=0},               0,                 100,TFLAGS, "fout_pad"},
139     {       "yaw", "yaw rotation",                     OFFSET(yaw), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f,TFLAGS, "yaw"},
140     {     "pitch", "pitch rotation",                 OFFSET(pitch), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f,TFLAGS, "pitch"},
141     {      "roll", "roll rotation",                   OFFSET(roll), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},        -180.f,               180.f,TFLAGS, "roll"},
142     {    "rorder", "rotation order",                OFFSET(rorder), AV_OPT_TYPE_STRING, {.str="ypr"},           0,                   0,TFLAGS, "rorder"},
143     {     "h_fov", "output horizontal field of view",OFFSET(h_fov), AV_OPT_TYPE_FLOAT,  {.dbl=90.f},     0.00001f,               360.f,TFLAGS, "h_fov"},
144     {     "v_fov", "output vertical field of view",  OFFSET(v_fov), AV_OPT_TYPE_FLOAT,  {.dbl=45.f},     0.00001f,               360.f,TFLAGS, "v_fov"},
145     {     "d_fov", "output diagonal field of view",  OFFSET(d_fov), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,               360.f,TFLAGS, "d_fov"},
146     {    "h_flip", "flip out video horizontally",   OFFSET(h_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1,TFLAGS, "h_flip"},
147     {    "v_flip", "flip out video vertically",     OFFSET(v_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1,TFLAGS, "v_flip"},
148     {    "d_flip", "flip out video indepth",        OFFSET(d_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1,TFLAGS, "d_flip"},
149     {   "ih_flip", "flip in video horizontally",   OFFSET(ih_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1,TFLAGS, "ih_flip"},
150     {   "iv_flip", "flip in video vertically",     OFFSET(iv_flip), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1,TFLAGS, "iv_flip"},
151     {  "in_trans", "transpose video input",   OFFSET(in_transpose), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "in_transpose"},
152     { "out_trans", "transpose video output", OFFSET(out_transpose), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "out_transpose"},
153     {    "ih_fov", "input horizontal field of view",OFFSET(ih_fov), AV_OPT_TYPE_FLOAT,  {.dbl=90.f},     0.00001f,               360.f,TFLAGS, "ih_fov"},
154     {    "iv_fov", "input vertical field of view",  OFFSET(iv_fov), AV_OPT_TYPE_FLOAT,  {.dbl=45.f},     0.00001f,               360.f,TFLAGS, "iv_fov"},
155     {    "id_fov", "input diagonal field of view",  OFFSET(id_fov), AV_OPT_TYPE_FLOAT,  {.dbl=0.f},           0.f,               360.f,TFLAGS, "id_fov"},
156     {"alpha_mask", "build mask in alpha plane",      OFFSET(alpha), AV_OPT_TYPE_BOOL,   {.i64=0},               0,                   1, FLAGS, "alpha"},
157     { NULL }
158 };
159
160 AVFILTER_DEFINE_CLASS(v360);
161
162 static int query_formats(AVFilterContext *ctx)
163 {
164     V360Context *s = ctx->priv;
165     static const enum AVPixelFormat pix_fmts[] = {
166         // YUVA444
167         AV_PIX_FMT_YUVA444P,   AV_PIX_FMT_YUVA444P9,
168         AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12,
169         AV_PIX_FMT_YUVA444P16,
170
171         // YUVA422
172         AV_PIX_FMT_YUVA422P,   AV_PIX_FMT_YUVA422P9,
173         AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12,
174         AV_PIX_FMT_YUVA422P16,
175
176         // YUVA420
177         AV_PIX_FMT_YUVA420P,   AV_PIX_FMT_YUVA420P9,
178         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
179
180         // YUVJ
181         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
182         AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
183         AV_PIX_FMT_YUVJ411P,
184
185         // YUV444
186         AV_PIX_FMT_YUV444P,   AV_PIX_FMT_YUV444P9,
187         AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12,
188         AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV444P16,
189
190         // YUV440
191         AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV440P10,
192         AV_PIX_FMT_YUV440P12,
193
194         // YUV422
195         AV_PIX_FMT_YUV422P,   AV_PIX_FMT_YUV422P9,
196         AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P12,
197         AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV422P16,
198
199         // YUV420
200         AV_PIX_FMT_YUV420P,   AV_PIX_FMT_YUV420P9,
201         AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P12,
202         AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV420P16,
203
204         // YUV411
205         AV_PIX_FMT_YUV411P,
206
207         // YUV410
208         AV_PIX_FMT_YUV410P,
209
210         // GBR
211         AV_PIX_FMT_GBRP,   AV_PIX_FMT_GBRP9,
212         AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12,
213         AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
214
215         // GBRA
216         AV_PIX_FMT_GBRAP,   AV_PIX_FMT_GBRAP10,
217         AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
218
219         // GRAY
220         AV_PIX_FMT_GRAY8,  AV_PIX_FMT_GRAY9,
221         AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12,
222         AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
223
224         AV_PIX_FMT_NONE
225     };
226     static const enum AVPixelFormat alpha_pix_fmts[] = {
227         AV_PIX_FMT_YUVA444P,   AV_PIX_FMT_YUVA444P9,
228         AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12,
229         AV_PIX_FMT_YUVA444P16,
230         AV_PIX_FMT_YUVA422P,   AV_PIX_FMT_YUVA422P9,
231         AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12,
232         AV_PIX_FMT_YUVA422P16,
233         AV_PIX_FMT_YUVA420P,   AV_PIX_FMT_YUVA420P9,
234         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
235         AV_PIX_FMT_GBRAP,   AV_PIX_FMT_GBRAP10,
236         AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
237         AV_PIX_FMT_NONE
238     };
239
240     AVFilterFormats *fmts_list = ff_make_format_list(s->alpha ? alpha_pix_fmts : pix_fmts);
241     if (!fmts_list)
242         return AVERROR(ENOMEM);
243     return ff_set_common_formats(ctx, fmts_list);
244 }
245
246 #define DEFINE_REMAP1_LINE(bits, div)                                                    \
247 static void remap1_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *const src, \
248                                       ptrdiff_t in_linesize,                             \
249                                       const int16_t *const u, const int16_t *const v,    \
250                                       const int16_t *const ker)                          \
251 {                                                                                        \
252     const uint##bits##_t *const s = (const uint##bits##_t *const)src;                    \
253     uint##bits##_t *d = (uint##bits##_t *)dst;                                           \
254                                                                                          \
255     in_linesize /= div;                                                                  \
256                                                                                          \
257     for (int x = 0; x < width; x++)                                                      \
258         d[x] = s[v[x] * in_linesize + u[x]];                                             \
259 }
260
261 DEFINE_REMAP1_LINE( 8, 1)
262 DEFINE_REMAP1_LINE(16, 2)
263
264 /**
265  * Generate remapping function with a given window size and pixel depth.
266  *
267  * @param ws size of interpolation window
268  * @param bits number of bits per pixel
269  */
270 #define DEFINE_REMAP(ws, bits)                                                                             \
271 static int remap##ws##_##bits##bit_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)          \
272 {                                                                                                          \
273     ThreadData *td = arg;                                                                                  \
274     const V360Context *s = ctx->priv;                                                                      \
275     const AVFrame *in = td->in;                                                                            \
276     AVFrame *out = td->out;                                                                                \
277                                                                                                            \
278     for (int stereo = 0; stereo < 1 + s->out_stereo > STEREO_2D; stereo++) {                               \
279         for (int plane = 0; plane < s->nb_planes; plane++) {                                               \
280             const unsigned map = s->map[plane];                                                            \
281             const int in_linesize  = in->linesize[plane];                                                  \
282             const int out_linesize = out->linesize[plane];                                                 \
283             const int uv_linesize = s->uv_linesize[plane];                                                 \
284             const int in_offset_w = stereo ? s->in_offset_w[plane] : 0;                                    \
285             const int in_offset_h = stereo ? s->in_offset_h[plane] : 0;                                    \
286             const int out_offset_w = stereo ? s->out_offset_w[plane] : 0;                                  \
287             const int out_offset_h = stereo ? s->out_offset_h[plane] : 0;                                  \
288             const uint8_t *const src = in->data[plane] +                                                   \
289                                                    in_offset_h * in_linesize + in_offset_w * (bits >> 3);  \
290             uint8_t *dst = out->data[plane] + out_offset_h * out_linesize + out_offset_w * (bits >> 3);    \
291             const uint8_t *mask = plane == 3 ? s->mask : NULL;                                             \
292             const int width = s->pr_width[plane];                                                          \
293             const int height = s->pr_height[plane];                                                        \
294                                                                                                            \
295             const int slice_start = (height *  jobnr     ) / nb_jobs;                                      \
296             const int slice_end   = (height * (jobnr + 1)) / nb_jobs;                                      \
297                                                                                                            \
298             for (int y = slice_start; y < slice_end && !mask; y++) {                                       \
299                 const int16_t *const u = s->u[map] + y * uv_linesize * ws * ws;                            \
300                 const int16_t *const v = s->v[map] + y * uv_linesize * ws * ws;                            \
301                 const int16_t *const ker = s->ker[map] + y * uv_linesize * ws * ws;                        \
302                                                                                                            \
303                 s->remap_line(dst + y * out_linesize, width, src, in_linesize, u, v, ker);                 \
304             }                                                                                              \
305                                                                                                            \
306             for (int y = slice_start; y < slice_end && mask; y++) {                                        \
307                 memcpy(dst + y * out_linesize, mask + y * width * (bits >> 3), width * (bits >> 3));       \
308             }                                                                                              \
309         }                                                                                                  \
310     }                                                                                                      \
311                                                                                                            \
312     return 0;                                                                                              \
313 }
314
315 DEFINE_REMAP(1,  8)
316 DEFINE_REMAP(2,  8)
317 DEFINE_REMAP(3,  8)
318 DEFINE_REMAP(4,  8)
319 DEFINE_REMAP(1, 16)
320 DEFINE_REMAP(2, 16)
321 DEFINE_REMAP(3, 16)
322 DEFINE_REMAP(4, 16)
323
324 #define DEFINE_REMAP_LINE(ws, bits, div)                                                      \
325 static void remap##ws##_##bits##bit_line_c(uint8_t *dst, int width, const uint8_t *const src, \
326                                            ptrdiff_t in_linesize,                             \
327                                            const int16_t *const u, const int16_t *const v,    \
328                                            const int16_t *const ker)                          \
329 {                                                                                             \
330     const uint##bits##_t *const s = (const uint##bits##_t *const)src;                         \
331     uint##bits##_t *d = (uint##bits##_t *)dst;                                                \
332                                                                                               \
333     in_linesize /= div;                                                                       \
334                                                                                               \
335     for (int x = 0; x < width; x++) {                                                         \
336         const int16_t *const uu = u + x * ws * ws;                                            \
337         const int16_t *const vv = v + x * ws * ws;                                            \
338         const int16_t *const kker = ker + x * ws * ws;                                        \
339         int tmp = 0;                                                                          \
340                                                                                               \
341         for (int i = 0; i < ws; i++) {                                                        \
342             for (int j = 0; j < ws; j++) {                                                    \
343                 tmp += kker[i * ws + j] * s[vv[i * ws + j] * in_linesize + uu[i * ws + j]];   \
344             }                                                                                 \
345         }                                                                                     \
346                                                                                               \
347         d[x] = av_clip_uint##bits(tmp >> 14);                                                 \
348     }                                                                                         \
349 }
350
351 DEFINE_REMAP_LINE(2,  8, 1)
352 DEFINE_REMAP_LINE(3,  8, 1)
353 DEFINE_REMAP_LINE(4,  8, 1)
354 DEFINE_REMAP_LINE(2, 16, 2)
355 DEFINE_REMAP_LINE(3, 16, 2)
356 DEFINE_REMAP_LINE(4, 16, 2)
357
358 void ff_v360_init(V360Context *s, int depth)
359 {
360     switch (s->interp) {
361     case NEAREST:
362         s->remap_line = depth <= 8 ? remap1_8bit_line_c : remap1_16bit_line_c;
363         break;
364     case BILINEAR:
365         s->remap_line = depth <= 8 ? remap2_8bit_line_c : remap2_16bit_line_c;
366         break;
367     case LAGRANGE9:
368         s->remap_line = depth <= 8 ? remap3_8bit_line_c : remap3_16bit_line_c;
369         break;
370     case BICUBIC:
371     case LANCZOS:
372     case SPLINE16:
373     case GAUSSIAN:
374         s->remap_line = depth <= 8 ? remap4_8bit_line_c : remap4_16bit_line_c;
375         break;
376     }
377
378     if (ARCH_X86)
379         ff_v360_init_x86(s, depth);
380 }
381
382 /**
383  * Save nearest pixel coordinates for remapping.
384  *
385  * @param du horizontal relative coordinate
386  * @param dv vertical relative coordinate
387  * @param rmap calculated 4x4 window
388  * @param u u remap data
389  * @param v v remap data
390  * @param ker ker remap data
391  */
392 static void nearest_kernel(float du, float dv, const XYRemap *rmap,
393                            int16_t *u, int16_t *v, int16_t *ker)
394 {
395     const int i = lrintf(dv) + 1;
396     const int j = lrintf(du) + 1;
397
398     u[0] = rmap->u[i][j];
399     v[0] = rmap->v[i][j];
400 }
401
402 /**
403  * Calculate kernel for bilinear interpolation.
404  *
405  * @param du horizontal relative coordinate
406  * @param dv vertical relative coordinate
407  * @param rmap calculated 4x4 window
408  * @param u u remap data
409  * @param v v remap data
410  * @param ker ker remap data
411  */
412 static void bilinear_kernel(float du, float dv, const XYRemap *rmap,
413                             int16_t *u, int16_t *v, int16_t *ker)
414 {
415     for (int i = 0; i < 2; i++) {
416         for (int j = 0; j < 2; j++) {
417             u[i * 2 + j] = rmap->u[i + 1][j + 1];
418             v[i * 2 + j] = rmap->v[i + 1][j + 1];
419         }
420     }
421
422     ker[0] = lrintf((1.f - du) * (1.f - dv) * 16385.f);
423     ker[1] = lrintf(       du  * (1.f - dv) * 16385.f);
424     ker[2] = lrintf((1.f - du) *        dv  * 16385.f);
425     ker[3] = lrintf(       du  *        dv  * 16385.f);
426 }
427
428 /**
429  * Calculate 1-dimensional lagrange coefficients.
430  *
431  * @param t relative coordinate
432  * @param coeffs coefficients
433  */
434 static inline void calculate_lagrange_coeffs(float t, float *coeffs)
435 {
436     coeffs[0] = (t - 1.f) * (t - 2.f) * 0.5f;
437     coeffs[1] = -t * (t - 2.f);
438     coeffs[2] =  t * (t - 1.f) * 0.5f;
439 }
440
441 /**
442  * Calculate kernel for lagrange interpolation.
443  *
444  * @param du horizontal relative coordinate
445  * @param dv vertical relative coordinate
446  * @param rmap calculated 4x4 window
447  * @param u u remap data
448  * @param v v remap data
449  * @param ker ker remap data
450  */
451 static void lagrange_kernel(float du, float dv, const XYRemap *rmap,
452                             int16_t *u, int16_t *v, int16_t *ker)
453 {
454     float du_coeffs[3];
455     float dv_coeffs[3];
456
457     calculate_lagrange_coeffs(du, du_coeffs);
458     calculate_lagrange_coeffs(dv, dv_coeffs);
459
460     for (int i = 0; i < 3; i++) {
461         for (int j = 0; j < 3; j++) {
462             u[i * 3 + j] = rmap->u[i + 1][j + 1];
463             v[i * 3 + j] = rmap->v[i + 1][j + 1];
464             ker[i * 3 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
465         }
466     }
467 }
468
469 /**
470  * Calculate 1-dimensional cubic coefficients.
471  *
472  * @param t relative coordinate
473  * @param coeffs coefficients
474  */
475 static inline void calculate_bicubic_coeffs(float t, float *coeffs)
476 {
477     const float tt  = t * t;
478     const float ttt = t * t * t;
479
480     coeffs[0] =     - t / 3.f + tt / 2.f - ttt / 6.f;
481     coeffs[1] = 1.f - t / 2.f - tt       + ttt / 2.f;
482     coeffs[2] =       t       + tt / 2.f - ttt / 2.f;
483     coeffs[3] =     - t / 6.f            + ttt / 6.f;
484 }
485
486 /**
487  * Calculate kernel for bicubic interpolation.
488  *
489  * @param du horizontal relative coordinate
490  * @param dv vertical relative coordinate
491  * @param rmap calculated 4x4 window
492  * @param u u remap data
493  * @param v v remap data
494  * @param ker ker remap data
495  */
496 static void bicubic_kernel(float du, float dv, const XYRemap *rmap,
497                            int16_t *u, int16_t *v, int16_t *ker)
498 {
499     float du_coeffs[4];
500     float dv_coeffs[4];
501
502     calculate_bicubic_coeffs(du, du_coeffs);
503     calculate_bicubic_coeffs(dv, dv_coeffs);
504
505     for (int i = 0; i < 4; i++) {
506         for (int j = 0; j < 4; j++) {
507             u[i * 4 + j] = rmap->u[i][j];
508             v[i * 4 + j] = rmap->v[i][j];
509             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
510         }
511     }
512 }
513
514 /**
515  * Calculate 1-dimensional lanczos coefficients.
516  *
517  * @param t relative coordinate
518  * @param coeffs coefficients
519  */
520 static inline void calculate_lanczos_coeffs(float t, float *coeffs)
521 {
522     float sum = 0.f;
523
524     for (int i = 0; i < 4; i++) {
525         const float x = M_PI * (t - i + 1);
526         if (x == 0.f) {
527             coeffs[i] = 1.f;
528         } else {
529             coeffs[i] = sinf(x) * sinf(x / 2.f) / (x * x / 2.f);
530         }
531         sum += coeffs[i];
532     }
533
534     for (int i = 0; i < 4; i++) {
535         coeffs[i] /= sum;
536     }
537 }
538
539 /**
540  * Calculate kernel for lanczos interpolation.
541  *
542  * @param du horizontal relative coordinate
543  * @param dv vertical relative coordinate
544  * @param rmap calculated 4x4 window
545  * @param u u remap data
546  * @param v v remap data
547  * @param ker ker remap data
548  */
549 static void lanczos_kernel(float du, float dv, const XYRemap *rmap,
550                            int16_t *u, int16_t *v, int16_t *ker)
551 {
552     float du_coeffs[4];
553     float dv_coeffs[4];
554
555     calculate_lanczos_coeffs(du, du_coeffs);
556     calculate_lanczos_coeffs(dv, dv_coeffs);
557
558     for (int i = 0; i < 4; i++) {
559         for (int j = 0; j < 4; j++) {
560             u[i * 4 + j] = rmap->u[i][j];
561             v[i * 4 + j] = rmap->v[i][j];
562             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
563         }
564     }
565 }
566
567 /**
568  * Calculate 1-dimensional spline16 coefficients.
569  *
570  * @param t relative coordinate
571  * @param coeffs coefficients
572  */
573 static void calculate_spline16_coeffs(float t, float *coeffs)
574 {
575     coeffs[0] = ((-1.f / 3.f * t + 0.8f) * t - 7.f / 15.f) * t;
576     coeffs[1] = ((t - 9.f / 5.f) * t - 0.2f) * t + 1.f;
577     coeffs[2] = ((6.f / 5.f - t) * t + 0.8f) * t;
578     coeffs[3] = ((1.f / 3.f * t - 0.2f) * t - 2.f / 15.f) * t;
579 }
580
581 /**
582  * Calculate kernel for spline16 interpolation.
583  *
584  * @param du horizontal relative coordinate
585  * @param dv vertical relative coordinate
586  * @param rmap calculated 4x4 window
587  * @param u u remap data
588  * @param v v remap data
589  * @param ker ker remap data
590  */
591 static void spline16_kernel(float du, float dv, const XYRemap *rmap,
592                             int16_t *u, int16_t *v, int16_t *ker)
593 {
594     float du_coeffs[4];
595     float dv_coeffs[4];
596
597     calculate_spline16_coeffs(du, du_coeffs);
598     calculate_spline16_coeffs(dv, dv_coeffs);
599
600     for (int i = 0; i < 4; i++) {
601         for (int j = 0; j < 4; j++) {
602             u[i * 4 + j] = rmap->u[i][j];
603             v[i * 4 + j] = rmap->v[i][j];
604             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
605         }
606     }
607 }
608
609 /**
610  * Calculate 1-dimensional gaussian coefficients.
611  *
612  * @param t relative coordinate
613  * @param coeffs coefficients
614  */
615 static void calculate_gaussian_coeffs(float t, float *coeffs)
616 {
617     float sum = 0.f;
618
619     for (int i = 0; i < 4; i++) {
620         const float x = t - (i - 1);
621         if (x == 0.f) {
622             coeffs[i] = 1.f;
623         } else {
624             coeffs[i] = expf(-2.f * x * x) * expf(-x * x / 2.f);
625         }
626         sum += coeffs[i];
627     }
628
629     for (int i = 0; i < 4; i++) {
630         coeffs[i] /= sum;
631     }
632 }
633
634 /**
635  * Calculate kernel for gaussian interpolation.
636  *
637  * @param du horizontal relative coordinate
638  * @param dv vertical relative coordinate
639  * @param rmap calculated 4x4 window
640  * @param u u remap data
641  * @param v v remap data
642  * @param ker ker remap data
643  */
644 static void gaussian_kernel(float du, float dv, const XYRemap *rmap,
645                             int16_t *u, int16_t *v, int16_t *ker)
646 {
647     float du_coeffs[4];
648     float dv_coeffs[4];
649
650     calculate_gaussian_coeffs(du, du_coeffs);
651     calculate_gaussian_coeffs(dv, dv_coeffs);
652
653     for (int i = 0; i < 4; i++) {
654         for (int j = 0; j < 4; j++) {
655             u[i * 4 + j] = rmap->u[i][j];
656             v[i * 4 + j] = rmap->v[i][j];
657             ker[i * 4 + j] = lrintf(du_coeffs[j] * dv_coeffs[i] * 16385.f);
658         }
659     }
660 }
661
662 /**
663  * Modulo operation with only positive remainders.
664  *
665  * @param a dividend
666  * @param b divisor
667  *
668  * @return positive remainder of (a / b)
669  */
670 static inline int mod(int a, int b)
671 {
672     const int res = a % b;
673     if (res < 0) {
674         return res + b;
675     } else {
676         return res;
677     }
678 }
679
680 /**
681  * Reflect y operation.
682  *
683  * @param y input vertical position
684  * @param h input height
685  */
686 static inline int reflecty(int y, int h)
687 {
688     if (y < 0) {
689         return -y;
690     } else if (y >= h) {
691         return 2 * h - 1 - y;
692     }
693
694     return y;
695 }
696
697 /**
698  * Reflect x operation for equirect.
699  *
700  * @param x input horizontal position
701  * @param y input vertical position
702  * @param w input width
703  * @param h input height
704  */
705 static inline int ereflectx(int x, int y, int w, int h)
706 {
707     if (y < 0 || y >= h)
708         x += w / 2;
709
710     return mod(x, w);
711 }
712
713 /**
714  * Reflect x operation.
715  *
716  * @param x input horizontal position
717  * @param y input vertical position
718  * @param w input width
719  * @param h input height
720  */
721 static inline int reflectx(int x, int y, int w, int h)
722 {
723     if (y < 0 || y >= h)
724         return w - 1 - x;
725
726     return mod(x, w);
727 }
728
729 /**
730  * Convert char to corresponding direction.
731  * Used for cubemap options.
732  */
733 static int get_direction(char c)
734 {
735     switch (c) {
736     case 'r':
737         return RIGHT;
738     case 'l':
739         return LEFT;
740     case 'u':
741         return UP;
742     case 'd':
743         return DOWN;
744     case 'f':
745         return FRONT;
746     case 'b':
747         return BACK;
748     default:
749         return -1;
750     }
751 }
752
753 /**
754  * Convert char to corresponding rotation angle.
755  * Used for cubemap options.
756  */
757 static int get_rotation(char c)
758 {
759     switch (c) {
760     case '0':
761         return ROT_0;
762     case '1':
763         return ROT_90;
764     case '2':
765         return ROT_180;
766     case '3':
767         return ROT_270;
768     default:
769         return -1;
770     }
771 }
772
773 /**
774  * Convert char to corresponding rotation order.
775  */
776 static int get_rorder(char c)
777 {
778     switch (c) {
779     case 'Y':
780     case 'y':
781         return YAW;
782     case 'P':
783     case 'p':
784         return PITCH;
785     case 'R':
786     case 'r':
787         return ROLL;
788     default:
789         return -1;
790     }
791 }
792
793 /**
794  * Prepare data for processing cubemap input format.
795  *
796  * @param ctx filter context
797  *
798  * @return error code
799  */
800 static int prepare_cube_in(AVFilterContext *ctx)
801 {
802     V360Context *s = ctx->priv;
803
804     for (int face = 0; face < NB_FACES; face++) {
805         const char c = s->in_forder[face];
806         int direction;
807
808         if (c == '\0') {
809             av_log(ctx, AV_LOG_ERROR,
810                    "Incomplete in_forder option. Direction for all 6 faces should be specified.\n");
811             return AVERROR(EINVAL);
812         }
813
814         direction = get_direction(c);
815         if (direction == -1) {
816             av_log(ctx, AV_LOG_ERROR,
817                    "Incorrect direction symbol '%c' in in_forder option.\n", c);
818             return AVERROR(EINVAL);
819         }
820
821         s->in_cubemap_face_order[direction] = face;
822     }
823
824     for (int face = 0; face < NB_FACES; face++) {
825         const char c = s->in_frot[face];
826         int rotation;
827
828         if (c == '\0') {
829             av_log(ctx, AV_LOG_ERROR,
830                    "Incomplete in_frot option. Rotation for all 6 faces should be specified.\n");
831             return AVERROR(EINVAL);
832         }
833
834         rotation = get_rotation(c);
835         if (rotation == -1) {
836             av_log(ctx, AV_LOG_ERROR,
837                    "Incorrect rotation symbol '%c' in in_frot option.\n", c);
838             return AVERROR(EINVAL);
839         }
840
841         s->in_cubemap_face_rotation[face] = rotation;
842     }
843
844     return 0;
845 }
846
847 /**
848  * Prepare data for processing cubemap output format.
849  *
850  * @param ctx filter context
851  *
852  * @return error code
853  */
854 static int prepare_cube_out(AVFilterContext *ctx)
855 {
856     V360Context *s = ctx->priv;
857
858     for (int face = 0; face < NB_FACES; face++) {
859         const char c = s->out_forder[face];
860         int direction;
861
862         if (c == '\0') {
863             av_log(ctx, AV_LOG_ERROR,
864                    "Incomplete out_forder option. Direction for all 6 faces should be specified.\n");
865             return AVERROR(EINVAL);
866         }
867
868         direction = get_direction(c);
869         if (direction == -1) {
870             av_log(ctx, AV_LOG_ERROR,
871                    "Incorrect direction symbol '%c' in out_forder option.\n", c);
872             return AVERROR(EINVAL);
873         }
874
875         s->out_cubemap_direction_order[face] = direction;
876     }
877
878     for (int face = 0; face < NB_FACES; face++) {
879         const char c = s->out_frot[face];
880         int rotation;
881
882         if (c == '\0') {
883             av_log(ctx, AV_LOG_ERROR,
884                    "Incomplete out_frot option. Rotation for all 6 faces should be specified.\n");
885             return AVERROR(EINVAL);
886         }
887
888         rotation = get_rotation(c);
889         if (rotation == -1) {
890             av_log(ctx, AV_LOG_ERROR,
891                    "Incorrect rotation symbol '%c' in out_frot option.\n", c);
892             return AVERROR(EINVAL);
893         }
894
895         s->out_cubemap_face_rotation[face] = rotation;
896     }
897
898     return 0;
899 }
900
901 static inline void rotate_cube_face(float *uf, float *vf, int rotation)
902 {
903     float tmp;
904
905     switch (rotation) {
906     case ROT_0:
907         break;
908     case ROT_90:
909         tmp =  *uf;
910         *uf = -*vf;
911         *vf =  tmp;
912         break;
913     case ROT_180:
914         *uf = -*uf;
915         *vf = -*vf;
916         break;
917     case ROT_270:
918         tmp = -*uf;
919         *uf =  *vf;
920         *vf =  tmp;
921         break;
922     default:
923         av_assert0(0);
924     }
925 }
926
927 static inline void rotate_cube_face_inverse(float *uf, float *vf, int rotation)
928 {
929     float tmp;
930
931     switch (rotation) {
932     case ROT_0:
933         break;
934     case ROT_90:
935         tmp = -*uf;
936         *uf =  *vf;
937         *vf =  tmp;
938         break;
939     case ROT_180:
940         *uf = -*uf;
941         *vf = -*vf;
942         break;
943     case ROT_270:
944         tmp =  *uf;
945         *uf = -*vf;
946         *vf =  tmp;
947         break;
948     default:
949         av_assert0(0);
950     }
951 }
952
953 /**
954  * Normalize vector.
955  *
956  * @param vec vector
957  */
958 static void normalize_vector(float *vec)
959 {
960     const float norm = sqrtf(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
961
962     vec[0] /= norm;
963     vec[1] /= norm;
964     vec[2] /= norm;
965 }
966
967 /**
968  * Calculate 3D coordinates on sphere for corresponding cubemap position.
969  * Common operation for every cubemap.
970  *
971  * @param s filter private context
972  * @param uf horizontal cubemap coordinate [0, 1)
973  * @param vf vertical cubemap coordinate [0, 1)
974  * @param face face of cubemap
975  * @param vec coordinates on sphere
976  * @param scalew scale for uf
977  * @param scaleh scale for vf
978  */
979 static void cube_to_xyz(const V360Context *s,
980                         float uf, float vf, int face,
981                         float *vec, float scalew, float scaleh)
982 {
983     const int direction = s->out_cubemap_direction_order[face];
984     float l_x, l_y, l_z;
985
986     uf /= scalew;
987     vf /= scaleh;
988
989     rotate_cube_face_inverse(&uf, &vf, s->out_cubemap_face_rotation[face]);
990
991     switch (direction) {
992     case RIGHT:
993         l_x =  1.f;
994         l_y =  vf;
995         l_z = -uf;
996         break;
997     case LEFT:
998         l_x = -1.f;
999         l_y =  vf;
1000         l_z =  uf;
1001         break;
1002     case UP:
1003         l_x =  uf;
1004         l_y = -1.f;
1005         l_z =  vf;
1006         break;
1007     case DOWN:
1008         l_x =  uf;
1009         l_y =  1.f;
1010         l_z = -vf;
1011         break;
1012     case FRONT:
1013         l_x =  uf;
1014         l_y =  vf;
1015         l_z =  1.f;
1016         break;
1017     case BACK:
1018         l_x = -uf;
1019         l_y =  vf;
1020         l_z = -1.f;
1021         break;
1022     default:
1023         av_assert0(0);
1024     }
1025
1026     vec[0] = l_x;
1027     vec[1] = l_y;
1028     vec[2] = l_z;
1029
1030     normalize_vector(vec);
1031 }
1032
1033 /**
1034  * Calculate cubemap position for corresponding 3D coordinates on sphere.
1035  * Common operation for every cubemap.
1036  *
1037  * @param s filter private context
1038  * @param vec coordinated on sphere
1039  * @param uf horizontal cubemap coordinate [0, 1)
1040  * @param vf vertical cubemap coordinate [0, 1)
1041  * @param direction direction of view
1042  */
1043 static void xyz_to_cube(const V360Context *s,
1044                         const float *vec,
1045                         float *uf, float *vf, int *direction)
1046 {
1047     const float phi   = atan2f(vec[0], vec[2]);
1048     const float theta = asinf(vec[1]);
1049     float phi_norm, theta_threshold;
1050     int face;
1051
1052     if (phi >= -M_PI_4 && phi < M_PI_4) {
1053         *direction = FRONT;
1054         phi_norm = phi;
1055     } else if (phi >= -(M_PI_2 + M_PI_4) && phi < -M_PI_4) {
1056         *direction = LEFT;
1057         phi_norm = phi + M_PI_2;
1058     } else if (phi >= M_PI_4 && phi < M_PI_2 + M_PI_4) {
1059         *direction = RIGHT;
1060         phi_norm = phi - M_PI_2;
1061     } else {
1062         *direction = BACK;
1063         phi_norm = phi + ((phi > 0.f) ? -M_PI : M_PI);
1064     }
1065
1066     theta_threshold = atanf(cosf(phi_norm));
1067     if (theta > theta_threshold) {
1068         *direction = DOWN;
1069     } else if (theta < -theta_threshold) {
1070         *direction = UP;
1071     }
1072
1073     switch (*direction) {
1074     case RIGHT:
1075         *uf = -vec[2] / vec[0];
1076         *vf =  vec[1] / vec[0];
1077         break;
1078     case LEFT:
1079         *uf = -vec[2] / vec[0];
1080         *vf = -vec[1] / vec[0];
1081         break;
1082     case UP:
1083         *uf = -vec[0] / vec[1];
1084         *vf = -vec[2] / vec[1];
1085         break;
1086     case DOWN:
1087         *uf =  vec[0] / vec[1];
1088         *vf = -vec[2] / vec[1];
1089         break;
1090     case FRONT:
1091         *uf =  vec[0] / vec[2];
1092         *vf =  vec[1] / vec[2];
1093         break;
1094     case BACK:
1095         *uf =  vec[0] / vec[2];
1096         *vf = -vec[1] / vec[2];
1097         break;
1098     default:
1099         av_assert0(0);
1100     }
1101
1102     face = s->in_cubemap_face_order[*direction];
1103     rotate_cube_face(uf, vf, s->in_cubemap_face_rotation[face]);
1104
1105     (*uf) *= s->input_mirror_modifier[0];
1106     (*vf) *= s->input_mirror_modifier[1];
1107 }
1108
1109 /**
1110  * Find position on another cube face in case of overflow/underflow.
1111  * Used for calculation of interpolation window.
1112  *
1113  * @param s filter private context
1114  * @param uf horizontal cubemap coordinate
1115  * @param vf vertical cubemap coordinate
1116  * @param direction direction of view
1117  * @param new_uf new horizontal cubemap coordinate
1118  * @param new_vf new vertical cubemap coordinate
1119  * @param face face position on cubemap
1120  */
1121 static void process_cube_coordinates(const V360Context *s,
1122                                      float uf, float vf, int direction,
1123                                      float *new_uf, float *new_vf, int *face)
1124 {
1125     /*
1126      *  Cubemap orientation
1127      *
1128      *           width
1129      *         <------->
1130      *         +-------+
1131      *         |       |                              U
1132      *         | up    |                   h       ------->
1133      * +-------+-------+-------+-------+ ^ e      |
1134      * |       |       |       |       | | i    V |
1135      * | left  | front | right | back  | | g      |
1136      * +-------+-------+-------+-------+ v h      v
1137      *         |       |                   t
1138      *         | down  |
1139      *         +-------+
1140      */
1141
1142     *face = s->in_cubemap_face_order[direction];
1143     rotate_cube_face_inverse(&uf, &vf, s->in_cubemap_face_rotation[*face]);
1144
1145     if ((uf < -1.f || uf >= 1.f) && (vf < -1.f || vf >= 1.f)) {
1146         // There are no pixels to use in this case
1147         *new_uf = uf;
1148         *new_vf = vf;
1149     } else if (uf < -1.f) {
1150         uf += 2.f;
1151         switch (direction) {
1152         case RIGHT:
1153             direction = FRONT;
1154             *new_uf =  uf;
1155             *new_vf =  vf;
1156             break;
1157         case LEFT:
1158             direction = BACK;
1159             *new_uf =  uf;
1160             *new_vf =  vf;
1161             break;
1162         case UP:
1163             direction = LEFT;
1164             *new_uf =  vf;
1165             *new_vf = -uf;
1166             break;
1167         case DOWN:
1168             direction = LEFT;
1169             *new_uf = -vf;
1170             *new_vf =  uf;
1171             break;
1172         case FRONT:
1173             direction = LEFT;
1174             *new_uf =  uf;
1175             *new_vf =  vf;
1176             break;
1177         case BACK:
1178             direction = RIGHT;
1179             *new_uf =  uf;
1180             *new_vf =  vf;
1181             break;
1182         default:
1183             av_assert0(0);
1184         }
1185     } else if (uf >= 1.f) {
1186         uf -= 2.f;
1187         switch (direction) {
1188         case RIGHT:
1189             direction = BACK;
1190             *new_uf =  uf;
1191             *new_vf =  vf;
1192             break;
1193         case LEFT:
1194             direction = FRONT;
1195             *new_uf =  uf;
1196             *new_vf =  vf;
1197             break;
1198         case UP:
1199             direction = RIGHT;
1200             *new_uf = -vf;
1201             *new_vf =  uf;
1202             break;
1203         case DOWN:
1204             direction = RIGHT;
1205             *new_uf =  vf;
1206             *new_vf = -uf;
1207             break;
1208         case FRONT:
1209             direction = RIGHT;
1210             *new_uf =  uf;
1211             *new_vf =  vf;
1212             break;
1213         case BACK:
1214             direction = LEFT;
1215             *new_uf =  uf;
1216             *new_vf =  vf;
1217             break;
1218         default:
1219             av_assert0(0);
1220         }
1221     } else if (vf < -1.f) {
1222         vf += 2.f;
1223         switch (direction) {
1224         case RIGHT:
1225             direction = UP;
1226             *new_uf =  vf;
1227             *new_vf = -uf;
1228             break;
1229         case LEFT:
1230             direction = UP;
1231             *new_uf = -vf;
1232             *new_vf =  uf;
1233             break;
1234         case UP:
1235             direction = BACK;
1236             *new_uf = -uf;
1237             *new_vf = -vf;
1238             break;
1239         case DOWN:
1240             direction = FRONT;
1241             *new_uf =  uf;
1242             *new_vf =  vf;
1243             break;
1244         case FRONT:
1245             direction = UP;
1246             *new_uf =  uf;
1247             *new_vf =  vf;
1248             break;
1249         case BACK:
1250             direction = UP;
1251             *new_uf = -uf;
1252             *new_vf = -vf;
1253             break;
1254         default:
1255             av_assert0(0);
1256         }
1257     } else if (vf >= 1.f) {
1258         vf -= 2.f;
1259         switch (direction) {
1260         case RIGHT:
1261             direction = DOWN;
1262             *new_uf = -vf;
1263             *new_vf =  uf;
1264             break;
1265         case LEFT:
1266             direction = DOWN;
1267             *new_uf =  vf;
1268             *new_vf = -uf;
1269             break;
1270         case UP:
1271             direction = FRONT;
1272             *new_uf =  uf;
1273             *new_vf =  vf;
1274             break;
1275         case DOWN:
1276             direction = BACK;
1277             *new_uf = -uf;
1278             *new_vf = -vf;
1279             break;
1280         case FRONT:
1281             direction = DOWN;
1282             *new_uf =  uf;
1283             *new_vf =  vf;
1284             break;
1285         case BACK:
1286             direction = DOWN;
1287             *new_uf = -uf;
1288             *new_vf = -vf;
1289             break;
1290         default:
1291             av_assert0(0);
1292         }
1293     } else {
1294         // Inside cube face
1295         *new_uf = uf;
1296         *new_vf = vf;
1297     }
1298
1299     *face = s->in_cubemap_face_order[direction];
1300     rotate_cube_face(new_uf, new_vf, s->in_cubemap_face_rotation[*face]);
1301 }
1302
1303 /**
1304  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap3x2 format.
1305  *
1306  * @param s filter private context
1307  * @param i horizontal position on frame [0, width)
1308  * @param j vertical position on frame [0, height)
1309  * @param width frame width
1310  * @param height frame height
1311  * @param vec coordinates on sphere
1312  */
1313 static int cube3x2_to_xyz(const V360Context *s,
1314                           int i, int j, int width, int height,
1315                           float *vec)
1316 {
1317     const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (width  / 3.f) : 1.f - s->out_pad;
1318     const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (height / 2.f) : 1.f - s->out_pad;
1319
1320     const float ew = width  / 3.f;
1321     const float eh = height / 2.f;
1322
1323     const int u_face = floorf(i / ew);
1324     const int v_face = floorf(j / eh);
1325     const int face = u_face + 3 * v_face;
1326
1327     const int u_shift = ceilf(ew * u_face);
1328     const int v_shift = ceilf(eh * v_face);
1329     const int ewi = ceilf(ew * (u_face + 1)) - u_shift;
1330     const int ehi = ceilf(eh * (v_face + 1)) - v_shift;
1331
1332     const float uf = 2.f * (i - u_shift + 0.5f) / ewi - 1.f;
1333     const float vf = 2.f * (j - v_shift + 0.5f) / ehi - 1.f;
1334
1335     cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1336
1337     return 1;
1338 }
1339
1340 /**
1341  * Calculate frame position in cubemap3x2 format for corresponding 3D coordinates on sphere.
1342  *
1343  * @param s filter private context
1344  * @param vec coordinates on sphere
1345  * @param width frame width
1346  * @param height frame height
1347  * @param us horizontal coordinates for interpolation window
1348  * @param vs vertical coordinates for interpolation window
1349  * @param du horizontal relative coordinate
1350  * @param dv vertical relative coordinate
1351  */
1352 static int xyz_to_cube3x2(const V360Context *s,
1353                           const float *vec, int width, int height,
1354                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1355 {
1356     const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (width  / 3.f) : 1.f - s->in_pad;
1357     const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (height / 2.f) : 1.f - s->in_pad;
1358     const float ew = width  / 3.f;
1359     const float eh = height / 2.f;
1360     float uf, vf;
1361     int ui, vi;
1362     int ewi, ehi;
1363     int direction, face;
1364     int u_face, v_face;
1365
1366     xyz_to_cube(s, vec, &uf, &vf, &direction);
1367
1368     uf *= scalew;
1369     vf *= scaleh;
1370
1371     face = s->in_cubemap_face_order[direction];
1372     u_face = face % 3;
1373     v_face = face / 3;
1374     ewi = ceilf(ew * (u_face + 1)) - ceilf(ew * u_face);
1375     ehi = ceilf(eh * (v_face + 1)) - ceilf(eh * v_face);
1376
1377     uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1378     vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1379
1380     ui = floorf(uf);
1381     vi = floorf(vf);
1382
1383     *du = uf - ui;
1384     *dv = vf - vi;
1385
1386     for (int i = 0; i < 4; i++) {
1387         for (int j = 0; j < 4; j++) {
1388             int new_ui = ui + j - 1;
1389             int new_vi = vi + i - 1;
1390             int u_shift, v_shift;
1391             int new_ewi, new_ehi;
1392
1393             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1394                 face = s->in_cubemap_face_order[direction];
1395
1396                 u_face = face % 3;
1397                 v_face = face / 3;
1398                 u_shift = ceilf(ew * u_face);
1399                 v_shift = ceilf(eh * v_face);
1400             } else {
1401                 uf = 2.f * new_ui / ewi - 1.f;
1402                 vf = 2.f * new_vi / ehi - 1.f;
1403
1404                 uf /= scalew;
1405                 vf /= scaleh;
1406
1407                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1408
1409                 uf *= scalew;
1410                 vf *= scaleh;
1411
1412                 u_face = face % 3;
1413                 v_face = face / 3;
1414                 u_shift = ceilf(ew * u_face);
1415                 v_shift = ceilf(eh * v_face);
1416                 new_ewi = ceilf(ew * (u_face + 1)) - u_shift;
1417                 new_ehi = ceilf(eh * (v_face + 1)) - v_shift;
1418
1419                 new_ui = av_clip(lrintf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1420                 new_vi = av_clip(lrintf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1421             }
1422
1423             us[i][j] = u_shift + new_ui;
1424             vs[i][j] = v_shift + new_vi;
1425         }
1426     }
1427
1428     return 1;
1429 }
1430
1431 /**
1432  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap1x6 format.
1433  *
1434  * @param s filter private context
1435  * @param i horizontal position on frame [0, width)
1436  * @param j vertical position on frame [0, height)
1437  * @param width frame width
1438  * @param height frame height
1439  * @param vec coordinates on sphere
1440  */
1441 static int cube1x6_to_xyz(const V360Context *s,
1442                           int i, int j, int width, int height,
1443                           float *vec)
1444 {
1445     const float scalew = s->fout_pad > 0 ? 1.f - (float)(s->fout_pad) / width : 1.f - s->out_pad;
1446     const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (height / 6.f) : 1.f - s->out_pad;
1447
1448     const float ew = width;
1449     const float eh = height / 6.f;
1450
1451     const int face = floorf(j / eh);
1452
1453     const int v_shift = ceilf(eh * face);
1454     const int ehi = ceilf(eh * (face + 1)) - v_shift;
1455
1456     const float uf = 2.f * (i           + 0.5f) / ew  - 1.f;
1457     const float vf = 2.f * (j - v_shift + 0.5f) / ehi - 1.f;
1458
1459     cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1460
1461     return 1;
1462 }
1463
1464 /**
1465  * Calculate 3D coordinates on sphere for corresponding frame position in cubemap6x1 format.
1466  *
1467  * @param s filter private context
1468  * @param i horizontal position on frame [0, width)
1469  * @param j vertical position on frame [0, height)
1470  * @param width frame width
1471  * @param height frame height
1472  * @param vec coordinates on sphere
1473  */
1474 static int cube6x1_to_xyz(const V360Context *s,
1475                           int i, int j, int width, int height,
1476                           float *vec)
1477 {
1478     const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (width / 6.f)   : 1.f - s->out_pad;
1479     const float scaleh = s->fout_pad > 0 ? 1.f - (float)(s->fout_pad) / height : 1.f - s->out_pad;
1480
1481     const float ew = width / 6.f;
1482     const float eh = height;
1483
1484     const int face = floorf(i / ew);
1485
1486     const int u_shift = ceilf(ew * face);
1487     const int ewi = ceilf(ew * (face + 1)) - u_shift;
1488
1489     const float uf = 2.f * (i - u_shift + 0.5f) / ewi - 1.f;
1490     const float vf = 2.f * (j           + 0.5f) / eh  - 1.f;
1491
1492     cube_to_xyz(s, uf, vf, face, vec, scalew, scaleh);
1493
1494     return 1;
1495 }
1496
1497 /**
1498  * Calculate frame position in cubemap1x6 format for corresponding 3D coordinates on sphere.
1499  *
1500  * @param s filter private context
1501  * @param vec coordinates on sphere
1502  * @param width frame width
1503  * @param height frame height
1504  * @param us horizontal coordinates for interpolation window
1505  * @param vs vertical coordinates for interpolation window
1506  * @param du horizontal relative coordinate
1507  * @param dv vertical relative coordinate
1508  */
1509 static int xyz_to_cube1x6(const V360Context *s,
1510                           const float *vec, int width, int height,
1511                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1512 {
1513     const float scalew = s->fin_pad > 0 ? 1.f - (float)(s->fin_pad) / width : 1.f - s->in_pad;
1514     const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (height / 6.f) : 1.f - s->in_pad;
1515     const float eh = height / 6.f;
1516     const int ewi = width;
1517     float uf, vf;
1518     int ui, vi;
1519     int ehi;
1520     int direction, face;
1521
1522     xyz_to_cube(s, vec, &uf, &vf, &direction);
1523
1524     uf *= scalew;
1525     vf *= scaleh;
1526
1527     face = s->in_cubemap_face_order[direction];
1528     ehi = ceilf(eh * (face + 1)) - ceilf(eh * face);
1529
1530     uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1531     vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1532
1533     ui = floorf(uf);
1534     vi = floorf(vf);
1535
1536     *du = uf - ui;
1537     *dv = vf - vi;
1538
1539     for (int i = 0; i < 4; i++) {
1540         for (int j = 0; j < 4; j++) {
1541             int new_ui = ui + j - 1;
1542             int new_vi = vi + i - 1;
1543             int v_shift;
1544             int new_ehi;
1545
1546             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1547                 face = s->in_cubemap_face_order[direction];
1548
1549                 v_shift = ceilf(eh * face);
1550             } else {
1551                 uf = 2.f * new_ui / ewi - 1.f;
1552                 vf = 2.f * new_vi / ehi - 1.f;
1553
1554                 uf /= scalew;
1555                 vf /= scaleh;
1556
1557                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1558
1559                 uf *= scalew;
1560                 vf *= scaleh;
1561
1562                 v_shift = ceilf(eh * face);
1563                 new_ehi = ceilf(eh * (face + 1)) - v_shift;
1564
1565                 new_ui = av_clip(lrintf(0.5f *     ewi * (uf + 1.f)), 0,     ewi - 1);
1566                 new_vi = av_clip(lrintf(0.5f * new_ehi * (vf + 1.f)), 0, new_ehi - 1);
1567             }
1568
1569             us[i][j] =           new_ui;
1570             vs[i][j] = v_shift + new_vi;
1571         }
1572     }
1573
1574     return 1;
1575 }
1576
1577 /**
1578  * Calculate frame position in cubemap6x1 format for corresponding 3D coordinates on sphere.
1579  *
1580  * @param s filter private context
1581  * @param vec coordinates on sphere
1582  * @param width frame width
1583  * @param height frame height
1584  * @param us horizontal coordinates for interpolation window
1585  * @param vs vertical coordinates for interpolation window
1586  * @param du horizontal relative coordinate
1587  * @param dv vertical relative coordinate
1588  */
1589 static int xyz_to_cube6x1(const V360Context *s,
1590                           const float *vec, int width, int height,
1591                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1592 {
1593     const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (width / 6.f)   : 1.f - s->in_pad;
1594     const float scaleh = s->fin_pad > 0 ? 1.f - (float)(s->fin_pad) / height : 1.f - s->in_pad;
1595     const float ew = width / 6.f;
1596     const int ehi = height;
1597     float uf, vf;
1598     int ui, vi;
1599     int ewi;
1600     int direction, face;
1601
1602     xyz_to_cube(s, vec, &uf, &vf, &direction);
1603
1604     uf *= scalew;
1605     vf *= scaleh;
1606
1607     face = s->in_cubemap_face_order[direction];
1608     ewi = ceilf(ew * (face + 1)) - ceilf(ew * face);
1609
1610     uf = 0.5f * ewi * (uf + 1.f) - 0.5f;
1611     vf = 0.5f * ehi * (vf + 1.f) - 0.5f;
1612
1613     ui = floorf(uf);
1614     vi = floorf(vf);
1615
1616     *du = uf - ui;
1617     *dv = vf - vi;
1618
1619     for (int i = 0; i < 4; i++) {
1620         for (int j = 0; j < 4; j++) {
1621             int new_ui = ui + j - 1;
1622             int new_vi = vi + i - 1;
1623             int u_shift;
1624             int new_ewi;
1625
1626             if (new_ui >= 0 && new_ui < ewi && new_vi >= 0 && new_vi < ehi) {
1627                 face = s->in_cubemap_face_order[direction];
1628
1629                 u_shift = ceilf(ew * face);
1630             } else {
1631                 uf = 2.f * new_ui / ewi - 1.f;
1632                 vf = 2.f * new_vi / ehi - 1.f;
1633
1634                 uf /= scalew;
1635                 vf /= scaleh;
1636
1637                 process_cube_coordinates(s, uf, vf, direction, &uf, &vf, &face);
1638
1639                 uf *= scalew;
1640                 vf *= scaleh;
1641
1642                 u_shift = ceilf(ew * face);
1643                 new_ewi = ceilf(ew * (face + 1)) - u_shift;
1644
1645                 new_ui = av_clip(lrintf(0.5f * new_ewi * (uf + 1.f)), 0, new_ewi - 1);
1646                 new_vi = av_clip(lrintf(0.5f *     ehi * (vf + 1.f)), 0,     ehi - 1);
1647             }
1648
1649             us[i][j] = u_shift + new_ui;
1650             vs[i][j] =           new_vi;
1651         }
1652     }
1653
1654     return 1;
1655 }
1656
1657 /**
1658  * Calculate 3D coordinates on sphere for corresponding frame position in equirectangular format.
1659  *
1660  * @param s filter private context
1661  * @param i horizontal position on frame [0, width)
1662  * @param j vertical position on frame [0, height)
1663  * @param width frame width
1664  * @param height frame height
1665  * @param vec coordinates on sphere
1666  */
1667 static int equirect_to_xyz(const V360Context *s,
1668                            int i, int j, int width, int height,
1669                            float *vec)
1670 {
1671     const float phi   = ((2.f * i + 0.5f) / width  - 1.f) * M_PI;
1672     const float theta = ((2.f * j + 0.5f) / height - 1.f) * M_PI_2;
1673
1674     const float sin_phi   = sinf(phi);
1675     const float cos_phi   = cosf(phi);
1676     const float sin_theta = sinf(theta);
1677     const float cos_theta = cosf(theta);
1678
1679     vec[0] = cos_theta * sin_phi;
1680     vec[1] = sin_theta;
1681     vec[2] = cos_theta * cos_phi;
1682
1683     return 1;
1684 }
1685
1686 /**
1687  * Calculate 3D coordinates on sphere for corresponding frame position in half equirectangular format.
1688  *
1689  * @param s filter private context
1690  * @param i horizontal position on frame [0, width)
1691  * @param j vertical position on frame [0, height)
1692  * @param width frame width
1693  * @param height frame height
1694  * @param vec coordinates on sphere
1695  */
1696 static int hequirect_to_xyz(const V360Context *s,
1697                             int i, int j, int width, int height,
1698                             float *vec)
1699 {
1700     const float phi   = ((2.f * i + 0.5f) / width  - 1.f) * M_PI_2;
1701     const float theta = ((2.f * j + 0.5f) / height - 1.f) * M_PI_2;
1702
1703     const float sin_phi   = sinf(phi);
1704     const float cos_phi   = cosf(phi);
1705     const float sin_theta = sinf(theta);
1706     const float cos_theta = cosf(theta);
1707
1708     vec[0] = cos_theta * sin_phi;
1709     vec[1] = sin_theta;
1710     vec[2] = cos_theta * cos_phi;
1711
1712     return 1;
1713 }
1714
1715 /**
1716  * Prepare data for processing stereographic output format.
1717  *
1718  * @param ctx filter context
1719  *
1720  * @return error code
1721  */
1722 static int prepare_stereographic_out(AVFilterContext *ctx)
1723 {
1724     V360Context *s = ctx->priv;
1725
1726     s->flat_range[0] = tanf(FFMIN(s->h_fov, 359.f) * M_PI / 720.f);
1727     s->flat_range[1] = tanf(FFMIN(s->v_fov, 359.f) * M_PI / 720.f);
1728
1729     return 0;
1730 }
1731
1732 /**
1733  * Calculate 3D coordinates on sphere for corresponding frame position in stereographic format.
1734  *
1735  * @param s filter private context
1736  * @param i horizontal position on frame [0, width)
1737  * @param j vertical position on frame [0, height)
1738  * @param width frame width
1739  * @param height frame height
1740  * @param vec coordinates on sphere
1741  */
1742 static int stereographic_to_xyz(const V360Context *s,
1743                                 int i, int j, int width, int height,
1744                                 float *vec)
1745 {
1746     const float x = ((2.f * i + 1.f) / width  - 1.f) * s->flat_range[0];
1747     const float y = ((2.f * j + 1.f) / height - 1.f) * s->flat_range[1];
1748     const float r = hypotf(x, y);
1749     const float theta = atanf(r) * 2.f;
1750     const float sin_theta = sinf(theta);
1751
1752     vec[0] = x / r * sin_theta;
1753     vec[1] = y / r * sin_theta;
1754     vec[2] = cosf(theta);
1755
1756     normalize_vector(vec);
1757
1758     return 1;
1759 }
1760
1761 /**
1762  * Prepare data for processing stereographic input format.
1763  *
1764  * @param ctx filter context
1765  *
1766  * @return error code
1767  */
1768 static int prepare_stereographic_in(AVFilterContext *ctx)
1769 {
1770     V360Context *s = ctx->priv;
1771
1772     s->iflat_range[0] = tanf(FFMIN(s->ih_fov, 359.f) * M_PI / 720.f);
1773     s->iflat_range[1] = tanf(FFMIN(s->iv_fov, 359.f) * M_PI / 720.f);
1774
1775     return 0;
1776 }
1777
1778 /**
1779  * Calculate frame position in stereographic format for corresponding 3D coordinates on sphere.
1780  *
1781  * @param s filter private context
1782  * @param vec coordinates on sphere
1783  * @param width frame width
1784  * @param height frame height
1785  * @param us horizontal coordinates for interpolation window
1786  * @param vs vertical coordinates for interpolation window
1787  * @param du horizontal relative coordinate
1788  * @param dv vertical relative coordinate
1789  */
1790 static int xyz_to_stereographic(const V360Context *s,
1791                                 const float *vec, int width, int height,
1792                                 int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1793 {
1794     const float theta = acosf(vec[2]);
1795     const float r = tanf(theta * 0.5f);
1796     const float c = r / hypotf(vec[0], vec[1]);
1797     const float x = vec[0] * c / s->iflat_range[0] * s->input_mirror_modifier[0];
1798     const float y = vec[1] * c / s->iflat_range[1] * s->input_mirror_modifier[1];
1799
1800     const float uf = (x + 1.f) * width  / 2.f;
1801     const float vf = (y + 1.f) * height / 2.f;
1802
1803     const int ui = floorf(uf);
1804     const int vi = floorf(vf);
1805
1806     const int visible = isfinite(x) && isfinite(y) && vi >= 0 && vi < height && ui >= 0 && ui < width;
1807
1808     *du = visible ? uf - ui : 0.f;
1809     *dv = visible ? vf - vi : 0.f;
1810
1811     for (int i = 0; i < 4; i++) {
1812         for (int j = 0; j < 4; j++) {
1813             us[i][j] = visible ? av_clip(ui + j - 1, 0, width  - 1) : 0;
1814             vs[i][j] = visible ? av_clip(vi + i - 1, 0, height - 1) : 0;
1815         }
1816     }
1817
1818     return visible;
1819 }
1820
1821 /**
1822  * Calculate frame position in equirectangular format for corresponding 3D coordinates on sphere.
1823  *
1824  * @param s filter private context
1825  * @param vec coordinates on sphere
1826  * @param width frame width
1827  * @param height frame height
1828  * @param us horizontal coordinates for interpolation window
1829  * @param vs vertical coordinates for interpolation window
1830  * @param du horizontal relative coordinate
1831  * @param dv vertical relative coordinate
1832  */
1833 static int xyz_to_equirect(const V360Context *s,
1834                            const float *vec, int width, int height,
1835                            int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1836 {
1837     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0];
1838     const float theta = asinf(vec[1]) * s->input_mirror_modifier[1];
1839
1840     const float uf = (phi   / M_PI   + 1.f) * width  / 2.f;
1841     const float vf = (theta / M_PI_2 + 1.f) * height / 2.f;
1842
1843     const int ui = floorf(uf);
1844     const int vi = floorf(vf);
1845
1846     *du = uf - ui;
1847     *dv = vf - vi;
1848
1849     for (int i = 0; i < 4; i++) {
1850         for (int j = 0; j < 4; j++) {
1851             us[i][j] = ereflectx(ui + j - 1, vi + i - 1, width, height);
1852             vs[i][j] = reflecty(vi + i - 1, height);
1853         }
1854     }
1855
1856     return 1;
1857 }
1858
1859 /**
1860  * Calculate frame position in half equirectangular format for corresponding 3D coordinates on sphere.
1861  *
1862  * @param s filter private context
1863  * @param vec coordinates on sphere
1864  * @param width frame width
1865  * @param height frame height
1866  * @param us horizontal coordinates for interpolation window
1867  * @param vs vertical coordinates for interpolation window
1868  * @param du horizontal relative coordinate
1869  * @param dv vertical relative coordinate
1870  */
1871 static int xyz_to_hequirect(const V360Context *s,
1872                             const float *vec, int width, int height,
1873                             int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1874 {
1875     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0];
1876     const float theta = asinf(vec[1]) * s->input_mirror_modifier[1];
1877
1878     const float uf = (phi   / M_PI_2 + 1.f) * width  / 2.f;
1879     const float vf = (theta / M_PI_2 + 1.f) * height / 2.f;
1880
1881     const int ui = floorf(uf);
1882     const int vi = floorf(vf);
1883
1884     const int visible = phi >= -M_PI_2 && phi <= M_PI_2;
1885
1886     *du = uf - ui;
1887     *dv = vf - vi;
1888
1889     for (int i = 0; i < 4; i++) {
1890         for (int j = 0; j < 4; j++) {
1891             us[i][j] = av_clip(ui + j - 1, 0, width  - 1);
1892             vs[i][j] = av_clip(vi + i - 1, 0, height - 1);
1893         }
1894     }
1895
1896     return visible;
1897 }
1898
1899 /**
1900  * Prepare data for processing flat input format.
1901  *
1902  * @param ctx filter context
1903  *
1904  * @return error code
1905  */
1906 static int prepare_flat_in(AVFilterContext *ctx)
1907 {
1908     V360Context *s = ctx->priv;
1909
1910     s->iflat_range[0] = tanf(0.5f * s->ih_fov * M_PI / 180.f);
1911     s->iflat_range[1] = tanf(0.5f * s->iv_fov * M_PI / 180.f);
1912
1913     return 0;
1914 }
1915
1916 /**
1917  * Calculate frame position in flat format for corresponding 3D coordinates on sphere.
1918  *
1919  * @param s filter private context
1920  * @param vec coordinates on sphere
1921  * @param width frame width
1922  * @param height frame height
1923  * @param us horizontal coordinates for interpolation window
1924  * @param vs vertical coordinates for interpolation window
1925  * @param du horizontal relative coordinate
1926  * @param dv vertical relative coordinate
1927  */
1928 static int xyz_to_flat(const V360Context *s,
1929                        const float *vec, int width, int height,
1930                        int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1931 {
1932     const float theta = acosf(vec[2]);
1933     const float r = tanf(theta);
1934     const float rr = fabsf(r) < 1e+6f ? r : hypotf(width, height);
1935     const float zf = vec[2];
1936     const float h = hypotf(vec[0], vec[1]);
1937     const float c = h <= 1e-6f ? 1.f : rr / h;
1938     float uf = vec[0] * c / s->iflat_range[0] * s->input_mirror_modifier[0];
1939     float vf = vec[1] * c / s->iflat_range[1] * s->input_mirror_modifier[1];
1940     int visible, ui, vi;
1941
1942     uf = zf >= 0.f ? (uf + 1.f) * width  / 2.f : 0.f;
1943     vf = zf >= 0.f ? (vf + 1.f) * height / 2.f : 0.f;
1944
1945     ui = floorf(uf);
1946     vi = floorf(vf);
1947
1948     visible = vi >= 0 && vi < height && ui >= 0 && ui < width && zf >= 0.f;
1949
1950     *du = uf - ui;
1951     *dv = vf - vi;
1952
1953     for (int i = 0; i < 4; i++) {
1954         for (int j = 0; j < 4; j++) {
1955             us[i][j] = visible ? av_clip(ui + j - 1, 0, width  - 1) : 0;
1956             vs[i][j] = visible ? av_clip(vi + i - 1, 0, height - 1) : 0;
1957         }
1958     }
1959
1960     return visible;
1961 }
1962
1963 /**
1964  * Calculate frame position in mercator format for corresponding 3D coordinates on sphere.
1965  *
1966  * @param s filter private context
1967  * @param vec coordinates on sphere
1968  * @param width frame width
1969  * @param height frame height
1970  * @param us horizontal coordinates for interpolation window
1971  * @param vs vertical coordinates for interpolation window
1972  * @param du horizontal relative coordinate
1973  * @param dv vertical relative coordinate
1974  */
1975 static int xyz_to_mercator(const V360Context *s,
1976                            const float *vec, int width, int height,
1977                            int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
1978 {
1979     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0];
1980     const float theta = vec[1] * s->input_mirror_modifier[1];
1981
1982     const float uf = (phi / M_PI + 1.f) * width / 2.f;
1983     const float vf = (av_clipf(logf((1.f + theta) / (1.f - theta)) / (2.f * M_PI), -1.f, 1.f) + 1.f) * height / 2.f;
1984
1985     const int ui = floorf(uf);
1986     const int vi = floorf(vf);
1987
1988     *du = uf - ui;
1989     *dv = vf - vi;
1990
1991     for (int i = 0; i < 4; i++) {
1992         for (int j = 0; j < 4; j++) {
1993             us[i][j] = av_clip(ui + j - 1, 0, width  - 1);
1994             vs[i][j] = av_clip(vi + i - 1, 0, height - 1);
1995         }
1996     }
1997
1998     return 1;
1999 }
2000
2001 /**
2002  * Calculate 3D coordinates on sphere for corresponding frame position in mercator format.
2003  *
2004  * @param s filter private context
2005  * @param i horizontal position on frame [0, width)
2006  * @param j vertical position on frame [0, height)
2007  * @param width frame width
2008  * @param height frame height
2009  * @param vec coordinates on sphere
2010  */
2011 static int mercator_to_xyz(const V360Context *s,
2012                            int i, int j, int width, int height,
2013                            float *vec)
2014 {
2015     const float phi = ((2.f * i + 1.f) / width  - 1.f) * M_PI + M_PI_2;
2016     const float y   = ((2.f * j + 1.f) / height - 1.f) * M_PI;
2017     const float div = expf(2.f * y) + 1.f;
2018
2019     const float sin_phi   = sinf(phi);
2020     const float cos_phi   = cosf(phi);
2021     const float sin_theta = 2.f * expf(y) / div;
2022     const float cos_theta = (expf(2.f * y) - 1.f) / div;
2023
2024     vec[0] = -sin_theta * cos_phi;
2025     vec[1] =  cos_theta;
2026     vec[2] =  sin_theta * sin_phi;
2027
2028     return 1;
2029 }
2030
2031 /**
2032  * Calculate frame position in ball format for corresponding 3D coordinates on sphere.
2033  *
2034  * @param s filter private context
2035  * @param vec coordinates on sphere
2036  * @param width frame width
2037  * @param height frame height
2038  * @param us horizontal coordinates for interpolation window
2039  * @param vs vertical coordinates for interpolation window
2040  * @param du horizontal relative coordinate
2041  * @param dv vertical relative coordinate
2042  */
2043 static int xyz_to_ball(const V360Context *s,
2044                        const float *vec, int width, int height,
2045                        int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2046 {
2047     const float l = hypotf(vec[0], vec[1]);
2048     const float r = sqrtf(1.f - vec[2]) / M_SQRT2;
2049
2050     const float uf = (1.f + r * vec[0] * s->input_mirror_modifier[0] / (l > 0.f ? l : 1.f)) * width  * 0.5f;
2051     const float vf = (1.f + r * vec[1] * s->input_mirror_modifier[1] / (l > 0.f ? l : 1.f)) * height * 0.5f;
2052
2053     const int ui = floorf(uf);
2054     const int vi = floorf(vf);
2055
2056     *du = uf - ui;
2057     *dv = vf - vi;
2058
2059     for (int i = 0; i < 4; i++) {
2060         for (int j = 0; j < 4; j++) {
2061             us[i][j] = av_clip(ui + j - 1, 0, width  - 1);
2062             vs[i][j] = av_clip(vi + i - 1, 0, height - 1);
2063         }
2064     }
2065
2066     return 1;
2067 }
2068
2069 /**
2070  * Calculate 3D coordinates on sphere for corresponding frame position in ball format.
2071  *
2072  * @param s filter private context
2073  * @param i horizontal position on frame [0, width)
2074  * @param j vertical position on frame [0, height)
2075  * @param width frame width
2076  * @param height frame height
2077  * @param vec coordinates on sphere
2078  */
2079 static int ball_to_xyz(const V360Context *s,
2080                        int i, int j, int width, int height,
2081                        float *vec)
2082 {
2083     const float x = (2.f * i + 1.f) / width  - 1.f;
2084     const float y = (2.f * j + 1.f) / height - 1.f;
2085     const float l = hypotf(x, y);
2086
2087     if (l <= 1.f) {
2088         const float z = 2.f * l * sqrtf(1.f - l * l);
2089
2090         vec[0] = z * x / (l > 0.f ? l : 1.f);
2091         vec[1] = z * y / (l > 0.f ? l : 1.f);
2092         vec[2] = 1.f - 2.f * l * l;
2093     } else {
2094         vec[0] = 0.f;
2095         vec[1] = 1.f;
2096         vec[2] = 0.f;
2097         return 0;
2098     }
2099
2100     return 1;
2101 }
2102
2103 /**
2104  * Calculate 3D coordinates on sphere for corresponding frame position in hammer format.
2105  *
2106  * @param s filter private context
2107  * @param i horizontal position on frame [0, width)
2108  * @param j vertical position on frame [0, height)
2109  * @param width frame width
2110  * @param height frame height
2111  * @param vec coordinates on sphere
2112  */
2113 static int hammer_to_xyz(const V360Context *s,
2114                          int i, int j, int width, int height,
2115                          float *vec)
2116 {
2117     const float x = ((2.f * i + 1.f) / width  - 1.f);
2118     const float y = ((2.f * j + 1.f) / height - 1.f);
2119
2120     const float xx = x * x;
2121     const float yy = y * y;
2122
2123     const float z = sqrtf(1.f - xx * 0.5f - yy * 0.5f);
2124
2125     const float a = M_SQRT2 * x * z;
2126     const float b = 2.f * z * z - 1.f;
2127
2128     const float aa = a * a;
2129     const float bb = b * b;
2130
2131     const float w = sqrtf(1.f - 2.f * yy * z * z);
2132
2133     vec[0] = w * 2.f * a * b / (aa + bb);
2134     vec[1] = M_SQRT2 * y * z;
2135     vec[2] = w * (bb  - aa) / (aa + bb);
2136
2137     normalize_vector(vec);
2138
2139     return 1;
2140 }
2141
2142 /**
2143  * Calculate frame position in hammer format for corresponding 3D coordinates on sphere.
2144  *
2145  * @param s filter private context
2146  * @param vec coordinates on sphere
2147  * @param width frame width
2148  * @param height frame height
2149  * @param us horizontal coordinates for interpolation window
2150  * @param vs vertical coordinates for interpolation window
2151  * @param du horizontal relative coordinate
2152  * @param dv vertical relative coordinate
2153  */
2154 static int xyz_to_hammer(const V360Context *s,
2155                          const float *vec, int width, int height,
2156                          int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2157 {
2158     const float theta = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0];
2159
2160     const float z = sqrtf(1.f + sqrtf(1.f - vec[1] * vec[1]) * cosf(theta * 0.5f));
2161     const float x = sqrtf(1.f - vec[1] * vec[1]) * sinf(theta * 0.5f) / z;
2162     const float y = vec[1] / z * s->input_mirror_modifier[1];
2163
2164     const float uf = (x + 1.f) * width  / 2.f;
2165     const float vf = (y + 1.f) * height / 2.f;
2166
2167     const int ui = floorf(uf);
2168     const int vi = floorf(vf);
2169
2170     *du = uf - ui;
2171     *dv = vf - vi;
2172
2173     for (int i = 0; i < 4; i++) {
2174         for (int j = 0; j < 4; j++) {
2175             us[i][j] = av_clip(ui + j - 1, 0, width  - 1);
2176             vs[i][j] = av_clip(vi + i - 1, 0, height - 1);
2177         }
2178     }
2179
2180     return 1;
2181 }
2182
2183 /**
2184  * Calculate 3D coordinates on sphere for corresponding frame position in sinusoidal format.
2185  *
2186  * @param s filter private context
2187  * @param i horizontal position on frame [0, width)
2188  * @param j vertical position on frame [0, height)
2189  * @param width frame width
2190  * @param height frame height
2191  * @param vec coordinates on sphere
2192  */
2193 static int sinusoidal_to_xyz(const V360Context *s,
2194                              int i, int j, int width, int height,
2195                              float *vec)
2196 {
2197     const float theta = ((2.f * j + 1.f) / height - 1.f) * M_PI_2;
2198     const float phi   = ((2.f * i + 1.f) / width  - 1.f) * M_PI / cosf(theta);
2199
2200     const float sin_phi   = sinf(phi);
2201     const float cos_phi   = cosf(phi);
2202     const float sin_theta = sinf(theta);
2203     const float cos_theta = cosf(theta);
2204
2205     vec[0] = cos_theta * sin_phi;
2206     vec[1] = sin_theta;
2207     vec[2] = cos_theta * cos_phi;
2208
2209     normalize_vector(vec);
2210
2211     return 1;
2212 }
2213
2214 /**
2215  * Calculate frame position in sinusoidal format for corresponding 3D coordinates on sphere.
2216  *
2217  * @param s filter private context
2218  * @param vec coordinates on sphere
2219  * @param width frame width
2220  * @param height frame height
2221  * @param us horizontal coordinates for interpolation window
2222  * @param vs vertical coordinates for interpolation window
2223  * @param du horizontal relative coordinate
2224  * @param dv vertical relative coordinate
2225  */
2226 static int xyz_to_sinusoidal(const V360Context *s,
2227                              const float *vec, int width, int height,
2228                              int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2229 {
2230     const float theta = asinf(vec[1]) * s->input_mirror_modifier[1];
2231     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0] * cosf(theta);
2232
2233     const float uf = (phi   / M_PI   + 1.f) * width  / 2.f;
2234     const float vf = (theta / M_PI_2 + 1.f) * height / 2.f;
2235
2236     const int ui = floorf(uf);
2237     const int vi = floorf(vf);
2238
2239     *du = uf - ui;
2240     *dv = vf - vi;
2241
2242     for (int i = 0; i < 4; i++) {
2243         for (int j = 0; j < 4; j++) {
2244             us[i][j] = av_clip(ui + j - 1, 0, width  - 1);
2245             vs[i][j] = av_clip(vi + i - 1, 0, height - 1);
2246         }
2247     }
2248
2249     return 1;
2250 }
2251
2252 /**
2253  * Prepare data for processing equi-angular cubemap input format.
2254  *
2255  * @param ctx filter context
2256  *
2257  * @return error code
2258  */
2259 static int prepare_eac_in(AVFilterContext *ctx)
2260 {
2261     V360Context *s = ctx->priv;
2262
2263     if (s->ih_flip && s->iv_flip) {
2264         s->in_cubemap_face_order[RIGHT] = BOTTOM_LEFT;
2265         s->in_cubemap_face_order[LEFT]  = BOTTOM_RIGHT;
2266         s->in_cubemap_face_order[UP]    = TOP_LEFT;
2267         s->in_cubemap_face_order[DOWN]  = TOP_RIGHT;
2268         s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
2269         s->in_cubemap_face_order[BACK]  = TOP_MIDDLE;
2270     } else if (s->ih_flip) {
2271         s->in_cubemap_face_order[RIGHT] = TOP_LEFT;
2272         s->in_cubemap_face_order[LEFT]  = TOP_RIGHT;
2273         s->in_cubemap_face_order[UP]    = BOTTOM_LEFT;
2274         s->in_cubemap_face_order[DOWN]  = BOTTOM_RIGHT;
2275         s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
2276         s->in_cubemap_face_order[BACK]  = BOTTOM_MIDDLE;
2277     } else if (s->iv_flip) {
2278         s->in_cubemap_face_order[RIGHT] = BOTTOM_RIGHT;
2279         s->in_cubemap_face_order[LEFT]  = BOTTOM_LEFT;
2280         s->in_cubemap_face_order[UP]    = TOP_RIGHT;
2281         s->in_cubemap_face_order[DOWN]  = TOP_LEFT;
2282         s->in_cubemap_face_order[FRONT] = BOTTOM_MIDDLE;
2283         s->in_cubemap_face_order[BACK]  = TOP_MIDDLE;
2284     } else {
2285         s->in_cubemap_face_order[RIGHT] = TOP_RIGHT;
2286         s->in_cubemap_face_order[LEFT]  = TOP_LEFT;
2287         s->in_cubemap_face_order[UP]    = BOTTOM_RIGHT;
2288         s->in_cubemap_face_order[DOWN]  = BOTTOM_LEFT;
2289         s->in_cubemap_face_order[FRONT] = TOP_MIDDLE;
2290         s->in_cubemap_face_order[BACK]  = BOTTOM_MIDDLE;
2291     }
2292
2293     if (s->iv_flip) {
2294         s->in_cubemap_face_rotation[TOP_LEFT]      = ROT_270;
2295         s->in_cubemap_face_rotation[TOP_MIDDLE]    = ROT_90;
2296         s->in_cubemap_face_rotation[TOP_RIGHT]     = ROT_270;
2297         s->in_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_0;
2298         s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_0;
2299         s->in_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_0;
2300     } else {
2301         s->in_cubemap_face_rotation[TOP_LEFT]      = ROT_0;
2302         s->in_cubemap_face_rotation[TOP_MIDDLE]    = ROT_0;
2303         s->in_cubemap_face_rotation[TOP_RIGHT]     = ROT_0;
2304         s->in_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_270;
2305         s->in_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
2306         s->in_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_270;
2307     }
2308
2309     return 0;
2310 }
2311
2312 /**
2313  * Prepare data for processing equi-angular cubemap output format.
2314  *
2315  * @param ctx filter context
2316  *
2317  * @return error code
2318  */
2319 static int prepare_eac_out(AVFilterContext *ctx)
2320 {
2321     V360Context *s = ctx->priv;
2322
2323     s->out_cubemap_direction_order[TOP_LEFT]      = LEFT;
2324     s->out_cubemap_direction_order[TOP_MIDDLE]    = FRONT;
2325     s->out_cubemap_direction_order[TOP_RIGHT]     = RIGHT;
2326     s->out_cubemap_direction_order[BOTTOM_LEFT]   = DOWN;
2327     s->out_cubemap_direction_order[BOTTOM_MIDDLE] = BACK;
2328     s->out_cubemap_direction_order[BOTTOM_RIGHT]  = UP;
2329
2330     s->out_cubemap_face_rotation[TOP_LEFT]      = ROT_0;
2331     s->out_cubemap_face_rotation[TOP_MIDDLE]    = ROT_0;
2332     s->out_cubemap_face_rotation[TOP_RIGHT]     = ROT_0;
2333     s->out_cubemap_face_rotation[BOTTOM_LEFT]   = ROT_270;
2334     s->out_cubemap_face_rotation[BOTTOM_MIDDLE] = ROT_90;
2335     s->out_cubemap_face_rotation[BOTTOM_RIGHT]  = ROT_270;
2336
2337     return 0;
2338 }
2339
2340 /**
2341  * Calculate 3D coordinates on sphere for corresponding frame position in equi-angular cubemap format.
2342  *
2343  * @param s filter private context
2344  * @param i horizontal position on frame [0, width)
2345  * @param j vertical position on frame [0, height)
2346  * @param width frame width
2347  * @param height frame height
2348  * @param vec coordinates on sphere
2349  */
2350 static int eac_to_xyz(const V360Context *s,
2351                       int i, int j, int width, int height,
2352                       float *vec)
2353 {
2354     const float pixel_pad = 2;
2355     const float u_pad = pixel_pad / width;
2356     const float v_pad = pixel_pad / height;
2357
2358     int u_face, v_face, face;
2359
2360     float l_x, l_y, l_z;
2361
2362     float uf = (i + 0.5f) / width;
2363     float vf = (j + 0.5f) / height;
2364
2365     // EAC has 2-pixel padding on faces except between faces on the same row
2366     // Padding pixels seems not to be stretched with tangent as regular pixels
2367     // Formulas below approximate original padding as close as I could get experimentally
2368
2369     // Horizontal padding
2370     uf = 3.f * (uf - u_pad) / (1.f - 2.f * u_pad);
2371     if (uf < 0.f) {
2372         u_face = 0;
2373         uf -= 0.5f;
2374     } else if (uf >= 3.f) {
2375         u_face = 2;
2376         uf -= 2.5f;
2377     } else {
2378         u_face = floorf(uf);
2379         uf = fmodf(uf, 1.f) - 0.5f;
2380     }
2381
2382     // Vertical padding
2383     v_face = floorf(vf * 2.f);
2384     vf = (vf - v_pad - 0.5f * v_face) / (0.5f - 2.f * v_pad) - 0.5f;
2385
2386     if (uf >= -0.5f && uf < 0.5f) {
2387         uf = tanf(M_PI_2 * uf);
2388     } else {
2389         uf = 2.f * uf;
2390     }
2391     if (vf >= -0.5f && vf < 0.5f) {
2392         vf = tanf(M_PI_2 * vf);
2393     } else {
2394         vf = 2.f * vf;
2395     }
2396
2397     face = u_face + 3 * v_face;
2398
2399     switch (face) {
2400     case TOP_LEFT:
2401         l_x = -1.f;
2402         l_y =  vf;
2403         l_z =  uf;
2404         break;
2405     case TOP_MIDDLE:
2406         l_x =  uf;
2407         l_y =  vf;
2408         l_z =  1.f;
2409         break;
2410     case TOP_RIGHT:
2411         l_x =  1.f;
2412         l_y =  vf;
2413         l_z = -uf;
2414         break;
2415     case BOTTOM_LEFT:
2416         l_x = -vf;
2417         l_y =  1.f;
2418         l_z = -uf;
2419         break;
2420     case BOTTOM_MIDDLE:
2421         l_x = -vf;
2422         l_y = -uf;
2423         l_z = -1.f;
2424         break;
2425     case BOTTOM_RIGHT:
2426         l_x = -vf;
2427         l_y = -1.f;
2428         l_z =  uf;
2429         break;
2430     default:
2431         av_assert0(0);
2432     }
2433
2434     vec[0] = l_x;
2435     vec[1] = l_y;
2436     vec[2] = l_z;
2437
2438     normalize_vector(vec);
2439
2440     return 1;
2441 }
2442
2443 /**
2444  * Calculate frame position in equi-angular cubemap format for corresponding 3D coordinates on sphere.
2445  *
2446  * @param s filter private context
2447  * @param vec coordinates on sphere
2448  * @param width frame width
2449  * @param height frame height
2450  * @param us horizontal coordinates for interpolation window
2451  * @param vs vertical coordinates for interpolation window
2452  * @param du horizontal relative coordinate
2453  * @param dv vertical relative coordinate
2454  */
2455 static int xyz_to_eac(const V360Context *s,
2456                       const float *vec, int width, int height,
2457                       int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2458 {
2459     const float pixel_pad = 2;
2460     const float u_pad = pixel_pad / width;
2461     const float v_pad = pixel_pad / height;
2462
2463     float uf, vf;
2464     int ui, vi;
2465     int direction, face;
2466     int u_face, v_face;
2467
2468     xyz_to_cube(s, vec, &uf, &vf, &direction);
2469
2470     face = s->in_cubemap_face_order[direction];
2471     u_face = face % 3;
2472     v_face = face / 3;
2473
2474     uf = M_2_PI * atanf(uf) + 0.5f;
2475     vf = M_2_PI * atanf(vf) + 0.5f;
2476
2477     // These formulas are inversed from eac_to_xyz ones
2478     uf = (uf + u_face) * (1.f - 2.f * u_pad) / 3.f + u_pad;
2479     vf = vf * (0.5f - 2.f * v_pad) + v_pad + 0.5f * v_face;
2480
2481     uf *= width;
2482     vf *= height;
2483
2484     uf -= 0.5f;
2485     vf -= 0.5f;
2486
2487     ui = floorf(uf);
2488     vi = floorf(vf);
2489
2490     *du = uf - ui;
2491     *dv = vf - vi;
2492
2493     for (int i = 0; i < 4; i++) {
2494         for (int j = 0; j < 4; j++) {
2495             us[i][j] = av_clip(ui + j - 1, 0, width  - 1);
2496             vs[i][j] = av_clip(vi + i - 1, 0, height - 1);
2497         }
2498     }
2499
2500     return 1;
2501 }
2502
2503 /**
2504  * Prepare data for processing flat output format.
2505  *
2506  * @param ctx filter context
2507  *
2508  * @return error code
2509  */
2510 static int prepare_flat_out(AVFilterContext *ctx)
2511 {
2512     V360Context *s = ctx->priv;
2513
2514     s->flat_range[0] = tanf(0.5f * s->h_fov * M_PI / 180.f);
2515     s->flat_range[1] = tanf(0.5f * s->v_fov * M_PI / 180.f);
2516
2517     return 0;
2518 }
2519
2520 /**
2521  * Calculate 3D coordinates on sphere for corresponding frame position in flat format.
2522  *
2523  * @param s filter private context
2524  * @param i horizontal position on frame [0, width)
2525  * @param j vertical position on frame [0, height)
2526  * @param width frame width
2527  * @param height frame height
2528  * @param vec coordinates on sphere
2529  */
2530 static int flat_to_xyz(const V360Context *s,
2531                        int i, int j, int width, int height,
2532                        float *vec)
2533 {
2534     const float l_x = s->flat_range[0] * ((2.f * i + 0.5f) / width  - 1.f);
2535     const float l_y = s->flat_range[1] * ((2.f * j + 0.5f) / height - 1.f);
2536
2537     vec[0] = l_x;
2538     vec[1] = l_y;
2539     vec[2] = 1.f;
2540
2541     normalize_vector(vec);
2542
2543     return 1;
2544 }
2545
2546 /**
2547  * Prepare data for processing fisheye output format.
2548  *
2549  * @param ctx filter context
2550  *
2551  * @return error code
2552  */
2553 static int prepare_fisheye_out(AVFilterContext *ctx)
2554 {
2555     V360Context *s = ctx->priv;
2556
2557     s->flat_range[0] = s->h_fov / 180.f;
2558     s->flat_range[1] = s->v_fov / 180.f;
2559
2560     return 0;
2561 }
2562
2563 /**
2564  * Calculate 3D coordinates on sphere for corresponding frame position in fisheye format.
2565  *
2566  * @param s filter private context
2567  * @param i horizontal position on frame [0, width)
2568  * @param j vertical position on frame [0, height)
2569  * @param width frame width
2570  * @param height frame height
2571  * @param vec coordinates on sphere
2572  */
2573 static int fisheye_to_xyz(const V360Context *s,
2574                           int i, int j, int width, int height,
2575                           float *vec)
2576 {
2577     const float uf = s->flat_range[0] * ((2.f * i) / width  - 1.f);
2578     const float vf = s->flat_range[1] * ((2.f * j + 1.f) / height - 1.f);
2579
2580     const float phi   = atan2f(vf, uf);
2581     const float theta = M_PI_2 * (1.f - hypotf(uf, vf));
2582
2583     const float sin_phi   = sinf(phi);
2584     const float cos_phi   = cosf(phi);
2585     const float sin_theta = sinf(theta);
2586     const float cos_theta = cosf(theta);
2587
2588     vec[0] = cos_theta * cos_phi;
2589     vec[1] = cos_theta * sin_phi;
2590     vec[2] = sin_theta;
2591
2592     normalize_vector(vec);
2593
2594     return 1;
2595 }
2596
2597 /**
2598  * Prepare data for processing fisheye input format.
2599  *
2600  * @param ctx filter context
2601  *
2602  * @return error code
2603  */
2604 static int prepare_fisheye_in(AVFilterContext *ctx)
2605 {
2606     V360Context *s = ctx->priv;
2607
2608     s->iflat_range[0] = s->ih_fov / 180.f;
2609     s->iflat_range[1] = s->iv_fov / 180.f;
2610
2611     return 0;
2612 }
2613
2614 /**
2615  * Calculate frame position in fisheye format for corresponding 3D coordinates on sphere.
2616  *
2617  * @param s filter private context
2618  * @param vec coordinates on sphere
2619  * @param width frame width
2620  * @param height frame height
2621  * @param us horizontal coordinates for interpolation window
2622  * @param vs vertical coordinates for interpolation window
2623  * @param du horizontal relative coordinate
2624  * @param dv vertical relative coordinate
2625  */
2626 static int xyz_to_fisheye(const V360Context *s,
2627                           const float *vec, int width, int height,
2628                           int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2629 {
2630     const float h   = hypotf(vec[0], vec[1]);
2631     const float lh  = h > 0.f ? h : 1.f;
2632     const float phi = atan2f(h, vec[2]) / M_PI;
2633
2634     float uf = vec[0] / lh * phi * s->input_mirror_modifier[0] / s->iflat_range[0];
2635     float vf = vec[1] / lh * phi * s->input_mirror_modifier[1] / s->iflat_range[1];
2636
2637     const int visible = hypotf(uf, vf) <= 0.5f;
2638     int ui, vi;
2639
2640     uf = (uf + 0.5f) * width;
2641     vf = (vf + 0.5f) * height;
2642
2643     ui = floorf(uf);
2644     vi = floorf(vf);
2645
2646     *du = visible ? uf - ui : 0.f;
2647     *dv = visible ? vf - vi : 0.f;
2648
2649     for (int i = 0; i < 4; i++) {
2650         for (int j = 0; j < 4; j++) {
2651             us[i][j] = visible ? av_clip(ui + j - 1, 0, width  - 1) : 0;
2652             vs[i][j] = visible ? av_clip(vi + i - 1, 0, height - 1) : 0;
2653         }
2654     }
2655
2656     return visible;
2657 }
2658
2659 /**
2660  * Calculate 3D coordinates on sphere for corresponding frame position in pannini format.
2661  *
2662  * @param s filter private context
2663  * @param i horizontal position on frame [0, width)
2664  * @param j vertical position on frame [0, height)
2665  * @param width frame width
2666  * @param height frame height
2667  * @param vec coordinates on sphere
2668  */
2669 static int pannini_to_xyz(const V360Context *s,
2670                           int i, int j, int width, int height,
2671                           float *vec)
2672 {
2673     const float uf = ((2.f * i + 1.f) / width  - 1.f);
2674     const float vf = ((2.f * j + 1.f) / height - 1.f);
2675
2676     const float d = s->h_fov;
2677     const float k = uf * uf / ((d + 1.f) * (d + 1.f));
2678     const float dscr = k * k * d * d - (k + 1.f) * (k * d * d - 1.f);
2679     const float clon = (-k * d + sqrtf(dscr)) / (k + 1.f);
2680     const float S = (d + 1.f) / (d + clon);
2681     const float lon = atan2f(uf, S * clon);
2682     const float lat = atan2f(vf, S);
2683
2684     vec[0] = sinf(lon) * cosf(lat);
2685     vec[1] = sinf(lat);
2686     vec[2] = cosf(lon) * cosf(lat);
2687
2688     normalize_vector(vec);
2689
2690     return 1;
2691 }
2692
2693 /**
2694  * Prepare data for processing cylindrical output format.
2695  *
2696  * @param ctx filter context
2697  *
2698  * @return error code
2699  */
2700 static int prepare_cylindrical_out(AVFilterContext *ctx)
2701 {
2702     V360Context *s = ctx->priv;
2703
2704     s->flat_range[0] = M_PI * s->h_fov / 360.f;
2705     s->flat_range[1] = tanf(0.5f * s->v_fov * M_PI / 180.f);
2706
2707     return 0;
2708 }
2709
2710 /**
2711  * Calculate 3D coordinates on sphere for corresponding frame position in cylindrical format.
2712  *
2713  * @param s filter private context
2714  * @param i horizontal position on frame [0, width)
2715  * @param j vertical position on frame [0, height)
2716  * @param width frame width
2717  * @param height frame height
2718  * @param vec coordinates on sphere
2719  */
2720 static int cylindrical_to_xyz(const V360Context *s,
2721                               int i, int j, int width, int height,
2722                               float *vec)
2723 {
2724     const float uf = s->flat_range[0] * ((2.f * i + 1.f) / width  - 1.f);
2725     const float vf = s->flat_range[1] * ((2.f * j + 1.f) / height - 1.f);
2726
2727     const float phi   = uf;
2728     const float theta = atanf(vf);
2729
2730     const float sin_phi   = sinf(phi);
2731     const float cos_phi   = cosf(phi);
2732     const float sin_theta = sinf(theta);
2733     const float cos_theta = cosf(theta);
2734
2735     vec[0] = cos_theta * sin_phi;
2736     vec[1] = sin_theta;
2737     vec[2] = cos_theta * cos_phi;
2738
2739     normalize_vector(vec);
2740
2741     return 1;
2742 }
2743
2744 /**
2745  * Prepare data for processing cylindrical input format.
2746  *
2747  * @param ctx filter context
2748  *
2749  * @return error code
2750  */
2751 static int prepare_cylindrical_in(AVFilterContext *ctx)
2752 {
2753     V360Context *s = ctx->priv;
2754
2755     s->iflat_range[0] = M_PI * s->ih_fov / 360.f;
2756     s->iflat_range[1] = tanf(0.5f * s->iv_fov * M_PI / 180.f);
2757
2758     return 0;
2759 }
2760
2761 /**
2762  * Calculate frame position in cylindrical format for corresponding 3D coordinates on sphere.
2763  *
2764  * @param s filter private context
2765  * @param vec coordinates on sphere
2766  * @param width frame width
2767  * @param height frame height
2768  * @param us horizontal coordinates for interpolation window
2769  * @param vs vertical coordinates for interpolation window
2770  * @param du horizontal relative coordinate
2771  * @param dv vertical relative coordinate
2772  */
2773 static int xyz_to_cylindrical(const V360Context *s,
2774                               const float *vec, int width, int height,
2775                               int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2776 {
2777     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0] / s->iflat_range[0];
2778     const float theta = asinf(vec[1]) * s->input_mirror_modifier[1];
2779
2780     const float uf = (phi + 1.f) * (width - 1) / 2.f;
2781     const float vf = (tanf(theta) / s->iflat_range[1] + 1.f) * height / 2.f;
2782
2783     const int ui = floorf(uf);
2784     const int vi = floorf(vf);
2785
2786     const int visible = vi >= 0 && vi < height && ui >= 0 && ui < width &&
2787                         theta <=  M_PI * s->iv_fov / 180.f &&
2788                         theta >= -M_PI * s->iv_fov / 180.f;
2789
2790     *du = uf - ui;
2791     *dv = vf - vi;
2792
2793     for (int i = 0; i < 4; i++) {
2794         for (int j = 0; j < 4; j++) {
2795             us[i][j] = visible ? av_clip(ui + j - 1, 0, width  - 1) : 0;
2796             vs[i][j] = visible ? av_clip(vi + i - 1, 0, height - 1) : 0;
2797         }
2798     }
2799
2800     return visible;
2801 }
2802
2803 /**
2804  * Calculate 3D coordinates on sphere for corresponding frame position in perspective format.
2805  *
2806  * @param s filter private context
2807  * @param i horizontal position on frame [0, width)
2808  * @param j vertical position on frame [0, height)
2809  * @param width frame width
2810  * @param height frame height
2811  * @param vec coordinates on sphere
2812  */
2813 static int perspective_to_xyz(const V360Context *s,
2814                               int i, int j, int width, int height,
2815                               float *vec)
2816 {
2817     const float uf = ((2.f * i + 1.f) / width  - 1.f);
2818     const float vf = ((2.f * j + 1.f) / height - 1.f);
2819     const float rh = hypotf(uf, vf);
2820     const float sinzz = 1.f - rh * rh;
2821     const float h = 1.f + s->v_fov;
2822     const float sinz = (h - sqrtf(sinzz)) / (h / rh + rh / h);
2823     const float sinz2 = sinz * sinz;
2824
2825     if (sinz2 <= 1.f) {
2826         const float cosz = sqrtf(1.f - sinz2);
2827
2828         const float theta = asinf(cosz);
2829         const float phi   = atan2f(uf, vf);
2830
2831         const float sin_phi   = sinf(phi);
2832         const float cos_phi   = cosf(phi);
2833         const float sin_theta = sinf(theta);
2834         const float cos_theta = cosf(theta);
2835
2836         vec[0] = cos_theta * sin_phi;
2837         vec[1] = sin_theta;
2838         vec[2] = cos_theta * cos_phi;
2839     } else {
2840         vec[0] = 0.f;
2841         vec[1] = 1.f;
2842         vec[2] = 0.f;
2843         return 0;
2844     }
2845
2846     normalize_vector(vec);
2847     return 1;
2848 }
2849
2850 /**
2851  * Calculate 3D coordinates on sphere for corresponding frame position in tetrahedron format.
2852  *
2853  * @param s filter private context
2854  * @param i horizontal position on frame [0, width)
2855  * @param j vertical position on frame [0, height)
2856  * @param width frame width
2857  * @param height frame height
2858  * @param vec coordinates on sphere
2859  */
2860 static int tetrahedron_to_xyz(const V360Context *s,
2861                               int i, int j, int width, int height,
2862                               float *vec)
2863 {
2864     const float uf = (float)i / width;
2865     const float vf = (float)j / height;
2866
2867     vec[0] = uf < 0.5f ? uf * 4.f - 1.f : 3.f - uf * 4.f;
2868     vec[1] = 1.f - vf * 2.f;
2869     vec[2] = 2.f * fabsf(1.f - fabsf(1.f - uf * 2.f + vf)) - 1.f;
2870
2871     normalize_vector(vec);
2872
2873     return 1;
2874 }
2875
2876 /**
2877  * Calculate frame position in tetrahedron format for corresponding 3D coordinates on sphere.
2878  *
2879  * @param s filter private context
2880  * @param vec coordinates on sphere
2881  * @param width frame width
2882  * @param height frame height
2883  * @param us horizontal coordinates for interpolation window
2884  * @param vs vertical coordinates for interpolation window
2885  * @param du horizontal relative coordinate
2886  * @param dv vertical relative coordinate
2887  */
2888 static int xyz_to_tetrahedron(const V360Context *s,
2889                               const float *vec, int width, int height,
2890                               int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2891 {
2892     const float d0 = vec[0] * 1.f + vec[1] * 1.f + vec[2] *-1.f;
2893     const float d1 = vec[0] *-1.f + vec[1] *-1.f + vec[2] *-1.f;
2894     const float d2 = vec[0] * 1.f + vec[1] *-1.f + vec[2] * 1.f;
2895     const float d3 = vec[0] *-1.f + vec[1] * 1.f + vec[2] * 1.f;
2896     const float d = FFMAX(d0, FFMAX3(d1, d2, d3));
2897
2898     float uf, vf, x, y, z;
2899     int ui, vi;
2900
2901     x =  vec[0] / d;
2902     y =  vec[1] / d;
2903     z = -vec[2] / d;
2904
2905     vf = 0.5f - y * 0.5f * s->input_mirror_modifier[1];
2906
2907     if ((x + y >= 0.f &&  y + z >= 0.f && -z - x <= 0.f) ||
2908         (x + y <= 0.f && -y + z >= 0.f &&  z - x >= 0.f)) {
2909         uf = 0.25f * x * s->input_mirror_modifier[0] + 0.25f;
2910     }  else {
2911         uf = 0.75f - 0.25f * x * s->input_mirror_modifier[0];
2912     }
2913
2914     uf *= width;
2915     vf *= height;
2916
2917     ui = floorf(uf);
2918     vi = floorf(vf);
2919
2920     *du = uf - ui;
2921     *dv = vf - vi;
2922
2923     for (int i = 0; i < 4; i++) {
2924         for (int j = 0; j < 4; j++) {
2925             us[i][j] = reflectx(ui + j - 1, vi + i - 1, width, height);
2926             vs[i][j] = reflecty(vi + i - 1, height);
2927         }
2928     }
2929
2930     return 1;
2931 }
2932
2933 /**
2934  * Calculate 3D coordinates on sphere for corresponding frame position in dual fisheye format.
2935  *
2936  * @param s filter private context
2937  * @param i horizontal position on frame [0, width)
2938  * @param j vertical position on frame [0, height)
2939  * @param width frame width
2940  * @param height frame height
2941  * @param vec coordinates on sphere
2942  */
2943 static int dfisheye_to_xyz(const V360Context *s,
2944                            int i, int j, int width, int height,
2945                            float *vec)
2946 {
2947     const float scale = 1.f + s->out_pad;
2948
2949     const float ew = width / 2.f;
2950     const float eh = height;
2951
2952     const int ei = i >= ew ? i - ew : i;
2953     const float m = i >= ew ? 1.f : -1.f;
2954
2955     const float uf = ((2.f * ei) / ew - 1.f) * scale;
2956     const float vf = ((2.f * j + 1.f) / eh - 1.f) * scale;
2957
2958     const float h     = hypotf(uf, vf);
2959     const float lh    = h > 0.f ? h : 1.f;
2960     const float theta = m * M_PI_2 * (1.f - h);
2961
2962     const float sin_theta = sinf(theta);
2963     const float cos_theta = cosf(theta);
2964
2965     vec[0] = cos_theta * m * uf / lh;
2966     vec[1] = cos_theta *     vf / lh;
2967     vec[2] = sin_theta;
2968
2969     normalize_vector(vec);
2970
2971     return 1;
2972 }
2973
2974 /**
2975  * Calculate frame position in dual fisheye format for corresponding 3D coordinates on sphere.
2976  *
2977  * @param s filter private context
2978  * @param vec coordinates on sphere
2979  * @param width frame width
2980  * @param height frame height
2981  * @param us horizontal coordinates for interpolation window
2982  * @param vs vertical coordinates for interpolation window
2983  * @param du horizontal relative coordinate
2984  * @param dv vertical relative coordinate
2985  */
2986 static int xyz_to_dfisheye(const V360Context *s,
2987                            const float *vec, int width, int height,
2988                            int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
2989 {
2990     const float scale = 1.f - s->in_pad;
2991
2992     const float ew = width / 2.f;
2993     const float eh = height;
2994
2995     const float h     = hypotf(vec[0], vec[1]);
2996     const float lh    = h > 0.f ? h : 1.f;
2997     const float theta = acosf(fabsf(vec[2])) / M_PI;
2998
2999     float uf = (theta * (vec[0] / lh) * s->input_mirror_modifier[0] * scale + 0.5f) * ew;
3000     float vf = (theta * (vec[1] / lh) * s->input_mirror_modifier[1] * scale + 0.5f) * eh;
3001
3002     int ui, vi;
3003     int u_shift;
3004
3005     if (vec[2] >= 0.f) {
3006         u_shift = ceilf(ew);
3007     } else {
3008         u_shift = 0;
3009         uf = ew - uf;
3010     }
3011
3012     ui = floorf(uf);
3013     vi = floorf(vf);
3014
3015     *du = uf - ui;
3016     *dv = vf - vi;
3017
3018     for (int i = 0; i < 4; i++) {
3019         for (int j = 0; j < 4; j++) {
3020             us[i][j] = av_clip(u_shift + ui + j - 1, 0, width  - 1);
3021             vs[i][j] = av_clip(          vi + i - 1, 0, height - 1);
3022         }
3023     }
3024
3025     return 1;
3026 }
3027
3028 /**
3029  * Calculate 3D coordinates on sphere for corresponding frame position in barrel facebook's format.
3030  *
3031  * @param s filter private context
3032  * @param i horizontal position on frame [0, width)
3033  * @param j vertical position on frame [0, height)
3034  * @param width frame width
3035  * @param height frame height
3036  * @param vec coordinates on sphere
3037  */
3038 static int barrel_to_xyz(const V360Context *s,
3039                          int i, int j, int width, int height,
3040                          float *vec)
3041 {
3042     const float scale = 0.99f;
3043     float l_x, l_y, l_z;
3044
3045     if (i < 4 * width / 5) {
3046         const float theta_range = M_PI_4;
3047
3048         const int ew = 4 * width / 5;
3049         const int eh = height;
3050
3051         const float phi   = ((2.f * i) / ew - 1.f) * M_PI        / scale;
3052         const float theta = ((2.f * j) / eh - 1.f) * theta_range / scale;
3053
3054         const float sin_phi   = sinf(phi);
3055         const float cos_phi   = cosf(phi);
3056         const float sin_theta = sinf(theta);
3057         const float cos_theta = cosf(theta);
3058
3059         l_x = cos_theta * sin_phi;
3060         l_y = sin_theta;
3061         l_z = cos_theta * cos_phi;
3062     } else {
3063         const int ew = width  / 5;
3064         const int eh = height / 2;
3065
3066         float uf, vf;
3067
3068         if (j < eh) {   // UP
3069             uf = 2.f * (i - 4 * ew) / ew - 1.f;
3070             vf = 2.f * (j         ) / eh - 1.f;
3071
3072             uf /= scale;
3073             vf /= scale;
3074
3075             l_x =  uf;
3076             l_y = -1.f;
3077             l_z =  vf;
3078         } else {            // DOWN
3079             uf = 2.f * (i - 4 * ew) / ew - 1.f;
3080             vf = 2.f * (j -     eh) / eh - 1.f;
3081
3082             uf /= scale;
3083             vf /= scale;
3084
3085             l_x =  uf;
3086             l_y =  1.f;
3087             l_z = -vf;
3088         }
3089     }
3090
3091     vec[0] = l_x;
3092     vec[1] = l_y;
3093     vec[2] = l_z;
3094
3095     normalize_vector(vec);
3096
3097     return 1;
3098 }
3099
3100 /**
3101  * Calculate frame position in barrel facebook's format for corresponding 3D coordinates on sphere.
3102  *
3103  * @param s filter private context
3104  * @param vec coordinates on sphere
3105  * @param width frame width
3106  * @param height frame height
3107  * @param us horizontal coordinates for interpolation window
3108  * @param vs vertical coordinates for interpolation window
3109  * @param du horizontal relative coordinate
3110  * @param dv vertical relative coordinate
3111  */
3112 static int xyz_to_barrel(const V360Context *s,
3113                          const float *vec, int width, int height,
3114                          int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
3115 {
3116     const float scale = 0.99f;
3117
3118     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0];
3119     const float theta = asinf(vec[1]) * s->input_mirror_modifier[1];
3120     const float theta_range = M_PI_4;
3121
3122     int ew, eh;
3123     int u_shift, v_shift;
3124     float uf, vf;
3125     int ui, vi;
3126
3127     if (theta > -theta_range && theta < theta_range) {
3128         ew = 4 * width / 5;
3129         eh = height;
3130
3131         u_shift = s->ih_flip ? width / 5 : 0;
3132         v_shift = 0;
3133
3134         uf = (phi   / M_PI        * scale + 1.f) * ew / 2.f;
3135         vf = (theta / theta_range * scale + 1.f) * eh / 2.f;
3136     } else {
3137         ew = width  / 5;
3138         eh = height / 2;
3139
3140         u_shift = s->ih_flip ? 0 : 4 * ew;
3141
3142         if (theta < 0.f) {  // UP
3143             uf = -vec[0] / vec[1];
3144             vf = -vec[2] / vec[1];
3145             v_shift = 0;
3146         } else {            // DOWN
3147             uf =  vec[0] / vec[1];
3148             vf = -vec[2] / vec[1];
3149             v_shift = eh;
3150         }
3151
3152         uf *= s->input_mirror_modifier[0] * s->input_mirror_modifier[1];
3153         vf *= s->input_mirror_modifier[1];
3154
3155         uf = 0.5f * ew * (uf * scale + 1.f);
3156         vf = 0.5f * eh * (vf * scale + 1.f);
3157     }
3158
3159     ui = floorf(uf);
3160     vi = floorf(vf);
3161
3162     *du = uf - ui;
3163     *dv = vf - vi;
3164
3165     for (int i = 0; i < 4; i++) {
3166         for (int j = 0; j < 4; j++) {
3167             us[i][j] = u_shift + av_clip(ui + j - 1, 0, ew - 1);
3168             vs[i][j] = v_shift + av_clip(vi + i - 1, 0, eh - 1);
3169         }
3170     }
3171
3172     return 1;
3173 }
3174
3175 /**
3176  * Calculate frame position in barrel split facebook's format for corresponding 3D coordinates on sphere.
3177  *
3178  * @param s filter private context
3179  * @param vec coordinates on sphere
3180  * @param width frame width
3181  * @param height frame height
3182  * @param us horizontal coordinates for interpolation window
3183  * @param vs vertical coordinates for interpolation window
3184  * @param du horizontal relative coordinate
3185  * @param dv vertical relative coordinate
3186  */
3187 static int xyz_to_barrelsplit(const V360Context *s,
3188                               const float *vec, int width, int height,
3189                               int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
3190 {
3191     const float phi   = atan2f(vec[0], vec[2]) * s->input_mirror_modifier[0];
3192     const float theta = asinf(vec[1]) * s->input_mirror_modifier[1];
3193
3194     const float theta_range = M_PI_4;
3195
3196     int ew, eh;
3197     int u_shift, v_shift;
3198     float uf, vf;
3199     int ui, vi;
3200
3201     if (theta >= -theta_range && theta <= theta_range) {
3202         const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (width * 2.f / 3.f) : 1.f - s->in_pad;
3203         const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (height / 2.f) : 1.f - s->in_pad;
3204
3205         ew = width / 3 * 2;
3206         eh = height / 2;
3207
3208         u_shift = s->ih_flip ? width / 3 : 0;
3209         v_shift = phi >= M_PI_2 || phi < -M_PI_2 ? eh : 0;
3210
3211         uf = fmodf(phi, M_PI_2) / M_PI_2;
3212         vf = theta / M_PI_4;
3213
3214         if (v_shift)
3215             uf = uf >= 0.f ? fmodf(uf - 1.f, 1.f) : fmodf(uf + 1.f, 1.f);
3216
3217         uf = (uf * scalew + 1.f) * width  / 3.f;
3218         vf = (vf * scaleh + 1.f) * height / 4.f;
3219     } else {
3220         const float scalew = s->fin_pad > 0 ? 1.f - s->fin_pad / (width  / 3.f) : 1.f - s->in_pad;
3221         const float scaleh = s->fin_pad > 0 ? 1.f - s->fin_pad / (height / 4.f) : 1.f - s->in_pad;
3222         int v_offset = 0;
3223
3224         ew = width  / 3;
3225         eh = height / 4;
3226
3227         u_shift = s->ih_flip ? 0 : 2 * ew;
3228
3229         if (theta <= 0.f && theta >= -M_PI_2 &&
3230             phi <= M_PI_2 && phi >= -M_PI_2) {
3231             uf = -vec[0] / vec[1];
3232             vf = -vec[2] / vec[1];
3233             v_shift = 0;
3234             v_offset = -eh;
3235         } else if (theta >= 0.f && theta <= M_PI_2 &&
3236                    phi <= M_PI_2 && phi >= -M_PI_2) {
3237             uf =  vec[0] / vec[1];
3238             vf = -vec[2] / vec[1];
3239             v_shift = height * 0.25f;
3240         } else if (theta <= 0.f && theta >= -M_PI_2) {
3241             uf =  vec[0] / vec[1];
3242             vf =  vec[2] / vec[1];
3243             v_shift = height * 0.5f;
3244             v_offset = -eh;
3245         } else {
3246             uf = -vec[0] / vec[1];
3247             vf =  vec[2] / vec[1];
3248             v_shift = height * 0.75f;
3249         }
3250
3251         uf *= s->input_mirror_modifier[0] * s->input_mirror_modifier[1];
3252         vf *= s->input_mirror_modifier[1];
3253
3254         uf = 0.5f * width / 3.f * (uf * scalew + 1.f);
3255         vf = height * 0.25f * (vf * scaleh + 1.f) + v_offset;
3256     }
3257
3258     ui = floorf(uf);
3259     vi = floorf(vf);
3260
3261     *du = uf - ui;
3262     *dv = vf - vi;
3263
3264     for (int i = 0; i < 4; i++) {
3265         for (int j = 0; j < 4; j++) {
3266             us[i][j] = u_shift + av_clip(ui + j - 1, 0, ew - 1);
3267             vs[i][j] = v_shift + av_clip(vi + i - 1, 0, eh - 1);
3268         }
3269     }
3270
3271     return 1;
3272 }
3273
3274 /**
3275  * Calculate 3D coordinates on sphere for corresponding frame position in barrel split facebook's format.
3276  *
3277  * @param s filter private context
3278  * @param i horizontal position on frame [0, width)
3279  * @param j vertical position on frame [0, height)
3280  * @param width frame width
3281  * @param height frame height
3282  * @param vec coordinates on sphere
3283  */
3284 static int barrelsplit_to_xyz(const V360Context *s,
3285                               int i, int j, int width, int height,
3286                               float *vec)
3287 {
3288     const float x = (i + 0.5f) / width;
3289     const float y = (j + 0.5f) / height;
3290     float l_x, l_y, l_z;
3291
3292     if (x < 2.f / 3.f) {
3293         const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (width * 2.f / 3.f) : 1.f - s->out_pad;
3294         const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (height / 2.f) : 1.f - s->out_pad;
3295
3296         const float back = floorf(y * 2.f);
3297
3298         const float phi   = ((3.f / 2.f * x - 0.5f) / scalew - back) * M_PI;
3299         const float theta = (y - 0.25f - 0.5f * back) / scaleh * M_PI;
3300
3301         const float sin_phi   = sinf(phi);
3302         const float cos_phi   = cosf(phi);
3303         const float sin_theta = sinf(theta);
3304         const float cos_theta = cosf(theta);
3305
3306         l_x = cos_theta * sin_phi;
3307         l_y = sin_theta;
3308         l_z = cos_theta * cos_phi;
3309     } else {
3310         const float scalew = s->fout_pad > 0 ? 1.f - s->fout_pad / (width  / 3.f) : 1.f - s->out_pad;
3311         const float scaleh = s->fout_pad > 0 ? 1.f - s->fout_pad / (height / 4.f) : 1.f - s->out_pad;
3312
3313         const int face = floorf(y * 4.f);
3314         float uf, vf;
3315
3316         uf = x * 3.f - 2.f;
3317
3318         switch (face) {
3319         case 0:
3320             vf = y * 2.f;
3321             uf = 1.f - uf;
3322             vf = 0.5f - vf;
3323
3324             l_x = (0.5f - uf) / scalew;
3325             l_y = -0.5f;
3326             l_z = (0.5f - vf) / scaleh;
3327             break;
3328         case 1:
3329             vf = y * 2.f;
3330             uf = 1.f - uf;
3331             vf = 1.f - (vf - 0.5f);
3332
3333             l_x = (0.5f - uf) / scalew;
3334             l_y =  0.5f;
3335             l_z = (-0.5f + vf) / scaleh;
3336             break;
3337         case 2:
3338             vf = y * 2.f - 0.5f;
3339             vf = 1.f - (1.f - vf);
3340
3341             l_x = (0.5f - uf) / scalew;
3342             l_y = -0.5f;
3343             l_z = (0.5f - vf) / scaleh;
3344             break;
3345         case 3:
3346             vf = y * 2.f - 1.5f;
3347
3348             l_x = (0.5f - uf) / scalew;
3349             l_y =  0.5f;
3350             l_z = (-0.5f + vf) / scaleh;
3351             break;
3352         }
3353     }
3354
3355     vec[0] = l_x;
3356     vec[1] = l_y;
3357     vec[2] = l_z;
3358
3359     normalize_vector(vec);
3360
3361     return 1;
3362 }
3363
3364 /**
3365  * Calculate 3D coordinates on sphere for corresponding frame position in tspyramid format.
3366  *
3367  * @param s filter private context
3368  * @param i horizontal position on frame [0, width)
3369  * @param j vertical position on frame [0, height)
3370  * @param width frame width
3371  * @param height frame height
3372  * @param vec coordinates on sphere
3373  */
3374 static int tspyramid_to_xyz(const V360Context *s,
3375                             int i, int j, int width, int height,
3376                             float *vec)
3377 {
3378     const float x = (i + 0.5f) / width;
3379     const float y = (j + 0.5f) / height;
3380
3381     if (x < 0.5f) {
3382         vec[0] =  x * 4.f - 1.f;
3383         vec[1] = (y * 2.f - 1.f);
3384         vec[2] = 1.f;
3385     } else if (x >= 0.6875f && x < 0.8125f &&
3386                y >= 0.375f  && y < 0.625f) {
3387         vec[0] = -(x - 0.6875f) * 16.f + 1.f;
3388         vec[1] = (y - 0.375f) * 8.f - 1.f;
3389         vec[2] = -1.f;
3390     } else if (0.5f <= x && x < 0.6875f &&
3391                ((0.f <= y && y < 0.375f && y >= 2.f * (x - 0.5f)) ||
3392                 (0.375f <= y && y < 0.625f) ||
3393                 (0.625f <= y && y < 1.f && y <= 2.f * (1.f - x)))) {
3394         vec[0] =  1.f;
3395         vec[1] =  2.f * (y - 2.f * x + 1.f) / (3.f - 4.f * x) - 1.f;
3396         vec[2] = -2.f * (x - 0.5f) / 0.1875f + 1.f;
3397     } else if (0.8125f <= x && x < 1.f &&
3398                ((0.f <= y && y < 0.375f && x >= (1.f - y / 2.f)) ||
3399                 (0.375f <= y && y < 0.625f) ||
3400                 (0.625f <= y && y < 1.f && y <= (2.f * x - 1.f)))) {
3401         vec[0] = -1.f;
3402         vec[1] =  2.f * (y + 2.f * x - 2.f) / (4.f * x - 3.f) - 1.f;
3403         vec[2] =  2.f * (x - 0.8125f) / 0.1875f - 1.f;
3404     } else if (0.f <= y && y < 0.375f &&
3405                ((0.5f <= x && x < 0.8125f && y < 2.f * (x - 0.5f)) ||
3406                 (0.6875f <= x && x < 0.8125f) ||
3407                 (0.8125f <= x && x < 1.f && x < (1.f - y / 2.f)))) {
3408         vec[0] =  2.f * (1.f - x - 0.5f * y) / (0.5f - y) - 1.f;
3409         vec[1] = -1.f;
3410         vec[2] =  2.f * (0.375f - y) / 0.375f - 1.f;
3411     } else {
3412         vec[0] =  2.f * (0.5f - x + 0.5f * y) / (y - 0.5f) - 1.f;
3413         vec[1] =  1.f;
3414         vec[2] = -2.f * (1.f - y) / 0.375f + 1.f;
3415     }
3416
3417     normalize_vector(vec);
3418
3419     return 1;
3420 }
3421
3422 /**
3423  * Calculate frame position in tspyramid format for corresponding 3D coordinates on sphere.
3424  *
3425  * @param s filter private context
3426  * @param vec coordinates on sphere
3427  * @param width frame width
3428  * @param height frame height
3429  * @param us horizontal coordinates for interpolation window
3430  * @param vs vertical coordinates for interpolation window
3431  * @param du horizontal relative coordinate
3432  * @param dv vertical relative coordinate
3433  */
3434 static int xyz_to_tspyramid(const V360Context *s,
3435                             const float *vec, int width, int height,
3436                             int16_t us[4][4], int16_t vs[4][4], float *du, float *dv)
3437 {
3438     float uf, vf;
3439     int ui, vi;
3440     int face;
3441
3442     xyz_to_cube(s, vec, &uf, &vf, &face);
3443
3444     uf = (uf + 1.f) * 0.5f;
3445     vf = (vf + 1.f) * 0.5f;
3446
3447     switch (face) {
3448     case UP:
3449         uf = 0.1875f * vf - 0.375f * uf * vf - 0.125f * uf + 0.8125f;
3450         vf = 0.375f - 0.375f * vf;
3451         break;
3452     case FRONT:
3453         uf = 0.5f * uf;
3454         break;
3455     case DOWN:
3456         uf = 1.f - 0.1875f * vf - 0.5f * uf + 0.375f * uf * vf;
3457         vf = 1.f - 0.375f * vf;
3458         break;
3459     case LEFT:
3460         vf = 0.25f * vf + 0.75f * uf * vf - 0.375f * uf + 0.375f;
3461         uf = 0.1875f * uf + 0.8125f;
3462         break;
3463     case RIGHT:
3464         vf = 0.375f * uf - 0.75f * uf * vf + vf;
3465         uf = 0.1875f * uf + 0.5f;
3466         break;
3467     case BACK:
3468         uf = 0.125f * uf + 0.6875f;
3469         vf = 0.25f * vf + 0.375f;
3470         break;
3471     }
3472
3473     uf *= width;
3474     vf *= height;
3475
3476     ui = floorf(uf);
3477     vi = floorf(vf);
3478
3479     *du = uf - ui;
3480     *dv = vf - vi;
3481
3482     for (int i = 0; i < 4; i++) {
3483         for (int j = 0; j < 4; j++) {
3484             us[i][j] = reflectx(ui + j - 1, vi + i - 1, width, height);
3485             vs[i][j] = reflecty(vi + i - 1, height);
3486         }
3487     }
3488
3489     return 1;
3490 }
3491
3492 static void multiply_matrix(float c[3][3], const float a[3][3], const float b[3][3])
3493 {
3494     for (int i = 0; i < 3; i++) {
3495         for (int j = 0; j < 3; j++) {
3496             float sum = 0.f;
3497
3498             for (int k = 0; k < 3; k++)
3499                 sum += a[i][k] * b[k][j];
3500
3501             c[i][j] = sum;
3502         }
3503     }
3504 }
3505
3506 /**
3507  * Calculate rotation matrix for yaw/pitch/roll angles.
3508  */
3509 static inline void calculate_rotation_matrix(float yaw, float pitch, float roll,
3510                                              float rot_mat[3][3],
3511                                              const int rotation_order[3])
3512 {
3513     const float yaw_rad   = yaw   * M_PI / 180.f;
3514     const float pitch_rad = pitch * M_PI / 180.f;
3515     const float roll_rad  = roll  * M_PI / 180.f;
3516
3517     const float sin_yaw   = sinf(yaw_rad);
3518     const float cos_yaw   = cosf(yaw_rad);
3519     const float sin_pitch = sinf(pitch_rad);
3520     const float cos_pitch = cosf(pitch_rad);
3521     const float sin_roll  = sinf(roll_rad);
3522     const float cos_roll  = cosf(roll_rad);
3523
3524     float m[3][3][3];
3525     float temp[3][3];
3526
3527     m[0][0][0] =  cos_yaw;  m[0][0][1] = 0;          m[0][0][2] =  sin_yaw;
3528     m[0][1][0] =  0;        m[0][1][1] = 1;          m[0][1][2] =  0;
3529     m[0][2][0] = -sin_yaw;  m[0][2][1] = 0;          m[0][2][2] =  cos_yaw;
3530
3531     m[1][0][0] = 1;         m[1][0][1] = 0;          m[1][0][2] =  0;
3532     m[1][1][0] = 0;         m[1][1][1] = cos_pitch;  m[1][1][2] = -sin_pitch;
3533     m[1][2][0] = 0;         m[1][2][1] = sin_pitch;  m[1][2][2] =  cos_pitch;
3534
3535     m[2][0][0] = cos_roll;  m[2][0][1] = -sin_roll;  m[2][0][2] =  0;
3536     m[2][1][0] = sin_roll;  m[2][1][1] =  cos_roll;  m[2][1][2] =  0;
3537     m[2][2][0] = 0;         m[2][2][1] =  0;         m[2][2][2] =  1;
3538
3539     multiply_matrix(temp, m[rotation_order[0]], m[rotation_order[1]]);
3540     multiply_matrix(rot_mat, temp, m[rotation_order[2]]);
3541 }
3542
3543 /**
3544  * Rotate vector with given rotation matrix.
3545  *
3546  * @param rot_mat rotation matrix
3547  * @param vec vector
3548  */
3549 static inline void rotate(const float rot_mat[3][3],
3550                           float *vec)
3551 {
3552     const float x_tmp = vec[0] * rot_mat[0][0] + vec[1] * rot_mat[0][1] + vec[2] * rot_mat[0][2];
3553     const float y_tmp = vec[0] * rot_mat[1][0] + vec[1] * rot_mat[1][1] + vec[2] * rot_mat[1][2];
3554     const float z_tmp = vec[0] * rot_mat[2][0] + vec[1] * rot_mat[2][1] + vec[2] * rot_mat[2][2];
3555
3556     vec[0] = x_tmp;
3557     vec[1] = y_tmp;
3558     vec[2] = z_tmp;
3559 }
3560
3561 static inline void set_mirror_modifier(int h_flip, int v_flip, int d_flip,
3562                                        float *modifier)
3563 {
3564     modifier[0] = h_flip ? -1.f : 1.f;
3565     modifier[1] = v_flip ? -1.f : 1.f;
3566     modifier[2] = d_flip ? -1.f : 1.f;
3567 }
3568
3569 static inline void mirror(const float *modifier, float *vec)
3570 {
3571     vec[0] *= modifier[0];
3572     vec[1] *= modifier[1];
3573     vec[2] *= modifier[2];
3574 }
3575
3576 static int allocate_plane(V360Context *s, int sizeof_uv, int sizeof_ker, int sizeof_mask, int p)
3577 {
3578     if (!s->u[p])
3579         s->u[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_uv);
3580     if (!s->v[p])
3581         s->v[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_uv);
3582     if (!s->u[p] || !s->v[p])
3583         return AVERROR(ENOMEM);
3584     if (sizeof_ker) {
3585         if (!s->ker[p])
3586             s->ker[p] = av_calloc(s->uv_linesize[p] * s->pr_height[p], sizeof_ker);
3587         if (!s->ker[p])
3588             return AVERROR(ENOMEM);
3589     }
3590
3591     if (sizeof_mask && !p) {
3592         if (!s->mask)
3593             s->mask = av_calloc(s->pr_width[p] * s->pr_height[p], sizeof_mask);
3594         if (!s->mask)
3595             return AVERROR(ENOMEM);
3596     }
3597
3598     return 0;
3599 }
3600
3601 static void fov_from_dfov(int format, float d_fov, float w, float h, float *h_fov, float *v_fov)
3602 {
3603     switch (format) {
3604     case STEREOGRAPHIC:
3605         {
3606             const float d = 0.5f * hypotf(w, h);
3607             const float l = d / (tanf(d_fov * M_PI / 720.f));
3608
3609             *h_fov = 2.f * atan2f(w * 0.5f, l) * 360.f / M_PI;
3610             *v_fov = 2.f * atan2f(h * 0.5f, l) * 360.f / M_PI;
3611         }
3612         break;
3613     case FISHEYE:
3614         {
3615             const float d = 0.5f * hypotf(w, h);
3616
3617             *h_fov = d / w * d_fov;
3618             *v_fov = d / h * d_fov;
3619         }
3620         break;
3621     case FLAT:
3622     default:
3623         {
3624             const float da = tanf(0.5f * FFMIN(d_fov, 359.f) * M_PI / 180.f);
3625             const float d = hypotf(w, h);
3626
3627             *h_fov = atan2f(da * w, d) * 360.f / M_PI;
3628             *v_fov = atan2f(da * h, d) * 360.f / M_PI;
3629
3630             if (*h_fov < 0.f)
3631                 *h_fov += 360.f;
3632             if (*v_fov < 0.f)
3633                 *v_fov += 360.f;
3634         }
3635         break;
3636     }
3637 }
3638
3639 static void set_dimensions(int *outw, int *outh, int w, int h, const AVPixFmtDescriptor *desc)
3640 {
3641     outw[1] = outw[2] = FF_CEIL_RSHIFT(w, desc->log2_chroma_w);
3642     outw[0] = outw[3] = w;
3643     outh[1] = outh[2] = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);
3644     outh[0] = outh[3] = h;
3645 }
3646
3647 // Calculate remap data
3648 static av_always_inline int v360_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
3649 {
3650     V360Context *s = ctx->priv;
3651
3652     for (int p = 0; p < s->nb_allocated; p++) {
3653         const int max_value = s->max_value;
3654         const int width = s->pr_width[p];
3655         const int uv_linesize = s->uv_linesize[p];
3656         const int height = s->pr_height[p];
3657         const int in_width = s->inplanewidth[p];
3658         const int in_height = s->inplaneheight[p];
3659         const int slice_start = (height *  jobnr     ) / nb_jobs;
3660         const int slice_end   = (height * (jobnr + 1)) / nb_jobs;
3661         float du, dv;
3662         float vec[3];
3663         XYRemap rmap;
3664
3665         for (int j = slice_start; j < slice_end; j++) {
3666             for (int i = 0; i < width; i++) {
3667                 int16_t *u = s->u[p] + (j * uv_linesize + i) * s->elements;
3668                 int16_t *v = s->v[p] + (j * uv_linesize + i) * s->elements;
3669                 int16_t *ker = s->ker[p] + (j * uv_linesize + i) * s->elements;
3670                 uint8_t *mask8 = p ? NULL : s->mask + (j * s->pr_width[0] + i);
3671                 uint16_t *mask16 = p ? NULL : (uint16_t *)s->mask + (j * s->pr_width[0] + i);
3672                 int in_mask, out_mask;
3673
3674                 if (s->out_transpose)
3675                     out_mask = s->out_transform(s, j, i, height, width, vec);
3676                 else
3677                     out_mask = s->out_transform(s, i, j, width, height, vec);
3678                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
3679                 rotate(s->rot_mat, vec);
3680                 av_assert1(!isnan(vec[0]) && !isnan(vec[1]) && !isnan(vec[2]));
3681                 normalize_vector(vec);
3682                 mirror(s->output_mirror_modifier, vec);
3683                 if (s->in_transpose)
3684                     in_mask = s->in_transform(s, vec, in_height, in_width, rmap.v, rmap.u, &du, &dv);
3685                 else
3686                     in_mask = s->in_transform(s, vec, in_width, in_height, rmap.u, rmap.v, &du, &dv);
3687                 av_assert1(!isnan(du) && !isnan(dv));
3688                 s->calculate_kernel(du, dv, &rmap, u, v, ker);
3689
3690                 if (!p && s->mask) {
3691                     if (s->mask_size == 1) {
3692                         mask8[0] = 255 * (out_mask & in_mask);
3693                     } else {
3694                         mask16[0] = max_value * (out_mask & in_mask);
3695                     }
3696                 }
3697             }
3698         }
3699     }
3700
3701     return 0;
3702 }
3703
3704 static int config_output(AVFilterLink *outlink)
3705 {
3706     AVFilterContext *ctx = outlink->src;
3707     AVFilterLink *inlink = ctx->inputs[0];
3708     V360Context *s = ctx->priv;
3709     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
3710     const int depth = desc->comp[0].depth;
3711     const int sizeof_mask = s->mask_size = (depth + 7) >> 3;
3712     int sizeof_uv;
3713     int sizeof_ker;
3714     int err;
3715     int h, w;
3716     int in_offset_h, in_offset_w;
3717     int out_offset_h, out_offset_w;
3718     float hf, wf;
3719     int (*prepare_out)(AVFilterContext *ctx);
3720     int have_alpha;
3721
3722     s->max_value = (1 << depth) - 1;
3723     s->input_mirror_modifier[0] = s->ih_flip ? -1.f : 1.f;
3724     s->input_mirror_modifier[1] = s->iv_flip ? -1.f : 1.f;
3725
3726     switch (s->interp) {
3727     case NEAREST:
3728         s->calculate_kernel = nearest_kernel;
3729         s->remap_slice = depth <= 8 ? remap1_8bit_slice : remap1_16bit_slice;
3730         s->elements = 1;
3731         sizeof_uv = sizeof(int16_t) * s->elements;
3732         sizeof_ker = 0;
3733         break;
3734     case BILINEAR:
3735         s->calculate_kernel = bilinear_kernel;
3736         s->remap_slice = depth <= 8 ? remap2_8bit_slice : remap2_16bit_slice;
3737         s->elements = 2 * 2;
3738         sizeof_uv = sizeof(int16_t) * s->elements;
3739         sizeof_ker = sizeof(int16_t) * s->elements;
3740         break;
3741     case LAGRANGE9:
3742         s->calculate_kernel = lagrange_kernel;
3743         s->remap_slice = depth <= 8 ? remap3_8bit_slice : remap3_16bit_slice;
3744         s->elements = 3 * 3;
3745         sizeof_uv = sizeof(int16_t) * s->elements;
3746         sizeof_ker = sizeof(int16_t) * s->elements;
3747         break;
3748     case BICUBIC:
3749         s->calculate_kernel = bicubic_kernel;
3750         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3751         s->elements = 4 * 4;
3752         sizeof_uv = sizeof(int16_t) * s->elements;
3753         sizeof_ker = sizeof(int16_t) * s->elements;
3754         break;
3755     case LANCZOS:
3756         s->calculate_kernel = lanczos_kernel;
3757         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3758         s->elements = 4 * 4;
3759         sizeof_uv = sizeof(int16_t) * s->elements;
3760         sizeof_ker = sizeof(int16_t) * s->elements;
3761         break;
3762     case SPLINE16:
3763         s->calculate_kernel = spline16_kernel;
3764         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3765         s->elements = 4 * 4;
3766         sizeof_uv = sizeof(int16_t) * s->elements;
3767         sizeof_ker = sizeof(int16_t) * s->elements;
3768         break;
3769     case GAUSSIAN:
3770         s->calculate_kernel = gaussian_kernel;
3771         s->remap_slice = depth <= 8 ? remap4_8bit_slice : remap4_16bit_slice;
3772         s->elements = 4 * 4;
3773         sizeof_uv = sizeof(int16_t) * s->elements;
3774         sizeof_ker = sizeof(int16_t) * s->elements;
3775         break;
3776     default:
3777         av_assert0(0);
3778     }
3779
3780     ff_v360_init(s, depth);
3781
3782     for (int order = 0; order < NB_RORDERS; order++) {
3783         const char c = s->rorder[order];
3784         int rorder;
3785
3786         if (c == '\0') {
3787             av_log(ctx, AV_LOG_WARNING,
3788                    "Incomplete rorder option. Direction for all 3 rotation orders should be specified. Switching to default rorder.\n");
3789             s->rotation_order[0] = YAW;
3790             s->rotation_order[1] = PITCH;
3791             s->rotation_order[2] = ROLL;
3792             break;
3793         }
3794
3795         rorder = get_rorder(c);
3796         if (rorder == -1) {
3797             av_log(ctx, AV_LOG_WARNING,
3798                    "Incorrect rotation order symbol '%c' in rorder option. Switching to default rorder.\n", c);
3799             s->rotation_order[0] = YAW;
3800             s->rotation_order[1] = PITCH;
3801             s->rotation_order[2] = ROLL;
3802             break;
3803         }
3804
3805         s->rotation_order[order] = rorder;
3806     }
3807
3808     switch (s->in_stereo) {
3809     case STEREO_2D:
3810         w = inlink->w;
3811         h = inlink->h;
3812         in_offset_w = in_offset_h = 0;
3813         break;
3814     case STEREO_SBS:
3815         w = inlink->w / 2;
3816         h = inlink->h;
3817         in_offset_w = w;
3818         in_offset_h = 0;
3819         break;
3820     case STEREO_TB:
3821         w = inlink->w;
3822         h = inlink->h / 2;
3823         in_offset_w = 0;
3824         in_offset_h = h;
3825         break;
3826     default:
3827         av_assert0(0);
3828     }
3829
3830     set_dimensions(s->inplanewidth, s->inplaneheight, w, h, desc);
3831     set_dimensions(s->in_offset_w, s->in_offset_h, in_offset_w, in_offset_h, desc);
3832
3833     s->in_width = s->inplanewidth[0];
3834     s->in_height = s->inplaneheight[0];
3835
3836     if (s->id_fov > 0.f)
3837         fov_from_dfov(s->in, s->id_fov, w, h, &s->ih_fov, &s->iv_fov);
3838
3839     if (s->in_transpose)
3840         FFSWAP(int, s->in_width, s->in_height);
3841
3842     switch (s->in) {
3843     case EQUIRECTANGULAR:
3844         s->in_transform = xyz_to_equirect;
3845         err = 0;
3846         wf = w;
3847         hf = h;
3848         break;
3849     case CUBEMAP_3_2:
3850         s->in_transform = xyz_to_cube3x2;
3851         err = prepare_cube_in(ctx);
3852         wf = w / 3.f * 4.f;
3853         hf = h;
3854         break;
3855     case CUBEMAP_1_6:
3856         s->in_transform = xyz_to_cube1x6;
3857         err = prepare_cube_in(ctx);
3858         wf = w * 4.f;
3859         hf = h / 3.f;
3860         break;
3861     case CUBEMAP_6_1:
3862         s->in_transform = xyz_to_cube6x1;
3863         err = prepare_cube_in(ctx);
3864         wf = w / 3.f * 2.f;
3865         hf = h * 2.f;
3866         break;
3867     case EQUIANGULAR:
3868         s->in_transform = xyz_to_eac;
3869         err = prepare_eac_in(ctx);
3870         wf = w;
3871         hf = h / 9.f * 8.f;
3872         break;
3873     case FLAT:
3874         s->in_transform = xyz_to_flat;
3875         err = prepare_flat_in(ctx);
3876         wf = w;
3877         hf = h;
3878         break;
3879     case PERSPECTIVE:
3880     case PANNINI:
3881         av_log(ctx, AV_LOG_ERROR, "Supplied format is not accepted as input.\n");
3882         return AVERROR(EINVAL);
3883     case DUAL_FISHEYE:
3884         s->in_transform = xyz_to_dfisheye;
3885         err = 0;
3886         wf = w;
3887         hf = h;
3888         break;
3889     case BARREL:
3890         s->in_transform = xyz_to_barrel;
3891         err = 0;
3892         wf = w / 5.f * 4.f;
3893         hf = h;
3894         break;
3895     case STEREOGRAPHIC:
3896         s->in_transform = xyz_to_stereographic;
3897         err = prepare_stereographic_in(ctx);
3898         wf = w;
3899         hf = h / 2.f;
3900         break;
3901     case MERCATOR:
3902         s->in_transform = xyz_to_mercator;
3903         err = 0;
3904         wf = w;
3905         hf = h / 2.f;
3906         break;
3907     case BALL:
3908         s->in_transform = xyz_to_ball;
3909         err = 0;
3910         wf = w;
3911         hf = h / 2.f;
3912         break;
3913     case HAMMER:
3914         s->in_transform = xyz_to_hammer;
3915         err = 0;
3916         wf = w;
3917         hf = h;
3918         break;
3919     case SINUSOIDAL:
3920         s->in_transform = xyz_to_sinusoidal;
3921         err = 0;
3922         wf = w;
3923         hf = h;
3924         break;
3925     case FISHEYE:
3926         s->in_transform = xyz_to_fisheye;
3927         err = prepare_fisheye_in(ctx);
3928         wf = w * 2;
3929         hf = h;
3930         break;
3931     case CYLINDRICAL:
3932         s->in_transform = xyz_to_cylindrical;
3933         err = prepare_cylindrical_in(ctx);
3934         wf = w;
3935         hf = h * 2.f;
3936         break;
3937     case TETRAHEDRON:
3938         s->in_transform = xyz_to_tetrahedron;
3939         err = 0;
3940         wf = w;
3941         hf = h;
3942         break;
3943     case BARREL_SPLIT:
3944         s->in_transform = xyz_to_barrelsplit;
3945         err = 0;
3946         wf = w * 4.f / 3.f;
3947         hf = h;
3948         break;
3949     case TSPYRAMID:
3950         s->in_transform = xyz_to_tspyramid;
3951         err = 0;
3952         wf = w;
3953         hf = h;
3954         break;
3955     case HEQUIRECTANGULAR:
3956         s->in_transform = xyz_to_hequirect;
3957         err = 0;
3958         wf = w * 2.f;
3959         hf = h;
3960         break;
3961     default:
3962         av_log(ctx, AV_LOG_ERROR, "Specified input format is not handled.\n");
3963         return AVERROR_BUG;
3964     }
3965
3966     if (err != 0) {
3967         return err;
3968     }
3969
3970     switch (s->out) {
3971     case EQUIRECTANGULAR:
3972         s->out_transform = equirect_to_xyz;
3973         prepare_out = NULL;
3974         w = lrintf(wf);
3975         h = lrintf(hf);
3976         break;
3977     case CUBEMAP_3_2:
3978         s->out_transform = cube3x2_to_xyz;
3979         prepare_out = prepare_cube_out;
3980         w = lrintf(wf / 4.f * 3.f);
3981         h = lrintf(hf);
3982         break;
3983     case CUBEMAP_1_6:
3984         s->out_transform = cube1x6_to_xyz;
3985         prepare_out = prepare_cube_out;
3986         w = lrintf(wf / 4.f);
3987         h = lrintf(hf * 3.f);
3988         break;
3989     case CUBEMAP_6_1:
3990         s->out_transform = cube6x1_to_xyz;
3991         prepare_out = prepare_cube_out;
3992         w = lrintf(wf / 2.f * 3.f);
3993         h = lrintf(hf / 2.f);
3994         break;
3995     case EQUIANGULAR:
3996         s->out_transform = eac_to_xyz;
3997         prepare_out = prepare_eac_out;
3998         w = lrintf(wf);
3999         h = lrintf(hf / 8.f * 9.f);
4000         break;
4001     case FLAT:
4002         s->out_transform = flat_to_xyz;
4003         prepare_out = prepare_flat_out;
4004         w = lrintf(wf);
4005         h = lrintf(hf);
4006         break;
4007     case DUAL_FISHEYE:
4008         s->out_transform = dfisheye_to_xyz;
4009         prepare_out = NULL;
4010         w = lrintf(wf);
4011         h = lrintf(hf);
4012         break;
4013     case BARREL:
4014         s->out_transform = barrel_to_xyz;
4015         prepare_out = NULL;
4016         w = lrintf(wf / 4.f * 5.f);
4017         h = lrintf(hf);
4018         break;
4019     case STEREOGRAPHIC:
4020         s->out_transform = stereographic_to_xyz;
4021         prepare_out = prepare_stereographic_out;
4022         w = lrintf(wf);
4023         h = lrintf(hf * 2.f);
4024         break;
4025     case MERCATOR:
4026         s->out_transform = mercator_to_xyz;
4027         prepare_out = NULL;
4028         w = lrintf(wf);
4029         h = lrintf(hf * 2.f);
4030         break;
4031     case BALL:
4032         s->out_transform = ball_to_xyz;
4033         prepare_out = NULL;
4034         w = lrintf(wf);
4035         h = lrintf(hf * 2.f);
4036         break;
4037     case HAMMER:
4038         s->out_transform = hammer_to_xyz;
4039         prepare_out = NULL;
4040         w = lrintf(wf);
4041         h = lrintf(hf);
4042         break;
4043     case SINUSOIDAL:
4044         s->out_transform = sinusoidal_to_xyz;
4045         prepare_out = NULL;
4046         w = lrintf(wf);
4047         h = lrintf(hf);
4048         break;
4049     case FISHEYE:
4050         s->out_transform = fisheye_to_xyz;
4051         prepare_out = prepare_fisheye_out;
4052         w = lrintf(wf * 0.5f);
4053         h = lrintf(hf);
4054         break;
4055     case PANNINI:
4056         s->out_transform = pannini_to_xyz;
4057         prepare_out = NULL;
4058         w = lrintf(wf);
4059         h = lrintf(hf);
4060         break;
4061     case CYLINDRICAL:
4062         s->out_transform = cylindrical_to_xyz;
4063         prepare_out = prepare_cylindrical_out;
4064         w = lrintf(wf);
4065         h = lrintf(hf * 0.5f);
4066         break;
4067     case PERSPECTIVE:
4068         s->out_transform = perspective_to_xyz;
4069         prepare_out = NULL;
4070         w = lrintf(wf / 2.f);
4071         h = lrintf(hf);
4072         break;
4073     case TETRAHEDRON:
4074         s->out_transform = tetrahedron_to_xyz;
4075         prepare_out = NULL;
4076         w = lrintf(wf);
4077         h = lrintf(hf);
4078         break;
4079     case BARREL_SPLIT:
4080         s->out_transform = barrelsplit_to_xyz;
4081         prepare_out = NULL;
4082         w = lrintf(wf / 4.f * 3.f);
4083         h = lrintf(hf);
4084         break;
4085     case TSPYRAMID:
4086         s->out_transform = tspyramid_to_xyz;
4087         prepare_out = NULL;
4088         w = lrintf(wf);
4089         h = lrintf(hf);
4090         break;
4091     case HEQUIRECTANGULAR:
4092         s->out_transform = hequirect_to_xyz;
4093         prepare_out = NULL;
4094         w = lrintf(wf / 2.f);
4095         h = lrintf(hf);
4096         break;
4097     default:
4098         av_log(ctx, AV_LOG_ERROR, "Specified output format is not handled.\n");
4099         return AVERROR_BUG;
4100     }
4101
4102     // Override resolution with user values if specified
4103     if (s->width > 0 && s->height <= 0 && s->h_fov > 0.f && s->v_fov > 0.f &&
4104         s->out == FLAT && s->d_fov == 0.f) {
4105         w = s->width;
4106         h = w / tanf(s->h_fov * M_PI / 360.f) * tanf(s->v_fov * M_PI / 360.f);
4107     } else if (s->width <= 0 && s->height > 0 && s->h_fov > 0.f && s->v_fov > 0.f &&
4108         s->out == FLAT && s->d_fov == 0.f) {
4109         h = s->height;
4110         w = h / tanf(s->v_fov * M_PI / 360.f) * tanf(s->h_fov * M_PI / 360.f);
4111     } else if (s->width > 0 && s->height > 0) {
4112         w = s->width;
4113         h = s->height;
4114     } else if (s->width > 0 || s->height > 0) {
4115         av_log(ctx, AV_LOG_ERROR, "Both width and height values should be specified.\n");
4116         return AVERROR(EINVAL);
4117     } else {
4118         if (s->out_transpose)
4119             FFSWAP(int, w, h);
4120
4121         if (s->in_transpose)
4122             FFSWAP(int, w, h);
4123     }
4124
4125     s->width  = w;
4126     s->height = h;
4127
4128     if (s->d_fov > 0.f)
4129         fov_from_dfov(s->out, s->d_fov, w, h, &s->h_fov, &s->v_fov);
4130
4131     if (prepare_out) {
4132         err = prepare_out(ctx);
4133         if (err != 0)
4134             return err;
4135     }
4136
4137     set_dimensions(s->pr_width, s->pr_height, w, h, desc);
4138
4139     switch (s->out_stereo) {
4140     case STEREO_2D:
4141         out_offset_w = out_offset_h = 0;
4142         break;
4143     case STEREO_SBS:
4144         out_offset_w = w;
4145         out_offset_h = 0;
4146         w *= 2;
4147         break;
4148     case STEREO_TB:
4149         out_offset_w = 0;
4150         out_offset_h = h;
4151         h *= 2;
4152         break;
4153     default:
4154         av_assert0(0);
4155     }
4156
4157     set_dimensions(s->out_offset_w, s->out_offset_h, out_offset_w, out_offset_h, desc);
4158     set_dimensions(s->planewidth, s->planeheight, w, h, desc);
4159
4160     for (int i = 0; i < 4; i++)
4161         s->uv_linesize[i] = FFALIGN(s->pr_width[i], 8);
4162
4163     outlink->h = h;
4164     outlink->w = w;
4165
4166     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
4167     have_alpha   = !!(desc->flags & AV_PIX_FMT_FLAG_ALPHA);
4168
4169     if (desc->log2_chroma_h == desc->log2_chroma_w && desc->log2_chroma_h == 0) {
4170         s->nb_allocated = 1;
4171         s->map[0] = s->map[1] = s->map[2] = s->map[3] = 0;
4172     } else {
4173         s->nb_allocated = 2;
4174         s->map[0] = s->map[3] = 0;
4175         s->map[1] = s->map[2] = 1;
4176     }
4177
4178     for (int i = 0; i < s->nb_allocated; i++)
4179         allocate_plane(s, sizeof_uv, sizeof_ker, sizeof_mask * have_alpha * s->alpha, i);
4180
4181     calculate_rotation_matrix(s->yaw, s->pitch, s->roll, s->rot_mat, s->rotation_order);
4182     set_mirror_modifier(s->h_flip, s->v_flip, s->d_flip, s->output_mirror_modifier);
4183
4184     ctx->internal->execute(ctx, v360_slice, NULL, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
4185
4186     return 0;
4187 }
4188
4189 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
4190 {
4191     AVFilterContext *ctx = inlink->dst;
4192     AVFilterLink *outlink = ctx->outputs[0];
4193     V360Context *s = ctx->priv;
4194     AVFrame *out;
4195     ThreadData td;
4196
4197     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
4198     if (!out) {
4199         av_frame_free(&in);
4200         return AVERROR(ENOMEM);
4201     }
4202     av_frame_copy_props(out, in);
4203
4204     td.in = in;
4205     td.out = out;
4206
4207     ctx->internal->execute(ctx, s->remap_slice, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
4208
4209     av_frame_free(&in);
4210     return ff_filter_frame(outlink, out);
4211 }
4212
4213 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
4214                            char *res, int res_len, int flags)
4215 {
4216     int ret;
4217
4218     ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
4219     if (ret < 0)
4220         return ret;
4221
4222     return config_output(ctx->outputs[0]);
4223 }
4224
4225 static av_cold void uninit(AVFilterContext *ctx)
4226 {
4227     V360Context *s = ctx->priv;
4228
4229     for (int p = 0; p < s->nb_allocated; p++) {
4230         av_freep(&s->u[p]);
4231         av_freep(&s->v[p]);
4232         av_freep(&s->ker[p]);
4233     }
4234     av_freep(&s->mask);
4235 }
4236
4237 static const AVFilterPad inputs[] = {
4238     {
4239         .name         = "default",
4240         .type         = AVMEDIA_TYPE_VIDEO,
4241         .filter_frame = filter_frame,
4242     },
4243     { NULL }
4244 };
4245
4246 static const AVFilterPad outputs[] = {
4247     {
4248         .name         = "default",
4249         .type         = AVMEDIA_TYPE_VIDEO,
4250         .config_props = config_output,
4251     },
4252     { NULL }
4253 };
4254
4255 AVFilter ff_vf_v360 = {
4256     .name          = "v360",
4257     .description   = NULL_IF_CONFIG_SMALL("Convert 360 projection of video."),
4258     .priv_size     = sizeof(V360Context),
4259     .uninit        = uninit,
4260     .query_formats = query_formats,
4261     .inputs        = inputs,
4262     .outputs       = outputs,
4263     .priv_class    = &v360_class,
4264     .flags         = AVFILTER_FLAG_SLICE_THREADS,
4265     .process_command = process_command,
4266 };