pjayofficial commited on
Commit
b00fb78
·
verified ·
1 Parent(s): 9f387ad

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +813 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Asasad
3
- emoji: 👀
4
- colorFrom: purple
5
- colorTo: indigo
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: asasad
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: red
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,813 @@
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 Dithering Editor</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
+ .canvas-container {
11
+ position: relative;
12
+ margin: 0 auto;
13
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
14
+ border-radius: 0.5rem;
15
+ overflow: hidden;
16
+ }
17
+ canvas {
18
+ display: block;
19
+ max-width: 100%;
20
+ height: auto;
21
+ }
22
+ .loading-overlay {
23
+ position: absolute;
24
+ top: 0;
25
+ left: 0;
26
+ width: 100%;
27
+ height: 100%;
28
+ background: rgba(0, 0, 0, 0.7);
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ color: white;
33
+ font-size: 1.5rem;
34
+ z-index: 10;
35
+ border-radius: 0.5rem;
36
+ }
37
+ .range-slider {
38
+ -webkit-appearance: none;
39
+ width: 100%;
40
+ height: 8px;
41
+ border-radius: 4px;
42
+ background: #e2e8f0;
43
+ outline: none;
44
+ }
45
+ .range-slider::-webkit-slider-thumb {
46
+ -webkit-appearance: none;
47
+ appearance: none;
48
+ width: 18px;
49
+ height: 18px;
50
+ border-radius: 50%;
51
+ background: #4f46e5;
52
+ cursor: pointer;
53
+ transition: all 0.2s;
54
+ }
55
+ .range-slider::-webkit-slider-thumb:hover {
56
+ transform: scale(1.2);
57
+ background: #6366f1;
58
+ }
59
+ .range-slider::-moz-range-thumb {
60
+ width: 18px;
61
+ height: 18px;
62
+ border-radius: 50%;
63
+ background: #4f46e5;
64
+ cursor: pointer;
65
+ transition: all 0.2s;
66
+ }
67
+ .range-slider::-moz-range-thumb:hover {
68
+ transform: scale(1.2);
69
+ background: #6366f1;
70
+ }
71
+ .algorithm-btn.active {
72
+ background-color: #4f46e5;
73
+ color: white;
74
+ }
75
+ .dropzone {
76
+ border: 2px dashed #cbd5e1;
77
+ transition: all 0.3s;
78
+ }
79
+ .dropzone:hover, .dropzone.dragover {
80
+ border-color: #4f46e5;
81
+ background-color: #f8fafc;
82
+ }
83
+ .animate-btn.active {
84
+ background-color: #10b981;
85
+ color: white;
86
+ }
87
+ @keyframes pulse {
88
+ 0% { transform: scale(1); }
89
+ 50% { transform: scale(1.05); }
90
+ 100% { transform: scale(1); }
91
+ }
92
+ .pulse {
93
+ animation: pulse 1.5s infinite;
94
+ }
95
+ </style>
96
+ </head>
97
+ <body class="bg-gray-50 min-h-screen">
98
+ <div class="container mx-auto px-4 py-8">
99
+ <header class="text-center mb-8">
100
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">Pixel Art Dithering Editor</h1>
101
+ <p class="text-gray-600 max-w-2xl mx-auto">Transform your images with customizable dithering effects. Upload an image, adjust the settings, and download your pixel-perfect creation.</p>
102
+ </header>
103
+
104
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
105
+ <!-- Left Panel - Controls -->
106
+ <div class="bg-white p-6 rounded-xl shadow-md">
107
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Controls</h2>
108
+
109
+ <!-- Image Upload -->
110
+ <div class="mb-6">
111
+ <label class="block text-sm font-medium text-gray-700 mb-2">Upload Image</label>
112
+ <div id="dropzone" class="dropzone rounded-lg p-6 text-center cursor-pointer">
113
+ <input type="file" id="imageInput" accept="image/*" class="hidden">
114
+ <div class="flex flex-col items-center justify-center">
115
+ <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-2"></i>
116
+ <p class="text-gray-500">Drag & drop an image or click to browse</p>
117
+ <p class="text-xs text-gray-400 mt-1">Supports JPG, PNG, GIF</p>
118
+ </div>
119
+ </div>
120
+ </div>
121
+
122
+ <!-- Dithering Algorithm -->
123
+ <div class="mb-6">
124
+ <label class="block text-sm font-medium text-gray-700 mb-2">Dithering Algorithm</label>
125
+ <div class="grid grid-cols-2 gap-2">
126
+ <button data-algorithm="none" class="algorithm-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition">None</button>
127
+ <button data-algorithm="floydSteinberg" class="algorithm-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition">Floyd-Steinberg</button>
128
+ <button data-algorithm="atkinson" class="algorithm-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition">Atkinson</button>
129
+ <button data-algorithm="bayer" class="algorithm-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition">Bayer</button>
130
+ <button data-algorithm="random" class="algorithm-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition">Random</button>
131
+ <button data-algorithm="threshold" class="algorithm-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition">Threshold</button>
132
+ </div>
133
+ </div>
134
+
135
+ <!-- Color Palette -->
136
+ <div class="mb-6">
137
+ <label class="block text-sm font-medium text-gray-700 mb-2">Color Palette</label>
138
+ <div class="grid grid-cols-5 gap-2 mb-3">
139
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background-color: #000000;" data-colors="1"></div>
140
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background-color: #ffffff;" data-colors="2"></div>
141
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #000000, #ffffff);" data-colors="2"></div>
142
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #000000, #808080, #ffffff);" data-colors="3"></div>
143
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #000000, #555555, #aaaaaa, #ffffff);" data-colors="4"></div>
144
+ </div>
145
+ <div class="grid grid-cols-4 gap-2">
146
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #ff0000, #00ff00, #0000ff);" data-colors="3"></div>
147
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff);" data-colors="6"></div>
148
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #e6194b, #3cb44b, #ffe119, #4363d8, #f58231, #911eb4);" data-colors="6"></div>
149
+ <div class="color-option h-8 rounded cursor-pointer border border-gray-300" style="background: linear-gradient(to right, #000000, #654321, #8b0000, #ff8c00, #ffd700, #ffffff);" data-colors="6"></div>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Customization Sliders -->
154
+ <div class="mb-4">
155
+ <label for="thresholdSlider" class="block text-sm font-medium text-gray-700 mb-1">Threshold</label>
156
+ <input type="range" id="thresholdSlider" min="0" max="255" value="128" class="range-slider">
157
+ <div class="flex justify-between text-xs text-gray-500">
158
+ <span>0</span>
159
+ <span>128</span>
160
+ <span>255</span>
161
+ </div>
162
+ </div>
163
+
164
+ <div class="mb-4">
165
+ <label for="intensitySlider" class="block text-sm font-medium text-gray-700 mb-1">Dither Intensity</label>
166
+ <input type="range" id="intensitySlider" min="0" max="100" value="50" class="range-slider">
167
+ <div class="flex justify-between text-xs text-gray-500">
168
+ <span>Low</span>
169
+ <span>Medium</span>
170
+ <span>High</span>
171
+ </div>
172
+ </div>
173
+
174
+ <div class="mb-4">
175
+ <label for="pixelSizeSlider" class="block text-sm font-medium text-gray-700 mb-1">Pixel Size</label>
176
+ <input type="range" id="pixelSizeSlider" min="1" max="20" value="1" class="range-slider">
177
+ <div class="flex justify-between text-xs text-gray-500">
178
+ <span>1px</span>
179
+ <span>10px</span>
180
+ <span>20px</span>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Animation Controls -->
185
+ <div class="mb-6 p-4 bg-gray-50 rounded-lg">
186
+ <label class="block text-sm font-medium text-gray-700 mb-2">Animation</label>
187
+ <div class="flex items-center space-x-4">
188
+ <button id="animateBtn" class="animate-btn py-2 px-4 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
189
+ <i class="fas fa-play mr-2"></i> Animate Threshold
190
+ </button>
191
+ <div class="flex-1">
192
+ <label for="speedSlider" class="block text-xs text-gray-500 mb-1">Speed</label>
193
+ <input type="range" id="speedSlider" min="1" max="10" value="5" class="range-slider">
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ <!-- Download Button -->
199
+ <button id="downloadBtn" class="w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-md transition flex items-center justify-center">
200
+ <i class="fas fa-download mr-2"></i> Download Image
201
+ </button>
202
+ </div>
203
+
204
+ <!-- Middle Panel - Canvas -->
205
+ <div class="lg:col-span-2">
206
+ <div class="canvas-container bg-white p-4 rounded-xl shadow-md">
207
+ <div id="loadingOverlay" class="loading-overlay hidden">
208
+ <div class="text-center">
209
+ <i class="fas fa-spinner fa-spin mb-2"></i>
210
+ <p>Processing image...</p>
211
+ </div>
212
+ </div>
213
+ <canvas id="canvas"></canvas>
214
+ </div>
215
+
216
+ <!-- Presets -->
217
+ <div class="mt-6 bg-white p-6 rounded-xl shadow-md">
218
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Quick Presets</h2>
219
+ <div class="grid grid-cols-2 md:grid-cols-3 gap-3">
220
+ <button data-preset="retro" class="preset-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
221
+ <i class="fas fa-gamepad mr-2 text-purple-500"></i> Retro Game
222
+ </button>
223
+ <button data-preset="newsprint" class="preset-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
224
+ <i class="fas fa-newspaper mr-2 text-gray-500"></i> Newsprint
225
+ </button>
226
+ <button data-preset="poster" class="preset-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
227
+ <i class="fas fa-palette mr-2 text-red-500"></i> Color Poster
228
+ </button>
229
+ <button data-preset="sketch" class="preset-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
230
+ <i class="fas fa-pencil-alt mr-2 text-blue-500"></i> Pencil Sketch
231
+ </button>
232
+ <button data-preset="xray" class="preset-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
233
+ <i class="fas fa-x-ray mr-2 text-green-500"></i> X-Ray
234
+ </button>
235
+ <button data-preset="vintage" class="preset-btn py-2 px-3 rounded-md bg-gray-100 hover:bg-gray-200 transition flex items-center">
236
+ <i class="fas fa-camera-retro mr-2 text-yellow-600"></i> Vintage
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <script>
245
+ document.addEventListener('DOMContentLoaded', function() {
246
+ // Canvas setup
247
+ const canvas = document.getElementById('canvas');
248
+ const ctx = canvas.getContext('2d');
249
+ const loadingOverlay = document.getElementById('loadingOverlay');
250
+
251
+ // State variables
252
+ let originalImage = null;
253
+ let currentAlgorithm = 'floydSteinberg';
254
+ let currentColors = ['#000000', '#ffffff'];
255
+ let threshold = 128;
256
+ let intensity = 50;
257
+ let pixelSize = 1;
258
+ let isAnimating = false;
259
+ let animationId = null;
260
+ let animationSpeed = 5;
261
+ let animationDirection = 1;
262
+
263
+ // UI Elements
264
+ const algorithmBtns = document.querySelectorAll('.algorithm-btn');
265
+ const colorOptions = document.querySelectorAll('.color-option');
266
+ const thresholdSlider = document.getElementById('thresholdSlider');
267
+ const intensitySlider = document.getElementById('intensitySlider');
268
+ const pixelSizeSlider = document.getElementById('pixelSizeSlider');
269
+ const downloadBtn = document.getElementById('downloadBtn');
270
+ const imageInput = document.getElementById('imageInput');
271
+ const dropzone = document.getElementById('dropzone');
272
+ const presetBtns = document.querySelectorAll('.preset-btn');
273
+ const animateBtn = document.getElementById('animateBtn');
274
+ const speedSlider = document.getElementById('speedSlider');
275
+
276
+ // Event Listeners
277
+ imageInput.addEventListener('change', handleImageUpload);
278
+ dropzone.addEventListener('click', () => imageInput.click());
279
+ dropzone.addEventListener('dragover', handleDragOver);
280
+ dropzone.addEventListener('dragleave', handleDragLeave);
281
+ dropzone.addEventListener('drop', handleDrop);
282
+
283
+ algorithmBtns.forEach(btn => {
284
+ btn.addEventListener('click', () => {
285
+ algorithmBtns.forEach(b => b.classList.remove('active'));
286
+ btn.classList.add('active');
287
+ currentAlgorithm = btn.dataset.algorithm;
288
+ applyDithering();
289
+ });
290
+ });
291
+
292
+ colorOptions.forEach(option => {
293
+ option.addEventListener('click', () => {
294
+ const colorCount = parseInt(option.dataset.colors);
295
+ if (colorCount === 1) {
296
+ currentColors = [option.style.backgroundColor];
297
+ } else if (colorCount === 2 && option.style.background.includes('gradient')) {
298
+ currentColors = ['#000000', '#ffffff'];
299
+ } else if (colorCount > 2) {
300
+ // Extract colors from gradient (simplified)
301
+ const gradientColors = option.style.background.match(/#[0-9a-f]{3,6}/gi);
302
+ currentColors = gradientColors || ['#000000', '#ffffff'];
303
+ }
304
+ applyDithering();
305
+ });
306
+ });
307
+
308
+ thresholdSlider.addEventListener('input', () => {
309
+ threshold = parseInt(thresholdSlider.value);
310
+ applyDithering();
311
+ });
312
+
313
+ intensitySlider.addEventListener('input', () => {
314
+ intensity = parseInt(intensitySlider.value);
315
+ applyDithering();
316
+ });
317
+
318
+ pixelSizeSlider.addEventListener('input', () => {
319
+ pixelSize = parseInt(pixelSizeSlider.value);
320
+ applyDithering();
321
+ });
322
+
323
+ downloadBtn.addEventListener('click', downloadImage);
324
+
325
+ presetBtns.forEach(btn => {
326
+ btn.addEventListener('click', () => {
327
+ const preset = btn.dataset.preset;
328
+ applyPreset(preset);
329
+ });
330
+ });
331
+
332
+ animateBtn.addEventListener('click', toggleAnimation);
333
+
334
+ speedSlider.addEventListener('input', () => {
335
+ animationSpeed = parseInt(speedSlider.value);
336
+ });
337
+
338
+ // Initialize with Floyd-Steinberg active
339
+ document.querySelector('[data-algorithm="floydSteinberg"]').classList.add('active');
340
+
341
+ // Functions
342
+ function handleImageUpload(e) {
343
+ const file = e.target.files[0];
344
+ if (!file) return;
345
+
346
+ const reader = new FileReader();
347
+ reader.onload = function(event) {
348
+ const img = new Image();
349
+ img.onload = function() {
350
+ originalImage = img;
351
+ resetCanvas();
352
+ applyDithering();
353
+ };
354
+ img.src = event.target.result;
355
+ };
356
+ reader.readAsDataURL(file);
357
+ }
358
+
359
+ function handleDragOver(e) {
360
+ e.preventDefault();
361
+ e.stopPropagation();
362
+ dropzone.classList.add('dragover');
363
+ }
364
+
365
+ function handleDragLeave(e) {
366
+ e.preventDefault();
367
+ e.stopPropagation();
368
+ dropzone.classList.remove('dragover');
369
+ }
370
+
371
+ function handleDrop(e) {
372
+ e.preventDefault();
373
+ e.stopPropagation();
374
+ dropzone.classList.remove('dragover');
375
+
376
+ const file = e.dataTransfer.files[0];
377
+ if (!file.type.match('image.*')) return;
378
+
379
+ const reader = new FileReader();
380
+ reader.onload = function(event) {
381
+ const img = new Image();
382
+ img.onload = function() {
383
+ originalImage = img;
384
+ resetCanvas();
385
+ applyDithering();
386
+ };
387
+ img.src = event.target.result;
388
+ };
389
+ reader.readAsDataURL(file);
390
+ }
391
+
392
+ function resetCanvas() {
393
+ if (!originalImage) return;
394
+
395
+ // Set canvas dimensions (max 800px width/height while maintaining aspect ratio)
396
+ const maxSize = 800;
397
+ let width = originalImage.width;
398
+ let height = originalImage.height;
399
+
400
+ if (width > height && width > maxSize) {
401
+ height = Math.round((maxSize / width) * height);
402
+ width = maxSize;
403
+ } else if (height > maxSize) {
404
+ width = Math.round((maxSize / height) * width);
405
+ height = maxSize;
406
+ }
407
+
408
+ canvas.width = width;
409
+ canvas.height = height;
410
+
411
+ // Draw original image
412
+ ctx.drawImage(originalImage, 0, 0, width, height);
413
+ }
414
+
415
+ function applyDithering() {
416
+ if (!originalImage) return;
417
+
418
+ showLoading();
419
+
420
+ // Use setTimeout to allow UI to update before heavy processing
421
+ setTimeout(() => {
422
+ try {
423
+ // Create image data
424
+ const width = canvas.width;
425
+ const height = canvas.height;
426
+
427
+ // Get original image data
428
+ ctx.drawImage(originalImage, 0, 0, width, height);
429
+ const imageData = ctx.getImageData(0, 0, width, height);
430
+ const data = imageData.data;
431
+
432
+ // Convert to grayscale first
433
+ for (let i = 0; i < data.length; i += 4) {
434
+ const r = data[i];
435
+ const g = data[i + 1];
436
+ const b = data[i + 2];
437
+ const gray = 0.299 * r + 0.587 * g + 0.114 * b;
438
+ data[i] = data[i + 1] = data[i + 2] = gray;
439
+ }
440
+
441
+ // Apply selected dithering algorithm
442
+ switch (currentAlgorithm) {
443
+ case 'floydSteinberg':
444
+ floydSteinbergDither(data, width, height);
445
+ break;
446
+ case 'atkinson':
447
+ atkinsonDither(data, width, height);
448
+ break;
449
+ case 'bayer':
450
+ bayerDither(data, width, height);
451
+ break;
452
+ case 'random':
453
+ randomDither(data, width, height);
454
+ break;
455
+ case 'threshold':
456
+ thresholdDither(data, width, height);
457
+ break;
458
+ default:
459
+ // No dithering
460
+ break;
461
+ }
462
+
463
+ // Apply color palette
464
+ applyColorPalette(data, currentColors);
465
+
466
+ // Apply pixelation if needed
467
+ if (pixelSize > 1) {
468
+ pixelateImage(data, width, height, pixelSize);
469
+ }
470
+
471
+ // Put the modified data back
472
+ ctx.putImageData(imageData, 0, 0);
473
+
474
+ } catch (error) {
475
+ console.error('Error applying dithering:', error);
476
+ } finally {
477
+ hideLoading();
478
+ }
479
+ }, 100);
480
+ }
481
+
482
+ function floydSteinbergDither(data, width, height) {
483
+ const adjustedThreshold = threshold * (intensity / 50);
484
+
485
+ for (let y = 0; y < height; y++) {
486
+ for (let x = 0; x < width; x++) {
487
+ const idx = (y * width + x) * 4;
488
+ const oldPixel = data[idx];
489
+ const newPixel = oldPixel < adjustedThreshold ? 0 : 255;
490
+ data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
491
+
492
+ const quantError = oldPixel - newPixel;
493
+
494
+ // Distribute error to neighboring pixels
495
+ if (x + 1 < width) {
496
+ const idxRight = idx + 4;
497
+ data[idxRight] += quantError * 7 / 16;
498
+ }
499
+ if (x > 0 && y + 1 < height) {
500
+ const idxDownLeft = idx + width * 4 - 4;
501
+ data[idxDownLeft] += quantError * 3 / 16;
502
+ }
503
+ if (y + 1 < height) {
504
+ const idxDown = idx + width * 4;
505
+ data[idxDown] += quantError * 5 / 16;
506
+ }
507
+ if (x + 1 < width && y + 1 < height) {
508
+ const idxDownRight = idx + width * 4 + 4;
509
+ data[idxDownRight] += quantError * 1 / 16;
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ function atkinsonDither(data, width, height) {
516
+ const adjustedThreshold = threshold * (intensity / 50);
517
+
518
+ for (let y = 0; y < height; y++) {
519
+ for (let x = 0; x < width; x++) {
520
+ const idx = (y * width + x) * 4;
521
+ const oldPixel = data[idx];
522
+ const newPixel = oldPixel < adjustedThreshold ? 0 : 255;
523
+ data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
524
+
525
+ const quantError = oldPixel - newPixel;
526
+ const errorPart = quantError / 8;
527
+
528
+ // Distribute error to neighboring pixels (Atkinson's algorithm)
529
+ if (x + 1 < width) {
530
+ const idxRight = idx + 4;
531
+ data[idxRight] += errorPart;
532
+ }
533
+ if (x + 2 < width) {
534
+ const idxRight2 = idx + 8;
535
+ data[idxRight2] += errorPart;
536
+ }
537
+ if (x > 0 && y + 1 < height) {
538
+ const idxDownLeft = idx + width * 4 - 4;
539
+ data[idxDownLeft] += errorPart;
540
+ }
541
+ if (y + 1 < height) {
542
+ const idxDown = idx + width * 4;
543
+ data[idxDown] += errorPart;
544
+ }
545
+ if (x + 1 < width && y + 1 < height) {
546
+ const idxDownRight = idx + width * 4 + 4;
547
+ data[idxDownRight] += errorPart;
548
+ }
549
+ if (y + 2 < height) {
550
+ const idxDown2 = idx + width * 8;
551
+ data[idxDown2] += errorPart;
552
+ }
553
+ }
554
+ }
555
+ }
556
+
557
+ function bayerDither(data, width, height) {
558
+ // 4x4 Bayer matrix
559
+ const bayerMatrix = [
560
+ [ 1, 9, 3, 11 ],
561
+ [ 13, 5, 15, 7 ],
562
+ [ 4, 12, 2, 10 ],
563
+ [ 16, 8, 14, 6 ]
564
+ ];
565
+
566
+ const adjustedThreshold = threshold * (intensity / 50) * (16 / 255);
567
+
568
+ for (let y = 0; y < height; y++) {
569
+ for (let x = 0; x < width; x++) {
570
+ const idx = (y * width + x) * 4;
571
+ const gray = data[idx];
572
+ const thresholdValue = bayerMatrix[y % 4][x % 4] * adjustedThreshold;
573
+
574
+ data[idx] = data[idx + 1] = data[idx + 2] = gray > thresholdValue ? 255 : 0;
575
+ }
576
+ }
577
+ }
578
+
579
+ function randomDither(data, width, height) {
580
+ const adjustedThreshold = threshold * (intensity / 50);
581
+
582
+ for (let i = 0; i < data.length; i += 4) {
583
+ const gray = data[i];
584
+ const randomThreshold = adjustedThreshold + (Math.random() * 50 - 25);
585
+ data[i] = data[i + 1] = data[i + 2] = gray < randomThreshold ? 0 : 255;
586
+ }
587
+ }
588
+
589
+ function thresholdDither(data, width, height) {
590
+ const adjustedThreshold = threshold * (intensity / 50);
591
+
592
+ for (let i = 0; i < data.length; i += 4) {
593
+ const gray = data[i];
594
+ data[i] = data[i + 1] = data[i + 2] = gray < adjustedThreshold ? 0 : 255;
595
+ }
596
+ }
597
+
598
+ function applyColorPalette(data, colors) {
599
+ if (colors.length === 1) return; // No color change needed
600
+
601
+ // Convert hex colors to RGB
602
+ const rgbColors = colors.map(hexToRgb);
603
+
604
+ for (let i = 0; i < data.length; i += 4) {
605
+ const gray = data[i];
606
+
607
+ // Find closest color in palette based on grayscale value
608
+ const colorIndex = Math.floor((gray / 255) * (rgbColors.length - 1));
609
+ const color = rgbColors[colorIndex];
610
+
611
+ data[i] = color.r;
612
+ data[i + 1] = color.g;
613
+ data[i + 2] = color.b;
614
+ }
615
+ }
616
+
617
+ function pixelateImage(data, width, height, pixelSize) {
618
+ if (pixelSize <= 1) return;
619
+
620
+ // Create a temporary canvas for pixelation
621
+ const tempCanvas = document.createElement('canvas');
622
+ tempCanvas.width = width;
623
+ tempCanvas.height = height;
624
+ const tempCtx = tempCanvas.getContext('2d');
625
+
626
+ // Put the current image data to temp canvas
627
+ const tempImageData = new ImageData(new Uint8ClampedArray(data), width, height);
628
+ tempCtx.putImageData(tempImageData, 0, 0);
629
+
630
+ // Calculate new dimensions
631
+ const pixelatedWidth = Math.ceil(width / pixelSize);
632
+ const pixelatedHeight = Math.ceil(height / pixelSize);
633
+
634
+ // Draw small version
635
+ tempCtx.imageSmoothingEnabled = false;
636
+ tempCtx.drawImage(tempCanvas, 0, 0, pixelatedWidth, pixelatedHeight);
637
+
638
+ // Draw back to original size
639
+ ctx.clearRect(0, 0, width, height);
640
+ ctx.imageSmoothingEnabled = false;
641
+ ctx.drawImage(tempCanvas, 0, 0, pixelatedWidth, pixelatedHeight, 0, 0, width, height);
642
+
643
+ // Get the pixelated data
644
+ const pixelatedData = ctx.getImageData(0, 0, width, height).data;
645
+
646
+ // Copy back to original data array
647
+ for (let i = 0; i < data.length; i++) {
648
+ data[i] = pixelatedData[i];
649
+ }
650
+ }
651
+
652
+ function hexToRgb(hex) {
653
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
654
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
655
+ hex = hex.replace(shorthandRegex, function(m, r, g, b) {
656
+ return r + r + g + g + b + b;
657
+ });
658
+
659
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
660
+ return result ? {
661
+ r: parseInt(result[1], 16),
662
+ g: parseInt(result[2], 16),
663
+ b: parseInt(result[3], 16)
664
+ } : { r: 0, g: 0, b: 0 };
665
+ }
666
+
667
+ function downloadImage() {
668
+ if (!originalImage) {
669
+ alert('Please upload an image first!');
670
+ return;
671
+ }
672
+
673
+ const link = document.createElement('a');
674
+ link.download = 'dithered-image.png';
675
+ link.href = canvas.toDataURL('image/png');
676
+ link.click();
677
+ }
678
+
679
+ function showLoading() {
680
+ loadingOverlay.classList.remove('hidden');
681
+ }
682
+
683
+ function hideLoading() {
684
+ loadingOverlay.classList.add('hidden');
685
+ }
686
+
687
+ function applyPreset(preset) {
688
+ switch (preset) {
689
+ case 'retro':
690
+ currentAlgorithm = 'floydSteinberg';
691
+ currentColors = ['#000000', '#ffffff'];
692
+ threshold = 128;
693
+ intensity = 60;
694
+ pixelSize = 4;
695
+ break;
696
+ case 'newsprint':
697
+ currentAlgorithm = 'bayer';
698
+ currentColors = ['#000000', '#ffffff'];
699
+ threshold = 100;
700
+ intensity = 70;
701
+ pixelSize = 1;
702
+ break;
703
+ case 'poster':
704
+ currentAlgorithm = 'atkinson';
705
+ currentColors = ['#ff0000', #00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'];
706
+ threshold = 128;
707
+ intensity = 80;
708
+ pixelSize = 2;
709
+ break;
710
+ case 'sketch':
711
+ currentAlgorithm = 'random';
712
+ currentColors = ['#000000', '#ffffff'];
713
+ threshold = 150;
714
+ intensity = 40;
715
+ pixelSize = 1;
716
+ break;
717
+ case 'xray':
718
+ currentAlgorithm = 'threshold';
719
+ currentColors = ['#000000', '#ffffff'];
720
+ threshold = 200;
721
+ intensity = 90;
722
+ pixelSize = 1;
723
+ break;
724
+ case 'vintage':
725
+ currentAlgorithm = 'floydSteinberg';
726
+ currentColors = ['#654321', '#8b0000', '#ff8c00', '#ffd700', '#ffffff'];
727
+ threshold = 100;
728
+ intensity = 50;
729
+ pixelSize = 3;
730
+ break;
731
+ }
732
+
733
+ // Update UI to match preset
734
+ algorithmBtns.forEach(btn => {
735
+ btn.classList.remove('active');
736
+ if (btn.dataset.algorithm === currentAlgorithm) {
737
+ btn.classList.add('active');
738
+ }
739
+ });
740
+
741
+ thresholdSlider.value = threshold;
742
+ intensitySlider.value = intensity;
743
+ pixelSizeSlider.value = pixelSize;
744
+
745
+ applyDithering();
746
+ }
747
+
748
+ function toggleAnimation() {
749
+ if (isAnimating) {
750
+ stopAnimation();
751
+ } else {
752
+ startAnimation();
753
+ }
754
+ }
755
+
756
+ function startAnimation() {
757
+ if (!originalImage) {
758
+ alert('Please upload an image first!');
759
+ return;
760
+ }
761
+
762
+ isAnimating = true;
763
+ animateBtn.classList.add('active', 'pulse');
764
+ animateBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Animation';
765
+
766
+ // Start animation loop
767
+ animateThreshold();
768
+ }
769
+
770
+ function stopAnimation() {
771
+ isAnimating = false;
772
+ cancelAnimationFrame(animationId);
773
+ animateBtn.classList.remove('active', 'pulse');
774
+ animateBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Animate Threshold';
775
+ }
776
+
777
+ function animateThreshold() {
778
+ if (!isAnimating) return;
779
+
780
+ // Update threshold value
781
+ threshold += animationDirection * (animationSpeed / 2);
782
+
783
+ // Reverse direction at boundaries
784
+ if (threshold >= 255) {
785
+ threshold = 255;
786
+ animationDirection = -1;
787
+ } else if (threshold <= 0) {
788
+ threshold = 0;
789
+ animationDirection = 1;
790
+ }
791
+
792
+ // Update UI
793
+ thresholdSlider.value = threshold;
794
+
795
+ // Apply dithering with new threshold
796
+ applyDithering();
797
+
798
+ // Continue animation
799
+ animationId = requestAnimationFrame(animateThreshold);
800
+ }
801
+
802
+ // Load a sample image on startup
803
+ const sampleImage = new Image();
804
+ sampleImage.onload = function() {
805
+ originalImage = sampleImage;
806
+ resetCanvas();
807
+ applyDithering();
808
+ };
809
+ sampleImage.src = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MDAiIGhlaWdodD0iNjAwIiB2aWV3Qm94PSIwIDAgODAwIDYwMCI+PHJlY3Qgd2lkdGg9IjgwMCIgaGVpZ2h0PSI2MDAiIGZpbGw9IiNmNWY1ZjUiLz48Y2lyY2xlIGN4PSI0MDAiIGN5PSIzMDAiIHI9IjI1MCIgZmlsbD0iI2Q0ZDVkNCIvPjxjaXJjbGUgY3g9IjMwMCIgY3k9IjIwMCIgcj0iNTAiIGZpbGw9IiNhYWEiLz48Y2lyY2xlIGN4PSI1MDAiIGN5PSIyMDAiIHI9IjUwIiBmaWxsPSIjYWFhIi8+PGNpcmNsZSBjeD0iMzUwIiBjeT0iMjUwIiByPSIxMDAiIGZpbGw9IiNhYWEiLz48Y2lyY2xlIGN4PSI0NTAiIGN5PSIyNTAiIHI9IjEwMCIgZmlsbD0iI2FhYSIvPjxwYXRoIGQ9Ik0zMDAgNDAwIHEgMTAwIDEwMCAyMDAgMCIgc3Ryb2tlPSIjYWFhIiBzdHJva2Utd2lkdGg9IjEwIiBmaWxsPSJub25lIi8+PC9zdmc+';
810
+ });
811
+ </script>
812
+ <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=pjayofficial/asasad" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
813
+ </html>