msvideo1enc: fix corner case in box split condition
[nihav.git] / nihav-ms / src / codecs / msvideo1enc.rs
index cdad0ce2aa49a2e9b50f624b85d1efe2bda85fcc..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() {
@@ -569,8 +610,8 @@ mod test {
                 tb_den:  0,
                 flags:   0,
             };
-        //test_encoding_to_file(&dec_config, &enc_config, enc_params);
-        test_encoding_md5(&dec_config, &enc_config, enc_params,
-                          &[0x0fc27a11, 0x04337f5d, 0xb8037362, 0xc4f69d8b]);
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, &[]);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, &[],
+                          &[0x6921e67e, 0x4f2ada95, 0x009ffc62, 0xd4bfab6a]);
     }
 }