| 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 | }; |