#![no_std]
extern crate byteorder;
use core::fmt;
use core::str::FromStr;
mod alpha;
mod colourblock;
mod colourfit;
mod colourset;
mod math;
use colourfit::{ClusterFit, ColourFit, RangeFit, SingleColourFit};
use colourset::ColourSet;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Format {
Bc1,
Bc2,
Bc3,
}
#[derive(Debug)]
pub enum ParseFormatError {
InvalidFormat,
}
impl fmt::Display for ParseFormatError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Not a valid format name")
}
}
impl FromStr for Format {
type Err = ParseFormatError;
fn from_str(s: &str) -> Result<Format, ParseFormatError> {
match s.to_lowercase().as_str() {
"bc1" => Ok(Format::Bc1),
"bc2" => Ok(Format::Bc2),
"bc3" => Ok(Format::Bc3),
_ => Err(ParseFormatError::InvalidFormat),
}
}
}
#[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 blocks_high = num_blocks(height);
let block_size = self.block_size();
for y in 0..blocks_high {
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 = 4 * y + py;
if sx < width && sy < height {
for i in 0..4 {
output[4 * (sx + sy * width) + i] = rgba[px + py * 4][i];
}
}
}
}
}
}
}
fn block_size(self) -> usize {
match self {
Format::Bc1 => 8,
Format::Bc2 => 16,
Format::Bc3 => 16,
}
}
pub fn compressed_size(self, width: usize, height: usize) -> usize {
let blocks = num_blocks(width) * num_blocks(height);
blocks * self.block_size()
}
fn compress_block_masked(
self,
rgba: [[u8; 4]; 16],
mask: u32,
params: Params,
output: &mut [u8],
) {
if self == Format::Bc2 {
alpha::compress_bc2(&rgba, mask, &mut output[..8]);
} else if self == Format::Bc3 {
alpha::compress_bc3(&rgba, mask, &mut output[..8]);
}
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);
}
}
fn decompress_block(self, block: &[u8]) -> [[u8; 4]; 16] {
let colour_offset = if self == Format::Bc1 { 0 } else { 8 };
let colour_block = &block[colour_offset..colour_offset + 8];
let mut rgba = colourblock::decompress(colour_block, self == Format::Bc1);
if self == Format::Bc2 {
alpha::decompress_bc2(&mut rgba, &block[..8]);
} else if self == Format::Bc3 {
alpha::decompress_bc3(&mut rgba, &block[..8]);
}
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_high = num_blocks(height);
let blocks_wide = num_blocks(width);
for y in 0..blocks_high {
for x in 0..blocks_wide {
let mut source_rgba = [[0u8; 4]; 16];
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;
}
}
}
let offset = x * block_size + y * blocks_wide * block_size;
let block = &mut output[offset..offset + block_size];
self.compress_block_masked(source_rgba, mask, params, block);
}
}
}
}
fn f32_to_i32_clamped(a: f32, limit: i32) -> i32 {
a.round().max(0.0).min(limit as f32) as i32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_storage_requirements_bc1_exact() {
let estimate = Format::Bc1.compressed_size(16, 32);
assert_eq!(estimate, 256);
}
#[test]
fn test_storage_requirements_bc1_padded() {
let estimate = Format::Bc1.compressed_size(15, 30);
assert_eq!(estimate, 256);
}
#[test]
fn test_storage_requirements_bc2_exact() {
let estimate = Format::Bc2.compressed_size(16, 32);
assert_eq!(estimate, 512);
}
#[test]
fn test_storage_requirements_bc2_padded() {
let estimate = Format::Bc2.compressed_size(15, 30);
assert_eq!(estimate, 512);
}
#[test]
fn test_storage_requirements_bc3_exact() {
let estimate = Format::Bc3.compressed_size(16, 32);
assert_eq!(estimate, 512);
}
#[test]
fn test_storage_requirements_bc3_padded() {
let estimate = Format::Bc3.compressed_size(15, 30);
assert_eq!(estimate, 512);
}
}