core/scale: add proper rescaling
authorKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 23 May 2023 17:36:04 +0000 (19:36 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 25 May 2023 15:51:26 +0000 (17:51 +0200)
nihav-core/src/scale/mod.rs
nihav-core/src/scale/scale.rs [deleted file]
nihav-core/src/scale/scale/mod.rs [new file with mode: 0644]

index 6c1daa9f0f71597a71cb0b36d8ebbccab1cfe85b..a79232d3e96136d34bfcd2d62f9d6bb7c215cb94 100644 (file)
@@ -595,4 +595,60 @@ mod test {
         assert_eq!(odata[paloff + 1], 129);
         assert_eq!(odata[paloff + 2], 170);
     }
+    #[test]
+    fn test_scale_modes() {
+        const IN_DATA: [[u8; 6]; 2] = [
+            [0xFF, 0xC0, 0x40, 0x00, 0x40, 0xC0],
+            [0x00, 0x40, 0xC0, 0xFF, 0xC0, 0x40]
+        ];
+        const TEST_DATA: &[(&str, [[u8; 9]; 3])] = &[
+            ("nn",
+               [[0xFF, 0xC0, 0x40, 0xFF, 0xC0, 0x40, 0x00, 0x40, 0xC0],
+                [0xFF, 0xC0, 0x40, 0xFF, 0xC0, 0x40, 0x00, 0x40, 0xC0],
+                [0x00, 0x40, 0xC0, 0x00, 0x40, 0xC0, 0xFF, 0xC0, 0x40]]),
+            ("bilin",
+               [[0xFF, 0xC0, 0x40, 0x55, 0x6A, 0x95, 0x00, 0x40, 0xC0],
+                [0x55, 0x6A, 0x95, 0x8D, 0x86, 0x78, 0xAA, 0x95, 0x6A],
+                [0x00, 0x40, 0xC0, 0xAA, 0x95, 0x6A, 0xFF, 0xC0, 0x40]]),
+            ("bicubic",
+               [[0xFF, 0xC0, 0x40, 0x4B, 0x65, 0x9A, 0x00, 0x36, 0xC9],
+                [0x4B, 0x65, 0x9A, 0x94, 0x8A, 0x74, 0xB3, 0x9D, 0x61],
+                [0x00, 0x36, 0xC9, 0xBA, 0x9D, 0x61, 0xFF, 0xD3, 0x2B]]),
+            ("lanczos",
+               [[0xFF, 0xC0, 0x40, 0x4C, 0x66, 0x98, 0x00, 0x31, 0xCD],
+                [0x4C, 0x66, 0x98, 0x91, 0x88, 0x74, 0xB1, 0x9D, 0x5F],
+                [0x00, 0x31, 0xCD, 0xBB, 0x9D, 0x5F, 0xFF, 0xDD, 0x1E]]),
+            ("lanczos2",
+               [[0xFF, 0xC0, 0x40, 0x4F, 0x68, 0x9B, 0x00, 0x35, 0xCD],
+                [0x4F, 0x68, 0x9B, 0x96, 0x8D, 0x79, 0xB3, 0xA0, 0x64],
+                [0x00, 0x35, 0xCD, 0xBE, 0xA1, 0x65, 0xFF, 0xDC, 0x28]]),
+        ];
+
+        let in_pic = alloc_video_buffer(NAVideoInfo::new(2, 2, false, RGB24_FORMAT), 3).unwrap();
+        if let Some(ref mut vbuf) = in_pic.get_vbuf() {
+            let stride = vbuf.get_stride(0);
+            let data = vbuf.get_data_mut().unwrap();
+            for (dline, rline) in data.chunks_mut(stride).zip(IN_DATA.iter()) {
+                dline[..6].copy_from_slice(rline);
+            }
+        } else {
+            panic!("wrong format");
+        }
+        let mut out_pic = alloc_video_buffer(NAVideoInfo::new(3, 3, false, RGB24_FORMAT), 3).unwrap();
+        let ifmt = get_scale_fmt_from_pic(&in_pic);
+        let ofmt = get_scale_fmt_from_pic(&out_pic);
+        for (method, ref_data) in TEST_DATA.iter() {
+            fill_pic(&mut out_pic, 0);
+            let mut scaler = NAScale::new_with_options(ifmt, ofmt, &[("scaler".to_string(), method.to_string())]).unwrap();
+            scaler.convert(&in_pic, &mut out_pic).unwrap();
+            let obuf = out_pic.get_vbuf().unwrap();
+            let ostride = obuf.get_stride(0);
+            let odata = obuf.get_data();
+            for (oline, rline) in odata.chunks(ostride).zip(ref_data.iter()) {
+                for (&a, &b) in oline.iter().zip(rline.iter()) {
+                    assert_eq!(a, b);
+                }
+            }
+        }
+    }
 }
diff --git a/nihav-core/src/scale/scale.rs b/nihav-core/src/scale/scale.rs
deleted file mode 100644 (file)
index a0bd0ca..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-use super::*;
-use super::kernel::Kernel;
-
-struct NNResampler {}
-
-impl NNResampler {
-    fn new() -> Self { Self{} }
-}
-
-#[allow(clippy::comparison_chain)]
-fn scale_line<T:Copy>(src: &[T], dst: &mut [T], src_w: usize, dst_w: usize) {
-    if src_w == dst_w {
-        (&mut dst[..dst_w]).copy_from_slice(&src[..dst_w]);
-    } else if src_w < dst_w {
-        if dst_w % src_w == 0 {
-            let step = dst_w / src_w;
-            for (out, srcv) in dst.chunks_exact_mut(step).take(src_w).zip(src.iter()) {
-                for el in out.iter_mut() {
-                    *el = *srcv;
-                }
-            }
-        } else {
-            let mut pos = 0;
-            for out in dst.iter_mut().take(dst_w) {
-                *out = src[pos / dst_w];
-                pos += src_w;
-            }
-        }
-    } else {
-        if dst_w % src_w == 0 {
-            let step = src_w / dst_w;
-            for (out, srcv) in dst.iter_mut().take(dst_w).zip(src.iter().step_by(step)) {
-                *out = *srcv;
-            }
-        } else {
-            let mut pos = 0;
-            for out in dst.iter_mut().take(dst_w) {
-                *out = src[pos / dst_w];
-                pos += src_w;
-            }
-        }
-    }
-}
-
-fn fill_plane<T: Copy>(dst: &mut [T], w: usize, h: usize, stride: usize, val: T) {
-    for row in dst.chunks_mut(stride).take(h) {
-        for el in row.iter_mut().take(w) {
-            *el = val;
-        }
-    }
-}
-
-macro_rules! scale_loop {
-    ($sbuf:expr, $dbuf:expr) => {
-            let fmt = $sbuf.get_info().get_format();
-            let ncomp = fmt.get_num_comp();
-            for comp in 0..ncomp {
-                let istride = $sbuf.get_stride(comp);
-                let dstride = $dbuf.get_stride(comp);
-                let (sw, sh) = $sbuf.get_dimensions(comp);
-                let (dw, dh) = $dbuf.get_dimensions(comp);
-                let ioff = $sbuf.get_offset(comp);
-                let mut doff = $dbuf.get_offset(comp);
-                let src = $sbuf.get_data();
-                let dst = $dbuf.get_data_mut().unwrap();
-                for y in 0..dh {
-                    let sy = y * sh / dh;
-                    let soff = ioff + sy * istride;
-                    scale_line(&src[soff..], &mut dst[doff..], sw, dw);
-                    doff += dstride;
-                }
-            }
-            let dfmt = $dbuf.get_info().get_format();
-            let ndcomp = dfmt.get_num_comp();
-            if ndcomp > ncomp {
-                if !fmt.alpha && dfmt.alpha {
-                    let acomp = ndcomp - 1;
-                    let dstride = $dbuf.get_stride(acomp);
-                    let (dw, dh) = $dbuf.get_dimensions(acomp);
-                    let doff = $dbuf.get_offset(acomp);
-                    let dst = $dbuf.get_data_mut().unwrap();
-                    fill_plane(&mut dst[doff..], dw, dh, dstride, 0);
-                }
-                if fmt.model.is_yuv() && ((!fmt.alpha && ncomp == 1) || (fmt.alpha && ncomp == 2)) && ndcomp >= 3 {
-                    let uval = 1 << (dfmt.comp_info[1].unwrap().depth - 1);
-                    let vval = 1 << (dfmt.comp_info[2].unwrap().depth - 1);
-
-                    let ustride = $dbuf.get_stride(1);
-                    let vstride = $dbuf.get_stride(2);
-                    let (uw, uh) = $dbuf.get_dimensions(1);
-                    let (vw, vh) = $dbuf.get_dimensions(2);
-                    let uoff = $dbuf.get_offset(1);
-                    let voff = $dbuf.get_offset(2);
-                    let dst = $dbuf.get_data_mut().unwrap();
-                    fill_plane(&mut dst[uoff..], uw, uh, ustride, uval);
-                    fill_plane(&mut dst[voff..], vw, vh, vstride, vval);
-                }
-            }
-    };
-}
-
-impl Kernel for NNResampler {
-    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo, _options: &[(String, String)]) -> ScaleResult<NABufferType> {
-        let res = alloc_video_buffer(NAVideoInfo::new(dest_fmt.width, dest_fmt.height, false, in_fmt.fmt), 3);
-        if res.is_err() { return Err(ScaleError::AllocError); }
-        Ok(res.unwrap())
-    }
-    fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) {
-        if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
-            scale_loop!(sbuf, dbuf);
-        } else if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf16(), pic_out.get_vbuf16()) {
-            scale_loop!(sbuf, dbuf);
-        } else if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf32(), pic_out.get_vbuf32()) {
-            scale_loop!(sbuf, dbuf);
-        } else {
-            unreachable!();
-        }
-    }
-}
-
-pub fn create_scale() -> Box<dyn Kernel> {
-    Box::new(NNResampler::new())
-}
-
diff --git a/nihav-core/src/scale/scale/mod.rs b/nihav-core/src/scale/scale/mod.rs
new file mode 100644 (file)
index 0000000..0a62e0f
--- /dev/null
@@ -0,0 +1,470 @@
+use super::*;
+use super::kernel::Kernel;
+
+trait ResizeLine<T> {
+    fn resize_line(&mut self, src: &[T], src_len: usize, sstep: usize, dst: &mut [T], dst_len: usize, dstep: usize);
+}
+
+trait CustomFrom<T> {
+    fn cvt_from(val: T) -> Self;
+}
+
+impl CustomFrom<usize> for u8 {
+    fn cvt_from(val: usize) -> Self { val as u8 }
+}
+impl CustomFrom<usize> for u16 {
+    fn cvt_from(val: usize) -> Self { val as u16 }
+}
+impl CustomFrom<u8> for usize {
+    fn cvt_from(val: u8) -> Self { usize::from(val) }
+}
+impl CustomFrom<u16> for usize {
+    fn cvt_from(val: u16) -> Self { usize::from(val) }
+}
+impl CustomFrom<f32> for u8 {
+    fn cvt_from(val: f32) -> Self { val.max(0.0).min(255.0) as u8 }
+}
+impl CustomFrom<f32> for u16 {
+    fn cvt_from(val: f32) -> Self { val.max(0.0).min(65535.0) as u16 }
+}
+impl CustomFrom<u8> for f32 {
+    fn cvt_from(val: u8) -> Self { val as f32 }
+}
+impl CustomFrom<u16> for f32 {
+    fn cvt_from(val: u16) -> Self { val as f32 }
+}
+
+trait CustomInto<T:Copy> {
+    fn cvt_into(self) -> T;
+}
+
+impl<T:Copy, U:Copy> CustomInto<U> for T where U: CustomFrom<T> {
+    fn cvt_into(self) -> U { U::cvt_from(self) }
+}
+
+#[derive(Clone,Copy,Default)]
+struct FracPos {
+    ipos:   usize,
+    frac:   usize,
+}
+
+impl FracPos {
+    fn new() -> Self { Self::default() }
+    fn add(&mut self, frac: Fraction) {
+        self.frac += frac.num;
+        while self.frac >= frac.den {
+            self.frac -= frac.den;
+            self.ipos += 1;
+        }
+    }
+}
+
+#[derive(Clone,Copy,PartialEq)]
+struct Fraction {
+    num:    usize,
+    den:    usize,
+}
+
+impl Fraction {
+    fn new(a: usize, b: usize) -> Self {
+        let cur_gcd = gcd(a, b);
+        Self {
+            num:    a / cur_gcd,
+            den:    b / cur_gcd,
+        }
+    }
+    fn is_one(self) -> bool { self.num == 1 && self.den == 1 }
+}
+
+fn gcd(mut a: usize, mut b: usize) -> usize {
+    while a != 0 && b != 0 {
+        if a > b {
+            a -= b;
+        } else {
+            b -= a;
+        }
+    }
+    a.max(b)
+}
+
+
+#[derive(Clone)]
+struct NNResampler {}
+
+impl NNResampler {
+    fn new() -> Self { Self{} }
+}
+
+impl<T:Copy> ResizeLine<T> for NNResampler {
+    fn resize_line(&mut self, src: &[T], src_len: usize, sstep: usize, dst: &mut [T], dst_len: usize, dstep: usize) {
+        let frac = Fraction::new(src_len, dst_len);
+        let mut pos = FracPos::new();
+        for el in dst.chunks_mut(dstep).take(dst_len) {
+            el[0] = src[pos.ipos * sstep];
+            pos.add(frac);
+        }
+    }
+}
+
+#[derive(Clone)]
+struct BilinResize {}
+
+impl BilinResize {
+    fn new() -> Self { Self {} }
+}
+
+impl<T> ResizeLine<T> for BilinResize
+where
+    T: Copy+CustomFrom<usize>+CustomInto<usize>
+{
+    fn resize_line(&mut self, src: &[T], src_len: usize, sstep: usize, dst: &mut [T], dst_len: usize, dstep: usize) {
+        let mut pos = FracPos::new();
+        let frac = Fraction::new(src_len, dst_len);
+
+        for el in dst.chunks_mut(dstep).take(dst_len) {
+            let spos0 = pos.ipos * sstep;
+            if pos.frac == 0 {
+                el[0] = src[spos0];
+            } else {
+                let spos1 = (pos.ipos + 1).min(src_len - 1) * sstep;
+                let s0: usize = T::cvt_into(src[spos0]);
+                let s1: usize = T::cvt_into(src[spos1]);
+                el[0] = usize::cvt_into((s0 * (frac.den - pos.frac) + s1 * pos.frac) / frac.den);
+            }
+            pos.add(frac);
+        }
+    }
+}
+
+#[derive(Clone)]
+struct BicubicResize {
+    last_frac:  Fraction,
+    ccache:     Vec<[f32; 4]>,
+}
+
+impl BicubicResize {
+    fn new() -> Self {
+        Self {
+            last_frac:  Fraction::new(1, 1),
+            ccache:     Vec::new(),
+        }
+    }
+    fn gen_coeffs(frac: f32) -> [f32; 4] {
+        let frac2 = frac * frac;
+        let frac3 = frac2 * frac;
+
+        [      -0.5 * frac +       frac2 - 0.5 * frac3,
+          1.0              - 2.5 * frac2 + 1.5 * frac3,
+                0.5 * frac + 2.0 * frac2 - 1.5 * frac3,
+                            -0.5 * frac2 + 0.5 * frac3 ]
+    }
+    fn gen_cache(den: usize) -> Vec<[f32; 4]> {
+        let mut cache = Vec::with_capacity(den);
+        for i in 1..den {
+            cache.push(Self::gen_coeffs((i as f32) / (den as f32)));
+        }
+        cache
+    }
+}
+
+impl<T> ResizeLine<T> for BicubicResize
+where
+    T: Copy+CustomFrom<f32>+CustomInto<f32>
+{
+    fn resize_line(&mut self, src: &[T], src_len: usize, sstep: usize, dst: &mut [T], dst_len: usize, dstep: usize) {
+        let frac = Fraction::new(src_len, dst_len);
+        if frac != self.last_frac {
+            self.last_frac = frac;
+            self.ccache = Self::gen_cache(frac.den);
+        }
+        let mut pos = FracPos::new();
+        let end = (src_len - 1) * sstep;
+        for el in dst.chunks_mut(dstep).take(dst_len) {
+            let spos0 = pos.ipos * sstep;
+            el[0] = if pos.frac == 0 {
+                    src[spos0]
+                } else {
+                    let spos1 = (spos0 + sstep).min(end);
+                    let spos2 = (spos1 + sstep).min(end);
+                    let sposm = spos0.saturating_sub(sstep);
+                    let sm = T::cvt_into(src[sposm]);
+                    let s0 = T::cvt_into(src[spos0]);
+                    let s1 = T::cvt_into(src[spos1]);
+                    let s2 = T::cvt_into(src[spos2]);
+
+                    let coeffs = &self.ccache[pos.frac - 1];
+                    T::cvt_from(sm * coeffs[0] + s0 * coeffs[1] + s1 * coeffs[2] + s2 * coeffs[3])
+                };
+            pos.add(frac);
+        }
+    }
+}
+
+#[derive(Clone)]
+struct LanczosResize {
+    last_frac:  Fraction,
+    order:      usize,
+    ccache:     Vec<Vec<f32>>,
+}
+
+impl LanczosResize {
+    fn new(order: usize) -> Self {
+        Self {
+            last_frac:  Fraction::new(1, 1),
+            order,
+            ccache:     Vec::new(),
+        }
+    }
+    fn get_coeffs(num: usize, den: usize, order: usize, coeffs: &mut [f32]) {
+        let norm = std::f32::consts::PI * std::f32::consts::PI;
+        let frac = (num as f32) / (den as f32);
+        let a = order as f32;
+        for i in 0..(order * 2) {
+            let x = frac - ((i as f32) + 1.0 - a);
+            let fp = std::f32::consts::PI * (x as f32);
+            coeffs[i] = a * fp.sin() * (fp / a).sin() / (norm * (x as f32) * (x as f32));
+        }
+    }
+    fn create_cache(order: usize, den: usize) -> Vec<Vec<f32>> {
+        let mut cache = Vec::with_capacity(den);
+        for i in 1..den {
+            let mut entry = vec![0.0; order * 2];
+            Self::get_coeffs(i, den, order, &mut entry);
+            cache.push(entry);
+        }
+        cache
+    }
+}
+
+impl<T> ResizeLine<T> for LanczosResize
+where
+    T: Copy+CustomFrom<f32>+CustomInto<f32>
+{
+    fn resize_line(&mut self, src: &[T], src_len: usize, sstep: usize, dst: &mut [T], dst_len: usize, dstep: usize) {
+        let frac = Fraction::new(src_len, dst_len);
+        if frac != self.last_frac {
+            self.last_frac = frac;
+            self.ccache = Self::create_cache(self.order, frac.den);
+        }
+
+        let mut pos = FracPos::new();
+
+        for el in dst.chunks_mut(dstep).take(dst_len) {
+            if pos.frac == 0 {
+                el[0] = src[pos.ipos * sstep];
+            } else {
+                let coeffs = &self.ccache[pos.frac - 1];
+                let mut sum = 0.0;
+                for (x, &coef) in coeffs.iter().enumerate() {
+                    let cpos = (pos.ipos + 1 + x).saturating_sub(self.order).min(src_len - 1) * sstep;
+                    sum += T::cvt_into(src[cpos]) * coef;
+                }
+                el[0] = T::cvt_from(sum);
+            }
+            pos.add(frac);
+        }
+    }
+}
+
+macro_rules! scale_loop {
+    ($sbuf:expr, $tbuf:expr, $dbuf:expr, $scalers:expr) => {
+            let fmt = $sbuf.get_info().get_format();
+            let dfmt = $dbuf.get_info().get_format();
+            let ndcomp = dfmt.get_num_comp();
+            let ncomp = fmt.get_num_comp().min(ndcomp);
+
+            for comp in 0..ncomp {
+                let istride = $sbuf.get_stride(comp);
+                let dstride = $dbuf.get_stride(comp);
+                let (sw, sh) = $sbuf.get_dimensions(comp);
+                let (dw, dh) = $dbuf.get_dimensions(comp);
+                let ioff = $sbuf.get_offset(comp);
+                let doff = $dbuf.get_offset(comp);
+                let src = $sbuf.get_data();
+                let dst = $dbuf.get_data_mut().unwrap();
+                let tstride = (dw + 15) & !15;
+
+                let cur_frac = Fraction::new(sw, dw);
+                if !cur_frac.is_one() {
+                    let mut idx = $scalers.len();
+                    for (i, &(frac, _)) in $scalers.iter().enumerate() {
+                        if frac == cur_frac {
+                            idx = i;
+                            break;
+                        }
+                    }
+                    let resizer = &mut $scalers[idx].1;
+                    for (dline, sline) in $tbuf.chunks_mut(tstride).zip(src[ioff..].chunks(istride)).take(dh) {
+                        resizer.resize_line(sline, sw, 1, dline, dw, 1);
+                    }
+                } else {
+                    for (dline, sline) in $tbuf.chunks_mut(tstride).zip(src[ioff..].chunks(istride)).take(dh) {
+                        dline[..dw].copy_from_slice(&sline[..sw]);
+                    }
+                }
+
+                let cur_frac = Fraction::new(sh, dh);
+                if !cur_frac.is_one() {
+                    let mut idx = $scalers.len();
+                    for (i, &(frac, _)) in $scalers.iter().enumerate() {
+                        if frac == cur_frac {
+                            idx = i;
+                            break;
+                        }
+                    }
+                    let resizer = &mut $scalers[idx].1;
+                    for x in 0..dw {
+                        resizer.resize_line(&$tbuf[x..], sh, tstride, &mut dst[doff + x..], dh, dstride);
+                    }
+                } else {
+                    for (dline, sline) in dst[doff..].chunks_mut(dstride).zip($tbuf.chunks(tstride)).take(dh) {
+                        dline[..dw].copy_from_slice(&sline[..dw]);
+                    }
+                }
+            }
+    };
+}
+
+type Resizer<T> = (Fraction, Box<dyn ResizeLine<T>>);
+
+struct Scaler {
+    resizers8:  Vec<Resizer<u8>>,
+    resizers16: Vec<Resizer<u16>>,
+    tmp8:       Vec<u8>,
+    tmp16:      Vec<u16>,
+}
+
+fn set_resizer<T, F>(dst: &mut Vec<Resizer<T>>, new_resizer: F, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo)
+    where F: Fn() -> Box<dyn ResizeLine<T>>
+{
+    let ncomp = in_fmt.fmt.get_num_comp().min(dest_fmt.fmt.get_num_comp());
+    for comp in 0..ncomp {
+        if let (Some(sfmt), Some(dfmt)) = (in_fmt.fmt.get_chromaton(comp), dest_fmt.fmt.get_chromaton(comp)) {
+            let sw = sfmt.get_width(in_fmt.width);
+            let sh = sfmt.get_height(in_fmt.height);
+            let dw = dfmt.get_width(dest_fmt.width);
+            let dh = dfmt.get_height(dest_fmt.height);
+            let frac1 = Fraction::new(sw, dw);
+            if !frac1.is_one() {
+                let frac1_present = dst.iter().any(|(frac, _)| *frac == frac1);
+                if !frac1_present {
+                    dst.push((frac1, new_resizer()));
+                }
+            }
+            let frac2 = Fraction::new(sh, dh);
+            if !frac2.is_one() {
+                let frac2_present = dst.iter().any(|(frac, _)| *frac == frac2);
+
+                if !frac2_present {
+                    dst.push((frac2, new_resizer()));
+                }
+            }
+        }
+    }
+}
+
+impl Scaler {
+    fn new() -> Self {
+        Self {
+            resizers8:  Vec::new(),
+            resizers16: Vec::new(),
+            tmp8:       Vec::new(),
+            tmp16:      Vec::new(),
+        }
+    }
+}
+
+impl Kernel for Scaler {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo, options: &[(String, String)]) -> ScaleResult<NABufferType> {
+        let is16 = in_fmt.fmt.get_max_depth() > 8;
+        for (name, value) in options.iter() {
+            if name.as_str() == "scaler" {
+                match value.as_str() {
+                    "nn" => {
+                        if !is16 {
+                            set_resizer(&mut self.resizers8, || Box::new(NNResampler::new()), in_fmt, dest_fmt);
+                        } else {
+                            set_resizer(&mut self.resizers16, || Box::new(NNResampler::new()), in_fmt, dest_fmt);
+                        }
+                    },
+                    "bilin" => {
+                        if !is16 {
+                            set_resizer(&mut self.resizers8, || Box::new(BilinResize::new()), in_fmt, dest_fmt);
+                        } else {
+                            set_resizer(&mut self.resizers16, || Box::new(BilinResize::new()), in_fmt, dest_fmt);
+                        }
+                    },
+                    "bicubic" => {
+                        if !is16 {
+                            set_resizer(&mut self.resizers8, || Box::new(BicubicResize::new()), in_fmt, dest_fmt);
+                        } else {
+                            set_resizer(&mut self.resizers16, || Box::new(BicubicResize::new()), in_fmt, dest_fmt);
+                        }
+                    },
+                    _ => {},
+                };
+                if value.as_str().starts_with("lanczos") {
+                    let tail = &value[7..];
+
+                    let mut filt_len = if let Ok(val) = tail.parse::<usize>() {
+                            if (2..=16).contains(&val) {
+                                val
+                            } else {
+                                0
+                            }
+                        } else {
+                            0
+                        };
+                    if filt_len == 0 {
+                        filt_len = 3;
+                    }
+                    if !is16 {
+                        set_resizer(&mut self.resizers8, || Box::new(LanczosResize::new(filt_len)), in_fmt, dest_fmt);
+                    } else {
+                        set_resizer(&mut self.resizers16, || Box::new(LanczosResize::new(filt_len)), in_fmt, dest_fmt);
+                    }
+                }
+            }
+        }
+        if !is16 && self.resizers8.is_empty() {
+            set_resizer(&mut self.resizers8, || Box::new(NNResampler::new()), in_fmt, dest_fmt);
+        }
+        if is16 && self.resizers16.is_empty() {
+            set_resizer(&mut self.resizers16, || Box::new(NNResampler::new()), in_fmt, dest_fmt);
+        }
+
+        let mut max_size = 0;
+        let ncomp = in_fmt.fmt.get_num_comp().min(dest_fmt.fmt.get_num_comp());
+        for comp in 0..ncomp {
+            if let (Some(sfmt), Some(dfmt)) = (in_fmt.fmt.get_chromaton(comp), dest_fmt.fmt.get_chromaton(comp)) {
+                let sh = sfmt.get_height(in_fmt.height);
+                let dw = dfmt.get_width(dest_fmt.width);
+                let tmp_size = sh * ((dw + 15) & !15);
+                max_size = max_size.max(tmp_size);
+            }
+        }
+        if !is16 {
+            self.tmp8.resize(max_size, 0);
+        } else {
+            self.tmp16.resize(max_size, 0);
+        }
+
+        let res = alloc_video_buffer(NAVideoInfo::new(dest_fmt.width, dest_fmt.height, false, in_fmt.fmt), 3);
+        if res.is_err() { return Err(ScaleError::AllocError); }
+        Ok(res.unwrap())
+    }
+    fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) {
+        if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
+            scale_loop!(sbuf, self.tmp8, dbuf, self.resizers8);
+        } else if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf16(), pic_out.get_vbuf16()) {
+            scale_loop!(sbuf, self.tmp16, dbuf, self.resizers16);
+        } else {
+            unreachable!();
+        }
+    }
+}
+
+pub fn create_scale() -> Box<dyn Kernel> {
+    Box::new(Scaler::new())
+}
+