| 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 FFFilter ff_vf_histeq = { | ||
| 257 | .p.name = "histeq", | ||
| 258 | .p.description = NULL_IF_CONFIG_SMALL("Apply global color histogram equalization."), | ||
| 259 | .p.priv_class = &histeq_class, | ||
| 260 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, | ||
| 261 | .priv_size = sizeof(HisteqContext), | ||
| 262 | .init = init, | ||
| 263 | FILTER_INPUTS(histeq_inputs), | ||
| 264 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
| 265 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 266 | }; | ||
| 267 |