annotate ffmpeg/libavfilter/vf_removelogo.c @ 13:844d341cf643 tip

Back up before ISMIR
author Yading Song <yading.song@eecs.qmul.ac.uk>
date Thu, 31 Oct 2013 13:17:06 +0000
parents 6840f77b83aa
children
rev   line source
yading@10 1 /*
yading@10 2 * Copyright (c) 2005 Robert Edele <yartrebo@earthlink.net>
yading@10 3 * Copyright (c) 2012 Stefano Sabatini
yading@10 4 *
yading@10 5 * This file is part of FFmpeg.
yading@10 6 *
yading@10 7 * FFmpeg is free software; you can redistribute it and/or
yading@10 8 * modify it under the terms of the GNU Lesser General Public
yading@10 9 * License as published by the Free Software Foundation; either
yading@10 10 * version 2.1 of the License, or (at your option) any later version.
yading@10 11 *
yading@10 12 * FFmpeg is distributed in the hope that it will be useful,
yading@10 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
yading@10 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
yading@10 15 * Lesser General Public License for more details.
yading@10 16 *
yading@10 17 * You should have received a copy of the GNU Lesser General Public
yading@10 18 * License along with FFmpeg; if not, write to the Free Software
yading@10 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
yading@10 20 */
yading@10 21
yading@10 22 /**
yading@10 23 * @file
yading@10 24 * Advanced blur-based logo removing filter
yading@10 25 *
yading@10 26 * This filter loads an image mask file showing where a logo is and
yading@10 27 * uses a blur transform to remove the logo.
yading@10 28 *
yading@10 29 * Based on the libmpcodecs remove-logo filter by Robert Edele.
yading@10 30 */
yading@10 31
yading@10 32 /**
yading@10 33 * This code implements a filter to remove annoying TV logos and other annoying
yading@10 34 * images placed onto a video stream. It works by filling in the pixels that
yading@10 35 * comprise the logo with neighboring pixels. The transform is very loosely
yading@10 36 * based on a gaussian blur, but it is different enough to merit its own
yading@10 37 * paragraph later on. It is a major improvement on the old delogo filter as it
yading@10 38 * both uses a better blurring algorithm and uses a bitmap to use an arbitrary
yading@10 39 * and generally much tighter fitting shape than a rectangle.
yading@10 40 *
yading@10 41 * The logo removal algorithm has two key points. The first is that it
yading@10 42 * distinguishes between pixels in the logo and those not in the logo by using
yading@10 43 * the passed-in bitmap. Pixels not in the logo are copied over directly without
yading@10 44 * being modified and they also serve as source pixels for the logo
yading@10 45 * fill-in. Pixels inside the logo have the mask applied.
yading@10 46 *
yading@10 47 * At init-time the bitmap is reprocessed internally, and the distance to the
yading@10 48 * nearest edge of the logo (Manhattan distance), along with a little extra to
yading@10 49 * remove rough edges, is stored in each pixel. This is done using an in-place
yading@10 50 * erosion algorithm, and incrementing each pixel that survives any given
yading@10 51 * erosion. Once every pixel is eroded, the maximum value is recorded, and a
yading@10 52 * set of masks from size 0 to this size are generaged. The masks are circular
yading@10 53 * binary masks, where each pixel within a radius N (where N is the size of the
yading@10 54 * mask) is a 1, and all other pixels are a 0. Although a gaussian mask would be
yading@10 55 * more mathematically accurate, a binary mask works better in practice because
yading@10 56 * we generally do not use the central pixels in the mask (because they are in
yading@10 57 * the logo region), and thus a gaussian mask will cause too little blur and
yading@10 58 * thus a very unstable image.
yading@10 59 *
yading@10 60 * The mask is applied in a special way. Namely, only pixels in the mask that
yading@10 61 * line up to pixels outside the logo are used. The dynamic mask size means that
yading@10 62 * the mask is just big enough so that the edges touch pixels outside the logo,
yading@10 63 * so the blurring is kept to a minimum and at least the first boundary
yading@10 64 * condition is met (that the image function itself is continuous), even if the
yading@10 65 * second boundary condition (that the derivative of the image function is
yading@10 66 * continuous) is not met. A masking algorithm that does preserve the second
yading@10 67 * boundary coundition (perhaps something based on a highly-modified bi-cubic
yading@10 68 * algorithm) should offer even better results on paper, but the noise in a
yading@10 69 * typical TV signal should make anything based on derivatives hopelessly noisy.
yading@10 70 */
yading@10 71
yading@10 72 #include "libavutil/imgutils.h"
yading@10 73 #include "libavutil/opt.h"
yading@10 74 #include "avfilter.h"
yading@10 75 #include "formats.h"
yading@10 76 #include "internal.h"
yading@10 77 #include "video.h"
yading@10 78 #include "bbox.h"
yading@10 79 #include "lavfutils.h"
yading@10 80 #include "lswsutils.h"
yading@10 81
yading@10 82 typedef struct {
yading@10 83 const AVClass *class;
yading@10 84 char *filename;
yading@10 85 /* Stores our collection of masks. The first is for an array of
yading@10 86 the second for the y axis, and the third for the x axis. */
yading@10 87 int ***mask;
yading@10 88 int max_mask_size;
yading@10 89 int mask_w, mask_h;
yading@10 90
yading@10 91 uint8_t *full_mask_data;
yading@10 92 FFBoundingBox full_mask_bbox;
yading@10 93 uint8_t *half_mask_data;
yading@10 94 FFBoundingBox half_mask_bbox;
yading@10 95 } RemovelogoContext;
yading@10 96
yading@10 97 #define OFFSET(x) offsetof(RemovelogoContext, x)
yading@10 98 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
yading@10 99 static const AVOption removelogo_options[] = {
yading@10 100 { "filename", "set bitmap filename", OFFSET(filename), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
yading@10 101 { "f", "set bitmap filename", OFFSET(filename), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
yading@10 102 { NULL }
yading@10 103 };
yading@10 104
yading@10 105 AVFILTER_DEFINE_CLASS(removelogo);
yading@10 106
yading@10 107 /**
yading@10 108 * Choose a slightly larger mask size to improve performance.
yading@10 109 *
yading@10 110 * This function maps the absolute minimum mask size needed to the
yading@10 111 * mask size we'll actually use. f(x) = x (the smallest that will
yading@10 112 * work) will produce the sharpest results, but will be quite
yading@10 113 * jittery. f(x) = 1.25x (what I'm using) is a good tradeoff in my
yading@10 114 * opinion. This will calculate only at init-time, so you can put a
yading@10 115 * long expression here without effecting performance.
yading@10 116 */
yading@10 117 #define apply_mask_fudge_factor(x) (((x) >> 2) + x)
yading@10 118
yading@10 119 /**
yading@10 120 * Pre-process an image to give distance information.
yading@10 121 *
yading@10 122 * This function takes a bitmap image and converts it in place into a
yading@10 123 * distance image. A distance image is zero for pixels outside of the
yading@10 124 * logo and is the Manhattan distance (|dx| + |dy|) from the logo edge
yading@10 125 * for pixels inside of the logo. This will overestimate the distance,
yading@10 126 * but that is safe, and is far easier to implement than a proper
yading@10 127 * pythagorean distance since I'm using a modified erosion algorithm
yading@10 128 * to compute the distances.
yading@10 129 *
yading@10 130 * @param mask image which will be converted from a greyscale image
yading@10 131 * into a distance image.
yading@10 132 */
yading@10 133 static void convert_mask_to_strength_mask(uint8_t *data, int linesize,
yading@10 134 int w, int h, int min_val,
yading@10 135 int *max_mask_size)
yading@10 136 {
yading@10 137 int x, y;
yading@10 138
yading@10 139 /* How many times we've gone through the loop. Used in the
yading@10 140 in-place erosion algorithm and to get us max_mask_size later on. */
yading@10 141 int current_pass = 0;
yading@10 142
yading@10 143 /* set all non-zero values to 1 */
yading@10 144 for (y = 0; y < h; y++)
yading@10 145 for (x = 0; x < w; x++)
yading@10 146 data[y*linesize + x] = data[y*linesize + x] > min_val;
yading@10 147
yading@10 148 /* For each pass, if a pixel is itself the same value as the
yading@10 149 current pass, and its four neighbors are too, then it is
yading@10 150 incremented. If no pixels are incremented by the end of the
yading@10 151 pass, then we go again. Edge pixels are counted as always
yading@10 152 excluded (this should be true anyway for any sane mask, but if
yading@10 153 it isn't this will ensure that we eventually exit). */
yading@10 154 while (1) {
yading@10 155 /* If this doesn't get set by the end of this pass, then we're done. */
yading@10 156 int has_anything_changed = 0;
yading@10 157 uint8_t *current_pixel0 = data, *current_pixel;
yading@10 158 current_pass++;
yading@10 159
yading@10 160 for (y = 1; y < h-1; y++) {
yading@10 161 current_pixel = current_pixel0;
yading@10 162 for (x = 1; x < w-1; x++) {
yading@10 163 /* Apply the in-place erosion transform. It is based
yading@10 164 on the following two premises:
yading@10 165 1 - Any pixel that fails 1 erosion will fail all
yading@10 166 future erosions.
yading@10 167
yading@10 168 2 - Only pixels having survived all erosions up to
yading@10 169 the present will be >= to current_pass.
yading@10 170 It doesn't matter if it survived the current pass,
yading@10 171 failed it, or hasn't been tested yet. By using >=
yading@10 172 instead of ==, we allow the algorithm to work in
yading@10 173 place. */
yading@10 174 if ( *current_pixel >= current_pass &&
yading@10 175 *(current_pixel + 1) >= current_pass &&
yading@10 176 *(current_pixel - 1) >= current_pass &&
yading@10 177 *(current_pixel + w) >= current_pass &&
yading@10 178 *(current_pixel - w) >= current_pass) {
yading@10 179 /* Increment the value since it still has not been
yading@10 180 * eroded, as evidenced by the if statement that
yading@10 181 * just evaluated to true. */
yading@10 182 (*current_pixel)++;
yading@10 183 has_anything_changed = 1;
yading@10 184 }
yading@10 185 current_pixel++;
yading@10 186 }
yading@10 187 current_pixel0 += linesize;
yading@10 188 }
yading@10 189 if (!has_anything_changed)
yading@10 190 break;
yading@10 191 }
yading@10 192
yading@10 193 /* Apply the fudge factor, which will increase the size of the
yading@10 194 * mask a little to reduce jitter at the cost of more blur. */
yading@10 195 for (y = 1; y < h - 1; y++)
yading@10 196 for (x = 1; x < w - 1; x++)
yading@10 197 data[(y * linesize) + x] = apply_mask_fudge_factor(data[(y * linesize) + x]);
yading@10 198
yading@10 199 /* As a side-effect, we now know the maximum mask size, which
yading@10 200 * we'll use to generate our masks. */
yading@10 201 /* Apply the fudge factor to this number too, since we must ensure
yading@10 202 * that enough masks are generated. */
yading@10 203 *max_mask_size = apply_mask_fudge_factor(current_pass + 1);
yading@10 204 }
yading@10 205
yading@10 206 static int query_formats(AVFilterContext *ctx)
yading@10 207 {
yading@10 208 static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
yading@10 209 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
yading@10 210 return 0;
yading@10 211 }
yading@10 212
yading@10 213 static int load_mask(uint8_t **mask, int *w, int *h,
yading@10 214 const char *filename, void *log_ctx)
yading@10 215 {
yading@10 216 int ret;
yading@10 217 enum AVPixelFormat pix_fmt;
yading@10 218 uint8_t *src_data[4], *gray_data[4];
yading@10 219 int src_linesize[4], gray_linesize[4];
yading@10 220
yading@10 221 /* load image from file */
yading@10 222 if ((ret = ff_load_image(src_data, src_linesize, w, h, &pix_fmt, filename, log_ctx)) < 0)
yading@10 223 return ret;
yading@10 224
yading@10 225 /* convert the image to GRAY8 */
yading@10 226 if ((ret = ff_scale_image(gray_data, gray_linesize, *w, *h, AV_PIX_FMT_GRAY8,
yading@10 227 src_data, src_linesize, *w, *h, pix_fmt,
yading@10 228 log_ctx)) < 0)
yading@10 229 goto end;
yading@10 230
yading@10 231 /* copy mask to a newly allocated array */
yading@10 232 *mask = av_malloc(*w * *h);
yading@10 233 if (!*mask)
yading@10 234 ret = AVERROR(ENOMEM);
yading@10 235 av_image_copy_plane(*mask, *w, gray_data[0], gray_linesize[0], *w, *h);
yading@10 236
yading@10 237 end:
yading@10 238 av_free(src_data[0]);
yading@10 239 av_free(gray_data[0]);
yading@10 240 return ret;
yading@10 241 }
yading@10 242
yading@10 243 /**
yading@10 244 * Generate a scaled down image with half width, height, and intensity.
yading@10 245 *
yading@10 246 * This function not only scales down an image, but halves the value
yading@10 247 * in each pixel too. The purpose of this is to produce a chroma
yading@10 248 * filter image out of a luma filter image. The pixel values store the
yading@10 249 * distance to the edge of the logo and halving the dimensions halves
yading@10 250 * the distance. This function rounds up, because a downwards rounding
yading@10 251 * error could cause the filter to fail, but an upwards rounding error
yading@10 252 * will only cause a minor amount of excess blur in the chroma planes.
yading@10 253 */
yading@10 254 static void generate_half_size_image(const uint8_t *src_data, int src_linesize,
yading@10 255 uint8_t *dst_data, int dst_linesize,
yading@10 256 int src_w, int src_h,
yading@10 257 int *max_mask_size)
yading@10 258 {
yading@10 259 int x, y;
yading@10 260
yading@10 261 /* Copy over the image data, using the average of 4 pixels for to
yading@10 262 * calculate each downsampled pixel. */
yading@10 263 for (y = 0; y < src_h/2; y++) {
yading@10 264 for (x = 0; x < src_w/2; x++) {
yading@10 265 /* Set the pixel if there exists a non-zero value in the
yading@10 266 * source pixels, else clear it. */
yading@10 267 dst_data[(y * dst_linesize) + x] =
yading@10 268 src_data[((y << 1) * src_linesize) + (x << 1)] ||
yading@10 269 src_data[((y << 1) * src_linesize) + (x << 1) + 1] ||
yading@10 270 src_data[(((y << 1) + 1) * src_linesize) + (x << 1)] ||
yading@10 271 src_data[(((y << 1) + 1) * src_linesize) + (x << 1) + 1];
yading@10 272 dst_data[(y * dst_linesize) + x] = FFMIN(1, dst_data[(y * dst_linesize) + x]);
yading@10 273 }
yading@10 274 }
yading@10 275
yading@10 276 convert_mask_to_strength_mask(dst_data, dst_linesize,
yading@10 277 src_w/2, src_h/2, 0, max_mask_size);
yading@10 278 }
yading@10 279
yading@10 280 static av_cold int init(AVFilterContext *ctx)
yading@10 281 {
yading@10 282 RemovelogoContext *removelogo = ctx->priv;
yading@10 283 int ***mask;
yading@10 284 int ret = 0;
yading@10 285 int a, b, c, w, h;
yading@10 286 int full_max_mask_size, half_max_mask_size;
yading@10 287
yading@10 288 if (!removelogo->filename) {
yading@10 289 av_log(ctx, AV_LOG_ERROR, "The bitmap file name is mandatory\n");
yading@10 290 return AVERROR(EINVAL);
yading@10 291 }
yading@10 292
yading@10 293 /* Load our mask image. */
yading@10 294 if ((ret = load_mask(&removelogo->full_mask_data, &w, &h, removelogo->filename, ctx)) < 0)
yading@10 295 return ret;
yading@10 296 removelogo->mask_w = w;
yading@10 297 removelogo->mask_h = h;
yading@10 298
yading@10 299 convert_mask_to_strength_mask(removelogo->full_mask_data, w, w, h,
yading@10 300 16, &full_max_mask_size);
yading@10 301
yading@10 302 /* Create the scaled down mask image for the chroma planes. */
yading@10 303 if (!(removelogo->half_mask_data = av_mallocz(w/2 * h/2)))
yading@10 304 return AVERROR(ENOMEM);
yading@10 305 generate_half_size_image(removelogo->full_mask_data, w,
yading@10 306 removelogo->half_mask_data, w/2,
yading@10 307 w, h, &half_max_mask_size);
yading@10 308
yading@10 309 removelogo->max_mask_size = FFMAX(full_max_mask_size, half_max_mask_size);
yading@10 310
yading@10 311 /* Create a circular mask for each size up to max_mask_size. When
yading@10 312 the filter is applied, the mask size is determined on a pixel
yading@10 313 by pixel basis, with pixels nearer the edge of the logo getting
yading@10 314 smaller mask sizes. */
yading@10 315 mask = (int ***)av_malloc(sizeof(int **) * (removelogo->max_mask_size + 1));
yading@10 316 if (!mask)
yading@10 317 return AVERROR(ENOMEM);
yading@10 318
yading@10 319 for (a = 0; a <= removelogo->max_mask_size; a++) {
yading@10 320 mask[a] = (int **)av_malloc(sizeof(int *) * ((a * 2) + 1));
yading@10 321 if (!mask[a])
yading@10 322 return AVERROR(ENOMEM);
yading@10 323 for (b = -a; b <= a; b++) {
yading@10 324 mask[a][b + a] = (int *)av_malloc(sizeof(int) * ((a * 2) + 1));
yading@10 325 if (!mask[a][b + a])
yading@10 326 return AVERROR(ENOMEM);
yading@10 327 for (c = -a; c <= a; c++) {
yading@10 328 if ((b * b) + (c * c) <= (a * a)) /* Circular 0/1 mask. */
yading@10 329 mask[a][b + a][c + a] = 1;
yading@10 330 else
yading@10 331 mask[a][b + a][c + a] = 0;
yading@10 332 }
yading@10 333 }
yading@10 334 }
yading@10 335 removelogo->mask = mask;
yading@10 336
yading@10 337 /* Calculate our bounding rectangles, which determine in what
yading@10 338 * region the logo resides for faster processing. */
yading@10 339 ff_calculate_bounding_box(&removelogo->full_mask_bbox, removelogo->full_mask_data, w, w, h, 0);
yading@10 340 ff_calculate_bounding_box(&removelogo->half_mask_bbox, removelogo->half_mask_data, w/2, w/2, h/2, 0);
yading@10 341
yading@10 342 #define SHOW_LOGO_INFO(mask_type) \
yading@10 343 av_log(ctx, AV_LOG_VERBOSE, #mask_type " x1:%d x2:%d y1:%d y2:%d max_mask_size:%d\n", \
yading@10 344 removelogo->mask_type##_mask_bbox.x1, removelogo->mask_type##_mask_bbox.x2, \
yading@10 345 removelogo->mask_type##_mask_bbox.y1, removelogo->mask_type##_mask_bbox.y2, \
yading@10 346 mask_type##_max_mask_size);
yading@10 347 SHOW_LOGO_INFO(full);
yading@10 348 SHOW_LOGO_INFO(half);
yading@10 349
yading@10 350 return 0;
yading@10 351 }
yading@10 352
yading@10 353 static int config_props_input(AVFilterLink *inlink)
yading@10 354 {
yading@10 355 AVFilterContext *ctx = inlink->dst;
yading@10 356 RemovelogoContext *removelogo = ctx->priv;
yading@10 357
yading@10 358 if (inlink->w != removelogo->mask_w || inlink->h != removelogo->mask_h) {
yading@10 359 av_log(ctx, AV_LOG_INFO,
yading@10 360 "Mask image size %dx%d does not match with the input video size %dx%d\n",
yading@10 361 removelogo->mask_w, removelogo->mask_h, inlink->w, inlink->h);
yading@10 362 return AVERROR(EINVAL);
yading@10 363 }
yading@10 364
yading@10 365 return 0;
yading@10 366 }
yading@10 367
yading@10 368 /**
yading@10 369 * Blur image.
yading@10 370 *
yading@10 371 * It takes a pixel that is inside the mask and blurs it. It does so
yading@10 372 * by finding the average of all the pixels within the mask and
yading@10 373 * outside of the mask.
yading@10 374 *
yading@10 375 * @param mask_data the mask plane to use for averaging
yading@10 376 * @param image_data the image plane to blur
yading@10 377 * @param w width of the image
yading@10 378 * @param h height of the image
yading@10 379 * @param x x-coordinate of the pixel to blur
yading@10 380 * @param y y-coordinate of the pixel to blur
yading@10 381 */
yading@10 382 static unsigned int blur_pixel(int ***mask,
yading@10 383 const uint8_t *mask_data, int mask_linesize,
yading@10 384 uint8_t *image_data, int image_linesize,
yading@10 385 int w, int h, int x, int y)
yading@10 386 {
yading@10 387 /* Mask size tells how large a circle to use. The radius is about
yading@10 388 * (slightly larger than) mask size. */
yading@10 389 int mask_size;
yading@10 390 int start_posx, start_posy, end_posx, end_posy;
yading@10 391 int i, j;
yading@10 392 unsigned int accumulator = 0, divisor = 0;
yading@10 393 /* What pixel we are reading out of the circular blur mask. */
yading@10 394 const uint8_t *image_read_position;
yading@10 395 /* What pixel we are reading out of the filter image. */
yading@10 396 const uint8_t *mask_read_position;
yading@10 397
yading@10 398 /* Prepare our bounding rectangle and clip it if need be. */
yading@10 399 mask_size = mask_data[y * mask_linesize + x];
yading@10 400 start_posx = FFMAX(0, x - mask_size);
yading@10 401 start_posy = FFMAX(0, y - mask_size);
yading@10 402 end_posx = FFMIN(w - 1, x + mask_size);
yading@10 403 end_posy = FFMIN(h - 1, y + mask_size);
yading@10 404
yading@10 405 image_read_position = image_data + image_linesize * start_posy + start_posx;
yading@10 406 mask_read_position = mask_data + mask_linesize * start_posy + start_posx;
yading@10 407
yading@10 408 for (j = start_posy; j <= end_posy; j++) {
yading@10 409 for (i = start_posx; i <= end_posx; i++) {
yading@10 410 /* Check if this pixel is in the mask or not. Only use the
yading@10 411 * pixel if it is not. */
yading@10 412 if (!(*mask_read_position) && mask[mask_size][i - start_posx][j - start_posy]) {
yading@10 413 accumulator += *image_read_position;
yading@10 414 divisor++;
yading@10 415 }
yading@10 416
yading@10 417 image_read_position++;
yading@10 418 mask_read_position++;
yading@10 419 }
yading@10 420
yading@10 421 image_read_position += (image_linesize - ((end_posx + 1) - start_posx));
yading@10 422 mask_read_position += (mask_linesize - ((end_posx + 1) - start_posx));
yading@10 423 }
yading@10 424
yading@10 425 /* If divisor is 0, it means that not a single pixel is outside of
yading@10 426 the logo, so we have no data. Else we need to normalise the
yading@10 427 data using the divisor. */
yading@10 428 return divisor == 0 ? 255:
yading@10 429 (accumulator + (divisor / 2)) / divisor; /* divide, taking into account average rounding error */
yading@10 430 }
yading@10 431
yading@10 432 /**
yading@10 433 * Blur image plane using a mask.
yading@10 434 *
yading@10 435 * @param source The image to have it's logo removed.
yading@10 436 * @param destination Where the output image will be stored.
yading@10 437 * @param source_stride How far apart (in memory) two consecutive lines are.
yading@10 438 * @param destination Same as source_stride, but for the destination image.
yading@10 439 * @param width Width of the image. This is the same for source and destination.
yading@10 440 * @param height Height of the image. This is the same for source and destination.
yading@10 441 * @param is_image_direct If the image is direct, then source and destination are
yading@10 442 * the same and we can save a lot of time by not copying pixels that
yading@10 443 * haven't changed.
yading@10 444 * @param filter The image that stores the distance to the edge of the logo for
yading@10 445 * each pixel.
yading@10 446 * @param logo_start_x smallest x-coordinate that contains at least 1 logo pixel.
yading@10 447 * @param logo_start_y smallest y-coordinate that contains at least 1 logo pixel.
yading@10 448 * @param logo_end_x largest x-coordinate that contains at least 1 logo pixel.
yading@10 449 * @param logo_end_y largest y-coordinate that contains at least 1 logo pixel.
yading@10 450 *
yading@10 451 * This function processes an entire plane. Pixels outside of the logo are copied
yading@10 452 * to the output without change, and pixels inside the logo have the de-blurring
yading@10 453 * function applied.
yading@10 454 */
yading@10 455 static void blur_image(int ***mask,
yading@10 456 const uint8_t *src_data, int src_linesize,
yading@10 457 uint8_t *dst_data, int dst_linesize,
yading@10 458 const uint8_t *mask_data, int mask_linesize,
yading@10 459 int w, int h, int direct,
yading@10 460 FFBoundingBox *bbox)
yading@10 461 {
yading@10 462 int x, y;
yading@10 463 uint8_t *dst_line;
yading@10 464 const uint8_t *src_line;
yading@10 465
yading@10 466 if (!direct)
yading@10 467 av_image_copy_plane(dst_data, dst_linesize, src_data, src_linesize, w, h);
yading@10 468
yading@10 469 for (y = bbox->y1; y <= bbox->y2; y++) {
yading@10 470 src_line = src_data + src_linesize * y;
yading@10 471 dst_line = dst_data + dst_linesize * y;
yading@10 472
yading@10 473 for (x = bbox->x1; x <= bbox->x2; x++) {
yading@10 474 if (mask_data[y * mask_linesize + x]) {
yading@10 475 /* Only process if we are in the mask. */
yading@10 476 dst_line[x] = blur_pixel(mask,
yading@10 477 mask_data, mask_linesize,
yading@10 478 dst_data, dst_linesize,
yading@10 479 w, h, x, y);
yading@10 480 } else {
yading@10 481 /* Else just copy the data. */
yading@10 482 if (!direct)
yading@10 483 dst_line[x] = src_line[x];
yading@10 484 }
yading@10 485 }
yading@10 486 }
yading@10 487 }
yading@10 488
yading@10 489 static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref)
yading@10 490 {
yading@10 491 RemovelogoContext *removelogo = inlink->dst->priv;
yading@10 492 AVFilterLink *outlink = inlink->dst->outputs[0];
yading@10 493 AVFrame *outpicref;
yading@10 494 int direct = 0;
yading@10 495
yading@10 496 if (av_frame_is_writable(inpicref)) {
yading@10 497 direct = 1;
yading@10 498 outpicref = inpicref;
yading@10 499 } else {
yading@10 500 outpicref = ff_get_video_buffer(outlink, outlink->w, outlink->h);
yading@10 501 if (!outpicref) {
yading@10 502 av_frame_free(&inpicref);
yading@10 503 return AVERROR(ENOMEM);
yading@10 504 }
yading@10 505 av_frame_copy_props(outpicref, inpicref);
yading@10 506 }
yading@10 507
yading@10 508 blur_image(removelogo->mask,
yading@10 509 inpicref ->data[0], inpicref ->linesize[0],
yading@10 510 outpicref->data[0], outpicref->linesize[0],
yading@10 511 removelogo->full_mask_data, inlink->w,
yading@10 512 inlink->w, inlink->h, direct, &removelogo->full_mask_bbox);
yading@10 513 blur_image(removelogo->mask,
yading@10 514 inpicref ->data[1], inpicref ->linesize[1],
yading@10 515 outpicref->data[1], outpicref->linesize[1],
yading@10 516 removelogo->half_mask_data, inlink->w/2,
yading@10 517 inlink->w/2, inlink->h/2, direct, &removelogo->half_mask_bbox);
yading@10 518 blur_image(removelogo->mask,
yading@10 519 inpicref ->data[2], inpicref ->linesize[2],
yading@10 520 outpicref->data[2], outpicref->linesize[2],
yading@10 521 removelogo->half_mask_data, inlink->w/2,
yading@10 522 inlink->w/2, inlink->h/2, direct, &removelogo->half_mask_bbox);
yading@10 523
yading@10 524 if (!direct)
yading@10 525 av_frame_free(&inpicref);
yading@10 526
yading@10 527 return ff_filter_frame(outlink, outpicref);
yading@10 528 }
yading@10 529
yading@10 530 static void uninit(AVFilterContext *ctx)
yading@10 531 {
yading@10 532 RemovelogoContext *removelogo = ctx->priv;
yading@10 533 int a, b;
yading@10 534
yading@10 535 av_freep(&removelogo->full_mask_data);
yading@10 536 av_freep(&removelogo->half_mask_data);
yading@10 537
yading@10 538 if (removelogo->mask) {
yading@10 539 /* Loop through each mask. */
yading@10 540 for (a = 0; a <= removelogo->max_mask_size; a++) {
yading@10 541 /* Loop through each scanline in a mask. */
yading@10 542 for (b = -a; b <= a; b++) {
yading@10 543 av_free(removelogo->mask[a][b + a]); /* Free a scanline. */
yading@10 544 }
yading@10 545 av_free(removelogo->mask[a]);
yading@10 546 }
yading@10 547 /* Free the array of pointers pointing to the masks. */
yading@10 548 av_freep(&removelogo->mask);
yading@10 549 }
yading@10 550 }
yading@10 551
yading@10 552 static const AVFilterPad removelogo_inputs[] = {
yading@10 553 {
yading@10 554 .name = "default",
yading@10 555 .type = AVMEDIA_TYPE_VIDEO,
yading@10 556 .get_video_buffer = ff_null_get_video_buffer,
yading@10 557 .config_props = config_props_input,
yading@10 558 .filter_frame = filter_frame,
yading@10 559 },
yading@10 560 { NULL }
yading@10 561 };
yading@10 562
yading@10 563 static const AVFilterPad removelogo_outputs[] = {
yading@10 564 {
yading@10 565 .name = "default",
yading@10 566 .type = AVMEDIA_TYPE_VIDEO,
yading@10 567 },
yading@10 568 { NULL }
yading@10 569 };
yading@10 570
yading@10 571 AVFilter avfilter_vf_removelogo = {
yading@10 572 .name = "removelogo",
yading@10 573 .description = NULL_IF_CONFIG_SMALL("Remove a TV logo based on a mask image."),
yading@10 574 .priv_size = sizeof(RemovelogoContext),
yading@10 575 .init = init,
yading@10 576 .uninit = uninit,
yading@10 577 .query_formats = query_formats,
yading@10 578 .inputs = removelogo_inputs,
yading@10 579 .outputs = removelogo_outputs,
yading@10 580 .priv_class = &removelogo_class,
yading@10 581 };