00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00083 #include <stdio.h>
00084 #include <stdlib.h>
00085 #include <string.h>
00086 #include <ctype.h>
00087 #include <inttypes.h>
00088
00089 #include "config.h"
00090 #include "mp_msg.h"
00091 #include "libvo/fastmemcpy.h"
00092
00093 #include "img_format.h"
00094 #include "mp_image.h"
00095 #include "vf.h"
00096
00097
00098
00100 #define max(x,y) ((x)>(y)?(x):(y))
00101
00102 #define min(x,y) ((x)>(y)?(y):(x))
00103
00107 #define test_filter(image, x, y) ((unsigned char) (image->pixel[((y) * image->width) + (x)]))
00108
00118 #define apply_mask_fudge_factor(x) (((x) >> 2) + x)
00119
00129 typedef struct
00130 {
00131 unsigned int width;
00132 unsigned int height;
00133
00134 unsigned char * pixel;
00135
00136 } pgm_structure;
00137
00144 struct vf_priv_s
00145 {
00146 unsigned int fmt;
00147 int max_mask_size;
00148 int * * * mask;
00149 pgm_structure * filter;
00150 pgm_structure * half_size_filter;
00151
00152 int bounding_rectangle_posx1;
00153 int bounding_rectangle_posy1;
00154 int bounding_rectangle_posx2;
00155 int bounding_rectangle_posy2;
00156 int bounding_rectangle_half_size_posx1;
00157 int bounding_rectangle_half_size_posy1;
00158 int bounding_rectangle_half_size_posx2;
00159 int bounding_rectangle_half_size_posy2;
00160 } vf_priv_s;
00161
00173 static void * safe_malloc(int size)
00174 {
00175 void * answer = malloc(size);
00176 if (answer == NULL)
00177 mp_msg(MSGT_VFILTER, MSGL_ERR, "Unable to allocate memory in vf_remove_logo.c\n");
00178
00179 return answer;
00180 }
00181
00193 static void calculate_bounding_rectangle(int * posx1, int * posy1, int * posx2, int * posy2, pgm_structure * filter)
00194 {
00195 int x;
00196 int y;
00197 int start_x;
00198 int start_y;
00199 int end_x = filter->width - 1;
00200 int end_y = filter->height - 1;
00201 int did_we_find_a_logo_pixel = 0;
00202
00203
00204 for (start_x = 0; start_x < filter->width && !did_we_find_a_logo_pixel; start_x++)
00205 {
00206 for (y = 0; y < filter->height; y++)
00207 {
00208 did_we_find_a_logo_pixel |= test_filter(filter, start_x, y);
00209 }
00210 }
00211 start_x--;
00212
00213
00214 did_we_find_a_logo_pixel = 0;
00215 for (end_x = filter->width - 1; end_x > start_x && !did_we_find_a_logo_pixel; end_x--)
00216 {
00217 for (y = 0; y < filter->height; y++)
00218 {
00219 did_we_find_a_logo_pixel |= test_filter(filter, end_x, y);
00220 }
00221 }
00222 end_x++;
00223
00224
00225 did_we_find_a_logo_pixel = 0;
00226 for (start_y = 0; start_y < filter->height && !did_we_find_a_logo_pixel; start_y++)
00227 {
00228 for (x = 0; x < filter->width; x++)
00229 {
00230 did_we_find_a_logo_pixel |= test_filter(filter, x, start_y);
00231 }
00232 }
00233 start_y--;
00234
00235
00236 did_we_find_a_logo_pixel = 0;
00237 for (end_y = filter->height - 1; end_y > start_y && !did_we_find_a_logo_pixel; end_y--)
00238 {
00239 for (x = 0; x < filter->width; x++)
00240 {
00241 did_we_find_a_logo_pixel |= test_filter(filter, x, end_y);
00242 }
00243 }
00244 end_y++;
00245
00246 *posx1 = start_x;
00247 *posy1 = start_y;
00248 *posx2 = end_x;
00249 *posy2 = end_y;
00250
00251 return;
00252 }
00253
00262 static void destroy_masks(vf_instance_t * vf)
00263 {
00264 int a, b;
00265
00266
00267 int * * * mask = vf->priv->mask;
00268 int max_mask_size = vf->priv->max_mask_size;
00269
00270 if (mask == NULL)
00271 return;
00272
00273
00274 for (a = 0; a <= max_mask_size; a++)
00275 {
00276 for (b = -a; b <= a; b++)
00277 {
00278 free(mask[a][b + a]);
00279 }
00280 free(mask[a]);
00281 }
00282 free(mask);
00283
00284
00285 vf->priv->mask = NULL;
00286
00287 return;
00288 }
00289
00299 static void initialize_masks(vf_instance_t * vf)
00300 {
00301 int a, b, c;
00302
00303
00304 int * * * mask = vf->priv->mask;
00305 int max_mask_size = vf->priv->max_mask_size;
00306
00307
00308
00309 mask = (int * * *) safe_malloc(sizeof(int * *) * (max_mask_size + 1));
00310 for (a = 0; a <= max_mask_size; a++)
00311 {
00312 mask[a] = (int * *) safe_malloc(sizeof(int *) * ((a * 2) + 1));
00313 for (b = -a; b <= a; b++)
00314 {
00315 mask[a][b + a] = (int *) safe_malloc(sizeof(int) * ((a * 2) + 1));
00316 for (c = -a; c <= a; c++)
00317 {
00318 if ((b * b) + (c * c) <= (a * a))
00319 mask[a][b + a][c + a] = 1;
00320 else
00321 mask[a][b + a][c + a] = 0;
00322 }
00323 }
00324 }
00325
00326
00327 vf->priv->mask = mask;
00328
00329 return;
00330 }
00331
00347 static void convert_mask_to_strength_mask(vf_instance_t * vf, pgm_structure * mask)
00348 {
00349 int x, y;
00350 int has_anything_changed = 1;
00351 int current_pass = 0;
00352
00353 int max_mask_size;
00354
00355 char * current_pixel = mask->pixel;
00356
00357
00358
00359 for (x = 0; x < mask->height * mask->width; x++, current_pixel++)
00360 if(*current_pixel) *current_pixel = 1;
00361
00362
00363
00364
00365
00366 while (has_anything_changed)
00367 {
00368 current_pass++;
00369 current_pixel = mask->pixel;
00370
00371 has_anything_changed = 0;
00372
00373 for (y = 1; y < mask->height - 1; y++)
00374 {
00375 for (x = 1; x < mask->width - 1; x++)
00376 {
00377
00378
00379
00380 if (*current_pixel >= current_pass &&
00381 *(current_pixel + 1) >= current_pass &&
00382 *(current_pixel - 1) >= current_pass &&
00383 *(current_pixel + mask->width) >= current_pass &&
00384 *(current_pixel - mask->width) >= current_pass)
00385 {
00386 (*current_pixel)++;
00387
00388 has_anything_changed = 1;
00389 }
00390 current_pixel++;
00391 }
00392 }
00393 }
00394
00395
00396 for (y = 1; y < mask->height - 1; y++)
00397 {
00398 for (x = 1; x < mask->width - 1; x++)
00399 {
00400 mask->pixel[(y * mask->width) + x] = apply_mask_fudge_factor(mask->pixel[(y * mask->width) + x]);
00401 }
00402 }
00403
00404 max_mask_size = current_pass + 1;
00405 max_mask_size = apply_mask_fudge_factor(max_mask_size);
00406
00407 vf->priv->max_mask_size = max_mask_size;
00408
00409 return;
00410 }
00411
00428 static void get_blur(const vf_instance_t * const vf, unsigned int * const value_out, const pgm_structure * const logo_mask,
00429 const mp_image_t * const image, const int x, const int y, const int plane)
00430 {
00431 int mask_size;
00432
00433 int * * * mask = vf->priv->mask;
00434
00435 int start_posx, start_posy, end_posx, end_posy;
00436 int i, j;
00437 unsigned int accumulator = 0, divisor = 0;
00438 const unsigned char * mask_read_position;
00439 const unsigned char * logo_mask_read_position;
00440
00441
00442 mask_size = test_filter(logo_mask, x, y);
00443 start_posx = max(0, x - mask_size);
00444 start_posy = max(0, y - mask_size);
00445 end_posx = min(image->width - 1, x + mask_size);
00446 end_posy = min(image->height - 1, y + mask_size);
00447
00448 mask_read_position = image->planes[plane] + (image->stride[plane] * start_posy) + start_posx;
00449 logo_mask_read_position = logo_mask->pixel + (start_posy * logo_mask->width) + start_posx;
00450
00451 for (j = start_posy; j <= end_posy; j++)
00452 {
00453 for (i = start_posx; i <= end_posx; i++)
00454 {
00455 if (!(*logo_mask_read_position) && mask[mask_size][i - start_posx][j - start_posy])
00456 {
00457 accumulator += *mask_read_position;
00458 divisor++;
00459 }
00460
00461 mask_read_position++;
00462 logo_mask_read_position++;
00463 }
00464
00465 mask_read_position += (image->stride[plane] - ((end_posx + 1) - start_posx));
00466 logo_mask_read_position += (logo_mask->width - ((end_posx + 1) - start_posx));
00467 }
00468
00469 if (divisor == 0)
00470 {
00471 *value_out = 255;
00472 }
00473 else
00474 {
00475 *value_out = (accumulator + (divisor / 2)) / divisor;
00476 }
00477
00478 return;
00479 }
00480
00484 static void destroy_pgm(pgm_structure * to_be_destroyed)
00485 {
00486 if (to_be_destroyed == NULL)
00487 return;
00488
00489
00490 if (to_be_destroyed->pixel != NULL)
00491 {
00492 free(to_be_destroyed->pixel);
00493 to_be_destroyed->pixel = NULL;
00494 }
00495
00496
00497 free(to_be_destroyed);
00498 }
00499
00501 static void load_pgm_skip(FILE *f) {
00502 int c, comment = 0;
00503 do {
00504 c = fgetc(f);
00505 if (c == '#')
00506 comment = 1;
00507 if (c == '\n')
00508 comment = 0;
00509 } while (c != EOF && (isspace(c) || comment));
00510 ungetc(c, f);
00511 }
00512
00513 #define REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE(message) {mp_msg(MSGT_VFILTER, MSGL_ERR, message); return NULL;}
00514
00531 static pgm_structure * load_pgm(const char * file_name)
00532 {
00533 int maximum_greyscale_value;
00534 FILE * input;
00535 int pnm_number;
00536 pgm_structure * new_pgm = (pgm_structure *) safe_malloc (sizeof(pgm_structure));
00537 char * write_position;
00538 char * end_position;
00539 int image_size;
00540
00541 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");
00542
00543
00544 if (fgetc(input) != 'P') REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: File is not a valid PGM or PPM file.\n");
00545 pnm_number = fgetc(input) - '0';
00546 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");
00547 load_pgm_skip(input);
00548 if (fscanf(input, "%i", &(new_pgm->width)) != 1) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PGM/PPM header.\n");
00549 load_pgm_skip(input);
00550 if (fscanf(input, "%i", &(new_pgm->height)) != 1) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PGM/PPM header.\n");
00551 load_pgm_skip(input);
00552 if (fscanf(input, "%i", &maximum_greyscale_value) != 1) REMOVE_LOGO_LOAD_PGM_ERROR_MESSAGE("[vf]remove-logo: Invalid PGM/PPM header.\n");
00553 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");
00554 load_pgm_skip(input);
00555
00556 new_pgm->pixel = (unsigned char *) safe_malloc (sizeof(unsigned char) * new_pgm->width * new_pgm->height);
00557
00558
00559
00560 image_size = new_pgm->width * new_pgm->height;
00561 end_position = new_pgm->pixel + image_size;
00562 for (write_position = new_pgm->pixel; write_position < end_position; write_position++)
00563 {
00564 *write_position = fgetc(input);
00565 if (pnm_number == 6)
00566 {
00567 *write_position |= fgetc(input);
00568 *write_position |= fgetc(input);
00569 }
00570 }
00571
00572 return new_pgm;
00573 }
00574
00592 static pgm_structure * generate_half_size_image(vf_instance_t * vf, pgm_structure * input_image)
00593 {
00594 int x, y;
00595 pgm_structure * new_pgm = (pgm_structure *) safe_malloc (sizeof(pgm_structure));
00596 int has_anything_changed = 1;
00597 int current_pass;
00598 int max_mask_size;
00599 char * current_pixel;
00600
00601 new_pgm->width = input_image->width / 2;
00602 new_pgm->height = input_image->height / 2;
00603 new_pgm->pixel = (unsigned char *) safe_malloc (sizeof(unsigned char) * new_pgm->width * new_pgm->height);
00604
00605
00606 for (y = 0; y < new_pgm->height; y++)
00607 for (x = 0; x < new_pgm->width; x++)
00608 {
00609
00610 new_pgm->pixel[(y * new_pgm->width) + x] = input_image->pixel[((y << 1) * input_image->width) + (x << 1)] ||
00611 input_image->pixel[((y << 1) * input_image->width) + (x << 1) + 1] ||
00612 input_image->pixel[(((y << 1) + 1) * input_image->width) + (x << 1)] ||
00613 input_image->pixel[(((y << 1) + 1) * input_image->width) + (x << 1) + 1];
00614 new_pgm->pixel[(y * new_pgm->width) + x] = min(1, new_pgm->pixel[(y * new_pgm->width) + x]);
00615 }
00616
00617
00618
00619
00620 current_pixel = new_pgm->pixel;
00621
00622 for (x = 0; x < new_pgm->height * new_pgm->width; x++, current_pixel++)
00623 if(*current_pixel) *current_pixel = 1;
00624
00625
00626
00627
00628
00629 current_pass = 0;
00630 while (has_anything_changed)
00631 {
00632 current_pass++;
00633
00634 has_anything_changed = 0;
00635
00636 for (y = 1; y < new_pgm->height - 1; y++)
00637 {
00638 for (x = 1; x < new_pgm->width - 1; x++)
00639 {
00640 if (new_pgm->pixel[(y * new_pgm->width) + x] >= current_pass &&
00641 new_pgm->pixel[(y * new_pgm->width) + (x + 1)] >= current_pass &&
00642 new_pgm->pixel[(y * new_pgm->width) + (x - 1)] >= current_pass &&
00643 new_pgm->pixel[((y + 1) * new_pgm->width) + x] >= current_pass &&
00644 new_pgm->pixel[((y - 1) * new_pgm->width) + x] >= current_pass)
00645 {
00646 new_pgm->pixel[(y * new_pgm->width) + x]++;
00647
00648 has_anything_changed = 1;
00649 }
00650 }
00651 }
00652 }
00653
00654 for (y = 1; y < new_pgm->height - 1; y++)
00655 {
00656 for (x = 1; x < new_pgm->width - 1; x++)
00657 {
00658 new_pgm->pixel[(y * new_pgm->width) + x] = apply_mask_fudge_factor(new_pgm->pixel[(y * new_pgm->width) + x]);
00659 }
00660 }
00661
00662 max_mask_size = current_pass + 1;
00663 max_mask_size = apply_mask_fudge_factor(max_mask_size);
00664
00665 vf->priv->max_mask_size = max(max_mask_size, vf->priv->max_mask_size);
00666
00667 return new_pgm;
00668 }
00669
00673 static unsigned int find_best(struct vf_instance *vf){
00674 int is_format_okay = vf_next_query_format(vf, IMGFMT_YV12);
00675 if ((is_format_okay & VFCAP_CSP_SUPPORTED_BY_HW) || (is_format_okay & VFCAP_CSP_SUPPORTED))
00676 return IMGFMT_YV12;
00677 else
00678 return 0;
00679 }
00680
00681
00682
00686 static int config(struct vf_instance *vf, int width, int height, int d_width, int d_height, unsigned int flags, unsigned int outfmt)
00687 {
00688 if(!(vf->priv->fmt=find_best(vf)))
00689 return 0;
00690 else
00691 return vf_next_config(vf,width,height,d_width,d_height,flags,vf->priv->fmt);
00692 }
00693
00718 static void convert_yv12(const vf_instance_t * const vf, const char * const source, const int source_stride,
00719 const mp_image_t * const source_image, const int width, const int height,
00720 char * const destination, const int destination_stride, int is_image_direct, pgm_structure * filter,
00721 const int plane, const int logo_start_x, const int logo_start_y, const int logo_end_x, const int logo_end_y)
00722 {
00723 int y;
00724 int x;
00725
00726
00727 const unsigned char * source_line;
00728 unsigned char * destination_line;
00729
00730 if (!is_image_direct)
00731 memcpy_pic(destination, source, width, height, destination_stride, source_stride);
00732
00733 for (y = logo_start_y; y <= logo_end_y; y++)
00734 {
00735 source_line = (const unsigned char *) source + (source_stride * y);
00736 destination_line = (unsigned char *) destination + (destination_stride * y);
00737
00738 for (x = logo_start_x; x <= logo_end_x; x++)
00739 {
00740 unsigned int output;
00741
00742 if (filter->pixel[(y * filter->width) + x])
00743 {
00744 get_blur(vf, &output, filter, source_image, x, y, plane);
00745 destination_line[x] = output;
00746 }
00747 else
00748 if (!is_image_direct)
00749 destination_line[x] = source_line[x];
00750 }
00751 }
00752 }
00753
00767 static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){
00768 mp_image_t *dmpi;
00769
00770 dmpi=vf_get_image(vf->next,vf->priv->fmt,
00771 MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE,
00772 mpi->w, mpi->h);
00773
00774
00775 if (vf->priv->filter->width != mpi->w || vf->priv->filter->height != mpi->h)
00776 {
00777 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",
00778 vf->priv->filter->width, vf->priv->filter->height, mpi->w, mpi->h);
00779 return 0;
00780 }
00781
00782 switch(dmpi->imgfmt){
00783 case IMGFMT_YV12:
00784 convert_yv12(vf, mpi->planes[0], mpi->stride[0], mpi, mpi->w, mpi->h,
00785 dmpi->planes[0], dmpi->stride[0],
00786 mpi->flags & MP_IMGFLAG_DIRECT, vf->priv->filter, 0,
00787 vf->priv->bounding_rectangle_posx1, vf->priv->bounding_rectangle_posy1,
00788 vf->priv->bounding_rectangle_posx2, vf->priv->bounding_rectangle_posy2);
00789 convert_yv12(vf, mpi->planes[1], mpi->stride[1], mpi, mpi->w / 2, mpi->h / 2,
00790 dmpi->planes[1], dmpi->stride[1],
00791 mpi->flags & MP_IMGFLAG_DIRECT, vf->priv->half_size_filter, 1,
00792 vf->priv->bounding_rectangle_half_size_posx1, vf->priv->bounding_rectangle_half_size_posy1,
00793 vf->priv->bounding_rectangle_half_size_posx2, vf->priv->bounding_rectangle_half_size_posy2);
00794 convert_yv12(vf, mpi->planes[2], mpi->stride[2], mpi, mpi->w / 2, mpi->h / 2,
00795 dmpi->planes[2], dmpi->stride[2],
00796 mpi->flags & MP_IMGFLAG_DIRECT, vf->priv->half_size_filter, 2,
00797 vf->priv->bounding_rectangle_half_size_posx1, vf->priv->bounding_rectangle_half_size_posy1,
00798 vf->priv->bounding_rectangle_half_size_posx2, vf->priv->bounding_rectangle_half_size_posy2);
00799 break;
00800
00801 default:
00802 mp_msg(MSGT_VFILTER,MSGL_ERR,"Unhandled format: 0x%X\n",dmpi->imgfmt);
00803 return 0;
00804 }
00805
00806 return vf_next_put_image(vf,dmpi, pts);
00807 }
00808
00809
00810
00814 static int query_format(struct vf_instance *vf, unsigned int fmt)
00815 {
00816 if (fmt == IMGFMT_YV12)
00817 return vf_next_query_format(vf, IMGFMT_YV12);
00818 else
00819 return 0;
00820 }
00821
00827 static void uninit(vf_instance_t *vf)
00828 {
00829
00830 destroy_pgm(vf->priv->filter);
00831 destroy_pgm(vf->priv->half_size_filter);
00832 destroy_masks(vf);
00833
00834
00835 free(vf->priv);
00836
00837 return;
00838 }
00839
00849 static int vf_open(vf_instance_t *vf, char *args)
00850 {
00851 vf->priv = safe_malloc(sizeof(vf_priv_s));
00852 vf->uninit = uninit;
00853
00854
00855 if (args)
00856 vf->priv->filter = load_pgm(args);
00857 else
00858 {
00859 mp_msg(MSGT_VFILTER, MSGL_ERR, "[vf]remove_logo usage: remove_logo=/path/to/filter_image_file.pgm\n");
00860 free(vf->priv);
00861 return 0;
00862 }
00863
00864 if (vf->priv->filter == NULL)
00865 {
00866
00867 free(vf->priv);
00868 return 0;
00869 }
00870
00871
00872 convert_mask_to_strength_mask(vf, vf->priv->filter);
00873 vf->priv->half_size_filter = generate_half_size_image(vf, vf->priv->filter);
00874
00875
00876 initialize_masks(vf);
00877
00878
00879 calculate_bounding_rectangle(&vf->priv->bounding_rectangle_posx1, &vf->priv->bounding_rectangle_posy1,
00880 &vf->priv->bounding_rectangle_posx2, &vf->priv->bounding_rectangle_posy2,
00881 vf->priv->filter);
00882 calculate_bounding_rectangle(&vf->priv->bounding_rectangle_half_size_posx1,
00883 &vf->priv->bounding_rectangle_half_size_posy1,
00884 &vf->priv->bounding_rectangle_half_size_posx2,
00885 &vf->priv->bounding_rectangle_half_size_posy2,
00886 vf->priv->half_size_filter);
00887
00888 vf->config=config;
00889 vf->put_image=put_image;
00890 vf->query_format=query_format;
00891 return 1;
00892 }
00893
00897 const vf_info_t vf_info_remove_logo = {
00898 "Removes a tv logo based on a mask image.",
00899 "remove-logo",
00900 "Robert Edele",
00901 "",
00902 vf_open,
00903 NULL
00904 };
00905
00906