Commit | Line | Data |
---|---|---|
2ba45a60 DM |
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/opt.h" | |
22 | #include "libavutil/bprint.h" | |
23 | #include "libavutil/eval.h" | |
24 | #include "libavutil/file.h" | |
25 | #include "libavutil/intreadwrite.h" | |
26 | #include "libavutil/avassert.h" | |
27 | #include "libavutil/pixdesc.h" | |
28 | #include "avfilter.h" | |
29 | #include "drawutils.h" | |
30 | #include "formats.h" | |
31 | #include "internal.h" | |
32 | #include "video.h" | |
33 | ||
34 | #define R 0 | |
35 | #define G 1 | |
36 | #define B 2 | |
37 | #define A 3 | |
38 | ||
39 | struct keypoint { | |
40 | double x, y; | |
41 | struct keypoint *next; | |
42 | }; | |
43 | ||
44 | #define NB_COMP 3 | |
45 | ||
46 | enum preset { | |
47 | PRESET_NONE, | |
48 | PRESET_COLOR_NEGATIVE, | |
49 | PRESET_CROSS_PROCESS, | |
50 | PRESET_DARKER, | |
51 | PRESET_INCREASE_CONTRAST, | |
52 | PRESET_LIGHTER, | |
53 | PRESET_LINEAR_CONTRAST, | |
54 | PRESET_MEDIUM_CONTRAST, | |
55 | PRESET_NEGATIVE, | |
56 | PRESET_STRONG_CONTRAST, | |
57 | PRESET_VINTAGE, | |
58 | NB_PRESETS, | |
59 | }; | |
60 | ||
61 | typedef struct { | |
62 | const AVClass *class; | |
63 | enum preset preset; | |
64 | char *comp_points_str[NB_COMP + 1]; | |
65 | char *comp_points_str_all; | |
66 | uint8_t graph[NB_COMP + 1][256]; | |
67 | char *psfile; | |
68 | uint8_t rgba_map[4]; | |
69 | int step; | |
70 | } CurvesContext; | |
71 | ||
72 | typedef struct ThreadData { | |
73 | AVFrame *in, *out; | |
74 | } ThreadData; | |
75 | ||
76 | #define OFFSET(x) offsetof(CurvesContext, x) | |
77 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | |
78 | static const AVOption curves_options[] = { | |
79 | { "preset", "select a color curves preset", OFFSET(preset), AV_OPT_TYPE_INT, {.i64=PRESET_NONE}, PRESET_NONE, NB_PRESETS-1, FLAGS, "preset_name" }, | |
80 | { "none", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NONE}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
81 | { "color_negative", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_COLOR_NEGATIVE}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
82 | { "cross_process", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_CROSS_PROCESS}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
83 | { "darker", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_DARKER}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
84 | { "increase_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_INCREASE_CONTRAST}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
85 | { "lighter", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_LIGHTER}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
86 | { "linear_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_LINEAR_CONTRAST}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
87 | { "medium_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_MEDIUM_CONTRAST}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
88 | { "negative", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NEGATIVE}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
89 | { "strong_contrast", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_STRONG_CONTRAST}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
90 | { "vintage", NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_VINTAGE}, INT_MIN, INT_MAX, FLAGS, "preset_name" }, | |
91 | { "master","set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
92 | { "m", "set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
93 | { "red", "set red points coordinates", OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
94 | { "r", "set red points coordinates", OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
95 | { "green", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
96 | { "g", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
97 | { "blue", "set blue points coordinates", OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
98 | { "b", "set blue points coordinates", OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
99 | { "all", "set points coordinates for all components", OFFSET(comp_points_str_all), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
100 | { "psfile", "set Photoshop curves file name", OFFSET(psfile), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, | |
101 | { NULL } | |
102 | }; | |
103 | ||
104 | AVFILTER_DEFINE_CLASS(curves); | |
105 | ||
106 | static const struct { | |
107 | const char *r; | |
108 | const char *g; | |
109 | const char *b; | |
110 | const char *master; | |
111 | } curves_presets[] = { | |
112 | [PRESET_COLOR_NEGATIVE] = { | |
113 | "0/1 0.129/1 0.466/0.498 0.725/0 1/0", | |
114 | "0/1 0.109/1 0.301/0.498 0.517/0 1/0", | |
115 | "0/1 0.098/1 0.235/0.498 0.423/0 1/0", | |
116 | }, | |
117 | [PRESET_CROSS_PROCESS] = { | |
118 | "0.25/0.156 0.501/0.501 0.686/0.745", | |
119 | "0.25/0.188 0.38/0.501 0.745/0.815 1/0.815", | |
120 | "0.231/0.094 0.709/0.874", | |
121 | }, | |
122 | [PRESET_DARKER] = { .master = "0.5/0.4" }, | |
123 | [PRESET_INCREASE_CONTRAST] = { .master = "0.149/0.066 0.831/0.905 0.905/0.98" }, | |
124 | [PRESET_LIGHTER] = { .master = "0.4/0.5" }, | |
125 | [PRESET_LINEAR_CONTRAST] = { .master = "0.305/0.286 0.694/0.713" }, | |
126 | [PRESET_MEDIUM_CONTRAST] = { .master = "0.286/0.219 0.639/0.643" }, | |
127 | [PRESET_NEGATIVE] = { .master = "0/1 1/0" }, | |
128 | [PRESET_STRONG_CONTRAST] = { .master = "0.301/0.196 0.592/0.6 0.686/0.737" }, | |
129 | [PRESET_VINTAGE] = { | |
130 | "0/0.11 0.42/0.51 1/0.95", | |
131 | "0.50/0.48", | |
132 | "0/0.22 0.49/0.44 1/0.8", | |
133 | } | |
134 | }; | |
135 | ||
136 | static struct keypoint *make_point(double x, double y, struct keypoint *next) | |
137 | { | |
138 | struct keypoint *point = av_mallocz(sizeof(*point)); | |
139 | ||
140 | if (!point) | |
141 | return NULL; | |
142 | point->x = x; | |
143 | point->y = y; | |
144 | point->next = next; | |
145 | return point; | |
146 | } | |
147 | ||
148 | static int parse_points_str(AVFilterContext *ctx, struct keypoint **points, const char *s) | |
149 | { | |
150 | char *p = (char *)s; // strtod won't alter the string | |
151 | struct keypoint *last = NULL; | |
152 | ||
153 | /* construct a linked list based on the key points string */ | |
154 | while (p && *p) { | |
155 | struct keypoint *point = make_point(0, 0, NULL); | |
156 | if (!point) | |
157 | return AVERROR(ENOMEM); | |
158 | point->x = av_strtod(p, &p); if (p && *p) p++; | |
159 | point->y = av_strtod(p, &p); if (p && *p) p++; | |
160 | if (point->x < 0 || point->x > 1 || point->y < 0 || point->y > 1) { | |
161 | av_log(ctx, AV_LOG_ERROR, "Invalid key point coordinates (%f;%f), " | |
162 | "x and y must be in the [0;1] range.\n", point->x, point->y); | |
163 | return AVERROR(EINVAL); | |
164 | } | |
165 | if (!*points) | |
166 | *points = point; | |
167 | if (last) { | |
168 | if ((int)(last->x * 255) >= (int)(point->x * 255)) { | |
169 | av_log(ctx, AV_LOG_ERROR, "Key point coordinates (%f;%f) " | |
170 | "and (%f;%f) are too close from each other or not " | |
171 | "strictly increasing on the x-axis\n", | |
172 | last->x, last->y, point->x, point->y); | |
173 | return AVERROR(EINVAL); | |
174 | } | |
175 | last->next = point; | |
176 | } | |
177 | last = point; | |
178 | } | |
179 | ||
180 | /* auto insert first key point if missing at x=0 */ | |
181 | if (!*points) { | |
182 | last = make_point(0, 0, NULL); | |
183 | if (!last) | |
184 | return AVERROR(ENOMEM); | |
185 | last->x = last->y = 0; | |
186 | *points = last; | |
187 | } else if ((*points)->x != 0.) { | |
188 | struct keypoint *newfirst = make_point(0, 0, *points); | |
189 | if (!newfirst) | |
190 | return AVERROR(ENOMEM); | |
191 | *points = newfirst; | |
192 | } | |
193 | ||
194 | av_assert0(last); | |
195 | ||
196 | /* auto insert last key point if missing at x=1 */ | |
197 | if (last->x != 1.) { | |
198 | struct keypoint *point = make_point(1, 1, NULL); | |
199 | if (!point) | |
200 | return AVERROR(ENOMEM); | |
201 | last->next = point; | |
202 | } | |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
207 | static int get_nb_points(const struct keypoint *d) | |
208 | { | |
209 | int n = 0; | |
210 | while (d) { | |
211 | n++; | |
212 | d = d->next; | |
213 | } | |
214 | return n; | |
215 | } | |
216 | ||
217 | /** | |
218 | * Natural cubic spline interpolation | |
219 | * Finding curves using Cubic Splines notes by Steven Rauch and John Stockie. | |
220 | * @see http://people.math.sfu.ca/~stockie/teaching/macm316/notes/splines.pdf | |
221 | */ | |
222 | static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint *points) | |
223 | { | |
224 | int i, ret = 0; | |
225 | const struct keypoint *point; | |
226 | double xprev = 0; | |
227 | ||
228 | int n = get_nb_points(points); // number of splines | |
229 | ||
230 | double (*matrix)[3] = av_calloc(n, sizeof(*matrix)); | |
231 | double *h = av_malloc((n - 1) * sizeof(*h)); | |
232 | double *r = av_calloc(n, sizeof(*r)); | |
233 | ||
234 | if (!matrix || !h || !r) { | |
235 | ret = AVERROR(ENOMEM); | |
236 | goto end; | |
237 | } | |
238 | ||
239 | /* h(i) = x(i+1) - x(i) */ | |
240 | i = -1; | |
241 | for (point = points; point; point = point->next) { | |
242 | if (i != -1) | |
243 | h[i] = point->x - xprev; | |
244 | xprev = point->x; | |
245 | i++; | |
246 | } | |
247 | ||
248 | /* right-side of the polynomials, will be modified to contains the solution */ | |
249 | point = points; | |
250 | for (i = 1; i < n - 1; i++) { | |
251 | double yp = point->y, | |
252 | yc = point->next->y, | |
253 | yn = point->next->next->y; | |
254 | r[i] = 6 * ((yn-yc)/h[i] - (yc-yp)/h[i-1]); | |
255 | point = point->next; | |
256 | } | |
257 | ||
258 | #define BD 0 /* sub diagonal (below main) */ | |
259 | #define MD 1 /* main diagonal (center) */ | |
260 | #define AD 2 /* sup diagonal (above main) */ | |
261 | ||
262 | /* left side of the polynomials into a tridiagonal matrix. */ | |
263 | matrix[0][MD] = matrix[n - 1][MD] = 1; | |
264 | for (i = 1; i < n - 1; i++) { | |
265 | matrix[i][BD] = h[i-1]; | |
266 | matrix[i][MD] = 2 * (h[i-1] + h[i]); | |
267 | matrix[i][AD] = h[i]; | |
268 | } | |
269 | ||
270 | /* tridiagonal solving of the linear system */ | |
271 | for (i = 1; i < n; i++) { | |
272 | double den = matrix[i][MD] - matrix[i][BD] * matrix[i-1][AD]; | |
273 | double k = den ? 1./den : 1.; | |
274 | matrix[i][AD] *= k; | |
275 | r[i] = (r[i] - matrix[i][BD] * r[i - 1]) * k; | |
276 | } | |
277 | for (i = n - 2; i >= 0; i--) | |
278 | r[i] = r[i] - matrix[i][AD] * r[i + 1]; | |
279 | ||
280 | /* compute the graph with x=[0..255] */ | |
281 | i = 0; | |
282 | point = points; | |
283 | av_assert0(point->next); // always at least 2 key points | |
284 | while (point->next) { | |
285 | double yc = point->y; | |
286 | double yn = point->next->y; | |
287 | ||
288 | double a = yc; | |
289 | double b = (yn-yc)/h[i] - h[i]*r[i]/2. - h[i]*(r[i+1]-r[i])/6.; | |
290 | double c = r[i] / 2.; | |
291 | double d = (r[i+1] - r[i]) / (6.*h[i]); | |
292 | ||
293 | int x; | |
294 | int x_start = point->x * 255; | |
295 | int x_end = point->next->x * 255; | |
296 | ||
297 | av_assert0(x_start >= 0 && x_start <= 255 && | |
298 | x_end >= 0 && x_end <= 255); | |
299 | ||
300 | for (x = x_start; x <= x_end; x++) { | |
301 | double xx = (x - x_start) * 1/255.; | |
302 | double yy = a + b*xx + c*xx*xx + d*xx*xx*xx; | |
303 | y[x] = av_clipf(yy, 0, 1) * 255; | |
304 | av_log(ctx, AV_LOG_DEBUG, "f(%f)=%f -> y[%d]=%d\n", xx, yy, x, y[x]); | |
305 | } | |
306 | ||
307 | point = point->next; | |
308 | i++; | |
309 | } | |
310 | ||
311 | end: | |
312 | av_free(matrix); | |
313 | av_free(h); | |
314 | av_free(r); | |
315 | return ret; | |
316 | } | |
317 | ||
318 | static int parse_psfile(AVFilterContext *ctx, const char *fname) | |
319 | { | |
320 | CurvesContext *curves = ctx->priv; | |
321 | uint8_t *buf; | |
322 | size_t size; | |
323 | int i, ret, av_unused(version), nb_curves; | |
324 | AVBPrint ptstr; | |
325 | static const int comp_ids[] = {3, 0, 1, 2}; | |
326 | ||
327 | av_bprint_init(&ptstr, 0, AV_BPRINT_SIZE_AUTOMATIC); | |
328 | ||
329 | ret = av_file_map(fname, &buf, &size, 0, NULL); | |
330 | if (ret < 0) | |
331 | return ret; | |
332 | ||
333 | #define READ16(dst) do { \ | |
334 | if (size < 2) { \ | |
335 | ret = AVERROR_INVALIDDATA; \ | |
336 | goto end; \ | |
337 | } \ | |
338 | dst = AV_RB16(buf); \ | |
339 | buf += 2; \ | |
340 | size -= 2; \ | |
341 | } while (0) | |
342 | ||
343 | READ16(version); | |
344 | READ16(nb_curves); | |
345 | for (i = 0; i < FFMIN(nb_curves, FF_ARRAY_ELEMS(comp_ids)); i++) { | |
346 | int nb_points, n; | |
347 | av_bprint_clear(&ptstr); | |
348 | READ16(nb_points); | |
349 | for (n = 0; n < nb_points; n++) { | |
350 | int y, x; | |
351 | READ16(y); | |
352 | READ16(x); | |
353 | av_bprintf(&ptstr, "%f/%f ", x / 255., y / 255.); | |
354 | } | |
355 | if (*ptstr.str) { | |
356 | char **pts = &curves->comp_points_str[comp_ids[i]]; | |
357 | if (!*pts) { | |
358 | *pts = av_strdup(ptstr.str); | |
359 | av_log(ctx, AV_LOG_DEBUG, "curves %d (intid=%d) [%d points]: [%s]\n", | |
360 | i, comp_ids[i], nb_points, *pts); | |
361 | if (!*pts) { | |
362 | ret = AVERROR(ENOMEM); | |
363 | goto end; | |
364 | } | |
365 | } | |
366 | } | |
367 | } | |
368 | end: | |
369 | av_bprint_finalize(&ptstr, NULL); | |
370 | av_file_unmap(buf, size); | |
371 | return ret; | |
372 | } | |
373 | ||
374 | static av_cold int init(AVFilterContext *ctx) | |
375 | { | |
376 | int i, j, ret; | |
377 | CurvesContext *curves = ctx->priv; | |
378 | struct keypoint *comp_points[NB_COMP + 1] = {0}; | |
379 | char **pts = curves->comp_points_str; | |
380 | const char *allp = curves->comp_points_str_all; | |
381 | ||
382 | //if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all) | |
383 | // allp = curves_presets[curves->preset].all; | |
384 | ||
385 | if (allp) { | |
386 | for (i = 0; i < NB_COMP; i++) { | |
387 | if (!pts[i]) | |
388 | pts[i] = av_strdup(allp); | |
389 | if (!pts[i]) | |
390 | return AVERROR(ENOMEM); | |
391 | } | |
392 | } | |
393 | ||
394 | if (curves->psfile) { | |
395 | ret = parse_psfile(ctx, curves->psfile); | |
396 | if (ret < 0) | |
397 | return ret; | |
398 | } | |
399 | ||
400 | if (curves->preset != PRESET_NONE) { | |
401 | #define SET_COMP_IF_NOT_SET(n, name) do { \ | |
402 | if (!pts[n] && curves_presets[curves->preset].name) { \ | |
403 | pts[n] = av_strdup(curves_presets[curves->preset].name); \ | |
404 | if (!pts[n]) \ | |
405 | return AVERROR(ENOMEM); \ | |
406 | } \ | |
407 | } while (0) | |
408 | SET_COMP_IF_NOT_SET(0, r); | |
409 | SET_COMP_IF_NOT_SET(1, g); | |
410 | SET_COMP_IF_NOT_SET(2, b); | |
411 | SET_COMP_IF_NOT_SET(3, master); | |
412 | } | |
413 | ||
414 | for (i = 0; i < NB_COMP + 1; i++) { | |
415 | ret = parse_points_str(ctx, comp_points + i, curves->comp_points_str[i]); | |
416 | if (ret < 0) | |
417 | return ret; | |
418 | ret = interpolate(ctx, curves->graph[i], comp_points[i]); | |
419 | if (ret < 0) | |
420 | return ret; | |
421 | } | |
422 | ||
423 | if (pts[NB_COMP]) { | |
424 | for (i = 0; i < NB_COMP; i++) | |
425 | for (j = 0; j < 256; j++) | |
426 | curves->graph[i][j] = curves->graph[NB_COMP][curves->graph[i][j]]; | |
427 | } | |
428 | ||
429 | if (av_log_get_level() >= AV_LOG_VERBOSE) { | |
430 | for (i = 0; i < NB_COMP; i++) { | |
431 | struct keypoint *point = comp_points[i]; | |
432 | av_log(ctx, AV_LOG_VERBOSE, "#%d points:", i); | |
433 | while (point) { | |
434 | av_log(ctx, AV_LOG_VERBOSE, " (%f;%f)", point->x, point->y); | |
435 | point = point->next; | |
436 | } | |
437 | av_log(ctx, AV_LOG_VERBOSE, "\n"); | |
438 | av_log(ctx, AV_LOG_VERBOSE, "#%d values:", i); | |
439 | for (j = 0; j < 256; j++) | |
440 | av_log(ctx, AV_LOG_VERBOSE, " %02X", curves->graph[i][j]); | |
441 | av_log(ctx, AV_LOG_VERBOSE, "\n"); | |
442 | } | |
443 | } | |
444 | ||
445 | for (i = 0; i < NB_COMP + 1; i++) { | |
446 | struct keypoint *point = comp_points[i]; | |
447 | while (point) { | |
448 | struct keypoint *next = point->next; | |
449 | av_free(point); | |
450 | point = next; | |
451 | } | |
452 | } | |
453 | ||
454 | return 0; | |
455 | } | |
456 | ||
457 | static int query_formats(AVFilterContext *ctx) | |
458 | { | |
459 | static const enum AVPixelFormat pix_fmts[] = { | |
460 | AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, | |
461 | AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, | |
462 | AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, | |
463 | AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR, | |
464 | AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0, | |
465 | AV_PIX_FMT_NONE | |
466 | }; | |
467 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); | |
468 | return 0; | |
469 | } | |
470 | ||
471 | static int config_input(AVFilterLink *inlink) | |
472 | { | |
473 | CurvesContext *curves = inlink->dst->priv; | |
474 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
475 | ||
476 | ff_fill_rgba_map(curves->rgba_map, inlink->format); | |
477 | curves->step = av_get_padded_bits_per_pixel(desc) >> 3; | |
478 | ||
479 | return 0; | |
480 | } | |
481 | ||
482 | static int filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
483 | { | |
484 | int x, y; | |
485 | const CurvesContext *curves = ctx->priv; | |
486 | const ThreadData *td = arg; | |
487 | const AVFrame *in = td->in; | |
488 | const AVFrame *out = td->out; | |
489 | const int direct = out == in; | |
490 | const int step = curves->step; | |
491 | const uint8_t r = curves->rgba_map[R]; | |
492 | const uint8_t g = curves->rgba_map[G]; | |
493 | const uint8_t b = curves->rgba_map[B]; | |
494 | const uint8_t a = curves->rgba_map[A]; | |
495 | const int slice_start = (in->height * jobnr ) / nb_jobs; | |
496 | const int slice_end = (in->height * (jobnr+1)) / nb_jobs; | |
497 | uint8_t *dst = out->data[0] + slice_start * out->linesize[0]; | |
498 | const uint8_t *src = in->data[0] + slice_start * in->linesize[0]; | |
499 | ||
500 | for (y = slice_start; y < slice_end; y++) { | |
501 | for (x = 0; x < in->width * step; x += step) { | |
502 | dst[x + r] = curves->graph[R][src[x + r]]; | |
503 | dst[x + g] = curves->graph[G][src[x + g]]; | |
504 | dst[x + b] = curves->graph[B][src[x + b]]; | |
505 | if (!direct && step == 4) | |
506 | dst[x + a] = src[x + a]; | |
507 | } | |
508 | dst += out->linesize[0]; | |
509 | src += in ->linesize[0]; | |
510 | } | |
511 | return 0; | |
512 | } | |
513 | ||
514 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
515 | { | |
516 | AVFilterContext *ctx = inlink->dst; | |
517 | AVFilterLink *outlink = ctx->outputs[0]; | |
518 | AVFrame *out; | |
519 | ThreadData td; | |
520 | ||
521 | if (av_frame_is_writable(in)) { | |
522 | out = in; | |
523 | } else { | |
524 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
525 | if (!out) { | |
526 | av_frame_free(&in); | |
527 | return AVERROR(ENOMEM); | |
528 | } | |
529 | av_frame_copy_props(out, in); | |
530 | } | |
531 | ||
532 | td.in = in; | |
533 | td.out = out; | |
534 | ctx->internal->execute(ctx, filter_slice, &td, NULL, FFMIN(outlink->h, ctx->graph->nb_threads)); | |
535 | ||
536 | if (out != in) | |
537 | av_frame_free(&in); | |
538 | ||
539 | return ff_filter_frame(outlink, out); | |
540 | } | |
541 | ||
542 | static const AVFilterPad curves_inputs[] = { | |
543 | { | |
544 | .name = "default", | |
545 | .type = AVMEDIA_TYPE_VIDEO, | |
546 | .filter_frame = filter_frame, | |
547 | .config_props = config_input, | |
548 | }, | |
549 | { NULL } | |
550 | }; | |
551 | ||
552 | static const AVFilterPad curves_outputs[] = { | |
553 | { | |
554 | .name = "default", | |
555 | .type = AVMEDIA_TYPE_VIDEO, | |
556 | }, | |
557 | { NULL } | |
558 | }; | |
559 | ||
560 | AVFilter ff_vf_curves = { | |
561 | .name = "curves", | |
562 | .description = NULL_IF_CONFIG_SMALL("Adjust components curves."), | |
563 | .priv_size = sizeof(CurvesContext), | |
564 | .init = init, | |
565 | .query_formats = query_formats, | |
566 | .inputs = curves_inputs, | |
567 | .outputs = curves_outputs, | |
568 | .priv_class = &curves_class, | |
569 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, | |
570 | }; |