Newer
Older
//! Assorted utility functions and structures.
use std::{
cell::RefCell,
fs::File,
io::{BufWriter, Read, Write},
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
/// Shared mutable type able to be passed between types
/// allowing for circular referencing and interior mutability.
/// 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)))?;
file.read_to_end(&mut data)
.map_err(|_| Error::CustomError(format!("Failed to read from file: {}", path)))?;
/// Writes the given data to the file at the given path.
pub fn write_file(path: &str, data: &[u8], flush: Option<bool>) -> Result<(), Error> {
.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)))?;
}
/// 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)))?;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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);
}
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
}
// 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 {
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#[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!");
}