Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2013 Clément Bœsch | ||
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/mem.h" | ||
22 | #include "libavutil/opt.h" | ||
23 | #include "libavutil/bprint.h" | ||
24 | #include "libavutil/eval.h" | ||
25 | #include "libavutil/file.h" | ||
26 | #include "libavutil/file_open.h" | ||
27 | #include "libavutil/intreadwrite.h" | ||
28 | #include "libavutil/avassert.h" | ||
29 | #include "libavutil/pixdesc.h" | ||
30 | #include "avfilter.h" | ||
31 | #include "drawutils.h" | ||
32 | #include "filters.h" | ||
33 | #include "video.h" | ||
34 | |||
35 | #define R 0 | ||
36 | #define G 1 | ||
37 | #define B 2 | ||
38 | #define A 3 | ||
39 | |||
40 | struct keypoint { | ||
41 | double x, y; | ||
42 | struct keypoint *next; | ||
43 | }; | ||
44 | |||
45 | #define NB_COMP 3 | ||
46 | |||
47 | enum preset { | ||
48 | PRESET_NONE, | ||
49 | PRESET_COLOR_NEGATIVE, | ||
50 | PRESET_CROSS_PROCESS, | ||
51 | PRESET_DARKER, | ||
52 | PRESET_INCREASE_CONTRAST, | ||
53 | PRESET_LIGHTER, | ||
54 | PRESET_LINEAR_CONTRAST, | ||
55 | PRESET_MEDIUM_CONTRAST, | ||
56 | PRESET_NEGATIVE, | ||
57 | PRESET_STRONG_CONTRAST, | ||
58 | PRESET_VINTAGE, | ||
59 | NB_PRESETS, | ||
60 | }; | ||
61 | |||
62 | enum interp { | ||
63 | INTERP_NATURAL, | ||
64 | INTERP_PCHIP, | ||
65 | NB_INTERPS, | ||
66 | }; | ||
67 | |||
68 | typedef struct CurvesContext { | ||
69 | const AVClass *class; | ||
70 | int preset; | ||
71 | char *comp_points_str[NB_COMP + 1]; | ||
72 | char *comp_points_str_all; | ||
73 | uint16_t *graph[NB_COMP + 1]; | ||
74 | int lut_size; | ||
75 | char *psfile; | ||
76 | uint8_t rgba_map[4]; | ||
77 | int step; | ||
78 | char *plot_filename; | ||
79 | int saved_plot; | ||
80 | int is_16bit; | ||
81 | int depth; | ||
82 | int parsed_psfile; | ||
83 | int interp; | ||
84 | |||
85 | int (*filter_slice)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); | ||
86 | } CurvesContext; | ||
87 | |||
88 | typedef struct ThreadData { | ||
89 | AVFrame *in, *out; | ||
90 | } ThreadData; | ||
91 | |||
92 | #define OFFSET(x) offsetof(CurvesContext, x) | ||
93 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM | ||
94 | static const AVOption curves_options[] = { | ||
95 | { "preset", "select a color curves preset", OFFSET(preset), AV_OPT_TYPE_INT, {.i64=PRESET_NONE}, PRESET_NONE, NB_PRESETS-1, FLAGS, .unit = "preset_name" }, | ||
96 | { "none", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NONE}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
97 | { "color_negative", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_COLOR_NEGATIVE}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
98 | { "cross_process", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_CROSS_PROCESS}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
99 | { "darker", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_DARKER}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
100 | { "increase_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_INCREASE_CONTRAST}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
101 | { "lighter", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_LIGHTER}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
102 | { "linear_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_LINEAR_CONTRAST}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
103 | { "medium_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_MEDIUM_CONTRAST}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
104 | { "negative", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NEGATIVE}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
105 | { "strong_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_STRONG_CONTRAST}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
106 | { "vintage", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_VINTAGE}, 0, 0, FLAGS, .unit = "preset_name" }, | ||
107 | { "master","set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
108 | { "m", "set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
109 | { "red", "set red points coordinates", OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
110 | { "r", "set red points coordinates", OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
111 | { "green", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
112 | { "g", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
113 | { "blue", "set blue points coordinates", OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
114 | { "b", "set blue points coordinates", OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
115 | { "all", "set points coordinates for all components", OFFSET(comp_points_str_all), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
116 | { "psfile", "set Photoshop curves file name", OFFSET(psfile), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
117 | { "plot", "save Gnuplot script of the curves in specified file", OFFSET(plot_filename), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | ||
118 | { "interp", "specify the kind of interpolation", OFFSET(interp), AV_OPT_TYPE_INT, {.i64=INTERP_NATURAL}, INTERP_NATURAL, NB_INTERPS-1, FLAGS, .unit = "interp_name" }, | ||
119 | { "natural", "natural cubic spline", 0, AV_OPT_TYPE_CONST, {.i64=INTERP_NATURAL}, 0, 0, FLAGS, .unit = "interp_name" }, | ||
120 | { "pchip", "monotonically cubic interpolation", 0, AV_OPT_TYPE_CONST, {.i64=INTERP_PCHIP}, 0, 0, FLAGS, .unit = "interp_name" }, | ||
121 | { NULL } | ||
122 | }; | ||
123 | |||
124 | AVFILTER_DEFINE_CLASS(curves); | ||
125 | |||
126 | static const struct { | ||
127 | const char *r; | ||
128 | const char *g; | ||
129 | const char *b; | ||
130 | const char *master; | ||
131 | } curves_presets[] = { | ||
132 | [PRESET_COLOR_NEGATIVE] = { | ||
133 | "0.129/1 0.466/0.498 0.725/0", | ||
134 | "0.109/1 0.301/0.498 0.517/0", | ||
135 | "0.098/1 0.235/0.498 0.423/0", | ||
136 | }, | ||
137 | [PRESET_CROSS_PROCESS] = { | ||
138 | "0/0 0.25/0.156 0.501/0.501 0.686/0.745 1/1", | ||
139 | "0/0 0.25/0.188 0.38/0.501 0.745/0.815 1/0.815", | ||
140 | "0/0 0.231/0.094 0.709/0.874 1/1", | ||
141 | }, | ||
142 | [PRESET_DARKER] = { .master = "0/0 0.5/0.4 1/1" }, | ||
143 | [PRESET_INCREASE_CONTRAST] = { .master = "0/0 0.149/0.066 0.831/0.905 0.905/0.98 1/1" }, | ||
144 | [PRESET_LIGHTER] = { .master = "0/0 0.4/0.5 1/1" }, | ||
145 | [PRESET_LINEAR_CONTRAST] = { .master = "0/0 0.305/0.286 0.694/0.713 1/1" }, | ||
146 | [PRESET_MEDIUM_CONTRAST] = { .master = "0/0 0.286/0.219 0.639/0.643 1/1" }, | ||
147 | [PRESET_NEGATIVE] = { .master = "0/1 1/0" }, | ||
148 | [PRESET_STRONG_CONTRAST] = { .master = "0/0 0.301/0.196 0.592/0.6 0.686/0.737 1/1" }, | ||
149 | [PRESET_VINTAGE] = { | ||
150 | "0/0.11 0.42/0.51 1/0.95", | ||
151 | "0/0 0.50/0.48 1/1", | ||
152 | "0/0.22 0.49/0.44 1/0.8", | ||
153 | } | ||
154 | }; | ||
155 | |||
156 | 9 | static struct keypoint *make_point(double x, double y, struct keypoint *next) | |
157 | { | ||
158 | 9 | struct keypoint *point = av_mallocz(sizeof(*point)); | |
159 | |||
160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (!point) |
161 | ✗ | return NULL; | |
162 | 9 | point->x = x; | |
163 | 9 | point->y = y; | |
164 | 9 | point->next = next; | |
165 | 9 | return point; | |
166 | } | ||
167 | |||
168 | 4 | static int parse_points_str(AVFilterContext *ctx, struct keypoint **points, const char *s, | |
169 | int lut_size) | ||
170 | { | ||
171 | 4 | char *p = (char *)s; // strtod won't alter the string | |
172 | 4 | struct keypoint *last = NULL; | |
173 | 4 | const int scale = lut_size - 1; | |
174 | |||
175 | /* construct a linked list based on the key points string */ | ||
176 |
4/4✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 3 times.
|
13 | while (p && *p) { |
177 | 9 | struct keypoint *point = make_point(0, 0, NULL); | |
178 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (!point) |
179 | ✗ | return AVERROR(ENOMEM); | |
180 |
2/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
|
9 | point->x = av_strtod(p, &p); if (p && *p) p++; |
181 |
3/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 3 times.
|
9 | point->y = av_strtod(p, &p); if (p && *p) p++; |
182 |
4/8✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 9 times.
|
9 | if (point->x < 0 || point->x > 1 || point->y < 0 || point->y > 1) { |
183 | ✗ | av_log(ctx, AV_LOG_ERROR, "Invalid key point coordinates (%f;%f), " | |
184 | "x and y must be in the [0;1] range.\n", point->x, point->y); | ||
185 | ✗ | av_free(point); | |
186 | ✗ | return AVERROR(EINVAL); | |
187 | } | ||
188 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | if (last) { |
189 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if ((int)(last->x * scale) >= (int)(point->x * scale)) { |
190 | ✗ | av_log(ctx, AV_LOG_ERROR, "Key point coordinates (%f;%f) " | |
191 | "and (%f;%f) are too close from each other or not " | ||
192 | "strictly increasing on the x-axis\n", | ||
193 | last->x, last->y, point->x, point->y); | ||
194 | ✗ | av_free(point); | |
195 | ✗ | return AVERROR(EINVAL); | |
196 | } | ||
197 | 6 | last->next = point; | |
198 | } | ||
199 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
|
9 | if (!*points) |
200 | 3 | *points = point; | |
201 | 9 | last = point; | |
202 | } | ||
203 | |||
204 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
4 | if (*points && !(*points)->next) { |
205 | ✗ | av_log(ctx, AV_LOG_WARNING, "Only one point (at (%f;%f)) is defined, " | |
206 | "this is unlikely to behave as you expect. You probably want" | ||
207 | "at least 2 points.", | ||
208 | ✗ | (*points)->x, (*points)->y); | |
209 | } | ||
210 | |||
211 | 4 | return 0; | |
212 | } | ||
213 | |||
214 | 4 | static int get_nb_points(const struct keypoint *d) | |
215 | { | ||
216 | 4 | int n = 0; | |
217 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4 times.
|
13 | while (d) { |
218 | 9 | n++; | |
219 | 9 | d = d->next; | |
220 | } | ||
221 | 4 | return n; | |
222 | } | ||
223 | |||
224 | /** | ||
225 | * Natural cubic spline interpolation | ||
226 | * Finding curves using Cubic Splines notes by Steven Rauch and John Stockie. | ||
227 | * @see http://people.math.sfu.ca/~stockie/teaching/macm316/notes/splines.pdf | ||
228 | */ | ||
229 | |||
230 | #define CLIP(v) (nbits == 8 ? av_clip_uint8(v) : av_clip_uintp2_c(v, nbits)) | ||
231 | |||
232 | 4 | static inline int interpolate(void *log_ctx, uint16_t *y, | |
233 | const struct keypoint *points, int nbits) | ||
234 | { | ||
235 | 4 | int i, ret = 0; | |
236 | 4 | const struct keypoint *point = points; | |
237 | 4 | double xprev = 0; | |
238 | 4 | const int lut_size = 1<<nbits; | |
239 | 4 | const int scale = lut_size - 1; | |
240 | |||
241 | double (*matrix)[3]; | ||
242 | double *h, *r; | ||
243 | 4 | const int n = get_nb_points(points); // number of splines | |
244 | |||
245 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
|
4 | if (n == 0) { |
246 |
2/2✓ Branch 0 taken 256 times.
✓ Branch 1 taken 1 times.
|
257 | for (i = 0; i < lut_size; i++) |
247 | 256 | y[i] = i; | |
248 | 1 | return 0; | |
249 | } | ||
250 | |||
251 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (n == 1) { |
252 | ✗ | for (i = 0; i < lut_size; i++) | |
253 | ✗ | y[i] = CLIP(point->y * scale); | |
254 | ✗ | return 0; | |
255 | } | ||
256 | |||
257 | 3 | matrix = av_calloc(n, sizeof(*matrix)); | |
258 | 3 | h = av_malloc((n - 1) * sizeof(*h)); | |
259 | 3 | r = av_calloc(n, sizeof(*r)); | |
260 | |||
261 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
3 | if (!matrix || !h || !r) { |
262 | ✗ | ret = AVERROR(ENOMEM); | |
263 | ✗ | goto end; | |
264 | } | ||
265 | |||
266 | /* h(i) = x(i+1) - x(i) */ | ||
267 | 3 | i = -1; | |
268 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
|
12 | for (point = points; point; point = point->next) { |
269 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | if (i != -1) |
270 | 6 | h[i] = point->x - xprev; | |
271 | 9 | xprev = point->x; | |
272 | 9 | i++; | |
273 | } | ||
274 | |||
275 | /* right-side of the polynomials, will be modified to contains the solution */ | ||
276 | 3 | point = points; | |
277 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (i = 1; i < n - 1; i++) { |
278 | 3 | const double yp = point->y; | |
279 | 3 | const double yc = point->next->y; | |
280 | 3 | const double yn = point->next->next->y; | |
281 | 3 | r[i] = 6 * ((yn-yc)/h[i] - (yc-yp)/h[i-1]); | |
282 | 3 | point = point->next; | |
283 | } | ||
284 | |||
285 | #define BD 0 /* sub diagonal (below main) */ | ||
286 | #define MD 1 /* main diagonal (center) */ | ||
287 | #define AD 2 /* sup diagonal (above main) */ | ||
288 | |||
289 | /* left side of the polynomials into a tridiagonal matrix. */ | ||
290 | 3 | matrix[0][MD] = matrix[n - 1][MD] = 1; | |
291 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (i = 1; i < n - 1; i++) { |
292 | 3 | matrix[i][BD] = h[i-1]; | |
293 | 3 | matrix[i][MD] = 2 * (h[i-1] + h[i]); | |
294 | 3 | matrix[i][AD] = h[i]; | |
295 | } | ||
296 | |||
297 | /* tridiagonal solving of the linear system */ | ||
298 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | for (i = 1; i < n; i++) { |
299 | 6 | const double den = matrix[i][MD] - matrix[i][BD] * matrix[i-1][AD]; | |
300 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | const double k = den ? 1./den : 1.; |
301 | 6 | matrix[i][AD] *= k; | |
302 | 6 | r[i] = (r[i] - matrix[i][BD] * r[i - 1]) * k; | |
303 | } | ||
304 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | for (i = n - 2; i >= 0; i--) |
305 | 6 | r[i] = r[i] - matrix[i][AD] * r[i + 1]; | |
306 | |||
307 | 3 | point = points; | |
308 | |||
309 | /* left padding */ | ||
310 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | for (i = 0; i < (int)(point->x * scale); i++) |
311 | ✗ | y[i] = CLIP(point->y * scale); | |
312 | |||
313 | /* compute the graph with x=[x0..xN] */ | ||
314 | 3 | i = 0; | |
315 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | av_assert0(point->next); // always at least 2 key points |
316 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | while (point->next) { |
317 | 6 | const double yc = point->y; | |
318 | 6 | const double yn = point->next->y; | |
319 | |||
320 | 6 | const double a = yc; | |
321 | 6 | const double b = (yn-yc)/h[i] - h[i]*r[i]/2. - h[i]*(r[i+1]-r[i])/6.; | |
322 | 6 | const double c = r[i] / 2.; | |
323 | 6 | const double d = (r[i+1] - r[i]) / (6.*h[i]); | |
324 | |||
325 | int x; | ||
326 | 6 | const int x_start = point->x * scale; | |
327 | 6 | const int x_end = point->next->x * scale; | |
328 | |||
329 |
4/8✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
|
6 | av_assert0(x_start >= 0 && x_start < lut_size && |
330 | x_end >= 0 && x_end < lut_size); | ||
331 | |||
332 |
2/2✓ Branch 0 taken 771 times.
✓ Branch 1 taken 6 times.
|
777 | for (x = x_start; x <= x_end; x++) { |
333 | 771 | const double xx = (x - x_start) * 1./scale; | |
334 | 771 | const double yy = a + b*xx + c*xx*xx + d*xx*xx*xx; | |
335 |
1/2✓ Branch 0 taken 771 times.
✗ Branch 1 not taken.
|
771 | y[x] = CLIP(yy * scale); |
336 | 771 | av_log(log_ctx, AV_LOG_DEBUG, "f(%f)=%f -> y[%d]=%d\n", xx, yy, x, y[x]); | |
337 | } | ||
338 | |||
339 | 6 | point = point->next; | |
340 | 6 | i++; | |
341 | } | ||
342 | |||
343 | /* right padding */ | ||
344 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (i = (int)(point->x * scale); i < lut_size; i++) |
345 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | y[i] = CLIP(point->y * scale); |
346 | |||
347 | 3 | end: | |
348 | 3 | av_free(matrix); | |
349 | 3 | av_free(h); | |
350 | 3 | av_free(r); | |
351 | 3 | return ret; | |
352 | |||
353 | } | ||
354 | |||
355 | #define SIGN(x) (x > 0.0 ? 1 : x < 0.0 ? -1 : 0) | ||
356 | |||
357 | /** | ||
358 | * Evalaute the derivative of an edge endpoint | ||
359 | * | ||
360 | * @param h0 input interval of the interval closest to the edge | ||
361 | * @param h1 input interval of the interval next to the closest | ||
362 | * @param m0 linear slope of the interval closest to the edge | ||
363 | * @param m1 linear slope of the intervalnext to the closest | ||
364 | * @return edge endpoint derivative | ||
365 | * | ||
366 | * Based on scipy.interpolate._edge_case() | ||
367 | * https://github.com/scipy/scipy/blob/2e5883ef7af4f5ed4a5b80a1759a45e43163bf3f/scipy/interpolate/_cubic.py#L239 | ||
368 | * which is a python implementation of the special case endpoints, as suggested in | ||
369 | * Cleve Moler, Numerical Computing with MATLAB, Chap 3.6 (pchiptx.m) | ||
370 | */ | ||
371 | ✗ | static double pchip_edge_case(double h0, double h1, double m0, double m1) | |
372 | { | ||
373 | int mask, mask2; | ||
374 | double d; | ||
375 | |||
376 | ✗ | d = ((2 * h0 + h1) * m0 - h0 * m1) / (h0 + h1); | |
377 | |||
378 | ✗ | mask = SIGN(d) != SIGN(m0); | |
379 | ✗ | mask2 = (SIGN(m0) != SIGN(m1)) && (fabs(d) > 3. * fabs(m0)); | |
380 | |||
381 | ✗ | if (mask) d = 0.0; | |
382 | ✗ | else if (mask2) d = 3.0 * m0; | |
383 | |||
384 | ✗ | return d; | |
385 | } | ||
386 | |||
387 | /** | ||
388 | * Evalaute the piecewise polynomial derivatives at endpoints | ||
389 | * | ||
390 | * @param n input interval of the interval closest to the edge | ||
391 | * @param hk input intervals | ||
392 | * @param mk linear slopes over intervals | ||
393 | * @param dk endpoint derivatives (output) | ||
394 | * @return 0 success | ||
395 | * | ||
396 | * Based on scipy.interpolate._find_derivatives() | ||
397 | * https://github.com/scipy/scipy/blob/2e5883ef7af4f5ed4a5b80a1759a45e43163bf3f/scipy/interpolate/_cubic.py#L254 | ||
398 | */ | ||
399 | |||
400 | ✗ | static int pchip_find_derivatives(const int n, const double *hk, const double *mk, double *dk) | |
401 | { | ||
402 | ✗ | int ret = 0; | |
403 | ✗ | const int m = n - 1; | |
404 | int8_t *smk; | ||
405 | |||
406 | ✗ | smk = av_malloc(n); | |
407 | ✗ | if (!smk) { | |
408 | ✗ | ret = AVERROR(ENOMEM); | |
409 | ✗ | goto end; | |
410 | } | ||
411 | |||
412 | /* smk = sgn(mk) */ | ||
413 | ✗ | for (int i = 0; i < n; i++) smk[i] = SIGN(mk[i]); | |
414 | |||
415 | /* check the strict monotonicity */ | ||
416 | ✗ | for (int i = 0; i < m; i++) { | |
417 | ✗ | int8_t condition = (smk[i + 1] != smk[i]) || (mk[i + 1] == 0) || (mk[i] == 0); | |
418 | ✗ | if (condition) { | |
419 | ✗ | dk[i + 1] = 0.0; | |
420 | } else { | ||
421 | ✗ | double w1 = 2 * hk[i + 1] + hk[i]; | |
422 | ✗ | double w2 = hk[i + 1] + 2 * hk[i]; | |
423 | ✗ | dk[i + 1] = (w1 + w2) / (w1 / mk[i] + w2 / mk[i + 1]); | |
424 | } | ||
425 | } | ||
426 | |||
427 | ✗ | dk[0] = pchip_edge_case(hk[0], hk[1], mk[0], mk[1]); | |
428 | ✗ | dk[n] = pchip_edge_case(hk[n - 1], hk[n - 2], mk[n - 1], mk[n - 2]); | |
429 | |||
430 | ✗ | end: | |
431 | ✗ | av_free(smk); | |
432 | |||
433 | ✗ | return ret; | |
434 | } | ||
435 | |||
436 | /** | ||
437 | * Evalaute half of the cubic hermite interpolation expression, wrt one interval endpoint | ||
438 | * | ||
439 | * @param x normalized input value at the endpoint | ||
440 | * @param f output value at the endpoint | ||
441 | * @param d derivative at the endpoint: normalized to the interval, and properly sign adjusted | ||
442 | * @return half of the interpolated value | ||
443 | */ | ||
444 | ✗ | static inline double interp_cubic_hermite_half(const double x, const double f, | |
445 | const double d) | ||
446 | { | ||
447 | ✗ | double x2 = x * x, x3 = x2 * x; | |
448 | ✗ | return f * (3.0 * x2 - 2.0 * x3) + d * (x3 - x2); | |
449 | } | ||
450 | |||
451 | /** | ||
452 | * Prepare the lookup table by piecewise monotonic cubic interpolation (PCHIP) | ||
453 | * | ||
454 | * @param log_ctx for logging | ||
455 | * @param y output lookup table (output) | ||
456 | * @param points user-defined control points/endpoints | ||
457 | * @param nbits bitdepth | ||
458 | * @return 0 success | ||
459 | * | ||
460 | * References: | ||
461 | * [1] F. N. Fritsch and J. Butland, A method for constructing local monotone piecewise | ||
462 | * cubic interpolants, SIAM J. Sci. Comput., 5(2), 300-304 (1984). DOI:10.1137/0905021. | ||
463 | * [2] scipy.interpolate: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PchipInterpolator.html | ||
464 | */ | ||
465 | ✗ | static inline int interpolate_pchip(void *log_ctx, uint16_t *y, | |
466 | const struct keypoint *points, int nbits) | ||
467 | { | ||
468 | ✗ | const struct keypoint *point = points; | |
469 | ✗ | const int lut_size = 1<<nbits; | |
470 | ✗ | const int n = get_nb_points(points); // number of endpoints | |
471 | double *xi, *fi, *di, *hi, *mi; | ||
472 | ✗ | const int scale = lut_size - 1; // white value | |
473 | uint16_t x; /* input index/value */ | ||
474 | ✗ | int ret = 0; | |
475 | |||
476 | /* no change for n = 0 or 1 */ | ||
477 | ✗ | if (n == 0) { | |
478 | /* no points, no change */ | ||
479 | ✗ | for (int i = 0; i < lut_size; i++) y[i] = i; | |
480 | ✗ | return 0; | |
481 | } | ||
482 | |||
483 | ✗ | if (n == 1) { | |
484 | /* 1 point - 1 color everywhere */ | ||
485 | ✗ | const uint16_t yval = CLIP(point->y * scale); | |
486 | ✗ | for (int i = 0; i < lut_size; i++) y[i] = yval; | |
487 | ✗ | return 0; | |
488 | } | ||
489 | |||
490 | ✗ | xi = av_calloc(3*n + 2*(n-1), sizeof(double)); /* output values at interval endpoints */ | |
491 | ✗ | if (!xi) { | |
492 | ✗ | ret = AVERROR(ENOMEM); | |
493 | ✗ | goto end; | |
494 | } | ||
495 | |||
496 | ✗ | fi = xi + n; /* output values at inteval endpoints */ | |
497 | ✗ | di = fi + n; /* output slope wrt normalized input at interval endpoints */ | |
498 | ✗ | hi = di + n; /* interval widths */ | |
499 | ✗ | mi = hi + n - 1; /* linear slope over intervals */ | |
500 | |||
501 | /* scale endpoints and store them in a contiguous memory block */ | ||
502 | ✗ | for (int i = 0; i < n; i++) { | |
503 | ✗ | xi[i] = point->x * scale; | |
504 | ✗ | fi[i] = point->y * scale; | |
505 | ✗ | point = point->next; | |
506 | } | ||
507 | |||
508 | /* h(i) = x(i+1) - x(i); mi(i) = (f(i+1)-f(i))/h(i) */ | ||
509 | ✗ | for (int i = 0; i < n - 1; i++) { | |
510 | ✗ | const double val = (xi[i+1]-xi[i]); | |
511 | ✗ | hi[i] = val; | |
512 | ✗ | mi[i] = (fi[i+1]-fi[i]) / val; | |
513 | } | ||
514 | |||
515 | ✗ | if (n == 2) { | |
516 | /* edge case, use linear interpolation */ | ||
517 | ✗ | const double m = mi[0], b = fi[0] - xi[0]*m; | |
518 | ✗ | for (int i = 0; i < lut_size; i++) y[i] = CLIP(i*m + b); | |
519 | ✗ | goto end; | |
520 | } | ||
521 | |||
522 | /* compute the derivatives at the endpoints*/ | ||
523 | ✗ | ret = pchip_find_derivatives(n-1, hi, mi, di); | |
524 | ✗ | if (ret) | |
525 | ✗ | goto end; | |
526 | |||
527 | /* interpolate/extrapolate */ | ||
528 | ✗ | x = 0; | |
529 | ✗ | if (xi[0] > 0) { | |
530 | /* below first endpoint, use the first endpoint value*/ | ||
531 | ✗ | const double xi0 = xi[0]; | |
532 | ✗ | const double yi0 = fi[0]; | |
533 | ✗ | const uint16_t yval = CLIP(yi0); | |
534 | ✗ | for (; x < xi0; x++) { | |
535 | ✗ | y[x] = yval; | |
536 | ✗ | av_log(log_ctx, AV_LOG_TRACE, "f(%f)=%f -> y[%d]=%d\n", xi0, yi0, x, y[x]); | |
537 | } | ||
538 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "Interval -1: [0, %d] -> %d\n", x - 1, yval); | |
539 | } | ||
540 | |||
541 | /* for each interval */ | ||
542 | ✗ | for (int i = 0, x0 = x; i < n-1; i++, x0 = x) { | |
543 | ✗ | const double xi0 = xi[i]; /* start-of-interval input value */ | |
544 | ✗ | const double xi1 = xi[i + 1]; /* end-of-interval input value */ | |
545 | ✗ | const double h = hi[i]; /* interval width */ | |
546 | ✗ | const double f0 = fi[i]; /* start-of-interval output value */ | |
547 | ✗ | const double f1 = fi[i + 1]; /* end-of-interval output value */ | |
548 | ✗ | const double d0 = di[i]; /* start-of-interval derivative */ | |
549 | ✗ | const double d1 = di[i + 1]; /* end-of-interval derivative */ | |
550 | |||
551 | /* fill the lut over the interval */ | ||
552 | ✗ | for (; x < xi1; x++) { /* safe not to check j < lut_size */ | |
553 | ✗ | const double xx = (x - xi0) / h; /* normalize input */ | |
554 | ✗ | const double yy = interp_cubic_hermite_half(1 - xx, f0, -h * d0) | |
555 | ✗ | + interp_cubic_hermite_half(xx, f1, h * d1); | |
556 | ✗ | y[x] = CLIP(yy); | |
557 | ✗ | av_log(log_ctx, AV_LOG_TRACE, "f(%f)=%f -> y[%d]=%d\n", xx, yy, x, y[x]); | |
558 | } | ||
559 | |||
560 | ✗ | if (x > x0) | |
561 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "Interval %d: [%d, %d] -> [%d, %d]\n", | |
562 | ✗ | i, x0, x-1, y[x0], y[x-1]); | |
563 | else | ||
564 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "Interval %d: empty\n", i); | |
565 | } | ||
566 | |||
567 | ✗ | if (x && x < lut_size) { | |
568 | /* above the last endpoint, use the last endpoint value*/ | ||
569 | ✗ | const double xi1 = xi[n - 1]; | |
570 | ✗ | const double yi1 = fi[n - 1]; | |
571 | ✗ | const uint16_t yval = CLIP(yi1); | |
572 | ✗ | av_log(log_ctx, AV_LOG_DEBUG, "Interval %d: [%d, %d] -> %d\n", | |
573 | n-1, x, lut_size - 1, yval); | ||
574 | ✗ | for (; x && x < lut_size; x++) { /* loop until int overflow */ | |
575 | ✗ | y[x] = yval; | |
576 | ✗ | av_log(log_ctx, AV_LOG_TRACE, "f(%f)=%f -> y[%d]=%d\n", xi1, yi1, x, yval); | |
577 | } | ||
578 | } | ||
579 | |||
580 | ✗ | end: | |
581 | ✗ | av_free(xi); | |
582 | ✗ | return ret; | |
583 | } | ||
584 | |||
585 | |||
586 | ✗ | static int parse_psfile(AVFilterContext *ctx, const char *fname) | |
587 | { | ||
588 | ✗ | CurvesContext *curves = ctx->priv; | |
589 | uint8_t *buf; | ||
590 | size_t size; | ||
591 | int i, ret, av_unused(version), nb_curves; | ||
592 | AVBPrint ptstr; | ||
593 | static const int comp_ids[] = {3, 0, 1, 2}; | ||
594 | |||
595 | ✗ | av_bprint_init(&ptstr, 0, AV_BPRINT_SIZE_AUTOMATIC); | |
596 | |||
597 | ✗ | ret = av_file_map(fname, &buf, &size, 0, NULL); | |
598 | ✗ | if (ret < 0) | |
599 | ✗ | return ret; | |
600 | |||
601 | #define READ16(dst) do { \ | ||
602 | if (size < 2) { \ | ||
603 | ret = AVERROR_INVALIDDATA; \ | ||
604 | goto end; \ | ||
605 | } \ | ||
606 | dst = AV_RB16(buf); \ | ||
607 | buf += 2; \ | ||
608 | size -= 2; \ | ||
609 | } while (0) | ||
610 | |||
611 | ✗ | READ16(version); | |
612 | ✗ | READ16(nb_curves); | |
613 | ✗ | for (i = 0; i < FFMIN(nb_curves, FF_ARRAY_ELEMS(comp_ids)); i++) { | |
614 | int nb_points, n; | ||
615 | ✗ | av_bprint_clear(&ptstr); | |
616 | ✗ | READ16(nb_points); | |
617 | ✗ | for (n = 0; n < nb_points; n++) { | |
618 | int y, x; | ||
619 | ✗ | READ16(y); | |
620 | ✗ | READ16(x); | |
621 | ✗ | av_bprintf(&ptstr, "%f/%f ", x / 255., y / 255.); | |
622 | } | ||
623 | ✗ | if (*ptstr.str) { | |
624 | ✗ | char **pts = &curves->comp_points_str[comp_ids[i]]; | |
625 | ✗ | if (!*pts) { | |
626 | ✗ | *pts = av_strdup(ptstr.str); | |
627 | ✗ | av_log(ctx, AV_LOG_DEBUG, "curves %d (intid=%d) [%d points]: [%s]\n", | |
628 | ✗ | i, comp_ids[i], nb_points, *pts); | |
629 | ✗ | if (!*pts) { | |
630 | ✗ | ret = AVERROR(ENOMEM); | |
631 | ✗ | goto end; | |
632 | } | ||
633 | } | ||
634 | } | ||
635 | } | ||
636 | ✗ | end: | |
637 | ✗ | av_bprint_finalize(&ptstr, NULL); | |
638 | ✗ | av_file_unmap(buf, size); | |
639 | ✗ | return ret; | |
640 | } | ||
641 | |||
642 | ✗ | static int dump_curves(const char *fname, uint16_t *graph[NB_COMP + 1], | |
643 | struct keypoint *comp_points[NB_COMP + 1], | ||
644 | int lut_size) | ||
645 | { | ||
646 | int i; | ||
647 | AVBPrint buf; | ||
648 | ✗ | const double scale = 1. / (lut_size - 1); | |
649 | static const char * const colors[] = { "red", "green", "blue", "#404040", }; | ||
650 | ✗ | FILE *f = avpriv_fopen_utf8(fname, "w"); | |
651 | |||
652 | av_assert0(FF_ARRAY_ELEMS(colors) == NB_COMP + 1); | ||
653 | |||
654 | ✗ | if (!f) { | |
655 | ✗ | int ret = AVERROR(errno); | |
656 | ✗ | av_log(NULL, AV_LOG_ERROR, "Cannot open file '%s' for writing: %s\n", | |
657 | ✗ | fname, av_err2str(ret)); | |
658 | ✗ | return ret; | |
659 | } | ||
660 | |||
661 | ✗ | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); | |
662 | |||
663 | ✗ | av_bprintf(&buf, "set xtics 0.1\n"); | |
664 | ✗ | av_bprintf(&buf, "set ytics 0.1\n"); | |
665 | ✗ | av_bprintf(&buf, "set size square\n"); | |
666 | ✗ | av_bprintf(&buf, "set grid\n"); | |
667 | |||
668 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(colors); i++) { | |
669 | ✗ | av_bprintf(&buf, "%s'-' using 1:2 with lines lc '%s' title ''", | |
670 | ✗ | i ? ", " : "plot ", colors[i]); | |
671 | ✗ | if (comp_points[i]) | |
672 | ✗ | av_bprintf(&buf, ", '-' using 1:2 with points pointtype 3 lc '%s' title ''", | |
673 | ✗ | colors[i]); | |
674 | } | ||
675 | ✗ | av_bprintf(&buf, "\n"); | |
676 | |||
677 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(colors); i++) { | |
678 | int x; | ||
679 | |||
680 | /* plot generated values */ | ||
681 | ✗ | for (x = 0; x < lut_size; x++) | |
682 | ✗ | av_bprintf(&buf, "%f %f\n", x * scale, graph[i][x] * scale); | |
683 | ✗ | av_bprintf(&buf, "e\n"); | |
684 | |||
685 | /* plot user knots */ | ||
686 | ✗ | if (comp_points[i]) { | |
687 | ✗ | const struct keypoint *point = comp_points[i]; | |
688 | |||
689 | ✗ | while (point) { | |
690 | ✗ | av_bprintf(&buf, "%f %f\n", point->x, point->y); | |
691 | ✗ | point = point->next; | |
692 | } | ||
693 | ✗ | av_bprintf(&buf, "e\n"); | |
694 | } | ||
695 | } | ||
696 | |||
697 | ✗ | fwrite(buf.str, 1, buf.len, f); | |
698 | ✗ | fclose(f); | |
699 | ✗ | av_bprint_finalize(&buf, NULL); | |
700 | ✗ | return 0; | |
701 | } | ||
702 | |||
703 | 2 | static av_cold int curves_init(AVFilterContext *ctx) | |
704 | { | ||
705 | int i, ret; | ||
706 | 2 | CurvesContext *curves = ctx->priv; | |
707 | 2 | char **pts = curves->comp_points_str; | |
708 | 2 | const char *allp = curves->comp_points_str_all; | |
709 | |||
710 | //if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all) | ||
711 | // allp = curves_presets[curves->preset].all; | ||
712 | |||
713 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (allp) { |
714 | ✗ | for (i = 0; i < NB_COMP; i++) { | |
715 | ✗ | if (!pts[i]) | |
716 | ✗ | pts[i] = av_strdup(allp); | |
717 | ✗ | if (!pts[i]) | |
718 | ✗ | return AVERROR(ENOMEM); | |
719 | } | ||
720 | } | ||
721 | |||
722 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
2 | if (curves->psfile && !curves->parsed_psfile) { |
723 | ✗ | ret = parse_psfile(ctx, curves->psfile); | |
724 | ✗ | if (ret < 0) | |
725 | ✗ | return ret; | |
726 | ✗ | curves->parsed_psfile = 1; | |
727 | } | ||
728 | |||
729 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (curves->preset != PRESET_NONE) { |
730 | #define SET_COMP_IF_NOT_SET(n, name) do { \ | ||
731 | if (!pts[n] && curves_presets[curves->preset].name) { \ | ||
732 | pts[n] = av_strdup(curves_presets[curves->preset].name); \ | ||
733 | if (!pts[n]) \ | ||
734 | return AVERROR(ENOMEM); \ | ||
735 | } \ | ||
736 | } while (0) | ||
737 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
|
2 | SET_COMP_IF_NOT_SET(0, r); |
738 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
|
2 | SET_COMP_IF_NOT_SET(1, g); |
739 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
|
2 | SET_COMP_IF_NOT_SET(2, b); |
740 |
2/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
2 | SET_COMP_IF_NOT_SET(3, master); |
741 | 2 | curves->preset = PRESET_NONE; | |
742 | } | ||
743 | |||
744 | 2 | return 0; | |
745 | } | ||
746 | |||
747 | ✗ | static int filter_slice_packed(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
748 | { | ||
749 | int x, y; | ||
750 | ✗ | const CurvesContext *curves = ctx->priv; | |
751 | ✗ | const ThreadData *td = arg; | |
752 | ✗ | const AVFrame *in = td->in; | |
753 | ✗ | const AVFrame *out = td->out; | |
754 | ✗ | const int direct = out == in; | |
755 | ✗ | const int step = curves->step; | |
756 | ✗ | const uint8_t r = curves->rgba_map[R]; | |
757 | ✗ | const uint8_t g = curves->rgba_map[G]; | |
758 | ✗ | const uint8_t b = curves->rgba_map[B]; | |
759 | ✗ | const uint8_t a = curves->rgba_map[A]; | |
760 | ✗ | const int slice_start = (in->height * jobnr ) / nb_jobs; | |
761 | ✗ | const int slice_end = (in->height * (jobnr+1)) / nb_jobs; | |
762 | |||
763 | ✗ | if (curves->is_16bit) { | |
764 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
765 | ✗ | uint16_t *dstp = ( uint16_t *)(out->data[0] + y * out->linesize[0]); | |
766 | ✗ | const uint16_t *srcp = (const uint16_t *)(in ->data[0] + y * in->linesize[0]); | |
767 | |||
768 | ✗ | for (x = 0; x < in->width * step; x += step) { | |
769 | ✗ | dstp[x + r] = curves->graph[R][srcp[x + r]]; | |
770 | ✗ | dstp[x + g] = curves->graph[G][srcp[x + g]]; | |
771 | ✗ | dstp[x + b] = curves->graph[B][srcp[x + b]]; | |
772 | ✗ | if (!direct && step == 4) | |
773 | ✗ | dstp[x + a] = srcp[x + a]; | |
774 | } | ||
775 | } | ||
776 | } else { | ||
777 | ✗ | uint8_t *dst = out->data[0] + slice_start * out->linesize[0]; | |
778 | ✗ | const uint8_t *src = in->data[0] + slice_start * in->linesize[0]; | |
779 | |||
780 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
781 | ✗ | for (x = 0; x < in->width * step; x += step) { | |
782 | ✗ | dst[x + r] = curves->graph[R][src[x + r]]; | |
783 | ✗ | dst[x + g] = curves->graph[G][src[x + g]]; | |
784 | ✗ | dst[x + b] = curves->graph[B][src[x + b]]; | |
785 | ✗ | if (!direct && step == 4) | |
786 | ✗ | dst[x + a] = src[x + a]; | |
787 | } | ||
788 | ✗ | dst += out->linesize[0]; | |
789 | ✗ | src += in ->linesize[0]; | |
790 | } | ||
791 | } | ||
792 | ✗ | return 0; | |
793 | } | ||
794 | |||
795 | 45 | static int filter_slice_planar(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
796 | { | ||
797 | int x, y; | ||
798 | 45 | const CurvesContext *curves = ctx->priv; | |
799 | 45 | const ThreadData *td = arg; | |
800 | 45 | const AVFrame *in = td->in; | |
801 | 45 | const AVFrame *out = td->out; | |
802 | 45 | const int direct = out == in; | |
803 | 45 | const int step = curves->step; | |
804 | 45 | const uint8_t r = curves->rgba_map[R]; | |
805 | 45 | const uint8_t g = curves->rgba_map[G]; | |
806 | 45 | const uint8_t b = curves->rgba_map[B]; | |
807 | 45 | const uint8_t a = curves->rgba_map[A]; | |
808 | 45 | const int slice_start = (in->height * jobnr ) / nb_jobs; | |
809 | 45 | const int slice_end = (in->height * (jobnr+1)) / nb_jobs; | |
810 | |||
811 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
|
45 | if (curves->is_16bit) { |
812 | ✗ | for (y = slice_start; y < slice_end; y++) { | |
813 | ✗ | uint16_t *dstrp = ( uint16_t *)(out->data[r] + y * out->linesize[r]); | |
814 | ✗ | uint16_t *dstgp = ( uint16_t *)(out->data[g] + y * out->linesize[g]); | |
815 | ✗ | uint16_t *dstbp = ( uint16_t *)(out->data[b] + y * out->linesize[b]); | |
816 | ✗ | uint16_t *dstap = ( uint16_t *)(out->data[a] + y * out->linesize[a]); | |
817 | ✗ | const uint16_t *srcrp = (const uint16_t *)(in ->data[r] + y * in->linesize[r]); | |
818 | ✗ | const uint16_t *srcgp = (const uint16_t *)(in ->data[g] + y * in->linesize[g]); | |
819 | ✗ | const uint16_t *srcbp = (const uint16_t *)(in ->data[b] + y * in->linesize[b]); | |
820 | ✗ | const uint16_t *srcap = (const uint16_t *)(in ->data[a] + y * in->linesize[a]); | |
821 | |||
822 | ✗ | for (x = 0; x < in->width; x++) { | |
823 | ✗ | dstrp[x] = curves->graph[R][srcrp[x]]; | |
824 | ✗ | dstgp[x] = curves->graph[G][srcgp[x]]; | |
825 | ✗ | dstbp[x] = curves->graph[B][srcbp[x]]; | |
826 | ✗ | if (!direct && step == 4) | |
827 | ✗ | dstap[x] = srcap[x]; | |
828 | } | ||
829 | } | ||
830 | } else { | ||
831 | 45 | uint8_t *dstr = out->data[r] + slice_start * out->linesize[r]; | |
832 | 45 | uint8_t *dstg = out->data[g] + slice_start * out->linesize[g]; | |
833 | 45 | uint8_t *dstb = out->data[b] + slice_start * out->linesize[b]; | |
834 | 45 | uint8_t *dsta = out->data[a] + slice_start * out->linesize[a]; | |
835 | 45 | const uint8_t *srcr = in->data[r] + slice_start * in->linesize[r]; | |
836 | 45 | const uint8_t *srcg = in->data[g] + slice_start * in->linesize[g]; | |
837 | 45 | const uint8_t *srcb = in->data[b] + slice_start * in->linesize[b]; | |
838 | 45 | const uint8_t *srca = in->data[a] + slice_start * in->linesize[a]; | |
839 | |||
840 |
2/2✓ Branch 0 taken 2400 times.
✓ Branch 1 taken 45 times.
|
2445 | for (y = slice_start; y < slice_end; y++) { |
841 |
2/2✓ Branch 0 taken 1536000 times.
✓ Branch 1 taken 2400 times.
|
1538400 | for (x = 0; x < in->width; x++) { |
842 | 1536000 | dstr[x] = curves->graph[R][srcr[x]]; | |
843 | 1536000 | dstg[x] = curves->graph[G][srcg[x]]; | |
844 | 1536000 | dstb[x] = curves->graph[B][srcb[x]]; | |
845 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1536000 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1536000 | if (!direct && step == 4) |
846 | ✗ | dsta[x] = srca[x]; | |
847 | } | ||
848 | 2400 | dstr += out->linesize[r]; | |
849 | 2400 | dstg += out->linesize[g]; | |
850 | 2400 | dstb += out->linesize[b]; | |
851 | 2400 | dsta += out->linesize[a]; | |
852 | 2400 | srcr += in ->linesize[r]; | |
853 | 2400 | srcg += in ->linesize[g]; | |
854 | 2400 | srcb += in ->linesize[b]; | |
855 | 2400 | srca += in ->linesize[a]; | |
856 | } | ||
857 | } | ||
858 | 45 | return 0; | |
859 | } | ||
860 | |||
861 | 1 | static int config_input(AVFilterLink *inlink) | |
862 | { | ||
863 | int i, j, ret; | ||
864 | 1 | AVFilterContext *ctx = inlink->dst; | |
865 | 1 | CurvesContext *curves = ctx->priv; | |
866 | 1 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
867 | 1 | char **pts = curves->comp_points_str; | |
868 | 1 | struct keypoint *comp_points[NB_COMP + 1] = {0}; | |
869 | |||
870 | 1 | ff_fill_rgba_map(curves->rgba_map, inlink->format); | |
871 | 1 | curves->is_16bit = desc->comp[0].depth > 8; | |
872 | 1 | curves->depth = desc->comp[0].depth; | |
873 | 1 | curves->lut_size = 1 << curves->depth; | |
874 | 1 | curves->step = av_get_padded_bits_per_pixel(desc) >> (3 + curves->is_16bit); | |
875 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | curves->filter_slice = desc->flags & AV_PIX_FMT_FLAG_PLANAR ? filter_slice_planar : filter_slice_packed; |
876 | |||
877 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | for (i = 0; i < NB_COMP + 1; i++) { |
878 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (!curves->graph[i]) |
879 | 4 | curves->graph[i] = av_calloc(curves->lut_size, sizeof(*curves->graph[0])); | |
880 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!curves->graph[i]) |
881 | ✗ | return AVERROR(ENOMEM); | |
882 | 4 | ret = parse_points_str(ctx, comp_points + i, curves->comp_points_str[i], curves->lut_size); | |
883 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
884 | ✗ | return ret; | |
885 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (curves->interp == INTERP_PCHIP) |
886 | ✗ | ret = interpolate_pchip(ctx, curves->graph[i], comp_points[i], curves->depth); | |
887 | else | ||
888 | 4 | ret = interpolate(ctx, curves->graph[i], comp_points[i], curves->depth); | |
889 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
890 | ✗ | return ret; | |
891 | } | ||
892 | |||
893 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (pts[NB_COMP]) { |
894 | ✗ | for (i = 0; i < NB_COMP; i++) | |
895 | ✗ | for (j = 0; j < curves->lut_size; j++) | |
896 | ✗ | curves->graph[i][j] = curves->graph[NB_COMP][curves->graph[i][j]]; | |
897 | } | ||
898 | |||
899 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (av_log_get_level() >= AV_LOG_VERBOSE) { |
900 | ✗ | for (i = 0; i < NB_COMP; i++) { | |
901 | ✗ | const struct keypoint *point = comp_points[i]; | |
902 | ✗ | av_log(ctx, AV_LOG_VERBOSE, "#%d points:", i); | |
903 | ✗ | while (point) { | |
904 | ✗ | av_log(ctx, AV_LOG_VERBOSE, " (%f;%f)", point->x, point->y); | |
905 | ✗ | point = point->next; | |
906 | } | ||
907 | } | ||
908 | } | ||
909 | |||
910 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | if (curves->plot_filename && !curves->saved_plot) { |
911 | ✗ | dump_curves(curves->plot_filename, curves->graph, comp_points, curves->lut_size); | |
912 | ✗ | curves->saved_plot = 1; | |
913 | } | ||
914 | |||
915 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | for (i = 0; i < NB_COMP + 1; i++) { |
916 | 4 | struct keypoint *point = comp_points[i]; | |
917 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4 times.
|
13 | while (point) { |
918 | 9 | struct keypoint *next = point->next; | |
919 | 9 | av_free(point); | |
920 | 9 | point = next; | |
921 | } | ||
922 | } | ||
923 | |||
924 | 1 | return 0; | |
925 | } | ||
926 | |||
927 | 5 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
928 | { | ||
929 | 5 | AVFilterContext *ctx = inlink->dst; | |
930 | 5 | CurvesContext *curves = ctx->priv; | |
931 | 5 | AVFilterLink *outlink = ctx->outputs[0]; | |
932 | AVFrame *out; | ||
933 | ThreadData td; | ||
934 | |||
935 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | if (av_frame_is_writable(in)) { |
936 | 5 | out = in; | |
937 | } else { | ||
938 | ✗ | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
939 | ✗ | if (!out) { | |
940 | ✗ | av_frame_free(&in); | |
941 | ✗ | return AVERROR(ENOMEM); | |
942 | } | ||
943 | ✗ | av_frame_copy_props(out, in); | |
944 | } | ||
945 | |||
946 | 5 | td.in = in; | |
947 | 5 | td.out = out; | |
948 | 5 | ff_filter_execute(ctx, curves->filter_slice, &td, NULL, | |
949 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | FFMIN(outlink->h, ff_filter_get_nb_threads(ctx))); |
950 | |||
951 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (out != in) |
952 | ✗ | av_frame_free(&in); | |
953 | |||
954 | 5 | return ff_filter_frame(outlink, out); | |
955 | } | ||
956 | |||
957 | ✗ | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
958 | char *res, int res_len, int flags) | ||
959 | { | ||
960 | ✗ | CurvesContext *curves = ctx->priv; | |
961 | int ret; | ||
962 | |||
963 | ✗ | if (!strcmp(cmd, "plot")) { | |
964 | ✗ | curves->saved_plot = 0; | |
965 | ✗ | } else if (!strcmp(cmd, "all") || !strcmp(cmd, "preset") || !strcmp(cmd, "psfile") || !strcmp(cmd, "interp")) { | |
966 | ✗ | if (!strcmp(cmd, "psfile")) | |
967 | ✗ | curves->parsed_psfile = 0; | |
968 | ✗ | av_freep(&curves->comp_points_str_all); | |
969 | ✗ | av_freep(&curves->comp_points_str[0]); | |
970 | ✗ | av_freep(&curves->comp_points_str[1]); | |
971 | ✗ | av_freep(&curves->comp_points_str[2]); | |
972 | ✗ | av_freep(&curves->comp_points_str[NB_COMP]); | |
973 | ✗ | } else if (!strcmp(cmd, "red") || !strcmp(cmd, "r")) { | |
974 | ✗ | av_freep(&curves->comp_points_str[0]); | |
975 | ✗ | } else if (!strcmp(cmd, "green") || !strcmp(cmd, "g")) { | |
976 | ✗ | av_freep(&curves->comp_points_str[1]); | |
977 | ✗ | } else if (!strcmp(cmd, "blue") || !strcmp(cmd, "b")) { | |
978 | ✗ | av_freep(&curves->comp_points_str[2]); | |
979 | ✗ | } else if (!strcmp(cmd, "master") || !strcmp(cmd, "m")) { | |
980 | ✗ | av_freep(&curves->comp_points_str[NB_COMP]); | |
981 | } | ||
982 | |||
983 | ✗ | ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags); | |
984 | ✗ | if (ret < 0) | |
985 | ✗ | return ret; | |
986 | |||
987 | ✗ | ret = curves_init(ctx); | |
988 | ✗ | if (ret < 0) | |
989 | ✗ | return ret; | |
990 | ✗ | return config_input(ctx->inputs[0]); | |
991 | } | ||
992 | |||
993 | 2 | static av_cold void curves_uninit(AVFilterContext *ctx) | |
994 | { | ||
995 | int i; | ||
996 | 2 | CurvesContext *curves = ctx->priv; | |
997 | |||
998 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | for (i = 0; i < NB_COMP + 1; i++) |
999 | 8 | av_freep(&curves->graph[i]); | |
1000 | 2 | } | |
1001 | |||
1002 | static const AVFilterPad curves_inputs[] = { | ||
1003 | { | ||
1004 | .name = "default", | ||
1005 | .type = AVMEDIA_TYPE_VIDEO, | ||
1006 | .filter_frame = filter_frame, | ||
1007 | .config_props = config_input, | ||
1008 | }, | ||
1009 | }; | ||
1010 | |||
1011 | const AVFilter ff_vf_curves = { | ||
1012 | .name = "curves", | ||
1013 | .description = NULL_IF_CONFIG_SMALL("Adjust components curves."), | ||
1014 | .priv_size = sizeof(CurvesContext), | ||
1015 | .init = curves_init, | ||
1016 | .uninit = curves_uninit, | ||
1017 | FILTER_INPUTS(curves_inputs), | ||
1018 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
1019 | FILTER_PIXFMTS(AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, | ||
1020 | AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, | ||
1021 | AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, | ||
1022 | AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR, | ||
1023 | AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0, | ||
1024 | AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48, | ||
1025 | AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64, | ||
1026 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, | ||
1027 | AV_PIX_FMT_GBRP9, | ||
1028 | AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRAP10, | ||
1029 | AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRAP12, | ||
1030 | AV_PIX_FMT_GBRP14, | ||
1031 | AV_PIX_FMT_GBRP16, AV_PIX_FMT_GBRAP16), | ||
1032 | .priv_class = &curves_class, | ||
1033 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, | ||
1034 | .process_command = process_command, | ||
1035 | }; | ||
1036 |