1use num_traits::ToPrimitive;
4use vapoursynth4_rs::{
5 frame::{VideoFormat, VideoFrame},
6 node::VideoNode,
7 ColorFamily, SampleType, VideoInfo,
8};
9
10use crate::enums::ColorRange;
11
12pub trait HoldsVideoFormat: Sized {
14 #[must_use]
16 fn video_format(&self) -> &VideoFormat;
17
18 #[must_use]
20 fn color_family(&self) -> ColorFamily {
21 self.video_format().color_family
22 }
23
24 #[must_use]
26 fn depth(&self) -> i32 {
27 self.video_format().bits_per_sample
28 }
29
30 #[must_use]
32 fn sample_type(&self) -> SampleType {
33 self.video_format().sample_type
34 }
35
36 #[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 #[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 #[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}