vf_cropdetect.c 6.8 KB
Newer Older
1
/*
2
 * Copyright (c) 2002 A'rpi
3
 * This file is part of Libav.
4
 *
5
 * Libav is free software; you can redistribute it and/or modify
6 7 8 9
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
10
 * Libav is distributed in the hope that it will be useful,
11 12 13 14 15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
16
 * with Libav; if not, write to the Free Software Foundation, Inc.,
17 18 19 20 21 22 23 24 25
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file
 * border detection filter
 * Ported from MPlayer libmpcodecs/vf_cropdetect.c.
 */

26 27
#include <stdio.h>

28
#include "libavutil/imgutils.h"
29
#include "libavutil/internal.h"
30 31
#include "libavutil/opt.h"

32
#include "avfilter.h"
33
#include "formats.h"
34
#include "internal.h"
35
#include "video.h"
36

37
typedef struct CropDetectContext {
38
    const AVClass *class;
39 40 41 42 43 44 45 46 47 48
    int x1, y1, x2, y2;
    int limit;
    int round;
    int reset_count;
    int frame_nb;
    int max_pixsteps[4];
} CropDetectContext;

static int query_formats(AVFilterContext *ctx)
{
49 50 51 52 53 54 55
    static const enum AVPixelFormat pix_fmts[] = {
        AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUVJ420P,
        AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVJ422P,
        AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVJ444P,
        AV_PIX_FMT_YUV411P, AV_PIX_FMT_GRAY8,
        AV_PIX_FMT_NV12,    AV_PIX_FMT_NV21,
        AV_PIX_FMT_NONE
56 57
    };

58
    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    return 0;
}

static int checkline(void *ctx, const unsigned char *src, int stride, int len, int bpp)
{
    int total = 0;
    int div = len;

    switch (bpp) {
    case 1:
        while (--len >= 0) {
            total += src[0];
            src += stride;
        }
        break;
    case 3:
    case 4:
        while (--len >= 0) {
            total += src[0] + src[1] + src[2];
            src += stride;
        }
        div *= 3;
        break;
    }
    total /= div;

    av_log(ctx, AV_LOG_DEBUG, "total:%d\n", total);
    return total;
}

89
static av_cold int init(AVFilterContext *ctx)
90
{
91
    CropDetectContext *s = ctx->priv;
92

93
    s->frame_nb = -2;
94

95
    av_log(ctx, AV_LOG_VERBOSE, "limit:%d round:%d reset_count:%d\n",
96
           s->limit, s->round, s->reset_count);
97 98 99 100 101 102 103

    return 0;
}

static int config_input(AVFilterLink *inlink)
{
    AVFilterContext *ctx = inlink->dst;
104
    CropDetectContext *s = ctx->priv;
105

106
    av_image_fill_max_pixsteps(s->max_pixsteps, NULL,
107
                               av_pix_fmt_desc_get(inlink->format));
108

109 110 111 112
    s->x1 = inlink->w - 1;
    s->y1 = inlink->h - 1;
    s->x2 = 0;
    s->y2 = 0;
113 114 115 116

    return 0;
}

117
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
118 119
{
    AVFilterContext *ctx = inlink->dst;
120 121
    CropDetectContext *s = ctx->priv;
    int bpp = s->max_pixsteps[0];
122 123 124
    int w, h, x, y, shrink_by;

    // ignore first 2 frames - they may be empty
125
    if (++s->frame_nb > 0) {
126
        // Reset the crop area every reset_count frames, if reset_count is > 0
127 128 129 130 131 132
        if (s->reset_count > 0 && s->frame_nb > s->reset_count) {
            s->x1 = frame->width  - 1;
            s->y1 = frame->height - 1;
            s->x2 = 0;
            s->y2 = 0;
            s->frame_nb = 1;
133 134
        }

135 136 137
        for (y = 0; y < s->y1; y++) {
            if (checkline(ctx, frame->data[0] + frame->linesize[0] * y, bpp, frame->width, bpp) > s->limit) {
                s->y1 = y;
138 139 140 141
                break;
            }
        }

142 143 144
        for (y = frame->height - 1; y > s->y2; y--) {
            if (checkline(ctx, frame->data[0] + frame->linesize[0] * y, bpp, frame->width, bpp) > s->limit) {
                s->y2 = y;
145 146 147 148
                break;
            }
        }

149 150 151
        for (y = 0; y < s->x1; y++) {
            if (checkline(ctx, frame->data[0] + bpp*y, frame->linesize[0], frame->height, bpp) > s->limit) {
                s->x1 = y;
152 153 154 155
                break;
            }
        }

156 157 158
        for (y = frame->width - 1; y > s->x2; y--) {
            if (checkline(ctx, frame->data[0] + bpp*y, frame->linesize[0], frame->height, bpp) > s->limit) {
                s->x2 = y;
159 160 161 162 163 164
                break;
            }
        }

        // round x and y (up), important for yuv colorspaces
        // make sure they stay rounded!
165 166
        x = (s->x1+1) & ~1;
        y = (s->y1+1) & ~1;
167

168 169
        w = s->x2 - x + 1;
        h = s->y2 - y + 1;
170 171 172

        // w and h must be divisible by 2 as well because of yuv
        // colorspace problems.
173 174 175 176
        if (s->round <= 1)
            s->round = 16;
        if (s->round % 2)
            s->round *= 2;
177

178
        shrink_by = w % s->round;
179 180 181
        w -= shrink_by;
        x += (shrink_by/2 + 1) & ~1;

182
        shrink_by = h % s->round;
183 184 185 186
        h -= shrink_by;
        y += (shrink_by/2 + 1) & ~1;

        av_log(ctx, AV_LOG_INFO,
187
               "x1:%d x2:%d y1:%d y2:%d w:%d h:%d x:%d y:%d pts:%"PRId64" t:%f crop=%d:%d:%d:%d\n",
188
               s->x1, s->x2, s->y1, s->y2, w, h, x, y, frame->pts,
189
               frame->pts == AV_NOPTS_VALUE ? -1 : frame->pts * av_q2d(inlink->time_base),
190 191 192
               w, h, x, y);
    }

193
    return ff_filter_frame(inlink->dst->outputs[0], frame);
194 195
}

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
#define OFFSET(x) offsetof(CropDetectContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM
static const AVOption options[] = {
    { "limit", "Threshold below which the pixel is considered black", OFFSET(limit),       AV_OPT_TYPE_INT, { .i64 = 24 }, 0, INT_MAX, FLAGS },
    { "round", "Value by which the width/height should be divisible", OFFSET(round),       AV_OPT_TYPE_INT, { .i64 = 0  }, 0, INT_MAX, FLAGS },
    { "reset", "Recalculate the crop area after this many frames",    OFFSET(reset_count), AV_OPT_TYPE_INT, { .i64 = 0 },  0, INT_MAX, FLAGS },
    { NULL },
};

static const AVClass cropdetect_class = {
    .class_name = "cropdetect",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

212 213 214 215 216 217
static const AVFilterPad avfilter_vf_cropdetect_inputs[] = {
    {
        .name             = "default",
        .type             = AVMEDIA_TYPE_VIDEO,
        .config_props     = config_input,
        .get_video_buffer = ff_null_get_video_buffer,
218
        .filter_frame     = filter_frame,
219 220 221 222 223 224 225 226 227 228 229 230
    },
    { NULL }
};

static const AVFilterPad avfilter_vf_cropdetect_outputs[] = {
    {
        .name = "default",
        .type = AVMEDIA_TYPE_VIDEO
    },
    { NULL }
};

231
AVFilter ff_vf_cropdetect = {
232 233 234 235
    .name        = "cropdetect",
    .description = NULL_IF_CONFIG_SMALL("Auto-detect crop size."),

    .priv_size = sizeof(CropDetectContext),
236
    .priv_class = &cropdetect_class,
237 238 239 240
    .init      = init,

    .query_formats = query_formats,

241 242 243
    .inputs    = avfilter_vf_cropdetect_inputs,

    .outputs   = avfilter_vf_cropdetect_outputs,
244
};