]> git.nihav.org Git - nihav.git/commitdiff
QuickDraw decoder (with limited functionality)
authorKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 28 Jan 2026 17:48:13 +0000 (18:48 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 28 Jan 2026 17:48:13 +0000 (18:48 +0100)
nihav-qt/Cargo.toml
nihav-qt/src/codecs/mod.rs
nihav-qt/src/codecs/qdraw.rs [new file with mode: 0644]
nihav-registry/src/register.rs

index a5c78d57c1db6953e0a6b931a07344445af68634..c953a7f23c77c4bf26bdfe9cb585f6760557fe0f 100644 (file)
@@ -20,10 +20,11 @@ default = ["all_decoders"]
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 decoders = []
 
-all_video_decoders = ["decoder_rle", "decoder_smc", "decoder_rpza", "decoder_svq1", "decoder_svq3"]
+all_video_decoders = ["decoder_rle", "decoder_smc", "decoder_rpza", "decoder_qdraw", "decoder_svq1", "decoder_svq3"]
 decoder_rle = ["decoders"]
 decoder_smc = ["decoders"]
 decoder_rpza = ["decoders"]
+decoder_qdraw = ["decoders"]
 decoder_svq1 = ["decoders"]
 decoder_svq3 = ["decoders"]
 
index 5757c37a07a6b0dd7710297a11456adaffa60347..65921b89fbad88caa790bc204415febe1f4d0d79 100644 (file)
@@ -9,6 +9,9 @@ macro_rules! validate {
     ($a:expr) => { if !$a { return Err(DecoderError::InvalidData); } };
 }
 
+#[cfg(feature="decoder_qdraw")]
+mod qdraw;
+
 #[cfg(feature="decoder_rle")]
 mod rle;
 
@@ -61,6 +64,8 @@ mod qdm2qmf;
 mod qdm2;
 
 const QT_CODECS: &[DecoderInfo] = &[
+#[cfg(feature="decoder_qdraw")]
+    DecoderInfo { name: "qdraw", get_decoder: qdraw::get_decoder },
 #[cfg(feature="decoder_rle")]
     DecoderInfo { name: "qt-rle", get_decoder: rle::get_decoder },
 #[cfg(feature="decoder_rpza")]
diff --git a/nihav-qt/src/codecs/qdraw.rs b/nihav-qt/src/codecs/qdraw.rs
new file mode 100644 (file)
index 0000000..5513267
--- /dev/null
@@ -0,0 +1,353 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+use nihav_codec_support::codecs::HAMShuffler;
+
+trait ReadQDTypes {
+    fn read_colour(&mut self) -> DecoderResult<[u8; 3]>;
+    fn read_pattern(&mut self) -> DecoderResult<[u8; 8]>;
+}
+
+impl<T: ?Sized + ByteIO> ReadQDTypes for T {
+    fn read_colour(&mut self) -> DecoderResult<[u8; 3]> {
+        let r       = self.read_byte()?;
+                      self.read_byte()?;
+        let g       = self.read_byte()?;
+                      self.read_byte()?;
+        let b       = self.read_byte()?;
+                      self.read_byte()?;
+        Ok([r, g, b])
+    }
+    fn read_pattern(&mut self) -> DecoderResult<[u8; 8]> {
+        let mut pat = [0; 8];
+                      self.read_buf(&mut pat)?;
+        Ok(pat)
+    }
+}
+
+#[derive(Default)]
+struct QDrawDecoder {
+    info:       NACodecInfoRef,
+    hams:       HAMShuffler<u8>,
+    width:      usize,
+    height:     usize,
+}
+
+impl QDrawDecoder {
+    fn new() -> Self {
+        Self::default()
+    }
+}
+
+fn decode_packbits(br: &mut dyn ByteIO, frm: &mut NASimpleVideoFrame<u8>, width: usize, height: usize, region: bool) -> DecoderResult<()> {
+    let row_bytes                       = br.read_u16be()?;
+    validate!(row_bytes & 0x8000 != 0);
+    let cur_y                           = usize::from(br.read_u16be()?);
+    let cur_x                           = usize::from(br.read_u16be()?);
+    let cur_h                           = usize::from(br.read_u16be()?);
+    let cur_w                           = usize::from(br.read_u16be()?);
+    validate!(cur_x < cur_w && cur_w > 0 && cur_w <= width);
+    validate!(cur_y < cur_h && cur_h > 0 && cur_h <= height);
+    let version                         = br.read_u16be()?;
+    validate!(version == 0);
+    let pack_type                       = br.read_u16be()?;
+    if pack_type != 0 {
+        return Err(DecoderError::NotImplemented);
+    }
+    let _pack_size                      = br.read_u32be()?;
+    let _h_res                          = br.read_u32be()?;
+    let _v_res                          = br.read_u32be()?;
+    let pixel_type                      = br.read_u16be()?;
+    let pixel_size                      = br.read_u16be()?;
+    let cmp_count                       = usize::from(br.read_u16be()?);
+    let cmp_size                        = br.read_u16be()?;
+    if pixel_type != 0 || pixel_size != 8 || cmp_count != 1 || cmp_size != 8 {
+        return Err(DecoderError::NotImplemented);
+    }
+    let _plane_bytes                    = br.read_u32be()?;
+    let _pm_table                       = br.read_u32be()?;
+    let _pm_reserved                    = br.read_u32be()?;
+
+    let _ctseed                         = br.read_u32be()?;
+    let _trans_index                    = br.read_u16be()?;
+    let ct_size                         = usize::from(br.read_u16be()?);
+    let mut pal = [0; 768];
+    for _ in 0..=ct_size {
+        let idx                         = usize::from(br.read_u16be()?);
+        validate!(idx < 256);
+        let clr                         = br.read_colour()?;
+        pal[idx * 3..][..3].copy_from_slice(&clr);
+    }
+
+                                          br.read_skip(8)?; // src rect
+                                          br.read_skip(8)?; // dst rect
+    let _mode                           = br.read_u16be()?;
+    if region {
+        let mask_rgn_size               = usize::from(br.read_u16be()?);
+        validate!(mask_rgn_size > 2);
+                                          br.read_skip(mask_rgn_size - 2)?;
+    }
+
+    if (row_bytes & 0x7FFF) >= 8 {
+        let line_end = cur_x + cur_w;
+        for dline in frm.data[frm.offset[0]..].chunks_exact_mut(frm.stride[0])
+                .skip(cur_y).take(cur_h) {
+            let size = if (row_bytes & 0x7FFF) > 250 { br.read_u16be()? } else { u16::from(br.read_byte()?) };
+
+            let end = br.tell() + u64::from(size);
+
+            let mut pos = cur_x;
+            while br.tell() < end {
+                let op                  = br.read_byte()?;
+                if (op & 0x80) != 0 {
+                    let pix             = br.read_byte()?;
+                    let len = 257 - usize::from(op);
+                    validate!(pos + len <= line_end);
+                    for dst in dline[pos * 3..].chunks_exact_mut(3).take(len) {
+                        dst.copy_from_slice(&pal[usize::from(pix) * 3..][..3]);
+                    }
+                    pos += len;
+                } else {
+                    let len = usize::from(op) + 1;
+                    validate!(pos + len <= line_end);
+                    for dst in dline[pos * 3..].chunks_exact_mut(3).take(len) {
+                        let pix         = br.read_byte()?;
+                        dst.copy_from_slice(&pal[usize::from(pix) * 3..][..3]);
+                    }
+                    pos += len;
+                }
+            }
+        }
+    } else {
+unimplemented!() // unpacked case
+    }
+    Ok(())
+}
+
+impl NADecoder for QDrawDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            self.width  = vinfo.get_width();
+            self.height = vinfo.get_height();
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, RGB24_FORMAT));
+            self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref();
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    #[allow(unused_assignments)]
+    #[allow(unused_variables)]
+    fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() >= 2);
+        let mut br = MemoryReader::new_read(src.as_slice());
+        if br.peek_u16le()? == 0 {
+                                          br.read_skip(0x200)?;
+        }
+                                          br.read_u16be()?; // low 16 bits of size
+        let cur_y                       = usize::from(br.read_u16be()?);
+        let cur_x                       = usize::from(br.read_u16be()?);
+        let cur_h                       = usize::from(br.read_u16be()?);
+        let cur_w                       = usize::from(br.read_u16be()?);
+        validate!(cur_x < cur_w && cur_w - cur_x <= self.width);
+        validate!(cur_y < cur_h && cur_h - cur_y <= self.height);
+        let version_op                  = br.read_u16be()?;
+        validate!(version_op == 0x0011);
+        let version                     = br.read_u16be()?;
+        validate!(version == 0x2FF);
+        let hdr_op                      = br.read_u16be()?;
+        validate!(hdr_op == 0x0C00);
+        let _size                       = br.read_u32be()?;
+                                          br.read_skip(16)?; // bounding box
+                                          br.read_skip(4)?; // reserved
+
+        let bufret = self.hams.clone_ref();
+        let mut buf;
+        if let Some(bbuf) = bufret {
+            buf = bbuf;
+        } else {
+            let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+            buf = bufinfo.get_vbuf().unwrap();
+            self.hams.add_frame(buf);
+            buf = self.hams.get_output_frame().unwrap();
+        }
+        let mut frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap();
+
+        let mut fg_clr = [0; 3];
+        let mut bg_clr = [0; 3];
+        let mut fill_clr = [0; 3];
+        while br.left() >= 2 {
+            let op                      = br.read_u16be()?;
+            match op {
+                0x0000 => {}, // nop
+                0x0001 => { // clip
+                    let size            = usize::from(br.read_u16be()?);
+                    validate!(size >= 2);
+                                          br.read_skip(size - 2)?;
+                },
+                0x0002 => { // bk pattern
+                                          br.read_pattern()?;
+                },
+                0x0003 => { // text font
+                                          br.read_u16be()?;
+                },
+                0x0004 => { // text face
+                                          br.read_byte()?;
+                },
+                0x0005 => { // text mode
+                                          br.read_u16be()?;
+                },
+                0x0007 => { // pen size
+                    let size            = br.read_u32be()?;
+                },
+                0x0008 => { // pen mode
+                                          br.read_u16be()?;
+                },
+                0x0009 => { // pen pattern
+                                          br.read_pattern()?;
+                },
+                0x000A => { // fill pattern
+                                          br.read_pattern()?;
+                },
+                0x000B => { // oval size
+                    let xpos            = br.read_u16be()?;
+                    let ypos            = br.read_u16be()?;
+                },
+                0x000C => { // origin
+                    let xpos            = br.read_u16be()?;
+                    let ypos            = br.read_u16be()?;
+                },
+                0x000D => { // text size
+                                          br.read_skip(2)?;
+                },
+                0x0010 => { // text ratio
+                                          br.read_skip(8)?;
+                },
+                0x0011 => {
+                    println!("duplicate version opcode!");
+                    return Err(DecoderError::InvalidData);
+                },
+                0x001A => { // fore color
+                    fg_clr              = br.read_colour()?;
+                },
+                0x001B => { // back color
+                    bg_clr              = br.read_colour()?;
+                },
+                0x001E => {}, // def hilite
+                0x001F => { // fill color
+                    fill_clr            = br.read_colour()?;
+                },
+                0x0020 => { // line
+                    let sx              = br.read_u16be()?;
+                    let sy              = br.read_u16be()?;
+                    let dx              = br.read_u16le()?;
+                    let dy              = br.read_u16le()?;
+                },
+                0x0021 => { // line from
+                    let dx              = br.read_u16le()?;
+                    let dy              = br.read_u16le()?;
+                },
+                0x0022 => { // short line
+                    let xpos            = br.read_u16be()?;
+                    let ypos            = br.read_u16be()?;
+                    let dx              = br.read_byte()? as i8;
+                    let dy              = br.read_byte()? as i8;
+                },
+                0x0023 => { // short line from
+                    let dx              = br.read_byte()? as i8;
+                    let dy              = br.read_byte()? as i8;
+                },
+                0x0028 => { // long text
+                    let _xpos           = br.read_u16be()?;
+                    let _ypos           = br.read_u16be()?;
+                    let count           = usize::from(br.read_byte()?);
+                                          br.read_skip(count)?;
+                },
+                0x0031 => { // paint rect
+                                          br.read_skip(8)?;
+                },
+                0x0098 => { // PackBits rect
+                    decode_packbits(&mut br, &mut frm, self.width, self.height, false)?;
+                },
+                0x009A => { // PackBits region
+                    decode_packbits(&mut br, &mut frm, self.width, self.height, true)?;
+                },
+                0x00A0 => { // short comment
+                                          br.read_u16be()?;
+                },
+                0x00A1 => { // long comment
+                                          br.read_u16be()?;
+                    let size            = usize::from(br.read_u16be()?);
+                                          br.read_skip(size)?;
+                },
+                0x00FF => break,
+                0x0C00 => {
+                    println!("duplicate header opcode!");
+                    return Err(DecoderError::InvalidData);
+                },
+                0x8200 => {
+                    println!("QT data encountered");
+                    return Err(DecoderError::NotImplemented);
+                },
+                0x8000..=0x80FF => {
+                    println!("unknown opcode {op:04X}");
+                },
+                0x8100..=0xFFFF => {
+                    println!("unknown opcode {op:04X}");
+                    let size            = br.read_u32be()? as usize;
+                    validate!(size >= 4);
+                                          br.read_skip(size)?;
+                },
+                _ => return Err(DecoderError::NotImplemented),
+            }
+            if (br.tell() & 1) != 0 {
+                                          br.read_skip(1)?;
+            }
+        }
+
+        let buftype = NABufferType::Video(buf);
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buftype);
+        frm.set_frame_type(if pkt.is_keyframe() { FrameType::I } else { FrameType::P });
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+        self.hams.clear();
+    }
+}
+
+impl NAOptionHandler for QDrawDecoder {
+    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(QDrawDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::qt_register_all_decoders;
+    use nihav_commonfmt::generic_register_all_demuxers;
+    #[test]
+    fn test_qdraw() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        qt_register_all_decoders(&mut dec_reg);
+
+        // sample: https://samples.mplayerhq.hu/V-codecs/QT-qdrw/Airplane.mov
+        test_decoding("mov", "qdraw", "assets/QT/Airplane.mov", None, &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5Frames(vec![
+                            [0x9aced174, 0x4c9f58ca, 0xeacace05, 0x0d11e351],
+                            [0x9aced174, 0x4c9f58ca, 0xeacace05, 0x0d11e351],
+                            [0xf7459501, 0xfbf81fd2, 0xcc436363, 0x95748ed6],
+                            [0xf7459501, 0xfbf81fd2, 0xcc436363, 0x95748ed6],
+                            [0xf7459501, 0xfbf81fd2, 0xcc436363, 0x95748ed6]]));
+    }
+}
index 1ed3dc78730888014ec0015a8005e156790ee76b..f80ebe48c877a924ddefdfb42958418e1087f7f2 100644 (file)
@@ -201,6 +201,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video;    "qt-smc",               "Apple Graphics"),
     desc!(video;    "qt-rle",               "Apple Animation"),
     desc!(video;    "apple-video",          "Apple video"),
+    desc!(video;    "qdraw",                "Apple QuickDraw"),
     desc!(video;    "sorenson-video",       "Sorenson Video"),
     desc!(video;    "sorenson-video3",      "Sorenson Video 3", CODEC_CAP_REORDER),
     desc!(audio-ll; "alac",                 "Apple Lossless Audio Codec"),
@@ -435,6 +436,7 @@ static MOV_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[
     (b"smc ", "qt-smc"),
     (b"rle ", "qt-rle"),
     (b"rpza", "apple-video"),
+    (b"qdrw", "qdraw"),
     (b"kpcd", "kodak-photocd"),
     //(b"mpeg", "mpeg-video"),
     (b"mjpa", "mjpeg-a"),