blob: 3d65e59d42c6fa480e00f4b7ab079677bcac876a
1 | /* |
2 | * Copyright (c) 2015 Timo Rothenpieler <timo@rothenpieler.org> |
3 | * |
4 | * This file is part of FFmpeg. |
5 | * |
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. |
10 | * |
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. |
15 | * |
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 |
19 | */ |
20 | |
21 | #include "libavutil/opt.h" |
22 | #include "libavutil/imgutils.h" |
23 | #include "avfilter.h" |
24 | #include "formats.h" |
25 | #include "internal.h" |
26 | #include "video.h" |
27 | |
28 | typedef struct ColorkeyContext { |
29 | const AVClass *class; |
30 | |
31 | /* color offsets rgba */ |
32 | int co[4]; |
33 | |
34 | uint8_t colorkey_rgba[4]; |
35 | float similarity; |
36 | float blend; |
37 | } ColorkeyContext; |
38 | |
39 | static uint8_t do_colorkey_pixel(ColorkeyContext *ctx, uint8_t r, uint8_t g, uint8_t b) |
40 | { |
41 | int dr = (int)r - ctx->colorkey_rgba[0]; |
42 | int dg = (int)g - ctx->colorkey_rgba[1]; |
43 | int db = (int)b - ctx->colorkey_rgba[2]; |
44 | |
45 | double diff = sqrt((dr * dr + dg * dg + db * db) / (255.0 * 255.0)); |
46 | |
47 | if (ctx->blend > 0.0001) { |
48 | return av_clipd((diff - ctx->similarity) / ctx->blend, 0.0, 1.0) * 255.0; |
49 | } else { |
50 | return (diff > ctx->similarity) ? 255 : 0; |
51 | } |
52 | } |
53 | |
54 | static int do_colorkey_slice(AVFilterContext *avctx, void *arg, int jobnr, int nb_jobs) |
55 | { |
56 | AVFrame *frame = arg; |
57 | |
58 | const int slice_start = (frame->height * jobnr) / nb_jobs; |
59 | const int slice_end = (frame->height * (jobnr + 1)) / nb_jobs; |
60 | |
61 | ColorkeyContext *ctx = avctx->priv; |
62 | |
63 | int o, x, y; |
64 | |
65 | for (y = slice_start; y < slice_end; ++y) { |
66 | for (x = 0; x < frame->width; ++x) { |
67 | o = frame->linesize[0] * y + x * 4; |
68 | |
69 | frame->data[0][o + ctx->co[3]] = |
70 | do_colorkey_pixel(ctx, |
71 | frame->data[0][o + ctx->co[0]], |
72 | frame->data[0][o + ctx->co[1]], |
73 | frame->data[0][o + ctx->co[2]]); |
74 | } |
75 | } |
76 | |
77 | return 0; |
78 | } |
79 | |
80 | static int filter_frame(AVFilterLink *link, AVFrame *frame) |
81 | { |
82 | AVFilterContext *avctx = link->dst; |
83 | int res; |
84 | |
85 | if (res = av_frame_make_writable(frame)) |
86 | return res; |
87 | |
88 | if (res = avctx->internal->execute(avctx, do_colorkey_slice, frame, NULL, FFMIN(frame->height, ff_filter_get_nb_threads(avctx)))) |
89 | return res; |
90 | |
91 | return ff_filter_frame(avctx->outputs[0], frame); |
92 | } |
93 | |
94 | static av_cold int config_output(AVFilterLink *outlink) |
95 | { |
96 | AVFilterContext *avctx = outlink->src; |
97 | ColorkeyContext *ctx = avctx->priv; |
98 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); |
99 | int i; |
100 | |
101 | outlink->w = avctx->inputs[0]->w; |
102 | outlink->h = avctx->inputs[0]->h; |
103 | outlink->time_base = avctx->inputs[0]->time_base; |
104 | |
105 | for (i = 0; i < 4; ++i) |
106 | ctx->co[i] = desc->comp[i].offset; |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | static av_cold int query_formats(AVFilterContext *avctx) |
112 | { |
113 | static const enum AVPixelFormat pixel_fmts[] = { |
114 | AV_PIX_FMT_ARGB, |
115 | AV_PIX_FMT_RGBA, |
116 | AV_PIX_FMT_ABGR, |
117 | AV_PIX_FMT_BGRA, |
118 | AV_PIX_FMT_NONE |
119 | }; |
120 | |
121 | AVFilterFormats *formats = NULL; |
122 | |
123 | formats = ff_make_format_list(pixel_fmts); |
124 | if (!formats) |
125 | return AVERROR(ENOMEM); |
126 | |
127 | return ff_set_common_formats(avctx, formats); |
128 | } |
129 | |
130 | static const AVFilterPad colorkey_inputs[] = { |
131 | { |
132 | .name = "default", |
133 | .type = AVMEDIA_TYPE_VIDEO, |
134 | .filter_frame = filter_frame, |
135 | }, |
136 | { NULL } |
137 | }; |
138 | |
139 | static const AVFilterPad colorkey_outputs[] = { |
140 | { |
141 | .name = "default", |
142 | .type = AVMEDIA_TYPE_VIDEO, |
143 | .config_props = config_output, |
144 | }, |
145 | { NULL } |
146 | }; |
147 | |
148 | #define OFFSET(x) offsetof(ColorkeyContext, x) |
149 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
150 | |
151 | static const AVOption colorkey_options[] = { |
152 | { "color", "set the colorkey key color", OFFSET(colorkey_rgba), AV_OPT_TYPE_COLOR, { .str = "black" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
153 | { "similarity", "set the colorkey similarity value", OFFSET(similarity), AV_OPT_TYPE_FLOAT, { .dbl = 0.01 }, 0.01, 1.0, FLAGS }, |
154 | { "blend", "set the colorkey key blend value", OFFSET(blend), AV_OPT_TYPE_FLOAT, { .dbl = 0.0 }, 0.0, 1.0, FLAGS }, |
155 | { NULL } |
156 | }; |
157 | |
158 | AVFILTER_DEFINE_CLASS(colorkey); |
159 | |
160 | AVFilter ff_vf_colorkey = { |
161 | .name = "colorkey", |
162 | .description = NULL_IF_CONFIG_SMALL("Turns a certain color into transparency. Operates on RGB colors."), |
163 | .priv_size = sizeof(ColorkeyContext), |
164 | .priv_class = &colorkey_class, |
165 | .query_formats = query_formats, |
166 | .inputs = colorkey_inputs, |
167 | .outputs = colorkey_outputs, |
168 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, |
169 | }; |
170 |