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