1use std::{
7 cell::RefCell,
8 fs::File,
9 io::{BufWriter, Read, Write},
10 path::Path,
11 rc::Rc,
12 sync::{Arc, Mutex},
13};
14
15#[cfg(feature = "wasm")]
16use wasm_bindgen::prelude::*;
17
18use crate::error::Error;
19
20pub type SharedMut<T> = Rc<RefCell<T>>;
23
24pub type SharedThread<T> = Arc<Mutex<T>>;
28
29const BMP_HEADER_SIZE: u32 = 54;
31
32pub fn read_file(path: &str) -> Result<Vec<u8>, Error> {
35 let mut file =
36 File::open(path).map_err(|_| Error::CustomError(format!("Failed to open file: {path}")))?;
37 let mut data = Vec::new();
38 file.read_to_end(&mut data)
39 .map_err(|_| Error::CustomError(format!("Failed to read from file: {path}")))?;
40 Ok(data)
41}
42
43pub fn write_file(path: &str, data: &[u8], flush: Option<bool>) -> Result<(), Error> {
45 let mut file = File::create(path)
46 .map_err(|_| Error::CustomError(format!("Failed to create file: {path}")))?;
47 file.write_all(data)
48 .map_err(|_| Error::CustomError(format!("Failed to write to file: {path}")))?;
49 if flush.unwrap_or(true) {
50 file.flush()
51 .map_err(|_| Error::CustomError(format!("Failed to flush file: {path}")))?;
52 }
53 Ok(())
54}
55
56pub fn replace_ext(path: &str, new_extension: &str) -> Option<String> {
60 let file_path = Path::new(path);
61 let parent_dir = file_path.parent()?;
62 let file_stem = file_path.file_stem()?;
63 let file_extension = file_path.extension()?;
64 if file_stem == file_extension {
65 return None;
66 }
67 let new_file_name = format!("{}.{}", file_stem.to_str()?, new_extension);
68 let new_file_path = parent_dir.join(new_file_name);
69 Some(String::from(new_file_path.to_str()?))
70}
71
72pub fn capitalize(string: &str) -> String {
74 let mut chars = string.chars();
75 match chars.next() {
76 None => String::new(),
77 Some(chr) => chr.to_uppercase().collect::<String>() + chars.as_str(),
78 }
79}
80
81pub fn save_bmp(path: &str, pixels: &[u8], width: u32, height: u32) -> Result<(), Error> {
89 let file = File::create(path)
90 .map_err(|_| Error::CustomError(format!("Failed to create file: {path}")))?;
91 let mut writer = BufWriter::new(file);
92
93 let row_bytes = (width * 3 + 3) & !3;
96 let image_size = row_bytes * height;
97 let file_size = BMP_HEADER_SIZE + image_size;
98
99 writer.write_all(&[0x42, 0x4d]).unwrap(); writer.write_all(&file_size.to_le_bytes()).unwrap(); writer.write_all(&[0x00, 0x00]).unwrap(); writer.write_all(&[0x00, 0x00]).unwrap(); writer.write_all(&[0x36, 0x00, 0x00, 0x00]).unwrap(); writer.write_all(&[0x28, 0x00, 0x00, 0x00]).unwrap(); writer.write_all(&(width as i32).to_le_bytes()).unwrap(); writer.write_all(&(height as i32).to_le_bytes()).unwrap(); writer.write_all(&[0x01, 0x00]).unwrap(); writer.write_all(&[0x18, 0x00]).unwrap(); writer.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); writer.write_all(&image_size.to_le_bytes()).unwrap(); writer.write_all(&[0x13, 0x0b, 0x00, 0x00]).unwrap(); writer.write_all(&[0x13, 0x0b, 0x00, 0x00]).unwrap(); writer.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); writer.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); for y in (0..height).rev() {
120 for x in 0..width {
121 let [r, g, b] = [
122 pixels[((y * width + x) * 3) as usize],
123 pixels[((y * width + x) * 3 + 1) as usize],
124 pixels[((y * width + x) * 3 + 2) as usize],
125 ];
126 writer.write_all(&[b, g, r]).unwrap();
127 }
128 let padding = (4 - ((width * 3) % 4)) % 4;
129 for _ in 0..padding {
130 writer.write_all(&[0x00]).unwrap();
131 }
132 }
133
134 Ok(())
135}
136
137pub fn copy_fast(src: &[u8], dst: &mut [u8], count: usize) {
142 assert!(src.len() >= count);
143 assert!(dst.len() >= count);
144
145 unsafe {
146 let src_ptr = src.as_ptr();
147 let dst_ptr = dst.as_mut_ptr();
148 std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, count);
149 }
150}
151
152pub fn interleave_arrays(a: &[u8], b: &[u8], output: &mut [u8]) {
155 assert_eq!(a.len(), b.len());
156 assert_eq!(output.len(), a.len() + b.len());
157
158 let len = a.len();
159
160 unsafe {
161 let mut out_ptr = output.as_mut_ptr();
162 let mut a_ptr = a.as_ptr();
163 let mut b_ptr = b.as_ptr();
164
165 for _ in 0..len {
166 std::ptr::write(out_ptr, *a_ptr);
167 out_ptr = out_ptr.add(1);
168 a_ptr = a_ptr.add(1);
169
170 std::ptr::write(out_ptr, *b_ptr);
171 out_ptr = out_ptr.add(1);
172 b_ptr = b_ptr.add(1);
173 }
174 }
175}
176
177pub fn flip_vertical(pixels: &[u8], width: usize, height: usize, channels: usize) -> Vec<u8> {
182 let row_len = width * channels;
183 let mut flipped = vec![0u8; pixels.len()];
184 for y in 0..height {
185 let src = &pixels[y * row_len..(y + 1) * row_len];
186 let dst = &mut flipped[(height - 1 - y) * row_len..(height - y) * row_len];
187 dst.copy_from_slice(src);
188 }
189 flipped
190}
191
192#[cfg(not(feature = "wasm"))]
197pub fn timestamp() -> u64 {
198 use std::time::{SystemTime, UNIX_EPOCH};
199
200 let now = SystemTime::now();
201 now.duration_since(UNIX_EPOCH).unwrap().as_secs()
202}
203
204#[cfg(feature = "wasm")]
211#[cfg_attr(feature = "wasm", wasm_bindgen)]
212pub fn timestamp() -> u64 {
213 use js_sys::Date;
214
215 (Date::now() / 1000.0) as u64
216}
217
218#[cfg(test)]
219mod tests {
220 use std::{
221 env::temp_dir,
222 fs::{read, remove_file},
223 path::Path,
224 };
225
226 use super::{capitalize, replace_ext, save_bmp};
227
228 #[test]
229 fn test_change_extension() {
230 let new_path = replace_ext("/path/to/file.txt", "dat").unwrap();
231 assert_eq!(
232 new_path,
233 Path::new("/path/to").join("file.dat").to_str().unwrap()
234 );
235
236 let new_path = replace_ext("/path/to/file.with.multiple.dots.txt", "dat").unwrap();
237 assert_eq!(
238 new_path,
239 Path::new("/path/to")
240 .join("file.with.multiple.dots.dat")
241 .to_str()
242 .unwrap()
243 );
244
245 let new_path = replace_ext("/path/to/file.without.extension", "dat").unwrap();
246 assert_eq!(
247 new_path,
248 Path::new("/path/to")
249 .join("file.without.dat")
250 .to_str()
251 .unwrap()
252 );
253
254 let new_path = replace_ext("/path/to/directory/", "dat");
255 assert_eq!(new_path, None);
256 }
257
258 #[test]
259 fn test_capitalize_empty_string() {
260 let result = capitalize("");
261 assert_eq!(result, "");
262 }
263
264 #[test]
265 fn test_capitalize_single_character() {
266 let result = capitalize("a");
267 assert_eq!(result, "A");
268 }
269
270 #[test]
271 fn test_capitalize_multiple_characters() {
272 let result = capitalize("hello, world!");
273 assert_eq!(result, "Hello, world!");
274 }
275
276 #[test]
277 fn test_bmp_le_bytes() {
278 let path = temp_dir().join("boytacean_le_test.bmp");
281 save_bmp(path.to_str().unwrap(), &[255, 0, 0], 1, 1).expect("Failed to save BMP file");
282 let data: Vec<u8> = read(&path).unwrap();
283 assert_eq!(&data[2..6], &(58u32).to_le_bytes());
284 assert_eq!(&data[34..38], &(4u32).to_le_bytes());
285 remove_file(path).unwrap();
286 }
287
288 #[test]
289 fn test_bmp_file_structure() {
290 let path = temp_dir().join("boytacean_spec_test.bmp");
293 let pixels = [
294 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0, ];
299 save_bmp(path.to_str().unwrap(), &pixels, 2, 2).expect("Failed to save BMP file");
300 let data = read(&path).unwrap();
301
302 assert_eq!(&data[0..2], b"BM");
304 assert_eq!(&data[2..6], &(70u32).to_le_bytes());
305 assert_eq!(&data[10..14], &(54u32).to_le_bytes());
306 assert_eq!(&data[14..18], &(40u32).to_le_bytes());
307 assert_eq!(&data[18..22], &(2i32).to_le_bytes());
308 assert_eq!(&data[22..26], &(2i32).to_le_bytes());
309 assert_eq!(&data[26..28], &(1u16).to_le_bytes());
310 assert_eq!(&data[28..30], &(24u16).to_le_bytes());
311 assert_eq!(&data[34..38], &(16u32).to_le_bytes());
312
313 remove_file(path).unwrap();
314 }
315}