/** * 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 = `
Original: ${origKB} KB ${data.target_format}: ${estKB} KB ${sizeChange}
`; } catch (e) { displayDiv.innerHTML = 'Could not estimate'; } } async function convertImage() { if (!convertFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnConvert'); const progress = document.getElementById('convertProgress'); setButtonLoading(btn, true, 'Converting...'); showProgress(progress, true, true); try { const fd = new FormData(); fd.append('file', convertFile); fd.append('target_format', document.getElementById('convert_format').value); fd.append('quality', document.getElementById('convert_quality').value); fd.append('output_name', document.getElementById('convert_output').value || 'converted'); const res = await fetch('/api/convert-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 = 'converted.jpg'; if (contentDisposition) { const match = contentDisposition.match(/filename="?([^"]+)"?/); if (match) filename = match[1]; } downloadBlob(blob, filename); const format = document.getElementById('convert_format').value.toUpperCase(); showSuccessAnimation('Image Converted!', `Converted to ${format}`); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Compress Image =============== let compressImgFile = null; function initCompressImage() { const dropZone = document.getElementById('compressimgDropZone'); const fileInput = document.getElementById('compressimg_file'); const btn = document.getElementById('btnCompressImg'); const btnPreview = document.getElementById('btnCompressImgPreview'); const qualitySlider = document.getElementById('compressimg_quality'); const qualityVal = document.getElementById('compressimg_quality_val'); if (!dropZone || !fileInput) return; initDropZone(dropZone, fileInput, { maxSize: 50 * 1024 * 1024, onFile: (file) => { compressImgFile = file; btn.disabled = !file; btnPreview.disabled = !file; document.getElementById('compressimgResult').style.display = 'none'; document.getElementById('compressimgPreviewCard').style.display = 'none'; if (file) { showToast('Image Selected', file.name, 'success', 2000); // Trigger initial estimate updateCompressImageEstimate(); } } }); // Quality slider with debounced estimate let estimateTimeout = null; qualitySlider.addEventListener('input', () => { qualityVal.textContent = qualitySlider.value; // Debounce the estimate call if (estimateTimeout) clearTimeout(estimateTimeout); estimateTimeout = setTimeout(() => { updateCompressImageEstimate(); }, 300); }); btnPreview.addEventListener('click', () => previewCompressImage()); btn.addEventListener('click', () => compressImage()); } async function updateCompressImageEstimate() { if (!compressImgFile) return; const estimateDiv = document.getElementById('compressimgEstimate'); const quality = document.getElementById('compressimg_quality').value; // Create estimate display if it doesn't exist let displayDiv = estimateDiv; if (!displayDiv) { displayDiv = document.createElement('div'); displayDiv.id = 'compressimgEstimate'; displayDiv.style.cssText = 'margin-top: 12px; padding: 12px; background: var(--border-light); border-radius: var(--radius); font-size: 13px;'; const qualityGroup = document.getElementById('compressimg_quality').closest('.form-group'); qualityGroup.appendChild(displayDiv); } displayDiv.innerHTML = 'Estimating file size...'; try { const fd = new FormData(); fd.append('file', compressImgFile); fd.append('quality', quality); const res = await fetch('/api/estimate/compress-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 reduction = data.reduction_percent; displayDiv.innerHTML = `
Original: ${origKB} KB Estimated: ${estKB} KB ${reduction > 0 ? '-' + reduction + '%' : 'No reduction'}
`; } catch (e) { displayDiv.innerHTML = 'Could not estimate'; } } async function previewCompressImage() { if (!compressImgFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnCompressImgPreview'); const previewCard = document.getElementById('compressimgPreviewCard'); const originalImg = document.getElementById('compressimgPreviewOriginal'); const processedImg = document.getElementById('compressimgPreviewProcessed'); setButtonLoading(btn, true, 'Loading...'); try { const fd = new FormData(); fd.append('file', compressImgFile); fd.append('quality', document.getElementById('compressimg_quality').value); const res = await fetch('/api/preview/compress-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; previewCard.style.display = 'block'; previewCard.scrollIntoView({ behavior: 'smooth', block: 'start' }); const origKB = (data.original_size / 1024).toFixed(1); const compKB = (data.compressed_size / 1024).toFixed(1); showToast('Preview Ready', `${origKB} KB → ${compKB} KB`, 'success', 2000); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); } } async function compressImage() { if (!compressImgFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnCompressImg'); const progress = document.getElementById('compressimgProgress'); const resultDiv = document.getElementById('compressimgResult'); const statsDiv = document.getElementById('compressimgStats'); setButtonLoading(btn, true, 'Compressing...'); showProgress(progress, true, true); resultDiv.style.display = 'none'; try { const fd = new FormData(); fd.append('file', compressImgFile); fd.append('quality', document.getElementById('compressimg_quality').value); fd.append('output_name', document.getElementById('compressimg_output').value || 'compressed'); const res = await fetch('/api/compress-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 = 'compressed.jpg'; if (contentDisposition) { const match = contentDisposition.match(/filename="?([^"]+)"?/); if (match) filename = match[1]; } downloadBlob(blob, filename); // Show compression stats const originalSize = res.headers.get('X-Original-Size'); const compressedSize = res.headers.get('X-Compressed-Size'); const reduction = res.headers.get('X-Reduction-Percent'); if (originalSize && compressedSize) { const origKB = (parseInt(originalSize) / 1024).toFixed(1); const compKB = (parseInt(compressedSize) / 1024).toFixed(1); statsDiv.textContent = `${origKB} KB → ${compKB} KB (${reduction}% smaller)`; resultDiv.style.display = 'block'; } showSuccessAnimation('Image Compressed!', `Reduced by ${reduction}%`); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Utility =============== function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } // =============== Enhance Image =============== let enhanceImgFile = null; function initEnhanceImage() { const dropZone = document.getElementById('enhanceImgDropZone'); const fileInput = document.getElementById('enhanceimg_file'); const btn = document.getElementById('btnEnhanceImg'); const btnPreview = document.getElementById('btnEnhanceImgPreview'); if (!dropZone || !fileInput) return; initDropZone(dropZone, fileInput, { maxSize: 50 * 1024 * 1024, onFile: (file) => { enhanceImgFile = file; btn.disabled = !file; btnPreview.disabled = !file; document.getElementById('enhanceimgPreviewCard').style.display = 'none'; if (file) showToast('Image Selected', file.name, 'success', 2000); } }); btnPreview.addEventListener('click', () => previewEnhanceImage()); btn.addEventListener('click', () => enhanceImage()); } async function previewEnhanceImage() { if (!enhanceImgFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnEnhanceImgPreview'); const previewCard = document.getElementById('enhanceimgPreviewCard'); const originalImg = document.getElementById('enhanceimgPreviewOriginal'); const processedImg = document.getElementById('enhanceimgPreviewProcessed'); setButtonLoading(btn, true, 'Loading...'); try { const fd = new FormData(); fd.append('file', enhanceImgFile); fd.append('level', document.getElementById('enhanceimg_level').value); fd.append('upscale', document.getElementById('enhanceimg_upscale').value); const res = await fetch('/api/preview/enhance-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; previewCard.style.display = 'block'; previewCard.scrollIntoView({ behavior: 'smooth', block: 'start' }); showToast('Preview Ready', 'Compare original vs enhanced', 'success', 2000); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); } } async function enhanceImage() { if (!enhanceImgFile) { showToast('No Image', 'Upload an image first', 'error'); return; } const btn = document.getElementById('btnEnhanceImg'); const progress = document.getElementById('enhanceimgProgress'); setButtonLoading(btn, true, 'Enhancing...'); showProgress(progress, true, true); try { const fd = new FormData(); fd.append('file', enhanceImgFile); fd.append('level', document.getElementById('enhanceimg_level').value); fd.append('upscale', document.getElementById('enhanceimg_upscale').value); fd.append('output_name', document.getElementById('enhanceimg_output').value || 'enhanced'); const res = await fetch('/api/enhance-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 = 'enhanced.jpg'; if (contentDisposition) { const match = contentDisposition.match(/filename="?([^"]+)"?/); if (match) filename = match[1]; } downloadBlob(blob, filename); showSuccessAnimation('Image Enhanced!', 'Download started'); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Enhance PDF =============== let enhancePdfFile = null; function initEnhancePdf() { const dropZone = document.getElementById('enhancePdfDropZone'); const fileInput = document.getElementById('enhancepdf_file'); const btn = document.getElementById('btnEnhancePdf'); if (!dropZone || !fileInput) return; initDropZone(dropZone, fileInput, { maxSize: 100 * 1024 * 1024, onFile: (file) => { enhancePdfFile = file; btn.disabled = !file; document.getElementById('enhancepdfResult').style.display = 'none'; if (file) showToast('PDF Selected', file.name, 'success', 2000); } }); btn.addEventListener('click', () => enhancePdf()); } async function enhancePdf() { if (!enhancePdfFile) { showToast('No PDF', 'Upload a PDF first', 'error'); return; } const btn = document.getElementById('btnEnhancePdf'); const progress = document.getElementById('enhancepdfProgress'); const resultDiv = document.getElementById('enhancepdfResult'); const statsDiv = document.getElementById('enhancepdfStats'); setButtonLoading(btn, true, 'Enhancing...'); showProgress(progress, true, true); resultDiv.style.display = 'none'; try { const fd = new FormData(); fd.append('file', enhancePdfFile); fd.append('level', document.getElementById('enhancepdf_level').value); fd.append('output_name', document.getElementById('enhancepdf_output').value || 'enhanced.pdf'); const res = await fetch('/api/enhance-pdf', { 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('enhancepdf_output').value || 'enhanced.pdf'; downloadBlob(blob, filename.endsWith('.pdf') ? filename : filename + '.pdf'); // Show stats const originalSize = res.headers.get('X-Original-Size'); const enhancedSize = res.headers.get('X-Enhanced-Size'); const imagesEnhanced = res.headers.get('X-Images-Enhanced'); if (originalSize && enhancedSize) { const origMB = (parseInt(originalSize) / 1024 / 1024).toFixed(2); const enhMB = (parseInt(enhancedSize) / 1024 / 1024).toFixed(2); const change = (((parseInt(enhancedSize) - parseInt(originalSize)) / parseInt(originalSize)) * 100).toFixed(1); statsDiv.textContent = `${origMB} MB → ${enhMB} MB (${change > 0 ? '+' : ''}${change}%), ${imagesEnhanced} images enhanced`; resultDiv.style.display = 'block'; } showSuccessAnimation('PDF Enhanced!', `${imagesEnhanced} images improved`); } catch (e) { showToast('Error', e.message, 'error'); } finally { setButtonLoading(btn, false); showProgress(progress, false); } } // =============== Initialize All =============== function initImageTools() { initRemoveBackground(); initImageWatermark(); initResizeImage(); initConvertImage(); initCompressImage(); initEnhanceImage(); initEnhancePdf(); }