watermark pos
This commit is contained in:
@@ -153,9 +153,8 @@ 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();
|
||||||
|
|
||||||
// 1. Calculate Font Scale based on Min Dimension (Consistent relative size)
|
// 1. Calculate Font Scale based on Image Height
|
||||||
let min_dim = width.min(height) as f32;
|
let mut scale_px = height as f32 * watermark.scale as f32;
|
||||||
let mut scale_px = min_dim * watermark.scale as f32;
|
|
||||||
|
|
||||||
// 2. Measure Text
|
// 2. Measure Text
|
||||||
let scaled_font = PxScale::from(scale_px);
|
let scaled_font = PxScale::from(scale_px);
|
||||||
@@ -176,24 +175,33 @@ async fn export_batch(images: Vec<ExportImageTask>, watermark: WatermarkSettings
|
|||||||
} 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.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_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. Boundary Clamping (Ensure text stays inside image with padding)
|
// 5. Strict Boundary Clamping
|
||||||
let padding = (height as f64 * 0.005).max(2.0) as i32; // 0.5% padding, min 2px
|
// We ensure the text box (final_t_width, final_t_height) is always inside (0, 0, width, height)
|
||||||
let max_x = (width as i32 - final_t_width as i32 - padding).max(padding);
|
let min_padding = 2; // Absolute minimum pixels from edge
|
||||||
let max_y = (height as i32 - final_t_height as i32 - padding).max(padding);
|
|
||||||
|
|
||||||
x = x.clamp(padding, max_x);
|
if x < min_padding { x = min_padding; }
|
||||||
y = y.clamp(padding, max_y);
|
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)
|
// 6. Draw Stroke (Simple 4-direction offset for black outline)
|
||||||
let stroke_color = image::Rgba([0, 0, 0, text_color[3]]);
|
let stroke_color = image::Rgba([0, 0, 0, text_color[3]]);
|
||||||
@@ -255,7 +263,7 @@ fn calculate_zca_internal(img: &image::DynamicImage) -> Result<ZcaResult, String
|
|||||||
|
|
||||||
let mut min_std_dev = f64::MAX;
|
let mut min_std_dev = f64::MAX;
|
||||||
let mut best_zone = "Center";
|
let mut best_zone = "Center";
|
||||||
let mut best_pos = (0.5, 0.99);
|
let mut best_pos = (0.5, 0.97);
|
||||||
|
|
||||||
for (name, start_x, start_y) in zones.iter() {
|
for (name, start_x, start_y) in zones.iter() {
|
||||||
let mut luma_values = Vec::with_capacity((zone_width * zone_height) as usize);
|
let mut luma_values = Vec::with_capacity((zone_width * zone_height) as usize);
|
||||||
@@ -282,8 +290,8 @@ fn calculate_zca_internal(img: &image::DynamicImage) -> Result<ZcaResult, String
|
|||||||
best_zone = name;
|
best_zone = name;
|
||||||
let center_x_px = *start_x as f64 + (zone_width as f64 / 2.0);
|
let center_x_px = *start_x as f64 + (zone_width as f64 / 2.0);
|
||||||
// Position closer to bottom
|
// Position closer to bottom
|
||||||
// 0.8 + 0.2 * 0.95 = 0.99
|
// 0.8 + 0.2 * 0.85 = 0.97
|
||||||
let center_y_px = *start_y as f64 + (zone_height as f64 * 0.95);
|
let center_y_px = *start_y as f64 + (zone_height as f64 * 0.85);
|
||||||
best_pos = (center_x_px / width as f64, center_y_px / height as f64);
|
best_pos = (center_x_px / width as f64, center_y_px / height as f64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ const onMouseLeave = () => {
|
|||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
opacity: store.watermarkSettings.opacity,
|
opacity: store.watermarkSettings.opacity,
|
||||||
color: store.watermarkSettings.color,
|
color: store.watermarkSettings.color,
|
||||||
/* Scale based on min-dimension of the IMAGE, not window */
|
/* Scale based on HEIGHT of the IMAGE */
|
||||||
fontSize: (Math.min(imageRect.width, imageRect.height) * store.watermarkSettings.scale) + 'px',
|
fontSize: (imageRect.height * store.watermarkSettings.scale) + 'px',
|
||||||
height: '0px',
|
height: '0px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const store = useGalleryStore();
|
|||||||
@input="e => store.updateWatermarkSettings({ scale: parseFloat((e.target as HTMLInputElement).value) })"
|
@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"
|
class="w-full h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer accent-blue-500"
|
||||||
/>
|
/>
|
||||||
<p class="text-[10px] text-gray-500 mt-1">Relative to image min-dimension</p>
|
<p class="text-[10px] text-gray-500 mt-1">Relative to image height</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ export const useGalleryStore = defineStore("gallery", () => {
|
|||||||
text: 'Watermark',
|
text: 'Watermark',
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
scale: 0.025, // Reduced from 0.05
|
scale: 0.03, // 3% of height default
|
||||||
manual_override: false,
|
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(() => {
|
const selectedImage = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user