fix export bug
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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("批量导出完成!");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user