00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00072 #include "libavutil/imgutils.h"
00073 #include "avfilter.h"
00074 #include "bbox.h"
00075 #include "lavfutils.h"
00076 #include "lswsutils.h"
00077
00078 typedef struct {
00079
00080
00081 int ***mask;
00082 int max_mask_size;
00083 int mask_w, mask_h;
00084
00085 uint8_t *full_mask_data;
00086 FFBoundingBox full_mask_bbox;
00087 uint8_t *half_mask_data;
00088 FFBoundingBox half_mask_bbox;
00089 } RemovelogoContext;
00090
00101 #define apply_mask_fudge_factor(x) (((x) >> 2) + x)
00102
00117 static void convert_mask_to_strength_mask(uint8_t *data, int linesize,
00118 int w, int h, int min_val,
00119 int *max_mask_size)
00120 {
00121 int x, y;
00122
00123
00124
00125 int current_pass = 0;
00126
00127
00128 for (y = 0; y < h; y++)
00129 for (x = 0; x < w; x++)
00130 data[y*linesize + x] = data[y*linesize + x] > min_val;
00131
00132
00133
00134
00135
00136
00137
00138 while (1) {
00139
00140 int has_anything_changed = 0;
00141 uint8_t *current_pixel0 = data, *current_pixel;
00142 current_pass++;
00143
00144 for (y = 1; y < h-1; y++) {
00145 current_pixel = current_pixel0;
00146 for (x = 1; x < w-1; x++) {
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158 if ( *current_pixel >= current_pass &&
00159 *(current_pixel + 1) >= current_pass &&
00160 *(current_pixel - 1) >= current_pass &&
00161 *(current_pixel + w) >= current_pass &&
00162 *(current_pixel - w) >= current_pass) {
00163
00164
00165
00166 (*current_pixel)++;
00167 has_anything_changed = 1;
00168 }
00169 current_pixel++;
00170 }
00171 current_pixel0 += linesize;
00172 }
00173 if (!has_anything_changed)
00174 break;
00175 }
00176
00177
00178
00179 for (y = 1; y < h - 1; y++)
00180 for (x = 1; x < w - 1; x++)
00181 data[(y * linesize) + x] = apply_mask_fudge_factor(data[(y * linesize) + x]);
00182
00183
00184
00185
00186
00187 *max_mask_size = apply_mask_fudge_factor(current_pass + 1);
00188 }
00189
00190 static int query_formats(AVFilterContext *ctx)
00191 {
00192 enum PixelFormat pix_fmts[] = { PIX_FMT_YUV420P, PIX_FMT_NONE };
00193 avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
00194 return 0;
00195 }
00196
00197 static int load_mask(uint8_t **mask, int *w, int *h,
00198 const char *filename, void *log_ctx)
00199 {
00200 int ret;
00201 enum PixelFormat pix_fmt;
00202 uint8_t *src_data[4], *gray_data[4];
00203 int src_linesize[4], gray_linesize[4];
00204
00205
00206 if ((ret = ff_load_image(src_data, src_linesize, w, h, &pix_fmt, filename, log_ctx)) < 0)
00207 return ret;
00208
00209
00210 if ((ret = ff_scale_image(gray_data, gray_linesize, *w, *h, PIX_FMT_GRAY8,
00211 src_data, src_linesize, *w, *h, pix_fmt,
00212 log_ctx)) < 0)
00213 goto end;
00214
00215
00216 *mask = av_malloc(*w * *h);
00217 if (!*mask)
00218 ret = AVERROR(ENOMEM);
00219 av_image_copy_plane(*mask, *w, gray_data[0], gray_linesize[0], *w, *h);
00220
00221 end:
00222 av_free(src_data[0]);
00223 av_free(gray_data[0]);
00224 return ret;
00225 }
00226
00238 static void generate_half_size_image(const uint8_t *src_data, int src_linesize,
00239 uint8_t *dst_data, int dst_linesize,
00240 int src_w, int src_h,
00241 int *max_mask_size)
00242 {
00243 int x, y;
00244
00245
00246
00247 for (y = 0; y < src_h/2; y++) {
00248 for (x = 0; x < src_w/2; x++) {
00249
00250
00251 dst_data[(y * dst_linesize) + x] =
00252 src_data[((y << 1) * src_linesize) + (x << 1)] ||
00253 src_data[((y << 1) * src_linesize) + (x << 1) + 1] ||
00254 src_data[(((y << 1) + 1) * src_linesize) + (x << 1)] ||
00255 src_data[(((y << 1) + 1) * src_linesize) + (x << 1) + 1];
00256 dst_data[(y * dst_linesize) + x] = FFMIN(1, dst_data[(y * dst_linesize) + x]);
00257 }
00258 }
00259
00260 convert_mask_to_strength_mask(dst_data, dst_linesize,
00261 src_w/2, src_h/2, 0, max_mask_size);
00262 }
00263
00264 static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
00265 {
00266 RemovelogoContext *removelogo = ctx->priv;
00267 int ***mask;
00268 int ret = 0;
00269 int a, b, c, w, h;
00270 int full_max_mask_size, half_max_mask_size;
00271
00272 if (!args) {
00273 av_log(ctx, AV_LOG_ERROR, "An image file must be specified as argument\n");
00274 return AVERROR(EINVAL);
00275 }
00276
00277
00278 if ((ret = load_mask(&removelogo->full_mask_data, &w, &h, args, ctx)) < 0)
00279 return ret;
00280 removelogo->mask_w = w;
00281 removelogo->mask_h = h;
00282
00283 convert_mask_to_strength_mask(removelogo->full_mask_data, w, w, h,
00284 16, &full_max_mask_size);
00285
00286
00287 if (!(removelogo->half_mask_data = av_mallocz(w/2 * h/2)))
00288 return AVERROR(ENOMEM);
00289 generate_half_size_image(removelogo->full_mask_data, w,
00290 removelogo->half_mask_data, w/2,
00291 w, h, &half_max_mask_size);
00292
00293 removelogo->max_mask_size = FFMAX(full_max_mask_size, half_max_mask_size);
00294
00295
00296
00297
00298
00299 mask = (int ***)av_malloc(sizeof(int **) * (removelogo->max_mask_size + 1));
00300 if (!mask)
00301 return AVERROR(ENOMEM);
00302
00303 for (a = 0; a <= removelogo->max_mask_size; a++) {
00304 mask[a] = (int **)av_malloc(sizeof(int *) * ((a * 2) + 1));
00305 if (!mask[a])
00306 return AVERROR(ENOMEM);
00307 for (b = -a; b <= a; b++) {
00308 mask[a][b + a] = (int *)av_malloc(sizeof(int) * ((a * 2) + 1));
00309 if (!mask[a][b + a])
00310 return AVERROR(ENOMEM);
00311 for (c = -a; c <= a; c++) {
00312 if ((b * b) + (c * c) <= (a * a))
00313 mask[a][b + a][c + a] = 1;
00314 else
00315 mask[a][b + a][c + a] = 0;
00316 }
00317 }
00318 }
00319 removelogo->mask = mask;
00320
00321
00322
00323 ff_calculate_bounding_box(&removelogo->full_mask_bbox, removelogo->full_mask_data, w, w, h, 0);
00324 ff_calculate_bounding_box(&removelogo->half_mask_bbox, removelogo->half_mask_data, w/2, w/2, h/2, 0);
00325
00326 #define SHOW_LOGO_INFO(mask_type) \
00327 av_log(ctx, AV_LOG_INFO, #mask_type " x1:%d x2:%d y1:%d y2:%d max_mask_size:%d\n", \
00328 removelogo->mask_type##_mask_bbox.x1, removelogo->mask_type##_mask_bbox.x2, \
00329 removelogo->mask_type##_mask_bbox.y1, removelogo->mask_type##_mask_bbox.y2, \
00330 mask_type##_max_mask_size);
00331 SHOW_LOGO_INFO(full);
00332 SHOW_LOGO_INFO(half);
00333
00334 return 0;
00335 }
00336
00337 static int config_props_input(AVFilterLink *inlink)
00338 {
00339 AVFilterContext *ctx = inlink->dst;
00340 RemovelogoContext *removelogo = ctx->priv;
00341
00342 if (inlink->w != removelogo->mask_w || inlink->h != removelogo->mask_h) {
00343 av_log(ctx, AV_LOG_INFO,
00344 "Mask image size %dx%d does not match with the input video size %dx%d\n",
00345 removelogo->mask_w, removelogo->mask_h, inlink->w, inlink->h);
00346 return AVERROR(EINVAL);
00347 }
00348
00349 return 0;
00350 }
00351
00366 static unsigned int blur_pixel(int ***mask,
00367 const uint8_t *mask_data, int mask_linesize,
00368 uint8_t *image_data, int image_linesize,
00369 int w, int h, int x, int y)
00370 {
00371
00372
00373 int mask_size;
00374 int start_posx, start_posy, end_posx, end_posy;
00375 int i, j;
00376 unsigned int accumulator = 0, divisor = 0;
00377
00378 const uint8_t *image_read_position;
00379
00380 const uint8_t *mask_read_position;
00381
00382
00383 mask_size = mask_data[y * mask_linesize + x];
00384 start_posx = FFMAX(0, x - mask_size);
00385 start_posy = FFMAX(0, y - mask_size);
00386 end_posx = FFMIN(w - 1, x + mask_size);
00387 end_posy = FFMIN(h - 1, y + mask_size);
00388
00389 image_read_position = image_data + image_linesize * start_posy + start_posx;
00390 mask_read_position = mask_data + mask_linesize * start_posy + start_posx;
00391
00392 for (j = start_posy; j <= end_posy; j++) {
00393 for (i = start_posx; i <= end_posx; i++) {
00394
00395
00396 if (!(*mask_read_position) && mask[mask_size][i - start_posx][j - start_posy]) {
00397 accumulator += *image_read_position;
00398 divisor++;
00399 }
00400
00401 image_read_position++;
00402 mask_read_position++;
00403 }
00404
00405 image_read_position += (image_linesize - ((end_posx + 1) - start_posx));
00406 mask_read_position += (mask_linesize - ((end_posx + 1) - start_posx));
00407 }
00408
00409
00410
00411
00412 return divisor == 0 ? 255:
00413 (accumulator + (divisor / 2)) / divisor;
00414 }
00415
00439 static void blur_image(int ***mask,
00440 const uint8_t *src_data, int src_linesize,
00441 uint8_t *dst_data, int dst_linesize,
00442 const uint8_t *mask_data, int mask_linesize,
00443 int w, int h, int direct,
00444 FFBoundingBox *bbox)
00445 {
00446 int x, y;
00447 uint8_t *dst_line;
00448 const uint8_t *src_line;
00449
00450 if (!direct)
00451 av_image_copy_plane(dst_data, dst_linesize, src_data, src_linesize, w, h);
00452
00453 for (y = bbox->y1; y <= bbox->y2; y++) {
00454 src_line = src_data + src_linesize * y;
00455 dst_line = dst_data + dst_linesize * y;
00456
00457 for (x = bbox->x1; x <= bbox->x2; x++) {
00458 if (mask_data[y * mask_linesize + x]) {
00459
00460 dst_line[x] = blur_pixel(mask,
00461 mask_data, mask_linesize,
00462 dst_data, dst_linesize,
00463 w, h, x, y);
00464 } else {
00465
00466 if (!direct)
00467 dst_line[x] = src_line[x];
00468 }
00469 }
00470 }
00471 }
00472
00473 static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
00474 {
00475 AVFilterLink *outlink = inlink->dst->outputs[0];
00476 AVFilterBufferRef *outpicref;
00477
00478 if (inpicref->perms & AV_PERM_PRESERVE) {
00479 outpicref = avfilter_get_video_buffer(outlink, AV_PERM_WRITE,
00480 outlink->w, outlink->h);
00481 avfilter_copy_buffer_ref_props(outpicref, inpicref);
00482 outpicref->video->w = outlink->w;
00483 outpicref->video->h = outlink->h;
00484 } else
00485 outpicref = inpicref;
00486
00487 outlink->out_buf = outpicref;
00488 avfilter_start_frame(outlink, avfilter_ref_buffer(outpicref, ~0));
00489 }
00490
00491 static void end_frame(AVFilterLink *inlink)
00492 {
00493 RemovelogoContext *removelogo = inlink->dst->priv;
00494 AVFilterLink *outlink = inlink->dst->outputs[0];
00495 AVFilterBufferRef *inpicref = inlink ->cur_buf;
00496 AVFilterBufferRef *outpicref = outlink->out_buf;
00497 int direct = inpicref == outpicref;
00498
00499 blur_image(removelogo->mask,
00500 inpicref ->data[0], inpicref ->linesize[0],
00501 outpicref->data[0], outpicref->linesize[0],
00502 removelogo->full_mask_data, inlink->w,
00503 inlink->w, inlink->h, direct, &removelogo->full_mask_bbox);
00504 blur_image(removelogo->mask,
00505 inpicref ->data[1], inpicref ->linesize[1],
00506 outpicref->data[1], outpicref->linesize[1],
00507 removelogo->half_mask_data, inlink->w/2,
00508 inlink->w/2, inlink->h/2, direct, &removelogo->half_mask_bbox);
00509 blur_image(removelogo->mask,
00510 inpicref ->data[2], inpicref ->linesize[2],
00511 outpicref->data[2], outpicref->linesize[2],
00512 removelogo->half_mask_data, inlink->w/2,
00513 inlink->w/2, inlink->h/2, direct, &removelogo->half_mask_bbox);
00514
00515 avfilter_draw_slice(outlink, 0, inlink->h, 1);
00516 avfilter_end_frame(outlink);
00517 avfilter_unref_buffer(inpicref);
00518 if (!direct)
00519 avfilter_unref_buffer(outpicref);
00520 }
00521
00522 static void uninit(AVFilterContext *ctx)
00523 {
00524 RemovelogoContext *removelogo = ctx->priv;
00525 int a, b;
00526
00527 av_freep(&removelogo->full_mask_data);
00528 av_freep(&removelogo->half_mask_data);
00529
00530 if (removelogo->mask) {
00531
00532 for (a = 0; a <= removelogo->max_mask_size; a++) {
00533
00534 for (b = -a; b <= a; b++) {
00535 av_free(removelogo->mask[a][b + a]);
00536 }
00537 av_free(removelogo->mask[a]);
00538 }
00539
00540 av_freep(&removelogo->mask);
00541 }
00542 }
00543
00544 static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
00545
00546 AVFilter avfilter_vf_removelogo = {
00547 .name = "removelogo",
00548 .description = NULL_IF_CONFIG_SMALL("Remove a TV logo based on a mask image."),
00549 .priv_size = sizeof(RemovelogoContext),
00550 .init = init,
00551 .uninit = uninit,
00552 .query_formats = query_formats,
00553
00554 .inputs = (const AVFilterPad[]) {
00555 { .name = "default",
00556 .type = AVMEDIA_TYPE_VIDEO,
00557 .get_video_buffer = avfilter_null_get_video_buffer,
00558 .config_props = config_props_input,
00559 .draw_slice = null_draw_slice,
00560 .start_frame = start_frame,
00561 .end_frame = end_frame,
00562 .min_perms = AV_PERM_WRITE | AV_PERM_READ,
00563 .rej_perms = AV_PERM_PRESERVE },
00564 { .name = NULL }
00565 },
00566 .outputs = (const AVFilterPad[]) {
00567 { .name = "default",
00568 .type = AVMEDIA_TYPE_VIDEO, },
00569 { .name = NULL }
00570 },
00571 };