1use crate::mlaf::mlaf;
30use crate::spline::FilmicSplineParameters;
31use crate::GainHdrMetadata;
32use std::ops::{Add, Div, Mul, Sub};
33
34#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
41pub enum ToneMappingMethod {
42 Rec2408(GainHdrMetadata),
44 Filmic,
47 Aces,
50 Reinhard,
53 ExtendedReinhard,
56 ReinhardJodie,
59 Clamp,
61 FilmicSpline(FilmicSplineParameters),
64}
65
66pub(crate) trait ToneMap {
67 fn process_lane(&self, in_place: &mut [f32]);
68 fn process_luma_lane(&self, in_place: &mut [f32]);
70}
71
72#[derive(Debug, Clone, Copy)]
73pub(crate) struct Rec2408ToneMapper<const CN: usize> {
74 w_a: f32,
75 w_b: f32,
76 primaries: [f32; 3],
77}
78
79impl<const CN: usize> Rec2408ToneMapper<CN> {
80 pub(crate) fn new(
81 content_max_brightness: f32,
82 display_max_brightness: f32,
83 white_point: f32,
84 primaries: [f32; 3],
85 ) -> Self {
86 let ld = content_max_brightness / white_point;
87 let w_a = (display_max_brightness / white_point) / (ld * ld);
88 let w_b = 1.0f32 / (display_max_brightness / white_point);
89 Self {
90 w_a,
91 w_b,
92 primaries,
93 }
94 }
95}
96
97impl<const CN: usize> Rec2408ToneMapper<CN> {
98 #[inline(always)]
99 fn tonemap(&self, luma: f32) -> f32 {
100 mlaf(1f32, self.w_a, luma) / mlaf(1f32, self.w_b, luma)
101 }
102}
103
104impl<const CN: usize> ToneMap for Rec2408ToneMapper<CN> {
105 fn process_lane(&self, in_place: &mut [f32]) {
106 for chunk in in_place.chunks_exact_mut(CN) {
107 let luma = chunk[0] * self.primaries[0]
108 + chunk[1] * self.primaries[1]
109 + chunk[2] * self.primaries[2];
110 if luma == 0. {
111 chunk[0] = 0.;
112 chunk[1] = 0.;
113 chunk[2] = 0.;
114 continue;
115 }
116 let scale = self.tonemap(luma);
117 chunk[0] = (chunk[0] * scale).min(1f32);
118 chunk[1] = (chunk[1] * scale).min(1f32);
119 chunk[2] = (chunk[2] * scale).min(1f32);
120 }
121 }
122
123 fn process_luma_lane(&self, in_place: &mut [f32]) {
124 for chunk in in_place.chunks_exact_mut(CN) {
125 let luma = chunk[0];
126 if luma == 0. {
127 chunk[0] = 0.;
128 continue;
129 }
130 let scale = self.tonemap(luma);
131 chunk[0] = (chunk[0] * scale).min(1f32);
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, Default)]
137pub(crate) struct FilmicToneMapper<const CN: usize> {}
138
139#[inline(always)]
140const fn uncharted2_tonemap_partial(x: f32) -> f32 {
141 const A: f32 = 0.15f32;
142 const B: f32 = 0.50f32;
143 const C: f32 = 0.10f32;
144 const D: f32 = 0.20f32;
145 const E: f32 = 0.02f32;
146 const F: f32 = 0.30f32;
147 ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
148}
149
150impl<const CN: usize> FilmicToneMapper<CN> {
151 #[inline(always)]
152 fn uncharted2_filmic(&self, v: f32) -> f32 {
153 let exposure_bias = 2.0f32;
154 let curr = uncharted2_tonemap_partial(v * exposure_bias);
155
156 const W: f32 = 11.2f32;
157 const W_S: f32 = 1.0f32 / uncharted2_tonemap_partial(W);
158 curr * W_S
159 }
160}
161
162impl<const CN: usize> ToneMap for FilmicToneMapper<CN> {
163 fn process_lane(&self, in_place: &mut [f32]) {
164 for chunk in in_place.chunks_exact_mut(CN) {
165 chunk[0] = self.uncharted2_filmic(chunk[0]).min(1f32);
166 chunk[1] = self.uncharted2_filmic(chunk[1]).min(1f32);
167 chunk[2] = self.uncharted2_filmic(chunk[2]).min(1f32);
168 }
169 }
170
171 fn process_luma_lane(&self, in_place: &mut [f32]) {
172 for chunk in in_place.chunks_exact_mut(CN) {
173 chunk[0] = self.uncharted2_filmic(chunk[0]).min(1f32);
174 }
175 }
176}
177
178#[derive(Debug, Clone, Copy, Default)]
179pub(crate) struct AcesToneMapper<const CN: usize> {}
180
181#[derive(Copy, Clone)]
182pub(crate) struct Rgb {
183 pub(crate) r: f32,
184 pub(crate) g: f32,
185 pub(crate) b: f32,
186}
187
188impl Mul<Rgb> for Rgb {
189 type Output = Self;
190 #[inline(always)]
191 fn mul(self, rhs: Rgb) -> Self::Output {
192 Self {
193 r: self.r * rhs.r,
194 g: self.g * rhs.g,
195 b: self.b * rhs.b,
196 }
197 }
198}
199
200impl Add<f32> for Rgb {
201 type Output = Self;
202 #[inline(always)]
203 fn add(self, rhs: f32) -> Self::Output {
204 Self {
205 r: self.r + rhs,
206 g: self.g + rhs,
207 b: self.b + rhs,
208 }
209 }
210}
211
212impl Sub<f32> for Rgb {
213 type Output = Self;
214 #[inline(always)]
215 fn sub(self, rhs: f32) -> Self::Output {
216 Self {
217 r: self.r - rhs,
218 g: self.g - rhs,
219 b: self.b - rhs,
220 }
221 }
222}
223
224impl Mul<f32> for Rgb {
225 type Output = Self;
226 #[inline(always)]
227 fn mul(self, rhs: f32) -> Self::Output {
228 Self {
229 r: self.r * rhs,
230 g: self.g * rhs,
231 b: self.b * rhs,
232 }
233 }
234}
235
236impl Div<f32> for Rgb {
237 type Output = Self;
238 #[inline(always)]
239 fn div(self, rhs: f32) -> Self::Output {
240 Self {
241 r: self.r / rhs,
242 g: self.g / rhs,
243 b: self.b / rhs,
244 }
245 }
246}
247
248impl Div<Rgb> for Rgb {
249 type Output = Self;
250 #[inline(always)]
251 fn div(self, rhs: Rgb) -> Self::Output {
252 Self {
253 r: self.r / rhs.r,
254 g: self.g / rhs.g,
255 b: self.b / rhs.b,
256 }
257 }
258}
259
260impl<const CN: usize> AcesToneMapper<CN> {
261 #[inline(always)]
262 fn mul_input(&self, color: Rgb) -> Rgb {
263 let a = mlaf(
264 mlaf(0.35458f32 * color.g, 0.04823f32, color.b),
265 0.59719f32,
266 color.r,
267 );
268 let b = mlaf(
269 mlaf(0.07600f32 * color.r, 0.90834f32, color.g),
270 0.01566f32,
271 color.b,
272 );
273 let c = mlaf(
274 mlaf(0.02840f32 * color.r, 0.13383f32, color.g),
275 0.83777f32,
276 color.b,
277 );
278 Rgb { r: a, g: b, b: c }
279 }
280
281 #[inline(always)]
282 fn mul_output(&self, color: Rgb) -> Rgb {
283 let a = mlaf(
284 mlaf(1.60475f32 * color.r, -0.53108f32, color.g),
285 -0.07367f32,
286 color.b,
287 );
288 let b = mlaf(
289 mlaf(-0.10208f32 * color.r, 1.10813f32, color.g),
290 -0.00605f32,
291 color.b,
292 );
293 let c = mlaf(
294 mlaf(-0.00327f32 * color.r, -0.07276f32, color.g),
295 1.07602f32,
296 color.b,
297 );
298 Rgb { r: a, g: b, b: c }
299 }
300}
301
302impl<const CN: usize> ToneMap for AcesToneMapper<CN> {
303 fn process_lane(&self, in_place: &mut [f32]) {
304 for chunk in in_place.chunks_exact_mut(CN) {
305 let color_in = self.mul_input(Rgb {
306 r: chunk[0],
307 g: chunk[1],
308 b: chunk[2],
309 });
310 let ca = color_in * (color_in + 0.0245786f32) - 0.000090537f32;
311 let cb = color_in * (color_in * 0.983729f32 + 0.4329510f32) + 0.238081f32;
312 let c_out = self.mul_output(ca / cb);
313 chunk[0] = c_out.r.min(1f32);
314 chunk[1] = c_out.g.min(1f32);
315 chunk[2] = c_out.b.min(1f32);
316 }
317 }
318
319 fn process_luma_lane(&self, in_place: &mut [f32]) {
320 for chunk in in_place.chunks_exact_mut(CN) {
321 let color_in = self.mul_input(Rgb {
322 r: chunk[0],
323 g: chunk[1],
324 b: chunk[2],
325 });
326 let ca = color_in * (color_in + 0.0245786f32) - 0.000090537f32;
327 let cb = color_in * (color_in * 0.983729f32 + 0.4329510f32) + 0.238081f32;
328 let c_out = self.mul_output(ca / cb);
329 chunk[0] = c_out.r.min(1f32);
330 }
331 }
332}
333
334#[derive(Debug, Clone, Copy, Default)]
335pub(crate) struct ReinhardToneMapper<const CN: usize> {}
336
337impl<const CN: usize> ToneMap for ReinhardToneMapper<CN> {
338 fn process_lane(&self, in_place: &mut [f32]) {
339 for chunk in in_place.chunks_exact_mut(CN) {
340 chunk[0] = (chunk[0] / (1f32 + chunk[0])).min(1f32);
341 chunk[1] = (chunk[1] / (1f32 + chunk[1])).min(1f32);
342 chunk[2] = (chunk[2] / (1f32 + chunk[2])).min(1f32);
343 }
344 }
345
346 fn process_luma_lane(&self, in_place: &mut [f32]) {
347 for chunk in in_place.chunks_exact_mut(CN) {
348 chunk[0] = (chunk[0] / (1f32 + chunk[0])).min(1f32);
349 }
350 }
351}
352
353#[derive(Debug, Clone, Copy, Default)]
354pub(crate) struct ExtendedReinhardToneMapper<const CN: usize> {
355 pub(crate) primaries: [f32; 3],
356}
357
358impl<const CN: usize> ToneMap for ExtendedReinhardToneMapper<CN> {
359 fn process_lane(&self, in_place: &mut [f32]) {
360 for chunk in in_place.chunks_exact_mut(CN) {
361 let luma = chunk[0] * self.primaries[0]
362 + chunk[1] * self.primaries[1]
363 + chunk[2] * self.primaries[2];
364 if luma == 0. {
365 chunk[0] = 0.;
366 chunk[1] = 0.;
367 chunk[2] = 0.;
368 continue;
369 }
370 let new_luma = luma / (1f32 + luma);
371 chunk[0] = (chunk[0] * new_luma).min(1f32);
372 chunk[1] = (chunk[1] * new_luma).min(1f32);
373 chunk[2] = (chunk[2] * new_luma).min(1f32);
374 }
375 }
376
377 fn process_luma_lane(&self, in_place: &mut [f32]) {
378 for chunk in in_place.chunks_exact_mut(CN) {
379 let luma = chunk[0];
380 if luma == 0. {
381 chunk[0] = 0.;
382 continue;
383 }
384 let new_luma = luma / (1f32 + luma);
385 chunk[0] = (chunk[0] * new_luma).min(1f32);
386 }
387 }
388}
389
390#[inline(always)]
391fn lerp(a: f32, b: f32, t: f32) -> f32 {
392 mlaf(a, t, b - a)
393}
394
395#[derive(Debug, Clone, Copy, Default)]
396pub(crate) struct ReinhardJodieToneMapper<const CN: usize> {
397 pub(crate) primaries: [f32; 3],
398}
399
400impl<const CN: usize> ToneMap for ReinhardJodieToneMapper<CN> {
401 fn process_lane(&self, in_place: &mut [f32]) {
402 for chunk in in_place.chunks_exact_mut(CN) {
403 let luma = chunk[0] * self.primaries[0]
404 + chunk[1] * self.primaries[1]
405 + chunk[2] * self.primaries[2];
406 if luma == 0. {
407 chunk[0] = 0.;
408 chunk[1] = 0.;
409 chunk[2] = 0.;
410 continue;
411 }
412 let tv_r = chunk[0] / (1.0f32 + chunk[0]);
413 let tv_g = chunk[1] / (1.0f32 + chunk[1]);
414 let tv_b = chunk[2] / (1.0f32 + chunk[2]);
415
416 chunk[0] = lerp(chunk[0] / (1f32 + luma), tv_r, tv_r).min(1f32);
417 chunk[1] = lerp(chunk[1] / (1f32 + luma), tv_g, tv_g).min(1f32);
418 chunk[2] = lerp(chunk[1] / (1f32 + luma), tv_b, tv_b).min(1f32);
419 }
420 }
421
422 fn process_luma_lane(&self, in_place: &mut [f32]) {
423 for chunk in in_place.chunks_exact_mut(CN) {
424 let luma = chunk[0];
425 if luma == 0. {
426 chunk[0] = 0.;
427 continue;
428 }
429 let tv_r = chunk[0] / (1.0f32 + chunk[0]);
430
431 chunk[0] = lerp(chunk[0] / (1f32 + luma), tv_r, tv_r).min(1f32);
432 }
433 }
434}
435
436#[derive(Debug, Clone, Copy, Default)]
437pub(crate) struct ClampToneMapper<const CN: usize> {}
438
439impl<const CN: usize> ToneMap for ClampToneMapper<CN> {
440 fn process_lane(&self, in_place: &mut [f32]) {
441 for chunk in in_place.chunks_exact_mut(CN) {
442 chunk[0] = chunk[0].min(1f32).max(0f32);
443 chunk[1] = chunk[1].min(1f32).max(0f32);
444 chunk[2] = chunk[2].min(1f32).max(0f32);
445 }
446 }
447
448 fn process_luma_lane(&self, in_place: &mut [f32]) {
449 for chunk in in_place.chunks_exact_mut(CN) {
450 chunk[0] = chunk[0].min(1f32).max(0f32);
451 }
452 }
453}