image/codecs/
dxt.rs

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