2 * Copyright (c) 2018 Paul B Mahol
4 * This file is part of FFmpeg.
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.
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.
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
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"
31 #include "framesync.h"
34 typedef struct ChromaShiftContext {
47 int (*filter_slice)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
50 static int query_formats(AVFilterContext *ctx)
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,
66 AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
68 return AVERROR(ENOMEM);
69 return ff_set_common_formats(ctx, fmts_list);
72 #define DEFINE_SMEAR(depth, type, div) \
73 static int smear_slice ## depth(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \
75 ChromaShiftContext *s = ctx->priv; \
76 AVFrame *in = s->in; \
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; \
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; \
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]; \
111 DEFINE_SMEAR(8, uint8_t, 1)
112 DEFINE_SMEAR(16, uint16_t, 2)
114 #define DEFINE_WRAP(depth, type, div) \
115 static int wrap_slice ## depth(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \
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; \
137 for (int y = slice_start; y < slice_end; y++) { \
138 int uy = (y - cbv) % h; \
139 int vy = (y - crv) % h; \
146 for (int x = 0; x < w; x++) { \
147 int ux = (x - cbh) % w; \
148 int vx = (x - crh) % w; \
155 du[x] = su[ux + uy * sulinesize]; \
156 dv[x] = sv[vx + vy * svlinesize]; \
166 DEFINE_WRAP(8, uint8_t, 1)
167 DEFINE_WRAP(16, uint16_t, 2)
169 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
171 AVFilterContext *ctx = inlink->dst;
172 AVFilterLink *outlink = ctx->outputs[0];
173 ChromaShiftContext *s = ctx->priv;
176 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
179 return AVERROR(ENOMEM);
181 av_frame_copy_props(out, in);
184 av_image_copy_plane(out->data[0] + 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,
191 ff_filter_get_nb_threads(ctx)));
194 return ff_filter_frame(outlink, out);
197 static int config_input(AVFilterLink *inlink)
199 AVFilterContext *ctx = inlink->dst;
200 ChromaShiftContext *s = ctx->priv;
201 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
203 s->depth = desc->comp[0].depth;
205 s->filter_slice = s->depth > 8 ? wrap_slice16 : wrap_slice8;
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;
213 return av_image_fill_linesizes(s->linesize, inlink->format, inlink->w);
216 #define OFFSET(x) offsetof(ChromaShiftContext, x)
217 #define VF AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
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" },
230 static const AVFilterPad inputs[] = {
233 .type = AVMEDIA_TYPE_VIDEO,
234 .filter_frame = filter_frame,
235 .config_props = config_input,
240 static const AVFilterPad outputs[] = {
243 .type = AVMEDIA_TYPE_VIDEO,
248 AVFILTER_DEFINE_CLASS(chromashift);
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,
258 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,