1use std::io::{self, BufRead};
2
3use crate::{cube, Cube, Result};
4
5pub struct Lut {
6 title: Option<String>,
7 comments: String,
8 in_video_range: bool,
9 out_video_range: bool,
10 cube: Cube,
11 shaper: Option<Cube>,
12 domain_min: Option<[f32; 3]>,
13 domain_max: Option<[f32; 3]>,
14}
15
16struct Collector {
17 cube: Option<Cube>,
18 len: usize,
19 capacity: usize,
20}
21
22impl Collector {
23 pub fn with_cube(cube: Cube) -> Self {
24 let capacity = cube.r_len();
25 Self {
26 cube: Some(cube),
27 len: 0,
28 capacity,
29 }
30 }
31
32 #[inline]
33 fn push<const N: usize>(&mut self, rgb: &[f32; N]) -> Result<Option<Box<Cube>>> {
34 let Some(cube) = self.cube.as_mut() else {
35 return Err("Cube is full".into());
36 };
37 let len = self.len;
38 let next = len + N;
39 cube.rgbs[len..next].copy_from_slice(rgb);
40 self.len = next;
41 if next == self.capacity {
42 Ok(Some(Box::new(self.cube.take().unwrap())))
43 } else {
44 Ok(None)
45 }
46 }
47}
48
49impl Lut {
50 pub fn title(&self) -> Option<&str> {
51 self.title.as_deref()
52 }
53
54 pub fn comments(&self) -> &str {
55 &self.comments
56 }
57
58 pub fn in_video_range(&self) -> bool {
59 self.in_video_range
60 }
61
62 pub fn out_video_range(&self) -> bool {
63 self.out_video_range
64 }
65
66 pub fn cube(&self) -> &Cube {
67 &self.cube
68 }
69
70 pub fn shaper(&self) -> Option<&Cube> {
71 self.shaper.as_ref()
72 }
73
74 pub fn domain_min(&self) -> Option<&[f32; 3]> {
75 self.domain_min.as_ref()
76 }
77
78 pub fn domain_max(&self) -> Option<&[f32; 3]> {
79 self.domain_max.as_ref()
80 }
81}
82
83impl Lut {
84 pub fn parse(reader: &mut impl BufRead) -> Result<Self> {
85 let mut rgb = [0.0f32; 3];
86 let mut title = None;
87 let mut in_video_range = false;
88 let mut out_video_range = false;
89 let mut comments = String::new();
90 let mut possible_shaper = false;
91 let mut cube: Option<Cube> = None;
92 let mut shaper: Option<Cube> = None;
93 let mut collector: Option<Collector> = None;
94 let delimiter = &[' ', '\t'];
95
96 let mut domain_min: Option<[f32; 3]> = None;
97 let mut domain_max: Option<[f32; 3]> = None;
98
99 let mut line = String::with_capacity(100);
100 loop {
101 line.clear();
102 if reader.read_line(&mut line)? == 0 {
103 Err(io::Error::from(io::ErrorKind::UnexpectedEof))?;
104 }
105 let s = line.trim();
107 if s.is_empty() {
108 continue;
109 }
110
111 if s.as_bytes()[0] == b'#' {
113 if !comments.is_empty() {
114 comments.push('\n');
115 }
116 comments.push_str(s);
117 continue;
118 }
119
120 let Some((a, b)) = s.split_once(delimiter) else {
121 continue;
122 };
123
124 if let Some(coll) = collector.as_mut() {
125 let Some((g, b)) = b.split_once(delimiter) else {
126 return Err(format!("invalid rgb in line: {line}").into());
127 };
128
129 rgb[0] = a.parse()?;
130 rgb[1] = g.parse()?;
131 rgb[2] = b.parse()?;
132
133 if let Some(completed_cube) = coll.push(&rgb)? {
134 if let Some(cube) = cube.take() {
135 shaper = Some(*completed_cube);
136 collector = Some(Collector::with_cube(cube));
137 } else {
138 return Ok(Self {
139 title,
140 comments,
141 in_video_range,
142 out_video_range,
143 cube: *completed_cube,
144 shaper,
145 domain_min,
146 domain_max,
147 });
148 };
149 }
150 continue;
151 }
152
153 match a {
154 "TITLE" => title = Some(b.to_owned()),
155 "LUT_IN_VIDEO_RANGE" => in_video_range = true,
156 "LUT_OUT_VIDEO_RANGE" => out_video_range = true,
157 "LUT_1D_SIZE" if possible_shaper => {
158 shaper = cube.take();
159 cube = Some(Cube::one_d(b.parse()?));
160 }
161 "LUT_1D_SIZE" => {
162 possible_shaper = true;
163 cube = Some(Cube::one_d(b.parse()?));
164 }
165 "LUT_3D_SIZE" if possible_shaper => {
166 shaper = cube.take();
167 cube = Some(Cube::three_d(b.parse()?));
168 }
169 "LUT_3D_SIZE" => {
170 cube = Some(Cube::three_d(b.parse()?));
171 }
172 "LUT_1D_INPUT_RANGE" | "LUT_3D_INPUT_RANGE" => {
173 let Some(c) = cube.as_mut() else {
174 return Err(format!("Unexpected input range: {line}").into());
175 };
176 c.set_input_range(Some(cube::parse_input_range(b, delimiter)?));
177 }
178 "DOMAIN_MIN" | "DOMAIN_MAX" => {
179 let Some((r, gb)) = b.split_once(delimiter) else {
180 return Err(format!("invalid rgb in line: {line}").into());
181 };
182 let Some((g, b)) = gb.split_once(delimiter) else {
183 return Err(format!("invalid rgb in line: {line}").into());
184 };
185 rgb[0] = r.parse()?;
186 rgb[1] = g.parse()?;
187 rgb[2] = b.parse()?;
188 if a == "DOMAIN_MIN" {
189 domain_min = Some(rgb);
190 } else {
191 domain_max = Some(rgb);
192 }
193 }
194 r => {
195 let Some((g, b)) = b.split_once(delimiter) else {
196 return Err(format!("invalid rgb in line: {line}").into());
197 };
198
199 rgb[0] = r.parse()?;
200 rgb[1] = g.parse()?;
201 rgb[2] = b.parse()?;
202
203 let mut coll = if let Some(shaper) = shaper.take() {
204 Collector::with_cube(shaper)
205 } else if let Some(cube) = cube.take() {
206 Collector::with_cube(cube)
207 } else {
208 return Err(io::Error::from(io::ErrorKind::InvalidData).into());
209 };
210
211 coll.push(&rgb)?;
212 collector = Some(coll);
213 }
214 }
215 }
216 }
217}