From de0ed2bdc2cb1ff71c3ba1734d921f10fc264e43 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Mon, 19 Jan 2026 00:27:47 -0400 Subject: [PATCH] watermark pos --- src-tauri/src/lib.rs | 36 +++++++++++++++++++------------- src/components/HeroView.vue | 4 ++-- src/components/SettingsPanel.vue | 2 +- src/stores/gallery.ts | 4 ++-- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 968bd1d..0fd851c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -153,9 +153,8 @@ async fn export_batch(images: Vec, watermark: WatermarkSettings let mut base_img = dynamic_img.to_rgba8(); let (width, height) = base_img.dimensions(); - // 1. Calculate Font Scale based on Min Dimension (Consistent relative size) - let min_dim = width.min(height) as f32; - let mut scale_px = min_dim * watermark.scale as f32; + // 1. Calculate Font Scale based on Image Height + let mut scale_px = height as f32 * watermark.scale as f32; // 2. Measure Text let scaled_font = PxScale::from(scale_px); @@ -176,24 +175,33 @@ async fn export_batch(images: Vec, watermark: WatermarkSettings } else { match calculate_zca_internal(&dynamic_img) { Ok(res) => (res.x, res.y), - Err(_) => (0.5, 0.99), + Err(_) => (0.5, 0.97), } }; - // Calculate top-left coordinate for draw_text_mut + // Calculate initial top-left based on center let center_x = width as f64 * pos_x_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 y = (center_y - (final_t_height as f64 / 2.0)) as i32; - // 5. Boundary Clamping (Ensure text stays inside image with padding) - let padding = (height as f64 * 0.005).max(2.0) as i32; // 0.5% padding, min 2px - let max_x = (width as i32 - final_t_width as i32 - padding).max(padding); - let max_y = (height as i32 - final_t_height as i32 - padding).max(padding); + // 5. Strict Boundary Clamping + // 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 - x = x.clamp(padding, max_x); - y = y.clamp(padding, max_y); + if x < min_padding { x = min_padding; } + if y < min_padding { y = 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; + } + if y + final_t_height as i32 > 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 + x = x.max(0); + y = y.max(0); // 6. Draw Stroke (Simple 4-direction offset for black outline) let stroke_color = image::Rgba([0, 0, 0, text_color[3]]); @@ -255,7 +263,7 @@ fn calculate_zca_internal(img: &image::DynamicImage) -> Result Result { transform: 'translate(-50%, -50%)', opacity: store.watermarkSettings.opacity, color: store.watermarkSettings.color, - /* Scale based on min-dimension of the IMAGE, not window */ - fontSize: (Math.min(imageRect.width, imageRect.height) * store.watermarkSettings.scale) + 'px', + /* Scale based on HEIGHT of the IMAGE */ + fontSize: (imageRect.height * store.watermarkSettings.scale) + 'px', height: '0px', display: 'flex', alignItems: 'center', diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue index 00c88ae..eba9d0f 100644 --- a/src/components/SettingsPanel.vue +++ b/src/components/SettingsPanel.vue @@ -56,7 +56,7 @@ const store = useGalleryStore(); @input="e => store.updateWatermarkSettings({ scale: parseFloat((e.target as HTMLInputElement).value) })" class="w-full h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer accent-blue-500" /> -

Relative to image min-dimension

+

Relative to image height

diff --git a/src/stores/gallery.ts b/src/stores/gallery.ts index 89a6e65..16fbee2 100644 --- a/src/stores/gallery.ts +++ b/src/stores/gallery.ts @@ -29,9 +29,9 @@ export const useGalleryStore = defineStore("gallery", () => { text: 'Watermark', color: '#FFFFFF', opacity: 0.8, - scale: 0.025, // Reduced from 0.05 + scale: 0.03, // 3% of height default manual_override: false, - manual_position: { x: 0.5, y: 0.99 } // Pushed to very bottom + manual_position: { x: 0.5, y: 0.97 } // 3% from bottom }); const selectedImage = computed(() => {