use base64::{engine::general_purpose::STANDARD, Engine};
use serde::Serialize;
#[derive(Serialize)]
pub struct ImageData {
pub data_url: String,
pub mime: String,
pub original_bytes: usize,
pub base64_len: usize,
}
fn mime_from_bytes(bytes: &[u8], path: &str) -> String {
if bytes.starts_with(b"\x89PNG\r\n\x1a\n") {
return "image/png".into();
}
if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) {
return "image/jpeg".into();
}
if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
return "image/gif".into();
}
if bytes.len() >= 12 && &bytes[0..4] == b"RIFF" && &bytes[8..12] == b"WEBP" {
return "image/webp".into();
}
if bytes.starts_with(&[0x00, 0x00, 0x01, 0x00]) {
return "image/x-icon".into();
}
if bytes.starts_with(b"BM") {
return "image/bmp".into();
}
if let Ok(text) = std::str::from_utf8(&bytes[..bytes.len().min(256)]) {
let trimmed = text.trim_start_matches('\u{feff}').trim_start();
if trimmed.starts_with("<?xml") || trimmed.starts_with("<svg") {
return "image/svg+xml".into();
}
}
let ext = std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
match ext.as_str() {
"png" => "image/png",
"jpg" | "jpeg" => "image/jpeg",
"gif" => "image/gif",
"webp" => "image/webp",
"svg" => "image/svg+xml",
"ico" => "image/x-icon",
"bmp" => "image/bmp",
_ => "application/octet-stream",
}
.to_string()
}
#[tauri::command]
pub fn image_to_base64(path: String) -> Result<ImageData, String> {
let bytes = std::fs::read(&path).map_err(|e| e.to_string())?;
let mime = mime_from_bytes(&bytes, &path);
let b64 = STANDARD.encode(&bytes);
let data_url = format!("data:{mime};base64,{b64}");
Ok(ImageData {
original_bytes: bytes.len(),
base64_len: b64.len(),
data_url,
mime,
})
}