1use crate::basics::uround;
7
8pub trait GammaFunction {
15 fn call(&self, x: f64) -> f64;
16}
17
18#[derive(Debug, Clone, Copy, Default)]
25pub struct GammaNone;
26
27impl GammaFunction for GammaNone {
28 #[inline]
29 fn call(&self, x: f64) -> f64 {
30 x
31 }
32}
33
34#[derive(Debug, Clone, Copy)]
41pub struct GammaPower {
42 gamma: f64,
43}
44
45impl GammaPower {
46 pub fn new(gamma: f64) -> Self {
47 Self { gamma }
48 }
49
50 pub fn gamma(&self) -> f64 {
51 self.gamma
52 }
53
54 pub fn set_gamma(&mut self, g: f64) {
55 self.gamma = g;
56 }
57}
58
59impl Default for GammaPower {
60 fn default() -> Self {
61 Self { gamma: 1.0 }
62 }
63}
64
65impl GammaFunction for GammaPower {
66 #[inline]
67 fn call(&self, x: f64) -> f64 {
68 x.powf(self.gamma)
69 }
70}
71
72#[derive(Debug, Clone, Copy)]
79pub struct GammaThreshold {
80 threshold: f64,
81}
82
83impl GammaThreshold {
84 pub fn new(threshold: f64) -> Self {
85 Self { threshold }
86 }
87
88 pub fn threshold(&self) -> f64 {
89 self.threshold
90 }
91
92 pub fn set_threshold(&mut self, t: f64) {
93 self.threshold = t;
94 }
95}
96
97impl Default for GammaThreshold {
98 fn default() -> Self {
99 Self { threshold: 0.5 }
100 }
101}
102
103impl GammaFunction for GammaThreshold {
104 #[inline]
105 fn call(&self, x: f64) -> f64 {
106 if x < self.threshold {
107 0.0
108 } else {
109 1.0
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy)]
121pub struct GammaLinear {
122 start: f64,
123 end: f64,
124}
125
126impl GammaLinear {
127 pub fn new(start: f64, end: f64) -> Self {
128 Self { start, end }
129 }
130
131 pub fn start(&self) -> f64 {
132 self.start
133 }
134
135 pub fn end(&self) -> f64 {
136 self.end
137 }
138
139 pub fn set_start(&mut self, s: f64) {
140 self.start = s;
141 }
142
143 pub fn set_end(&mut self, e: f64) {
144 self.end = e;
145 }
146
147 pub fn set(&mut self, s: f64, e: f64) {
148 self.start = s;
149 self.end = e;
150 }
151}
152
153impl Default for GammaLinear {
154 fn default() -> Self {
155 Self {
156 start: 0.0,
157 end: 1.0,
158 }
159 }
160}
161
162impl GammaFunction for GammaLinear {
163 #[inline]
164 fn call(&self, x: f64) -> f64 {
165 if x < self.start {
166 0.0
167 } else if x > self.end {
168 1.0
169 } else {
170 (x - self.start) / (self.end - self.start)
171 }
172 }
173}
174
175#[derive(Debug, Clone, Copy)]
182pub struct GammaMultiply {
183 mul: f64,
184}
185
186impl GammaMultiply {
187 pub fn new(mul: f64) -> Self {
188 Self { mul }
189 }
190
191 pub fn value(&self) -> f64 {
192 self.mul
193 }
194
195 pub fn set_value(&mut self, v: f64) {
196 self.mul = v;
197 }
198}
199
200impl Default for GammaMultiply {
201 fn default() -> Self {
202 Self { mul: 1.0 }
203 }
204}
205
206impl GammaFunction for GammaMultiply {
207 #[inline]
208 fn call(&self, x: f64) -> f64 {
209 let y = x * self.mul;
210 if y > 1.0 {
211 1.0
212 } else {
213 y
214 }
215 }
216}
217
218#[inline]
224pub fn srgb_to_linear(x: f64) -> f64 {
225 if x <= 0.04045 {
226 x / 12.92
227 } else {
228 ((x + 0.055) / 1.055).powf(2.4)
229 }
230}
231
232#[inline]
234pub fn linear_to_srgb(x: f64) -> f64 {
235 if x <= 0.0031308 {
236 x * 12.92
237 } else {
238 1.055 * x.powf(1.0 / 2.4) - 0.055
239 }
240}
241
242pub struct GammaLut {
252 gamma: f64,
253 #[allow(dead_code)]
254 gamma_shift: u32,
255 gamma_size: usize,
256 gamma_mask: f64,
257 #[allow(dead_code)]
258 hi_res_shift: u32,
259 hi_res_size: usize,
260 hi_res_mask: f64,
261 dir_gamma: Vec<u8>,
262 inv_gamma: Vec<u8>,
263}
264
265impl GammaLut {
266 pub fn new() -> Self {
269 Self::with_shifts(8, 8)
270 }
271
272 pub fn new_with_gamma(g: f64) -> Self {
274 let mut lut = Self::new();
275 lut.set_gamma(g);
276 lut
277 }
278
279 pub fn with_shifts(gamma_shift: u32, hi_res_shift: u32) -> Self {
281 let gamma_size = 1usize << gamma_shift;
282 let hi_res_size = 1usize << hi_res_shift;
283
284 let mut dir_gamma = vec![0u8; gamma_size];
285 let mut inv_gamma = vec![0u8; hi_res_size];
286
287 for (i, entry) in dir_gamma.iter_mut().enumerate() {
289 *entry = (i << (hi_res_shift - gamma_shift)) as u8;
290 }
291 for (i, entry) in inv_gamma.iter_mut().enumerate() {
292 *entry = (i >> (hi_res_shift - gamma_shift)) as u8;
293 }
294
295 Self {
296 gamma: 1.0,
297 gamma_shift,
298 gamma_size,
299 gamma_mask: (gamma_size - 1) as f64,
300 hi_res_shift,
301 hi_res_size,
302 hi_res_mask: (hi_res_size - 1) as f64,
303 dir_gamma,
304 inv_gamma,
305 }
306 }
307
308 pub fn set_gamma(&mut self, g: f64) {
310 self.gamma = g;
311
312 for i in 0..self.gamma_size {
313 self.dir_gamma[i] =
314 uround((i as f64 / self.gamma_mask).powf(self.gamma) * self.hi_res_mask) as u8;
315 }
316
317 let inv_g = 1.0 / g;
318 for i in 0..self.hi_res_size {
319 self.inv_gamma[i] =
320 uround((i as f64 / self.hi_res_mask).powf(inv_g) * self.gamma_mask) as u8;
321 }
322 }
323
324 pub fn gamma(&self) -> f64 {
326 self.gamma
327 }
328
329 #[inline]
331 pub fn dir(&self, v: u8) -> u8 {
332 self.dir_gamma[v as usize]
333 }
334
335 #[inline]
337 pub fn inv(&self, v: u8) -> u8 {
338 self.inv_gamma[v as usize]
339 }
340}
341
342impl Default for GammaLut {
343 fn default() -> Self {
344 Self::new()
345 }
346}
347
348pub struct GammaSpline {
365 gamma: [u8; 256],
366 x: [f64; 4],
367 y_pts: [f64; 4],
368 spline: crate::bspline::Bspline,
369 x1: f64,
370 y1: f64,
371 x2: f64,
372 y2: f64,
373 cur_x: f64,
374}
375
376impl GammaSpline {
377 pub fn new() -> Self {
378 let mut gs = Self {
379 gamma: [0; 256],
380 x: [0.0; 4],
381 y_pts: [0.0; 4],
382 spline: crate::bspline::Bspline::new(),
383 x1: 0.0,
384 y1: 0.0,
385 x2: 10.0,
386 y2: 10.0,
387 cur_x: 0.0,
388 };
389 gs.set_values(1.0, 1.0, 1.0, 1.0);
390 gs
391 }
392
393 pub fn set_values(&mut self, kx1: f64, ky1: f64, kx2: f64, ky2: f64) {
397 let kx1 = kx1.clamp(0.001, 1.999);
398 let ky1 = ky1.clamp(0.001, 1.999);
399 let kx2 = kx2.clamp(0.001, 1.999);
400 let ky2 = ky2.clamp(0.001, 1.999);
401
402 self.x[0] = 0.0;
403 self.y_pts[0] = 0.0;
404 self.x[1] = kx1 * 0.25;
405 self.y_pts[1] = ky1 * 0.25;
406 self.x[2] = 1.0 - kx2 * 0.25;
407 self.y_pts[2] = 1.0 - ky2 * 0.25;
408 self.x[3] = 1.0;
409 self.y_pts[3] = 1.0;
410
411 self.spline.init(&self.x, &self.y_pts);
412
413 for i in 0..256 {
414 self.gamma[i] = (self.y(i as f64 / 255.0) * 255.0) as u8;
415 }
416 }
417
418 pub fn get_values(&self) -> (f64, f64, f64, f64) {
420 (
421 self.x[1] * 4.0,
422 self.y_pts[1] * 4.0,
423 (1.0 - self.x[2]) * 4.0,
424 (1.0 - self.y_pts[2]) * 4.0,
425 )
426 }
427
428 pub fn gamma(&self) -> &[u8; 256] {
430 &self.gamma
431 }
432
433 pub fn y(&self, x: f64) -> f64 {
435 let x = x.clamp(0.0, 1.0);
436 let val = self.spline.get(x);
437 val.clamp(0.0, 1.0)
438 }
439
440 pub fn set_box(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
442 self.x1 = x1;
443 self.y1 = y1;
444 self.x2 = x2;
445 self.y2 = y2;
446 }
447
448 pub fn rewind(&mut self, _idx: u32) {
450 self.cur_x = 0.0;
451 }
452
453 pub fn vertex(&mut self, vx: &mut f64, vy: &mut f64) -> u32 {
455 use crate::basics::{PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
456
457 if self.cur_x == 0.0 {
458 *vx = self.x1;
459 *vy = self.y1;
460 self.cur_x += 1.0 / (self.x2 - self.x1);
461 return PATH_CMD_MOVE_TO;
462 }
463
464 if self.cur_x > 1.0 {
465 return PATH_CMD_STOP;
466 }
467
468 *vx = self.x1 + self.cur_x * (self.x2 - self.x1);
469 *vy = self.y1 + self.y(self.cur_x) * (self.y2 - self.y1);
470
471 self.cur_x += 1.0 / (self.x2 - self.x1);
472 PATH_CMD_LINE_TO
473 }
474}
475
476impl Default for GammaSpline {
477 fn default() -> Self {
478 Self::new()
479 }
480}
481
482impl GammaFunction for GammaSpline {
483 fn call(&self, x: f64) -> f64 {
484 self.y(x)
485 }
486}
487
488#[cfg(test)]
493mod tests {
494 use super::*;
495
496 const EPSILON: f64 = 1e-10;
497
498 #[test]
499 fn test_gamma_none() {
500 let g = GammaNone;
501 assert!((g.call(0.0) - 0.0).abs() < EPSILON);
502 assert!((g.call(0.5) - 0.5).abs() < EPSILON);
503 assert!((g.call(1.0) - 1.0).abs() < EPSILON);
504 }
505
506 #[test]
507 fn test_gamma_power_identity() {
508 let g = GammaPower::new(1.0);
509 assert!((g.call(0.5) - 0.5).abs() < EPSILON);
510 }
511
512 #[test]
513 fn test_gamma_power_square() {
514 let g = GammaPower::new(2.0);
515 assert!((g.call(0.5) - 0.25).abs() < EPSILON);
516 assert!((g.call(0.0) - 0.0).abs() < EPSILON);
517 assert!((g.call(1.0) - 1.0).abs() < EPSILON);
518 }
519
520 #[test]
521 fn test_gamma_threshold() {
522 let g = GammaThreshold::new(0.5);
523 assert_eq!(g.call(0.3), 0.0);
524 assert_eq!(g.call(0.5), 1.0);
525 assert_eq!(g.call(0.7), 1.0);
526 }
527
528 #[test]
529 fn test_gamma_linear() {
530 let g = GammaLinear::new(0.2, 0.8);
531 assert_eq!(g.call(0.1), 0.0);
532 assert_eq!(g.call(0.9), 1.0);
533 assert!((g.call(0.5) - 0.5).abs() < EPSILON);
534 }
535
536 #[test]
537 fn test_gamma_multiply() {
538 let g = GammaMultiply::new(2.0);
539 assert!((g.call(0.3) - 0.6).abs() < EPSILON);
540 assert_eq!(g.call(0.7), 1.0); assert_eq!(g.call(1.0), 1.0);
542 }
543
544 #[test]
545 fn test_srgb_roundtrip() {
546 for i in 0..=10 {
547 let x = i as f64 / 10.0;
548 let linear = srgb_to_linear(x);
549 let back = linear_to_srgb(linear);
550 assert!(
551 (x - back).abs() < 1e-6,
552 "sRGB roundtrip failed for x={}: got {}",
553 x,
554 back
555 );
556 }
557 }
558
559 #[test]
560 fn test_srgb_endpoints() {
561 assert!((srgb_to_linear(0.0)).abs() < EPSILON);
562 assert!((srgb_to_linear(1.0) - 1.0).abs() < EPSILON);
563 assert!((linear_to_srgb(0.0)).abs() < EPSILON);
564 assert!((linear_to_srgb(1.0) - 1.0).abs() < EPSILON);
565 }
566
567 #[test]
568 fn test_gamma_lut_identity() {
569 let lut = GammaLut::new();
570 assert_eq!(lut.gamma(), 1.0);
571 assert_eq!(lut.dir(0), 0);
573 assert_eq!(lut.dir(128), 128);
574 assert_eq!(lut.dir(255), 255);
575 assert_eq!(lut.inv(0), 0);
576 assert_eq!(lut.inv(128), 128);
577 assert_eq!(lut.inv(255), 255);
578 }
579
580 #[test]
581 fn test_gamma_lut_roundtrip() {
582 let lut = GammaLut::new_with_gamma(2.2);
583 for v in [0u8, 64, 128, 192, 255] {
585 let forward = lut.dir(v);
586 let back = lut.inv(forward);
587 assert!(
588 (v as i32 - back as i32).unsigned_abs() <= 1,
589 "Roundtrip failed for v={}: dir={}, inv={}",
590 v,
591 forward,
592 back
593 );
594 }
595 }
596
597 #[test]
598 fn test_gamma_lut_gamma_2() {
599 let lut = GammaLut::new_with_gamma(2.0);
600 let d = lut.dir(128);
602 assert!(
603 (d as i32 - 64).unsigned_abs() <= 1,
604 "dir(128) at gamma 2.0 should be ~64, got {}",
605 d
606 );
607 }
608
609 #[test]
614 fn test_gamma_spline_default() {
615 let gs = GammaSpline::new();
616 let (kx1, ky1, kx2, ky2) = gs.get_values();
618 assert!((kx1 - 1.0).abs() < 0.01);
619 assert!((ky1 - 1.0).abs() < 0.01);
620 assert!((kx2 - 1.0).abs() < 0.01);
621 assert!((ky2 - 1.0).abs() < 0.01);
622 }
623
624 #[test]
625 fn test_gamma_spline_identity_curve() {
626 let gs = GammaSpline::new();
627 assert!((gs.y(0.0)).abs() < 0.01);
629 assert!((gs.y(1.0) - 1.0).abs() < 0.01);
630 assert!((gs.y(0.5) - 0.5).abs() < 0.05);
631 }
632
633 #[test]
634 fn test_gamma_spline_gamma_table() {
635 let gs = GammaSpline::new();
636 let gamma = gs.gamma();
637 assert_eq!(gamma[0], 0);
639 assert_eq!(gamma[255], 255);
640 assert!((gamma[128] as i32 - 128).unsigned_abs() <= 5);
642 }
643
644 #[test]
645 fn test_gamma_spline_roundtrip_values() {
646 let mut gs = GammaSpline::new();
647 gs.set_values(0.5, 1.5, 0.8, 1.2);
648 let (kx1, ky1, kx2, ky2) = gs.get_values();
649 assert!((kx1 - 0.5).abs() < 0.001);
650 assert!((ky1 - 1.5).abs() < 0.001);
651 assert!((kx2 - 0.8).abs() < 0.001);
652 assert!((ky2 - 1.2).abs() < 0.001);
653 }
654
655 #[test]
656 fn test_gamma_spline_vertex_source() {
657 let mut gs = GammaSpline::new();
658 gs.set_box(0.0, 0.0, 100.0, 100.0);
659 gs.rewind(0);
660
661 let (mut x, mut y) = (0.0, 0.0);
662 let cmd = gs.vertex(&mut x, &mut y);
663 assert_eq!(cmd, crate::basics::PATH_CMD_MOVE_TO);
664 assert!((x - 0.0).abs() < 0.01);
665
666 let mut count = 0;
668 loop {
669 let cmd = gs.vertex(&mut x, &mut y);
670 if cmd == crate::basics::PATH_CMD_STOP {
671 break;
672 }
673 assert_eq!(cmd, crate::basics::PATH_CMD_LINE_TO);
674 count += 1;
675 }
676 assert!(count >= 99 && count <= 101); }
678
679 #[test]
680 fn test_gamma_spline_clamping() {
681 let mut gs = GammaSpline::new();
682 gs.set_values(0.0, 3.0, -1.0, 2.5);
683 let (kx1, ky1, kx2, ky2) = gs.get_values();
684 assert!((kx1 - 0.001).abs() < 0.001);
686 assert!((ky1 - 1.999).abs() < 0.001);
687 assert!((kx2 - 0.001).abs() < 0.001);
688 assert!((ky2 - 1.999).abs() < 0.001);
689 }
690
691 #[test]
692 fn test_gamma_spline_as_gamma_function() {
693 let gs = GammaSpline::new();
694 assert!((gs.call(0.0)).abs() < 0.01);
696 assert!((gs.call(1.0) - 1.0).abs() < 0.01);
697 }
698}