diff --git a/squish/src/colourblock.rs b/squish/src/colourblock.rs index 649cfcc..85e592b 100644 --- a/squish/src/colourblock.rs +++ b/squish/src/colourblock.rs @@ -20,7 +20,7 @@ // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use core::{mem, u8}; +use core::mem; use crate::Format; use crate::math::{f32_to_i32_clamped, Vec3}; @@ -110,7 +110,7 @@ pub fn write4(start: &Vec3, end: &Vec3, indices: &[u8; 16], block: &mut [u8], fo /// Convert a little endian 565-packed colour to 8bpc RGBA fn unpack_565(packed: &[u8], format: Format) -> [u8; 4] { - assert!(packed.len() == 2); + assert_eq!(packed.len(), 2); // get components let mut tmp = [0u8; 2]; tmp.copy_from_slice(&packed[0..2]); @@ -133,7 +133,7 @@ fn unpack_565(packed: &[u8], format: Format) -> [u8; 4] { /// Decompress a BC1/2/3 block to 4x4 RGBA pixels pub fn decompress(bytes: &[u8], format: Format) -> [[u8; 4]; 16] { - assert!(bytes.len() == 8); + assert_eq!(bytes.len(), 8); let is_gcn = format == Format::Bc1Gcn; let is_bc1 = format == Format::Bc1 || is_gcn; diff --git a/squish/src/colourfit/cluster.rs b/squish/src/colourfit/cluster.rs index 8714ef6..ba0fcc4 100644 --- a/squish/src/colourfit/cluster.rs +++ b/squish/src/colourfit/cluster.rs @@ -151,17 +151,17 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { fn compress3(&mut self) { let count = self.colourset.count(); - let two = Vec4::new(2.0, 2.0, 2.0, 2.0); - let one = Vec4::new(1.0, 1.0, 1.0, 1.0); - let half_half2 = Vec4::new(0.5, 0.5, 0.5, 0.25); - let zero = Vec4::new(0.0, 0.0, 0.0, 0.0); - let half = Vec4::new(0.5, 0.5, 0.5, 0.5); - let grid = Vec4::new(31.0, 63.0, 31.0, 0.0); - let gridrcp = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0); + const TWO: Vec4 = Vec4::new(2.0, 2.0, 2.0, 2.0); + const ONE: Vec4 = Vec4::new(1.0, 1.0, 1.0, 1.0); + const HALF_HALF2: Vec4 = Vec4::new(0.5, 0.5, 0.5, 0.25); + const ZERO: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.0); + const HALF: Vec4 = Vec4::new(0.5, 0.5, 0.5, 0.5); + const GRID: Vec4 = Vec4::new(31.0, 63.0, 31.0, 0.0); + const GRID_RCP: Vec4 = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0); // check all possible clusters and iterate on the total order - let mut best_start = zero; - let mut best_end = zero; + let mut best_start = ZERO; + let mut best_end = ZERO; let mut best_error = self.best_error; let mut best_indices = [0u8; 16]; let mut best_iteration = 0; @@ -178,11 +178,11 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { } // first cluster [0,i) is at the start - let mut part0 = zero; + let mut part0 = ZERO; for i in 0..count { // second cluster [i,j) is halfway along let mut part1 = - if i == 0 { self.points_weights[0] } else { zero }; + if i == 0 { self.points_weights[0] } else { ZERO }; let jmin = if i == 0 { 1 } else { i }; for j in jmin..=count { @@ -190,13 +190,13 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { let part2 = self.xsum_wsum - part1 - part0; // compute least squares term directly - let alphax_sum = part1 * half_half2 + part0; + let alphax_sum = part1 * HALF_HALF2 + part0; let alpha2_sum = alphax_sum.splat_w(); - let betax_sum = part1 * half_half2 + part2; + let betax_sum = part1 * HALF_HALF2 + part2; let beta2_sum = betax_sum.splat_w(); - let alphabeta_sum = (part1 * half_half2).splat_w(); + let alphabeta_sum = (part1 * HALF_HALF2).splat_w(); // compute the least-squares optimal points let factor = @@ -205,16 +205,16 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { let b = ((betax_sum * alpha2_sum) - alphax_sum * alphabeta_sum) * factor; // clamp to the grid - let a = one.min(zero.max(a)); - let b = one.min(zero.max(b)); - let a = (grid * a + half).truncate() * gridrcp; - let b = (grid * b + half).truncate() * gridrcp; + let a = ONE.min(ZERO.max(a)); + let b = ONE.min(ZERO.max(b)); + let a = (GRID * a + HALF).truncate() * GRID_RCP; + let b = (GRID * b + HALF).truncate() * GRID_RCP; // compute the error (we skip the constant xxsum) let e1 = (a * a) * alpha2_sum + (b * b * beta2_sum); let e2 = (a * b * alphabeta_sum) - a * alphax_sum; let e3 = e2 - b * betax_sum; - let e4 = two * e3 + e1; + let e4 = TWO * e3 + e1; // apply the channel weights to the error term let e5 = e4 * self.weights; @@ -276,19 +276,19 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { fn compress4(&mut self) { let count = self.colourset.count(); - let two = Vec4::new(2.0, 2.0, 2.0, 2.0); - let one = Vec4::new(1.0, 1.0, 1.0, 1.0); - let onethird_onethird2 = Vec4::new(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 9.0); - let twothirds_twothirds2 = Vec4::new(2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 4.0 / 9.0); - let twoninths = Vec4::new(2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0); - let zero = Vec4::new(0.0, 0.0, 0.0, 0.0); - let half = Vec4::new(0.5, 0.5, 0.5, 0.5); - let grid = Vec4::new(31.0, 63.0, 31.0, 0.0); - let gridrcp = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0); + const TWO: Vec4 = Vec4::new(2.0, 2.0, 2.0, 2.0); + const ONE: Vec4 = Vec4::new(1.0, 1.0, 1.0, 1.0); + const ONETHIRD_ONETHIRD2: Vec4 = Vec4::new(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 9.0); + const TWOTHIRDS_TWOTHIRDS2: Vec4 = Vec4::new(2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 4.0 / 9.0); + const TWONINTHS: Vec4 = Vec4::new(2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0); + const ZERO: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.0); + const HALF: Vec4 = Vec4::new(0.5, 0.5, 0.5, 0.5); + const GRID: Vec4 = Vec4::new(31.0, 63.0, 31.0, 0.0); + const GRID_RCP: Vec4 = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0); // check all possible clusters and iterate on the total order - let mut best_start = zero; - let mut best_end = zero; + let mut best_start = ZERO; + let mut best_end = ZERO; let mut best_error = self.best_error; let mut best_indices = [0u8; 16]; let mut best_iteration = 0; @@ -306,17 +306,17 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { } // first cluster [0,i) is at the start - let mut part0 = zero; + let mut part0 = ZERO; for i in 0..count { // second cluster [i,j) is one third along - let mut part1 = zero; + let mut part1 = ZERO; for j in i..=count { // third cluster [j, k) is two thirds along let mut part2 = if j == 0 { self.points_weights[0] } else { - zero + ZERO }; let kmin = if j == 0 { 1 } else { j }; @@ -326,14 +326,14 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { // compute least squares terms directly let alphax_sum = - part2 * onethird_onethird2 + (part1 * twothirds_twothirds2 + part0); + part2 * ONETHIRD_ONETHIRD2 + (part1 * TWOTHIRDS_TWOTHIRDS2 + part0); let alpha2_sum = alphax_sum.splat_w(); let betax_sum = - part1 * onethird_onethird2 + (part2 * twothirds_twothirds2 + part3); + part1 * ONETHIRD_ONETHIRD2 + (part2 * TWOTHIRDS_TWOTHIRDS2 + part3); let beta2_sum = betax_sum.splat_w(); - let alphabeta_sum = twoninths * (part1 + part2).splat_w(); + let alphabeta_sum = TWONINTHS * (part1 + part2).splat_w(); // compute the least-squares optimal points let factor = @@ -342,16 +342,16 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> { let b = ((betax_sum * alpha2_sum) - alphax_sum * alphabeta_sum) * factor; // clamp to the grid - let a = one.min(zero.max(a)); - let b = one.min(zero.max(b)); - let a = (grid * a + half).truncate() * gridrcp; - let b = (grid * b + half).truncate() * gridrcp; + let a = ONE.min(ZERO.max(a)); + let b = ONE.min(ZERO.max(b)); + let a = (GRID * a + HALF).truncate() * GRID_RCP; + let b = (GRID * b + HALF).truncate() * GRID_RCP; // compute the error (we skip the constant xxsum) let e1 = (a * a) * alpha2_sum + (b * b * beta2_sum); let e2 = (a * b * alphabeta_sum) - a * alphax_sum; let e3 = e2 - b * betax_sum; - let e4 = two * e3 + e1; + let e4 = TWO * e3 + e1; // apply the channel weights to the error term let e5 = e4 * self.weights; diff --git a/squish/src/colourfit/range.rs b/squish/src/colourfit/range.rs index dd090ae..a172812 100644 --- a/squish/src/colourfit/range.rs +++ b/squish/src/colourfit/range.rs @@ -20,12 +20,10 @@ // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use core::f32; - +use crate::{ColourWeights, Format}; use crate::colourblock; use crate::colourset::ColourSet; use crate::math::{Sym3x3, Vec3}; -use crate::{ColourWeights, Format}; use super::ColourFitImpl; @@ -174,11 +172,13 @@ impl<'a> ColourFitImpl<'a> for RangeFit<'a> { fn compress4(&mut self) { // create a codebook + let one_third = if self.format == Format::Bc1Gcn { 3.0 / 8.0 } else { 1.0 / 3.0 }; + let two_thirds = if self.format == Format::Bc1Gcn { 5.0 / 8.0 } else { 2.0 / 3.0 }; let codes = [ self.start, self.end, - self.start * (2.0 / 3.0) + self.end * (1.0 / 3.0), - self.start * (1.0 / 3.0) + self.end * (2.0 / 3.0), + self.start * two_thirds + self.end * one_third, + self.start * one_third + self.end * two_thirds, ]; if self.compression_helper(&codes) { diff --git a/squish/src/colourfit/single.rs b/squish/src/colourfit/single.rs index 2c8f0e8..49b8cac 100644 --- a/squish/src/colourfit/single.rs +++ b/squish/src/colourfit/single.rs @@ -20,8 +20,6 @@ // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use core::u32; - use crate::colourblock; use crate::colourset::ColourSet; use crate::math::{f32_to_i32_clamped, Vec3}; diff --git a/squish/src/lib.rs b/squish/src/lib.rs index 3a2f4b1..1103b33 100644 --- a/squish/src/lib.rs +++ b/squish/src/lib.rs @@ -25,17 +25,18 @@ #![no_std] +#[cfg(feature="rayon")] +use rayon::prelude::*; + +use crate::colourfit::{ClusterFit, ColourFit, RangeFit, SingleColourFit}; +use crate::colourset::ColourSet; + 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::*; - /// Defines a compression format #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Format { @@ -78,6 +79,9 @@ pub const COLOUR_WEIGHTS_UNIFORM: ColourWeights = [1.0, 1.0, 1.0]; /// Weights based on the perceived brightness of each colour channel pub const COLOUR_WEIGHTS_PERCEPTUAL: ColourWeights = [0.2126, 0.7152, 0.0722]; +/// Perceptual weights used for GCN/CMPR encoding +pub const COLOUR_WEIGHTS_PERCEPTUAL_GCN: ColourWeights = [0.3086, 0.6094, 0.0820]; + #[derive(Clone, Copy)] pub struct Params { /// The compression algorithm to be used @@ -199,7 +203,7 @@ impl Format { ) { // compress alpha block(s) match self { - Format::Bc1 => {}, + Format::Bc1 | Format::Bc1Gcn => {}, 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]), @@ -211,11 +215,11 @@ impl Format { // compress colour block if the format has one match self { - Format::Bc1 | Format::Bc2 | Format::Bc3 => { + Format::Bc1 | Format::Bc1Gcn | Format::Bc2 | Format::Bc3 => { // create the minimal point set let colours = ColourSet::new(&rgba, mask, self, params.weigh_colour_by_alpha); - let colour_offset = if self == Format::Bc1 { 0 } else { 8 }; + let colour_offset = if self == Format::Bc1 || self == Format::Bc1Gcn { 0 } else { 8 }; let colour_block = &mut output[colour_offset..colour_offset + 8]; // compress with appropriate compression algorithm @@ -246,9 +250,9 @@ impl Format { let mut rgba; // decompress colour block match self { - Format::Bc1 | Format::Bc2 | Format::Bc3 => { + Format::Bc1 | Format::Bc1Gcn | Format::Bc2 | Format::Bc3 => { // get reference to the actual colour block - let colour_offset = if self == Format::Bc1 { 0 } else { 8 }; + let colour_offset = if self == Format::Bc1 || self == Format::Bc1Gcn { 0 } else { 8 }; let colour_block = &block[colour_offset..colour_offset + 8]; // decompress colour block @@ -261,7 +265,7 @@ impl Format { // decompress alpha block(s) match self { - Format::Bc1 => (), + Format::Bc1 | Format::Bc1Gcn => (), Format::Bc2 => alpha::decompress_bc2(&mut rgba, &block[..8]), Format::Bc3 => alpha::decompress_bc3(&mut rgba, 3, &block[..8]), Format::Bc4 => { @@ -352,6 +356,8 @@ mod tests { 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::Bc1Gcn.compressed_size(16, 32), 256); + assert_eq!(Format::Bc1Gcn.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); @@ -391,18 +397,6 @@ mod tests { assert_eq!(output_actual, decoded_block_gray_4x4_as_rgba()); } - #[test] - fn test_storage_requirements_bc1_gcn_exact() { - let estimate = Format::Bc1Gcn.compressed_size(16, 32); - assert_eq!(estimate, 256); - } - - #[test] - fn test_storage_requirements_bc1_gcn_padded() { - let estimate = Format::Bc1Gcn.compressed_size(15, 30); - assert_eq!(estimate, 256); - } - #[test] fn test_bc1_compression_gray() { fn test(algorithm: Algorithm) { @@ -431,23 +425,30 @@ mod tests { // A colour test-pattern (RGB) with the first row in one colour, // the second in another and the third and last row in a third colour. - static DECODED_BLOCK_COLOUR_4X4: &[u8] = &[ + static DECODED_BLOCK_COLOUR_4X4: [u8; 4 * 4 * 3] = [ 255, 150, 74, 255, 150, 74, 255, 150, 74, 255, 150, 74, // row 0 255, 120, 52, 255, 120, 52, 255, 120, 52, 255, 120, 52, // row 1 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 2 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 3 ]; + static DECODED_BLOCK_COLOUR_4X4_GCN: [u8; 4 * 4 * 3] = [ + 255, 150, 74, 255, 150, 74, 255, 150, 74, 255, 150, 74, // row 0 + 255, 121, 53, 255, 121, 53, 255, 121, 53, 255, 121, 53, // row 1 + 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 2 + 255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 3 + ]; // BC1 data created with AMD Compressonator v4.1.5083 and is the same as libsquish static ENCODED_BLOCK_COLOUR_4X4: [u8; 8] = [0xA9, 0xFC, 0x45, 0xFB, 0x00, 0xFF, 0x55, 0x55]; + static ENCODED_BLOCK_COLOUR_4X4_GCN: [u8; 8] = [0xFC, 0xA9, 0xFB, 0x45, 0x00, 0xFF, 0x55, 0x55]; - fn decoded_block_colour_4x4_as_rgba() -> [u8; 4 * 4 * 4] { + fn rgb_to_rgba(block: &[u8; 4 * 4 * 3]) -> [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]; // R - output[i * 4 + 1] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 1]; // G - output[i * 4 + 2] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 2]; // B - output[i * 4 + 3] = 0xFF; //A + output[i * 4 + 0] = block[i * 3 + 0]; // R + output[i * 4 + 1] = block[i * 3 + 1]; // G + output[i * 4 + 2] = block[i * 3 + 2]; // B + output[i * 4 + 3] = 0xFF; // A } output } @@ -457,7 +458,7 @@ mod tests { 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()); + assert_eq!(output_actual, rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4)); } #[test] @@ -465,7 +466,7 @@ mod tests { fn test(algorithm: Algorithm) { let mut output_actual = [0u8; 8]; Format::Bc1.compress( - &decoded_block_colour_4x4_as_rgba(), + &rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4), 4, 4, Params { @@ -484,4 +485,35 @@ mod tests { test(Algorithm::RangeFit); test(Algorithm::IterativeClusterFit); } + + #[test] + fn test_bc1gcn_decompression_colour() { + let encoded: [u8; 8] = ENCODED_BLOCK_COLOUR_4X4_GCN; + let mut output_actual = [0u8; 4 * 4 * 4]; + Format::Bc1Gcn.decompress(&encoded, 4, 4, &mut output_actual); + assert_eq!(output_actual, rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4_GCN)); + } + + #[test] + fn test_bc1gcn_compression_colour() { + fn test(algorithm: Algorithm) { + let mut output_actual = [0u8; 8]; + Format::Bc1Gcn.compress( + &rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4_GCN), + 4, + 4, + Params { + algorithm, + weights: COLOUR_WEIGHTS_UNIFORM, + weigh_colour_by_alpha: false, + }, + &mut output_actual, + ); + let output_expected = ENCODED_BLOCK_COLOUR_4X4_GCN; + assert_eq!(output_actual, output_expected); + } + + // only RangeFit implemented for GCN + test(Algorithm::RangeFit); + } } diff --git a/squish/src/math/vec3.rs b/squish/src/math/vec3.rs index 2a593fa..1a2ca0a 100644 --- a/squish/src/math/vec3.rs +++ b/squish/src/math/vec3.rs @@ -32,19 +32,19 @@ pub struct Vec3 { } impl Vec3 { - pub fn new(x: f32, y: f32, z: f32) -> Self { + pub const fn new(x: f32, y: f32, z: f32) -> Self { Self { x, y, z } } - pub fn x(&self) -> f32 { + pub const fn x(&self) -> f32 { self.x } - pub fn y(&self) -> f32 { + pub const fn y(&self) -> f32 { self.y } - pub fn z(&self) -> f32 { + pub const fn z(&self) -> f32 { self.z } diff --git a/squish/src/math/vec4.rs b/squish/src/math/vec4.rs index 2c3ca80..00ad59d 100644 --- a/squish/src/math/vec4.rs +++ b/squish/src/math/vec4.rs @@ -33,43 +33,43 @@ pub struct Vec4 { } impl Vec4 { - pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self { Self { x, y, z, w } } - pub fn x(&self) -> f32 { + pub const fn x(&self) -> f32 { self.x } - pub fn y(&self) -> f32 { + pub const fn y(&self) -> f32 { self.y } - pub fn z(&self) -> f32 { + pub const fn z(&self) -> f32 { self.z } - pub fn w(&self) -> f32 { + pub const fn w(&self) -> f32 { self.w } - pub fn to_vec3(&self) -> Vec3 { + pub const fn to_vec3(&self) -> Vec3 { Vec3::new(self.x, self.y, self.z) } - pub fn splat_x(&self) -> Vec4 { + pub const fn splat_x(&self) -> Vec4 { Vec4::new(self.x, self.x, self.x, self.x) } - pub fn splat_y(&self) -> Vec4 { + pub const fn splat_y(&self) -> Vec4 { Vec4::new(self.y, self.y, self.y, self.y) } - pub fn splat_z(&self) -> Vec4 { + pub const fn splat_z(&self) -> Vec4 { Vec4::new(self.z, self.z, self.z, self.z) } - pub fn splat_w(&self) -> Vec4 { + pub const fn splat_w(&self) -> Vec4 { Vec4::new(self.w, self.w, self.w, self.w) }