1use base64::{engine::general_purpose, Engine as _};
2use image::ImageOutputFormat;
3use std::io::{Cursor, Write};
4use wasm_bindgen::prelude::*;
5
6#[wasm_bindgen]
7pub fn set_panic_hook() {
8 console_error_panic_hook::set_once();
9}
10
11const DEFAULT_SIZES: [u32; 6] = [16, 32, 48, 64, 128, 256];
12
13pub fn imgico_core(input: &[u8], sizes: Option<Vec<u32>>) -> Result<Vec<u8>, String> {
14 let sizes = sizes.unwrap_or_else(|| DEFAULT_SIZES.to_vec());
15 let img = image::load_from_memory(input).map_err(|e| format!("Failed to load image: {}", e))?;
16
17 let mut images = Vec::new();
18
19 for size in sizes {
20 if size < 1 || size > 256 {
21 return Err(format!(
22 "Invalid icon size: {}. Size must be between 1 and 256.",
23 size
24 ));
25 }
26
27 let (width, height) = (img.width(), img.height());
29 let min_dim = width.min(height);
30 let x_offset = (width - min_dim) / 2;
31 let y_offset = (height - min_dim) / 2;
32
33 let square_img = img.crop_imm(x_offset, y_offset, min_dim, min_dim);
34
35 let resized = square_img.resize_exact(size, size, image::imageops::FilterType::Lanczos3);
37
38 let mut buffer = Cursor::new(Vec::new());
39 resized
40 .write_to(&mut buffer, ImageOutputFormat::Png)
41 .map_err(|e| format!("Failed to write PNG: {}", e))?;
42
43 images.push((buffer.into_inner(), size));
44 }
45
46 let mut ico_data = Vec::new();
48
49 ico_data.write_all(&0u16.to_le_bytes()).unwrap(); ico_data.write_all(&1u16.to_le_bytes()).unwrap(); ico_data
53 .write_all(&(images.len() as u16).to_le_bytes())
54 .unwrap(); let directory_size = 16 * images.len();
57 let mut offset = 6 + directory_size;
58
59 for (buffer, size) in &images {
61 let dim = if *size >= 256 { 0 } else { *size as u8 };
62 ico_data.write_all(&[dim]).unwrap(); ico_data.write_all(&[dim]).unwrap(); ico_data.write_all(&[0]).unwrap(); ico_data.write_all(&[0]).unwrap(); ico_data.write_all(&1u16.to_le_bytes()).unwrap(); ico_data.write_all(&32u16.to_le_bytes()).unwrap(); ico_data
69 .write_all(&(buffer.len() as u32).to_le_bytes())
70 .unwrap(); ico_data.write_all(&(offset as u32).to_le_bytes()).unwrap(); offset += buffer.len();
74 }
75
76 for (buffer, _) in images {
78 ico_data.write_all(&buffer).unwrap();
79 }
80
81 Ok(ico_data)
82}
83
84#[wasm_bindgen]
85pub fn imgico(input: &[u8], sizes: Option<Vec<u32>>) -> Result<Vec<u8>, JsValue> {
86 imgico_core(input, sizes).map_err(|e| JsValue::from_str(&e))
87}
88
89pub fn imgsvg_core(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, String> {
90 let img = image::load_from_memory(input).map_err(|e| format!("Failed to load image: {}", e))?;
91
92 let final_img = if let Some(s) = size {
93 let (width, height) = (img.width(), img.height());
95 let min_dim = width.min(height);
96 let x_offset = (width - min_dim) / 2;
97 let y_offset = (height - min_dim) / 2;
98
99 let square_img = img.crop_imm(x_offset, y_offset, min_dim, min_dim);
100 square_img.resize_exact(s, s, image::imageops::FilterType::Lanczos3)
101 } else {
102 img
103 };
104
105 let mut buffer = Cursor::new(Vec::new());
106 final_img
107 .write_to(&mut buffer, ImageOutputFormat::Png)
108 .map_err(|e| format!("Failed to write PNG: {}", e))?;
109
110 let png_data = buffer.into_inner();
111 let width = final_img.width();
112 let height = final_img.height();
113
114 let b64 = general_purpose::STANDARD.encode(&png_data);
115
116 let svg = format!(
117 r#"<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
118 <image width="{}" height="{}" xlink:href="data:image/png;base64,{}" />
119</svg>"#,
120 width, height, width, height, b64
121 );
122
123 Ok(svg.into_bytes())
124}
125
126#[wasm_bindgen]
127pub fn imgsvg(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, JsValue> {
128 imgsvg_core(input, size).map_err(|e| JsValue::from_str(&e))
129}
130
131pub fn imgpng_core(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, String> {
132 let img = image::load_from_memory(input).map_err(|e| format!("Failed to load image: {}", e))?;
133
134 let final_img = if let Some(s) = size {
135 let (width, height) = (img.width(), img.height());
137 let min_dim = width.min(height);
138 let x_offset = (width - min_dim) / 2;
139 let y_offset = (height - min_dim) / 2;
140
141 let square_img = img.crop_imm(x_offset, y_offset, min_dim, min_dim);
142 square_img.resize_exact(s, s, image::imageops::FilterType::Lanczos3)
143 } else {
144 img
145 };
146
147 let mut buffer = Cursor::new(Vec::new());
148 final_img
149 .write_to(&mut buffer, ImageOutputFormat::Png)
150 .map_err(|e| format!("Failed to write PNG: {}", e))?;
151
152 Ok(buffer.into_inner())
153}
154
155#[wasm_bindgen]
156pub fn imgpng(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, JsValue> {
157 imgpng_core(input, size).map_err(|e| JsValue::from_str(&e))
158}