/src/image/src/codecs/dxt.rs
Line | Count | Source |
1 | | //! Decoding of DXT (S3TC) compression |
2 | | //! |
3 | | //! DXT is an image format that supports lossy compression |
4 | | //! |
5 | | //! # Related Links |
6 | | //! * <https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_compression_s3tc.txt> - Description of the DXT compression OpenGL extensions. |
7 | | //! |
8 | | //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds |
9 | | |
10 | | use std::io::{self, Read}; |
11 | | |
12 | | use crate::color::ColorType; |
13 | | use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; |
14 | | use crate::io::ReadExt; |
15 | | use crate::ImageDecoder; |
16 | | |
17 | | /// What version of DXT compression are we using? |
18 | | /// Note that DXT2 and DXT4 are left away as they're |
19 | | /// just DXT3 and DXT5 with premultiplied alpha |
20 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
21 | | pub(crate) enum DxtVariant { |
22 | | /// The DXT1 format. 48 bytes of RGB data in a 4x4 pixel square is |
23 | | /// compressed into an 8 byte block of DXT1 data |
24 | | DXT1, |
25 | | /// The DXT3 format. 64 bytes of RGBA data in a 4x4 pixel square is |
26 | | /// compressed into a 16 byte block of DXT3 data |
27 | | DXT3, |
28 | | /// The DXT5 format. 64 bytes of RGBA data in a 4x4 pixel square is |
29 | | /// compressed into a 16 byte block of DXT5 data |
30 | | DXT5, |
31 | | } |
32 | | |
33 | | impl DxtVariant { |
34 | | /// Returns the amount of bytes of raw image data |
35 | | /// that is encoded in a single DXTn block |
36 | 2.24k | fn decoded_bytes_per_block(self) -> usize { |
37 | 2.24k | match self { |
38 | 1.36k | DxtVariant::DXT1 => 48, |
39 | 873 | DxtVariant::DXT3 | DxtVariant::DXT5 => 64, |
40 | | } |
41 | 2.24k | } |
42 | | |
43 | | /// Returns the amount of bytes per block of encoded DXTn data |
44 | 2.13k | fn encoded_bytes_per_block(self) -> usize { |
45 | 2.13k | match self { |
46 | 1.32k | DxtVariant::DXT1 => 8, |
47 | 815 | DxtVariant::DXT3 | DxtVariant::DXT5 => 16, |
48 | | } |
49 | 2.13k | } |
50 | | |
51 | | /// Returns the color type that is stored in this DXT variant |
52 | 525 | pub(crate) fn color_type(self) -> ColorType { |
53 | 525 | match self { |
54 | 232 | DxtVariant::DXT1 => ColorType::Rgb8, |
55 | 293 | DxtVariant::DXT3 | DxtVariant::DXT5 => ColorType::Rgba8, |
56 | | } |
57 | 525 | } |
58 | | } |
59 | | |
60 | | /// DXT decoder |
61 | | pub(crate) struct DxtDecoder<R: Read> { |
62 | | inner: R, |
63 | | width_blocks: u32, |
64 | | height_blocks: u32, |
65 | | variant: DxtVariant, |
66 | | row: u32, |
67 | | } |
68 | | |
69 | | impl<R: Read> DxtDecoder<R> { |
70 | | /// Create a new DXT decoder that decodes from the stream ```r```. |
71 | | /// As DXT is often stored as raw buffers with the width/height |
72 | | /// somewhere else the width and height of the image need |
73 | | /// to be passed in ```width``` and ```height```, as well as the |
74 | | /// DXT variant in ```variant```. |
75 | | /// width and height are required to be powers of 2 and at least 4. |
76 | | /// otherwise an error will be returned |
77 | 107 | pub(crate) fn new( |
78 | 107 | r: R, |
79 | 107 | width: u32, |
80 | 107 | height: u32, |
81 | 107 | variant: DxtVariant, |
82 | 107 | ) -> Result<DxtDecoder<R>, ImageError> { |
83 | 107 | if !width.is_multiple_of(4) || !height.is_multiple_of(4) { |
84 | | // TODO: this is actually a bit of a weird case. We could return `DecodingError` but |
85 | | // it's not really the format that is wrong However, the encoder should surely return |
86 | | // `EncodingError` so it would be the logical choice for symmetry. |
87 | 2 | return Err(ImageError::Parameter(ParameterError::from_kind( |
88 | 2 | ParameterErrorKind::DimensionMismatch, |
89 | 2 | ))); |
90 | 105 | } |
91 | 105 | let width_blocks = width / 4; |
92 | 105 | let height_blocks = height / 4; |
93 | 105 | Ok(DxtDecoder { |
94 | 105 | inner: r, |
95 | 105 | width_blocks, |
96 | 105 | height_blocks, |
97 | 105 | variant, |
98 | 105 | row: 0, |
99 | 105 | }) |
100 | 107 | } |
101 | | |
102 | 2.24k | fn scanline_bytes(&self) -> u64 { |
103 | 2.24k | self.variant.decoded_bytes_per_block() as u64 * u64::from(self.width_blocks) |
104 | 2.24k | } |
105 | | |
106 | 2.13k | fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
107 | 2.13k | assert_eq!( |
108 | 2.13k | u64::try_from(buf.len()), |
109 | 2.13k | Ok( |
110 | 2.13k | #[allow(deprecated)] |
111 | 2.13k | self.scanline_bytes() |
112 | 2.13k | ) |
113 | | ); |
114 | | |
115 | 2.13k | let len = self.variant.encoded_bytes_per_block() * self.width_blocks as usize; |
116 | 2.13k | let mut src = Vec::new(); |
117 | 2.13k | self.inner.read_exact_vec(&mut src, len)?; |
118 | | |
119 | 2.03k | match self.variant { |
120 | 1.27k | DxtVariant::DXT1 => decode_dxt1_row(&src, buf), |
121 | 329 | DxtVariant::DXT3 => decode_dxt3_row(&src, buf), |
122 | 431 | DxtVariant::DXT5 => decode_dxt5_row(&src, buf), |
123 | | } |
124 | 2.03k | self.row += 1; |
125 | 2.03k | Ok(buf.len()) |
126 | 2.13k | } |
127 | | } |
128 | | |
129 | | // Note that, due to the way that DXT compression works, a scanline is considered to consist out of |
130 | | // 4 lines of pixels. |
131 | | impl<R: Read> ImageDecoder for DxtDecoder<R> { |
132 | 521 | fn dimensions(&self) -> (u32, u32) { |
133 | 521 | (self.width_blocks * 4, self.height_blocks * 4) |
134 | 521 | } |
135 | | |
136 | 417 | fn color_type(&self) -> ColorType { |
137 | 417 | self.variant.color_type() |
138 | 417 | } |
139 | | |
140 | 104 | fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { |
141 | 104 | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); |
142 | | |
143 | | #[allow(deprecated)] |
144 | 2.13k | for chunk in buf.chunks_mut(self.scanline_bytes().max(1) as usize) { |
145 | 2.13k | self.read_scanline(chunk)?; |
146 | | } |
147 | 4 | Ok(()) |
148 | 104 | } |
149 | | |
150 | 0 | fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> { |
151 | 0 | (*self).read_image(buf) |
152 | 0 | } |
153 | | } |
154 | | |
155 | | /** |
156 | | * Actual encoding/decoding logic below. |
157 | | */ |
158 | | type Rgb = [u8; 3]; |
159 | | |
160 | | /// decodes a 5-bit R, 6-bit G, 5-bit B 16-bit packed color value into 8-bit RGB |
161 | | /// mapping is done so min/max range values are preserved. So for 5-bit |
162 | | /// values 0x00 -> 0x00 and 0x1F -> 0xFF |
163 | 77.7k | fn enc565_decode(value: u16) -> Rgb { |
164 | 77.7k | let red = (value >> 11) & 0x1F; |
165 | 77.7k | let green = (value >> 5) & 0x3F; |
166 | 77.7k | let blue = (value) & 0x1F; |
167 | 77.7k | [ |
168 | 77.7k | (red * 0xFF / 0x1F) as u8, |
169 | 77.7k | (green * 0xFF / 0x3F) as u8, |
170 | 77.7k | (blue * 0xFF / 0x1F) as u8, |
171 | 77.7k | ] |
172 | 77.7k | } |
173 | | |
174 | | /* |
175 | | * Functions for decoding DXT compression |
176 | | */ |
177 | | |
178 | | /// Constructs the DXT5 alpha lookup table from the two alpha entries |
179 | | /// if alpha0 > alpha1, constructs a table of [a0, a1, 6 linearly interpolated values from a0 to a1] |
180 | | /// if alpha0 <= alpha1, constructs a table of [a0, a1, 4 linearly interpolated values from a0 to a1, 0, 0xFF] |
181 | 947 | fn alpha_table_dxt5(alpha0: u8, alpha1: u8) -> [u8; 8] { |
182 | 947 | let mut table = [alpha0, alpha1, 0, 0, 0, 0, 0, 0xFF]; |
183 | 947 | if alpha0 > alpha1 { |
184 | 2.37k | for i in 2..8u16 { |
185 | 2.03k | table[i as usize] = |
186 | 2.03k | (((8 - i) * u16::from(alpha0) + (i - 1) * u16::from(alpha1)) / 7) as u8; |
187 | 2.03k | } |
188 | | } else { |
189 | 3.04k | for i in 2..6u16 { |
190 | 2.43k | table[i as usize] = |
191 | 2.43k | (((6 - i) * u16::from(alpha0) + (i - 1) * u16::from(alpha1)) / 5) as u8; |
192 | 2.43k | } |
193 | | } |
194 | 947 | table |
195 | 947 | } |
196 | | |
197 | | /// decodes an 8-byte dxt color block into the RGB channels of a 16xRGB or 16xRGBA block. |
198 | | /// source should have a length of 8, dest a length of 48 (RGB) or 64 (RGBA) |
199 | | #[allow(clippy::needless_range_loop)] // False positive, the 0..3 loop is not an enumerate |
200 | 38.8k | fn decode_dxt_colors(source: &[u8], dest: &mut [u8], is_dxt1: bool) { |
201 | | // sanity checks, also enable the compiler to elide all following bound checks |
202 | 38.8k | assert!(source.len() == 8 && (dest.len() == 48 || dest.len() == 64)); |
203 | | // calculate pitch to store RGB values in dest (3 for RGB, 4 for RGBA) |
204 | 38.8k | let pitch = dest.len() / 16; |
205 | | |
206 | | // extract color data |
207 | 38.8k | let color0 = u16::from(source[0]) | (u16::from(source[1]) << 8); |
208 | 38.8k | let color1 = u16::from(source[2]) | (u16::from(source[3]) << 8); |
209 | 38.8k | let color_table = u32::from(source[4]) |
210 | 38.8k | | (u32::from(source[5]) << 8) |
211 | 38.8k | | (u32::from(source[6]) << 16) |
212 | 38.8k | | (u32::from(source[7]) << 24); |
213 | | // let color_table = source[4..8].iter().rev().fold(0, |t, &b| (t << 8) | b as u32); |
214 | | |
215 | | // decode the colors to rgb format |
216 | 38.8k | let mut colors = [[0; 3]; 4]; |
217 | 38.8k | colors[0] = enc565_decode(color0); |
218 | 38.8k | colors[1] = enc565_decode(color1); |
219 | | |
220 | | // determine color interpolation method |
221 | 38.8k | if color0 > color1 || !is_dxt1 { |
222 | | // linearly interpolate the other two color table entries |
223 | 131k | for i in 0..3 { |
224 | 98.7k | colors[2][i] = ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3) as u8; |
225 | 98.7k | colors[3][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3) as u8; |
226 | 98.7k | } |
227 | | } else { |
228 | | // linearly interpolate one other entry, keep the other at 0 |
229 | 23.8k | for i in 0..3 { |
230 | 17.8k | colors[2][i] = (u16::from(colors[0][i]) + u16::from(colors[1][i])).div_ceil(2) as u8; |
231 | 17.8k | } |
232 | | } |
233 | | |
234 | | // serialize the result. Every color is determined by looking up |
235 | | // two bits in color_table which identify which color to actually pick from the 4 possible colors |
236 | 661k | for i in 0..16 { |
237 | 622k | dest[i * pitch..i * pitch + 3] |
238 | 622k | .copy_from_slice(&colors[(color_table >> (i * 2)) as usize & 3]); |
239 | 622k | } |
240 | 38.8k | } |
241 | | |
242 | | /// Decodes a 16-byte bock of dxt5 data to a 16xRGBA block |
243 | 947 | fn decode_dxt5_block(source: &[u8], dest: &mut [u8]) { |
244 | 947 | assert!(source.len() == 16 && dest.len() == 64); |
245 | | |
246 | | // extract alpha index table (stored as little endian 64-bit value) |
247 | 947 | let alpha_table = source[2..8] |
248 | 947 | .iter() |
249 | 947 | .rev() |
250 | 5.68k | .fold(0, |t, &b| (t << 8) | u64::from(b)); |
251 | | |
252 | | // alpha level decode |
253 | 947 | let alphas = alpha_table_dxt5(source[0], source[1]); |
254 | | |
255 | | // serialize alpha |
256 | 16.0k | for i in 0..16 { |
257 | 15.1k | dest[i * 4 + 3] = alphas[(alpha_table >> (i * 3)) as usize & 7]; |
258 | 15.1k | } |
259 | | |
260 | | // handle colors |
261 | 947 | decode_dxt_colors(&source[8..16], dest, false); |
262 | 947 | } |
263 | | |
264 | | /// Decodes a 16-byte bock of dxt3 data to a 16xRGBA block |
265 | 27.4k | fn decode_dxt3_block(source: &[u8], dest: &mut [u8]) { |
266 | 27.4k | assert!(source.len() == 16 && dest.len() == 64); |
267 | | |
268 | | // extract alpha index table (stored as little endian 64-bit value) |
269 | 27.4k | let alpha_table = source[0..8] |
270 | 27.4k | .iter() |
271 | 27.4k | .rev() |
272 | 219k | .fold(0, |t, &b| (t << 8) | u64::from(b)); |
273 | | |
274 | | // serialize alpha (stored as 4-bit values) |
275 | 466k | for i in 0..16 { |
276 | 439k | dest[i * 4 + 3] = ((alpha_table >> (i * 4)) as u8 & 0xF) * 0x11; |
277 | 439k | } |
278 | | |
279 | | // handle colors |
280 | 27.4k | decode_dxt_colors(&source[8..16], dest, false); |
281 | 27.4k | } |
282 | | |
283 | | /// Decodes a 8-byte bock of dxt5 data to a 16xRGB block |
284 | 10.4k | fn decode_dxt1_block(source: &[u8], dest: &mut [u8]) { |
285 | 10.4k | assert!(source.len() == 8 && dest.len() == 48); |
286 | 10.4k | decode_dxt_colors(source, dest, true); |
287 | 10.4k | } |
288 | | |
289 | | /// Decode a row of DXT1 data to four rows of RGB data. |
290 | | /// `source.len()` should be a multiple of 8, otherwise this panics. |
291 | 1.27k | fn decode_dxt1_row(source: &[u8], dest: &mut [u8]) { |
292 | 1.27k | assert!(source.len().is_multiple_of(8)); |
293 | 1.27k | let block_count = source.len() / 8; |
294 | 1.27k | assert!(dest.len() >= block_count * 48); |
295 | | |
296 | | // contains the 16 decoded pixels per block |
297 | 1.27k | let mut decoded_block = [0u8; 48]; |
298 | | |
299 | 10.4k | for (x, encoded_block) in source.chunks(8).enumerate() { |
300 | 10.4k | decode_dxt1_block(encoded_block, &mut decoded_block); |
301 | | |
302 | | // copy the values from the decoded block to linewise RGB layout |
303 | 52.4k | for line in 0..4 { |
304 | 41.9k | let offset = (block_count * line + x) * 12; |
305 | 41.9k | dest[offset..offset + 12].copy_from_slice(&decoded_block[line * 12..(line + 1) * 12]); |
306 | 41.9k | } |
307 | | } |
308 | 1.27k | } |
309 | | |
310 | | /// Decode a row of DXT3 data to four rows of RGBA data. |
311 | | /// `source.len()` should be a multiple of 16, otherwise this panics. |
312 | 329 | fn decode_dxt3_row(source: &[u8], dest: &mut [u8]) { |
313 | 329 | assert!(source.len().is_multiple_of(16)); |
314 | 329 | let block_count = source.len() / 16; |
315 | 329 | assert!(dest.len() >= block_count * 64); |
316 | | |
317 | | // contains the 16 decoded pixels per block |
318 | 329 | let mut decoded_block = [0u8; 64]; |
319 | | |
320 | 27.4k | for (x, encoded_block) in source.chunks(16).enumerate() { |
321 | 27.4k | decode_dxt3_block(encoded_block, &mut decoded_block); |
322 | | |
323 | | // copy the values from the decoded block to linewise RGB layout |
324 | 137k | for line in 0..4 { |
325 | 109k | let offset = (block_count * line + x) * 16; |
326 | 109k | dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]); |
327 | 109k | } |
328 | | } |
329 | 329 | } |
330 | | |
331 | | /// Decode a row of DXT5 data to four rows of RGBA data. |
332 | | /// `source.len()` should be a multiple of 16, otherwise this panics. |
333 | 431 | fn decode_dxt5_row(source: &[u8], dest: &mut [u8]) { |
334 | 431 | assert!(source.len().is_multiple_of(16)); |
335 | 431 | let block_count = source.len() / 16; |
336 | 431 | assert!(dest.len() >= block_count * 64); |
337 | | |
338 | | // contains the 16 decoded pixels per block |
339 | 431 | let mut decoded_block = [0u8; 64]; |
340 | | |
341 | 947 | for (x, encoded_block) in source.chunks(16).enumerate() { |
342 | 947 | decode_dxt5_block(encoded_block, &mut decoded_block); |
343 | | |
344 | | // copy the values from the decoded block to linewise RGB layout |
345 | 4.73k | for line in 0..4 { |
346 | 3.78k | let offset = (block_count * line + x) * 16; |
347 | 3.78k | dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]); |
348 | 3.78k | } |
349 | | } |
350 | 431 | } |