musealpha / src-tauri /src /browser /capture.rs
asdf98's picture
Upload 112 files
3d7d9b5 verified
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}"))
}