Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
220 lines (172 sloc) 5.74 KB
//! A Rust library for allocation-limited computation of the Discrete Cosine Transform.
//!
//! 1D DCTs are allocation-free but 2D requires allocation.
//!
//! Features:
//!
//! * `simd`: use SIMD types to speed computation (2D DCT only)
//! * `cos-approx`: use a Taylor series approximation of cosine instead of the stdlib
//! implementation (which is usually much slower but also higher precision)
use std::f64::consts::{PI, SQRT_2};
use std::ops::Range;
/// An allocation-free one-dimensional Discrete Cosine Transform.
///
/// Each iteration produces the next DCT value in the sequence.
#[derive(Clone, Debug)]
pub struct DCT1D<'a> {
data: &'a [f64],
curr: Range<usize>,
}
impl<'a> DCT1D<'a> {
/// Create a new DCT 1D adaptor from a 1D vector of data.
pub fn new(data: &[f64]) -> DCT1D {
let curr = 0 .. data.len();
DCT1D {
data: data,
curr: curr,
}
}
// Converted from the C implementation here:
// http://unix4lyfe.org/dct/listing2.c
// Source page:
// http://unix4lyfe.org/dct/ (Accessed 8/10/2014)
fn next_dct_val(&mut self) -> Option<f64> {
self.curr.next().map(|u| {
let mut z = 0.0;
let data_len = self.data.len();
for (x_idx, &x) in self.data.iter().enumerate() {
z += x * cos(
PI * u as f64 * (2 * x_idx + 1) as f64
/ (2 * data_len) as f64
);
}
if u == 0 {
z *= 1.0 / SQRT_2;
}
z / 2.0
})
}
}
impl<'a> Iterator for DCT1D<'a> {
type Item = f64;
fn next(&mut self) -> Option<f64> {
self.next_dct_val()
}
}
/// An implementation of cosine that switches to a Taylor-series approximation when throughput is
/// preferred over precision.
#[inline(always)]
pub fn cos(x: f64) -> f64 {
// This branch should be optimized out.
if cfg!(feature = "cos-approx") {
// Normalize to [0, pi] or else the Taylor series spits out very wrong results.
let x = (x.abs() + PI) % (2.0 * PI) - PI;
// Approximate the cosine of `val` using a 4-term Taylor series.
// Can be expanded for higher precision.
let x2 = x.powi(2);
let x4 = x.powi(4);
let x6 = x.powi(6);
let x8 = x.powi(8);
1.0 - (x2 / 2.0) + (x4 / 24.0) - (x6 / 720.0) + (x8 / 40320.0)
} else {
x.cos()
}
}
/// Perform a 2D DCT on a 1D-packed vector with a given rowstride.
///
/// E.g. a vector of length 9 with a rowstride of 3 will be processed as a 3x3 matrix.
///
/// Returns a vector of the same size packed in the same way.
pub fn dct_2d(packed_2d: &[f64], rowstride: usize) -> Vec<f64> {
assert_eq!(packed_2d.len() % rowstride, 0);
let mut row_dct: Vec<f64> = packed_2d
.chunks(rowstride)
.flat_map(DCT1D::new)
.collect();
swap_rows_columns(&mut row_dct, rowstride);
let mut column_dct: Vec<f64> = packed_2d
.chunks(rowstride)
.flat_map(DCT1D::new)
.collect();
swap_rows_columns(&mut column_dct, rowstride);
column_dct
}
fn swap_rows_columns(data: &mut [f64], rowstride: usize) {
let height = data.len() / rowstride;
for y in 0 .. height {
for x in 0 .. rowstride {
data.swap(y * rowstride + x, x * rowstride + y);
}
}
}
#[cfg_attr(all(test, feature = "cos-approx"), test)]
#[cfg_attr(not(all(test, feature = "cos-approx")), allow(dead_code))]
fn test_cos_approx() {
const ERROR: f64 = 0.05;
fn test_cos_approx(x: f64) {
let approx = cos(x);
let cos = x.cos();
assert!(
approx.abs_sub(x.cos()) <= ERROR,
"Approximation cos({x}) = {approx} was outside a tolerance of {error}; control value: {cos}",
x = x, approx = approx, error = ERROR, cos = cos,
);
}
let test_values = [PI, PI / 2.0, PI / 4.0, 1.0, -1.0, 2.0 * PI, 3.0 * PI, 4.0 / 3.0 * PI];
for &x in &test_values {
test_cos_approx(x);
test_cos_approx(-x);
}
}
/*
#[cfg(feature = "simd")]
mod dct_simd {
use simdty::f64x2;
use std::f64::consts::{PI, SQRT_2};
macro_rules! valx2 ( ($val:expr) => ( ::simdty::f64x2($val, $val) ) );
const PI: f64x2 = valx2!(PI);
const ONE_DIV_SQRT_2: f64x2 = valx2!(1 / SQRT_2);
const SQRT_2: f64x2 = valx2!(SQRT_2);
pub dct_rows(vals: &[Vec<f64>]) -> Vec<Vec<f64>> {
let mut out = Vec::with_capacity(vals.len());
for pair in vals.iter().chunks(2) {
if pair.len() == 2 {
let vals = pair[0].iter().cloned().zip(pair[1].iter().cloned())
.map(f64x2)
.collect();
dct_1dx2(vals);
}
}
fn dct_1dx2(vec: Vec<f64x2>) -> Vec<f64x2> {
let mut out = Vec::with_capacity(vec.len());
for u in 0 .. vec.len() {
let mut z = valx2!(0.0);
for x in 0 .. vec.len() {
z += vec[x] * cos_approx(
PI * valx2!(
u as f64 * (2 * x + 1) as f64
/ (2 * vec.len()) as f64
)
);
}
if u == 0 {
z *= ONE_DIV_SQRT_2;
}
out.insert(u, z / valx2!(2.0));
}
out
}
fn cos_approx(x2: f64x2) -> f64x2 {
#[inline(always)]
fn powi(val: f64x2, pow: i32) -> f64x2 {
unsafe { llvmint::powi_v2f64(val, pow) }
}
let x2 = powi(val, 2);
let x4 = powi(val, 4);
let x6 = powi(val, 6);
let x8 = powi(val, 8);
valx2!(1.0) - (x2 / valx2!(2.0)) + (x4 / valx2!(24.0))
- (x6 / valx2!(720.0)) + (x8 / valx2!(40320.0))
}
}
*/