FFmpeg
lut3d.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2024 Niklas Haas
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include <assert.h>
22 #include <string.h>
23 
24 #include "libavutil/attributes.h"
25 #include "libavutil/avassert.h"
26 #include "libavutil/mem.h"
27 
28 #include "cms.h"
29 #include "csputils.h"
30 #include "lut3d.h"
31 
33 {
34  SwsLut3D *lut3d = av_malloc(sizeof(*lut3d));
35  if (!lut3d)
36  return NULL;
37 
38  lut3d->dynamic = false;
39  return lut3d;
40 }
41 
42 void sws_lut3d_free(SwsLut3D **plut3d)
43 {
44  av_freep(plut3d);
45 }
46 
48 {
49  return fmt == AV_PIX_FMT_RGBA64;
50 }
51 
53 {
54  return AV_PIX_FMT_RGBA64;
55 }
56 
57 /**
58  * v0 and v1 are 'black' and 'white'
59  * v2 and v3 are closest RGB/CMY vertices
60  * x >= y >= z are relative weights
61  */
62 static av_always_inline
63 v3u16_t barycentric(int shift, int x, int y, int z,
64  v3u16_t v0, v3u16_t v1, v3u16_t v2, v3u16_t v3)
65 {
66  const int a = (1 << shift) - x;
67  const int b = x - y;
68  const int c = y - z;
69  const int d = z;
70  av_assert2(x >= y);
71  av_assert2(y >= z);
72 
73  return (v3u16_t) {
74  (a * v0.x + b * v1.x + c * v2.x + d * v3.x) >> shift,
75  (a * v0.y + b * v1.y + c * v2.y + d * v3.y) >> shift,
76  (a * v0.z + b * v1.z + c * v2.z + d * v3.z) >> shift,
77  };
78 }
79 
80 static av_always_inline
81 v3u16_t tetrahedral(const SwsLut3D *lut3d, int Rx, int Gx, int Bx,
82  int Rf, int Gf, int Bf)
83 {
84  const int shift = 16 - INPUT_LUT_BITS;
85  const int Rn = FFMIN(Rx + 1, INPUT_LUT_SIZE - 1);
86  const int Gn = FFMIN(Gx + 1, INPUT_LUT_SIZE - 1);
87  const int Bn = FFMIN(Bx + 1, INPUT_LUT_SIZE - 1);
88 
89  const v3u16_t c000 = lut3d->input[Bx][Gx][Rx];
90  const v3u16_t c111 = lut3d->input[Bn][Gn][Rn];
91  if (Rf > Gf) {
92  if (Gf > Bf) {
93  const v3u16_t c100 = lut3d->input[Bx][Gx][Rn];
94  const v3u16_t c110 = lut3d->input[Bx][Gn][Rn];
95  return barycentric(shift, Rf, Gf, Bf, c000, c100, c110, c111);
96  } else if (Rf > Bf) {
97  const v3u16_t c100 = lut3d->input[Bx][Gx][Rn];
98  const v3u16_t c101 = lut3d->input[Bn][Gx][Rn];
99  return barycentric(shift, Rf, Bf, Gf, c000, c100, c101, c111);
100  } else {
101  const v3u16_t c001 = lut3d->input[Bn][Gx][Rx];
102  const v3u16_t c101 = lut3d->input[Bn][Gx][Rn];
103  return barycentric(shift, Bf, Rf, Gf, c000, c001, c101, c111);
104  }
105  } else {
106  if (Bf > Gf) {
107  const v3u16_t c001 = lut3d->input[Bn][Gx][Rx];
108  const v3u16_t c011 = lut3d->input[Bn][Gn][Rx];
109  return barycentric(shift, Bf, Gf, Rf, c000, c001, c011, c111);
110  } else if (Bf > Rf) {
111  const v3u16_t c010 = lut3d->input[Bx][Gn][Rx];
112  const v3u16_t c011 = lut3d->input[Bn][Gn][Rx];
113  return barycentric(shift, Gf, Bf, Rf, c000, c010, c011, c111);
114  } else {
115  const v3u16_t c010 = lut3d->input[Bx][Gn][Rx];
116  const v3u16_t c110 = lut3d->input[Bx][Gn][Rn];
117  return barycentric(shift, Gf, Rf, Bf, c000, c010, c110, c111);
118  }
119  }
120 }
121 
123 {
124  const int shift = 16 - INPUT_LUT_BITS;
125  const int Rx = rgb.x >> shift;
126  const int Gx = rgb.y >> shift;
127  const int Bx = rgb.z >> shift;
128  const int Rf = rgb.x & ((1 << shift) - 1);
129  const int Gf = rgb.y & ((1 << shift) - 1);
130  const int Bf = rgb.z & ((1 << shift) - 1);
131  return tetrahedral(lut3d, Rx, Gx, Bx, Rf, Gf, Bf);
132 }
133 
135 {
136  static_assert(INPUT_LUT_BITS <= 8, "INPUT_LUT_BITS must be <= 8");
137  const int shift = 8 - INPUT_LUT_BITS;
138  const int Rx = rgb.x >> shift;
139  const int Gx = rgb.y >> shift;
140  const int Bx = rgb.z >> shift;
141  const int Rf = rgb.x & ((1 << shift) - 1);
142  const int Gf = rgb.y & ((1 << shift) - 1);
143  const int Bf = rgb.z & ((1 << shift) - 1);
144  return tetrahedral(lut3d, Rx, Gx, Bx, Rf, Gf, Bf);
145 }
146 
147 /**
148  * Note: These functions are scaled such that x == (1 << shift) corresponds to
149  * a value of 1.0. This makes them suitable for use when interpolation LUT
150  * entries with a fractional part that is just masked away from the index,
151  * since a fractional coordinate of e.g. 0xFFFF corresponds to a mix weight of
152  * just slightly *less* than 1.0.
153  */
155 {
156  const int xi = (1 << shift) - x;
157  return (v2u16_t) {
158  (a.x * xi + b.x * x) >> shift,
159  (a.y * xi + b.y * x) >> shift,
160  };
161 }
162 
164 {
165  const int xi = (1 << shift) - x;
166  return (v3u16_t) {
167  (a.x * xi + b.x * x) >> shift,
168  (a.y * xi + b.y * x) >> shift,
169  (a.z * xi + b.z * x) >> shift,
170  };
171 }
172 
174 {
175  const int Ishift = 16 - OUTPUT_LUT_BITS_I;
176  const int Cshift = 16 - OUTPUT_LUT_BITS_PT;
177  const int Ix = ipt.x >> Ishift;
178  const int Px = ipt.y >> Cshift;
179  const int Tx = ipt.z >> Cshift;
180  const int If = ipt.x & ((1 << Ishift) - 1);
181  const int Pf = ipt.y & ((1 << Cshift) - 1);
182  const int Tf = ipt.z & ((1 << Cshift) - 1);
183  const int In = FFMIN(Ix + 1, OUTPUT_LUT_SIZE_I - 1);
184  const int Pn = FFMIN(Px + 1, OUTPUT_LUT_SIZE_PT - 1);
185  const int Tn = FFMIN(Tx + 1, OUTPUT_LUT_SIZE_PT - 1);
186 
187  /* Trilinear interpolation */
188  const v3u16_t c000 = lut3d->output[Tx][Px][Ix];
189  const v3u16_t c001 = lut3d->output[Tx][Px][In];
190  const v3u16_t c010 = lut3d->output[Tx][Pn][Ix];
191  const v3u16_t c011 = lut3d->output[Tx][Pn][In];
192  const v3u16_t c100 = lut3d->output[Tn][Px][Ix];
193  const v3u16_t c101 = lut3d->output[Tn][Px][In];
194  const v3u16_t c110 = lut3d->output[Tn][Pn][Ix];
195  const v3u16_t c111 = lut3d->output[Tn][Pn][In];
196  const v3u16_t c00 = lerp3u16(c000, c100, Tf, Cshift);
197  const v3u16_t c10 = lerp3u16(c010, c110, Tf, Cshift);
198  const v3u16_t c01 = lerp3u16(c001, c101, Tf, Cshift);
199  const v3u16_t c11 = lerp3u16(c011, c111, Tf, Cshift);
200  const v3u16_t c0 = lerp3u16(c00, c10, Pf, Cshift);
201  const v3u16_t c1 = lerp3u16(c01, c11, Pf, Cshift);
202  const v3u16_t c = lerp3u16(c0, c1, If, Ishift);
203  return c;
204 }
205 
207 {
208  const int shift = 16 - TONE_LUT_BITS;
209  const int Ix = ipt.x >> shift;
210  const int If = ipt.x & ((1 << shift) - 1);
211  const int In = FFMIN(Ix + 1, TONE_LUT_SIZE - 1);
212 
213  const v2u16_t w0 = lut3d->tone_map[Ix];
214  const v2u16_t w1 = lut3d->tone_map[In];
215  const v2u16_t w = lerp2u16(w0, w1, If, shift);
216  const int base = (1 << 15) - w.y;
217 
218  ipt.x = w.x;
219  ipt.y = base + (ipt.y * w.y >> 15);
220  ipt.z = base + (ipt.z * w.y >> 15);
221  return ipt;
222 }
223 
224 int sws_lut3d_generate(SwsLut3D *lut3d, enum AVPixelFormat fmt_in,
225  enum AVPixelFormat fmt_out, const SwsColorMap *map)
226 {
227  int ret;
228 
229  if (!sws_lut3d_test_fmt(fmt_in, 0) || !sws_lut3d_test_fmt(fmt_out, 1))
230  return AVERROR(EINVAL);
231 
232  lut3d->dynamic = map->src.frame_peak.num > 0;
233  lut3d->map = *map;
234 
235  if (lut3d->dynamic) {
236  ret = sws_color_map_generate_dynamic(&lut3d->input[0][0][0],
237  &lut3d->output[0][0][0],
240  if (ret < 0)
241  return ret;
242 
243  /* Make sure initial state is valid */
244  sws_lut3d_update(lut3d, &map->src);
245  return 0;
246  } else {
247  return sws_color_map_generate_static(&lut3d->input[0][0][0],
249  }
250 }
251 
252 void sws_lut3d_update(SwsLut3D *lut3d, const SwsColor *new_src)
253 {
254  if (!new_src || !lut3d->dynamic)
255  return;
256 
257  lut3d->map.src.frame_peak = new_src->frame_peak;
258  lut3d->map.src.frame_avg = new_src->frame_avg;
259 
261 }
262 
263 void sws_lut3d_apply(const SwsLut3D *lut3d, const uint8_t *in, int in_stride,
264  uint8_t *out, int out_stride, int w, int h)
265 {
266  while (h--) {
267  const uint16_t *in16 = (const uint16_t *) in;
268  uint16_t *out16 = (uint16_t *) out;
269 
270  for (int x = 0; x < w; x++) {
271  v3u16_t c = { in16[0], in16[1], in16[2] };
272  c = lookup_input16(lut3d, c);
273 
274  if (lut3d->dynamic) {
275  c = apply_tone_map(lut3d, c);
276  c = lookup_output(lut3d, c);
277  }
278 
279  out16[0] = c.x;
280  out16[1] = c.y;
281  out16[2] = c.z;
282  out16[3] = in16[3];
283  in16 += 4;
284  out16 += 4;
285  }
286 
287  in += in_stride;
288  out += out_stride;
289  }
290 }
AVPixelFormat
AVPixelFormat
Pixel format.
Definition: pixfmt.h:71
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
INPUT_LUT_SIZE
@ INPUT_LUT_SIZE
Definition: lut3d.h:36
SwsLut3D::dynamic
bool dynamic
Definition: lut3d.h:52
sws_lut3d_update
void sws_lut3d_update(SwsLut3D *lut3d, const SwsColor *new_src)
Update the tone mapping state.
Definition: lut3d.c:252
out
FILE * out
Definition: movenc.c:55
sws_lut3d_alloc
SwsLut3D * sws_lut3d_alloc(void)
Definition: lut3d.c:32
v3u16_t
Definition: csputils.h:77
SwsLut3D::output
v3u16_t output[OUTPUT_LUT_SIZE_PT][OUTPUT_LUT_SIZE_PT][OUTPUT_LUT_SIZE_I]
Definition: lut3d.h:56
output
filter_frame For filters that do not use the this method is called when a frame is pushed to the filter s input It can be called at any time except in a reentrant way If the input frame is enough to produce output
Definition: filter_design.txt:225
w
uint8_t w
Definition: llviddspenc.c:38
b
#define b
Definition: input.c:41
TONE_LUT_SIZE
@ TONE_LUT_SIZE
Definition: lut3d.h:40
base
uint8_t base
Definition: vp3data.h:128
SwsLut3D::tone_map
v2u16_t tone_map[TONE_LUT_SIZE]
Definition: lut3d.h:59
OUTPUT_LUT_BITS_PT
@ OUTPUT_LUT_BITS_PT
Definition: lut3d.h:44
c1
static const uint64_t c1
Definition: murmur3.c:52
v3u16_t::y
uint16_t y
Definition: csputils.h:78
lookup_input16
static av_always_inline v3u16_t lookup_input16(const SwsLut3D *lut3d, v3u16_t rgb)
Definition: lut3d.c:122
av_malloc
#define av_malloc(s)
Definition: tableprint_vlc.h:30
SwsColorMap
Definition: cms.h:60
rgb
Definition: rpzaenc.c:60
sws_lut3d_pick_pixfmt
enum AVPixelFormat sws_lut3d_pick_pixfmt(SwsFormat fmt, int output)
Pick the best compatible pixfmt for a given SwsFormat.
Definition: lut3d.c:52
apply_tone_map
static av_always_inline v3u16_t apply_tone_map(const SwsLut3D *lut3d, v3u16_t ipt)
Definition: lut3d.c:206
sws_color_map_generate_static
int sws_color_map_generate_static(v3u16_t *lut, int size, const SwsColorMap *map)
Generates a single end-to-end color mapping 3DLUT embedding a static tone mapping curve.
Definition: cms.c:679
avassert.h
lerp2u16
static av_always_inline v2u16_t lerp2u16(v2u16_t a, v2u16_t b, int x, int shift)
Note: These functions are scaled such that x == (1 << shift) corresponds to a value of 1....
Definition: lut3d.c:154
sws_color_map_generate_dynamic
int sws_color_map_generate_dynamic(v3u16_t *input, v3u16_t *output, int size_input, int size_I, int size_PT, const SwsColorMap *map)
Generates a split pair of 3DLUTS, going to IPT and back, allowing an arbitrary dynamic EETF to be nes...
Definition: cms.c:684
xi
#define xi(width, name, var, range_min, range_max, subs,...)
Definition: cbs_h2645.c:418
lookup_output
static av_always_inline v3u16_t lookup_output(const SwsLut3D *lut3d, v3u16_t ipt)
Definition: lut3d.c:173
lerp3u16
static av_always_inline v3u16_t lerp3u16(v3u16_t a, v3u16_t b, int x, int shift)
Definition: lut3d.c:163
SwsColor::frame_peak
AVRational frame_peak
Definition: utils.h:64
AV_PIX_FMT_RGBA64
#define AV_PIX_FMT_RGBA64
Definition: pixfmt.h:492
v2u16_t
Definition: csputils.h:73
NULL
#define NULL
Definition: coverity.c:32
sws_lut3d_apply
void sws_lut3d_apply(const SwsLut3D *lut3d, const uint8_t *in, int in_stride, uint8_t *out, int out_stride, int w, int h)
Applies a color transformation to a plane.
Definition: lut3d.c:263
TONE_LUT_BITS
@ TONE_LUT_BITS
Definition: lut3d.h:39
c
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
SwsLut3D::input
v3u16_t input[INPUT_LUT_SIZE][INPUT_LUT_SIZE][INPUT_LUT_SIZE]
Definition: lut3d.h:55
lut3d.h
shift
static int shift(int a, int b)
Definition: bonk.c:261
SwsFormat
Definition: utils.h:75
a
The reader does not expect b to be semantically here and if the code is changed by maybe adding a a division or other the signedness will almost certainly be mistaken To avoid this confusion a new type was SUINT is the C unsigned type but it holds a signed int to use the same example SUINT a
Definition: undefined.txt:41
SwsColor
Definition: utils.h:58
attributes.h
SwsColor::frame_avg
AVRational frame_avg
Definition: utils.h:65
SwsLut3D::map
SwsColorMap map
Definition: lut3d.h:51
barycentric
static av_always_inline v3u16_t barycentric(int shift, int x, int y, int z, v3u16_t v0, v3u16_t v1, v3u16_t v2, v3u16_t v3)
v0 and v1 are 'black' and 'white' v2 and v3 are closest RGB/CMY vertices x >= y >= z are relative wei...
Definition: lut3d.c:63
v3u8_t
Definition: csputils.h:69
av_assert2
#define av_assert2(cond)
assert() equivalent, that does lie in speed critical code.
Definition: avassert.h:67
SwsLut3D
Definition: lut3d.h:50
sws_lut3d_free
void sws_lut3d_free(SwsLut3D **plut3d)
Definition: lut3d.c:42
tetrahedral
static av_always_inline v3u16_t tetrahedral(const SwsLut3D *lut3d, int Rx, int Gx, int Bx, int Rf, int Gf, int Bf)
Definition: lut3d.c:81
OUTPUT_LUT_SIZE_I
@ OUTPUT_LUT_SIZE_I
Definition: lut3d.h:46
av_always_inline
#define av_always_inline
Definition: attributes.h:49
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
ret
ret
Definition: filter_design.txt:187
sws_lut3d_generate
int sws_lut3d_generate(SwsLut3D *lut3d, enum AVPixelFormat fmt_in, enum AVPixelFormat fmt_out, const SwsColorMap *map)
Recalculate the (static) 3DLUT state with new settings.
Definition: lut3d.c:224
OUTPUT_LUT_BITS_I
@ OUTPUT_LUT_BITS_I
Definition: lut3d.h:43
sws_tone_map_generate
void sws_tone_map_generate(v2u16_t *lut, int size, const SwsColorMap *map)
Generate a 1D LUT of size size adapting intensity (I) levels from the source to the destination color...
Definition: cms.c:743
cms.h
v3u16_t::x
uint16_t x
Definition: csputils.h:78
lookup_input8
static av_always_inline v3u16_t lookup_input8(const SwsLut3D *lut3d, v3u8_t rgb)
Definition: lut3d.c:134
mem.h
map
const VDPAUPixFmtMap * map
Definition: hwcontext_vdpau.c:71
v3u16_t::z
uint16_t z
Definition: csputils.h:78
INPUT_LUT_BITS
@ INPUT_LUT_BITS
Definition: lut3d.h:35
OUTPUT_LUT_SIZE_PT
@ OUTPUT_LUT_SIZE_PT
Definition: lut3d.h:47
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:34
sws_lut3d_test_fmt
bool sws_lut3d_test_fmt(enum AVPixelFormat fmt, int output)
Test to see if a given format is supported by the 3DLUT input/output code.
Definition: lut3d.c:47
h
h
Definition: vp9dsp_template.c:2070
SwsColorMap::src
SwsColor src
Definition: cms.h:61
csputils.h