| @@ -0,0 +1,582 @@ |
| | 1 | /* |
| | 2 | * Copyright (c) 2022 Paul B Mahol |
| | 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 | /** |
| | 22 | * @file |
| | 23 | * Compute a look-up table from map of colors. |
| | 24 | */ |
| | 25 | |
| | 26 | #include "config_components.h" |
| | 27 | |
| | 28 | #include "libavutil/attributes.h" |
| | 29 | #include "libavutil/avstring.h" |
| | 30 | #include "libavutil/common.h" |
| | 31 | #include "libavutil/opt.h" |
| | 32 | #include "libavutil/pixdesc.h" |
| | 33 | #include "avfilter.h" |
| | 34 | #include "formats.h" |
| | 35 | #include "internal.h" |
| | 36 | #include "framesync.h" |
| | 37 | #include "video.h" |
| | 38 | |
| | 39 | #define MAX_SIZE 64 |
| | 40 | |
| | 41 | enum KernelType { |
| | 42 | EUCLIDEAN, |
| | 43 | WEUCLIDEAN, |
| | 44 | NB_KERNELS, |
| | 45 | }; |
| | 46 | |
| | 47 | typedef struct ColorMapContext { |
| | 48 | const AVClass *class; |
| | 49 | int w, h; |
| | 50 | int size; |
| | 51 | int nb_maps; |
| | 52 | int changed[2]; |
| | 53 | |
| | 54 | float source[MAX_SIZE][4]; |
| | 55 | float ttarget[MAX_SIZE][4]; |
| | 56 | float target[MAX_SIZE][4]; |
| | 57 | float icoeff[4][4]; |
| | 58 | float coeff[MAX_SIZE][4]; |
| | 59 | |
| | 60 | int target_type; |
| | 61 | int kernel_type; |
| | 62 | float (*kernel)(const float *x, const float *y); |
| | 63 | |
| | 64 | FFFrameSync fs; |
| | 65 | } ColorMapContext; |
| | 66 | |
| | 67 | #define OFFSET(x) offsetof(ColorMapContext, x) |
| | 68 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM |
| | 69 | |
| | 70 | static const AVOption colormap_options[] = { |
| | 71 | { "patch_size", "set patch size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "64x64"}, 0, 0, FLAGS }, |
| | 72 | { "nb_patches", "set number of patches", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 8}, 1, MAX_SIZE, FLAGS }, |
| | 73 | { "type", "set the target type used", OFFSET(target_type), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS, "type" }, |
| | 74 | { "relative", "the target colors are relative", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 1, FLAGS, "type" }, |
| | 75 | { "absolute", "the target colors are absolute", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 1, FLAGS, "type" }, |
| | 76 | { "kernel", "set the kernel used for measuring color difference", OFFSET(kernel_type), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_KERNELS-1, FLAGS, "kernel" }, |
| | 77 | { "euclidean", "square root of sum of squared differences", 0, AV_OPT_TYPE_CONST, {.i64=EUCLIDEAN}, 0, 0, FLAGS, "kernel" }, |
| | 78 | { "weuclidean", "weighted square root of sum of squared differences",0, AV_OPT_TYPE_CONST, {.i64=WEUCLIDEAN}, 0, 0, FLAGS, "kernel" }, |
| | 79 | { NULL } |
| | 80 | }; |
| | 81 | |
| | 82 | static int gauss_make_triangular(double *A, int *p, int n) |
| | 83 | { |
| | 84 | p[n - 1] = n - 1; |
| | 85 | for (int k = 0; k < n; k++) { |
| | 86 | double t1; |
| | 87 | int m = k; |
| | 88 | |
| | 89 | for (int i = k + 1; i < n; i++) |
| | 90 | if (fabs(A[k + n * i]) > fabs(A[k + n * m])) |
| | 91 | m = i; |
| | 92 | p[k] = m; |
| | 93 | t1 = A[k + n * m]; |
| | 94 | A[k + n * m] = A[k + n * k]; |
| | 95 | A[k + n * k] = t1; |
| | 96 | if (t1 != 0) { |
| | 97 | for (int i = k + 1; i < n; i++) |
| | 98 | A[k + n * i] /= -t1; |
| | 99 | if (k != m) |
| | 100 | for (int i = k + 1; i < n; i++) { |
| | 101 | double t2 = A[i + n * m]; |
| | 102 | A[i + n * m] = A[i + n * k]; |
| | 103 | A[i + n * k] = t2; |
| | 104 | } |
| | 105 | for (int j = k + 1; j < n; j++) |
| | 106 | for (int i = k + 1; i < n; i++) |
| | 107 | A[i + n * j] += A[k + j * n] * A[i + k * n]; |
| | 108 | } else { |
| | 109 | return 0; |
| | 110 | } |
| | 111 | } |
| | 112 | |
| | 113 | return 1; |
| | 114 | } |
| | 115 | |
| | 116 | static void gauss_solve_triangular(const double *A, const int *p, double *b, int n) |
| | 117 | { |
| | 118 | for(int k = 0; k < n - 1; k++) { |
| | 119 | int m = p[k]; |
| | 120 | double t = b[m]; |
| | 121 | b[m] = b[k]; |
| | 122 | b[k] = t; |
| | 123 | for (int i = k + 1; i < n; i++) |
| | 124 | b[i] += A[k + n * i] * t; |
| | 125 | } |
| | 126 | |
| | 127 | for(int k = n - 1; k > 0; k--) { |
| | 128 | b[k] /= A[k + n * k]; |
| | 129 | double t = b[k]; |
| | 130 | for (int i = 0; i < k; i++) |
| | 131 | b[i] -= A[k + n * i] * t; |
| | 132 | } |
| | 133 | |
| | 134 | b[0] /= A[0 + 0 * n]; |
| | 135 | } |
| | 136 | |
| | 137 | static int gauss_solve(double *A, double *b, int n) |
| | 138 | { |
| | 139 | int *p = av_calloc(n, sizeof(*p)); |
| | 140 | |
| | 141 | if (!p) |
| | 142 | return 1; |
| | 143 | |
| | 144 | if (!gauss_make_triangular(A, p, n)) { |
| | 145 | av_freep(&p); |
| | 146 | return 1; |
| | 147 | } |
| | 148 | |
| | 149 | gauss_solve_triangular(A, p, b, n); |
| | 150 | |
| | 151 | av_freep(&p); |
| | 152 | |
| | 153 | return 0; |
| | 154 | } |
| | 155 | |
| | 156 | #define P2(x) ((x)*(x)) |
| | 157 | |
| | 158 | static float euclidean_kernel(const float *x, const float *y) |
| | 159 | { |
| | 160 | const float d2 = P2(x[0]-y[0]) + |
| | 161 | P2(x[1]-y[1]) + |
| | 162 | P2(x[2]-y[2]); |
| | 163 | return sqrtf(d2); |
| | 164 | } |
| | 165 | |
| | 166 | static float weuclidean_kernel(const float *x, const float *y) |
| | 167 | { |
| | 168 | const float rm = (x[0] + y[0]) * 0.5f; |
| | 169 | const float d2 = P2(x[0]-y[0]) * (2.f + rm) + |
| | 170 | P2(x[1]-y[1]) * 4.f + |
| | 171 | P2(x[2]-y[2]) * (3.f - rm); |
| | 172 | return sqrtf(d2); |
| | 173 | } |
| | 174 | |
| | 175 | static void build_map(AVFilterContext *ctx) |
| | 176 | { |
| | 177 | ColorMapContext *s = ctx->priv; |
| | 178 | |
| | 179 | for (int j = 0; j < s->nb_maps; j++) { |
| | 180 | s->target[j][0] = s->target_type == 0 ? s->source[j][0] + s->ttarget[j][0] : s->ttarget[j][0]; |
| | 181 | s->target[j][1] = s->target_type == 0 ? s->source[j][1] + s->ttarget[j][1] : s->ttarget[j][1]; |
| | 182 | s->target[j][2] = s->target_type == 0 ? s->source[j][2] + s->ttarget[j][2] : s->ttarget[j][2]; |
| | 183 | } |
| | 184 | |
| | 185 | for (int c = 0; c < 3; c++) { |
| | 186 | for (int j = 0; j < s->nb_maps; j++) |
| | 187 | s->coeff[j][c] = 0.f; |
| | 188 | |
| | 189 | for (int j = 0; j < 4; j++) { |
| | 190 | s->icoeff[j][c] = 0; |
| | 191 | s->icoeff[j][c] = 0; |
| | 192 | s->icoeff[j][c] = 0; |
| | 193 | } |
| | 194 | |
| | 195 | s->icoeff[c+1][c] = 1.f; |
| | 196 | |
| | 197 | switch (s->nb_maps) { |
| | 198 | case 1: |
| | 199 | { |
| | 200 | float div = fabsf(s->source[0][c]) < 1e-6f ? 1e-6f : s->source[0][c]; |
| | 201 | s->icoeff[c][1+c] = s->target[0][c] / div; |
| | 202 | } |
| | 203 | break; |
| | 204 | case 2: |
| | 205 | { |
| | 206 | double A[2 * 2] = { 1, s->source[0][c], |
| | 207 | 1, s->source[1][c] }; |
| | 208 | double b[2] = { s->target[0][c], s->target[1][c] }; |
| | 209 | |
| | 210 | if (gauss_solve(A, b, 2)) |
| | 211 | continue; |
| | 212 | |
| | 213 | s->icoeff[0 ][c] = b[0]; |
| | 214 | s->icoeff[1+c][c] = b[1]; |
| | 215 | } |
| | 216 | break; |
| | 217 | case 3: |
| | 218 | { |
| | 219 | const uint8_t idx[3][3] = {{ 0, 1, 2 }, |
| | 220 | { 1, 0, 2 }, |
| | 221 | { 2, 0, 1 }}; |
| | 222 | const uint8_t didx[3][4] = {{ 0, 1, 2, 2 }, |
| | 223 | { 0, 2, 1, 2 }, |
| | 224 | { 0, 2, 2, 1 }}; |
| | 225 | const int C0 = idx[c][0]; |
| | 226 | const int C1 = idx[c][1]; |
| | 227 | const int C2 = idx[c][2]; |
| | 228 | double A[3 * 3] = { 1, s->source[0][C0], s->source[0][C1] + s->source[0][C2], |
| | 229 | 1, s->source[1][C0], s->source[1][C1] + s->source[1][C2], |
| | 230 | 1, s->source[2][C0], s->source[2][C1] + s->source[2][C2] }; |
| | 231 | double b[3] = { s->target[0][c], s->target[1][c], s->target[2][c] }; |
| | 232 | |
| | 233 | if (gauss_solve(A, b, 3)) |
| | 234 | continue; |
| | 235 | |
| | 236 | s->icoeff[0][c] = b[didx[c][0]]; |
| | 237 | s->icoeff[1][c] = b[didx[c][1]]; |
| | 238 | s->icoeff[2][c] = b[didx[c][2]]; |
| | 239 | s->icoeff[3][c] = b[didx[c][3]]; |
| | 240 | } |
| | 241 | break; |
| | 242 | case 4: |
| | 243 | { |
| | 244 | double A[4 * 4] = { 1, s->source[0][0], s->source[0][1], s->source[0][2], |
| | 245 | 1, s->source[1][0], s->source[1][1], s->source[1][2], |
| | 246 | 1, s->source[2][0], s->source[2][1], s->source[2][2], |
| | 247 | 1, s->source[3][0], s->source[3][1], s->source[3][2] }; |
| | 248 | double b[4] = { s->target[0][c], s->target[1][c], s->target[2][c], s->target[3][c] }; |
| | 249 | int pivot[4]; |
| | 250 | |
| | 251 | if (!gauss_make_triangular(A, pivot, 4)) |
| | 252 | continue; |
| | 253 | gauss_solve_triangular(A, pivot, b, 4); |
| | 254 | |
| | 255 | s->icoeff[0][c] = b[0]; |
| | 256 | s->icoeff[1][c] = b[1]; |
| | 257 | s->icoeff[2][c] = b[2]; |
| | 258 | s->icoeff[3][c] = b[3]; |
| | 259 | } |
| | 260 | break; |
| | 261 | default: |
| | 262 | { |
| | 263 | const int N = s->nb_maps; |
| | 264 | const int N4 = N + 4; |
| | 265 | double *A = av_calloc(sizeof(*A), N4 * N4); |
| | 266 | double *b = av_calloc(sizeof(*b), N4); |
| | 267 | int *pivot = NULL; |
| | 268 | |
| | 269 | if (!A || !b) |
| | 270 | goto error; |
| | 271 | |
| | 272 | for (int j = 0; j < N; j++) |
| | 273 | for (int i = j; i < N; i++) |
| | 274 | A[j*N4+i] = A[i*N4+j] = s->kernel(s->source[i], s->source[j]); |
| | 275 | |
| | 276 | for (int i = 0; i < N; i++) |
| | 277 | A[i*N4+N+0] = A[(N+0)*N4+i] = 1; |
| | 278 | for (int i = 0; i < N; i++) |
| | 279 | A[i*N4+N+1] = A[(N+1)*N4+i] = s->source[i][0]; |
| | 280 | for (int i = 0; i < N; i++) |
| | 281 | A[i*N4+N+2] = A[(N+2)*N4+i] = s->source[i][1]; |
| | 282 | for (int i = 0; i < N; i++) |
| | 283 | A[i*N4+N+3] = A[(N+3)*N4+i] = s->source[i][2]; |
| | 284 | |
| | 285 | for (int j = N; j < N4; j++) |
| | 286 | for (int i = N;i < N4; i++) |
| | 287 | A[j * N4 + i] = 0.; |
| | 288 | |
| | 289 | pivot = av_calloc(N4, sizeof(*pivot)); |
| | 290 | if (!pivot) |
| | 291 | goto error; |
| | 292 | |
| | 293 | if (gauss_make_triangular(A, pivot, N4)) { |
| | 294 | for (int i = 0; i < N; i++) |
| | 295 | b[i] = s->target[i][c]; |
| | 296 | for (int i = N; i < N + 4; i++) |
| | 297 | b[i] = 0; |
| | 298 | |
| | 299 | gauss_solve_triangular(A, pivot, b, N4); |
| | 300 | |
| | 301 | for (int i = 0; i < N; i++) |
| | 302 | s->coeff[i][c] = b[i]; |
| | 303 | |
| | 304 | for (int i = 0; i < 4; i++) |
| | 305 | s->icoeff[i][c] = b[N + i]; |
| | 306 | } |
| | 307 | error: |
| | 308 | av_free(pivot); |
| | 309 | av_free(b); |
| | 310 | av_free(A); |
| | 311 | } |
| | 312 | } |
| | 313 | } |
| | 314 | } |
| | 315 | |
| | 316 | typedef struct ThreadData { |
| | 317 | AVFrame *in, *out; |
| | 318 | } ThreadData; |
| | 319 | |
| | 320 | static int colormap_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) |
| | 321 | { |
| | 322 | ColorMapContext *s = ctx->priv; |
| | 323 | ThreadData *td = arg; |
| | 324 | AVFrame *in = td->in; |
| | 325 | AVFrame *out = td->out; |
| | 326 | const int maps = s->nb_maps; |
| | 327 | const int width = out->width; |
| | 328 | const int height = out->height; |
| | 329 | const int slice_start = (height * jobnr) / nb_jobs; |
| | 330 | const int slice_end = (height * (jobnr + 1)) / nb_jobs; |
| | 331 | const int sr_linesize = in->linesize[2] / 4; |
| | 332 | const int dr_linesize = out->linesize[2] / 4; |
| | 333 | const int sg_linesize = in->linesize[0] / 4; |
| | 334 | const int dg_linesize = out->linesize[0] / 4; |
| | 335 | const int sb_linesize = in->linesize[1] / 4; |
| | 336 | const int db_linesize = out->linesize[1] / 4; |
| | 337 | const float *sr = (float *)in->data[2] + slice_start * sr_linesize; |
| | 338 | const float *sg = (float *)in->data[0] + slice_start * sg_linesize; |
| | 339 | const float *sb = (float *)in->data[1] + slice_start * sb_linesize; |
| | 340 | float *r = (float *)out->data[2] + slice_start * dr_linesize; |
| | 341 | float *g = (float *)out->data[0] + slice_start * dg_linesize; |
| | 342 | float *b = (float *)out->data[1] + slice_start * db_linesize; |
| | 343 | float (*kernel)(const float *x, const float *y) = s->kernel; |
| | 344 | const float *icoeff[4] = { s->icoeff[0], s->icoeff[1], s->icoeff[2], s->icoeff[3] }; |
| | 345 | |
| | 346 | for (int y = slice_start; y < slice_end; y++) { |
| | 347 | for (int x = 0; x < width; x++) { |
| | 348 | const float input[3] = { sr[x], sg[x], sb[x] }; |
| | 349 | float srv, sgv, sbv; |
| | 350 | float rv, gv, bv; |
| | 351 | |
| | 352 | srv = sr[x]; |
| | 353 | sgv = sg[x]; |
| | 354 | sbv = sb[x]; |
| | 355 | |
| | 356 | rv = icoeff[0][0]; |
| | 357 | gv = icoeff[0][1]; |
| | 358 | bv = icoeff[0][2]; |
| | 359 | |
| | 360 | rv += icoeff[1][0] * srv + icoeff[2][0] * sgv + icoeff[3][0] * sbv; |
| | 361 | gv += icoeff[1][1] * srv + icoeff[2][1] * sgv + icoeff[3][1] * sbv; |
| | 362 | bv += icoeff[1][2] * srv + icoeff[2][2] * sgv + icoeff[3][2] * sbv; |
| | 363 | |
| | 364 | for (int z = 0; z < maps && maps > 4; z++) { |
| | 365 | const float *coeff = s->coeff[z]; |
| | 366 | const float cr = coeff[0]; |
| | 367 | const float cg = coeff[1]; |
| | 368 | const float cb = coeff[2]; |
| | 369 | const float f = kernel(input, s->source[z]); |
| | 370 | |
| | 371 | rv += f * cr; |
| | 372 | gv += f * cg; |
| | 373 | bv += f * cb; |
| | 374 | } |
| | 375 | |
| | 376 | r[x] = rv; |
| | 377 | g[x] = gv; |
| | 378 | b[x] = bv; |
| | 379 | } |
| | 380 | |
| | 381 | sg += sg_linesize; |
| | 382 | g += dg_linesize; |
| | 383 | sb += sb_linesize; |
| | 384 | b += db_linesize; |
| | 385 | sr += sr_linesize; |
| | 386 | r += dr_linesize; |
| | 387 | } |
| | 388 | |
| | 389 | return 0; |
| | 390 | } |
| | 391 | |
| | 392 | static int import_map(AVFilterLink *inlink, AVFrame *in) |
| | 393 | { |
| | 394 | AVFilterContext *ctx = inlink->dst; |
| | 395 | ColorMapContext *s = ctx->priv; |
| | 396 | const int is_target = FF_INLINK_IDX(inlink) > 1; |
| | 397 | const int pw = s->w; |
| | 398 | const int pw2 = s->w / 2; |
| | 399 | const int ph = s->h; |
| | 400 | const int ph2 = s->h / 2; |
| | 401 | int changed = 0; |
| | 402 | int idx; |
| | 403 | |
| | 404 | for (int plane = 0; plane < 3; plane++) { |
| | 405 | const int c = plane == 0 ? 1 : plane == 1 ? 2 : 0; |
| | 406 | |
| | 407 | idx = 0; |
| | 408 | for (int y = ph2; y < in->height && idx < MAX_SIZE; y += ph) { |
| | 409 | const float *src = (const float *)(in->data[plane] + y * in->linesize[plane]); |
| | 410 | |
| | 411 | for (int x = pw2; x < in->width && idx < MAX_SIZE; x += pw) { |
| | 412 | float value = src[x]; |
| | 413 | |
| | 414 | if (is_target) { |
| | 415 | if (s->ttarget[idx][c] != value) |
| | 416 | changed = 1; |
| | 417 | s->ttarget[idx][c] = value; |
| | 418 | } else { |
| | 419 | if (s->source[idx][c] != value) |
| | 420 | changed = 1; |
| | 421 | s->source[idx][c] = value; |
| | 422 | } |
| | 423 | |
| | 424 | idx++; |
| | 425 | } |
| | 426 | } |
| | 427 | } |
| | 428 | |
| | 429 | if (changed) |
| | 430 | s->changed[is_target] = 1; |
| | 431 | if (!is_target) |
| | 432 | s->nb_maps = FFMIN(idx, s->size); |
| | 433 | |
| | 434 | return 0; |
| | 435 | } |
| | 436 | |
| | 437 | static int process_frame(FFFrameSync *fs) |
| | 438 | { |
| | 439 | AVFilterContext *ctx = fs->parent; |
| | 440 | ColorMapContext *s = fs->opaque; |
| | 441 | AVFilterLink *outlink = ctx->outputs[0]; |
| | 442 | AVFrame *in, *out, *source, *target; |
| | 443 | ThreadData td; |
| | 444 | int ret; |
| | 445 | |
| | 446 | switch (s->kernel_type) { |
| | 447 | case EUCLIDEAN: |
| | 448 | s->kernel = euclidean_kernel; |
| | 449 | break; |
| | 450 | case WEUCLIDEAN: |
| | 451 | s->kernel = weuclidean_kernel; |
| | 452 | break; |
| | 453 | default: |
| | 454 | return AVERROR_BUG; |
| | 455 | } |
| | 456 | |
| | 457 | if ((ret = ff_framesync_get_frame(&s->fs, 0, &in, 1)) < 0 || |
| | 458 | (ret = ff_framesync_get_frame(&s->fs, 1, &source, 0)) < 0 || |
| | 459 | (ret = ff_framesync_get_frame(&s->fs, 2, &target, 0)) < 0) |
| | 460 | return ret; |
| | 461 | |
| | 462 | import_map(ctx->inputs[1], source); |
| | 463 | import_map(ctx->inputs[2], target); |
| | 464 | |
| | 465 | if (s->changed[0] || s->changed[1]) { |
| | 466 | build_map(ctx); |
| | 467 | s->changed[0] = s->changed[1] = 0; |
| | 468 | } |
| | 469 | |
| | 470 | if (!ctx->is_disabled) { |
| | 471 | if (av_frame_is_writable(in)) { |
| | 472 | out = in; |
| | 473 | } else { |
| | 474 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| | 475 | if (!out) { |
| | 476 | av_frame_free(&in); |
| | 477 | return AVERROR(ENOMEM); |
| | 478 | } |
| | 479 | av_frame_copy_props(out, in); |
| | 480 | } |
| | 481 | |
| | 482 | td.in = in; |
| | 483 | td.out = out; |
| | 484 | ff_filter_execute(ctx, colormap_slice, &td, NULL, |
| | 485 | FFMIN(in->height, ff_filter_get_nb_threads(ctx))); |
| | 486 | |
| | 487 | if (out != in) |
| | 488 | av_frame_free(&in); |
| | 489 | } else { |
| | 490 | out = in; |
| | 491 | } |
| | 492 | |
| | 493 | out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); |
| | 494 | |
| | 495 | return ff_filter_frame(outlink, out); |
| | 496 | } |
| | 497 | |
| | 498 | static int config_output(AVFilterLink *outlink) |
| | 499 | { |
| | 500 | AVFilterContext *ctx = outlink->src; |
| | 501 | ColorMapContext *s = ctx->priv; |
| | 502 | AVFilterLink *inlink = ctx->inputs[0]; |
| | 503 | AVFilterLink *source = ctx->inputs[1]; |
| | 504 | AVFilterLink *target = ctx->inputs[2]; |
| | 505 | FFFrameSyncIn *in; |
| | 506 | int ret; |
| | 507 | |
| | 508 | outlink->time_base = inlink->time_base; |
| | 509 | outlink->frame_rate = inlink->frame_rate; |
| | 510 | outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; |
| | 511 | outlink->w = inlink->w; |
| | 512 | outlink->h = inlink->h; |
| | 513 | |
| | 514 | if ((ret = ff_framesync_init(&s->fs, ctx, 3)) < 0) |
| | 515 | return ret; |
| | 516 | |
| | 517 | in = s->fs.in; |
| | 518 | in[0].time_base = inlink->time_base; |
| | 519 | in[1].time_base = source->time_base; |
| | 520 | in[2].time_base = target->time_base; |
| | 521 | in[0].sync = 1; |
| | 522 | in[0].before = EXT_STOP; |
| | 523 | in[0].after = EXT_INFINITY; |
| | 524 | in[1].sync = 1; |
| | 525 | in[1].before = EXT_STOP; |
| | 526 | in[1].after = EXT_INFINITY; |
| | 527 | in[2].sync = 1; |
| | 528 | in[2].before = EXT_STOP; |
| | 529 | in[2].after = EXT_INFINITY; |
| | 530 | s->fs.opaque = s; |
| | 531 | s->fs.on_event = process_frame; |
| | 532 | |
| | 533 | ret = ff_framesync_configure(&s->fs); |
| | 534 | outlink->time_base = s->fs.time_base; |
| | 535 | |
| | 536 | return ret; |
| | 537 | } |
| | 538 | |
| | 539 | static int activate(AVFilterContext *ctx) |
| | 540 | { |
| | 541 | ColorMapContext *s = ctx->priv; |
| | 542 | return ff_framesync_activate(&s->fs); |
| | 543 | } |
| | 544 | |
| | 545 | static const AVFilterPad inputs[] = { |
| | 546 | { |
| | 547 | .name = "default", |
| | 548 | .type = AVMEDIA_TYPE_VIDEO, |
| | 549 | }, |
| | 550 | { |
| | 551 | .name = "source", |
| | 552 | .type = AVMEDIA_TYPE_VIDEO, |
| | 553 | }, |
| | 554 | { |
| | 555 | .name = "target", |
| | 556 | .type = AVMEDIA_TYPE_VIDEO, |
| | 557 | }, |
| | 558 | }; |
| | 559 | |
| | 560 | static const AVFilterPad outputs[] = { |
| | 561 | { |
| | 562 | .name = "default", |
| | 563 | .type = AVMEDIA_TYPE_VIDEO, |
| | 564 | .config_props = config_output, |
| | 565 | }, |
| | 566 | }; |
| | 567 | |
| | 568 | AVFILTER_DEFINE_CLASS(colormap); |
| | 569 | |
| | 570 | const AVFilter ff_vf_colormap = { |
| | 571 | .name = "colormap", |
| | 572 | .description = NULL_IF_CONFIG_SMALL("Apply custom Color Maps to video stream."), |
| | 573 | .priv_class = &colormap_class, |
| | 574 | .priv_size = sizeof(ColorMapContext), |
| | 575 | .activate = activate, |
| | 576 | FILTER_INPUTS(inputs), |
| | 577 | FILTER_OUTPUTS(outputs), |
| | 578 | FILTER_PIXFMTS(AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32), |
| | 579 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | |
| | 580 | AVFILTER_FLAG_SLICE_THREADS, |
| | 581 | .process_command = ff_filter_process_command, |
| | 582 | }; |