fix export bug

This commit is contained in:
Julian Freeman
2026-01-19 13:18:57 -04:00
parent 6cb5b7a61f
commit 84ae92d1e3
2 changed files with 92 additions and 83 deletions

View File

@@ -144,7 +144,7 @@ fn parse_hex_color(hex: &str) -> image::Rgba<u8> {
} }
#[tauri::command] #[tauri::command]
async fn export_batch(images: Vec<ExportImageTask>, watermark: WatermarkSettings, output_dir: String) -> Result<String, String> { async fn export_batch(images: Vec<ExportImageTask>, watermark: WatermarkSettings, output_dir: String, mode: String) -> Result<String, String> {
let font = FontRef::try_from_slice(FONT_DATA).map_err(|e| format!("Font error: {}", e))?; let font = FontRef::try_from_slice(FONT_DATA).map_err(|e| format!("Font error: {}", e))?;
// Note: Settings are now resolved per-task // Note: Settings are now resolved per-task
@@ -157,92 +157,98 @@ async fn export_batch(images: Vec<ExportImageTask>, watermark: WatermarkSettings
let mut base_img = dynamic_img.to_rgba8(); let mut base_img = dynamic_img.to_rgba8();
let (width, height) = base_img.dimensions(); let (width, height) = base_img.dimensions();
// Determine effective settings (Task > Global) // ONLY EXECUTE WATERMARK LOGIC IF MODE IS 'ADD'
let eff_scale = task.scale.unwrap_or(watermark.scale); if mode == "add" {
let eff_opacity = task.opacity.unwrap_or(watermark.opacity); // Determine effective settings (Task > Global)
let eff_color_hex = task.color.as_ref().unwrap_or(&watermark.color); let eff_scale = task.scale.unwrap_or(watermark.scale);
let eff_opacity = task.opacity.unwrap_or(watermark.opacity);
let eff_color_hex = task.color.as_ref().unwrap_or(&watermark.color);
// Calculate final color // Calculate final color
let base_color = parse_hex_color(eff_color_hex); let base_color = parse_hex_color(eff_color_hex);
let alpha = (eff_opacity * 255.0) as u8; let alpha = (eff_opacity * 255.0) as u8;
let text_color = image::Rgba([base_color[0], base_color[1], base_color[2], alpha]); let text_color = image::Rgba([base_color[0], base_color[1], base_color[2], alpha]);
// 1. Calculate Font Scale based on Image Height // 1. Calculate Font Scale based on Image Height
let mut scale_px = height as f32 * eff_scale as f32; let mut scale_px = height as f32 * eff_scale as f32;
// 2. Measure Text // 2. Measure Text
let scaled_font = PxScale::from(scale_px); let scaled_font = PxScale::from(scale_px);
let (t_width, _t_height) = imageproc::drawing::text_size(scaled_font, &font, &watermark.text); let (t_width, _t_height) = imageproc::drawing::text_size(scaled_font, &font, &watermark.text);
// 3. Ensure it fits width (Padding 15%) // 3. Ensure it fits width (Padding 15%)
let max_width = (width as f32 * 0.85) as u32; let max_width = (width as f32 * 0.85) as u32;
if t_width > max_width { if t_width > max_width {
let ratio = max_width as f32 / t_width as f32; let ratio = max_width as f32 / t_width as f32;
scale_px *= ratio; scale_px *= ratio;
} }
let final_scale = PxScale::from(scale_px); let final_scale = PxScale::from(scale_px);
let (final_t_width, final_t_height) = imageproc::drawing::text_size(final_scale, &font, &watermark.text); let (final_t_width, final_t_height) = imageproc::drawing::text_size(final_scale, &font, &watermark.text);
// 4. Determine Position (Task Specific > ZCA) // 4. Determine Position (Task Specific > ZCA)
// If task has manual_position, use it. Otherwise calculate ZCA. // If task has manual_position, use it. Otherwise calculate ZCA.
let (pos_x_pct, pos_y_pct) = if let Some(pos) = &task.manual_position { let (pos_x_pct, pos_y_pct) = if let Some(pos) = &task.manual_position {
(pos.x, pos.y) (pos.x, pos.y)
} else { } else {
match calculate_zca_internal(&dynamic_img) { match calculate_zca_internal(&dynamic_img) {
Ok(res) => (res.x, res.y), Ok(res) => (res.x, res.y),
Err(_) => (0.5, 0.97), Err(_) => (0.5, 0.97),
} }
}; };
// Calculate initial top-left based on center // Calculate initial top-left based on center
let center_x = width as f64 * pos_x_pct; let center_x = width as f64 * pos_x_pct;
let center_y = height as f64 * pos_y_pct; let center_y = height as f64 * pos_y_pct;
let mut x = (center_x - (final_t_width as f64 / 2.0)) as i32; let mut x = (center_x - (final_t_width as f64 / 2.0)) as i32;
let mut y = (center_y - (final_t_height as f64 / 2.0)) as i32; let mut y = (center_y - (final_t_height as f64 / 2.0)) as i32;
// 5. Strict Boundary Clamping // 5. Strict Boundary Clamping
// We ensure the text box (final_t_width, final_t_height) is always inside (0, 0, width, height) // We ensure the text box (final_t_width, final_t_height) is always inside (0, 0, width, height)
let min_padding = 2; // Absolute minimum pixels from edge let min_padding = 2; // Absolute minimum pixels from edge
if x < min_padding { x = min_padding; } if x < min_padding { x = min_padding; }
if y < min_padding { y = min_padding; } if y < min_padding { y = min_padding; }
if x + final_t_width as i32 > width as i32 - min_padding { if x + final_t_width as i32 > width as i32 - min_padding {
x = width as i32 - final_t_width as i32 - min_padding; x = width as i32 - final_t_width as i32 - min_padding;
} }
if y + final_t_height as i32 > height as i32 - min_padding { if y + final_t_height as i32 > height as i32 - min_padding {
y = height as i32 - final_t_height as i32 - min_padding; y = height as i32 - final_t_height as i32 - min_padding;
} }
// Re-clamp just in case of very small images where text is larger than image // Re-clamp just in case of very small images where text is larger than image
x = x.max(0); x = x.max(0);
y = y.max(0); y = y.max(0);
// 6. Draw Stroke (Simple 4-direction offset for black outline) // SKIP DRAWING if text is empty (e.g. Remove Mode)
// Stroke alpha should match text alpha if !watermark.text.trim().is_empty() {
let stroke_color = image::Rgba([0, 0, 0, text_color[3]]); // 6. Draw Stroke (Simple 4-direction offset for black outline)
for offset in [(-1, -1), (-1, 1), (1, -1), (1, 1)] { // Stroke alpha should match text alpha
draw_text_mut( let stroke_color = image::Rgba([0, 0, 0, text_color[3]]);
&mut base_img, for offset in [(-1, -1), (-1, 1), (1, -1), (1, 1)] {
stroke_color, draw_text_mut(
x + offset.0, &mut base_img,
y + offset.1, stroke_color,
final_scale, x + offset.0,
&font, y + offset.1,
&watermark.text, final_scale,
); &font,
} &watermark.text,
);
}
// 7. Draw Main Text // 7. Draw Main Text
draw_text_mut( draw_text_mut(
&mut base_img, &mut base_img,
text_color, text_color,
x, x,
y, y,
final_scale, final_scale,
&font, &font,
&watermark.text, &watermark.text,
); );
}
} // END IF MODE == ADD
// Save // Save
let file_name = input_path.file_name().unwrap_or_default(); let file_name = input_path.file_name().unwrap_or_default();

View File

@@ -32,7 +32,9 @@ async function openFolder() {
async function exportBatch() { async function exportBatch() {
if (store.images.length === 0) return; if (store.images.length === 0) return;
if (!store.watermarkSettings.text) {
// Only require text if in ADD mode
if (store.editMode === 'add' && !store.watermarkSettings.text) {
alert("请输入水印文字。"); alert("请输入水印文字。");
return; return;
} }
@@ -67,7 +69,8 @@ async function exportBatch() {
await invoke('export_batch', { await invoke('export_batch', {
images: exportTasks, images: exportTasks,
watermark: rustWatermarkSettings, watermark: rustWatermarkSettings,
outputDir: outputDir outputDir: outputDir,
mode: store.editMode
}); });
alert("批量导出完成!"); alert("批量导出完成!");
} }