Signed-off-by: Paul B Mahol <one...@gmail.com> --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_panorama.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 libavfilter/vf_panorama.c
diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 740a640..36ade75 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -192,6 +192,7 @@ OBJS-$(CONFIG_OWDENOISE_FILTER) += vf_owdenoise.o OBJS-$(CONFIG_PAD_FILTER) += vf_pad.o OBJS-$(CONFIG_PALETTEGEN_FILTER) += vf_palettegen.o OBJS-$(CONFIG_PALETTEUSE_FILTER) += vf_paletteuse.o dualinput.o framesync.o +OBJS-$(CONFIG_PANORAMA_FILTER) += vf_panorama.o OBJS-$(CONFIG_PERMS_FILTER) += f_perms.o OBJS-$(CONFIG_PERSPECTIVE_FILTER) += vf_perspective.o OBJS-$(CONFIG_PHASE_FILTER) += vf_phase.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 6557612..c2ea7e5 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -213,6 +213,7 @@ void avfilter_register_all(void) REGISTER_FILTER(PAD, pad, vf); REGISTER_FILTER(PALETTEGEN, palettegen, vf); REGISTER_FILTER(PALETTEUSE, paletteuse, vf); + REGISTER_FILTER(PANORAMA, panorama, vf); REGISTER_FILTER(PERMS, perms, vf); REGISTER_FILTER(PERSPECTIVE, perspective, vf); REGISTER_FILTER(PHASE, phase, vf); diff --git a/libavfilter/vf_panorama.c b/libavfilter/vf_panorama.c new file mode 100644 index 0000000..4cc0831 --- /dev/null +++ b/libavfilter/vf_panorama.c @@ -0,0 +1,304 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/avassert.h" +#include "libavutil/eval.h" +#include "libavutil/imgutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/opt.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +enum Projections { + EQUIRECTANGULAR, + CUBIC, + NB_PROJECTIONS, +}; + +struct XYRemap { + int vi, ui; + int v2, u2; + float a, b, c, d; +}; + +typedef struct PanoramaContext { + const AVClass *class; + int in, out; + + int planewidth[4], planeheight[4]; + int inplanewidth[4], inplaneheight[4]; + int nb_planes; + + struct XYRemap *remap[4]; + int (*panorama)(struct PanoramaContext *s, + const uint8_t *src, uint8_t *dst, + int width, int height, + int in_width, int in_height, + int in_linesize, int out_linesize, + struct XYRemap *remap); +} PanoramaContext; + +#define OFFSET(x) offsetof(PanoramaContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption panorama_options[] = { + { "i", "set input projection", OFFSET(in), AV_OPT_TYPE_INT, {.i64=EQUIRECTANGULAR}, 0, NB_PROJECTIONS-1, FLAGS }, + { "o", "set output projection", OFFSET(out), AV_OPT_TYPE_INT, {.i64=CUBIC}, 0, NB_PROJECTIONS-1, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(panorama); + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P,AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P, + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE + }; + + AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); + if (!fmts_list) + return AVERROR(ENOMEM); + return ff_set_common_formats(ctx, fmts_list); +} + +static void to_xyz(int i, int j, int face, float edge, float *x, float *y, float *z) +{ + float a = 2.0 * i / edge; + float b = 2.0 * j / edge; + + if (face == 0) { // back + *x = -1.0; + *y = 1.0 - a; + *z = 3.0 - b; + } else if (face == 1) { // left + *x = a - 3.0; + *y = -1.0; + *z = 3.0 - b; + } else if (face == 2) { // front + *x = 1.0; + *y = a - 5.0; + *z = 3.0 - b; + } else if (face == 3) { // right + *x = 7.0 - a; + *y = 1.0; + *z = 3.0 - b; + } else if (face == 4) { // top + *x = b - 1.0; + *y = a - 5.0; + *z = 1.0; + } else if (face == 5) { // bottom + *x = 5.0 - b; + *y = a - 5.0; + *z = -1.0; + } +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AVFilterLink *inlink = ctx->inputs[0]; + PanoramaContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + int p; + + if (s->in == EQUIRECTANGULAR && s->out == CUBIC) { + int w = inlink->w; + int h = inlink->w * 3 / 4; + + s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(h, desc->log2_chroma_h); + s->planeheight[0] = s->planeheight[3] = h; + s->planewidth[1] = s->planewidth[2] = FF_CEIL_RSHIFT(w, desc->log2_chroma_w); + s->planewidth[0] = s->planewidth[3] = w; + outlink->h = h; + outlink->w = w; + } else { + av_assert0(0); + } + + s->inplaneheight[1] = s->inplaneheight[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->inplaneheight[0] = s->inplaneheight[3] = inlink->h; + s->inplanewidth[1] = s->inplanewidth[2] = FF_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); + s->inplanewidth[0] = s->inplanewidth[3] = inlink->w; + s->nb_planes = av_pix_fmt_count_planes(inlink->format); + + for (p = 0; p < s->nb_planes; p++) + s->remap[p] = av_calloc(s->planewidth[p] * s->planeheight[p], sizeof(struct XYRemap)); + + for (p = 0; p < s->nb_planes; p++) { + int face, face2, start, end, ui, vi, u2, v2; + float theta, R, phi, uf, vf, mu, nu, x, y, z; + int edge = s->planewidth[p] / 4; + int width = s->planewidth[p]; + int in_width = s->inplanewidth[p]; + int in_height = s->inplaneheight[p]; + int i, j; + + for (i = 0; i < width; i++) { + face = i / edge; // 0 - back, 1 - left 2 - front, 3 - right + if (face == 2) { + start = 0; + end = edge * 3; + } else { + start = edge; + end = edge * 2; + } + + for (j = start; j < end; j++) { + struct XYRemap *r = &s->remap[p][j * width + i]; + + if (j < edge) + face2 = 4; // top + else if (j >= 2 * edge) + face2 = 5; // bottom + else + face2 = face; + + to_xyz(i, j, face2, edge, &x, &y, &z); + theta = atan2(y, x); + R = hypot(x , y); + phi = atan2(z , R); + uf = (2.0 * edge * (theta + M_PI) / M_PI); + vf = (2.0 * edge * (M_PI/2 - phi) / M_PI); + + ui = floor(uf); // coord of pixel to bottom left + vi = floor(vf); + u2 = ui + 1; // coords of pixel to top right + v2 = vi + 1; + mu = uf - ui; // fraction of way across pixel + nu = vf - vi; + r->vi = av_clip(vi, 0, in_height - 1); + r->ui = ui % in_width; + r->v2 = av_clip(v2, 0, in_height - 1); + r->u2 = u2 % in_width; + r->a = (1-mu)*(1-nu); + r->b = (mu)*(1-nu); + r->c = (1-mu)*nu; + r->d = mu*nu; + } + } + } + + return 0; +} + +static int erect2cubic(PanoramaContext *s, + const uint8_t *src, uint8_t *dst, + int width, int height, + int in_width, int in_height, + int in_linesize, int out_linesize, + struct XYRemap *remap) +{ + int i, j, edge = width / 4; // the length of each edge in pixels + int face, start, end; + float A, B, C, D; + + for (i = 0; i < width; i++) { + face = i / edge; // 0 - back, 1 - left 2 - front, 3 - right + if (face == 2) { + start = 0; + end = edge * 3; + } else { + start = edge; + end = edge * 2; + } + + for (j = start; j < end; j++) { + struct XYRemap *r = &remap[j * width + i]; + A = src[r->vi * in_linesize + r->ui]; + B = src[r->vi * in_linesize + r->u2]; + C = src[r->v2 * in_linesize + r->ui]; + D = src[r->v2 * in_linesize + r->u2]; + dst[j * out_linesize + i] = A * r->a + B * r->b + C * r->c + D * r->d; + } + } + return 0; +} + +static av_cold int init(AVFilterContext *ctx) +{ + PanoramaContext *s = ctx->priv; + + if (s->in == EQUIRECTANGULAR && s->out == CUBIC) { + s->panorama = erect2cubic; + } else { + av_log(ctx, AV_LOG_ERROR, "Unsupported input and output combination!\n"); + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + PanoramaContext *s = ctx->priv; + AVFrame *out; + int plane; + + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + + for (plane = 0; plane < s->nb_planes; plane++) { + s->panorama(s, in->data[plane], out->data[plane], + s->planewidth[plane], s->planeheight[plane], + s->inplanewidth[plane], s->inplaneheight[plane], + in->linesize[plane], out->linesize[plane], + s->remap[plane]); + } + + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static const AVFilterPad inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + }, + { NULL } +}; + +static const AVFilterPad outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_output, + }, + { NULL } +}; + +AVFilter ff_vf_panorama = { + .name = "panorama", + .description = NULL_IF_CONFIG_SMALL("Convert panorama projection of video."), + .priv_size = sizeof(PanoramaContext), + .init = init, + .query_formats = query_formats, + .inputs = inputs, + .outputs = outputs, + .priv_class = &panorama_class, +}; -- 1.9.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel