2 * Copyright (c) 2020 Paul B Mahol
4 * This file is part of FFmpeg.
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.
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.
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
21 #include "libavutil/imgutils.h"
22 #include "libavutil/eval.h"
23 #include "libavutil/opt.h"
24 #include "libavutil/pixfmt.h"
31 enum XFadeTransitions {
55 typedef struct XFadeContext {
79 void (*transitionf)(AVFilterContext *ctx, const AVFrame *a, const AVFrame *b, AVFrame *out, float progress,
80 int slice_start, int slice_end, int jobnr);
85 static const char *const var_names[] = { "X", "Y", "W", "H", "A", "B", "PLANE", "P", NULL };
86 enum { VAR_X, VAR_Y, VAR_W, VAR_H, VAR_A, VAR_B, VAR_PLANE, VAR_PROGRESS, VAR_VARS_NB };
88 typedef struct ThreadData {
94 static int query_formats(AVFilterContext *ctx)
96 static const enum AVPixelFormat pix_fmts[] = {
100 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8,
101 AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_GBRP9,
102 AV_PIX_FMT_YUV444P10,
103 AV_PIX_FMT_YUVA444P10,
104 AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GRAY10,
105 AV_PIX_FMT_YUV444P12,
106 AV_PIX_FMT_YUVA444P12,
107 AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GRAY12,
108 AV_PIX_FMT_YUV444P14, AV_PIX_FMT_GBRP14,
109 AV_PIX_FMT_YUV444P16,
110 AV_PIX_FMT_YUVA444P16,
111 AV_PIX_FMT_GBRP16, AV_PIX_FMT_GBRAP16, AV_PIX_FMT_GRAY16,
115 AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
117 return AVERROR(ENOMEM);
118 return ff_set_common_formats(ctx, fmts_list);
121 static av_cold void uninit(AVFilterContext *ctx)
123 XFadeContext *s = ctx->priv;
128 #define OFFSET(x) offsetof(XFadeContext, x)
129 #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
131 static const AVOption xfade_options[] = {
132 { "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=FADE}, -1, NB_TRANSITIONS-1, FLAGS, "transition" },
133 { "custom", "custom transition", 0, AV_OPT_TYPE_CONST, {.i64=CUSTOM}, 0, 0, FLAGS, "transition" },
134 { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" },
135 { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" },
136 { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" },
137 { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, "transition" },
138 { "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, "transition" },
139 { "slideleft", "slide left transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDELEFT}, 0, 0, FLAGS, "transition" },
140 { "slideright", "slide right transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDERIGHT}, 0, 0, FLAGS, "transition" },
141 { "slideup", "slide up transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEUP}, 0, 0, FLAGS, "transition" },
142 { "slidedown", "slide down transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEDOWN}, 0, 0, FLAGS, "transition" },
143 { "circlecrop", "circle crop transition", 0, AV_OPT_TYPE_CONST, {.i64=CIRCLECROP}, 0, 0, FLAGS, "transition" },
144 { "rectcrop", "rect crop transition", 0, AV_OPT_TYPE_CONST, {.i64=RECTCROP}, 0, 0, FLAGS, "transition" },
145 { "distance", "distance transition", 0, AV_OPT_TYPE_CONST, {.i64=DISTANCE}, 0, 0, FLAGS, "transition" },
146 { "fadeblack", "fadeblack transition", 0, AV_OPT_TYPE_CONST, {.i64=FADEBLACK}, 0, 0, FLAGS, "transition" },
147 { "fadewhite", "fadewhite transition", 0, AV_OPT_TYPE_CONST, {.i64=FADEWHITE}, 0, 0, FLAGS, "transition" },
148 { "radial", "radial transition", 0, AV_OPT_TYPE_CONST, {.i64=RADIAL}, 0, 0, FLAGS, "transition" },
149 { "smoothleft", "smoothleft transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHLEFT}, 0, 0, FLAGS, "transition" },
150 { "smoothright","smoothright transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHRIGHT},0, 0, FLAGS, "transition" },
151 { "smoothup", "smoothup transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHUP}, 0, 0, FLAGS, "transition" },
152 { "smoothdown", "smoothdown transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHDOWN}, 0, 0, FLAGS, "transition" },
153 { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS },
154 { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS },
155 { "expr", "set expression for custom transition", OFFSET(custom_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
159 AVFILTER_DEFINE_CLASS(xfade);
161 #define CUSTOM_TRANSITION(name, type, div) \
162 static void custom##name##_transition(AVFilterContext *ctx, \
163 const AVFrame *a, const AVFrame *b, AVFrame *out, \
165 int slice_start, int slice_end, int jobnr) \
167 XFadeContext *s = ctx->priv; \
168 const int height = slice_end - slice_start; \
170 double values[VAR_VARS_NB]; \
171 values[VAR_W] = out->width; \
172 values[VAR_H] = out->height; \
173 values[VAR_PROGRESS] = progress; \
175 for (int p = 0; p < s->nb_planes; p++) { \
176 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
177 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
178 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
180 values[VAR_PLANE] = p; \
182 for (int y = 0; y < height; y++) { \
183 values[VAR_Y] = slice_start + y; \
184 for (int x = 0; x < out->width; x++) { \
186 values[VAR_A] = xf0[x]; \
187 values[VAR_B] = xf1[x]; \
188 dst[x] = av_expr_eval(s->e, values, s); \
191 dst += out->linesize[p] / div; \
192 xf0 += a->linesize[p] / div; \
193 xf1 += b->linesize[p] / div; \
198 CUSTOM_TRANSITION(8, uint8_t, 1)
199 CUSTOM_TRANSITION(16, uint16_t, 2)
201 static inline float mix(float a, float b, float mix)
203 return a * mix + b * (1.f - mix);
206 static inline float smoothstep(float edge0, float edge1, float x)
210 t = av_clipf((x - edge0) / (edge1 - edge0), 0.f, 1.f);
212 return t * t * (3.f - 2.f * t);
215 #define FADE_TRANSITION(name, type, div) \
216 static void fade##name##_transition(AVFilterContext *ctx, \
217 const AVFrame *a, const AVFrame *b, AVFrame *out, \
219 int slice_start, int slice_end, int jobnr) \
221 XFadeContext *s = ctx->priv; \
222 const int height = slice_end - slice_start; \
224 for (int p = 0; p < s->nb_planes; p++) { \
225 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
226 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
227 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
229 for (int y = 0; y < height; y++) { \
230 for (int x = 0; x < out->width; x++) { \
231 dst[x] = mix(xf0[x], xf1[x], progress); \
234 dst += out->linesize[p] / div; \
235 xf0 += a->linesize[p] / div; \
236 xf1 += b->linesize[p] / div; \
241 FADE_TRANSITION(8, uint8_t, 1)
242 FADE_TRANSITION(16, uint16_t, 2)
244 #define WIPELEFT_TRANSITION(name, type, div) \
245 static void wipeleft##name##_transition(AVFilterContext *ctx, \
246 const AVFrame *a, const AVFrame *b, AVFrame *out, \
248 int slice_start, int slice_end, int jobnr) \
250 XFadeContext *s = ctx->priv; \
251 const int height = slice_end - slice_start; \
252 const int z = out->width * progress; \
254 for (int p = 0; p < s->nb_planes; p++) { \
255 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
256 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
257 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
259 for (int y = 0; y < height; y++) { \
260 for (int x = 0; x < out->width; x++) { \
261 dst[x] = x > z ? xf1[x] : xf0[x]; \
264 dst += out->linesize[p] / div; \
265 xf0 += a->linesize[p] / div; \
266 xf1 += b->linesize[p] / div; \
271 WIPELEFT_TRANSITION(8, uint8_t, 1)
272 WIPELEFT_TRANSITION(16, uint16_t, 2)
274 #define WIPERIGHT_TRANSITION(name, type, div) \
275 static void wiperight##name##_transition(AVFilterContext *ctx, \
276 const AVFrame *a, const AVFrame *b, AVFrame *out, \
278 int slice_start, int slice_end, int jobnr) \
280 XFadeContext *s = ctx->priv; \
281 const int height = slice_end - slice_start; \
282 const int z = out->width * (1.f - progress); \
284 for (int p = 0; p < s->nb_planes; p++) { \
285 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
286 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
287 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
289 for (int y = 0; y < height; y++) { \
290 for (int x = 0; x < out->width; x++) { \
291 dst[x] = x > z ? xf0[x] : xf1[x]; \
294 dst += out->linesize[p] / div; \
295 xf0 += a->linesize[p] / div; \
296 xf1 += b->linesize[p] / div; \
301 WIPERIGHT_TRANSITION(8, uint8_t, 1)
302 WIPERIGHT_TRANSITION(16, uint16_t, 2)
304 #define WIPEUP_TRANSITION(name, type, div) \
305 static void wipeup##name##_transition(AVFilterContext *ctx, \
306 const AVFrame *a, const AVFrame *b, AVFrame *out, \
308 int slice_start, int slice_end, int jobnr) \
310 XFadeContext *s = ctx->priv; \
311 const int height = slice_end - slice_start; \
312 const int z = out->height * progress; \
314 for (int p = 0; p < s->nb_planes; p++) { \
315 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
316 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
317 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
319 for (int y = 0; y < height; y++) { \
320 for (int x = 0; x < out->width; x++) { \
321 dst[x] = slice_start + y > z ? xf1[x] : xf0[x]; \
324 dst += out->linesize[p] / div; \
325 xf0 += a->linesize[p] / div; \
326 xf1 += b->linesize[p] / div; \
331 WIPEUP_TRANSITION(8, uint8_t, 1)
332 WIPEUP_TRANSITION(16, uint16_t, 2)
334 #define WIPEDOWN_TRANSITION(name, type, div) \
335 static void wipedown##name##_transition(AVFilterContext *ctx, \
336 const AVFrame *a, const AVFrame *b, AVFrame *out, \
338 int slice_start, int slice_end, int jobnr) \
340 XFadeContext *s = ctx->priv; \
341 const int height = slice_end - slice_start; \
342 const int z = out->height * (1.f - progress); \
344 for (int p = 0; p < s->nb_planes; p++) { \
345 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
346 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
347 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
349 for (int y = 0; y < height; y++) { \
350 for (int x = 0; x < out->width; x++) { \
351 dst[x] = slice_start + y > z ? xf0[x] : xf1[x]; \
354 dst += out->linesize[p] / div; \
355 xf0 += a->linesize[p] / div; \
356 xf1 += b->linesize[p] / div; \
361 WIPEDOWN_TRANSITION(8, uint8_t, 1)
362 WIPEDOWN_TRANSITION(16, uint16_t, 2)
364 #define SLIDELEFT_TRANSITION(name, type, div) \
365 static void slideleft##name##_transition(AVFilterContext *ctx, \
366 const AVFrame *a, const AVFrame *b, AVFrame *out, \
368 int slice_start, int slice_end, int jobnr) \
370 XFadeContext *s = ctx->priv; \
371 const int height = slice_end - slice_start; \
372 const int width = out->width; \
373 const int z = -progress * width; \
375 for (int p = 0; p < s->nb_planes; p++) { \
376 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
377 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
378 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
380 for (int y = 0; y < height; y++) { \
381 for (int x = 0; x < width; x++) { \
382 const int zx = z + x; \
383 const int zz = zx % width + width * (zx < 0); \
384 dst[x] = (zx > 0) && (zx < width) ? xf1[zz] : xf0[zz]; \
387 dst += out->linesize[p] / div; \
388 xf0 += a->linesize[p] / div; \
389 xf1 += b->linesize[p] / div; \
394 SLIDELEFT_TRANSITION(8, uint8_t, 1)
395 SLIDELEFT_TRANSITION(16, uint16_t, 2)
397 #define SLIDERIGHT_TRANSITION(name, type, div) \
398 static void slideright##name##_transition(AVFilterContext *ctx, \
399 const AVFrame *a, const AVFrame *b, AVFrame *out, \
401 int slice_start, int slice_end, int jobnr) \
403 XFadeContext *s = ctx->priv; \
404 const int height = slice_end - slice_start; \
405 const int width = out->width; \
406 const int z = progress * width; \
408 for (int p = 0; p < s->nb_planes; p++) { \
409 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
410 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
411 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
413 for (int y = 0; y < height; y++) { \
414 for (int x = 0; x < out->width; x++) { \
415 const int zx = z + x; \
416 const int zz = zx % width + width * (zx < 0); \
417 dst[x] = (zx > 0) && (zx < width) ? xf1[zz] : xf0[zz]; \
420 dst += out->linesize[p] / div; \
421 xf0 += a->linesize[p] / div; \
422 xf1 += b->linesize[p] / div; \
427 SLIDERIGHT_TRANSITION(8, uint8_t, 1)
428 SLIDERIGHT_TRANSITION(16, uint16_t, 2)
430 #define SLIDEUP_TRANSITION(name, type, div) \
431 static void slideup##name##_transition(AVFilterContext *ctx, \
432 const AVFrame *a, const AVFrame *b, AVFrame *out, \
434 int slice_start, int slice_end, int jobnr) \
436 XFadeContext *s = ctx->priv; \
437 const int height = out->height; \
438 const int z = -progress * height; \
440 for (int p = 0; p < s->nb_planes; p++) { \
441 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
443 for (int y = slice_start; y < slice_end; y++) { \
444 const int zy = z + y; \
445 const int zz = zy % height + height * (zy < 0); \
446 const type *xf0 = (const type *)(a->data[p] + zz * a->linesize[p]); \
447 const type *xf1 = (const type *)(b->data[p] + zz * b->linesize[p]); \
449 for (int x = 0; x < out->width; x++) { \
450 dst[x] = (zy > 0) && (zy < height) ? xf1[x] : xf0[x]; \
453 dst += out->linesize[p] / div; \
458 SLIDEUP_TRANSITION(8, uint8_t, 1)
459 SLIDEUP_TRANSITION(16, uint16_t, 2)
461 #define SLIDEDOWN_TRANSITION(name, type, div) \
462 static void slidedown##name##_transition(AVFilterContext *ctx, \
463 const AVFrame *a, const AVFrame *b, AVFrame *out, \
465 int slice_start, int slice_end, int jobnr) \
467 XFadeContext *s = ctx->priv; \
468 const int height = out->height; \
469 const int z = progress * height; \
471 for (int p = 0; p < s->nb_planes; p++) { \
472 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
474 for (int y = slice_start; y < slice_end; y++) { \
475 const int zy = z + y; \
476 const int zz = zy % height + height * (zy < 0); \
477 const type *xf0 = (const type *)(a->data[p] + zz * a->linesize[p]); \
478 const type *xf1 = (const type *)(b->data[p] + zz * b->linesize[p]); \
480 for (int x = 0; x < out->width; x++) { \
481 dst[x] = (zy > 0) && (zy < height) ? xf1[x] : xf0[x]; \
484 dst += out->linesize[p] / div; \
489 SLIDEDOWN_TRANSITION(8, uint8_t, 1)
490 SLIDEDOWN_TRANSITION(16, uint16_t, 2)
492 #define CIRCLECROP_TRANSITION(name, type, div) \
493 static void circlecrop##name##_transition(AVFilterContext *ctx, \
494 const AVFrame *a, const AVFrame *b, AVFrame *out, \
496 int slice_start, int slice_end, int jobnr) \
498 XFadeContext *s = ctx->priv; \
499 const int width = out->width; \
500 const int height = out->height; \
501 float z = powf(2.f * fabsf(progress - 0.5f), 3.f) * hypotf(width/2, height/2); \
503 for (int p = 0; p < s->nb_planes; p++) { \
504 const int bg = s->black[p]; \
505 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
507 for (int y = slice_start; y < slice_end; y++) { \
508 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
509 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
511 for (int x = 0; x < width; x++) { \
512 float dist = hypotf(x - width / 2, y - height / 2); \
513 int val = progress < 0.5f ? xf1[x] : xf0[x]; \
514 dst[x] = (z < dist) ? bg : val; \
517 dst += out->linesize[p] / div; \
522 CIRCLECROP_TRANSITION(8, uint8_t, 1)
523 CIRCLECROP_TRANSITION(16, uint16_t, 2)
525 #define RECTCROP_TRANSITION(name, type, div) \
526 static void rectcrop##name##_transition(AVFilterContext *ctx, \
527 const AVFrame *a, const AVFrame *b, AVFrame *out, \
529 int slice_start, int slice_end, int jobnr) \
531 XFadeContext *s = ctx->priv; \
532 const int width = out->width; \
533 const int height = out->height; \
534 int zh = fabsf(progress - 0.5f) * height; \
535 int zw = fabsf(progress - 0.5f) * width; \
537 for (int p = 0; p < s->nb_planes; p++) { \
538 const int bg = s->black[p]; \
539 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
541 for (int y = slice_start; y < slice_end; y++) { \
542 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
543 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
545 for (int x = 0; x < width; x++) { \
546 int dist = FFABS(x - width / 2) < zw && \
547 FFABS(y - height / 2) < zh; \
548 int val = progress < 0.5f ? xf1[x] : xf0[x]; \
549 dst[x] = !dist ? bg : val; \
552 dst += out->linesize[p] / div; \
557 RECTCROP_TRANSITION(8, uint8_t, 1)
558 RECTCROP_TRANSITION(16, uint16_t, 2)
560 #define DISTANCE_TRANSITION(name, type, div) \
561 static void distance##name##_transition(AVFilterContext *ctx, \
562 const AVFrame *a, const AVFrame *b, AVFrame *out, \
564 int slice_start, int slice_end, int jobnr) \
566 XFadeContext *s = ctx->priv; \
567 const int width = out->width; \
568 const float max = s->max_value; \
570 for (int y = slice_start; y < slice_end; y++) { \
571 for (int x = 0; x < width; x++) { \
573 for (int p = 0; p < s->nb_planes; p++) { \
574 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
575 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
577 dist += (xf0[x] / max - xf1[x] / max) * \
578 (xf0[x] / max - xf1[x] / max); \
581 dist = sqrtf(dist) <= progress; \
582 for (int p = 0; p < s->nb_planes; p++) { \
583 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
584 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
585 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
586 dst[x] = mix(mix(xf0[x], xf1[x], dist), xf1[x], progress); \
592 DISTANCE_TRANSITION(8, uint8_t, 1)
593 DISTANCE_TRANSITION(16, uint16_t, 2)
595 #define FADEBLACK_TRANSITION(name, type, div) \
596 static void fadeblack##name##_transition(AVFilterContext *ctx, \
597 const AVFrame *a, const AVFrame *b, AVFrame *out, \
599 int slice_start, int slice_end, int jobnr) \
601 XFadeContext *s = ctx->priv; \
602 const int height = slice_end - slice_start; \
603 const float phase = 0.2f; \
605 for (int p = 0; p < s->nb_planes; p++) { \
606 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
607 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
608 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
609 const int bg = s->black[p]; \
611 for (int y = 0; y < height; y++) { \
612 for (int x = 0; x < out->width; x++) { \
613 dst[x] = mix(mix(xf0[x], bg, smoothstep(1.f-phase, 1.f, progress)), \
614 mix(bg, xf1[x], smoothstep(phase, 1.f, progress)), \
618 dst += out->linesize[p] / div; \
619 xf0 += a->linesize[p] / div; \
620 xf1 += b->linesize[p] / div; \
625 FADEBLACK_TRANSITION(8, uint8_t, 1)
626 FADEBLACK_TRANSITION(16, uint16_t, 2)
628 #define FADEWHITE_TRANSITION(name, type, div) \
629 static void fadewhite##name##_transition(AVFilterContext *ctx, \
630 const AVFrame *a, const AVFrame *b, AVFrame *out, \
632 int slice_start, int slice_end, int jobnr) \
634 XFadeContext *s = ctx->priv; \
635 const int height = slice_end - slice_start; \
636 const float phase = 0.2f; \
638 for (int p = 0; p < s->nb_planes; p++) { \
639 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
640 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
641 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
642 const int bg = s->white[p]; \
644 for (int y = 0; y < height; y++) { \
645 for (int x = 0; x < out->width; x++) { \
646 dst[x] = mix(mix(xf0[x], bg, smoothstep(1.f-phase, 1.f, progress)), \
647 mix(bg, xf1[x], smoothstep(phase, 1.f, progress)), \
651 dst += out->linesize[p] / div; \
652 xf0 += a->linesize[p] / div; \
653 xf1 += b->linesize[p] / div; \
658 FADEWHITE_TRANSITION(8, uint8_t, 1)
659 FADEWHITE_TRANSITION(16, uint16_t, 2)
661 #define RADIAL_TRANSITION(name, type, div) \
662 static void radial##name##_transition(AVFilterContext *ctx, \
663 const AVFrame *a, const AVFrame *b, AVFrame *out, \
665 int slice_start, int slice_end, int jobnr) \
667 XFadeContext *s = ctx->priv; \
668 const int width = out->width; \
669 const int height = out->height; \
671 for (int y = slice_start; y < slice_end; y++) { \
672 for (int x = 0; x < width; x++) { \
673 const float smooth = atan2f(x - width / 2, y - height / 2) - \
674 (progress - 0.5f) * (M_PI * 2.5f); \
675 for (int p = 0; p < s->nb_planes; p++) { \
676 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
677 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
678 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
680 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
686 RADIAL_TRANSITION(8, uint8_t, 1)
687 RADIAL_TRANSITION(16, uint16_t, 2)
689 #define SMOOTHLEFT_TRANSITION(name, type, div) \
690 static void smoothleft##name##_transition(AVFilterContext *ctx, \
691 const AVFrame *a, const AVFrame *b, AVFrame *out, \
693 int slice_start, int slice_end, int jobnr) \
695 XFadeContext *s = ctx->priv; \
696 const int width = out->width; \
697 const float w = width; \
699 for (int y = slice_start; y < slice_end; y++) { \
700 for (int x = 0; x < width; x++) { \
701 const float smooth = 1.f + x / w - progress * 2.f; \
703 for (int p = 0; p < s->nb_planes; p++) { \
704 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
705 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
706 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
708 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
714 SMOOTHLEFT_TRANSITION(8, uint8_t, 1)
715 SMOOTHLEFT_TRANSITION(16, uint16_t, 2)
717 #define SMOOTHRIGHT_TRANSITION(name, type, div) \
718 static void smoothright##name##_transition(AVFilterContext *ctx, \
719 const AVFrame *a, const AVFrame *b, AVFrame *out, \
721 int slice_start, int slice_end, int jobnr) \
723 XFadeContext *s = ctx->priv; \
724 const int width = out->width; \
725 const float w = width; \
727 for (int y = slice_start; y < slice_end; y++) { \
728 for (int x = 0; x < width; x++) { \
729 const float smooth = 1.f + (w - 1 - x) / w - progress * 2.f; \
731 for (int p = 0; p < s->nb_planes; p++) { \
732 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
733 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
734 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
736 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
742 SMOOTHRIGHT_TRANSITION(8, uint8_t, 1)
743 SMOOTHRIGHT_TRANSITION(16, uint16_t, 2)
745 #define SMOOTHUP_TRANSITION(name, type, div) \
746 static void smoothup##name##_transition(AVFilterContext *ctx, \
747 const AVFrame *a, const AVFrame *b, AVFrame *out, \
749 int slice_start, int slice_end, int jobnr) \
751 XFadeContext *s = ctx->priv; \
752 const int width = out->width; \
753 const float h = out->height; \
755 for (int y = slice_start; y < slice_end; y++) { \
756 const float smooth = 1.f + y / h - progress * 2.f; \
757 for (int x = 0; x < width; x++) { \
758 for (int p = 0; p < s->nb_planes; p++) { \
759 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
760 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
761 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
763 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
769 SMOOTHUP_TRANSITION(8, uint8_t, 1)
770 SMOOTHUP_TRANSITION(16, uint16_t, 2)
772 #define SMOOTHDOWN_TRANSITION(name, type, div) \
773 static void smoothdown##name##_transition(AVFilterContext *ctx, \
774 const AVFrame *a, const AVFrame *b, AVFrame *out, \
776 int slice_start, int slice_end, int jobnr) \
778 XFadeContext *s = ctx->priv; \
779 const int width = out->width; \
780 const float h = out->height; \
782 for (int y = slice_start; y < slice_end; y++) { \
783 const float smooth = 1.f + (h - 1 - y) / h - progress * 2.f; \
784 for (int x = 0; x < width; x++) { \
785 for (int p = 0; p < s->nb_planes; p++) { \
786 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
787 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
788 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
790 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
796 SMOOTHDOWN_TRANSITION(8, uint8_t, 1)
797 SMOOTHDOWN_TRANSITION(16, uint16_t, 2)
799 static inline double getpix(void *priv, double x, double y, int plane, int nb)
801 XFadeContext *s = priv;
802 AVFrame *in = s->xf[nb];
803 const uint8_t *src = in->data[FFMIN(plane, s->nb_planes - 1)];
804 int linesize = in->linesize[FFMIN(plane, s->nb_planes - 1)];
805 const int w = in->width;
806 const int h = in->height;
810 xi = av_clipd(x, 0, w - 1);
811 yi = av_clipd(y, 0, h - 1);
814 const uint16_t *src16 = (const uint16_t*)src;
817 return src16[xi + yi * linesize];
819 return src[xi + yi * linesize];
823 static double a0(void *priv, double x, double y) { return getpix(priv, x, y, 0, 0); }
824 static double a1(void *priv, double x, double y) { return getpix(priv, x, y, 1, 0); }
825 static double a2(void *priv, double x, double y) { return getpix(priv, x, y, 2, 0); }
826 static double a3(void *priv, double x, double y) { return getpix(priv, x, y, 3, 0); }
828 static double b0(void *priv, double x, double y) { return getpix(priv, x, y, 0, 1); }
829 static double b1(void *priv, double x, double y) { return getpix(priv, x, y, 1, 1); }
830 static double b2(void *priv, double x, double y) { return getpix(priv, x, y, 2, 1); }
831 static double b3(void *priv, double x, double y) { return getpix(priv, x, y, 3, 1); }
833 static int config_output(AVFilterLink *outlink)
835 AVFilterContext *ctx = outlink->src;
836 AVFilterLink *inlink0 = ctx->inputs[0];
837 AVFilterLink *inlink1 = ctx->inputs[1];
838 XFadeContext *s = ctx->priv;
839 const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink0->format);
842 if (inlink0->format != inlink1->format) {
843 av_log(ctx, AV_LOG_ERROR, "inputs must be of same pixel format\n");
844 return AVERROR(EINVAL);
846 if (inlink0->w != inlink1->w || inlink0->h != inlink1->h) {
847 av_log(ctx, AV_LOG_ERROR, "First input link %s parameters "
848 "(size %dx%d) do not match the corresponding "
849 "second input link %s parameters (size %dx%d)\n",
850 ctx->input_pads[0].name, inlink0->w, inlink0->h,
851 ctx->input_pads[1].name, inlink1->w, inlink1->h);
852 return AVERROR(EINVAL);
855 if (inlink0->time_base.num != inlink1->time_base.num ||
856 inlink0->time_base.den != inlink1->time_base.den) {
857 av_log(ctx, AV_LOG_ERROR, "First input link %s timebase "
858 "(%d/%d) do not match the corresponding "
859 "second input link %s timebase (%d/%d)\n",
860 ctx->input_pads[0].name, inlink0->time_base.num, inlink0->time_base.den,
861 ctx->input_pads[1].name, inlink1->time_base.num, inlink1->time_base.den);
862 return AVERROR(EINVAL);
865 outlink->w = inlink0->w;
866 outlink->h = inlink0->h;
867 outlink->time_base = inlink0->time_base;
868 outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio;
869 outlink->frame_rate = inlink0->frame_rate;
871 s->depth = pix_desc->comp[0].depth;
872 is_rgb = !!(pix_desc->flags & AV_PIX_FMT_FLAG_RGB);
873 s->nb_planes = av_pix_fmt_count_planes(inlink0->format);
874 s->max_value = (1 << s->depth) - 1;
876 s->black[1] = s->black[2] = is_rgb ? 0 : s->max_value / 2;
877 s->black[3] = s->max_value;
878 s->white[0] = s->white[3] = s->max_value;
879 s->white[1] = s->white[2] = is_rgb ? s->max_value : s->max_value / 2;
881 s->first_pts = s->last_pts = s->pts = AV_NOPTS_VALUE;
884 s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, outlink->time_base);
886 s->offset_pts = av_rescale_q(s->offset, AV_TIME_BASE_Q, outlink->time_base);
888 switch (s->transition) {
889 case CUSTOM: s->transitionf = s->depth <= 8 ? custom8_transition : custom16_transition; break;
890 case FADE: s->transitionf = s->depth <= 8 ? fade8_transition : fade16_transition; break;
891 case WIPELEFT: s->transitionf = s->depth <= 8 ? wipeleft8_transition : wipeleft16_transition; break;
892 case WIPERIGHT: s->transitionf = s->depth <= 8 ? wiperight8_transition : wiperight16_transition; break;
893 case WIPEUP: s->transitionf = s->depth <= 8 ? wipeup8_transition : wipeup16_transition; break;
894 case WIPEDOWN: s->transitionf = s->depth <= 8 ? wipedown8_transition : wipedown16_transition; break;
895 case SLIDELEFT: s->transitionf = s->depth <= 8 ? slideleft8_transition : slideleft16_transition; break;
896 case SLIDERIGHT: s->transitionf = s->depth <= 8 ? slideright8_transition : slideright16_transition; break;
897 case SLIDEUP: s->transitionf = s->depth <= 8 ? slideup8_transition : slideup16_transition; break;
898 case SLIDEDOWN: s->transitionf = s->depth <= 8 ? slidedown8_transition : slidedown16_transition; break;
899 case CIRCLECROP: s->transitionf = s->depth <= 8 ? circlecrop8_transition : circlecrop16_transition; break;
900 case RECTCROP: s->transitionf = s->depth <= 8 ? rectcrop8_transition : rectcrop16_transition; break;
901 case DISTANCE: s->transitionf = s->depth <= 8 ? distance8_transition : distance16_transition; break;
902 case FADEBLACK: s->transitionf = s->depth <= 8 ? fadeblack8_transition : fadeblack16_transition; break;
903 case FADEWHITE: s->transitionf = s->depth <= 8 ? fadewhite8_transition : fadewhite16_transition; break;
904 case RADIAL: s->transitionf = s->depth <= 8 ? radial8_transition : radial16_transition; break;
905 case SMOOTHLEFT: s->transitionf = s->depth <= 8 ? smoothleft8_transition : smoothleft16_transition; break;
906 case SMOOTHRIGHT:s->transitionf = s->depth <= 8 ? smoothright8_transition: smoothright16_transition;break;
907 case SMOOTHUP: s->transitionf = s->depth <= 8 ? smoothup8_transition : smoothup16_transition; break;
908 case SMOOTHDOWN: s->transitionf = s->depth <= 8 ? smoothdown8_transition : smoothdown16_transition; break;
911 if (s->transition == CUSTOM) {
912 static const char *const func2_names[] = {
913 "a0", "a1", "a2", "a3",
914 "b0", "b1", "b2", "b3",
917 double (*func2[])(void *, double, double) = {
924 return AVERROR(EINVAL);
925 ret = av_expr_parse(&s->e, s->custom_str, var_names,
926 NULL, NULL, func2_names, func2, 0, ctx);
934 static int xfade_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
936 XFadeContext *s = ctx->priv;
937 AVFilterLink *outlink = ctx->outputs[0];
938 ThreadData *td = arg;
939 int slice_start = (outlink->h * jobnr ) / nb_jobs;
940 int slice_end = (outlink->h * (jobnr+1)) / nb_jobs;
942 s->transitionf(ctx, td->xf[0], td->xf[1], td->out, td->progress, slice_start, slice_end, jobnr);
947 static int xfade_frame(AVFilterContext *ctx, AVFrame *a, AVFrame *b)
949 XFadeContext *s = ctx->priv;
950 AVFilterLink *outlink = ctx->outputs[0];
951 float progress = av_clipf(1.f - ((float)(s->pts - s->first_pts - s->offset_pts) / s->duration_pts), 0.f, 1.f);
955 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
957 return AVERROR(ENOMEM);
959 td.xf[0] = a, td.xf[1] = b, td.out = out, td.progress = progress;
960 ctx->internal->execute(ctx, xfade_slice, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
964 return ff_filter_frame(outlink, out);
967 static int xfade_activate(AVFilterContext *ctx)
969 XFadeContext *s = ctx->priv;
970 AVFilterLink *outlink = ctx->outputs[0];
975 FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, ctx);
977 if (s->xfade_is_over) {
978 ret = ff_inlink_consume_frame(ctx->inputs[1], &in);
981 } else if (ff_inlink_acknowledge_status(ctx->inputs[1], &status, &pts)) {
982 ff_outlink_set_status(outlink, status, s->pts);
985 if (ff_outlink_frame_wanted(outlink)) {
986 ff_inlink_request_frame(ctx->inputs[1]);
990 in->pts = (in->pts - s->last_pts) + s->pts;
991 return ff_filter_frame(outlink, in);
995 if (ff_inlink_queued_frames(ctx->inputs[0]) > 0) {
996 s->xf[0] = ff_inlink_peek_frame(ctx->inputs[0], 0);
998 if (s->first_pts == AV_NOPTS_VALUE) {
999 s->first_pts = s->xf[0]->pts;
1001 s->pts = s->xf[0]->pts;
1002 if (s->first_pts + s->offset_pts > s->xf[0]->pts) {
1005 ff_inlink_consume_frame(ctx->inputs[0], &in);
1006 return ff_filter_frame(outlink, in);
1013 if (s->xf[0] && ff_inlink_queued_frames(ctx->inputs[1]) > 0) {
1014 ff_inlink_consume_frame(ctx->inputs[0], &s->xf[0]);
1015 ff_inlink_consume_frame(ctx->inputs[1], &s->xf[1]);
1017 s->last_pts = s->xf[1]->pts;
1018 s->pts = s->xf[0]->pts;
1019 if (s->xf[0]->pts - (s->first_pts + s->offset_pts) > s->duration_pts)
1020 s->xfade_is_over = 1;
1021 ret = xfade_frame(ctx, s->xf[0], s->xf[1]);
1022 av_frame_free(&s->xf[0]);
1023 av_frame_free(&s->xf[1]);
1027 if (ff_inlink_queued_frames(ctx->inputs[0]) > 0 &&
1028 ff_inlink_queued_frames(ctx->inputs[1]) > 0) {
1029 ff_filter_set_ready(ctx, 100);
1033 if (ff_outlink_frame_wanted(outlink)) {
1034 if (!s->eof[0] && ff_outlink_get_status(ctx->inputs[0])) {
1036 s->xfade_is_over = 1;
1038 if (!s->eof[1] && ff_outlink_get_status(ctx->inputs[1])) {
1041 if (!s->eof[0] && !s->xf[0])
1042 ff_inlink_request_frame(ctx->inputs[0]);
1043 if (!s->eof[1] && (s->need_second || s->eof[0]))
1044 ff_inlink_request_frame(ctx->inputs[1]);
1045 if (s->eof[0] && s->eof[1] && (
1046 ff_inlink_queued_frames(ctx->inputs[0]) <= 0 ||
1047 ff_inlink_queued_frames(ctx->inputs[1]) <= 0))
1048 ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE);
1052 return FFERROR_NOT_READY;
1055 static const AVFilterPad xfade_inputs[] = {
1058 .type = AVMEDIA_TYPE_VIDEO,
1062 .type = AVMEDIA_TYPE_VIDEO,
1067 static const AVFilterPad xfade_outputs[] = {
1070 .type = AVMEDIA_TYPE_VIDEO,
1071 .config_props = config_output,
1076 AVFilter ff_vf_xfade = {
1078 .description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."),
1079 .priv_size = sizeof(XFadeContext),
1080 .priv_class = &xfade_class,
1081 .query_formats = query_formats,
1082 .activate = xfade_activate,
1084 .inputs = xfade_inputs,
1085 .outputs = xfade_outputs,
1086 .flags = AVFILTER_FLAG_SLICE_THREADS,