File size: 3,921 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
89
90
91
92
93
94
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Manager};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CaptureClip {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContentLayout {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
}

#[tauri::command]
pub fn browser_capture_viewport(app: AppHandle, layout: ContentLayout) -> Result<String, String> {
    if layout.width < 10.0 || layout.height < 10.0 {
        return Err("content area is too small to capture".into());
    }
    let window = app.get_window("main").ok_or("main window not found")?;
    let scale = window.scale_factor().map_err(|e| format!("scale_factor: {e}"))?;
    let outer = window.outer_position().map_err(|e| format!("outer_position: {e}"))?;
    let abs_x = outer.x + (layout.x * scale).round() as i32;
    let abs_y = outer.y + (layout.y * scale).round() as i32;
    let abs_w = (layout.width * scale).round().max(1.0) as u32;
    let abs_h = (layout.height * scale).round().max(1.0) as u32;
    capture_screen_region(abs_x, abs_y, abs_w, abs_h)
}

#[tauri::command]
pub fn browser_capture_clip(app: AppHandle, layout: ContentLayout, clip: CaptureClip) -> Result<String, String> {
    if clip.width < 1.0 || clip.height < 1.0 {
        return Err("clip area is too small".into());
    }
    if layout.width < 10.0 || layout.height < 10.0 {
        return Err("content area is too small".into());
    }
    let window = app.get_window("main").ok_or("main window not found")?;
    let scale = window.scale_factor().map_err(|e| format!("scale_factor: {e}"))?;
    let outer = window.outer_position().map_err(|e| format!("outer_position: {e}"))?;
    let abs_x = outer.x + ((layout.x + clip.x) * scale).round() as i32;
    let abs_y = outer.y + ((layout.y + clip.y) * scale).round() as i32;
    let abs_w = (clip.width * scale).round().max(1.0) as u32;
    let abs_h = (clip.height * scale).round().max(1.0) as u32;
    capture_screen_region(abs_x, abs_y, abs_w, abs_h)
}

#[tauri::command]
pub fn browser_capture_full_page(app: AppHandle, layout: ContentLayout) -> Result<String, String> {
    browser_capture_viewport(app, layout)
}

fn capture_screen_region(x: i32, y: i32, width: u32, height: u32) -> Result<String, String> {
    use base64::{engine::general_purpose, Engine as _};
    use screenshots::Screen;

    if width < 1 || height < 1 {
        return Err("capture region is empty".into());
    }
    let screens = Screen::all().map_err(|e| format!("failed to enumerate screens: {e}"))?;
    let cx = x + (width as i32 / 2);
    let cy = y + (height as i32 / 2);
    let screen = screens.iter().find(|s| {
        let di = s.display_info;
        let sx = di.x as i32;
        let sy = di.y as i32;
        let sw = di.width as i32;
        let sh = di.height as i32;
        cx >= sx && cy >= sy && cx < sx + sw && cy < sy + sh
    }).or_else(|| screens.first()).ok_or("no screen found")?;

    let capture = screen.capture_area(x, y, width, height)
        .map_err(|e| format!("screen capture failed (x={x}, y={y}, w={width}, h={height}): {e}"))?;
    let cw = capture.width();
    let ch = capture.height();
    let raw = capture.into_raw();

    let img_buf = image::RgbaImage::from_raw(cw, ch, raw)
        .ok_or_else(|| "failed to create image buffer".to_string())?;
    let dynamic = image::DynamicImage::ImageRgba8(img_buf);
    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}"))
}