Line |
Branch |
Exec |
Source |
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/internal.h" |
32 |
|
|
#include "libavutil/opt.h" |
33 |
|
|
#include "libavutil/pixdesc.h" |
34 |
|
|
|
35 |
|
|
#include "avfilter.h" |
36 |
|
|
#include "drawutils.h" |
37 |
|
|
#include "filters.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 HisteqContext { |
57 |
|
|
const AVClass *class; |
58 |
|
|
float strength; |
59 |
|
|
float intensity; |
60 |
|
|
int antibanding; ///< HisteqAntibanding |
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, u) { name, help, 0, AV_OPT_TYPE_CONST, {.i64=val}, INT_MIN, INT_MAX, FLAGS, .unit = u } |
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, .unit = "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 const enum AVPixelFormat pix_fmts[] = { |
96 |
|
|
AV_PIX_FMT_ARGB, AV_PIX_FMT_RGBA, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA, |
97 |
|
|
AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, |
98 |
|
|
AV_PIX_FMT_NONE |
99 |
|
|
}; |
100 |
|
|
|
101 |
|
✗ |
static int config_input(AVFilterLink *inlink) |
102 |
|
|
{ |
103 |
|
✗ |
AVFilterContext *ctx = inlink->dst; |
104 |
|
✗ |
HisteqContext *histeq = ctx->priv; |
105 |
|
✗ |
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink->format); |
106 |
|
|
|
107 |
|
✗ |
histeq->bpp = av_get_bits_per_pixel(pix_desc) / 8; |
108 |
|
✗ |
ff_fill_rgba_map(histeq->rgba_map, inlink->format); |
109 |
|
|
|
110 |
|
✗ |
return 0; |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
#define R 0 |
114 |
|
|
#define G 1 |
115 |
|
|
#define B 2 |
116 |
|
|
#define A 3 |
117 |
|
|
|
118 |
|
|
#define GET_RGB_VALUES(r, g, b, src, map) do { \ |
119 |
|
|
r = src[x + map[R]]; \ |
120 |
|
|
g = src[x + map[G]]; \ |
121 |
|
|
b = src[x + map[B]]; \ |
122 |
|
|
} while (0) |
123 |
|
|
|
124 |
|
✗ |
static int filter_frame(AVFilterLink *inlink, AVFrame *inpic) |
125 |
|
|
{ |
126 |
|
✗ |
AVFilterContext *ctx = inlink->dst; |
127 |
|
✗ |
HisteqContext *histeq = ctx->priv; |
128 |
|
✗ |
AVFilterLink *outlink = ctx->outputs[0]; |
129 |
|
✗ |
int strength = histeq->strength * 1000; |
130 |
|
✗ |
int intensity = histeq->intensity * 1000; |
131 |
|
|
int x, y, i, luthi, lutlo, lut, luma, oluma, m; |
132 |
|
|
AVFrame *outpic; |
133 |
|
|
unsigned int r, g, b, jran; |
134 |
|
|
uint8_t *src, *dst; |
135 |
|
|
|
136 |
|
✗ |
outpic = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
137 |
|
✗ |
if (!outpic) { |
138 |
|
✗ |
av_frame_free(&inpic); |
139 |
|
✗ |
return AVERROR(ENOMEM); |
140 |
|
|
} |
141 |
|
✗ |
av_frame_copy_props(outpic, inpic); |
142 |
|
|
|
143 |
|
|
/* Seed random generator for antibanding. */ |
144 |
|
✗ |
jran = LCG_SEED; |
145 |
|
|
|
146 |
|
|
/* Calculate and store the luminance and calculate the global histogram |
147 |
|
|
based on the luminance. */ |
148 |
|
✗ |
memset(histeq->in_histogram, 0, sizeof(histeq->in_histogram)); |
149 |
|
✗ |
src = inpic->data[0]; |
150 |
|
✗ |
dst = outpic->data[0]; |
151 |
|
✗ |
for (y = 0; y < inlink->h; y++) { |
152 |
|
✗ |
for (x = 0; x < inlink->w * histeq->bpp; x += histeq->bpp) { |
153 |
|
✗ |
GET_RGB_VALUES(r, g, b, src, histeq->rgba_map); |
154 |
|
✗ |
luma = (55 * r + 182 * g + 19 * b) >> 8; |
155 |
|
✗ |
dst[x + histeq->rgba_map[A]] = luma; |
156 |
|
✗ |
histeq->in_histogram[luma]++; |
157 |
|
|
} |
158 |
|
✗ |
src += inpic->linesize[0]; |
159 |
|
✗ |
dst += outpic->linesize[0]; |
160 |
|
|
} |
161 |
|
|
|
162 |
|
|
#ifdef DEBUG |
163 |
|
|
for (x = 0; x < 256; x++) |
164 |
|
|
ff_dlog(ctx, "in[%d]: %u\n", x, histeq->in_histogram[x]); |
165 |
|
|
#endif |
166 |
|
|
|
167 |
|
|
/* Calculate the lookup table. */ |
168 |
|
✗ |
histeq->LUT[0] = histeq->in_histogram[0]; |
169 |
|
|
/* Accumulate */ |
170 |
|
✗ |
for (x = 1; x < 256; x++) |
171 |
|
✗ |
histeq->LUT[x] = histeq->LUT[x-1] + histeq->in_histogram[x]; |
172 |
|
|
|
173 |
|
|
/* Normalize */ |
174 |
|
✗ |
for (x = 0; x < 256; x++) |
175 |
|
✗ |
histeq->LUT[x] = (histeq->LUT[x] * intensity) / (inlink->h * inlink->w); |
176 |
|
|
|
177 |
|
|
/* Adjust the LUT based on the selected strength. This is an alpha |
178 |
|
|
mix of the calculated LUT and a linear LUT with gain 1. */ |
179 |
|
✗ |
for (x = 0; x < 256; x++) |
180 |
|
✗ |
histeq->LUT[x] = (strength * histeq->LUT[x]) / 255 + |
181 |
|
✗ |
((255 - strength) * x) / 255; |
182 |
|
|
|
183 |
|
|
/* Output the equalized frame. */ |
184 |
|
✗ |
memset(histeq->out_histogram, 0, sizeof(histeq->out_histogram)); |
185 |
|
|
|
186 |
|
✗ |
src = inpic->data[0]; |
187 |
|
✗ |
dst = outpic->data[0]; |
188 |
|
✗ |
for (y = 0; y < inlink->h; y++) { |
189 |
|
✗ |
for (x = 0; x < inlink->w * histeq->bpp; x += histeq->bpp) { |
190 |
|
✗ |
luma = dst[x + histeq->rgba_map[A]]; |
191 |
|
✗ |
if (luma == 0) { |
192 |
|
✗ |
for (i = 0; i < histeq->bpp; ++i) |
193 |
|
✗ |
dst[x + i] = 0; |
194 |
|
✗ |
histeq->out_histogram[0]++; |
195 |
|
|
} else { |
196 |
|
✗ |
lut = histeq->LUT[luma]; |
197 |
|
✗ |
if (histeq->antibanding != HISTEQ_ANTIBANDING_NONE) { |
198 |
|
✗ |
if (luma > 0) { |
199 |
|
✗ |
lutlo = histeq->antibanding == HISTEQ_ANTIBANDING_WEAK ? |
200 |
|
✗ |
(histeq->LUT[luma] + histeq->LUT[luma - 1]) / 2 : |
201 |
|
✗ |
histeq->LUT[luma - 1]; |
202 |
|
|
} else |
203 |
|
✗ |
lutlo = lut; |
204 |
|
|
|
205 |
|
✗ |
if (luma < 255) { |
206 |
|
✗ |
luthi = (histeq->antibanding == HISTEQ_ANTIBANDING_WEAK) ? |
207 |
|
✗ |
(histeq->LUT[luma] + histeq->LUT[luma + 1]) / 2 : |
208 |
|
✗ |
histeq->LUT[luma + 1]; |
209 |
|
|
} else |
210 |
|
✗ |
luthi = lut; |
211 |
|
|
|
212 |
|
✗ |
if (lutlo != luthi) { |
213 |
|
✗ |
jran = LCG(jran); |
214 |
|
✗ |
lut = lutlo + ((luthi - lutlo + 1) * jran) / LCG_M; |
215 |
|
|
} |
216 |
|
|
} |
217 |
|
|
|
218 |
|
✗ |
GET_RGB_VALUES(r, g, b, src, histeq->rgba_map); |
219 |
|
✗ |
if (((m = FFMAX3(r, g, b)) * lut) / luma > 255) { |
220 |
|
✗ |
r = (r * 255) / m; |
221 |
|
✗ |
g = (g * 255) / m; |
222 |
|
✗ |
b = (b * 255) / m; |
223 |
|
|
} else { |
224 |
|
✗ |
r = (r * lut) / luma; |
225 |
|
✗ |
g = (g * lut) / luma; |
226 |
|
✗ |
b = (b * lut) / luma; |
227 |
|
|
} |
228 |
|
✗ |
dst[x + histeq->rgba_map[R]] = r; |
229 |
|
✗ |
dst[x + histeq->rgba_map[G]] = g; |
230 |
|
✗ |
dst[x + histeq->rgba_map[B]] = b; |
231 |
|
✗ |
oluma = av_clip_uint8((55 * r + 182 * g + 19 * b) >> 8); |
232 |
|
✗ |
histeq->out_histogram[oluma]++; |
233 |
|
|
} |
234 |
|
|
} |
235 |
|
✗ |
src += inpic->linesize[0]; |
236 |
|
✗ |
dst += outpic->linesize[0]; |
237 |
|
|
} |
238 |
|
|
#ifdef DEBUG |
239 |
|
|
for (x = 0; x < 256; x++) |
240 |
|
|
ff_dlog(ctx, "out[%d]: %u\n", x, histeq->out_histogram[x]); |
241 |
|
|
#endif |
242 |
|
|
|
243 |
|
✗ |
av_frame_free(&inpic); |
244 |
|
✗ |
return ff_filter_frame(outlink, outpic); |
245 |
|
|
} |
246 |
|
|
|
247 |
|
|
static const AVFilterPad histeq_inputs[] = { |
248 |
|
|
{ |
249 |
|
|
.name = "default", |
250 |
|
|
.type = AVMEDIA_TYPE_VIDEO, |
251 |
|
|
.config_props = config_input, |
252 |
|
|
.filter_frame = filter_frame, |
253 |
|
|
}, |
254 |
|
|
}; |
255 |
|
|
|
256 |
|
|
const AVFilter ff_vf_histeq = { |
257 |
|
|
.name = "histeq", |
258 |
|
|
.description = NULL_IF_CONFIG_SMALL("Apply global color histogram equalization."), |
259 |
|
|
.priv_size = sizeof(HisteqContext), |
260 |
|
|
.init = init, |
261 |
|
|
FILTER_INPUTS(histeq_inputs), |
262 |
|
|
FILTER_OUTPUTS(ff_video_default_filterpad), |
263 |
|
|
FILTER_PIXFMTS_ARRAY(pix_fmts), |
264 |
|
|
.priv_class = &histeq_class, |
265 |
|
|
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
266 |
|
|
}; |
267 |
|
|
|