crnlib/
unpack.rs

1use std::io::prelude::*;
2use anyhow::{Error, Context, bail};
3use serde::{Serialize, Deserialize};
4use crate::{Tables, Huffman, codec::Codec};
5
6pub trait Block: Serialize {
7  const BLOCK_SIZE: usize;
8  fn write_to<W: Write>(&self, mut w: W) -> std::io::Result<()> {
9    use bincode::Options;
10    let bin = bincode::config::DefaultOptions::new()
11      .with_fixint_encoding()
12      .with_little_endian()
13      .serialize(self)
14      .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
15    w.write(&bin)?;
16    Ok(())
17  }
18}
19
20pub trait Unpack {
21  fn unpack(tables: &Tables, codec: &mut Codec, width: u16, height: u16, face: u8) -> Result<Vec<u8>, Error>;
22  fn next_tile_idx(codec: &mut Codec, encoding: &Huffman, tile_bits: &mut u32) -> Result<(usize, [usize; 4]), Error> {
23    if *tile_bits == 1 {
24      *tile_bits = encoding.next(codec).context("read chunk encoding bits")? | 512;
25    }
26
27    let tile_index = *tile_bits as usize & 7;
28    *tile_bits >>= 3;
29    Ok((Self::COUNT_TILES[tile_index], Self::TILES[tile_index]))
30  }
31
32  const TRUNK_SIZE: usize = 2;
33
34  const COUNT_TILES: [usize; 8] = [ 1, 2, 2, 3, 3, 3, 3, 4 ];
35  const TILES: [[usize; 4]; 8] = [
36    [ 0, 0, 0, 0 ],
37    [ 0, 0, 1, 1 ], [ 0, 1, 0, 1 ],
38    [ 0, 0, 1, 2 ], [ 1, 2, 0, 0 ],
39    [ 0, 1, 0, 2 ], [ 1, 0, 2, 0 ],
40    [ 0, 1, 2, 3 ]
41  ];
42}
43
44
45
46#[derive(Debug, Default, Serialize, Deserialize)]
47pub struct Dxt1 {
48  pub color_endpoint: (u16, u16),
49  pub color_selector: [u8; 4],
50}
51
52impl Block for Dxt1 {
53  const BLOCK_SIZE: usize = 8;
54}
55impl Unpack for Dxt1 {
56  fn unpack(tables: &Tables, codec: &mut Codec, width: u16, height: u16, face: u8) -> Result<Vec<u8>, Error> {
57    let block_x = (width + 3) / 4;
58    let block_y = (height + 3) / 4;
59    let chunk_x = (block_x + 1) as usize / Self::TRUNK_SIZE;
60    let chunk_y = (block_y + 1) as usize / Self::TRUNK_SIZE;
61
62    let mut tile_bits = 1u32;
63
64    let mut color_endpoint_index = 0;
65    let mut color_selector_index = 0;
66
67    let pitch = block_x as usize * Self::BLOCK_SIZE;
68
69    let mut result = vec![0u8; block_y as usize * pitch];
70    let mut cursor = std::io::Cursor::new(&mut result[..]);
71
72    for _f in 0..face {
73      // let mut row = Vec::new();
74      for y in 0..chunk_y {
75        let skip_y = y == (chunk_y - 1) && block_y & 1 == 1;
76        let xrange: Box<dyn Iterator<Item=_>> = if y & 1 == 1 { Box::new((0..chunk_x).rev()) } else { Box::new(0..chunk_x) };
77        for x in xrange {
78          let skip_x = block_x & 1 == 1 && x == (chunk_x - 1);
79          let mut color_endpoints = [(0, 0); 4];
80
81          let (tiles_count, tiles) = Self::next_tile_idx(codec, &tables.chunk_encoding, &mut tile_bits)?;
82
83          for i in 0..tiles_count {
84            color_endpoints[i] = tables.color_endpoint()?.next(codec, &mut color_endpoint_index).context("read color_endpoint_delta")?;
85          }
86          for (i, &tile) in tiles.iter().enumerate() {
87            let color_selector = tables.color_selector()?.next(codec, &mut color_selector_index).context("read color_selector_delta")?;
88
89            // println!("{:x?}", (delta0, delta1, alpha_selector_index, color_selector_index, tables.color_selectors[color_selector_index]));
90            if !skip_x && !skip_y {
91              if i % Self::TRUNK_SIZE == 0 {
92                let pos = (y * Self::TRUNK_SIZE + i / Self::TRUNK_SIZE) * pitch + x * Self::BLOCK_SIZE * Self::TRUNK_SIZE;
93                // println!("seek {}x{} + {} => {:x}", x, y, i, pos);
94                cursor.seek(std::io::SeekFrom::Start(pos as _)).expect("seek");
95              }
96              Dxt1 {
97                color_endpoint: color_endpoints[tile],
98                color_selector,
99              }.write_to(&mut cursor).context("write block")?;
100            }
101          }
102        }
103      }
104    }
105    if !codec.is_complete() { bail!("extra bytes in codec") }
106    Ok(result)
107  }
108}
109
110
111#[derive(Debug, Default, Serialize, Deserialize)]
112pub struct Dxt5 {
113  pub alpha_endpoint: (u8, u8),
114  pub alpha_selector: [u8; 6],
115  pub color_endpoint: (u16, u16),
116  pub color_selector: [u8; 4],
117}
118
119impl Block for Dxt5 {
120  const BLOCK_SIZE: usize = 16;
121}
122impl Unpack for Dxt5 {
123  fn unpack(tables: &Tables, codec: &mut Codec, width: u16, height: u16, face: u8) -> Result<Vec<u8>, Error> {
124    let block_x = (width + 3) / 4;
125    let block_y = (height + 3) / 4;
126    let chunk_x = (block_x + 1) as usize / Self::TRUNK_SIZE;
127    let chunk_y = (block_y + 1) as usize / Self::TRUNK_SIZE;
128
129    let mut tile_bits = 1u32;
130
131    let mut color_endpoint_index = 0;
132    let mut color_selector_index = 0;
133    let mut alpha_endpoint_index = 0;
134    let mut alpha_selector_index = 0;
135
136    let pitch = block_x as usize * Self::BLOCK_SIZE;
137
138    let mut result = vec![0u8; block_y as usize * pitch];
139    let mut cursor = std::io::Cursor::new(&mut result[..]);
140
141    for _f in 0..face {
142      // let mut row = Vec::new();
143      for y in 0..chunk_y {
144        let skip_y = y == (chunk_y - 1) && block_y & 1 == 1;
145        let xrange: Box<dyn Iterator<Item=_>> = if y & 1 == 1 { Box::new((0..chunk_x).rev()) } else { Box::new(0..chunk_x) };
146        for x in xrange {
147          let skip_x = block_x & 1 == 1 && x == (chunk_x - 1);
148          let mut color_endpoints = [(0, 0); 4];
149          let mut alpha_endpoints = [(0, 0); 4];
150
151          let (tiles_count, tiles) = Self::next_tile_idx(codec, &tables.chunk_encoding, &mut tile_bits)?;
152
153          for i in 0..tiles_count {
154            alpha_endpoints[i] = tables.alpha_endpoint()?.next(codec, &mut alpha_endpoint_index).context("read alpha_endpoint_delta")?;
155          }
156
157          for i in 0..tiles_count {
158            color_endpoints[i] = tables.color_endpoint()?.next(codec, &mut color_endpoint_index).context("read color_endpoint_delta")?;
159          }
160
161          // println!("tile: {:x?}", tiles);
162          for (i, &tile) in tiles.iter().enumerate() {
163            let alpha_selector = tables.alpha_selector()?.next(codec, &mut alpha_selector_index).context("read alpha_selector_delta")?;
164            let color_selector = tables.color_selector()?.next(codec, &mut color_selector_index).context("read color_selector_delta")?;
165
166            // println!("{:x?}", (delta0, delta1, alpha_selector_index, color_selector_index, tables.color_selectors[color_selector_index]));
167            if !skip_x && !skip_y {
168              if i % Self::TRUNK_SIZE == 0 {
169                let pos = (y * Self::TRUNK_SIZE + i / Self::TRUNK_SIZE) * pitch + x * Self::BLOCK_SIZE * Self::TRUNK_SIZE;
170                // println!("seek {}x{} + {} => {:x}", x, y, i, pos);
171                cursor.seek(std::io::SeekFrom::Start(pos as _)).expect("seek");
172              }
173              Dxt5 {
174                alpha_endpoint: alpha_endpoints[tile],
175                alpha_selector,
176                color_endpoint: color_endpoints[tile],
177                color_selector,
178              }.write_to(&mut cursor).context("write block")?;
179            }
180          }
181        }
182      }
183    }
184    if !codec.is_complete() { bail!("extra bytes in codec") }
185    Ok(result)
186  }
187}
188
189
190#[derive(Debug, Default, Serialize, Deserialize)]
191pub struct Dxt5A {
192  pub alpha_endpoint: (u8, u8),
193  pub alpha_selector: [u8; 6],
194}
195
196impl Block for Dxt5A {
197  const BLOCK_SIZE: usize = 8;
198}
199impl Unpack for Dxt5A {
200  fn unpack(tables: &Tables, codec: &mut Codec, width: u16, height: u16, face: u8) -> Result<Vec<u8>, Error> {
201    let block_x = (width + 3) / 4;
202    let block_y = (height + 3) / 4;
203    let chunk_x = (block_x + 1) as usize / Self::TRUNK_SIZE;
204    let chunk_y = (block_y + 1) as usize / Self::TRUNK_SIZE;
205
206    let mut tile_bits = 1u32;
207
208    let mut alpha_endpoint_index = 0;
209    let mut alpha_selector_index = 0;
210
211    let pitch = block_x as usize * Self::BLOCK_SIZE;
212
213    let mut result = vec![0u8; block_y as usize * pitch];
214    let mut cursor = std::io::Cursor::new(&mut result[..]);
215
216    for _f in 0..face {
217      // let mut row = Vec::new();
218      for y in 0..chunk_y {
219        let skip_y = y == (chunk_y - 1) && block_y & 1 == 1;
220        let xrange: Box<dyn Iterator<Item=_>> = if y & 1 == 1 { Box::new((0..chunk_x).rev()) } else { Box::new(0..chunk_x) };
221        for x in xrange {
222          let skip_x = block_x & 1 == 1 && x == (chunk_x - 1);
223          let mut alpha_endpoints = [(0, 0); 4];
224
225          let (tiles_count, tiles) = Self::next_tile_idx(codec, &tables.chunk_encoding, &mut tile_bits)?;
226
227          for i in 0..tiles_count {
228            alpha_endpoints[i] = tables.alpha_endpoint()?.next(codec, &mut alpha_endpoint_index).context("read alpha_endpoint_delta")?;
229          }
230          for (i, &tile) in tiles.iter().enumerate() {
231            let alpha_selector = tables.alpha_selector()?.next(codec, &mut alpha_selector_index).context("read alpha_selector_delta")?;
232
233            // println!("{:x?}", (delta0, delta1, alpha_selector_index, color_selector_index, tables.color_selectors[color_selector_index]));
234            if !skip_x && !skip_y {
235              if i % Self::TRUNK_SIZE == 0 {
236                let pos = (y * Self::TRUNK_SIZE + i / Self::TRUNK_SIZE) * pitch + x * Self::BLOCK_SIZE * Self::TRUNK_SIZE;
237                // println!("seek {}x{} + {} => {:x}", x, y, i, pos);
238                cursor.seek(std::io::SeekFrom::Start(pos as _)).expect("seek");
239              }
240              Dxt5A {
241                alpha_endpoint: alpha_endpoints[tile],
242                alpha_selector,
243              }.write_to(&mut cursor).context("write block")?;
244            }
245          }
246        }
247      }
248    }
249    if !codec.is_complete() { bail!("extra bytes in codec") }
250    Ok(result)
251  }
252}
253
254
255#[derive(Debug, Default, Serialize, Deserialize)]
256pub struct Dxn {
257  pub alpha0_endpoint: (u8, u8),
258  pub alpha0_selector: [u8; 6],
259  pub alpha1_endpoint: (u8, u8),
260  pub alpha1_selector: [u8; 6],
261}
262
263impl Block for Dxn {
264  const BLOCK_SIZE: usize = 16;
265}
266impl Unpack for Dxn {
267  fn unpack(tables: &Tables, codec: &mut Codec, width: u16, height: u16, face: u8) -> Result<Vec<u8>, Error> {
268    let block_x = (width + 3) / 4;
269    let block_y = (height + 3) / 4;
270    let chunk_x = (block_x + 1) as usize / Self::TRUNK_SIZE;
271    let chunk_y = (block_y + 1) as usize / Self::TRUNK_SIZE;
272
273    let mut tile_bits = 1u32;
274
275    let mut alpha0_endpoint_index = 0;
276    let mut alpha0_selector_index = 0;
277    let mut alpha1_endpoint_index = 0;
278    let mut alpha1_selector_index = 0;
279
280    let pitch = block_x as usize * Self::BLOCK_SIZE;
281
282    let mut result = vec![0u8; block_y as usize * pitch];
283    let mut cursor = std::io::Cursor::new(&mut result[..]);
284
285    for _f in 0..face {
286      // let mut row = Vec::new();
287      for y in 0..chunk_y {
288        let skip_y = y == (chunk_y - 1) && block_y & 1 == 1;
289        let xrange: Box<dyn Iterator<Item=_>> = if y & 1 == 1 { Box::new((0..chunk_x).rev()) } else { Box::new(0..chunk_x) };
290        for x in xrange {
291          let skip_x = block_x & 1 == 1 && x == (chunk_x - 1);
292          let mut alpha0_endpoints = [(0, 0); 4];
293          let mut alpha1_endpoints = [(0, 0); 4];
294
295          let (tiles_count, tiles) = Self::next_tile_idx(codec, &tables.chunk_encoding, &mut tile_bits)?;
296
297          for i in 0..tiles_count {
298            alpha0_endpoints[i] = tables.alpha_endpoint()?.next(codec, &mut alpha0_endpoint_index).context("read alpha0_endpoint_delta")?;
299          }
300          for i in 0..tiles_count {
301            alpha1_endpoints[i] = tables.alpha_endpoint()?.next(codec, &mut alpha1_endpoint_index).context("read alpha1_endpoint_delta")?;
302          }
303          for (i, &tile) in tiles.iter().enumerate() {
304            let alpha0_selector = tables.alpha_selector()?.next(codec, &mut alpha0_selector_index).context("read alpha0_selector_delta")?;
305            let alpha1_selector = tables.alpha_selector()?.next(codec, &mut alpha1_selector_index).context("read alpha1_selector_delta")?;
306
307            // println!("{:x?}", (delta0, delta1, alpha_selector_index, color_selector_index, tables.color_selectors[color_selector_index]));
308            if !skip_x && !skip_y {
309              if i % Self::TRUNK_SIZE == 0 {
310                let pos = (y * Self::TRUNK_SIZE + i / Self::TRUNK_SIZE) * pitch + x * Self::BLOCK_SIZE * Self::TRUNK_SIZE;
311                // println!("seek {}x{} + {} => {:x}", x, y, i, pos);
312                cursor.seek(std::io::SeekFrom::Start(pos as _)).expect("seek");
313              }
314              Dxn {
315                alpha0_endpoint: alpha0_endpoints[tile],
316                alpha0_selector,
317                alpha1_endpoint: alpha1_endpoints[tile],
318                alpha1_selector,
319              }.write_to(&mut cursor).context("write block")?;
320            }
321          }
322        }
323      }
324    }
325    if !codec.is_complete() { bail!("extra bytes in codec") }
326    Ok(result)
327  }
328}
329
330#[test]
331fn test_constant() {
332  assert_eq!(Dxt5::TILES.len(), Dxt5::COUNT_TILES.len());
333  assert_eq!(Dxt5::TILES[0].len(), Dxt5::TRUNK_SIZE * Dxt5::TRUNK_SIZE);
334
335  use bincode::Options;
336  let option = || bincode::config::DefaultOptions::new()
337    .with_fixint_encoding()
338    .with_little_endian();
339  assert_eq!(option().serialized_size(&Dxt1::default()).unwrap(), Dxt1::BLOCK_SIZE as u64);
340  assert_eq!(option().serialized_size(&Dxt5::default()).unwrap(), Dxt5::BLOCK_SIZE as u64);
341  assert_eq!(option().serialized_size(&Dxt5A::default()).unwrap(), Dxt5A::BLOCK_SIZE as u64);
342  assert_eq!(option().serialized_size(&Dxn::default()).unwrap(), Dxn::BLOCK_SIZE as u64);
343
344  assert_eq!(option().serialize(&Dxt5 {
345    alpha_endpoint: (0x17, 0x18),
346    alpha_selector: [0x20, 0x21, 0x22, 0x23, 0x24, 0x25],
347    color_endpoint: (0x3234, 0x3537),
348    color_selector: [0x49, 0x48, 0x47, 0x46],
349  }).unwrap(), &[
350    0x17, 0x18,
351    0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
352    0x34, 0x32, 0x37, 0x35,
353    0x49, 0x48, 0x47, 0x46]);
354}