1#![forbid(unsafe_code)]
44#![allow(clippy::cast_lossless)]
45#![allow(clippy::cast_precision_loss)]
46#![allow(clippy::cast_possible_truncation)]
47#![allow(clippy::cast_sign_loss)]
48#![allow(clippy::similar_names)]
49#![allow(clippy::many_single_char_names)]
50#![allow(clippy::too_many_lines)]
51#![allow(clippy::excessive_precision)]
52#![allow(clippy::module_name_repetitions)]
53#![allow(dead_code)]
54
55use crate::error::{GraphError, GraphResult};
56use crate::frame::FilterFrame;
57use crate::node::{Node, NodeId, NodeState, NodeType};
58use crate::port::{InputPort, OutputPort, PortId, PortType};
59use oximedia_codec::VideoFrame;
60use oximedia_core::PixelFormat;
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum LogFormat {
69 Cineon,
71 ArriLogC3,
73 ArriLogC4,
75 SonySLog3,
77 PanasonicVLog,
79 RedLog3G10,
81 DjiDLog,
83 CanonCLog,
85 BlackmagicFilm5,
87 AcesCct,
89 AcesProxy10,
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum LogDirection {
96 LinearToLog,
98 LogToLinear,
100}
101
102#[derive(Clone, Copy, Debug)]
111pub struct CineonLog {
112 pub black: f64,
114 pub white: f64,
116 pub gamma: f64,
118}
119
120impl Default for CineonLog {
121 fn default() -> Self {
122 Self {
123 black: 95.0 / 1023.0,
124 white: 685.0 / 1023.0,
125 gamma: 0.6,
126 }
127 }
128}
129
130impl CineonLog {
131 #[must_use]
133 pub fn linear_to_log(self, linear: f64) -> f64 {
134 if linear <= 0.0 {
135 return self.black;
136 }
137
138 let log_val = self.black
139 + (self.white - self.black) * (linear.log10() * self.gamma + (1.0 - self.black));
140
141 log_val.clamp(0.0, 1.0)
142 }
143
144 #[must_use]
146 pub fn log_to_linear(self, log: f64) -> f64 {
147 if log <= self.black {
148 return 0.0;
149 }
150
151 let normalized = (log - self.black) / (self.white - self.black);
152 let linear = 10_f64.powf((normalized - (1.0 - self.black)) / self.gamma);
153
154 linear.max(0.0)
155 }
156}
157
158#[derive(Clone, Copy, Debug)]
167pub struct ArriLogC3 {
168 pub cut: f64,
170 pub a: f64,
172 pub b: f64,
174 pub c: f64,
176 pub d: f64,
178 pub e: f64,
180 pub f: f64,
182}
183
184impl Default for ArriLogC3 {
185 fn default() -> Self {
186 Self {
188 cut: 0.010591,
189 a: 5.555556,
190 b: 0.052272,
191 c: 0.247190,
192 d: 0.385537,
193 e: 5.555556,
194 f: 0.092809,
195 }
196 }
197}
198
199impl ArriLogC3 {
200 #[must_use]
202 pub fn linear_to_log(self, linear: f64) -> f64 {
203 if linear > self.cut {
204 self.c * (self.a * linear + self.b).log10() + self.d
205 } else {
206 self.e * linear + self.f
207 }
208 }
209
210 #[must_use]
212 pub fn log_to_linear(self, log: f64) -> f64 {
213 let cut_log = self.c * (self.a * self.cut + self.b).log10() + self.d;
214
215 if log > cut_log {
216 (10_f64.powf((log - self.d) / self.c) - self.b) / self.a
217 } else {
218 (log - self.f) / self.e
219 }
220 }
221}
222
223#[derive(Clone, Copy, Debug)]
227pub struct ArriLogC4 {
228 pub a: f64,
230 pub b: f64,
232 pub c: f64,
234 pub d: f64,
236 pub e: f64,
238 pub f: f64,
240}
241
242impl Default for ArriLogC4 {
243 fn default() -> Self {
244 Self {
245 a: 2048.0,
246 b: 0.0,
247 c: 0.184904,
248 d: 0.385537,
249 e: 5.555556,
250 f: 0.092809,
251 }
252 }
253}
254
255impl ArriLogC4 {
256 #[must_use]
258 pub fn linear_to_log(self, linear: f64) -> f64 {
259 if linear <= 0.0 {
260 return self.b;
261 }
262
263 (((linear + self.e) / (1.0 + self.e)).ln() / self.c.ln() + self.d + self.b) / self.a
264 }
265
266 #[must_use]
268 pub fn log_to_linear(self, log: f64) -> f64 {
269 let x = log * self.a - self.d - self.b;
270 self.c.powf(x) * (1.0 + self.e) - self.e
271 }
272}
273
274#[derive(Clone, Copy, Debug, Default)]
283pub struct SonySLog3;
284
285impl SonySLog3 {
286 #[must_use]
288 pub fn linear_to_log(self, linear: f64) -> f64 {
289 if linear >= 0.01125000 {
290 (420.0 + (((linear + 0.01) / (0.18 + 0.01)).log10() * 261.5)) / 1023.0
291 } else {
292 (linear * (171.2102946929 - 95.0) / 0.01125000 + 95.0) / 1023.0
293 }
294 }
295
296 #[must_use]
298 pub fn log_to_linear(self, log: f64) -> f64 {
299 let log_1023 = log * 1023.0;
300
301 if log_1023 >= 171.2102946929 {
302 10_f64.powf((log_1023 - 420.0) / 261.5) * (0.18 + 0.01) - 0.01
303 } else {
304 (log_1023 - 95.0) * 0.01125000 / (171.2102946929 - 95.0)
305 }
306 }
307}
308
309#[derive(Clone, Copy, Debug)]
318pub struct PanasonicVLog {
319 pub cut: f64,
321 pub b: f64,
323 pub c: f64,
325 pub d: f64,
327}
328
329impl Default for PanasonicVLog {
330 fn default() -> Self {
331 Self {
332 cut: 0.01,
333 b: 0.00873,
334 c: 0.241514,
335 d: 0.598206,
336 }
337 }
338}
339
340impl PanasonicVLog {
341 #[must_use]
343 pub fn linear_to_log(self, linear: f64) -> f64 {
344 if linear < self.cut {
345 5.6 * linear + 0.125
346 } else {
347 self.c * (linear + self.b).log10() + self.d
348 }
349 }
350
351 #[must_use]
353 pub fn log_to_linear(self, log: f64) -> f64 {
354 let cut_log = self.c * (self.cut + self.b).log10() + self.d;
355
356 if log < cut_log {
357 (log - 0.125) / 5.6
358 } else {
359 10_f64.powf((log - self.d) / self.c) - self.b
360 }
361 }
362}
363
364#[derive(Clone, Copy, Debug, Default)]
373pub struct RedLog3G10;
374
375impl RedLog3G10 {
376 #[must_use]
378 pub fn linear_to_log(self, linear: f64) -> f64 {
379 if linear < 0.0 {
380 return 0.0;
381 }
382
383 let a = 0.224282;
384 let b = 155.975327;
385 let c = 0.01;
386
387 a * (linear * b + c).log10() + 0.5
388 }
389
390 #[must_use]
392 pub fn log_to_linear(self, log: f64) -> f64 {
393 let a = 0.224282;
394 let b = 155.975327;
395 let c = 0.01;
396
397 (10_f64.powf((log - 0.5) / a) - c) / b
398 }
399}
400
401#[derive(Clone, Copy, Debug, Default)]
409pub struct DjiDLog;
410
411impl DjiDLog {
412 #[must_use]
414 pub fn linear_to_log(self, linear: f64) -> f64 {
415 if linear <= 0.0078 {
416 6.025 * linear + 0.0929
417 } else {
418 ((linear + 0.0078) / (1.0 + 0.0078)).ln() / 0.9892 / 6.025 + 0.584
419 }
420 }
421
422 #[must_use]
424 pub fn log_to_linear(self, log: f64) -> f64 {
425 if log <= 0.14 {
426 (log - 0.0929) / 6.025
427 } else {
428 (0.9892_f64.powf(6.025 * (log - 0.584))) * (1.0 + 0.0078) - 0.0078
429 }
430 }
431}
432
433#[derive(Clone, Copy, Debug, Default)]
441pub struct CanonCLog;
442
443impl CanonCLog {
444 #[must_use]
446 pub fn linear_to_log(self, linear: f64) -> f64 {
447 if linear < 0.0 {
448 return 0.0;
449 }
450
451 let a = 0.529136;
452 let b = 0.0047622;
453 let c = 0.312689;
454 let d = 0.092864;
455
456 a * (a * linear + b).ln() + c * linear + d
457 }
458
459 #[must_use]
461 pub fn log_to_linear(self, log: f64) -> f64 {
462 let a = 0.529136;
464 let c = 0.312689;
465 let d = 0.092864;
466
467 if log <= d {
468 return 0.0;
469 }
470
471 let mut x = (log - d) / c;
473 for _ in 0..5 {
474 let fx = a * (a * x + 0.0047622).ln() + c * x + d - log;
475 let dfx = a * a / (a * x + 0.0047622) + c;
476 x -= fx / dfx;
477 }
478
479 x.max(0.0)
480 }
481}
482
483#[derive(Clone, Copy, Debug, Default)]
491pub struct BlackmagicFilm5;
492
493impl BlackmagicFilm5 {
494 #[must_use]
496 pub fn linear_to_log(self, linear: f64) -> f64 {
497 if linear < 0.005 {
498 return linear * 8.283605932402494;
499 }
500
501 0.2 * (linear + 0.01).ln() + 0.40975773852480107
502 }
503
504 #[must_use]
506 pub fn log_to_linear(self, log: f64) -> f64 {
507 if log < 0.04426550899923 {
508 return log / 8.283605932402494;
509 }
510
511 ((log - 0.40975773852480107) / 0.2).exp() - 0.01
512 }
513}
514
515#[derive(Clone, Copy, Debug, Default)]
523pub struct AcesCct;
524
525impl AcesCct {
526 #[must_use]
528 pub fn linear_to_log(self, linear: f64) -> f64 {
529 if linear <= 0.0078125 {
530 10.5402377416545 * linear + 0.0729055341958355
531 } else {
532 ((linear + 0.0000152587890625).max(1e-10).log2() + 9.72) / 17.52
533 }
534 }
535
536 #[must_use]
538 pub fn log_to_linear(self, log: f64) -> f64 {
539 if log <= 0.155251141552511 {
540 (log - 0.0729055341958355) / 10.5402377416545
541 } else {
542 2_f64.powf(log * 17.52 - 9.72) - 0.0000152587890625
543 }
544 }
545}
546
547#[derive(Clone, Copy, Debug, Default)]
551pub struct AcesProxy10;
552
553impl AcesProxy10 {
554 #[must_use]
556 pub fn linear_to_log(self, linear: f64) -> f64 {
557 if linear <= 0.0 {
558 return 0.0;
559 }
560
561 let log2_val = linear.max(1e-10).log2();
562 ((log2_val + 2.5) / 10.0 * 1023.0 + 64.0) / 1023.0
563 }
564
565 #[must_use]
567 pub fn log_to_linear(self, log: f64) -> f64 {
568 let cv = log * 1023.0;
569 let log2_val = (cv - 64.0) / 1023.0 * 10.0 - 2.5;
570
571 2_f64.powf(log2_val)
572 }
573}
574
575#[derive(Clone, Copy, Debug)]
581pub struct LogConverter {
582 format: LogFormat,
583}
584
585impl LogConverter {
586 #[must_use]
588 pub const fn new(format: LogFormat) -> Self {
589 Self { format }
590 }
591
592 #[must_use]
594 pub fn linear_to_log(self, linear: f64) -> f64 {
595 match self.format {
596 LogFormat::Cineon => CineonLog::default().linear_to_log(linear),
597 LogFormat::ArriLogC3 => ArriLogC3::default().linear_to_log(linear),
598 LogFormat::ArriLogC4 => ArriLogC4::default().linear_to_log(linear),
599 LogFormat::SonySLog3 => SonySLog3.linear_to_log(linear),
600 LogFormat::PanasonicVLog => PanasonicVLog::default().linear_to_log(linear),
601 LogFormat::RedLog3G10 => RedLog3G10.linear_to_log(linear),
602 LogFormat::DjiDLog => DjiDLog.linear_to_log(linear),
603 LogFormat::CanonCLog => CanonCLog.linear_to_log(linear),
604 LogFormat::BlackmagicFilm5 => BlackmagicFilm5.linear_to_log(linear),
605 LogFormat::AcesCct => AcesCct.linear_to_log(linear),
606 LogFormat::AcesProxy10 => AcesProxy10.linear_to_log(linear),
607 }
608 }
609
610 #[must_use]
612 pub fn log_to_linear(self, log: f64) -> f64 {
613 match self.format {
614 LogFormat::Cineon => CineonLog::default().log_to_linear(log),
615 LogFormat::ArriLogC3 => ArriLogC3::default().log_to_linear(log),
616 LogFormat::ArriLogC4 => ArriLogC4::default().log_to_linear(log),
617 LogFormat::SonySLog3 => SonySLog3.log_to_linear(log),
618 LogFormat::PanasonicVLog => PanasonicVLog::default().log_to_linear(log),
619 LogFormat::RedLog3G10 => RedLog3G10.log_to_linear(log),
620 LogFormat::DjiDLog => DjiDLog.log_to_linear(log),
621 LogFormat::CanonCLog => CanonCLog.log_to_linear(log),
622 LogFormat::BlackmagicFilm5 => BlackmagicFilm5.log_to_linear(log),
623 LogFormat::AcesCct => AcesCct.log_to_linear(log),
624 LogFormat::AcesProxy10 => AcesProxy10.log_to_linear(log),
625 }
626 }
627
628 #[must_use]
630 pub fn convert_rgb(self, r: f64, g: f64, b: f64, direction: LogDirection) -> (f64, f64, f64) {
631 match direction {
632 LogDirection::LinearToLog => (
633 self.linear_to_log(r),
634 self.linear_to_log(g),
635 self.linear_to_log(b),
636 ),
637 LogDirection::LogToLinear => (
638 self.log_to_linear(r),
639 self.log_to_linear(g),
640 self.log_to_linear(b),
641 ),
642 }
643 }
644}
645
646pub struct LogLinearFilter {
652 id: NodeId,
653 name: String,
654 state: NodeState,
655 input: InputPort,
656 output: OutputPort,
657 converter: LogConverter,
658 direction: LogDirection,
659}
660
661impl LogLinearFilter {
662 #[must_use]
664 pub fn new(id: NodeId, name: &str, format: LogFormat, direction: LogDirection) -> Self {
665 Self {
666 id,
667 name: name.to_string(),
668 state: NodeState::Idle,
669 input: InputPort::new(PortId(0), "input", PortType::Video),
670 output: OutputPort::new(PortId(1), "output", PortType::Video),
671 converter: LogConverter::new(format),
672 direction,
673 }
674 }
675
676 fn process_frame(&self, frame: VideoFrame) -> GraphResult<VideoFrame> {
678 match frame.format {
680 PixelFormat::Rgb24 | PixelFormat::Rgba32 => self.process_rgb_frame(frame),
681 _ => Ok(frame),
682 }
683 }
684
685 fn process_rgb_frame(&self, mut frame: VideoFrame) -> GraphResult<VideoFrame> {
687 let width = frame.width;
688 let height = frame.height;
689 let planes = &mut frame.planes;
690
691 if planes.is_empty() {
692 return Ok(frame);
693 }
694
695 let plane = &mut planes[0];
696 let stride = plane.stride;
697 let data = plane.data.as_mut_slice();
698
699 let bytes_per_pixel = match frame.format {
700 PixelFormat::Rgb24 => 3,
701 PixelFormat::Rgba32 => 4,
702 _ => return Ok(frame),
703 };
704
705 for y in 0..height as usize {
706 for x in 0..width as usize {
707 let offset = y * stride + x * bytes_per_pixel;
708
709 let r = data[offset] as f64 / 255.0;
711 let g = data[offset + 1] as f64 / 255.0;
712 let b = data[offset + 2] as f64 / 255.0;
713
714 let (r_out, g_out, b_out) = self.converter.convert_rgb(r, g, b, self.direction);
716
717 data[offset] = (r_out.clamp(0.0, 1.0) * 255.0) as u8;
719 data[offset + 1] = (g_out.clamp(0.0, 1.0) * 255.0) as u8;
720 data[offset + 2] = (b_out.clamp(0.0, 1.0) * 255.0) as u8;
721 }
722 }
723
724 Ok(frame)
725 }
726}
727
728impl Node for LogLinearFilter {
729 fn id(&self) -> NodeId {
730 self.id
731 }
732
733 fn name(&self) -> &str {
734 &self.name
735 }
736
737 fn node_type(&self) -> NodeType {
738 NodeType::Filter
739 }
740
741 fn state(&self) -> NodeState {
742 self.state
743 }
744
745 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
746 self.state = state;
747 Ok(())
748 }
749
750 fn inputs(&self) -> &[InputPort] {
751 std::slice::from_ref(&self.input)
752 }
753
754 fn outputs(&self) -> &[OutputPort] {
755 std::slice::from_ref(&self.output)
756 }
757
758 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
759 match input {
760 Some(FilterFrame::Video(video_frame)) => {
761 let processed = self.process_frame(video_frame)?;
762 Ok(Some(FilterFrame::Video(processed)))
763 }
764 Some(_) => Err(GraphError::ProcessingError {
765 node: self.id,
766 message: "Log/Linear filter expects video input".to_string(),
767 }),
768 None => Ok(None),
769 }
770 }
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776
777 #[test]
778 fn test_cineon_roundtrip() {
779 let cineon = CineonLog::default();
780 let linear = 0.18; let log = cineon.linear_to_log(linear);
782 let back = cineon.log_to_linear(log);
783 assert!((linear - back).abs() < 0.01);
784 }
785
786 #[test]
787 fn test_arri_logc3_roundtrip() {
788 let logc = ArriLogC3::default();
789 let linear = 0.18;
790 let log = logc.linear_to_log(linear);
791 let back = logc.log_to_linear(log);
792 assert!((linear - back).abs() < 0.01);
793 }
794
795 #[test]
796 fn test_sony_slog3_roundtrip() {
797 let slog = SonySLog3;
798 let linear = 0.18;
799 let log = slog.linear_to_log(linear);
800 let back = slog.log_to_linear(log);
801 assert!((linear - back).abs() < 0.01);
802 }
803
804 #[test]
805 fn test_panasonic_vlog_roundtrip() {
806 let vlog = PanasonicVLog::default();
807 let linear = 0.18;
808 let log = vlog.linear_to_log(linear);
809 let back = vlog.log_to_linear(log);
810 assert!((linear - back).abs() < 0.01);
811 }
812
813 #[test]
814 fn test_red_log3g10_roundtrip() {
815 let red = RedLog3G10;
816 let linear = 0.18;
817 let log = red.linear_to_log(linear);
818 let back = red.log_to_linear(log);
819 assert!((linear - back).abs() < 0.01);
820 }
821
822 #[test]
823 fn test_aces_cct_roundtrip() {
824 let aces = AcesCct;
825 let linear = 0.18;
826 let log = aces.linear_to_log(linear);
827 let back = aces.log_to_linear(log);
828 assert!((linear - back).abs() < 0.01);
829 }
830
831 #[test]
832 fn test_log_converter() {
833 let converter = LogConverter::new(LogFormat::ArriLogC3);
834 let linear = 0.18;
835 let log = converter.linear_to_log(linear);
836 let back = converter.log_to_linear(log);
837 assert!((linear - back).abs() < 0.01);
838 }
839
840 #[test]
841 fn test_log_converter_rgb() {
842 let converter = LogConverter::new(LogFormat::SonySLog3);
843 let (r, g, b) = (0.2, 0.5, 0.8);
844 let (log_r, log_g, log_b) = converter.convert_rgb(r, g, b, LogDirection::LinearToLog);
845 let (back_r, back_g, back_b) =
846 converter.convert_rgb(log_r, log_g, log_b, LogDirection::LogToLinear);
847
848 assert!((r - back_r).abs() < 0.01);
849 assert!((g - back_g).abs() < 0.01);
850 assert!((b - back_b).abs() < 0.01);
851 }
852}