diff options
author | Paul B Mahol <onemda@gmail.com> | 2017-08-05 20:27:32 +0200 |
---|---|---|
committer | Paul B Mahol <onemda@gmail.com> | 2017-08-05 21:05:22 +0200 |
commit | 2cc56741b1f4e33c4fdb8234da31bdfc3c5c5e05 (patch) | |
tree | a331dd1964957e66a317b26a65d64290a6e9310d /libavfilter/vf_floodfill.c | |
parent | c47491041466043a260412504f4294a2b458ebdb (diff) | |
download | ffmpeg-2cc56741b1f4e33c4fdb8234da31bdfc3c5c5e05.tar.gz |
avfilter: add floodfill filter
Diffstat (limited to 'libavfilter/vf_floodfill.c')
-rw-r--r-- | libavfilter/vf_floodfill.c | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/libavfilter/vf_floodfill.c b/libavfilter/vf_floodfill.c new file mode 100644 index 0000000000..323dd0e2fa --- /dev/null +++ b/libavfilter/vf_floodfill.c @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2017 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/opt.h" +#include "libavutil/imgutils.h" +#include "libavutil/intreadwrite.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef struct Points { + uint16_t x, y; +} Points; + +typedef struct FloodfillContext { + const AVClass *class; + + int x, y; + int s0, s1, s2, s3; + int d0, d1, d2, d3; + + int back, front; + Points *points; + + int (*is_same)(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3); + void (*set_pixel)(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3); + void (*pick_pixel)(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3); +} FloodfillContext; + +static int is_inside(int x, int y, int w, int h) +{ + if (x >= 0 && x < w && y >= 0 && y < h) + return 1; + return 0; +} + +static int is_same4(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3) +{ + unsigned c0 = frame->data[0][y * frame->linesize[0] + x]; + unsigned c1 = frame->data[1][y * frame->linesize[1] + x]; + unsigned c2 = frame->data[2][y * frame->linesize[2] + x]; + unsigned c3 = frame->data[3][y * frame->linesize[3] + x]; + + if (s0 == c0 && s1 == c1 && s2 == c2 && s3 == c3) + return 1; + return 0; +} + +static int is_same4_16(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3) +{ + unsigned c0 = AV_RN16(frame->data[0] + y * frame->linesize[0] + 2 * x); + unsigned c1 = AV_RN16(frame->data[1] + y * frame->linesize[1] + 2 * x); + unsigned c2 = AV_RN16(frame->data[2] + y * frame->linesize[2] + 2 * x); + unsigned c3 = AV_RN16(frame->data[3] + y * frame->linesize[3] + 2 * x); + + if (s0 == c0 && s1 == c1 && s2 == c2 && s3 == c3) + return 1; + return 0; +} + +static int is_same3(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3) +{ + unsigned c0 = frame->data[0][y * frame->linesize[0] + x]; + unsigned c1 = frame->data[1][y * frame->linesize[1] + x]; + unsigned c2 = frame->data[2][y * frame->linesize[2] + x]; + + if (s0 == c0 && s1 == c1 && s2 == c2) + return 1; + return 0; +} + +static int is_same3_16(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3) +{ + unsigned c0 = AV_RN16(frame->data[0] + y * frame->linesize[0] + 2 * x); + unsigned c1 = AV_RN16(frame->data[1] + y * frame->linesize[1] + 2 * x); + unsigned c2 = AV_RN16(frame->data[2] + y * frame->linesize[2] + 2 * x); + + if (s0 == c0 && s1 == c1 && s2 == c2) + return 1; + return 0; +} + +static int is_same1(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3) +{ + unsigned c0 = frame->data[0][y * frame->linesize[0] + x]; + + if (s0 == c0) + return 1; + return 0; +} + +static int is_same1_16(AVFrame *frame, int x, int y, + unsigned s0, unsigned s1, unsigned s2, unsigned s3) +{ + unsigned c0 = AV_RN16(frame->data[0] + y * frame->linesize[0] + 2 * x); + + if (s0 == c0) + return 1; + return 0; +} + +static void set_pixel1(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3) +{ + frame->data[0][y * frame->linesize[0] + x] = d0; +} + +static void set_pixel1_16(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3) +{ + AV_WN16(frame->data[0] + y * frame->linesize[0] + 2 * x, d0); +} + +static void set_pixel3(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3) +{ + frame->data[0][y * frame->linesize[0] + x] = d0; + frame->data[1][y * frame->linesize[1] + x] = d1; + frame->data[2][y * frame->linesize[2] + x] = d2; +} + +static void set_pixel3_16(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3) +{ + AV_WN16(frame->data[0] + y * frame->linesize[0] + 2 * x, d0); + AV_WN16(frame->data[1] + y * frame->linesize[1] + 2 * x, d1); + AV_WN16(frame->data[2] + y * frame->linesize[2] + 2 * x, d2); +} + +static void set_pixel4(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3) +{ + frame->data[0][y * frame->linesize[0] + x] = d0; + frame->data[1][y * frame->linesize[1] + x] = d1; + frame->data[2][y * frame->linesize[2] + x] = d2; + frame->data[3][y * frame->linesize[3] + x] = d3; +} + +static void set_pixel4_16(AVFrame *frame, int x, int y, + unsigned d0, unsigned d1, unsigned d2, unsigned d3) +{ + AV_WN16(frame->data[0] + y * frame->linesize[0] + 2 * x, d0); + AV_WN16(frame->data[1] + y * frame->linesize[1] + 2 * x, d1); + AV_WN16(frame->data[2] + y * frame->linesize[2] + 2 * x, d2); + AV_WN16(frame->data[3] + y * frame->linesize[3] + 2 * x, d3); +} + +static void pick_pixel1(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3) +{ + if (*s0 < 0) + *s0 = frame->data[0][y * frame->linesize[0] + x]; +} + +static void pick_pixel1_16(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3) +{ + if (*s0 < 0) + *s0 = AV_RN16(frame->data[0] + y * frame->linesize[0] + 2 * x); +} + +static void pick_pixel3(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3) +{ + if (*s0 < 0) + *s0 = frame->data[0][y * frame->linesize[0] + x]; + if (*s1 < 0) + *s1 = frame->data[1][y * frame->linesize[1] + x]; + if (*s2 < 0) + *s2 = frame->data[2][y * frame->linesize[2] + x]; +} + +static void pick_pixel3_16(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3) +{ + if (*s0 < 0) + *s0 = AV_RN16(frame->data[0] + y * frame->linesize[0] + 2 * x); + if (*s1 < 0) + *s1 = AV_RN16(frame->data[1] + y * frame->linesize[1] + 2 * x); + if (*s2 < 0) + *s2 = AV_RN16(frame->data[2] + y * frame->linesize[2] + 2 * x); +} + +static void pick_pixel4(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3) +{ + if (*s0 < 0) + *s0 = frame->data[0][y * frame->linesize[0] + x]; + if (*s1 < 0) + *s1 = frame->data[1][y * frame->linesize[1] + x]; + if (*s2 < 0) + *s2 = frame->data[2][y * frame->linesize[2] + x]; + if (*s3 < 0) + *s3 = frame->data[3][y * frame->linesize[3] + x]; +} + +static void pick_pixel4_16(AVFrame *frame, int x, int y, + int *s0, int *s1, int *s2, int *s3) +{ + if (*s0 < 0) + *s0 = AV_RN16(frame->data[0] + y * frame->linesize[0] + 2 * x); + if (*s1 < 0) + *s1 = AV_RN16(frame->data[1] + y * frame->linesize[1] + 2 * x); + if (*s2 < 0) + *s2 = AV_RN16(frame->data[2] + y * frame->linesize[2] + 2 * x); + if (*s3 < 0) + *s3 = AV_RN16(frame->data[3] + y * frame->linesize[3] + 2 * x); +} + +static int config_input(AVFilterLink *inlink) +{ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + AVFilterContext *ctx = inlink->dst; + FloodfillContext *s = ctx->priv; + int nb_planes = av_pix_fmt_count_planes(inlink->format); + int depth; + + depth = desc->comp[0].depth; + if (depth == 8) { + switch (nb_planes) { + case 1: s->set_pixel = set_pixel1; + s->is_same = is_same1; + s->pick_pixel = pick_pixel1; break; + case 3: s->set_pixel = set_pixel3; + s->is_same = is_same3; + s->pick_pixel = pick_pixel3; break; + case 4: s->set_pixel = set_pixel4; + s->is_same = is_same4; + s->pick_pixel = pick_pixel4; break; + } + } else { + switch (nb_planes) { + case 1: s->set_pixel = set_pixel1_16; + s->is_same = is_same1_16; + s->pick_pixel = pick_pixel1_16; break; + case 3: s->set_pixel = set_pixel3_16; + s->is_same = is_same3_16; + s->pick_pixel = pick_pixel3_16; break; + case 4: s->set_pixel = set_pixel4_16; + s->is_same = is_same4_16; + s->pick_pixel = pick_pixel4_16; break; + } + } + + s->front = s->back = 0; + s->points = av_calloc(inlink->w * inlink->h, 4 * sizeof(Points)); + if (!s->points) + return AVERROR(ENOMEM); + + return 0; +} + +static int filter_frame(AVFilterLink *link, AVFrame *frame) +{ + AVFilterContext *ctx = link->dst; + FloodfillContext *s = ctx->priv; + const unsigned d0 = s->d0; + const unsigned d1 = s->d1; + const unsigned d2 = s->d2; + const unsigned d3 = s->d3; + int s0 = s->s0; + int s1 = s->s1; + int s2 = s->s2; + int s3 = s->s3; + const int w = frame->width; + const int h = frame->height; + int ret; + + if (ret = av_frame_make_writable(frame)) + return ret; + + if (is_inside(s->x, s->y, w, h)) { + s->pick_pixel(frame, s->x, s->y, &s0, &s1, &s2, &s3); + + if (s->is_same(frame, s->x, s->y, s0, s1, s2, s3)) { + s->points[s->front].x = s->x; + s->points[s->front].y = s->y; + s->front++; + } + + while (s->front > s->back) { + int x, y; + + s->front--; + x = s->points[s->front].x; + y = s->points[s->front].y; + + if (s->is_same(frame, x, y, s0, s1, s2, s3)) { + s->set_pixel(frame, x, y, d0, d1, d2, d3); + + if (is_inside(x + 1, y, w, h)) { + s->points[s->front] .x = x + 1; + s->points[s->front++].y = y; + } + + if (is_inside(x - 1, y, w, h)) { + s->points[s->front] .x = x - 1; + s->points[s->front++].y = y; + } + + if (is_inside(x, y + 1, w, h)) { + s->points[s->front] .x = x; + s->points[s->front++].y = y + 1; + } + + if (is_inside(x, y - 1, w, h)) { + s->points[s->front] .x = x; + s->points[s->front++].y = y - 1; + } + } + } + } + + return ff_filter_frame(ctx->outputs[0], frame); +} + +static av_cold int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pixel_fmts[] = { + AV_PIX_FMT_GRAY8, + AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUVA444P, + AV_PIX_FMT_GBRP, + AV_PIX_FMT_GBRP9, + AV_PIX_FMT_GBRP10, + AV_PIX_FMT_GBRAP10, + AV_PIX_FMT_GBRP12, + AV_PIX_FMT_GBRAP12, + AV_PIX_FMT_GBRP14, + AV_PIX_FMT_GBRP16, + AV_PIX_FMT_GBRAP16, + AV_PIX_FMT_GBRAP, + AV_PIX_FMT_YUV444P9, + AV_PIX_FMT_YUVA444P9, + AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUVA444P10, + AV_PIX_FMT_YUV444P12, + AV_PIX_FMT_YUV444P14, + AV_PIX_FMT_GRAY16, + AV_PIX_FMT_YUV444P16, + AV_PIX_FMT_YUVA444P16, + AV_PIX_FMT_NONE + }; + AVFilterFormats *formats; + + formats = ff_make_format_list(pixel_fmts); + if (!formats) + return AVERROR(ENOMEM); + + return ff_set_common_formats(ctx, formats); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + FloodfillContext *s = ctx->priv; + + av_freep(&s->points); +} + +static const AVFilterPad floodfill_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad floodfill_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +#define OFFSET(x) offsetof(FloodfillContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption floodfill_options[] = { + { "x", "set pixel x coordinate", OFFSET(x), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, + { "y", "set pixel y coordinate", OFFSET(y), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, + { "s0", "set source #0 component value", OFFSET(s0), AV_OPT_TYPE_INT, {.i64=0},-1, UINT16_MAX, FLAGS }, + { "s1", "set source #1 component value", OFFSET(s1), AV_OPT_TYPE_INT, {.i64=0},-1, UINT16_MAX, FLAGS }, + { "s2", "set source #2 component value", OFFSET(s2), AV_OPT_TYPE_INT, {.i64=0},-1, UINT16_MAX, FLAGS }, + { "s3", "set source #3 component value", OFFSET(s3), AV_OPT_TYPE_INT, {.i64=0},-1, UINT16_MAX, FLAGS }, + { "d0", "set destination #0 component value", OFFSET(d0), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, + { "d1", "set destination #1 component value", OFFSET(d1), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, + { "d2", "set destination #2 component value", OFFSET(d2), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, + { "d3", "set destination #3 component value", OFFSET(d3), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT16_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(floodfill); + +AVFilter ff_vf_floodfill = { + .name = "floodfill", + .description = NULL_IF_CONFIG_SMALL("Fill area with same color with another color."), + .priv_size = sizeof(FloodfillContext), + .priv_class = &floodfill_class, + .query_formats = query_formats, + .uninit = uninit, + .inputs = floodfill_inputs, + .outputs = floodfill_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, +}; |