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