Svngoku commited on
Commit
1e958b1
·
verified ·
1 Parent(s): 1e87f5e

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +542 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Image Upscaler Ui
3
- emoji: 📈
4
- colorFrom: indigo
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: image-upscaler-ui
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,542 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Image Upscaler Pro</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .dropzone {
11
+ border: 2px dashed #9CA3AF;
12
+ transition: all 0.3s ease;
13
+ }
14
+ .dropzone.active {
15
+ border-color: #3B82F6;
16
+ background-color: rgba(59, 130, 246, 0.05);
17
+ }
18
+ .image-card {
19
+ transition: all 0.3s ease;
20
+ }
21
+ .image-card:hover {
22
+ transform: translateY(-2px);
23
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
24
+ }
25
+ .progress-bar {
26
+ transition: width 0.5s ease;
27
+ }
28
+ @keyframes pulse {
29
+ 0%, 100% {
30
+ opacity: 1;
31
+ }
32
+ 50% {
33
+ opacity: 0.5;
34
+ }
35
+ }
36
+ .animate-pulse {
37
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
38
+ }
39
+ </style>
40
+ </head>
41
+ <body class="bg-gray-50 min-h-screen">
42
+ <div class="container mx-auto px-4 py-8">
43
+ <!-- Header -->
44
+ <header class="mb-10 text-center">
45
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">Image Upscaler Pro</h1>
46
+ <p class="text-gray-600 max-w-2xl mx-auto">Enhance your images with our advanced upscaling technology. Upload multiple images, process them, and download the results.</p>
47
+ </header>
48
+
49
+ <!-- Main Content -->
50
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
51
+ <!-- Upload Section -->
52
+ <div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6 h-fit sticky top-6">
53
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Upload Images</h2>
54
+
55
+ <!-- Dropzone -->
56
+ <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-4">
57
+ <div class="flex flex-col items-center justify-center space-y-2">
58
+ <i class="fas fa-cloud-upload-alt text-4xl text-blue-500"></i>
59
+ <p class="text-gray-600">Drag & drop images here or click to browse</p>
60
+ <p class="text-sm text-gray-400">Supports JPG, PNG, WEBP (Max 10MB each)</p>
61
+ </div>
62
+ <input type="file" id="fileInput" class="hidden" accept="image/*" multiple>
63
+ </div>
64
+
65
+ <button id="uploadBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg flex items-center justify-center space-x-2">
66
+ <i class="fas fa-upload"></i>
67
+ <span>Select Files</span>
68
+ </button>
69
+
70
+ <div class="mt-6">
71
+ <h3 class="font-medium text-gray-700 mb-2">Upscale Options</h3>
72
+ <div class="space-y-3">
73
+ <div class="flex items-center">
74
+ <input type="radio" id="scale2x" name="scale" value="2" class="h-4 w-4 text-blue-600" checked>
75
+ <label for="scale2x" class="ml-2 text-gray-700">2x Upscale</label>
76
+ </div>
77
+ <div class="flex items-center">
78
+ <input type="radio" id="scale4x" name="scale" value="4" class="h-4 w-4 text-blue-600">
79
+ <label for="scale4x" class="ml-2 text-gray-700">4x Upscale</label>
80
+ </div>
81
+ <div class="flex items-center">
82
+ <input type="radio" id="scale8x" name="scale" value="8" class="h-4 w-4 text-blue-600">
83
+ <label for="scale8x" class="ml-2 text-gray-700">8x Upscale (Slow)</label>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="mt-6">
89
+ <h3 class="font-medium text-gray-700 mb-2">Processing Options</h3>
90
+ <div class="space-y-3">
91
+ <div class="flex items-center">
92
+ <input type="checkbox" id="enhanceDetails" class="h-4 w-4 text-blue-600 rounded" checked>
93
+ <label for="enhanceDetails" class="ml-2 text-gray-700">Enhance Details</label>
94
+ </div>
95
+ <div class="flex items-center">
96
+ <input type="checkbox" id="reduceNoise" class="h-4 w-4 text-blue-600 rounded" checked>
97
+ <label for="reduceNoise" class="ml-2 text-gray-700">Reduce Noise</label>
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <button id="processBtn" class="w-full mt-6 bg-green-600 hover:bg-green-700 text-white py-3 px-4 rounded-lg font-medium flex items-center justify-center space-x-2 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
103
+ <i class="fas fa-magic"></i>
104
+ <span>Process Images</span>
105
+ </button>
106
+
107
+ <button id="downloadBtn" class="w-full mt-4 bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg flex items-center justify-center space-x-2 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
108
+ <i class="fas fa-file-export"></i>
109
+ <span>Export Results</span>
110
+ </button>
111
+ </div>
112
+
113
+ <!-- Image Gallery -->
114
+ <div class="lg:col-span-2">
115
+ <div class="bg-white rounded-xl shadow-md p-6">
116
+ <div class="flex justify-between items-center mb-6">
117
+ <h2 class="text-xl font-semibold text-gray-800">Image Gallery</h2>
118
+ <div class="text-sm text-gray-500">
119
+ <span id="imageCount">0</span> images selected
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Status Messages -->
124
+ <div id="statusMessage" class="hidden mb-6 p-4 rounded-lg"></div>
125
+
126
+ <!-- Processing Progress -->
127
+ <div id="progressContainer" class="hidden mb-6">
128
+ <div class="flex justify-between text-sm text-gray-600 mb-1">
129
+ <span>Processing images</span>
130
+ <span id="progressText">0%</span>
131
+ </div>
132
+ <div class="w-full bg-gray-200 rounded-full h-2.5">
133
+ <div id="progressBar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Empty State -->
138
+ <div id="emptyState" class="text-center py-12">
139
+ <i class="fas fa-images text-4xl text-gray-300 mb-4"></i>
140
+ <h3 class="text-lg font-medium text-gray-500">No images uploaded yet</h3>
141
+ <p class="text-gray-400 mt-1">Upload some images to get started</p>
142
+ </div>
143
+
144
+ <!-- Image Grid -->
145
+ <div id="imageGrid" class="grid grid-cols-1 sm:grid-cols-2 gap-4 hidden">
146
+ <!-- Images will be added here dynamically -->
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <script>
154
+ document.addEventListener('DOMContentLoaded', function() {
155
+ // DOM Elements
156
+ const dropzone = document.getElementById('dropzone');
157
+ const fileInput = document.getElementById('fileInput');
158
+ const uploadBtn = document.getElementById('uploadBtn');
159
+ const processBtn = document.getElementById('processBtn');
160
+ const downloadBtn = document.getElementById('downloadBtn');
161
+ const imageGrid = document.getElementById('imageGrid');
162
+ const emptyState = document.getElementById('emptyState');
163
+ const imageCount = document.getElementById('imageCount');
164
+ const progressContainer = document.getElementById('progressContainer');
165
+ const progressBar = document.getElementById('progressBar');
166
+ const progressText = document.getElementById('progressText');
167
+ const statusMessage = document.getElementById('statusMessage');
168
+
169
+ // State
170
+ let images = [];
171
+ let processedImages = [];
172
+
173
+ // Event Listeners
174
+ uploadBtn.addEventListener('click', () => fileInput.click());
175
+
176
+ fileInput.addEventListener('change', handleFileSelect);
177
+
178
+ dropzone.addEventListener('dragover', (e) => {
179
+ e.preventDefault();
180
+ dropzone.classList.add('active');
181
+ });
182
+
183
+ dropzone.addEventListener('dragleave', () => {
184
+ dropzone.classList.remove('active');
185
+ });
186
+
187
+ dropzone.addEventListener('drop', (e) => {
188
+ e.preventDefault();
189
+ dropzone.classList.remove('active');
190
+ fileInput.files = e.dataTransfer.files;
191
+ handleFileSelect({ target: fileInput });
192
+ });
193
+
194
+ dropzone.addEventListener('click', () => fileInput.click());
195
+
196
+ processBtn.addEventListener('click', processImages);
197
+
198
+ downloadBtn.addEventListener('click', exportResults);
199
+
200
+ // Functions
201
+ function handleFileSelect(event) {
202
+ const files = event.target.files;
203
+ if (!files.length) return;
204
+
205
+ images = [];
206
+ processedImages = [];
207
+ imageGrid.innerHTML = '';
208
+
209
+ let validFiles = 0;
210
+
211
+ for (let i = 0; i < files.length; i++) {
212
+ const file = files[i];
213
+
214
+ // Check file type
215
+ if (!file.type.match('image.*')) {
216
+ showStatus(`Skipped ${file.name}: Not an image file`, 'warning');
217
+ continue;
218
+ }
219
+
220
+ // Check file size (10MB max)
221
+ if (file.size > 10 * 1024 * 1024) {
222
+ showStatus(`Skipped ${file.name}: File too large (max 10MB)`, 'warning');
223
+ continue;
224
+ }
225
+
226
+ validFiles++;
227
+
228
+ const reader = new FileReader();
229
+ reader.onload = function(e) {
230
+ const img = new Image();
231
+ img.onload = function() {
232
+ images.push({
233
+ id: Date.now() + i,
234
+ name: file.name,
235
+ original: e.target.result,
236
+ processed: null,
237
+ width: img.width,
238
+ height: img.height,
239
+ status: 'pending'
240
+ });
241
+
242
+ updateUI();
243
+ };
244
+ img.src = e.target.result;
245
+ };
246
+ reader.readAsDataURL(file);
247
+ }
248
+
249
+ if (validFiles > 0) {
250
+ showStatus(`Successfully added ${validFiles} image(s)`, 'success');
251
+ processBtn.disabled = false;
252
+ }
253
+ }
254
+
255
+ function updateUI() {
256
+ if (images.length === 0) {
257
+ emptyState.classList.remove('hidden');
258
+ imageGrid.classList.add('hidden');
259
+ imageCount.textContent = '0';
260
+ processBtn.disabled = true;
261
+ downloadBtn.disabled = true;
262
+ return;
263
+ }
264
+
265
+ emptyState.classList.add('hidden');
266
+ imageGrid.classList.remove('hidden');
267
+ imageCount.textContent = images.length;
268
+
269
+ imageGrid.innerHTML = '';
270
+
271
+ images.forEach((img, index) => {
272
+ const card = document.createElement('div');
273
+ card.className = 'image-card bg-gray-50 rounded-lg overflow-hidden border border-gray-200';
274
+ card.innerHTML = `
275
+ <div class="relative">
276
+ <img src="${img.original}" alt="${img.name}" class="w-full h-40 object-contain bg-gray-100">
277
+ <div class="absolute top-2 right-2 bg-white rounded-full p-1 shadow">
278
+ ${img.status === 'processed' ?
279
+ '<i class="fas fa-check-circle text-green-500"></i>' :
280
+ img.status === 'processing' ?
281
+ '<i class="fas fa-spinner fa-spin text-blue-500"></i>' :
282
+ '<i class="fas fa-clock text-gray-400"></i>'}
283
+ </div>
284
+ </div>
285
+ <div class="p-3">
286
+ <div class="flex justify-between items-start">
287
+ <div class="truncate">
288
+ <h3 class="text-sm font-medium text-gray-800 truncate">${img.name}</h3>
289
+ <p class="text-xs text-gray-500">${img.width} × ${img.height}</p>
290
+ </div>
291
+ ${img.processed ?
292
+ `<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">${Math.round((img.processed.size / img.original.length) * 100)}% larger</span>` :
293
+ ''}
294
+ </div>
295
+ ${img.status === 'processed' ? `
296
+ <div class="mt-2 flex space-x-2">
297
+ <button class="view-result-btn flex-1 bg-blue-50 hover:bg-blue-100 text-blue-600 text-xs py-1 px-2 rounded flex items-center justify-center space-x-1" data-id="${img.id}">
298
+ <i class="fas fa-eye text-xs"></i>
299
+ <span>View</span>
300
+ </button>
301
+ </div>` : ''}
302
+ </div>
303
+ `;
304
+ imageGrid.appendChild(card);
305
+ });
306
+
307
+ // Add event listeners to view buttons
308
+ document.querySelectorAll('.view-result-btn').forEach(btn => {
309
+ btn.addEventListener('click', function() {
310
+ const id = parseInt(this.getAttribute('data-id'));
311
+ viewResult(id);
312
+ });
313
+ });
314
+ }
315
+
316
+ function processImages() {
317
+ if (images.length === 0) return;
318
+
319
+ processBtn.disabled = true;
320
+ downloadBtn.disabled = true;
321
+ progressContainer.classList.remove('hidden');
322
+
323
+ // Simulate processing with progress updates
324
+ let processed = 0;
325
+ const total = images.length;
326
+
327
+ // Update all images to processing status
328
+ images.forEach(img => {
329
+ img.status = 'processing';
330
+ });
331
+ updateUI();
332
+
333
+ // Process each image (simulated)
334
+ const processInterval = setInterval(() => {
335
+ if (processed >= total) {
336
+ clearInterval(processInterval);
337
+ processingComplete();
338
+ return;
339
+ }
340
+
341
+ const currentImage = images[processed];
342
+
343
+ // Simulate processing time (1-3 seconds per image)
344
+ setTimeout(() => {
345
+ // This is where you would normally call your actual upscaling API
346
+ // For demo purposes, we're just creating a slightly larger version
347
+ const scaleFactor = parseInt(document.querySelector('input[name="scale"]:checked').value);
348
+ const enhanceDetails = document.getElementById('enhanceDetails').checked;
349
+ const reduceNoise = document.getElementById('reduceNoise').checked;
350
+
351
+ // Create a canvas to "process" the image
352
+ const canvas = document.createElement('canvas');
353
+ const img = new Image();
354
+ img.onload = function() {
355
+ canvas.width = img.width * scaleFactor;
356
+ canvas.height = img.height * scaleFactor;
357
+ const ctx = canvas.getContext('2d');
358
+ ctx.imageSmoothingEnabled = true;
359
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
360
+
361
+ // Apply some "enhancements" (simulated)
362
+ if (enhanceDetails || reduceNoise) {
363
+ // In a real app, you would apply actual image processing here
364
+ // For demo, we'll just add a slight overlay
365
+ ctx.globalCompositeOperation = 'overlay';
366
+ ctx.fillStyle = enhanceDetails ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.02)';
367
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
368
+ ctx.globalCompositeOperation = 'source-over';
369
+ }
370
+
371
+ const processedDataUrl = canvas.toDataURL('image/jpeg', 0.9);
372
+
373
+ currentImage.processed = {
374
+ data: processedDataUrl,
375
+ size: processedDataUrl.length,
376
+ width: canvas.width,
377
+ height: canvas.height,
378
+ scale: scaleFactor,
379
+ options: {
380
+ enhanceDetails,
381
+ reduceNoise
382
+ }
383
+ };
384
+ currentImage.status = 'processed';
385
+
386
+ processed++;
387
+ const progress = Math.round((processed / total) * 100);
388
+ progressBar.style.width = `${progress}%`;
389
+ progressText.textContent = `${progress}%`;
390
+
391
+ updateUI();
392
+
393
+ if (processed === total) {
394
+ processingComplete();
395
+ }
396
+ };
397
+ img.src = currentImage.original;
398
+ }, Math.random() * 2000 + 1000);
399
+
400
+ }, 100);
401
+ }
402
+
403
+ function processingComplete() {
404
+ showStatus('All images processed successfully!', 'success');
405
+ processBtn.disabled = true;
406
+ downloadBtn.disabled = false;
407
+ processedImages = [...images];
408
+
409
+ // Hide progress bar after a delay
410
+ setTimeout(() => {
411
+ progressContainer.classList.add('hidden');
412
+ progressBar.style.width = '0%';
413
+ progressText.textContent = '0%';
414
+ }, 2000);
415
+ }
416
+
417
+ function viewResult(id) {
418
+ const image = images.find(img => img.id === id);
419
+ if (!image || !image.processed) return;
420
+
421
+ // Create modal
422
+ const modal = document.createElement('div');
423
+ modal.className = 'fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4';
424
+ modal.innerHTML = `
425
+ <div class="bg-white rounded-xl max-w-4xl w-full max-h-[90vh] overflow-hidden flex flex-col">
426
+ <div class="flex justify-between items-center border-b p-4">
427
+ <h3 class="text-lg font-semibold">${image.name}</h3>
428
+ <button class="close-modal text-gray-500 hover:text-gray-700">
429
+ <i class="fas fa-times"></i>
430
+ </button>
431
+ </div>
432
+ <div class="flex-1 overflow-auto p-4">
433
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
434
+ <div>
435
+ <h4 class="text-sm font-medium text-gray-500 mb-2">Original (${image.width} × ${image.height})</h4>
436
+ <img src="${image.original}" class="w-full h-auto max-h-[60vh] object-contain bg-gray-100 rounded">
437
+ </div>
438
+ <div>
439
+ <h4 class="text-sm font-medium text-gray-500 mb-2">Upscaled ${image.processed.scale}x (${image.processed.width} × ${image.processed.height})</h4>
440
+ <img src="${image.processed.data}" class="w-full h-auto max-h-[60vh] object-contain bg-gray-100 rounded">
441
+ </div>
442
+ </div>
443
+ </div>
444
+ <div class="border-t p-4 flex justify-end space-x-3">
445
+ <button class="download-single-btn bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg flex items-center space-x-2" data-id="${image.id}">
446
+ <i class="fas fa-download"></i>
447
+ <span>Download</span>
448
+ </button>
449
+ </div>
450
+ </div>
451
+ `;
452
+
453
+ document.body.appendChild(modal);
454
+
455
+ // Add event listeners
456
+ modal.querySelector('.close-modal').addEventListener('click', () => {
457
+ document.body.removeChild(modal);
458
+ });
459
+
460
+ modal.querySelector('.download-single-btn').addEventListener('click', () => {
461
+ downloadSingleImage(image);
462
+ document.body.removeChild(modal);
463
+ });
464
+ }
465
+
466
+ function downloadSingleImage(image) {
467
+ if (!image.processed) return;
468
+
469
+ const a = document.createElement('a');
470
+ a.href = image.processed.data;
471
+ a.download = `upscaled_${image.name.split('.')[0]}.jpg`;
472
+ document.body.appendChild(a);
473
+ a.click();
474
+ document.body.removeChild(a);
475
+
476
+ showStatus(`Downloaded upscaled version of ${image.name}`, 'success');
477
+ }
478
+
479
+ function exportResults() {
480
+ if (processedImages.length === 0) return;
481
+
482
+ // Prepare data for export
483
+ const exportData = processedImages.map(img => ({
484
+ id: img.id,
485
+ name: img.name,
486
+ originalWidth: img.width,
487
+ originalHeight: img.height,
488
+ originalBase64: img.original,
489
+ processedWidth: img.processed.width,
490
+ processedHeight: img.processed.height,
491
+ processedBase64: img.processed.data,
492
+ scaleFactor: img.processed.scale,
493
+ processingOptions: img.processed.options,
494
+ timestamp: new Date().toISOString()
495
+ }));
496
+
497
+ // Create JSON file
498
+ const json = JSON.stringify(exportData, null, 2);
499
+ const blob = new Blob([json], { type: 'application/json' });
500
+ const url = URL.createObjectURL(blob);
501
+
502
+ // Create download link
503
+ const a = document.createElement('a');
504
+ a.href = url;
505
+ a.download = `image_upscale_results_${new Date().toISOString().slice(0, 10)}.json`;
506
+ document.body.appendChild(a);
507
+ a.click();
508
+
509
+ // Clean up
510
+ setTimeout(() => {
511
+ document.body.removeChild(a);
512
+ URL.revokeObjectURL(url);
513
+ }, 100);
514
+
515
+ showStatus(`Exported ${processedImages.length} image results`, 'success');
516
+ }
517
+
518
+ function showStatus(message, type) {
519
+ statusMessage.classList.remove('hidden');
520
+ statusMessage.textContent = message;
521
+
522
+ // Set color based on type
523
+ const colors = {
524
+ success: 'bg-green-100 text-green-800',
525
+ warning: 'bg-yellow-100 text-yellow-800',
526
+ error: 'bg-red-100 text-red-800',
527
+ info: 'bg-blue-100 text-blue-800'
528
+ };
529
+
530
+ // Reset classes
531
+ statusMessage.className = 'mb-6 p-4 rounded-lg';
532
+ statusMessage.classList.add(colors[type] || colors.info);
533
+
534
+ // Auto-hide after 5 seconds
535
+ setTimeout(() => {
536
+ statusMessage.classList.add('hidden');
537
+ }, 5000);
538
+ }
539
+ });
540
+ </script>
541
+ <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=Svngoku/image-upscaler-ui" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
542
+ </html>