msvideo1enc: fix corner case in box split condition
[nihav.git] / nihav-ms / src / codecs / msvideo1enc.rs
index 9acfb3c78b8dd657e9dc69df5753f337a750fd03..959c5d5d3e2fca70a59ac098688d29878bbcc82f 100644 (file)
@@ -6,12 +6,14 @@ use nihav_codec_support::vq::*;
 struct Pixel16(u16);
 
 impl Pixel16 {
-    fn unpack(self) -> (u8, u8, u8) {
-        (((self.0 >> 10) & 0x1F) as u8, ((self.0 >> 5) & 0x1F) as u8, (self.0 & 0x1F) as u8)
+    fn unpack(self) -> (u16, u16, u16) {
+        ((self.0 >> 10) & 0x1F, (self.0 >> 5) & 0x1F, self.0 & 0x1F)
     }
-    fn pack(r: u8, g: u8, b: u8) -> Self {
-        Pixel16{ 0: (u16::from(r) << 10) | (u16::from(g) << 5) | u16::from(b) }
+    fn pack(r: u16, g: u16, b: u16) -> Self {
+        Pixel16{ 0: (r << 10) | (g << 5) | b }
     }
+    fn invalid() -> Self { Self(0x8000) }
+    fn is_invalid(self) -> bool { self == Self::invalid() }
 }
 impl VQElement for Pixel16 {
     fn dist(&self, rval: Self) -> u32 {
@@ -50,7 +52,8 @@ impl VQElement for Pixel16 {
         for i in 0..31 {
             offs[i + 1] = offs[i] + counts[i];
         }
-        let mut dst = vec![Pixel16(0); arr.len()];
+        let mut dst = [Pixel16(0); 16];
+        assert!(dst.len() >= arr.len());
         for pix in arr.iter() {
             let (r, g, b) = pix.unpack();
             let idx = match component {
@@ -61,7 +64,8 @@ impl VQElement for Pixel16 {
             dst[offs[idx]] = *pix;
             offs[idx] += 1;
         }
-        arr.copy_from_slice(dst.as_slice());
+        let len = arr.len();
+        arr.copy_from_slice(&dst[..len]);
     }
     fn max_dist_component(min: &Self, max: &Self) -> usize {
         let (r0, g0, b0) = max.unpack();
@@ -69,7 +73,7 @@ impl VQElement for Pixel16 {
         let rd = u32::from(r0) - u32::from(r1);
         let gd = u32::from(g0) - u32::from(g1);
         let bd = u32::from(b0) - u32::from(b1);
-        if rd > gd && rd > bd {
+        if rd > gd && rd >= bd {
             0
         } else if bd > rd && bd > gd {
             2
@@ -80,26 +84,27 @@ impl VQElement for Pixel16 {
 }
 
 struct Pixel16Sum {
-    rsum: u64,
-    gsum: u64,
-    bsum: u64,
-    count: u64,
+    rsum: u16,
+    gsum: u16,
+    bsum: u16,
+    count: u16,
 }
 
 impl VQElementSum<Pixel16> for Pixel16Sum {
     fn zero() -> Self { Pixel16Sum { rsum: 0, gsum: 0, bsum: 0, count: 0 } }
     fn add(&mut self, rval: Pixel16, count: u64) {
         let (r, g, b) = rval.unpack();
-        self.rsum += u64::from(r) * count;
-        self.gsum += u64::from(g) * count;
-        self.bsum += u64::from(b) * count;
+        let count = count as u16;
+        self.rsum += r * count;
+        self.gsum += g * count;
+        self.bsum += b * count;
         self.count += count;
     }
     fn get_centroid(&self) -> Pixel16 {
         if self.count != 0 {
-            let r = ((self.rsum + self.count / 2) / self.count) as u8;
-            let g = ((self.gsum + self.count / 2) / self.count) as u8;
-            let b = ((self.bsum + self.count / 2) / self.count) as u8;
+            let r = (self.rsum + self.count / 2) / self.count;
+            let g = (self.gsum + self.count / 2) / self.count;
+            let b = (self.bsum + self.count / 2) / self.count;
             Pixel16::pack(r, g, b)
         } else {
             Pixel16(0x0000)
@@ -120,20 +125,42 @@ struct BlockState {
 }
 
 impl BlockState {
+    fn calc_clrs(buf: &[Pixel16; 16]) -> Option<(Pixel16, Pixel16)> {
+        let     clr0 = buf[0];
+        let mut clr1 = Pixel16::invalid();
+        for &pix in buf[1..].iter() {
+            if pix != clr0 && pix != clr1 {
+                if clr1.is_invalid() {
+                    clr1 = pix;
+                } else {
+                    return None;
+                }
+            }
+        }
+        Some((clr0, clr1))
+    }
     fn calc_stats(&mut self, buf: &[Pixel16; 16]) {
-        let num_cw = quantise_median_cut::<Pixel16, Pixel16Sum>(buf, &mut self.clr2);
-        if num_cw == 1 {
-            self.fill_val = Pixel16 { 0: buf[0].0 & !0x400 };
-        } else {
+        let mut filled = false;
+        let mut two_clr = false;
+        if let Some((clr0, clr1)) = Self::calc_clrs(buf) {
+            self.clr2[0] = clr0;
+            self.clr2[1] = if !clr1.is_invalid() { clr1 } else { clr0 };
+            if clr0 == clr1 {
+                self.fill_val = Pixel16 { 0: buf[0].0 & !0x400 };
+                filled = true;
+            }
+            two_clr = true;
+        }
+        self.fill_dist = 0;
+        if !filled {
             let mut avg = Pixel16Sum::zero();
             for pix in buf.iter() {
                 avg.add(*pix, 1);
             }
             self.fill_val = Pixel16 { 0: avg.get_centroid().0 & !0x400 };
-        }
-        self.fill_dist = 0;
-        for pix in buf.iter() {
-            self.fill_dist += pix.dist(self.fill_val);
+            for pix in buf.iter() {
+                self.fill_dist += pix.dist(self.fill_val);
+            }
         }
         if self.fill_dist == 0 {
             self.clr2_dist = std::u32::MAX;
@@ -142,7 +169,21 @@ impl BlockState {
         }
 
         self.clr2_flags = 0u16;
-        if num_cw == 2 {
+        if two_clr {
+            let mut mask = 1;
+            self.clr2_dist = 0;
+            for &pix in buf.iter() {
+                if pix == self.clr2[0] {
+                    self.clr2_flags |= mask;
+                } else {
+                }
+                mask <<= 1;
+            }
+            if (self.clr2_flags & 0x8000) != 0 {
+                self.clr2_flags = !self.clr2_flags;
+                self.clr2.swap(0, 1);
+            }
+        } else if quantise_median_cut::<Pixel16, Pixel16Sum>(buf, &mut self.clr2) == 2 {
             let mut mask = 1;
             self.clr2_dist = 0;
             for pix in buf.iter() {
@@ -432,7 +473,7 @@ impl NAEncoder for MSVideo1Encoder {
 
                 let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, true, RGB555_FORMAT);
                 let info = NACodecInfo::new("msvideo1", NACodecTypeInfo::Video(out_info), None);
-                let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den);
+                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();
                 if self.pool.prealloc_video(out_info, 2).is_err() {
@@ -534,8 +575,8 @@ mod test {
         let mut dmx_reg = RegisteredDemuxers::new();
         generic_register_all_demuxers(&mut dmx_reg);
         let mut dec_reg = RegisteredDecoders::new();
-        generic_register_all_codecs(&mut dec_reg);
-        ms_register_all_codecs(&mut dec_reg);
+        generic_register_all_decoders(&mut dec_reg);
+        ms_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();
@@ -545,7 +586,7 @@ mod test {
                 demuxer:        "avi",
                 in_name:        "assets/Misc/TalkingHead_352x288.avi",
                 stream_type:    StreamType::Video,
-                limit:          Some(32),
+                limit:          Some(3),
                 dmx_reg, dec_reg,
             };
         let enc_config = EncoderTestParams {
@@ -569,6 +610,8 @@ mod test {
                 tb_den:  0,
                 flags:   0,
             };
-        test_encoding_to_file(&dec_config, &enc_config, enc_params);
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, &[]);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, &[],
+                          &[0x6921e67e, 0x4f2ada95, 0x009ffc62, 0xd4bfab6a]);
     }
 }