]> git.nihav.org Git - nihav.git/commitdiff
Indeo 3 encoder
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 22 Dec 2022 13:03:26 +0000 (14:03 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Mon, 6 Feb 2023 16:50:21 +0000 (17:50 +0100)
nihav-allstuff/src/lib.rs
nihav-indeo/Cargo.toml
nihav-indeo/src/codecs/indeo3enc/cell.rs [new file with mode: 0644]
nihav-indeo/src/codecs/indeo3enc/mod.rs [new file with mode: 0644]
nihav-indeo/src/codecs/indeo3enc/mv.rs [new file with mode: 0644]
nihav-indeo/src/codecs/indeo3enc/ratectl.rs [new file with mode: 0644]
nihav-indeo/src/codecs/indeo3enc/tree.rs [new file with mode: 0644]
nihav-indeo/src/codecs/mod.rs
nihav-indeo/src/lib.rs

index 90ee6c3af2fe380cee88cb93b670a6db2a1cc64a..f655b953d1d5bbd7c29952b14941f43b1a244ffd 100644 (file)
@@ -67,6 +67,7 @@ pub fn nihav_register_all_raw_demuxers(rd: &mut RegisteredRawDemuxers) {
 pub fn nihav_register_all_encoders(re: &mut RegisteredEncoders) {
     flash_register_all_encoders(re);
     generic_register_all_encoders(re);
+    indeo_register_all_encoders(re);
     duck_register_all_encoders(re);
     llaudio_register_all_encoders(re);
     ms_register_all_encoders(re);
index 4243e434b2b557169e4e93329f9621d49d2bfcbb..2594903691263c011d442337cd51df3f3b2bf80a 100644 (file)
@@ -12,10 +12,10 @@ path = "../nihav-codec-support"
 features = ["h263", "fft", "dsp_window"]
 
 [dev-dependencies]
-nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers"] }
+nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers", "muxer_avi"] }
 
 [features]
-default = ["all_decoders", "all_demuxers"]
+default = ["all_decoders", "all_demuxers", "all_encoders"]
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 all_video_decoders = ["decoder_indeo2", "decoder_indeo3", "decoder_indeo4", "decoder_indeo5", "decoder_intel263"]
@@ -32,4 +32,10 @@ decoder_intel263 = ["decoders"]
 all_demuxers = ["demuxer_ivf"]
 demuxers = []
 
-demuxer_ivf = ["demuxers"]
\ No newline at end of file
+demuxer_ivf = ["demuxers"]
+
+all_encoders = ["all_video_encoders"]
+all_video_encoders = ["encoder_indeo3"]
+encoders = []
+
+encoder_indeo3 = ["encoders"]
\ No newline at end of file
diff --git a/nihav-indeo/src/codecs/indeo3enc/cell.rs b/nihav-indeo/src/codecs/indeo3enc/cell.rs
new file mode 100644 (file)
index 0000000..6f641eb
--- /dev/null
@@ -0,0 +1,967 @@
+use super::CB_SELECTORS;
+use super::mv::MV;
+use super::super::indeo3data::*;
+use super::{Indeo3Cell, Plane};
+
+pub const MAX_CELL_SIZE: usize = 2400;
+const DEFAULT_PIXEL: u8 = 0x40;
+const INTRA_FLAT_THRESHOLD: u32 = 8;
+const INTER_FLAT_THRESHOLD: u32 = 8;
+
+struct IndexWriter<'a> {
+    dst:    &'a mut [u8],
+    pos:    usize,
+    do_rle: bool,
+}
+
+const SKIP_CELL: u8 = 0xFD;
+const ZERO_CELL: u8 = 0xFD;
+
+impl<'a> IndexWriter<'a> {
+    fn new(dst: &'a mut [u8], do_rle: bool) -> Self {
+        Self {
+            dst,
+            pos:    0,
+            do_rle,
+        }
+    }
+    fn write_pair(&mut self, idx0: u8, idx1: u8, quad_radix: u8, esc: u8) {
+        //xxx: in theory one in theory the other index just has to make output fit byte range
+        if idx0 < quad_radix && idx1 < quad_radix {
+            let quad = idx1 * quad_radix + idx0 + esc;
+            self.dst[self.pos] = quad;
+            self.pos += 1;
+        } else {
+            self.dst[self.pos] = idx0;
+            self.pos += 1;
+            self.dst[self.pos] = idx1;
+            self.pos += 1;
+        }
+    }
+    fn write_byte(&mut self, val: u8) {
+        self.dst[self.pos] = val;
+        self.pos += 1;
+    }
+    fn compact_cell(&mut self, esc_vals: [u8; 4]) {
+        if !self.do_rle {
+            return;
+        }
+        let tail = &self.dst[self.pos - 4..][..4];
+        let mut count = 0;
+        for (&a, &b) in tail.iter().zip(esc_vals.iter()).rev() {
+            if a != b {
+                break;
+            }
+            count += 1;
+        }
+        if count > 1 {
+            self.pos -= count;
+            self.dst[self.pos] = ZERO_CELL;
+            self.pos += 1;
+        }
+    }
+    fn compact_all_cells(&mut self) {
+        if !self.do_rle {
+            return;
+        }
+        if self.pos > 2 {
+            let mut i = 0;
+            while i + 2 < self.pos {
+                if self.dst[i] == ZERO_CELL && self.dst[i + 1] == ZERO_CELL {
+                    let mut last_idx = i;
+                    for j in (i + 1)..self.pos {
+                        if self.dst[j] != ZERO_CELL {
+                            break;
+                        }
+                        last_idx = j;
+                    }
+                    let len = (last_idx - i + 1).min(31);
+                    if len == 2 {
+                        self.dst[i] = 0xFC;
+                        move_tail(&mut self.dst[i + 1..self.pos], 1);
+                        self.pos -= 1;
+                    } else {
+                        self.dst[i] = 0xFB;
+                        self.dst[i + 1] = len as u8;
+                        move_tail(&mut self.dst[i + 2..self.pos], len - 2);
+                        self.pos -= len - 2;
+                    }
+                }
+                i += 1;
+            }
+        }
+    }
+    fn end(self) -> usize {
+        self.pos
+    }
+}
+
+fn move_tail(buf: &mut [u8], off: usize) {
+    let len = buf.len();
+    for i in off..len {
+        buf[i - off] = buf[i];
+    }
+}
+
+#[derive(Default)]
+struct CodebookSuggester {
+    count:  [u16; 16],
+}
+
+const BINNING_FACTORS: [u8; 16] = [3, 7, 9, 12, 14, 16, 18, 40, 2, 3, 4, 5, 6, 7, 8, 9];
+
+impl CodebookSuggester {
+    fn new() -> Self { Self::default() }
+    fn merge(cs1: &Self, cs2: &Self) -> Self {
+        let mut count = [0; 16];
+        for (dst, (&src1, &src2)) in count.iter_mut().zip(cs1.count.iter().zip(cs2.count.iter())) {
+            *dst = src1 + src2;
+        }
+        Self { count }
+    }
+    fn add_delta(&mut self, delta: u8) {
+        for (i, &fac) in BINNING_FACTORS.iter().enumerate() {
+            let val = if i < 8 { delta + fac - 1 } else { delta };
+            if (val % fac) == 0 {
+                self.count[i] += 1;
+            }
+        }
+    }
+    fn add_line(&mut self, src: &[i8]) {
+        for &delta in src.iter() {
+            if delta == 0 {
+                continue;
+            }
+            let delta = delta.abs() as u8;
+            self.add_delta(delta);
+        }
+    }
+    fn add_line_half(&mut self, src: &[i8]) {
+        for &delta in src.iter().step_by(2) {
+            if delta == 0 {
+                continue;
+            }
+            let delta = delta.abs() as u8;
+            self.add_delta(delta);
+        }
+    }
+    fn get_best(&self) -> u8 {
+        let mut idx = 0;
+        for (i, &cnt) in self.count.iter().enumerate().skip(1) {
+            if cnt > self.count[idx] {
+                idx = i;
+            }
+        }
+        idx as u8
+    }
+}
+
+pub struct CellEncoder {
+        buf:    [u8; MAX_CELL_SIZE + 160],
+        rbuf:   [u8; MAX_CELL_SIZE + 160],
+        deltas: [i8; MAX_CELL_SIZE],
+        cell:   Indeo3Cell,
+    pub out:    [u8; MAX_CELL_SIZE / 2 + 1],
+    pub osize:  usize,
+
+    pub flat_thr_i: u32,
+    pub flat_thr_p: u32,
+    pub do_rle:     bool,
+    pub quant:      Option<u8>,
+}
+
+impl CellEncoder {
+    pub fn new() -> Self {
+        Self {
+            buf:    [0; MAX_CELL_SIZE + 160],
+            rbuf:   [0; MAX_CELL_SIZE + 160],
+            deltas: [0; MAX_CELL_SIZE],
+            cell:   Indeo3Cell::new(0, 0, false),
+            out:    [0; MAX_CELL_SIZE / 2 + 1],
+            osize:  0,
+
+            flat_thr_i: INTRA_FLAT_THRESHOLD,
+            flat_thr_p: INTER_FLAT_THRESHOLD,
+            do_rle:     true,
+            quant:      None,
+        }
+    }
+    pub fn read_buffer(&mut self, plane: &Plane, cell: Indeo3Cell) {
+        self.cell = cell;
+
+        let src = &plane.data[cell.get_x() + cell.get_y() * plane.width..];
+        let dst_w = cell.get_width();
+        for (dline, sline) in self.buf.chunks_mut(dst_w).skip(1).zip(src.chunks(plane.width)).take(cell.get_height()) {
+            dline.copy_from_slice(&sline[..dst_w]);
+        }
+        if cell.get_y() > 0 {
+            self.buf[..dst_w].copy_from_slice(&plane.data[cell.get_x() + (cell.get_y() - 1) * plane.width..][..dst_w]);
+        } else {
+            for el in self.buf[..dst_w].iter_mut() {
+                *el = DEFAULT_PIXEL;
+            }
+        }
+    }
+    pub fn read_mv_buffer(&mut self, plane: &Plane, cell: Indeo3Cell, mv: MV) {
+        self.cell = cell;
+
+        let xoff = (cell.get_x() as isize + isize::from(mv.x)) as usize;
+        let yoff = (cell.get_y() as isize + isize::from(mv.y)) as usize;
+        let src = &plane.data[xoff + yoff * plane.width..];
+        let dst_w = cell.get_width();
+        for (dline, sline) in self.rbuf.chunks_mut(dst_w).skip(1).zip(src.chunks(plane.width)).take(cell.get_height()) {
+            dline.copy_from_slice(&sline[..dst_w]);
+        }
+    }
+    pub fn null_mv(&mut self) {
+        let stride = self.cell.get_width();
+        self.buf[stride..].copy_from_slice(&self.rbuf[stride..]);
+    }
+    pub fn gen_diffs_intra(&mut self) {
+        let stride = self.cell.get_width();
+        let mut start = stride;
+        for dline in self.deltas.chunks_mut(stride).take(self.cell.get_height()) {
+            let (pprev, cur) = self.buf.split_at(start);
+            let prev = &pprev[pprev.len() - stride..];
+
+            for (dst, (&cur, &top)) in dline.iter_mut().zip(cur.iter().zip(prev.iter())) {
+                *dst = (cur as i8) - (top as i8);
+            }
+
+            start += stride;
+        }
+    }
+    pub fn gen_diffs_inter(&mut self) {
+        let stride = self.cell.get_width();
+        let prev_iter = self.rbuf.chunks(stride).skip(1);
+        let cur_iter = self.buf.chunks(stride).skip(1);
+        for (dline, (cur, prev)) in self.deltas.chunks_mut(stride).take(self.cell.get_height()).zip(cur_iter.zip(prev_iter)) {
+            for (dst, (&cur, &prev)) in dline.iter_mut().zip(cur.iter().zip(prev.iter())) {
+                *dst = (cur as i8) - (prev as i8);
+            }
+        }
+    }
+    pub fn put_buffer(&self, plane: &mut Plane) {
+        let to_skip = if !self.cell.is_intra() || self.cell.get_y() == 0 { 1 } else { 0 };
+
+        let dst = &mut plane.data[self.cell.get_x() + (self.cell.get_y() + to_skip - 1) * plane.width..];
+        let src_w = self.cell.get_width();
+        for (sline, dline) in self.buf.chunks(src_w).skip(to_skip).zip(dst.chunks_mut(plane.width)).take(self.cell.get_height() + 1 - to_skip) {
+            dline[..src_w].copy_from_slice(sline);
+        }
+    }
+    fn determine_mode(&self, intra: bool, mut mode_hint: u8) -> (u8, [u8; 2]) {
+        if let Some(qmode) = self.quant {
+            if intra {
+                return (mode_hint, [qmode as u8, qmode as u8]);
+            } else {
+                let qmode = (qmode & 7) as u8;
+                return (mode_hint, [qmode, qmode]);
+            }
+        }
+
+        let stride = self.cell.get_width();
+
+        let mut cb_p = CodebookSuggester::new();
+        let mut cb_s = CodebookSuggester::new();
+        if !intra && (self.cell.get_height() & 7 == 0) {
+            let mut vdiff = 0;
+            let mut hdiff = 0;
+            for line_pair in self.deltas.chunks(stride * 2).take(self.cell.get_height() / 2) {
+                let (line1, line2) = line_pair.split_at(stride);
+                for (&el1, &el2) in line1.iter().zip(line2.iter()) {
+                    let diff = i32::from(el1) - i32::from(el2);
+                    vdiff += (diff * diff) as u32;
+                }
+            }
+            for line in self.deltas.chunks(stride).take(self.cell.get_height()) {
+                for pair in line.chunks(2) {
+                    let diff = i32::from(pair[1]) - i32::from(pair[0]);
+                    hdiff += (diff * diff) as u32;
+                }
+            }
+            vdiff /= (self.cell.get_width() * self.cell.get_height() / 2) as u32;
+            hdiff /= (self.cell.get_width() * self.cell.get_height() / 2) as u32;
+
+            mode_hint = match ((vdiff > self.flat_thr_p), (hdiff > self.flat_thr_p)) {
+                    (false, false) if (self.cell.get_width() & 7) == 0 => 10,
+                    (false, _)     => 11,
+                    _              => 0,
+                };
+        }
+        match mode_hint {
+            0 => {
+                for line_pair in self.deltas.chunks(stride * 2).take(self.cell.get_height() / 2) {
+                    let (line1, line2) = line_pair.split_at(stride);
+                    cb_p.add_line(line1);
+                    cb_s.add_line(line2);
+                }
+            },
+            3 => {
+                for line_quad in self.deltas.chunks(stride * 4).take(self.cell.get_height() / 4) {
+                    let (line01, line23) = line_quad.split_at(stride * 2);
+                    let (_line0, line1) = line01.split_at(stride);
+                    let (_line2, line3) = line23.split_at(stride);
+                    cb_p.add_line(line1);
+                    cb_s.add_line(line3);
+                }
+            },
+            10 => {
+                for line_quad in self.deltas.chunks(stride * 4).take(self.cell.get_height() / 4) {
+                    let (line01, line23) = line_quad.split_at(stride * 2);
+                    let (_line0, line1) = line01.split_at(stride);
+                    let (_line2, line3) = line23.split_at(stride);
+                    cb_p.add_line_half(line1);
+                    cb_s.add_line_half(line3);
+                }
+            },
+            11 => {
+                for line_quad in self.deltas.chunks(stride * 4).take(self.cell.get_height() / 4) {
+                    let (line01, line23) = line_quad.split_at(stride * 2);
+                    let (_line0, line1) = line01.split_at(stride);
+                    let (_line2, line3) = line23.split_at(stride);
+                    cb_p.add_line(line1);
+                    cb_s.add_line(line3);
+                }
+            },
+            _ => unreachable!(),
+        };
+        let cb_f = CodebookSuggester::merge(&cb_p, &cb_s).get_best();
+        let cb_p = cb_p.get_best();
+        let mut cb_s = cb_s.get_best();
+
+        let mut use_single = !intra || mode_hint == 10 || cb_p == cb_s;
+        if !use_single {
+            if cb_s == 0 { // we can adjust to the CB_SELECTORS here
+                cb_s = (((cb_p & 7) + 1) * 2).min(15);
+            }
+            let ncb = (cb_p << 4) + cb_s;
+            use_single = !CB_SELECTORS.contains(&ncb);
+        }
+
+        if use_single {
+            if intra || cb_f < 8 { // we don't want requant happening in inter mode
+                (mode_hint, [cb_f, cb_f])
+            } else {
+                (mode_hint, [0, 0])
+            }
+        } else {
+            (mode_hint + 1, [cb_p, cb_s])
+        }
+    }
+    pub fn compress_intra(&mut self, mode_hint: u8) {
+        let (mode, vq_idx) = self.determine_mode(true, mode_hint);
+
+        let cb_no1 = usize::from(vq_idx[1]);
+        let cb_no2 = usize::from(vq_idx[0]);
+        let cb1 = IVI3_DELTA_CBS[cb_no1];
+        let cb2 = IVI3_DELTA_CBS[cb_no2];
+
+        let mut requant_idx = None;
+        if (mode == 1) || (mode == 4) {
+            let aq_idx = (vq_idx[0] << 4) | vq_idx[1];
+            let mut idx = 42;
+            for (i, &el) in CB_SELECTORS.iter().enumerate() {
+                if el == aq_idx {
+                    idx = i;
+                    break;
+                }
+            }
+            self.out[0] = (mode << 4) | (idx as u8);
+
+            if idx >= 8 {
+                requant_idx = Some(idx - 8);
+            }
+        } else {
+            self.out[0] = (mode << 4) | (cb_no1 as u8);
+
+            if (8..=15).contains(&cb_no1) {
+                requant_idx = Some(cb_no1 - 8);
+            }
+        }
+        if self.cell.get_y() == 0 {
+            requant_idx = None;
+        }
+
+        let start = 1;
+        let mut iwriter = IndexWriter::new(&mut self.out[start..], self.do_rle);
+
+        let esc_val1 = (cb1.data.len() / 2) as u8;
+        let esc_val2 = (cb2.data.len() / 2) as u8;
+
+        let cbs = [cb1, cb2, cb1, cb2];
+        let esc_vals = [esc_val1, esc_val2, esc_val1, esc_val2];
+
+        let mut first_line = self.cell.get_y() == 0;
+        let stride = self.cell.get_width();
+
+        if let Some(ridx) = requant_idx {// && !first_line {
+            requant(&mut self.buf[..stride], ridx);
+        }
+
+        let mut cell4 = [0; 20];
+        match mode {
+            0 | 1 | 2 => {
+                for y in (0..self.cell.get_height()).step_by(4) {
+                    for x in (0..self.cell.get_width()).step_by(4) {
+                        Self::get_cell4(&self.buf, x, y, stride, &mut cell4);
+                        // first check if the cell can be coded with zero predictor
+                        let mut diff = 0;
+                        let mut pivot = 4;
+                        for _y in 0..4 {
+                            let (top, cur) = cell4.split_at(pivot);
+                            let top = &top[top.len() - 4..];
+                            for (&tval, &cval) in top.iter().zip(cur.iter()) {
+                                let cdiff = i32::from(tval) - i32::from(cval);
+                                diff += cdiff * cdiff;
+                            }
+                            pivot += 4;
+                        }
+                        if (diff as u32) < self.flat_thr_i {
+                            iwriter.write_byte(ZERO_CELL);
+                            let (top, tail) = cell4.split_at_mut(4);
+                            for dline in tail.chunks_mut(4) {
+                                dline.copy_from_slice(top);
+                            }
+                            Self::put_cell4(&mut self.buf, x, y, stride, &cell4);
+                            continue;
+                        }
+
+                        compress_intra_cell(&mut iwriter, &mut cell4, &cbs, esc_vals);
+                        Self::put_cell4(&mut self.buf, x, y, stride, &cell4);
+                    }
+                }
+            },
+            3 | 4 => {
+                for y in (0..self.cell.get_height()).step_by(8) {
+                    for x in (0..self.cell.get_width()).step_by(4) {
+                        Self::get_cell_mode3(&self.buf, x, y, stride, &mut cell4);
+                        compress_intra_cell(&mut iwriter, &mut cell4, &cbs, esc_vals);
+                        Self::put_cell_mode3(&mut self.buf, x, y, stride, &cell4, first_line);
+                    }
+                    first_line = false;
+                }
+            },
+            10 => {
+                for y in (0..self.cell.get_height()).step_by(8) {
+                    for x in (0..self.cell.get_width()).step_by(8) {
+                        Self::get_cell_mode10i(&self.buf, x, y, stride, &mut cell4);
+                        compress_intra_cell(&mut iwriter, &mut cell4, &cbs, esc_vals);
+                        Self::put_cell_mode10i(&mut self.buf, x, y, stride, &cell4, first_line);
+                    }
+                    first_line = false;
+                }
+            },
+            _ => unreachable!(),
+        };
+        iwriter.compact_all_cells();
+
+        self.osize = iwriter.end() + start;
+    }
+    pub fn compress_inter(&mut self) {
+        let (mode, vq_idx) = self.determine_mode(false, 0);
+
+        let cb_no1 = usize::from(vq_idx[1]);
+        let cb_no2 = usize::from(vq_idx[0]);
+        let cb1 = IVI3_DELTA_CBS[cb_no1];
+        let cb2 = IVI3_DELTA_CBS[cb_no2];
+
+        if (mode == 1) || (mode == 4) {
+            let aq_idx = (vq_idx[0] << 4) | vq_idx[1];
+            let mut idx = 42;
+            for (i, &el) in CB_SELECTORS.iter().enumerate() {
+                if el == aq_idx {
+                    idx = i;
+                    break;
+                }
+            }
+            self.out[0] = (mode << 4) | (idx as u8);
+        } else {
+            self.out[0] = (mode << 4) | (cb_no1 as u8);
+        }
+        let start = 1;
+        let mut iwriter = IndexWriter::new(&mut self.out[start..], self.do_rle);
+
+        let esc_val1 = (cb1.data.len() / 2) as u8;
+        let esc_val2 = (cb2.data.len() / 2) as u8;
+
+        let cbs = [cb1, cb2, cb1, cb2];
+        let esc_vals = [esc_val1, esc_val2, esc_val1, esc_val2];
+
+
+        let stride = self.cell.get_width();
+        let mut ccell4 = [0; 20];
+        let mut pcell4 = [0; 20];
+        match mode {
+            0 | 1 | 2 => {
+                for y in (0..self.cell.get_height()).step_by(4) {
+                    for x in (0..self.cell.get_width()).step_by(4) {
+                        Self::get_cell4(&self.buf,  x, y, stride, &mut ccell4);
+                        Self::get_cell4(&self.rbuf, x, y, stride, &mut pcell4);
+                        // first check if the cell can be coded with zero predictor
+                        let mut diff = 0;
+                        for (&pval, &cval) in pcell4[4..].iter().zip(ccell4[4..].iter()) {
+                            let cdiff = i32::from(pval) - i32::from(cval);
+                            diff += cdiff * cdiff;
+                        }
+                        if diff < 8 {
+                            iwriter.write_byte(SKIP_CELL);
+                            Self::put_cell4(&mut self.buf, x, y, stride, &pcell4);
+                            continue;
+                        }
+
+                        compress_inter_cell(&mut iwriter, &mut ccell4, &pcell4, &cbs, esc_vals);
+                        Self::put_cell4(&mut self.buf, x, y, stride, &ccell4);
+                    }
+                }
+            },
+            10 => {
+                let mut offset = 0;
+                let mut ref_cell = [0; 64];
+                let mut avg_diff: [i16; 16];
+                for _y in (0..self.cell.get_height()).step_by(8) {
+                    for x in (0..self.cell.get_width()).step_by(8) {
+                        for (dline, sline) in ref_cell.chunks_mut(8).zip(self.rbuf[offset + stride + x..].chunks(stride)) {
+                            dline.copy_from_slice(&sline[..8]);
+                        }
+                        avg_diff = [0; 16];
+                        for j in 0..8 {
+                            for i in 0..8 {
+                                avg_diff[i / 2 + (j / 2) * 4] += i16::from(self.deltas[offset + x + i + j * stride]);
+                            }
+                        }
+                        for el in avg_diff.iter_mut() {
+                            *el = (*el + 2) >> 2;
+                        }
+                        compress_inter_cell_mode10(&mut iwriter, &mut ref_cell, &avg_diff, &cbs, esc_vals);
+                        for (sline, dline) in ref_cell.chunks(8).zip(self.buf[offset + stride + x..].chunks_mut(stride)) {
+                            dline[..8].copy_from_slice(sline);
+                        }
+                    }
+                    offset += stride * 8;
+                }
+            },
+            11 => {
+                let mut offset = 0;
+                let mut ref_cell = [0; 32];
+                let mut avg_diff: [i16; 16];
+                for _y in (0..self.cell.get_height()).step_by(8) {
+                    for x in (0..self.cell.get_width()).step_by(4) {
+                        for (dline, sline) in ref_cell.chunks_mut(4).zip(self.rbuf[offset + stride + x..].chunks(stride)) {
+                            dline.copy_from_slice(&sline[..4]);
+                        }
+                        avg_diff = [0; 16];
+                        for j in 0..8 {
+                            for i in 0..4 {
+                                avg_diff[i + (j / 2) * 4] += i16::from(self.deltas[offset + x + i + j * stride]);
+                            }
+                        }
+                        for el in avg_diff.iter_mut() {
+                            *el = (*el + 1) >> 1;
+                        }
+
+                        compress_inter_cell_mode11(&mut iwriter, &mut ref_cell, &avg_diff, &cbs, esc_vals);
+                        for (sline, dline) in ref_cell.chunks(4).zip(self.buf[offset + stride + x..].chunks_mut(stride)) {
+                            dline[..4].copy_from_slice(sline);
+                        }
+                    }
+                    offset += stride * 8;
+                }
+            },
+            _ => unreachable!(),
+        };
+        iwriter.compact_all_cells();
+
+        self.osize = iwriter.end() + start;
+    }
+
+    fn get_cell4(data: &[u8], x: usize, y: usize, stride: usize, cell: &mut [u8; 20]) {
+        for (dst, src) in cell.chunks_mut(4).zip(data[x + y * stride..].chunks(stride)) {
+            dst.copy_from_slice(&src[..4]);
+        }
+    }
+    fn put_cell4(data: &mut [u8], x: usize, y: usize, stride: usize, cell: &[u8; 20]) {
+        for (src, dst) in cell.chunks(4).zip(data[x + y * stride..].chunks_mut(stride)).skip(1) {
+            dst[..4].copy_from_slice(src);
+        }
+    }
+    fn get_cell_mode3(data: &[u8], x: usize, y: usize, stride: usize, cell: &mut [u8; 20]) {
+        let src = &data[x + y * stride..];
+        for (dline, slines) in cell.chunks_mut(4).zip(src.chunks(stride * 2)) {
+            dline.copy_from_slice(&slines[..4]);
+        }
+    }
+    fn put_cell_mode3(data: &mut [u8], x: usize, y: usize, stride: usize, cell: &[u8; 20], first_line: bool) {
+        let dst = &mut data[x + y * stride..];
+        let mut dst_idx = stride;
+        for line in 0..4 {
+            for x in 0..4 {
+                let top = cell[line * 4 + x];
+                let cur = cell[(line + 1) * 4 + x];
+                dst[dst_idx + x]          = (top + cur) >> 1;
+                dst[dst_idx + stride + x] = cur;
+            }
+            dst_idx += stride * 2;
+        }
+        if first_line {
+            dst[stride..][..4].copy_from_slice(&cell[4..8]);
+        }
+    }
+    fn get_cell_mode10i(data: &[u8], x: usize, y: usize, stride: usize, cell: &mut [u8; 20]) {
+        let src = &data[x + y * stride..];
+        for (dline, src_pair) in cell.chunks_mut(4).zip(src.chunks(stride * 2)) {
+            for (dst, src) in dline.iter_mut().zip(src_pair.chunks(2)) {
+                *dst = src[0];
+            }
+        }
+    }
+    fn put_cell_mode10i(data: &mut [u8], x: usize, y: usize, stride: usize, cell: &[u8; 20], first_line: bool) {
+        let dst = &mut data[x + y * stride..];
+        let mut dst_idx = stride;
+        for line in 0..4 {
+            for x in 0..4 {
+                let top = dst[dst_idx - stride + x * 2];
+                let cur = cell[(line + 1) * 4 + x];
+                dst[dst_idx + x * 2]     = (top + cur) >> 1;
+                dst[dst_idx + x * 2 + 1] = (top + cur) >> 1;
+                dst[dst_idx + stride + x * 2]     = cur;
+                dst[dst_idx + stride + x * 2 + 1] = cur;
+            }
+            dst_idx += stride * 2;
+        }
+        if first_line {
+            let (top, tail) = dst[stride..].split_at_mut(stride);
+            top[..8].copy_from_slice(&tail[..8]);
+        }
+    }
+}
+
+fn requant(line: &mut [u8], rq_index: usize) {
+    let tab = &REQUANT_TAB[rq_index];
+    for el in line.iter_mut() {
+        *el = tab[usize::from(*el)];
+    }
+}
+
+fn compress_intra_cell(iwriter: &mut IndexWriter, cell4: &mut [u8; 20], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) {
+    let mut pivot = 4;
+    for y in 0..4 {
+        let cb = cbs[y];
+        let esc_val = esc_vals[y];
+
+        let (prev, cur) = cell4.split_at_mut(pivot);
+        let prev = &prev[prev.len() - 4..];
+        let cur = &mut cur[..4];
+        let (idx0, idx1) = find_quad(&cb.data, prev, cur);
+
+        cur[0] = ((prev[0] as i8) + cb.data[usize::from(idx1) * 2]) as u8;
+        cur[1] = ((prev[1] as i8) + cb.data[usize::from(idx1) * 2 + 1]) as u8;
+        cur[2] = ((prev[2] as i8) + cb.data[usize::from(idx0) * 2]) as u8;
+        cur[3] = ((prev[3] as i8) + cb.data[usize::from(idx0) * 2 + 1]) as u8;
+
+        iwriter.write_pair(idx0, idx1, cb.quad_radix, esc_val);
+
+        pivot += 4;
+    }
+    iwriter.compact_cell(esc_vals);
+}
+
+fn compress_inter_cell(iwriter: &mut IndexWriter, ccell4: &mut [u8; 20], pcell: &[u8; 20], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) {
+    for (y, (prev, cur)) in pcell[4..].chunks(4).zip(ccell4[4..].chunks_mut(4)).enumerate() {
+        let cb = cbs[y];
+        let esc_val = esc_vals[y];
+
+        let (idx0, idx1) = find_quad(&cb.data, prev, cur);
+
+        cur[0] = ((prev[0] as i8) + cb.data[usize::from(idx1) * 2]) as u8;
+        cur[1] = ((prev[1] as i8) + cb.data[usize::from(idx1) * 2 + 1]) as u8;
+        cur[2] = ((prev[2] as i8) + cb.data[usize::from(idx0) * 2]) as u8;
+        cur[3] = ((prev[3] as i8) + cb.data[usize::from(idx0) * 2 + 1]) as u8;
+
+        iwriter.write_pair(idx0, idx1, cb.quad_radix, esc_val);
+    }
+    iwriter.compact_cell(esc_vals);
+}
+
+fn compress_inter_cell_mode10(iwriter: &mut IndexWriter, cell: &mut [u8; 64], diffs: &[i16; 16], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) {
+    for y in 0..4 {
+        let cb = cbs[y];
+        let esc_val = esc_vals[y];
+        let mut indices = [0, 0];
+        for pair_no in (0..4).step_by(2) {
+            let src_idx = y * 8 * 2 + pair_no * 2;
+            let src0 = [cell[src_idx], cell[src_idx + 1], cell[src_idx + 8], cell[src_idx + 9]];
+            let src1 = [cell[src_idx + 2], cell[src_idx + 3], cell[src_idx + 10], cell[src_idx + 11]];
+
+            let cur_diff = [diffs[y * 4 + pair_no] as i8, diffs[y * 4 + pair_no + 1] as i8];
+
+            let mut best_idx = 0;
+            let mut best_dist = pair_dist(&cur_diff, &[0, 0]);
+            for (idx, cbpair) in cb.data.chunks(2).enumerate().skip(1) {
+                let dist = pair_dist(&cur_diff, cbpair);
+                if dist < best_dist {
+                    let mut fits = true;
+                    for &el in src0.iter() {
+                        if !in_range(el as i8, cbpair[0]) {
+                            fits = false;
+                            break;
+                        }
+                    }
+                    for &el in src1.iter() {
+                        if !in_range(el as i8, cbpair[1]) {
+                            fits = false;
+                            break;
+                        }
+                    }
+                    if fits {
+                        best_dist = dist;
+                        best_idx = idx;
+                    }
+                }
+            }
+
+            indices[pair_no / 2] = best_idx as u8;
+
+            let cb_pair = &cb.data[best_idx * 2..];
+            for row in cell[src_idx..].chunks_mut(8).take(2) {
+                row[0] = ((row[0] as i8) + cb_pair[0]) as u8;
+                row[1] = ((row[1] as i8) + cb_pair[0]) as u8;
+                row[2] = ((row[2] as i8) + cb_pair[1]) as u8;
+                row[3] = ((row[3] as i8) + cb_pair[1]) as u8;
+            }
+        }
+        iwriter.write_pair(indices[1], indices[0], cb.quad_radix, esc_val);
+    }
+    iwriter.compact_cell(esc_vals);
+}
+
+fn compress_inter_cell_mode11(iwriter: &mut IndexWriter, cell: &mut [u8; 32], diffs: &[i16; 16], cbs: &[&IviDeltaCB; 4], esc_vals: [u8; 4]) {
+    for y in 0..4 {
+        let cb = cbs[y];
+        let esc_val = esc_vals[y];
+        let mut indices = [0, 0];
+        for pair_no in (0..4).step_by(2) {
+            let src_idx = y * 4 * 2 + pair_no;
+            let src0 = [cell[src_idx], cell[src_idx + 4]];
+            let src1 = [cell[src_idx + 1], cell[src_idx + 5]];
+
+            let cur_diff = [diffs[y * 4 + pair_no] as i8, diffs[y * 4 + pair_no + 1] as i8];
+
+            let mut best_idx = 0;
+            let mut best_dist = pair_dist(&cur_diff, &[0, 0]);
+            for (idx, cbpair) in cb.data.chunks(2).enumerate().skip(1) {
+                let dist = pair_dist(&cur_diff, cbpair);
+                if dist < best_dist {
+                    let mut fits = true;
+                    for &el in src0.iter() {
+                        if !in_range(el as i8, cbpair[0]) {
+                            fits = false;
+                            break;
+                        }
+                    }
+                    for &el in src1.iter() {
+                        if !in_range(el as i8, cbpair[1]) {
+                            fits = false;
+                            break;
+                        }
+                    }
+                    if fits {
+                        best_dist = dist;
+                        best_idx = idx;
+                    }
+                }
+            }
+
+            indices[pair_no / 2] = best_idx as u8;
+
+            let cb_pair = &cb.data[best_idx * 2..];
+            cell[src_idx]     = ((cell[src_idx]     as i8) + cb_pair[0]) as u8;
+            cell[src_idx + 4] = ((cell[src_idx + 4] as i8) + cb_pair[0]) as u8;
+            cell[src_idx + 1] = ((cell[src_idx + 1] as i8) + cb_pair[1]) as u8;
+            cell[src_idx + 5] = ((cell[src_idx + 5] as i8) + cb_pair[1]) as u8;
+        }
+        iwriter.write_pair(indices[1], indices[0], cb.quad_radix, esc_val);
+    }
+    iwriter.compact_cell(esc_vals);
+}
+
+fn pair_dist(src: &[i8], pair: &[i8]) -> u32 {
+    let d0 = (i32::from(src[0]) - i32::from(pair[0])).abs() as u32;
+    let d1 = (i32::from(src[1]) - i32::from(pair[1])).abs() as u32;
+    d0 * d0 + d1 * d1
+}
+
+fn in_range(base: i8, delta: i8) -> bool {
+    if let Some(val) = base.checked_add(delta) {
+        val >= 0
+    } else {
+        false
+    }
+}
+
+fn find_pair(cb_data: &[i8], ppair: &[u8], cpair: &[u8]) -> u8 {
+    let ppair = [ppair[0] as i8, ppair[1] as i8];
+    let diff = [(cpair[0] as i8) - ppair[0], (cpair[1] as i8) - ppair[1]];
+    // pair 0 is always zero;
+    if diff == [0, 0] {
+        return 0;
+    }
+    let mut best_idx = 0;
+    let mut best_dist = pair_dist(&diff, &[0, 0]);
+    for (idx, cbpair) in cb_data.chunks(2).enumerate().skip(1) {
+        let dist = pair_dist(&diff, cbpair);
+        if dist < best_dist && in_range(ppair[0], cbpair[0]) && in_range(ppair[1], cbpair[1]) {
+            best_dist = dist;
+            best_idx = idx;
+        }
+    }
+    best_idx as u8
+}
+
+fn find_quad(cb_data: &[i8], prev: &[u8], cur: &[u8]) -> (u8, u8) {
+    let (ppair1, ppair0) = prev.split_at(2);
+    let (cpair1, cpair0) = cur.split_at(2);
+    let idx1 = find_pair(cb_data, ppair1, cpair1);
+    let idx0 = find_pair(cb_data, ppair0, cpair0);
+    (idx0, idx1)
+}
+
+const REQUANT_TAB: [[u8; 128]; 8] = [
+  [
+    0x00, 0x02, 0x02, 0x04, 0x04, 0x06, 0x06, 0x08,
+    0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x0e, 0x0e, 0x10,
+    0x10, 0x12, 0x12, 0x14, 0x14, 0x16, 0x16, 0x18,
+    0x18, 0x1a, 0x1a, 0x1c, 0x1c, 0x1e, 0x1e, 0x20,
+    0x20, 0x22, 0x22, 0x24, 0x24, 0x26, 0x26, 0x28,
+    0x28, 0x2a, 0x2a, 0x2c, 0x2c, 0x2e, 0x2e, 0x30,
+    0x30, 0x32, 0x32, 0x34, 0x34, 0x36, 0x36, 0x38,
+    0x38, 0x3a, 0x3a, 0x3c, 0x3c, 0x3e, 0x3e, 0x40,
+    0x40, 0x42, 0x42, 0x44, 0x44, 0x46, 0x46, 0x48,
+    0x48, 0x4a, 0x4a, 0x4c, 0x4c, 0x4e, 0x4e, 0x50,
+    0x50, 0x52, 0x52, 0x54, 0x54, 0x56, 0x56, 0x58,
+    0x58, 0x5a, 0x5a, 0x5c, 0x5c, 0x5e, 0x5e, 0x60,
+    0x60, 0x62, 0x62, 0x64, 0x64, 0x66, 0x66, 0x68,
+    0x68, 0x6a, 0x6a, 0x6c, 0x6c, 0x6e, 0x6e, 0x70,
+    0x70, 0x72, 0x72, 0x74, 0x74, 0x76, 0x76, 0x78,
+    0x78, 0x7a, 0x7a, 0x7c, 0x7c, 0x7e, 0x7e, 0x7e
+  ], [
+    0x01, 0x01, 0x04, 0x04, 0x04, 0x07, 0x07, 0x0a,
+    0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, 0x10, 0x10,
+    0x10, 0x13, 0x13, 0x13, 0x16, 0x16, 0x16, 0x19,
+    0x19, 0x19, 0x1c, 0x1c, 0x1c, 0x1f, 0x1f, 0x1f,
+    0x22, 0x22, 0x22, 0x25, 0x25, 0x25, 0x28, 0x28,
+    0x28, 0x2b, 0x2b, 0x2b, 0x2e, 0x2e, 0x2e, 0x31,
+    0x31, 0x31, 0x34, 0x34, 0x34, 0x37, 0x37, 0x37,
+    0x3a, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x40, 0x40,
+    0x40, 0x43, 0x43, 0x43, 0x46, 0x46, 0x46, 0x49,
+    0x49, 0x49, 0x4c, 0x4c, 0x4c, 0x4f, 0x4f, 0x4f,
+    0x52, 0x52, 0x52, 0x55, 0x55, 0x55, 0x58, 0x58,
+    0x58, 0x5b, 0x5b, 0x5b, 0x5e, 0x5e, 0x5e, 0x61,
+    0x61, 0x61, 0x64, 0x64, 0x64, 0x67, 0x67, 0x67,
+    0x6a, 0x6a, 0x6a, 0x6d, 0x6d, 0x6d, 0x70, 0x70,
+    0x70, 0x73, 0x73, 0x73, 0x76, 0x76, 0x76, 0x76,
+    0x76, 0x79, 0x7c, 0x7c, 0x7c, 0x7f, 0x7f, 0x7f
+  ], [
+    0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08,
+    0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x10, 0x10,
+    0x10, 0x10, 0x14, 0x14, 0x14, 0x14, 0x18, 0x18,
+    0x18, 0x18, 0x1c, 0x1c, 0x1c, 0x1c, 0x20, 0x20,
+    0x20, 0x20, 0x24, 0x24, 0x24, 0x24, 0x28, 0x28,
+    0x28, 0x28, 0x2c, 0x2c, 0x2c, 0x2c, 0x30, 0x30,
+    0x30, 0x30, 0x34, 0x34, 0x34, 0x34, 0x38, 0x38,
+    0x38, 0x38, 0x3c, 0x3c, 0x3c, 0x3c, 0x40, 0x40,
+    0x40, 0x40, 0x44, 0x44, 0x44, 0x44, 0x48, 0x48,
+    0x48, 0x48, 0x4c, 0x4c, 0x4c, 0x4c, 0x50, 0x50,
+    0x50, 0x50, 0x54, 0x54, 0x54, 0x54, 0x58, 0x58,
+    0x58, 0x58, 0x5c, 0x5c, 0x5c, 0x5c, 0x60, 0x60,
+    0x60, 0x60, 0x64, 0x64, 0x64, 0x64, 0x68, 0x68,
+    0x68, 0x68, 0x6c, 0x6c, 0x6c, 0x6c, 0x70, 0x70,
+    0x70, 0x70, 0x74, 0x74, 0x74, 0x74, 0x78, 0x78,
+    0x78, 0x78, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c
+  ], [
+    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+    0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e,
+    0x0e, 0x0e, 0x13, 0x13, 0x13, 0x13, 0x13, 0x18,
+    0x18, 0x18, 0x18, 0x18, 0x1d, 0x1d, 0x1d, 0x1d,
+    0x1d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x27, 0x27,
+    0x27, 0x27, 0x27, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+    0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x45, 0x45, 0x45, 0x45,
+    0x45, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4f, 0x4f,
+    0x4f, 0x4f, 0x4f, 0x54, 0x54, 0x54, 0x54, 0x54,
+    0x59, 0x59, 0x59, 0x59, 0x59, 0x5e, 0x5e, 0x5e,
+    0x5e, 0x5e, 0x63, 0x63, 0x63, 0x63, 0x63, 0x68,
+    0x68, 0x68, 0x68, 0x68, 0x6d, 0x6d, 0x6d, 0x6d,
+    0x6d, 0x72, 0x72, 0x72, 0x72, 0x72, 0x77, 0x77,
+    0x77, 0x77, 0x77, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c
+  ], [
+    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+    0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x10,
+    0x10, 0x10, 0x10, 0x10, 0x10, 0x16, 0x16, 0x16,
+    0x16, 0x16, 0x16, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+    0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x28,
+    0x28, 0x28, 0x28, 0x28, 0x28, 0x2e, 0x2e, 0x2e,
+    0x2e, 0x2e, 0x2e, 0x34, 0x34, 0x34, 0x34, 0x34,
+    0x34, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x46, 0x46, 0x46,
+    0x46, 0x46, 0x46, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,
+    0x4c, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x58,
+    0x58, 0x58, 0x58, 0x58, 0x58, 0x5e, 0x5e, 0x5e,
+    0x5e, 0x5e, 0x5e, 0x64, 0x64, 0x64, 0x64, 0x64,
+    0x64, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x70,
+    0x70, 0x70, 0x70, 0x70, 0x70, 0x76, 0x76, 0x76,
+    0x76, 0x76, 0x76, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c
+  ], [
+    0x01, 0x01, 0x01, 0x01, 0x08, 0x08, 0x08, 0x08,
+    0x08, 0x08, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+    0x0f, 0x0f, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+    0x16, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+    0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x2b,
+    0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x32, 0x32,
+    0x32, 0x32, 0x32, 0x32, 0x32, 0x39, 0x39, 0x39,
+    0x39, 0x39, 0x39, 0x39, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x47, 0x47, 0x47, 0x47, 0x47,
+    0x47, 0x47, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e,
+    0x4e, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x63,
+    0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x6a, 0x6a,
+    0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x71, 0x71, 0x71,
+    0x71, 0x71, 0x71, 0x71, 0x78, 0x78, 0x78, 0x78,
+    0x78, 0x78, 0x78, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
+  ], [
+    0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08,
+    0x08, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10,
+    0x10, 0x10, 0x10, 0x10, 0x18, 0x18, 0x18, 0x18,
+    0x18, 0x18, 0x18, 0x18, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x28,
+    0x28, 0x28, 0x28, 0x28, 0x30, 0x30, 0x30, 0x30,
+    0x30, 0x30, 0x30, 0x30, 0x38, 0x38, 0x38, 0x38,
+    0x38, 0x38, 0x38, 0x38, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x48, 0x48, 0x48, 0x48,
+    0x48, 0x48, 0x48, 0x48, 0x50, 0x50, 0x50, 0x50,
+    0x50, 0x50, 0x50, 0x50, 0x58, 0x58, 0x58, 0x58,
+    0x58, 0x58, 0x58, 0x58, 0x60, 0x60, 0x60, 0x60,
+    0x60, 0x60, 0x60, 0x60, 0x68, 0x68, 0x68, 0x68,
+    0x68, 0x68, 0x68, 0x68, 0x70, 0x70, 0x70, 0x70,
+    0x70, 0x70, 0x70, 0x70, 0x78, 0x78, 0x78, 0x78,
+    0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78
+  ], [
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x0a, 0x0a, 0x0a,
+    0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x13, 0x13,
+    0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x1c,
+    0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+    0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+    0x25, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+    0x2e, 0x2e, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+    0x37, 0x37, 0x37, 0x40, 0x40, 0x40, 0x40, 0x40,
+    0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x49, 0x49,
+    0x49, 0x49, 0x49, 0x49, 0x49, 0x52, 0x52, 0x52,
+    0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x5b, 0x5b,
+    0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x64,
+    0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
+    0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d,
+    0x6d, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76,
+    0x76, 0x76, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
+  ]
+];
diff --git a/nihav-indeo/src/codecs/indeo3enc/mod.rs b/nihav-indeo/src/codecs/indeo3enc/mod.rs
new file mode 100644 (file)
index 0000000..8ff2aec
--- /dev/null
@@ -0,0 +1,660 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+
+mod cell;
+use cell::*;
+mod mv;
+use mv::*;
+mod ratectl;
+use ratectl::*;
+mod tree;
+pub use tree::{Indeo3Cell, Plane};
+use tree::Indeo3PrimaryTree;
+
+const OS_HEADER_SIZE: usize = 16;
+const BITSTREAM_HEADER_SIZE: usize = 48;
+const HDR_FIELD_2: u32 = 0;
+const FRMH_TAG: u32 = ((b'F' as u32) << 24) | ((b'R' as u32) << 16)
+                     | ((b'M' as u32) << 8) | (b'H' as u32);
+const PLANE_OFFSETS: usize = 32;
+
+const CB_SELECTORS: [u8; 16] = [
+    0x02, 0x14, 0x26, 0x38, 0x4A, 0x5C, 0x6E, 0x7F,
+    0x82, 0x94, 0xA6, 0xB8, 0xCA, 0xDC, 0xEE, 0xFF
+];
+
+const PLANE_ORDER: [usize; 3] = [1, 2, 0];
+
+pub struct Indeo3Writer<'a> {
+    dst:    &'a mut Vec<u8>,
+    bitbuf: u8,
+    bits:   u8,
+    bitpos: Option<usize>,
+}
+
+impl<'a> Indeo3Writer<'a> {
+    fn new(dst: &'a mut Vec<u8>) -> Self {
+        Self {
+            dst,
+            bitbuf: 0,
+            bits:   0,
+            bitpos: None,
+        }
+    }
+    pub fn put_byte(&mut self, b: u8) {
+        self.dst.push(b);
+    }
+    pub fn put_2bits(&mut self, val: u8) {
+        if self.bits == 0 {
+            self.bitpos = Some(self.dst.len());
+            self.dst.push(0);
+        }
+        self.bitbuf |= val << (6 - self.bits);
+        self.bits += 2;
+        if self.bits == 8 {
+            let bpos = self.bitpos.unwrap_or(0);
+            self.dst[bpos] = self.bitbuf;
+            self.bitbuf = 0;
+            self.bits   = 0;
+            self.bitpos = None;
+        }
+    }
+}
+
+impl<'a> Drop for Indeo3Writer<'a> {
+    fn drop(&mut self) {
+        if self.bits != 0 {
+            let bpos = self.bitpos.unwrap_or(0);
+            self.dst[bpos] = self.bitbuf;
+        }
+    }
+}
+
+#[derive(Default)]
+struct Indeo3Frame {
+    plane:  [Plane; 3],
+}
+
+impl Indeo3Frame {
+    fn new() -> Self { Self::default() }
+    fn alloc(&mut self, width: usize, height: usize) {
+        self.plane[0].alloc(width,     height,     40);
+        self.plane[1].alloc(width / 4, height / 4, 10);
+        self.plane[2].alloc(width / 4, height / 4, 10);
+    }
+    fn fill(&mut self, vbuf: &NAVideoBufferRef<u8>) {
+        let data = vbuf.get_data();
+        for (plane_no, plane) in self.plane.iter_mut().enumerate() {
+            plane.fill(&data[vbuf.get_offset(plane_no)..], vbuf.get_stride(plane_no));
+        }
+    }
+    fn clear_mvs(&mut self) {
+        for plane in self.plane.iter_mut() {
+            plane.clear_mvs();
+        }
+    }
+}
+
+struct Indeo3Encoder {
+    stream:     Option<NAStreamRef>,
+    pkt:        Option<NAPacket>,
+    cframe:     Indeo3Frame,
+    pframe:     Indeo3Frame,
+    cenc:       CellEncoder,
+    mv_est:     MotionEstimator,
+    rc:         RateControl,
+    frameno:    u32,
+    buf_sel:    bool,
+    width:      usize,
+    height:     usize,
+
+    debug_tree: bool,
+    debug_frm:  bool,
+    try_again:  bool,
+}
+
+impl Indeo3Encoder {
+    fn new() -> Self {
+        Self {
+            stream:     None,
+            pkt:        None,
+            cframe:     Indeo3Frame::new(),
+            pframe:     Indeo3Frame::new(),
+            cenc:       CellEncoder::new(),
+            mv_est:     MotionEstimator::new(),
+            rc:         RateControl::new(),
+            frameno:    0,
+            buf_sel:    false,
+            width:      0,
+            height:     0,
+
+            debug_tree: false,
+            debug_frm:  false,
+            try_again:  false,
+        }
+    }
+    fn encode_planes(&mut self, dbuf: &mut Vec<u8>, trees: &[Box<Indeo3PrimaryTree>], is_intra: bool) -> EncoderResult<()> {
+        for (&planeno, tree) in PLANE_ORDER.iter().zip(trees.iter()) {
+            let offset = dbuf.len();
+            let ref_plane = &self.pframe.plane[planeno];
+
+            let mut mc_count = [0; 4];
+            let mvs = &self.cframe.plane[planeno].mvs;
+            write_u32le(&mut mc_count, mvs.len() as u32)?;
+            dbuf.extend_from_slice(&mc_count);
+            for &(mv, _) in mvs.iter() {
+                dbuf.push(mv.y as u8);
+                dbuf.push(mv.x as u8);
+            }
+
+            let mut iw = Indeo3Writer::new(dbuf);
+            self.cframe.plane[planeno].encode_tree(&mut iw, &tree, &mut self.cenc, is_intra, ref_plane);
+            drop(iw);
+            while (dbuf.len() & 3) != 0 {
+                dbuf.push(0);
+            }
+
+            let plane_off = PLANE_OFFSETS + 4 * if planeno > 0 { planeno ^ 3 } else { 0 };
+            write_u32le(&mut dbuf[plane_off..], (offset - OS_HEADER_SIZE) as u32)?;
+        }
+
+        let mut checksum = 0;
+        for plane in self.cframe.plane.iter() {
+            checksum ^= plane.checksum();
+        }
+        write_u16le(&mut dbuf[26..], checksum)?;
+
+        let size = (dbuf.len() - OS_HEADER_SIZE) as u32;
+        write_u32le(&mut dbuf[8..], self.frameno ^ HDR_FIELD_2 ^ FRMH_TAG ^ size)?;
+        write_u32le(&mut dbuf[12..], size)?;
+        write_u32le(&mut dbuf[20..], size * 8)?;
+
+        if is_intra {
+            dbuf.extend_from_slice(b"\x0d\x0aVer 3.99.00.00\x0d\x0a\x00");
+            while (dbuf.len() & 3) != 0 {
+                dbuf.push(0);
+            }
+        }
+
+        Ok(())
+    }
+}
+
+impl NAEncoder for Indeo3Encoder {
+    fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> {
+        match encinfo.format {
+            NACodecTypeInfo::None => {
+                Ok(EncodeParameters {
+                        format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV410_FORMAT)),
+                        ..Default::default()
+                    })
+            },
+            NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Video(vinfo) => {
+                let pix_fmt = YUV410_FORMAT;
+                let outinfo = NAVideoInfo::new((vinfo.width + 15) & !15, (vinfo.height + 15) & !15, false, pix_fmt);
+                let mut ofmt = *encinfo;
+                ofmt.format = NACodecTypeInfo::Video(outinfo);
+                Ok(ofmt)
+            }
+        }
+    }
+    fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> {
+        match encinfo.format {
+            NACodecTypeInfo::None => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
+            NACodecTypeInfo::Video(vinfo) => {
+                if vinfo.format != YUV410_FORMAT {
+                    return Err(EncoderError::FormatError);
+                }
+                if ((vinfo.width | vinfo.height) & 15) != 0 {
+                    return Err(EncoderError::FormatError);
+                }
+                if (vinfo.width > 640) || (vinfo.height > 480) {
+                    return Err(EncoderError::FormatError);
+                }
+
+                self.width  = vinfo.width;
+                self.height = vinfo.height;
+
+                let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, false, vinfo.format);
+                let info = NACodecInfo::new("indeo3", NACodecTypeInfo::Video(out_info), None);
+                let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0);
+                stream.set_num(stream_id as usize);
+                let stream = stream.into_ref();
+
+                self.stream = Some(stream.clone());
+
+                self.cframe.alloc(vinfo.width, vinfo.height);
+                self.pframe.alloc(vinfo.width, vinfo.height);
+
+                self.rc.set_bitrate(encinfo.bitrate, encinfo.tb_num, encinfo.tb_den);
+                self.rc.set_quality(encinfo.quality);
+
+                Ok(stream)
+            },
+        }
+    }
+    fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> {
+        let buf = frm.get_buffer();
+        if self.debug_tree || self.debug_frm {
+            println!("frame {}:", self.frameno);
+        }
+
+        let mut skip_frame = frm.get_frame_type() == FrameType::Skip;
+        if let NABufferType::None = buf {
+            skip_frame = true;
+        }
+        if skip_frame {
+            let mut dbuf = Vec::with_capacity(16);
+            let mut gw   = GrowableMemoryWriter::new_write(&mut dbuf);
+            let mut bw   = ByteWriter::new(&mut gw);
+
+            // OS header
+            bw.write_u32le(self.frameno)?;
+            bw.write_u32le(HDR_FIELD_2)?;
+            bw.write_u32le(0)?; // check
+            bw.write_u32le(0)?; // size
+
+            // bitstream header
+            bw.write_u16le(32)?; // version
+            bw.write_u16le(0)?;
+            bw.write_u32le(0)?; // data size in bits
+            bw.write_byte(0)?; // cb offset
+            bw.write_byte(14)?; // reserved
+            bw.write_u16le(0)?; // checksum
+            bw.write_u16le(self.height as u16)?;
+            bw.write_u16le(self.width as u16)?;
+
+            let size = (dbuf.len() - OS_HEADER_SIZE) as u32;
+            write_u32le(&mut dbuf[8..], self.frameno ^ HDR_FIELD_2 ^ FRMH_TAG ^ size)?;
+            write_u32le(&mut dbuf[12..], size)?;
+            write_u32le(&mut dbuf[20..], size * 8)?;
+
+            let fsize = dbuf.len() as u32;
+            self.rc.advance(fsize);
+
+            self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, false, dbuf));
+            return Ok(());
+        }
+
+        if let Some(ref vbuf) = buf.get_vbuf() {
+            let mut dbuf = Vec::with_capacity(16);
+            let mut gw   = GrowableMemoryWriter::new_write(&mut dbuf);
+            let mut bw   = ByteWriter::new(&mut gw);
+
+            let (width, height) = vbuf.get_dimensions(0);
+            if width != self.width || height != self.height {
+                self.width  = width;
+                self.height = height;
+                self.cframe.alloc(width, height);
+                self.pframe.alloc(width, height);
+                self.rc.reset();
+            }
+
+            let (is_intra, quant) = self.rc.get_quant(self.frameno);
+            self.cenc.quant = quant;
+
+            if is_intra {
+                self.buf_sel = false;
+            } else {
+                self.buf_sel = !self.buf_sel;
+            }
+
+            self.cframe.fill(vbuf);
+            self.cframe.clear_mvs();
+
+            // OS header
+            bw.write_u32le(self.frameno)?;
+            bw.write_u32le(HDR_FIELD_2)?;
+            bw.write_u32le(0)?; // check
+            bw.write_u32le(0)?; // size
+
+            // bitstream header
+            bw.write_u16le(32)?; // version
+            let mut flags = 0;
+            if is_intra {
+                flags |= 0x5;
+            } else {
+                flags |= 1;
+                if self.buf_sel {
+                    flags |= 1 << 9;
+                }
+            }
+            bw.write_u16le(flags)?;
+            bw.write_u32le(0)?; // data size in bits
+            bw.write_byte(0)?; // cb offset
+            bw.write_byte(14)?; // reserved
+            bw.write_u16le(0)?; // checksum
+            bw.write_u16le(height as u16)?;
+            bw.write_u16le(width as u16)?;
+            for _ in 0..3 {
+                bw.write_u32le(0)?; // plane data offset
+            }
+            bw.write_u32le(0)?; // reserved
+            bw.write_buf(&CB_SELECTORS)?;
+
+            let mut trees = Vec::with_capacity(PLANE_ORDER.len());
+
+            // prepare plane data structure
+            for &planeno in PLANE_ORDER.iter() {
+                let ref_plane = &self.pframe.plane[planeno];
+                let tree = self.cframe.plane[planeno].find_cells(is_intra, ref_plane, &self.mv_est);
+                if self.debug_tree {
+                    println!(" tree for plane {}:", planeno);
+                    tree.print();
+                }
+                trees.push(tree);
+                let mvs = &mut self.cframe.plane[planeno].mvs;
+                compact_mvs(mvs);
+            }
+
+            self.encode_planes(&mut dbuf, &trees, is_intra)?;
+
+            let cur_quant = self.cenc.quant.unwrap_or(42);
+            if !is_intra && cur_quant < 8 {
+                let expected_size = self.rc.get_expected_size();
+                if expected_size > 0 {
+                    let cur_size = dbuf.len() as u32;
+                    // try re-encoding frame if possible
+                    if cur_size > expected_size * 3 / 2 {
+                        self.cframe.fill(vbuf);
+                        let new_quant = if cur_quant < 7 {
+                                cur_quant + 1
+                            } else {
+                                cur_quant - 1
+                            };
+                        self.cenc.quant = Some(new_quant);
+                        dbuf.truncate(OS_HEADER_SIZE + BITSTREAM_HEADER_SIZE);
+                        self.encode_planes(&mut dbuf, &trees, is_intra)?;
+                    }
+                }
+            }
+
+            if self.debug_frm {
+                for plane in self.cframe.plane.iter() {
+                    for (y, line) in plane.data.chunks(plane.width).enumerate() {
+                        print!(" {:3}:", y);
+                        for &el in line.iter() { print!(" {:02X}", el); }
+                        println!();
+                    }
+                    println!();
+                }
+            }
+
+            std::mem::swap(&mut self.cframe, &mut self.pframe);
+            self.frameno += 1;
+
+            let fsize = dbuf.len() as u32;
+            self.rc.advance(fsize);
+
+            self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf));
+            Ok(())
+        } else {
+            Err(EncoderError::InvalidParameters)
+        }
+    }
+    fn get_packet(&mut self) -> EncoderResult<Option<NAPacket>> {
+        let mut npkt = None;
+        std::mem::swap(&mut self.pkt, &mut npkt);
+        Ok(npkt)
+    }
+    fn flush(&mut self) -> EncoderResult<()> {
+        Ok(())
+    }
+}
+
+const DEBUG_TREE_OPTION: &str = "debug_tree";
+const DEBUG_FRAME_OPTION: &str = "debug_frame";
+const MV_RANGE_OPTION: &str = "mv_range";
+const MV_FLAT_OPTION: &str = "mv_flat_threshold";
+const MV_THRESHOLD_OPTION: &str = "mv_threshold";
+const CELL_I_THRESHOLD_OPTION: &str = "cell_i_threshold";
+const CELL_P_THRESHOLD_OPTION: &str = "cell_p_threshold";
+const DO_RLE_OPTION: &str = "rle";
+const TRY_AGAIN_OPTION: &str = "try_recompress";
+
+const ENCODER_OPTS: &[NAOptionDefinition] = &[
+    NAOptionDefinition {
+        name: KEYFRAME_OPTION, description: KEYFRAME_OPTION_DESC,
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) },
+    NAOptionDefinition {
+        name: DEBUG_TREE_OPTION, description: "Print frame trees",
+        opt_type: NAOptionDefinitionType::Bool },
+    NAOptionDefinition {
+        name: DEBUG_FRAME_OPTION, description: "Print encoder-reconstructed frames",
+        opt_type: NAOptionDefinitionType::Bool },
+    NAOptionDefinition {
+        name: MV_RANGE_OPTION, description: "Motion search range",
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(120)) },
+    NAOptionDefinition {
+        name: MV_FLAT_OPTION, description: "Threshold for coding cell as skipped one",
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(1000)) },
+    NAOptionDefinition {
+        name: MV_THRESHOLD_OPTION, description: "Threshold for coding cell as inter",
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(1000)) },
+    NAOptionDefinition {
+        name: CELL_I_THRESHOLD_OPTION, description: "Threshold for coding intra block as flat",
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) },
+    NAOptionDefinition {
+        name: CELL_P_THRESHOLD_OPTION, description: "Threshold for coding inter cell in coarser mode",
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) },
+    NAOptionDefinition {
+        name: DO_RLE_OPTION, description: "Perform zero run length compation",
+        opt_type: NAOptionDefinitionType::Bool },
+    NAOptionDefinition {
+        name: TRY_AGAIN_OPTION, description: "Try compressing the frame again for the better bitrate fit",
+        opt_type: NAOptionDefinitionType::Bool },
+];
+
+impl NAOptionHandler for Indeo3Encoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { ENCODER_OPTS }
+    fn set_options(&mut self, options: &[NAOption]) {
+        for option in options.iter() {
+            for opt_def in ENCODER_OPTS.iter() {
+                if opt_def.check(option).is_ok() {
+                    match option.name {
+                        KEYFRAME_OPTION => {
+                            if let NAValue::Int(val) = option.value {
+                                self.rc.set_key_int(val as u32);
+                            }
+                        },
+                        DEBUG_TREE_OPTION => {
+                            if let NAValue::Bool(val) = option.value {
+                                self.debug_tree = val;
+                            }
+                        },
+                        DEBUG_FRAME_OPTION => {
+                            if let NAValue::Bool(val) = option.value {
+                                self.debug_frm = val;
+                            }
+                        },
+                        MV_RANGE_OPTION => {
+                            if let NAValue::Int(val) = option.value {
+                                self.mv_est.mv_range = val as i8;
+                            }
+                        },
+                        MV_FLAT_OPTION => {
+                            if let NAValue::Int(val) = option.value {
+                                self.mv_est.flat_thr = val as u16;
+                            }
+                        },
+                        MV_THRESHOLD_OPTION => {
+                            if let NAValue::Int(val) = option.value {
+                                self.mv_est.mv_thr = val as u16;
+                            }
+                        },
+                        CELL_I_THRESHOLD_OPTION => {
+                            if let NAValue::Int(val) = option.value {
+                                self.cenc.flat_thr_i = val as u32;
+                            }
+                        },
+                        CELL_P_THRESHOLD_OPTION => {
+                            if let NAValue::Int(val) = option.value {
+                                self.cenc.flat_thr_p = val as u32;
+                            }
+                        },
+                        DO_RLE_OPTION => {
+                            if let NAValue::Bool(val) = option.value {
+                                self.cenc.do_rle = val;
+                            }
+                        },
+                        TRY_AGAIN_OPTION => {
+                            if let NAValue::Bool(val) = option.value {
+                                self.try_again = val;
+                            }
+                        },
+                        _ => {},
+                    };
+                }
+            }
+        }
+    }
+    fn query_option_value(&self, name: &str) -> Option<NAValue> {
+        match name {
+            KEYFRAME_OPTION => Some(NAValue::Int(i64::from(self.rc.get_key_int()))),
+            DEBUG_TREE_OPTION => Some(NAValue::Bool(self.debug_tree)),
+            DEBUG_FRAME_OPTION => Some(NAValue::Bool(self.debug_frm)),
+            MV_RANGE_OPTION => Some(NAValue::Int(i64::from(self.mv_est.mv_range))),
+            MV_FLAT_OPTION => Some(NAValue::Int(i64::from(self.mv_est.flat_thr))),
+            MV_THRESHOLD_OPTION => Some(NAValue::Int(i64::from(self.mv_est.mv_thr))),
+            CELL_I_THRESHOLD_OPTION => Some(NAValue::Int(i64::from(self.cenc.flat_thr_i))),
+            CELL_P_THRESHOLD_OPTION => Some(NAValue::Int(i64::from(self.cenc.flat_thr_p))),
+            DO_RLE_OPTION => Some(NAValue::Bool(self.cenc.do_rle)),
+            TRY_AGAIN_OPTION => Some(NAValue::Bool(self.try_again)),
+            _ => None,
+        }
+    }
+}
+
+pub fn get_encoder() -> Box<dyn NAEncoder + Send> {
+    Box::new(Indeo3Encoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use crate::*;
+    use nihav_core::codecs::*;
+    use nihav_core::demuxers::*;
+    use nihav_core::muxers::*;
+    use nihav_commonfmt::*;
+    use nihav_codec_support::test::enc_video::*;
+
+    #[allow(unused_variables)]
+    fn encode_test(name: &'static str, enc_options: &[NAOption], limit: Option<u64>, hash: &[u32; 4]) {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        indeo_register_all_decoders(&mut dec_reg);
+        let mut mux_reg = RegisteredMuxers::new();
+        generic_register_all_muxers(&mut mux_reg);
+        let mut enc_reg = RegisteredEncoders::new();
+        indeo_register_all_encoders(&mut enc_reg);
+
+        let dec_config = DecoderTestParams {
+                demuxer:        "avi",
+                in_name:        "assets/Indeo/laser05.avi",
+                stream_type:    StreamType::Video,
+                limit,
+                dmx_reg, dec_reg,
+            };
+        let enc_config = EncoderTestParams {
+                muxer:          "avi",
+                enc_name:       "indeo3",
+                out_name:       name,
+                mux_reg, enc_reg,
+            };
+        let dst_vinfo = NAVideoInfo {
+                width:   0,
+                height:  0,
+                format:  YUV410_FORMAT,
+                flipped: false,
+                bits:    9,
+            };
+        let enc_params = EncodeParameters {
+                format:  NACodecTypeInfo::Video(dst_vinfo),
+                quality: 0,
+                bitrate: 25000 * 8,
+                tb_num:  0,
+                tb_den:  0,
+                flags:   0,
+            };
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, hash);
+    }
+    #[test]
+    fn test_indeo3_encoder1() {
+        let enc_options = &[
+                NAOption { name: super::TRY_AGAIN_OPTION, value: NAValue::Bool(true) },
+            ];
+        encode_test("indeo3.avi", enc_options, Some(4), &[0x4cc927d3, 0x9872f824, 0x92dee9cb, 0xaf912ecc]);
+    }
+    /*#[test]
+    fn test_indeo3_roundtrip() {
+        const YPATTERN: [u8; 16] = [32, 72, 40, 106, 80, 20, 33, 58, 77, 140, 121, 100, 83, 57, 30, 11];
+        const CPATTERN: [u8; 4] = [0x80; 4];
+
+        let dst_vinfo = NAVideoInfo {
+                width:   16,
+                height:  16,
+                format:  YUV410_FORMAT,
+                flipped: false,
+                bits:    9,
+            };
+        let enc_params = EncodeParameters {
+                format:  NACodecTypeInfo::Video(dst_vinfo),
+                quality: 0,
+                bitrate: 0,
+                tb_num:  0,
+                tb_den:  0,
+                flags:   0,
+            };
+
+        let mut ienc = super::get_encoder();
+        ienc.init(0, enc_params).unwrap();
+        let mut buffer = alloc_video_buffer(dst_vinfo, 2).unwrap();
+        if let NABufferType::Video(ref mut buf) = buffer {
+            let vbuf = NASimpleVideoFrame::from_video_buf(buf).unwrap();
+            for i in 0..16 {
+                vbuf.data[vbuf.offset[0] + i * vbuf.stride[0]..][..16].copy_from_slice(&YPATTERN);
+            }
+            for plane in 1..3 {
+                for i in 0..4 {
+                    vbuf.data[vbuf.offset[plane] + i * vbuf.stride[plane]..][..4].copy_from_slice(&CPATTERN);
+                }
+            }
+        }
+        let info = NACodecInfo::new("indeo3", NACodecTypeInfo::Video(dst_vinfo), None).into_ref();
+        let frm = NAFrame::new(NATimeInfo::new(Some(0), None, None, 1, 12), FrameType::I, true, info.clone(), buffer);
+        ienc.encode(&frm).unwrap();
+        let pkt = ienc.get_packet().unwrap().unwrap();
+        println!(" pkt size {}", pkt.get_buffer().len());
+
+        let mut dec_reg = RegisteredDecoders::new();
+        indeo_register_all_decoders(&mut dec_reg);
+        let decfunc = dec_reg.find_decoder("indeo3").unwrap();
+        let mut dec = (decfunc)();
+        let mut dsupp = Box::new(NADecoderSupport::new());
+        dec.init(&mut dsupp, info).unwrap();
+        let dst = dec.decode(&mut dsupp, &pkt).unwrap();
+        if let NABufferType::Video(ref vbuf) = dst.get_buffer() {
+            for plane in 0..3 {
+                let size = if plane == 0 { 16 } else { 4 };
+                let start = vbuf.get_offset(plane);
+                for line in vbuf.get_data()[start..].chunks(vbuf.get_stride(plane)).take(size) {
+                    print!("   ");
+                    for &el in line[..size].iter() {
+                        print!(" {:02X}", el >> 1);
+                    }
+                    println!();
+                }
+                if plane == 0 {
+                    print!("ref");
+                    for &el in YPATTERN.iter() { print!(" {:02X}", el >> 1); } println!();
+                }
+                println!();
+            }
+        }
+        panic!("end");
+    }*/
+}
diff --git a/nihav-indeo/src/codecs/indeo3enc/mv.rs b/nihav-indeo/src/codecs/indeo3enc/mv.rs
new file mode 100644 (file)
index 0000000..498a6ef
--- /dev/null
@@ -0,0 +1,153 @@
+use super::{Indeo3Cell, Plane};
+
+const MV_THRESHOLD: u16 = 64;
+const FLAT_THRESHOLD: u16 = 8;
+
+const DIA_LARGE: [(i8, i8); 5] = [(0, 0), (2, 0), (0, 2), (-2, 0), (0, -2)];
+const DIA_SMALL: [(i8, i8); 5] = [(0, 0), (1, 0), (0, 1), (-1, 0), (0, -1)];
+const SEARCH_RANGE: i8 = 16;
+
+#[derive(Clone, Copy, PartialEq)]
+pub struct MV {
+    pub x: i8,
+    pub y: i8
+}
+
+pub struct MotionEstimator {
+    pub mv_range:   i8,
+    pub flat_thr:   u16,
+    pub mv_thr:     u16,
+}
+
+impl MotionEstimator {
+    pub fn new() -> Self {
+        Self {
+            mv_range:       SEARCH_RANGE,
+            flat_thr:       FLAT_THRESHOLD,
+            mv_thr:         MV_THRESHOLD,
+        }
+    }
+    pub fn mv_search(&self, cur: &Plane, prev: &Plane, cell: Indeo3Cell) -> Option<(MV, bool)> {
+        let plane_w = prev.width  as isize;
+        let plane_h = prev.height as isize;
+        let cell_w = cell.get_width()  as isize;
+        let cell_h = cell.get_height() as isize;
+        let start_x = cell.get_x() as isize;
+        let start_y = cell.get_y() as isize;
+
+        let check_mv = |mv: MV| {
+                if mv.x.abs() < SEARCH_RANGE && mv.y.abs() < SEARCH_RANGE {
+                    let new_x = start_x + isize::from(mv.x);
+                    let new_y = start_y + isize::from(mv.y);
+                    new_x >= 0 && new_x + cell_w <= plane_w && new_y >= 0 && new_y + cell_h <= plane_h
+                } else {
+                    false
+                }
+            };
+
+        let area = (cell.get_width() * cell.get_height()) as u32;
+        let flat_thr = u32::from(self.flat_thr) * area;
+
+        let mut best_mv = MV{ x: 0, y: 0 };
+        let mut best_score = calc_mv_score(cur, prev, cell, best_mv);
+
+        if best_score < flat_thr {
+            return Some((best_mv, true));
+        }
+
+        let mut found_better = true;
+        while found_better {
+            found_better = false;
+            for step in DIA_LARGE.iter() {
+                let new_mv = MV{ x: best_mv.x + step.0, y: best_mv.y + step.1 };
+                if !check_mv(new_mv) {
+                    continue;
+                }
+                let score = calc_mv_score(cur, prev, cell, new_mv);
+                if score < best_score {
+                    best_mv = new_mv;
+                    best_score = score;
+                    found_better = true;
+                    if best_score < flat_thr {
+                        return Some((best_mv, true));
+                    }
+                }
+            }
+        }
+        for step in DIA_SMALL.iter() {
+            let new_mv = MV{ x: best_mv.x + step.0, y: best_mv.y + step.1 };
+            if !check_mv(new_mv) {
+                continue;
+            }
+            let score = calc_mv_score(cur, prev, cell, new_mv);
+            if score < best_score {
+                best_mv = new_mv;
+                best_score = score;
+                if best_score < flat_thr {
+                    break;
+                }
+            }
+        }
+        let score = (best_score / area) as u16;
+        if score < self.mv_thr {
+            Some((best_mv, false))
+        } else {
+            None
+        }
+    }
+}
+
+fn calc_cell_diff(src1: &[u8], src2: &[u8], stride: usize, width: usize, height: usize) -> u32 {
+    let mut score = 0;
+    for (line1, line2) in src1.chunks(stride).zip(src2.chunks(stride)).take(height) {
+        for (&a, &b) in line1.iter().zip(line2.iter()).take(width) {
+            let diff = if a >= b { u32::from(a - b) } else { u32::from(b - a) };
+            score += diff * diff;
+        }
+    }
+    score
+}
+
+fn calc_mv_score(cur: &Plane, prev: &Plane, cell: Indeo3Cell, mv: MV) -> u32 {
+    let xoff = (cell.get_x() as isize + isize::from(mv.x)) as usize;
+    let yoff = (cell.get_y() as isize + isize::from(mv.y)) as usize;
+
+    let cur_ptr = &cur.data[cell.get_x() + (cell.get_y() + 1) * cur.width..];
+    let ref_ptr = &prev.data[xoff + (yoff + 1) * prev.width..];
+
+    calc_cell_diff(cur_ptr, ref_ptr, cur.width, cell.get_width(), cell.get_height())
+}
+
+fn get_mv_diff(mv1: MV, mv2: MV) -> i8 {
+    (mv1.x - mv2.x).abs() + (mv1.y - mv2.y).abs()
+}
+
+pub fn compact_mvs(mvs: &mut Vec<(MV, u16)>) {
+    mvs.sort_by(|a, b| a.1.cmp(&b.1));
+    while mvs.len() > 256 {
+        let (mv, _) = mvs.pop().unwrap();
+        if let Some(idx) = find_mv(mv, mvs) {
+            mvs[usize::from(idx)].1 += 1;
+        }
+    }
+}
+
+pub fn find_mv(mv: MV, mvs: &[(MV, u16)]) -> Option<u8> {
+    let mut best_idx = None;
+    let mut best_cand_diff = 42;
+    let mut best_cand_count = 0;
+    for (i, &(cand_mv, count)) in mvs.iter().enumerate() {
+        if cand_mv == mv {
+            return Some(i as u8);
+        }
+        let diff = get_mv_diff(mv, cand_mv);
+        if diff <= 2 {
+            if diff < best_cand_diff || (diff == best_cand_diff && count > best_cand_count) {
+                best_idx = Some(i as u8);
+                best_cand_diff = diff;
+                best_cand_count = count;
+            }
+        }
+    }
+    best_idx
+}
diff --git a/nihav-indeo/src/codecs/indeo3enc/ratectl.rs b/nihav-indeo/src/codecs/indeo3enc/ratectl.rs
new file mode 100644 (file)
index 0000000..4bb74c0
--- /dev/null
@@ -0,0 +1,77 @@
+#[derive(Default)]
+pub struct RateControl {
+    bitrate:    u32,
+    br_pool:    u32,
+    f_pos:      u32,
+    fracs:      u32,
+    key_int:    u32,
+    tb_num:     u32,
+    tb_den:     u32,
+    quality:    Option<u8>,
+}
+
+impl RateControl {
+    pub fn new() -> Self { Self{ key_int: 10, ..Default::default() } }
+    pub fn set_quality(&mut self, quality: u8) {
+        if quality > 0 {
+            self.quality = Some((quality - 1).min(15));
+        } else {
+            self.quality = None;
+        }
+    }
+    pub fn set_bitrate(&mut self, br: u32, tb_num: u32, tb_den: u32) {
+        if tb_num > 0 && tb_den > 0 {
+            self.bitrate = br / 8;
+            self.tb_num  = tb_num;
+            self.tb_den  = tb_den;
+        } else {
+            self.bitrate = 0;
+            self.tb_num  = 0;
+            self.tb_den  = 0;
+        }
+        self.quality = Some(0);
+        self.reset();
+    }
+    pub fn set_key_int(&mut self, key_int: u32) {
+        self.key_int = key_int;
+        self.reset();
+    }
+    pub fn reset(&mut self) {
+        self.br_pool = self.bitrate;
+        self.f_pos   = 0;
+        self.fracs   = self.tb_den;
+    }
+    pub fn get_key_int(&self) -> u32 { self.key_int }
+    pub fn get_quant(&self, frameno: u32) -> (bool, Option<u8>) {
+        let is_intra = self.key_int == 0 || (frameno % self.key_int) == 0;
+        (is_intra, self.quality)
+    }
+    pub fn advance(&mut self, size: u32) {
+        if self.bitrate != 0 {
+            let expected = self.get_expected_size();
+            let cur_quant = self.quality.unwrap_or(0);
+            if (size > expected + expected / 10) && (cur_quant < 7) {
+                self.quality = Some(cur_quant + 1);
+            } else if (size < expected - expected / 10) && (cur_quant > 0) {
+                self.quality = Some(cur_quant - 1);
+            }
+
+            self.f_pos += self.tb_num;
+            while self.f_pos >= self.tb_den {
+                self.f_pos   -= self.tb_den;
+                self.br_pool += self.bitrate;
+                self.fracs   += self.tb_den;
+            }
+            self.fracs -= self.tb_num;
+
+            self.br_pool = self.br_pool.saturating_sub(size).min(self.bitrate * 3);
+        }
+    }
+    pub fn get_expected_size(&self) -> u32 {
+        if self.bitrate != 0 {
+            self.br_pool * self.tb_num / self.fracs
+        } else {
+            0
+        }
+    }
+}
diff --git a/nihav-indeo/src/codecs/indeo3enc/tree.rs b/nihav-indeo/src/codecs/indeo3enc/tree.rs
new file mode 100644 (file)
index 0000000..8c51ad7
--- /dev/null
@@ -0,0 +1,432 @@
+use super::Indeo3Writer;
+use super::mv::*;
+use super::cell::{CellEncoder, MAX_CELL_SIZE};
+
+pub enum Indeo3PrimaryTree {
+    VSplit(Box<Indeo3PrimaryTree>, Box<Indeo3PrimaryTree>),
+    HSplit(Box<Indeo3PrimaryTree>, Box<Indeo3PrimaryTree>),
+    RelFill(MV, Box<Indeo3SecondaryTree>),
+    AbsFill(Box<Indeo3SecondaryTree>),
+}
+
+impl Indeo3PrimaryTree {
+    pub fn print(&self) {
+        println!("Plane tree:");
+        self.print1(1);
+    }
+    fn print1(&self, depth: u8) {
+        for _ in 0..depth {
+            print!("    ");
+        }
+        match self {
+            Indeo3PrimaryTree::VSplit(t1, t2) => {
+                println!("vertical split");
+                t1.print1(depth + 1);
+                t2.print1(depth + 1);
+            },
+            Indeo3PrimaryTree::HSplit(t1, t2) => {
+                println!("horizontal split");
+                t1.print1(depth + 1);
+                t2.print1(depth + 1);
+            },
+            Indeo3PrimaryTree::RelFill(mv, sec) => {
+                println!("relative fill {},{}", mv.x, mv.y);
+                sec.print1(depth + 1);
+            },
+            Indeo3PrimaryTree::AbsFill(sec) => {
+                println!("absolute fill");
+                sec.print1(depth + 1);
+            }
+        }
+    }
+}
+
+pub enum Indeo3SecondaryTree {
+    VSplit(Box<Indeo3SecondaryTree>, Box<Indeo3SecondaryTree>),
+    HSplit(Box<Indeo3SecondaryTree>, Box<Indeo3SecondaryTree>),
+    VQData(u8),
+    VQNull(u8),
+}
+
+impl Indeo3SecondaryTree {
+    fn print1(&self, depth: u8) {
+        for _ in 0..depth {
+            print!("    ");
+        }
+        match self {
+            Indeo3SecondaryTree::VSplit(t1, t2) => {
+                println!("vertical split");
+                t1.print1(depth + 1);
+                t2.print1(depth + 1);
+            },
+            Indeo3SecondaryTree::HSplit(t1, t2) => {
+                println!("horizontal split");
+                t1.print1(depth + 1);
+                t2.print1(depth + 1);
+            },
+            Indeo3SecondaryTree::VQData(mode) => {
+                println!("VQ data ({})", mode);
+            },
+            Indeo3SecondaryTree::VQNull(mode) => {
+                println!("VQ Null ({})", mode);
+            }
+        }
+    }
+}
+
+const THRESHOLD: u32 = 64;
+
+#[derive(Clone, Copy)]
+pub struct Indeo3Cell {
+    x:      u8,
+    y:      u8,
+    w:      u8,
+    h:      u8,
+    intra:  bool,
+}
+
+impl Indeo3Cell {
+    pub fn new(width: usize, height: usize, intra: bool) -> Self {
+        Self {
+            x:      0,
+            y:      0,
+            w:      (width / 4) as u8,
+            h:      (height / 4) as u8,
+            intra,
+        }
+    }
+
+    pub fn get_x(&self) -> usize { usize::from(self.x) * 4 }
+    pub fn get_y(&self) -> usize { usize::from(self.y) * 4 }
+    pub fn get_width(&self) -> usize { usize::from(self.w) * 4 }
+    pub fn get_height(&self) -> usize { usize::from(self.h) * 4 }
+    pub fn is_intra(&self) -> bool { self.intra }
+
+    fn split_h(&self) -> (Self, Self) {
+        let h1 = if self.h > 2 { ((self.h + 2) >> 2) << 1 } else { 1 };
+        let h2 = self.h - h1;
+        let mut cell1 = *self;
+        cell1.h  = h1;
+        let mut cell2 = *self;
+        cell2.y += h1;
+        cell2.h  = h2;
+        (cell1, cell2)
+    }
+    fn split_v(&self, stripw: u8) -> (Self, Self) {
+        let w1 = if self.w > stripw {
+                if self.w > stripw * 2 { stripw * 2 } else { stripw }
+            } else {
+                if self.w > 2 { ((self.w + 2) >> 2) << 1 } else { 1 }
+            };
+        let w2 = self.w - w1;
+        let mut cell1 = *self;
+        cell1.w  = w1;
+        let mut cell2 = *self;
+        cell2.x += w1;
+        cell2.w  = w2;
+        (cell1, cell2)
+    }
+}
+
+impl std::fmt::Display for Indeo3Cell {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "[{}x{} @ {},{}{}]", self.get_width(), self.get_height(), self.get_x(), self.get_y(), if self.intra { " intra" } else { "" })
+    }
+}
+
+#[derive(Default)]
+pub struct Plane {
+    pub data:   Vec<u8>,
+    pub width:  usize,
+    pub height: usize,
+    pub stripw: u8,
+    pub mvs:    Vec<(MV, u16)>,
+}
+
+fn ssd(a: u8, b: u8) -> u32 {
+    let diff = i32::from(a) - i32::from(b);
+    (diff * diff) as u32
+}
+
+impl Plane {
+    pub fn alloc(&mut self, width: usize, height: usize, stripw: u8) {
+        self.data.resize(width * height, 0);
+        self.width  = width;
+        self.height = height;
+        self.stripw = stripw;
+        if self.mvs.capacity() < 256 {
+            self.mvs.reserve(256);
+        }
+    }
+    pub fn fill(&mut self, src: &[u8], stride: usize) {
+        for (dline, sline) in self.data.chunks_mut(self.width).zip(src.chunks(stride)) {
+            for (dst, &src) in dline.iter_mut().zip(sline.iter()) {
+                *dst = src >> 1;
+            }
+        }
+    }
+    pub fn clear_mvs(&mut self){
+        self.mvs.clear();
+    }
+    pub fn checksum(&self) -> u16 {
+        let xors = self.data[self.width..].chunks(2).fold([0u8; 2], |acc, pair| [acc[0] ^ pair[0], acc[1] ^ pair[1]]);
+        u16::from(xors[0]) | (u16::from(xors[1]) * 256)
+    }
+    pub fn find_cells(&mut self, is_intra: bool, pplane: &Plane, mv_est: &MotionEstimator) -> Box<Indeo3PrimaryTree> {
+        let cell = Indeo3Cell::new(self.width, self.height, is_intra);
+        self.split_pri(cell, pplane, mv_est)
+    }
+    fn split_pri(&mut self, cell: Indeo3Cell, pplane: &Plane, mv_est: &MotionEstimator) -> Box<Indeo3PrimaryTree> {
+        let width  = cell.get_width();
+        let height = cell.get_height();
+        if width * height > MAX_CELL_SIZE {
+            let (hsplit, vsplit) = if width != height {
+                    (width > (self.stripw as usize) * 4 || width > height, height > width)
+                } else {
+                    let (hdiff, vdiff) = self.calculate_diffs(cell);
+                    (vdiff > THRESHOLD && vdiff > hdiff,
+                     hdiff > THRESHOLD && hdiff > vdiff)
+                };
+            match (hsplit, vsplit) {
+                (true, _) => {
+                    let (cell1, cell2) = cell.split_v(self.stripw);
+                    let tree1 = self.split_pri(cell1, pplane, mv_est);
+                    let tree2 = self.split_pri(cell2, pplane, mv_est);
+                    Box::new(Indeo3PrimaryTree::VSplit(tree1, tree2))
+                },
+                (_, true) => {
+                    let (cell1, cell2) = cell.split_h();
+                    let tree1 = self.split_pri(cell1, pplane, mv_est);
+                    let tree2 = self.split_pri(cell2, pplane, mv_est);
+                    Box::new(Indeo3PrimaryTree::HSplit(tree1, tree2))
+                },
+                (false, false) => {
+                    let sec = self.split_sec(cell);
+                    Box::new(Indeo3PrimaryTree::AbsFill(sec))
+                },
+            }
+        } else {
+            if !cell.intra {
+                if let Some((mv, flat)) = mv_est.mv_search(self, pplane, cell) {
+                    return self.add_mv_tree(mv, flat);
+                }
+
+                // try splitting once to see if it improves situation
+                if width >= 16 && height >= 16 {
+                    let vsplit = width > height;
+                    let (cell1, cell2) = if vsplit {
+                            cell.split_v(self.stripw)
+                        } else {
+                            cell.split_h()
+                        };
+                    let search1 = mv_est.mv_search(self, pplane, cell1);
+                    let search2 = mv_est.mv_search(self, pplane, cell2);
+                    if search1.is_some() || search2.is_some() {
+                        let tree1 = if let Some((mv, flat)) = search1 {
+                                self.add_mv_tree(mv, flat)
+                            } else {
+                                let sec = self.split_sec(cell1);
+                                Box::new(Indeo3PrimaryTree::AbsFill(sec))
+                            };
+                        let tree2 = if let Some((mv, flat)) = search2 {
+                                self.add_mv_tree(mv, flat)
+                            } else {
+                                let sec = self.split_sec(cell2);
+                                Box::new(Indeo3PrimaryTree::AbsFill(sec))
+                            };
+                        return if vsplit {
+                                Box::new(Indeo3PrimaryTree::VSplit(tree1, tree2))
+                            } else {
+                                Box::new(Indeo3PrimaryTree::HSplit(tree1, tree2))
+                            }
+                    }
+                }
+            }
+            let sec = self.split_sec(cell);
+            Box::new(Indeo3PrimaryTree::AbsFill(sec))
+        }
+    }
+    fn add_mv_tree(&mut self, mv: MV, flat: bool) -> Box<Indeo3PrimaryTree> {
+        let sec = if flat {
+                Box::new(Indeo3SecondaryTree::VQNull(0))
+            } else {
+                Box::new(Indeo3SecondaryTree::VQData(0))
+            };
+
+        let mut found = false;
+        for (ref cmv, ref mut count) in self.mvs.iter_mut() {
+            if cmv == &mv {
+                *count += 1;
+                found = true;
+                break;
+            }
+        }
+        if !found {
+            self.mvs.push((mv, 1));
+        }
+
+        Box::new(Indeo3PrimaryTree::RelFill(mv, sec))
+    }
+    fn split_sec(&mut self, cell: Indeo3Cell) -> Box<Indeo3SecondaryTree> {
+        let (hdiff, vdiff) = self.calculate_diffs(cell);
+        if hdiff == 0 && vdiff == 0 {
+            if !cell.intra {
+                return Box::new(Indeo3SecondaryTree::VQNull(0));
+            } else {
+                return Box::new(Indeo3SecondaryTree::VQData(0));
+            }
+        }
+        if cell.get_width() > 16 && cell.get_height() > 16 {
+            let hsplit = vdiff > THRESHOLD && vdiff > hdiff * 2;
+            let vsplit = hdiff > THRESHOLD && hdiff > vdiff * 2;
+            match (vsplit, hsplit) {
+                (true, _) => {
+                    let (cell1, cell2) = cell.split_v(self.stripw);
+                    let tree1 = self.split_sec(cell1);
+                    let tree2 = self.split_sec(cell2);
+                    Box::new(Indeo3SecondaryTree::VSplit(tree1, tree2))
+                },
+                (_, true) => {
+                    let (cell1, cell2) = cell.split_h();
+                    let tree1 = self.split_sec(cell1);
+                    let tree2 = self.split_sec(cell2);
+                    Box::new(Indeo3SecondaryTree::HSplit(tree1, tree2))
+                },
+                _ => {
+                    Box::new(Indeo3SecondaryTree::VQData(0))
+                },
+            }
+        } else {
+            let is_w8 = (cell.get_width()  & 7) == 0;
+            let is_h8 = (cell.get_height() & 7) == 0;
+            let mode = match (hdiff > THRESHOLD, vdiff > THRESHOLD) {
+                    (false, false) if is_w8 && is_h8 => 10,
+                    (_, true)      if is_h8          => 3,
+                    _                                => 0,
+                };
+            Box::new(Indeo3SecondaryTree::VQData(mode))
+        }
+    }
+    fn calculate_diffs(&self, cell: Indeo3Cell) -> (u32, u32) {
+        let offset = cell.get_x() + cell.get_y() * self.width;
+        let mut w = cell.get_width();
+        if cell.get_x() + w == self.width { w -= 1; }
+        let mut h = cell.get_height();
+        if cell.get_y() + h == self.height { h -= 1; }
+
+        let mut vdiff = 0;
+        let mut hdiff = 0;
+        let src0 = &self.data[offset..];
+        let src1 = &self.data[offset + self.width..];
+        for (line0, line1) in src0.chunks(self.width).zip(src1.chunks(self.width)).take(h) {
+            for ((&cur, &right), &bottom) in line0.iter().zip(line0[1..].iter()).zip(line1.iter()).take(w) {
+                hdiff += ssd(cur, right);
+                vdiff += ssd(cur, bottom);
+            }
+        }
+        let area = (w * h) as u32;
+        (hdiff * 16 / area, vdiff * 16 / area)
+    }
+    pub fn encode_tree(&mut self, iw: &mut Indeo3Writer, tree: &Indeo3PrimaryTree, cenc: &mut CellEncoder, is_intra: bool, refp: &Plane) {
+        let cell = Indeo3Cell::new(self.width, self.height, is_intra);
+        self.encode_pri(iw, cell, tree, cenc, refp);
+    }
+    fn encode_pri(&mut self, iw: &mut Indeo3Writer, mut cell: Indeo3Cell, tree: &Indeo3PrimaryTree, cenc: &mut CellEncoder, refp: &Plane) {
+        match tree {
+            Indeo3PrimaryTree::HSplit(t1, t2) => {
+                iw.put_2bits(0);
+                let (cell1, cell2) = cell.split_h();
+                self.encode_pri(iw, cell1, t1, cenc, refp);
+                self.encode_pri(iw, cell2, t2, cenc, refp);
+            },
+            Indeo3PrimaryTree::VSplit(t1, t2) => {
+                iw.put_2bits(1);
+                let (cell1, cell2) = cell.split_v(self.stripw);
+                self.encode_pri(iw, cell1, t1, cenc, refp);
+                self.encode_pri(iw, cell2, t2, cenc, refp);
+            },
+            Indeo3PrimaryTree::AbsFill(sec) => {
+                iw.put_2bits(2);
+                cell.intra = true;
+                self.encode_sec(iw, cell, sec, cenc);
+            }
+            Indeo3PrimaryTree::RelFill(mv, sec) => {
+                if let Some(mv_idx) = find_mv(*mv, &self.mvs) {
+                    iw.put_2bits(3);
+                    iw.put_byte(mv_idx);
+                    cell.intra = false;
+                    let real_mv = self.mvs[usize::from(mv_idx)].0;
+                    self.encode_sec_inter(iw, cell, sec, cenc, real_mv, refp);
+                } else {
+                    iw.put_2bits(2);
+                    cell.intra = true;
+                    self.encode_sec(iw, cell, sec, cenc);
+                }
+            },
+        }
+    }
+    fn encode_sec(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, tree: &Indeo3SecondaryTree, cenc: &mut CellEncoder) {
+        match tree {
+            Indeo3SecondaryTree::HSplit(t1, t2) => {
+                iw.put_2bits(0);
+                let (cell1, cell2) = cell.split_h();
+                self.encode_sec(iw, cell1, t1, cenc);
+                self.encode_sec(iw, cell2, t2, cenc);
+            },
+            Indeo3SecondaryTree::VSplit(t1, t2) => {
+                iw.put_2bits(1);
+                let (cell1, cell2) = cell.split_v(self.stripw);
+                self.encode_sec(iw, cell1, t1, cenc);
+                self.encode_sec(iw, cell2, t2, cenc);
+            },
+            Indeo3SecondaryTree::VQNull(mode) => {
+                iw.put_2bits(2);
+                iw.put_2bits(*mode);
+            },
+            Indeo3SecondaryTree::VQData(mode) => {
+                iw.put_2bits(3);
+                self.encode_cell_data_intra(iw, cell, cenc, *mode);
+            },
+        }
+    }
+    fn encode_sec_inter(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, tree: &Indeo3SecondaryTree, cenc: &mut CellEncoder, mv: MV, refp: &Plane) {
+        match tree {
+            Indeo3SecondaryTree::HSplit(_t1, _t2) => {
+                unimplemented!();
+            },
+            Indeo3SecondaryTree::VSplit(_t1, _t2) => {
+                unimplemented!();
+            },
+            Indeo3SecondaryTree::VQNull(mode) => {
+                iw.put_2bits(2);
+                iw.put_2bits(*mode);
+                cenc.read_mv_buffer(refp, cell, mv);
+                cenc.null_mv();
+                cenc.put_buffer(self);
+            },
+            Indeo3SecondaryTree::VQData(_mode) => {
+                iw.put_2bits(3);
+                self.encode_cell_data_inter(iw, cell, cenc, mv, refp);
+            },
+        }
+    }
+    fn encode_cell_data_intra(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, cenc: &mut CellEncoder, mode: u8) {
+        cenc.read_buffer(self, cell);
+        cenc.gen_diffs_intra();
+        cenc.compress_intra(mode);
+        cenc.put_buffer(self);
+        for &b in cenc.out[..cenc.osize].iter() {
+            iw.put_byte(b);
+        }
+    }
+    fn encode_cell_data_inter(&mut self, iw: &mut Indeo3Writer, cell: Indeo3Cell, cenc: &mut CellEncoder, mv: MV, refp: &Plane) {
+        cenc.read_buffer(self, cell);
+        cenc.read_mv_buffer(refp, cell, mv);
+        cenc.gen_diffs_inter();
+        cenc.compress_inter();
+        cenc.put_buffer(self);
+        for &b in cenc.out[..cenc.osize].iter() {
+            iw.put_byte(b);
+        }
+    }
+}
index 9525d7c1e5fc04bd1e4792a93579e85465aa75cf..85523d6b6a4e151a4087879fcd0179f088c34cd3 100644 (file)
@@ -15,7 +15,7 @@ mod intel263;
 mod indeo2;
 #[cfg(feature="decoder_indeo3")]
 mod indeo3;
-#[cfg(feature="decoder_indeo3")]
+#[cfg(any(feature="decoder_indeo3", feature="encoder_indeo3"))]
 mod indeo3data;
 #[cfg(feature="decoder_indeo4")]
 mod indeo4;
@@ -61,3 +61,19 @@ pub fn indeo_register_all_decoders(rd: &mut RegisteredDecoders) {
         rd.add_decoder(*decoder);
     }
 }
+
+#[cfg(feature="encoder_indeo3")]
+mod indeo3enc;
+
+const INDEO_ENCODERS: &[EncoderInfo] = &[
+#[cfg(feature="encoder_indeo3")]
+    EncoderInfo { name: "indeo3", get_encoder: indeo3enc::get_encoder },
+];
+
+/// Registers all available encoders provided by this crate.
+pub fn indeo_register_all_encoders(re: &mut RegisteredEncoders) {
+    for encoder in INDEO_ENCODERS.iter() {
+        re.add_encoder(*encoder);
+    }
+}
+
index c709b237153ec24a5533b9582d5d02ce2b44d18c..22ef878a5e58032d4b683e7dee51bfc5eefbd6bf 100644 (file)
@@ -11,6 +11,7 @@ extern crate nihav_codec_support;
 mod codecs;
 
 pub use crate::codecs::indeo_register_all_decoders;
+pub use crate::codecs::indeo_register_all_encoders;
 
 mod demuxers;