watermark pos bug
This commit is contained in:
@@ -153,9 +153,9 @@ 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
|
// 1. Calculate Font Scale based on Min Dimension (Consistent relative size)
|
||||||
// watermark.scale is percentage of image height. e.g. 0.05
|
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,7 +176,7 @@ 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.96),
|
Err(_) => (0.5, 0.99),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ async fn export_batch(images: Vec<ExportImageTask>, watermark: WatermarkSettings
|
|||||||
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. Boundary Clamping (Ensure text stays inside image with padding)
|
||||||
let padding = (height as f64 * 0.01).max(5.0) as i32; // 1% 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_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);
|
let max_y = (height as i32 - final_t_height as i32 - padding).max(padding);
|
||||||
|
|
||||||
@@ -255,7 +255,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.95);
|
let mut best_pos = (0.5, 0.99);
|
||||||
|
|
||||||
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);
|
||||||
@@ -281,7 +281,9 @@ fn calculate_zca_internal(img: &image::DynamicImage) -> Result<ZcaResult, String
|
|||||||
min_std_dev = std_dev;
|
min_std_dev = std_dev;
|
||||||
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);
|
||||||
let center_y_px = *start_y as f64 + (zone_height as f64 * 0.75);
|
// Position closer to bottom
|
||||||
|
// 0.8 + 0.2 * 0.95 = 0.99
|
||||||
|
let center_y_px = *start_y as f64 + (zone_height as f64 * 0.95);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,20 @@ const isDragging = ref(false);
|
|||||||
const dragStart = ref({ x: 0, y: 0 });
|
const dragStart = ref({ x: 0, y: 0 });
|
||||||
const imgRef = ref<HTMLImageElement | null>(null);
|
const imgRef = ref<HTMLImageElement | null>(null);
|
||||||
const containerRef = ref<HTMLElement | null>(null);
|
const containerRef = ref<HTMLElement | null>(null);
|
||||||
const renderHeight = ref(0);
|
const renderRefSize = ref(0); // Min of width/height
|
||||||
|
|
||||||
let resizeObserver: ResizeObserver | null = null;
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
const updateHeight = () => {
|
const updateSize = () => {
|
||||||
if (imgRef.value) {
|
if (imgRef.value) {
|
||||||
renderHeight.value = imgRef.value.clientHeight;
|
// Use the smaller dimension to calculate scale, matching backend logic
|
||||||
|
renderRefSize.value = Math.min(imgRef.value.clientWidth, imgRef.value.clientHeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
resizeObserver = new ResizeObserver(() => {
|
resizeObserver = new ResizeObserver(() => {
|
||||||
updateHeight();
|
updateSize();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,8 +34,6 @@ watch(imgRef, (el) => {
|
|||||||
if (el && resizeObserver) {
|
if (el && resizeObserver) {
|
||||||
resizeObserver.disconnect(); // clear old
|
resizeObserver.disconnect(); // clear old
|
||||||
resizeObserver.observe(el);
|
resizeObserver.observe(el);
|
||||||
// Force update immediately
|
|
||||||
// Wait for load? ResizeObserver usually handles layout shifts
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,13 +42,11 @@ const position = computed(() => {
|
|||||||
if (store.watermarkSettings.manual_override) {
|
if (store.watermarkSettings.manual_override) {
|
||||||
return store.watermarkSettings.manual_position;
|
return store.watermarkSettings.manual_position;
|
||||||
}
|
}
|
||||||
return store.selectedImage?.zcaSuggestion ? { x: store.selectedImage.zcaSuggestion.x, y: store.selectedImage.zcaSuggestion.y } : { x: 0.5, y: 0.5 };
|
// Default to bottom center if no ZCA or manual
|
||||||
|
return store.selectedImage?.zcaSuggestion ? { x: store.selectedImage.zcaSuggestion.x, y: store.selectedImage.zcaSuggestion.y } : { x: 0.5, y: 0.98 };
|
||||||
});
|
});
|
||||||
|
|
||||||
const onMouseDown = (e: MouseEvent) => {
|
const onMouseDown = (e: MouseEvent) => {
|
||||||
// Only allow dragging if target is the watermark itself (or its child)
|
|
||||||
// Actually, user should be able to click watermark to start drag.
|
|
||||||
// e.preventDefault to stop image drag
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isDragging.value = true;
|
isDragging.value = true;
|
||||||
dragStart.value = { x: e.clientX, y: e.clientY };
|
dragStart.value = { x: e.clientX, y: e.clientY };
|
||||||
@@ -69,12 +66,9 @@ const onMouseMove = (e: MouseEvent) => {
|
|||||||
let newX = position.value.x + deltaX;
|
let newX = position.value.x + deltaX;
|
||||||
let newY = position.value.y + deltaY;
|
let newY = position.value.y + deltaY;
|
||||||
|
|
||||||
// Clamp logic (Simple 0-1 clamp for center point first)
|
// Clamp logic (Allow getting very close to edge, padding handled by visual/backend)
|
||||||
// Ideally we should clamp so the box stays inside, but we don't know the exact text width in px easily here without measurement.
|
// Using small padding to prevent sticking perfectly to edge if not desired
|
||||||
// For MVP, clamping center to [0, 1] is "safe enough" to prevent losing it,
|
const padding = 0.005;
|
||||||
// but user asked for "not exceeding boundary".
|
|
||||||
// Visual clamping: slightly inside 0-1.
|
|
||||||
const padding = 0.01;
|
|
||||||
newX = Math.max(padding, Math.min(1 - padding, newX));
|
newX = Math.max(padding, Math.min(1 - padding, newX));
|
||||||
newY = Math.max(padding, Math.min(1 - padding, newY));
|
newY = Math.max(padding, Math.min(1 - padding, newY));
|
||||||
|
|
||||||
@@ -98,27 +92,28 @@ const onMouseLeave = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="w-full h-full flex items-center justify-center bg-black relative p-4 overflow-hidden"
|
class="absolute inset-0 flex items-center justify-center bg-black p-4 overflow-hidden"
|
||||||
@mousemove="onMouseMove"
|
@mousemove="onMouseMove"
|
||||||
@mouseup="onMouseUp"
|
@mouseup="onMouseUp"
|
||||||
@mouseleave="onMouseLeave"
|
@mouseleave="onMouseLeave"
|
||||||
>
|
>
|
||||||
|
<!-- Wrapper to constrain image -->
|
||||||
<div
|
<div
|
||||||
v-if="store.selectedImage"
|
v-if="store.selectedImage"
|
||||||
ref="containerRef"
|
ref="containerRef"
|
||||||
class="relative inline-flex justify-center items-center image-container"
|
class="relative flex justify-center items-center"
|
||||||
style="max-width: 100%; max-height: 100%;"
|
style="width: 100%; height: 100%;"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
ref="imgRef"
|
ref="imgRef"
|
||||||
:src="convertFileSrc(store.selectedImage.path)"
|
:src="convertFileSrc(store.selectedImage.path)"
|
||||||
class="max-w-full max-h-full w-auto h-auto block shadow-lg select-none pointer-events-none"
|
class="block shadow-2xl select-none pointer-events-none"
|
||||||
style="max-height: calc(100vh - 10rem);"
|
style="max-width: 100%; max-height: 100%; object-fit: contain;"
|
||||||
alt="Hero Image"
|
alt="Hero Image"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
decoding="sync"
|
decoding="sync"
|
||||||
fetchpriority="high"
|
fetchpriority="high"
|
||||||
@load="updateHeight"
|
@load="updateSize"
|
||||||
@error="(e) => console.error('Hero Image Load Error:', e)"
|
@error="(e) => console.error('Hero Image Load Error:', e)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -132,7 +127,7 @@ 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,
|
||||||
fontSize: (renderHeight * store.watermarkSettings.scale) + 'px',
|
fontSize: (renderRefSize * 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 height</p>
|
<p class="text-[10px] text-gray-500 mt-1">Relative to image min-dimension</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const useGalleryStore = defineStore("gallery", () => {
|
|||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
scale: 0.025, // Reduced from 0.05
|
scale: 0.025, // Reduced from 0.05
|
||||||
manual_override: false,
|
manual_override: false,
|
||||||
manual_position: { x: 0.5, y: 0.96 } // Lowered from 0.9
|
manual_position: { x: 0.5, y: 0.99 } // Pushed to very bottom
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedImage = computed(() => {
|
const selectedImage = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user