#![deny(missing_docs)]
use std::f32;
use std::mem;
pub enum Type {
Point,
Triangle,
Catrom,
Mitchell,
Lanczos3,
Custom(Filter),
}
pub struct Filter {
kernel: Box<dyn Fn(f32) -> f32>,
support: f32,
}
impl Filter {
#[must_use]
pub fn new(kernel: Box<dyn Fn(f32) -> f32>, support: f32) -> Self {
Self { kernel, support }
}
#[must_use]
pub fn new_cubic(b: f32, c: f32) -> Self {
Self::new(Box::new(move |x| cubic_bc(b, c, x)), 2.0)
}
#[must_use]
pub fn new_lanczos(radius: f32) -> Self {
Self::new(Box::new(move |x| lanczos(radius, x)), radius)
}
}
#[inline]
fn point_kernel(_: f32) -> f32 {
1.0
}
#[inline]
fn triangle_kernel(x: f32) -> f32 {
f32::max(1.0 - x.abs(), 0.0)
}
#[inline]
fn cubic_bc(b: f32, c: f32, x: f32) -> f32 {
let a = x.abs();
let k = if a < 1.0 {
(12.0 - 9.0 * b - 6.0 * c) * a.powi(3) +
(-18.0 + 12.0 * b + 6.0 * c) * a.powi(2) +
(6.0 - 2.0 * b)
} else if a < 2.0 {
(-b - 6.0 * c) * a.powi(3) +
(6.0 * b + 30.0 * c) * a.powi(2) +
(-12.0 * b - 48.0 * c) * a +
(8.0 * b + 24.0 * c)
} else {
0.0
};
k / 6.0
}
#[inline]
fn sinc(x: f32) -> f32 {
if x == 0.0 {
1.0
} else {
let a = x * f32::consts::PI;
a.sin() / a
}
}
#[inline]
fn lanczos(taps: f32, x: f32) -> f32 {
if x.abs() < taps {
sinc(x) * sinc(x / taps)
} else {
0.0
}
}
#[allow(non_snake_case)]
pub mod Pixel {
#[derive(Debug, Clone, Copy)]
pub struct Gray8;
#[derive(Debug, Clone, Copy)]
pub struct Gray16;
#[derive(Debug, Clone, Copy)]
pub struct RGB24;
#[derive(Debug, Clone, Copy)]
pub struct RGB48;
#[derive(Debug, Clone, Copy)]
pub struct RGBA;
#[derive(Debug, Clone, Copy)]
pub struct RGBA64;
}
pub trait PixelFormat: Copy {
type Accumulator: AsRef<[f32]> + AsMut<[f32]>;
type Subpixel: Copy + Into<f32>;
fn new_accum() -> Self::Accumulator;
fn into_subpixel(v: f32) -> Self::Subpixel;
fn get_size(&self) -> usize {
self.get_ncomponents() * mem::size_of::<Self::Subpixel>()
}
#[inline(always)]
fn get_ncomponents(&self) -> usize {
Self::new_accum().as_ref().len()
}
}
impl PixelFormat for Pixel::Gray8 {
type Accumulator = [f32; 1];
type Subpixel = u8;
#[must_use]
fn new_accum() -> Self::Accumulator {
[0.0; 1]
}
#[must_use]
fn into_subpixel(v: f32) -> Self::Subpixel {
Resizer::<Self>::pack_u8(v)
}
}
impl PixelFormat for Pixel::Gray16 {
type Accumulator = [f32; 1];
type Subpixel = u16;
#[must_use]
fn new_accum() -> Self::Accumulator {
[0.0; 1]
}
#[must_use]
fn into_subpixel(v: f32) -> Self::Subpixel {
Resizer::<Self>::pack_u16(v)
}
}
impl PixelFormat for Pixel::RGB24 {
type Accumulator = [f32; 3];
type Subpixel = u8;
#[must_use]
fn new_accum() -> Self::Accumulator {
[0.0; 3]
}
#[must_use]
fn into_subpixel(v: f32) -> Self::Subpixel {
Resizer::<Self>::pack_u8(v)
}
}
impl PixelFormat for Pixel::RGBA {
type Accumulator = [f32; 4];
type Subpixel = u8;
#[must_use]
fn new_accum() -> Self::Accumulator {
[0.0; 4]
}
#[must_use]
fn into_subpixel(v: f32) -> Self::Subpixel {
Resizer::<Self>::pack_u8(v)
}
}
impl PixelFormat for Pixel::RGB48 {
type Accumulator = [f32; 3];
type Subpixel = u16;
#[must_use]
fn new_accum() -> Self::Accumulator {
[0.0; 3]
}
#[must_use]
fn into_subpixel(v: f32) -> Self::Subpixel {
Resizer::<Self>::pack_u16(v)
}
}
impl PixelFormat for Pixel::RGBA64 {
type Accumulator = [f32; 4];
type Subpixel = u16;
#[must_use]
fn new_accum() -> Self::Accumulator {
[0.0; 4]
}
#[must_use]
fn into_subpixel(v: f32) -> Self::Subpixel {
Resizer::<Self>::pack_u16(v)
}
}
#[derive(Debug)]
pub struct Resizer<Pixel: PixelFormat> {
w1: usize,
h1: usize,
w2: usize,
h2: usize,
pix_fmt: Pixel,
tmp: Vec<f32>,
coeffs_w: Vec<CoeffsLine>,
coeffs_h: Vec<CoeffsLine>,
}
#[derive(Debug)]
struct CoeffsLine {
left: usize,
data: Vec<f32>,
}
impl<Pixel: PixelFormat> Resizer<Pixel> {
pub fn new(source_width: usize, source_heigth: usize, dest_width: usize, dest_height: usize, pixel_format: Pixel, filter_type: Type) -> Self {
let filter = match filter_type {
Type::Point => Filter::new(Box::new(point_kernel), 0.0),
Type::Triangle => Filter::new(Box::new(triangle_kernel), 1.0),
Type::Catrom => Filter::new_cubic(0.0, 0.5),
Type::Mitchell => Filter::new_cubic(1.0/3.0, 1.0/3.0),
Type::Lanczos3 => Filter::new_lanczos(3.0),
Type::Custom(f) => f,
};
Self {
w1: source_width,
h1: source_heigth,
w2: dest_width,
h2: dest_height,
pix_fmt: pixel_format,
tmp: vec![0.0; source_width * dest_height * pixel_format.get_ncomponents()],
coeffs_w: Self::calc_coeffs(source_width, dest_width, &filter),
coeffs_h: Self::calc_coeffs(source_heigth, dest_height, &filter),
}
}
fn calc_coeffs(s1: usize, s2: usize, f: &Filter) -> Vec<CoeffsLine> {
let ratio = s1 as f32 / s2 as f32;
let filter_scale = if ratio > 1.0 { ratio } else { 1.0 };
let filter_radius = (f.support * filter_scale).ceil();
let mut coeffs = Vec::with_capacity(s2);
for x2 in 0..s2 {
let x1 = (x2 as f32 + 0.5) * ratio - 0.5;
let left = (x1 - filter_radius).ceil() as isize;
let left = Self::clamp(left, 0, s1 as isize - 1) as usize;
let right = (x1 + filter_radius).floor() as isize;
let right = Self::clamp(right, 0, s1 as isize - 1) as usize;
let mut data = Vec::with_capacity(right + 1 - left);
let mut sum = 0.0;
for i in left..=right {
sum += (f.kernel)((i as f32 - x1) / filter_scale);
}
for i in left..=right {
let v = (f.kernel)((i as f32 - x1) / filter_scale);
data.push(v / sum);
}
coeffs.push(CoeffsLine { left, data });
}
coeffs
}
#[inline]
fn clamp<N: PartialOrd>(input: N, min: N, max: N) -> N {
if input > max {
max
} else if input < min {
min
} else {
input
}
}
#[inline]
fn pack_u8(v: f32) -> u8 {
if v > 255.0 {
255
} else if v < 0.0 {
0
} else {
v.round() as u8
}
}
#[inline]
fn pack_u16(v: f32) -> u16 {
if v > 65535.0 {
65535
} else if v < 0.0 {
0
} else {
v.round() as u16
}
}
fn sample_rows(&mut self, src: &[Pixel::Subpixel], stride: usize) {
let ncomp = self.pix_fmt.get_ncomponents();
let mut offset = 0;
for x1 in 0..self.w1 {
for y2 in 0..self.h2 {
let mut accum = Pixel::new_accum();
let line = &self.coeffs_h[y2];
for (i, &coeff) in line.data.iter().enumerate() {
let y0 = line.left + i;
let base = (y0 * stride + x1) * ncomp;
let src = &src[base..base + ncomp];
for (acc, &s) in accum.as_mut().iter_mut().zip(src) {
*acc += s.into() * coeff;
}
}
for &v in accum.as_ref().iter() {
self.tmp[offset] = v;
offset += 1;
}
}
}
}
fn sample_cols(&mut self, dst: &mut [Pixel::Subpixel]) {
let ncomp = self.pix_fmt.get_ncomponents();
let mut offset = 0;
for y2 in 0..self.h2 {
for x2 in 0..self.w2 {
let mut accum = Pixel::new_accum();
let line = &self.coeffs_w[x2];
for (i, &coeff) in line.data.iter().enumerate() {
let x0 = line.left + i;
let base = (x0 * self.h2 + y2) * ncomp;
let tmp = &self.tmp[base..base + ncomp];
for (acc, &p) in accum.as_mut().iter_mut().zip(tmp) {
*acc += p * coeff;
}
}
for &v in accum.as_ref().iter() {
dst[offset] = Pixel::into_subpixel(v);
offset += 1;
}
}
}
}
pub fn resize(&mut self, src: &[Pixel::Subpixel], dst: &mut [Pixel::Subpixel]) {
let stride = self.w1;
self.resize_stride(src, stride, dst)
}
pub fn resize_stride(&mut self, src: &[Pixel::Subpixel], src_stride: usize, dst: &mut [Pixel::Subpixel]) {
assert!(self.w1 <= src_stride);
assert!(src.len() >= src_stride * self.h1 * self.pix_fmt.get_ncomponents());
assert_eq!(dst.len(), self.w2 * self.h2 * self.pix_fmt.get_ncomponents());
self.sample_rows(src, src_stride);
self.sample_cols(dst)
}
}
pub fn new<Pixel: PixelFormat>(src_width: usize, src_height: usize, dest_width: usize, dest_height: usize, pixel_format: Pixel, filter_type: Type) -> Resizer<Pixel> {
Resizer::new(src_width, src_height, dest_width, dest_height, pixel_format, filter_type)
}
pub fn resize<Pixel: PixelFormat>(
src_width: usize, src_height: usize, dest_width: usize, dest_height: usize,
pixel_format: Pixel, filter_type: Type,
src: &[Pixel::Subpixel], dst: &mut [Pixel::Subpixel],
) {
Resizer::new(src_width, src_height, dest_width, dest_height, pixel_format, filter_type).resize(src, dst)
}
#[test]
fn pixel_sizes() {
assert_eq!(Pixel::RGB24.get_ncomponents(), 3);
assert_eq!(Pixel::RGB24.get_size(), 3 * 1);
assert_eq!(Pixel::RGBA.get_size(), 4 * 1);
assert_eq!(Pixel::RGB48.get_ncomponents(), 3);
assert_eq!(Pixel::RGB48.get_size(), 3 * 2);
assert_eq!(Pixel::RGBA64.get_ncomponents(), 4);
assert_eq!(Pixel::RGBA64.get_size(), 4 * 2);
}
#[test]
fn resize_stride() {
let mut r = new(2, 2, 3, 4, Pixel::Gray16, Type::Triangle);
let mut dst = vec![0; 12];
r.resize_stride(&[
65535,65535,1,2,
65535,65535,3,4,
], 4, &mut dst);
assert_eq!(&dst, &[65535; 12]);
}