#![no_std]
mod alpha;
mod colourblock;
mod colourfit;
mod colourset;
mod math;
use crate::colourfit::{ClusterFit, ColourFit, RangeFit, SingleColourFit};
use crate::colourset::ColourSet;
#[cfg(feature="rayon")]
use rayon::prelude::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Format {
Bc1,
Bc2,
Bc3,
Bc4,
Bc5,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Algorithm {
RangeFit,
ClusterFit,
IterativeClusterFit,
}
impl Default for Algorithm {
fn default() -> Self {
Algorithm::ClusterFit
}
}
pub type ColourWeights = [f32; 3];
pub const COLOUR_WEIGHTS_UNIFORM: ColourWeights = [1.0, 1.0, 1.0];
pub const COLOUR_WEIGHTS_PERCEPTUAL: ColourWeights = [0.2126, 0.7152, 0.0722];
#[derive(Clone, Copy)]
pub struct Params {
pub algorithm: Algorithm,
pub weights: ColourWeights,
pub weigh_colour_by_alpha: bool,
}
impl Default for Params {
fn default() -> Self {
Params {
algorithm: Algorithm::default(),
weights: COLOUR_WEIGHTS_PERCEPTUAL,
weigh_colour_by_alpha: false,
}
}
}
pub fn num_blocks(size: usize) -> usize {
(size + 3) / 4
}
impl Format {
pub fn decompress(self, data: &[u8], width: usize, height: usize, output: &mut [u8]) {
let blocks_wide = num_blocks(width);
let block_size = self.block_size();
#[cfg(feature="rayon")]
let output_rows = output.par_chunks_mut(width * 4 * 4);
#[cfg(not(feature="rayon"))]
let output_rows = output.chunks_mut(width * 4 * 4);
output_rows.enumerate().for_each(|(y, output_row)| {
for x in 0..blocks_wide {
let bidx = (x + y * blocks_wide) * block_size;
let rgba = self.decompress_block(&data[bidx..bidx + block_size]);
for py in 0..4 {
for px in 0..4 {
let sx = 4 * x + px;
let sy = py;
if sx < width && sy < height {
for i in 0..4 {
output_row[4 * (sx + sy * width) + i] = rgba[px + py * 4][i];
}
}
}
}
}
});
}
pub fn block_size(self) -> usize {
match self {
Format::Bc1 => 8,
Format::Bc2 => 16,
Format::Bc3 => 16,
Format::Bc4 => 8,
Format::Bc5 => 16,
}
}
pub fn compressed_size(self, width: usize, height: usize) -> usize {
let blocks = num_blocks(width) * num_blocks(height);
blocks * self.block_size()
}
pub fn compress_block_masked(
self,
rgba: [[u8; 4]; 16],
mask: u32,
params: Params,
output: &mut [u8],
) {
match self {
Format::Bc1 => {},
Format::Bc2 => alpha::compress_bc2(&rgba, mask, &mut output[..8]),
Format::Bc3 => alpha::compress_bc3(&rgba, 3, mask, &mut output[..8]),
Format::Bc4 => alpha::compress_bc3(&rgba, 0, mask, &mut output[..8]),
Format::Bc5 => {
alpha::compress_bc3(&rgba, 0, mask, &mut output[0..8]);
alpha::compress_bc3(&rgba, 1, mask, &mut output[8..16]);
},
}
match self {
Format::Bc1 | Format::Bc2 | Format::Bc3 => {
let colours = ColourSet::new(&rgba, mask, self, params.weigh_colour_by_alpha);
let colour_offset = if self == Format::Bc1 { 0 } else { 8 };
let colour_block = &mut output[colour_offset..colour_offset + 8];
if colours.count() == 1 {
let mut fit = SingleColourFit::new(&colours, self);
fit.compress(colour_block);
} else if (params.algorithm == Algorithm::RangeFit) || (colours.count() == 0) {
let mut fit = RangeFit::new(&colours, self, params.weights);
fit.compress(colour_block);
} else {
let iterate = params.algorithm == Algorithm::IterativeClusterFit;
let mut fit = ClusterFit::new(&colours, self, params.weights, iterate);
fit.compress(colour_block);
}
},
Format::Bc4 | Format::Bc5 => {},
}
}
pub fn decompress_block(self, block: &[u8]) -> [[u8; 4]; 16] {
let mut rgba;
match self {
Format::Bc1 | Format::Bc2 | Format::Bc3 => {
let colour_offset = if self == Format::Bc1 { 0 } else { 8 };
let colour_block = &block[colour_offset..colour_offset + 8];
rgba = colourblock::decompress(colour_block, self == Format::Bc1);
},
_ => {
rgba = [[0, 0, 0, 0xFF]; 16];
},
}
match self {
Format::Bc1 => (),
Format::Bc2 => alpha::decompress_bc2(&mut rgba, &block[..8]),
Format::Bc3 => alpha::decompress_bc3(&mut rgba, 3, &block[..8]),
Format::Bc4 => {
alpha::decompress_bc3(&mut rgba, 0, &block[..8]);
for ref mut pixel in rgba {
pixel[1] = pixel[0];
pixel[2] = pixel[0];
}
},
Format::Bc5 => {
alpha::decompress_bc3(&mut rgba, 0, &block[..8]);
alpha::decompress_bc3(&mut rgba, 1, &block[8..16]);
},
}
rgba
}
pub fn compress(
self,
rgba: &[u8],
width: usize,
height: usize,
params: Params,
output: &mut [u8],
) {
assert!(output.len() >= self.compressed_size(width, height));
let block_size = self.block_size();
let blocks_wide = num_blocks(width);
#[cfg(feature="rayon")]
let output_rows = output.par_chunks_mut(blocks_wide * block_size);
#[cfg(not(feature="rayon"))]
let output_rows = output.chunks_mut(blocks_wide * block_size);
output_rows.enumerate().for_each(|(y, output_row)| {
let mut source_rgba = [[0u8; 4]; 16];
let output_blocks = output_row.chunks_mut(block_size);
output_blocks.enumerate().for_each(|(x, output_block)| {
let mut mask = 0u32;
for py in 0..4 {
for px in 0..4 {
let index = 4 * py + px;
let sx = 4 * x + px;
let sy = 4 * y + py;
if sx < width && sy < height {
let src_index = 4 * (width * sy + sx);
source_rgba[index].copy_from_slice(&rgba[src_index..src_index + 4]);
mask |= 1 << index;
}
}
}
self.compress_block_masked(source_rgba, mask, params, output_block);
});
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_storage_requirements() {
assert_eq!(Format::Bc1.compressed_size(16, 32), 256);
assert_eq!(Format::Bc1.compressed_size(15, 32), 256);
assert_eq!(Format::Bc2.compressed_size(16, 32), 512);
assert_eq!(Format::Bc2.compressed_size(15, 32), 512);
assert_eq!(Format::Bc3.compressed_size(16, 32), 512);
assert_eq!(Format::Bc3.compressed_size(15, 32), 512);
assert_eq!(Format::Bc4.compressed_size(16, 32), 256);
assert_eq!(Format::Bc4.compressed_size(15, 32), 256);
assert_eq!(Format::Bc5.compressed_size(16, 32), 512);
assert_eq!(Format::Bc5.compressed_size(15, 32), 512);
}
static DECODED_BLOCK_GRAY_4X4: &[u8] = &[
0xFF, 0x00, 0xFF, 0x00, 0x00, 0x7F, 0x7F, 0xFF, 0xFF, 0x7F, 0x7F, 0x00, 0x00, 0xFF, 0x00, 0xFF, ];
fn decoded_block_gray_4x4_as_rgba() -> [u8; 4 * 4 * 4] {
let mut output = [0u8; 4 * 4 * 4];
for i in 0..DECODED_BLOCK_GRAY_4X4.len() {
output[i * 4 + 0] = DECODED_BLOCK_GRAY_4X4[i]; output[i * 4 + 1] = DECODED_BLOCK_GRAY_4X4[i]; output[i * 4 + 2] = DECODED_BLOCK_GRAY_4X4[i]; output[i * 4 + 3] = 0xFF; }
output
}
#[test]
fn test_bc1_decompression_gray() {
let encoded: [u8; 8] = [0x00, 0x00, 0xFF, 0xFF, 0x11, 0x68, 0x29, 0x44];
let mut output_actual = [0u8; 4 * 4 * 4];
Format::Bc1.decompress(&encoded, 4, 4, &mut output_actual);
assert_eq!(output_actual, decoded_block_gray_4x4_as_rgba());
}
#[test]
fn test_bc1_compression_gray() {
fn test(algorithm: Algorithm) {
let mut output_actual = [0u8; 8];
Format::Bc1.compress(
&decoded_block_gray_4x4_as_rgba(),
4,
4,
Params {
algorithm,
weights: COLOUR_WEIGHTS_UNIFORM,
weigh_colour_by_alpha: false,
},
&mut output_actual,
);
let output_expected = [0x00, 0x00, 0xFF, 0xFF, 0x11, 0x68, 0x29, 0x44];
assert_eq!(output_actual, output_expected);
}
test(Algorithm::ClusterFit);
test(Algorithm::RangeFit);
test(Algorithm::IterativeClusterFit);
}
static DECODED_BLOCK_COLOUR_4X4: &[u8] = &[
255, 150, 74, 255, 150, 74, 255, 150, 74, 255, 150, 74, 255, 120, 52, 255, 120, 52, 255, 120, 52, 255, 120, 52, 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, ];
static ENCODED_BLOCK_COLOUR_4X4: [u8; 8] = [0xA9, 0xFC, 0x45, 0xFB, 0x00, 0xFF, 0x55, 0x55];
fn decoded_block_colour_4x4_as_rgba() -> [u8; 4 * 4 * 4] {
let mut output = [0u8; 4 * 4 * 4];
for i in 0..4 * 4 {
output[i * 4 + 0] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 0]; output[i * 4 + 1] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 1]; output[i * 4 + 2] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 2]; output[i * 4 + 3] = 0xFF; }
output
}
#[test]
fn test_bc1_decompression_colour() {
let encoded: [u8; 8] = ENCODED_BLOCK_COLOUR_4X4;
let mut output_actual = [0u8; 4 * 4 * 4];
Format::Bc1.decompress(&encoded, 4, 4, &mut output_actual);
assert_eq!(output_actual, decoded_block_colour_4x4_as_rgba());
}
#[test]
fn test_bc1_compression_colour() {
fn test(algorithm: Algorithm) {
let mut output_actual = [0u8; 8];
Format::Bc1.compress(
&decoded_block_colour_4x4_as_rgba(),
4,
4,
Params {
algorithm,
weights: COLOUR_WEIGHTS_UNIFORM,
weigh_colour_by_alpha: false,
},
&mut output_actual,
);
let output_expected = ENCODED_BLOCK_COLOUR_4X4;
assert_eq!(output_actual, output_expected);
}
test(Algorithm::ClusterFit);
test(Algorithm::RangeFit);
test(Algorithm::IterativeClusterFit);
}
}