From 6c23044ed3c6a44e81474404324a8434bd8a0d5f Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Wed, 28 Jan 2026 18:48:13 +0100 Subject: [PATCH] QuickDraw decoder (with limited functionality) --- nihav-qt/Cargo.toml | 3 +- nihav-qt/src/codecs/mod.rs | 5 + nihav-qt/src/codecs/qdraw.rs | 353 +++++++++++++++++++++++++++++++++ nihav-registry/src/register.rs | 2 + 4 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 nihav-qt/src/codecs/qdraw.rs diff --git a/nihav-qt/Cargo.toml b/nihav-qt/Cargo.toml index a5c78d5..c953a7f 100644 --- a/nihav-qt/Cargo.toml +++ b/nihav-qt/Cargo.toml @@ -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"] diff --git a/nihav-qt/src/codecs/mod.rs b/nihav-qt/src/codecs/mod.rs index 5757c37..65921b8 100644 --- a/nihav-qt/src/codecs/mod.rs +++ b/nihav-qt/src/codecs/mod.rs @@ -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 index 0000000..5513267 --- /dev/null +++ b/nihav-qt/src/codecs/qdraw.rs @@ -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 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, + width: usize, + height: usize, +} + +impl QDrawDecoder { + fn new() -> Self { + Self::default() + } +} + +fn decode_packbits(br: &mut dyn ByteIO, frm: &mut NASimpleVideoFrame, 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 { + 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 { None } +} + +pub fn get_decoder() -> Box { + 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]])); + } +} diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 1ed3dc7..f80ebe4 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -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"), -- 2.39.5