ocirema commited on
Commit
af76036
·
verified ·
1 Parent(s): 00a2e8f

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +275 -477
index.html CHANGED
@@ -1,30 +1,39 @@
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 Editor 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
  .slider-container {
11
  width: 100%;
12
- margin: 10px 0;
13
  }
14
- .slider-container input[type="range"] {
 
15
  width: 100%;
16
  height: 8px;
17
- -webkit-appearance: none;
18
- background: #ddd;
19
- border-radius: 5px;
20
  outline: none;
21
  }
22
- .slider-container input[type="range"]::-webkit-slider-thumb {
23
  -webkit-appearance: none;
 
24
  width: 18px;
25
  height: 18px;
26
- background: #4f46e5;
27
  border-radius: 50%;
 
28
  cursor: pointer;
29
  }
30
  .filter-option {
@@ -32,568 +41,357 @@
32
  }
33
  .filter-option:hover {
34
  transform: scale(1.05);
35
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
36
  }
37
  .filter-option.active {
38
- border: 3px solid #4f46e5;
39
- }
40
- canvas {
41
- max-width: 100%;
42
- max-height: 70vh;
43
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
44
- }
45
- .tool-btn {
46
- transition: all 0.2s ease;
47
- }
48
- .tool-btn:hover {
49
- background-color: #e0e7ff;
50
- }
51
- .tool-btn.active {
52
- background-color: #4f46e5;
53
- color: white;
54
  }
55
  </style>
56
  </head>
57
- <body class="bg-gray-100 font-sans">
58
  <div class="container mx-auto px-4 py-8">
59
  <header class="mb-8 text-center">
60
- <h1 class="text-4xl font-bold text-indigo-700 mb-2">Image Editor Pro</h1>
61
- <p class="text-gray-600">Edit your images with powerful tools right in your browser</p>
62
  </header>
63
 
64
- <div class="bg-white rounded-xl shadow-lg overflow-hidden">
65
- <div class="flex flex-col lg:flex-row">
66
- <!-- Sidebar with tools -->
67
- <div class="w-full lg:w-64 bg-gray-50 p-4 border-r border-gray-200">
68
- <div class="mb-6">
69
- <h3 class="font-semibold text-gray-700 mb-3 flex items-center">
70
- <i class="fas fa-upload mr-2"></i> Upload Image
71
- </h3>
72
- <input type="file" id="imageInput" accept="image/*" class="hidden">
73
- <label for="imageInput" class="block w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md text-center cursor-pointer transition">
74
- Choose Image
75
  </label>
 
 
 
 
 
 
76
  </div>
77
-
78
- <div class="mb-6">
79
- <h3 class="font-semibold text-gray-700 mb-3 flex items-center">
80
- <i class="fas fa-sliders-h mr-2"></i> Adjustments
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  </h3>
 
82
  <div class="space-y-4">
83
- <div class="slider-container">
84
- <label class="block text-sm text-gray-600 mb-1">Brightness</label>
85
- <input type="range" id="brightness" min="-100" max="100" value="0">
 
 
 
 
 
 
 
86
  </div>
87
- <div class="slider-container">
88
- <label class="block text-sm text-gray-600 mb-1">Contrast</label>
89
- <input type="range" id="contrast" min="-100" max="100" value="0">
 
 
 
 
 
 
 
 
90
  </div>
91
- <div class="slider-container">
92
- <label class="block text-sm text-gray-600 mb-1">Saturation</label>
93
- <input type="range" id="saturation" min="-100" max="100" value="0">
 
 
 
 
 
 
 
 
94
  </div>
95
- <div class="slider-container">
96
- <label class="block text-sm text-gray-600 mb-1">Temperature</label>
97
- <input type="range" id="temperature" min="-100" max="100" value="0">
 
 
 
 
 
 
 
 
98
  </div>
99
  </div>
100
  </div>
101
-
102
- <div class="mb-6">
103
- <h3 class="font-semibold text-gray-700 mb-3 flex items-center">
104
- <i class="fas fa-magic mr-2"></i> Filters
 
105
  </h3>
 
106
  <div class="grid grid-cols-3 gap-2">
107
- <div class="filter-option p-2 rounded-md cursor-pointer" data-filter="none">
108
- <div class="w-full h-12 bg-gray-200 rounded-md"></div>
109
- <p class="text-xs text-center mt-1">Original</p>
110
  </div>
111
- <div class="filter-option p-2 rounded-md cursor-pointer" data-filter="grayscale">
112
- <div class="w-full h-12 bg-gray-400 rounded-md"></div>
113
- <p class="text-xs text-center mt-1">Grayscale</p>
114
  </div>
115
- <div class="filter-option p-2 rounded-md cursor-pointer" data-filter="sepia">
116
- <div class="w-full h-12 bg-amber-200 rounded-md"></div>
117
- <p class="text-xs text-center mt-1">Sepia</p>
118
  </div>
119
- <div class="filter-option p-2 rounded-md cursor-pointer" data-filter="invert">
120
- <div class="w-full h-12 bg-gray-800 rounded-md"></div>
121
- <p class="text-xs text-center mt-1">Invert</p>
122
  </div>
123
- <div class="filter-option p-2 rounded-md cursor-pointer" data-filter="vintage">
124
- <div class="w-full h-12 bg-yellow-700 rounded-md"></div>
125
- <p class="text-xs text-center mt-1">Vintage</p>
126
  </div>
127
- <div class="filter-option p-2 rounded-md cursor-pointer" data-filter="blur">
128
- <div class="w-full h-12 bg-blue-100 rounded-md"></div>
129
- <p class="text-xs text-center mt-1">Blur</p>
130
  </div>
131
  </div>
132
  </div>
133
  </div>
134
-
135
- <!-- Main content area -->
136
- <div class="flex-1 p-6">
137
- <div class="flex justify-between items-center mb-6">
138
- <h2 class="text-xl font-semibold text-gray-800">Editor</h2>
139
- <div class="flex space-x-2">
140
- <button id="resetBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition">
141
- <i class="fas fa-undo mr-2"></i>Reset
142
- </button>
143
- <button id="downloadBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition">
144
- <i class="fas fa-download mr-2"></i>Download
145
- </button>
146
- </div>
147
- </div>
148
-
149
- <div class="flex justify-center mb-6">
150
- <div class="relative">
151
- <canvas id="canvas" class="border border-gray-200 rounded-lg"></canvas>
152
- <div id="cropOverlay" class="absolute inset-0 border-2 border-white border-dashed pointer-events-none hidden"></div>
153
- </div>
154
- </div>
155
-
156
- <div class="bg-gray-50 p-4 rounded-lg">
157
- <h3 class="font-semibold text-gray-700 mb-3 flex items-center">
158
- <i class="fas fa-cut mr-2"></i> Crop & Rotate
159
- </h3>
160
- <div class="flex flex-wrap gap-2">
161
- <button id="cropBtn" class="tool-btn bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition">
162
- <i class="fas fa-crop mr-2"></i>Crop
163
- </button>
164
- <button id="rotateLeftBtn" class="tool-btn bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition">
165
- <i class="fas fa-undo mr-2"></i>Rotate Left
166
- </button>
167
- <button id="rotateRightBtn" class="tool-btn bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition">
168
- <i class="fas fa-redo mr-2"></i>Rotate Right
169
- </button>
170
- <button id="flipHorizontalBtn" class="tool-btn bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition">
171
- <i class="fas fa-arrows-alt-h mr-2"></i>Flip H
172
- </button>
173
- <button id="flipVerticalBtn" class="tool-btn bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition">
174
- <i class="fas fa-arrows-alt-v mr-2"></i>Flip V
175
- </button>
176
- </div>
177
- </div>
178
- </div>
179
  </div>
180
  </div>
181
-
182
- <footer class="mt-12 text-center text-gray-500 text-sm">
183
- <p>Image Editor Pro &copy; 2023 - All rights reserved</p>
184
  </footer>
185
  </div>
186
 
187
  <script>
188
  document.addEventListener('DOMContentLoaded', function() {
189
- // Canvas setup
190
- const canvas = document.getElementById('canvas');
 
 
 
191
  const ctx = canvas.getContext('2d');
192
- let image = null;
193
- let originalImageData = null;
194
- let isCropping = false;
195
- let cropStartX, cropStartY, cropEndX, cropEndY;
196
- let isDragging = false;
197
-
198
- // UI elements
199
- const imageInput = document.getElementById('imageInput');
200
- const brightnessSlider = document.getElementById('brightness');
201
- const contrastSlider = document.getElementById('contrast');
202
- const saturationSlider = document.getElementById('saturation');
203
- const temperatureSlider = document.getElementById('temperature');
204
- const filterOptions = document.querySelectorAll('.filter-option');
205
- const resetBtn = document.getElementById('resetBtn');
206
  const downloadBtn = document.getElementById('downloadBtn');
207
- const cropBtn = document.getElementById('cropBtn');
 
 
 
 
 
 
 
208
  const rotateLeftBtn = document.getElementById('rotateLeftBtn');
209
  const rotateRightBtn = document.getElementById('rotateRightBtn');
210
- const flipHorizontalBtn = document.getElementById('flipHorizontalBtn');
211
- const flipVerticalBtn = document.getElementById('flipVerticalBtn');
212
- const cropOverlay = document.getElementById('cropOverlay');
213
-
214
- // Current filter state
 
 
215
  let currentFilter = 'none';
216
- let adjustments = {
217
- brightness: 0,
218
- contrast: 0,
219
- saturation: 0,
220
- temperature: 0
221
- };
222
-
223
- // Load image
224
- imageInput.addEventListener('change', function(e) {
225
  if (e.target.files && e.target.files[0]) {
226
  const reader = new FileReader();
 
227
  reader.onload = function(event) {
228
- image = new Image();
229
- image.onload = function() {
230
- // Set canvas dimensions to match image
231
- canvas.width = image.width;
232
- canvas.height = image.height;
233
-
234
- // Draw the image
235
- ctx.drawImage(image, 0, 0);
236
 
237
- // Store original image data
238
- originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
239
-
240
- // Reset adjustments
241
- resetAdjustments();
242
  };
243
- image.src = event.target.result;
 
244
  };
 
245
  reader.readAsDataURL(e.target.files[0]);
246
  }
247
  });
248
-
249
- // Apply adjustments
250
- function applyAdjustments() {
251
- if (!image) return;
252
-
253
- // Reset to original image
254
- ctx.putImageData(originalImageData, 0, 0);
255
 
256
- // Get current image data
257
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
258
- const data = imageData.data;
259
 
260
- // Apply brightness
261
- if (adjustments.brightness !== 0) {
262
- const brightness = adjustments.brightness / 100;
263
- for (let i = 0; i < data.length; i += 4) {
264
- data[i] = data[i] + (255 * brightness);
265
- data[i + 1] = data[i + 1] + (255 * brightness);
266
- data[i + 2] = data[i + 2] + (255 * brightness);
267
- }
268
  }
269
 
270
- // Apply contrast
271
- if (adjustments.contrast !== 0) {
272
- const contrast = (adjustments.contrast / 100) + 1;
273
- const intercept = 128 * (1 - contrast);
274
- for (let i = 0; i < data.length; i += 4) {
275
- data[i] = data[i] * contrast + intercept;
276
- data[i + 1] = data[i + 1] * contrast + intercept;
277
- data[i + 2] = data[i + 2] * contrast + intercept;
278
- }
279
  }
280
 
281
- // Apply saturation
282
- if (adjustments.saturation !== 0) {
283
- const saturation = adjustments.saturation / 100;
284
- for (let i = 0; i < data.length; i += 4) {
285
- const r = data[i];
286
- const g = data[i + 1];
287
- const b = data[i + 2];
288
- const gray = 0.2989 * r + 0.5870 * g + 0.1140 * b;
289
- data[i] = gray + (r - gray) * (1 + saturation);
290
- data[i + 1] = gray + (g - gray) * (1 + saturation);
291
- data[i + 2] = gray + (b - gray) * (1 + saturation);
292
- }
293
- }
294
 
295
- // Apply temperature
296
- if (adjustments.temperature !== 0) {
297
- const temperature = adjustments.temperature / 100;
298
- for (let i = 0; i < data.length; i += 4) {
299
- data[i] = data[i] + (255 * temperature);
300
- data[i + 2] = data[i + 2] - (255 * temperature);
301
- }
302
- }
303
 
304
- // Apply filter
305
- applyFilter(data);
 
 
 
 
 
 
306
 
307
- // Put the modified data back
308
- ctx.putImageData(imageData, 0, 0);
 
 
309
  }
310
 
311
- // Apply selected filter
312
- function applyFilter(data) {
 
 
 
 
 
 
 
 
 
 
 
 
313
  switch(currentFilter) {
314
  case 'grayscale':
315
- for (let i = 0; i < data.length; i += 4) {
316
- const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
317
- data[i] = avg;
318
- data[i + 1] = avg;
319
- data[i + 2] = avg;
320
- }
321
  break;
322
  case 'sepia':
323
- for (let i = 0; i < data.length; i += 4) {
324
- const r = data[i];
325
- const g = data[i + 1];
326
- const b = data[i + 2];
327
- data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
328
- data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
329
- data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
330
- }
331
  break;
332
  case 'invert':
333
- for (let i = 0; i < data.length; i += 4) {
334
- data[i] = 255 - data[i];
335
- data[i + 1] = 255 - data[i + 1];
336
- data[i + 2] = 255 - data[i + 2];
337
- }
338
- break;
339
- case 'vintage':
340
- for (let i = 0; i < data.length; i += 4) {
341
- const r = data[i];
342
- const g = data[i + 1];
343
- const b = data[i + 2];
344
- data[i] = (r * 0.3) + (g * 0.59) + (b * 0.11);
345
- data[i + 1] = (r * 0.3) + (g * 0.59) + (b * 0.11);
346
- data[i + 2] = (r * 0.3) + (g * 0.59) + (b * 0.11);
347
- data[i] += 40;
348
- data[i + 1] += 20;
349
- data[i + 2] -= 20;
350
- }
351
  break;
352
  case 'blur':
353
- // Simple box blur (for demo purposes)
354
- const tempCanvas = document.createElement('canvas');
355
- const tempCtx = tempCanvas.getContext('2d');
356
- tempCanvas.width = canvas.width;
357
- tempCanvas.height = canvas.height;
358
- tempCtx.putImageData(new ImageData(data, canvas.width), 0, 0);
359
-
360
- ctx.filter = 'blur(4px)';
361
- ctx.drawImage(tempCanvas, 0, 0);
362
- ctx.filter = 'none';
363
- return;
364
  }
365
- }
366
-
367
- // Reset all adjustments
368
- function resetAdjustments() {
369
- brightnessSlider.value = 0;
370
- contrastSlider.value = 0;
371
- saturationSlider.value = 0;
372
- temperatureSlider.value = 0;
373
 
374
- adjustments = {
375
- brightness: 0,
376
- contrast: 0,
377
- saturation: 0,
378
- temperature: 0
379
- };
380
-
381
- // Reset filter
382
- currentFilter = 'none';
383
- filterOptions.forEach(option => {
384
- option.classList.remove('active');
385
- if (option.dataset.filter === 'none') {
386
- option.classList.add('active');
387
- }
388
- });
389
-
390
- // Redraw original image
391
- if (originalImageData) {
392
- ctx.putImageData(originalImageData, 0, 0);
393
- }
394
  }
395
 
396
- // Event listeners for adjustments
397
- brightnessSlider.addEventListener('input', function() {
398
- adjustments.brightness = parseInt(this.value);
399
- applyAdjustments();
400
- });
401
 
402
- contrastSlider.addEventListener('input', function() {
403
- adjustments.contrast = parseInt(this.value);
404
- applyAdjustments();
405
- });
406
-
407
- saturationSlider.addEventListener('input', function() {
408
- adjustments.saturation = parseInt(this.value);
409
- applyAdjustments();
410
  });
411
 
412
- temperatureSlider.addEventListener('input', function() {
413
- adjustments.temperature = parseInt(this.value);
414
- applyAdjustments();
415
  });
416
 
417
- // Filter selection
418
  filterOptions.forEach(option => {
419
  option.addEventListener('click', function() {
 
420
  filterOptions.forEach(opt => opt.classList.remove('active'));
 
 
421
  this.classList.add('active');
 
 
422
  currentFilter = this.dataset.filter;
423
- applyAdjustments();
424
  });
425
  });
426
 
427
- // Reset button
428
- resetBtn.addEventListener('click', resetAdjustments);
429
-
430
- // Download button
431
- downloadBtn.addEventListener('click', function() {
432
- if (!image) return;
433
 
434
- const link = document.createElement('a');
435
- link.download = 'edited-image.png';
436
- link.href = canvas.toDataURL('image/png');
437
- link.click();
438
- });
439
-
440
- // Crop functionality
441
- cropBtn.addEventListener('click', function() {
442
- isCropping = !isCropping;
443
- cropBtn.classList.toggle('active', isCropping);
444
- cropOverlay.classList.toggle('hidden', !isCropping);
445
 
446
- if (!isCropping) {
447
- // Perform the crop
448
- if (cropStartX && cropStartY && cropEndX && cropEndY) {
449
- const x = Math.min(cropStartX, cropEndX);
450
- const y = Math.min(cropStartY, cropEndY);
451
- const width = Math.abs(cropEndX - cropStartX);
452
- const height = Math.abs(cropEndY - cropStartY);
453
-
454
- if (width > 10 && height > 10) { // Minimum size
455
- // Create a temporary canvas for the cropped image
456
- const tempCanvas = document.createElement('canvas');
457
- const tempCtx = tempCanvas.getContext('2d');
458
- tempCanvas.width = width;
459
- tempCanvas.height = height;
460
-
461
- // Draw the cropped portion
462
- tempCtx.drawImage(canvas, x, y, width, height, 0, 0, width, height);
463
-
464
- // Resize the main canvas
465
- canvas.width = width;
466
- canvas.height = height;
467
-
468
- // Draw the cropped image
469
- ctx.drawImage(tempCanvas, 0, 0);
470
-
471
- // Update the original image data
472
- originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
473
- }
474
- }
475
-
476
- // Reset crop coordinates
477
- cropStartX = cropStartY = cropEndX = cropEndY = null;
478
- }
479
- });
480
-
481
- // Canvas mouse events for cropping
482
- canvas.addEventListener('mousedown', function(e) {
483
- if (!isCropping) return;
484
 
485
- const rect = canvas.getBoundingClientRect();
486
- cropStartX = e.clientX - rect.left;
487
- cropStartY = e.clientY - rect.top;
488
- isDragging = true;
489
  });
490
 
491
- canvas.addEventListener('mousemove', function(e) {
492
- if (!isCropping || !isDragging) return;
493
-
494
- const rect = canvas.getBoundingClientRect();
495
- cropEndX = e.clientX - rect.left;
496
- cropEndY = e.clientY - rect.top;
497
-
498
- // Update crop overlay
499
- if (cropStartX && cropStartY) {
500
- const left = Math.min(cropStartX, cropEndX);
501
- const top = Math.min(cropStartY, cropEndY);
502
- const width = Math.abs(cropEndX - cropStartX);
503
- const height = Math.abs(cropEndY - cropStartY);
504
-
505
- cropOverlay.style.left = `${left}px`;
506
- cropOverlay.style.top = `${top}px`;
507
- cropOverlay.style.width = `${width}px`;
508
- cropOverlay.style.height = `${height}px`;
509
  }
510
- });
511
-
512
- canvas.addEventListener('mouseup', function() {
513
- isDragging = false;
514
- });
515
-
516
- canvas.addEventListener('mouseleave', function() {
517
- isDragging = false;
518
- });
519
-
520
- // Rotate and flip buttons
521
- rotateLeftBtn.addEventListener('click', function() {
522
- if (!image) return;
523
-
524
- // Create a temporary canvas
525
- const tempCanvas = document.createElement('canvas');
526
- const tempCtx = tempCanvas.getContext('2d');
527
-
528
- // Swap width and height
529
- tempCanvas.width = canvas.height;
530
- tempCanvas.height = canvas.width;
531
-
532
- // Rotate and draw
533
- tempCtx.translate(tempCanvas.width / 2, tempCanvas.height / 2);
534
- tempCtx.rotate(-Math.PI / 2);
535
- tempCtx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);
536
-
537
- // Update main canvas
538
- canvas.width = tempCanvas.width;
539
- canvas.height = tempCanvas.height;
540
- ctx.drawImage(tempCanvas, 0, 0);
541
-
542
- // Update original image data
543
- originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
544
- applyAdjustments();
545
- });
546
-
547
- rotateRightBtn.addEventListener('click', function() {
548
- if (!image) return;
549
-
550
- // Create a temporary canvas
551
- const tempCanvas = document.createElement('canvas');
552
- const tempCtx = tempCanvas.getContext('2d');
553
 
554
- // Swap width and height
555
- tempCanvas.width = canvas.height;
556
- tempCanvas.height = canvas.width;
557
-
558
- // Rotate and draw
559
- tempCtx.translate(tempCanvas.width / 2, tempCanvas.height / 2);
560
- tempCtx.rotate(Math.PI / 2);
561
- tempCtx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);
562
-
563
- // Update main canvas
564
- canvas.width = tempCanvas.width;
565
- canvas.height = tempCanvas.height;
566
- ctx.drawImage(tempCanvas, 0, 0);
567
-
568
- // Update original image data
569
- originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
570
- applyAdjustments();
571
- });
572
-
573
- flipHorizontalBtn.addEventListener('click', function() {
574
- if (!image) return;
575
-
576
- ctx.save();
577
- ctx.scale(-1, 1);
578
- ctx.drawImage(canvas, -canvas.width, 0);
579
- ctx.restore();
580
-
581
- // Update original image data
582
- originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
583
- applyAdjustments();
584
- });
585
-
586
- flipVerticalBtn.addEventListener('click', function() {
587
- if (!image) return;
588
-
589
- ctx.save();
590
- ctx.scale(1, -1);
591
- ctx.drawImage(canvas, 0, -canvas.height);
592
- ctx.restore();
593
-
594
- // Update original image data
595
- originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
596
- applyAdjustments();
597
  });
598
  });
599
  </script>
 
1
  <!DOCTYPE html>
2
+ <html lang="pt-PT">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Editor de Imagens | PT</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
+ .image-container {
11
+ max-height: 70vh;
12
+ overflow: hidden;
13
+ }
14
+ #imagePreview {
15
+ max-width: 100%;
16
+ max-height: 100%;
17
+ transition: all 0.3s ease;
18
+ }
19
  .slider-container {
20
  width: 100%;
 
21
  }
22
+ input[type="range"] {
23
+ -webkit-appearance: none;
24
  width: 100%;
25
  height: 8px;
26
+ border-radius: 4px;
27
+ background: #e2e8f0;
 
28
  outline: none;
29
  }
30
+ input[type="range"]::-webkit-slider-thumb {
31
  -webkit-appearance: none;
32
+ appearance: none;
33
  width: 18px;
34
  height: 18px;
 
35
  border-radius: 50%;
36
+ background: #3b82f6;
37
  cursor: pointer;
38
  }
39
  .filter-option {
 
41
  }
42
  .filter-option:hover {
43
  transform: scale(1.05);
 
44
  }
45
  .filter-option.active {
46
+ border-color: #3b82f6;
47
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
  </style>
50
  </head>
51
+ <body class="bg-gray-50 font-sans">
52
  <div class="container mx-auto px-4 py-8">
53
  <header class="mb-8 text-center">
54
+ <h1 class="text-3xl font-bold text-gray-800 mb-2">Editor de Imagens</h1>
55
+ <p class="text-gray-600">Edite as suas imagens diretamente no navegador</p>
56
  </header>
57
 
58
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
59
+ <div class="p-4 border-b border-gray-200 bg-gray-50">
60
+ <div class="flex flex-wrap gap-4 items-center justify-between">
61
+ <div class="flex items-center space-x-4">
62
+ <label for="imageUpload" class="cursor-pointer bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition flex items-center">
63
+ <i class="fas fa-upload mr-2"></i>
64
+ Carregar Imagem
 
 
 
 
65
  </label>
66
+ <input type="file" id="imageUpload" accept="image/*" class="hidden">
67
+
68
+ <button id="downloadBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition flex items-center">
69
+ <i class="fas fa-download mr-2"></i>
70
+ Descarregar
71
+ </button>
72
  </div>
73
+
74
+ <div class="flex items-center space-x-4">
75
+ <button id="resetBtn" class="text-gray-600 hover:text-gray-800 transition flex items-center">
76
+ <i class="fas fa-undo mr-2"></i>
77
+ Repor
78
+ </button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <div class="p-6 flex flex-col md:flex-row gap-6">
84
+ <div class="image-container w-full md:w-3/4 flex items-center justify-center bg-gray-100 rounded-lg">
85
+ <canvas id="imageCanvas" class="hidden"></canvas>
86
+ <div id="previewPlaceholder" class="text-center p-8 text-gray-500">
87
+ <i class="fas fa-image text-5xl mb-4 text-gray-300"></i>
88
+ <p>Carregue uma imagem para começar a editar</p>
89
+ </div>
90
+ <img id="imagePreview" class="hidden" alt="Pré-visualização da imagem">
91
+ </div>
92
+
93
+ <div class="w-full md:w-1/4 space-y-6">
94
+ <div class="bg-gray-50 p-4 rounded-lg">
95
+ <h3 class="font-medium text-gray-800 mb-3 flex items-center">
96
+ <i class="fas fa-sliders-h mr-2 text-blue-500"></i>
97
+ Ajustes
98
  </h3>
99
+
100
  <div class="space-y-4">
101
+ <div>
102
+ <label class="block text-sm font-medium text-gray-700 mb-1">Brilho</label>
103
+ <div class="slider-container">
104
+ <input type="range" id="brightnessSlider" min="0" max="200" value="100" class="w-full">
105
+ </div>
106
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
107
+ <span>0%</span>
108
+ <span>100%</span>
109
+ <span>200%</span>
110
+ </div>
111
  </div>
112
+
113
+ <div>
114
+ <label class="block text-sm font-medium text-gray-700 mb-1">Contraste</label>
115
+ <div class="slider-container">
116
+ <input type="range" id="contrastSlider" min="0" max="200" value="100" class="w-full">
117
+ </div>
118
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
119
+ <span>0%</span>
120
+ <span>100%</span>
121
+ <span>200%</span>
122
+ </div>
123
  </div>
124
+
125
+ <div>
126
+ <label class="block text-sm font-medium text-gray-700 mb-1">Saturação</label>
127
+ <div class="slider-container">
128
+ <input type="range" id="saturationSlider" min="0" max="200" value="100" class="w-full">
129
+ </div>
130
+ <div class="flex justify-between text-xs text-gray-500 mt-1">
131
+ <span>0%</span>
132
+ <span>100%</span>
133
+ <span>200%</span>
134
+ </div>
135
  </div>
136
+
137
+ <div>
138
+ <label class="block text-sm font-medium text-gray-700 mb-1">Rotação</label>
139
+ <div class="flex gap-2">
140
+ <button id="rotateLeftBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 p-2 rounded transition">
141
+ <i class="fas fa-undo"></i>
142
+ </button>
143
+ <button id="rotateRightBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 p-2 rounded transition">
144
+ <i class="fas fa-redo"></i>
145
+ </button>
146
+ </div>
147
  </div>
148
  </div>
149
  </div>
150
+
151
+ <div class="bg-gray-50 p-4 rounded-lg">
152
+ <h3 class="font-medium text-gray-800 mb-3 flex items-center">
153
+ <i class="fas fa-magic mr-2 text-purple-500"></i>
154
+ Filtros
155
  </h3>
156
+
157
  <div class="grid grid-cols-3 gap-2">
158
+ <div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="none">
159
+ <div class="w-full h-12 bg-gray-200 mb-1 rounded"></div>
160
+ <span class="text-xs">Original</span>
161
  </div>
162
+ <div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="grayscale">
163
+ <div class="w-full h-12 bg-gray-400 mb-1 rounded"></div>
164
+ <span class="text-xs">Preto e Branco</span>
165
  </div>
166
+ <div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="sepia">
167
+ <div class="w-full h-12 bg-amber-300 mb-1 rounded"></div>
168
+ <span class="text-xs">Sépia</span>
169
  </div>
170
+ <div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="invert">
171
+ <div class="w-full h-12 bg-gray-800 mb-1 rounded"></div>
172
+ <span class="text-xs">Inverter</span>
173
  </div>
174
+ <div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="blur">
175
+ <div class="w-full h-12 bg-blue-100 mb-1 rounded"></div>
176
+ <span class="text-xs">Desfocado</span>
177
  </div>
178
+ <div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="hue-rotate">
179
+ <div class="w-full h-12 bg-green-200 mb-1 rounded"></div>
180
+ <span class="text-xs">Matiz</span>
181
  </div>
182
  </div>
183
  </div>
184
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  </div>
186
  </div>
187
+
188
+ <footer class="text-center text-gray-500 text-sm mt-12">
189
+ <p 2023 Editor de Imagens. Todos os direitos reservados.</p>
190
  </footer>
191
  </div>
192
 
193
  <script>
194
  document.addEventListener('DOMContentLoaded', function() {
195
+ // Elementos DOM
196
+ const imageUpload = document.getElementById('imageUpload');
197
+ const imagePreview = document.getElementById('imagePreview');
198
+ const previewPlaceholder = document.getElementById('previewPlaceholder');
199
+ const canvas = document.getElementById('imageCanvas');
200
  const ctx = canvas.getContext('2d');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  const downloadBtn = document.getElementById('downloadBtn');
202
+ const resetBtn = document.getElementById('resetBtn');
203
+
204
+ // Sliders
205
+ const brightnessSlider = document.getElementById('brightnessSlider');
206
+ const contrastSlider = document.getElementById('contrastSlider');
207
+ const saturationSlider = document.getElementById('saturationSlider');
208
+
209
+ // Botões de rotação
210
  const rotateLeftBtn = document.getElementById('rotateLeftBtn');
211
  const rotateRightBtn = document.getElementById('rotateRightBtn');
212
+
213
+ // Filtros
214
+ const filterOptions = document.querySelectorAll('.filter-option');
215
+
216
+ // Estado da imagem
217
+ let originalImage = null;
218
+ let currentRotation = 0;
219
  let currentFilter = 'none';
220
+
221
+ // Carregar imagem
222
+ imageUpload.addEventListener('change', function(e) {
 
 
 
 
 
 
223
  if (e.target.files && e.target.files[0]) {
224
  const reader = new FileReader();
225
+
226
  reader.onload = function(event) {
227
+ originalImage = new Image();
228
+ originalImage.onload = function() {
229
+ // Mostrar canvas e esconder placeholder
230
+ previewPlaceholder.classList.add('hidden');
231
+ imagePreview.classList.add('hidden');
232
+ canvas.classList.remove('hidden');
 
 
233
 
234
+ // Redimensionar canvas para a imagem
235
+ resizeCanvas();
236
+ applyChanges();
 
 
237
  };
238
+ originalImage.src = event.target.result;
239
+ imagePreview.src = event.target.result;
240
  };
241
+
242
  reader.readAsDataURL(e.target.files[0]);
243
  }
244
  });
245
+
246
+ // Redimensionar canvas
247
+ function resizeCanvas() {
248
+ const maxWidth = document.querySelector('.image-container').clientWidth;
249
+ const maxHeight = document.querySelector('.image-container').clientHeight;
 
 
250
 
251
+ let width = originalImage.width;
252
+ let height = originalImage.height;
 
253
 
254
+ // Redimensionar mantendo proporção
255
+ if (width > maxWidth) {
256
+ const ratio = maxWidth / width;
257
+ width = maxWidth;
258
+ height = height * ratio;
 
 
 
259
  }
260
 
261
+ if (height > maxHeight) {
262
+ const ratio = maxHeight / height;
263
+ height = maxHeight;
264
+ width = width * ratio;
 
 
 
 
 
265
  }
266
 
267
+ canvas.width = width;
268
+ canvas.height = height;
269
+ }
270
+
271
+ // Aplicar todas as alterações
272
+ function applyChanges() {
273
+ if (!originalImage) return;
 
 
 
 
 
 
274
 
275
+ // Limpar canvas
276
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
277
+
278
+ // Aplicar rotação
279
+ ctx.save();
280
+ ctx.translate(canvas.width / 2, canvas.height / 2);
281
+ ctx.rotate(currentRotation * Math.PI / 180);
 
282
 
283
+ // Desenhar imagem com rotação
284
+ ctx.drawImage(
285
+ originalImage,
286
+ -canvas.width / 2,
287
+ -canvas.height / 2,
288
+ canvas.width,
289
+ canvas.height
290
+ );
291
 
292
+ ctx.restore();
293
+
294
+ // Aplicar filtros
295
+ applyFilters();
296
  }
297
 
298
+ // Aplicar filtros CSS
299
+ function applyFilters() {
300
+ if (!originalImage) return;
301
+
302
+ let filter = '';
303
+
304
+ // Brilho e contraste
305
+ const brightness = brightnessSlider.value / 100;
306
+ const contrast = contrastSlider.value / 100;
307
+ const saturation = saturationSlider.value / 100;
308
+
309
+ filter += `brightness(${brightness}) contrast(${contrast}) saturate(${saturation})`;
310
+
311
+ // Filtros adicionais
312
  switch(currentFilter) {
313
  case 'grayscale':
314
+ filter += ' grayscale(100%)';
 
 
 
 
 
315
  break;
316
  case 'sepia':
317
+ filter += ' sepia(100%)';
 
 
 
 
 
 
 
318
  break;
319
  case 'invert':
320
+ filter += ' invert(100%)';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  break;
322
  case 'blur':
323
+ filter += ' blur(2px)';
324
+ break;
325
+ case 'hue-rotate':
326
+ filter += ' hue-rotate(90deg)';
327
+ break;
 
 
 
 
 
 
328
  }
 
 
 
 
 
 
 
 
329
 
330
+ canvas.style.filter = filter;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  }
332
 
333
+ // Event listeners para sliders
334
+ brightnessSlider.addEventListener('input', applyChanges);
335
+ contrastSlider.addEventListener('input', applyChanges);
336
+ saturationSlider.addEventListener('input', applyChanges);
 
337
 
338
+ // Event listeners para rotação
339
+ rotateLeftBtn.addEventListener('click', function() {
340
+ currentRotation -= 90;
341
+ applyChanges();
 
 
 
 
342
  });
343
 
344
+ rotateRightBtn.addEventListener('click', function() {
345
+ currentRotation += 90;
346
+ applyChanges();
347
  });
348
 
349
+ // Event listeners para filtros
350
  filterOptions.forEach(option => {
351
  option.addEventListener('click', function() {
352
+ // Remover classe active de todos
353
  filterOptions.forEach(opt => opt.classList.remove('active'));
354
+
355
+ // Adicionar classe active ao selecionado
356
  this.classList.add('active');
357
+
358
+ // Atualizar filtro atual
359
  currentFilter = this.dataset.filter;
360
+ applyChanges();
361
  });
362
  });
363
 
364
+ // Repor imagem
365
+ resetBtn.addEventListener('click', function() {
366
+ if (!originalImage) return;
 
 
 
367
 
368
+ // Repor valores
369
+ brightnessSlider.value = 100;
370
+ contrastSlider.value = 100;
371
+ saturationSlider.value = 100;
372
+ currentRotation = 0;
373
+ currentFilter = 'none';
 
 
 
 
 
374
 
375
+ // Remover seleção de filtros
376
+ filterOptions.forEach(opt => opt.classList.remove('active'));
377
+ document.querySelector('.filter-option[data-filter="none"]').classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
+ // Aplicar alterações
380
+ applyChanges();
 
 
381
  });
382
 
383
+ // Descarregar imagem
384
+ downloadBtn.addEventListener('click', function() {
385
+ if (!originalImage) {
386
+ alert('Por favor, carregue uma imagem primeiro.');
387
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ // Criar link de download
391
+ const link = document.createElement('a');
392
+ link.download = 'imagem-editada.png';
393
+ link.href = canvas.toDataURL('image/png');
394
+ link.click();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  });
396
  });
397
  </script>