scaler initial work
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 14 Feb 2019 15:14:06 +0000 (16:14 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Mon, 29 Apr 2019 12:55:10 +0000 (14:55 +0200)
nihav-core/src/lib.rs
nihav-core/src/scale/colorcvt.rs [new file with mode: 0644]
nihav-core/src/scale/kernel.rs [new file with mode: 0644]
nihav-core/src/scale/mod.rs [new file with mode: 0644]
nihav-core/src/scale/repack.rs [new file with mode: 0644]
nihav-core/src/scale/scale.rs [new file with mode: 0644]

index 615a0cf997a259e91e987f0131a9e9dc308472a8..d36297f51d9bce4f214cb7e4f175089488dd32a8 100644 (file)
@@ -10,6 +10,7 @@ pub mod io;
 pub mod refs;
 pub mod register;
 pub mod detect;
+pub mod scale;
 
 #[cfg(feature="dsp")]
 pub mod dsp;
diff --git a/nihav-core/src/scale/colorcvt.rs b/nihav-core/src/scale/colorcvt.rs
new file mode 100644 (file)
index 0000000..2e2f299
--- /dev/null
@@ -0,0 +1,323 @@
+use super::*;
+use super::kernel::Kernel;
+
+const YUV_PARAMS: &[[f32; 2]] = &[
+    [ 0.333,    0.333   ], // RGB
+    [ 0.2126,   0.0722  ], // ITU-R BT709
+    [ 0.333,    0.333   ], // unspecified
+    [ 0.333,    0.333   ], // reserved
+    [ 0.299,    0.114   ], // ITU-R BT601
+    [ 0.299,    0.114   ], // ITU-R BT470
+    [ 0.299,    0.114   ], // SMPTE 170M
+    [ 0.212,    0.087   ], // SMPTE 240M
+    [ 0.333,    0.333   ], // YCoCg
+    [ 0.2627,   0.0593  ], // ITU-R BT2020
+    [ 0.2627,   0.0593  ], // ITU-R BT2020
+];
+
+const BT_PAL_COEFFS: [f32; 2] = [ 0.493, 0.877 ];
+
+const SMPTE_NTSC_COEFFS: &[f32; 4] = &[ -0.268, 0.7358, 0.4127, 0.4778 ];
+
+/*const RGB2YCOCG: [[f32; 3]; 3] = [
+    [  0.25,  0.5,  0.25 ],
+    [ -0.25,  0.5, -0.25 ],
+    [  0.5,   0.0, -0.5  ]
+];
+const YCOCG2RGB: [[f32; 3]; 3] = [
+    [ 1.0, -1.0,  1.0 ],
+    [ 1.0,  1.0,  0.0 ],
+    [ 1.0, -1.0, -1.0 ]
+];
+
+const XYZ2RGB: [[f32; 3]; 3] = [
+    [ 0.49,    0.31,   0.2     ],
+    [ 0.17697, 0.8124, 0.01063 ],
+    [ 0.0,     0.01,   0.99    ]
+];
+const RGB2XYZ: [[f32; 3]; 3] = [
+    [  2.364613, -0.89654, -0.46807 ],
+    [ -0.515167,  1.42641,  0.08876 ],
+    [  0.0052,   -0.01441,  1.00920 ]
+];*/
+
+fn make_rgb2yuv(kr: f32, kb: f32, mat: &mut [[f32; 3]; 3]) {
+    // Y
+    mat[0][0] = kr;
+    mat[0][1] = 1.0 - kr - kb;
+    mat[0][2] = kb;
+    // Cb
+    mat[1][0] = -mat[0][0] * 0.5 / (1.0 - kb);
+    mat[1][1] = -mat[0][1] * 0.5 / (1.0 - kb);
+    mat[1][2] = 0.5;
+    // Cr
+    mat[2][0] = 0.5;
+    mat[2][1] = -mat[0][1] * 0.5 / (1.0 - kr);
+    mat[2][2] = -mat[0][2] * 0.5 / (1.0 - kr);
+}
+
+fn make_yuv2rgb(kr: f32, kb: f32, mat: &mut [[f32; 3]; 3]) {
+    let kg = 1.0 - kr - kb;
+
+    // R
+    mat[0][0] = 1.0;
+    mat[0][1] = 0.0;
+    mat[0][2] = 2.0 * (1.0 - kr);
+    // G
+    mat[1][0] = 1.0;
+    mat[1][1] = -kb * 2.0 * (1.0 - kb) / kg;
+    mat[1][2] = -kr * 2.0 * (1.0 - kr) / kg;
+    // B
+    mat[2][0] = 1.0;
+    mat[2][1] = 2.0 * (1.0 - kb);
+    mat[2][2] = 0.0;
+}
+
+fn apply_pal_rgb2yuv(eu: f32, ev: f32, mat: &mut [[f32; 3]; 3]) {
+    let ufac = 2.0 * (1.0 - mat[0][2]) * eu;
+    let vfac = 2.0 * (1.0 - mat[0][0]) * ev;
+
+    // U
+    mat[1][0] *= ufac;
+    mat[1][1] *= ufac;
+    mat[1][2]  = eu * (1.0 - mat[0][2]);
+    // V
+    mat[2][0]  = ev * (1.0 - mat[0][0]);
+    mat[2][1] *= vfac;
+    mat[2][2] *= vfac;
+}
+
+fn apply_pal_yuv2rgb(eu: f32, ev: f32, mat: &mut [[f32; 3]; 3]) {
+    let ufac = 1.0 / (mat[2][1] * eu);
+    let vfac = 1.0 / (mat[0][2] * ev);
+
+    // R
+    mat[0][2] *= vfac;
+    // G
+    mat[1][1] *= ufac;
+    mat[1][2] *= vfac;
+    // B
+    mat[2][1] *= ufac;
+}
+
+fn apply_ntsc_rgb2yiq(params: &[f32; 4], mat: &mut [[f32; 3]; 3]) {
+    let ufac = 2.0 * (1.0 - mat[0][2]);
+    let vfac = 2.0 * (1.0 - mat[0][0]);
+    let mut tmp: [[f32; 3]; 2] = [[0.0; 3]; 2];
+
+    for i in 0..3 {
+        tmp[0][i] = mat[1][i] * ufac;
+        tmp[1][i] = mat[2][i] * vfac;
+    }
+    for i in 0..3 {
+        mat[1][i] = params[0] * tmp[0][i] + params[1] * tmp[1][i];
+        mat[2][i] = params[2] * tmp[0][i] + params[3] * tmp[1][i];
+    }
+}
+
+fn subm_det(mat: &[[f32; 3]; 3], col: usize, row: usize) -> f32 {
+    let row0 = if row == 0 { 1 } else { 0 };
+    let row1 = if (row == 1) || (row0 == 1) { 2 } else { 1 };
+    let col0 = if col == 0 { 1 } else { 0 };
+    let col1 = if (col == 1) || (col0 == 1) { 2 } else { 1 };
+
+    let det = mat[row0][col0] * mat[row1][col1] - mat[row0][col1] * mat[row1][col0];
+    if ((col ^ row) & 1) == 0 {
+        det
+    } else {
+        -det
+    }
+}
+
+fn invert_matrix(mat: &mut [[f32; 3]; 3]) {
+    let d00 = subm_det(mat, 0, 0);
+    let d01 = subm_det(mat, 0, 1);
+    let d02 = subm_det(mat, 0, 2);
+    let d10 = subm_det(mat, 1, 0);
+    let d11 = subm_det(mat, 1, 1);
+    let d12 = subm_det(mat, 1, 2);
+    let d20 = subm_det(mat, 2, 0);
+    let d21 = subm_det(mat, 2, 1);
+    let d22 = subm_det(mat, 2, 2);
+    let det = 1.0 / (mat[0][0] * d00 + mat[0][1] * d10 + mat[0][2] * d20).abs();
+
+    mat[0][0] = det * d00;
+    mat[0][1] = det * d01;
+    mat[0][2] = det * d02;
+    mat[1][0] = det * d10;
+    mat[1][1] = det * d11;
+    mat[1][2] = det * d12;
+    mat[2][0] = det * d20;
+    mat[2][1] = det * d21;
+    mat[2][2] = det * d22;
+}
+
+fn matrix_mul(mat: &[[f32; 3]; 3], a: f32, b: f32, c: f32) -> (f32, f32, f32) {
+    (a * mat[0][0] + b * mat[0][1] + c * mat[0][2],
+     a * mat[1][0] + b * mat[1][1] + c * mat[1][2],
+     a * mat[2][0] + b * mat[2][1] + c * mat[2][2] )
+}
+
+#[derive(Default)]
+struct RgbToYuv {
+    matrix: [[f32; 3]; 3],
+}
+
+impl RgbToYuv {
+    fn new() -> Self { Self::default() }
+}
+
+impl Kernel for RgbToYuv {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType> {
+        let mut df = dest_fmt.fmt;
+//todo coeff selection
+        make_rgb2yuv(YUV_PARAMS[2][0], YUV_PARAMS[2][1], &mut self.matrix);
+        if let ColorModel::YUV(yuvsm) = df.get_model() {
+            match yuvsm {
+            YUVSubmodel::YCbCr  => {},
+            YUVSubmodel::YIQ    => { apply_ntsc_rgb2yiq(SMPTE_NTSC_COEFFS, &mut self.matrix); },
+            YUVSubmodel::YUVJ   => { apply_pal_rgb2yuv(BT_PAL_COEFFS[0], BT_PAL_COEFFS[1], &mut self.matrix); },
+            };
+        } else {
+            return Err(ScaleError::InvalidArgument);
+        }
+        for i in 0..MAX_CHROMATONS {
+            if let Some(ref mut chr) = df.comp_info[i] {
+                chr.packed = false;
+                chr.comp_offs = i as u8;
+                chr.h_ss = 0;
+                chr.v_ss = 0;
+            }
+        }
+println!(" [intermediate format {}]", df);
+        let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, df), 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()) {
+            let istrides = [sbuf.get_stride(0), sbuf.get_stride(1), sbuf.get_stride(2)];
+            let dstrides = [dbuf.get_stride(0), dbuf.get_stride(1), dbuf.get_stride(2)];
+            let (w, h) = sbuf.get_dimensions(0);
+
+            let mut roff = sbuf.get_offset(0);
+            let mut goff = sbuf.get_offset(1);
+            let mut boff = sbuf.get_offset(2);
+            let mut yoff = dbuf.get_offset(0);
+            let mut uoff = dbuf.get_offset(1);
+            let mut voff = dbuf.get_offset(2);
+            let src = sbuf.get_data();
+            let dst = dbuf.get_data_mut().unwrap();
+            for _y in 0..h {
+                for x in 0..w {
+                    let r = src[roff + x] as f32;
+                    let g = src[goff + x] as f32;
+                    let b = src[boff + x] as f32;
+                    let (y, u, v) = matrix_mul(&self.matrix, r, g, b);
+
+                    dst[yoff + x] = (y as i16).max(0).min(255) as u8;
+                    dst[uoff + x] = ((u as i16).max(-128).min(128) + 128) as u8;
+                    dst[voff + x] = ((v as i16).max(-128).min(128) + 128) as u8;
+                }
+                roff += istrides[0];
+                goff += istrides[1];
+                boff += istrides[2];
+                yoff += dstrides[0];
+                uoff += dstrides[1];
+                voff += dstrides[2];
+            }
+        }
+    }
+}
+
+pub fn create_rgb2yuv() -> Box<Kernel> {
+    Box::new(RgbToYuv::new())
+}
+
+#[derive(Default)]
+struct YuvToRgb {
+    matrix: [[f32; 3]; 3],
+}
+
+impl YuvToRgb {
+    fn new() -> Self { Self::default() }
+}
+
+impl Kernel for YuvToRgb {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType> {
+        let mut df = dest_fmt.fmt;
+//todo coeff selection
+        make_yuv2rgb(YUV_PARAMS[2][0], YUV_PARAMS[2][1], &mut self.matrix);
+        if let ColorModel::YUV(yuvsm) = in_fmt.fmt.get_model() {
+            match yuvsm {
+                YUVSubmodel::YCbCr  => {},
+                YUVSubmodel::YIQ    => {
+                    make_rgb2yuv(YUV_PARAMS[2][0], YUV_PARAMS[2][1], &mut self.matrix);
+                    apply_ntsc_rgb2yiq(SMPTE_NTSC_COEFFS, &mut self.matrix);
+                    invert_matrix(&mut self.matrix);
+                },
+                YUVSubmodel::YUVJ   => {
+                    apply_pal_yuv2rgb(BT_PAL_COEFFS[0], BT_PAL_COEFFS[1], &mut self.matrix);
+                },
+            };
+        } else {
+            return Err(ScaleError::InvalidArgument);
+        }
+        for i in 0..MAX_CHROMATONS {
+            if let Some(ref mut chr) = df.comp_info[i] {
+                chr.packed = false;
+                chr.comp_offs = i as u8;
+            }
+        }
+println!(" [intermediate format {}]", df);
+        let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, df), 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()) {
+            let istrides = [sbuf.get_stride(0), sbuf.get_stride(1), sbuf.get_stride(2)];
+            let dstrides = [dbuf.get_stride(0), dbuf.get_stride(1), dbuf.get_stride(2)];
+            let (w, h) = sbuf.get_dimensions(0);
+            let (sv0, sh0) = sbuf.get_info().get_format().get_chromaton(1).unwrap().get_subsampling();
+            let (sv1, sh1) = sbuf.get_info().get_format().get_chromaton(2).unwrap().get_subsampling();
+
+            let uhmask = (1 << sh0) - 1;
+            let vhmask = (1 << sh1) - 1;
+            let mut roff = dbuf.get_offset(0);
+            let mut goff = dbuf.get_offset(1);
+            let mut boff = dbuf.get_offset(2);
+            let mut yoff = sbuf.get_offset(0);
+            let mut uoff = sbuf.get_offset(1);
+            let mut voff = sbuf.get_offset(2);
+            let src = sbuf.get_data();
+            let dst = dbuf.get_data_mut().unwrap();
+            for y in 0..h {
+                for x in 0..w {
+                    let y = src[yoff + x] as f32;
+                    let u = ((src[uoff + (x >> sv0)] as i16) - 128) as f32;
+                    let v = ((src[voff + (x >> sv1)] as i16) - 128) as f32;
+
+                    let (r, g, b) = matrix_mul(&self.matrix, y, u, v);
+                    dst[roff + x] = (r as i16).max(0).min(255) as u8;
+                    dst[goff + x] = (g as i16).max(0).min(255) as u8;
+                    dst[boff + x] = (b as i16).max(0).min(255) as u8;
+                }
+                roff += dstrides[0];
+                goff += dstrides[1];
+                boff += dstrides[2];
+                yoff += istrides[0];
+                if (y & uhmask) == uhmask {
+                    uoff += istrides[1];
+                }
+                if (y & vhmask) == vhmask {
+                    voff += istrides[2];
+                }
+            }
+        }
+    }
+}
+
+pub fn create_yuv2rgb() -> Box<Kernel> {
+    Box::new(YuvToRgb::new())
+}
diff --git a/nihav-core/src/scale/kernel.rs b/nihav-core/src/scale/kernel.rs
new file mode 100644 (file)
index 0000000..84a69b4
--- /dev/null
@@ -0,0 +1,7 @@
+use crate::frame::*;
+use super::{ScaleInfo, ScaleResult};
+
+pub trait Kernel {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType>;
+    fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType);
+}
diff --git a/nihav-core/src/scale/mod.rs b/nihav-core/src/scale/mod.rs
new file mode 100644 (file)
index 0000000..c569b2f
--- /dev/null
@@ -0,0 +1,348 @@
+use crate::frame::*;
+
+mod kernel;
+
+mod colorcvt;
+mod repack;
+mod scale;
+
+#[derive(Clone,Copy,PartialEq)]
+pub struct ScaleInfo {
+    pub fmt:    NAPixelFormaton,
+    pub width:  usize,
+    pub height: usize,
+}
+
+impl std::fmt::Display for ScaleInfo {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "({}x{}, {})", self.width, self.height, self.fmt)
+    }
+}
+
+#[derive(Debug,Clone,Copy,PartialEq)]
+#[allow(dead_code)]
+pub enum ScaleError {
+    NoFrame,
+    AllocError,
+    InvalidArgument,
+    NotImplemented,
+    Bug,
+}
+
+pub type ScaleResult<T> = Result<T, ScaleError>;
+
+/*trait Kernel {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType>;
+    fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType);
+}*/
+
+struct KernelDesc {
+    name:       &'static str,
+    create:     fn () -> Box<kernel::Kernel>,
+}
+
+impl KernelDesc {
+    fn find(name: &str) -> ScaleResult<Box<kernel::Kernel>> {
+        for kern in KERNELS.iter() {
+            if kern.name == name {
+                return Ok((kern.create)());
+            }
+        }
+        Err(ScaleError::InvalidArgument)
+    }
+}
+
+const KERNELS: &[KernelDesc] = &[
+    KernelDesc { name: "pack",          create: repack::create_pack },
+    KernelDesc { name: "unpack",        create: repack::create_unpack },
+    KernelDesc { name: "depal",         create: repack::create_depal },
+    KernelDesc { name: "scale",         create: scale::create_scale },
+    KernelDesc { name: "rgb_to_yuv",    create: colorcvt::create_rgb2yuv },
+    KernelDesc { name: "yuv_to_rgb",    create: colorcvt::create_yuv2rgb },
+];
+
+struct Stage {
+    fmt_out:    ScaleInfo,
+    tmp_pic:    NABufferType,
+    next:       Option<Box<Stage>>,
+    worker:     Box<kernel::Kernel>,
+}
+
+pub fn get_scale_fmt_from_pic(pic: &NABufferType) -> ScaleInfo {
+    let info = pic.get_video_info().unwrap();
+    ScaleInfo { fmt: info.get_format(), width: info.get_width(), height: info.get_height() }
+}
+
+impl Stage {
+    fn new(name: &str, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<Self> {
+        let mut worker = KernelDesc::find(name)?;
+        let tmp_pic = worker.init(in_fmt, dest_fmt)?;
+        let fmt_out = get_scale_fmt_from_pic(&tmp_pic);
+        Ok(Self { fmt_out, tmp_pic, next: None, worker })
+    }
+    fn add(&mut self, new: Stage) {
+        if let Some(ref mut next) = self.next {
+            next.add(new);
+        } else {
+            self.next = Some(Box::new(new));
+        }
+    }
+    fn process(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) -> ScaleResult<()> {
+        if let Some(ref mut nextstage) = self.next {
+            self.worker.process(pic_in, &mut self.tmp_pic);
+            nextstage.process(&self.tmp_pic, pic_out)?;
+        } else {
+            self.worker.process(pic_in, pic_out);
+        }
+        Ok(())
+    }
+    fn drop_last_tmp(&mut self) {
+        if let Some(ref mut nextstage) = self.next {
+            nextstage.drop_last_tmp();
+        } else {
+            self.tmp_pic = NABufferType::None;
+        }
+    }
+}
+
+pub struct NAScale {
+    fmt_in:         ScaleInfo,
+    fmt_out:        ScaleInfo,
+    just_convert:   bool,
+    pipeline:       Option<Stage>,
+}
+
+fn check_format(in_fmt: NAVideoInfo, ref_fmt: &ScaleInfo, just_convert: bool) -> ScaleResult<()> {
+    if in_fmt.get_format() != ref_fmt.fmt { return Err(ScaleError::InvalidArgument); }
+    if !just_convert && (in_fmt.get_width() != ref_fmt.width || in_fmt.get_height() != ref_fmt.height) {
+        return Err(ScaleError::InvalidArgument);
+    }
+    Ok(())
+}
+
+fn copy(pic_in: &NABufferType, pic_out: &mut NABufferType)
+{
+    if let (Some(ref sbuf), Some(ref mut dbuf)) = (pic_in.get_vbuf(), pic_out.get_vbuf()) {
+        let sdata = sbuf.get_data();
+        let ddata = dbuf.get_data_mut().unwrap();
+        ddata.copy_from_slice(&sdata[0..]);
+    } else {
+        unimplemented!();
+    }
+}
+
+macro_rules! add_stage {
+    ($head:expr, $new:expr) => {
+        if let Some(ref mut h) = $head {
+            h.add($new);
+        } else {
+            $head = Some($new);
+        }
+    };
+}
+fn is_better_fmt(a: &ScaleInfo, b: &ScaleInfo) -> bool {
+    if (a.width >= b.width) && (a.height >= b.height) {
+        return true;
+    }
+    if a.fmt.get_max_depth() > b.fmt.get_max_depth() {
+        return true;
+    }
+    if a.fmt.get_max_subsampling() < b.fmt.get_max_subsampling() {
+        return true;
+    }
+    false
+}
+fn build_pipeline(ifmt: &ScaleInfo, ofmt: &ScaleInfo, just_convert: bool) -> ScaleResult<Option<Stage>> {
+    let inname  = ifmt.fmt.get_model().get_short_name();
+    let outname = ofmt.fmt.get_model().get_short_name();
+
+println!("convert {} -> {}", ifmt, ofmt);
+    let mut needs_scale  = !just_convert;
+    if (ofmt.fmt.get_max_subsampling() > 0) &&
+        (ofmt.fmt.get_max_subsampling() != ifmt.fmt.get_max_subsampling()) {
+        needs_scale = true;
+    }
+    let needs_unpack = needs_scale || !ifmt.fmt.is_unpacked();
+    let needs_pack = !ofmt.fmt.is_unpacked();
+    let mut needs_convert = false;
+    if inname != outname {
+        needs_convert = true;
+    }
+    let scale_before_cvt = is_better_fmt(&ifmt, &ofmt) && needs_convert
+                           && (ofmt.fmt.get_max_subsampling() == 0);
+//todo stages for model and gamma conversion
+
+    let mut stages: Option<Stage> = None;
+    let mut cur_fmt = *ifmt;
+
+    if needs_unpack {
+println!("[adding unpack]");
+        let new_stage;
+        if !cur_fmt.fmt.is_paletted() {
+            new_stage = Stage::new("unpack", &cur_fmt, &ofmt)?;
+        } else {
+            new_stage = Stage::new("depal", &cur_fmt, &ofmt)?;
+        }
+        cur_fmt = new_stage.fmt_out;
+        add_stage!(stages, new_stage);
+    }
+    if needs_scale && scale_before_cvt {
+println!("[adding scale]");
+        let new_stage = Stage::new("scale", &cur_fmt, &ofmt)?;
+        cur_fmt = new_stage.fmt_out;
+        add_stage!(stages, new_stage);
+    }
+    if needs_convert {
+println!("[adding convert]");
+        let cvtname = format!("{}_to_{}", inname, outname);
+println!("[{}]", cvtname);
+        let new_stage = Stage::new(&cvtname, &cur_fmt, &ofmt)?;
+//todo if fails try converting via RGB or YUV
+        cur_fmt = new_stage.fmt_out;
+        add_stage!(stages, new_stage);
+//todo alpha plane copy/add
+    }
+    if needs_scale && !scale_before_cvt {
+println!("[adding scale]");
+        let new_stage = Stage::new("scale", &cur_fmt, &ofmt)?;
+        cur_fmt = new_stage.fmt_out;
+        add_stage!(stages, new_stage);
+    }
+//todo flip if needed
+    if needs_pack {
+println!("[adding pack]");
+        let new_stage = Stage::new("pack", &cur_fmt, &ofmt)?;
+        //cur_fmt = new_stage.fmt_out;
+        add_stage!(stages, new_stage);
+    }
+
+    if let Some(ref mut head) = stages {
+        head.drop_last_tmp();
+    }
+    
+    Ok(stages)
+}
+
+impl NAScale {
+    pub fn new(fmt_in: ScaleInfo, fmt_out: ScaleInfo) -> ScaleResult<Self> {
+        let pipeline;
+        let just_convert = (fmt_in.width == fmt_out.width) && (fmt_in.height == fmt_out.height);
+        if fmt_in != fmt_out {
+            pipeline = build_pipeline(&fmt_in, &fmt_out, just_convert)?;
+        } else {
+            pipeline = None;
+        }
+        Ok(Self { fmt_in, fmt_out, just_convert, pipeline })
+    }
+    pub fn needs_processing(&self) -> bool { self.pipeline.is_some() }
+    pub fn get_in_fmt(&self) -> ScaleInfo { self.fmt_in }
+    pub fn get_out_fmt(&self) -> ScaleInfo { self.fmt_out }
+    pub fn convert(&mut self, pic_in: &NABufferType, pic_out: &mut NABufferType) -> ScaleResult<()> {
+        let in_info  = pic_in.get_video_info();
+        let out_info = pic_out.get_video_info();
+        if in_info.is_none() || out_info.is_none() { return Err(ScaleError::InvalidArgument); }
+        let in_info  = in_info.unwrap();
+        let out_info = out_info.unwrap();
+        if self.just_convert &&
+                (in_info.get_width() != out_info.get_width() || in_info.get_height() != out_info.get_height()) {
+            return Err(ScaleError::InvalidArgument);
+        }
+        check_format(in_info,  &self.fmt_in,  self.just_convert)?;
+        check_format(out_info, &self.fmt_out, self.just_convert)?;
+        if let Some(ref mut pipe) = self.pipeline {
+            pipe.process(pic_in, pic_out)
+        } else {
+            copy(pic_in, pic_out);
+            Ok(())
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    fn fill_pic(pic: &mut NABufferType, val: u8) {
+        if let Some(ref mut buf) = pic.get_vbuf() {
+            let data = buf.get_data_mut().unwrap();
+            for el in data.iter_mut() { *el = val; }
+        } else if let Some(ref mut buf) = pic.get_vbuf16() {
+            let data = buf.get_data_mut().unwrap();
+            for el in data.iter_mut() { *el = val as u16; }
+        } else if let Some(ref mut buf) = pic.get_vbuf32() {
+            let data = buf.get_data_mut().unwrap();
+            for el in data.iter_mut() { *el = (val as u32) * 0x01010101; }
+        }
+    }
+    #[test]
+    fn test_convert() {
+        let mut in_pic = alloc_video_buffer(NAVideoInfo::new(1, 1, false, RGB565_FORMAT), 3).unwrap();
+        fill_pic(&mut in_pic, 42);
+        let mut out_pic = alloc_video_buffer(NAVideoInfo::new(1, 1, false, RGB24_FORMAT), 3).unwrap();
+        fill_pic(&mut out_pic, 0);
+        let ifmt = get_scale_fmt_from_pic(&in_pic);
+        let ofmt = get_scale_fmt_from_pic(&out_pic);
+        let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
+        scaler.convert(&in_pic, &mut out_pic).unwrap();
+        let obuf = out_pic.get_vbuf().unwrap();
+        let odata = obuf.get_data();
+        assert_eq!(odata[0], 0x0);
+        assert_eq!(odata[1], 0x4);
+        assert_eq!(odata[2], 0x52);
+
+        let mut in_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, RGB24_FORMAT), 3).unwrap();
+        fill_pic(&mut in_pic, 42);
+        let mut out_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, YUV420_FORMAT), 3).unwrap();
+        fill_pic(&mut out_pic, 0);
+        let ifmt = get_scale_fmt_from_pic(&in_pic);
+        let ofmt = get_scale_fmt_from_pic(&out_pic);
+        let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
+        scaler.convert(&in_pic, &mut out_pic).unwrap();
+        let obuf = out_pic.get_vbuf().unwrap();
+        let yoff = obuf.get_offset(0);
+        let uoff = obuf.get_offset(1);
+        let voff = obuf.get_offset(2);
+        let odata = obuf.get_data();
+        assert_eq!(odata[yoff], 42);
+        assert!(((odata[uoff] ^ 0x80) as i8).abs() <= 1);
+        assert!(((odata[voff] ^ 0x80) as i8).abs() <= 1);
+        let mut scaler = NAScale::new(ofmt, ifmt).unwrap();
+        scaler.convert(&out_pic, &mut in_pic).unwrap();
+        let obuf = in_pic.get_vbuf().unwrap();
+        let odata = obuf.get_data();
+        assert_eq!(odata[0], 42);
+    }
+    #[test]
+    fn test_scale() {
+        let mut in_pic = alloc_video_buffer(NAVideoInfo::new(2, 2, false, RGB565_FORMAT), 3).unwrap();
+        fill_pic(&mut in_pic, 42);
+        let mut out_pic = alloc_video_buffer(NAVideoInfo::new(3, 3, false, RGB565_FORMAT), 3).unwrap();
+        fill_pic(&mut out_pic, 0);
+        let ifmt = get_scale_fmt_from_pic(&in_pic);
+        let ofmt = get_scale_fmt_from_pic(&out_pic);
+        let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
+        scaler.convert(&in_pic, &mut out_pic).unwrap();
+        let obuf = out_pic.get_vbuf16().unwrap();
+        let odata = obuf.get_data();
+        assert_eq!(odata[0], 42);
+    }
+    #[test]
+    fn test_scale_and_convert() {
+        let mut in_pic = alloc_video_buffer(NAVideoInfo::new(7, 3, false, RGB565_FORMAT), 3).unwrap();
+        fill_pic(&mut in_pic, 42);
+        let mut out_pic = alloc_video_buffer(NAVideoInfo::new(4, 4, false, YUV420_FORMAT), 3).unwrap();
+        fill_pic(&mut out_pic, 0);
+        let ifmt = get_scale_fmt_from_pic(&in_pic);
+        let ofmt = get_scale_fmt_from_pic(&out_pic);
+        let mut scaler = NAScale::new(ifmt, ofmt).unwrap();
+        scaler.convert(&in_pic, &mut out_pic).unwrap();
+        let obuf = out_pic.get_vbuf().unwrap();
+        let yoff = obuf.get_offset(0);
+        let uoff = obuf.get_offset(1);
+        let voff = obuf.get_offset(2);
+        let odata = obuf.get_data();
+        assert_eq!(odata[yoff], 28);
+        assert_eq!(odata[uoff], 154);
+        assert_eq!(odata[voff], 103);
+    }
+}
diff --git a/nihav-core/src/scale/repack.rs b/nihav-core/src/scale/repack.rs
new file mode 100644 (file)
index 0000000..4f2ce99
--- /dev/null
@@ -0,0 +1,298 @@
+use crate::formats::*;
+use super::*;
+use super::kernel::Kernel;
+
+fn convert_depth(val: u32, indepth: u8, outdepth: u8) -> u32 {
+    if indepth >= outdepth {
+        val >> (indepth - outdepth)
+    } else {
+        let mut val2 = val << (outdepth - indepth);
+        let mut shift = outdepth - indepth;
+        while shift >= indepth {
+            shift -= indepth;
+            val2 |= val << shift;
+        }
+        if shift > 0 {
+            val2 |= val >> (indepth - shift);
+        }
+        val2
+    }
+}
+
+#[derive(Default)]
+struct PackKernel {
+    shifts: [u8;  MAX_CHROMATONS],
+    depths: [u8;  MAX_CHROMATONS],
+    ncomps: usize,
+    osize:  [u8;  MAX_CHROMATONS],
+    ooff:   [usize; MAX_CHROMATONS],
+}
+
+impl PackKernel {
+    fn new() -> Self { Self::default() }
+}
+
+impl Kernel for PackKernel {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType> {
+        self.ncomps = in_fmt.fmt.components as usize;
+        for i in 0..self.ncomps {
+            let ichr = in_fmt.fmt.comp_info[i].unwrap();
+            let ochr = dest_fmt.fmt.comp_info[i].unwrap();
+            self.shifts[i] = ochr.shift;
+            self.osize[i] = ochr.depth;
+            self.depths[i] = ichr.depth;
+            self.ooff[i] = ochr.comp_offs as usize;
+        }
+        let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, dest_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 buf) = pic_in.get_vbuf() {
+            if let Some(ref mut dbuf) = pic_out.get_vbuf() {
+                let dstride = dbuf.get_stride(0);
+                for comp in 0..self.ncomps {
+                    let ioff = buf.get_offset(comp);
+                    let istride = buf.get_stride(comp);
+                    let step = dbuf.get_info().get_format().get_chromaton(comp).unwrap().get_step() as usize;
+                    let (w, h) = dbuf.get_dimensions(comp);
+                    let sdata = buf.get_data();
+                    let sdata = &sdata[ioff..];
+                    let ddata = dbuf.get_data_mut().unwrap();
+                    for (src, dst) in sdata.chunks(istride).zip(ddata.chunks_mut(dstride)).take(h) {
+                        for x in 0..w {
+                            dst[x * step + self.ooff[comp]] = convert_depth(src[x] as u32, self.depths[comp], self.osize[comp]) as u8;
+                        }
+                    }
+                }
+            } else if let Some(ref mut dbuf) = pic_out.get_vbuf16() {
+                let (w, h) = dbuf.get_dimensions(0);
+                let dstride = dbuf.get_stride(0);
+                let ddata = dbuf.get_data_mut().unwrap();
+                let src = buf.get_data();
+                let mut ioff: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+                let mut istride: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+                for comp in 0..self.ncomps {
+                    ioff[comp] = buf.get_offset(comp);
+                    istride[comp] = buf.get_stride(comp);
+                }
+                for dst in ddata.chunks_mut(dstride).take(h) {
+                    for x in 0..w {
+                        let mut elem: u32 = 0;
+                        for comp in 0..self.ncomps {
+                            let c = src[ioff[comp] + x] as u32;
+                            elem |= convert_depth(c, self.depths[comp], self.osize[comp]) << self.shifts[comp];
+                        }
+                        dst[x] = elem as u16;
+                    }
+                    for comp in 0..self.ncomps {
+                        ioff[comp] += istride[comp];
+                    }
+                }
+            } else {
+unimplemented!();
+            }
+        } else if let Some(ref _buf) = pic_in.get_vbuf16() {
+unimplemented!();
+        } else if let Some(ref _buf) = pic_in.get_vbuf32() {
+unimplemented!();
+        } else {
+            unreachable!();
+        }
+    }
+}
+
+pub fn create_pack() -> Box<Kernel> {
+    Box::new(PackKernel::new())
+}
+
+#[derive(Default)]
+struct UnpackKernel {
+    shifts: [u8;  MAX_CHROMATONS],
+    masks:  [u32; MAX_CHROMATONS],
+    depths: [u8;  MAX_CHROMATONS],
+    ncomps: usize,
+    osize:  [u8;  MAX_CHROMATONS],
+}
+
+impl UnpackKernel {
+    fn new() -> Self { Self::default() }
+}
+
+impl Kernel for UnpackKernel {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType> {
+        self.ncomps = in_fmt.fmt.components as usize;
+        let mut chr: Vec<Option<NAPixelChromaton>> = Vec::with_capacity(MAX_CHROMATONS);
+        for i in 0..self.ncomps {
+            let ichr = in_fmt.fmt.comp_info[i].unwrap();
+            let ochr = dest_fmt.fmt.comp_info[i].unwrap();
+            self.shifts[i] = ichr.shift;
+            self.masks[i] = (1 << ichr.depth) - 1;
+            if ochr.depth > ichr.depth {
+                self.osize[i] = ochr.depth;
+            } else {
+                self.osize[i] = (ichr.depth + 7) & !7;
+            }
+            self.depths[i] = ichr.depth;
+            let mut dchr = ichr;
+            dchr.packed     = false;
+            dchr.depth      = self.osize[i];
+            dchr.shift      = 0;
+            dchr.comp_offs  = 0;
+            dchr.next_elem  = 0;
+            chr.push(Some(dchr));
+        }
+        let mut df = in_fmt.fmt;
+        for i in 0..self.ncomps {
+            df.comp_info[i] = chr[i];
+        }
+println!(" [intermediate format {}]", df);
+        let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, df), 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 buf) = pic_in.get_vbuf() {
+            let step = buf.get_info().get_format().get_chromaton(0).unwrap().get_step() as usize;
+            let mut soff: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+            for i in 0..self.ncomps {
+                soff[i] = buf.get_info().get_format().get_chromaton(i).unwrap().get_offset() as usize;
+            }
+            let (w, h) = buf.get_dimensions(0);
+            let ioff = buf.get_offset(0);
+            let istride = buf.get_stride(0);
+            let sdata1 = buf.get_data();
+            let sdata = &sdata1[ioff..];
+            if let Some(ref mut dbuf) = pic_out.get_vbuf() {
+                let mut ostride: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+                let mut offs: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+                for i in 0..self.ncomps {
+                    ostride[i] = dbuf.get_stride(i);
+                    offs[i]    = dbuf.get_offset(i);
+                }
+                let dst = dbuf.get_data_mut().unwrap();
+                for src in sdata.chunks(istride).take(h) {
+                    for x in 0..w {
+                        for i in 0..self.ncomps {
+                            dst[offs[i] + x] = src[x * step + soff[i]];
+                        }
+                    }
+                    for i in 0..self.ncomps { offs[i] += ostride[i]; }
+                }
+            } else {
+unimplemented!();
+            }
+        } else if let Some(ref buf) = pic_in.get_vbuf16() {
+            let (w, h) = buf.get_dimensions(0);
+            let ioff = buf.get_offset(0);
+            let istride = buf.get_stride(0);
+            let sdata1 = buf.get_data();
+            let sdata = &sdata1[ioff..];
+            if let Some(ref mut dbuf) = pic_out.get_vbuf() {
+                let mut ostride: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+                let mut offs: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+                for i in 0..self.ncomps {
+                    ostride[i] = dbuf.get_stride(i);
+                    offs[i]    = dbuf.get_offset(i);
+                }
+                let dst = dbuf.get_data_mut().unwrap();
+                for src in sdata.chunks(istride).take(h) {
+                    for x in 0..w {
+                        let elem = src[x] as u32;
+                        for i in 0..self.ncomps {
+                            dst[offs[i] + x] = convert_depth((elem >> self.shifts[i]) & self.masks[i], self.depths[i], self.osize[i]) as u8;
+                        }
+                    }
+                    for i in 0..self.ncomps { offs[i] += ostride[i]; }
+                }
+            } else {
+unimplemented!();
+            }
+        } else if let Some(ref _buf) = pic_in.get_vbuf32() {
+unimplemented!();
+        } else {
+            unreachable!();
+        }
+    }
+}
+
+pub fn create_unpack() -> Box<Kernel> {
+    Box::new(UnpackKernel::new())
+}
+
+#[derive(Default)]
+struct DepalKernel {
+    depths:     [u8; MAX_CHROMATONS],
+    coffs:      [usize; MAX_CHROMATONS],
+    ncomps:     usize,
+    palstep:    usize,
+}
+
+impl DepalKernel {
+    fn new() -> Self { Self::default() }
+}
+
+impl Kernel for DepalKernel {
+    fn init(&mut self, in_fmt: &ScaleInfo, _dest_fmt: &ScaleInfo) -> ScaleResult<NABufferType> {
+//todo select output more fitting for dest_fmt if possible
+        self.ncomps = in_fmt.fmt.components as usize;
+        let mut chr: Vec<Option<NAPixelChromaton>> = Vec::with_capacity(MAX_CHROMATONS);
+        self.palstep = in_fmt.fmt.elem_size as usize;
+        for i in 0..self.ncomps {
+            let ichr = in_fmt.fmt.comp_info[i].unwrap();
+            self.coffs[i]  = ichr.comp_offs as usize;
+            self.depths[i] = ichr.depth;
+            let mut dchr = ichr;
+            dchr.packed     = false;
+            dchr.depth      = 8;
+            dchr.shift      = 0;
+            dchr.comp_offs  = i as u8;
+            dchr.next_elem  = 0;
+            chr.push(Some(dchr));
+        }
+        let mut df = in_fmt.fmt;
+        df.palette = false;
+        for i in 0..self.ncomps {
+            df.comp_info[i] = chr[i];
+        }
+println!(" [intermediate format {}]", df);
+        let res = alloc_video_buffer(NAVideoInfo::new(in_fmt.width, in_fmt.height, false, df), 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()) {
+            let ioff = sbuf.get_offset(0);
+            let paloff = sbuf.get_offset(1);
+            let (w, h) = sbuf.get_dimensions(0);
+            let istride = sbuf.get_stride(0);
+            let sdata1 = sbuf.get_data();
+            let sdata = &sdata1[ioff..];
+            let pal = &sdata1[paloff..];
+
+            let mut ostride: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+            let mut offs: [usize; MAX_CHROMATONS] = [0; MAX_CHROMATONS];
+            for i in 0..self.ncomps {
+                ostride[i] = dbuf.get_stride(i);
+                offs[i]    = dbuf.get_offset(i);
+            }
+            let dst = dbuf.get_data_mut().unwrap();
+            for src in sdata.chunks(istride).take(h) {
+                for x in 0..w {
+                    let palidx = src[x] as usize;
+                    for i in 0..self.ncomps {
+                        let elem = pal[palidx * self.palstep + self.coffs[i]] as u32;
+                        dst[offs[i] + x] = convert_depth(elem, self.depths[i], 8) as u8;
+                    }
+                }
+                for i in 0..self.ncomps { offs[i] += ostride[i]; }
+            }
+        } else {
+            unreachable!();
+        }
+    }
+}
+
+pub fn create_depal() -> Box<Kernel> {
+    Box::new(DepalKernel::new())
+}
diff --git a/nihav-core/src/scale/scale.rs b/nihav-core/src/scale/scale.rs
new file mode 100644 (file)
index 0000000..197b7c4
--- /dev/null
@@ -0,0 +1,58 @@
+use super::*;
+use super::kernel::Kernel;
+
+struct NNResampler {}
+
+impl NNResampler {
+    fn new() -> Self { Self{} }
+}
+
+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;
+                    for x in 0..dw {
+                        let sx = x * sw / dw;
+                        dst[doff + x] = src[soff + sx];
+                    }
+                    doff += dstride;
+                }
+            }
+    };
+}
+
+impl Kernel for NNResampler {
+    fn init(&mut self, in_fmt: &ScaleInfo, dest_fmt: &ScaleInfo) -> 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<Kernel> {
+    Box::new(NNResampler::new())
+}
+