File size: 4,084 Bytes
3d7d9b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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<String, String> {
    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<String, String> {
    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<u8>) -> Result<String, String> {
    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<u8> = 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<String, String> {
    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<String, String> {
    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<String, String> {
    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()
}