|
|
<!DOCTYPE html> |
|
|
<html lang="zh-CN"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>图片优化大师 | 压缩图片 & 去除水印</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.dropzone { |
|
|
border: 2px dashed #cbd5e0; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.dropzone.active { |
|
|
border-color: #4f46e5; |
|
|
background-color: #f0f4ff; |
|
|
} |
|
|
.progress-bar { |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
.watermark-preview { |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
.watermark-box { |
|
|
position: absolute; |
|
|
border: 2px dashed red; |
|
|
background-color: rgba(255, 0, 0, 0.2); |
|
|
} |
|
|
.settings-panel { |
|
|
transition: all 0.3s ease; |
|
|
overflow: hidden; |
|
|
} |
|
|
.settings-panel.closed { |
|
|
max-height: 0; |
|
|
} |
|
|
.settings-panel.open { |
|
|
max-height: 500px; |
|
|
} |
|
|
.tooltip { |
|
|
position: relative; |
|
|
} |
|
|
.tooltip-text { |
|
|
visibility: hidden; |
|
|
width: 200px; |
|
|
background-color: #333; |
|
|
color: #fff; |
|
|
text-align: center; |
|
|
border-radius: 6px; |
|
|
padding: 5px; |
|
|
position: absolute; |
|
|
z-index: 1; |
|
|
bottom: 125%; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
opacity: 0; |
|
|
transition: opacity 0.3s; |
|
|
} |
|
|
.tooltip:hover .tooltip-text { |
|
|
visibility: visible; |
|
|
opacity: 1; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
|
<div> |
|
|
<h1 class="text-3xl font-bold text-indigo-600">图片优化大师</h1> |
|
|
<p class="text-gray-600">轻松压缩图片并去除水印</p> |
|
|
</div> |
|
|
<button id="premium-btn" class="bg-gradient-to-r from-purple-500 to-indigo-600 text-white px-4 py-2 rounded-lg shadow-md hover:shadow-lg transition-all flex items-center"> |
|
|
<i class="fas fa-crown mr-2"></i> |
|
|
升级专业版 |
|
|
</button> |
|
|
</header> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6"> |
|
|
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6"> |
|
|
<div class="flex flex-col items-center justify-center"> |
|
|
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-4"></i> |
|
|
<h3 class="text-xl font-semibold mb-2">拖放图片到此处</h3> |
|
|
<p class="text-gray-500 mb-4">或点击选择文件</p> |
|
|
<input type="file" id="fileInput" class="hidden" accept="image/*" multiple> |
|
|
<button id="browseBtn" class="bg-indigo-500 text-white px-6 py-2 rounded-lg hover:bg-indigo-600 transition"> |
|
|
选择图片 |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="fileInfo" class="hidden mb-6"> |
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
<span class="font-medium">已选文件:</span> |
|
|
<span id="fileCount" class="text-gray-600">0 个文件</span> |
|
|
</div> |
|
|
<div id="fileList" class="border rounded-lg p-3 max-h-40 overflow-y-auto"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<button id="settingsToggle" class="flex justify-between items-center w-full bg-gray-100 hover:bg-gray-200 px-4 py-3 rounded-lg transition"> |
|
|
<span class="font-medium">高级设置</span> |
|
|
<i class="fas fa-chevron-up transition-transform"></i> |
|
|
</button> |
|
|
<div id="settingsPanel" class="settings-panel open bg-gray-50 rounded-lg mt-2 px-4 py-3"> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2">压缩级别</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="range" id="compressionLevel" min="0" max="100" value="70" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
|
|
<span id="compressionValue" class="ml-3 text-gray-700 w-12 text-center">70%</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2">输出格式</label> |
|
|
<select id="outputFormat" class="w-full border rounded-lg px-3 py-2"> |
|
|
<option value="original">保持原格式</option> |
|
|
<option value="jpeg">JPEG</option> |
|
|
<option value="png">PNG</option> |
|
|
<option value="webp">WebP</option> |
|
|
</select> |
|
|
</div> |
|
|
<div class="flex items-center mb-4"> |
|
|
<input type="checkbox" id="removeMetadata" class="mr-2" checked> |
|
|
<label for="removeMetadata" class="text-gray-700">移除元数据</label> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<input type="checkbox" id="removeWatermark" class="mr-2" checked> |
|
|
<label for="removeWatermark" class="text-gray-700">去除水印</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button id="processBtn" class="w-full bg-green-500 hover:bg-green-600 text-white py-3 rounded-lg font-medium transition flex items-center justify-center hidden"> |
|
|
<i class="fas fa-magic mr-2"></i> |
|
|
处理图片 |
|
|
</button> |
|
|
|
|
|
<div id="progressContainer" class="hidden mt-6"> |
|
|
<div class="flex justify-between mb-1"> |
|
|
<span>处理中...</span> |
|
|
<span id="progressPercent">0%</span> |
|
|
</div> |
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5"> |
|
|
<div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6"> |
|
|
<h2 class="text-xl font-semibold mb-4">水印去除工具</h2> |
|
|
<div id="watermarkPreviewContainer" class="hidden"> |
|
|
<div class="watermark-preview mb-4 border rounded-lg overflow-hidden relative" style="height: 300px;"> |
|
|
<img id="watermarkPreview" src="#" alt="预览" class="w-full h-full object-contain"> |
|
|
<div id="watermarkBox" class="watermark-box hidden"></div> |
|
|
</div> |
|
|
<div class="flex justify-between mb-4"> |
|
|
<div> |
|
|
<label class="block text-gray-700 mb-2">选择水印区域</label> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="selectBtn" class="bg-indigo-500 text-white px-3 py-1 rounded hover:bg-indigo-600"> |
|
|
<i class="fas fa-vector-square mr-1"></i> 选择 |
|
|
</button> |
|
|
<button id="clearSelectionBtn" class="bg-gray-200 text-gray-700 px-3 py-1 rounded hover:bg-gray-300"> |
|
|
<i class="fas fa-eraser mr-1"></i> 清除 |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="tooltip"> |
|
|
<button id="autoDetectBtn" class="bg-purple-500 text-white px-3 py-1 rounded hover:bg-purple-600"> |
|
|
<i class="fas fa-robot mr-1"></i> 自动检测 |
|
|
</button> |
|
|
<span class="tooltip-text">我们的AI将尝试自动检测常见水印模式</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2">去除强度</label> |
|
|
<div class="flex items-center"> |
|
|
<input type="range" id="removalStrength" min="1" max="10" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> |
|
|
<span id="removalStrengthValue" class="ml-3 text-gray-700 w-12 text-center">5</span> |
|
|
</div> |
|
|
</div> |
|
|
<button id="removeWatermarkBtn" class="w-full bg-red-500 hover:bg-red-600 text-white py-3 rounded-lg font-medium transition flex items-center justify-center"> |
|
|
<i class="fas fa-trash-alt mr-2"></i> |
|
|
去除水印 |
|
|
</button> |
|
|
</div> |
|
|
<div id="noImageSelected" class="text-center py-12 text-gray-500"> |
|
|
<i class="fas fa-image fa-3x mb-4"></i> |
|
|
<p>未选择用于去除水印的图片。</p> |
|
|
<p>请先上传图片以使用此工具。</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="resultsSection" class="hidden mt-8 bg-white rounded-xl shadow-md p-6"> |
|
|
<h2 class="text-xl font-semibold mb-4">处理结果</h2> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<div> |
|
|
<span class="text-gray-600">原始大小:</span> |
|
|
<span id="originalSize" class="font-medium ml-2">-</span> |
|
|
</div> |
|
|
<div> |
|
|
<span class="text-gray-600">压缩后大小:</span> |
|
|
<span id="compressedSize" class="font-medium ml-2">-</span> |
|
|
</div> |
|
|
<div> |
|
|
<span class="text-gray-600">节省空间:</span> |
|
|
<span id="savings" class="font-medium ml-2">-</span> |
|
|
</div> |
|
|
<button id="downloadAllBtn" class="bg-indigo-500 text-white px-4 py-2 rounded-lg hover:bg-indigo-600 transition flex items-center"> |
|
|
<i class="fas fa-download mr-2"></i> |
|
|
全部下载 |
|
|
</button> |
|
|
</div> |
|
|
<div id="resultsGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const dropzone = document.getElementById('dropzone'); |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const browseBtn = document.getElementById('browseBtn'); |
|
|
const fileInfo = document.getElementById('fileInfo'); |
|
|
const fileList = document.getElementById('fileList'); |
|
|
const fileCount = document.getElementById('fileCount'); |
|
|
const processBtn = document.getElementById('processBtn'); |
|
|
const progressContainer = document.getElementById('progressContainer'); |
|
|
const progressBar = document.getElementById('progressBar'); |
|
|
const progressPercent = document.getElementById('progressPercent'); |
|
|
const compressionLevel = document.getElementById('compressionLevel'); |
|
|
const compressionValue = document.getElementById('compressionValue'); |
|
|
const outputFormat = document.getElementById('outputFormat'); |
|
|
const removeMetadata = document.getElementById('removeMetadata'); |
|
|
const removeWatermark = document.getElementById('removeWatermark'); |
|
|
const settingsToggle = document.getElementById('settingsToggle'); |
|
|
const settingsPanel = document.getElementById('settingsPanel'); |
|
|
const watermarkPreviewContainer = document.getElementById('watermarkPreviewContainer'); |
|
|
const watermarkPreview = document.getElementById('watermarkPreview'); |
|
|
const noImageSelected = document.getElementById('noImageSelected'); |
|
|
const selectBtn = document.getElementById('selectBtn'); |
|
|
const clearSelectionBtn = document.getElementById('clearSelectionBtn'); |
|
|
const autoDetectBtn = document.getElementById('autoDetectBtn'); |
|
|
const removalStrength = document.getElementById('removalStrength'); |
|
|
const removalStrengthValue = document.getElementById('removalStrengthValue'); |
|
|
const removeWatermarkBtn = document.getElementById('removeWatermarkBtn'); |
|
|
const watermarkBox = document.getElementById('watermarkBox'); |
|
|
const resultsSection = document.getElementById('resultsSection'); |
|
|
const resultsGrid = document.getElementById('resultsGrid'); |
|
|
const originalSize = document.getElementById('originalSize'); |
|
|
const compressedSize = document.getElementById('compressedSize'); |
|
|
const savings = document.getElementById('savings'); |
|
|
const downloadAllBtn = document.getElementById('downloadAllBtn'); |
|
|
const premiumBtn = document.getElementById('premium-btn'); |
|
|
|
|
|
|
|
|
let selectedFiles = []; |
|
|
let isSelectingWatermark = false; |
|
|
let selectionStart = { x: 0, y: 0 }; |
|
|
let currentImageForWatermark = null; |
|
|
let processedFiles = []; |
|
|
|
|
|
|
|
|
browseBtn.addEventListener('click', () => fileInput.click()); |
|
|
fileInput.addEventListener('change', handleFileSelect); |
|
|
dropzone.addEventListener('dragover', handleDragOver); |
|
|
dropzone.addEventListener('dragleave', handleDragLeave); |
|
|
dropzone.addEventListener('drop', handleDrop); |
|
|
processBtn.addEventListener('click', processImages); |
|
|
compressionLevel.addEventListener('input', updateCompressionValue); |
|
|
settingsToggle.addEventListener('click', toggleSettingsPanel); |
|
|
removalStrength.addEventListener('input', updateRemovalStrengthValue); |
|
|
selectBtn.addEventListener('click', startWatermarkSelection); |
|
|
clearSelectionBtn.addEventListener('click', clearWatermarkSelection); |
|
|
autoDetectBtn.addEventListener('click', autoDetectWatermark); |
|
|
removeWatermarkBtn.addEventListener('click', processWatermarkRemoval); |
|
|
downloadAllBtn.addEventListener('click', downloadAllProcessedFiles); |
|
|
premiumBtn.addEventListener('click', showPremiumModal); |
|
|
|
|
|
|
|
|
watermarkPreview.addEventListener('mousedown', startSelection); |
|
|
document.addEventListener('mousemove', updateSelection); |
|
|
document.addEventListener('mouseup', endSelection); |
|
|
|
|
|
|
|
|
function handleFileSelect(e) { |
|
|
const files = e.target.files || e.dataTransfer.files; |
|
|
addFilesToSelection(files); |
|
|
} |
|
|
|
|
|
function handleDragOver(e) { |
|
|
e.preventDefault(); |
|
|
dropzone.classList.add('active'); |
|
|
} |
|
|
|
|
|
function handleDragLeave() { |
|
|
dropzone.classList.remove('active'); |
|
|
} |
|
|
|
|
|
function handleDrop(e) { |
|
|
e.preventDefault(); |
|
|
dropzone.classList.remove('active'); |
|
|
const files = e.dataTransfer.files; |
|
|
addFilesToSelection(files); |
|
|
} |
|
|
|
|
|
function addFilesToSelection(files) { |
|
|
for (let i = 0; i < files.length; i++) { |
|
|
if (files[i].type.match('image.*')) { |
|
|
selectedFiles.push(files[i]); |
|
|
} |
|
|
} |
|
|
|
|
|
updateFileList(); |
|
|
|
|
|
if (selectedFiles.length > 0) { |
|
|
fileInfo.classList.remove('hidden'); |
|
|
processBtn.classList.remove('hidden'); |
|
|
fileCount.textContent = `${selectedFiles.length} 个文件`; |
|
|
|
|
|
|
|
|
if (!currentImageForWatermark) { |
|
|
setImageForWatermarkRemoval(selectedFiles[0]); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function updateFileList() { |
|
|
fileList.innerHTML = ''; |
|
|
selectedFiles.forEach((file, index) => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'flex justify-between items-center py-2 border-b last:border-b-0'; |
|
|
|
|
|
const fileName = document.createElement('span'); |
|
|
fileName.className = 'truncate'; |
|
|
fileName.textContent = file.name; |
|
|
|
|
|
const fileSize = document.createElement('span'); |
|
|
fileSize.className = 'text-gray-500 text-sm'; |
|
|
fileSize.textContent = formatFileSize(file.size); |
|
|
|
|
|
const removeBtn = document.createElement('button'); |
|
|
removeBtn.className = 'text-red-500 hover:text-red-700 ml-2'; |
|
|
removeBtn.innerHTML = '<i class="fas fa-times"></i>'; |
|
|
removeBtn.addEventListener('click', () => removeFile(index)); |
|
|
|
|
|
div.appendChild(fileName); |
|
|
div.appendChild(fileSize); |
|
|
div.appendChild(removeBtn); |
|
|
fileList.appendChild(div); |
|
|
}); |
|
|
} |
|
|
|
|
|
function removeFile(index) { |
|
|
selectedFiles.splice(index, 1); |
|
|
updateFileList(); |
|
|
fileCount.textContent = `${selectedFiles.length} 个文件`; |
|
|
|
|
|
if (selectedFiles.length === 0) { |
|
|
fileInfo.classList.add('hidden'); |
|
|
processBtn.classList.add('hidden'); |
|
|
watermarkPreviewContainer.classList.add('hidden'); |
|
|
noImageSelected.classList.remove('hidden'); |
|
|
currentImageForWatermark = null; |
|
|
} else if (index === 0) { |
|
|
setImageForWatermarkRemoval(selectedFiles[0]); |
|
|
} |
|
|
} |
|
|
|
|
|
function formatFileSize(bytes) { |
|
|
if (bytes === 0) return '0 字节'; |
|
|
const k = 1024; |
|
|
const sizes = ['字节', 'KB', 'MB', 'GB']; |
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
|
} |
|
|
|
|
|
function updateCompressionValue() { |
|
|
compressionValue.textContent = `${compressionLevel.value}%`; |
|
|
} |
|
|
|
|
|
function updateRemovalStrengthValue() { |
|
|
removalStrengthValue.textContent = removalStrength.value; |
|
|
} |
|
|
|
|
|
function toggleSettingsPanel() { |
|
|
settingsPanel.classList.toggle('open'); |
|
|
const icon = settingsToggle.querySelector('i'); |
|
|
icon.classList.toggle('fa-chevron-down'); |
|
|
icon.classList.toggle('fa-chevron-up'); |
|
|
} |
|
|
|
|
|
function processImages() { |
|
|
if (selectedFiles.length === 0) return; |
|
|
|
|
|
progressContainer.classList.remove('hidden'); |
|
|
processBtn.classList.add('hidden'); |
|
|
|
|
|
let processedCount = 0; |
|
|
processedFiles = []; |
|
|
|
|
|
|
|
|
selectedFiles.forEach((file, index) => { |
|
|
setTimeout(() => { |
|
|
|
|
|
simulateProgress(index, () => { |
|
|
processedCount++; |
|
|
|
|
|
|
|
|
const originalSize = file.size; |
|
|
const compressionRatio = parseInt(compressionLevel.value) / 100; |
|
|
const compressedSize = Math.max(1000, originalSize * compressionRatio); |
|
|
|
|
|
|
|
|
const result = { |
|
|
originalFile: file, |
|
|
compressedFile: new File([file], `压缩_${file.name}`, { type: file.type }), |
|
|
originalSize, |
|
|
compressedSize, |
|
|
downloadUrl: URL.createObjectURL(file) |
|
|
}; |
|
|
|
|
|
processedFiles.push(result); |
|
|
|
|
|
|
|
|
const progress = Math.round((processedCount / selectedFiles.length) * 100); |
|
|
progressBar.style.width = `${progress}%`; |
|
|
progressPercent.textContent = `${progress}%`; |
|
|
|
|
|
|
|
|
if (processedCount === selectedFiles.length) { |
|
|
setTimeout(showResults, 500); |
|
|
} |
|
|
}); |
|
|
}, index * 500); |
|
|
}); |
|
|
} |
|
|
|
|
|
function simulateProgress(index, callback) { |
|
|
let progress = 0; |
|
|
const interval = setInterval(() => { |
|
|
progress += 5; |
|
|
const overallProgress = Math.min(100, ((index * 100) + progress) / selectedFiles.length); |
|
|
progressBar.style.width = `${overallProgress}%`; |
|
|
progressPercent.textContent = `${Math.round(overallProgress)}%`; |
|
|
|
|
|
if (progress >= 100) { |
|
|
clearInterval(interval); |
|
|
callback(); |
|
|
} |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
function showResults() { |
|
|
progressContainer.classList.add('hidden'); |
|
|
resultsSection.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const totalOriginal = processedFiles.reduce((sum, file) => sum + file.originalSize, 0); |
|
|
const totalCompressed = processedFiles.reduce((sum, file) => sum + file.compressedSize, 0); |
|
|
const savingsPercentage = ((totalOriginal - totalCompressed) / totalOriginal * 100).toFixed(1); |
|
|
|
|
|
originalSize.textContent = formatFileSize(totalOriginal); |
|
|
compressedSize.textContent = formatFileSize(totalCompressed); |
|
|
savings.textContent = `${savingsPercentage}% (${formatFileSize(totalOriginal - totalCompressed)})`; |
|
|
|
|
|
|
|
|
resultsGrid.innerHTML = ''; |
|
|
processedFiles.forEach((file, index) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
const resultCard = document.createElement('div'); |
|
|
resultCard.className = 'bg-gray-50 rounded-lg overflow-hidden border'; |
|
|
|
|
|
const imgContainer = document.createElement('div'); |
|
|
imgContainer.className = 'relative pt-[100%]'; |
|
|
|
|
|
const img = document.createElement('img'); |
|
|
img.src = e.target.result; |
|
|
img.className = 'absolute top-0 left-0 w-full h-full object-cover'; |
|
|
|
|
|
const infoContainer = document.createElement('div'); |
|
|
infoContainer.className = 'p-3'; |
|
|
|
|
|
const fileName = document.createElement('div'); |
|
|
fileName.className = 'font-medium truncate mb-1'; |
|
|
fileName.textContent = file.originalFile.name; |
|
|
|
|
|
const sizeInfo = document.createElement('div'); |
|
|
sizeInfo.className = 'flex justify-between text-sm text-gray-600 mb-2'; |
|
|
|
|
|
const originalSizeSpan = document.createElement('span'); |
|
|
originalSizeSpan.textContent = formatFileSize(file.originalSize); |
|
|
|
|
|
const compressedSizeSpan = document.createElement('span'); |
|
|
compressedSizeSpan.className = 'text-green-600 font-medium'; |
|
|
compressedSizeSpan.textContent = formatFileSize(file.compressedSize); |
|
|
|
|
|
sizeInfo.appendChild(originalSizeSpan); |
|
|
sizeInfo.appendChild(compressedSizeSpan); |
|
|
|
|
|
const downloadBtn = document.createElement('button'); |
|
|
downloadBtn.className = 'w-full bg-indigo-500 hover:bg-indigo-600 text-white py-2 rounded text-sm transition flex items-center justify-center'; |
|
|
downloadBtn.innerHTML = '<i class="fas fa-download mr-2"></i> 下载'; |
|
|
downloadBtn.addEventListener('click', () => downloadFile(file)); |
|
|
|
|
|
infoContainer.appendChild(fileName); |
|
|
infoContainer.appendChild(sizeInfo); |
|
|
infoContainer.appendChild(downloadBtn); |
|
|
|
|
|
imgContainer.appendChild(img); |
|
|
resultCard.appendChild(imgContainer); |
|
|
resultCard.appendChild(infoContainer); |
|
|
|
|
|
resultsGrid.appendChild(resultCard); |
|
|
}; |
|
|
reader.readAsDataURL(file.originalFile); |
|
|
}); |
|
|
} |
|
|
|
|
|
function downloadFile(file) { |
|
|
const a = document.createElement('a'); |
|
|
a.href = file.downloadUrl; |
|
|
a.download = file.compressedFile.name; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
} |
|
|
|
|
|
function downloadAllProcessedFiles() { |
|
|
processedFiles.forEach((file, index) => { |
|
|
setTimeout(() => { |
|
|
downloadFile(file); |
|
|
}, index * 500); |
|
|
}); |
|
|
} |
|
|
|
|
|
function setImageForWatermarkRemoval(file) { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
watermarkPreview.src = e.target.result; |
|
|
watermarkPreviewContainer.classList.remove('hidden'); |
|
|
noImageSelected.classList.add('hidden'); |
|
|
currentImageForWatermark = file; |
|
|
clearWatermarkSelection(); |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} |
|
|
|
|
|
function startWatermarkSelection() { |
|
|
isSelectingWatermark = true; |
|
|
watermarkBox.classList.remove('hidden'); |
|
|
selectBtn.classList.add('bg-indigo-600'); |
|
|
} |
|
|
|
|
|
function clearWatermarkSelection() { |
|
|
watermarkBox.classList.add('hidden'); |
|
|
watermarkBox.style.width = '0'; |
|
|
watermarkBox.style.height = '0'; |
|
|
selectBtn.classList.remove('bg-indigo-600'); |
|
|
isSelectingWatermark = false; |
|
|
} |
|
|
|
|
|
function startSelection(e) { |
|
|
if (!isSelectingWatermark) return; |
|
|
|
|
|
e.preventDefault(); |
|
|
const rect = watermarkPreview.getBoundingClientRect(); |
|
|
selectionStart = { |
|
|
x: e.clientX - rect.left, |
|
|
y: e.clientY - rect.top |
|
|
}; |
|
|
|
|
|
watermarkBox.style.left = `${selectionStart.x}px`; |
|
|
watermarkBox.style.top = `${selectionStart.y}px`; |
|
|
watermarkBox.style.width = '0'; |
|
|
watermarkBox.style.height = '0'; |
|
|
} |
|
|
|
|
|
function updateSelection(e) { |
|
|
if (!isSelectingWatermark || !selectionStart) return; |
|
|
|
|
|
const rect = watermarkPreview.getBoundingClientRect(); |
|
|
const currentX = e.clientX - rect.left; |
|
|
const currentY = e.clientY - rect.top; |
|
|
|
|
|
const width = currentX - selectionStart.x; |
|
|
const height = currentY - selectionStart.y; |
|
|
|
|
|
watermarkBox.style.width = `${Math.abs(width)}px`; |
|
|
watermarkBox.style.height = `${Math.abs(height)}px`; |
|
|
|
|
|
if (width < 0) { |
|
|
watermarkBox.style.left = `${currentX}px`; |
|
|
} |
|
|
if (height < 0) { |
|
|
watermarkBox.style.top = `${currentY}px`; |
|
|
} |
|
|
} |
|
|
|
|
|
function endSelection() { |
|
|
if (!isSelectingWatermark) return; |
|
|
isSelectingWatermark = false; |
|
|
selectBtn.classList.remove('bg-indigo-600'); |
|
|
} |
|
|
|
|
|
function autoDetectWatermark() { |
|
|
|
|
|
watermarkBox.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const rect = watermarkPreview.getBoundingClientRect(); |
|
|
const width = Math.min(100, rect.width * 0.3); |
|
|
const height = Math.min(50, rect.height * 0.1); |
|
|
const left = Math.random() * (rect.width - width); |
|
|
const top = rect.height - height - 10; |
|
|
|
|
|
watermarkBox.style.left = `${left}px`; |
|
|
watermarkBox.style.top = `${top}px`; |
|
|
watermarkBox.style.width = `${width}px`; |
|
|
watermarkBox.style.height = `${height}px`; |
|
|
|
|
|
|
|
|
const originalText = autoDetectBtn.innerHTML; |
|
|
autoDetectBtn.innerHTML = '<i class="fas fa-check mr-1"></i> 已检测!'; |
|
|
autoDetectBtn.classList.remove('bg-purple-500'); |
|
|
autoDetectBtn.classList.add('bg-green-500'); |
|
|
|
|
|
setTimeout(() => { |
|
|
autoDetectBtn.innerHTML = originalText; |
|
|
autoDetectBtn.classList.add('bg-purple-500'); |
|
|
autoDetectBtn.classList.remove('bg-green-500'); |
|
|
}, 2000); |
|
|
} |
|
|
|
|
|
function processWatermarkRemoval() { |
|
|
if (!watermarkBox || watermarkBox.classList.contains('hidden')) { |
|
|
alert('请先选择水印区域'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
removeWatermarkBtn.disabled = true; |
|
|
removeWatermarkBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> 处理中...'; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
|
|
|
removeWatermarkBtn.innerHTML = '<i class="fas fa-check mr-2"></i> 水印已去除!'; |
|
|
removeWatermarkBtn.classList.remove('bg-red-500'); |
|
|
removeWatermarkBtn.classList.add('bg-green-500'); |
|
|
|
|
|
setTimeout(() => { |
|
|
removeWatermarkBtn.innerHTML = '<i class="fas fa-trash-alt mr-2"></i> 去除水印'; |
|
|
removeWatermarkBtn.classList.add('bg-red-500'); |
|
|
removeWatermarkBtn.classList.remove('bg-green-500'); |
|
|
removeWatermarkBtn.disabled = false; |
|
|
}, 2000); |
|
|
}, 2000); |
|
|
} |
|
|
|
|
|
function showPremiumModal() { |
|
|
|
|
|
const modal = document.createElement('div'); |
|
|
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; |
|
|
modal.innerHTML = ` |
|
|
<div class="bg-white rounded-xl p-6 max-w-md w-full mx-4"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="text-xl font-bold text-indigo-600">升级专业版</h3> |
|
|
<button id="closeModal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="mb-6"> |
|
|
<div class="bg-indigo-50 rounded-lg p-4 mb-4"> |
|
|
<div class="flex items-center mb-2"> |
|
|
<i class="fas fa-check-circle text-green-500 mr-2"></i> |
|
|
<span>自动去除所有水印</span> |
|
|
</div> |
|
|
<div class="flex items-center mb-2"> |
|
|
<i class="fas fa-check-circle text-green-500 mr-2"></i> |
|
|
<span>批量处理无限图片</span> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-check-circle text-green-500 mr-2"></i> |
|
|
<span>优先技术支持</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="text-center py-4"> |
|
|
<p class="text-3xl font-bold text-gray-800 mb-2">¥9.99<span class="text-lg text-gray-600">/月</span></p> |
|
|
<p class="text-gray-600">或 ¥99.99/年 (节省20%)</p> |
|
|
</div> |
|
|
</div> |
|
|
<button id="upgradeBtn" class="w-full bg-gradient-to-r from-purple-500 to-indigo-600 text-white py-3 rounded-lg font-medium hover:opacity-90 transition flex items-center justify-center"> |
|
|
<i class="fas fa-crown mr-2"></i> |
|
|
立即升级 |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
document.body.appendChild(modal); |
|
|
|
|
|
|
|
|
modal.querySelector('#closeModal').addEventListener('click', () => { |
|
|
modal.remove(); |
|
|
}); |
|
|
|
|
|
modal.querySelector('#upgradeBtn').addEventListener('click', () => { |
|
|
alert('感谢选择专业版! 正在跳转支付...'); |
|
|
modal.remove(); |
|
|
}); |
|
|
|
|
|
|
|
|
modal.addEventListener('click', (e) => { |
|
|
if (e.target === modal) { |
|
|
modal.remove(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
updateCompressionValue(); |
|
|
updateRemovalStrengthValue(); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=raineye/ds" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |