Imagination Pilots Matte Animation decoder
authorKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 5 Oct 2021 10:56:52 +0000 (12:56 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 5 Oct 2021 10:56:52 +0000 (12:56 +0200)
nihav-game/Cargo.toml
nihav-game/src/codecs/ipma.rs [new file with mode: 0644]
nihav-game/src/codecs/mod.rs
nihav-registry/src/register.rs

index cfbfca8..8079a21 100644 (file)
@@ -32,12 +32,13 @@ demuxer_vx = ["demuxers"]
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 decoders = []
 
-all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_vmd", "decoder_vx"]
+all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_vmd", "decoder_vx"]
 decoder_bmv = ["decoders"]
 decoder_bmv3 = ["decoders"]
 decoder_fstvid = ["decoders"]
 decoder_gdvvid = ["decoders"]
 decoder_imax = ["decoders"]
+decoder_ipma = ["decoders"]
 decoder_midivid = ["decoders"]
 decoder_midivid3 = ["decoders"]
 decoder_q = ["decoders"]
diff --git a/nihav-game/src/codecs/ipma.rs b/nihav-game/src/codecs/ipma.rs
new file mode 100644 (file)
index 0000000..21f657d
--- /dev/null
@@ -0,0 +1,252 @@
+use nihav_core::codecs::*;
+use nihav_core::io::bitreader::*;
+
+const DICT_SIZE: usize = 4096;
+const START_BITS: u8 = 9;
+const MAX_BITS:   u8 = 12;
+const START_POS:   usize = 258;
+const INVALID_POS: usize = 65536;
+
+struct LZWState {
+    dict_sym:   [u8; DICT_SIZE],
+    dict_prev:  [u16; DICT_SIZE],
+    dict_pos:   usize,
+    dict_lim:   usize,
+    idx_bits:   u8,
+}
+
+impl LZWState {
+    fn new() -> Self {
+        Self {
+            dict_sym:   [0; DICT_SIZE],
+            dict_prev:  [0; DICT_SIZE],
+            dict_pos:   START_POS,
+            dict_lim:   1 << START_BITS,
+            idx_bits:   START_BITS,
+        }
+    }
+    fn reset(&mut self) {
+        self.dict_pos = START_POS;
+        self.dict_lim = 1 << START_BITS;
+        self.idx_bits = START_BITS;
+    }
+    fn add(&mut self, prev: usize, sym: u8) {
+        if self.dict_pos < self.dict_lim {
+            self.dict_sym [self.dict_pos] = sym;
+            self.dict_prev[self.dict_pos] = prev as u16;
+            self.dict_pos += 1;
+        }
+    }
+    fn decode_idx(&self, dst: &mut [u8], pos: usize, idx: usize) -> DecoderResult<usize> {
+        let mut tot_len = 1;
+        let mut tidx = idx;
+        while tidx > 256 {
+            tidx = self.dict_prev[tidx] as usize;
+            tot_len += 1;
+        }
+        validate!(pos + tot_len <= dst.len());
+
+        let mut end = pos + tot_len - 1;
+        let mut tidx = idx;
+        while tidx > 256 {
+            dst[end] = self.dict_sym[tidx];
+            end -= 1;
+            tidx = self.dict_prev[tidx] as usize;
+        }
+        dst[end] = tidx as u8;
+
+        Ok(tot_len)
+    }
+    #[allow(clippy::comparison_chain)]
+    fn decode(&mut self, br: &mut BitReader, dst: &mut [u8]) -> DecoderResult<()> {
+        self.reset();
+
+        let mut pos = 0;
+        let mut lastidx = INVALID_POS;
+        loop {
+            let idx         = br.read(self.idx_bits)? as usize;
+            if idx == 256 {
+                self.reset();
+                lastidx = INVALID_POS;
+                continue;
+            }
+            if idx == 257 {
+                break;
+            }
+            if idx < self.dict_pos {
+                let len = self.decode_idx(dst, pos, idx)?;
+                if lastidx != INVALID_POS {
+                    self.add(lastidx, dst[pos]);
+                }
+                pos += len;
+            } else if idx == self.dict_pos {
+                validate!(lastidx != INVALID_POS);
+                let len = self.decode_idx(dst, pos, lastidx)?;
+                let lastsym = dst[pos];
+                pos += len;
+                validate!(pos < dst.len());
+                dst[pos] = lastsym;
+                pos += 1;
+                self.add(lastidx, lastsym);
+            } else {
+                return Err(DecoderError::InvalidData);
+            }
+            lastidx = idx;
+            if self.dict_pos == self.dict_lim && self.idx_bits < MAX_BITS {
+                self.dict_lim <<= 1;
+                self.idx_bits += 1;
+            }
+        }
+        validate!(pos == dst.len());
+        Ok(())
+    }
+}
+
+struct IPMADecoder {
+    info:       NACodecInfoRef,
+    v1:         bool,
+    pal:        [u8; 768],
+    frame:      Vec<u8>,
+    fframe:     Vec<u8>,
+    dbuf:       Vec<u8>,
+    width:      usize,
+    height:     usize,
+    first:      bool,
+    lzw:        LZWState,
+}
+
+impl IPMADecoder {
+    fn new(v1: bool) -> Self {
+        Self {
+            info:       NACodecInfoRef::default(),
+            v1,
+            pal:        [0; 768],
+            frame:      Vec::new(),
+            fframe:     Vec::new(),
+            dbuf:       Vec::new(),
+            width:      0,
+            height:     0,
+            first:      false,
+            lzw:        LZWState::new(),
+        }
+    }
+}
+
+impl NADecoder for IPMADecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            self.width  = vinfo.width;
+            self.height = vinfo.height;
+            self.frame  = vec![0; self.width * self.height];
+            self.fframe = vec![0; self.width * self.height];
+            self.dbuf   = vec![0; self.width * self.height];
+            self.pal    = [0; 768];
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, true, PAL8_FORMAT));
+            self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
+
+            self.first = self.v1;
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() > 0);
+
+        for sd in pkt.side_data.iter() {
+            if let NASideData::Palette(true, ref pal) = sd {
+                for (dst, src) in self.pal.chunks_mut(3).zip(pal.chunks(4)) {
+                    dst[0] = src[0];
+                    dst[1] = src[1];
+                    dst[2] = src[2];
+                }
+                break;
+            }
+        }
+
+        let mut br = BitReader::new(&src, BitReaderMode::LE);
+        self.lzw.decode(&mut br, &mut self.dbuf)?;
+        if self.first {
+            self.fframe.copy_from_slice(&self.dbuf);
+            self.first = false;
+        }
+        for (dp, (&sp, &rp)) in self.frame.iter_mut().zip(self.dbuf.iter().zip(self.fframe.iter())) {
+            match sp {
+                0 => {},
+                1 => *dp = rp,
+                _ => *dp = sp,
+            };
+        }
+
+        let buf = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+        let mut vbuf = buf.get_vbuf().unwrap();
+        let paloff = vbuf.get_offset(1);
+        let stride = vbuf.get_stride(0);
+        let data = vbuf.get_data_mut().unwrap();
+
+        for (drow, srow) in data.chunks_mut(stride).zip(self.frame.chunks(self.width)) {
+            drow[..self.width].copy_from_slice(srow);
+        }
+        data[paloff..][..768].copy_from_slice(&self.pal);
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buf);
+        let ftype = if pkt.keyframe { FrameType::I } else { FrameType::P };
+        frm.set_frame_type(ftype);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for IPMADecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(IPMADecoder::new(true))
+}
+
+pub fn get_decoder_v2() -> Box<dyn NADecoder + Send> {
+    Box::new(IPMADecoder::new(false))
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::game_register_all_decoders;
+    use nihav_commonfmt::generic_register_all_demuxers;
+    #[test]
+    fn test_ipma_video() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        game_register_all_decoders(&mut dec_reg);
+
+        test_decoding("avi", "ipma", "assets/Game/lripa07.avi",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x13a4e2e5, 0xeac494b1, 0xef107b9f, 0x1213bbc6],
+                            [0x09e2684a, 0xe453ec76, 0xff1c3cee, 0xf38701c9],
+                            [0xd39258b1, 0x2491b8d9, 0x37dd4415, 0x8ddb4669]]));
+    }
+    #[test]
+    fn test_ip20_video() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        game_register_all_decoders(&mut dec_reg);
+
+        test_decoding("avi", "ipma2", "assets/Game/27mwza00.avi",
+                      Some(2), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x8b000e62, 0x2e4e0344, 0x471e40ca, 0x7b9f83d4],
+                            [0xc1429aa9, 0x3f2e856b, 0x80b27114, 0x25b5d453],
+                            [0xfa24d5fa, 0x29bf2e17, 0x3cf5f550, 0x296e29b0]]));
+    }
+}
index 799ecc8..8e735f2 100644 (file)
@@ -14,6 +14,8 @@ pub mod futurevision;
 pub mod gremlinvideo;
 #[cfg(feature="decoder_imax")]
 pub mod imax;
+#[cfg(feature="decoder_ipma")]
+pub mod ipma;
 #[cfg(feature="decoder_lhst500f22")]
 pub mod lhst500f22;
 #[cfg(feature="decoder_midivid")]
@@ -48,6 +50,10 @@ const GAME_CODECS: &[DecoderInfo] = &[
     DecoderInfo { name: "fst-video", get_decoder: futurevision::get_decoder_video },
 #[cfg(feature="decoder_imax")]
     DecoderInfo { name: "fable-imax", get_decoder: imax::get_decoder },
+#[cfg(feature="decoder_ipma")]
+    DecoderInfo { name: "ipma", get_decoder: ipma::get_decoder },
+#[cfg(feature="decoder_ipma")]
+    DecoderInfo { name: "ipma2", get_decoder: ipma::get_decoder_v2 },
 #[cfg(feature="decoder_q")]
     DecoderInfo { name: "legend-q-video", get_decoder: q::get_decoder },
 #[cfg(feature="decoder_vmd")]
index a89e2a7..1a8e1eb 100644 (file)
@@ -236,6 +236,8 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video;    "fable-imax",    "Fable IMAX video"),
     desc!(video;    "fst-video",     "FutureVision video"),
     desc!(audio;    "fst-audio",     "FutureVision audio"),
+    desc!(video-llp; "ipma",         "Imagination Pilots Matte Animation"),
+    desc!(video-llp; "ipma2",        "Imagination Pilots Matte Animation v2"),
     desc!(video;    "legend-q-video", "Legend Entertainment Q video"),
     desc!(video;    "midivid",       "MidiVid"),
     desc!(video;    "midivid3",      "MidiVid 3"),
@@ -290,6 +292,9 @@ static AVI_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[
     (b"cvid", "cinepak"),
     (b"ZMBV", "zmbv"),
 
+    (b"Ipma", "ipma"),
+    (b"Ip20", "ipma2"),
+
     (b"MVDV", "midivid"),
     (b"MV30", "midivid3"),
     (b"MVLZ", "midivid-ll"),