FFmpeg
Loading...
Searching...
No Matches
cms.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 <math.h>
22#include <string.h>
23
25#include "libavutil/avassert.h"
26#include "libavutil/csp.h"
28
29#include "cms.h"
30#include "csputils.h"
31#include "libswscale/swscale.h"
32#include "format.h"
33
35{
36 /* If the encoding space is different, we must go through a conversion */
37 if (map->src.prim != map->dst.prim || map->src.trc != map->dst.trc)
38 return false;
39
40 /* If the black point changes, we have to perform black point compensation */
41 if (av_cmp_q(map->src.min_luma, map->dst.min_luma))
42 return false;
43
44 switch (map->intent) {
47 return ff_prim_superset(&map->dst.gamut, &map->src.gamut) &&
48 av_cmp_q(map->src.max_luma, map->dst.max_luma) <= 0;
51 return ff_prim_equal(&map->dst.gamut, &map->src.gamut) &&
52 !av_cmp_q(map->src.max_luma, map->dst.max_luma);
53 default:
54 av_assert0(!"Invalid gamut mapping intent?");
55 return true;
56 }
57}
58
59/* Approximation of gamut hull at a given intensity level */
60static float hull(float I)
61{
62 return ((I - 6.0f) * I + 9.0f) * I;
63}
64
65/* For some minimal type safety, and code cleanliness */
66typedef struct RGB {
67 float R, G, B; /* nits */
68} RGB;
69
70typedef struct IPT {
71 float I, P, T;
72} IPT;
73
74typedef struct ICh {
75 float I, C, h;
76} ICh;
77
79{
80 return (ICh) {
81 .I = c.I,
82 .C = sqrtf(c.P * c.P + c.T * c.T),
83 .h = atan2f(c.T, c.P),
84 };
85}
86
88{
89 return (IPT) {
90 .I = c.I,
91 .P = c.C * cosf(c.h),
92 .T = c.C * sinf(c.h),
93 };
94}
95
96/* Helper struct containing pre-computed cached values describing a gamut */
111
113{
115 const AVColorPrimariesDesc content = {
116 .prim = fmt->gamut,
117 .wp = encoding->wp,
118 };
119
120 const float Lw = av_q2d(fmt->max_luma), Lb = av_q2d(fmt->min_luma);
121 const float Imax = pq_oetf(Lw);
122
123 return (Gamut) {
124 .encoding2lms = ff_sws_ipt_rgb2lms(encoding),
125 .lms2encoding = ff_sws_ipt_lms2rgb(encoding),
126 .lms2content = ff_sws_ipt_lms2rgb(&content),
127 .content2lms = ff_sws_ipt_rgb2lms(&content),
128 .eotf = av_csp_itu_eotf(fmt->trc),
129 .eotf_inv = av_csp_itu_eotf_inv(fmt->trc),
130 .wp = encoding->wp,
131 .Imin = pq_oetf(Lb),
132 .Imax = Imax,
133 .Imax_frame = fmt->frame_peak.den ? pq_oetf(av_q2d(fmt->frame_peak)) : Imax,
134 .Iavg_frame = fmt->frame_avg.den ? pq_oetf(av_q2d(fmt->frame_avg)) : 0.0f,
135 .Lb = Lb,
136 .Lw = Lw,
137 };
138}
139
141{
142 const float L = rgb2lms.m[0][0] * c.R +
143 rgb2lms.m[0][1] * c.G +
144 rgb2lms.m[0][2] * c.B;
145 const float M = rgb2lms.m[1][0] * c.R +
146 rgb2lms.m[1][1] * c.G +
147 rgb2lms.m[1][2] * c.B;
148 const float S = rgb2lms.m[2][0] * c.R +
149 rgb2lms.m[2][1] * c.G +
150 rgb2lms.m[2][2] * c.B;
151 const float Lp = pq_oetf(L);
152 const float Mp = pq_oetf(M);
153 const float Sp = pq_oetf(S);
154 return (IPT) {
155 .I = 0.4000f * Lp + 0.4000f * Mp + 0.2000f * Sp,
156 .P = 4.4550f * Lp - 4.8510f * Mp + 0.3960f * Sp,
157 .T = 0.8056f * Lp + 0.3572f * Mp - 1.1628f * Sp,
158 };
159}
160
162{
163 const float Lp = c.I + 0.0975689f * c.P + 0.205226f * c.T;
164 const float Mp = c.I - 0.1138760f * c.P + 0.133217f * c.T;
165 const float Sp = c.I + 0.0326151f * c.P - 0.676887f * c.T;
166 const float L = pq_eotf(Lp);
167 const float M = pq_eotf(Mp);
168 const float S = pq_eotf(Sp);
169 return (RGB) {
170 .R = lms2rgb.m[0][0] * L +
171 lms2rgb.m[0][1] * M +
172 lms2rgb.m[0][2] * S,
173 .G = lms2rgb.m[1][0] * L +
174 lms2rgb.m[1][1] * M +
175 lms2rgb.m[1][2] * S,
176 .B = lms2rgb.m[2][0] * L +
177 lms2rgb.m[2][1] * M +
178 lms2rgb.m[2][2] * S,
179 };
180}
181
182static inline bool ingamut(IPT c, Gamut gamut)
183{
184 const float min_rgb = gamut.Lb - 1e-4f;
185 const float max_rgb = gamut.Lw + 1e-2f;
186 const float Lp = c.I + 0.0975689f * c.P + 0.205226f * c.T;
187 const float Mp = c.I - 0.1138760f * c.P + 0.133217f * c.T;
188 const float Sp = c.I + 0.0326151f * c.P - 0.676887f * c.T;
189 if (Lp < gamut.Imin || Lp > gamut.Imax ||
190 Mp < gamut.Imin || Mp > gamut.Imax ||
191 Sp < gamut.Imin || Sp > gamut.Imax)
192 {
193 /* Values outside legal LMS range */
194 return false;
195 } else {
196 const float L = pq_eotf(Lp);
197 const float M = pq_eotf(Mp);
198 const float S = pq_eotf(Sp);
199 RGB rgb = {
200 .R = gamut.lms2content.m[0][0] * L +
201 gamut.lms2content.m[0][1] * M +
202 gamut.lms2content.m[0][2] * S,
203 .G = gamut.lms2content.m[1][0] * L +
204 gamut.lms2content.m[1][1] * M +
205 gamut.lms2content.m[1][2] * S,
206 .B = gamut.lms2content.m[2][0] * L +
207 gamut.lms2content.m[2][1] * M +
208 gamut.lms2content.m[2][2] * S,
209 };
210 return rgb.R >= min_rgb && rgb.R <= max_rgb &&
211 rgb.G >= min_rgb && rgb.G <= max_rgb &&
212 rgb.B >= min_rgb && rgb.B <= max_rgb;
213 }
214}
215
216static const float maxDelta = 5e-5f;
217
218// Find gamut intersection using specified bounds
219static inline ICh
220desat_bounded(float I, float h, float Cmin, float Cmax, Gamut gamut)
221{
222 if (I <= gamut.Imin)
223 return (ICh) { .I = gamut.Imin, .C = 0, .h = h };
224 else if (I >= gamut.Imax)
225 return (ICh) { .I = gamut.Imax, .C = 0, .h = h };
226 else {
227 const float maxDI = I * maxDelta;
228 ICh res = { .I = I, .C = (Cmin + Cmax) / 2, .h = h };
229 do {
230 if (ingamut(ich2ipt(res), gamut)) {
231 Cmin = res.C;
232 } else {
233 Cmax = res.C;
234 }
235 res.C = (Cmin + Cmax) / 2;
236 } while (Cmax - Cmin > maxDI);
237
238 return res;
239 }
240}
241
242// Finds maximally saturated in-gamut color (for given hue)
243static inline ICh saturate(float hue, Gamut gamut)
244{
245 static const float invphi = 0.6180339887498948f;
246 static const float invphi2 = 0.38196601125010515f;
247
248 ICh lo = { .I = gamut.Imin, .h = hue };
249 ICh hi = { .I = gamut.Imax, .h = hue };
250 float de = hi.I - lo.I;
251 ICh a = { .I = lo.I + invphi2 * de };
252 ICh b = { .I = lo.I + invphi * de };
253 a = desat_bounded(a.I, hue, 0.0f, 0.5f, gamut);
254 b = desat_bounded(b.I, hue, 0.0f, 0.5f, gamut);
255
256 while (de > maxDelta) {
257 de *= invphi;
258 if (a.C > b.C) {
259 hi = b;
260 b = a;
261 a.I = lo.I + invphi2 * de;
262 a = desat_bounded(a.I, hue, lo.C - maxDelta, 0.5f, gamut);
263 } else {
264 lo = a;
265 a = b;
266 b.I = lo.I + invphi * de;
267 b = desat_bounded(b.I, hue, hi.C - maxDelta, 0.5f, gamut);
268 }
269 }
270
271 return a.C > b.C ? a : b;
272}
273
274static float softclip(float value, float source, float target)
275{
276 const float j = SOFTCLIP_KNEE;
277 float peak, x, a, b, scale;
278 if (!target)
279 return 0.0f;
280
281 peak = source / target;
282 x = fminf(value / target, peak);
283 if (x <= j || peak <= 1.0)
284 return value;
285
286 /* Apply simple mobius function */
287 a = -j*j * (peak - 1.0f) / (j*j - 2.0f * j + peak);
288 b = (j*j - 2.0f * j * peak + peak) / fmaxf(1e-6f, peak - 1.0f);
289 scale = (b*b + 2.0f * b*j + j*j) / (b - a);
290
291 return scale * (x + a) / (x + b) * target;
292}
293
294/**
295 * Something like fmixf(base, c, x) but follows an exponential curve, note
296 * that this can be used to extend 'c' outwards for x > 1
297 */
298static inline ICh mix_exp(ICh c, float x, float gamma, float base)
299{
300 return (ICh) {
301 .I = base + (c.I - base) * powf(x, gamma),
302 .C = c.C * x,
303 .h = c.h,
304 };
305}
306
307/**
308 * Drop gamma for colors approaching black and achromatic to avoid numerical
309 * instabilities, and excessive brightness boosting of grain, while also
310 * strongly boosting gamma for values exceeding the target peak
311 */
312static inline float scale_gamma(float gamma, ICh ich, Gamut gamut)
313{
314 const float Imin = gamut.Imin;
315 const float Irel = fmaxf((ich.I - Imin) / (gamut.peak.I - Imin), 0.0f);
316 return gamma * powf(Irel, 3) * fminf(ich.C / gamut.peak.C, 1.0f);
317}
318
319/* Clip a color along the exponential curve given by `gamma` */
320static inline IPT clip_gamma(IPT ipt, float gamma, Gamut gamut)
321{
322 float lo = 0.0f, hi = 1.0f, x = 0.5f;
323 const float maxDI = fmaxf(ipt.I * maxDelta, 1e-7f);
324 ICh ich;
325
326 if (ipt.I <= gamut.Imin)
327 return (IPT) { .I = gamut.Imin };
328 if (ingamut(ipt, gamut))
329 return ipt;
330
331 ich = ipt2ich(ipt);
332 if (!gamma)
333 return ich2ipt(desat_bounded(ich.I, ich.h, 0.0f, ich.C, gamut));
334
335 gamma = scale_gamma(gamma, ich, gamut);
336 do {
337 ICh test = mix_exp(ich, x, gamma, gamut.peak.I);
338 if (ingamut(ich2ipt(test), gamut)) {
339 lo = x;
340 } else {
341 hi = x;
342 }
343 x = (lo + hi) / 2.0f;
344 } while (hi - lo > maxDI);
345
346 return ich2ipt(mix_exp(ich, x, gamma, gamut.peak.I));
347}
348
349typedef struct CmsCtx CmsCtx;
350struct CmsCtx {
351 /* Tone mapping parameters */
352 float Qa, Qb, Qc, Pa, Pb, src_knee, dst_knee; /* perceptual */
353 float I_scale, I_offset; /* linear methods */
354
355 /* Colorspace parameters */
357 Gamut tmp; /* after tone mapping */
359 SwsMatrix3x3 adaptation; /* for absolute intent */
360
361 /* Invocation parameters */
363 float (*tone_map)(const CmsCtx *ctx, float I);
364 IPT (*adapt_colors)(const CmsCtx *ctx, IPT ipt);
367
368 /* Threading parameters */
373};
374
375/**
376 * Helper function to pick a knee point based on the * HDR10+ brightness
377 * metadata and scene brightness average matching.
378 *
379 * Inspired by SMPTE ST2094-10, with some modifications
380 */
381static void st2094_pick_knee(float src_max, float src_min, float src_avg,
382 float dst_max, float dst_min,
383 float *out_src_knee, float *out_dst_knee)
384{
385 const float min_knee = PERCEPTUAL_KNEE_MIN;
386 const float max_knee = PERCEPTUAL_KNEE_MAX;
387 const float def_knee = PERCEPTUAL_KNEE_DEF;
388 const float src_knee_min = fmixf(src_min, src_max, min_knee);
389 const float src_knee_max = fmixf(src_min, src_max, max_knee);
390 const float dst_knee_min = fmixf(dst_min, dst_max, min_knee);
391 const float dst_knee_max = fmixf(dst_min, dst_max, max_knee);
392 float src_knee, target, adapted, tuning, adaptation, dst_knee;
393
394 /* Choose source knee based on dynamic source scene brightness */
395 src_knee = src_avg ? src_avg : fmixf(src_min, src_max, def_knee);
396 src_knee = av_clipf(src_knee, src_knee_min, src_knee_max);
397
398 /* Choose target adaptation point based on linearly re-scaling source knee */
399 target = (src_knee - src_min) / (src_max - src_min);
400 adapted = fmixf(dst_min, dst_max, target);
401
402 /**
403 * Choose the destination knee by picking the perceptual adaptation point
404 * between the source knee and the desired target. This moves the knee
405 * point, on the vertical axis, closer to the 1:1 (neutral) line.
406 *
407 * Adjust the adaptation strength towards 1 based on how close the knee
408 * point is to its extreme values (min/max knee)
409 */
410 tuning = smoothstepf(max_knee, def_knee, target) *
411 smoothstepf(min_knee, def_knee, target);
412 adaptation = fmixf(1.0f, PERCEPTUAL_ADAPTATION, tuning);
413 dst_knee = fmixf(src_knee, adapted, adaptation);
414 dst_knee = av_clipf(dst_knee, dst_knee_min, dst_knee_max);
415
416 *out_src_knee = src_knee;
417 *out_dst_knee = dst_knee;
418}
419
420static void tone_map_setup(CmsCtx *ctx, bool dynamic)
421{
422 const float dst_min = ctx->dst.Imin;
423 const float dst_max = ctx->dst.Imax;
424 const float src_min = ctx->src.Imin;
425 const float src_max = dynamic ? ctx->src.Imax_frame : ctx->src.Imax;
426 const float src_avg = dynamic ? ctx->src.Iavg_frame : 0.0f;
427 float slope, ratio, in_min, in_max, out_min, out_max, t;
428
429 switch (ctx->map.intent) {
431 st2094_pick_knee(src_max, src_min, src_avg, dst_max, dst_min,
432 &ctx->src_knee, &ctx->dst_knee);
433
434 /* Solve for linear knee (Pa = 0) */
435 slope = (ctx->dst_knee - dst_min) / (ctx->src_knee - src_min);
436
437 /**
438 * Tune the slope at the knee point slightly: raise it to a user-provided
439 * gamma exponent, multiplied by an extra tuning coefficient designed to
440 * make the slope closer to 1.0 when the difference in peaks is low, and
441 * closer to linear when the difference between peaks is high.
442 */
443 ratio = src_max / dst_max - 1.0f;
444 ratio = av_clipf(SLOPE_TUNING * ratio, SLOPE_OFFSET, 1.0f + SLOPE_OFFSET);
445 slope = powf(slope, (1.0f - PERCEPTUAL_CONTRAST) * ratio);
446
447 /* Normalize everything the pivot to make the math easier */
448 in_min = src_min - ctx->src_knee;
449 in_max = src_max - ctx->src_knee;
450 out_min = dst_min - ctx->dst_knee;
451 out_max = dst_max - ctx->dst_knee;
452
453 /**
454 * Solve P of order 2 for:
455 * P(in_min) = out_min
456 * P'(0.0) = slope
457 * P(0.0) = 0.0
458 */
459 ctx->Pa = (out_min - slope * in_min) / (in_min * in_min);
460 ctx->Pb = slope;
461
462 /**
463 * Solve Q of order 3 for:
464 * Q(in_max) = out_max
465 * Q''(in_max) = 0.0
466 * Q(0.0) = 0.0
467 * Q'(0.0) = slope
468 */
469 t = 2 * in_max * in_max;
470 ctx->Qa = (slope * in_max - out_max) / (in_max * t);
471 ctx->Qb = -3 * (slope * in_max - out_max) / t;
472 ctx->Qc = slope;
473 break;
475 /* Linear stretch */
476 ctx->I_scale = (dst_max - dst_min) / (src_max - src_min);
477 ctx->I_offset = dst_min - src_min * ctx->I_scale;
478 break;
480 /* Pure black point adaptation */
481 ctx->I_scale = src_max / (src_max - src_min) /
482 (dst_max / (dst_max - dst_min));
483 ctx->I_offset = dst_min - src_min * ctx->I_scale;
484 break;
486 /* Hard clip */
487 ctx->I_scale = 1.0f;
488 ctx->I_offset = 0.0f;
489 break;
490 }
491}
492
494{
495 float I = ipt.I, desat;
496
497 if (ctx->map.intent == SWS_INTENT_PERCEPTUAL) {
498 const float Pa = ctx->Pa, Pb = ctx->Pb;
499 const float Qa = ctx->Qa, Qb = ctx->Qb, Qc = ctx->Qc;
500 I -= ctx->src_knee;
501 I = I > 0 ? ((Qa * I + Qb) * I + Qc) * I : (Pa * I + Pb) * I;
502 I += ctx->dst_knee;
503 } else {
504 I = ctx->I_scale * I + ctx->I_offset;
505 }
506
507 /**
508 * Avoids raising saturation excessively when raising brightness, and
509 * also desaturates when reducing brightness greatly to account for the
510 * reduction in gamut volume.
511 */
512 desat = fminf(ipt.I / I, hull(I) / hull(ipt.I));
513 return (IPT) {
514 .I = I,
515 .P = ipt.P * desat,
516 .T = ipt.T * desat,
517 };
518}
519
520static IPT perceptual(const CmsCtx *ctx, IPT ipt)
521{
522 ICh ich = ipt2ich(ipt);
523 IPT mapped = rgb2ipt(ipt2rgb(ipt, ctx->tmp.lms2content), ctx->dst.content2lms);
524 RGB rgb;
525 float maxRGB;
526
527 /* Protect in gamut region */
528 const float maxC = fmaxf(ctx->tmp.peak.C, ctx->dst.peak.C);
529 float k = smoothstepf(PERCEPTUAL_DEADZONE, 1.0f, ich.C / maxC);
531 ipt.I = fmixf(ipt.I, mapped.I, k);
532 ipt.P = fmixf(ipt.P, mapped.P, k);
533 ipt.T = fmixf(ipt.T, mapped.T, k);
534
535 rgb = ipt2rgb(ipt, ctx->dst.lms2content);
536 maxRGB = fmaxf(rgb.R, fmaxf(rgb.G, rgb.B));
537 rgb.R = fmaxf(softclip(rgb.R, maxRGB, ctx->dst.Lw), ctx->dst.Lb);
538 rgb.G = fmaxf(softclip(rgb.G, maxRGB, ctx->dst.Lw), ctx->dst.Lb);
539 rgb.B = fmaxf(softclip(rgb.B, maxRGB, ctx->dst.Lw), ctx->dst.Lb);
540
541 return rgb2ipt(rgb, ctx->dst.content2lms);
542}
543
544static IPT relative(const CmsCtx *ctx, IPT ipt)
545{
546 return clip_gamma(ipt, COLORIMETRIC_GAMMA, ctx->dst);
547}
548
549static IPT absolute(const CmsCtx *ctx, IPT ipt)
550{
551 RGB rgb = ipt2rgb(ipt, ctx->dst.lms2encoding);
552 float c[3] = { rgb.R, rgb.G, rgb.B };
553 ff_sws_matrix3x3_apply(&ctx->adaptation, c);
554 ipt = rgb2ipt((RGB) { c[0], c[1], c[2] }, ctx->dst.encoding2lms);
555
556 return clip_gamma(ipt, COLORIMETRIC_GAMMA, ctx->dst);
557}
558
559static IPT saturation(const CmsCtx * ctx, IPT ipt)
560{
561 RGB rgb = ipt2rgb(ipt, ctx->tmp.lms2content);
562 return rgb2ipt(rgb, ctx->dst.content2lms);
563}
564
566{
567 return av_clip_uint16(x * UINT16_MAX + 0.5f);
568}
569
570static av_always_inline av_const uint16_t round_pt16(float x)
571{
572 return av_clip_uint16(x * (1 << 16) + 0.5f);
573}
574
575/* Call this whenever the hue changes inside the loop body */
576static av_always_inline void update_hue_peaks(CmsCtx *ctx, float P, float T)
577{
578 const float hue = atan2f(T, P);
579 switch (ctx->map.intent) {
581 ctx->tmp.peak = saturate(hue, ctx->tmp);
585 ctx->dst.peak = saturate(hue, ctx->dst);
586 return;
587 default:
588 return;
589 }
590}
591
592static int generate_slice(void *priv, int jobnr, int threadnr, int nb_jobs,
593 int nb_threads)
594{
595 CmsCtx ctx = *(const CmsCtx *) priv;
596
597 const int slice_start = jobnr * ctx.slice_size;
598 const int slice_stride = ctx.size_input * ctx.size_input;
599 const int slice_end = FFMIN((jobnr + 1) * ctx.slice_size, ctx.size_input);
600 v3u16_t *input = &ctx.input[slice_start * slice_stride];
601
602 const int output_slice_h = (ctx.size_output_PT + nb_jobs - 1) / nb_jobs;
603 const int output_start = jobnr * output_slice_h;
604 const int output_stride = ctx.size_output_PT * ctx.size_output_I;
605 const int output_end = FFMIN((jobnr + 1) * output_slice_h, ctx.size_output_PT);
606 v3u16_t *output = ctx.output ? &ctx.output[output_start * output_stride] : NULL;
607
608 const float I_scale = 1.0f / (ctx.src.Imax - ctx.src.Imin);
609 const float I_offset = -ctx.src.Imin * I_scale;
610
611 const float input_scale = 1.0f / (ctx.size_input - 1);
612 const float output_scale_PT = 1.0f / (ctx.size_output_PT - 1);
613 const float output_scale_I = (ctx.tmp.Imax - ctx.tmp.Imin) /
614 (ctx.size_output_I - 1);
615
616 for (int Bx = slice_start; Bx < slice_end; Bx++) {
617 const float B = input_scale * Bx;
618 for (int Gx = 0; Gx < ctx.size_input; Gx++) {
619 const float G = input_scale * Gx;
620 for (int Rx = 0; Rx < ctx.size_input; Rx++) {
621 double c[3] = { input_scale * Rx, G, B };
622 RGB rgb;
623 IPT ipt;
624
625 ctx.src.eotf(ctx.src.Lw, ctx.src.Lb, c);
626 rgb = (RGB) { c[0], c[1], c[2] };
627 ipt = rgb2ipt(rgb, ctx.src.encoding2lms);
628
629 if (output) {
630 /* Save intermediate value to 3DLUT */
631 *input++ = (v3u16_t) {
632 round_unorm16(I_scale * ipt.I + I_offset),
633 round_pt16(ipt.P + 0.5f),
634 round_pt16(ipt.T + 0.5f),
635 };
636 } else {
637 update_hue_peaks(&ctx, ipt.P, ipt.T);
638
639 ipt = tone_map_apply(&ctx, ipt);
640 ipt = ctx.adapt_colors(&ctx, ipt);
641 rgb = ipt2rgb(ipt, ctx.dst.lms2encoding);
642
643 c[0] = rgb.R;
644 c[1] = rgb.G;
645 c[2] = rgb.B;
646 ctx.dst.eotf_inv(ctx.dst.Lw, ctx.dst.Lb, c);
647 *input++ = (v3u16_t) {
648 round_unorm16(c[0]),
649 round_unorm16(c[1]),
650 round_unorm16(c[2]),
651 };
652 }
653 }
654 }
655 }
656
657 if (!output)
658 return 0;
659
660 /* Generate split gamut mapping LUT */
661 for (int Tx = output_start; Tx < output_end; Tx++) {
662 const float T = output_scale_PT * Tx - 0.5f;
663 for (int Px = 0; Px < ctx.size_output_PT; Px++) {
664 const float P = output_scale_PT * Px - 0.5f;
666
667 for (int Ix = 0; Ix < ctx.size_output_I; Ix++) {
668 const float I = output_scale_I * Ix + ctx.tmp.Imin;
669 IPT ipt = ctx.adapt_colors(&ctx, (IPT) { I, P, T });
670 RGB rgb = ipt2rgb(ipt, ctx.dst.lms2encoding);
671 double c[3] = { rgb.R, rgb.G, rgb.B };
672 ctx.dst.eotf_inv(ctx.dst.Lw, ctx.dst.Lb, c);
673 *output++ = (v3u16_t) {
674 round_unorm16(c[0]),
675 round_unorm16(c[1]),
676 round_unorm16(c[2]),
677 };
678 }
679 }
680 }
681
682 return 0;
683}
684
689
691 int size_input, int size_I, int size_PT,
692 const SwsColorMap *map)
693{
694 AVSliceThread *slicethread;
695 int ret, num_slices;
696
697 CmsCtx ctx = {
698 .map = *map,
699 .input = input,
700 .output = output,
701 .size_input = size_input,
702 .size_output_I = size_I,
703 .size_output_PT = size_PT,
704 .src = gamut_from_colorspace(&map->src),
705 .dst = gamut_from_colorspace(&map->dst),
706 };
707
708 switch (ctx.map.intent) {
709 case SWS_INTENT_PERCEPTUAL: ctx.adapt_colors = perceptual; break;
710 case SWS_INTENT_RELATIVE_COLORIMETRIC: ctx.adapt_colors = relative; break;
711 case SWS_INTENT_SATURATION: ctx.adapt_colors = saturation; break;
712 case SWS_INTENT_ABSOLUTE_COLORIMETRIC: ctx.adapt_colors = absolute; break;
713 default: return AVERROR(EINVAL);
714 }
715
716 if (!output) {
717 /* Tone mapping is handled in a separate step when using dynamic TM */
718 tone_map_setup(&ctx, false);
719 }
720
721 /* Intermediate color space after tone mapping */
722 ctx.tmp = ctx.src;
723 ctx.tmp.Lb = ctx.dst.Lb;
724 ctx.tmp.Lw = ctx.dst.Lw;
725 ctx.tmp.Imin = ctx.dst.Imin;
726 ctx.tmp.Imax = ctx.dst.Imax;
727
728 if (ctx.map.intent == SWS_INTENT_ABSOLUTE_COLORIMETRIC) {
729 /**
730 * The IPT transform already implies an explicit white point adaptation
731 * from src to dst, so to get absolute colorimetric semantics we have
732 * to explicitly undo this adaptation with a * corresponding inverse.
733 */
734 ctx.adaptation = ff_sws_get_adaptation(&ctx.map.dst.gamut,
735 ctx.dst.wp, ctx.src.wp);
736 }
737
738 ret = avpriv_slicethread_create2(&slicethread, &ctx, generate_slice, NULL, 0);
739 if (ret < 0)
740 return ret;
741
742 ctx.slice_size = (ctx.size_input + ret - 1) / ret;
743 num_slices = (ctx.size_input + ctx.slice_size - 1) / ctx.slice_size;
744 avpriv_slicethread_execute2(slicethread, num_slices, 0);
745 avpriv_slicethread_free(&slicethread);
746 return 0;
747}
748
750{
751 CmsCtx ctx = {
752 .map = *map,
753 .src = gamut_from_colorspace(&map->src),
754 .dst = gamut_from_colorspace(&map->dst),
755 };
756
757 const float src_scale = (ctx.src.Imax - ctx.src.Imin) / (size - 1);
758 const float src_offset = ctx.src.Imin;
759 const float dst_scale = 1.0f / (ctx.dst.Imax - ctx.dst.Imin);
760 const float dst_offset = -ctx.dst.Imin * dst_scale;
761
762 tone_map_setup(&ctx, true);
763
764 for (int i = 0; i < size; i++) {
765 const float I = src_scale * i + src_offset;
766 IPT ipt = tone_map_apply(&ctx, (IPT) { I, 1.0f });
767 lut[i] = (v2u16_t) {
768 round_unorm16(dst_scale * ipt.I + dst_offset),
769 av_clip_uint16(ipt.P * (1 << 15) + 0.5f),
770 };
771 }
772}
static AVFormatContext * ctx
#define T(x)
Definition vpx_arith.h:29
#define L(x)
Definition vpx_arith.h:36
simple assert() macros that are a bit more flexible than ISO C assert().
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition avassert.h:42
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
static av_always_inline void update_hue_peaks(CmsCtx *ctx, float P, float T)
Definition cms.c:576
static av_always_inline av_const uint16_t round_pt16(float x)
Definition cms.c:570
static ICh mix_exp(ICh c, float x, float gamma, float base)
Something like fmixf(base, c, x) but follows an exponential curve, note that this can be used to exte...
Definition cms.c:298
static Gamut gamut_from_colorspace(const SwsColor *fmt)
Definition cms.c:112
static av_always_inline av_const uint16_t round_unorm16(float x)
Definition cms.c:565
static void st2094_pick_knee(float src_max, float src_min, float src_avg, float dst_max, float dst_min, float *out_src_knee, float *out_dst_knee)
Helper function to pick a knee point based on the * HDR10+ brightness metadata and scene brightness a...
Definition cms.c:381
int ff_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:685
static const float maxDelta
Definition cms.c:216
static IPT perceptual(const CmsCtx *ctx, IPT ipt)
Definition cms.c:520
static av_always_inline ICh ipt2ich(IPT c)
Definition cms.c:78
static IPT relative(const CmsCtx *ctx, IPT ipt)
Definition cms.c:544
static ICh desat_bounded(float I, float h, float Cmin, float Cmax, Gamut gamut)
Definition cms.c:220
static av_always_inline IPT rgb2ipt(RGB c, const SwsMatrix3x3 rgb2lms)
Definition cms.c:140
static ICh saturate(float hue, Gamut gamut)
Definition cms.c:243
static float softclip(float value, float source, float target)
Definition cms.c:274
static float hull(float I)
Definition cms.c:60
void ff_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:749
bool ff_sws_color_map_noop(const SwsColorMap *map)
Returns true if the given color map is a semantic no-op - that is, the overall RGB end to end transfo...
Definition cms.c:34
static av_always_inline IPT tone_map_apply(const CmsCtx *ctx, IPT ipt)
Definition cms.c:493
static av_always_inline IPT ich2ipt(ICh c)
Definition cms.c:87
static bool ingamut(IPT c, Gamut gamut)
Definition cms.c:182
static IPT clip_gamma(IPT ipt, float gamma, Gamut gamut)
Definition cms.c:320
static av_always_inline RGB ipt2rgb(IPT c, const SwsMatrix3x3 lms2rgb)
Definition cms.c:161
static float scale_gamma(float gamma, ICh ich, Gamut gamut)
Drop gamma for colors approaching black and achromatic to avoid numerical instabilities,...
Definition cms.c:312
static IPT absolute(const CmsCtx *ctx, IPT ipt)
Definition cms.c:549
int ff_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:690
static int generate_slice(void *priv, int jobnr, int threadnr, int nb_jobs, int nb_threads)
Definition cms.c:592
static void tone_map_setup(CmsCtx *ctx, bool dynamic)
Definition cms.c:420
static IPT saturation(const CmsCtx *ctx, IPT ipt)
Definition cms.c:559
#define PERCEPTUAL_KNEE_MIN
Definition cms.h:33
#define SOFTCLIP_KNEE
Definition cms.h:54
#define PERCEPTUAL_DEADZONE
Definition cms.h:41
#define PERCEPTUAL_KNEE_DEF
Definition cms.h:35
#define PERCEPTUAL_CONTRAST
Definition cms.h:44
#define PERCEPTUAL_ADAPTATION
Definition cms.h:38
#define PERCEPTUAL_KNEE_MAX
Definition cms.h:34
#define SLOPE_TUNING
Definition cms.h:47
#define COLORIMETRIC_GAMMA
Definition cms.h:57
#define PERCEPTUAL_STRENGTH
Definition cms.h:51
#define SLOPE_OFFSET
Definition cms.h:48
#define av_clip_uint16
Definition common.h:112
#define av_clipf
Definition common.h:145
#define NULL
Definition coverity.c:32
Colorspace value utility functions for libavutil.
SwsMatrix3x3 ff_sws_get_adaptation(const AVPrimaryCoefficients *prim, AVWhitepointCoefficients from, AVWhitepointCoefficients to)
Definition csputils.c:191
SwsMatrix3x3 ff_sws_ipt_lms2rgb(const AVColorPrimariesDesc *prim)
Definition csputils.c:239
bool ff_prim_superset(const AVPrimaryCoefficients *a, const AVPrimaryCoefficients *b)
Returns true if 'b' is entirely contained in 'a'.
Definition csputils.c:268
void ff_sws_matrix3x3_apply(const SwsMatrix3x3 *mat, float vec[3])
Definition csputils.c:72
SwsMatrix3x3 ff_sws_ipt_rgb2lms(const AVColorPrimariesDesc *prim)
Definition csputils.c:219
static float smoothstepf(float edge0, float edge1, float x)
Definition csputils.h:37
#define fmixf(a, b, x)
Definition csputils.h:35
static float pq_oetf(float x)
Definition csputils.h:98
static float pq_eotf(float x)
Definition csputils.h:90
static __device__ float sqrtf(float a)
float fminf(float, float)
float fmaxf(float, float)
double value
Definition eval.c:102
#define M(chr)
Definition exr.c:177
#define S(s, c, i)
static int ff_prim_equal(const AVPrimaryCoefficients *a, const AVPrimaryCoefficients *b)
Definition format.h:47
#define AVERROR(e)
Definition error.h:45
const AVColorPrimariesDesc * av_csp_primaries_desc_from_id(enum AVColorPrimaries prm)
Retrieves a complete gamut description from an enum constant describing the color primaries.
Definition csp.c:95
av_csp_eotf_function av_csp_itu_eotf_inv(enum AVColorTransferCharacteristic trc)
Returns the mathematical inverse of the corresponding EOTF.
Definition csp.c:710
void(* av_csp_eotf_function)(double Lw, double Lb, double c[3])
Function pointer representing an ITU EOTF transfer for a given reference display configuration.
Definition csp.h:178
av_csp_eotf_function av_csp_itu_eotf(enum AVColorTransferCharacteristic trc)
Returns the ITU EOTF corresponding to a given TRC.
Definition csp.c:684
static double av_q2d(AVRational a)
Convert an AVRational to a double.
Definition rational.h:104
static int av_cmp_q(AVRational a, AVRational b)
Compare two rationals.
Definition rational.h:89
@ SWS_INTENT_PERCEPTUAL
Perceptual tone mapping.
Definition swscale.h:211
@ SWS_INTENT_ABSOLUTE_COLORIMETRIC
Absolute colorimetric clipping.
Definition swscale.h:214
@ SWS_INTENT_RELATIVE_COLORIMETRIC
Relative colorimetric clipping.
Definition swscale.h:212
@ SWS_INTENT_SATURATION
Saturation mapping.
Definition swscale.h:213
int a
#define B
Definition huffyuv.h:42
#define G
Definition huffyuv.h:43
const VDPAUPixFmtMap * map
#define b
Definition input.c:43
static void scale(int *out, const int *in, const int w, const int h, const int shift)
Definition intra.c:278
Macro definitions for various function/variable attributes.
#define av_always_inline
Definition attributes.h:72
#define av_fallthrough
Definition attributes.h:67
#define av_const
Definition attributes.h:111
#define sinf(x)
Definition libm.h:421
#define cosf(x)
Definition libm.h:80
#define atan2f(y, x)
Definition libm.h:47
#define powf(x, y)
Definition libm.h:52
#define FFMIN(a, b)
Definition macros.h:49
static int slice_end(AVCodecContext *avctx, AVFrame *pict, int *got_output)
Handle slice ends.
Definition mpeg12dec.c:1697
#define P
void avpriv_slicethread_free(AVSliceThread **pctx)
Destroy slice threading context.
int avpriv_slicethread_execute2(AVSliceThread *ctx, int nb_jobs, int execute_main)
Execute slice threading.
int avpriv_slicethread_create2(AVSliceThread **pctx, void *priv, int(*worker_func)(void *priv, int jobnr, int threadnr, int nb_jobs, int nb_threads), int(*main_func)(void *priv), int nb_threads)
Create slice threading context.
struct AVSliceThread AVSliceThread
Definition slicethread.h:25
Struct containing chromaticity x and y values for the standard CIE 1931 chromaticity definition.
Definition csp.h:56
Struct that contains both white point location and primaries location, providing the complete descrip...
Definition csp.h:78
AVWhitepointCoefficients wp
Definition csp.h:79
int den
Denominator.
Definition rational.h:60
Definition cms.c:350
float(* tone_map)(const CmsCtx *ctx, float I)
Definition cms.c:363
Gamut src
Definition cms.c:356
float Pb
Definition cms.c:352
float Qa
Definition cms.c:352
float dst_knee
Definition cms.c:352
int size_output_PT
Definition cms.c:372
SwsColorMap map
Definition cms.c:362
Gamut tmp
Definition cms.c:357
float src_knee
Definition cms.c:352
float Qc
Definition cms.c:352
Gamut dst
Definition cms.c:358
v3u16_t * output
Definition cms.c:366
float Pa
Definition cms.c:352
float I_scale
Definition cms.c:353
float I_offset
Definition cms.c:353
int size_output_I
Definition cms.c:371
int slice_size
Definition cms.c:369
float Qb
Definition cms.c:352
int size_input
Definition cms.c:370
SwsMatrix3x3 adaptation
Definition cms.c:359
IPT(* adapt_colors)(const CmsCtx *ctx, IPT ipt)
Definition cms.c:364
v3u16_t * input
Definition cms.c:365
Definition cms.c:97
float Imax_frame
Definition cms.c:105
SwsMatrix3x3 encoding2lms
Definition cms.c:98
SwsMatrix3x3 lms2content
Definition cms.c:100
ICh peak
Definition cms.c:109
float Lb
Definition cms.c:107
SwsMatrix3x3 lms2encoding
Definition cms.c:99
av_csp_eotf_function eotf
Definition cms.c:102
float Imin
Definition cms.c:106
float Iavg_frame
Definition cms.c:104
float Imax
Definition cms.c:106
SwsMatrix3x3 content2lms
Definition cms.c:101
av_csp_eotf_function eotf_inv
Definition cms.c:103
AVCIExy wp
Definition cms.c:108
float Lw
Definition cms.c:107
Definition cms.c:74
float I
Definition cms.c:75
float h
Definition cms.c:75
float C
Definition cms.c:75
Definition cms.c:70
float P
Definition cms.c:71
float T
Definition cms.c:71
float I
Definition cms.c:71
Definition cms.c:66
float G
Definition cms.c:67
float B
Definition cms.c:67
float R
Definition cms.c:67
AVPrimaryCoefficients gamut
Definition format.h:63
AVRational min_luma
Definition format.h:64
enum AVColorPrimaries prim
Definition format.h:61
AVRational frame_peak
Definition format.h:66
AVRational max_luma
Definition format.h:65
AVRational frame_avg
Definition format.h:67
enum AVColorTransferCharacteristic trc
Definition format.h:62
float m[3][3]
Definition csputils.h:48
Definition rpzaenc.c:60
Definition idctdsp.c:35
external API header
int size
static const float lms2rgb[3][3]
static const float rgb2lms[3][3]
uint8_t base
Definition vp3data.h:128
static double c[64]
static int slice_start(SliceContext *sc, VVCContext *s, VVCFrameContext *fc, const CodedBitstreamUnit *unit, const int is_first_slice)
Definition dec.c:844