X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavfilter%2Fvf_lut3d.c;h=fda85b16b4d1b5680127371590e23cd0b97b5438;hb=46d2b2071b3f42c6f885efad547080106985dd5e;hp=27b79b860bd88cd2b332bd3245953d24acd295b3;hpb=26148e923613e718787c6fc4bf3f64e8909f597c;p=ffmpeg diff --git a/libavfilter/vf_lut3d.c b/libavfilter/vf_lut3d.c index 27b79b860bd..fda85b16b4d 100644 --- a/libavfilter/vf_lut3d.c +++ b/libavfilter/vf_lut3d.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2013 Clément Bœsch + * Copyright (c) 2018 Paul B Mahol * * This file is part of FFmpeg. * @@ -54,7 +55,7 @@ struct rgbvec { /* 3D LUT don't often go up to level 32, but it is common to have a Hald CLUT * of 512x512 (64x64x64) */ -#define MAX_LEVEL 64 +#define MAX_LEVEL 256 typedef struct LUT3DContext { const AVClass *class; @@ -63,8 +64,10 @@ typedef struct LUT3DContext { uint8_t rgba_map[4]; int step; avfilter_action_func *interp; - struct rgbvec lut[MAX_LEVEL][MAX_LEVEL][MAX_LEVEL]; + struct rgbvec scale; + struct rgbvec *lut; int lutsize; + int lutsize2; #if CONFIG_HALDCLUT_FILTER uint8_t clut_rgba_map[4]; int clut_step; @@ -111,7 +114,7 @@ static inline struct rgbvec lerp(const struct rgbvec *v0, const struct rgbvec *v static inline struct rgbvec interp_nearest(const LUT3DContext *lut3d, const struct rgbvec *s) { - return lut3d->lut[NEAR(s->r)][NEAR(s->g)][NEAR(s->b)]; + return lut3d->lut[NEAR(s->r) * lut3d->lutsize2 + NEAR(s->g) * lut3d->lutsize + NEAR(s->b)]; } /** @@ -121,17 +124,19 @@ static inline struct rgbvec interp_nearest(const LUT3DContext *lut3d, static inline struct rgbvec interp_trilinear(const LUT3DContext *lut3d, const struct rgbvec *s) { + const int lutsize2 = lut3d->lutsize2; + const int lutsize = lut3d->lutsize; const int prev[] = {PREV(s->r), PREV(s->g), PREV(s->b)}; const int next[] = {NEXT(s->r), NEXT(s->g), NEXT(s->b)}; const struct rgbvec d = {s->r - prev[0], s->g - prev[1], s->b - prev[2]}; - const struct rgbvec c000 = lut3d->lut[prev[0]][prev[1]][prev[2]]; - const struct rgbvec c001 = lut3d->lut[prev[0]][prev[1]][next[2]]; - const struct rgbvec c010 = lut3d->lut[prev[0]][next[1]][prev[2]]; - const struct rgbvec c011 = lut3d->lut[prev[0]][next[1]][next[2]]; - const struct rgbvec c100 = lut3d->lut[next[0]][prev[1]][prev[2]]; - const struct rgbvec c101 = lut3d->lut[next[0]][prev[1]][next[2]]; - const struct rgbvec c110 = lut3d->lut[next[0]][next[1]][prev[2]]; - const struct rgbvec c111 = lut3d->lut[next[0]][next[1]][next[2]]; + const struct rgbvec c000 = lut3d->lut[prev[0] * lutsize2 + prev[1] * lutsize + prev[2]]; + const struct rgbvec c001 = lut3d->lut[prev[0] * lutsize2 + prev[1] * lutsize + next[2]]; + const struct rgbvec c010 = lut3d->lut[prev[0] * lutsize2 + next[1] * lutsize + prev[2]]; + const struct rgbvec c011 = lut3d->lut[prev[0] * lutsize2 + next[1] * lutsize + next[2]]; + const struct rgbvec c100 = lut3d->lut[next[0] * lutsize2 + prev[1] * lutsize + prev[2]]; + const struct rgbvec c101 = lut3d->lut[next[0] * lutsize2 + prev[1] * lutsize + next[2]]; + const struct rgbvec c110 = lut3d->lut[next[0] * lutsize2 + next[1] * lutsize + prev[2]]; + const struct rgbvec c111 = lut3d->lut[next[0] * lutsize2 + next[1] * lutsize + next[2]]; const struct rgbvec c00 = lerp(&c000, &c100, d.r); const struct rgbvec c10 = lerp(&c010, &c110, d.r); const struct rgbvec c01 = lerp(&c001, &c101, d.r); @@ -149,48 +154,50 @@ static inline struct rgbvec interp_trilinear(const LUT3DContext *lut3d, static inline struct rgbvec interp_tetrahedral(const LUT3DContext *lut3d, const struct rgbvec *s) { + const int lutsize2 = lut3d->lutsize2; + const int lutsize = lut3d->lutsize; const int prev[] = {PREV(s->r), PREV(s->g), PREV(s->b)}; const int next[] = {NEXT(s->r), NEXT(s->g), NEXT(s->b)}; const struct rgbvec d = {s->r - prev[0], s->g - prev[1], s->b - prev[2]}; - const struct rgbvec c000 = lut3d->lut[prev[0]][prev[1]][prev[2]]; - const struct rgbvec c111 = lut3d->lut[next[0]][next[1]][next[2]]; + const struct rgbvec c000 = lut3d->lut[prev[0] * lutsize2 + prev[1] * lutsize + prev[2]]; + const struct rgbvec c111 = lut3d->lut[next[0] * lutsize2 + next[1] * lutsize + next[2]]; struct rgbvec c; if (d.r > d.g) { if (d.g > d.b) { - const struct rgbvec c100 = lut3d->lut[next[0]][prev[1]][prev[2]]; - const struct rgbvec c110 = lut3d->lut[next[0]][next[1]][prev[2]]; + const struct rgbvec c100 = lut3d->lut[next[0] * lutsize2 + prev[1] * lutsize + prev[2]]; + const struct rgbvec c110 = lut3d->lut[next[0] * lutsize2 + next[1] * lutsize + prev[2]]; c.r = (1-d.r) * c000.r + (d.r-d.g) * c100.r + (d.g-d.b) * c110.r + (d.b) * c111.r; c.g = (1-d.r) * c000.g + (d.r-d.g) * c100.g + (d.g-d.b) * c110.g + (d.b) * c111.g; c.b = (1-d.r) * c000.b + (d.r-d.g) * c100.b + (d.g-d.b) * c110.b + (d.b) * c111.b; } else if (d.r > d.b) { - const struct rgbvec c100 = lut3d->lut[next[0]][prev[1]][prev[2]]; - const struct rgbvec c101 = lut3d->lut[next[0]][prev[1]][next[2]]; + const struct rgbvec c100 = lut3d->lut[next[0] * lutsize2 + prev[1] * lutsize + prev[2]]; + const struct rgbvec c101 = lut3d->lut[next[0] * lutsize2 + prev[1] * lutsize + next[2]]; c.r = (1-d.r) * c000.r + (d.r-d.b) * c100.r + (d.b-d.g) * c101.r + (d.g) * c111.r; c.g = (1-d.r) * c000.g + (d.r-d.b) * c100.g + (d.b-d.g) * c101.g + (d.g) * c111.g; c.b = (1-d.r) * c000.b + (d.r-d.b) * c100.b + (d.b-d.g) * c101.b + (d.g) * c111.b; } else { - const struct rgbvec c001 = lut3d->lut[prev[0]][prev[1]][next[2]]; - const struct rgbvec c101 = lut3d->lut[next[0]][prev[1]][next[2]]; + const struct rgbvec c001 = lut3d->lut[prev[0] * lutsize2 + prev[1] * lutsize + next[2]]; + const struct rgbvec c101 = lut3d->lut[next[0] * lutsize2 + prev[1] * lutsize + next[2]]; c.r = (1-d.b) * c000.r + (d.b-d.r) * c001.r + (d.r-d.g) * c101.r + (d.g) * c111.r; c.g = (1-d.b) * c000.g + (d.b-d.r) * c001.g + (d.r-d.g) * c101.g + (d.g) * c111.g; c.b = (1-d.b) * c000.b + (d.b-d.r) * c001.b + (d.r-d.g) * c101.b + (d.g) * c111.b; } } else { if (d.b > d.g) { - const struct rgbvec c001 = lut3d->lut[prev[0]][prev[1]][next[2]]; - const struct rgbvec c011 = lut3d->lut[prev[0]][next[1]][next[2]]; + const struct rgbvec c001 = lut3d->lut[prev[0] * lutsize2 + prev[1] * lutsize + next[2]]; + const struct rgbvec c011 = lut3d->lut[prev[0] * lutsize2 + next[1] * lutsize + next[2]]; c.r = (1-d.b) * c000.r + (d.b-d.g) * c001.r + (d.g-d.r) * c011.r + (d.r) * c111.r; c.g = (1-d.b) * c000.g + (d.b-d.g) * c001.g + (d.g-d.r) * c011.g + (d.r) * c111.g; c.b = (1-d.b) * c000.b + (d.b-d.g) * c001.b + (d.g-d.r) * c011.b + (d.r) * c111.b; } else if (d.b > d.r) { - const struct rgbvec c010 = lut3d->lut[prev[0]][next[1]][prev[2]]; - const struct rgbvec c011 = lut3d->lut[prev[0]][next[1]][next[2]]; + const struct rgbvec c010 = lut3d->lut[prev[0] * lutsize2 + next[1] * lutsize + prev[2]]; + const struct rgbvec c011 = lut3d->lut[prev[0] * lutsize2 + next[1] * lutsize + next[2]]; c.r = (1-d.g) * c000.r + (d.g-d.b) * c010.r + (d.b-d.r) * c011.r + (d.r) * c111.r; c.g = (1-d.g) * c000.g + (d.g-d.b) * c010.g + (d.b-d.r) * c011.g + (d.r) * c111.g; c.b = (1-d.g) * c000.b + (d.g-d.b) * c010.b + (d.b-d.r) * c011.b + (d.r) * c111.b; } else { - const struct rgbvec c010 = lut3d->lut[prev[0]][next[1]][prev[2]]; - const struct rgbvec c110 = lut3d->lut[next[0]][next[1]][prev[2]]; + const struct rgbvec c010 = lut3d->lut[prev[0] * lutsize2 + next[1] * lutsize + prev[2]]; + const struct rgbvec c110 = lut3d->lut[next[0] * lutsize2 + next[1] * lutsize + prev[2]]; c.r = (1-d.g) * c000.r + (d.g-d.r) * c010.r + (d.r-d.b) * c110.r + (d.b) * c111.r; c.g = (1-d.g) * c000.g + (d.g-d.r) * c010.g + (d.r-d.b) * c110.g + (d.b) * c111.g; c.b = (1-d.g) * c000.b + (d.g-d.r) * c010.b + (d.r-d.b) * c110.b + (d.b) * c111.b; @@ -218,7 +225,9 @@ static int interp_##nbits##_##name##_p##depth(AVFilterContext *ctx, void *arg, i const uint8_t *srcbrow = in->data[1] + slice_start * in->linesize[1]; \ const uint8_t *srcrrow = in->data[2] + slice_start * in->linesize[2]; \ const uint8_t *srcarow = in->data[3] + slice_start * in->linesize[3]; \ - const float scale = (1. / ((1<lutsize - 1); \ + const float scale_r = (lut3d->scale.r / ((1<lutsize - 1); \ + const float scale_g = (lut3d->scale.g / ((1<lutsize - 1); \ + const float scale_b = (lut3d->scale.b / ((1<lutsize - 1); \ \ for (y = slice_start; y < slice_end; y++) { \ uint##nbits##_t *dstg = (uint##nbits##_t *)grow; \ @@ -230,9 +239,9 @@ static int interp_##nbits##_##name##_p##depth(AVFilterContext *ctx, void *arg, i const uint##nbits##_t *srcr = (const uint##nbits##_t *)srcrrow; \ const uint##nbits##_t *srca = (const uint##nbits##_t *)srcarow; \ for (x = 0; x < in->width; x++) { \ - const struct rgbvec scaled_rgb = {srcr[x] * scale, \ - srcg[x] * scale, \ - srcb[x] * scale}; \ + const struct rgbvec scaled_rgb = {srcr[x] * scale_r, \ + srcg[x] * scale_g, \ + srcb[x] * scale_b}; \ struct rgbvec vec = interp_##name(lut3d, &scaled_rgb); \ dstr[x] = av_clip_uintp2(vec.r * (float)((1<height * (jobnr+1)) / nb_jobs; \ uint8_t *dstrow = out->data[0] + slice_start * out->linesize[0]; \ const uint8_t *srcrow = in ->data[0] + slice_start * in ->linesize[0]; \ - const float scale = (1. / ((1<lutsize - 1); \ + const float scale_r = (lut3d->scale.r / ((1<lutsize - 1); \ + const float scale_g = (lut3d->scale.g / ((1<lutsize - 1); \ + const float scale_b = (lut3d->scale.b / ((1<lutsize - 1); \ \ for (y = slice_start; y < slice_end; y++) { \ uint##nbits##_t *dst = (uint##nbits##_t *)dstrow; \ const uint##nbits##_t *src = (const uint##nbits##_t *)srcrow; \ for (x = 0; x < in->width * step; x += step) { \ - const struct rgbvec scaled_rgb = {src[x + r] * scale, \ - src[x + g] * scale, \ - src[x + b] * scale}; \ + const struct rgbvec scaled_rgb = {src[x + r] * scale_r, \ + src[x + g] * scale_g, \ + src[x + b] * scale_b}; \ struct rgbvec vec = interp_##name(lut3d, &scaled_rgb); \ dst[x + r] = av_clip_uint##nbits(vec.r * (float)((1<priv; + + if (lutsize < 2 || lutsize > MAX_LEVEL) { + av_log(ctx, AV_LOG_ERROR, "Too large or invalid 3D LUT size\n"); + return AVERROR(EINVAL); + } + + av_freep(&lut3d->lut); + lut3d->lut = av_malloc_array(lutsize * lutsize * lutsize, sizeof(*lut3d->lut)); + if (!lut3d->lut) + return AVERROR(ENOMEM); + lut3d->lutsize = lutsize; + lut3d->lutsize2 = lutsize * lutsize; + return 0; +} + /* Basically r g and b float values on each line, with a facultative 3DLUTSIZE * directive; seems to be generated by Davinci */ static int parse_dat(AVFilterContext *ctx, FILE *f) { LUT3DContext *lut3d = ctx->priv; char line[MAX_LINE_SIZE]; - int i, j, k, size; + int ret, i, j, k, size, size2; lut3d->lutsize = size = 33; + size2 = size * size; NEXT_LINE(skip_line(line)); if (!strncmp(line, "3DLUTSIZE ", 10)) { size = strtol(line + 10, NULL, 0); - if (size < 2 || size > MAX_LEVEL) { - av_log(ctx, AV_LOG_ERROR, "Too large or invalid 3D LUT size\n"); - return AVERROR(EINVAL); - } - lut3d->lutsize = size; + NEXT_LINE(skip_line(line)); } + + ret = allocate_3dlut(ctx, size); + if (ret < 0) + return ret; + for (k = 0; k < size; k++) { for (j = 0; j < size; j++) { for (i = 0; i < size; i++) { - struct rgbvec *vec = &lut3d->lut[k][j][i]; + struct rgbvec *vec = &lut3d->lut[k * size2 + j * size + i]; if (k != 0 || j != 0 || i != 0) NEXT_LINE(skip_line(line)); - if (sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != 3) + if (av_sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != 3) return AVERROR_INVALIDDATA; } } @@ -383,19 +414,19 @@ static int parse_cube(AVFilterContext *ctx, FILE *f) float max[3] = {1.0, 1.0, 1.0}; while (fgets(line, sizeof(line), f)) { - if (!strncmp(line, "LUT_3D_SIZE ", 12)) { - int i, j, k; + if (!strncmp(line, "LUT_3D_SIZE", 11)) { + int ret, i, j, k; const int size = strtol(line + 12, NULL, 0); + const int size2 = size * size; + + ret = allocate_3dlut(ctx, size); + if (ret < 0) + return ret; - if (size < 2 || size > MAX_LEVEL) { - av_log(ctx, AV_LOG_ERROR, "Too large or invalid 3D LUT size\n"); - return AVERROR(EINVAL); - } - lut3d->lutsize = size; for (k = 0; k < size; k++) { for (j = 0; j < size; j++) { for (i = 0; i < size; i++) { - struct rgbvec *vec = &lut3d->lut[i][j][k]; + struct rgbvec *vec = &lut3d->lut[i * size2 + j * size + k]; do { try_again: @@ -406,23 +437,27 @@ try_again: else if (!strncmp(line + 7, "MAX ", 4)) vals = max; if (!vals) return AVERROR_INVALIDDATA; - sscanf(line + 11, "%f %f %f", vals, vals + 1, vals + 2); + av_sscanf(line + 11, "%f %f %f", vals, vals + 1, vals + 2); av_log(ctx, AV_LOG_DEBUG, "min: %f %f %f | max: %f %f %f\n", min[0], min[1], min[2], max[0], max[1], max[2]); goto try_again; + } else if (!strncmp(line, "TITLE", 5)) { + goto try_again; } } while (skip_line(line)); - if (sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != 3) + if (av_sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != 3) return AVERROR_INVALIDDATA; - vec->r *= max[0] - min[0]; - vec->g *= max[1] - min[1]; - vec->b *= max[2] - min[2]; } } } break; } } + + lut3d->scale.r = av_clipf(1. / (max[0] - min[0]), 0.f, 1.f); + lut3d->scale.g = av_clipf(1. / (max[1] - min[1]), 0.f, 1.f); + lut3d->scale.b = av_clipf(1. / (max[2] - min[2]), 0.f, 1.f); + return 0; } @@ -432,20 +467,26 @@ static int parse_3dl(AVFilterContext *ctx, FILE *f) { char line[MAX_LINE_SIZE]; LUT3DContext *lut3d = ctx->priv; - int i, j, k; + int ret, i, j, k; const int size = 17; + const int size2 = 17 * 17; const float scale = 16*16*16; lut3d->lutsize = size; + + ret = allocate_3dlut(ctx, size); + if (ret < 0) + return ret; + NEXT_LINE(skip_line(line)); for (k = 0; k < size; k++) { for (j = 0; j < size; j++) { for (i = 0; i < size; i++) { int r, g, b; - struct rgbvec *vec = &lut3d->lut[k][j][i]; + struct rgbvec *vec = &lut3d->lut[k * size2 + j * size + i]; NEXT_LINE(skip_line(line)); - if (sscanf(line, "%d %d %d", &r, &g, &b) != 3) + if (av_sscanf(line, "%d %d %d", &r, &g, &b) != 3) return AVERROR_INVALIDDATA; vec->r = r / scale; vec->g = g / scale; @@ -461,7 +502,7 @@ static int parse_m3d(AVFilterContext *ctx, FILE *f) { LUT3DContext *lut3d = ctx->priv; float scale; - int i, j, k, size, in = -1, out = -1; + int ret, i, j, k, size, size2, in = -1, out = -1; char line[MAX_LINE_SIZE]; uint8_t rgb_map[3] = {0, 1, 2}; @@ -500,16 +541,22 @@ static int parse_m3d(AVFilterContext *ctx, FILE *f) } for (size = 1; size*size*size < in; size++); lut3d->lutsize = size; + size2 = size * size; + + ret = allocate_3dlut(ctx, size); + if (ret < 0) + return ret; + scale = 1. / (out - 1); for (k = 0; k < size; k++) { for (j = 0; j < size; j++) { for (i = 0; i < size; i++) { - struct rgbvec *vec = &lut3d->lut[k][j][i]; + struct rgbvec *vec = &lut3d->lut[k * size2 + j * size + i]; float val[3]; NEXT_LINE(0); - if (sscanf(line, "%f %f %f", val, val + 1, val + 2) != 3) + if (av_sscanf(line, "%f %f %f", val, val + 1, val + 2) != 3) return AVERROR_INVALIDDATA; vec->r = val[rgb_map[0]] * scale; vec->g = val[rgb_map[1]] * scale; @@ -520,22 +567,122 @@ static int parse_m3d(AVFilterContext *ctx, FILE *f) return 0; } -static void set_identity_matrix(LUT3DContext *lut3d, int size) +static int parse_cinespace(AVFilterContext *ctx, FILE *f) { - int i, j, k; + LUT3DContext *lut3d = ctx->priv; + char line[MAX_LINE_SIZE]; + float in_min[3] = {0.0, 0.0, 0.0}; + float in_max[3] = {1.0, 1.0, 1.0}; + float out_min[3] = {0.0, 0.0, 0.0}; + float out_max[3] = {1.0, 1.0, 1.0}; + int ret, inside_metadata = 0, size, size2; + + NEXT_LINE(skip_line(line)); + if (strncmp(line, "CSPLUTV100", 10)) { + av_log(ctx, AV_LOG_ERROR, "Not cineSpace LUT format\n"); + return AVERROR(EINVAL); + } + + NEXT_LINE(skip_line(line)); + if (strncmp(line, "3D", 2)) { + av_log(ctx, AV_LOG_ERROR, "Not 3D LUT format\n"); + return AVERROR(EINVAL); + } + + while (1) { + NEXT_LINE(skip_line(line)); + + if (!strncmp(line, "BEGIN METADATA", 14)) { + inside_metadata = 1; + continue; + } + if (!strncmp(line, "END METADATA", 12)) { + inside_metadata = 0; + continue; + } + if (inside_metadata == 0) { + int size_r, size_g, size_b; + + for (int i = 0; i < 3; i++) { + int npoints = strtol(line, NULL, 0); + + if (npoints != 2) { + av_log(ctx, AV_LOG_ERROR, "Unsupported number of pre-lut points.\n"); + return AVERROR_PATCHWELCOME; + } + + NEXT_LINE(skip_line(line)); + if (av_sscanf(line, "%f %f", &in_min[i], &in_max[i]) != 2) + return AVERROR_INVALIDDATA; + NEXT_LINE(skip_line(line)); + if (av_sscanf(line, "%f %f", &out_min[i], &out_max[i]) != 2) + return AVERROR_INVALIDDATA; + NEXT_LINE(skip_line(line)); + } + + if (av_sscanf(line, "%d %d %d", &size_r, &size_g, &size_b) != 3) + return AVERROR(EINVAL); + if (size_r != size_g || size_r != size_b) { + av_log(ctx, AV_LOG_ERROR, "Unsupported size combination: %dx%dx%d.\n", size_r, size_g, size_b); + return AVERROR_PATCHWELCOME; + } + + size = size_r; + size2 = size * size; + + ret = allocate_3dlut(ctx, size); + if (ret < 0) + return ret; + + for (int k = 0; k < size; k++) { + for (int j = 0; j < size; j++) { + for (int i = 0; i < size; i++) { + struct rgbvec *vec = &lut3d->lut[i * size2 + j * size + k]; + if (k != 0 || j != 0 || i != 0) + NEXT_LINE(skip_line(line)); + if (av_sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != 3) + return AVERROR_INVALIDDATA; + vec->r *= out_max[0] - out_min[0]; + vec->g *= out_max[1] - out_min[1]; + vec->b *= out_max[2] - out_min[2]; + } + } + } + + break; + } + } + + lut3d->scale.r = av_clipf(1. / (in_max[0] - in_min[0]), 0.f, 1.f); + lut3d->scale.g = av_clipf(1. / (in_max[1] - in_min[1]), 0.f, 1.f); + lut3d->scale.b = av_clipf(1. / (in_max[2] - in_min[2]), 0.f, 1.f); + + return 0; +} + +static int set_identity_matrix(AVFilterContext *ctx, int size) +{ + LUT3DContext *lut3d = ctx->priv; + int ret, i, j, k; + const int size2 = size * size; const float c = 1. / (size - 1); - lut3d->lutsize = size; + ret = allocate_3dlut(ctx, size); + if (ret < 0) + return ret; + for (k = 0; k < size; k++) { for (j = 0; j < size; j++) { for (i = 0; i < size; i++) { - struct rgbvec *vec = &lut3d->lut[k][j][i]; + struct rgbvec *vec = &lut3d->lut[k * size2 + j * size + i]; vec->r = k * c; vec->g = j * c; vec->b = i * c; } } } + + return 0; } static int query_formats(AVFilterContext *ctx) @@ -564,34 +711,13 @@ static int query_formats(AVFilterContext *ctx) static int config_input(AVFilterLink *inlink) { - int depth, is16bit = 0, planar = 0; + int depth, is16bit, planar; LUT3DContext *lut3d = inlink->dst->priv; const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); depth = desc->comp[0].depth; - - switch (inlink->format) { - case AV_PIX_FMT_RGB48: - case AV_PIX_FMT_BGR48: - case AV_PIX_FMT_RGBA64: - case AV_PIX_FMT_BGRA64: - is16bit = 1; - break; - case AV_PIX_FMT_GBRP9: - case AV_PIX_FMT_GBRP10: - case AV_PIX_FMT_GBRP12: - case AV_PIX_FMT_GBRP14: - case AV_PIX_FMT_GBRP16: - case AV_PIX_FMT_GBRAP10: - case AV_PIX_FMT_GBRAP12: - case AV_PIX_FMT_GBRAP16: - is16bit = 1; - case AV_PIX_FMT_GBRP: - case AV_PIX_FMT_GBRAP: - planar = 1; - break; - } - + is16bit = desc->comp[0].depth > 8; + planar = desc->flags & AV_PIX_FMT_FLAG_PLANAR; ff_fill_rgba_map(lut3d->rgba_map, inlink->format); lut3d->step = av_get_padded_bits_per_pixel(desc) >> (3 + is16bit); @@ -673,12 +799,13 @@ static av_cold int lut3d_init(AVFilterContext *ctx) const char *ext; LUT3DContext *lut3d = ctx->priv; + lut3d->scale.r = lut3d->scale.g = lut3d->scale.b = 1.f; + if (!lut3d->file) { - set_identity_matrix(lut3d, 32); - return 0; + return set_identity_matrix(ctx, 32); } - f = fopen(lut3d->file, "r"); + f = av_fopen_utf8(lut3d->file, "r"); if (!f) { ret = AVERROR(errno); av_log(ctx, AV_LOG_ERROR, "%s: %s\n", lut3d->file, av_err2str(ret)); @@ -701,6 +828,8 @@ static av_cold int lut3d_init(AVFilterContext *ctx) ret = parse_cube(ctx, f); } else if (!av_strcasecmp(ext, "m3d")) { ret = parse_m3d(ctx, f); + } else if (!av_strcasecmp(ext, "csp")) { + ret = parse_cinespace(ctx, f); } else { av_log(ctx, AV_LOG_ERROR, "Unrecognized '.%s' file type\n", ext); ret = AVERROR(EINVAL); @@ -716,6 +845,13 @@ end: return ret; } +static av_cold void lut3d_uninit(AVFilterContext *ctx) +{ + LUT3DContext *lut3d = ctx->priv; + + av_freep(&lut3d->lut); +} + static const AVFilterPad lut3d_inputs[] = { { .name = "default", @@ -739,6 +875,7 @@ AVFilter ff_vf_lut3d = { .description = NULL_IF_CONFIG_SMALL("Adjust colors using a 3D LUT."), .priv_size = sizeof(LUT3DContext), .init = lut3d_init, + .uninit = lut3d_uninit, .query_formats = query_formats, .inputs = lut3d_inputs, .outputs = lut3d_outputs, @@ -757,6 +894,7 @@ static void update_clut_packed(LUT3DContext *lut3d, const AVFrame *frame) const int step = lut3d->clut_step; const uint8_t *rgba_map = lut3d->clut_rgba_map; const int level = lut3d->lutsize; + const int level2 = lut3d->lutsize2; #define LOAD_CLUT(nbits) do { \ int i, j, k, x = 0, y = 0; \ @@ -766,7 +904,7 @@ static void update_clut_packed(LUT3DContext *lut3d, const AVFrame *frame) for (i = 0; i < level; i++) { \ const uint##nbits##_t *src = (const uint##nbits##_t *) \ (data + y*linesize + x*step); \ - struct rgbvec *vec = &lut3d->lut[i][j][k]; \ + struct rgbvec *vec = &lut3d->lut[i * level2 + j * level + k]; \ vec->r = src[rgba_map[0]] / (float)((1<<(nbits)) - 1); \ vec->g = src[rgba_map[1]] / (float)((1<<(nbits)) - 1); \ vec->b = src[rgba_map[2]] / (float)((1<<(nbits)) - 1); \ @@ -795,6 +933,7 @@ static void update_clut_planar(LUT3DContext *lut3d, const AVFrame *frame) const int rlinesize = frame->linesize[2]; const int w = lut3d->clut_width; const int level = lut3d->lutsize; + const int level2 = lut3d->lutsize2; #define LOAD_CLUT_PLANAR(nbits, depth) do { \ int i, j, k, x = 0, y = 0; \ @@ -808,7 +947,7 @@ static void update_clut_planar(LUT3DContext *lut3d, const AVFrame *frame) (datab + y*blinesize); \ const uint##nbits##_t *rsrc = (const uint##nbits##_t *) \ (datar + y*rlinesize); \ - struct rgbvec *vec = &lut3d->lut[i][j][k]; \ + struct rgbvec *vec = &lut3d->lut[i * level2 + j * level + k]; \ vec->r = gsrc[x] / (float)((1<<(depth)) - 1); \ vec->g = bsrc[x] / (float)((1<<(depth)) - 1); \ vec->b = rsrc[x] / (float)((1<<(depth)) - 1); \ @@ -893,9 +1032,8 @@ static int config_clut(AVFilterLink *inlink) max_clut_level, max_clut_size, max_clut_size); return AVERROR(EINVAL); } - lut3d->lutsize = level; - return 0; + return allocate_3dlut(ctx, level); } static int update_apply_clut(FFFrameSync *fs) @@ -922,6 +1060,7 @@ static int update_apply_clut(FFFrameSync *fs) static av_cold int haldclut_init(AVFilterContext *ctx) { LUT3DContext *lut3d = ctx->priv; + lut3d->scale.r = lut3d->scale.g = lut3d->scale.b = 1.f; lut3d->fs.on_event = update_apply_clut; return 0; } @@ -930,6 +1069,7 @@ static av_cold void haldclut_uninit(AVFilterContext *ctx) { LUT3DContext *lut3d = ctx->priv; ff_framesync_uninit(&lut3d->fs); + av_freep(&lut3d->lut); } static const AVOption haldclut_options[] = { @@ -975,3 +1115,578 @@ AVFilter ff_vf_haldclut = { .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | AVFILTER_FLAG_SLICE_THREADS, }; #endif + +#if CONFIG_LUT1D_FILTER + +enum interp_1d_mode { + INTERPOLATE_1D_NEAREST, + INTERPOLATE_1D_LINEAR, + INTERPOLATE_1D_CUBIC, + INTERPOLATE_1D_COSINE, + INTERPOLATE_1D_SPLINE, + NB_INTERP_1D_MODE +}; + +#define MAX_1D_LEVEL 65536 + +typedef struct LUT1DContext { + const AVClass *class; + char *file; + int interpolation; ///lutsize = size; + for (i = 0; i < size; i++) { + lut1d->lut[0][i] = i * c; + lut1d->lut[1][i] = i * c; + lut1d->lut[2][i] = i * c; + } +} + +static int parse_cinespace_1d(AVFilterContext *ctx, FILE *f) +{ + LUT1DContext *lut1d = ctx->priv; + char line[MAX_LINE_SIZE]; + float in_min[3] = {0.0, 0.0, 0.0}; + float in_max[3] = {1.0, 1.0, 1.0}; + float out_min[3] = {0.0, 0.0, 0.0}; + float out_max[3] = {1.0, 1.0, 1.0}; + int inside_metadata = 0, size; + + NEXT_LINE(skip_line(line)); + if (strncmp(line, "CSPLUTV100", 10)) { + av_log(ctx, AV_LOG_ERROR, "Not cineSpace LUT format\n"); + return AVERROR(EINVAL); + } + + NEXT_LINE(skip_line(line)); + if (strncmp(line, "1D", 2)) { + av_log(ctx, AV_LOG_ERROR, "Not 1D LUT format\n"); + return AVERROR(EINVAL); + } + + while (1) { + NEXT_LINE(skip_line(line)); + + if (!strncmp(line, "BEGIN METADATA", 14)) { + inside_metadata = 1; + continue; + } + if (!strncmp(line, "END METADATA", 12)) { + inside_metadata = 0; + continue; + } + if (inside_metadata == 0) { + for (int i = 0; i < 3; i++) { + int npoints = strtol(line, NULL, 0); + + if (npoints != 2) { + av_log(ctx, AV_LOG_ERROR, "Unsupported number of pre-lut points.\n"); + return AVERROR_PATCHWELCOME; + } + + NEXT_LINE(skip_line(line)); + if (av_sscanf(line, "%f %f", &in_min[i], &in_max[i]) != 2) + return AVERROR_INVALIDDATA; + NEXT_LINE(skip_line(line)); + if (av_sscanf(line, "%f %f", &out_min[i], &out_max[i]) != 2) + return AVERROR_INVALIDDATA; + NEXT_LINE(skip_line(line)); + } + + size = strtol(line, NULL, 0); + + if (size < 2 || size > MAX_1D_LEVEL) { + av_log(ctx, AV_LOG_ERROR, "Too large or invalid 1D LUT size\n"); + return AVERROR(EINVAL); + } + + lut1d->lutsize = size; + + for (int i = 0; i < size; i++) { + NEXT_LINE(skip_line(line)); + if (av_sscanf(line, "%f %f %f", &lut1d->lut[0][i], &lut1d->lut[1][i], &lut1d->lut[2][i]) != 3) + return AVERROR_INVALIDDATA; + lut1d->lut[0][i] *= out_max[0] - out_min[0]; + lut1d->lut[1][i] *= out_max[1] - out_min[1]; + lut1d->lut[2][i] *= out_max[2] - out_min[2]; + } + + break; + } + } + + lut1d->scale.r = av_clipf(1. / (in_max[0] - in_min[0]), 0.f, 1.f); + lut1d->scale.g = av_clipf(1. / (in_max[1] - in_min[1]), 0.f, 1.f); + lut1d->scale.b = av_clipf(1. / (in_max[2] - in_min[2]), 0.f, 1.f); + + return 0; +} + +static int parse_cube_1d(AVFilterContext *ctx, FILE *f) +{ + LUT1DContext *lut1d = ctx->priv; + char line[MAX_LINE_SIZE]; + float min[3] = {0.0, 0.0, 0.0}; + float max[3] = {1.0, 1.0, 1.0}; + + while (fgets(line, sizeof(line), f)) { + if (!strncmp(line, "LUT_1D_SIZE", 11)) { + const int size = strtol(line + 12, NULL, 0); + int i; + + if (size < 2 || size > MAX_1D_LEVEL) { + av_log(ctx, AV_LOG_ERROR, "Too large or invalid 1D LUT size\n"); + return AVERROR(EINVAL); + } + lut1d->lutsize = size; + for (i = 0; i < size; i++) { + do { +try_again: + NEXT_LINE(0); + if (!strncmp(line, "DOMAIN_", 7)) { + float *vals = NULL; + if (!strncmp(line + 7, "MIN ", 4)) vals = min; + else if (!strncmp(line + 7, "MAX ", 4)) vals = max; + if (!vals) + return AVERROR_INVALIDDATA; + av_sscanf(line + 11, "%f %f %f", vals, vals + 1, vals + 2); + av_log(ctx, AV_LOG_DEBUG, "min: %f %f %f | max: %f %f %f\n", + min[0], min[1], min[2], max[0], max[1], max[2]); + goto try_again; + } else if (!strncmp(line, "LUT_1D_INPUT_RANGE ", 19)) { + av_sscanf(line + 19, "%f %f", min, max); + min[1] = min[2] = min[0]; + max[1] = max[2] = max[0]; + goto try_again; + } else if (!strncmp(line, "TITLE", 5)) { + goto try_again; + } + } while (skip_line(line)); + if (av_sscanf(line, "%f %f %f", &lut1d->lut[0][i], &lut1d->lut[1][i], &lut1d->lut[2][i]) != 3) + return AVERROR_INVALIDDATA; + } + break; + } + } + + lut1d->scale.r = av_clipf(1. / (max[0] - min[0]), 0.f, 1.f); + lut1d->scale.g = av_clipf(1. / (max[1] - min[1]), 0.f, 1.f); + lut1d->scale.b = av_clipf(1. / (max[2] - min[2]), 0.f, 1.f); + + return 0; +} + +static const AVOption lut1d_options[] = { + { "file", "set 1D LUT file name", OFFSET(file), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, + { "interp", "select interpolation mode", OFFSET(interpolation), AV_OPT_TYPE_INT, {.i64=INTERPOLATE_1D_LINEAR}, 0, NB_INTERP_1D_MODE-1, FLAGS, "interp_mode" }, + { "nearest", "use values from the nearest defined points", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_NEAREST}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, + { "linear", "use values from the linear interpolation", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_LINEAR}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, + { "cosine", "use values from the cosine interpolation", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_COSINE}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, + { "cubic", "use values from the cubic interpolation", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_CUBIC}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, + { "spline", "use values from the spline interpolation", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_SPLINE}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(lut1d); + +static inline float interp_1d_nearest(const LUT1DContext *lut1d, + int idx, const float s) +{ + return lut1d->lut[idx][NEAR(s)]; +} + +#define NEXT1D(x) (FFMIN((int)(x) + 1, lut1d->lutsize - 1)) + +static inline float interp_1d_linear(const LUT1DContext *lut1d, + int idx, const float s) +{ + const int prev = PREV(s); + const int next = NEXT1D(s); + const float d = s - prev; + const float p = lut1d->lut[idx][prev]; + const float n = lut1d->lut[idx][next]; + + return lerpf(p, n, d); +} + +static inline float interp_1d_cosine(const LUT1DContext *lut1d, + int idx, const float s) +{ + const int prev = PREV(s); + const int next = NEXT1D(s); + const float d = s - prev; + const float p = lut1d->lut[idx][prev]; + const float n = lut1d->lut[idx][next]; + const float m = (1.f - cosf(d * M_PI)) * .5f; + + return lerpf(p, n, m); +} + +static inline float interp_1d_cubic(const LUT1DContext *lut1d, + int idx, const float s) +{ + const int prev = PREV(s); + const int next = NEXT1D(s); + const float mu = s - prev; + float a0, a1, a2, a3, mu2; + + float y0 = lut1d->lut[idx][FFMAX(prev - 1, 0)]; + float y1 = lut1d->lut[idx][prev]; + float y2 = lut1d->lut[idx][next]; + float y3 = lut1d->lut[idx][FFMIN(next + 1, lut1d->lutsize - 1)]; + + + mu2 = mu * mu; + a0 = y3 - y2 - y0 + y1; + a1 = y0 - y1 - a0; + a2 = y2 - y0; + a3 = y1; + + return a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3; +} + +static inline float interp_1d_spline(const LUT1DContext *lut1d, + int idx, const float s) +{ + const int prev = PREV(s); + const int next = NEXT1D(s); + const float x = s - prev; + float c0, c1, c2, c3; + + float y0 = lut1d->lut[idx][FFMAX(prev - 1, 0)]; + float y1 = lut1d->lut[idx][prev]; + float y2 = lut1d->lut[idx][next]; + float y3 = lut1d->lut[idx][FFMIN(next + 1, lut1d->lutsize - 1)]; + + c0 = y1; + c1 = .5f * (y2 - y0); + c2 = y0 - 2.5f * y1 + 2.f * y2 - .5f * y3; + c3 = .5f * (y3 - y0) + 1.5f * (y1 - y2); + + return ((c3 * x + c2) * x + c1) * x + c0; +} + +#define DEFINE_INTERP_FUNC_PLANAR_1D(name, nbits, depth) \ +static int interp_1d_##nbits##_##name##_p##depth(AVFilterContext *ctx, \ + void *arg, int jobnr, \ + int nb_jobs) \ +{ \ + int x, y; \ + const LUT1DContext *lut1d = ctx->priv; \ + const ThreadData *td = arg; \ + const AVFrame *in = td->in; \ + const AVFrame *out = td->out; \ + const int direct = out == in; \ + const int slice_start = (in->height * jobnr ) / nb_jobs; \ + const int slice_end = (in->height * (jobnr+1)) / nb_jobs; \ + uint8_t *grow = out->data[0] + slice_start * out->linesize[0]; \ + uint8_t *brow = out->data[1] + slice_start * out->linesize[1]; \ + uint8_t *rrow = out->data[2] + slice_start * out->linesize[2]; \ + uint8_t *arow = out->data[3] + slice_start * out->linesize[3]; \ + const uint8_t *srcgrow = in->data[0] + slice_start * in->linesize[0]; \ + const uint8_t *srcbrow = in->data[1] + slice_start * in->linesize[1]; \ + const uint8_t *srcrrow = in->data[2] + slice_start * in->linesize[2]; \ + const uint8_t *srcarow = in->data[3] + slice_start * in->linesize[3]; \ + const float factor = (1 << depth) - 1; \ + const float scale_r = (lut1d->scale.r / factor) * (lut1d->lutsize - 1); \ + const float scale_g = (lut1d->scale.g / factor) * (lut1d->lutsize - 1); \ + const float scale_b = (lut1d->scale.b / factor) * (lut1d->lutsize - 1); \ + \ + for (y = slice_start; y < slice_end; y++) { \ + uint##nbits##_t *dstg = (uint##nbits##_t *)grow; \ + uint##nbits##_t *dstb = (uint##nbits##_t *)brow; \ + uint##nbits##_t *dstr = (uint##nbits##_t *)rrow; \ + uint##nbits##_t *dsta = (uint##nbits##_t *)arow; \ + const uint##nbits##_t *srcg = (const uint##nbits##_t *)srcgrow; \ + const uint##nbits##_t *srcb = (const uint##nbits##_t *)srcbrow; \ + const uint##nbits##_t *srcr = (const uint##nbits##_t *)srcrrow; \ + const uint##nbits##_t *srca = (const uint##nbits##_t *)srcarow; \ + for (x = 0; x < in->width; x++) { \ + float r = srcr[x] * scale_r; \ + float g = srcg[x] * scale_g; \ + float b = srcb[x] * scale_b; \ + r = interp_1d_##name(lut1d, 0, r); \ + g = interp_1d_##name(lut1d, 1, g); \ + b = interp_1d_##name(lut1d, 2, b); \ + dstr[x] = av_clip_uintp2(r * factor, depth); \ + dstg[x] = av_clip_uintp2(g * factor, depth); \ + dstb[x] = av_clip_uintp2(b * factor, depth); \ + if (!direct && in->linesize[3]) \ + dsta[x] = srca[x]; \ + } \ + grow += out->linesize[0]; \ + brow += out->linesize[1]; \ + rrow += out->linesize[2]; \ + arow += out->linesize[3]; \ + srcgrow += in->linesize[0]; \ + srcbrow += in->linesize[1]; \ + srcrrow += in->linesize[2]; \ + srcarow += in->linesize[3]; \ + } \ + return 0; \ +} + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 8, 8) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 8, 8) +DEFINE_INTERP_FUNC_PLANAR_1D(cosine, 8, 8) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 8, 8) +DEFINE_INTERP_FUNC_PLANAR_1D(spline, 8, 8) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 9) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 9) +DEFINE_INTERP_FUNC_PLANAR_1D(cosine, 16, 9) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 9) +DEFINE_INTERP_FUNC_PLANAR_1D(spline, 16, 9) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 10) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 10) +DEFINE_INTERP_FUNC_PLANAR_1D(cosine, 16, 10) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 10) +DEFINE_INTERP_FUNC_PLANAR_1D(spline, 16, 10) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 12) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 12) +DEFINE_INTERP_FUNC_PLANAR_1D(cosine, 16, 12) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 12) +DEFINE_INTERP_FUNC_PLANAR_1D(spline, 16, 12) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 14) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 14) +DEFINE_INTERP_FUNC_PLANAR_1D(cosine, 16, 14) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 14) +DEFINE_INTERP_FUNC_PLANAR_1D(spline, 16, 14) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 16) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 16) +DEFINE_INTERP_FUNC_PLANAR_1D(cosine, 16, 16) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 16) +DEFINE_INTERP_FUNC_PLANAR_1D(spline, 16, 16) + +#define DEFINE_INTERP_FUNC_1D(name, nbits) \ +static int interp_1d_##nbits##_##name(AVFilterContext *ctx, void *arg, \ + int jobnr, int nb_jobs) \ +{ \ + int x, y; \ + const LUT1DContext *lut1d = ctx->priv; \ + const ThreadData *td = arg; \ + const AVFrame *in = td->in; \ + const AVFrame *out = td->out; \ + const int direct = out == in; \ + const int step = lut1d->step; \ + const uint8_t r = lut1d->rgba_map[R]; \ + const uint8_t g = lut1d->rgba_map[G]; \ + const uint8_t b = lut1d->rgba_map[B]; \ + const uint8_t a = lut1d->rgba_map[A]; \ + const int slice_start = (in->height * jobnr ) / nb_jobs; \ + const int slice_end = (in->height * (jobnr+1)) / nb_jobs; \ + uint8_t *dstrow = out->data[0] + slice_start * out->linesize[0]; \ + const uint8_t *srcrow = in ->data[0] + slice_start * in ->linesize[0]; \ + const float factor = (1 << nbits) - 1; \ + const float scale_r = (lut1d->scale.r / factor) * (lut1d->lutsize - 1); \ + const float scale_g = (lut1d->scale.g / factor) * (lut1d->lutsize - 1); \ + const float scale_b = (lut1d->scale.b / factor) * (lut1d->lutsize - 1); \ + \ + for (y = slice_start; y < slice_end; y++) { \ + uint##nbits##_t *dst = (uint##nbits##_t *)dstrow; \ + const uint##nbits##_t *src = (const uint##nbits##_t *)srcrow; \ + for (x = 0; x < in->width * step; x += step) { \ + float rr = src[x + r] * scale_r; \ + float gg = src[x + g] * scale_g; \ + float bb = src[x + b] * scale_b; \ + rr = interp_1d_##name(lut1d, 0, rr); \ + gg = interp_1d_##name(lut1d, 1, gg); \ + bb = interp_1d_##name(lut1d, 2, bb); \ + dst[x + r] = av_clip_uint##nbits(rr * factor); \ + dst[x + g] = av_clip_uint##nbits(gg * factor); \ + dst[x + b] = av_clip_uint##nbits(bb * factor); \ + if (!direct && step == 4) \ + dst[x + a] = src[x + a]; \ + } \ + dstrow += out->linesize[0]; \ + srcrow += in ->linesize[0]; \ + } \ + return 0; \ +} + +DEFINE_INTERP_FUNC_1D(nearest, 8) +DEFINE_INTERP_FUNC_1D(linear, 8) +DEFINE_INTERP_FUNC_1D(cosine, 8) +DEFINE_INTERP_FUNC_1D(cubic, 8) +DEFINE_INTERP_FUNC_1D(spline, 8) + +DEFINE_INTERP_FUNC_1D(nearest, 16) +DEFINE_INTERP_FUNC_1D(linear, 16) +DEFINE_INTERP_FUNC_1D(cosine, 16) +DEFINE_INTERP_FUNC_1D(cubic, 16) +DEFINE_INTERP_FUNC_1D(spline, 16) + +static int config_input_1d(AVFilterLink *inlink) +{ + int depth, is16bit, planar; + LUT1DContext *lut1d = inlink->dst->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + + depth = desc->comp[0].depth; + is16bit = desc->comp[0].depth > 8; + planar = desc->flags & AV_PIX_FMT_FLAG_PLANAR; + ff_fill_rgba_map(lut1d->rgba_map, inlink->format); + lut1d->step = av_get_padded_bits_per_pixel(desc) >> (3 + is16bit); + +#define SET_FUNC_1D(name) do { \ + if (planar) { \ + switch (depth) { \ + case 8: lut1d->interp = interp_1d_8_##name##_p8; break; \ + case 9: lut1d->interp = interp_1d_16_##name##_p9; break; \ + case 10: lut1d->interp = interp_1d_16_##name##_p10; break; \ + case 12: lut1d->interp = interp_1d_16_##name##_p12; break; \ + case 14: lut1d->interp = interp_1d_16_##name##_p14; break; \ + case 16: lut1d->interp = interp_1d_16_##name##_p16; break; \ + } \ + } else if (is16bit) { lut1d->interp = interp_1d_16_##name; \ + } else { lut1d->interp = interp_1d_8_##name; } \ +} while (0) + + switch (lut1d->interpolation) { + case INTERPOLATE_1D_NEAREST: SET_FUNC_1D(nearest); break; + case INTERPOLATE_1D_LINEAR: SET_FUNC_1D(linear); break; + case INTERPOLATE_1D_COSINE: SET_FUNC_1D(cosine); break; + case INTERPOLATE_1D_CUBIC: SET_FUNC_1D(cubic); break; + case INTERPOLATE_1D_SPLINE: SET_FUNC_1D(spline); break; + default: + av_assert0(0); + } + + return 0; +} + +static av_cold int lut1d_init(AVFilterContext *ctx) +{ + int ret; + FILE *f; + const char *ext; + LUT1DContext *lut1d = ctx->priv; + + lut1d->scale.r = lut1d->scale.g = lut1d->scale.b = 1.f; + + if (!lut1d->file) { + set_identity_matrix_1d(lut1d, 32); + return 0; + } + + f = av_fopen_utf8(lut1d->file, "r"); + if (!f) { + ret = AVERROR(errno); + av_log(ctx, AV_LOG_ERROR, "%s: %s\n", lut1d->file, av_err2str(ret)); + return ret; + } + + ext = strrchr(lut1d->file, '.'); + if (!ext) { + av_log(ctx, AV_LOG_ERROR, "Unable to guess the format from the extension\n"); + ret = AVERROR_INVALIDDATA; + goto end; + } + ext++; + + if (!av_strcasecmp(ext, "cube") || !av_strcasecmp(ext, "1dlut")) { + ret = parse_cube_1d(ctx, f); + } else if (!av_strcasecmp(ext, "csp")) { + ret = parse_cinespace_1d(ctx, f); + } else { + av_log(ctx, AV_LOG_ERROR, "Unrecognized '.%s' file type\n", ext); + ret = AVERROR(EINVAL); + } + + if (!ret && !lut1d->lutsize) { + av_log(ctx, AV_LOG_ERROR, "1D LUT is empty\n"); + ret = AVERROR_INVALIDDATA; + } + +end: + fclose(f); + return ret; +} + +static AVFrame *apply_1d_lut(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + LUT1DContext *lut1d = ctx->priv; + AVFilterLink *outlink = inlink->dst->outputs[0]; + AVFrame *out; + ThreadData td; + + if (av_frame_is_writable(in)) { + out = in; + } else { + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return NULL; + } + av_frame_copy_props(out, in); + } + + td.in = in; + td.out = out; + ctx->internal->execute(ctx, lut1d->interp, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx))); + + if (out != in) + av_frame_free(&in); + + return out; +} + +static int filter_frame_1d(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterLink *outlink = inlink->dst->outputs[0]; + AVFrame *out = apply_1d_lut(inlink, in); + if (!out) + return AVERROR(ENOMEM); + return ff_filter_frame(outlink, out); +} + +static const AVFilterPad lut1d_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame_1d, + .config_props = config_input_1d, + }, + { NULL } +}; + +static const AVFilterPad lut1d_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +AVFilter ff_vf_lut1d = { + .name = "lut1d", + .description = NULL_IF_CONFIG_SMALL("Adjust colors using a 1D LUT."), + .priv_size = sizeof(LUT1DContext), + .init = lut1d_init, + .query_formats = query_formats, + .inputs = lut1d_inputs, + .outputs = lut1d_outputs, + .priv_class = &lut1d_class, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, +}; +#endif