1mod bits;
22mod entropy;
23mod layout;
24mod plane;
25mod wavelet;
26
27use flashraw_core::{Error, Result, par};
28use layout::Image;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct CrxHeader {
33 pub version: u16,
36 pub width: u32,
38 pub height: u32,
39 pub tile_width: u32,
40 pub tile_height: u32,
41 pub bits: u8,
42 pub planes: u8,
44 pub cfa_layout: u8,
46 pub enc_type: u8,
48 pub levels: u8,
50 pub mdat_header_size: u32,
52 pub median_bits: u8,
54}
55
56impl CrxHeader {
57 pub fn parse(d: &[u8]) -> Result<CrxHeader> {
59 if d.len() < 32 {
60 return Err(Error::corrupt("CRX: CMP1 header too short"));
61 }
62 let be16 = |o: usize| u16::from_be_bytes([d[o], d[o + 1]]);
63 let be32 = |o: usize| u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]);
64 let bits = d[24];
65 let mut h = CrxHeader {
66 version: be16(4),
67 width: be32(8),
68 height: be32(12),
69 tile_width: be32(16),
70 tile_height: be32(20),
71 bits,
72 planes: d[25] >> 4,
73 cfa_layout: d[25] & 0xF,
74 enc_type: d[26] >> 4,
75 levels: d[26] & 0xF,
76 mdat_header_size: be32(28),
77 median_bits: bits,
78 };
79 let has_tile_cols = d[27] >> 7;
80 let has_tile_rows = (d[27] >> 6) & 1;
81 let ext_header = d.get(32).is_some_and(|b| b >> 7 != 0);
82 if ext_header && d.len() >= 56 && h.planes == 4 && d.get(56).is_some_and(|b| (b >> 6) & 1 != 0) {
83 if let Some(&mb) = d.get(84) {
84 h.median_bits = mb;
85 }
86 }
87 let bad = |what: &str| Err(Error::unsupported_compression("CR3", format!("CRX {what}")));
88 if (h.version != 0x100 && h.version != 0x200) || h.mdat_header_size == 0 {
89 return bad("header version");
90 }
91 match h.enc_type {
92 1 if h.bits > 15 => return bad("bit depth"),
93 0 | 3 if h.bits > 14 => return bad("bit depth"),
94 0 | 1 | 3 => {}
95 _ => return bad("encoding type"),
96 }
97 if h.planes == 1 {
98 if h.cfa_layout != 0 || h.enc_type != 0 || h.bits != 8 {
99 return bad("single-plane layout");
100 }
101 } else if h.planes != 4
102 || h.width & 1 != 0
103 || h.height & 1 != 0
104 || h.tile_width & 1 != 0
105 || h.tile_height & 1 != 0
106 || h.cfa_layout > 3
107 || h.bits == 8
108 {
109 return bad("plane layout");
110 }
111 if h.tile_width > h.width || h.tile_height > h.height {
112 return bad("tile size");
113 }
114 if h.levels > 3 || has_tile_cols > 1 || has_tile_rows > 1 {
115 return bad("wavelet levels");
116 }
117 if h.bits == 0 || (h.enc_type == 3 && !(1..=16).contains(&h.median_bits)) {
118 return bad("bit depth");
119 }
120 Ok(h)
121 }
122
123 pub fn plane_position(&self, p: usize) -> (usize, usize) {
125 const POS: [[(usize, usize); 4]; 4] = [
127 [(0, 0), (1, 0), (0, 1), (1, 1)],
128 [(1, 0), (0, 0), (1, 1), (0, 1)],
129 [(0, 1), (1, 1), (0, 0), (1, 0)],
130 [(1, 1), (0, 1), (1, 0), (0, 0)],
131 ];
132 POS[self.cfa_layout as usize & 3][p & 3]
133 }
134
135 pub fn white_level(&self) -> u32 {
137 (1u32 << self.bits.min(16)) - 1
138 }
139}
140
141pub fn decode(h: &CrxHeader, data: &[u8], out: &mut [u16], parallel: bool) -> Result<()> {
146 let (w, ht) = (h.width as usize, h.height as usize);
147 if out.len() < w * ht {
148 return Err(Error::InvalidArgument("CRX: output buffer too small".into()));
149 }
150 let img = Image::parse(h, data)?;
151 let planes = h.planes as usize;
152 let levels = h.levels as usize;
153 let units = img.tiles.len() * planes;
154 let results: Vec<Result<Vec<u16>>> = par::map((0..units).collect(), parallel, |u| {
157 let tile = &img.tiles[u / planes];
158 let mut buf = vec![0u16; tile.width * tile.height];
159 let conv = Converter::new(h);
160 plane::decode_plane(data, tile, u % planes, levels, |y, row| {
161 conv.convert(row, &mut buf[y * tile.width..(y + 1) * tile.width]);
162 })?;
163 Ok(buf)
164 });
165 let mut bufs = Vec::with_capacity(units);
166 for r in results {
167 bufs.push(r?);
168 }
169 interleave(h, &img, &bufs, out, parallel);
170 Ok(())
171}
172
173struct Converter {
175 enc_type: u8,
176 planes: u8,
177 bits: u32,
178}
179
180impl Converter {
181 fn new(h: &CrxHeader) -> Self {
182 Converter { enc_type: h.enc_type, planes: h.planes, bits: h.bits as u32 }
183 }
184
185 #[inline]
186 fn convert(&self, row: &[i32], out: &mut [u16]) {
187 let median = 1i32 << (self.bits - 1);
188 if self.enc_type == 1 {
189 let (lo, hi) = (-median, median - 1);
191 for (o, &v) in out.iter_mut().zip(row) {
192 *o = v.clamp(lo, hi) as i16 as u16;
193 }
194 } else if self.enc_type == 3 && self.planes == 4 {
195 for (o, &v) in out.iter_mut().zip(row) {
198 *o = v as i16 as u16;
199 }
200 } else {
201 let max = (1i32 << self.bits) - 1;
202 for (o, &v) in out.iter_mut().zip(row) {
203 *o = median.wrapping_add(v).clamp(0, max) as u16;
204 }
205 }
206 }
207}
208
209fn interleave(h: &CrxHeader, img: &Image, bufs: &[Vec<u16>], out: &mut [u16], parallel: bool) {
211 let w = h.width as usize;
212 let planes = h.planes as usize;
213 if planes == 1 {
214 par::for_each_row_chunk(&mut out[..w * img.plane_height], w, 16, parallel, |y0, chunk| {
215 for (r, row) in chunk.chunks_exact_mut(w).enumerate() {
216 let y = y0 + r;
217 for (ti, t) in img.tiles.iter().enumerate() {
218 if y < t.y || y >= t.y + t.height {
219 continue;
220 }
221 let src = &bufs[ti][(y - t.y) * t.width..(y - t.y + 1) * t.width];
222 row[t.x..t.x + t.width].copy_from_slice(src);
223 }
224 }
225 });
226 return;
227 }
228 let pos: [(usize, usize); 4] = std::array::from_fn(|p| h.plane_position(p));
229 let e3 = h.enc_type == 3;
230 let mb = h.median_bits as u32;
231 par::for_each_row_chunk(&mut out[..w * 2 * img.plane_height], 2 * w, 8, parallel, |py0, chunk| {
233 for (r, pair) in chunk.chunks_exact_mut(2 * w).enumerate() {
234 let py = py0 + r;
235 for (ti, t) in img.tiles.iter().enumerate() {
236 if py < t.y || py >= t.y + t.height {
237 continue;
238 }
239 let line = |p: usize| {
240 let b = &bufs[ti * 4 + p];
241 &b[(py - t.y) * t.width..(py - t.y + 1) * t.width]
242 };
243 let src = [line(0), line(1), line(2), line(3)];
244 if e3 {
245 for i in 0..t.width {
246 let v = combine_e3([src[0][i], src[1][i], src[2][i], src[3][i]], mb);
247 for (p, &(dx, dy)) in pos.iter().enumerate() {
248 pair[dy * w + 2 * (t.x + i) + dx] = v[p];
249 }
250 }
251 } else {
252 for (p, &(dx, dy)) in pos.iter().enumerate() {
253 let dst = &mut pair[dy * w + 2 * t.x..dy * w + 2 * (t.x + t.width)];
254 for (d, &s) in dst.iter_mut().skip(dx).step_by(2).zip(src[p]) {
255 *d = s;
256 }
257 }
258 }
259 }
260 }
261 });
262}
263
264#[inline]
267fn combine_e3(p: [u16; 4], median_bits: u32) -> [u16; 4] {
268 let [p0, p1, p2, p3] = p.map(|v| v as i16 as i32);
269 let median = (1i32 << (median_bits - 1)) << 10;
270 let max = (1i32 << median_bits) - 1;
271 let base = median + (p0 << 10);
272 let g = base - 168 * p1 - 585 * p3;
273 let g = if g < 0 { -(((-g + 512) >> 9) & !1) } else { ((g + 512) >> 9) & !1 };
274 let r = (base + 1510 * p3 + 512) >> 10;
275 let g1 = (p2 + g + 1) >> 1;
276 let g2 = (g - p2 + 1) >> 1;
277 let b = (base + 1927 * p1 + 512) >> 10;
278 [r, g1, g2, b].map(|v| v.clamp(0, max) as u16)
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[allow(clippy::too_many_arguments)]
286 fn cmp1(version: u16, w: u32, h: u32, tw: u32, th: u32, bits: u8, planes_cfa: u8, enc_lev: u8) -> Vec<u8> {
287 let mut d = vec![0u8; 52];
288 d[4..6].copy_from_slice(&version.to_be_bytes());
289 d[8..12].copy_from_slice(&w.to_be_bytes());
290 d[12..16].copy_from_slice(&h.to_be_bytes());
291 d[16..20].copy_from_slice(&tw.to_be_bytes());
292 d[20..24].copy_from_slice(&th.to_be_bytes());
293 d[24] = bits;
294 d[25] = planes_cfa;
295 d[26] = enc_lev;
296 d[28..32].copy_from_slice(&112u32.to_be_bytes());
297 d
298 }
299
300 #[test]
301 fn header_parse_and_validation() {
302 let h = CrxHeader::parse(&cmp1(0x100, 6888, 4546, 3444, 4546, 14, 0x40, 0x03)).unwrap();
303 assert_eq!((h.width, h.height, h.planes, h.levels, h.bits, h.median_bits), (6888, 4546, 4, 3, 14, 14));
304 assert_eq!(h.white_level(), 16383);
305 assert!(CrxHeader::parse(&cmp1(0x300, 64, 64, 64, 64, 14, 0x40, 0)).is_err());
306 assert!(CrxHeader::parse(&cmp1(0x100, 65, 64, 64, 64, 14, 0x40, 0)).is_err());
307 assert!(CrxHeader::parse(&cmp1(0x100, 64, 64, 128, 64, 14, 0x40, 0)).is_err());
308 assert!(CrxHeader::parse(&cmp1(0x100, 64, 64, 64, 64, 15, 0x40, 0)).is_err());
309 assert!(CrxHeader::parse(&cmp1(0x100, 64, 64, 64, 64, 14, 0x40, 0x04)).is_err());
310 assert!(CrxHeader::parse(&cmp1(0x100, 64, 64, 64, 64, 14, 0x44, 0)).is_err());
311 assert!(CrxHeader::parse(&cmp1(0x100, 64, 64, 64, 64, 8, 0x10, 0)).is_ok());
312 assert!(CrxHeader::parse(&cmp1(0x100, 64, 64, 64, 64, 8, 0x40, 0)).is_err());
313 assert!(CrxHeader::parse(&[0u8; 20]).is_err());
314 }
315
316 #[test]
317 fn plane_positions_match_cfa_layouts() {
318 let mut h = CrxHeader::parse(&cmp1(0x100, 64, 64, 64, 64, 14, 0x40, 0)).unwrap();
320 assert_eq!(h.plane_position(0), (0, 0));
321 assert_eq!(h.plane_position(3), (1, 1));
322 h.cfa_layout = 1; assert_eq!(h.plane_position(0), (1, 0));
324 assert_eq!(h.plane_position(3), (0, 1));
325 h.cfa_layout = 3;
326 assert_eq!(h.plane_position(0), (1, 1));
327 }
328
329 #[test]
330 fn e3_neutral() {
331 assert_eq!(combine_e3([0, 0, 0, 0], 14), [8192, 8192, 8192, 8192]);
333 assert_eq!(combine_e3([0x7fff, 0, 0, 0], 14)[0], 16383);
335 }
336
337 #[test]
338 fn decode_rejects_garbage_without_panicking() {
339 let h = CrxHeader::parse(&cmp1(0x100, 128, 64, 128, 64, 14, 0x40, 0)).unwrap();
340 let mut out = vec![0u16; 128 * 64];
341 let mut seed = 7u32;
342 for len in [0usize, 10, 112, 200, 4096] {
343 let data: Vec<u8> = (0..len)
344 .map(|_| {
345 seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
346 (seed >> 24) as u8
347 })
348 .collect();
349 assert!(decode(&h, &data, &mut out, false).is_err());
350 }
351 }
352}