syeve 0.1.0

Simple yet efficient video encoding (lossless streaming codec)
Documentation
#[cfg(feature = "simd")]
use packed_simd_2::*;

#[cfg(feature = "simd")]
fn abs(v: i16x4) -> i16x4 {
	let mask = v >> 15;
	(v + mask) ^ mask
}

#[cfg(feature = "simd")]
fn if_then_else(c: i16x4, t: i16x4, e: i16x4) -> i16x4 {
	(c & t) | (e & !c)
}

fn paeth(a: u8, b: u8, c: u8) -> u8 {
	let (a, b, c) = (a as i16, b as i16, c as i16);

	let mut pa = b - c;
	let mut pb = a - c;
	let mut pc = pa + pb;

	pa = pa.abs();
	pb = pb.abs();
	pc = pc.abs();

	(if pa <= pb && pa <= pc {
		a
	} else if pb <= pa && pb <= pc {
		b
	} else {
		c
	} as u8)
}

#[allow(clippy::many_single_char_names)]
pub fn filter(size: (usize, usize), pixel_size: usize, pixels: &mut [u8], old_pixels: &mut [u8]) {
	assert!(size.0 < u32::MAX as usize);
	assert!(size.1 < u32::MAX as usize);
	assert!(pixel_size < u8::MAX as usize);
	assert_eq!(pixels.len(), size.0 * size.1 * pixel_size);

	assert_eq!(pixels.len(), old_pixels.len());

	for i in (size.0 * pixel_size..pixels.len()).rev() {
		// I think this is safe...
		let z = unsafe { old_pixels.get_unchecked_mut(i) };
		let a = *unsafe { pixels.get_unchecked(i - pixel_size) };
		let b = *unsafe { pixels.get_unchecked(i - size.0 * pixel_size) };
		let c = *unsafe { pixels.get_unchecked(i - (size.0 - 1) * pixel_size) };
		let x = unsafe { pixels.get_unchecked_mut(i) };

		let old_z = *z;
		*z = x.wrapping_sub(paeth(a, b, c));
		*x = z.wrapping_sub(old_z);
	}
	for i in (pixel_size..size.0 * pixel_size).rev() {
		let z = unsafe { old_pixels.get_unchecked_mut(i) };
		let a = *unsafe { pixels.get_unchecked(i - pixel_size) };
		let x = unsafe { pixels.get_unchecked_mut(i) };

		let old_z = *z;
		*z = x.wrapping_sub(a);
		*x = z.wrapping_sub(old_z);
	}
	if !pixels.is_empty() {
		for i in 0..pixel_size {
			let z = unsafe { old_pixels.get_unchecked_mut(i) };
			let x = unsafe { pixels.get_unchecked_mut(i) };

			let old_z = *z;
			*z = *x;
			*x = x.wrapping_sub(old_z);
		}
	}
}

#[cfg(feature = "simd")]
#[allow(clippy::many_single_char_names)]
pub fn filter_simd_4(
	size: (usize, usize),
	pixel_size: usize,
	pixels: &mut [u8],
	old_pixels: &mut [u8],
) {
	assert!(size.0 < u32::MAX as usize);
	assert!(size.1 < u32::MAX as usize);
	assert_eq!(pixel_size % 4, 0);
	assert!(pixel_size < u8::MAX as usize);
	assert_eq!(pixels.len(), size.0 * size.1 * pixel_size);

	assert_eq!(pixels.len(), old_pixels.len());

	for i in (size.0 * pixel_size..pixels.len()).step_by(4).rev() {
		let a = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		});
		let b = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - size.0 * pixel_size..i - size.0 * pixel_size + 4),
			)
		});
		let c =
			i16x4::from(unsafe {
				u8x4::from_slice_unaligned_unchecked(pixels.get_unchecked(
					i - (size.0 - 1) * pixel_size..i - (size.0 - 1) * pixel_size + 4,
				))
			});

		let mut pa = b - c;
		let mut pb = a - c;
		let mut pc = pa + pb;

		pa = abs(pa);
		pb = abs(pb);
		pc = abs(pc);

		let smallest = pa.min(pb).min(pc);
		let nearest = u8x4::from_cast(if_then_else(
			i16x4::from_cast(smallest.eq(pa)),
			a,
			if_then_else(i16x4::from_cast(smallest.eq(pb)), b, c),
		));

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let old_zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		let zv = xv - nearest;
		unsafe {
			zv.write_to_slice_unaligned_unchecked(z);
			(zv - old_zv).write_to_slice_unaligned_unchecked(x)
		};
	}
	for i in (pixel_size..size.0 * pixel_size).step_by(4).rev() {
		let a = unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		};

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let old_zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		let zv = xv - a;
		unsafe {
			zv.write_to_slice_unaligned_unchecked(z);
			(zv - old_zv).write_to_slice_unaligned_unchecked(x);
		}
	}
	if !pixels.is_empty() {
		let z = unsafe { old_pixels.get_unchecked_mut(0..4) };
		let x = unsafe { pixels.get_unchecked_mut(0..4) };

		let old_zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		unsafe {
			xv.write_to_slice_unaligned_unchecked(z);
			(xv - old_zv).write_to_slice_unaligned_unchecked(x);
		}
	}
}

#[cfg(feature = "simd")]
#[allow(clippy::many_single_char_names)]
pub fn filter_simd_3(
	size: (usize, usize),
	pixel_size: usize,
	pixels: &mut [u8],
	old_pixels: &mut [u8],
) {
	assert!(size.0 < u32::MAX as usize);
	assert!(size.1 < u32::MAX as usize);
	assert_eq!(pixel_size % 3, 0);
	assert!(pixel_size < u8::MAX as usize);
	assert_eq!(pixels.len(), size.0 * size.1 * pixel_size);

	assert_eq!(pixels.len(), old_pixels.len());

	for i in (size.0 * pixel_size..pixels.len()).step_by(3).rev() {
		let a = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		});
		let b = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - size.0 * pixel_size..i - size.0 * pixel_size + 4),
			)
		});
		let c =
			i16x4::from(unsafe {
				u8x4::from_slice_unaligned_unchecked(pixels.get_unchecked(
					i - (size.0 - 1) * pixel_size..i - (size.0 - 1) * pixel_size + 4,
				))
			});

		let mut pa = b - c;
		let mut pb = a - c;
		let mut pc = pa + pb;

		pa = abs(pa);
		pb = abs(pb);
		pc = abs(pc);

		let smallest = pa.min(pb).min(pc);
		let nearest = u8x4::from_cast(if_then_else(
			i16x4::from_cast(smallest.eq(pa)),
			a,
			if_then_else(i16x4::from_cast(smallest.eq(pb)), b, c),
		));

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let old_zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		let mut zv = xv - nearest;
		z[0] = unsafe { zv.extract_unchecked(0) };
		z[1] = unsafe { zv.extract_unchecked(1) };
		z[2] = unsafe { zv.extract_unchecked(2) };
		zv -= old_zv;
		x[0] = unsafe { zv.extract_unchecked(0) };
		x[1] = unsafe { zv.extract_unchecked(1) };
		x[2] = unsafe { zv.extract_unchecked(2) };
	}
	for i in (pixel_size..size.0 * pixel_size).step_by(3).rev() {
		let a = unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		};

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let old_zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		let mut zv = xv - a;
		z[0] = unsafe { zv.extract_unchecked(0) };
		z[1] = unsafe { zv.extract_unchecked(1) };
		z[2] = unsafe { zv.extract_unchecked(2) };
		zv -= old_zv;
		x[0] = unsafe { zv.extract_unchecked(0) };
		x[1] = unsafe { zv.extract_unchecked(1) };
		x[2] = unsafe { zv.extract_unchecked(2) };
	}
	if !pixels.is_empty() {
		let z = unsafe { old_pixels.get_unchecked_mut(0..4) };
		let x = unsafe { pixels.get_unchecked_mut(0..4) };

		let old_zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let mut xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		z[0] = unsafe { xv.extract_unchecked(0) };
		z[1] = unsafe { xv.extract_unchecked(1) };
		z[2] = unsafe { xv.extract_unchecked(2) };
		xv -= old_zv;
		x[0] = unsafe { xv.extract_unchecked(0) };
		x[1] = unsafe { xv.extract_unchecked(1) };
		x[2] = unsafe { xv.extract_unchecked(2) };
	}
}

#[allow(clippy::many_single_char_names)]
pub fn unfilter(size: (usize, usize), pixel_size: usize, pixels: &mut [u8], old_pixels: &mut [u8]) {
	assert!(size.0 < u32::MAX as usize);
	assert!(size.1 < u32::MAX as usize);
	assert!(pixel_size < u8::MAX as usize);
	assert_eq!(pixels.len(), size.0 * size.1 * pixel_size);

	assert_eq!(pixels.len(), old_pixels.len());

	if !pixels.is_empty() {
		for i in 0..pixel_size {
			let z = unsafe { old_pixels.get_unchecked_mut(i) };
			let x = unsafe { pixels.get_unchecked_mut(i) };

			*z = z.wrapping_add(*x);
			*x = *z;
		}
	}
	for i in pixel_size..size.0 * pixel_size {
		let z = unsafe { old_pixels.get_unchecked_mut(i) };
		let a = *unsafe { pixels.get_unchecked(i - pixel_size) };
		let x = unsafe { pixels.get_unchecked_mut(i) };

		*z = z.wrapping_add(*x);
		*x = z.wrapping_add(a);
	}
	for i in size.0 * pixel_size..pixels.len() {
		let z = unsafe { old_pixels.get_unchecked_mut(i) };
		let a = *unsafe { pixels.get_unchecked(i - pixel_size) };
		let b = *unsafe { pixels.get_unchecked(i - size.0 * pixel_size) };
		let c = *unsafe { pixels.get_unchecked(i - (size.0 - 1) * pixel_size) };
		let x = unsafe { pixels.get_unchecked_mut(i) };

		*z = z.wrapping_add(*x);
		*x = z.wrapping_add(paeth(a, b, c));
	}
}

#[cfg(feature = "simd")]
#[allow(clippy::many_single_char_names)]
pub fn unfilter_simd_4(
	size: (usize, usize),
	pixel_size: usize,
	pixels: &mut [u8],
	old_pixels: &mut [u8],
) {
	assert!(size.0 < u32::MAX as usize);
	assert!(size.1 < u32::MAX as usize);
	assert_eq!(pixel_size % 4, 0);
	assert!(pixel_size < u8::MAX as usize);
	assert_eq!(pixels.len(), size.0 * size.1 * pixel_size);

	assert_eq!(pixels.len(), old_pixels.len());

	if !pixels.is_empty() {
		let z = unsafe { old_pixels.get_unchecked_mut(0..4) };
		let x = unsafe { pixels.get_unchecked_mut(0..4) };

		let mut zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		zv += xv;
		unsafe {
			zv.write_to_slice_unaligned_unchecked(z);
			zv.write_to_slice_unaligned_unchecked(x);
		}
	}
	for i in (pixel_size..size.0 * pixel_size).step_by(4) {
		let a = unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		};

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let mut zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		zv += xv;
		unsafe {
			zv.write_to_slice_unaligned_unchecked(z);
			(zv + a).write_to_slice_unaligned_unchecked(x);
		}
	}
	for i in (size.0 * pixel_size..pixels.len()).step_by(4) {
		let a = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		});
		let b = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - size.0 * pixel_size..i - size.0 * pixel_size + 4),
			)
		});
		let c =
			i16x4::from(unsafe {
				u8x4::from_slice_unaligned_unchecked(pixels.get_unchecked(
					i - (size.0 - 1) * pixel_size..i - (size.0 - 1) * pixel_size + 4,
				))
			});

		let mut pa = b - c;
		let mut pb = a - c;
		let mut pc = pa + pb;

		pa = abs(pa);
		pb = abs(pb);
		pc = abs(pc);

		let smallest = pa.min(pb).min(pc);
		let nearest = u8x4::from_cast(if_then_else(
			i16x4::from_cast(smallest.eq(pa)),
			a,
			if_then_else(i16x4::from_cast(smallest.eq(pb)), b, c),
		));

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let mut zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		zv += xv;
		unsafe {
			zv.write_to_slice_unaligned_unchecked(z);
			(zv + nearest).write_to_slice_unaligned_unchecked(x);
		}
	}
}

#[cfg(feature = "simd")]
#[allow(clippy::many_single_char_names)]
pub fn unfilter_simd_3(
	size: (usize, usize),
	pixel_size: usize,
	pixels: &mut [u8],
	old_pixels: &mut [u8],
) {
	assert!(size.0 < u32::MAX as usize);
	assert!(size.1 < u32::MAX as usize);
	assert_eq!(pixel_size % 3, 0);
	assert!(pixel_size < u8::MAX as usize);
	assert_eq!(pixels.len(), size.0 * size.1 * pixel_size);

	assert_eq!(pixels.len(), old_pixels.len());

	if !pixels.is_empty() {
		let z = unsafe { old_pixels.get_unchecked_mut(0..4) };
		let x = unsafe { pixels.get_unchecked_mut(0..4) };

		let mut zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		zv += xv;
		z[0] = unsafe { zv.extract_unchecked(0) };
		z[1] = unsafe { zv.extract_unchecked(1) };
		z[2] = unsafe { zv.extract_unchecked(2) };
		x[0] = unsafe { zv.extract_unchecked(0) };
		x[1] = unsafe { zv.extract_unchecked(1) };
		x[2] = unsafe { zv.extract_unchecked(2) };
	}
	for i in (pixel_size..size.0 * pixel_size).step_by(3) {
		let a = unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		};

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let mut zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		zv += xv;
		z[0] = unsafe { zv.extract_unchecked(0) };
		z[1] = unsafe { zv.extract_unchecked(1) };
		z[2] = unsafe { zv.extract_unchecked(2) };
		zv += a;
		x[0] = unsafe { zv.extract_unchecked(0) };
		x[1] = unsafe { zv.extract_unchecked(1) };
		x[2] = unsafe { zv.extract_unchecked(2) };
	}
	for i in (size.0 * pixel_size..pixels.len()).step_by(3) {
		let a = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - pixel_size..i - pixel_size + 4),
			)
		});
		let b = i16x4::from(unsafe {
			u8x4::from_slice_unaligned_unchecked(
				pixels.get_unchecked(i - size.0 * pixel_size..i - size.0 * pixel_size + 4),
			)
		});
		let c =
			i16x4::from(unsafe {
				u8x4::from_slice_unaligned_unchecked(pixels.get_unchecked(
					i - (size.0 - 1) * pixel_size..i - (size.0 - 1) * pixel_size + 4,
				))
			});

		let mut pa = b - c;
		let mut pb = a - c;
		let mut pc = pa + pb;

		pa = abs(pa);
		pb = abs(pb);
		pc = abs(pc);

		let smallest = pa.min(pb).min(pc);
		let nearest = u8x4::from_cast(if_then_else(
			i16x4::from_cast(smallest.eq(pa)),
			a,
			if_then_else(i16x4::from_cast(smallest.eq(pb)), b, c),
		));

		let z = unsafe { old_pixels.get_unchecked_mut(i..i + 4) };
		let x = unsafe { pixels.get_unchecked_mut(i..i + 4) };

		let mut zv = unsafe { u8x4::from_slice_unaligned_unchecked(z) };
		let xv = unsafe { u8x4::from_slice_unaligned_unchecked(x) };

		zv += xv;
		z[0] = unsafe { zv.extract_unchecked(0) };
		z[1] = unsafe { zv.extract_unchecked(1) };
		z[2] = unsafe { zv.extract_unchecked(2) };
		zv += nearest;
		x[0] = unsafe { zv.extract_unchecked(0) };
		x[1] = unsafe { zv.extract_unchecked(1) };
		x[2] = unsafe { zv.extract_unchecked(2) };
	}
}

#[cfg(test)]
mod test {
	use super::*;

	use rand::{
		distributions::{Distribution, Uniform},
		Fill,
	};

	/// Allocate Vec without initializing
	pub fn valloc<T>(n: usize) -> Vec<T> {
		let mut v = Vec::with_capacity(n);
		unsafe {
			v.set_len(n);
		}
		v
	}

	#[test]
	fn test_filter_reversibility() {
		let maxpixels = 100;

		let mut pixels = valloc(maxpixels * maxpixels * 4);
		let mut pixels_save = valloc(maxpixels * maxpixels * 4);
		let mut old_pixels_enc = valloc(maxpixels * maxpixels * 4);
		let mut old_pixels_dec = valloc(maxpixels * maxpixels * 4);
		let size_range = Uniform::new(2, maxpixels);
		let pixel_size_range = Uniform::new(1, 5);
		let mut rng = rand::thread_rng();
		for _ in 0..100 {
			let size = (size_range.sample(&mut rng), size_range.sample(&mut rng));
			let pixel_size = pixel_size_range.sample(&mut rng);
			let len = size.0 * size.1 * pixel_size;

			old_pixels_enc[..len].fill(0);
			old_pixels_dec[..len].fill(0);

			for _ in 0..10 {
				pixels[..len].try_fill(&mut rng).unwrap();
				pixels_save[..len].copy_from_slice(&pixels[..len]);

				filter(
					size,
					pixel_size,
					&mut pixels[..len],
					&mut old_pixels_enc[..len],
				);
				unfilter(
					size,
					pixel_size,
					&mut pixels[..len],
					&mut old_pixels_dec[..len],
				);

				assert_eq!(&pixels[..len], &pixels_save[..len]);
			}
		}
	}

	#[test]
	fn test_filter_same_image() {
		let maxpixels = 100;

		let mut pixels = valloc(maxpixels * maxpixels * 4);
		let mut pixels_save = valloc(maxpixels * maxpixels * 4);
		let mut old_pixels = valloc(maxpixels * maxpixels * 4);
		let size_range = Uniform::new(2, maxpixels);
		let pixel_size_range = Uniform::new(1, 5);
		let mut rng = rand::thread_rng();
		for _ in 0..10 {
			let size = (size_range.sample(&mut rng), size_range.sample(&mut rng));
			let pixel_size = pixel_size_range.sample(&mut rng);
			let len = size.0 * size.1 * pixel_size;

			pixels[..len].try_fill(&mut rng).unwrap();
			pixels_save[..len].copy_from_slice(&pixels[..len]);

			old_pixels[..len].try_fill(&mut rng).unwrap();

			filter(size, pixel_size, &mut pixels[..len], &mut old_pixels[..len]);
			filter(
				size,
				pixel_size,
				&mut pixels_save[..len],
				&mut old_pixels[..len],
			);

			pixels_save[pixel_size..len]
				.iter()
				.for_each(|&o| assert_eq!(o, 0));
		}
	}

	#[cfg(feature = "simd")]
	#[test]
	fn test_filter_simd_4() {
		let maxpixels = 10;

		let mut pixels_sisd = valloc(maxpixels * maxpixels * 4);
		let mut pixels_simd = valloc(maxpixels * maxpixels * 4);
		let mut old_pixels_sisd = valloc(maxpixels * maxpixels * 4);
		let mut old_pixels_simd = valloc(maxpixels * maxpixels * 4);
		let size_range = Uniform::new(2, maxpixels);
		let mut rng = rand::thread_rng();
		for _ in 0..100 {
			let size = (size_range.sample(&mut rng), size_range.sample(&mut rng));
			let pixel_size = 4;
			let len = size.0 * size.1 * pixel_size;

			pixels_sisd[..len].try_fill(&mut rng).unwrap();
			pixels_simd[..len].copy_from_slice(&pixels_sisd[..len]);

			old_pixels_sisd[..len].try_fill(&mut rng).unwrap();
			old_pixels_simd[..len].copy_from_slice(&old_pixels_sisd[..len]);

			filter(
				size,
				pixel_size,
				&mut pixels_sisd[..len],
				&mut old_pixels_sisd[..len],
			);
			filter_simd_4(
				size,
				pixel_size,
				&mut pixels_simd[..len],
				&mut old_pixels_simd[..len],
			);

			assert_eq!(&pixels_sisd[..len], &pixels_simd[..len]);
			assert_eq!(&old_pixels_sisd[..len], &old_pixels_simd[..len]);
		}
		for _ in 0..100 {
			let size = (size_range.sample(&mut rng), size_range.sample(&mut rng));
			let pixel_size = 4;
			let len = size.0 * size.1 * pixel_size;

			pixels_sisd[..len].try_fill(&mut rng).unwrap();
			pixels_simd[..len].copy_from_slice(&pixels_sisd[..len]);

			old_pixels_sisd[..len].try_fill(&mut rng).unwrap();
			old_pixels_simd[..len].copy_from_slice(&old_pixels_sisd[..len]);

			unfilter(
				size,
				pixel_size,
				&mut pixels_sisd[..len],
				&mut old_pixels_sisd[..len],
			);
			unfilter_simd_4(
				size,
				pixel_size,
				&mut pixels_simd[..len],
				&mut old_pixels_simd[..len],
			);

			assert_eq!(&pixels_sisd[..len], &pixels_simd[..len]);
			assert_eq!(&old_pixels_sisd[..len], &old_pixels_simd[..len]);
		}
	}

	#[cfg(feature = "simd")]
	#[test]
	fn test_filter_simd_3() {
		let maxpixels = 10;

		let mut pixels_sisd = valloc(maxpixels * maxpixels * 3);
		let mut pixels_simd = valloc(maxpixels * maxpixels * 3);
		let mut old_pixels_sisd = valloc(maxpixels * maxpixels * 3);
		let mut old_pixels_simd = valloc(maxpixels * maxpixels * 3);
		let size_range = Uniform::new(2, maxpixels);
		let mut rng = rand::thread_rng();
		for _ in 0..100 {
			let size = (size_range.sample(&mut rng), size_range.sample(&mut rng));
			let pixel_size = 3;
			let len = size.0 * size.1 * pixel_size;

			pixels_sisd[..len].try_fill(&mut rng).unwrap();
			pixels_simd[..len].copy_from_slice(&pixels_sisd[..len]);

			old_pixels_sisd[..len].try_fill(&mut rng).unwrap();
			old_pixels_simd[..len].copy_from_slice(&old_pixels_sisd[..len]);

			filter(
				size,
				pixel_size,
				&mut pixels_sisd[..len],
				&mut old_pixels_sisd[..len],
			);
			filter_simd_3(
				size,
				pixel_size,
				&mut pixels_simd[..len],
				&mut old_pixels_simd[..len],
			);

			assert_eq!(&pixels_sisd[..len], &pixels_simd[..len]);
			assert_eq!(&old_pixels_sisd[..len], &old_pixels_simd[..len]);
		}
		for _ in 0..100 {
			let size = (size_range.sample(&mut rng), size_range.sample(&mut rng));
			let pixel_size = 3;
			let len = size.0 * size.1 * pixel_size;

			pixels_sisd[..len].try_fill(&mut rng).unwrap();
			pixels_simd[..len].copy_from_slice(&pixels_sisd[..len]);

			old_pixels_sisd[..len].try_fill(&mut rng).unwrap();
			old_pixels_simd[..len].copy_from_slice(&old_pixels_sisd[..len]);

			unfilter(
				size,
				pixel_size,
				&mut pixels_sisd[..len],
				&mut old_pixels_sisd[..len],
			);
			unfilter_simd_3(
				size,
				pixel_size,
				&mut pixels_simd[..len],
				&mut old_pixels_simd[..len],
			);

			assert_eq!(&pixels_sisd[..len], &pixels_simd[..len]);
			assert_eq!(&old_pixels_sisd[..len], &old_pixels_simd[..len]);
		}
	}
}