Sindbad~EG File Manager
//! 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() 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.seek(SeekFrom::Current(0))?;
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
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists