| @@ -0,0 +1,350 @@ |
| | 1 | /* |
| | 2 | * Copyright (c) 2002 Michael Niedermayer <michaelni@gmx.at> |
| | 3 | * Copyright (c) 2011 Stefano Sabatini |
| | 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 | * Apply a boxblur filter to the input video. |
| | 25 | * Ported from MPlayer libmpcodecs/vf_boxblur.c. |
| | 26 | */ |
| | 27 | |
| | 28 | #include "libavutil/avstring.h" |
| | 29 | #include "libavutil/eval.h" |
| | 30 | #include "libavutil/pixdesc.h" |
| | 31 | #include "avfilter.h" |
| | 32 | |
| | 33 | static const char *var_names[] = { |
| | 34 | "w", |
| | 35 | "h", |
| | 36 | "cw", |
| | 37 | "ch", |
| | 38 | "hsub", |
| | 39 | "vsub", |
| | 40 | NULL |
| | 41 | }; |
| | 42 | |
| | 43 | enum var_name { |
| | 44 | VAR_W, |
| | 45 | VAR_H, |
| | 46 | VAR_CW, |
| | 47 | VAR_CH, |
| | 48 | VAR_HSUB, |
| | 49 | VAR_VSUB, |
| | 50 | VARS_NB |
| | 51 | }; |
| | 52 | |
| | 53 | typedef struct { |
| | 54 | int radius; |
| | 55 | int power; |
| | 56 | } FilterParam; |
| | 57 | |
| | 58 | typedef struct { |
| | 59 | FilterParam luma_param; |
| | 60 | FilterParam chroma_param; |
| | 61 | FilterParam alpha_param; |
| | 62 | char luma_radius_expr [256]; |
| | 63 | char chroma_radius_expr[256]; |
| | 64 | char alpha_radius_expr [256]; |
| | 65 | |
| | 66 | int hsub, vsub; |
| | 67 | int radius[4]; |
| | 68 | int power[4]; |
| | 69 | uint8_t *temp[2]; ///< temporary buffer used in blur_power() |
| | 70 | } BoxBlurContext; |
| | 71 | |
| | 72 | #define Y 0 |
| | 73 | #define U 1 |
| | 74 | #define V 2 |
| | 75 | #define A 3 |
| | 76 | |
| | 77 | static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) |
| | 78 | { |
| | 79 | BoxBlurContext *boxblur = ctx->priv; |
| | 80 | int e; |
| | 81 | |
| | 82 | if (!args) { |
| | 83 | av_log(ctx, AV_LOG_ERROR, |
| | 84 | "Filter expects 2 or 4 arguments, none provided\n"); |
| | 85 | return AVERROR(EINVAL); |
| | 86 | } |
| | 87 | |
| | 88 | e = sscanf(args, "%255[^:]:%d:%255[^:]:%d:%255[^:]:%d", |
| | 89 | boxblur->luma_radius_expr, &boxblur->luma_param .power, |
| | 90 | boxblur->chroma_radius_expr, &boxblur->chroma_param.power, |
| | 91 | boxblur->alpha_radius_expr, &boxblur->alpha_param .power); |
| | 92 | |
| | 93 | if (e != 2 && e != 4 && e != 6) { |
| | 94 | av_log(ctx, AV_LOG_ERROR, |
| | 95 | "Filter expects 2 or 4 or 6 params, provided %d\n", e); |
| | 96 | return AVERROR(EINVAL); |
| | 97 | } |
| | 98 | |
| | 99 | if (e < 4) { |
| | 100 | boxblur->chroma_param.power = boxblur->luma_param.power; |
| | 101 | av_strlcpy(boxblur->chroma_radius_expr, boxblur->luma_radius_expr, |
| | 102 | sizeof(boxblur->chroma_radius_expr)); |
| | 103 | } |
| | 104 | if (e < 6) { |
| | 105 | boxblur->alpha_param.power = boxblur->luma_param.power; |
| | 106 | av_strlcpy(boxblur->alpha_radius_expr, boxblur->luma_radius_expr, |
| | 107 | sizeof(boxblur->alpha_radius_expr)); |
| | 108 | } |
| | 109 | |
| | 110 | return 0; |
| | 111 | } |
| | 112 | |
| | 113 | static av_cold void uninit(AVFilterContext *ctx) |
| | 114 | { |
| | 115 | BoxBlurContext *boxblur = ctx->priv; |
| | 116 | |
| | 117 | av_freep(&boxblur->temp[0]); |
| | 118 | av_freep(&boxblur->temp[1]); |
| | 119 | } |
| | 120 | |
| | 121 | static int query_formats(AVFilterContext *ctx) |
| | 122 | { |
| | 123 | enum PixelFormat pix_fmts[] = { |
| | 124 | PIX_FMT_YUV444P, PIX_FMT_YUV422P, PIX_FMT_YUV420P, |
| | 125 | PIX_FMT_YUV411P, PIX_FMT_YUV410P, PIX_FMT_YUVA420P, |
| | 126 | PIX_FMT_YUV440P, PIX_FMT_GRAY8, |
| | 127 | PIX_FMT_YUVJ444P, PIX_FMT_YUVJ422P, PIX_FMT_YUVJ420P, |
| | 128 | PIX_FMT_YUVJ440P, |
| | 129 | PIX_FMT_NONE |
| | 130 | }; |
| | 131 | |
| | 132 | avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts)); |
| | 133 | return 0; |
| | 134 | } |
| | 135 | |
| | 136 | static int config_input(AVFilterLink *inlink) |
| | 137 | { |
| | 138 | AVFilterContext *ctx = inlink->dst; |
| | 139 | BoxBlurContext *boxblur = ctx->priv; |
| | 140 | const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[inlink->format]; |
| | 141 | int w = inlink->w, h = inlink->h; |
| | 142 | int cw, ch; |
| | 143 | double var_values[VARS_NB], res; |
| | 144 | char *expr; |
| | 145 | int ret; |
| | 146 | |
| | 147 | if (!(boxblur->temp[0] = av_malloc(w)) || |
| | 148 | !(boxblur->temp[1] = av_malloc(w))) |
| | 149 | return AVERROR(ENOMEM); |
| | 150 | |
| | 151 | boxblur->hsub = desc->log2_chroma_w; |
| | 152 | boxblur->vsub = desc->log2_chroma_h; |
| | 153 | |
| | 154 | var_values[VAR_W] = inlink->w; |
| | 155 | var_values[VAR_H] = inlink->h; |
| | 156 | var_values[VAR_CW] = cw = w>>boxblur->hsub; |
| | 157 | var_values[VAR_CH] = ch = h>>boxblur->vsub; |
| | 158 | var_values[VAR_HSUB] = 1<<boxblur->hsub; |
| | 159 | var_values[VAR_VSUB] = 1<<boxblur->vsub; |
| | 160 | |
| | 161 | #define EVAL_RADIUS_EXPR(comp) \ |
| | 162 | expr = boxblur->comp##_radius_expr; \ |
| | 163 | ret = av_expr_parse_and_eval(&res, expr, var_names, var_values, \ |
| | 164 | NULL, NULL, NULL, NULL, NULL, 0, ctx); \ |
| | 165 | boxblur->comp##_param.radius = res; \ |
| | 166 | if (ret < 0) { \ |
| | 167 | av_log(NULL, AV_LOG_ERROR, \ |
| | 168 | "Error when evaluating " #comp " radius expression '%s'\n", expr); \ |
| | 169 | return ret; \ |
| | 170 | } |
| | 171 | EVAL_RADIUS_EXPR(luma); |
| | 172 | EVAL_RADIUS_EXPR(chroma); |
| | 173 | EVAL_RADIUS_EXPR(alpha); |
| | 174 | |
| | 175 | av_log(ctx, AV_LOG_INFO, |
| | 176 | "luma_radius:%d luma_power:%d " |
| | 177 | "chroma_radius:%d chroma_power:%d " |
| | 178 | "alpha_radius:%d alpha_power:%d " |
| | 179 | "w:%d chroma_w:%d h:%d chroma_h:%d\n", |
| | 180 | boxblur->luma_param .radius, boxblur->luma_param .power, |
| | 181 | boxblur->chroma_param.radius, boxblur->chroma_param.power, |
| | 182 | boxblur->alpha_param .radius, boxblur->alpha_param .power, |
| | 183 | w, cw, h, ch); |
| | 184 | |
| | 185 | #define CHECK_RADIUS_VAL(w_, h_, comp) \ |
| | 186 | if (boxblur->comp##_param.radius < 0 || \ |
| | 187 | 2*boxblur->comp##_param.radius > FFMIN(w_, h_)) { \ |
| | 188 | av_log(ctx, AV_LOG_ERROR, \ |
| | 189 | "Invalid " #comp " radius value %d, must be >= 0 and <= %d\n", \ |
| | 190 | boxblur->comp##_param.radius, FFMIN(w_, h_)/2); \ |
| | 191 | return AVERROR(EINVAL); \ |
| | 192 | } |
| | 193 | CHECK_RADIUS_VAL(w, h, luma); |
| | 194 | CHECK_RADIUS_VAL(cw, ch, chroma); |
| | 195 | CHECK_RADIUS_VAL(w, h, alpha); |
| | 196 | |
| | 197 | boxblur->radius[Y] = boxblur->luma_param.radius; |
| | 198 | boxblur->radius[U] = boxblur->radius[V] = boxblur->chroma_param.radius; |
| | 199 | boxblur->radius[A] = boxblur->alpha_param.radius; |
| | 200 | |
| | 201 | boxblur->power[Y] = boxblur->luma_param.power; |
| | 202 | boxblur->power[U] = boxblur->power[V] = boxblur->chroma_param.power; |
| | 203 | boxblur->power[A] = boxblur->alpha_param.power; |
| | 204 | |
| | 205 | return 0; |
| | 206 | } |
| | 207 | |
| | 208 | static inline void blur(uint8_t *dst, int dst_step, const uint8_t *src, int src_step, |
| | 209 | int w, int radius) |
| | 210 | { |
| | 211 | /* Naive boxblur would sum source pixels from x-radius .. x+radius |
| | 212 | * for destination pixel x. That would be O(radius*width). |
| | 213 | * If you now look at what source pixels represent 2 consecutive |
| | 214 | * output pixels, then you see they are almost identical and only |
| | 215 | * differ by 2 pixels, like: |
| | 216 | * src0 111111111 |
| | 217 | * dst0 1 |
| | 218 | * src1 111111111 |
| | 219 | * dst1 1 |
| | 220 | * src0-src1 1 -1 |
| | 221 | * so when you know one output pixel you can find the next by just adding |
| | 222 | * and subtracting 1 input pixel. |
| | 223 | * The following code adopts this faster variant. |
| | 224 | */ |
| | 225 | int x, sum = 0; |
| | 226 | const int length = radius*2 + 1; |
| | 227 | const int inv = ((1<<16) + length/2)/length; |
| | 228 | |
| | 229 | for (x = 0; x < radius; x++) |
| | 230 | sum += src[x*src_step]<<1; |
| | 231 | sum += src[radius*src_step]; |
| | 232 | |
| | 233 | for (x = 0; x <= radius; x++) { |
| | 234 | sum += src[(radius+x)*src_step] - src[(radius-x)*src_step]; |
| | 235 | dst[x*dst_step] = (sum*inv + (1<<15))>>16; |
| | 236 | } |
| | 237 | |
| | 238 | for (; x < w-radius; x++) { |
| | 239 | sum += src[(radius+x)*src_step] - src[(x-radius-1)*src_step]; |
| | 240 | dst[x*dst_step] = (sum*inv + (1<<15))>>16; |
| | 241 | } |
| | 242 | |
| | 243 | for (; x < w; x++) { |
| | 244 | sum += src[(2*w-radius-x-1)*src_step] - src[(x-radius-1)*src_step]; |
| | 245 | dst[x*dst_step] = (sum*inv + (1<<15))>>16; |
| | 246 | } |
| | 247 | } |
| | 248 | |
| | 249 | static inline void blur_power(uint8_t *dst, int dst_step, const uint8_t *src, int src_step, |
| | 250 | int w, int radius, int power, uint8_t *temp[2]) |
| | 251 | { |
| | 252 | uint8_t *a = temp[0], *b = temp[1]; |
| | 253 | |
| | 254 | if (radius && power) { |
| | 255 | blur(a, 1, src, src_step, w, radius); |
| | 256 | for (; power > 2; power--) { |
| | 257 | uint8_t *c; |
| | 258 | blur(b, 1, a, 1, w, radius); |
| | 259 | c = a; a = b; b = c; |
| | 260 | } |
| | 261 | if (power > 1) { |
| | 262 | blur(dst, dst_step, a, 1, w, radius); |
| | 263 | } else { |
| | 264 | int i; |
| | 265 | for (i = 0; i < w; i++) |
| | 266 | dst[i*dst_step] = a[i]; |
| | 267 | } |
| | 268 | } else { |
| | 269 | int i; |
| | 270 | for (i = 0; i < w; i++) |
| | 271 | dst[i*dst_step] = src[i*src_step]; |
| | 272 | } |
| | 273 | } |
| | 274 | |
| | 275 | static void hblur(uint8_t *dst, int dst_linesize, const uint8_t *src, int src_linesize, |
| | 276 | int w, int h, int radius, int power, uint8_t *temp[2]) |
| | 277 | { |
| | 278 | int y; |
| | 279 | |
| | 280 | if (radius == 0 && dst == src) |
| | 281 | return; |
| | 282 | |
| | 283 | for (y = 0; y < h; y++) |
| | 284 | blur_power(dst + y*dst_linesize, 1, src + y*src_linesize, 1, |
| | 285 | w, radius, power, temp); |
| | 286 | } |
| | 287 | |
| | 288 | static void vblur(uint8_t *dst, int dst_linesize, const uint8_t *src, int src_linesize, |
| | 289 | int w, int h, int radius, int power, uint8_t *temp[2]) |
| | 290 | { |
| | 291 | int x; |
| | 292 | |
| | 293 | if (radius == 0 && dst == src) |
| | 294 | return; |
| | 295 | |
| | 296 | for (x = 0; x < w; x++) |
| | 297 | blur_power(dst + x, dst_linesize, src + x, src_linesize, |
| | 298 | h, radius, power, temp); |
| | 299 | } |
| | 300 | |
| | 301 | static void draw_slice(AVFilterLink *inlink, int y0, int h0, int slice_dir) |
| | 302 | { |
| | 303 | AVFilterContext *ctx = inlink->dst; |
| | 304 | BoxBlurContext *boxblur = ctx->priv; |
| | 305 | AVFilterLink *outlink = inlink->dst->outputs[0]; |
| | 306 | AVFilterBufferRef *inpicref = inlink ->cur_buf; |
| | 307 | AVFilterBufferRef *outpicref = outlink->out_buf; |
| | 308 | int plane; |
| | 309 | int cw = inlink->w >> boxblur->hsub, ch = h0 >> boxblur->vsub; |
| | 310 | int w[4] = { inlink->w, cw, cw, inlink->w }; |
| | 311 | int h[4] = { h0, ch, ch, h0 }; |
| | 312 | uint8_t *dst[4], *src[4]; |
| | 313 | |
| | 314 | for (plane = 0; inpicref->data[plane] && plane < 4; plane++) { |
| | 315 | int y = plane == 1 || plane == 2 ? y0 >> boxblur->vsub : y0; |
| | 316 | src[plane] = inpicref ->data[plane] + inpicref ->linesize[plane] * y; |
| | 317 | dst[plane] = outpicref->data[plane] + outpicref->linesize[plane] * y; |
| | 318 | } |
| | 319 | |
| | 320 | for (plane = 0; inpicref->data[plane] && plane < 4; plane++) |
| | 321 | hblur(outpicref->data[plane], outpicref->linesize[plane], |
| | 322 | inpicref ->data[plane], inpicref ->linesize[plane], |
| | 323 | w[plane], h[plane], boxblur->radius[plane], boxblur->power[plane], |
| | 324 | boxblur->temp); |
| | 325 | |
| | 326 | for (plane = 0; inpicref->data[plane] && plane < 4; plane++) |
| | 327 | vblur(outpicref->data[plane], outpicref->linesize[plane], |
| | 328 | outpicref->data[plane], outpicref->linesize[plane], |
| | 329 | w[plane], h[plane], boxblur->radius[plane], boxblur->power[plane], |
| | 330 | boxblur->temp); |
| | 331 | } |
| | 332 | |
| | 333 | AVFilter avfilter_vf_boxblur = { |
| | 334 | .name = "boxblur", |
| | 335 | .description = NULL_IF_CONFIG_SMALL("Blur the input."), |
| | 336 | .priv_size = sizeof(BoxBlurContext), |
| | 337 | .init = init, |
| | 338 | .uninit = uninit, |
| | 339 | .query_formats = query_formats, |
| | 340 | |
| | 341 | .inputs = (AVFilterPad[]) {{ .name = "default", |
| | 342 | .type = AVMEDIA_TYPE_VIDEO, |
| | 343 | .config_props = config_input, |
| | 344 | .draw_slice = draw_slice, |
| | 345 | .min_perms = AV_PERM_READ }, |
| | 346 | { .name = NULL}}, |
| | 347 | .outputs = (AVFilterPad[]) {{ .name = "default", |
| | 348 | .type = AVMEDIA_TYPE_VIDEO, }, |
| | 349 | { .name = NULL}}, |
| | 350 | }; |