vapours/
generic.rs

1//! Traits that apply to multiple types.
2
3use num_traits::ToPrimitive;
4use vapoursynth4_rs::{
5  frame::{VideoFormat, VideoFrame},
6  node::VideoNode,
7  ColorFamily, SampleType, VideoInfo,
8};
9
10use crate::enums::ColorRange;
11
12/// A trait for types that hold a video format.
13pub trait HoldsVideoFormat: Sized {
14  /// Get the video format.
15  #[must_use]
16  fn video_format(&self) -> &VideoFormat;
17
18  /// Get the color family of this clip or format.
19  #[must_use]
20  fn color_family(&self) -> ColorFamily {
21    self.video_format().color_family
22  }
23
24  /// Get the bit depth of this clip or format.
25  #[must_use]
26  fn depth(&self) -> i32 {
27    self.video_format().bits_per_sample
28  }
29
30  /// Get the sample type of this clip or format.
31  #[must_use]
32  fn sample_type(&self) -> SampleType {
33    self.video_format().sample_type
34  }
35
36  /// Returns the lowest value for the bit depth of this clip or format.
37  #[must_use]
38  fn lowest_value(&self, chroma: Option<bool>, range_in: Option<ColorRange>) -> f32 {
39    let is_rgb = self.color_family() == ColorFamily::RGB;
40    let chroma = if is_rgb {
41      false
42    } else {
43      chroma.unwrap_or(false)
44    };
45
46    if self.sample_type() == SampleType::Float {
47      return if chroma { -0.5 } else { 0.0 };
48    }
49
50    let range_in = range_in.unwrap_or(if is_rgb {
51      ColorRange::Full
52    } else {
53      ColorRange::Limited
54    });
55
56    if range_in == ColorRange::Limited {
57      return (16 << (self.depth() - 8))
58        .to_f32()
59        .expect("result should fit in a f32");
60    }
61
62    0.0
63  }
64
65  /// Returns the midpoint value for the bit depth of this clip or format.
66  #[must_use]
67  fn neutral_value(&self) -> f32 {
68    if self.sample_type() == SampleType::Float {
69      return 0.0;
70    }
71
72    (1 << (self.depth() - 1))
73      .to_f32()
74      .expect("result should fit in a f32")
75  }
76
77  /// Returns the peak value for the bit depth of this clip or format.
78  #[must_use]
79  fn peak_value(&self, chroma: Option<bool>, range_in: Option<ColorRange>) -> f32 {
80    let is_rgb = self.color_family() == ColorFamily::RGB;
81    let chroma = if is_rgb {
82      false
83    } else {
84      chroma.unwrap_or(false)
85    };
86
87    if self.sample_type() == SampleType::Float {
88      return if chroma { 0.5 } else { 1.0 };
89    }
90
91    let range_in = range_in.unwrap_or(if is_rgb {
92      ColorRange::Full
93    } else {
94      ColorRange::Limited
95    });
96
97    if range_in == ColorRange::Limited {
98      return (if chroma { 240 } else { 235 } << (self.depth() - 8))
99        .to_f32()
100        .expect("result should fit in a f32");
101    }
102
103    ((1 << self.depth()) - 1)
104      .to_f32()
105      .expect("result should fit in a f32")
106  }
107}
108
109impl HoldsVideoFormat for VideoFrame {
110  fn video_format(&self) -> &VideoFormat {
111    self.get_video_format()
112  }
113}
114
115impl HoldsVideoFormat for VideoFormat {
116  fn video_format(&self) -> &VideoFormat {
117    self
118  }
119}
120
121impl HoldsVideoFormat for VideoInfo {
122  fn video_format(&self) -> &VideoFormat {
123    &self.format
124  }
125}
126
127impl HoldsVideoFormat for VideoNode {
128  fn video_format(&self) -> &VideoFormat {
129    self.info().video_format()
130  }
131}
132
133#[cfg(test)]
134mod tests {
135  use approx::assert_relative_eq;
136  use rstest::rstest;
137  use vapoursynth4_rs::frame::VideoFormat;
138
139  use crate::vs_enums::{
140    GRAY16, GRAY8, GRAYH, GRAYS, RGB24, RGBH, RGBS, YUV420P16, YUV420P8, YUV420PS, YUV444P16,
141    YUV444P8, YUV444PS,
142  };
143
144  use super::*;
145
146  #[rstest]
147  #[case(GRAY16, ColorFamily::Gray)]
148  #[case(RGB24, ColorFamily::RGB)]
149  #[case(YUV420P16, ColorFamily::YUV)]
150  fn test_color_family(#[case] format: VideoFormat, #[case] expected: ColorFamily) {
151    assert_eq!(format.color_family(), expected);
152  }
153
154  #[rstest]
155  #[case(GRAY8, 16.0)]
156  #[case(GRAY16, 4096.0)]
157  #[case(RGB24, 0.0)]
158  #[case(RGBH, 0.0)]
159  #[case(RGBS, 0.0)]
160  #[case(YUV420P8, 16.0)]
161  #[case(YUV420P16, 4096.0)]
162  #[case(YUV420PS, 0.0)]
163  #[case(YUV444P8, 16.0)]
164  #[case(YUV444P16, 4096.0)]
165  #[case(YUV444PS, 0.0)]
166  fn test_lowest_value_defaults(#[case] format: VideoFormat, #[case] expected: f32) {
167    assert_relative_eq!(format.lowest_value(None, None), expected);
168  }
169
170  #[rstest]
171  #[case(GRAY8, 16.0)]
172  #[case(GRAY16, 4096.0)]
173  #[case(RGB24, 0.0)]
174  #[case(RGBH, 0.0)]
175  #[case(RGBS, 0.0)]
176  #[case(YUV420P8, 16.0)]
177  #[case(YUV420P16, 4096.0)]
178  #[case(YUV420PS, -0.5)]
179  #[case(YUV444P8, 16.0)]
180  #[case(YUV444P16, 4096.0)]
181  #[case(YUV444PS, -0.5)]
182  fn test_lowest_value_chroma(#[case] format: VideoFormat, #[case] expected: f32) {
183    assert_relative_eq!(format.lowest_value(Some(true), None), expected);
184  }
185
186  #[rstest]
187  #[case(GRAY8, 128.0)]
188  #[case(GRAY16, 32768.0)]
189  #[case(GRAYH, 0.0)]
190  #[case(GRAYS, 0.0)]
191  fn test_neutral_value_defaults(#[case] format: VideoFormat, #[case] expected: f32) {
192    assert_relative_eq!(format.neutral_value(), expected);
193  }
194
195  #[rstest]
196  #[case(GRAY8, 235.0)]
197  #[case(GRAY16, 60160.0)]
198  #[case(GRAYH, 1.0)]
199  #[case(GRAYS, 1.0)]
200  #[case(RGB24, 255.0)]
201  #[case(RGBH, 1.0)]
202  #[case(RGBS, 1.0)]
203  fn test_peak_value_defaults(#[case] format: VideoFormat, #[case] expected: f32) {
204    assert_relative_eq!(format.peak_value(None, None), expected);
205  }
206}