photo-imageperfect / index.html
zakkx2000's picture
Add 2 files
ea451fa verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CMS - DICI/SAMU IMAGE PERFECT</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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #0f172a;
color: #e2e8f0;
}
.dropzone {
border: 2px dashed #3b82f6;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #10b981;
background-color: rgba(16, 185, 129, 0.05);
}
.progress-bar {
height: 6px;
background-color: #1e40af;
transition: width 0.3s ease;
}
.slider-thumb::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.slider-thumb::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.result-image {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
max-height: 300px;
object-fit: contain;
}
.result-image:hover {
transform: scale(1.02);
}
.tooltip {
position: relative;
}
.tooltip-text {
visibility: hidden;
width: 120px;
background-color: #1e293b;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
.preview-container {
position: relative;
background-color: #1e293b;
background-image:
linear-gradient(45deg, #334155 25%, transparent 25%),
linear-gradient(-45deg, #334155 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #334155 75%),
linear-gradient(-45deg, transparent 75%, #334155 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
}
.model-btn.active {
background-color: #3b82f6;
color: white;
}
/* Modal styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
overflow-y: auto;
}
.modal-content {
background-color: #1e293b;
margin: 5% auto;
padding: 20px;
border-radius: 10px;
width: 90%;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.close-modal {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close-modal:hover {
color: #fff;
}
.batch-preview {
max-height: 300px;
overflow-y: auto;
margin-bottom: 20px;
border: 1px solid #334155;
border-radius: 5px;
padding: 10px;
}
.batch-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 1px solid #334155;
}
.batch-item:last-child {
border-bottom: none;
}
.batch-thumbnail {
width: 50px;
height: 50px;
object-fit: cover;
margin-right: 10px;
border-radius: 3px;
}
.batch-info {
flex-grow: 1;
}
.batch-status {
margin-left: 10px;
font-size: 12px;
padding: 3px 6px;
border-radius: 3px;
}
.status-pending {
background-color: #3b82f6;
}
.status-completed {
background-color: #10b981;
}
.status-error {
background-color: #ef4444;
}
.batch-actions {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.preset-option {
transition: all 0.2s ease;
cursor: pointer;
}
.preset-option:hover {
transform: translateY(-2px);
}
.preset-option.selected {
border: 2px solid #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
</style>
</head>
<body class="min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="flex justify-between items-center mb-10">
<div class="flex items-center space-x-2">
<i class="fas fa-expand text-blue-500 text-2xl"></i>
<h1 class="text-2xl font-bold bg-gradient-to-r from-blue-500 to-emerald-500 bg-clip-text text-transparent">CMS - DICI/SAMU IMAGE PERFECT</h1>
</div>
<div class="flex space-x-4">
<button id="batchBtn" class="px-4 py-2 rounded-full bg-slate-800 hover:bg-slate-700 transition-colors">
<i class="fas fa-layer-group mr-2"></i>Batch Resize
</button>
<button class="px-4 py-2 rounded-full bg-slate-800 hover:bg-slate-700 transition-colors">
<i class="fas fa-question-circle mr-2"></i>Help
</button>
</div>
</header>
<!-- Main Content -->
<main>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Upload Section -->
<div class="bg-slate-800 rounded-xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Upload Image</h2>
<p class="text-slate-400 mb-6">Enhance your images with AI-powered upscaling. Supports JPG, PNG up to 10MB.</p>
<div id="dropzone" class="dropzone rounded-xl p-8 text-center cursor-pointer mb-6">
<div id="uploadContent" class="flex flex-col items-center justify-center space-y-3">
<i class="fas fa-cloud-upload-alt text-blue-500 text-4xl"></i>
<p class="font-medium">Drag & drop your image here</p>
<p class="text-slate-400 text-sm">or click to browse files</p>
<input type="file" id="fileInput" class="hidden" accept="image/*">
</div>
<div id="imagePreview" class="hidden">
<div class="preview-container rounded-lg overflow-hidden mb-3">
<img id="previewImage" src="" alt="Preview" class="w-full h-auto max-h-64 mx-auto">
</div>
<p id="fileName" class="text-sm font-medium truncate max-w-full"></p>
<p id="fileSize" class="text-xs text-slate-400"></p>
</div>
</div>
<div class="flex justify-between items-center mb-6">
<div>
<h3 class="font-medium mb-1">Upscale Settings</h3>
<p class="text-slate-400 text-sm">Adjust quality and scale</p>
</div>
<button id="resetBtn" class="px-3 py-1 rounded-md bg-slate-700 hover:bg-slate-600 text-sm transition-colors">
<i class="fas fa-redo mr-1"></i>Reset
</button>
</div>
<!-- Settings -->
<div class="space-y-5">
<div>
<div class="flex justify-between mb-2">
<label class="font-medium">Scale Factor</label>
<span id="scaleValue" class="text-blue-400">2x</span>
</div>
<input type="range" id="scaleSlider" min="1" max="4" step="1" value="2" class="w-full slider-thumb">
<div class="flex justify-between text-xs text-slate-400 mt-1">
<span>1x</span>
<span>2x</span>
<span>3x</span>
<span>4x</span>
</div>
</div>
<div>
<div class="flex justify-between mb-2">
<label class="font-medium">AI Model</label>
<span id="modelValue" class="text-blue-400">Standard</span>
</div>
<div class="grid grid-cols-2 gap-2">
<button id="standardModel" class="py-2 px-1 rounded-md bg-blue-600 hover:bg-blue-700 transition-colors text-sm active">
Standard
</button>
<button id="enhancedModel" class="py-2 px-1 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors text-sm">
Enhanced
</button>
</div>
</div>
<div>
<div class="flex justify-between mb-2">
<label class="font-medium">Noise Reduction</label>
<span id="noiseValue" class="text-blue-400">50%</span>
</div>
<input type="range" id="noiseSlider" min="0" max="100" value="50" class="w-full slider-thumb">
</div>
</div>
<button id="processBtn" class="w-full mt-8 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-emerald-600 hover:from-blue-700 hover:to-emerald-700 font-medium transition-all transform hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-magic mr-2"></i>Upscale Image
</button>
</div>
<!-- Results Section -->
<div class="bg-slate-800 rounded-xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Results</h2>
<p class="text-slate-400 mb-6">Your enhanced images will appear here after processing.</p>
<div id="resultContainer" class="hidden">
<div class="flex justify-between items-center mb-4">
<div>
<h3 class="font-medium">Comparison</h3>
<p class="text-slate-400 text-sm">Original vs Upscaled</p>
</div>
<div class="flex space-x-2">
<button id="downloadBtn" class="p-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors tooltip">
<i class="fas fa-download"></i>
<span class="tooltip-text">Download</span>
</button>
<button class="p-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors tooltip">
<i class="fas fa-copy"></i>
<span class="tooltip-text">Copy</span>
</button>
<button class="p-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors tooltip">
<i class="fas fa-share-alt"></i>
<span class="tooltip-text">Share</span>
</button>
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<div>
<div class="preview-container rounded-lg overflow-hidden mb-2">
<img id="originalImage" src="" alt="Original" class="w-full h-auto result-image">
</div>
<p class="text-center text-sm text-slate-400">Original</p>
</div>
<div>
<div class="preview-container rounded-lg overflow-hidden mb-2 relative">
<img id="upscaledImage" src="" alt="Upscaled" class="w-full h-auto result-image">
<div class="absolute top-2 right-2 bg-blue-600 text-white text-xs px-2 py-1 rounded-full">
<span id="scaleBadge">2x</span>
</div>
</div>
<p class="text-center text-sm text-slate-400">Upscaled</p>
</div>
</div>
<div class="bg-slate-900 rounded-lg p-4">
<div class="flex justify-between items-center mb-2">
<span class="font-medium">Details</span>
<span class="text-xs text-slate-400">Processing time: <span id="processingTime">1.4</span>s</span>
</div>
<div class="grid grid-cols-3 gap-2 text-center text-sm">
<div class="bg-slate-800 p-2 rounded">
<div class="text-slate-400">Original Size</div>
<div id="originalSize" class="font-medium">1.2 MB</div>
</div>
<div class="bg-slate-800 p-2 rounded">
<div class="text-slate-400">New Size</div>
<div id="newSize" class="font-medium">3.8 MB</div>
</div>
<div class="bg-slate-800 p-2 rounded">
<div class="text-slate-400">Resolution</div>
<div id="resolution" class="font-medium">1920×1080 → 3840×2160</div>
</div>
</div>
</div>
</div>
<div id="emptyState" class="flex flex-col items-center justify-center py-12">
<div class="bg-slate-900 rounded-full p-6 mb-4">
<i class="fas fa-image text-3xl text-slate-600"></i>
</div>
<h3 class="font-medium mb-1">No image processed yet</h3>
<p class="text-slate-400 text-sm">Upload an image and click "Upscale Image"</p>
</div>
<div id="progressContainer" class="hidden">
<div class="flex flex-col items-center justify-center py-12 space-y-4">
<div class="relative">
<div class="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
<i class="fas fa-magic text-blue-500 text-xl absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"></i>
</div>
<h3 class="font-medium">Processing your image</h3>
<p class="text-slate-400 text-sm">This may take a few seconds...</p>
<div class="w-full bg-slate-700 rounded-full h-1.5">
<div id="progressBar" class="progress-bar h-1.5 rounded-full" style="width: 0%"></div>
</div>
<p id="progressText" class="text-xs text-slate-400">0% completed</p>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="mt-16 text-center text-slate-400 text-sm">
<p>© 2025 CMS - DICI/SAMU IMAGE PERFECT. All rights reserved.</p>
</footer>
</div>
<!-- Batch Resize Modal -->
<div id="batchModal" class="modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2 class="text-xl font-semibold mb-4">Batch Image Resize</h2>
<p class="text-slate-400 mb-6">Resize multiple images to standard dimensions in one operation.</p>
<div class="dropzone rounded-xl p-8 text-center cursor-pointer mb-6" id="batchDropzone">
<div id="batchUploadContent" class="flex flex-col items-center justify-center space-y-3">
<i class="fas fa-layer-group text-blue-500 text-4xl"></i>
<p class="font-medium">Drag & drop your images here</p>
<p class="text-slate-400 text-sm">or click to browse files (max 20 images)</p>
<input type="file" id="batchFileInput" class="hidden" accept="image/*" multiple>
</div>
</div>
<div id="batchPreview" class="batch-preview hidden">
<!-- Preview items will be added here -->
</div>
<div class="space-y-5">
<h3 class="font-medium">Resize Presets</h3>
<div class="grid grid-cols-2 gap-4">
<div id="eviarPreset" class="preset-option bg-slate-800 p-4 rounded-lg selected">
<div class="flex items-center mb-3">
<input type="radio" name="preset" value="eviar" class="mr-2" checked>
<label class="font-medium">EVIAR</label>
</div>
<p class="text-slate-400 text-sm">32.4cm × 21.6cm @ 300dpi</p>
<p class="text-slate-400 text-xs">(3827 × 2551 pixels)</p>
</div>
<div id="sitePreset" class="preset-option bg-slate-800 p-4 rounded-lg">
<div class="flex items-center mb-3">
<input type="radio" name="preset" value="site" class="mr-2">
<label class="font-medium">SITE</label>
</div>
<p class="text-slate-400 text-sm">1000px × 667px @ 72dpi</p>
<p class="text-slate-400 text-xs">(Optimized for web)</p>
</div>
</div>
<div>
<div class="flex justify-between mb-2">
<label class="font-medium">JPEG Quality</label>
<span id="qualityValue" class="text-blue-400">85%</span>
</div>
<input type="range" id="qualitySlider" min="50" max="100" value="85" class="w-full slider-thumb">
</div>
</div>
<div class="batch-actions">
<button id="cancelBatchBtn" class="px-4 py-2 rounded-md bg-slate-700 hover:bg-slate-600 transition-colors">
Cancel
</button>
<button id="processBatchBtn" class="px-4 py-2 rounded-md bg-gradient-to-r from-blue-600 to-emerald-600 hover:from-blue-700 hover:to-emerald-700 font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-cogs mr-2"></i>Process Batch
</button>
</div>
<div id="batchProgressContainer" class="hidden mt-6">
<h4 class="font-medium mb-2">Processing Progress</h4>
<div class="w-full bg-slate-700 rounded-full h-2.5 mb-2">
<div id="batchProgressBar" class="progress-bar h-2.5 rounded-full" style="width: 0%"></div>
</div>
<div class="flex justify-between text-sm text-slate-400">
<span id="batchProgressText">0% (0/0)</span>
<span id="batchTimeRemaining">Estimated time: calculating...</span>
</div>
</div>
<div id="batchResults" class="hidden mt-6">
<h4 class="font-medium mb-2">Results</h4>
<div class="bg-slate-900 rounded-lg p-4">
<div class="grid grid-cols-3 gap-2 text-center text-sm">
<div class="bg-slate-800 p-2 rounded">
<div class="text-slate-400">Processed</div>
<div id="processedCount" class="font-medium">0</div>
</div>
<div class="bg-slate-800 p-2 rounded">
<div class="text-slate-400">Success</div>
<div id="successCount" class="font-medium">0</div>
</div>
<div class="bg-slate-800 p-2 rounded">
<div class="text-slate-400">Errors</div>
<div id="errorCount" class="font-medium">0</div>
</div>
</div>
</div>
<button id="downloadBatchBtn" class="w-full mt-4 py-2 rounded-md bg-gradient-to-r from-blue-600 to-emerald-600 hover:from-blue-700 hover:to-emerald-700 font-medium transition-colors">
<i class="fas fa-file-archive mr-2"></i>Download All as ZIP
</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const processBtn = document.getElementById('processBtn');
const resetBtn = document.getElementById('resetBtn');
const scaleSlider = document.getElementById('scaleSlider');
const scaleValue = document.getElementById('scaleValue');
const noiseSlider = document.getElementById('noiseSlider');
const noiseValue = document.getElementById('noiseValue');
const resultContainer = document.getElementById('resultContainer');
const emptyState = document.getElementById('emptyState');
const progressContainer = document.getElementById('progressContainer');
const originalImage = document.getElementById('originalImage');
const upscaledImage = document.getElementById('upscaledImage');
const originalSize = document.getElementById('originalSize');
const newSize = document.getElementById('newSize');
const resolution = document.getElementById('resolution');
const processingTime = document.getElementById('processingTime');
const scaleBadge = document.getElementById('scaleBadge');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const uploadContent = document.getElementById('uploadContent');
const imagePreview = document.getElementById('imagePreview');
const previewImage = document.getElementById('previewImage');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const downloadBtn = document.getElementById('downloadBtn');
const standardModel = document.getElementById('standardModel');
const enhancedModel = document.getElementById('enhancedModel');
const modelValue = document.getElementById('modelValue');
// Batch resize elements
const batchBtn = document.getElementById('batchBtn');
const batchModal = document.getElementById('batchModal');
const closeModal = document.querySelector('.close-modal');
const cancelBatchBtn = document.getElementById('cancelBatchBtn');
const batchDropzone = document.getElementById('batchDropzone');
const batchFileInput = document.getElementById('batchFileInput');
const batchUploadContent = document.getElementById('batchUploadContent');
const batchPreview = document.getElementById('batchPreview');
const processBatchBtn = document.getElementById('processBatchBtn');
const batchProgressContainer = document.getElementById('batchProgressContainer');
const batchProgressBar = document.getElementById('batchProgressBar');
const batchProgressText = document.getElementById('batchProgressText');
const batchTimeRemaining = document.getElementById('batchTimeRemaining');
const batchResults = document.getElementById('batchResults');
const processedCount = document.getElementById('processedCount');
const successCount = document.getElementById('successCount');
const errorCount = document.getElementById('errorCount');
const downloadBatchBtn = document.getElementById('downloadBatchBtn');
const qualitySlider = document.getElementById('qualitySlider');
const qualityValue = document.getElementById('qualityValue');
const eviarPreset = document.getElementById('eviarPreset');
const sitePreset = document.getElementById('sitePreset');
// Variables
let selectedFile = null;
let upscaledImageBlob = null;
let currentModel = 'standard';
let batchFiles = [];
let batchResultsData = [];
let selectedPreset = 'eviar'; // Default to EVIAR
// Event Listeners
dropzone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleFileSelect(e.target.files[0]);
}
});
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
['dragleave', 'dragend'].forEach(type => {
dropzone.addEventListener(type, () => {
dropzone.classList.remove('active');
});
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
if (e.dataTransfer.files.length) {
handleFileSelect(e.dataTransfer.files[0]);
}
});
scaleSlider.addEventListener('input', () => {
scaleValue.textContent = `${scaleSlider.value}x`;
});
noiseSlider.addEventListener('input', () => {
noiseValue.textContent = `${noiseSlider.value}%`;
});
standardModel.addEventListener('click', () => {
currentModel = 'standard';
modelValue.textContent = 'Standard';
standardModel.classList.add('bg-blue-600');
standardModel.classList.remove('bg-slate-700');
enhancedModel.classList.add('bg-slate-700');
enhancedModel.classList.remove('bg-blue-600');
});
enhancedModel.addEventListener('click', () => {
currentModel = 'enhanced';
modelValue.textContent = 'Enhanced';
enhancedModel.classList.add('bg-blue-600');
enhancedModel.classList.remove('bg-slate-700');
standardModel.classList.add('bg-slate-700');
standardModel.classList.remove('bg-blue-600');
});
processBtn.addEventListener('click', processImage);
resetBtn.addEventListener('click', resetApp);
downloadBtn.addEventListener('click', downloadUpscaledImage);
// Batch resize event listeners
batchBtn.addEventListener('click', () => {
batchModal.style.display = 'block';
});
closeModal.addEventListener('click', () => {
batchModal.style.display = 'none';
});
cancelBatchBtn.addEventListener('click', () => {
batchModal.style.display = 'none';
resetBatchModal();
});
batchDropzone.addEventListener('click', () => batchFileInput.click());
batchFileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleBatchFiles(e.target.files);
}
});
batchDropzone.addEventListener('dragover', (e) => {
e.preventDefault();
batchDropzone.classList.add('active');
});
['dragleave', 'dragend'].forEach(type => {
batchDropzone.addEventListener(type, () => {
batchDropzone.classList.remove('active');
});
});
batchDropzone.addEventListener('drop', (e) => {
e.preventDefault();
batchDropzone.classList.remove('active');
if (e.dataTransfer.files.length) {
handleBatchFiles(e.dataTransfer.files);
}
});
qualitySlider.addEventListener('input', () => {
qualityValue.textContent = `${qualitySlider.value}%`;
});
// Preset selection
eviarPreset.addEventListener('click', () => {
selectedPreset = 'eviar';
eviarPreset.classList.add('selected');
sitePreset.classList.remove('selected');
document.querySelector('input[name="preset"][value="eviar"]').checked = true;
});
sitePreset.addEventListener('click', () => {
selectedPreset = 'site';
sitePreset.classList.add('selected');
eviarPreset.classList.remove('selected');
document.querySelector('input[name="preset"][value="site"]').checked = true;
});
processBatchBtn.addEventListener('click', processBatchImages);
downloadBatchBtn.addEventListener('click', downloadBatchResults);
// Close modal when clicking outside
window.addEventListener('click', (e) => {
if (e.target === batchModal) {
batchModal.style.display = 'none';
}
});
// Functions
function handleFileSelect(file) {
// Check if file is an image
if (!file.type.match('image.*')) {
alert('Please select an image file (JPG, PNG)');
return;
}
// Check file size (max 10MB)
if (file.size > 10 * 1024 * 1024) {
alert('File size exceeds 10MB limit');
return;
}
selectedFile = file;
// Display file preview
const reader = new FileReader();
reader.onload = function(e) {
// Show preview
uploadContent.classList.add('hidden');
imagePreview.classList.remove('hidden');
previewImage.src = e.target.result;
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
// Set original image for comparison
originalImage.src = e.target.result;
// Simulate file info
originalSize.textContent = formatFileSize(file.size);
const dimensions = getRandomDimensions();
resolution.textContent = `${dimensions.original}${dimensions.upscaled}`;
// Enable process button
processBtn.disabled = false;
};
reader.readAsDataURL(file);
}
function processImage() {
if (!selectedFile) {
alert('Please select an image first');
return;
}
// Show progress
emptyState.classList.add('hidden');
resultContainer.classList.add('hidden');
progressContainer.classList.remove('hidden');
// Simulate processing
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress > 100) progress = 100;
progressBar.style.width = `${progress}%`;
progressText.textContent = `${Math.round(progress)}% completed`;
if (progress === 100) {
clearInterval(interval);
setTimeout(() => {
// For demo purposes, we'll create a slightly modified version of the original
createUpscaledImage(selectedFile).then(blob => {
upscaledImageBlob = blob;
showResults();
});
}, 500);
}
}, 300);
}
function createUpscaledImage(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
// Create a canvas to "upscale" the image (just for demo)
const canvas = document.createElement('canvas');
const scale = parseInt(scaleSlider.value);
canvas.width = img.width * scale;
canvas.height = img.height * scale;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = true;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Apply different effects based on selected model
if (currentModel === 'enhanced') {
// Enhanced model adds more sharpness
ctx.globalAlpha = 0.7;
ctx.filter = 'contrast(1.1) saturate(1.1)';
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
ctx.filter = 'none';
}
// Apply noise reduction based on slider
if (noiseSlider.value > 50) {
ctx.globalAlpha = 0.5;
ctx.filter = 'blur(0.5px)';
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
ctx.filter = 'none';
}
// Convert to blob
canvas.toBlob(blob => {
resolve(blob);
}, file.type, 0.9);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
function showResults() {
progressContainer.classList.add('hidden');
resultContainer.classList.remove('hidden');
// Create object URL for the upscaled image
const upscaledUrl = URL.createObjectURL(upscaledImageBlob);
upscaledImage.src = upscaledUrl;
// Update info
const scale = scaleSlider.value;
scaleBadge.textContent = `${scale}x`;
// Simulate upscaled file size (original * scale factor)
const originalSizeValue = selectedFile.size;
const newSizeValue = originalSizeValue * scale * (currentModel === 'enhanced' ? 1.2 : 0.8);
newSize.textContent = formatFileSize(newSizeValue);
// Simulate processing time (1-3 seconds for standard, 2-4 for enhanced)
const processingTimeValue = currentModel === 'enhanced'
? (2 + Math.random() * 2).toFixed(1)
: (1 + Math.random() * 2).toFixed(1);
processingTime.textContent = processingTimeValue;
}
function downloadUpscaledImage() {
if (!upscaledImageBlob) return;
const a = document.createElement('a');
const url = URL.createObjectURL(upscaledImageBlob);
const fileName = selectedFile.name.replace(/\.[^/.]+$/, '') + '_upscaled' + selectedFile.name.match(/\.[^/.]+$/)[0];
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);
}
function resetApp() {
// Reset file input
fileInput.value = '';
selectedFile = null;
upscaledImageBlob = null;
// Reset UI
originalImage.src = '';
upscaledImage.src = '';
emptyState.classList.remove('hidden');
resultContainer.classList.add('hidden');
progressContainer.classList.add('hidden');
uploadContent.classList.remove('hidden');
imagePreview.classList.add('hidden');
previewImage.src = '';
// Reset sliders
scaleSlider.value = 2;
scaleValue.textContent = '2x';
noiseSlider.value = 50;
noiseValue.textContent = '50%';
// Reset model to standard
currentModel = 'standard';
modelValue.textContent = 'Standard';
standardModel.classList.add('bg-blue-600');
standardModel.classList.remove('bg-slate-700');
enhancedModel.classList.add('bg-slate-700');
enhancedModel.classList.remove('bg-blue-600');
// Disable process button
processBtn.disabled = true;
}
// Batch resize functions
function handleBatchFiles(files) {
// Clear previous files
batchFiles = [];
// Convert FileList to array and filter images
const fileArray = Array.from(files).filter(file =>
file.type.match('image.*') && file.size <= 10 * 1024 * 1024
);
// Limit to 20 files
if (fileArray.length > 20) {
alert('Maximum 20 images allowed. Only the first 20 will be processed.');
batchFiles = fileArray.slice(0, 20);
} else {
batchFiles = fileArray;
}
if (batchFiles.length === 0) {
alert('No valid image files selected. Please select JPG or PNG files under 10MB.');
return;
}
// Show preview
batchUploadContent.classList.add('hidden');
batchPreview.classList.remove('hidden');
batchPreview.innerHTML = '';
// Add each file to preview
batchFiles.forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(e) {
const item = document.createElement('div');
item.className = 'batch-item';
item.innerHTML = `
<img src="${e.target.result}" class="batch-thumbnail">
<div class="batch-info">
<div class="font-medium truncate">${file.name}</div>
<div class="text-xs text-slate-400">${formatFileSize(file.size)}</div>
</div>
<span class="batch-status status-pending">Pending</span>
`;
batchPreview.appendChild(item);
};
reader.readAsDataURL(file);
});
// Enable process button
processBatchBtn.disabled = false;
}
function processBatchImages() {
if (batchFiles.length === 0) {
alert('Please select images first');
return;
}
// Get selected preset and quality
const quality = parseInt(qualitySlider.value) / 100;
// Show progress
batchProgressContainer.classList.remove('hidden');
batchResults.classList.add('hidden');
// Reset results data
batchResultsData = [];
// Process each image
let processed = 0;
let success = 0;
let errors = 0;
// Calculate estimated time (1-3 seconds per image)
const estimatedTime = batchFiles.length * (2 + Math.random());
let remainingTime = estimatedTime;
const updateTime = setInterval(() => {
remainingTime -= 0.1;
if (remainingTime <= 0) remainingTime = 0;
batchTimeRemaining.textContent = `Estimated time: ${remainingTime.toFixed(1)}s remaining`;
}, 100);
const processNextImage = () => {
if (processed >= batchFiles.length) {
clearInterval(updateTime);
// Show results
batchProgressContainer.classList.add('hidden');
batchResults.classList.remove('hidden');
processedCount.textContent = processed;
successCount.textContent = success;
errorCount.textContent = errors;
return;
}
const file = batchFiles[processed];
const item = batchPreview.children[processed];
// Update progress
processed++;
const progress = (processed / batchFiles.length) * 100;
batchProgressBar.style.width = `${progress}%`;
batchProgressText.textContent = `${Math.round(progress)}% (${processed}/${batchFiles.length})`;
// Process the image
resizeImage(file, selectedPreset, quality).then(result => {
if (result.status === 'success') {
success++;
item.querySelector('.batch-status').className = 'batch-status status-completed';
item.querySelector('.batch-status').textContent = 'Completed';
batchResultsData.push({
file: file,
status: 'success',
processedFile: result.blob,
fileName: result.fileName
});
} else {
errors++;
item.querySelector('.batch-status').className = 'batch-status status-error';
item.querySelector('.batch-status').textContent = 'Error';
batchResultsData.push({
file: file,
status: 'error',
error: result.error
});
}
// Process next image
setTimeout(processNextImage, 300);
}).catch(error => {
errors++;
item.querySelector('.batch-status').className = 'batch-status status-error';
item.querySelector('.batch-status').textContent = 'Error';
batchResultsData.push({
file: file,
status: 'error',
error: error.message
});
// Process next image
setTimeout(processNextImage, 300);
});
};
// Start processing
processNextImage();
}
function resizeImage(file, preset, quality) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
try {
// Determine target dimensions based on preset
let targetWidth, targetHeight;
if (preset === 'eviar') {
// EVIAR: 3827 × 2551 pixels (32.4cm × 21.6cm @ 300dpi)
targetWidth = 3827;
targetHeight = 2551;
} else {
// SITE: 1000 × 667 pixels
targetWidth = 1000;
targetHeight = 667;
}
// Calculate new dimensions maintaining aspect ratio
let newWidth, newHeight;
const aspectRatio = img.width / img.height;
if (img.width / img.height > targetWidth / targetHeight) {
// Image is wider than target - scale to width
newWidth = targetWidth;
newHeight = targetWidth / aspectRatio;
} else {
// Image is taller than target - scale to height
newHeight = targetHeight;
newWidth = targetHeight * aspectRatio;
}
// Create canvas
const canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
const ctx = canvas.getContext('2d');
// Fill with white background (for transparent PNGs)
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw image centered
const offsetX = (targetWidth - newWidth) / 2;
const offsetY = (targetHeight - newHeight) / 2;
ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight);
// Convert to blob
canvas.toBlob(blob => {
const fileName = file.name.replace(/\.[^/.]+$/, '') +
(preset === 'eviar' ? '_eviar' : '_site') +
(file.name.match(/\.[^/.]+$/) ? file.name.match(/\.[^/.]+$/)[0] : '.jpg');
resolve({
status: 'success',
blob: blob,
fileName: fileName
});
}, 'image/jpeg', quality);
} catch (error) {
reject(new Error('Image processing failed'));
}
};
img.onerror = function() {
reject(new Error('Failed to load image'));
};
img.src = e.target.result;
};
reader.onerror = function() {
reject(new Error('Failed to read file'));
};
reader.readAsDataURL(file);
});
}
function downloadBatchResults() {
if (batchResultsData.length === 0 || batchResultsData.filter(item => item.status === 'success').length === 0) {
alert('No successfully processed images to download');
return;
}
// Create a new JSZip instance
const zip = new JSZip();
const successful = batchResultsData.filter(item => item.status === 'success');
// Add each successful image to the zip
successful.forEach(item => {
zip.file(item.fileName, item.processedFile);
});
// Generate the zip file
zip.generateAsync({ type: 'blob' }).then(content => {
// Create download link
const a = document.createElement('a');
const url = URL.createObjectURL(content);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const zipName = `batch-resize-${timestamp}.zip`;
a.href = url;
a.download = zipName;
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);
});
}
function resetBatchModal() {
batchFileInput.value = '';
batchFiles = [];
batchResultsData = [];
batchUploadContent.classList.remove('hidden');
batchPreview.classList.add('hidden');
batchProgressContainer.classList.add('hidden');
batchResults.classList.add('hidden');
processBatchBtn.disabled = true;
// Reset to default values
qualitySlider.value = 85;
qualityValue.textContent = '85%';
selectedPreset = 'eviar';
eviarPreset.classList.add('selected');
sitePreset.classList.remove('selected');
document.querySelector('input[name="preset"][value="eviar"]').checked = true;
}
// Helper functions
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' bytes';
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
else return (bytes / 1048576).toFixed(1) + ' MB';
}
function getRandomDimensions() {
const widths = [640, 800, 1024, 1280, 1920];
const heights = [480, 600, 768, 720, 1080];
const randomIndex = Math.floor(Math.random() * widths.length);
const originalWidth = widths[randomIndex];
const originalHeight = heights[randomIndex];
const scale = parseInt(scaleSlider.value);
const upscaledWidth = originalWidth * scale;
const upscaledHeight = originalHeight * scale;
return {
original: `${originalWidth}×${originalHeight}`,
upscaled: `${upscaledWidth}×${upscaledHeight}`
};
}
});
</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=zakkx2000/photo-imageperfect" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>