LCOV - code coverage report
Current view: top level - libavfilter - vf_removelogo.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 171 0.0 %
Date: 2017-12-14 19:11:59 Functions: 0 10 0.0 %

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

Generated by: LCOV version 1.13