From 213e9f9e88cb73c8b21fc61dbf3ddc65e1fb3924 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Tue, 23 May 2023 19:36:04 +0200 Subject: [PATCH] core/scale: add proper rescaling --- nihav-core/src/scale/mod.rs | 56 ++++ nihav-core/src/scale/scale.rs | 124 -------- nihav-core/src/scale/scale/mod.rs | 470 ++++++++++++++++++++++++++++++ 3 files changed, 526 insertions(+), 124 deletions(-) delete mode 100644 nihav-core/src/scale/scale.rs create mode 100644 nihav-core/src/scale/scale/mod.rs diff --git a/nihav-core/src/scale/mod.rs b/nihav-core/src/scale/mod.rs index 6c1daa9..a79232d 100644 --- a/nihav-core/src/scale/mod.rs +++ b/nihav-core/src/scale/mod.rs @@ -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 index a0bd0ca..0000000 --- a/nihav-core/src/scale/scale.rs +++ /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(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(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 { - 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 { - 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 index 0000000..0a62e0f --- /dev/null +++ b/nihav-core/src/scale/scale/mod.rs @@ -0,0 +1,470 @@ +use super::*; +use super::kernel::Kernel; + +trait ResizeLine { + fn resize_line(&mut self, src: &[T], src_len: usize, sstep: usize, dst: &mut [T], dst_len: usize, dstep: usize); +} + +trait CustomFrom { + fn cvt_from(val: T) -> Self; +} + +impl CustomFrom for u8 { + fn cvt_from(val: usize) -> Self { val as u8 } +} +impl CustomFrom for u16 { + fn cvt_from(val: usize) -> Self { val as u16 } +} +impl CustomFrom for usize { + fn cvt_from(val: u8) -> Self { usize::from(val) } +} +impl CustomFrom for usize { + fn cvt_from(val: u16) -> Self { usize::from(val) } +} +impl CustomFrom for u8 { + fn cvt_from(val: f32) -> Self { val.max(0.0).min(255.0) as u8 } +} +impl CustomFrom for u16 { + fn cvt_from(val: f32) -> Self { val.max(0.0).min(65535.0) as u16 } +} +impl CustomFrom for f32 { + fn cvt_from(val: u8) -> Self { val as f32 } +} +impl CustomFrom for f32 { + fn cvt_from(val: u16) -> Self { val as f32 } +} + +trait CustomInto { + fn cvt_into(self) -> T; +} + +impl CustomInto for T where U: CustomFrom { + 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 ResizeLine 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 ResizeLine for BilinResize +where + T: Copy+CustomFrom+CustomInto +{ + 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 ResizeLine for BicubicResize +where + T: Copy+CustomFrom+CustomInto +{ + 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>, +} + +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> { + 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 ResizeLine for LanczosResize +where + T: Copy+CustomFrom+CustomInto +{ + 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 = (Fraction, Box>); + +struct Scaler { + resizers8: Vec>, + resizers16: Vec>, + tmp8: Vec, + tmp16: Vec, +} + +fn set_resizer(dst: &mut Vec>, new_resizer: F, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) + where F: Fn() -> Box> +{ + 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 { + 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::() { + 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 { + Box::new(Scaler::new()) +} + -- 2.30.2