Browse code

lavfi: add colorlevels filter

Signed-off-by: Paul B Mahol <onemda@gmail.com>

Paul B Mahol authored on 2014/12/06 23:04:38
Showing 6 changed files
... ...
@@ -4,6 +4,7 @@ releases are sorted from youngest to oldest.
4 4
 version <next>:
5 5
 - nvenc encoder
6 6
 - 10bit spp filter
7
+- colorlevels filter
7 8
 
8 9
 
9 10
 version 2.5:
... ...
@@ -2934,6 +2934,75 @@ colorbalance=rs=.3
2934 2934
 @end example
2935 2935
 @end itemize
2936 2936
 
2937
+@section colorlevels
2938
+
2939
+Adjust video input frames using levels.
2940
+
2941
+The filter accepts the following options:
2942
+
2943
+@table @option
2944
+@item rimin
2945
+@item gimin
2946
+@item bimin
2947
+@item aimin
2948
+Adjust red, green, blue and alpha input black point.
2949
+Allowed ranges for options are @code{[-1.0, 1.0]}. Defaults are @code{0}.
2950
+
2951
+@item rimax
2952
+@item gimax
2953
+@item bimax
2954
+@item aimax
2955
+Adjust red, green, blue and alpha input white point.
2956
+Allowed ranges for options are @code{[-1.0, 1.0]}. Defaults are @code{1}.
2957
+
2958
+Input levels are used to lighten highlights (bright tones), darken shadows
2959
+(dark tones), change the balance of bright and dark tones.
2960
+
2961
+@item romin
2962
+@item gomin
2963
+@item bomin
2964
+@item aomin
2965
+Adjust red, green, blue and alpha output black point.
2966
+Allowed ranges for options are @code{[0, 1.0]}. Defaults are @code{0}.
2967
+
2968
+@item romax
2969
+@item gomax
2970
+@item bomax
2971
+@item aomax
2972
+Adjust red, green, blue and alpha output white point.
2973
+Allowed ranges for options are @code{[0, 1.0]}. Defaults are @code{1}.
2974
+
2975
+Output levels allows manual selection of a constrained output level range.
2976
+@end table
2977
+
2978
+@subsection Examples
2979
+
2980
+@itemize
2981
+@item
2982
+Make video output darker:
2983
+@example
2984
+colorlevels=rimin=0.058:gimin=0.058:bimin=0.058
2985
+@end example
2986
+
2987
+@item
2988
+Increase contrast:
2989
+@example
2990
+colorlevels=rimin=0.039:gimin=0.039:bimin=0.039:rimax=0.96:gimax=0.96:bimax=0.96
2991
+@end example
2992
+
2993
+@item
2994
+Make video output lighter:
2995
+@example
2996
+colorlevels=rimax=0.902:gimax=0.902:bimax=0.902
2997
+@end example
2998
+
2999
+@item
3000
+Increase brightness:
3001
+@example
3002
+colorlevels=romin=0.5:gomin=0.5:bomin=0.5
3003
+@end example
3004
+@end itemize
3005
+
2937 3006
 @section colorchannelmixer
2938 3007
 
2939 3008
 Adjust video input frames by re-mixing color channels.
... ...
@@ -100,6 +100,7 @@ OBJS-$(CONFIG_BOXBLUR_FILTER)                += vf_boxblur.o
100 100
 OBJS-$(CONFIG_CODECVIEW_FILTER)              += vf_codecview.o
101 101
 OBJS-$(CONFIG_COLORBALANCE_FILTER)           += vf_colorbalance.o
102 102
 OBJS-$(CONFIG_COLORCHANNELMIXER_FILTER)      += vf_colorchannelmixer.o
103
+OBJS-$(CONFIG_COLORLEVELS_FILTER)            += vf_colorlevels.o
103 104
 OBJS-$(CONFIG_COLORMATRIX_FILTER)            += vf_colormatrix.o
104 105
 OBJS-$(CONFIG_COPY_FILTER)                   += vf_copy.o
105 106
 OBJS-$(CONFIG_CROP_FILTER)                   += vf_crop.o
... ...
@@ -116,6 +116,7 @@ void avfilter_register_all(void)
116 116
     REGISTER_FILTER(CODECVIEW,      codecview,      vf);
117 117
     REGISTER_FILTER(COLORBALANCE,   colorbalance,   vf);
118 118
     REGISTER_FILTER(COLORCHANNELMIXER, colorchannelmixer, vf);
119
+    REGISTER_FILTER(COLORLEVELS,    colorlevels,    vf);
119 120
     REGISTER_FILTER(COLORMATRIX,    colormatrix,    vf);
120 121
     REGISTER_FILTER(COPY,           copy,           vf);
121 122
     REGISTER_FILTER(CROP,           crop,           vf);
... ...
@@ -30,8 +30,8 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR  5
33
-#define LIBAVFILTER_VERSION_MINOR  3
34
-#define LIBAVFILTER_VERSION_MICRO 101
33
+#define LIBAVFILTER_VERSION_MINOR  4
34
+#define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
                                                LIBAVFILTER_VERSION_MINOR, \
38 38
new file mode 100644
... ...
@@ -0,0 +1,254 @@
0
+/*
1
+ * Copyright (c) 2013 Paul B Mahol
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+#include "libavutil/imgutils.h"
21
+#include "libavutil/opt.h"
22
+#include "libavutil/pixdesc.h"
23
+#include "avfilter.h"
24
+#include "drawutils.h"
25
+#include "formats.h"
26
+#include "internal.h"
27
+#include "video.h"
28
+
29
+#define R 0
30
+#define G 1
31
+#define B 2
32
+#define A 3
33
+
34
+typedef struct {
35
+    double in_min, in_max;
36
+    double out_min, out_max;
37
+} Range;
38
+
39
+typedef struct {
40
+    const AVClass *class;
41
+    Range range[4];
42
+    int nb_comp;
43
+    int bpp;
44
+    int step;
45
+    uint8_t rgba_map[4];
46
+    int linesize;
47
+} ColorLevelsContext;
48
+
49
+#define OFFSET(x) offsetof(ColorLevelsContext, x)
50
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
51
+static const AVOption colorlevels_options[] = {
52
+    { "rimin", "set input red black point",    OFFSET(range[R].in_min),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS },
53
+    { "gimin", "set input green black point",  OFFSET(range[G].in_min),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS },
54
+    { "bimin", "set input blue black point",   OFFSET(range[B].in_min),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS },
55
+    { "aimin", "set input alpha black point",  OFFSET(range[A].in_min),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -1, 1, FLAGS },
56
+    { "rimax", "set input red white point",    OFFSET(range[R].in_max),  AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS },
57
+    { "gimax", "set input green white point",  OFFSET(range[G].in_max),  AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS },
58
+    { "bimax", "set input blue white point",   OFFSET(range[B].in_max),  AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS },
59
+    { "aimax", "set input alpha white point",  OFFSET(range[A].in_max),  AV_OPT_TYPE_DOUBLE, {.dbl=1}, -1, 1, FLAGS },
60
+    { "romin", "set output red black point",   OFFSET(range[R].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0},  0, 1, FLAGS },
61
+    { "gomin", "set output green black point", OFFSET(range[G].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0},  0, 1, FLAGS },
62
+    { "bomin", "set output blue black point",  OFFSET(range[B].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0},  0, 1, FLAGS },
63
+    { "aomin", "set output alpha black point", OFFSET(range[A].out_min), AV_OPT_TYPE_DOUBLE, {.dbl=0},  0, 1, FLAGS },
64
+    { "romax", "set output red white point",   OFFSET(range[R].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1},  0, 1, FLAGS },
65
+    { "gomax", "set output green white point", OFFSET(range[G].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1},  0, 1, FLAGS },
66
+    { "bomax", "set output blue white point",  OFFSET(range[B].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1},  0, 1, FLAGS },
67
+    { "aomax", "set output alpha white point", OFFSET(range[A].out_max), AV_OPT_TYPE_DOUBLE, {.dbl=1},  0, 1, FLAGS },
68
+    { NULL }
69
+};
70
+
71
+AVFILTER_DEFINE_CLASS(colorlevels);
72
+
73
+static int query_formats(AVFilterContext *ctx)
74
+{
75
+    static const enum AVPixelFormat pix_fmts[] = {
76
+        AV_PIX_FMT_0RGB,  AV_PIX_FMT_0BGR,
77
+        AV_PIX_FMT_ARGB,  AV_PIX_FMT_ABGR,
78
+        AV_PIX_FMT_RGB0,  AV_PIX_FMT_BGR0,
79
+        AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
80
+        AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48,
81
+        AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64,
82
+        AV_PIX_FMT_RGBA,  AV_PIX_FMT_BGRA,
83
+        AV_PIX_FMT_NONE
84
+    };
85
+
86
+    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
87
+    return 0;
88
+}
89
+
90
+static int config_input(AVFilterLink *inlink)
91
+{
92
+    AVFilterContext *ctx = inlink->dst;
93
+    ColorLevelsContext *s = ctx->priv;
94
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
95
+
96
+    s->nb_comp = desc->nb_components;
97
+    s->bpp = (desc->comp[0].depth_minus1 + 1) >> 3;
98
+    s->step = (av_get_padded_bits_per_pixel(desc) >> 3) / s->bpp;
99
+    s->linesize = inlink->w * s->step;
100
+    ff_fill_rgba_map(s->rgba_map, inlink->format);
101
+
102
+    return 0;
103
+}
104
+
105
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
106
+{
107
+    AVFilterContext *ctx = inlink->dst;
108
+    ColorLevelsContext *s = ctx->priv;
109
+    AVFilterLink *outlink = ctx->outputs[0];
110
+    const int step = s->step;
111
+    AVFrame *out;
112
+    int x, y, i;
113
+
114
+    if (av_frame_is_writable(in)) {
115
+        out = in;
116
+    } else {
117
+        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
118
+        if (!out) {
119
+            av_frame_free(&in);
120
+            return AVERROR(ENOMEM);
121
+        }
122
+        av_frame_copy_props(out, in);
123
+    }
124
+
125
+    switch (s->bpp) {
126
+    case 1:
127
+        for (i = 0; i < s->nb_comp; i++) {
128
+            Range *r = &s->range[i];
129
+            const uint8_t offset = s->rgba_map[i];
130
+            const uint8_t *srcrow = in->data[0];
131
+            uint8_t *dstrow = out->data[0];
132
+            int imin = round(r->in_min  * UINT8_MAX);
133
+            int imax = round(r->in_max  * UINT8_MAX);
134
+            int omin = round(r->out_min * UINT8_MAX);
135
+            int omax = round(r->out_max * UINT8_MAX);
136
+            double coeff;
137
+
138
+            if (imin < 0) {
139
+                imin = UINT8_MAX;
140
+                for (y = 0; y < inlink->h; y++) {
141
+                    const uint8_t *src = srcrow;
142
+
143
+                    for (x = 0; x < s->linesize; x += step)
144
+                        imin = FFMIN(imin, src[x + offset]);
145
+                    srcrow += in->linesize[0];
146
+                }
147
+            }
148
+            if (imax < 0) {
149
+                srcrow = in->data[0];
150
+                imax = 0;
151
+                for (y = 0; y < inlink->h; y++) {
152
+                    const uint8_t *src = srcrow;
153
+
154
+                    for (x = 0; x < s->linesize; x += step)
155
+                        imax = FFMAX(imax, src[x + offset]);
156
+                    srcrow += in->linesize[0];
157
+                }
158
+            }
159
+
160
+            srcrow = in->data[0];
161
+            coeff = (omax - omin) / (double)(imax - imin);
162
+            for (y = 0; y < inlink->h; y++) {
163
+                const uint8_t *src = srcrow;
164
+                uint8_t *dst = dstrow;
165
+
166
+                for (x = 0; x < s->linesize; x += step)
167
+                    dst[x + offset] = av_clip_uint8((src[x + offset] - imin) * coeff + omin);
168
+                dstrow += out->linesize[0];
169
+                srcrow += in->linesize[0];
170
+            }
171
+        }
172
+        break;
173
+    case 2:
174
+        for (i = 0; i < s->nb_comp; i++) {
175
+            Range *r = &s->range[i];
176
+            const uint8_t offset = s->rgba_map[i];
177
+            const uint8_t *srcrow = in->data[0];
178
+            uint8_t *dstrow = out->data[0];
179
+            int imin = round(r->in_min  * UINT16_MAX);
180
+            int imax = round(r->in_max  * UINT16_MAX);
181
+            int omin = round(r->out_min * UINT16_MAX);
182
+            int omax = round(r->out_max * UINT16_MAX);
183
+            double coeff;
184
+
185
+            if (imin < 0) {
186
+                imin = UINT16_MAX;
187
+                for (y = 0; y < inlink->h; y++) {
188
+                    const uint16_t *src = (const uint16_t *)srcrow;
189
+
190
+                    for (x = 0; x < s->linesize; x += step)
191
+                        imin = FFMIN(imin, src[x + offset]);
192
+                    srcrow += in->linesize[0];
193
+                }
194
+            }
195
+            if (imax < 0) {
196
+                srcrow = in->data[0];
197
+                imax = 0;
198
+                for (y = 0; y < inlink->h; y++) {
199
+                    const uint16_t *src = (const uint16_t *)srcrow;
200
+
201
+                    for (x = 0; x < s->linesize; x += step)
202
+                        imax = FFMAX(imax, src[x + offset]);
203
+                    srcrow += in->linesize[0];
204
+                }
205
+            }
206
+
207
+            srcrow = in->data[0];
208
+            coeff = (omax - omin) / (double)(imax - imin);
209
+            for (y = 0; y < inlink->h; y++) {
210
+                const uint16_t *src = (const uint16_t*)srcrow;
211
+                uint16_t *dst = (uint16_t *)dstrow;
212
+
213
+                for (x = 0; x < s->linesize; x += step)
214
+                    dst[x + offset] = av_clip_uint16((src[x + offset] - imin) * coeff + omin);
215
+                dstrow += out->linesize[0];
216
+                srcrow += in->linesize[0];
217
+            }
218
+        }
219
+    }
220
+
221
+    if (in != out)
222
+        av_frame_free(&in);
223
+    return ff_filter_frame(outlink, out);
224
+}
225
+
226
+static const AVFilterPad colorlevels_inputs[] = {
227
+    {
228
+        .name         = "default",
229
+        .type         = AVMEDIA_TYPE_VIDEO,
230
+        .filter_frame = filter_frame,
231
+        .config_props = config_input,
232
+    },
233
+    { NULL }
234
+};
235
+
236
+static const AVFilterPad colorlevels_outputs[] = {
237
+    {
238
+        .name = "default",
239
+        .type = AVMEDIA_TYPE_VIDEO,
240
+    },
241
+    { NULL }
242
+};
243
+
244
+AVFilter ff_vf_colorlevels = {
245
+    .name          = "colorlevels",
246
+    .description   = NULL_IF_CONFIG_SMALL("Adjust the color levels."),
247
+    .priv_size     = sizeof(ColorLevelsContext),
248
+    .priv_class    = &colorlevels_class,
249
+    .query_formats = query_formats,
250
+    .inputs        = colorlevels_inputs,
251
+    .outputs       = colorlevels_outputs,
252
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
253
+};