/** * Image Tools Module * Remove Background, Add Watermark, Resize, Convert, Compress */ // =============== Remove Background =============== let rembgFile = null; function initRemoveBackground() { const dropZone = document.getElementById('rembgDropZone'); const fileInput = document.getElementById('rembg_file'); const btn = document.getElementById('btnRembg'); const btnPreview = document.getElementById('btnRembgPreview'); const notice = document.getElementById('rembgNotice'); if (!dropZone || !fileInput) return; // Check if rembg is available fetch('/api/rembg-status') .then(r => r.json()) .then(data => { if (!data.available && notice) { notice.style.display = 'flex'; } }) .catch(() => {}); initDropZone(dropZone, fileInput, { maxSize: 50 * 1024 * 1024, onFile: (file) => { rembgFile = file; btn.disabled = !file; btnPreview.disabled = !file; document.getElementById('rembgPreviewCard').style.display = 'none'; if (file) showToast('Image Selected', file.name, 'success', 2000); } }); btnPreview.addEventListener('click', () => previewRemoveBackground()); btn.addEventListener('click', () => removeBackground()); } async function previewRemoveBackground() { if (!rembgFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnRembgPreview'); const previewCard = document.getElementById('rembgPreviewCard'); const originalImg = document.getElementById('rembgPreviewOriginal'); const processedImg = document.getElementById('rembgPreviewProcessed'); setButtonLoading(btn, true, 'Processing...'); try { const fd = new FormData(); fd.append('file', rembgFile); const res = await fetch('/api/preview/remove-background', { method: 'POST', body: fd }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Failed' })); throw new Error(err.detail); } const data = await res.json(); originalImg.src = 'data:image/png;base64,' + data.original; processedImg.src = 'data:image/png;base64,' + data.processed; previewCard.style.display = 'block'; previewCard.scrollIntoView({ behavior: 'smooth', block: 'start' }); showToast('Preview Ready', 'Compare original vs processed', 'success', 2000); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); } } async function removeBackground() { if (!rembgFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnRembg'); const progress = document.getElementById('rembgProgress'); setButtonLoading(btn, true, 'Processing...'); showProgress(progress, true, true); try { const fd = new FormData(); fd.append('file', rembgFile); fd.append('output_name', document.getElementById('rembg_output').value || 'no-background.png'); const res = await fetch('/api/remove-background', { method: 'POST', body: fd }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Failed' })); throw new Error(err.detail); } const blob = await res.blob(); const filename = document.getElementById('rembg_output').value || 'no-background.png'; downloadBlob(blob, filename.endsWith('.png') ? filename : filename + '.png'); showSuccessAnimation('Background Removed!', 'Download started'); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Add Watermark =============== let imgWatermarkFile = null; function initImageWatermark() { const dropZone = document.getElementById('imgwatermarkDropZone'); const fileInput = document.getElementById('imgwatermark_file'); const btn = document.getElementById('btnImgWatermark'); const btnPreview = document.getElementById('btnImgWatermarkPreview'); if (!dropZone || !fileInput) return; initDropZone(dropZone, fileInput, { maxSize: 50 * 1024 * 1024, onFile: (file) => { imgWatermarkFile = file; btn.disabled = !file; btnPreview.disabled = !file; document.getElementById('imgwatermarkPreviewCard').style.display = 'none'; if (file) showToast('Image Selected', file.name, 'success', 2000); } }); btnPreview.addEventListener('click', () => previewImageWatermark()); btn.addEventListener('click', () => addImageWatermark()); } async function previewImageWatermark() { if (!imgWatermarkFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const text = document.getElementById('imgwatermark_text').value; if (!text.trim()) { showToast('No Text', 'Enter watermark text', 'error'); return; } const btn = document.getElementById('btnImgWatermarkPreview'); const previewCard = document.getElementById('imgwatermarkPreviewCard'); const originalImg = document.getElementById('imgwatermarkPreviewOriginal'); const processedImg = document.getElementById('imgwatermarkPreviewProcessed'); setButtonLoading(btn, true, 'Loading...'); try { const fd = new FormData(); fd.append('file', imgWatermarkFile); fd.append('text', text); fd.append('position', document.getElementById('imgwatermark_position').value); fd.append('opacity', document.getElementById('imgwatermark_opacity').value); fd.append('font_size', document.getElementById('imgwatermark_fontsize').value); fd.append('color', document.getElementById('imgwatermark_color').value); const res = await fetch('/api/preview/image-watermark', { method: 'POST', body: fd }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Failed' })); throw new Error(err.detail); } const data = await res.json(); originalImg.src = 'data:image/jpeg;base64,' + data.original; processedImg.src = 'data:image/jpeg;base64,' + data.processed; previewCard.style.display = 'block'; previewCard.scrollIntoView({ behavior: 'smooth', block: 'start' }); showToast('Preview Ready', 'Compare original vs watermarked', 'success', 2000); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); } } async function addImageWatermark() { if (!imgWatermarkFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const text = document.getElementById('imgwatermark_text').value; if (!text.trim()) { showToast('No Text', 'Enter watermark text', 'error'); return; } const btn = document.getElementById('btnImgWatermark'); const progress = document.getElementById('imgwatermarkProgress'); setButtonLoading(btn, true, 'Processing...'); showProgress(progress, true, true); try { const fd = new FormData(); fd.append('file', imgWatermarkFile); fd.append('text', text); fd.append('position', document.getElementById('imgwatermark_position').value); fd.append('opacity', document.getElementById('imgwatermark_opacity').value); fd.append('font_size', document.getElementById('imgwatermark_fontsize').value); fd.append('color', document.getElementById('imgwatermark_color').value); fd.append('output_name', document.getElementById('imgwatermark_output').value || 'watermarked'); const res = await fetch('/api/add-image-watermark', { method: 'POST', body: fd }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Failed' })); throw new Error(err.detail); } const blob = await res.blob(); const contentDisposition = res.headers.get('content-disposition'); let filename = 'watermarked.jpg'; if (contentDisposition) { const match = contentDisposition.match(/filename="?([^"]+)"?/); if (match) filename = match[1]; } downloadBlob(blob, filename); showSuccessAnimation('Watermark Added!', 'Download started'); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Resize Image =============== let resizeFile = null; function initResizeImage() { const dropZone = document.getElementById('resizeDropZone'); const fileInput = document.getElementById('resize_file'); const btn = document.getElementById('btnResize'); const btnPreview = document.getElementById('btnResizePreview'); const presetSelect = document.getElementById('resize_preset'); const widthInput = document.getElementById('resize_width'); const heightInput = document.getElementById('resize_height'); if (!dropZone || !fileInput) return; initDropZone(dropZone, fileInput, { maxSize: 50 * 1024 * 1024, onFile: (file) => { resizeFile = file; btn.disabled = !file; btnPreview.disabled = !file; document.getElementById('resizePreviewCard').style.display = 'none'; if (file) showToast('Image Selected', file.name, 'success', 2000); } }); // Preset change handler presetSelect.addEventListener('change', () => { const preset = presetSelect.value; if (preset !== 'custom') { widthInput.disabled = true; heightInput.disabled = true; widthInput.placeholder = 'Auto'; heightInput.placeholder = 'Auto'; } else { widthInput.disabled = false; heightInput.disabled = false; } }); btnPreview.addEventListener('click', () => previewResizeImage()); btn.addEventListener('click', () => resizeImage()); } async function previewResizeImage() { if (!resizeFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnResizePreview'); const previewCard = document.getElementById('resizePreviewCard'); const originalImg = document.getElementById('resizePreviewOriginal'); const processedImg = document.getElementById('resizePreviewProcessed'); const originalSizeSpan = document.getElementById('resizeOriginalSize'); const newSizeSpan = document.getElementById('resizeNewSize'); setButtonLoading(btn, true, 'Loading...'); try { const fd = new FormData(); fd.append('file', resizeFile); fd.append('preset', document.getElementById('resize_preset').value); fd.append('width', document.getElementById('resize_width').value || '0'); fd.append('height', document.getElementById('resize_height').value || '0'); fd.append('maintain_aspect', document.getElementById('resize_aspect').checked); const res = await fetch('/api/preview/resize-image', { method: 'POST', body: fd }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Failed' })); throw new Error(err.detail); } const data = await res.json(); originalImg.src = 'data:image/jpeg;base64,' + data.original; processedImg.src = 'data:image/jpeg;base64,' + data.processed; originalSizeSpan.textContent = `(${data.original_size})`; newSizeSpan.textContent = `(${data.new_size})`; previewCard.style.display = 'block'; previewCard.scrollIntoView({ behavior: 'smooth', block: 'start' }); showToast('Preview Ready', `${data.original_size} → ${data.new_size}`, 'success', 2000); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); } } async function resizeImage() { if (!resizeFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnResize'); const progress = document.getElementById('resizeProgress'); setButtonLoading(btn, true, 'Resizing...'); showProgress(progress, true, true); try { const fd = new FormData(); fd.append('file', resizeFile); fd.append('preset', document.getElementById('resize_preset').value); fd.append('width', document.getElementById('resize_width').value || '0'); fd.append('height', document.getElementById('resize_height').value || '0'); fd.append('maintain_aspect', document.getElementById('resize_aspect').checked); fd.append('output_name', document.getElementById('resize_output').value || 'resized'); const res = await fetch('/api/resize-image', { method: 'POST', body: fd }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Failed' })); throw new Error(err.detail); } const blob = await res.blob(); const contentDisposition = res.headers.get('content-disposition'); let filename = 'resized.jpg'; if (contentDisposition) { const match = contentDisposition.match(/filename="?([^"]+)"?/); if (match) filename = match[1]; } downloadBlob(blob, filename); const newSize = res.headers.get('X-New-Size'); showSuccessAnimation('Image Resized!', newSize ? `New size: ${newSize}` : 'Download started'); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Convert Format =============== let convertFile = null; function initConvertImage() { const dropZone = document.getElementById('convertDropZone'); const fileInput = document.getElementById('convert_file'); const btn = document.getElementById('btnConvert'); const formatSelect = document.getElementById('convert_format'); const qualityInput = document.getElementById('convert_quality'); if (!dropZone || !fileInput) return; initDropZone(dropZone, fileInput, { maxSize: 50 * 1024 * 1024, onFile: (file) => { convertFile = file; btn.disabled = !file; if (file) { showToast('Image Selected', file.name, 'success', 2000); // Trigger initial estimate updateConvertImageEstimate(); } } }); // Format and quality change handlers with debounce let estimateTimeout = null; const triggerEstimate = () => { if (estimateTimeout) clearTimeout(estimateTimeout); estimateTimeout = setTimeout(() => { updateConvertImageEstimate(); }, 300); }; formatSelect.addEventListener('change', triggerEstimate); qualityInput.addEventListener('input', triggerEstimate); btn.addEventListener('click', () => convertImage()); } async function updateConvertImageEstimate() { if (!convertFile) return; const format = document.getElementById('convert_format').value; const quality = document.getElementById('convert_quality').value; // Create estimate display if it doesn't exist let displayDiv = document.getElementById('convertEstimate'); if (!displayDiv) { displayDiv = document.createElement('div'); displayDiv.id = 'convertEstimate'; displayDiv.style.cssText = 'margin-top: 12px; padding: 12px; background: var(--border-light); border-radius: var(--radius); font-size: 13px;'; const btnGroup = document.querySelector('#page-convert .btn-group'); btnGroup.parentNode.insertBefore(displayDiv, btnGroup); } displayDiv.innerHTML = 'Estimating file size...'; try { const fd = new FormData(); fd.append('file', convertFile); fd.append('target_format', format); fd.append('quality', quality); const res = await fetch('/api/estimate/convert-image', { method: 'POST', body: fd }); if (!res.ok) { displayDiv.innerHTML = 'Could not estimate'; return; } const data = await res.json(); const origKB = (data.original_size / 1024).toFixed(1); const estKB = (data.estimated_size / 1024).toFixed(1); const diff = data.estimated_size - data.original_size; const diffPercent = ((diff / data.original_size) * 100).toFixed(1); let sizeChange = ''; if (diff < 0) { sizeChange = `${diffPercent}%`; } else if (diff > 0) { sizeChange = `+${diffPercent}%`; } else { sizeChange = `Same size`; } displayDiv.innerHTML = `