| 1 | /* |
| 2 | * Copyright (c) 2002 Jindrich Makovicka <makovick@gmail.com> |
| 3 | * Copyright (c) 2011 Stefano Sabatini |
| 4 | * Copyright (c) 2013 Jean Delvare <khali@linux-fr.org> |
| 5 | * |
| 6 | * This file is part of FFmpeg. |
| 7 | * |
| 8 | * FFmpeg is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License as published by |
| 10 | * the Free Software Foundation; either version 2 of the License, or |
| 11 | * (at your option) any later version. |
| 12 | * |
| 13 | * FFmpeg is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | * GNU General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU General Public License along |
| 19 | * with FFmpeg; if not, write to the Free Software Foundation, Inc., |
| 20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 21 | */ |
| 22 | |
| 23 | /** |
| 24 | * @file |
| 25 | * A very simple tv station logo remover |
| 26 | * Originally imported from MPlayer libmpcodecs/vf_delogo.c, |
| 27 | * the algorithm was later improved. |
| 28 | */ |
| 29 | |
| 30 | #include "libavutil/common.h" |
| 31 | #include "libavutil/imgutils.h" |
| 32 | #include "libavutil/opt.h" |
| 33 | #include "libavutil/pixdesc.h" |
| 34 | #include "avfilter.h" |
| 35 | #include "formats.h" |
| 36 | #include "internal.h" |
| 37 | #include "video.h" |
| 38 | |
| 39 | /** |
| 40 | * Apply a simple delogo algorithm to the image in src and put the |
| 41 | * result in dst. |
| 42 | * |
| 43 | * The algorithm is only applied to the region specified by the logo |
| 44 | * parameters. |
| 45 | * |
| 46 | * @param w width of the input image |
| 47 | * @param h height of the input image |
| 48 | * @param logo_x x coordinate of the top left corner of the logo region |
| 49 | * @param logo_y y coordinate of the top left corner of the logo region |
| 50 | * @param logo_w width of the logo |
| 51 | * @param logo_h height of the logo |
| 52 | * @param band the size of the band around the processed area |
| 53 | * @param show show a rectangle around the processed area, useful for |
| 54 | * parameters tweaking |
| 55 | * @param direct if non-zero perform in-place processing |
| 56 | */ |
| 57 | static void apply_delogo(uint8_t *dst, int dst_linesize, |
| 58 | uint8_t *src, int src_linesize, |
| 59 | int w, int h, AVRational sar, |
| 60 | int logo_x, int logo_y, int logo_w, int logo_h, |
| 61 | unsigned int band, int show, int direct) |
| 62 | { |
| 63 | int x, y; |
| 64 | uint64_t interp, weightl, weightr, weightt, weightb; |
| 65 | uint8_t *xdst, *xsrc; |
| 66 | |
| 67 | uint8_t *topleft, *botleft, *topright; |
| 68 | unsigned int left_sample, right_sample; |
| 69 | int xclipl, xclipr, yclipt, yclipb; |
| 70 | int logo_x1, logo_x2, logo_y1, logo_y2; |
| 71 | |
| 72 | xclipl = FFMAX(-logo_x, 0); |
| 73 | xclipr = FFMAX(logo_x+logo_w-w, 0); |
| 74 | yclipt = FFMAX(-logo_y, 0); |
| 75 | yclipb = FFMAX(logo_y+logo_h-h, 0); |
| 76 | |
| 77 | logo_x1 = logo_x + xclipl; |
| 78 | logo_x2 = logo_x + logo_w - xclipr; |
| 79 | logo_y1 = logo_y + yclipt; |
| 80 | logo_y2 = logo_y + logo_h - yclipb; |
| 81 | |
| 82 | topleft = src+logo_y1 * src_linesize+logo_x1; |
| 83 | topright = src+logo_y1 * src_linesize+logo_x2-1; |
| 84 | botleft = src+(logo_y2-1) * src_linesize+logo_x1; |
| 85 | |
| 86 | if (!direct) |
| 87 | av_image_copy_plane(dst, dst_linesize, src, src_linesize, w, h); |
| 88 | |
| 89 | dst += (logo_y1 + 1) * dst_linesize; |
| 90 | src += (logo_y1 + 1) * src_linesize; |
| 91 | |
| 92 | for (y = logo_y1+1; y < logo_y2-1; y++) { |
| 93 | left_sample = topleft[src_linesize*(y-logo_y1)] + |
| 94 | topleft[src_linesize*(y-logo_y1-1)] + |
| 95 | topleft[src_linesize*(y-logo_y1+1)]; |
| 96 | right_sample = topright[src_linesize*(y-logo_y1)] + |
| 97 | topright[src_linesize*(y-logo_y1-1)] + |
| 98 | topright[src_linesize*(y-logo_y1+1)]; |
| 99 | |
| 100 | for (x = logo_x1+1, |
| 101 | xdst = dst+logo_x1+1, |
| 102 | xsrc = src+logo_x1+1; x < logo_x2-1; x++, xdst++, xsrc++) { |
| 103 | |
| 104 | /* Weighted interpolation based on relative distances, taking SAR into account */ |
| 105 | weightl = (uint64_t) (logo_x2-1-x) * (y-logo_y1) * (logo_y2-1-y) * sar.den; |
| 106 | weightr = (uint64_t)(x-logo_x1) * (y-logo_y1) * (logo_y2-1-y) * sar.den; |
| 107 | weightt = (uint64_t)(x-logo_x1) * (logo_x2-1-x) * (logo_y2-1-y) * sar.num; |
| 108 | weightb = (uint64_t)(x-logo_x1) * (logo_x2-1-x) * (y-logo_y1) * sar.num; |
| 109 | |
| 110 | interp = |
| 111 | left_sample * weightl |
| 112 | + |
| 113 | right_sample * weightr |
| 114 | + |
| 115 | (topleft[x-logo_x1] + |
| 116 | topleft[x-logo_x1-1] + |
| 117 | topleft[x-logo_x1+1]) * weightt |
| 118 | + |
| 119 | (botleft[x-logo_x1] + |
| 120 | botleft[x-logo_x1-1] + |
| 121 | botleft[x-logo_x1+1]) * weightb; |
| 122 | interp /= (weightl + weightr + weightt + weightb) * 3U; |
| 123 | |
| 124 | if (y >= logo_y+band && y < logo_y+logo_h-band && |
| 125 | x >= logo_x+band && x < logo_x+logo_w-band) { |
| 126 | *xdst = interp; |
| 127 | } else { |
| 128 | unsigned dist = 0; |
| 129 | |
| 130 | if (x < logo_x+band) |
| 131 | dist = FFMAX(dist, logo_x-x+band); |
| 132 | else if (x >= logo_x+logo_w-band) |
| 133 | dist = FFMAX(dist, x-(logo_x+logo_w-1-band)); |
| 134 | |
| 135 | if (y < logo_y+band) |
| 136 | dist = FFMAX(dist, logo_y-y+band); |
| 137 | else if (y >= logo_y+logo_h-band) |
| 138 | dist = FFMAX(dist, y-(logo_y+logo_h-1-band)); |
| 139 | |
| 140 | *xdst = (*xsrc*dist + interp*(band-dist))/band; |
| 141 | if (show && (dist == band-1)) |
| 142 | *xdst = 0; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | dst += dst_linesize; |
| 147 | src += src_linesize; |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | typedef struct DelogoContext { |
| 152 | const AVClass *class; |
| 153 | int x, y, w, h, band, show; |
| 154 | } DelogoContext; |
| 155 | |
| 156 | #define OFFSET(x) offsetof(DelogoContext, x) |
| 157 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
| 158 | |
| 159 | static const AVOption delogo_options[]= { |
| 160 | { "x", "set logo x position", OFFSET(x), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, FLAGS }, |
| 161 | { "y", "set logo y position", OFFSET(y), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, FLAGS }, |
| 162 | { "w", "set logo width", OFFSET(w), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, FLAGS }, |
| 163 | { "h", "set logo height", OFFSET(h), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, FLAGS }, |
| 164 | { "band", "set delogo area band size", OFFSET(band), AV_OPT_TYPE_INT, { .i64 = 4 }, 1, INT_MAX, FLAGS }, |
| 165 | { "t", "set delogo area band size", OFFSET(band), AV_OPT_TYPE_INT, { .i64 = 4 }, 1, INT_MAX, FLAGS }, |
| 166 | { "show", "show delogo area", OFFSET(show), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, |
| 167 | { NULL } |
| 168 | }; |
| 169 | |
| 170 | AVFILTER_DEFINE_CLASS(delogo); |
| 171 | |
| 172 | static int query_formats(AVFilterContext *ctx) |
| 173 | { |
| 174 | static const enum AVPixelFormat pix_fmts[] = { |
| 175 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, |
| 176 | AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P, |
| 177 | AV_PIX_FMT_YUVA420P, AV_PIX_FMT_GRAY8, |
| 178 | AV_PIX_FMT_NONE |
| 179 | }; |
| 180 | |
| 181 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); |
| 182 | return 0; |
| 183 | } |
| 184 | |
| 185 | static av_cold int init(AVFilterContext *ctx) |
| 186 | { |
| 187 | DelogoContext *s = ctx->priv; |
| 188 | |
| 189 | #define CHECK_UNSET_OPT(opt) \ |
| 190 | if (s->opt == -1) { \ |
| 191 | av_log(s, AV_LOG_ERROR, "Option %s was not set.\n", #opt); \ |
| 192 | return AVERROR(EINVAL); \ |
| 193 | } |
| 194 | CHECK_UNSET_OPT(x); |
| 195 | CHECK_UNSET_OPT(y); |
| 196 | CHECK_UNSET_OPT(w); |
| 197 | CHECK_UNSET_OPT(h); |
| 198 | |
| 199 | av_log(ctx, AV_LOG_VERBOSE, "x:%d y:%d, w:%d h:%d band:%d show:%d\n", |
| 200 | s->x, s->y, s->w, s->h, s->band, s->show); |
| 201 | |
| 202 | s->w += s->band*2; |
| 203 | s->h += s->band*2; |
| 204 | s->x -= s->band; |
| 205 | s->y -= s->band; |
| 206 | |
| 207 | return 0; |
| 208 | } |
| 209 | |
| 210 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) |
| 211 | { |
| 212 | DelogoContext *s = inlink->dst->priv; |
| 213 | AVFilterLink *outlink = inlink->dst->outputs[0]; |
| 214 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
| 215 | AVFrame *out; |
| 216 | int hsub0 = desc->log2_chroma_w; |
| 217 | int vsub0 = desc->log2_chroma_h; |
| 218 | int direct = 0; |
| 219 | int plane; |
| 220 | AVRational sar; |
| 221 | |
| 222 | if (av_frame_is_writable(in)) { |
| 223 | direct = 1; |
| 224 | out = in; |
| 225 | } else { |
| 226 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| 227 | if (!out) { |
| 228 | av_frame_free(&in); |
| 229 | return AVERROR(ENOMEM); |
| 230 | } |
| 231 | |
| 232 | av_frame_copy_props(out, in); |
| 233 | } |
| 234 | |
| 235 | sar = in->sample_aspect_ratio; |
| 236 | /* Assume square pixels if SAR is unknown */ |
| 237 | if (!sar.num) |
| 238 | sar.num = sar.den = 1; |
| 239 | |
| 240 | for (plane = 0; plane < 4 && in->data[plane] && in->linesize[plane]; plane++) { |
| 241 | int hsub = plane == 1 || plane == 2 ? hsub0 : 0; |
| 242 | int vsub = plane == 1 || plane == 2 ? vsub0 : 0; |
| 243 | |
| 244 | apply_delogo(out->data[plane], out->linesize[plane], |
| 245 | in ->data[plane], in ->linesize[plane], |
| 246 | FF_CEIL_RSHIFT(inlink->w, hsub), |
| 247 | FF_CEIL_RSHIFT(inlink->h, vsub), |
| 248 | sar, s->x>>hsub, s->y>>vsub, |
| 249 | /* Up and left borders were rounded down, inject lost bits |
| 250 | * into width and height to avoid error accumulation */ |
| 251 | FF_CEIL_RSHIFT(s->w + (s->x & ((1<<hsub)-1)), hsub), |
| 252 | FF_CEIL_RSHIFT(s->h + (s->y & ((1<<vsub)-1)), vsub), |
| 253 | s->band>>FFMIN(hsub, vsub), |
| 254 | s->show, direct); |
| 255 | } |
| 256 | |
| 257 | if (!direct) |
| 258 | av_frame_free(&in); |
| 259 | |
| 260 | return ff_filter_frame(outlink, out); |
| 261 | } |
| 262 | |
| 263 | static const AVFilterPad avfilter_vf_delogo_inputs[] = { |
| 264 | { |
| 265 | .name = "default", |
| 266 | .type = AVMEDIA_TYPE_VIDEO, |
| 267 | .filter_frame = filter_frame, |
| 268 | }, |
| 269 | { NULL } |
| 270 | }; |
| 271 | |
| 272 | static const AVFilterPad avfilter_vf_delogo_outputs[] = { |
| 273 | { |
| 274 | .name = "default", |
| 275 | .type = AVMEDIA_TYPE_VIDEO, |
| 276 | }, |
| 277 | { NULL } |
| 278 | }; |
| 279 | |
| 280 | AVFilter ff_vf_delogo = { |
| 281 | .name = "delogo", |
| 282 | .description = NULL_IF_CONFIG_SMALL("Remove logo from input video."), |
| 283 | .priv_size = sizeof(DelogoContext), |
| 284 | .priv_class = &delogo_class, |
| 285 | .init = init, |
| 286 | .query_formats = query_formats, |
| 287 | .inputs = avfilter_vf_delogo_inputs, |
| 288 | .outputs = avfilter_vf_delogo_outputs, |
| 289 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
| 290 | }; |