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, ImageDecoderExt, ImageReadBuffer, Progress};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DXTVariant {
DXT1,
DXT3,
DXT5,
}
#[allow(dead_code)]
pub type DxtVariant = DXTVariant;
impl DXTVariant {
fn decoded_bytes_per_block(self) -> usize {
match self {
DXTVariant::DXT1 => 48,
DXTVariant::DXT3 | DXTVariant::DXT5 => 64,
}
}
fn encoded_bytes_per_block(self) -> usize {
match self {
DXTVariant::DXT1 => 8,
DXTVariant::DXT3 | DXTVariant::DXT5 => 16,
}
}
pub fn color_type(self) -> ColorType {
match self {
DXTVariant::DXT1 => ColorType::Rgb8,
DXTVariant::DXT3 | DXTVariant::DXT5 => ColorType::Rgba8,
}
}
}
pub struct DxtDecoder<R: Read> {
inner: R,
width_blocks: u32,
height_blocks: u32,
variant: DXTVariant,
row: u32,
}
impl<R: Read> DxtDecoder<R> {
pub fn new(
r: R,
width: u32,
height: u32,
variant: DXTVariant,
) -> Result<DxtDecoder<R>, ImageError> {
if width % 4 != 0 || height % 4 != 0 {
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())
}
}
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> ImageDecoderExt<'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(())
}
}
pub struct DxtReader<R: Read> {
buffer: ImageReadBuffer,
decoder: DxtDecoder<R>,
}
#[allow(dead_code)]
#[deprecated(note = "Use `DxtReader` instead")]
pub type DXTReader<R> = DxtReader<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))
}
}
pub struct DxtEncoder<W: Write> {
w: W,
}
#[allow(dead_code)]
#[deprecated(note = "Use `DxtEncoder` instead")]
pub type DXTEncoder<W> = DxtEncoder<W>;
impl<W: Write> DxtEncoder<W> {
pub fn new(w: W) -> DxtEncoder<W> {
DxtEncoder { w }
}
pub fn encode(
mut self,
data: &[u8],
width: u32,
height: u32,
variant: DXTVariant,
) -> ImageResult<()> {
if width % 4 != 0 || height % 4 != 0 {
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(())
}
}
use std::mem::swap;
type Rgb = [u8; 3];
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,
]
}
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
}
fn square(a: i32) -> i32 {
a * a
}
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]))
}
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
}
fn decode_dxt_colors(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 8 && (dest.len() == 48 || dest.len() == 64));
let pitch = dest.len() / 16;
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 mut colors = [[0; 3]; 4];
colors[0] = enc565_decode(color0);
colors[1] = enc565_decode(color1);
if color0 > color1 {
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 {
for i in 0..3 {
colors[2][i] = ((u16::from(colors[0][i]) + u16::from(colors[1][i]) + 1) / 2) as u8;
}
}
for i in 0..16 {
dest[i * pitch..i * pitch + 3]
.copy_from_slice(&colors[(color_table >> (i * 2)) as usize & 3]);
}
}
fn decode_dxt5_block(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 16 && dest.len() == 64);
let alpha_table = source[2..8]
.iter()
.rev()
.fold(0, |t, &b| (t << 8) | u64::from(b));
let alphas = alpha_table_dxt5(source[0], source[1]);
for i in 0..16 {
dest[i * 4 + 3] = alphas[(alpha_table >> (i * 3)) as usize & 7];
}
decode_dxt_colors(&source[8..16], dest);
}
fn decode_dxt3_block(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 16 && dest.len() == 64);
let alpha_table = source[0..8]
.iter()
.rev()
.fold(0, |t, &b| (t << 8) | u64::from(b));
for i in 0..16 {
dest[i * 4 + 3] = ((alpha_table >> (i * 4)) as u8 & 0xF) * 0x11;
}
decode_dxt_colors(&source[8..16], dest);
}
fn decode_dxt1_block(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 8 && dest.len() == 48);
decode_dxt_colors(&source, dest);
}
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);
let mut decoded_block = [0u8; 48];
for (x, encoded_block) in source.chunks(8).enumerate() {
decode_dxt1_block(encoded_block, &mut decoded_block);
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]);
}
}
}
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);
let mut decoded_block = [0u8; 64];
for (x, encoded_block) in source.chunks(16).enumerate() {
decode_dxt3_block(encoded_block, &mut decoded_block);
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]);
}
}
}
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);
let mut decoded_block = [0u8; 64];
for (x, encoded_block) in source.chunks(16).enumerate() {
decode_dxt5_block(encoded_block, &mut decoded_block);
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]);
}
}
}
fn encode_dxt_colors(source: &[u8], dest: &mut [u8]) {
assert!((source.len() == 64 || source.len() == 48) && dest.len() == 8);
let stride = source.len() / 16;
let mut colors = [[0u8; 3]; 4];
let mut targets = [[0u8; 3]; 16];
for (s, d) in source.chunks(stride).rev().zip(&mut targets) {
*d = [s[0], s[1], s[2]];
}
let mut colorspace = targets.to_vec();
for rgb in &mut colorspace {
*rgb = enc565_decode(enc565_encode(*rgb));
}
colorspace.dedup();
if colorspace.len() == 1 {
let ref_rgb = colorspace[0];
let mut rgb = targets
.iter()
.cloned()
.max_by_key(|rgb| diff(*rgb, ref_rgb))
.unwrap();
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;
}
let encoded = enc565_encode(rgb);
let rgb = enc565_decode(encoded);
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;
}
colorspace.push(rgb);
}
let mut chosen_colors = [[0; 3]; 4];
let mut chosen_use_0 = false;
let mut chosen_error = 0xFFFF_FFFFu32;
'search: for (i, &c1) in colorspace.iter().enumerate() {
colors[0] = c1;
for &c2 in &colorspace[0..i] {
colors[1] = c2;
for use_0 in 0..2 {
if use_0 != 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 {
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;
}
}
let total_error = targets
.iter()
.map(|t| colors.iter().map(|c| diff(*c, *t) as u32).min().unwrap())
.sum();
if total_error < chosen_error {
chosen_colors = colors;
chosen_use_0 = use_0 != 0;
chosen_error = total_error;
if total_error < 4 {
break 'search;
}
}
}
}
}
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;
}
let mut color0 = enc565_encode(chosen_colors[0]);
let mut color1 = enc565_encode(chosen_colors[1]);
if color0 > color1 {
if chosen_use_0 {
swap(&mut color0, &mut color1);
let filter = (chosen_indices & 0xAAAA_AAAA) >> 1;
chosen_indices ^= filter ^ 0x5555_5555;
}
} else if !chosen_use_0 {
swap(&mut color0, &mut color1);
chosen_indices ^= 0x5555_5555;
}
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;
}
}
fn encode_dxt5_alpha(alpha0: u8, alpha1: u8, alphas: &[u8; 16]) -> (i32, u64) {
let table = alpha_table_dxt5(alpha0, alpha1);
let mut indices = 0u64;
let mut total_error = 0i32;
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)
}
fn encode_dxt5_block(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 64 && dest.len() == 16);
encode_dxt_colors(source, &mut dest[8..16]);
let mut alphas = [0; 16];
for i in 0..16 {
alphas[i] = source[i * 4 + 3];
}
let alpha07 = alphas.iter().cloned().min().unwrap();
let alpha17 = alphas.iter().cloned().max().unwrap();
let (error7, indices7) = encode_dxt5_alpha(alpha07, alpha17, &alphas);
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);
let mut alpha_table = if error5 < error7 {
dest[0] = alpha05;
dest[1] = alpha15;
indices5
} else {
dest[0] = alpha07;
dest[1] = alpha17;
indices7
};
for byte in dest[2..8].iter_mut() {
*byte = alpha_table as u8;
alpha_table >>= 8;
}
}
fn encode_dxt3_block(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 64 && dest.len() == 16);
encode_dxt_colors(source, &mut dest[8..16]);
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);
}
for byte in &mut dest[0..8] {
*byte = alpha_table as u8;
alpha_table >>= 8;
}
}
fn encode_dxt1_block(source: &[u8], dest: &mut [u8]) {
assert!(source.len() == 48 && dest.len() == 8);
encode_dxt_colors(source, dest);
}
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];
let mut decoded_block = [0u8; 48];
for (x, encoded_block) in dest.chunks_mut(8).enumerate() {
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
}
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];
let mut decoded_block = [0u8; 64];
for (x, encoded_block) in dest.chunks_mut(16).enumerate() {
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
}
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];
let mut decoded_block = [0u8; 64];
for (x, encoded_block) in dest.chunks_mut(16).enumerate() {
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
}