summaryrefslogtreecommitdiff
path: root/vendor/image/src/codecs/dxt.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/dxt.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/dxt.rs')
-rw-r--r--vendor/image/src/codecs/dxt.rs869
1 files changed, 869 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/dxt.rs b/vendor/image/src/codecs/dxt.rs
new file mode 100644
index 0000000..8737fb3
--- /dev/null
+++ b/vendor/image/src/codecs/dxt.rs
@@ -0,0 +1,869 @@
+//! Decoding of DXT (S3TC) compression
+//!
+//! DXT is an image format that supports lossy compression
+//!
+//! # Related Links
+//! * <https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_compression_s3tc.txt> - Description of the DXT compression OpenGL extensions.
+//!
+//! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds
+
+use std::convert::TryFrom;
+use std::io::{self, Read, Seek, SeekFrom, Write};
+
+use crate::color::ColorType;
+use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
+use crate::image::{self, ImageDecoder, ImageDecoderRect, ImageReadBuffer, Progress};
+
+/// What version of DXT compression are we using?
+/// Note that DXT2 and DXT4 are left away as they're
+/// just DXT3 and DXT5 with premultiplied alpha
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum DxtVariant {
+ /// The DXT1 format. 48 bytes of RGB data in a 4x4 pixel square is
+ /// compressed into an 8 byte block of DXT1 data
+ DXT1,
+ /// The DXT3 format. 64 bytes of RGBA data in a 4x4 pixel square is
+ /// compressed into a 16 byte block of DXT3 data
+ DXT3,
+ /// The DXT5 format. 64 bytes of RGBA data in a 4x4 pixel square is
+ /// compressed into a 16 byte block of DXT5 data
+ DXT5,
+}
+
+impl DxtVariant {
+ /// Returns the amount of bytes of raw image data
+ /// that is encoded in a single DXTn block
+ fn decoded_bytes_per_block(self) -> usize {
+ match self {
+ DxtVariant::DXT1 => 48,
+ DxtVariant::DXT3 | DxtVariant::DXT5 => 64,
+ }
+ }
+
+ /// Returns the amount of bytes per block of encoded DXTn data
+ fn encoded_bytes_per_block(self) -> usize {
+ match self {
+ DxtVariant::DXT1 => 8,
+ DxtVariant::DXT3 | DxtVariant::DXT5 => 16,
+ }
+ }
+
+ /// Returns the color type that is stored in this DXT variant
+ pub fn color_type(self) -> ColorType {
+ match self {
+ DxtVariant::DXT1 => ColorType::Rgb8,
+ DxtVariant::DXT3 | DxtVariant::DXT5 => ColorType::Rgba8,
+ }
+ }
+}
+
+/// DXT decoder
+pub struct DxtDecoder<R: Read> {
+ inner: R,
+ width_blocks: u32,
+ height_blocks: u32,
+ variant: DxtVariant,
+ row: u32,
+}
+
+impl<R: Read> DxtDecoder<R> {
+ /// Create a new DXT decoder that decodes from the stream ```r```.
+ /// As DXT is often stored as raw buffers with the width/height
+ /// somewhere else the width and height of the image need
+ /// to be passed in ```width``` and ```height```, as well as the
+ /// DXT variant in ```variant```.
+ /// width and height are required to be powers of 2 and at least 4.
+ /// otherwise an error will be returned
+ pub fn new(
+ r: R,
+ width: u32,
+ height: u32,
+ variant: DxtVariant,
+ ) -> Result<DxtDecoder<R>, ImageError> {
+ if width % 4 != 0 || height % 4 != 0 {
+ // TODO: this is actually a bit of a weird case. We could return `DecodingError` but
+ // it's not really the format that is wrong However, the encoder should surely return
+ // `EncodingError` so it would be the logical choice for symmetry.
+ return Err(ImageError::Parameter(ParameterError::from_kind(
+ ParameterErrorKind::DimensionMismatch,
+ )));
+ }
+ let width_blocks = width / 4;
+ let height_blocks = height / 4;
+ Ok(DxtDecoder {
+ inner: r,
+ width_blocks,
+ height_blocks,
+ variant,
+ row: 0,
+ })
+ }
+
+ fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ assert_eq!(u64::try_from(buf.len()), Ok(self.scanline_bytes()));
+
+ let mut src =
+ vec![0u8; self.variant.encoded_bytes_per_block() * self.width_blocks as usize];
+ self.inner.read_exact(&mut src)?;
+ match self.variant {
+ DxtVariant::DXT1 => decode_dxt1_row(&src, buf),
+ DxtVariant::DXT3 => decode_dxt3_row(&src, buf),
+ DxtVariant::DXT5 => decode_dxt5_row(&src, buf),
+ }
+ self.row += 1;
+ Ok(buf.len())
+ }
+}
+
+// Note that, due to the way that DXT compression works, a scanline is considered to consist out of
+// 4 lines of pixels.
+impl<'a, R: 'a + Read> ImageDecoder<'a> for DxtDecoder<R> {
+ type Reader = DxtReader<R>;
+
+ fn dimensions(&self) -> (u32, u32) {
+ (self.width_blocks * 4, self.height_blocks * 4)
+ }
+
+ fn color_type(&self) -> ColorType {
+ self.variant.color_type()
+ }
+
+ fn scanline_bytes(&self) -> u64 {
+ self.variant.decoded_bytes_per_block() as u64 * u64::from(self.width_blocks)
+ }
+
+ fn into_reader(self) -> ImageResult<Self::Reader> {
+ Ok(DxtReader {
+ buffer: ImageReadBuffer::new(self.scanline_bytes(), self.total_bytes()),
+ decoder: self,
+ })
+ }
+
+ fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
+ assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
+
+ for chunk in buf.chunks_mut(self.scanline_bytes().max(1) as usize) {
+ self.read_scanline(chunk)?;
+ }
+ Ok(())
+ }
+}
+
+impl<'a, R: 'a + Read + Seek> ImageDecoderRect<'a> for DxtDecoder<R> {
+ fn read_rect_with_progress<F: Fn(Progress)>(
+ &mut self,
+ x: u32,
+ y: u32,
+ width: u32,
+ height: u32,
+ buf: &mut [u8],
+ progress_callback: F,
+ ) -> ImageResult<()> {
+ let encoded_scanline_bytes =
+ self.variant.encoded_bytes_per_block() as u64 * u64::from(self.width_blocks);
+
+ let start = self.inner.stream_position()?;
+ image::load_rect(
+ x,
+ y,
+ width,
+ height,
+ buf,
+ progress_callback,
+ self,
+ |s, scanline| {
+ s.inner
+ .seek(SeekFrom::Start(start + scanline * encoded_scanline_bytes))?;
+ Ok(())
+ },
+ |s, buf| s.read_scanline(buf).map(|_| ()),
+ )?;
+ self.inner.seek(SeekFrom::Start(start))?;
+ Ok(())
+ }
+}
+
+/// DXT reader
+pub struct DxtReader<R: Read> {
+ buffer: ImageReadBuffer,
+ decoder: DxtDecoder<R>,
+}
+
+impl<R: Read> Read for DxtReader<R> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let decoder = &mut self.decoder;
+ self.buffer.read(buf, |buf| decoder.read_scanline(buf))
+ }
+}
+
+/// DXT encoder
+pub struct DxtEncoder<W: Write> {
+ w: W,
+}
+
+impl<W: Write> DxtEncoder<W> {
+ /// Create a new encoder that writes its output to ```w```
+ pub fn new(w: W) -> DxtEncoder<W> {
+ DxtEncoder { w }
+ }
+
+ /// Encodes the image data ```data```
+ /// that has dimensions ```width``` and ```height```
+ /// in ```DxtVariant``` ```variant```
+ /// data is assumed to be in variant.color_type()
+ pub fn encode(
+ mut self,
+ data: &[u8],
+ width: u32,
+ height: u32,
+ variant: DxtVariant,
+ ) -> ImageResult<()> {
+ if width % 4 != 0 || height % 4 != 0 {
+ // TODO: this is not very idiomatic yet. Should return an EncodingError.
+ return Err(ImageError::Parameter(ParameterError::from_kind(
+ ParameterErrorKind::DimensionMismatch,
+ )));
+ }
+ let width_blocks = width / 4;
+ let height_blocks = height / 4;
+
+ let stride = variant.decoded_bytes_per_block();
+
+ assert!(data.len() >= width_blocks as usize * height_blocks as usize * stride);
+
+ for chunk in data.chunks(width_blocks as usize * stride) {
+ let data = match variant {
+ DxtVariant::DXT1 => encode_dxt1_row(chunk),
+ DxtVariant::DXT3 => encode_dxt3_row(chunk),
+ DxtVariant::DXT5 => encode_dxt5_row(chunk),
+ };
+ self.w.write_all(&data)?;
+ }
+ Ok(())
+ }
+}
+
+/**
+ * Actual encoding/decoding logic below.
+ */
+use std::mem::swap;
+
+type Rgb = [u8; 3];
+
+/// decodes a 5-bit R, 6-bit G, 5-bit B 16-bit packed color value into 8-bit RGB
+/// mapping is done so min/max range values are preserved. So for 5-bit
+/// values 0x00 -> 0x00 and 0x1F -> 0xFF
+fn enc565_decode(value: u16) -> Rgb {
+ let red = (value >> 11) & 0x1F;
+ let green = (value >> 5) & 0x3F;
+ let blue = (value) & 0x1F;
+ [
+ (red * 0xFF / 0x1F) as u8,
+ (green * 0xFF / 0x3F) as u8,
+ (blue * 0xFF / 0x1F) as u8,
+ ]
+}
+
+/// encodes an 8-bit RGB value into a 5-bit R, 6-bit G, 5-bit B 16-bit packed color value
+/// mapping preserves min/max values. It is guaranteed that i == encode(decode(i)) for all i
+fn enc565_encode(rgb: Rgb) -> u16 {
+ let red = (u16::from(rgb[0]) * 0x1F + 0x7E) / 0xFF;
+ let green = (u16::from(rgb[1]) * 0x3F + 0x7E) / 0xFF;
+ let blue = (u16::from(rgb[2]) * 0x1F + 0x7E) / 0xFF;
+ (red << 11) | (green << 5) | blue
+}
+
+/// utility function: squares a value
+fn square(a: i32) -> i32 {
+ a * a
+}
+
+/// returns the squared error between two RGB values
+fn diff(a: Rgb, b: Rgb) -> i32 {
+ square(i32::from(a[0]) - i32::from(b[0]))
+ + square(i32::from(a[1]) - i32::from(b[1]))
+ + square(i32::from(a[2]) - i32::from(b[2]))
+}
+
+/*
+ * Functions for decoding DXT compression
+ */
+
+/// Constructs the DXT5 alpha lookup table from the two alpha entries
+/// if alpha0 > alpha1, constructs a table of [a0, a1, 6 linearly interpolated values from a0 to a1]
+/// if alpha0 <= alpha1, constructs a table of [a0, a1, 4 linearly interpolated values from a0 to a1, 0, 0xFF]
+fn alpha_table_dxt5(alpha0: u8, alpha1: u8) -> [u8; 8] {
+ let mut table = [alpha0, alpha1, 0, 0, 0, 0, 0, 0xFF];
+ if alpha0 > alpha1 {
+ for i in 2..8u16 {
+ table[i as usize] =
+ (((8 - i) * u16::from(alpha0) + (i - 1) * u16::from(alpha1)) / 7) as u8;
+ }
+ } else {
+ for i in 2..6u16 {
+ table[i as usize] =
+ (((6 - i) * u16::from(alpha0) + (i - 1) * u16::from(alpha1)) / 5) as u8;
+ }
+ }
+ table
+}
+
+/// decodes an 8-byte dxt color block into the RGB channels of a 16xRGB or 16xRGBA block.
+/// source should have a length of 8, dest a length of 48 (RGB) or 64 (RGBA)
+fn decode_dxt_colors(source: &[u8], dest: &mut [u8], is_dxt1: bool) {
+ // sanity checks, also enable the compiler to elide all following bound checks
+ assert!(source.len() == 8 && (dest.len() == 48 || dest.len() == 64));
+ // calculate pitch to store RGB values in dest (3 for RGB, 4 for RGBA)
+ let pitch = dest.len() / 16;
+
+ // extract color data
+ let color0 = u16::from(source[0]) | (u16::from(source[1]) << 8);
+ let color1 = u16::from(source[2]) | (u16::from(source[3]) << 8);
+ let color_table = u32::from(source[4])
+ | (u32::from(source[5]) << 8)
+ | (u32::from(source[6]) << 16)
+ | (u32::from(source[7]) << 24);
+ // let color_table = source[4..8].iter().rev().fold(0, |t, &b| (t << 8) | b as u32);
+
+ // decode the colors to rgb format
+ let mut colors = [[0; 3]; 4];
+ colors[0] = enc565_decode(color0);
+ colors[1] = enc565_decode(color1);
+
+ // determine color interpolation method
+ if color0 > color1 || !is_dxt1 {
+ // linearly interpolate the other two color table entries
+ for i in 0..3 {
+ colors[2][i] = ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3) as u8;
+ colors[3][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3) as u8;
+ }
+ } else {
+ // linearly interpolate one other entry, keep the other at 0
+ for i in 0..3 {
+ colors[2][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) + 1) / 2) as u8;
+ }
+ }
+
+ // serialize the result. Every color is determined by looking up
+ // two bits in color_table which identify which color to actually pick from the 4 possible colors
+ for i in 0..16 {
+ dest[i * pitch..i * pitch + 3]
+ .copy_from_slice(&colors[(color_table >> (i * 2)) as usize & 3]);
+ }
+}
+
+/// Decodes a 16-byte bock of dxt5 data to a 16xRGBA block
+fn decode_dxt5_block(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() == 16 && dest.len() == 64);
+
+ // extract alpha index table (stored as little endian 64-bit value)
+ let alpha_table = source[2..8]
+ .iter()
+ .rev()
+ .fold(0, |t, &b| (t << 8) | u64::from(b));
+
+ // alhpa level decode
+ let alphas = alpha_table_dxt5(source[0], source[1]);
+
+ // serialize alpha
+ for i in 0..16 {
+ dest[i * 4 + 3] = alphas[(alpha_table >> (i * 3)) as usize & 7];
+ }
+
+ // handle colors
+ decode_dxt_colors(&source[8..16], dest, false);
+}
+
+/// Decodes a 16-byte bock of dxt3 data to a 16xRGBA block
+fn decode_dxt3_block(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() == 16 && dest.len() == 64);
+
+ // extract alpha index table (stored as little endian 64-bit value)
+ let alpha_table = source[0..8]
+ .iter()
+ .rev()
+ .fold(0, |t, &b| (t << 8) | u64::from(b));
+
+ // serialize alpha (stored as 4-bit values)
+ for i in 0..16 {
+ dest[i * 4 + 3] = ((alpha_table >> (i * 4)) as u8 & 0xF) * 0x11;
+ }
+
+ // handle colors
+ decode_dxt_colors(&source[8..16], dest, false);
+}
+
+/// Decodes a 8-byte bock of dxt5 data to a 16xRGB block
+fn decode_dxt1_block(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() == 8 && dest.len() == 48);
+ decode_dxt_colors(source, dest, true);
+}
+
+/// Decode a row of DXT1 data to four rows of RGB data.
+/// source.len() should be a multiple of 8, otherwise this panics.
+fn decode_dxt1_row(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() % 8 == 0);
+ let block_count = source.len() / 8;
+ assert!(dest.len() >= block_count * 48);
+
+ // contains the 16 decoded pixels per block
+ let mut decoded_block = [0u8; 48];
+
+ for (x, encoded_block) in source.chunks(8).enumerate() {
+ decode_dxt1_block(encoded_block, &mut decoded_block);
+
+ // copy the values from the decoded block to linewise RGB layout
+ for line in 0..4 {
+ let offset = (block_count * line + x) * 12;
+ dest[offset..offset + 12].copy_from_slice(&decoded_block[line * 12..(line + 1) * 12]);
+ }
+ }
+}
+
+/// Decode a row of DXT3 data to four rows of RGBA data.
+/// source.len() should be a multiple of 16, otherwise this panics.
+fn decode_dxt3_row(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() % 16 == 0);
+ let block_count = source.len() / 16;
+ assert!(dest.len() >= block_count * 64);
+
+ // contains the 16 decoded pixels per block
+ let mut decoded_block = [0u8; 64];
+
+ for (x, encoded_block) in source.chunks(16).enumerate() {
+ decode_dxt3_block(encoded_block, &mut decoded_block);
+
+ // copy the values from the decoded block to linewise RGB layout
+ for line in 0..4 {
+ let offset = (block_count * line + x) * 16;
+ dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]);
+ }
+ }
+}
+
+/// Decode a row of DXT5 data to four rows of RGBA data.
+/// source.len() should be a multiple of 16, otherwise this panics.
+fn decode_dxt5_row(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() % 16 == 0);
+ let block_count = source.len() / 16;
+ assert!(dest.len() >= block_count * 64);
+
+ // contains the 16 decoded pixels per block
+ let mut decoded_block = [0u8; 64];
+
+ for (x, encoded_block) in source.chunks(16).enumerate() {
+ decode_dxt5_block(encoded_block, &mut decoded_block);
+
+ // copy the values from the decoded block to linewise RGB layout
+ for line in 0..4 {
+ let offset = (block_count * line + x) * 16;
+ dest[offset..offset + 16].copy_from_slice(&decoded_block[line * 16..(line + 1) * 16]);
+ }
+ }
+}
+
+/*
+ * Functions for encoding DXT compression
+ */
+
+/// Tries to perform the color encoding part of dxt compression
+/// the approach taken is simple, it picks unique combinations
+/// of the colors present in the block, and attempts to encode the
+/// block with each, picking the encoding that yields the least
+/// squared error out of all of them.
+///
+/// This could probably be faster but is already reasonably fast
+/// and a good reference impl to optimize others against.
+///
+/// Another way to perform this analysis would be to perform a
+/// singular value decomposition of the different colors, and
+/// then pick 2 points on this line as the base colors. But
+/// this is still rather unwieldy math and has issues
+/// with the 3-linear-colors-and-0 case, it's also worse
+/// at conserving the original colors.
+///
+/// source: should be RGBAx16 or RGBx16 bytes of data,
+/// dest 8 bytes of resulting encoded color data
+fn encode_dxt_colors(source: &[u8], dest: &mut [u8], is_dxt1: bool) {
+ // sanity checks and determine stride when parsing the source data
+ assert!((source.len() == 64 || source.len() == 48) && dest.len() == 8);
+ let stride = source.len() / 16;
+
+ // reference colors array
+ let mut colors = [[0u8; 3]; 4];
+
+ // Put the colors we're going to be processing in an array with pure RGB layout
+ // note: we reverse the pixel order here. The reason for this is found in the inner quantization loop.
+ let mut targets = [[0u8; 3]; 16];
+ for (s, d) in source.chunks(stride).rev().zip(&mut targets) {
+ *d = [s[0], s[1], s[2]];
+ }
+
+ // roundtrip all colors through the r5g6b5 encoding
+ for rgb in &mut targets {
+ *rgb = enc565_decode(enc565_encode(*rgb));
+ }
+
+ // and deduplicate the set of colors to choose from as the algorithm is O(N^2) in this
+ let mut colorspace_ = [[0u8; 3]; 16];
+ let mut colorspace_len = 0;
+ for color in &targets {
+ if !colorspace_[..colorspace_len].contains(color) {
+ colorspace_[colorspace_len] = *color;
+ colorspace_len += 1;
+ }
+ }
+ let mut colorspace = &colorspace_[..colorspace_len];
+
+ // in case of slight gradients it can happen that there's only one entry left in the color table.
+ // as the resulting banding can be quite bad if we would just left the block at the closest
+ // encodable color, we have a special path here that tries to emulate the wanted color
+ // using the linear interpolation between gradients
+ if colorspace.len() == 1 {
+ // the base color we got from colorspace reduction
+ let ref_rgb = colorspace[0];
+ // the unreduced color in this block that's the furthest away from the actual block
+ let mut rgb = targets
+ .iter()
+ .cloned()
+ .max_by_key(|rgb| diff(*rgb, ref_rgb))
+ .unwrap();
+ // amplify differences by 2.5, which should push them to the next quantized value
+ // if possible without overshoot
+ for i in 0..3 {
+ rgb[i] =
+ ((i16::from(rgb[i]) - i16::from(ref_rgb[i])) * 5 / 2 + i16::from(ref_rgb[i])) as u8;
+ }
+
+ // roundtrip it through quantization
+ let encoded = enc565_encode(rgb);
+ let rgb = enc565_decode(encoded);
+
+ // in case this didn't land us a different color the best way to represent this field is
+ // as a single color block
+ if rgb == ref_rgb {
+ dest[0] = encoded as u8;
+ dest[1] = (encoded >> 8) as u8;
+
+ for d in dest.iter_mut().take(8).skip(2) {
+ *d = 0;
+ }
+ return;
+ }
+
+ // we did find a separate value: add it to the options so after one round of quantization
+ // we're done
+ colorspace_[1] = rgb;
+ colorspace = &colorspace_[..2];
+ }
+
+ // block quantization loop: we basically just try every possible combination, returning
+ // the combination with the least squared error
+ // stores the best candidate colors
+ let mut chosen_colors = [[0; 3]; 4];
+ // did this index table use the [0,0,0] variant
+ let mut chosen_use_0 = false;
+ // error calculated for the last entry
+ let mut chosen_error = 0xFFFF_FFFFu32;
+
+ // loop through unique permutations of the colorspace, where c1 != c2
+ 'search: for (i, &c1) in colorspace.iter().enumerate() {
+ colors[0] = c1;
+
+ for &c2 in &colorspace[0..i] {
+ colors[1] = c2;
+
+ if is_dxt1 {
+ // what's inside here is ran at most 120 times.
+ for use_0 in 0..2 {
+ // and 240 times here.
+
+ if use_0 != 0 {
+ // interpolate one color, set the other to 0
+ for i in 0..3 {
+ colors[2][i] =
+ ((u16::from(colors[0][i]) + u16::from(colors[1][i]) + 1) / 2) as u8;
+ }
+ colors[3] = [0, 0, 0];
+ } else {
+ // interpolate to get 2 more colors
+ for i in 0..3 {
+ colors[2][i] =
+ ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3)
+ as u8;
+ colors[3][i] =
+ ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3)
+ as u8;
+ }
+ }
+
+ // calculate the total error if we were to quantize the block with these color combinations
+ // both these loops have statically known iteration counts and are well vectorizable
+ // note that the inside of this can be run about 15360 times worst case, i.e. 960 times per
+ // pixel.
+ let total_error = targets
+ .iter()
+ .map(|t| colors.iter().map(|c| diff(*c, *t) as u32).min().unwrap())
+ .sum();
+
+ // update the match if we found a better one
+ if total_error < chosen_error {
+ chosen_colors = colors;
+ chosen_use_0 = use_0 != 0;
+ chosen_error = total_error;
+
+ // if we've got a perfect or at most 1 LSB off match, we're done
+ if total_error < 4 {
+ break 'search;
+ }
+ }
+ }
+ } else {
+ // what's inside here is ran at most 120 times.
+
+ // interpolate to get 2 more colors
+ for i in 0..3 {
+ colors[2][i] =
+ ((u16::from(colors[0][i]) * 2 + u16::from(colors[1][i]) + 1) / 3) as u8;
+ colors[3][i] =
+ ((u16::from(colors[0][i]) + u16::from(colors[1][i]) * 2 + 1) / 3) as u8;
+ }
+
+ // calculate the total error if we were to quantize the block with these color combinations
+ // both these loops have statically known iteration counts and are well vectorizable
+ // note that the inside of this can be run about 15360 times worst case, i.e. 960 times per
+ // pixel.
+ let total_error = targets
+ .iter()
+ .map(|t| colors.iter().map(|c| diff(*c, *t) as u32).min().unwrap())
+ .sum();
+
+ // update the match if we found a better one
+ if total_error < chosen_error {
+ chosen_colors = colors;
+ chosen_error = total_error;
+
+ // if we've got a perfect or at most 1 LSB off match, we're done
+ if total_error < 4 {
+ break 'search;
+ }
+ }
+ }
+ }
+ }
+
+ // calculate the final indices
+ // note that targets is already in reverse pixel order, to make the index computation easy.
+ let mut chosen_indices = 0u32;
+ for t in &targets {
+ let (idx, _) = chosen_colors
+ .iter()
+ .enumerate()
+ .min_by_key(|&(_, c)| diff(*c, *t))
+ .unwrap();
+ chosen_indices = (chosen_indices << 2) | idx as u32;
+ }
+
+ // encode the colors
+ let mut color0 = enc565_encode(chosen_colors[0]);
+ let mut color1 = enc565_encode(chosen_colors[1]);
+
+ // determine encoding. Note that color0 == color1 is impossible at this point
+ if is_dxt1 {
+ if color0 > color1 {
+ if chosen_use_0 {
+ swap(&mut color0, &mut color1);
+ // Indexes are packed 2 bits wide, swap index 0/1 but preserve 2/3.
+ let filter = (chosen_indices & 0xAAAA_AAAA) >> 1;
+ chosen_indices ^= filter ^ 0x5555_5555;
+ }
+ } else if !chosen_use_0 {
+ swap(&mut color0, &mut color1);
+ // Indexes are packed 2 bits wide, swap index 0/1 and 2/3.
+ chosen_indices ^= 0x5555_5555;
+ }
+ }
+
+ // encode everything.
+ dest[0] = color0 as u8;
+ dest[1] = (color0 >> 8) as u8;
+ dest[2] = color1 as u8;
+ dest[3] = (color1 >> 8) as u8;
+ for i in 0..4 {
+ dest[i + 4] = (chosen_indices >> (i * 8)) as u8;
+ }
+}
+
+/// Encodes a buffer of 16 alpha bytes into a dxt5 alpha index table,
+/// where the alpha table they are indexed against is created by
+/// calling alpha_table_dxt5(alpha0, alpha1)
+/// returns the resulting error and alpha table
+fn encode_dxt5_alpha(alpha0: u8, alpha1: u8, alphas: &[u8; 16]) -> (i32, u64) {
+ // create a table for the given alpha ranges
+ let table = alpha_table_dxt5(alpha0, alpha1);
+ let mut indices = 0u64;
+ let mut total_error = 0i32;
+
+ // least error brute force search
+ for (i, &a) in alphas.iter().enumerate() {
+ let (index, error) = table
+ .iter()
+ .enumerate()
+ .map(|(i, &e)| (i, square(i32::from(e) - i32::from(a))))
+ .min_by_key(|&(_, e)| e)
+ .unwrap();
+ total_error += error;
+ indices |= (index as u64) << (i * 3);
+ }
+
+ (total_error, indices)
+}
+
+/// Encodes a RGBAx16 sequence of bytes to a 16 bytes DXT5 block
+fn encode_dxt5_block(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() == 64 && dest.len() == 16);
+
+ // perform dxt color encoding
+ encode_dxt_colors(source, &mut dest[8..16], false);
+
+ // copy out the alpha bytes
+ let mut alphas = [0; 16];
+ for i in 0..16 {
+ alphas[i] = source[i * 4 + 3];
+ }
+
+ // try both alpha compression methods, see which has the least error.
+ let alpha07 = alphas.iter().cloned().min().unwrap();
+ let alpha17 = alphas.iter().cloned().max().unwrap();
+ let (error7, indices7) = encode_dxt5_alpha(alpha07, alpha17, &alphas);
+
+ // if all alphas are 0 or 255 it doesn't particularly matter what we do here.
+ let alpha05 = alphas
+ .iter()
+ .cloned()
+ .filter(|&i| i != 255)
+ .max()
+ .unwrap_or(255);
+ let alpha15 = alphas
+ .iter()
+ .cloned()
+ .filter(|&i| i != 0)
+ .min()
+ .unwrap_or(0);
+ let (error5, indices5) = encode_dxt5_alpha(alpha05, alpha15, &alphas);
+
+ // pick the best one, encode the min/max values
+ let mut alpha_table = if error5 < error7 {
+ dest[0] = alpha05;
+ dest[1] = alpha15;
+ indices5
+ } else {
+ dest[0] = alpha07;
+ dest[1] = alpha17;
+ indices7
+ };
+
+ // encode the alphas
+ for byte in dest[2..8].iter_mut() {
+ *byte = alpha_table as u8;
+ alpha_table >>= 8;
+ }
+}
+
+/// Encodes a RGBAx16 sequence of bytes into a 16 bytes DXT3 block
+fn encode_dxt3_block(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() == 64 && dest.len() == 16);
+
+ // perform dxt color encoding
+ encode_dxt_colors(source, &mut dest[8..16], false);
+
+ // DXT3 alpha compression is very simple, just round towards the nearest value
+
+ // index the alpha values into the 64bit alpha table
+ let mut alpha_table = 0u64;
+ for i in 0..16 {
+ let alpha = u64::from(source[i * 4 + 3]);
+ let alpha = (alpha + 0x8) / 0x11;
+ alpha_table |= alpha << (i * 4);
+ }
+
+ // encode the alpha values
+ for byte in &mut dest[0..8] {
+ *byte = alpha_table as u8;
+ alpha_table >>= 8;
+ }
+}
+
+/// Encodes a RGBx16 sequence of bytes into a 8 bytes DXT1 block
+fn encode_dxt1_block(source: &[u8], dest: &mut [u8]) {
+ assert!(source.len() == 48 && dest.len() == 8);
+
+ // perform dxt color encoding
+ encode_dxt_colors(source, dest, true);
+}
+
+/// Decode a row of DXT1 data to four rows of RGBA data.
+/// source.len() should be a multiple of 8, otherwise this panics.
+fn encode_dxt1_row(source: &[u8]) -> Vec<u8> {
+ assert!(source.len() % 48 == 0);
+ let block_count = source.len() / 48;
+
+ let mut dest = vec![0u8; block_count * 8];
+ // contains the 16 decoded pixels per block
+ let mut decoded_block = [0u8; 48];
+
+ for (x, encoded_block) in dest.chunks_mut(8).enumerate() {
+ // copy the values from the decoded block to linewise RGB layout
+ for line in 0..4 {
+ let offset = (block_count * line + x) * 12;
+ decoded_block[line * 12..(line + 1) * 12].copy_from_slice(&source[offset..offset + 12]);
+ }
+
+ encode_dxt1_block(&decoded_block, encoded_block);
+ }
+ dest
+}
+
+/// Decode a row of DXT3 data to four rows of RGBA data.
+/// source.len() should be a multiple of 16, otherwise this panics.
+fn encode_dxt3_row(source: &[u8]) -> Vec<u8> {
+ assert!(source.len() % 64 == 0);
+ let block_count = source.len() / 64;
+
+ let mut dest = vec![0u8; block_count * 16];
+ // contains the 16 decoded pixels per block
+ let mut decoded_block = [0u8; 64];
+
+ for (x, encoded_block) in dest.chunks_mut(16).enumerate() {
+ // copy the values from the decoded block to linewise RGB layout
+ for line in 0..4 {
+ let offset = (block_count * line + x) * 16;
+ decoded_block[line * 16..(line + 1) * 16].copy_from_slice(&source[offset..offset + 16]);
+ }
+
+ encode_dxt3_block(&decoded_block, encoded_block);
+ }
+ dest
+}
+
+/// Decode a row of DXT5 data to four rows of RGBA data.
+/// source.len() should be a multiple of 16, otherwise this panics.
+fn encode_dxt5_row(source: &[u8]) -> Vec<u8> {
+ assert!(source.len() % 64 == 0);
+ let block_count = source.len() / 64;
+
+ let mut dest = vec![0u8; block_count * 16];
+ // contains the 16 decoded pixels per block
+ let mut decoded_block = [0u8; 64];
+
+ for (x, encoded_block) in dest.chunks_mut(16).enumerate() {
+ // copy the values from the decoded block to linewise RGB layout
+ for line in 0..4 {
+ let offset = (block_count * line + x) * 16;
+ decoded_block[line * 16..(line + 1) * 16].copy_from_slice(&source[offset..offset + 16]);
+ }
+
+ encode_dxt5_block(&decoded_block, encoded_block);
+ }
+ dest
+}