1/* 2 * Copyright (c) 2002 Michael Niedermayer <michaelni@gmx.at> 3 * Copyright (c) 2013 Paul B Mahol 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * FFmpeg is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with FFmpeg; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22#include "libavutil/eval.h" 23#include "libavutil/imgutils.h" 24#include "libavutil/pixdesc.h" 25#include "libavutil/opt.h" 26#include "avfilter.h" 27#include "formats.h" 28#include "internal.h" 29#include "video.h" 30 31#define SUB_PIXEL_BITS 8 32#define SUB_PIXELS (1 << SUB_PIXEL_BITS) 33#define COEFF_BITS 11 34 35#define LINEAR 0 36#define CUBIC 1 37 38typedef struct PerspectiveContext { 39 const AVClass *class; 40 char *expr_str[4][2]; 41 double ref[4][2]; 42 int32_t (*pv)[2]; 43 int32_t coeff[SUB_PIXELS][4]; 44 int interpolation; 45 int linesize[4]; 46 int height[4]; 47 int hsub, vsub; 48 int nb_planes; 49 50 void (*perspective)(struct PerspectiveContext *s, 51 uint8_t *dst, int dst_linesize, 52 uint8_t *src, int src_linesize, 53 int w, int h, int hsub, int vsub); 54} PerspectiveContext; 55 56#define OFFSET(x) offsetof(PerspectiveContext, x) 57#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM 58 59static const AVOption perspective_options[] = { 60 { "x0", "set top left x coordinate", OFFSET(expr_str[0][0]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, 61 { "y0", "set top left y coordinate", OFFSET(expr_str[0][1]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, 62 { "x1", "set top right x coordinate", OFFSET(expr_str[1][0]), AV_OPT_TYPE_STRING, {.str="W"}, 0, 0, FLAGS }, 63 { "y1", "set top right y coordinate", OFFSET(expr_str[1][1]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, 64 { "x2", "set bottom left x coordinate", OFFSET(expr_str[2][0]), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS }, 65 { "y2", "set bottom left y coordinate", OFFSET(expr_str[2][1]), AV_OPT_TYPE_STRING, {.str="H"}, 0, 0, FLAGS }, 66 { "x3", "set bottom right x coordinate", OFFSET(expr_str[3][0]), AV_OPT_TYPE_STRING, {.str="W"}, 0, 0, FLAGS }, 67 { "y3", "set bottom right y coordinate", OFFSET(expr_str[3][1]), AV_OPT_TYPE_STRING, {.str="H"}, 0, 0, FLAGS }, 68 { "interpolation", "set interpolation", OFFSET(interpolation), AV_OPT_TYPE_INT, {.i64=LINEAR}, 0, 1, FLAGS, "interpolation" }, 69 { "linear", "", 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "interpolation" }, 70 { "cubic", "", 0, AV_OPT_TYPE_CONST, {.i64=CUBIC}, 0, 0, FLAGS, "interpolation" }, 71 { NULL } 72}; 73 74AVFILTER_DEFINE_CLASS(perspective); 75 76static int query_formats(AVFilterContext *ctx) 77{ 78 static const enum AVPixelFormat pix_fmts[] = { 79 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P, 80 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P,AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P, 81 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, 82 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE 83 }; 84 85 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); 86 return 0; 87} 88 89static inline double get_coeff(double d) 90{ 91 double coeff, A = -0.60; 92 93 d = fabs(d); 94 95 if (d < 1.0) 96 coeff = (1.0 - (A + 3.0) * d * d + (A + 2.0) * d * d * d); 97 else if (d < 2.0) 98 coeff = (-4.0 * A + 8.0 * A * d - 5.0 * A * d * d + A * d * d * d); 99 else 100 coeff = 0.0; 101 102 return coeff; 103} 104 105static const char *const var_names[] = { "W", "H", NULL }; 106enum { VAR_W, VAR_H, VAR_VARS_NB }; 107 108static int config_input(AVFilterLink *inlink) 109{ 110 double x0, x1, x2, x3, x4, x5, x6, x7, q; 111 AVFilterContext *ctx = inlink->dst; 112 PerspectiveContext *s = ctx->priv; 113 double (*ref)[2] = s->ref; 114 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); 115 double values[VAR_VARS_NB] = { [VAR_W] = inlink->w, [VAR_H] = inlink->h }; 116 int h = inlink->h; 117 int w = inlink->w; 118 int x, y, i, j, ret; 119 120 for (i = 0; i < 4; i++) { 121 for (j = 0; j < 2; j++) { 122 if (!s->expr_str[i][j]) 123 return AVERROR(EINVAL); 124 ret = av_expr_parse_and_eval(&s->ref[i][j], s->expr_str[i][j], 125 var_names, &values[0], 126 NULL, NULL, NULL, NULL, 127 0, 0, ctx); 128 if (ret < 0) 129 return ret; 130 } 131 } 132 133 s->hsub = desc->log2_chroma_w; 134 s->vsub = desc->log2_chroma_h; 135 s->nb_planes = av_pix_fmt_count_planes(inlink->format); 136 if ((ret = av_image_fill_linesizes(s->linesize, inlink->format, inlink->w)) < 0) 137 return ret; 138 139 s->height[1] = s->height[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); 140 s->height[0] = s->height[3] = inlink->h; 141 142 s->pv = av_realloc_f(s->pv, w * h, 2 * sizeof(*s->pv)); 143 if (!s->pv) 144 return AVERROR(ENOMEM); 145 146 x6 = ((ref[0][0] - ref[1][0] - ref[2][0] + ref[3][0]) * 147 (ref[2][1] - ref[3][1]) - 148 ( ref[0][1] - ref[1][1] - ref[2][1] + ref[3][1]) * 149 (ref[2][0] - ref[3][0])) * h; 150 x7 = ((ref[0][1] - ref[1][1] - ref[2][1] + ref[3][1]) * 151 (ref[1][0] - ref[3][0]) - 152 ( ref[0][0] - ref[1][0] - ref[2][0] + ref[3][0]) * 153 (ref[1][1] - ref[3][1])) * w; 154 q = ( ref[1][0] - ref[3][0]) * (ref[2][1] - ref[3][1]) - 155 ( ref[2][0] - ref[3][0]) * (ref[1][1] - ref[3][1]); 156 157 x0 = q * (ref[1][0] - ref[0][0]) * h + x6 * ref[1][0]; 158 x1 = q * (ref[2][0] - ref[0][0]) * w + x7 * ref[2][0]; 159 x2 = q * ref[0][0] * w * h; 160 x3 = q * (ref[1][1] - ref[0][1]) * h + x6 * ref[1][1]; 161 x4 = q * (ref[2][1] - ref[0][1]) * w + x7 * ref[2][1]; 162 x5 = q * ref[0][1] * w * h; 163 164 for (y = 0; y < h; y++){ 165 for (x = 0; x < w; x++){ 166 int u, v; 167 168 u = (int)floor(SUB_PIXELS * (x0 * x + x1 * y + x2) / 169 (x6 * x + x7 * y + q * w * h) + 0.5); 170 v = (int)floor(SUB_PIXELS * (x3 * x + x4 * y + x5) / 171 (x6 * x + x7 * y + q * w * h) + 0.5); 172 173 s->pv[x + y * w][0] = u; 174 s->pv[x + y * w][1] = v; 175 } 176 } 177 178 for (i = 0; i < SUB_PIXELS; i++){ 179 double d = i / (double)SUB_PIXELS; 180 double temp[4]; 181 double sum = 0; 182 183 for (j = 0; j < 4; j++) 184 temp[j] = get_coeff(j - d - 1); 185 186 for (j = 0; j < 4; j++) 187 sum += temp[j]; 188 189 for (j = 0; j < 4; j++) 190 s->coeff[i][j] = (int)floor((1 << COEFF_BITS) * temp[j] / sum + 0.5); 191 } 192 193 return 0; 194} 195 196static void resample_cubic(PerspectiveContext *s, 197 uint8_t *dst, int dst_linesize, 198 uint8_t *src, int src_linesize, 199 int w, int h, int hsub, int vsub) 200{ 201 const int linesize = s->linesize[0]; 202 int x, y; 203 204 for (y = 0; y < h; y++) { 205 int sy = y << vsub; 206 for (x = 0; x < w; x++) { 207 int u, v, subU, subV, sum, sx; 208 209 sx = x << hsub; 210 u = s->pv[sx + sy * linesize][0] >> hsub; 211 v = s->pv[sx + sy * linesize][1] >> vsub; 212 subU = u & (SUB_PIXELS - 1); 213 subV = v & (SUB_PIXELS - 1); 214 u >>= SUB_PIXEL_BITS; 215 v >>= SUB_PIXEL_BITS; 216 217 if (u > 0 && v > 0 && u < w - 2 && v < h - 2){ 218 const int index = u + v*src_linesize; 219 const int a = s->coeff[subU][0]; 220 const int b = s->coeff[subU][1]; 221 const int c = s->coeff[subU][2]; 222 const int d = s->coeff[subU][3]; 223 224 sum = s->coeff[subV][0] * (a * src[index - 1 - src_linesize] + b * src[index - 0 - src_linesize] + 225 c * src[index + 1 - src_linesize] + d * src[index + 2 - src_linesize]) + 226 s->coeff[subV][1] * (a * src[index - 1 ] + b * src[index - 0 ] + 227 c * src[index + 1 ] + d * src[index + 2 ]) + 228 s->coeff[subV][2] * (a * src[index - 1 + src_linesize] + b * src[index - 0 + src_linesize] + 229 c * src[index + 1 + src_linesize] + d * src[index + 2 + src_linesize]) + 230 s->coeff[subV][3] * (a * src[index - 1 + 2 * src_linesize] + b * src[index - 0 + 2 * src_linesize] + 231 c * src[index + 1 + 2 * src_linesize] + d * src[index + 2 + 2 * src_linesize]); 232 } else { 233 int dx, dy; 234 235 sum = 0; 236 237 for (dy = 0; dy < 4; dy++) { 238 int iy = v + dy - 1; 239 240 if (iy < 0) 241 iy = 0; 242 else if (iy >= h) 243 iy = h-1; 244 for (dx = 0; dx < 4; dx++) { 245 int ix = u + dx - 1; 246 247 if (ix < 0) 248 ix = 0; 249 else if (ix >= w) 250 ix = w - 1; 251 252 sum += s->coeff[subU][dx] * s->coeff[subV][dy] * src[ ix + iy * src_linesize]; 253 } 254 } 255 } 256 257 sum = (sum + (1<<(COEFF_BITS * 2 - 1))) >> (COEFF_BITS * 2); 258 sum = av_clip(sum, 0, 255); 259 dst[x + y * dst_linesize] = sum; 260 } 261 } 262} 263 264static void resample_linear(PerspectiveContext *s, 265 uint8_t *dst, int dst_linesize, 266 uint8_t *src, int src_linesize, 267 int w, int h, int hsub, int vsub) 268{ 269 const int linesize = s->linesize[0]; 270 int x, y; 271 272 for (y = 0; y < h; y++){ 273 int sy = y << vsub; 274 for (x = 0; x < w; x++){ 275 int u, v, subU, subV, sum, sx, index, subUI, subVI; 276 277 sx = x << hsub; 278 u = s->pv[sx + sy * linesize][0] >> hsub; 279 v = s->pv[sx + sy * linesize][1] >> vsub; 280 subU = u & (SUB_PIXELS - 1); 281 subV = v & (SUB_PIXELS - 1); 282 u >>= SUB_PIXEL_BITS; 283 v >>= SUB_PIXEL_BITS; 284 285 index = u + v * src_linesize; 286 subUI = SUB_PIXELS - subU; 287 subVI = SUB_PIXELS - subV; 288 289 if ((unsigned)u < (unsigned)(w - 1)){ 290 if((unsigned)v < (unsigned)(h - 1)){ 291 sum = subVI * (subUI * src[index] + subU * src[index + 1]) + 292 subV * (subUI * src[index + src_linesize] + subU * src[index + src_linesize + 1]); 293 sum = (sum + (1 << (SUB_PIXEL_BITS * 2 - 1)))>> (SUB_PIXEL_BITS * 2); 294 } else { 295 if (v < 0) 296 v = 0; 297 else 298 v = h - 1; 299 index = u + v * src_linesize; 300 sum = subUI * src[index] + subU * src[index + 1]; 301 sum = (sum + (1 << (SUB_PIXEL_BITS - 1))) >> SUB_PIXEL_BITS; 302 } 303 } else { 304 if (u < 0) 305 u = 0; 306 else 307 u = w - 1; 308 if ((unsigned)v < (unsigned)(h - 1)){ 309 index = u + v * src_linesize; 310 sum = subVI * src[index] + subV * src[index + src_linesize]; 311 sum = (sum + (1 << (SUB_PIXEL_BITS - 1))) >> SUB_PIXEL_BITS; 312 } else { 313 if (v < 0) 314 v = 0; 315 else 316 v = h - 1; 317 index = u + v * src_linesize; 318 sum = src[index]; 319 } 320 } 321 322 sum = av_clip(sum, 0, 255); 323 dst[x + y * dst_linesize] = sum; 324 } 325 } 326} 327 328static av_cold int init(AVFilterContext *ctx) 329{ 330 PerspectiveContext *s = ctx->priv; 331 332 switch (s->interpolation) { 333 case LINEAR: s->perspective = resample_linear; break; 334 case CUBIC: s->perspective = resample_cubic; break; 335 } 336 337 return 0; 338} 339 340static int filter_frame(AVFilterLink *inlink, AVFrame *frame) 341{ 342 AVFilterContext *ctx = inlink->dst; 343 AVFilterLink *outlink = ctx->outputs[0]; 344 PerspectiveContext *s = ctx->priv; 345 AVFrame *out; 346 int plane; 347 348 out = ff_get_video_buffer(outlink, outlink->w, outlink->h); 349 if (!out) { 350 av_frame_free(&frame); 351 return AVERROR(ENOMEM); 352 } 353 av_frame_copy_props(out, frame); 354 355 for (plane = 0; plane < s->nb_planes; plane++) { 356 int hsub = plane == 1 || plane == 2 ? s->hsub : 0; 357 int vsub = plane == 1 || plane == 2 ? s->vsub : 0; 358 s->perspective(s, out->data[plane], out->linesize[plane], 359 frame->data[plane], frame->linesize[plane], 360 s->linesize[plane], s->height[plane], hsub, vsub); 361 } 362 363 av_frame_free(&frame); 364 return ff_filter_frame(outlink, out); 365} 366 367static av_cold void uninit(AVFilterContext *ctx) 368{ 369 PerspectiveContext *s = ctx->priv; 370 371 av_freep(&s->pv); 372} 373 374static const AVFilterPad perspective_inputs[] = { 375 { 376 .name = "default", 377 .type = AVMEDIA_TYPE_VIDEO, 378 .filter_frame = filter_frame, 379 .config_props = config_input, 380 }, 381 { NULL } 382}; 383 384static const AVFilterPad perspective_outputs[] = { 385 { 386 .name = "default", 387 .type = AVMEDIA_TYPE_VIDEO, 388 }, 389 { NULL } 390}; 391 392AVFilter ff_vf_perspective = { 393 .name = "perspective", 394 .description = NULL_IF_CONFIG_SMALL("Correct the perspective of video."), 395 .priv_size = sizeof(PerspectiveContext), 396 .init = init, 397 .uninit = uninit, 398 .query_formats = query_formats, 399 .inputs = perspective_inputs, 400 .outputs = perspective_outputs, 401 .priv_class = &perspective_class, 402 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, 403}; 404