/*
* Copyright (c) 2013 Clément Bœsch
+ * Copyright (c) 2018 Paul B Mahol
*
* This file is part of FFmpeg.
*
/* 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;
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;
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)];
}
/**
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);
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;
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<<depth) - 1)) * (lut3d->lutsize - 1); \
+ const float scale_r = (lut3d->scale.r / ((1<<depth) - 1)) * (lut3d->lutsize - 1); \
+ const float scale_g = (lut3d->scale.g / ((1<<depth) - 1)) * (lut3d->lutsize - 1); \
+ const float scale_b = (lut3d->scale.b / ((1<<depth) - 1)) * (lut3d->lutsize - 1); \
\
for (y = slice_start; y < slice_end; y++) { \
uint##nbits##_t *dstg = (uint##nbits##_t *)grow; \
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<<depth) - 1), depth); \
dstg[x] = av_clip_uintp2(vec.g * (float)((1<<depth) - 1), depth); \
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 scale = (1. / ((1<<nbits) - 1)) * (lut3d->lutsize - 1); \
+ const float scale_r = (lut3d->scale.r / ((1<<nbits) - 1)) * (lut3d->lutsize - 1); \
+ const float scale_g = (lut3d->scale.g / ((1<<nbits) - 1)) * (lut3d->lutsize - 1); \
+ const float scale_b = (lut3d->scale.b / ((1<<nbits) - 1)) * (lut3d->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<<nbits) - 1)); \
dst[x + g] = av_clip_uint##nbits(vec.g * (float)((1<<nbits) - 1)); \
} \
} while (loop_cond)
+static int allocate_3dlut(AVFilterContext *ctx, int lutsize)
+{
+ LUT3DContext *lut3d = ctx->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;
}
}
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:
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;
}
{
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;
{
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};
}
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;
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)
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);
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));
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);
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",
.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,
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; \
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); \
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; \
(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); \
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)
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;
}
{
LUT3DContext *lut3d = ctx->priv;
ff_framesync_uninit(&lut3d->fs);
+ av_freep(&lut3d->lut);
}
static const AVOption haldclut_options[] = {
.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; ///<interp_1d_mode
+ struct rgbvec scale;
+ uint8_t rgba_map[4];
+ int step;
+ float lut[3][MAX_1D_LEVEL];
+ int lutsize;
+ avfilter_action_func *interp;
+} LUT1DContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(LUT1DContext, x)
+
+static void set_identity_matrix_1d(LUT1DContext *lut1d, int size)
+{
+ const float c = 1. / (size - 1);
+ int i;
+
+ lut1d->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