Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2012 Jeremy Tran | |
3 | * Copyright (c) 2001 Donald A. Graft | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (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 | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along | |
18 | * with FFmpeg; if not, write to the Free Software Foundation, Inc., | |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
20 | */ | |
21 | ||
22 | /** | |
23 | * @file | |
24 | * Histogram equalization filter, based on the VirtualDub filter by | |
25 | * Donald A. Graft <neuron2 AT home DOT com>. | |
26 | * Implements global automatic contrast adjustment by means of | |
27 | * histogram equalization. | |
28 | */ | |
29 | ||
30 | #include "libavutil/common.h" | |
31 | #include "libavutil/opt.h" | |
32 | #include "libavutil/pixdesc.h" | |
33 | ||
34 | #include "avfilter.h" | |
35 | #include "drawutils.h" | |
36 | #include "formats.h" | |
37 | #include "internal.h" | |
38 | #include "video.h" | |
39 | ||
40 | // #define DEBUG | |
41 | ||
42 | // Linear Congruential Generator, see "Numerical Recipes" | |
43 | #define LCG_A 4096 | |
44 | #define LCG_C 150889 | |
45 | #define LCG_M 714025 | |
46 | #define LCG(x) (((x) * LCG_A + LCG_C) % LCG_M) | |
47 | #define LCG_SEED 739187 | |
48 | ||
49 | enum HisteqAntibanding { | |
50 | HISTEQ_ANTIBANDING_NONE = 0, | |
51 | HISTEQ_ANTIBANDING_WEAK = 1, | |
52 | HISTEQ_ANTIBANDING_STRONG = 2, | |
53 | HISTEQ_ANTIBANDING_NB, | |
54 | }; | |
55 | ||
56 | typedef struct { | |
57 | const AVClass *class; | |
58 | float strength; | |
59 | float intensity; | |
60 | enum HisteqAntibanding antibanding; | |
61 | int in_histogram [256]; ///< input histogram | |
62 | int out_histogram[256]; ///< output histogram | |
63 | int LUT[256]; ///< lookup table derived from histogram[] | |
64 | uint8_t rgba_map[4]; ///< components position | |
65 | int bpp; ///< bytes per pixel | |
66 | } HisteqContext; | |
67 | ||
68 | #define OFFSET(x) offsetof(HisteqContext, x) | |
69 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | |
70 | #define CONST(name, help, val, unit) { name, help, 0, AV_OPT_TYPE_CONST, {.i64=val}, INT_MIN, INT_MAX, FLAGS, unit } | |
71 | ||
72 | static const AVOption histeq_options[] = { | |
73 | { "strength", "set the strength", OFFSET(strength), AV_OPT_TYPE_FLOAT, {.dbl=0.2}, 0, 1, FLAGS }, | |
74 | { "intensity", "set the intensity", OFFSET(intensity), AV_OPT_TYPE_FLOAT, {.dbl=0.21}, 0, 1, FLAGS }, | |
75 | { "antibanding", "set the antibanding level", OFFSET(antibanding), AV_OPT_TYPE_INT, {.i64=HISTEQ_ANTIBANDING_NONE}, 0, HISTEQ_ANTIBANDING_NB-1, FLAGS, "antibanding" }, | |
76 | CONST("none", "apply no antibanding", HISTEQ_ANTIBANDING_NONE, "antibanding"), | |
77 | CONST("weak", "apply weak antibanding", HISTEQ_ANTIBANDING_WEAK, "antibanding"), | |
78 | CONST("strong", "apply strong antibanding", HISTEQ_ANTIBANDING_STRONG, "antibanding"), | |
79 | { NULL } | |
80 | }; | |
81 | ||
82 | AVFILTER_DEFINE_CLASS(histeq); | |
83 | ||
84 | static av_cold int init(AVFilterContext *ctx) | |
85 | { | |
86 | HisteqContext *histeq = ctx->priv; | |
87 | ||
88 | av_log(ctx, AV_LOG_VERBOSE, | |
89 | "strength:%0.3f intensity:%0.3f antibanding:%d\n", | |
90 | histeq->strength, histeq->intensity, histeq->antibanding); | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | static int query_formats(AVFilterContext *ctx) | |
96 | { | |
97 | static const enum PixelFormat pix_fmts[] = { | |
98 | AV_PIX_FMT_ARGB, AV_PIX_FMT_RGBA, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA, | |
99 | AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, | |
100 | AV_PIX_FMT_NONE | |
101 | }; | |
102 | ||
103 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static int config_input(AVFilterLink *inlink) | |
108 | { | |
109 | AVFilterContext *ctx = inlink->dst; | |
110 | HisteqContext *histeq = ctx->priv; | |
111 | const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink->format); | |
112 | ||
113 | histeq->bpp = av_get_bits_per_pixel(pix_desc) / 8; | |
114 | ff_fill_rgba_map(histeq->rgba_map, inlink->format); | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
119 | #define R 0 | |
120 | #define G 1 | |
121 | #define B 2 | |
122 | #define A 3 | |
123 | ||
124 | #define GET_RGB_VALUES(r, g, b, src, map) do { \ | |
125 | r = src[x + map[R]]; \ | |
126 | g = src[x + map[G]]; \ | |
127 | b = src[x + map[B]]; \ | |
128 | } while (0) | |
129 | ||
130 | static int filter_frame(AVFilterLink *inlink, AVFrame *inpic) | |
131 | { | |
132 | AVFilterContext *ctx = inlink->dst; | |
133 | HisteqContext *histeq = ctx->priv; | |
134 | AVFilterLink *outlink = ctx->outputs[0]; | |
135 | int strength = histeq->strength * 1000; | |
136 | int intensity = histeq->intensity * 1000; | |
137 | int x, y, i, luthi, lutlo, lut, luma, oluma, m; | |
138 | AVFrame *outpic; | |
139 | unsigned int r, g, b, jran; | |
140 | uint8_t *src, *dst; | |
141 | ||
142 | outpic = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
143 | if (!outpic) { | |
144 | av_frame_free(&inpic); | |
145 | return AVERROR(ENOMEM); | |
146 | } | |
147 | av_frame_copy_props(outpic, inpic); | |
148 | ||
149 | /* Seed random generator for antibanding. */ | |
150 | jran = LCG_SEED; | |
151 | ||
152 | /* Calculate and store the luminance and calculate the global histogram | |
153 | based on the luminance. */ | |
154 | memset(histeq->in_histogram, 0, sizeof(histeq->in_histogram)); | |
155 | src = inpic->data[0]; | |
156 | dst = outpic->data[0]; | |
157 | for (y = 0; y < inlink->h; y++) { | |
158 | for (x = 0; x < inlink->w * histeq->bpp; x += histeq->bpp) { | |
159 | GET_RGB_VALUES(r, g, b, src, histeq->rgba_map); | |
160 | luma = (55 * r + 182 * g + 19 * b) >> 8; | |
161 | dst[x + histeq->rgba_map[A]] = luma; | |
162 | histeq->in_histogram[luma]++; | |
163 | } | |
164 | src += inpic->linesize[0]; | |
165 | dst += outpic->linesize[0]; | |
166 | } | |
167 | ||
168 | #ifdef DEBUG | |
169 | for (x = 0; x < 256; x++) | |
170 | av_dlog(ctx, "in[%d]: %u\n", x, histeq->in_histogram[x]); | |
171 | #endif | |
172 | ||
173 | /* Calculate the lookup table. */ | |
174 | histeq->LUT[0] = histeq->in_histogram[0]; | |
175 | /* Accumulate */ | |
176 | for (x = 1; x < 256; x++) | |
177 | histeq->LUT[x] = histeq->LUT[x-1] + histeq->in_histogram[x]; | |
178 | ||
179 | /* Normalize */ | |
180 | for (x = 0; x < 256; x++) | |
181 | histeq->LUT[x] = (histeq->LUT[x] * intensity) / (inlink->h * inlink->w); | |
182 | ||
183 | /* Adjust the LUT based on the selected strength. This is an alpha | |
184 | mix of the calculated LUT and a linear LUT with gain 1. */ | |
185 | for (x = 0; x < 256; x++) | |
186 | histeq->LUT[x] = (strength * histeq->LUT[x]) / 255 + | |
187 | ((255 - strength) * x) / 255; | |
188 | ||
189 | /* Output the equalized frame. */ | |
190 | memset(histeq->out_histogram, 0, sizeof(histeq->out_histogram)); | |
191 | ||
192 | src = inpic->data[0]; | |
193 | dst = outpic->data[0]; | |
194 | for (y = 0; y < inlink->h; y++) { | |
195 | for (x = 0; x < inlink->w * histeq->bpp; x += histeq->bpp) { | |
196 | luma = dst[x + histeq->rgba_map[A]]; | |
197 | if (luma == 0) { | |
198 | for (i = 0; i < histeq->bpp; ++i) | |
199 | dst[x + i] = 0; | |
200 | histeq->out_histogram[0]++; | |
201 | } else { | |
202 | lut = histeq->LUT[luma]; | |
203 | if (histeq->antibanding != HISTEQ_ANTIBANDING_NONE) { | |
204 | if (luma > 0) { | |
205 | lutlo = histeq->antibanding == HISTEQ_ANTIBANDING_WEAK ? | |
206 | (histeq->LUT[luma] + histeq->LUT[luma - 1]) / 2 : | |
207 | histeq->LUT[luma - 1]; | |
208 | } else | |
209 | lutlo = lut; | |
210 | ||
211 | if (luma < 255) { | |
212 | luthi = (histeq->antibanding == HISTEQ_ANTIBANDING_WEAK) ? | |
213 | (histeq->LUT[luma] + histeq->LUT[luma + 1]) / 2 : | |
214 | histeq->LUT[luma + 1]; | |
215 | } else | |
216 | luthi = lut; | |
217 | ||
218 | if (lutlo != luthi) { | |
219 | jran = LCG(jran); | |
220 | lut = lutlo + ((luthi - lutlo + 1) * jran) / LCG_M; | |
221 | } | |
222 | } | |
223 | ||
224 | GET_RGB_VALUES(r, g, b, src, histeq->rgba_map); | |
225 | if (((m = FFMAX3(r, g, b)) * lut) / luma > 255) { | |
226 | r = (r * 255) / m; | |
227 | g = (g * 255) / m; | |
228 | b = (b * 255) / m; | |
229 | } else { | |
230 | r = (r * lut) / luma; | |
231 | g = (g * lut) / luma; | |
232 | b = (b * lut) / luma; | |
233 | } | |
234 | dst[x + histeq->rgba_map[R]] = r; | |
235 | dst[x + histeq->rgba_map[G]] = g; | |
236 | dst[x + histeq->rgba_map[B]] = b; | |
237 | oluma = av_clip_uint8((55 * r + 182 * g + 19 * b) >> 8); | |
238 | histeq->out_histogram[oluma]++; | |
239 | } | |
240 | } | |
241 | src += inpic->linesize[0]; | |
242 | dst += outpic->linesize[0]; | |
243 | } | |
244 | #ifdef DEBUG | |
245 | for (x = 0; x < 256; x++) | |
246 | av_dlog(ctx, "out[%d]: %u\n", x, histeq->out_histogram[x]); | |
247 | #endif | |
248 | ||
249 | av_frame_free(&inpic); | |
250 | return ff_filter_frame(outlink, outpic); | |
251 | } | |
252 | ||
253 | static const AVFilterPad histeq_inputs[] = { | |
254 | { | |
255 | .name = "default", | |
256 | .type = AVMEDIA_TYPE_VIDEO, | |
257 | .config_props = config_input, | |
258 | .filter_frame = filter_frame, | |
259 | }, | |
260 | { NULL } | |
261 | }; | |
262 | ||
263 | static const AVFilterPad histeq_outputs[] = { | |
264 | { | |
265 | .name = "default", | |
266 | .type = AVMEDIA_TYPE_VIDEO, | |
267 | }, | |
268 | { NULL } | |
269 | }; | |
270 | ||
271 | AVFilter ff_vf_histeq = { | |
272 | .name = "histeq", | |
273 | .description = NULL_IF_CONFIG_SMALL("Apply global color histogram equalization."), | |
274 | .priv_size = sizeof(HisteqContext), | |
275 | .init = init, | |
276 | .query_formats = query_formats, | |
277 | .inputs = histeq_inputs, | |
278 | .outputs = histeq_outputs, | |
279 | .priv_class = &histeq_class, | |
280 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, | |
281 | }; |