diff --git a/Cargo.toml b/Cargo.toml index 578fab1..b7b9fd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,8 @@ anyhow = "1.0" png = "0.17" walkdir = "2.3" cfg-if = "1.0" +rand = "0.8" +libqoi = { path = "libqoi"} [lib] name = "qoi_fast" diff --git a/tests/test_gen.rs b/tests/test_gen.rs new file mode 100644 index 0000000..713a650 --- /dev/null +++ b/tests/test_gen.rs @@ -0,0 +1,172 @@ +mod common; + +use cfg_if::cfg_if; +use rand::{rngs::StdRng, Rng, SeedableRng}; + +use libqoi::{qoi_decode, qoi_encode}; +use qoi_fast::{decode_to_vec, encode_to_vec}; + +use self::common::hash; + +struct GenState { + index: [[u8; N]; 64], + pixels: Vec, + prev: [u8; N], + len: usize, +} + +impl GenState { + pub fn with_capacity(capacity: usize) -> Self { + Self { + index: [[0; N]; 64], + pixels: Vec::with_capacity(capacity * N), + prev: Self::zero(), + len: 0, + } + } + pub fn write(&mut self, px: [u8; N]) { + self.index[hash(px) as usize] = px; + for i in 0..N { + self.pixels.push(px[i]); + } + self.prev = px; + self.len += 1; + } + + pub fn pick_from_index(&self, rng: &mut impl Rng) -> [u8; N] { + self.index[rng.gen_range(0_usize..64)] + } + + pub fn zero() -> [u8; N] { + let mut px = [0; N]; + if N >= 4 { + px[3] = 0xff; + } + px + } +} + +struct ImageGen { + p_new: f64, + p_index: f64, + p_repeat: f64, + p_diff: f64, + p_luma: f64, +} + +impl ImageGen { + pub fn new_random(rng: &mut impl Rng) -> Self { + let p = [0; 6].map(|_| rng.gen::()); + let t = p.iter().sum::(); + Self { + p_new: p[0] / t, + p_index: p[1] / t, + p_repeat: p[2] / t, + p_diff: p[3] / t, + p_luma: p[4] / t, + } + } + + pub fn generate(&self, rng: &mut impl Rng, channels: usize, min_len: usize) -> Vec { + match channels { + 3 => self.generate_const::<_, 3>(rng, min_len), + 4 => self.generate_const::<_, 4>(rng, min_len), + _ => panic!(), + } + } + + fn generate_const(&self, rng: &mut R, min_len: usize) -> Vec { + let mut s = GenState::::with_capacity(min_len); + let zero = GenState::::zero(); + + while s.len < min_len { + let mut p = rng.gen_range(0.0..1.0); + + if p < self.p_new { + s.write([0; N].map(|_| rng.gen())); + continue; + } + p -= self.p_new; + + if p < self.p_index { + let px = s.pick_from_index(rng); + s.write(px); + continue; + } + p -= self.p_index; + + if p < self.p_repeat { + let px = s.prev; + let n_repeat = rng.gen_range(1_usize..=70); + for _ in 0..n_repeat { + s.write(px); + } + continue; + } + p -= self.p_repeat; + + if p < self.p_diff { + let mut px = s.prev; + let d = [0; 3].map(|_| rng.gen_range(0_u8..4).wrapping_sub(2)); + px[0] = px[0].wrapping_add(d[0]); + px[1] = px[1].wrapping_add(d[0]); + px[2] = px[2].wrapping_add(d[0]); + s.write(px); + continue; + } + p -= self.p_diff; + + if p < self.p_luma { + let mut px = s.prev; + let vg = rng.gen_range(0_u8..64).wrapping_sub(32); + let vr = rng.gen_range(0_u8..16).wrapping_sub(8).wrapping_add(vg); + let vb = rng.gen_range(0_u8..16).wrapping_sub(8).wrapping_add(vg); + px[0] = px[0].wrapping_add(vr); + px[1] = px[1].wrapping_add(vg); + px[2] = px[2].wrapping_add(vb); + s.write(px); + continue; + } + + s.write(zero); + } + + s.pixels + } +} + +#[test] +fn test_generated() { + let mut rng = StdRng::seed_from_u64(0); + + let mut n_pixels = 0; + while n_pixels < 20_000_000 { + let min_len = rng.gen_range(1..=5000); + let channels = rng.gen_range(3..=4); + let gen = ImageGen::new_random(&mut rng); + let img = gen.generate(&mut rng, channels, min_len); + let size = img.len() / channels; + + let encoded = encode_to_vec(&img, size as _, 1).unwrap(); + let decoded = decode_to_vec(&encoded).unwrap().1; + assert_eq!(&img, &decoded, "qoi-fast: roundtrip fail"); + + let encoded_c = qoi_encode(&img, size as _, 1, channels as _).unwrap(); + let decoded_c = qoi_decode(encoded_c.as_ref(), channels as _).unwrap().1; + assert_eq!(&img, decoded_c.as_ref(), "qoi.h: roundtrip fail"); + + cfg_if! { + if #[cfg(feature = "reference")] { + assert_eq!(&encoded, encoded_c.as_ref(), "qoi-fast[reference] doesn't match qoi.h"); + } + } + + let encoded_internal_decoded_c = qoi_decode(encoded.as_ref(), channels as _).unwrap().1; + assert_eq!(encoded_internal_decoded_c.as_ref(), &img, "qoi-fast -> qoi.h: roundtrip fail"); + + let encoded_c_decoded_internal = decode_to_vec(encoded_c.as_ref()).unwrap().1; + assert_eq!(&encoded_c_decoded_internal, &img, "qoi.h -> qoi-fast: roundtrip fail"); + + n_pixels += size; + } +}