]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_chromashift.c
avfilter: add chromashift filter
[ffmpeg] / libavfilter / vf_chromashift.c
1 /*
2  * Copyright (c) 2018 Paul B Mahol
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "libavutil/avstring.h"
22 #include "libavutil/eval.h"
23 #include "libavutil/imgutils.h"
24 #include "libavutil/intreadwrite.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/pixdesc.h"
27
28 #include "avfilter.h"
29 #include "formats.h"
30 #include "internal.h"
31 #include "framesync.h"
32 #include "video.h"
33
34 typedef struct ChromaShiftContext {
35     const AVClass *class;
36     int cbh, cbv;
37     int crh, crv;
38     int edge;
39
40     int depth;
41     int height[4];
42     int width[4];
43     int linesize[4];
44
45     AVFrame *in;
46
47     int (*filter_slice)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
48 } ChromaShiftContext;
49
50 static int query_formats(AVFilterContext *ctx)
51 {
52     static const enum AVPixelFormat pix_fmts[] = {
53         AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P,
54         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P,AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P,
55         AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
56         AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
57         AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV440P10,
58         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
59         AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
60         AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14,
61         AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
62         AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
63         AV_PIX_FMT_NONE
64     };
65
66     AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
67     if (!fmts_list)
68         return AVERROR(ENOMEM);
69     return ff_set_common_formats(ctx, fmts_list);
70 }
71
72 #define DEFINE_SMEAR(depth, type, div)                                                    \
73 static int smear_slice ## depth(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)  \
74 {                                                                                         \
75     ChromaShiftContext *s = ctx->priv;                                                    \
76     AVFrame *in = s->in;                                                                  \
77     AVFrame *out = arg;                                                                   \
78     const int sulinesize = in->linesize[1] / div;                                         \
79     const int svlinesize = in->linesize[2] / div;                                         \
80     const int ulinesize = out->linesize[1] / div;                                         \
81     const int vlinesize = out->linesize[2] / div;                                         \
82     const int cbh = s->cbh;                                                               \
83     const int cbv = s->cbv;                                                               \
84     const int crh = s->crh;                                                               \
85     const int crv = s->crv;                                                               \
86     const int h = s->height[1];                                                           \
87     const int w = s->width[1];                                                            \
88     const int slice_start = (h * jobnr) / nb_jobs;                                        \
89     const int slice_end = (h * (jobnr+1)) / nb_jobs;                                      \
90     const type *su = (const type *)in->data[1];                                           \
91     const type *sv = (const type *)in->data[2];                                           \
92     type *du = (type *)out->data[1] + slice_start * ulinesize;                            \
93     type *dv = (type *)out->data[2] + slice_start * vlinesize;                            \
94                                                                                           \
95     for (int y = slice_start; y < slice_end; y++) {                                       \
96         const int duy = av_clip(y - cbv, 0, h-1) * sulinesize;                            \
97         const int dvy = av_clip(y - crv, 0, h-1) * svlinesize;                            \
98                                                                                           \
99         for (int x = 0; x < w; x++) {                                                     \
100             du[x] = su[av_clip(x - cbh, 0, w - 1) + duy];                                 \
101             dv[x] = sv[av_clip(x - crh, 0, w - 1) + dvy];                                 \
102         }                                                                                 \
103                                                                                           \
104         du += ulinesize;                                                                  \
105         dv += vlinesize;                                                                  \
106     }                                                                                     \
107                                                                                           \
108     return 0;                                                                             \
109 }
110
111 DEFINE_SMEAR(8, uint8_t, 1)
112 DEFINE_SMEAR(16, uint16_t, 2)
113
114 #define DEFINE_WRAP(depth, type, div)                                                     \
115 static int wrap_slice ## depth(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)   \
116 {                                                                                         \
117     ChromaShiftContext *s = ctx->priv;                                                    \
118     AVFrame *in = s->in;                                                                  \
119     AVFrame *out = arg;                                                                   \
120     const int sulinesize = in->linesize[1] / div;                                         \
121     const int svlinesize = in->linesize[2] / div;                                         \
122     const int ulinesize = out->linesize[1] / div;                                         \
123     const int vlinesize = out->linesize[2] / div;                                         \
124     const int cbh = s->cbh;                                                               \
125     const int cbv = s->cbv;                                                               \
126     const int crh = s->crh;                                                               \
127     const int crv = s->crv;                                                               \
128     const int h = s->height[1];                                                           \
129     const int w = s->width[1];                                                            \
130     const int slice_start = (h * jobnr) / nb_jobs;                                        \
131     const int slice_end = (h * (jobnr+1)) / nb_jobs;                                      \
132     const type *su = (const type *)in->data[1];                                           \
133     const type *sv = (const type *)in->data[2];                                           \
134     type *du = (type *)out->data[1] + slice_start * ulinesize;                            \
135     type *dv = (type *)out->data[2] + slice_start * vlinesize;                            \
136                                                                                           \
137     for (int y = slice_start; y < slice_end; y++) {                                       \
138         int uy = (y - cbv) % h;                                                           \
139         int vy = (y - crv) % h;                                                           \
140                                                                                           \
141         if (uy < 0)                                                                       \
142             uy += h;                                                                      \
143         if (vy < 0)                                                                       \
144             vy += h;                                                                      \
145                                                                                           \
146         for (int x = 0; x < w; x++) {                                                     \
147             int ux = (x - cbh) % w;                                                       \
148             int vx = (x - crh) % w;                                                       \
149                                                                                           \
150             if (ux < 0)                                                                   \
151                 ux += w;                                                                  \
152             if (vx < 0)                                                                   \
153                 vx += w;                                                                  \
154                                                                                           \
155             du[x] = su[ux + uy * sulinesize];                                             \
156             dv[x] = sv[vx + vy * svlinesize];                                             \
157         }                                                                                 \
158                                                                                           \
159         du += ulinesize;                                                                  \
160         dv += vlinesize;                                                                  \
161     }                                                                                     \
162                                                                                           \
163     return 0;                                                                             \
164 }
165
166 DEFINE_WRAP(8, uint8_t, 1)
167 DEFINE_WRAP(16, uint16_t, 2)
168
169 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
170 {
171     AVFilterContext *ctx = inlink->dst;
172     AVFilterLink *outlink = ctx->outputs[0];
173     ChromaShiftContext *s = ctx->priv;
174     AVFrame *out;
175
176     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
177     if (!out) {
178         av_frame_free(&in);
179         return AVERROR(ENOMEM);
180     }
181     av_frame_copy_props(out, in);
182
183     s->in = in;
184     av_image_copy_plane(out->data[0] + out->linesize[0],
185                         out->linesize[0],
186                         in->data[0], in->linesize[0],
187                         s->linesize[0], s->height[0]);
188     ctx->internal->execute(ctx, s->filter_slice, out, NULL,
189                            FFMIN3(s->height[1],
190                                   s->height[2],
191                                   ff_filter_get_nb_threads(ctx)));
192     s->in = NULL;
193     av_frame_free(&in);
194     return ff_filter_frame(outlink, out);
195 }
196
197 static int config_input(AVFilterLink *inlink)
198 {
199     AVFilterContext *ctx = inlink->dst;
200     ChromaShiftContext *s = ctx->priv;
201     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
202
203     s->depth = desc->comp[0].depth;
204     if (s->edge)
205         s->filter_slice = s->depth > 8 ? wrap_slice16 : wrap_slice8;
206     else
207         s->filter_slice = s->depth > 8 ? smear_slice16 : smear_slice8;
208     s->height[1] = s->height[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
209     s->height[0] = s->height[3] = inlink->h;
210     s->width[1] = s->width[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
211     s->width[0] = s->width[3] = inlink->w;
212
213     return av_image_fill_linesizes(s->linesize, inlink->format, inlink->w);
214 }
215
216 #define OFFSET(x) offsetof(ChromaShiftContext, x)
217 #define VF AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
218
219 static const AVOption chromashift_options[] = {
220     { "cbh", "shift chroma-blue horizontally", OFFSET(cbh),  AV_OPT_TYPE_INT,   {.i64=0}, -255, 255, .flags = VF },
221     { "cbv", "shift chroma-blue vertically",   OFFSET(cbv),  AV_OPT_TYPE_INT,   {.i64=0}, -255, 255, .flags = VF },
222     { "crh", "shift chroma-red horizontally",  OFFSET(crh),  AV_OPT_TYPE_INT,   {.i64=0}, -255, 255, .flags = VF },
223     { "crv", "shift chroma-red vertically",    OFFSET(crv),  AV_OPT_TYPE_INT,   {.i64=0}, -255, 255, .flags = VF },
224     { "edge", "set edge operation",            OFFSET(edge), AV_OPT_TYPE_INT,   {.i64=0},    0,   1, .flags = VF, "edge" },
225     { "smear",                              0,            0, AV_OPT_TYPE_CONST, {.i64=0},    0,   0, .flags = VF, "edge" },
226     { "wrap",                               0,            0, AV_OPT_TYPE_CONST, {.i64=1},    0,   0, .flags = VF, "edge" },
227     { NULL },
228 };
229
230 static const AVFilterPad inputs[] = {
231     {
232         .name         = "default",
233         .type         = AVMEDIA_TYPE_VIDEO,
234         .filter_frame = filter_frame,
235         .config_props = config_input,
236     },
237     { NULL }
238 };
239
240 static const AVFilterPad outputs[] = {
241     {
242         .name = "default",
243         .type = AVMEDIA_TYPE_VIDEO,
244     },
245     { NULL }
246 };
247
248 AVFILTER_DEFINE_CLASS(chromashift);
249
250 AVFilter ff_vf_chromashift = {
251     .name          = "chromashift",
252     .description   = NULL_IF_CONFIG_SMALL("Shift chroma."),
253     .priv_size     = sizeof(ChromaShiftContext),
254     .priv_class    = &chromashift_class,
255     .query_formats = query_formats,
256     .outputs       = outputs,
257     .inputs        = inputs,
258     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
259 };