lut_cube/
lut.rs

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            // remove '\n' and spaces at the end and start
106            let s = line.trim();
107            if s.is_empty() {
108                continue;
109            }
110
111            // # Comment
112            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}