colorbox/formats/
cube_iridas.rs1use std::io::{BufRead, Write};
7
8use super::filter_non_finite;
9use crate::lut::{Lut1D, Lut3D};
10
11pub fn write_1d<W: Write>(
13 mut writer: W,
14 ranges: [(f32, f32); 3],
15 tables: [&[f32]; 3],
16) -> std::io::Result<()> {
17 assert!(tables[0].len() == tables[1].len() && tables[1].len() == tables[2].len());
18
19 writer.write_all(b"TITLE \"untitled\"\n")?;
20 writer.write_all(
21 format!(
22 "DOMAIN_MIN {} {} {}\n",
23 filter_non_finite(ranges[0].0),
24 filter_non_finite(ranges[1].0),
25 filter_non_finite(ranges[2].0),
26 )
27 .as_bytes(),
28 )?;
29 writer.write_all(
30 format!(
31 "DOMAIN_MAX {} {} {}\n",
32 filter_non_finite(ranges[0].1),
33 filter_non_finite(ranges[1].1),
34 filter_non_finite(ranges[2].1),
35 )
36 .as_bytes(),
37 )?;
38 writer.write_all(format!("LUT_1D_SIZE {}\n", tables[0].len()).as_bytes())?;
39
40 for ((r, g), b) in tables[0]
41 .iter()
42 .copied()
43 .zip(tables[1].iter().copied())
44 .zip(tables[2].iter().copied())
45 {
46 writer.write_all(
47 format!(
48 "{} {} {}\n",
49 filter_non_finite(r),
50 filter_non_finite(g),
51 filter_non_finite(b),
52 )
53 .as_bytes(),
54 )?;
55 }
56
57 Ok(())
58}
59
60pub fn write_3d<W: Write>(
65 mut writer: W,
66 ranges: [(f32, f32); 3],
67 resolution: usize,
68 tables: [&[f32]; 3],
69) -> std::io::Result<()> {
70 assert!(tables[0].len() == (resolution * resolution * resolution));
71 assert!(tables[0].len() == tables[1].len() && tables[1].len() == tables[2].len());
72
73 writer.write_all(b"TITLE \"untitled\"\n")?;
74 writer.write_all(
75 format!(
76 "DOMAIN_MIN {} {} {}\n",
77 filter_non_finite(ranges[0].0),
78 filter_non_finite(ranges[1].0),
79 filter_non_finite(ranges[2].0),
80 )
81 .as_bytes(),
82 )?;
83 writer.write_all(
84 format!(
85 "DOMAIN_MAX {} {} {}\n",
86 filter_non_finite(ranges[0].1),
87 filter_non_finite(ranges[1].1),
88 filter_non_finite(ranges[2].1),
89 )
90 .as_bytes(),
91 )?;
92 writer.write_all(format!("LUT_3D_SIZE {}\n", resolution).as_bytes())?;
93
94 for ((r, g), b) in tables[0]
95 .iter()
96 .copied()
97 .zip(tables[1].iter().copied())
98 .zip(tables[2].iter().copied())
99 {
100 writer.write_all(
101 format!(
102 "{} {} {}\n",
103 filter_non_finite(r),
104 filter_non_finite(g),
105 filter_non_finite(b),
106 )
107 .as_bytes(),
108 )?;
109 }
110
111 Ok(())
112}
113
114pub fn read_1d<R: BufRead>(reader: R) -> Result<Lut1D, super::ReadError> {
116 let mut ranges = [(0.0f32, 1.0f32); 3];
118 let mut length = None;
119 let mut tables = [Vec::new(), Vec::new(), Vec::new()];
120
121 for line in reader.lines() {
122 let line = line?;
123 let parts: Vec<_> = line.split_whitespace().collect();
124
125 if parts.is_empty() || parts[0].starts_with("#") {
126 continue;
127 } else if parts[0] == "TITLE" && parts.len() > 1 {
128 let name_parts: Vec<_> = line.trim().split("\"").collect();
129 if name_parts.len() != 3 || !name_parts[2].is_empty() {
130 return Err(super::ReadError::FormatErr);
131 }
132 continue;
134 } else if parts[0] == "DOMAIN_MIN" && parts.len() == 4 {
135 ranges[0].0 = parts[1].parse::<f32>()?;
136 ranges[1].0 = parts[2].parse::<f32>()?;
137 ranges[2].0 = parts[3].parse::<f32>()?;
138 continue;
139 } else if parts[0] == "DOMAIN_MAX" && parts.len() == 4 {
140 ranges[0].1 = parts[1].parse::<f32>()?;
141 ranges[1].1 = parts[2].parse::<f32>()?;
142 ranges[2].1 = parts[3].parse::<f32>()?;
143 continue;
144 } else if parts[0] == "LUT_1D_SIZE" && parts.len() == 2 {
145 length = Some(parts[1].parse::<usize>()?);
146 continue;
147 } else if parts.len() == 3 {
148 tables[0].push(parts[0].parse::<f32>()?);
149 tables[1].push(parts[1].parse::<f32>()?);
150 tables[2].push(parts[2].parse::<f32>()?);
151 continue;
152 } else {
153 return Err(super::ReadError::FormatErr);
155 }
156 }
157
158 if !tables.iter().flatten().all(|n| n.is_finite())
159 || !ranges.iter().all(|(a, b)| a.is_finite() && b.is_finite())
160 {
161 return Err(super::ReadError::FormatErr);
163 }
164
165 let [table_r, table_g, table_b] = tables;
166 match length {
167 Some(len) if len == table_r.len() => Ok(Lut1D {
168 ranges: vec![ranges[0], ranges[1], ranges[2]],
169 tables: vec![table_r, table_g, table_b],
170 }),
171 _ => Err(super::ReadError::FormatErr),
172 }
173}
174
175pub fn read_3d<R: BufRead>(reader: R) -> Result<Lut3D, super::ReadError> {
177 let mut ranges = [(0.0f32, 1.0f32); 3];
179 let mut resolution = None;
180 let mut tables = [Vec::new(), Vec::new(), Vec::new()];
181
182 for line in reader.lines() {
183 let line = line?;
184 let parts: Vec<_> = line.split_whitespace().collect();
185
186 if parts.is_empty() || parts[0].starts_with("#") {
187 continue;
188 } else if parts[0] == "TITLE" && parts.len() > 1 {
189 let name_parts: Vec<_> = line.trim().split("\"").collect();
190 if name_parts.len() != 3 || !name_parts[2].is_empty() {
191 return Err(super::ReadError::FormatErr);
192 }
193 continue;
195 } else if parts[0] == "DOMAIN_MIN" && parts.len() == 4 {
196 ranges[0].0 = parts[1].parse::<f32>()?;
197 ranges[1].0 = parts[2].parse::<f32>()?;
198 ranges[2].0 = parts[3].parse::<f32>()?;
199 continue;
200 } else if parts[0] == "DOMAIN_MAX" && parts.len() == 4 {
201 ranges[0].1 = parts[1].parse::<f32>()?;
202 ranges[1].1 = parts[2].parse::<f32>()?;
203 ranges[2].1 = parts[3].parse::<f32>()?;
204 continue;
205 } else if parts[0] == "LUT_3D_SIZE" && parts.len() == 2 {
206 resolution = Some(parts[1].parse::<usize>()?);
207 continue;
208 } else if parts.len() == 3 {
209 tables[0].push(parts[0].parse::<f32>()?);
210 tables[1].push(parts[1].parse::<f32>()?);
211 tables[2].push(parts[2].parse::<f32>()?);
212 continue;
213 } else {
214 return Err(super::ReadError::FormatErr);
216 }
217 }
218
219 if !tables.iter().flatten().all(|n| n.is_finite())
220 || !ranges.iter().all(|(a, b)| a.is_finite() && b.is_finite())
221 {
222 return Err(super::ReadError::FormatErr);
224 }
225
226 let [table_r, table_g, table_b] = tables;
227 match resolution {
228 Some(res) if (res * res * res) == table_r.len() => Ok(Lut3D {
229 range: ranges,
230 resolution: [res, res, res],
231 tables: vec![table_r, table_g, table_b],
232 }),
233 _ => Err(super::ReadError::FormatErr),
234 }
235}