From fd90ac1df337e075fd93d5850adcb2769136f905 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Mon, 19 Jan 2026 00:48:16 -0400 Subject: [PATCH] seperate size --- src-tauri/src/lib.rs | 19 +++++++---- src/App.vue | 6 ++-- src/components/HeroView.vue | 9 +++-- src/components/SettingsPanel.vue | 57 ++++++++++++++++++++++++++------ src/stores/gallery.ts | 38 ++++++++++++++++++--- 5 files changed, 103 insertions(+), 26 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index dab387b..311e02b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -107,6 +107,8 @@ struct ZcaResult { struct ExportImageTask { path: String, manual_position: Option, + scale: Option, + opacity: Option, } #[derive(serde::Deserialize)] @@ -141,11 +143,7 @@ async fn export_batch(images: Vec, watermark: WatermarkSettings let font = FontRef::try_from_slice(FONT_DATA).map_err(|e| format!("Font error: {}", e))?; let base_color = parse_hex_color(&watermark.color); - // Calculate final color with opacity - // imageproc draws with the exact color given. To support opacity, we need to handle it. - // However, draw_text_mut blends. If we provide an Rgba with alpha < 255, it should blend. - let alpha = (watermark.opacity * 255.0) as u8; - let text_color = image::Rgba([base_color[0], base_color[1], base_color[2], alpha]); + // Note: opacity and final text color are now calculated per-task let results: Vec> = images.par_iter().map(|task| { let input_path = Path::new(&task.path); @@ -155,8 +153,16 @@ async fn export_batch(images: Vec, watermark: WatermarkSettings let mut base_img = dynamic_img.to_rgba8(); let (width, height) = base_img.dimensions(); + // Determine effective settings (Task > Global) + let eff_scale = task.scale.unwrap_or(watermark.scale); + let eff_opacity = task.opacity.unwrap_or(watermark.opacity); + + // Calculate final color with effective opacity + let alpha = (eff_opacity * 255.0) as u8; + let text_color = image::Rgba([base_color[0], base_color[1], base_color[2], alpha]); + // 1. Calculate Font Scale based on Image Height - let mut scale_px = height as f32 * watermark.scale as f32; + let mut scale_px = height as f32 * eff_scale as f32; // 2. Measure Text let scaled_font = PxScale::from(scale_px); @@ -207,6 +213,7 @@ async fn export_batch(images: Vec, watermark: WatermarkSettings y = y.max(0); // 6. Draw Stroke (Simple 4-direction offset for black outline) + // Stroke alpha should match text alpha let stroke_color = image::Rgba([0, 0, 0, text_color[3]]); for offset in [(-1, -1), (-1, 1), (1, -1), (1, 1)] { draw_text_mut( diff --git a/src/App.vue b/src/App.vue index 15b1703..c43d41a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -47,10 +47,12 @@ async function exportBatch() { if (outputDir && typeof outputDir === 'string') { isExporting.value = true; - // Map images to include manual position + // Map images to include manual settings const exportTasks = store.images.map(img => ({ path: img.path, - manual_position: img.manualPosition || null + manual_position: img.manualPosition || null, + scale: img.scale || null, + opacity: img.opacity || null })); // Pass dummy globals for rust struct compatibility diff --git a/src/components/HeroView.vue b/src/components/HeroView.vue index 98cd75f..73f3b3e 100644 --- a/src/components/HeroView.vue +++ b/src/components/HeroView.vue @@ -64,7 +64,7 @@ watch(() => store.selectedImage, () => { nextTick(() => calculateLayout()); }); -// Use either manual position (if specific image has it) or ZCA suggestion +// Use either manual position (if override is true) or ZCA suggestion const position = computed(() => { if (store.selectedImage?.manualPosition) { return store.selectedImage.manualPosition; @@ -73,6 +73,9 @@ const position = computed(() => { return store.selectedImage?.zcaSuggestion ? { x: store.selectedImage.zcaSuggestion.x, y: store.selectedImage.zcaSuggestion.y } : { x: 0.5, y: 0.97 }; }); +const effectiveScale = computed(() => store.selectedImage?.scale ?? store.watermarkSettings.scale); +const effectiveOpacity = computed(() => store.selectedImage?.opacity ?? store.watermarkSettings.opacity); + const onMouseDown = (e: MouseEvent) => { e.preventDefault(); isDragging.value = true; @@ -155,10 +158,10 @@ const onMouseLeave = () => { left: (position.x * 100) + '%', top: (position.y * 100) + '%', transform: 'translate(-50%, -50%)', - opacity: store.watermarkSettings.opacity, + opacity: effectiveOpacity, color: store.watermarkSettings.color, /* Scale based on HEIGHT of the IMAGE */ - fontSize: (imageRect.height * store.watermarkSettings.scale) + 'px', + fontSize: (imageRect.height * effectiveScale) + 'px', height: '0px', display: 'flex', alignItems: 'center', diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue index ec38e0e..e360d5b 100644 --- a/src/components/SettingsPanel.vue +++ b/src/components/SettingsPanel.vue @@ -1,15 +1,54 @@