|
| 1 | +const canvas = document.getElementById('canvas'); |
| 2 | +const ctx = canvas.getContext('2d'); |
| 3 | +const img = new Image(); |
| 4 | +const fileName = 'edited-image'; |
| 5 | +let brightness = 0; |
| 6 | +let contrast = 0; |
| 7 | +let saturation = 0; |
| 8 | +let vibrance = 0; |
| 9 | + |
| 10 | +// Handle file upload |
| 11 | +document.getElementById('upload-file').addEventListener('change', (e) => { |
| 12 | + const file = e.target.files[0]; |
| 13 | + const reader = new FileReader(); |
| 14 | + |
| 15 | + reader.onload = function (event) { |
| 16 | + img.src = event.target.result; |
| 17 | + }; |
| 18 | + |
| 19 | + if (file) { |
| 20 | + reader.readAsDataURL(file); |
| 21 | + } |
| 22 | +}); |
| 23 | + |
| 24 | +// Helper function to clamp values |
| 25 | +function clamp(value) { |
| 26 | + return Math.min(Math.max(value, 0), 255); |
| 27 | +} |
| 28 | + |
| 29 | +// Draw image to canvas |
| 30 | +img.onload = function () { |
| 31 | + canvas.width = img.width; |
| 32 | + canvas.height = img.height; |
| 33 | + ctx.drawImage(img, 0, 0); |
| 34 | +}; |
| 35 | + |
| 36 | +// Redraw image with current adjustments |
| 37 | +function drawImage() { |
| 38 | + ctx.drawImage(img, 0, 0); |
| 39 | + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| 40 | + const { data } = imageData; |
| 41 | + |
| 42 | + // Apply brightness |
| 43 | + for (let i = 0; i < data.length; i += 4) { |
| 44 | + data[i] = clamp(data[i] + brightness); // Red |
| 45 | + data[i + 1] = clamp(data[i + 1] + brightness); // Green |
| 46 | + data[i + 2] = clamp(data[i + 2] + brightness); // Blue |
| 47 | + } |
| 48 | + |
| 49 | + // Apply contrast |
| 50 | + const factor = (259 * (contrast + 255)) / (255 * (259 - contrast)); |
| 51 | + for (let i = 0; i < data.length; i += 4) { |
| 52 | + data[i] = clamp(factor * (data[i] - 128) + 128); // Red |
| 53 | + data[i + 1] = clamp(factor * (data[i + 1] - 128) + 128); // Green |
| 54 | + data[i + 2] = clamp(factor * (data[i + 2] - 128) + 128); // Blue |
| 55 | + } |
| 56 | + |
| 57 | + // Apply saturation |
| 58 | + for (let i = 0; i < data.length; i += 4) { |
| 59 | + const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; |
| 60 | + data[i] = clamp(avg + (data[i] - avg) * (1 + saturation / 100)); // Red |
| 61 | + data[i + 1] = clamp(avg + (data[i + 1] - avg) * (1 + saturation / 100)); // Green |
| 62 | + data[i + 2] = clamp(avg + (data[i + 2] - avg) * (1 + saturation / 100)); // Blue |
| 63 | + } |
| 64 | + |
| 65 | + // Apply vibrance |
| 66 | + for (let i = 0; i < data.length; i += 4) { |
| 67 | + const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; |
| 68 | + const max = Math.max(data[i], data[i + 1], data[i + 2]); |
| 69 | + const amount = (((max - avg) * 2) / 255) * vibrance; |
| 70 | + data[i] = clamp(data[i] + amount); // Red |
| 71 | + data[i + 1] = clamp(data[i + 1] + amount); // Green |
| 72 | + data[i + 2] = clamp(data[i + 2] + amount); // Blue |
| 73 | + } |
| 74 | + ctx.putImageData(imageData, 0, 0); |
| 75 | +} |
| 76 | + |
| 77 | +// Apply brightness |
| 78 | +function applyBrightness(value) { |
| 79 | + brightness += value; |
| 80 | + drawImage(); |
| 81 | +} |
| 82 | + |
| 83 | +// Apply contrast |
| 84 | +function applyContrast(value) { |
| 85 | + contrast += value; |
| 86 | + drawImage(); |
| 87 | +} |
| 88 | + |
| 89 | +// Apply saturation |
| 90 | +function applySaturation(value) { |
| 91 | + saturation += value; |
| 92 | + drawImage(); |
| 93 | +} |
| 94 | + |
| 95 | +// Apply vibrance |
| 96 | +function applyVibrance(value) { |
| 97 | + vibrance += value; |
| 98 | + drawImage(); |
| 99 | +} |
| 100 | + |
| 101 | +// Apply effect |
| 102 | +function applyEffect(effect) { |
| 103 | + drawImage(); |
| 104 | + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| 105 | + const { data } = imageData; |
| 106 | + |
| 107 | + if (effect === 'vintage') { |
| 108 | + // Vintage effect |
| 109 | + for (let i = 0; i < data.length; i += 4) { |
| 110 | + data[i] = clamp(data[i] * 0.9); // Red |
| 111 | + data[i + 1] = clamp(data[i + 1] * 0.7); // Green |
| 112 | + data[i + 2] = clamp(data[i + 2] * 0.5); // Blue |
| 113 | + } |
| 114 | + } else if (effect === 'lomo') { |
| 115 | + // Lomo effect |
| 116 | + for (let i = 0; i < data.length; i += 4) { |
| 117 | + data[i] = clamp(data[i] * 1.2); // Red |
| 118 | + data[i + 1] = clamp(data[i + 1] * 1.2); // Green |
| 119 | + data[i + 2] = clamp(data[i + 2] * 1.2); // Blue |
| 120 | + } |
| 121 | + } else if (effect === 'clarity') { |
| 122 | + // Clarity effect |
| 123 | + // (Increase contrast) |
| 124 | + applyContrast(20); |
| 125 | + } else if (effect === 'sincity') { |
| 126 | + // Sin City effect |
| 127 | + for (let i = 0; i < data.length; i += 4) { |
| 128 | + const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; |
| 129 | + data[i] = avg; // Red |
| 130 | + data[i + 1] = avg; // Green |
| 131 | + data[i + 2] = avg; // Blue |
| 132 | + } |
| 133 | + } else if (effect === 'crossprocess') { |
| 134 | + // Cross Process effect |
| 135 | + for (let i = 0; i < data.length; i += 4) { |
| 136 | + data[i] = clamp(data[i] * 1.3); // Red |
| 137 | + data[i + 1] = clamp(data[i + 1] * 1.1); // Green |
| 138 | + data[i + 2] = clamp(data[i + 2] * 0.9); // Blue |
| 139 | + } |
| 140 | + } else if (effect === 'pinhole') { |
| 141 | + // Pinhole effect |
| 142 | + for (let i = 0; i < data.length; i += 4) { |
| 143 | + const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; |
| 144 | + data[i] = avg * 0.9; // Red |
| 145 | + data[i + 1] = avg * 0.9; // Green |
| 146 | + data[i + 2] = avg * 0.9; // Blue |
| 147 | + } |
| 148 | + } else if (effect === 'nostalgia') { |
| 149 | + // Nostalgia effect |
| 150 | + for (let i = 0; i < data.length; i += 4) { |
| 151 | + data[i] = clamp(data[i] * 0.9 + 50); // Red |
| 152 | + data[i + 1] = clamp(data[i + 1] * 0.7 + 20); // Green |
| 153 | + data[i + 2] = clamp(data[i + 2] * 0.5 + 10); // Blue |
| 154 | + } |
| 155 | + } else if (effect === 'hermajesty') { |
| 156 | + // Her Majesty effect |
| 157 | + for (let i = 0; i < data.length; i += 4) { |
| 158 | + data[i] = clamp(data[i] * 1.1); // Red |
| 159 | + data[i + 1] = clamp(data[i + 1] * 0.95); // Green |
| 160 | + data[i + 2] = clamp(data[i + 2] * 1.3); // Blue |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + ctx.putImageData(imageData, 0, 0); |
| 165 | +} |
| 166 | + |
| 167 | +// Download image |
| 168 | +function downloadImage() { |
| 169 | + const link = document.createElement('a'); |
| 170 | + link.download = fileName; |
| 171 | + link.href = canvas.toDataURL('image/jpeg'); |
| 172 | + link.click(); |
| 173 | +} |
| 174 | + |
| 175 | +// Revert filters |
| 176 | +function revertFilters() { |
| 177 | + brightness = 0; |
| 178 | + contrast = 0; |
| 179 | + saturation = 0; |
| 180 | + vibrance = 0; |
| 181 | + drawImage(); |
| 182 | +} |
| 183 | + |
| 184 | +// Event listeners for filter buttons |
| 185 | +document |
| 186 | + .querySelector('.brightness-add') |
| 187 | + .addEventListener('click', () => applyBrightness(10)); |
| 188 | +document |
| 189 | + .querySelector('.brightness-remove') |
| 190 | + .addEventListener('click', () => applyBrightness(-10)); |
| 191 | +document |
| 192 | + .querySelector('.contrast-add') |
| 193 | + .addEventListener('click', () => applyContrast(10)); |
| 194 | +document |
| 195 | + .querySelector('.contrast-remove') |
| 196 | + .addEventListener('click', () => applyContrast(-10)); |
| 197 | +document |
| 198 | + .querySelector('.saturation-add') |
| 199 | + .addEventListener('click', () => applySaturation(10)); |
| 200 | +document |
| 201 | + .querySelector('.saturation-remove') |
| 202 | + .addEventListener('click', () => applySaturation(-10)); |
| 203 | +document |
| 204 | + .querySelector('.vibrance-add') |
| 205 | + .addEventListener('click', () => applyVibrance(10)); |
| 206 | +document |
| 207 | + .querySelector('.vibrance-remove') |
| 208 | + .addEventListener('click', () => applyVibrance(-10)); |
| 209 | + |
| 210 | +// Event listeners for effect buttons |
| 211 | +document |
| 212 | + .querySelector('.vintage-add') |
| 213 | + .addEventListener('click', () => applyEffect('vintage')); |
| 214 | +document |
| 215 | + .querySelector('.lomo-add') |
| 216 | + .addEventListener('click', () => applyEffect('lomo')); |
| 217 | +document |
| 218 | + .querySelector('.clarity-add') |
| 219 | + .addEventListener('click', () => applyEffect('clarity')); |
| 220 | +document |
| 221 | + .querySelector('.sincity-add') |
| 222 | + .addEventListener('click', () => applyEffect('sincity')); |
| 223 | + |
| 224 | +document |
| 225 | + .querySelector('.crossprocess-add') |
| 226 | + .addEventListener('click', () => applyEffect('crossprocess')); |
| 227 | +document |
| 228 | + .querySelector('.pinhole-add') |
| 229 | + .addEventListener('click', () => applyEffect('pinhole')); |
| 230 | +document |
| 231 | + .querySelector('.nostalgia-add') |
| 232 | + .addEventListener('click', () => applyEffect('nostalgia')); |
| 233 | +document |
| 234 | + .querySelector('.hermajesty-add') |
| 235 | + .addEventListener('click', () => applyEffect('hermajesty')); |
| 236 | + |
| 237 | +// Event listeners for download and revert buttons |
| 238 | +document |
| 239 | + .getElementById('download-btn') |
| 240 | + .addEventListener('click', downloadImage); |
| 241 | +document.getElementById('revert-btn').addEventListener('click', revertFilters); |
0 commit comments