blob: 8f05b716c9d456d2c3d1915ee02feb4614f7efe1
1 | /* |
2 | * Copyright (c) 2013 Oka Motofumi (chikuzen.mo at gmail dot com) |
3 | * Copyright (c) 2016 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 Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 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 GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along 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/imgutils.h" |
23 | #include "libavutil/pixdesc.h" |
24 | #include "libavutil/opt.h" |
25 | #include "avfilter.h" |
26 | #include "formats.h" |
27 | #include "internal.h" |
28 | #include "video.h" |
29 | #include "framesync.h" |
30 | |
31 | #define OFFSET(x) offsetof(HysteresisContext, x) |
32 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
33 | |
34 | typedef struct HysteresisContext { |
35 | const AVClass *class; |
36 | |
37 | int planes; |
38 | int threshold; |
39 | |
40 | int width[4], height[4]; |
41 | int nb_planes; |
42 | int depth; |
43 | FFFrameSync fs; |
44 | |
45 | uint8_t *map; |
46 | uint32_t *xy; |
47 | int index; |
48 | |
49 | void (*hysteresis)(struct HysteresisContext *s, const uint8_t *bsrc, const uint8_t *osrc, uint8_t *dst, |
50 | ptrdiff_t blinesize, ptrdiff_t olinesize, |
51 | ptrdiff_t destlinesize, |
52 | int w, int h); |
53 | } HysteresisContext; |
54 | |
55 | static const AVOption hysteresis_options[] = { |
56 | { "planes", "set planes", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=0xF}, 0, 0xF, FLAGS }, |
57 | { "threshold", "set threshold", OFFSET(threshold), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, |
58 | { NULL } |
59 | }; |
60 | |
61 | AVFILTER_DEFINE_CLASS(hysteresis); |
62 | |
63 | static int query_formats(AVFilterContext *ctx) |
64 | { |
65 | static const enum AVPixelFormat pix_fmts[] = { |
66 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, |
67 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, |
68 | AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, |
69 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, |
70 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, |
71 | AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, |
72 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, |
73 | AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12, |
74 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, |
75 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, |
76 | AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, |
77 | AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, |
78 | AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, |
79 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, |
80 | AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, |
81 | AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, |
82 | AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY16, |
83 | AV_PIX_FMT_NONE |
84 | }; |
85 | |
86 | return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); |
87 | } |
88 | |
89 | static int process_frame(FFFrameSync *fs) |
90 | { |
91 | AVFilterContext *ctx = fs->parent; |
92 | HysteresisContext *s = fs->opaque; |
93 | AVFilterLink *outlink = ctx->outputs[0]; |
94 | AVFrame *out, *base, *alt; |
95 | int ret; |
96 | |
97 | if ((ret = ff_framesync_get_frame(&s->fs, 0, &base, 0)) < 0 || |
98 | (ret = ff_framesync_get_frame(&s->fs, 1, &alt, 0)) < 0) |
99 | return ret; |
100 | |
101 | if (ctx->is_disabled) { |
102 | out = av_frame_clone(base); |
103 | if (!out) |
104 | return AVERROR(ENOMEM); |
105 | } else { |
106 | int p; |
107 | |
108 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
109 | if (!out) |
110 | return AVERROR(ENOMEM); |
111 | av_frame_copy_props(out, base); |
112 | |
113 | for (p = 0; p < s->nb_planes; p++) { |
114 | if (!((1 << p) & s->planes)) { |
115 | av_image_copy_plane(out->data[p], out->linesize[p], base->data[p], base->linesize[p], |
116 | s->width[p], s->height[p]); |
117 | continue; |
118 | } else { |
119 | int y; |
120 | |
121 | for (y = 0; y < s->height[p]; y++) { |
122 | memset(out->data[p] + y * out->linesize[p], 0, s->width[p]); |
123 | } |
124 | } |
125 | |
126 | s->index = -1; |
127 | memset(s->map, 0, s->width[0] * s->height[0]); |
128 | memset(s->xy, 0, s->width[0] * s->height[0] * 4); |
129 | |
130 | s->hysteresis(s, base->data[p], alt->data[p], |
131 | out->data[p], |
132 | base->linesize[p], alt->linesize[p], |
133 | out->linesize[p], |
134 | s->width[p], s->height[p]); |
135 | } |
136 | } |
137 | out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); |
138 | |
139 | return ff_filter_frame(outlink, out); |
140 | } |
141 | |
142 | static int passed(HysteresisContext *s, int x, int y, int w) |
143 | { |
144 | return s->map[x + y * w]; |
145 | } |
146 | |
147 | static void push(HysteresisContext *s, int x, int y, int w) |
148 | { |
149 | s->map[x + y * w] = 0xff; |
150 | s->xy[++s->index] = (uint16_t)(x) << 16 | (uint16_t)y; |
151 | } |
152 | |
153 | static void pop(HysteresisContext *s, int *x, int *y) |
154 | { |
155 | uint32_t val = s->xy[s->index--]; |
156 | |
157 | *x = val >> 16; |
158 | *y = val & 0x0000FFFF; |
159 | } |
160 | |
161 | static int is_empty(HysteresisContext *s) |
162 | { |
163 | return s->index < 0; |
164 | } |
165 | |
166 | static void hysteresis8(HysteresisContext *s, const uint8_t *bsrc, const uint8_t *asrc, |
167 | uint8_t *dst, |
168 | ptrdiff_t blinesize, ptrdiff_t alinesize, |
169 | ptrdiff_t dlinesize, |
170 | int w, int h) |
171 | { |
172 | const int t = s->threshold; |
173 | int x, y; |
174 | |
175 | for (y = 0; y < h; y++) { |
176 | for (x = 0; x < w; x++) { |
177 | if ((bsrc[x + y * blinesize] > t) && (asrc[x + y * alinesize] > t) && !passed(s, x, y, w)) { |
178 | int posx, posy; |
179 | |
180 | dst[x + y * dlinesize] = asrc[x + y * alinesize]; |
181 | |
182 | push(s, x, y, w); |
183 | |
184 | while (!is_empty(s)) { |
185 | int x_min, x_max, y_min, y_max, yy, xx; |
186 | |
187 | pop(s, &posx, &posy); |
188 | |
189 | x_min = posx > 0 ? posx - 1 : 0; |
190 | x_max = posx < w - 1 ? posx + 1 : posx; |
191 | y_min = posy > 0 ? posy - 1 : 0; |
192 | y_max = posy < h - 1 ? posy + 1 : posy; |
193 | |
194 | for (yy = y_min; yy <= y_max; yy++) { |
195 | for (xx = x_min; xx <= x_max; xx++) { |
196 | if ((asrc[xx + yy * alinesize] > t) && !passed(s, xx, yy, w)) { |
197 | dst[xx + yy * dlinesize] = asrc[xx + yy * alinesize]; |
198 | push(s, xx, yy, w); |
199 | } |
200 | } |
201 | } |
202 | } |
203 | } |
204 | } |
205 | } |
206 | } |
207 | |
208 | static void hysteresis16(HysteresisContext *s, const uint8_t *bbsrc, const uint8_t *aasrc, |
209 | uint8_t *ddst, |
210 | ptrdiff_t blinesize, ptrdiff_t alinesize, |
211 | ptrdiff_t dlinesize, |
212 | int w, int h) |
213 | { |
214 | const uint16_t *bsrc = (const uint16_t *)bbsrc; |
215 | const uint16_t *asrc = (const uint16_t *)aasrc; |
216 | uint16_t *dst = (uint16_t *)ddst; |
217 | const int t = s->threshold; |
218 | int x, y; |
219 | |
220 | blinesize /= 2; |
221 | alinesize /= 2; |
222 | dlinesize /= 2; |
223 | |
224 | for (y = 0; y < h; y++) { |
225 | for (x = 0; x < w; x++) { |
226 | if ((bsrc[x + y * blinesize] > t) && (asrc[x + y * alinesize] > t) && !passed(s, x, y, w)) { |
227 | int posx, posy; |
228 | |
229 | dst[x + y * dlinesize] = asrc[x + y * alinesize]; |
230 | |
231 | push(s, x, y, w); |
232 | |
233 | while (!is_empty(s)) { |
234 | int x_min, x_max, y_min, y_max, yy, xx; |
235 | |
236 | pop(s, &posx, &posy); |
237 | |
238 | x_min = posx > 0 ? posx - 1 : 0; |
239 | x_max = posx < w - 1 ? posx + 1 : posx; |
240 | y_min = posy > 0 ? posy - 1 : 0; |
241 | y_max = posy < h - 1 ? posy + 1 : posy; |
242 | |
243 | for (yy = y_min; yy <= y_max; yy++) { |
244 | for (xx = x_min; xx <= x_max; xx++) { |
245 | if ((asrc[xx + yy * alinesize] > t) && !passed(s, xx, yy, w)) { |
246 | dst[xx + yy * dlinesize] = asrc[xx + yy * alinesize]; |
247 | push(s, xx, yy, w); |
248 | } |
249 | } |
250 | } |
251 | } |
252 | } |
253 | } |
254 | } |
255 | } |
256 | |
257 | static int config_input(AVFilterLink *inlink) |
258 | { |
259 | AVFilterContext *ctx = inlink->dst; |
260 | HysteresisContext *s = ctx->priv; |
261 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
262 | int vsub, hsub; |
263 | |
264 | s->nb_planes = av_pix_fmt_count_planes(inlink->format); |
265 | |
266 | hsub = desc->log2_chroma_w; |
267 | vsub = desc->log2_chroma_h; |
268 | s->height[1] = s->height[2] = AV_CEIL_RSHIFT(inlink->h, vsub); |
269 | s->height[0] = s->height[3] = inlink->h; |
270 | s->width[1] = s->width[2] = AV_CEIL_RSHIFT(inlink->w, hsub); |
271 | s->width[0] = s->width[3] = inlink->w; |
272 | |
273 | s->depth = desc->comp[0].depth; |
274 | |
275 | if (desc->comp[0].depth == 8) |
276 | s->hysteresis = hysteresis8; |
277 | else |
278 | s->hysteresis = hysteresis16; |
279 | |
280 | s->map = av_calloc(inlink->w, inlink->h * sizeof (*s->map)); |
281 | if (!s->map) |
282 | return AVERROR(ENOMEM); |
283 | |
284 | s->xy = av_calloc(inlink->w, inlink->h * sizeof(*s->xy)); |
285 | if (!s->xy) |
286 | return AVERROR(ENOMEM); |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | static int config_output(AVFilterLink *outlink) |
292 | { |
293 | AVFilterContext *ctx = outlink->src; |
294 | HysteresisContext *s = ctx->priv; |
295 | AVFilterLink *base = ctx->inputs[0]; |
296 | AVFilterLink *alt = ctx->inputs[1]; |
297 | FFFrameSyncIn *in; |
298 | int ret; |
299 | |
300 | if (base->format != alt->format) { |
301 | av_log(ctx, AV_LOG_ERROR, "inputs must be of same pixel format\n"); |
302 | return AVERROR(EINVAL); |
303 | } |
304 | if (base->w != alt->w || |
305 | base->h != alt->h || |
306 | base->sample_aspect_ratio.num != alt->sample_aspect_ratio.num || |
307 | base->sample_aspect_ratio.den != alt->sample_aspect_ratio.den) { |
308 | av_log(ctx, AV_LOG_ERROR, "First input link %s parameters " |
309 | "(size %dx%d, SAR %d:%d) do not match the corresponding " |
310 | "second input link %s parameters (%dx%d, SAR %d:%d)\n", |
311 | ctx->input_pads[0].name, base->w, base->h, |
312 | base->sample_aspect_ratio.num, |
313 | base->sample_aspect_ratio.den, |
314 | ctx->input_pads[1].name, |
315 | alt->w, alt->h, |
316 | alt->sample_aspect_ratio.num, |
317 | alt->sample_aspect_ratio.den); |
318 | return AVERROR(EINVAL); |
319 | } |
320 | |
321 | outlink->w = base->w; |
322 | outlink->h = base->h; |
323 | outlink->time_base = base->time_base; |
324 | outlink->sample_aspect_ratio = base->sample_aspect_ratio; |
325 | outlink->frame_rate = base->frame_rate; |
326 | |
327 | if ((ret = ff_framesync_init(&s->fs, ctx, 2)) < 0) |
328 | return ret; |
329 | |
330 | in = s->fs.in; |
331 | in[0].time_base = base->time_base; |
332 | in[1].time_base = alt->time_base; |
333 | in[0].sync = 1; |
334 | in[0].before = EXT_STOP; |
335 | in[0].after = EXT_INFINITY; |
336 | in[1].sync = 1; |
337 | in[1].before = EXT_STOP; |
338 | in[1].after = EXT_INFINITY; |
339 | s->fs.opaque = s; |
340 | s->fs.on_event = process_frame; |
341 | |
342 | return ff_framesync_configure(&s->fs); |
343 | } |
344 | |
345 | static int filter_frame(AVFilterLink *inlink, AVFrame *buf) |
346 | { |
347 | HysteresisContext *s = inlink->dst->priv; |
348 | return ff_framesync_filter_frame(&s->fs, inlink, buf); |
349 | } |
350 | |
351 | static int request_frame(AVFilterLink *outlink) |
352 | { |
353 | HysteresisContext *s = outlink->src->priv; |
354 | return ff_framesync_request_frame(&s->fs, outlink); |
355 | } |
356 | |
357 | static av_cold void uninit(AVFilterContext *ctx) |
358 | { |
359 | HysteresisContext *s = ctx->priv; |
360 | |
361 | ff_framesync_uninit(&s->fs); |
362 | av_freep(&s->map); |
363 | av_freep(&s->xy); |
364 | } |
365 | |
366 | static const AVFilterPad hysteresis_inputs[] = { |
367 | { |
368 | .name = "base", |
369 | .type = AVMEDIA_TYPE_VIDEO, |
370 | .filter_frame = filter_frame, |
371 | .config_props = config_input, |
372 | }, |
373 | { |
374 | .name = "alt", |
375 | .type = AVMEDIA_TYPE_VIDEO, |
376 | .filter_frame = filter_frame, |
377 | }, |
378 | { NULL } |
379 | }; |
380 | |
381 | static const AVFilterPad hysteresis_outputs[] = { |
382 | { |
383 | .name = "default", |
384 | .type = AVMEDIA_TYPE_VIDEO, |
385 | .config_props = config_output, |
386 | .request_frame = request_frame, |
387 | }, |
388 | { NULL } |
389 | }; |
390 | |
391 | AVFilter ff_vf_hysteresis = { |
392 | .name = "hysteresis", |
393 | .description = NULL_IF_CONFIG_SMALL("Grow first stream into second stream by connecting components."), |
394 | .priv_size = sizeof(HysteresisContext), |
395 | .uninit = uninit, |
396 | .query_formats = query_formats, |
397 | .inputs = hysteresis_inputs, |
398 | .outputs = hysteresis_outputs, |
399 | .priv_class = &hysteresis_class, |
400 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, |
401 | }; |
402 |