feat(compression): add zlib compression for scene files and image optimization

- Add flate2 dependency for zlib compression support in Rust backend
- Implement compressed scene file format with magic number (PURGIFZ1) for format detection
- Add backward compatibility for loading uncompressed legacy scene files
- Convert scene JSON to compact format (no indentation) to reduce file size
- Add image compression utilities with configurable quality and dimensions
- Implement image size detection and compression necessity checking
- Add helper functions for base64 image size calculation and MIME type detection
- Disable global Tauri object in web context for security
- Reduce memory footprint for large scene files and high-resolution images through compression
This commit is contained in:
2025-12-22 12:16:22 +08:00
parent a9e998b603
commit 5416a2df36
7 changed files with 162 additions and 7 deletions

1
src-tauri/Cargo.lock generated
View File

@@ -3559,6 +3559,7 @@ version = "0.1.1"
dependencies = [
"arboard",
"base64 0.22.1",
"flate2",
"image",
"serde",
"serde_json",

View File

@@ -28,6 +28,7 @@ serde_json = "1"
base64 = "0.22"
image = "0.25"
arboard = "3"
flate2 = "1.0" # zlib 压缩
[target.'cfg(windows)'.dependencies]
windows = { version = "0.58", features = [

View File

@@ -2,8 +2,12 @@
// Handles scene save/load and file reading operations
use std::fs;
use std::io::{Read, Write};
use std::path::Path;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use flate2::Compression;
use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use serde::{Deserialize, Serialize};
use tauri_plugin_dialog::{DialogExt, FilePath};
use tauri::AppHandle;
@@ -15,16 +19,51 @@ pub struct FileDialogResult {
pub cancelled: bool,
}
/// Save scene data to a file
/// 文件魔数,用于识别压缩格式
const COMPRESSED_MAGIC: &[u8] = b"PURGIFZ1";
/// Save scene data to a file (with zlib compression)
#[tauri::command]
pub async fn save_scene(path: String, data: String) -> Result<(), String> {
fs::write(&path, data).map_err(|e| format!("Failed to save scene: {}", e))
// 使用 zlib 压缩数据
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(data.as_bytes())
.map_err(|e| format!("Failed to compress data: {}", e))?;
let compressed = encoder.finish()
.map_err(|e| format!("Failed to finish compression: {}", e))?;
// 写入魔数 + 压缩数据
let mut output = Vec::with_capacity(COMPRESSED_MAGIC.len() + compressed.len());
output.extend_from_slice(COMPRESSED_MAGIC);
output.extend_from_slice(&compressed);
let output_len = output.len();
fs::write(&path, output).map_err(|e| format!("Failed to save scene: {}", e))?;
println!("Scene saved: {} bytes -> {} bytes (compressed)", data.len(), output_len);
Ok(())
}
/// Load scene data from a file
/// Load scene data from a file (supports both compressed and uncompressed)
#[tauri::command]
pub async fn load_scene(path: String) -> Result<String, String> {
fs::read_to_string(&path).map_err(|e| format!("Failed to load scene: {}", e))
let data = fs::read(&path).map_err(|e| format!("Failed to read scene: {}", e))?;
// 检查是否是压缩格式
if data.starts_with(COMPRESSED_MAGIC) {
// 解压缩
let compressed = &data[COMPRESSED_MAGIC.len()..];
let mut decoder = ZlibDecoder::new(compressed);
let mut decompressed = String::new();
decoder.read_to_string(&mut decompressed)
.map_err(|e| format!("Failed to decompress data: {}", e))?;
println!("Scene loaded: {} bytes -> {} bytes (decompressed)", data.len(), decompressed.len());
Ok(decompressed)
} else {
// 旧格式,直接读取为字符串
String::from_utf8(data).map_err(|e| format!("Failed to parse scene: {}", e))
}
}
/// Read a file and return its contents as base64

View File

@@ -25,7 +25,8 @@
],
"security": {
"csp": null
}
},
"withGlobalTauri": false
},
"bundle": {
"active": true,