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 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 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 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 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 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 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 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 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 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 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 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 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 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}