use base64::{engine::general_purpose, Engine as _};
use image::ImageOutputFormat;
use std::io::{Cursor, Write};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn set_panic_hook() {
console_error_panic_hook::set_once();
}
const DEFAULT_SIZES: [u32; 6] = [16, 32, 48, 64, 128, 256];
pub fn imgico_core(input: &[u8], sizes: Option<Vec<u32>>) -> Result<Vec<u8>, String> {
let sizes = sizes.unwrap_or_else(|| DEFAULT_SIZES.to_vec());
let img = image::load_from_memory(input).map_err(|e| format!("Failed to load image: {}", e))?;
let mut images = Vec::new();
for size in sizes {
if size < 1 || size > 256 {
return Err(format!(
"Invalid icon size: {}. Size must be between 1 and 256.",
size
));
}
let (width, height) = (img.width(), img.height());
let min_dim = width.min(height);
let x_offset = (width - min_dim) / 2;
let y_offset = (height - min_dim) / 2;
let square_img = img.crop_imm(x_offset, y_offset, min_dim, min_dim);
let resized = square_img.resize_exact(size, size, image::imageops::FilterType::Lanczos3);
let mut buffer = Cursor::new(Vec::new());
resized
.write_to(&mut buffer, ImageOutputFormat::Png)
.map_err(|e| format!("Failed to write PNG: {}", e))?;
images.push((buffer.into_inner(), size));
}
let mut ico_data = Vec::new();
ico_data.write_all(&0u16.to_le_bytes()).unwrap(); ico_data.write_all(&1u16.to_le_bytes()).unwrap(); ico_data
.write_all(&(images.len() as u16).to_le_bytes())
.unwrap();
let directory_size = 16 * images.len();
let mut offset = 6 + directory_size;
for (buffer, size) in &images {
let dim = if *size >= 256 { 0 } else { *size as u8 };
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
.write_all(&(buffer.len() as u32).to_le_bytes())
.unwrap(); ico_data.write_all(&(offset as u32).to_le_bytes()).unwrap();
offset += buffer.len();
}
for (buffer, _) in images {
ico_data.write_all(&buffer).unwrap();
}
Ok(ico_data)
}
#[wasm_bindgen]
pub fn imgico(input: &[u8], sizes: Option<Vec<u32>>) -> Result<Vec<u8>, JsValue> {
imgico_core(input, sizes).map_err(|e| JsValue::from_str(&e))
}
pub fn imgsvg_core(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, String> {
let img = image::load_from_memory(input).map_err(|e| format!("Failed to load image: {}", e))?;
let final_img = if let Some(s) = size {
let (width, height) = (img.width(), img.height());
let min_dim = width.min(height);
let x_offset = (width - min_dim) / 2;
let y_offset = (height - min_dim) / 2;
let square_img = img.crop_imm(x_offset, y_offset, min_dim, min_dim);
square_img.resize_exact(s, s, image::imageops::FilterType::Lanczos3)
} else {
img
};
let mut buffer = Cursor::new(Vec::new());
final_img
.write_to(&mut buffer, ImageOutputFormat::Png)
.map_err(|e| format!("Failed to write PNG: {}", e))?;
let png_data = buffer.into_inner();
let width = final_img.width();
let height = final_img.height();
let b64 = general_purpose::STANDARD.encode(&png_data);
let svg = format!(
r#"<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="{}" height="{}" xlink:href="data:image/png;base64,{}" />
</svg>"#,
width, height, width, height, b64
);
Ok(svg.into_bytes())
}
#[wasm_bindgen]
pub fn imgsvg(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, JsValue> {
imgsvg_core(input, size).map_err(|e| JsValue::from_str(&e))
}
pub fn imgpng_core(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, String> {
let img = image::load_from_memory(input).map_err(|e| format!("Failed to load image: {}", e))?;
let final_img = if let Some(s) = size {
let (width, height) = (img.width(), img.height());
let min_dim = width.min(height);
let x_offset = (width - min_dim) / 2;
let y_offset = (height - min_dim) / 2;
let square_img = img.crop_imm(x_offset, y_offset, min_dim, min_dim);
square_img.resize_exact(s, s, image::imageops::FilterType::Lanczos3)
} else {
img
};
let mut buffer = Cursor::new(Vec::new());
final_img
.write_to(&mut buffer, ImageOutputFormat::Png)
.map_err(|e| format!("Failed to write PNG: {}", e))?;
Ok(buffer.into_inner())
}
#[wasm_bindgen]
pub fn imgpng(input: &[u8], size: Option<u32>) -> Result<Vec<u8>, JsValue> {
imgpng_core(input, size).map_err(|e| JsValue::from_str(&e))
}