stream_dct/
lib.rs

1//! A Rust library for allocation-limited computation of the Discrete Cosine Transform.
2//!
3//! 1D DCTs are allocation-free but 2D requires allocation.
4//!
5//! Features:
6//!
7//! * `simd`: use SIMD types to speed computation (2D DCT only)
8//! * `cos-approx`: use a Taylor series approximation of cosine instead of the stdlib
9//! implementation (which is usually much slower but also higher precision)
10
11use std::f64::consts::{PI, SQRT_2};
12use std::ops::Range;
13
14/// An allocation-free one-dimensional Discrete Cosine Transform.
15///
16/// Each iteration produces the next DCT value in the sequence.
17#[derive(Clone, Debug)]
18pub struct DCT1D<'a> {
19    data: &'a [f64],
20    curr: Range<usize>,
21}
22
23impl<'a> DCT1D<'a> {
24    /// Create a new DCT 1D adaptor from a 1D vector of data.
25    pub fn new(data: &[f64]) -> DCT1D {
26        let curr = 0 .. data.len();
27
28        DCT1D {
29            data: data,
30            curr: curr,
31        }
32    }
33
34    // Converted from the C implementation here:
35    // http://unix4lyfe.org/dct/listing2.c
36    // Source page:
37    // http://unix4lyfe.org/dct/ (Accessed 8/10/2014)
38    fn next_dct_val(&mut self) -> Option<f64> {
39        self.curr.next().map(|u| {
40            let mut z = 0.0;
41
42            let data_len = self.data.len();
43
44            for (x_idx, &x) in self.data.iter().enumerate() {
45                z += x * cos(
46                    PI * u as f64 * (2 * x_idx + 1) as f64 
47                    / (2 * data_len) as f64
48                );
49            } 
50
51            if u == 0 {
52                z *= 1.0 / SQRT_2;
53            }
54
55            z / 2.0
56        })
57    }
58}
59
60impl<'a> Iterator for DCT1D<'a> {
61    type Item = f64;
62
63    fn next(&mut self) -> Option<f64> {
64        self.next_dct_val()
65    }
66}
67
68#[inline(always)]
69fn cos(x: f64) -> f64 {
70    if cfg!(feature = "cos-approx") {
71        let x = (x.abs() + PI) % (2.0 * PI) - PI;
72
73        // Approximate the cosine of `val` using a 4-term Taylor series.
74        // Can be expanded for higher precision.
75        let x2 = x.powi(2);
76        let x4 = x.powi(4);
77        let x6 = x.powi(6);
78        let x8 = x.powi(8);
79
80        1.0 - (x2 / 2.0) + (x4 / 24.0) - (x6 / 720.0) + (x8 / 40320.0)
81    } else {
82        x.cos()
83    }
84}
85
86/// Perform a 2D DCT on a 1D-packed vector with a given rowstride.
87///
88/// E.g. a vector of length 9 with a rowstride of 3 will be processed as a 3x3 matrix.
89///
90/// Returns a vector of the same size packed in the same way.
91pub fn dct_2d(packed_2d: &[f64], rowstride: usize) -> Vec<f64> {
92    assert_eq!(packed_2d.len() % rowstride, 0);
93
94    let mut row_dct: Vec<f64> = packed_2d
95        .chunks(rowstride)
96        .flat_map(DCT1D::new)
97        .collect();
98
99    swap_rows_columns(&mut row_dct, rowstride);
100
101    let mut column_dct: Vec<f64> = packed_2d
102        .chunks(rowstride)
103        .flat_map(DCT1D::new)
104        .collect();
105
106    swap_rows_columns(&mut column_dct, rowstride);
107
108    column_dct
109}
110
111fn swap_rows_columns(data: &mut [f64], rowstride: usize) {
112    let height = data.len() / rowstride;
113
114    for y in 0 .. height {
115        for x in 0 .. rowstride {
116            data.swap(y * rowstride + x, x * rowstride + y);
117        }
118    }    
119}
120
121#[cfg_attr(all(test, feature = "cos-approx"), test)]
122#[cfg_attr(not(all(test, feature = "cos-approx")), allow(dead_code))]
123fn test_cos_approx() {
124    use std::f64::consts::PI;
125
126    const ERROR: f64 = 0.05;
127
128    fn test_cos_approx(x: f64) {
129        let approx = cos(x);
130        let cos = x.cos();
131
132        assert!(
133            approx.abs_sub(x.cos()) <= ERROR, 
134            "Approximation cos({x}) = {approx} was outside a tolerance of {error}; control value: {cos}",
135            x = x, approx = approx, error = ERROR, cos = cos,
136        );
137    }
138
139    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];
140
141    for &x in &test_values {
142        test_cos_approx(x);
143        test_cos_approx(-x);
144    }
145}
146
147/*
148#[cfg(feature = "simd")]
149mod dct_simd {
150    use simdty::f64x2;
151
152    use std::f64::consts::{PI, SQRT_2};
153    
154    macro_rules! valx2 ( ($val:expr) => ( ::simdty::f64x2($val, $val) ) );
155
156    const PI: f64x2 = valx2!(PI);
157    const ONE_DIV_SQRT_2: f64x2 = valx2!(1 / SQRT_2);
158    const SQRT_2: f64x2 = valx2!(SQRT_2);
159
160    pub dct_rows(vals: &[Vec<f64>]) -> Vec<Vec<f64>> {
161        let mut out = Vec::with_capacity(vals.len());
162
163        for pair in vals.iter().chunks(2) {
164            if pair.len() == 2 {
165                let vals = pair[0].iter().cloned().zip(pair[1].iter().cloned())
166                    .map(f64x2)
167                    .collect();
168
169                dct_1dx2(vals);
170
171
172        
173        }
174    }
175
176    fn dct_1dx2(vec: Vec<f64x2>) -> Vec<f64x2> {
177        let mut out = Vec::with_capacity(vec.len());
178
179        for u in 0 .. vec.len() {
180            let mut z = valx2!(0.0);
181
182            for x in 0 .. vec.len() {
183                z += vec[x] * cos_approx(
184                    PI * valx2!(
185                        u as f64 * (2 * x + 1) as f64 
186                            / (2 * vec.len()) as f64
187                    )
188                );
189            }
190
191            if u == 0 {
192                z *= ONE_DIV_SQRT_2;
193            }
194
195            out.insert(u, z / valx2!(2.0));
196        }
197
198        out 
199    }
200
201    fn cos_approx(x2: f64x2) -> f64x2 {
202        #[inline(always)]
203        fn powi(val: f64x2, pow: i32) -> f64x2 {
204            unsafe { llvmint::powi_v2f64(val, pow) }
205        }
206
207        let x2 = powi(val, 2);
208        let x4 = powi(val, 4);
209        let x6 = powi(val, 6);
210        let x8 = powi(val, 8);
211
212        valx2!(1.0) - (x2 / valx2!(2.0)) + (x4 / valx2!(24.0)) 
213            - (x6 / valx2!(720.0)) + (x8 / valx2!(40320.0))
214    }
215}
216*/
217