Browse code

avfilter: add zscale filter

Signed-off-by: Paul B Mahol <onemda@gmail.com>

Paul B Mahol authored on 2015/09/21 22:34:15
Showing 7 changed files
... ...
@@ -21,6 +21,7 @@ version <next>:
21 21
 - extensive native AAC encoder improvements
22 22
 - ADPCM PSX decoder
23 23
 - genh, vag, ads & svag demuxer
24
+- zscale filter
24 25
 
25 26
 
26 27
 version 2.8:
... ...
@@ -269,6 +269,7 @@ External library support:
269 269
   --enable-libxcb-shape    enable X11 grabbing shape rendering [autodetect]
270 270
   --enable-libxvid         enable Xvid encoding via xvidcore,
271 271
                            native MPEG-4/Xvid encoder exists [no]
272
+  --enable-libzimg         enable z.lib, needed for zscale filter [no]
272 273
   --enable-libzmq          enable message passing via libzmq [no]
273 274
   --enable-libzvbi         enable teletext support via libzvbi [no]
274 275
   --disable-lzma           disable lzma [autodetect]
... ...
@@ -1456,6 +1457,7 @@ EXTERNAL_LIBRARY_LIST="
1456 1456
     libxcb_shape
1457 1457
     libxcb_xfixes
1458 1458
     libxvid
1459
+    libzimg
1459 1460
     libzmq
1460 1461
     libzvbi
1461 1462
     lzma
... ...
@@ -2843,6 +2845,7 @@ tinterlace_pad_test_deps="tinterlace_filter"
2843 2843
 uspp_filter_deps="gpl avcodec"
2844 2844
 zmq_filter_deps="libzmq"
2845 2845
 zoompan_filter_deps="swscale"
2846
+zscale_filter_deps="libzimg"
2846 2847
 
2847 2848
 # examples
2848 2849
 avio_reading="avformat avcodec avutil"
... ...
@@ -5423,6 +5426,7 @@ enabled libx265           && require_pkg_config x265 x265.h x265_api_get &&
5423 5423
                                die "ERROR: libx265 version must be >= 57."; }
5424 5424
 enabled libxavs           && require libxavs xavs.h xavs_encoder_encode -lxavs
5425 5425
 enabled libxvid           && require libxvid xvid.h xvid_global -lxvidcore
5426
+enabled libzimg           && require_pkg_config zimg zimg.h zimg_get_api_version
5426 5427
 enabled libzmq            && require_pkg_config libzmq zmq.h zmq_ctx_new
5427 5428
 enabled libzvbi           && require libzvbi libzvbi.h vbi_decoder_new -lzvbi
5428 5429
 enabled mmal              && { check_lib interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host ||
... ...
@@ -11856,6 +11856,177 @@ zoompan=z='min(zoom+0.0015,1.5)':d=700:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)'
11856 11856
 @end example
11857 11857
 @end itemize
11858 11858
 
11859
+@section zscale
11860
+Scale (resize) the input video, using the z.lib library:
11861
+https://github.com/sekrit-twc/zimg.
11862
+
11863
+The zscale filter forces the output display aspect ratio to be the same
11864
+as the input, by changing the output sample aspect ratio.
11865
+
11866
+If the input image format is different from the format requested by
11867
+the next filter, the zscale filter will convert the input to the
11868
+requested format.
11869
+
11870
+@subsection Options
11871
+The filter accepts the following options.
11872
+
11873
+@table @option
11874
+@item width, w
11875
+@item height, h
11876
+Set the output video dimension expression. Default value is the input
11877
+dimension.
11878
+
11879
+If the @var{width} or @var{w} is 0, the input width is used for the output.
11880
+If the @var{height} or @var{h} is 0, the input height is used for the output.
11881
+
11882
+If one of the values is -1, the zscale filter will use a value that
11883
+maintains the aspect ratio of the input image, calculated from the
11884
+other specified dimension. If both of them are -1, the input size is
11885
+used
11886
+
11887
+If one of the values is -n with n > 1, the zscale filter will also use a value
11888
+that maintains the aspect ratio of the input image, calculated from the other
11889
+specified dimension. After that it will, however, make sure that the calculated
11890
+dimension is divisible by n and adjust the value if necessary.
11891
+
11892
+See below for the list of accepted constants for use in the dimension
11893
+expression.
11894
+
11895
+@item size, s
11896
+Set the video size. For the syntax of this option, check the
11897
+@ref{video size syntax,,"Video size" section in the ffmpeg-utils manual,ffmpeg-utils}.
11898
+
11899
+@item dither, d
11900
+Set the dither type.
11901
+
11902
+Possible values are:
11903
+@table @var
11904
+@item none
11905
+@item ordered
11906
+@item random
11907
+@item error_diffusion
11908
+@end table
11909
+
11910
+Default is none.
11911
+
11912
+@item filter, f
11913
+Set the resize filter type.
11914
+
11915
+Possible values are:
11916
+@table @var
11917
+@item point
11918
+@item bilinear
11919
+@item bicubic
11920
+@item spline16
11921
+@item spline36
11922
+@item lanczos
11923
+@end table
11924
+
11925
+Default is bilinear.
11926
+
11927
+@item range, r
11928
+Set the color range.
11929
+
11930
+Possible values are:
11931
+@table @var
11932
+@item input
11933
+@item limited
11934
+@item full
11935
+@end table
11936
+
11937
+Default is same as input.
11938
+
11939
+@item primaries, p
11940
+Set the color primaries.
11941
+
11942
+Possible values are:
11943
+@table @var
11944
+@item input
11945
+@item 709
11946
+@item unspecified
11947
+@item 170m
11948
+@item 240m
11949
+@item 2020
11950
+@end table
11951
+
11952
+Default is same as input.
11953
+
11954
+@item transfer, t
11955
+Set the transfer characteristics.
11956
+
11957
+Possible values are:
11958
+@table @var
11959
+@item input
11960
+@item 709
11961
+@item unspecified
11962
+@item 601
11963
+@item linear
11964
+@item 2020_10
11965
+@item 2020_12
11966
+@end table
11967
+
11968
+Default is same as input.
11969
+
11970
+@item matrix, m
11971
+Set the colorspace matrix.
11972
+
11973
+Possible value are:
11974
+@table @var
11975
+@item input
11976
+@item 709
11977
+@item unspecified
11978
+@item 470bg
11979
+@item 170m
11980
+@item 2020_ncl
11981
+@item 2020_cl
11982
+@end table
11983
+
11984
+Default is same as input.
11985
+@end table
11986
+
11987
+The values of the @option{w} and @option{h} options are expressions
11988
+containing the following constants:
11989
+
11990
+@table @var
11991
+@item in_w
11992
+@item in_h
11993
+The input width and height
11994
+
11995
+@item iw
11996
+@item ih
11997
+These are the same as @var{in_w} and @var{in_h}.
11998
+
11999
+@item out_w
12000
+@item out_h
12001
+The output (scaled) width and height
12002
+
12003
+@item ow
12004
+@item oh
12005
+These are the same as @var{out_w} and @var{out_h}
12006
+
12007
+@item a
12008
+The same as @var{iw} / @var{ih}
12009
+
12010
+@item sar
12011
+input sample aspect ratio
12012
+
12013
+@item dar
12014
+The input display aspect ratio. Calculated from @code{(iw / ih) * sar}.
12015
+
12016
+@item hsub
12017
+@item vsub
12018
+horizontal and vertical input chroma subsample values. For example for the
12019
+pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1.
12020
+
12021
+@item ohsub
12022
+@item ovsub
12023
+horizontal and vertical output chroma subsample values. For example for the
12024
+pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1.
12025
+@end table
12026
+
12027
+@table @option
12028
+@end table
12029
+
11859 12030
 @c man end VIDEO FILTERS
11860 12031
 
11861 12032
 @chapter Video Sources
... ...
@@ -246,6 +246,7 @@ OBJS-$(CONFIG_XBR_FILTER)                    += vf_xbr.o
246 246
 OBJS-$(CONFIG_YADIF_FILTER)                  += vf_yadif.o
247 247
 OBJS-$(CONFIG_ZMQ_FILTER)                    += f_zmq.o
248 248
 OBJS-$(CONFIG_ZOOMPAN_FILTER)                += vf_zoompan.o
249
+OBJS-$(CONFIG_ZSCALE_FILTER)                 += vf_zscale.o
249 250
 
250 251
 OBJS-$(CONFIG_ALLRGB_FILTER)                 += vsrc_testsrc.o
251 252
 OBJS-$(CONFIG_ALLYUV_FILTER)                 += vsrc_testsrc.o
... ...
@@ -267,6 +267,7 @@ void avfilter_register_all(void)
267 267
     REGISTER_FILTER(YADIF,          yadif,          vf);
268 268
     REGISTER_FILTER(ZMQ,            zmq,            vf);
269 269
     REGISTER_FILTER(ZOOMPAN,        zoompan,        vf);
270
+    REGISTER_FILTER(ZSCALE,         zscale,         vf);
270 271
 
271 272
     REGISTER_FILTER(ALLRGB,         allrgb,         vsrc);
272 273
     REGISTER_FILTER(ALLYUV,         allyuv,         vsrc);
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR   6
33
-#define LIBAVFILTER_VERSION_MINOR  11
33
+#define LIBAVFILTER_VERSION_MINOR  12
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
37 37
new file mode 100644
... ...
@@ -0,0 +1,736 @@
0
+/*
1
+ * Copyright (c) 2015 Paul B Mahol
2
+ *
3
+ * This file is part of FFmpeg.
4
+ *
5
+ * FFmpeg is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ *
10
+ * FFmpeg is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
+ * Lesser General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Lesser General Public
16
+ * License along with FFmpeg; if not, write to the Free Software
17
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ */
19
+
20
+/**
21
+ * @file
22
+ * zscale video filter using z.lib library
23
+ */
24
+
25
+#include <stdio.h>
26
+#include <string.h>
27
+
28
+#include <zimg.h>
29
+
30
+#include "avfilter.h"
31
+#include "formats.h"
32
+#include "internal.h"
33
+#include "video.h"
34
+#include "libavutil/avstring.h"
35
+#include "libavutil/eval.h"
36
+#include "libavutil/internal.h"
37
+#include "libavutil/mathematics.h"
38
+#include "libavutil/opt.h"
39
+#include "libavutil/parseutils.h"
40
+#include "libavutil/pixdesc.h"
41
+#include "libavutil/imgutils.h"
42
+#include "libavutil/avassert.h"
43
+
44
+static const char *const var_names[] = {
45
+    "in_w",   "iw",
46
+    "in_h",   "ih",
47
+    "out_w",  "ow",
48
+    "out_h",  "oh",
49
+    "a",
50
+    "sar",
51
+    "dar",
52
+    "hsub",
53
+    "vsub",
54
+    "ohsub",
55
+    "ovsub",
56
+    NULL
57
+};
58
+
59
+enum var_name {
60
+    VAR_IN_W,   VAR_IW,
61
+    VAR_IN_H,   VAR_IH,
62
+    VAR_OUT_W,  VAR_OW,
63
+    VAR_OUT_H,  VAR_OH,
64
+    VAR_A,
65
+    VAR_SAR,
66
+    VAR_DAR,
67
+    VAR_HSUB,
68
+    VAR_VSUB,
69
+    VAR_OHSUB,
70
+    VAR_OVSUB,
71
+    VARS_NB
72
+};
73
+
74
+typedef struct ZScaleContext {
75
+    const AVClass *class;
76
+
77
+    /**
78
+     * New dimensions. Special values are:
79
+     *   0 = original width/height
80
+     *  -1 = keep original aspect
81
+     *  -N = try to keep aspect but make sure it is divisible by N
82
+     */
83
+    int w, h;
84
+    int dither;
85
+    int filter;
86
+    int colorspace;
87
+    int trc;
88
+    int primaries;
89
+    int range;
90
+    char *size_str;
91
+
92
+    char *w_expr;               ///< width  expression string
93
+    char *h_expr;               ///< height expression string
94
+
95
+    int out_h_chr_pos;
96
+    int out_v_chr_pos;
97
+    int in_h_chr_pos;
98
+    int in_v_chr_pos;
99
+
100
+    int force_original_aspect_ratio;
101
+
102
+    void *tmp;
103
+    size_t tmp_size;
104
+
105
+    zimg_image_format src_format, dst_format;
106
+    zimg_image_format alpha_src_format, alpha_dst_format;
107
+    zimg_graph_builder_params alpha_params, params;
108
+    zimg_filter_graph *alpha_graph, *graph;
109
+
110
+    enum AVColorSpace in_colorspace, out_colorspace;
111
+    enum AVColorTransferCharacteristic in_trc, out_trc;
112
+    enum AVColorPrimaries in_primaries, out_primaries;
113
+    enum AVColorRange in_range, out_range;
114
+} ZScaleContext;
115
+
116
+static av_cold int init_dict(AVFilterContext *ctx, AVDictionary **opts)
117
+{
118
+    ZScaleContext *s = ctx->priv;
119
+    int ret;
120
+
121
+    if (s->size_str && (s->w_expr || s->h_expr)) {
122
+        av_log(ctx, AV_LOG_ERROR,
123
+               "Size and width/height expressions cannot be set at the same time.\n");
124
+            return AVERROR(EINVAL);
125
+    }
126
+
127
+    if (s->w_expr && !s->h_expr)
128
+        FFSWAP(char *, s->w_expr, s->size_str);
129
+
130
+    if (s->size_str) {
131
+        char buf[32];
132
+        if ((ret = av_parse_video_size(&s->w, &s->h, s->size_str)) < 0) {
133
+            av_log(ctx, AV_LOG_ERROR,
134
+                   "Invalid size '%s'\n", s->size_str);
135
+            return ret;
136
+        }
137
+        snprintf(buf, sizeof(buf)-1, "%d", s->w);
138
+        av_opt_set(s, "w", buf, 0);
139
+        snprintf(buf, sizeof(buf)-1, "%d", s->h);
140
+        av_opt_set(s, "h", buf, 0);
141
+    }
142
+    if (!s->w_expr)
143
+        av_opt_set(s, "w", "iw", 0);
144
+    if (!s->h_expr)
145
+        av_opt_set(s, "h", "ih", 0);
146
+
147
+    return 0;
148
+}
149
+
150
+static int query_formats(AVFilterContext *ctx)
151
+{
152
+    static const enum AVPixelFormat pixel_fmts[] = {
153
+        AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
154
+        AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
155
+        AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P,
156
+        AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P,
157
+        AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P,
158
+        AV_PIX_FMT_YUVJ411P,
159
+        AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
160
+        AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
161
+        AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12,
162
+        AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
163
+        AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
164
+        AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P,
165
+        AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
166
+        AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
167
+        AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
168
+        AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
169
+        AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
170
+        AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP16,
171
+        AV_PIX_FMT_NONE
172
+    };
173
+    int ret;
174
+
175
+    ret = ff_formats_ref(ff_make_format_list(pixel_fmts), &ctx->inputs[0]->out_formats);
176
+    if (ret < 0)
177
+        return ret;
178
+    return ff_formats_ref(ff_make_format_list(pixel_fmts), &ctx->outputs[0]->in_formats);
179
+}
180
+
181
+static int config_props(AVFilterLink *outlink)
182
+{
183
+    AVFilterContext *ctx = outlink->src;
184
+    AVFilterLink *inlink = outlink->src->inputs[0];
185
+    ZScaleContext *s = ctx->priv;
186
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
187
+    const AVPixFmtDescriptor *out_desc = av_pix_fmt_desc_get(outlink->format);
188
+    int64_t w, h;
189
+    double var_values[VARS_NB], res;
190
+    char *expr;
191
+    int ret;
192
+    int factor_w, factor_h;
193
+
194
+    var_values[VAR_IN_W]  = var_values[VAR_IW] = inlink->w;
195
+    var_values[VAR_IN_H]  = var_values[VAR_IH] = inlink->h;
196
+    var_values[VAR_OUT_W] = var_values[VAR_OW] = NAN;
197
+    var_values[VAR_OUT_H] = var_values[VAR_OH] = NAN;
198
+    var_values[VAR_A]     = (double) inlink->w / inlink->h;
199
+    var_values[VAR_SAR]   = inlink->sample_aspect_ratio.num ?
200
+        (double) inlink->sample_aspect_ratio.num / inlink->sample_aspect_ratio.den : 1;
201
+    var_values[VAR_DAR]   = var_values[VAR_A] * var_values[VAR_SAR];
202
+    var_values[VAR_HSUB]  = 1 << desc->log2_chroma_w;
203
+    var_values[VAR_VSUB]  = 1 << desc->log2_chroma_h;
204
+    var_values[VAR_OHSUB] = 1 << out_desc->log2_chroma_w;
205
+    var_values[VAR_OVSUB] = 1 << out_desc->log2_chroma_h;
206
+
207
+    /* evaluate width and height */
208
+    av_expr_parse_and_eval(&res, (expr = s->w_expr),
209
+                           var_names, var_values,
210
+                           NULL, NULL, NULL, NULL, NULL, 0, ctx);
211
+    s->w = var_values[VAR_OUT_W] = var_values[VAR_OW] = res;
212
+    if ((ret = av_expr_parse_and_eval(&res, (expr = s->h_expr),
213
+                                      var_names, var_values,
214
+                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
215
+        goto fail;
216
+    s->h = var_values[VAR_OUT_H] = var_values[VAR_OH] = res;
217
+    /* evaluate again the width, as it may depend on the output height */
218
+    if ((ret = av_expr_parse_and_eval(&res, (expr = s->w_expr),
219
+                                      var_names, var_values,
220
+                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
221
+        goto fail;
222
+    s->w = res;
223
+
224
+    w = s->w;
225
+    h = s->h;
226
+
227
+    /* Check if it is requested that the result has to be divisible by a some
228
+     * factor (w or h = -n with n being the factor). */
229
+    factor_w = 1;
230
+    factor_h = 1;
231
+    if (w < -1) {
232
+        factor_w = -w;
233
+    }
234
+    if (h < -1) {
235
+        factor_h = -h;
236
+    }
237
+
238
+    if (w < 0 && h < 0)
239
+        s->w = s->h = 0;
240
+
241
+    if (!(w = s->w))
242
+        w = inlink->w;
243
+    if (!(h = s->h))
244
+        h = inlink->h;
245
+
246
+    /* Make sure that the result is divisible by the factor we determined
247
+     * earlier. If no factor was set, it is nothing will happen as the default
248
+     * factor is 1 */
249
+    if (w < 0)
250
+        w = av_rescale(h, inlink->w, inlink->h * factor_w) * factor_w;
251
+    if (h < 0)
252
+        h = av_rescale(w, inlink->h, inlink->w * factor_h) * factor_h;
253
+
254
+    /* Note that force_original_aspect_ratio may overwrite the previous set
255
+     * dimensions so that it is not divisible by the set factors anymore. */
256
+    if (s->force_original_aspect_ratio) {
257
+        int tmp_w = av_rescale(h, inlink->w, inlink->h);
258
+        int tmp_h = av_rescale(w, inlink->h, inlink->w);
259
+
260
+        if (s->force_original_aspect_ratio == 1) {
261
+             w = FFMIN(tmp_w, w);
262
+             h = FFMIN(tmp_h, h);
263
+        } else {
264
+             w = FFMAX(tmp_w, w);
265
+             h = FFMAX(tmp_h, h);
266
+        }
267
+    }
268
+
269
+    if (w > INT_MAX || h > INT_MAX ||
270
+        (h * inlink->w) > INT_MAX  ||
271
+        (w * inlink->h) > INT_MAX)
272
+        av_log(ctx, AV_LOG_ERROR, "Rescaled value for width or height is too big.\n");
273
+
274
+    outlink->w = w;
275
+    outlink->h = h;
276
+
277
+    if (inlink->w == outlink->w &&
278
+        inlink->h == outlink->h &&
279
+        inlink->format == outlink->format)
280
+        ;
281
+    else {
282
+    }
283
+
284
+    if (inlink->sample_aspect_ratio.num){
285
+        outlink->sample_aspect_ratio = av_mul_q((AVRational){outlink->h * inlink->w, outlink->w * inlink->h}, inlink->sample_aspect_ratio);
286
+    } else
287
+        outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
288
+
289
+    av_log(ctx, AV_LOG_VERBOSE, "w:%d h:%d fmt:%s sar:%d/%d -> w:%d h:%d fmt:%s sar:%d/%d\n",
290
+           inlink ->w, inlink ->h, av_get_pix_fmt_name( inlink->format),
291
+           inlink->sample_aspect_ratio.num, inlink->sample_aspect_ratio.den,
292
+           outlink->w, outlink->h, av_get_pix_fmt_name(outlink->format),
293
+           outlink->sample_aspect_ratio.num, outlink->sample_aspect_ratio.den);
294
+    return 0;
295
+
296
+fail:
297
+    av_log(ctx, AV_LOG_ERROR,
298
+           "Error when evaluating the expression '%s'.\n"
299
+           "Maybe the expression for out_w:'%s' or for out_h:'%s' is self-referencing.\n",
300
+           expr, s->w_expr, s->h_expr);
301
+    return ret;
302
+}
303
+
304
+static int print_zimg_error(AVFilterContext *ctx)
305
+{
306
+    char err_msg[1024];
307
+    int err_code = zimg_get_last_error(err_msg, sizeof(err_msg));
308
+
309
+    av_log(ctx, AV_LOG_ERROR, "code %d: %s\n", err_code, err_msg);
310
+
311
+    return err_code;
312
+}
313
+
314
+static int convert_matrix(enum AVColorSpace colorspace)
315
+{
316
+    switch (colorspace) {
317
+    case AVCOL_SPC_RGB:
318
+        return ZIMG_MATRIX_RGB;
319
+    case AVCOL_SPC_BT709:
320
+        return ZIMG_MATRIX_709;
321
+    case AVCOL_SPC_UNSPECIFIED:
322
+        return ZIMG_MATRIX_UNSPECIFIED;
323
+    case AVCOL_SPC_BT470BG:
324
+        return ZIMG_MATRIX_470BG;
325
+    case AVCOL_SPC_SMPTE170M:
326
+        return ZIMG_MATRIX_170M;
327
+    case AVCOL_SPC_YCGCO:
328
+        return ZIMG_MATRIX_YCGCO;
329
+    case AVCOL_SPC_BT2020_NCL:
330
+        return ZIMG_MATRIX_2020_NCL;
331
+    case AVCOL_SPC_BT2020_CL:
332
+        return ZIMG_MATRIX_2020_CL;
333
+    }
334
+    return ZIMG_MATRIX_UNSPECIFIED;
335
+}
336
+
337
+static int convert_trc(enum AVColorTransferCharacteristic color_trc)
338
+{
339
+    switch (color_trc) {
340
+    case AVCOL_TRC_UNSPECIFIED:
341
+        return ZIMG_TRANSFER_UNSPECIFIED;
342
+    case AVCOL_TRC_BT709:
343
+        return ZIMG_TRANSFER_709;
344
+    case AVCOL_TRC_SMPTE170M:
345
+        return ZIMG_TRANSFER_601;
346
+    case AVCOL_TRC_LINEAR:
347
+        return ZIMG_TRANSFER_LINEAR;
348
+    case AVCOL_TRC_BT2020_10:
349
+        return ZIMG_TRANSFER_2020_10;
350
+    case AVCOL_TRC_BT2020_12:
351
+        return ZIMG_TRANSFER_2020_12;
352
+    }
353
+    return ZIMG_TRANSFER_UNSPECIFIED;
354
+}
355
+
356
+static int convert_primaries(enum AVColorPrimaries color_primaries)
357
+{
358
+    switch (color_primaries) {
359
+    case AVCOL_PRI_UNSPECIFIED:
360
+        return ZIMG_PRIMARIES_UNSPECIFIED;
361
+    case AVCOL_PRI_BT709:
362
+        return ZIMG_PRIMARIES_709;
363
+    case AVCOL_PRI_SMPTE170M:
364
+        return ZIMG_PRIMARIES_170M;
365
+    case AVCOL_PRI_SMPTE240M:
366
+        return ZIMG_PRIMARIES_240M;
367
+    case AVCOL_PRI_BT2020:
368
+        return ZIMG_PRIMARIES_2020;
369
+    }
370
+    return ZIMG_PRIMARIES_UNSPECIFIED;
371
+}
372
+
373
+static int convert_range(enum AVColorRange color_range)
374
+{
375
+    switch (color_range) {
376
+    case AVCOL_RANGE_UNSPECIFIED:
377
+    case AVCOL_RANGE_MPEG:
378
+        return ZIMG_RANGE_LIMITED;
379
+    case AVCOL_RANGE_JPEG:
380
+        return ZIMG_RANGE_FULL;
381
+    }
382
+    return ZIMG_RANGE_LIMITED;
383
+}
384
+
385
+static int filter_frame(AVFilterLink *link, AVFrame *in)
386
+{
387
+    ZScaleContext *s = link->dst->priv;
388
+    AVFilterLink *outlink = link->dst->outputs[0];
389
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
390
+    const AVPixFmtDescriptor *odesc = av_pix_fmt_desc_get(outlink->format);
391
+    zimg_image_buffer_const src_buf = { ZIMG_API_VERSION };
392
+    zimg_image_buffer dst_buf = { ZIMG_API_VERSION };
393
+    char buf[32];
394
+    size_t tmp_size;
395
+    int ret = 0, plane;
396
+    AVFrame *out;
397
+
398
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
399
+    if (!out) {
400
+        av_frame_free(&in);
401
+        return AVERROR(ENOMEM);
402
+    }
403
+
404
+    av_frame_copy_props(out, in);
405
+    out->width  = outlink->w;
406
+    out->height = outlink->h;
407
+
408
+    if(   in->width  != link->w
409
+       || in->height != link->h
410
+       || in->format != link->format
411
+       || s->in_colorspace != in->colorspace
412
+       || s->in_trc  != in->color_trc
413
+       || s->in_primaries != in->color_primaries
414
+       || s->in_range != in->color_range
415
+       || s->out_colorspace != out->colorspace
416
+       || s->out_trc  != out->color_trc
417
+       || s->out_primaries != out->color_primaries
418
+       || s->out_range != out->color_range) {
419
+        snprintf(buf, sizeof(buf)-1, "%d", outlink->w);
420
+        av_opt_set(s, "w", buf, 0);
421
+        snprintf(buf, sizeof(buf)-1, "%d", outlink->h);
422
+        av_opt_set(s, "h", buf, 0);
423
+
424
+        link->dst->inputs[0]->format = in->format;
425
+        link->dst->inputs[0]->w      = in->width;
426
+        link->dst->inputs[0]->h      = in->height;
427
+
428
+        if ((ret = config_props(outlink)) < 0) {
429
+            av_frame_free(&in);
430
+            av_frame_free(&out);
431
+            return ret;
432
+        }
433
+
434
+        zimg_image_format_default(&s->src_format, ZIMG_API_VERSION);
435
+        zimg_image_format_default(&s->dst_format, ZIMG_API_VERSION);
436
+        zimg_graph_builder_params_default(&s->params, ZIMG_API_VERSION);
437
+
438
+        s->params.dither_type = s->dither;
439
+        s->params.cpu_type = ZIMG_CPU_AUTO;
440
+        s->params.resample_filter = s->filter;
441
+        s->params.resample_filter_uv = s->filter;
442
+
443
+        s->src_format.width = in->width;
444
+        s->src_format.height = in->height;
445
+        s->src_format.subsample_w = desc->log2_chroma_w;
446
+        s->src_format.subsample_h = desc->log2_chroma_h;
447
+        s->src_format.depth = desc->comp[0].depth;
448
+        s->src_format.pixel_type = desc->comp[0].depth > 8 ? ZIMG_PIXEL_WORD : ZIMG_PIXEL_BYTE;
449
+        s->src_format.color_family = (desc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_COLOR_RGB : ZIMG_COLOR_YUV;
450
+        s->src_format.matrix_coefficients = (desc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_MATRIX_RGB : convert_matrix(in->colorspace);
451
+        s->src_format.transfer_characteristics = (desc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_TRANSFER_UNSPECIFIED : convert_trc(in->color_trc);
452
+        s->src_format.color_primaries = (desc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_PRIMARIES_UNSPECIFIED : convert_primaries(in->color_primaries);
453
+        s->src_format.pixel_range = (desc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_RANGE_FULL : convert_range(in->color_range);
454
+
455
+        s->dst_format.width = out->width;
456
+        s->dst_format.height = out->height;
457
+        s->dst_format.subsample_w = odesc->log2_chroma_w;
458
+        s->dst_format.subsample_h = odesc->log2_chroma_h;
459
+        s->dst_format.depth = odesc->comp[0].depth;
460
+        s->dst_format.pixel_type = odesc->comp[0].depth > 8 ? ZIMG_PIXEL_WORD : ZIMG_PIXEL_BYTE;
461
+        s->dst_format.color_family = (odesc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_COLOR_RGB : ZIMG_COLOR_YUV;
462
+        s->dst_format.matrix_coefficients = (odesc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_MATRIX_RGB : s->colorspace == -1 ? convert_matrix(out->colorspace) : s->colorspace;
463
+        s->dst_format.transfer_characteristics = (odesc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_TRANSFER_UNSPECIFIED : s->trc == -1 ? convert_trc(out->color_trc) : s->trc;
464
+        s->dst_format.color_primaries = (odesc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_PRIMARIES_UNSPECIFIED : s->primaries == -1 ? convert_primaries(out->color_primaries) : s->primaries;
465
+        s->dst_format.pixel_range = (odesc->flags & AV_PIX_FMT_FLAG_RGB) ? ZIMG_RANGE_FULL : s->range == -1 ? convert_range(out->color_range) : s->range;
466
+
467
+        if (s->colorspace != -1)
468
+            out->colorspace = (int)s->dst_format.matrix_coefficients;
469
+
470
+        if (s->primaries != -1)
471
+            out->color_primaries = (int)s->dst_format.color_primaries;
472
+
473
+        if (s->range != -1)
474
+            out->color_range = (int)s->dst_format.pixel_range + 1;
475
+
476
+        if (s->trc != -1)
477
+            out->color_trc = (int)s->dst_format.transfer_characteristics;
478
+
479
+        zimg_filter_graph_free(s->graph);
480
+        s->graph = zimg_filter_graph_build(&s->src_format, &s->dst_format, &s->params);
481
+        if (!s->graph) {
482
+            ret = print_zimg_error(link->dst);
483
+            goto fail;
484
+        }
485
+
486
+        if ((ret = zimg_filter_graph_get_tmp_size(s->graph, &tmp_size))) {
487
+            ret = print_zimg_error(link->dst);
488
+            goto fail;
489
+        }
490
+
491
+        if (tmp_size > s->tmp_size) {
492
+            av_freep(&s->tmp);
493
+            s->tmp = av_malloc(tmp_size);
494
+            if (!s->tmp) {
495
+                ret = AVERROR(ENOMEM);
496
+                goto fail;
497
+            }
498
+            s->tmp_size = tmp_size;
499
+        }
500
+
501
+        s->in_colorspace  = in->colorspace;
502
+        s->in_trc         = in->color_trc;
503
+        s->in_primaries   = in->color_primaries;
504
+        s->in_range       = in->color_range;
505
+        s->out_colorspace = out->colorspace;
506
+        s->out_trc        = out->color_trc;
507
+        s->out_primaries  = out->color_primaries;
508
+        s->out_range      = out->color_range;
509
+
510
+        if (desc->flags & AV_PIX_FMT_FLAG_ALPHA && odesc->flags & AV_PIX_FMT_FLAG_ALPHA) {
511
+            zimg_image_format_default(&s->alpha_src_format, ZIMG_API_VERSION);
512
+            zimg_image_format_default(&s->alpha_dst_format, ZIMG_API_VERSION);
513
+            zimg_graph_builder_params_default(&s->alpha_params, ZIMG_API_VERSION);
514
+
515
+            s->alpha_params.dither_type = s->dither;
516
+            s->alpha_params.cpu_type = ZIMG_CPU_AUTO;
517
+            s->alpha_params.resample_filter = s->filter;
518
+
519
+            s->alpha_src_format.width = in->width;
520
+            s->alpha_src_format.height = in->height;
521
+            s->alpha_src_format.depth = desc->comp[0].depth;
522
+            s->alpha_src_format.pixel_type = desc->comp[0].depth > 8 ? ZIMG_PIXEL_WORD : ZIMG_PIXEL_BYTE;
523
+            s->alpha_src_format.color_family = ZIMG_COLOR_GREY;
524
+
525
+            s->alpha_dst_format.width = out->width;
526
+            s->alpha_dst_format.height = out->height;
527
+            s->alpha_dst_format.depth = odesc->comp[0].depth;
528
+            s->alpha_dst_format.pixel_type = odesc->comp[0].depth > 8 ? ZIMG_PIXEL_WORD : ZIMG_PIXEL_BYTE;
529
+            s->alpha_dst_format.color_family = ZIMG_COLOR_GREY;
530
+
531
+            zimg_filter_graph_free(s->alpha_graph);
532
+            s->alpha_graph = zimg_filter_graph_build(&s->alpha_src_format, &s->alpha_dst_format, &s->alpha_params);
533
+            if (!s->alpha_graph) {
534
+                ret = print_zimg_error(link->dst);
535
+                goto fail;
536
+            }
537
+        }
538
+    }
539
+
540
+    if (s->colorspace != -1)
541
+        out->colorspace = (int)s->dst_format.matrix_coefficients;
542
+
543
+    if (s->primaries != -1)
544
+        out->color_primaries = (int)s->dst_format.color_primaries;
545
+
546
+    if (s->range != -1)
547
+        out->color_range = (int)s->dst_format.pixel_range;
548
+
549
+    if (s->trc != -1)
550
+        out->color_trc = (int)s->dst_format.transfer_characteristics;
551
+
552
+    av_reduce(&out->sample_aspect_ratio.num, &out->sample_aspect_ratio.den,
553
+              (int64_t)in->sample_aspect_ratio.num * outlink->h * link->w,
554
+              (int64_t)in->sample_aspect_ratio.den * outlink->w * link->h,
555
+              INT_MAX);
556
+
557
+    for (plane = 0; plane < 3; plane++) {
558
+        int p = desc->comp[plane].plane;
559
+        src_buf.plane[plane].data   = in->data[p];
560
+        src_buf.plane[plane].stride = in->linesize[p];
561
+        src_buf.plane[plane].mask   = -1;
562
+
563
+        p = odesc->comp[plane].plane;
564
+        dst_buf.plane[plane].data   = out->data[p];
565
+        dst_buf.plane[plane].stride = out->linesize[p];
566
+        dst_buf.plane[plane].mask   = -1;
567
+    }
568
+
569
+    ret = zimg_filter_graph_process(s->graph, &src_buf, &dst_buf, s->tmp, 0, 0, 0, 0);
570
+    if (ret) {
571
+        print_zimg_error(link->dst);
572
+        goto fail;
573
+    }
574
+
575
+    if (desc->flags & AV_PIX_FMT_FLAG_ALPHA && odesc->flags & AV_PIX_FMT_FLAG_ALPHA) {
576
+        src_buf.plane[0].data   = in->data[3];
577
+        src_buf.plane[0].stride = in->linesize[3];
578
+        src_buf.plane[0].mask   = -1;
579
+
580
+        dst_buf.plane[0].data   = out->data[3];
581
+        dst_buf.plane[0].stride = out->linesize[3];
582
+        dst_buf.plane[0].mask   = -1;
583
+
584
+        ret = zimg_filter_graph_process(s->alpha_graph, &src_buf, &dst_buf, s->tmp, 0, 0, 0, 0);
585
+        if (ret) {
586
+            print_zimg_error(link->dst);
587
+            goto fail;
588
+        }
589
+    } else if (odesc->flags & AV_PIX_FMT_FLAG_ALPHA) {
590
+        int y;
591
+
592
+        for (y = 0; y < outlink->h; y++)
593
+            memset(out->data[3] + y * out->linesize[3], 0xff, outlink->w);
594
+    }
595
+
596
+fail:
597
+    av_frame_free(&in);
598
+    if (ret) {
599
+        av_frame_free(&out);
600
+        return ret;
601
+    }
602
+
603
+    return ff_filter_frame(outlink, out);
604
+}
605
+
606
+static void uninit(AVFilterContext *ctx)
607
+{
608
+    ZScaleContext *s = ctx->priv;
609
+
610
+    zimg_filter_graph_free(s->graph);
611
+    av_freep(&s->tmp);
612
+    s->tmp_size = 0;
613
+}
614
+
615
+static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
616
+                           char *res, int res_len, int flags)
617
+{
618
+    ZScaleContext *s = ctx->priv;
619
+    int ret;
620
+
621
+    if (   !strcmp(cmd, "width")  || !strcmp(cmd, "w")
622
+        || !strcmp(cmd, "height") || !strcmp(cmd, "h")) {
623
+
624
+        int old_w = s->w;
625
+        int old_h = s->h;
626
+        AVFilterLink *outlink = ctx->outputs[0];
627
+
628
+        av_opt_set(s, cmd, args, 0);
629
+        if ((ret = config_props(outlink)) < 0) {
630
+            s->w = old_w;
631
+            s->h = old_h;
632
+        }
633
+    } else
634
+        ret = AVERROR(ENOSYS);
635
+
636
+    return ret;
637
+}
638
+
639
+#define OFFSET(x) offsetof(ZScaleContext, x)
640
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
641
+
642
+static const AVOption zscale_options[] = {
643
+    { "w",      "Output video width",  OFFSET(w_expr),    AV_OPT_TYPE_STRING, .flags = FLAGS },
644
+    { "width",  "Output video width",  OFFSET(w_expr),    AV_OPT_TYPE_STRING, .flags = FLAGS },
645
+    { "h",      "Output video height", OFFSET(h_expr),    AV_OPT_TYPE_STRING, .flags = FLAGS },
646
+    { "height", "Output video height", OFFSET(h_expr),    AV_OPT_TYPE_STRING, .flags = FLAGS },
647
+    { "size",   "set video size",      OFFSET(size_str),  AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
648
+    { "s",      "set video size",      OFFSET(size_str),  AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
649
+    { "dither", "set dither type",     OFFSET(dither),    AV_OPT_TYPE_INT, {.i64 = 0}, 0, ZIMG_DITHER_ERROR_DIFFUSION, FLAGS, "dither" },
650
+    { "d",      "set dither type",     OFFSET(dither),    AV_OPT_TYPE_INT, {.i64 = 0}, 0, ZIMG_DITHER_ERROR_DIFFUSION, FLAGS, "dither" },
651
+    {     "none",             0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_DITHER_NONE},     0, 0, FLAGS, "dither" },
652
+    {     "ordered",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_DITHER_ORDERED},  0, 0, FLAGS, "dither" },
653
+    {     "random",           0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_DITHER_RANDOM},   0, 0, FLAGS, "dither" },
654
+    {     "error_diffusion",  0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_DITHER_ERROR_DIFFUSION}, 0, 0, FLAGS, "dither" },
655
+    { "filter", "set filter type",     OFFSET(filter),    AV_OPT_TYPE_INT, {.i64 = ZIMG_RESIZE_BILINEAR}, 0, ZIMG_RESIZE_LANCZOS, FLAGS, "filter" },
656
+    { "f",      "set filter type",     OFFSET(filter),    AV_OPT_TYPE_INT, {.i64 = ZIMG_RESIZE_BILINEAR}, 0, ZIMG_RESIZE_LANCZOS, FLAGS, "filter" },
657
+    {     "point",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RESIZE_POINT},    0, 0, FLAGS, "filter" },
658
+    {     "bilinear",         0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RESIZE_BILINEAR}, 0, 0, FLAGS, "filter" },
659
+    {     "bicubic",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RESIZE_BICUBIC},  0, 0, FLAGS, "filter" },
660
+    {     "spline16",         0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RESIZE_SPLINE16}, 0, 0, FLAGS, "filter" },
661
+    {     "splite36",         0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RESIZE_SPLINE36}, 0, 0, FLAGS, "filter" },
662
+    {     "lanczos",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RESIZE_LANCZOS},  0, 0, FLAGS, "filter" },
663
+    { "range", "set color range",      OFFSET(range),     AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_RANGE_FULL, FLAGS, "range" },
664
+    { "r",     "set color range",      OFFSET(range),     AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_RANGE_FULL, FLAGS, "range" },
665
+    {     "input",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = -1},                 0, 0, FLAGS, "range" },
666
+    {     "limited",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RANGE_LIMITED}, 0, 0, FLAGS, "range" },
667
+    {     "full",             0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_RANGE_FULL},    0, 0, FLAGS, "range" },
668
+    { "primaries", "set color primaries", OFFSET(primaries), AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_PRIMARIES_2020, FLAGS, "primaries" },
669
+    { "p",         "set color primaries", OFFSET(primaries), AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_PRIMARIES_2020, FLAGS, "primaries" },
670
+    {     "input",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = -1},                         0, 0, FLAGS, "primaries" },
671
+    {     "709",              0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_PRIMARIES_709},         0, 0, FLAGS, "primaries" },
672
+    {     "unspecified",      0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_PRIMARIES_UNSPECIFIED}, 0, 0, FLAGS, "primaries" },
673
+    {     "170m",             0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_PRIMARIES_170M},        0, 0, FLAGS, "primaries" },
674
+    {     "240m",             0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_PRIMARIES_240M},        0, 0, FLAGS, "primaries" },
675
+    {     "2020",             0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_PRIMARIES_2020},        0, 0, FLAGS, "primaries" },
676
+    { "transfer", "set transfer characteristic", OFFSET(trc), AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_TRANSFER_2020_12, FLAGS, "transfer" },
677
+    { "t",        "set transfer characteristic", OFFSET(trc), AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_TRANSFER_2020_12, FLAGS, "transfer" },
678
+    {     "input",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = -1},                         0, 0, FLAGS, "transfer" },
679
+    {     "709",              0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_TRANSFER_709},         0, 0, FLAGS, "transfer" },
680
+    {     "unspecified",      0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_TRANSFER_UNSPECIFIED}, 0, 0, FLAGS, "transfer" },
681
+    {     "601",              0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_TRANSFER_601},         0, 0, FLAGS, "transfer" },
682
+    {     "linear",           0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_TRANSFER_LINEAR},      0, 0, FLAGS, "transfer" },
683
+    {     "2020_10",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_TRANSFER_2020_10},     0, 0, FLAGS, "transfer" },
684
+    {     "2020_12",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_TRANSFER_2020_12},     0, 0, FLAGS, "transfer" },
685
+    { "matrix", "set colorspace matrix", OFFSET(colorspace), AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_MATRIX_2020_CL, FLAGS, "matrix" },
686
+    { "m",      "set colorspace matrix", OFFSET(colorspace), AV_OPT_TYPE_INT, {.i64 = -1}, -1, ZIMG_MATRIX_2020_CL, FLAGS, "matrix" },
687
+    {     "input",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = -1},                      0, 0, FLAGS, "matrix" },
688
+    {     "709",              0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_709},         0, 0, FLAGS, "matrix" },
689
+    {     "unspecified",      0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_UNSPECIFIED}, 0, 0, FLAGS, "matrix" },
690
+    {     "470bg",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_470BG},       0, 0, FLAGS, "matrix" },
691
+    {     "170m",             0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_170M},        0, 0, FLAGS, "matrix" },
692
+    {     "ycgco",            0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_YCGCO},       0, 0, FLAGS, "matrix" },
693
+    {     "2020_ncl",         0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_2020_NCL},    0, 0, FLAGS, "matrix" },
694
+    {     "2020_cl",          0,       0,                 AV_OPT_TYPE_CONST, {.i64 = ZIMG_MATRIX_2020_CL},     0, 0, FLAGS, "matrix" },
695
+    { NULL }
696
+};
697
+
698
+static const AVClass zscale_class = {
699
+    .class_name       = "zscale",
700
+    .item_name        = av_default_item_name,
701
+    .option           = zscale_options,
702
+    .version          = LIBAVUTIL_VERSION_INT,
703
+    .category         = AV_CLASS_CATEGORY_FILTER,
704
+};
705
+
706
+static const AVFilterPad avfilter_vf_zscale_inputs[] = {
707
+    {
708
+        .name         = "default",
709
+        .type         = AVMEDIA_TYPE_VIDEO,
710
+        .filter_frame = filter_frame,
711
+    },
712
+    { NULL }
713
+};
714
+
715
+static const AVFilterPad avfilter_vf_zscale_outputs[] = {
716
+    {
717
+        .name         = "default",
718
+        .type         = AVMEDIA_TYPE_VIDEO,
719
+        .config_props = config_props,
720
+    },
721
+    { NULL }
722
+};
723
+
724
+AVFilter ff_vf_zscale = {
725
+    .name            = "zscale",
726
+    .description     = NULL_IF_CONFIG_SMALL("Apply resizing, colorspace and bit depth conversion."),
727
+    .init_dict       = init_dict,
728
+    .query_formats   = query_formats,
729
+    .priv_size       = sizeof(ZScaleContext),
730
+    .priv_class      = &zscale_class,
731
+    .uninit          = uninit,
732
+    .inputs          = avfilter_vf_zscale_inputs,
733
+    .outputs         = avfilter_vf_zscale_outputs,
734
+    .process_command = process_command,
735
+};