bit-twiddler 0.2.1

Cross-platform developer toolbox: bit manipulation, hashing, YAML/JSON/SQL, QR, Markdown, cron, and 40+ more tools — Tauri v2, no Node.js
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();
    }
    // SVG is text, not magic-byte sniffable the same way
    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();
        }
    }
    // Fall back to the file extension when magic-byte sniffing is inconclusive
    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,
    })
}