pjayofficial commited on
Commit
07a27e6
·
verified ·
1 Parent(s): 3cb59db

Add 2 files

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