Skip to content
Snippets Groups Projects
util.rs 7.46 KiB
Newer Older
  • Learn to ignore specific revisions
  • //! Assorted utility functions and structures.
    
    
    use std::{
        cell::RefCell,
        fs::File,
    
        io::{BufWriter, Read, Write},
    
        sync::{Arc, Mutex},
    
    use crate::error::Error;
    
    
    #[cfg(feature = "wasm")]
    use wasm_bindgen::prelude::*;
    
    
    /// Shared mutable type able to be passed between types
    /// allowing for circular referencing and interior mutability.
    
    pub type SharedMut<T> = Rc<RefCell<T>>;
    
    /// Shared thread type able to be passed between threads.
    /// Significant performance overhead compared to `SharedMut`.
    
    pub type SharedThread<T> = Arc<Mutex<T>>;
    
    
    /// Reads the contents of the file at the given path into
    /// a vector of bytes.
    
    pub fn read_file(path: &str) -> Result<Vec<u8>, Error> {
        let mut file = File::open(path)
            .map_err(|_| Error::CustomError(format!("Failed to open file: {}", path)))?;
    
        let mut data = Vec::new();
    
        file.read_to_end(&mut data)
            .map_err(|_| Error::CustomError(format!("Failed to read from file: {}", path)))?;
    
        Ok(data)
    
    /// Writes the given data to the file at the given path.
    
    pub fn write_file(path: &str, data: &[u8], flush: Option<bool>) -> Result<(), Error> {
    
        let mut file = File::create(path)
    
            .map_err(|_| Error::CustomError(format!("Failed to create file: {}", path)))?;
    
        file.write_all(data)
            .map_err(|_| Error::CustomError(format!("Failed to write to file: {}", path)))?;
    
        if flush.unwrap_or(true) {
            file.flush()
                .map_err(|_| Error::CustomError(format!("Failed to flush file: {}", path)))?;
        }
    
        Ok(())
    
    /// Replaces the extension in the given path with the provided extension.
    /// This function allows for simple associated file discovery.
    pub fn replace_ext(path: &str, new_extension: &str) -> Option<String> {
        let file_path = Path::new(path);
        let parent_dir = file_path.parent()?;
        let file_stem = file_path.file_stem()?;
        let file_extension = file_path.extension()?;
        if file_stem == file_extension {
            return None;
        }
        let new_file_name = format!("{}.{}", file_stem.to_str()?, new_extension);
        let new_file_path = parent_dir.join(new_file_name);
        Some(String::from(new_file_path.to_str()?))
    }
    
    
    /// Capitalizes the first character in the provided string.
    pub fn capitalize(string: &str) -> String {
        let mut chars = string.chars();
        match chars.next() {
            None => String::new(),
            Some(chr) => chr.to_uppercase().collect::<String>() + chars.as_str(),
        }
    }
    
    
    pub fn save_bmp(path: &str, pixels: &[u8], width: u32, height: u32) -> Result<(), Error> {
        let file = File::create(path)
            .map_err(|_| Error::CustomError(format!("Failed to create file: {}", path)))?;
    
        let mut writer = BufWriter::new(file);
    
        // writes the BMP file header
        let file_size = 54 + (width * height * 3);
        writer.write_all(&[0x42, 0x4d]).unwrap(); // "BM" magic number
        writer.write_all(&file_size.to_le_bytes()).unwrap(); // file size
        writer.write_all(&[0x00, 0x00]).unwrap(); // reserved
        writer.write_all(&[0x00, 0x00]).unwrap(); // reserved
        writer.write_all(&[0x36, 0x00, 0x00, 0x00]).unwrap(); // offset to pixel data
        writer.write_all(&[0x28, 0x00, 0x00, 0x00]).unwrap(); // DIB header size
        writer.write_all(&(width as i32).to_le_bytes()).unwrap(); // image width
        writer.write_all(&(height as i32).to_le_bytes()).unwrap(); // image height
        writer.write_all(&[0x01, 0x00]).unwrap(); // color planes
        writer.write_all(&[0x18, 0x00]).unwrap(); // bits per pixel
        writer.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); // compression method
        writer
            .write_all(&[(width * height * 3) as u8, 0x00, 0x00, 0x00])
            .unwrap(); // image size
        writer.write_all(&[0x13, 0x0b, 0x00, 0x00]).unwrap(); // horizontal resolution (72 DPI)
        writer.write_all(&[0x13, 0x0b, 0x00, 0x00]).unwrap(); // vertical resolution (72 DPI)
        writer.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); // color palette
        writer.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); // important colors
    
        // iterates over the complete array of pixels in reverse order
        // to account for the fact that BMP files are stored upside down
        for y in (0..height).rev() {
            for x in 0..width {
                let [r, g, b] = [
                    pixels[((y * width + x) * 3) as usize],
                    pixels[((y * width + x) * 3 + 1) as usize],
                    pixels[((y * width + x) * 3 + 2) as usize],
                ];
                writer.write_all(&[b, g, r]).unwrap();
            }
            let padding = (4 - ((width * 3) % 4)) % 4;
            for _ in 0..padding {
                writer.write_all(&[0x00]).unwrap();
            }
        }
    
        Ok(())
    }
    
    
    /// Copies the contents of the source slice into the destination slice.
    ///
    /// This function is optimized for performance and uses pointer-based
    /// operations to copy the data as fast as possible.
    
    pub fn copy_fast(src: &[u8], dst: &mut [u8], count: usize) {
    
        assert!(src.len() >= count);
        assert!(dst.len() >= count);
    
        unsafe {
            let src_ptr = src.as_ptr();
            let dst_ptr = dst.as_mut_ptr();
            std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, count);
        }
    
    }
    
    // Interleaves two arrays of bytes into a single array using
    // a pointer-based approach for performance reasons.
    pub fn interleave_arrays(a: &[u8], b: &[u8], output: &mut [u8]) {
        assert_eq!(a.len(), b.len());
        assert_eq!(output.len(), a.len() + b.len());
    
        let len = a.len();
    
        unsafe {
            let mut out_ptr = output.as_mut_ptr();
            let mut a_ptr = a.as_ptr();
            let mut b_ptr = b.as_ptr();
    
            for _ in 0..len {
                std::ptr::write(out_ptr, *a_ptr);
                out_ptr = out_ptr.add(1);
                a_ptr = a_ptr.add(1);
    
                std::ptr::write(out_ptr, *b_ptr);
                out_ptr = out_ptr.add(1);
                b_ptr = b_ptr.add(1);
            }
        }
    }
    
    
    #[cfg(not(feature = "wasm"))]
    pub fn get_timestamp() -> u64 {
        use std::time::{SystemTime, UNIX_EPOCH};
    
        let now = SystemTime::now();
        now.duration_since(UNIX_EPOCH).unwrap().as_secs()
    }
    
    #[cfg(feature = "wasm")]
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    pub fn get_timestamp() -> u64 {
        use js_sys::Date;
    
        (Date::now() / 1000.0) as u64
    }
    
    
    #[cfg(test)]
    mod tests {
    
        use std::path::Path;
    
    
        use super::{capitalize, replace_ext};
    
    
        #[test]
        fn test_change_extension() {
            let new_path = replace_ext("/path/to/file.txt", "dat").unwrap();
            assert_eq!(
                new_path,
                Path::new("/path/to").join("file.dat").to_str().unwrap()
            );
    
            let new_path = replace_ext("/path/to/file.with.multiple.dots.txt", "dat").unwrap();
            assert_eq!(
                new_path,
                Path::new("/path/to")
                    .join("file.with.multiple.dots.dat")
                    .to_str()
                    .unwrap()
            );
    
            let new_path = replace_ext("/path/to/file.without.extension", "dat").unwrap();
            assert_eq!(
                new_path,
                Path::new("/path/to")
                    .join("file.without.dat")
                    .to_str()
                    .unwrap()
            );
    
            let new_path = replace_ext("/path/to/directory/", "dat");
            assert_eq!(new_path, None);
        }
    
    
        #[test]
        fn test_capitalize_empty_string() {
            let result = capitalize("");
            assert_eq!(result, "");
        }
    
        #[test]
        fn test_capitalize_single_character() {
            let result = capitalize("a");
            assert_eq!(result, "A");
        }
    
        #[test]
        fn test_capitalize_multiple_characters() {
            let result = capitalize("hello, world!");
            assert_eq!(result, "Hello, world!");
        }