2 * Copyright (C) 2024 Niklas Haas
4 * This file is part of FFmpeg.
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.
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.
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
24 #include "libavutil/attributes.h"
25 #include "libavutil/avassert.h"
26 #include "libavutil/csp.h"
27 #include "libavutil/slicethread.h"
31 #include "libswscale/swscale.h"
34 bool ff_sws_color_map_noop(const SwsColorMap
*map
)
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
)
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
))
44 switch (map
->intent
) {
45 case SWS_INTENT_ABSOLUTE_COLORIMETRIC
:
46 case SWS_INTENT_RELATIVE_COLORIMETRIC
:
47 return ff_prim_superset(&map
->dst
.gamut
, &map
->src
.gamut
) &&
48 av_cmp_q(map
->src
.max_luma
, map
->dst
.max_luma
) <= 0;
49 case SWS_INTENT_PERCEPTUAL
:
50 case SWS_INTENT_SATURATION
:
51 return ff_prim_equal(&map
->dst
.gamut
, &map
->src
.gamut
) &&
52 !av_cmp_q(map
->src
.max_luma
, map
->dst
.max_luma
);
54 av_assert0(!"Invalid gamut mapping intent?");
59 /* Approximation of gamut hull at a given intensity level */
60 static const float hull(float I
)
62 return ((I
- 6.0f
) * I
+ 9.0f
) * I
;
65 /* For some minimal type safety, and code cleanliness */
67 float R
, G
, B
; /* nits */
78 static av_always_inline ICh
ipt2ich(IPT c
)
82 .C
= sqrtf(c
.P
* c
.P
+ c
.T
* c
.T
),
83 .h
= atan2f(c
.T
, c
.P
),
87 static av_always_inline IPT
ich2ipt(ICh c
)
96 /* Helper struct containing pre-computed cached values describing a gamut */
97 typedef struct Gamut
{
98 SwsMatrix3x3 encoding2lms
;
99 SwsMatrix3x3 lms2encoding
;
100 SwsMatrix3x3 lms2content
;
101 SwsMatrix3x3 content2lms
;
102 av_csp_eotf_function eotf
;
103 av_csp_eotf_function eotf_inv
;
109 ICh peak
; /* updated as needed in loop body when hue changes */
112 static Gamut
gamut_from_colorspace(SwsColor fmt
)
114 const AVColorPrimariesDesc
*encoding
= av_csp_primaries_desc_from_id(fmt
.prim
);
115 const AVColorPrimariesDesc content
= {
120 const float Lw
= av_q2d(fmt
.max_luma
), Lb
= av_q2d(fmt
.min_luma
);
121 const float Imax
= pq_oetf(Lw
);
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
),
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
,
140 static av_always_inline IPT
rgb2ipt(RGB c
, const SwsMatrix3x3 rgb2lms
)
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
);
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
,
161 static av_always_inline RGB
ipt2rgb(IPT c
, const SwsMatrix3x3 lms2rgb
)
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
);
170 .R
= lms2rgb
.m
[0][0] * L
+
171 lms2rgb
.m
[0][1] * M
+
173 .G
= lms2rgb
.m
[1][0] * L
+
174 lms2rgb
.m
[1][1] * M
+
176 .B
= lms2rgb
.m
[2][0] * L
+
177 lms2rgb
.m
[2][1] * M
+
182 static inline bool ingamut(IPT c
, Gamut gamut
)
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
)
193 /* Values outside legal LMS range */
196 const float L
= pq_eotf(Lp
);
197 const float M
= pq_eotf(Mp
);
198 const float S
= pq_eotf(Sp
);
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
,
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
;
216 static const float maxDelta
= 5e-5f
;
218 // Find gamut intersection using specified bounds
220 desat_bounded(float I
, float h
, float Cmin
, float Cmax
, Gamut gamut
)
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
};
227 const float maxDI
= I
* maxDelta
;
228 ICh res
= { .I
= I
, .C
= (Cmin
+ Cmax
) / 2, .h
= h
};
230 if (ingamut(ich2ipt(res
), gamut
)) {
235 res
.C
= (Cmin
+ Cmax
) / 2;
236 } while (Cmax
- Cmin
> maxDI
);
242 // Finds maximally saturated in-gamut color (for given hue)
243 static inline ICh
saturate(float hue
, Gamut gamut
)
245 static const float invphi
= 0.6180339887498948f
;
246 static const float invphi2
= 0.38196601125010515f
;
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
);
256 while (de
> maxDelta
) {
261 a
.I
= lo
.I
+ invphi2
* de
;
262 a
= desat_bounded(a
.I
, hue
, lo
.C
- maxDelta
, 0.5f
, gamut
);
266 b
.I
= lo
.I
+ invphi
* de
;
267 b
= desat_bounded(b
.I
, hue
, hi
.C
- maxDelta
, 0.5f
, gamut
);
271 return a
.C
> b
.C
? a
: b
;
274 static float softclip(float value
, float source
, float target
)
276 const float j
= SOFTCLIP_KNEE
;
277 float peak
, x
, a
, b
, scale
;
281 peak
= source
/ target
;
282 x
= fminf(value
/ target
, peak
);
283 if (x
<= j
|| peak
<= 1.0)
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
);
291 return scale
* (x
+ a
) / (x
+ b
) * target
;
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
298 static inline ICh
mix_exp(ICh c
, float x
, float gamma
, float base
)
301 .I
= base
+ (c
.I
- base
) * powf(x
, gamma
),
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
312 static inline float scale_gamma(float gamma
, ICh ich
, Gamut gamut
)
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
);
319 /* Clip a color along the exponential curve given by `gamma` */
320 static inline IPT
clip_gamma(IPT ipt
, float gamma
, Gamut gamut
)
322 float lo
= 0.0f
, hi
= 1.0f
, x
= 0.5f
;
323 const float maxDI
= fmaxf(ipt
.I
* maxDelta
, 1e-7f
);
326 if (ipt
.I
<= gamut
.Imin
)
327 return (IPT
) { .I
= gamut
.Imin
};
328 if (ingamut(ipt
, gamut
))
333 return ich2ipt(desat_bounded(ich
.I
, ich
.h
, 0.0f
, ich
.C
, gamut
));
335 gamma
= scale_gamma(gamma
, ich
, gamut
);
337 ICh test
= mix_exp(ich
, x
, gamma
, gamut
.peak
.I
);
338 if (ingamut(ich2ipt(test
), gamut
)) {
343 x
= (lo
+ hi
) / 2.0f
;
344 } while (hi
- lo
> maxDI
);
346 return ich2ipt(mix_exp(ich
, x
, gamma
, gamut
.peak
.I
));
349 typedef struct CmsCtx 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 */
355 /* Colorspace parameters */
357 Gamut tmp
; /* after tone mapping */
359 SwsMatrix3x3 adaptation
; /* for absolute intent */
361 /* Invocation parameters */
363 float (*tone_map
)(const CmsCtx
*ctx
, float I
);
364 IPT (*adapt_colors
)(const CmsCtx
*ctx
, IPT ipt
);
368 /* Threading parameters */
376 * Helper function to pick a knee point based on the * HDR10+ brightness
377 * metadata and scene brightness average matching.
379 * Inspired by SMPTE ST2094-10, with some modifications
381 static 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
)
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
;
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
);
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
);
403 * Choose the destnation 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.
407 * Adjust the adaptation strength towards 1 based on how close the knee
408 * point is to its extreme values (min/max knee)
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
);
416 *out_src_knee
= src_knee
;
417 *out_dst_knee
= dst_knee
;
420 static void tone_map_setup(CmsCtx
*ctx
, bool dynamic
)
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
;
429 switch (ctx
->map
.intent
) {
430 case SWS_INTENT_PERCEPTUAL
:
431 st2094_pick_knee(src_max
, src_min
, src_avg
, dst_max
, dst_min
,
432 &ctx
->src_knee
, &ctx
->dst_knee
);
434 /* Solve for linear knee (Pa = 0) */
435 slope
= (ctx
->dst_knee
- dst_min
) / (ctx
->src_knee
- src_min
);
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.
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
);
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
;
454 * Solve P of order 2 for:
455 * P(in_min) = out_min
459 ctx
->Pa
= (out_min
- slope
* in_min
) / (in_min
* in_min
);
463 * Solve Q of order 3 for:
464 * Q(in_max) = out_max
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
;
474 case SWS_INTENT_SATURATION
:
476 ctx
->I_scale
= (dst_max
- dst_min
) / (src_max
- src_min
);
477 ctx
->I_offset
= dst_min
- src_min
* ctx
->I_scale
;
479 case SWS_INTENT_RELATIVE_COLORIMETRIC
:
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
;
485 case SWS_INTENT_ABSOLUTE_COLORIMETRIC
:
488 ctx
->I_offset
= 0.0f
;
493 static av_always_inline IPT
tone_map_apply(const CmsCtx
*ctx
, IPT ipt
)
495 float I
= ipt
.I
, desat
;
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
;
501 I
= I
> 0 ? ((Qa
* I
+ Qb
) * I
+ Qc
) * I
: (Pa
* I
+ Pb
) * I
;
504 I
= ctx
->I_scale
* I
+ ctx
->I_offset
;
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.
512 desat
= fminf(ipt
.I
/ I
, hull(I
) / hull(ipt
.I
));
520 static IPT
perceptual(const CmsCtx
*ctx
, IPT ipt
)
522 ICh ich
= ipt2ich(ipt
);
523 IPT mapped
= rgb2ipt(ipt2rgb(ipt
, ctx
->tmp
.lms2content
), ctx
->dst
.content2lms
);
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
);
530 k
*= PERCEPTUAL_STRENGTH
;
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
);
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
);
541 return rgb2ipt(rgb
, ctx
->dst
.content2lms
);
544 static IPT
relative(const CmsCtx
*ctx
, IPT ipt
)
546 return clip_gamma(ipt
, COLORIMETRIC_GAMMA
, ctx
->dst
);
549 static IPT
absolute(const CmsCtx
*ctx
, IPT ipt
)
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
);
556 return clip_gamma(ipt
, COLORIMETRIC_GAMMA
, ctx
->dst
);
559 static IPT
saturation(const CmsCtx
* ctx
, IPT ipt
)
561 RGB rgb
= ipt2rgb(ipt
, ctx
->tmp
.lms2content
);
562 return rgb2ipt(rgb
, ctx
->dst
.content2lms
);
565 static av_always_inline av_const
uint16_t av_round16f(float x
)
567 return av_clip_uint16(x
* (UINT16_MAX
- 1) + 0.5f
);
570 /* Call this whenever the hue changes inside the loop body */
571 static av_always_inline
void update_hue_peaks(CmsCtx
*ctx
, float P
, float T
)
573 const float hue
= atan2f(T
, P
);
574 switch (ctx
->map
.intent
) {
575 case SWS_INTENT_PERCEPTUAL
:
576 ctx
->tmp
.peak
= saturate(hue
, ctx
->tmp
);
578 case SWS_INTENT_RELATIVE_COLORIMETRIC
:
579 case SWS_INTENT_ABSOLUTE_COLORIMETRIC
:
580 ctx
->dst
.peak
= saturate(hue
, ctx
->dst
);
587 static void generate_slice(void *priv
, int jobnr
, int threadnr
, int nb_jobs
,
590 CmsCtx ctx
= *(const CmsCtx
*) priv
;
592 const int slice_start
= jobnr
* ctx
.slice_size
;
593 const int slice_stride
= ctx
.size_input
* ctx
.size_input
;
594 const int slice_end
= FFMIN((jobnr
+ 1) * ctx
.slice_size
, ctx
.size_input
);
595 v3u16_t
*input
= &ctx
.input
[slice_start
* slice_stride
];
597 const int output_slice_h
= (ctx
.size_output_PT
+ nb_jobs
- 1) / nb_jobs
;
598 const int output_start
= jobnr
* output_slice_h
;
599 const int output_stride
= ctx
.size_output_PT
* ctx
.size_output_I
;
600 const int output_end
= FFMIN((jobnr
+ 1) * output_slice_h
, ctx
.size_output_PT
);
601 v3u16_t
*output
= ctx
.output
? &ctx
.output
[output_start
* output_stride
] : NULL
;
603 const float I_scale
= 1.0f
/ (ctx
.src
.Imax
- ctx
.src
.Imin
);
604 const float I_offset
= -ctx
.src
.Imin
* I_scale
;
605 const float PT_offset
= (float) (1 << 15) / (UINT16_MAX
- 1);
607 const float input_scale
= 1.0f
/ (ctx
.size_input
- 1);
608 const float output_scale_PT
= 1.0f
/ (ctx
.size_output_PT
- 1);
609 const float output_scale_I
= (ctx
.tmp
.Imax
- ctx
.tmp
.Imin
) /
610 (ctx
.size_output_I
- 1);
612 for (int Bx
= slice_start
; Bx
< slice_end
; Bx
++) {
613 const float B
= input_scale
* Bx
;
614 for (int Gx
= 0; Gx
< ctx
.size_input
; Gx
++) {
615 const float G
= input_scale
* Gx
;
616 for (int Rx
= 0; Rx
< ctx
.size_input
; Rx
++) {
617 double c
[3] = { input_scale
* Rx
, G
, B
};
621 ctx
.src
.eotf(ctx
.src
.Lw
, ctx
.src
.Lb
, c
);
622 rgb
= (RGB
) { c
[0], c
[1], c
[2] };
623 ipt
= rgb2ipt(rgb
, ctx
.src
.encoding2lms
);
626 /* Save intermediate value to 3DLUT */
627 *input
++ = (v3u16_t
) {
628 av_round16f(I_scale
* ipt
.I
+ I_offset
),
629 av_round16f(ipt
.P
+ PT_offset
),
630 av_round16f(ipt
.T
+ PT_offset
),
633 update_hue_peaks(&ctx
, ipt
.P
, ipt
.T
);
635 ipt
= tone_map_apply(&ctx
, ipt
);
636 ipt
= ctx
.adapt_colors(&ctx
, ipt
);
637 rgb
= ipt2rgb(ipt
, ctx
.dst
.lms2encoding
);
642 ctx
.dst
.eotf_inv(ctx
.dst
.Lw
, ctx
.dst
.Lb
, c
);
643 *input
++ = (v3u16_t
) {
656 /* Generate split gamut mapping LUT */
657 for (int Tx
= output_start
; Tx
< output_end
; Tx
++) {
658 const float T
= output_scale_PT
* Tx
- PT_offset
;
659 for (int Px
= 0; Px
< ctx
.size_output_PT
; Px
++) {
660 const float P
= output_scale_PT
* Px
- PT_offset
;
661 update_hue_peaks(&ctx
, P
, T
);
663 for (int Ix
= 0; Ix
< ctx
.size_output_I
; Ix
++) {
664 const float I
= output_scale_I
* Ix
+ ctx
.tmp
.Imin
;
665 IPT ipt
= ctx
.adapt_colors(&ctx
, (IPT
) { I
, P
, T
});
666 RGB rgb
= ipt2rgb(ipt
, ctx
.dst
.lms2encoding
);
667 double c
[3] = { rgb
.R
, rgb
.G
, rgb
.B
};
668 ctx
.dst
.eotf_inv(ctx
.dst
.Lw
, ctx
.dst
.Lb
, c
);
669 *output
++ = (v3u16_t
) {
679 int ff_sws_color_map_generate_static(v3u16_t
*lut
, int size
, const SwsColorMap
*map
)
681 return ff_sws_color_map_generate_dynamic(lut
, NULL
, size
, 1, 1, map
);
684 int ff_sws_color_map_generate_dynamic(v3u16_t
*input
, v3u16_t
*output
,
685 int size_input
, int size_I
, int size_PT
,
686 const SwsColorMap
*map
)
688 AVSliceThread
*slicethread
;
695 .size_input
= size_input
,
696 .size_output_I
= size_I
,
697 .size_output_PT
= size_PT
,
698 .src
= gamut_from_colorspace(map
->src
),
699 .dst
= gamut_from_colorspace(map
->dst
),
702 switch (ctx
.map
.intent
) {
703 case SWS_INTENT_PERCEPTUAL
: ctx
.adapt_colors
= perceptual
; break;
704 case SWS_INTENT_RELATIVE_COLORIMETRIC
: ctx
.adapt_colors
= relative
; break;
705 case SWS_INTENT_SATURATION
: ctx
.adapt_colors
= saturation
; break;
706 case SWS_INTENT_ABSOLUTE_COLORIMETRIC
: ctx
.adapt_colors
= absolute
; break;
707 default: return AVERROR(EINVAL
);
711 /* Tone mapping is handled in a separate step when using dynamic TM */
712 tone_map_setup(&ctx
, false);
715 /* Intermediate color space after tone mapping */
717 ctx
.tmp
.Lb
= ctx
.dst
.Lb
;
718 ctx
.tmp
.Lw
= ctx
.dst
.Lw
;
719 ctx
.tmp
.Imin
= ctx
.dst
.Imin
;
720 ctx
.tmp
.Imax
= ctx
.dst
.Imax
;
722 if (ctx
.map
.intent
== SWS_INTENT_ABSOLUTE_COLORIMETRIC
) {
724 * The IPT transform already implies an explicit white point adaptation
725 * from src to dst, so to get absolute colorimetric semantics we have
726 * to explicitly undo this adaptation with a * corresponding inverse.
728 ctx
.adaptation
= ff_sws_get_adaptation(&ctx
.map
.dst
.gamut
,
729 ctx
.dst
.wp
, ctx
.src
.wp
);
732 ret
= avpriv_slicethread_create(&slicethread
, &ctx
, generate_slice
, NULL
, 0);
736 ctx
.slice_size
= (ctx
.size_input
+ ret
- 1) / ret
;
737 num_slices
= (ctx
.size_input
+ ctx
.slice_size
- 1) / ctx
.slice_size
;
738 avpriv_slicethread_execute(slicethread
, num_slices
, 0);
739 avpriv_slicethread_free(&slicethread
);
743 void ff_sws_tone_map_generate(v2u16_t
*lut
, int size
, const SwsColorMap
*map
)
747 .src
= gamut_from_colorspace(map
->src
),
748 .dst
= gamut_from_colorspace(map
->dst
),
751 const float src_scale
= (ctx
.src
.Imax
- ctx
.src
.Imin
) / (size
- 1);
752 const float src_offset
= ctx
.src
.Imin
;
753 const float dst_scale
= 1.0f
/ (ctx
.dst
.Imax
- ctx
.dst
.Imin
);
754 const float dst_offset
= -ctx
.dst
.Imin
* dst_scale
;
756 tone_map_setup(&ctx
, true);
758 for (int i
= 0; i
< size
; i
++) {
759 const float I
= src_scale
* i
+ src_offset
;
760 IPT ipt
= tone_map_apply(&ctx
, (IPT
) { I
, 1.0f
});
762 av_round16f(dst_scale
* ipt
.I
+ dst_offset
),
763 av_clip_uint16(ipt
.P
* (1 << 15) + 0.5f
),