comparison libmpcodecs/vf_remove_logo.c @ 15693:98cc17f305de

remove_logo filter by yartrebo, committed with fixes for c++ variable declarations
author rfelker
date Wed, 08 Jun 2005 03:11:53 +0000
parents
children 99988abe7afb
comparison
equal deleted inserted replaced
15692:05f2d8e27cf4 15693:98cc17f305de
1 /*
2 Copyright 2005 Robert Edele.
3
4 e-mail: yartrebo@earthlink.net
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Public License for more
14 details.
15
16 You should have reveived a copy of the GNU General Public License
17 along with this program; if not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA
20
21 __________________________________________________________________________
22 | Robert Edele Fri. 4-Feb-2005 |
23 | This program loads a .pgm mask file showing where a logo is and uses |
24 | a blur transform to remove the logo. |
25 |________________________________________________________________________|
26 */
27
28 /**
29 * \file vf_remove_logo.c
30 *
31 * \brief Advanced blur-based logo removing filter.
32
33 * Hello and welcome. This code implements a filter to remove annoying TV
34 * logos and other annoying images placed onto a video stream. It works by filling
35 * in the pixels that comprise the logo with neighboring pixels. The transform is
36 * very loosely based on a gaussian blur, but it is different enough to merit its
37 * own paragraph later on. It is a major improvement on the old delogo filter as
38 * it 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 filter requires 1 argument and has no optional arguments. It requires
42 * a filter bitmap, which must be in PGM or PPM format. A sample invocation would
43 * be -vf remove_logo=/home/username/logo_bitmaps/xyz.pgm. Pixels with a value of
44 * zero are not part of the logo, and non-zero pixels are part of the logo. If you
45 * use white (255) for the logo and black (0) for the rest, you will be safe. For
46 * making the filter bitmap, I recommend taking a screen capture of a black frame
47 * with the logo visible, and then using The GIMP's threshold filter followed by
48 * the erode filter once or twice. If needed, little splotches can be fixed
49 * manually. Remember that if logo pixels are not covered, the filter quality will
50 * be much reduced. Marking too many pixels as part of the logo doesn't hurt as
51 * much, but it will increase the amount of blurring needed to cover over the
52 * image and will destroy more information than necessary. Additionally, this blur
53 * algorithm is O(n) = n^4, where n is the width and height of a hypothetical
54 * square logo, so extra pixels will slow things down on a large lo
55 *
56 * The logo removal algorithm has two key points. The first is that it
57 * distinguishes between pixels in the logo and those not in the logo by using the
58 * passed-in bitmap. Pixels not in the logo are copied over directly without being
59 * modified and they also serve as source pixels for the logo fill-in. Pixels
60 * inside the logo have the mask applied.
61 *
62 * At init-time the bitmap is reprocessed internally, and the distance to the
63 * nearest edge of the logo (Manhattan distance), along with a little extra to
64 * remove rough edges, is stored in each pixel. This is done using an in-place
65 * erosion algorithm, and incrementing each pixel that survives any given erosion.
66 * Once every pixel is eroded, the maximum value is recorded, and a set of masks
67 * from size 0 to this size are generaged. The masks are circular binary masks,
68 * where each pixel within a radius N (where N is the size of the mask) is a 1,
69 * and all other pixels are a 0. Although a gaussian mask would be more
70 * mathematically accurate, a binary mask works better in practice because we
71 * generally do not use the central pixels in the mask (because they are in the
72 * logo region), and thus a gaussian mask will cause too little blur and thus a
73 * very unstable image.
74 *
75 * The mask is applied in a special way. Namely, only pixels in the mask that
76 * line up to pixels outside the logo are used. The dynamic mask size means that
77 * the mask is just big enough so that the edges touch pixels outside the logo, so
78 * the blurring is kept to a minimum and at least the first boundary condition is
79 * met (that the image function itself is continuous), even if the second boundary
80 * condition (that the derivative of the image function is continuous) is not met.
81 * A masking algorithm that does preserve the second boundary coundition
82 * (perhaps something based on a highly-modified bi-cubic algorithm) should offer
83 * even better results on paper, but the noise in a typical TV signal should make
84 * anything based on derivatives hopelessly noisy.
85 */
86
87 #include <stdio.h>
88 #include <stdlib.h>
89 #include <string.h>
90 #include <inttypes.h>
91
92 #include "../config.h"
93 #include "../mp_msg.h"
94 #include "../libvo/fastmemcpy.h"
95
96 #include "img_format.h"
97 #include "mp_image.h"
98 #include "vf.h"
99
100 //===========================================================================//
101
102 /** \brief Returns the larger of the two arguments. **/
103 #define max(x,y) ((x)>(y)?(x):(y))
104 /** \brief Returns the smaller of the two arguments. **/
105 #define min(x,y) ((x)>(y)?(y):(x))
106
107 /**
108 * \brief Test if a pixel is part of the logo.
109 */
110 #define test_filter(image, x, y) ((unsigned char) (image->pixel[((y) * image->width) + (x)]))
111
112 /**
113 * \brief Chooses a slightly larger mask size to improve performance.
114 *
115 * This function maps the absolute minimum mask size needed to the mask size we'll
116 * actually use. f(x) = x (the smallest that will work) will produce the sharpest
117 * results, but will be quite jittery. f(x) = 1.25x (what I'm using) is a good
118 * tradeoff in my opinion. This will calculate only at init-time, so you can put a
119 * long expression here without effecting performance.
120 */
121 #define apply_mask_fudge_factor(x) (((x) >> 2) + x)
122
123 /**
124 * \brief Simple implementation of the PGM image format.
125 *
126 * This struct holds a bare-bones image loaded from a PGM or PPM file. Once
127 * loaded and pre-processed, each pixel in this struct will contain how far from
128 * the edge of the logo each pixel is, using the manhattan distance (|dx| + |dy|).
129 *
130 * pixels in char * pixel can be addressed using (y * width) + height.
131 */
132 typedef struct
133 {
134 unsigned short int width;
135 unsigned short int height;
136
137 unsigned char * pixel;
138
139 } pgm_structure;
140
141 /**
142 * \brief Stores persistant variables.
143 *
144 * Variables stored here are kept from frame to frame, and seperate instances of
145 * the filter will get their own seperate copies.
146 */
147 typedef struct
148 {
149 unsigned int fmt; /* Not exactly sure of the use for this. It came with the example filter I used as a basis for this, and it looks like a lot of stuff will break if I remove it. */
150 int max_mask_size; /* The largest possible mask size that will be needed with the given filter and corresponding half_size_filter. The half_size_filter can have a larger requirment in some rare (but not degenerate) cases. */
151 int * * * mask; /* Stores our collection of masks. The first * is for an array of masks, the second for the y axis, and the third for the x axis. */
152 pgm_structure * filter; /* Stores the full-size filter image. This is used to tell what pixels are in the logo or not in the luma plane. */
153 pgm_structure * half_size_filter; /* Stores a 50% width and 50% height filter image. This is used to tell what pixels are in the logo or not in the chroma planes. */
154 /* These 8 variables store the bounding rectangles that the logo resides in. */
155 int bounding_rectangle_posx1;
156 int bounding_rectangle_posy1;
157 int bounding_rectangle_posx2;
158 int bounding_rectangle_posy2;
159 int bounding_rectangle_half_size_posx1;
160 int bounding_rectangle_half_size_posy1;
161 int bounding_rectangle_half_size_posx2;
162 int bounding_rectangle_half_size_posy2;
163 } vf_priv_s;
164
165 /**
166 * \brief Mallocs memory and checks to make sure it succeeded.
167 *
168 * \param size How many bytes to allocate.
169 *
170 * \return A pointer to the freshly allocated memory block, or NULL on failutre.
171 *
172 * Mallocs memory, and checks to make sure it was successfully allocated. Because
173 * of how MPlayer works, it cannot safely halt execution, but at least the user
174 * will get an error message before the segfault happens.
175 */
176 void * safe_malloc(int size)
177 {
178 void * answer = malloc(size);
179 if (answer == NULL)
180 mp_msg(MSGT_VFILTER, MSGL_ERR, "Unable to allocate memory in vf_remove_logo.c\n");
181
182 return answer;
183 }
184
185 /**
186 * \brief Calculates the smallest rectangle that will encompass the logo region.
187 *
188 * \param filter This image contains the logo around which the rectangle will
189 * will be fitted.
190 *
191 * The bounding rectangle is calculated by testing successive lines (from the four
192 * sides of the rectangle) until no more can be removed without removing logo
193 * pixels. The results are returned by reference to posx1, posy1, posx2, and
194 * posy2.
195 */
196
197 void calculate_bounding_rectangle(int * posx1, int * posy1, int * posx2, int * posy2, pgm_structure * filter)
198 {
199 int x; /* Temporary variables to run */
200 int y; /* through each row or column. */
201 int start_x;
202 int start_y;
203 int end_x = filter->width - 1;
204 int end_y = filter->height - 1;
205 int did_we_find_a_logo_pixel = 0;
206
207 /* Let's find the top bound first. */
208 for (start_x = 0; start_x < filter->width && !did_we_find_a_logo_pixel; start_x++)
209 {
210 for (y = 0; y < filter->height; y++)
211 {
212 did_we_find_a_logo_pixel |= test_filter(filter, start_x, y);
213 }
214 }
215 start_x--;
216
217 /* Now the bottom bound. */
218 did_we_find_a_logo_pixel = 0;
219 for (end_x = filter->width - 1; end_x > start_x && !did_we_find_a_logo_pixel; end_x--)
220 {
221 for (y = 0; y < filter->height; y++)
222 {
223 did_we_find_a_logo_pixel |= test_filter(filter, end_x, y);
224 }
225 }
226 end_x++;
227
228 /* Left bound. */
229 did_we_find_a_logo_pixel = 0;
230 for (start_y = 0; start_y < filter->height && !did_we_find_a_logo_pixel; start_y++)
231 {
232 for (x = 0; x < filter->width; x++)
233 {
234 did_we_find_a_logo_pixel |= test_filter(filter, x, start_y);
235 }
236 }
237 start_y--;
238
239 /* Right bound. */
240 did_we_find_a_logo_pixel = 0;
241 for (end_y = filter->height - 1; end_y > start_y && !did_we_find_a_logo_pixel; end_y--)
242 {
243 for (x = 0; x < filter->width; x++)
244 {
245 did_we_find_a_logo_pixel |= test_filter(filter, x, end_y);
246 }
247 }
248 end_y++;
249
250 *posx1 = start_x;
251 *posy1 = start_y;
252 *posx2 = end_x;
253 *posy2 = end_y;
254
255 return;
256 }
257
258 /**
259 * \brief Free mask memory.
260 *
261 * \param vf Data structure which stores our persistant data, and is to be freed.
262 *
263 * We call this function when our filter is done. It will free the memory
264 * allocated to the masks and leave the variables in a safe state.
265 */
266 void destroy_masks(vf_instance_t * vf)
267 {
268 int a, b;
269
270 /* Load values from the vf->priv struct for faster dereferencing. */
271 int * * * mask = ((vf_priv_s *)vf->priv)->mask;
272 int max_mask_size = ((vf_priv_s *)vf->priv)->max_mask_size;
273
274 if (mask == NULL)
275 return; /* Nothing allocated, so return before we segfault. */
276
277 /* Free all allocated memory. */
278 for (a = 0; a <= max_mask_size; a++) /* Loop through each mask. */
279 {
280 for (b = -a; b <= a; b++) /* Loop through each scanline in a mask. */
281 {
282 free(mask[a][b + a]); /* Free a scanline. */
283 }
284 free(mask[a]); /* Free a mask. */
285 }
286 free(mask); /* Free the array of pointers pointing to the masks. */
287
288 /* Set the pointer to NULL, so that any duplicate calls to this function will not cause a crash. */
289 ((vf_priv_s *)vf->priv)->mask = NULL;
290
291 return;
292 }
293
294 /**
295 * \brief Set up our array of masks.
296 *
297 * \param vf Where our filter stores persistance data, like these masks.
298 *
299 * This creates an array of progressively larger masks and calculates their
300 * values. The values will not change during program execution once this function
301 * is done.
302 */
303 void initialize_masks(vf_instance_t * vf)
304 {
305 int a, b, c;
306
307 /* Load values from the vf->priv struct for faster dereferencing. */
308 int * * * mask = ((vf_priv_s *)vf->priv)->mask;
309 int max_mask_size = ((vf_priv_s *)vf->priv)->max_mask_size; /* This tells us how many masks we'll need to generate. */
310
311 /* Create a circular mask for each size up to max_mask_size. When the filter is applied, the mask size is
312 determined on a pixel by pixel basis, with pixels nearer the edge of the logo getting smaller mask sizes. */
313 mask = (int * * *) safe_malloc(sizeof(int * *) * (max_mask_size + 1));
314 for (a = 0; a <= max_mask_size; a++)
315 {
316 mask[a] = (int * *) safe_malloc(sizeof(int *) * ((a * 2) + 1));
317 for (b = -a; b <= a; b++)
318 {
319 mask[a][b + a] = (int *) safe_malloc(sizeof(int) * ((a * 2) + 1));
320 for (c = -a; c <= a; c++)
321 {
322 if ((b * b) + (c * c) <= (a * a)) /* Circular 0/1 mask. */
323 mask[a][b + a][c + a] = 1;
324 else
325 mask[a][b + a][c + a] = 0;
326 }
327 }
328 }
329
330 /* Store values back to vf->priv so they aren't lost after the function returns. */
331 ((vf_priv_s *)vf->priv)->mask = mask;
332
333 return;
334 }
335
336 /**
337 * \brief Pre-processes an image to give distance information.
338 *
339 * \param vf Data structure that holds persistant information. All it is used for
340 in this function is to store the calculated max_mask_size variable.
341 * \param mask This image will be converted from a greyscale image into a
342 * distance image.
343 *
344 * This function takes a greyscale image (pgm_structure * mask) and converts it
345 * in place into a distance image. A distance image is zero for pixels ourside of
346 * the logo and is the manhattan distance (|dx| + |dy|) for pixels inside of the
347 * logo. This will overestimate the distance, but that is safe, and is far easier
348 * to implement than a proper pythagorean distance since I'm using a modified
349 * erosion algorithm to compute the distances.
350 */
351 void convert_mask_to_strength_mask(vf_instance_t * vf, pgm_structure * mask)
352 {
353 int x, y; /* Used by our for loops to go through every single pixel in the picture one at a time. */
354 int has_anything_changed = 1; /* Used by the main while() loop to know if anything changed on the last erosion. */
355 int current_pass = 0; /* How many times we've gone through the loop. Used in the in-place erosion algorithm
356 and to get us max_mask_size later on. */
357 int max_mask_size; /* This will record how large a mask the pixel that is the furthest from the edge of the logo
358 (and thus the neediest) is. */
359 char * current_pixel = mask->pixel; /* This stores the actual pixel data. */
360
361 /* First pass, set all non-zero values to 1. After this loop finishes, the data should be considered numeric
362 data for the filter, not color data. */
363 for (x = 0; x < mask->height * mask->width; x++, current_pixel++)
364 if(*current_pixel) *current_pixel = 1;
365
366 /* Second pass and future passes. For each pass, if a pixel is itself the same value as the current pass,
367 and its four neighbors are too, then it is incremented. If no pixels are incremented by the end of the pass,
368 then we go again. Edge pixels are counted as always excluded (this should be true anyway for any sane mask,
369 but if it isn't this will ensure that we eventually exit). */
370 while (has_anything_changed)
371 {
372 current_pass++;
373 current_pixel = mask->pixel;
374
375 has_anything_changed = 0; /* If this doesn't get set by the end of this pass, then we're done. */
376
377 for (y = 1; y < mask->height - 1; y++)
378 {
379 for (x = 1; x < mask->width - 1; x++)
380 {
381 /* Apply the in-place erosion transform. It is based on the following two premises: 1 - Any pixel that fails 1 erosion
382 will fail all future erosions. 2 - Only pixels having survived all erosions up to the present will be >= to
383 current_pass. It doesn't matter if it survived the current pass, failed it, or hasn't been tested yet. */
384 if (*current_pixel >= current_pass && /* By using >= instead of ==, we allow the algorithm to work in place. */
385 *(current_pixel + 1) >= current_pass &&
386 *(current_pixel - 1) >= current_pass &&
387 *(current_pixel + mask->width) >= current_pass &&
388 *(current_pixel - mask->width) >= current_pass)
389 {
390 (*current_pixel)++; /* Increment the value since it still has not been eroded, as evidenced by the if statement
391 that just evaluated to true. */
392 has_anything_changed = 1;
393 }
394 current_pixel++;
395 }
396 }
397 }
398
399 /* Apply the fudge factor, which will increase the size of the mask a little to reduce jitter at the cost of more blur. */
400 for (y = 1; y < mask->height - 1; y++)
401 {
402 for (x = 1; x < mask->width - 1; x++)
403 {
404 mask->pixel[(y * mask->width) + x] = apply_mask_fudge_factor(mask->pixel[(y * mask->width) + x]);
405 }
406 }
407
408 max_mask_size = current_pass + 1; /* As a side-effect, we now know the maximum mask size, which we'll use to generate our masks. */
409 max_mask_size = apply_mask_fudge_factor(max_mask_size); /* Apply the fudge factor to this number too, since we must
410 ensure that enough masks are generated. */
411 ((vf_priv_s *)vf->priv)->max_mask_size = max_mask_size; /* Commit the newly calculated max_mask_size to the vf->priv struct. */
412
413 return;
414 }
415
416 /**
417 * \brief Our blurring function.
418 *
419 * \param vf Stores persistant data. In this function we are interested in the
420 * array of masks.
421 * \param value_out The properly blurred and delogoed pixel is outputted here.
422 * \param logo_mask Tells us which pixels are in the logo and which aren't.
423 * \param image The image that is having its logo removed.
424 * \param x x-coordinate of the pixel to blur.
425 * \param y y-coordinate of the pixel to blur.
426 * \param plane 0 = luma, 1 = blue chroma, 2 = red chroma (YUV).
427 *
428 * This function is the core of the filter. It takes a pixel that is inside the
429 * logo and blurs it. It does so by finding the average of all the pixels within
430 * the mask and outside of the logo.
431 */
432 void get_blur(const vf_instance_t * const vf, unsigned int * const value_out, const pgm_structure * const logo_mask,
433 const mp_image_t * const image, const int x, const int y, const int plane)
434 {
435 int mask_size; /* Mask size tells how large a circle to use. The radius is about (slightly larger than) mask size. */
436 /* Get values from vf->priv for faster dereferencing. */
437 int * * * mask = ((vf_priv_s *)vf->priv)->mask;
438
439 int start_posx, start_posy, end_posx, end_posy;
440 int i, j;
441 unsigned int accumulator = 0, divisor = 0;
442 const unsigned char * mask_read_position; /* What pixel we are reading out of the circular blur mask. */
443 const unsigned char * logo_mask_read_position; /* What pixel we are reading out of the filter image. */
444
445 /* Prepare our bounding rectangle and clip it if need be. */
446 mask_size = test_filter(logo_mask, x, y);
447 start_posx = max(0, x - mask_size);
448 start_posy = max(0, y - mask_size);
449 end_posx = min(image->width - 1, x + mask_size);
450 end_posy = min(image->height - 1, y + mask_size);
451
452 mask_read_position = image->planes[plane] + (image->stride[plane] * start_posy) + start_posx;
453 logo_mask_read_position = logo_mask->pixel + (start_posy * logo_mask->width) + start_posx;
454
455 for (j = start_posy; j <= end_posy; j++)
456 {
457 for (i = start_posx; i <= end_posx; i++)
458 {
459 if (!(*logo_mask_read_position) && mask[mask_size][i - start_posx][j - start_posy])
460 { /* Check to see if this pixel is in the logo or not. Only use the pixel if it is not. */
461 accumulator += *mask_read_position;
462 divisor++;
463 }
464
465 mask_read_position++;
466 logo_mask_read_position++;
467 }
468
469 mask_read_position += (image->stride[plane] - ((end_posx + 1) - start_posx));
470 logo_mask_read_position += (logo_mask->width - ((end_posx + 1) - start_posx));
471 }
472
473 if (divisor == 0) /* This means that not a single pixel is outside of the logo, so we have no data. */
474 { /* We should put some eye catching value here, to indicate the flaw to the user. */
475 *value_out = 255;
476 }
477 else /* Else we need to normalise the data using the divisor. */
478 {
479 *value_out = (accumulator + (divisor / 2)) / divisor; /* Divide, taking into account average rounding error. */
480 }
481
482 return;
483 }
484
485 /**
486 * \brief Free a pgm_structure. Undoes load_pgm(...).
487 */
488 void destroy_pgm(pgm_structure * to_be_destroyed)
489 {
490 if (to_be_destroyed == NULL)
491 return; /* Don't do anything if a NULL pointer was passed it. */
492
493 /* Internally allocated memory. */
494 if (to_be_destroyed->pixel != NULL)
495 {
496 free(to_be_destroyed->pixel);
497 to_be_destroyed->pixel = NULL;
498 }
499
500 /* Free the actual struct instance. This is done here and not by the calling function. */
501 free(to_be_destroyed);
502 }
503
504 /** \brief Helper function for load_pgm(...) to skip whitespace. */
505 void load_pgm_skip(FILE *f) {
506 int c, comment = 0;
507 do {
508 c = fgetc(f);
509 if (c == '#')
510 comment = 1;
511 if (c == '\n')
512 comment = 0;
513 } while (c != EOF && (isspace(c) || comment));
514 ungetc(c, f);
515 }
516
517 #define REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE(message) {mp_msg(MSGT_VFILTER, MSGL_ERR, message); return NULL;}
518
519 /**
520 * \brief Loads a raw pgm or ppm file into a newly created pgm_structure object.
521 *
522 * \param file_name The name of the file to be loaded. So long as the file is a
523 * valid pgm or ppm file, it will load correctly, even if the
524 * extension is missing or invalid.
525 *
526 * \return A pointer to the newly created pgm_structure object. Don't forget to
527 * call destroy_pgm(...) when you're done with this. If an error occurs,
528 * NULL is returned.
529 *
530 * Can load either raw pgm (P5) or raw ppm (P6) image files as a binary image.
531 * While a pgm file will be loaded normally (greyscale), the only thing that is
532 * guaranteed with ppm is that all zero (R = 0, G = 0, B = 0) pixels will remain
533 * zero, and non-zero pixels will remain non-zero.
534 */
535 pgm_structure * load_pgm(const char * file_name)
536 {
537 unsigned char flags;
538 int x, y;
539 int maximum_greyscale_value;
540 FILE * input;
541 int pnm_number;
542 pgm_structure * new_pgm = (pgm_structure *) safe_malloc (sizeof(pgm_structure));
543 char * write_position;
544 char * end_position;
545 int image_size; /* width * height */
546
547 if((input = fopen(file_name, "rb")) == NULL) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Unable to open file. File not found or insufficient permissions.\n");
548
549 /* Parse the PGM header. */
550 if (fgetc(input) != 'P') REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: File is not a valid PGM or PPM file.\n");
551 pnm_number = fgetc(input) - '0';
552 if (pnm_number != 5 && pnm_number != 6) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PNM file. Only raw PGM (Portable Gray Map) and raw PPM (Portable Pixel Map) subtypes are allowed.\n");
553 load_pgm_skip(input);
554 if (fscanf(input, "%i", &(new_pgm->width)) != 1) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PGM/PPM header.\n");
555 load_pgm_skip(input);
556 if (fscanf(input, "%i", &(new_pgm->height)) != 1) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PGM/PPM header.\n");
557 load_pgm_skip(input);
558 if (fscanf(input, "%i", &maximum_greyscale_value) != 1) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PGM/PPM header.\n");
559 if (maximum_greyscale_value >= 256) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove_logo: Only 1 byte per pixel (pgm) or 1 byte per color value (ppm) are supported.\n");
560 load_pgm_skip(input);
561
562 new_pgm->pixel = (unsigned char *) safe_malloc (sizeof(unsigned char) * new_pgm->width * new_pgm->height);
563
564 /* Load the pixels. */
565 /* Note: I am aware that fgetc(input) isn't the fastest way of doing things, but it is quite compact and the code only runs once when the filter is initialized.*/
566 image_size = new_pgm->width * new_pgm->height;
567 end_position = new_pgm->pixel + image_size;
568 for (write_position = new_pgm->pixel; write_position < end_position; write_position++)
569 {
570 *write_position = fgetc(input);
571 if (pnm_number == 6) /* This tests to see if the file is a PPM file. */
572 { /* If it is, then consider the pixel set if any of the three color channels are set. Since we just care about == 0 or != 0, a bitwise or will do the trick. */
573 *write_position |= fgetc(input);
574 *write_position |= fgetc(input);
575 }
576 }
577
578 return new_pgm;
579 }
580
581 /**
582 * \brief Generates a scaled down image with half width, height, and intensity.
583 *
584 * \param vf Our struct for persistant data. In this case, it is used to update
585 * mask_max_size with the larger of the old or new value.
586 * \param input_image The image from which the new half-sized one will be based.
587 *
588 * \return The newly allocated and shrunken image.
589 *
590 * This function not only scales down an image, but halves the value in each pixel
591 * too. The purpose of this is to produce a chroma filter image out of a luma
592 * filter image. The pixel values store the distance to the edge of the logo and
593 * halving the dimensions halves the distance. This function rounds up, because
594 * a downwards rounding error could cause the filter to fail, but an upwards
595 * rounding error will only cause a minor amount of excess blur in the chroma
596 * planes.
597 */
598 pgm_structure * generate_half_size_image(vf_instance_t * vf, pgm_structure * input_image)
599 {
600 int x, y;
601 pgm_structure * new_pgm = (pgm_structure *) safe_malloc (sizeof(pgm_structure));
602 int has_anything_changed = 1;
603 int current_pass;
604 int max_mask_size;
605 char * current_pixel;
606
607 new_pgm->width = input_image->width / 2;
608 new_pgm->height = input_image->height / 2;
609 new_pgm->pixel = (unsigned char *) safe_malloc (sizeof(unsigned char) * new_pgm->width * new_pgm->height);
610
611 /* Copy over the image data, using the average of 4 pixels for to calculate each downsampled pixel. */
612 for (y = 0; y < new_pgm->height; y++)
613 for (x = 0; x < new_pgm->width; x++)
614 {
615 /* Set the pixel if there exists a non-zero value in the source pixels, else clear it. */
616 new_pgm->pixel[(y * new_pgm->width) + x] = input_image->pixel[((y << 1) * input_image->width) + (x << 1)] ||
617 input_image->pixel[((y << 1) * input_image->width) + (x << 1) + 1] ||
618 input_image->pixel[(((y << 1) + 1) * input_image->width) + (x << 1)] ||
619 input_image->pixel[(((y << 1) + 1) * input_image->width) + (x << 1) + 1];
620 new_pgm->pixel[(y * new_pgm->width) + x] = min(1, new_pgm->pixel[(y * new_pgm->width) + x]);
621 }
622
623 /* Now we need to recalculate the numbers for the smaller size. Just using the old_value / 2 can cause subtle
624 and fairly rare, but very nasty, bugs. */
625
626 current_pixel = new_pgm->pixel;
627 /* First pass, set all non-zero values to 1. */
628 for (x = 0; x < new_pgm->height * new_pgm->width; x++, current_pixel++)
629 if(*current_pixel) *current_pixel = 1;
630
631 /* Second pass and future passes. For each pass, if a pixel is itself the same value as the current pass,
632 and its four neighbors are too, then it is incremented. If no pixels are incremented by the end of the pass,
633 then we go again. Edge pixels are counted as always excluded (this should be true anyway for any sane mask,
634 but if it isn't this will ensure that we eventually exit). */
635 current_pass = 0;
636 while (has_anything_changed)
637 {
638 current_pass++;
639
640 has_anything_changed = 0; /* If this doesn't get set by the end of this pass, then we're done. */
641
642 for (y = 1; y < new_pgm->height - 1; y++)
643 {
644 for (x = 1; x < new_pgm->width - 1; x++)
645 {
646 if (new_pgm->pixel[(y * new_pgm->width) + x] >= current_pass && /* By using >= instead of ==, we allow the algorithm to work in place. */
647 new_pgm->pixel[(y * new_pgm->width) + (x + 1)] >= current_pass &&
648 new_pgm->pixel[(y * new_pgm->width) + (x - 1)] >= current_pass &&
649 new_pgm->pixel[((y + 1) * new_pgm->width) + x] >= current_pass &&
650 new_pgm->pixel[((y - 1) * new_pgm->width) + x] >= current_pass)
651 {
652 new_pgm->pixel[(y * new_pgm->width) + x]++; /* Increment the value since it still has not been eroded,
653 as evidenced by the if statement that just evaluated to true. */
654 has_anything_changed = 1;
655 }
656 }
657 }
658 }
659
660 for (y = 1; y < new_pgm->height - 1; y++)
661 {
662 for (x = 1; x < new_pgm->width - 1; x++)
663 {
664 new_pgm->pixel[(y * new_pgm->width) + x] = apply_mask_fudge_factor(new_pgm->pixel[(y * new_pgm->width) + x]);
665 }
666 }
667
668 max_mask_size = current_pass + 1; /* As a side-effect, we now know the maximum mask size, which we'll use to generate our masks. */
669 max_mask_size = apply_mask_fudge_factor(max_mask_size);
670 /* Commit the newly calculated max_mask_size to the vf->priv struct. */
671 ((vf_priv_s *)vf->priv)->max_mask_size = max(max_mask_size, ((vf_priv_s *)vf->priv)->max_mask_size);
672
673 return new_pgm;
674 }
675
676 /**
677 * \brief Checks if YV12 is supported by the next filter.
678 */
679 static unsigned int find_best(struct vf_instance_s* vf){
680 int is_format_okay = vf->next->query_format(vf->next, IMGFMT_YV12);
681 if ((is_format_okay & VFCAP_CSP_SUPPORTED_BY_HW) || (is_format_okay & VFCAP_CSP_SUPPORTED))
682 return IMGFMT_YV12;
683 else
684 return 0;
685 }
686
687 //===========================================================================//
688
689 /**
690 * \brief Configure the filter and call the next filter's config function.
691 */
692 static int config(struct vf_instance_s* vf, int width, int height, int d_width, int d_height, unsigned int flags, unsigned int outfmt)
693 {
694 if(!(((vf_priv_s *)vf->priv)->fmt=find_best(vf)))
695 return 0;
696 else
697 return vf_next_config(vf,width,height,d_width,d_height,flags,((vf_priv_s *)vf->priv)->fmt);
698 }
699
700 /**
701 * \brief Removes the logo from a plane (either luma or chroma).
702 *
703 * \param vf Not needed by this function, but needed by the blur function.
704 * \param source The image to have it's logo removed.
705 * \param destination Where the output image will be stored.
706 * \param source_stride How far apart (in memory) two consecutive lines are.
707 * \param destination Same as source_stride, but for the destination image.
708 * \param width Width of the image. This is the same for source and destination.
709 * \param height Height of the image. This is the same for source and destination.
710 * \param is_image_direct If the image is direct, then source and destination are
711 * the same and we can save a lot of time by not copying pixels that
712 * haven't changed.
713 * \param filter The image that stores the distance to the edge of the logo for
714 * each pixel.
715 * \param logo_start_x Smallest x-coordinate that contains at least 1 logo pixel.
716 * \param logo_start_y Smallest y-coordinate that contains at least 1 logo pixel.
717 * \param logo_end_x Largest x-coordinate that contains at least 1 logo pixel.
718 * \param logo_end_y Largest y-coordinate that contains at least 1 logo pixel.
719 *
720 * This function processes an entire plane. Pixels outside of the logo are copied
721 * to the output without change, and pixels inside the logo have the de-blurring
722 * function applied.
723 */
724 static void convert_yv12(const vf_instance_t * const vf, const char * const source, const int source_stride,
725 const mp_image_t * const source_image, const int width, const int height,
726 char * const destination, const int destination_stride, int is_image_direct, pgm_structure * filter,
727 const int plane, const int logo_start_x, const int logo_start_y, const int logo_end_x, const int logo_end_y)
728 {
729 int y;
730 int x;
731
732 /* These pointers point to where we are getting our pixel data (inside mpi) and where we are storing it (inside dmpi). */
733 const unsigned char * source_line;
734 unsigned char * destination_line;
735
736 if (!is_image_direct)
737 memcpy_pic(destination, source, width, height, destination_stride, source_stride);
738
739 for (y = logo_start_y; y <= logo_end_y; y++)
740 {
741 source_line = (const unsigned char *) source + (source_stride * y);
742 destination_line = (unsigned char *) destination + (destination_stride * y);
743
744 for (x = logo_start_x; x <= logo_end_x; x++)
745 {
746 unsigned int output;
747
748 if (filter->pixel[(y * filter->width) + x]) /* Only process if we are in the logo. */
749 {
750 get_blur(vf, &output, filter, source_image, x, y, plane);
751 destination_line[x] = output;
752 }
753 else /* Else just copy the data. */
754 if (!is_image_direct)
755 destination_line[x] = source_line[x];
756 }
757 }
758 }
759
760 /**
761 * \brief Process a frame.
762 *
763 * \param mpi The image sent to use by the previous filter.
764 * \param dmpi Where we will store the processed output image.
765 * \param vf This is how the filter gets access to it's persistant data.
766 *
767 * \return The return code of the next filter, or 0 on failure/error.
768 *
769 * This function processes an entire frame. The frame is sent by the previous
770 * filter, has the logo removed by the filter, and is then sent to the next
771 * filter.
772 */
773 static int put_image(struct vf_instance_s* vf, mp_image_t *mpi){
774 mp_image_t *dmpi;
775
776 dmpi=vf_get_image(vf->next,((vf_priv_s *)vf->priv)->fmt,
777 MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE,
778 mpi->w, mpi->h);
779
780 /* Check to make sure that the filter image and the video stream are the same size. */
781 if ((((vf_priv_s *)vf->priv)->filter->width != mpi->w) || (((vf_priv_s *)vf->priv)->filter->height != mpi->h))
782 {
783 mp_msg(MSGT_VFILTER,MSGL_ERR, "Filter image and video stream are not of the same size. (Filter: %d x %d, Stream: %d x %d)\n",
784 ((vf_priv_s *)vf->priv)->filter->width, ((vf_priv_s *)vf->priv)->filter->height, mpi->w, mpi->h);
785 return 0;
786 }
787
788 switch(dmpi->imgfmt){
789 case IMGFMT_YV12:
790 convert_yv12(vf, mpi->planes[0], mpi->stride[0], mpi, mpi->w, mpi->h,
791 dmpi->planes[0], dmpi->stride[0],
792 mpi->flags & MP_IMGFLAG_DIRECT, ((vf_priv_s *)vf->priv)->filter, 0,
793 ((vf_priv_s *)vf->priv)->bounding_rectangle_posx1, ((vf_priv_s *)vf->priv)->bounding_rectangle_posy1,
794 ((vf_priv_s *)vf->priv)->bounding_rectangle_posx2, ((vf_priv_s *)vf->priv)->bounding_rectangle_posy2);
795 convert_yv12(vf, mpi->planes[1], mpi->stride[1], mpi, mpi->w / 2, mpi->h / 2,
796 dmpi->planes[1], dmpi->stride[1],
797 mpi->flags & MP_IMGFLAG_DIRECT, ((vf_priv_s *)vf->priv)->half_size_filter, 1,
798 ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posx1, ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posy1,
799 ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posx2, ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posy2);
800 convert_yv12(vf, mpi->planes[2], mpi->stride[2], mpi, mpi->w / 2, mpi->h / 2,
801 dmpi->planes[2], dmpi->stride[2],
802 mpi->flags & MP_IMGFLAG_DIRECT, ((vf_priv_s *)vf->priv)->half_size_filter, 2,
803 ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posx1, ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posy1,
804 ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posx2, ((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posy2);
805 break;
806
807 default:
808 mp_msg(MSGT_VFILTER,MSGL_ERR,"Unhandled format: 0x%X\n",dmpi->imgfmt);
809 return 0;
810 }
811
812 return vf_next_put_image(vf,dmpi);
813 }
814
815 //===========================================================================//
816
817 /**
818 * \brief Checks to see if the next filter accepts YV12 images.
819 */
820 static int query_format(struct vf_instance_s * vf, unsigned int fmt)
821 {
822 if (fmt == IMGFMT_YV12)
823 return vf->next->query_format(vf->next, IMGFMT_YV12);
824 else
825 return 0;
826 }
827
828 /**
829 * \brief Initializes our filter.
830 *
831 * \param args The arguments passed in from the command line go here. This
832 * filter expects only a single argument telling it where the PGM
833 * or PPM file that describes the logo region is.
834 *
835 * This sets up our instance variables and parses the arguments to the filter.
836 */
837 static int open(vf_instance_t * vf, char * args)
838 {
839 vf->priv = safe_malloc(sizeof(vf_priv_s));
840
841 /* Load our filter image. */
842 if (args)
843 ((vf_priv_s *)vf->priv)->filter = load_pgm(args);
844 else
845 {
846 mp_msg(MSGT_VFILTER, MSGL_ERR, "[vf]remove_logo usage: remove_logo=/path/to/filter_image_file.pgm\n");
847 free(vf->priv);
848 return 0;
849 }
850
851 if (((vf_priv_s *)vf->priv)->filter == NULL)
852 {
853 /* Error message was displayed by load_pgm(). */
854 free(vf->priv);
855 return 0;
856 }
857
858 /* Create the scaled down filter image for the chroma planes. */
859 convert_mask_to_strength_mask(vf, ((vf_priv_s *)vf->priv)->filter);
860 ((vf_priv_s *)vf->priv)->half_size_filter = generate_half_size_image(vf, ((vf_priv_s *)vf->priv)->filter);
861
862 /* Now that we know how many masks we need (the info is in vf), we can generate the masks. */
863 initialize_masks(vf);
864
865 /* Calculate our bounding rectangles, which determine in what region the logo resides for faster processing. */
866 calculate_bounding_rectangle(&((vf_priv_s *)vf->priv)->bounding_rectangle_posx1, &((vf_priv_s *)vf->priv)->bounding_rectangle_posy1,
867 &((vf_priv_s *)vf->priv)->bounding_rectangle_posx2, &((vf_priv_s *)vf->priv)->bounding_rectangle_posy2,
868 ((vf_priv_s *)vf->priv)->filter);
869 calculate_bounding_rectangle(&((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posx1,
870 &((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posy1,
871 &((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posx2,
872 &((vf_priv_s *)vf->priv)->bounding_rectangle_half_size_posy2,
873 ((vf_priv_s *)vf->priv)->half_size_filter);
874
875 vf->config=config;
876 vf->put_image=put_image;
877 vf->query_format=query_format;
878 return 1;
879 }
880
881 /**
882 * \brief Frees memory that our filter allocated.
883 *
884 * This is called at exit-time.
885 */
886 void uninit(vf_instance_t * vf)
887 {
888 /* Destroy our masks and images. */
889 destroy_pgm(((vf_priv_s *)vf->priv)->filter);
890 destroy_pgm(((vf_priv_s *)vf->priv)->half_size_filter);
891 destroy_masks(vf);
892
893 /* Destroy our private structure that had been used to store those masks and images. */
894 free(vf->priv);
895
896 return;
897 }
898
899 /**
900 * \brief Meta data about our filter.
901 */
902 vf_info_t vf_info_remove_logo = {
903 "Removes a tv logo based on a mask image.",
904 "remove-logo",
905 "Robert Edele",
906 "",
907 open,
908 NULL
909 };
910
911 //===========================================================================//