use base64::{engine::general_purpose, Engine as _}; use tauri::{AppHandle, Manager}; /// Board state persistence for the canvas-first architecture. #[tauri::command] pub fn board_load_state(app: AppHandle) -> Result { let path = crate::persistence::app_file_path(&app, "canvas_state.json")?; if !path.exists() { return Ok("{}".to_string()); } std::fs::read_to_string(&path).map_err(|e| e.to_string()) } #[tauri::command] pub fn board_save_state(app: AppHandle, state: String) -> Result<(), String> { let path = crate::persistence::app_file_path(&app, "canvas_state.json")?; std::fs::write(&path, &state).map_err(|e| e.to_string()) } #[tauri::command] pub fn board_export_file(_app: AppHandle, filepath: String, state: String) -> Result<(), String> { std::fs::write(&filepath, &state).map_err(|e| e.to_string()) } #[tauri::command] pub fn board_import_file(_app: AppHandle, filepath: String) -> Result { let content = std::fs::read_to_string(&filepath).map_err(|e| e.to_string())?; let _: serde_json::Value = serde_json::from_str(&content).map_err(|e| format!("Invalid board file: {e}"))?; Ok(content) } fn encode_rgba_to_png_data_url(width: u32, height: u32, raw_pixels: Vec) -> Result { let img_buffer = image::RgbaImage::from_raw(width, height, raw_pixels) .ok_or_else(|| "Failed to create image buffer from screenshot pixels".to_string())?; let dynamic = image::DynamicImage::ImageRgba8(img_buffer); let mut png_bytes: Vec = Vec::new(); dynamic.write_to(&mut std::io::Cursor::new(&mut png_bytes), image::ImageFormat::Png) .map_err(|e| format!("PNG encode failed: {e}"))?; let b64 = general_purpose::STANDARD.encode(&png_bytes); Ok(format!("data:image/png;base64,{b64}")) } /// Capture full primary screen and return as base64 PNG data URL. #[tauri::command] pub fn screen_capture_full() -> Result { use screenshots::Screen; let screens = Screen::all().map_err(|e| format!("Failed to get screens: {e}"))?; let screen = screens.first().ok_or("No screen found")?; let capture = screen.capture().map_err(|e| format!("Capture failed: {e}"))?; let width = capture.width(); let height = capture.height(); encode_rgba_to_png_data_url(width, height, capture.into_raw()) } /// Low-level absolute physical-pixel capture. #[tauri::command] pub fn screen_capture_region(x: i32, y: i32, width: u32, height: u32) -> Result { use screenshots::Screen; let screens = Screen::all().map_err(|e| format!("Failed to get screens: {e}"))?; let screen = screens.first().ok_or("No screen found")?; let capture = screen.capture_area(x, y, width, height) .map_err(|e| format!("Region capture failed: {e}"))?; let w = capture.width(); let h = capture.height(); encode_rgba_to_png_data_url(w, h, capture.into_raw()) } /// DPI-correct capture of a rectangle in the main Tauri window's CSS/logical coordinate space. /// Frontend getBoundingClientRect() values are logical pixels. screenshots::capture_area expects /// physical screen pixels, so we convert using the window outer position and scale factor. #[tauri::command] pub fn screen_capture_window_region(app: AppHandle, x: f64, y: f64, width: f64, height: f64) -> Result { let window = app.get_window("main").ok_or("main window not found")?; let scale = window.scale_factor().map_err(|e| format!("scale_factor failed: {e}"))?; let pos = window.outer_position().map_err(|e| format!("outer_position failed: {e}"))?; let px = pos.x + (x * scale).round() as i32; let py = pos.y + (y * scale).round() as i32; let pw = (width * scale).round().max(1.0) as u32; let ph = (height * scale).round().max(1.0) as u32; screen_capture_region(px, py, pw, ph) } #[tauri::command] pub fn phase0_status() -> String { "Refstudio v1.0-alpha — Board-first reference tool for artists".to_string() }