avfilter/vf_neighbor: rewrite without using temp memory
[ffmpeg.git] / libavfilter / vf_neighbor.c
1 /*
2  * Copyright (c) 2012-2013 Oka Motofumi (chikuzen.mo at gmail dot com)
3  * Copyright (c) 2015 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
30 typedef struct NContext {
31     const AVClass *class;
32     int planeheight[4];
33     int planewidth[4];
34     int nb_planes;
35     int threshold[4];
36     int coordinates;
37
38     void (*filter)(uint8_t *dst, const uint8_t *p1, int width,
39                    int threshold, const uint8_t *coordinates[], int coord);
40 } NContext;
41
42 static int query_formats(AVFilterContext *ctx)
43 {
44     static const enum AVPixelFormat pix_fmts[] = {
45         AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P,
46         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P,AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P,
47         AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
48         AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE
49     };
50
51     return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
52 }
53
54 static void erosion(uint8_t *dst, const uint8_t *p1, int width,
55                     int threshold, const uint8_t *coordinates[], int coord)
56 {
57     int x, i;
58
59     for (x = 0; x < width; x++) {
60         int min = p1[x];
61         int limit = FFMAX(min - threshold, 0);
62
63         for (i = 0; i < 8; i++) {
64             if (coord & (1 << i)) {
65                 min = FFMIN(min, *(coordinates[i] + x));
66             }
67             min = FFMAX(min, limit);
68         }
69
70         dst[x] = min;
71     }
72 }
73
74 static void dilation(uint8_t *dst, const uint8_t *p1, int width,
75                      int threshold, const uint8_t *coordinates[], int coord)
76 {
77     int x, i;
78
79     for (x = 0; x < width; x++) {
80         int max = p1[x];
81         int limit = FFMIN(max + threshold, 255);
82
83         for (i = 0; i < 8; i++) {
84             if (coord & (1 << i)) {
85                 max = FFMAX(max, *(coordinates[i] + x));
86             }
87             max = FFMIN(max, limit);
88         }
89
90         dst[x] = max;
91     }
92 }
93
94 static void deflate(uint8_t *dst, const uint8_t *p1, int width,
95                     int threshold, const uint8_t *coordinates[], int coord)
96 {
97     int x, i;
98
99     for (x = 0; x < width; x++) {
100         int sum = 0;
101         int limit = FFMAX(p1[x] - threshold, 0);
102
103         for (i = 0; i < 8; sum += *(coordinates[i++] + x));
104
105         dst[x] = FFMAX(FFMIN(sum / 8, p1[x]), limit);
106     }
107 }
108
109 static void inflate(uint8_t *dst, const uint8_t *p1, int width,
110                     int threshold, const uint8_t *coordinates[], int coord)
111 {
112     int x, i;
113
114     for (x = 0; x < width; x++) {
115         int sum = 0;
116         int limit = FFMIN(p1[x] + threshold, 255);
117
118         for (i = 0; i < 8; sum += *(coordinates[i++] + x));
119
120         dst[x] = FFMIN(FFMAX(sum / 8, p1[x]), limit);
121     }
122 }
123
124 static int config_input(AVFilterLink *inlink)
125 {
126     AVFilterContext *ctx = inlink->dst;
127     NContext *s = ctx->priv;
128     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
129     int ret;
130
131     if ((ret = av_image_fill_linesizes(s->planewidth, inlink->format, inlink->w)) < 0)
132         return ret;
133
134     s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
135     s->planeheight[0] = s->planeheight[3] = inlink->h;
136
137     s->nb_planes = av_pix_fmt_count_planes(inlink->format);
138
139     if (!strcmp(ctx->filter->name, "erosion"))
140         s->filter = erosion;
141     else if (!strcmp(ctx->filter->name, "dilation"))
142         s->filter = dilation;
143     else if (!strcmp(ctx->filter->name, "deflate"))
144         s->filter = deflate;
145     else if (!strcmp(ctx->filter->name, "inflate"))
146         s->filter = inflate;
147
148     return 0;
149 }
150
151 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
152 {
153     AVFilterContext *ctx = inlink->dst;
154     AVFilterLink *outlink = ctx->outputs[0];
155     NContext *s = ctx->priv;
156     AVFrame *out;
157     int plane, y;
158
159     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
160     if (!out) {
161         av_frame_free(&in);
162         return AVERROR(ENOMEM);
163     }
164     av_frame_copy_props(out, in);
165
166     for (plane = 0; plane < s->nb_planes; plane++) {
167         const int threshold = s->threshold[plane];
168
169         if (threshold) {
170             const int stride = in->linesize[plane];
171             const int dstride = out->linesize[plane];
172             const uint8_t *src = in->data[plane];
173             uint8_t *dst = out->data[plane];
174             const int height = s->planeheight[plane];
175             const int width  = s->planewidth[plane];
176
177             for (y = 0; y < height; y++) {
178                 const int nh = y > 0;
179                 const int ph = y < height - 1;
180                 const uint8_t *coordinates[] = { src - nh * stride, src + 1 - nh * stride, src + 2 - nh * stride,
181                                                  src,                                      src + 2,
182                                                  src + ph * stride, src + 1 + ph * stride, src + 2 + ph * stride};
183
184                 const uint8_t *coordinateslb[] = { src - nh * stride, src - nh * stride, src + 1 - nh * stride,
185                                                    src,                                  src + 1,
186                                                    src + ph * stride, src + ph * stride, src + 1 + ph * stride};
187
188                 const uint8_t *coordinatesrb[] = { src + width - 2 - nh * stride, src + width - 1 - nh * stride, src + width - 1 - nh * stride,
189                                                    src + width - 2,                                              src + width - 1,
190                                                    src + width - 2 + ph * stride, src + width - 1 + ph * stride, src + width - 1 + ph * stride};
191
192                 s->filter(dst,             src,             1,         threshold, coordinateslb, s->coordinates);
193                 s->filter(dst         + 1, src         + 1, width - 2, threshold, coordinates,   s->coordinates);
194                 s->filter(dst + width - 1, src + width - 1, 1,         threshold, coordinatesrb, s->coordinates);
195
196                 src += stride;
197                 dst += dstride;
198             }
199         } else {
200             av_image_copy_plane(out->data[plane], out->linesize[plane],
201                                 in->data[plane], in->linesize[plane],
202                                 s->planewidth[plane], s->planeheight[plane]);
203         }
204     }
205
206     av_frame_free(&in);
207     return ff_filter_frame(outlink, out);
208 }
209
210 static const AVFilterPad neighbor_inputs[] = {
211     {
212         .name         = "default",
213         .type         = AVMEDIA_TYPE_VIDEO,
214         .filter_frame = filter_frame,
215         .config_props = config_input,
216     },
217     { NULL }
218 };
219
220 static const AVFilterPad neighbor_outputs[] = {
221     {
222         .name = "default",
223         .type = AVMEDIA_TYPE_VIDEO,
224     },
225     { NULL }
226 };
227
228 #define OFFSET(x) offsetof(NContext, x)
229 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
230
231 #define DEFINE_NEIGHBOR_FILTER(name_, description_)          \
232 AVFILTER_DEFINE_CLASS(name_);                                \
233                                                              \
234 AVFilter ff_vf_##name_ = {                                   \
235     .name          = #name_,                                 \
236     .description   = NULL_IF_CONFIG_SMALL(description_),     \
237     .priv_size     = sizeof(NContext),                       \
238     .priv_class    = &name_##_class,                         \
239     .query_formats = query_formats,                          \
240     .inputs        = neighbor_inputs,                        \
241     .outputs       = neighbor_outputs,                       \
242     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, \
243 }
244
245 #if CONFIG_EROSION_FILTER
246
247 static const AVOption erosion_options[] = {
248     { "threshold0",  "set threshold for 1st plane",   OFFSET(threshold[0]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
249     { "threshold1",  "set threshold for 2nd plane",   OFFSET(threshold[1]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
250     { "threshold2",  "set threshold for 3rd plane",   OFFSET(threshold[2]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
251     { "threshold3",  "set threshold for 4th plane",   OFFSET(threshold[3]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
252     { "coordinates", "set coordinates",               OFFSET(coordinates),    AV_OPT_TYPE_INT, {.i64=255},   0, 255,   FLAGS },
253     { NULL }
254 };
255
256 DEFINE_NEIGHBOR_FILTER(erosion, "Apply erosion effect.");
257
258 #endif /* CONFIG_EROSION_FILTER */
259
260 #if CONFIG_DILATION_FILTER
261
262 static const AVOption dilation_options[] = {
263     { "threshold0",  "set threshold for 1st plane",   OFFSET(threshold[0]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
264     { "threshold1",  "set threshold for 2nd plane",   OFFSET(threshold[1]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
265     { "threshold2",  "set threshold for 3rd plane",   OFFSET(threshold[2]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
266     { "threshold3",  "set threshold for 4th plane",   OFFSET(threshold[3]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
267     { "coordinates", "set coordinates",               OFFSET(coordinates),    AV_OPT_TYPE_INT, {.i64=255},   0, 255,   FLAGS },
268     { NULL }
269 };
270
271 DEFINE_NEIGHBOR_FILTER(dilation, "Apply dilation effect.");
272
273 #endif /* CONFIG_DILATION_FILTER */
274
275 #if CONFIG_DEFLATE_FILTER
276
277 static const AVOption deflate_options[] = {
278     { "threshold0", "set threshold for 1st plane",   OFFSET(threshold[0]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
279     { "threshold1", "set threshold for 2nd plane",   OFFSET(threshold[1]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
280     { "threshold2", "set threshold for 3rd plane",   OFFSET(threshold[2]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
281     { "threshold3", "set threshold for 4th plane",   OFFSET(threshold[3]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
282     { NULL }
283 };
284
285 DEFINE_NEIGHBOR_FILTER(deflate, "Apply deflate effect.");
286
287 #endif /* CONFIG_DEFLATE_FILTER */
288
289 #if CONFIG_INFLATE_FILTER
290
291 static const AVOption inflate_options[] = {
292     { "threshold0", "set threshold for 1st plane",   OFFSET(threshold[0]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
293     { "threshold1", "set threshold for 2nd plane",   OFFSET(threshold[1]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
294     { "threshold2", "set threshold for 3rd plane",   OFFSET(threshold[2]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
295     { "threshold3", "set threshold for 4th plane",   OFFSET(threshold[3]),   AV_OPT_TYPE_INT, {.i64=65535}, 0, 65535, FLAGS },
296     { NULL }
297 };
298
299 DEFINE_NEIGHBOR_FILTER(inflate, "Apply inflate effect.");
300
301 #endif /* CONFIG_INFLATE_FILTER */