| 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::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)] |
| 20 | pub(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 | |
| 32 | impl 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 |
| 60 | pub(crate) struct DxtDecoder<R: Read> { |
| 61 | inner: R, |
| 62 | width_blocks: u32, |
| 63 | height_blocks: u32, |
| 64 | variant: DxtVariant, |
| 65 | row: u32, |
| 66 | } |
| 67 | |
| 68 | impl<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. |
| 129 | impl<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: &mut [u8] in buf.chunks_mut(self.scanline_bytes().max(1) as usize) { |
| 143 | self.read_scanline(buf: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 | */ |
| 156 | type 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 |
| 161 | fn enc565_decode(value: u16) -> Rgb { |
| 162 | let red: u16 = (value >> 11) & 0x1F; |
| 163 | let green: u16 = (value >> 5) & 0x3F; |
| 164 | let blue: u16 = (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] |
| 179 | fn alpha_table_dxt5(alpha0: u8, alpha1: u8) -> [u8; 8] { |
| 180 | let mut table: [u8; 8] = [alpha0, alpha1, 0, 0, 0, 0, 0, 0xFF]; |
| 181 | if alpha0 > alpha1 { |
| 182 | for i: u16 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: u16 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) |
| 197 | fn 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 |
| 240 | fn 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: u64 = source[2..8] |
| 245 | .iter() |
| 246 | .rev() |
| 247 | .fold(init:0, |t: u64, &b: u8| (t << 8) | u64::from(b)); |
| 248 | |
| 249 | // alpha level decode |
| 250 | let alphas: [u8; 8] = alpha_table_dxt5(alpha0:source[0], alpha1:source[1]); |
| 251 | |
| 252 | // serialize alpha |
| 253 | for i: usize 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, is_dxt1:false); |
| 259 | } |
| 260 | |
| 261 | /// Decodes a 16-byte bock of dxt3 data to a 16xRGBA block |
| 262 | fn 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: u64 = source[0..8] |
| 267 | .iter() |
| 268 | .rev() |
| 269 | .fold(init:0, |t: u64, &b: u8| (t << 8) | u64::from(b)); |
| 270 | |
| 271 | // serialize alpha (stored as 4-bit values) |
| 272 | for i: usize 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, is_dxt1:false); |
| 278 | } |
| 279 | |
| 280 | /// Decodes a 8-byte bock of dxt5 data to a 16xRGB block |
| 281 | fn decode_dxt1_block(source: &[u8], dest: &mut [u8]) { |
| 282 | assert!(source.len() == 8 && dest.len() == 48); |
| 283 | decode_dxt_colors(source, dest, is_dxt1: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. |
| 288 | fn decode_dxt1_row(source: &[u8], dest: &mut [u8]) { |
| 289 | assert!(source.len() % 8 == 0); |
| 290 | let block_count: usize = source.len() / 8; |
| 291 | assert!(dest.len() >= block_count * 48); |
| 292 | |
| 293 | // contains the 16 decoded pixels per block |
| 294 | let mut decoded_block: [u8; 48] = [0u8; 48]; |
| 295 | |
| 296 | for (x: usize, encoded_block: &[u8]) in source.chunks(chunk_size:8).enumerate() { |
| 297 | decode_dxt1_block(source:encoded_block, &mut decoded_block); |
| 298 | |
| 299 | // copy the values from the decoded block to linewise RGB layout |
| 300 | for line: usize in 0..4 { |
| 301 | let offset: usize = (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. |
| 309 | fn decode_dxt3_row(source: &[u8], dest: &mut [u8]) { |
| 310 | assert!(source.len() % 16 == 0); |
| 311 | let block_count: usize = source.len() / 16; |
| 312 | assert!(dest.len() >= block_count * 64); |
| 313 | |
| 314 | // contains the 16 decoded pixels per block |
| 315 | let mut decoded_block: [u8; 64] = [0u8; 64]; |
| 316 | |
| 317 | for (x: usize, encoded_block: &[u8]) in source.chunks(chunk_size:16).enumerate() { |
| 318 | decode_dxt3_block(source:encoded_block, &mut decoded_block); |
| 319 | |
| 320 | // copy the values from the decoded block to linewise RGB layout |
| 321 | for line: usize in 0..4 { |
| 322 | let offset: usize = (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. |
| 330 | fn decode_dxt5_row(source: &[u8], dest: &mut [u8]) { |
| 331 | assert!(source.len() % 16 == 0); |
| 332 | let block_count: usize = source.len() / 16; |
| 333 | assert!(dest.len() >= block_count * 64); |
| 334 | |
| 335 | // contains the 16 decoded pixels per block |
| 336 | let mut decoded_block: [u8; 64] = [0u8; 64]; |
| 337 | |
| 338 | for (x: usize, encoded_block: &[u8]) in source.chunks(chunk_size:16).enumerate() { |
| 339 | decode_dxt5_block(source:encoded_block, &mut decoded_block); |
| 340 | |
| 341 | // copy the values from the decoded block to linewise RGB layout |
| 342 | for line: usize in 0..4 { |
| 343 | let offset: usize = (block_count * line + x) * 16; |
| 344 | dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]); |
| 345 | } |
| 346 | } |
| 347 | } |
| 348 | |