截图模块优化调整
This commit is contained in:
@@ -78,12 +78,6 @@ fn bgra_to_bmp(bgra: &[u8], width: i32, height: i32) -> Result<Vec<u8>, String>
|
||||
let data_size = w * h * 4;
|
||||
let file_size = 14 + 40 + data_size;
|
||||
|
||||
// BitBlt 取出的 alpha 通道未定义(常为 0),强制不透明
|
||||
let mut pixels = bgra.to_vec();
|
||||
for a in pixels.iter_mut().skip(3).step_by(4) {
|
||||
*a = 255;
|
||||
}
|
||||
|
||||
let mut bmp = Vec::with_capacity(file_size);
|
||||
// BITMAPFILEHEADER
|
||||
bmp.extend_from_slice(b"BM");
|
||||
@@ -103,7 +97,12 @@ fn bgra_to_bmp(bgra: &[u8], width: i32, height: i32) -> Result<Vec<u8>, String>
|
||||
bmp.extend_from_slice(&0u32.to_le_bytes()); // y ppm
|
||||
bmp.extend_from_slice(&0u32.to_le_bytes()); // colors used
|
||||
bmp.extend_from_slice(&0u32.to_le_bytes()); // important colors
|
||||
bmp.extend_from_slice(&pixels);
|
||||
// 像素数据:memcpy 一次性拷贝,再原地修正 alpha(避免 to_vec 中间分配)
|
||||
let pixel_start = bmp.len();
|
||||
bmp.extend_from_slice(bgra);
|
||||
for i in (pixel_start + 3..bmp.len()).step_by(4) {
|
||||
bmp[i] = 255;
|
||||
}
|
||||
Ok(bmp)
|
||||
}
|
||||
|
||||
@@ -117,16 +116,16 @@ fn bgra_to_png(bgra: &[u8], width: i32, height: i32) -> Result<Vec<u8>, String>
|
||||
if width <= 0 || height <= 0 {
|
||||
return Err("无效尺寸".into());
|
||||
}
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let mut rgba = vec![0u8; w * h * 4];
|
||||
for i in 0..(w * h) {
|
||||
rgba[i * 4] = bgra[i * 4 + 2]; // R
|
||||
rgba[i * 4 + 1] = bgra[i * 4 + 1]; // G
|
||||
rgba[i * 4 + 2] = bgra[i * 4]; // B
|
||||
rgba[i * 4 + 3] = 255; // 强制不透明
|
||||
let pixel_count = (width as usize) * (height as usize);
|
||||
let mut rgba = vec![0u8; pixel_count * 4];
|
||||
// chunks_exact 迭代器写法:4 字节对齐连续读取,编译器更容易自动向量化
|
||||
for (src, dst) in bgra.chunks_exact(4).zip(rgba.chunks_exact_mut(4)) {
|
||||
dst[0] = src[2]; // R
|
||||
dst[1] = src[1]; // G
|
||||
dst[2] = src[0]; // B
|
||||
dst[3] = 255; // A 强制不透明
|
||||
}
|
||||
let mut buf = Vec::with_capacity(w * h * 4 / 3);
|
||||
let mut buf = Vec::with_capacity(pixel_count * 4 / 3);
|
||||
PngEncoder::new_with_quality(
|
||||
&mut buf,
|
||||
CompressionType::Fast,
|
||||
@@ -493,7 +492,9 @@ fn bgra_to_dib(bgra: &[u8], width: i32, height: i32) -> Vec<u8> {
|
||||
let header_size = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let mut dib = Vec::with_capacity(header_size as usize + w * h * 4);
|
||||
let row_size = w * 4;
|
||||
let pixel_size = row_size * h;
|
||||
let mut dib = Vec::with_capacity(header_size as usize + pixel_size);
|
||||
let header = BITMAPINFOHEADER {
|
||||
biSize: header_size,
|
||||
biWidth: width,
|
||||
@@ -501,7 +502,7 @@ fn bgra_to_dib(bgra: &[u8], width: i32, height: i32) -> Vec<u8> {
|
||||
biPlanes: 1,
|
||||
biBitCount: 32,
|
||||
biCompression: BI_RGB,
|
||||
biSizeImage: (w * h * 4) as u32,
|
||||
biSizeImage: (pixel_size) as u32,
|
||||
biXPelsPerMeter: 0,
|
||||
biYPelsPerMeter: 0,
|
||||
biClrUsed: 0,
|
||||
@@ -514,15 +515,17 @@ fn bgra_to_dib(bgra: &[u8], width: i32, height: i32) -> Vec<u8> {
|
||||
)
|
||||
};
|
||||
dib.extend_from_slice(raw);
|
||||
// 从 top-down 行序倒序拷贝为 bottom-up,alpha 强制 255
|
||||
for y in (0..h).rev() {
|
||||
let row = &bgra[y * w * 4..(y + 1) * w * 4];
|
||||
for x in 0..w {
|
||||
dib.push(row[x * 4]); // B
|
||||
dib.push(row[x * 4 + 1]); // G
|
||||
dib.push(row[x * 4 + 2]); // R
|
||||
dib.push(255); // A
|
||||
}
|
||||
// 预分配像素区,按行 memcpy 拷贝(top-down → bottom-up 行序翻转),再原地修正 alpha
|
||||
let pixel_start = dib.len();
|
||||
dib.resize(dib.len() + pixel_size, 0);
|
||||
for y in 0..h {
|
||||
let src = y * row_size;
|
||||
let dst = (h - 1 - y) * row_size;
|
||||
dib[pixel_start + dst..pixel_start + dst + row_size]
|
||||
.copy_from_slice(&bgra[src..src + row_size]);
|
||||
}
|
||||
for i in (pixel_start + 3..pixel_start + pixel_size).step_by(4) {
|
||||
dib[i] = 255;
|
||||
}
|
||||
dib
|
||||
}
|
||||
@@ -592,11 +595,17 @@ pub fn copy_png_to_clipboard(png_base64: &str) -> Result<(), String> {
|
||||
write_dib_to_clipboard(&dib)
|
||||
}
|
||||
|
||||
/// RGBA → CF_DIB(BITMAPINFOHEADER + BGRA bottom-up,alpha 强制 255)
|
||||
fn rgba_to_dib(rgba: &image::RgbaImage, width: i32, height: i32) -> Vec<u8> {
|
||||
/// raw RGBA 像素 → CF_DIB(直接从连续像素切片构造,无需 image crate 解码)
|
||||
///
|
||||
/// 用于有标注导出路径:前端 canvas.getImageData() 直传 raw RGBA,
|
||||
/// 省去 toDataURL(PNG 编码+base64) → Rust base64 解码 → PNG 解码 的三次往返。
|
||||
fn rgba_raw_to_dib(rgba: &[u8], width: i32, height: i32) -> Vec<u8> {
|
||||
let header_size = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
let row_size = (width as usize) * 4;
|
||||
let mut dib = Vec::with_capacity(header_size as usize + row_size * height as usize);
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let row_size = w * 4;
|
||||
let pixel_size = row_size * h;
|
||||
let mut dib = Vec::with_capacity(header_size as usize + pixel_size);
|
||||
let header = BITMAPINFOHEADER {
|
||||
biSize: header_size,
|
||||
biWidth: width,
|
||||
@@ -604,7 +613,7 @@ fn rgba_to_dib(rgba: &image::RgbaImage, width: i32, height: i32) -> Vec<u8> {
|
||||
biPlanes: 1,
|
||||
biBitCount: 32,
|
||||
biCompression: BI_RGB,
|
||||
biSizeImage: (row_size * height as usize) as u32,
|
||||
biSizeImage: (pixel_size) as u32,
|
||||
biXPelsPerMeter: 0,
|
||||
biYPelsPerMeter: 0,
|
||||
biClrUsed: 0,
|
||||
@@ -617,14 +626,105 @@ fn rgba_to_dib(rgba: &image::RgbaImage, width: i32, height: i32) -> Vec<u8> {
|
||||
)
|
||||
};
|
||||
dib.extend_from_slice(raw);
|
||||
// bottom-up 行序,BGRA 像素,alpha 强制 255
|
||||
for y in (0..height as usize).rev() {
|
||||
for x in 0..width as usize {
|
||||
let p = rgba.get_pixel(x as u32, y as u32).0;
|
||||
dib.push(p[2]); // B
|
||||
dib.push(p[1]); // G
|
||||
dib.push(p[0]); // R
|
||||
dib.push(255); // A
|
||||
let pixel_start = dib.len();
|
||||
dib.resize(dib.len() + pixel_size, 0);
|
||||
// RGBA top-down → BGRA bottom-up,alpha 强制 255
|
||||
for y in 0..h {
|
||||
let src_off = (h - 1 - y) * row_size;
|
||||
let dst_off = y * row_size;
|
||||
for x in 0..w {
|
||||
let si = src_off + x * 4;
|
||||
let di = pixel_start + dst_off + x * 4;
|
||||
dib[di] = rgba[si + 2]; // B
|
||||
dib[di + 1] = rgba[si + 1]; // G
|
||||
dib[di + 2] = rgba[si]; // R
|
||||
dib[di + 3] = 255; // A
|
||||
}
|
||||
}
|
||||
dib
|
||||
}
|
||||
|
||||
/// raw RGBA → PNG(直接从连续像素切片编码,跳过 image crate 的 RgbaImage 包装)
|
||||
fn rgba_raw_to_png(rgba: &[u8], width: i32, height: i32) -> Result<Vec<u8>, String> {
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::ImageEncoder;
|
||||
if width <= 0 || height <= 0 {
|
||||
return Err("无效尺寸".into());
|
||||
}
|
||||
let mut buf = Vec::with_capacity(rgba.len() / 3);
|
||||
PngEncoder::new_with_quality(
|
||||
&mut buf,
|
||||
CompressionType::Fast,
|
||||
FilterType::NoFilter,
|
||||
)
|
||||
.write_image(rgba, width as u32, height as u32, image::ExtendedColorType::Rgba8)
|
||||
.map_err(|e| format!("PNG 编码失败: {}", e))?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// 有标注导出:raw RGBA → 剪贴板 DIB + PNG base64(一次调用完成复制+编码)
|
||||
///
|
||||
/// 替代原 composeBase64 + screenshot_copy_image 两步:
|
||||
/// 省去前端 toDataURL(PNG 编码+base64) + Rust base64 解码 + PNG 解码,
|
||||
/// 直接 raw RGBA 传输 → DIB 写剪贴板 + PNG 编码返回。
|
||||
pub fn compose_copy_rgba(rgba: &[u8], width: i32, height: i32) -> Result<CaptureData, String> {
|
||||
let expected = (width as usize) * (height as usize) * 4;
|
||||
if rgba.len() < expected {
|
||||
return Err(format!("像素数据不足: {} < {}", rgba.len(), expected));
|
||||
}
|
||||
let dib = rgba_raw_to_dib(rgba, width, height);
|
||||
write_dib_to_clipboard(&dib)?;
|
||||
let png = rgba_raw_to_png(rgba, width, height)?;
|
||||
Ok(CaptureData {
|
||||
png_base64: base64_encode(&png),
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
/// RGBA → CF_DIB(BITMAPINFOHEADER + BGRA bottom-up,alpha 强制 255)
|
||||
fn rgba_to_dib(rgba: &image::RgbaImage, width: i32, height: i32) -> Vec<u8> {
|
||||
let header_size = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let row_size = w * 4;
|
||||
let pixel_size = row_size * h;
|
||||
let mut dib = Vec::with_capacity(header_size as usize + pixel_size);
|
||||
let header = BITMAPINFOHEADER {
|
||||
biSize: header_size,
|
||||
biWidth: width,
|
||||
biHeight: height, // 正值 = bottom-up
|
||||
biPlanes: 1,
|
||||
biBitCount: 32,
|
||||
biCompression: BI_RGB,
|
||||
biSizeImage: (pixel_size) as u32,
|
||||
biXPelsPerMeter: 0,
|
||||
biYPelsPerMeter: 0,
|
||||
biClrUsed: 0,
|
||||
biClrImportant: 0,
|
||||
};
|
||||
let raw = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
&header as *const _ as *const u8,
|
||||
std::mem::size_of::<BITMAPINFOHEADER>(),
|
||||
)
|
||||
};
|
||||
dib.extend_from_slice(raw);
|
||||
// 预分配像素区,用 as_raw() 连续切片替代逐像素 get_pixel
|
||||
let pixel_start = dib.len();
|
||||
dib.resize(dib.len() + pixel_size, 0);
|
||||
let src = rgba.as_raw(); // &[u8] RGBA top-down
|
||||
for y in 0..h {
|
||||
// bottom-up: DIB 第一行 = 图像最后一行
|
||||
let src_off = (h - 1 - y) * row_size;
|
||||
let dst_off = y * row_size;
|
||||
for x in 0..w {
|
||||
let si = src_off + x * 4;
|
||||
let di = pixel_start + dst_off + x * 4;
|
||||
dib[di] = src[si + 2]; // B
|
||||
dib[di + 1] = src[si + 1]; // G
|
||||
dib[di + 2] = src[si]; // R
|
||||
dib[di + 3] = 255; // A
|
||||
}
|
||||
}
|
||||
dib
|
||||
|
||||
@@ -279,6 +279,39 @@ pub async fn screenshot_copy_image(png_base64: String) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 有标注导出:接收 raw RGBA(前端 canvas.getImageData 直传),一次完成 剪贴板+PNG base64。
|
||||
///
|
||||
/// 省去前端 toDataURL(PNG 编码+base64) → Rust base64 解码 → PNG 解码 三次往返。
|
||||
/// body 格式:前 8 字节 = width(i32 LE) + height(i32 LE),之后为 raw RGBA 像素。
|
||||
#[tauri::command]
|
||||
pub async fn screenshot_compose_copy(
|
||||
request: tauri::ipc::Request<'_>,
|
||||
) -> Result<super::CaptureData, String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let body = match request.body() {
|
||||
tauri::ipc::InvokeBody::Raw(data) => data.clone(),
|
||||
_ => return Err("需要 raw body(ArrayBuffer)".into()),
|
||||
};
|
||||
if body.len() < 8 {
|
||||
return Err("数据不足:缺少尺寸头".into());
|
||||
}
|
||||
let width = i32::from_le_bytes([body[0], body[1], body[2], body[3]]);
|
||||
let height = i32::from_le_bytes([body[4], body[5], body[6], body[7]]);
|
||||
let rgba = body[8..].to_vec();
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
super::capture::compose_copy_rgba(&rgba, width, height)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("合成复制任务失败: {}", e))?
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let _ = request;
|
||||
Err("截图仅支持 Windows".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// 将 PNG base64 写入文件
|
||||
#[tauri::command]
|
||||
pub async fn screenshot_save_png(png_base64: String, path: String) -> Result<(), String> {
|
||||
|
||||
Reference in New Issue
Block a user