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