blob: dedbe30d1914a4cde1011d891ef79140ef4e4f0a
1 | /* |
2 | * Copyright (c) 2013 Paul B Mahol |
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/imgutils.h" |
22 | #include "libavutil/opt.h" |
23 | #include "libavutil/pixdesc.h" |
24 | #include "avfilter.h" |
25 | #include "drawutils.h" |
26 | #include "formats.h" |
27 | #include "internal.h" |
28 | #include "video.h" |
29 | |
30 | #define R 0 |
31 | #define G 1 |
32 | #define B 2 |
33 | #define A 3 |
34 | |
35 | typedef struct { |
36 | double in_min, in_max; |
37 | double out_min, out_max; |
38 | } Range; |
39 | |
40 | typedef struct { |
41 | const AVClass *class; |
42 | Range range[4]; |
43 | int nb_comp; |
44 | int bpp; |
45 | int step; |
46 | uint8_t rgba_map[4]; |
47 | int linesize; |
48 | } ColorLevelsContext; |
49 | |
50 | #define OFFSET(x) offsetof(ColorLevelsContext, x) |
51 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
52 | static const AVOption colorlevels_options[] = { |
53 | { "rimin", "set input red black point", OFFSET(range[R].in_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS }, |
54 | { "gimin", "set input green black point", OFFSET(range[G].in_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS }, |
55 | { "bimin", "set input blue black point", OFFSET(range[B].in_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS }, |
56 | { "aimin", "set input alpha black point", OFFSET(range[A].in_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS }, |
57 | { "rimax", "set input red white point", OFFSET(range[R].in_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS }, |
58 | { "gimax", "set input green white point", OFFSET(range[G].in_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS }, |
59 | { "bimax", "set input blue white point", OFFSET(range[B].in_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS }, |
60 | { "aimax", "set input alpha white point", OFFSET(range[A].in_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS }, |
61 | { "romin", "set output red black point", OFFSET(range[R].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1, FLAGS }, |
62 | { "gomin", "set output green black point", OFFSET(range[G].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1, FLAGS }, |
63 | { "bomin", "set output blue black point", OFFSET(range[B].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1, FLAGS }, |
64 | { "aomin", "set output alpha black point", OFFSET(range[A].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1, FLAGS }, |
65 | { "romax", "set output red white point", OFFSET(range[R].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 1, FLAGS }, |
66 | { "gomax", "set output green white point", OFFSET(range[G].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 1, FLAGS }, |
67 | { "bomax", "set output blue white point", OFFSET(range[B].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 1, FLAGS }, |
68 | { "aomax", "set output alpha white point", OFFSET(range[A].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 1, FLAGS }, |
69 | { NULL } |
70 | }; |
71 | |
72 | AVFILTER_DEFINE_CLASS(colorlevels); |
73 | |
74 | static int query_formats(AVFilterContext *ctx) |
75 | { |
76 | static const enum AVPixelFormat pix_fmts[] = { |
77 | AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR, |
78 | AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, |
79 | AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0, |
80 | AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, |
81 | AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48, |
82 | AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64, |
83 | AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, |
84 | AV_PIX_FMT_NONE |
85 | }; |
86 | |
87 | AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); |
88 | if (!fmts_list) |
89 | return AVERROR(ENOMEM); |
90 | return ff_set_common_formats(ctx, fmts_list); |
91 | } |
92 | |
93 | static int config_input(AVFilterLink *inlink) |
94 | { |
95 | AVFilterContext *ctx = inlink->dst; |
96 | ColorLevelsContext *s = ctx->priv; |
97 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
98 | |
99 | s->nb_comp = desc->nb_components; |
100 | s->bpp = desc->comp[0].depth >> 3; |
101 | s->step = (av_get_padded_bits_per_pixel(desc) >> 3) / s->bpp; |
102 | s->linesize = inlink->w * s->step; |
103 | ff_fill_rgba_map(s->rgba_map, inlink->format); |
104 | |
105 | return 0; |
106 | } |
107 | |
108 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) |
109 | { |
110 | AVFilterContext *ctx = inlink->dst; |
111 | ColorLevelsContext *s = ctx->priv; |
112 | AVFilterLink *outlink = ctx->outputs[0]; |
113 | const int step = s->step; |
114 | AVFrame *out; |
115 | int x, y, i; |
116 | |
117 | if (av_frame_is_writable(in)) { |
118 | out = in; |
119 | } else { |
120 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
121 | if (!out) { |
122 | av_frame_free(&in); |
123 | return AVERROR(ENOMEM); |
124 | } |
125 | av_frame_copy_props(out, in); |
126 | } |
127 | |
128 | switch (s->bpp) { |
129 | case 1: |
130 | for (i = 0; i < s->nb_comp; i++) { |
131 | Range *r = &s->range[i]; |
132 | const uint8_t offset = s->rgba_map[i]; |
133 | const uint8_t *srcrow = in->data[0]; |
134 | uint8_t *dstrow = out->data[0]; |
135 | int imin = lrint(r->in_min * UINT8_MAX); |
136 | int imax = lrint(r->in_max * UINT8_MAX); |
137 | int omin = lrint(r->out_min * UINT8_MAX); |
138 | int omax = lrint(r->out_max * UINT8_MAX); |
139 | double coeff; |
140 | |
141 | if (imin < 0) { |
142 | imin = UINT8_MAX; |
143 | for (y = 0; y < inlink->h; y++) { |
144 | const uint8_t *src = srcrow; |
145 | |
146 | for (x = 0; x < s->linesize; x += step) |
147 | imin = FFMIN(imin, src[x + offset]); |
148 | srcrow += in->linesize[0]; |
149 | } |
150 | } |
151 | if (imax < 0) { |
152 | srcrow = in->data[0]; |
153 | imax = 0; |
154 | for (y = 0; y < inlink->h; y++) { |
155 | const uint8_t *src = srcrow; |
156 | |
157 | for (x = 0; x < s->linesize; x += step) |
158 | imax = FFMAX(imax, src[x + offset]); |
159 | srcrow += in->linesize[0]; |
160 | } |
161 | } |
162 | |
163 | srcrow = in->data[0]; |
164 | coeff = (omax - omin) / (double)(imax - imin); |
165 | for (y = 0; y < inlink->h; y++) { |
166 | const uint8_t *src = srcrow; |
167 | uint8_t *dst = dstrow; |
168 | |
169 | for (x = 0; x < s->linesize; x += step) |
170 | dst[x + offset] = av_clip_uint8((src[x + offset] - imin) * coeff + omin); |
171 | dstrow += out->linesize[0]; |
172 | srcrow += in->linesize[0]; |
173 | } |
174 | } |
175 | break; |
176 | case 2: |
177 | for (i = 0; i < s->nb_comp; i++) { |
178 | Range *r = &s->range[i]; |
179 | const uint8_t offset = s->rgba_map[i]; |
180 | const uint8_t *srcrow = in->data[0]; |
181 | uint8_t *dstrow = out->data[0]; |
182 | int imin = lrint(r->in_min * UINT16_MAX); |
183 | int imax = lrint(r->in_max * UINT16_MAX); |
184 | int omin = lrint(r->out_min * UINT16_MAX); |
185 | int omax = lrint(r->out_max * UINT16_MAX); |
186 | double coeff; |
187 | |
188 | if (imin < 0) { |
189 | imin = UINT16_MAX; |
190 | for (y = 0; y < inlink->h; y++) { |
191 | const uint16_t *src = (const uint16_t *)srcrow; |
192 | |
193 | for (x = 0; x < s->linesize; x += step) |
194 | imin = FFMIN(imin, src[x + offset]); |
195 | srcrow += in->linesize[0]; |
196 | } |
197 | } |
198 | if (imax < 0) { |
199 | srcrow = in->data[0]; |
200 | imax = 0; |
201 | for (y = 0; y < inlink->h; y++) { |
202 | const uint16_t *src = (const uint16_t *)srcrow; |
203 | |
204 | for (x = 0; x < s->linesize; x += step) |
205 | imax = FFMAX(imax, src[x + offset]); |
206 | srcrow += in->linesize[0]; |
207 | } |
208 | } |
209 | |
210 | srcrow = in->data[0]; |
211 | coeff = (omax - omin) / (double)(imax - imin); |
212 | for (y = 0; y < inlink->h; y++) { |
213 | const uint16_t *src = (const uint16_t*)srcrow; |
214 | uint16_t *dst = (uint16_t *)dstrow; |
215 | |
216 | for (x = 0; x < s->linesize; x += step) |
217 | dst[x + offset] = av_clip_uint16((src[x + offset] - imin) * coeff + omin); |
218 | dstrow += out->linesize[0]; |
219 | srcrow += in->linesize[0]; |
220 | } |
221 | } |
222 | } |
223 | |
224 | if (in != out) |
225 | av_frame_free(&in); |
226 | return ff_filter_frame(outlink, out); |
227 | } |
228 | |
229 | static const AVFilterPad colorlevels_inputs[] = { |
230 | { |
231 | .name = "default", |
232 | .type = AVMEDIA_TYPE_VIDEO, |
233 | .filter_frame = filter_frame, |
234 | .config_props = config_input, |
235 | }, |
236 | { NULL } |
237 | }; |
238 | |
239 | static const AVFilterPad colorlevels_outputs[] = { |
240 | { |
241 | .name = "default", |
242 | .type = AVMEDIA_TYPE_VIDEO, |
243 | }, |
244 | { NULL } |
245 | }; |
246 | |
247 | AVFilter ff_vf_colorlevels = { |
248 | .name = "colorlevels", |
249 | .description = NULL_IF_CONFIG_SMALL("Adjust the color levels."), |
250 | .priv_size = sizeof(ColorLevelsContext), |
251 | .priv_class = &colorlevels_class, |
252 | .query_formats = query_formats, |
253 | .inputs = colorlevels_inputs, |
254 | .outputs = colorlevels_outputs, |
255 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
256 | }; |
257 |