]> git.nihav.org Git - nihav.git/commitdiff
nihav_codec_support: factor out support for BITMAPINFO/WAVEFORMAT(EX)
authorKostya Shishkov <kostya.shishkov@gmail.com>
Fri, 27 Feb 2026 17:15:56 +0000 (18:15 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Fri, 27 Feb 2026 17:15:56 +0000 (18:15 +0100)
nihav-codec-support/src/codecs/mod.rs
nihav-codec-support/src/codecs/msstructs.rs [new file with mode: 0644]

index 63810adca15bdcca5f70e871628ddcf5cebf0c84..60d665c8edb83229f226e97eb70b59b6639b22df 100644 (file)
@@ -292,3 +292,5 @@ pub const ZIGZAG: [usize; 64] = [
 ];
 
 pub mod imaadpcm;
+
+pub mod msstructs;
diff --git a/nihav-codec-support/src/codecs/msstructs.rs b/nihav-codec-support/src/codecs/msstructs.rs
new file mode 100644 (file)
index 0000000..b1b10ec
--- /dev/null
@@ -0,0 +1,221 @@
+//! Common BITMAPINFOHEADER and WAVEFORMAT(EX) definitions and reading support.
+use nihav_core::formats::*;
+use nihav_core::frame::{NAVideoInfo, NAAudioInfo};
+use nihav_core::io::byteio::*;
+
+/// DIB/AVI bitmap header plus optional extradata
+#[derive(Clone,Debug,Default)]
+pub struct MSBitmapInfo {
+    /// Original structure size.
+    pub bi_size:        usize,
+    /// Image width.
+    pub width:          usize,
+    /// Image height.
+    pub height:         usize,
+    /// Image is flipped (i.e. original height was negative).
+    pub flipped:        bool,
+    /// Number of image planes.
+    pub planes:         u16,
+    /// Bits per pixel.
+    pub bitcount:       u16,
+    /// Compression ID.
+    pub compression:    [u8; 4],
+    /// Original image size.
+    pub image_size:     u32,
+    /// Horizontal resolution.
+    pub x_dpi:          u32,
+    /// Vertical resolution,
+    pub y_dpi:          u32,
+    /// Number of colour indices in the palette.
+    pub colors:         usize,
+    /// Number of colours considered important for displaying bitmap.
+    pub clr_important:  u32,
+    /// Additional data.
+    pub extradata:      Option<Vec<u8>>,
+}
+
+impl MSBitmapInfo {
+    /// Checks header for validity and returns the failed field name in case of error.
+    pub fn validate(&self) -> Result<(), &'static str> {
+        const KNOWN_BAD_FORMATS: &[&[u8; 4]] = &[
+            b"ZMBV", b"zmbv",
+        ];
+        if !(40..=(1 << 24)).contains(&self.bi_size) {
+            return Err("bi_size");
+        }
+        if !(1..=65535).contains(&self.width) {
+            return Err("width");
+        }
+        if !(1..=65535).contains(&self.height) {
+            return Err("height");
+        }
+        if !KNOWN_BAD_FORMATS.contains(&&self.compression) {
+            if self.planes == 0 || self.planes > self.bitcount {
+                return Err("planes");
+            }
+            if !(1..=32).contains(&self.bitcount) {
+                return Err("bitcount");
+            }
+            if (self.bitcount <= 8 && (self.colors == 0 || self.colors > (1 << self.bitcount))) || (self.bitcount > 8 && self.colors != 0) {
+                return Err("clr_used");
+            }
+            if self.clr_important as usize > self.colors {
+                return Err("clr_important");
+            }
+        }
+        Ok(())
+    }
+    /// Reads structure from the input stream.
+    ///
+    /// Use `validate()` function for checking whether the result is valid.
+    pub fn read(src: &mut dyn ByteIO) -> ByteIOResult<Self> {
+        let bi_size         = src.read_u32le()? as usize;
+        let width           = src.read_u32le()? as usize;
+        let height          = src.read_u32le()? as i32;
+        let flipped = height < 0;
+        let height = height.unsigned_abs() as usize;
+        let planes          = src.read_u16le()?;
+        let bitcount        = src.read_u16le()?;
+        let compression     = src.read_tag()?;
+        let image_size      = src.read_u32le()?;
+        let x_dpi           = src.read_u32le()?;
+        let y_dpi           = src.read_u32le()?;
+        let colors          = src.read_u32le()? as usize;
+        let clr_important   = src.read_u32le()?;
+        let extradata = if bi_size > 40 {
+                let edata_size = bi_size - 40;
+                if edata_size >= 1 << 24 {
+                    return Err(ByteIOError::ReadError);
+                }
+                let mut edata = vec![0; edata_size];
+                              src.read_buf(&mut edata)?;
+                Some(edata)
+            } else {
+                None
+            };
+        Ok(Self {
+            bi_size, width, height, flipped, planes, bitcount,
+            compression, image_size, x_dpi, y_dpi, colors, clr_important,
+            extradata
+        })
+    }
+    /// Generates `NihAV` video information.
+    pub fn get_video_info(&self) -> NAVideoInfo {
+        let format = if self.bitcount > 8 { RGB24_FORMAT } else { PAL8_FORMAT };
+        let mut vhdr = NAVideoInfo::new(self.width, self.height, self.flipped, format);
+        vhdr.bits = (self.planes as u8) * (self.bitcount as u8);
+        vhdr
+    }
+    /// Extracts extradata out of the structure and returns it.
+    pub fn take_extradata(&mut self) -> Option<Vec<u8>> { self.extradata.take() }
+    /// Reports number of bytes required to store this structure.
+    pub fn get_size(&self) -> usize {
+        40 + self.extradata.as_ref().map_or_else(|| 0, |edata| edata.len())
+    }
+    /// Writes structure to the provided output.
+    pub fn write(&self, dst: &mut dyn ByteIO) -> ByteIOResult<()> {
+        let size = self.get_size();
+        dst.write_u32le(size as u32)?;
+        dst.write_u32le(self.width as u32)?;
+        if !self.flipped {
+            dst.write_u32le(self.height as u32)?;
+        } else {
+            dst.write_u32le((-(self.height as i32)) as u32)?;
+        }
+        dst.write_u16le(self.planes)?;
+        dst.write_u16le(self.bitcount)?;
+        dst.write_buf(&self.compression)?;
+        dst.write_u32le(self.image_size)?;
+        dst.write_u32le(self.x_dpi)?;
+        dst.write_u32le(self.y_dpi)?;
+        dst.write_u32le(self.colors as u32)?;
+        dst.write_u32le(self.clr_important)?;
+        if let Some(ref edata) = self.extradata {
+            dst.write_buf(edata.as_slice())?;
+        }
+        Ok(())
+    }
+}
+
+/// Wave format definition.
+#[derive(Clone,Debug,Default)]
+pub struct MSWaveFormat {
+    pub format_tag:         u16,
+    pub channels:           u16,
+    pub sample_rate:        u32,
+    pub avg_bytes_per_sec:  u32,
+    pub block_align:        usize,
+    pub bits_per_sample:    u16,
+    pub extradata:          Option<Vec<u8>>,
+}
+
+impl MSWaveFormat {
+    /// Checks header for validity and returns the failed field name in case of error.
+    pub fn validate(&self) -> Result<(), &'static str> {
+        if !(1..=64).contains(&self.channels) {
+            return Err("channels");
+        }
+        if !(4000..=400000).contains(&self.sample_rate) {
+            return Err("sample_rate");
+        }
+        if self.block_align == 0 {
+            return Err("block_align");
+        }
+        Ok(())
+    }
+    /// Reads structure from the input stream.
+    ///
+    /// Use `validate()` function for checking whether the result is valid.
+    pub fn read(src: &mut dyn ByteIO, size: usize) -> ByteIOResult<Self> {
+        if !(size == 14 || size == 16 || size >= 18) {
+            return Err(ByteIOError::ReadError);
+        }
+        let format_tag          = src.read_u16le()?;
+        let channels            = src.read_u16le()?;
+        let sample_rate         = src.read_u32le()?;
+        let avg_bytes_per_sec   = src.read_u32le()?;
+        let block_align         = usize::from(src.read_u16le()?);
+        let bits_per_sample     = if size > 14 { src.read_u16le()? } else { 8 };
+        let mut extradata = None;
+        if size > 16 {
+            let cb_size         = usize::from(src.read_u16le()?);
+            if cb_size > 0 {
+                let mut edata = vec![0; cb_size];
+                                  src.read_buf(&mut edata)?;
+                extradata = Some(edata);
+            }
+        }
+        Ok(Self {
+            format_tag, channels, sample_rate, avg_bytes_per_sec,
+            block_align, bits_per_sample, extradata
+        })
+    }
+    /// Generates `NihAV` audio information.
+    pub fn get_audio_info(&self) -> NAAudioInfo {
+        let signed = self.bits_per_sample > 8;
+        let soniton = NASoniton::new(self.bits_per_sample as u8, if signed { SONITON_FLAG_SIGNED } else { 0 });
+        NAAudioInfo::new(self.sample_rate, self.channels as u8, soniton, self.block_align)
+    }
+    /// Extracts extradata out of the structure and returns it.
+    pub fn take_extradata(&mut self) -> Option<Vec<u8>> { self.extradata.take() }
+    /// Reports number of bytes required to store this structure.
+    pub fn get_size(&self) -> usize {
+        18 + self.extradata.as_ref().map_or_else(|| 0, |edata| edata.len())
+    }
+    /// Writes structure to the provided output.
+    pub fn write(&self, dst: &mut dyn ByteIO) -> ByteIOResult<()> {
+        dst.write_u16le(self.format_tag)?;
+        dst.write_u16le(self.channels)?;
+        dst.write_u32le(self.sample_rate)?;
+        dst.write_u32le(self.avg_bytes_per_sec)?;
+        dst.write_u16le(self.block_align as u16)?;
+        dst.write_u16le(self.bits_per_sample)?;
+        if let Some(ref edata) = self.extradata {
+            dst.write_u16le(edata.len() as u16)?;
+            dst.write_buf(edata.as_slice())?;
+        } else {
+            dst.write_u16le(0)?;
+        }
+        Ok(())
+    }
+}