Spaces:
Running
Running
<html lang="pt-PT"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Editor de Imagens | PT</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.image-container { | |
max-height: 70vh; | |
overflow: hidden; | |
} | |
#imagePreview { | |
max-width: 100%; | |
max-height: 100%; | |
transition: all 0.3s ease; | |
} | |
.slider-container { | |
width: 100%; | |
} | |
input[type="range"] { | |
-webkit-appearance: none; | |
width: 100%; | |
height: 8px; | |
border-radius: 4px; | |
background: #e2e8f0; | |
outline: none; | |
} | |
input[type="range"]::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 18px; | |
height: 18px; | |
border-radius: 50%; | |
background: #3b82f6; | |
cursor: pointer; | |
} | |
.filter-option { | |
transition: all 0.2s ease; | |
} | |
.filter-option:hover { | |
transform: scale(1.05); | |
} | |
.filter-option.active { | |
border-color: #3b82f6; | |
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3); | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 font-sans"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8 text-center"> | |
<h1 class="text-3xl font-bold text-gray-800 mb-2">Editor de Imagens</h1> | |
<p class="text-gray-600">Edite as suas imagens diretamente no navegador</p> | |
</header> | |
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8"> | |
<div class="p-4 border-b border-gray-200 bg-gray-50"> | |
<div class="flex flex-wrap gap-4 items-center justify-between"> | |
<div class="flex items-center space-x-4"> | |
<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"> | |
<i class="fas fa-upload mr-2"></i> | |
Carregar Imagem | |
</label> | |
<input type="file" id="imageUpload" accept="image/*" class="hidden"> | |
<button id="downloadBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition flex items-center"> | |
<i class="fas fa-download mr-2"></i> | |
Descarregar | |
</button> | |
</div> | |
<div class="flex items-center space-x-4"> | |
<button id="resetBtn" class="text-gray-600 hover:text-gray-800 transition flex items-center"> | |
<i class="fas fa-undo mr-2"></i> | |
Repor | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="p-6 flex flex-col md:flex-row gap-6"> | |
<div class="image-container w-full md:w-3/4 flex items-center justify-center bg-gray-100 rounded-lg"> | |
<canvas id="imageCanvas" class="hidden"></canvas> | |
<div id="previewPlaceholder" class="text-center p-8 text-gray-500"> | |
<i class="fas fa-image text-5xl mb-4 text-gray-300"></i> | |
<p>Carregue uma imagem para começar a editar</p> | |
</div> | |
<img id="imagePreview" class="hidden" alt="Pré-visualização da imagem"> | |
</div> | |
<div class="w-full md:w-1/4 space-y-6"> | |
<div class="bg-gray-50 p-4 rounded-lg"> | |
<h3 class="font-medium text-gray-800 mb-3 flex items-center"> | |
<i class="fas fa-sliders-h mr-2 text-blue-500"></i> | |
Ajustes | |
</h3> | |
<div class="space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Brilho</label> | |
<div class="slider-container"> | |
<input type="range" id="brightnessSlider" min="0" max="200" value="100" class="w-full"> | |
</div> | |
<div class="flex justify-between text-xs text-gray-500 mt-1"> | |
<span>0%</span> | |
<span>100%</span> | |
<span>200%</span> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Contraste</label> | |
<div class="slider-container"> | |
<input type="range" id="contrastSlider" min="0" max="200" value="100" class="w-full"> | |
</div> | |
<div class="flex justify-between text-xs text-gray-500 mt-1"> | |
<span>0%</span> | |
<span>100%</span> | |
<span>200%</span> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Saturação</label> | |
<div class="slider-container"> | |
<input type="range" id="saturationSlider" min="0" max="200" value="100" class="w-full"> | |
</div> | |
<div class="flex justify-between text-xs text-gray-500 mt-1"> | |
<span>0%</span> | |
<span>100%</span> | |
<span>200%</span> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Rotação</label> | |
<div class="flex gap-2"> | |
<button id="rotateLeftBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 p-2 rounded transition"> | |
<i class="fas fa-undo"></i> | |
</button> | |
<button id="rotateRightBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 p-2 rounded transition"> | |
<i class="fas fa-redo"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="bg-gray-50 p-4 rounded-lg"> | |
<h3 class="font-medium text-gray-800 mb-3 flex items-center"> | |
<i class="fas fa-magic mr-2 text-purple-500"></i> | |
Filtros | |
</h3> | |
<div class="grid grid-cols-3 gap-2"> | |
<div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="none"> | |
<div class="w-full h-12 bg-gray-200 mb-1 rounded"></div> | |
<span class="text-xs">Original</span> | |
</div> | |
<div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="grayscale"> | |
<div class="w-full h-12 bg-gray-400 mb-1 rounded"></div> | |
<span class="text-xs">Preto e Branco</span> | |
</div> | |
<div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="sepia"> | |
<div class="w-full h-12 bg-amber-300 mb-1 rounded"></div> | |
<span class="text-xs">Sépia</span> | |
</div> | |
<div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="invert"> | |
<div class="w-full h-12 bg-gray-800 mb-1 rounded"></div> | |
<span class="text-xs">Inverter</span> | |
</div> | |
<div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="blur"> | |
<div class="w-full h-12 bg-blue-100 mb-1 rounded"></div> | |
<span class="text-xs">Desfocado</span> | |
</div> | |
<div class="filter-option p-2 border rounded cursor-pointer text-center" data-filter="hue-rotate"> | |
<div class="w-full h-12 bg-green-200 mb-1 rounded"></div> | |
<span class="text-xs">Matiz</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<footer class="text-center text-gray-500 text-sm mt-12"> | |
<p>© 2023 Editor de Imagens. Todos os direitos reservados.</p> | |
</footer> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Elementos DOM | |
const imageUpload = document.getElementById('imageUpload'); | |
const imagePreview = document.getElementById('imagePreview'); | |
const previewPlaceholder = document.getElementById('previewPlaceholder'); | |
const canvas = document.getElementById('imageCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const downloadBtn = document.getElementById('downloadBtn'); | |
const resetBtn = document.getElementById('resetBtn'); | |
// Sliders | |
const brightnessSlider = document.getElementById('brightnessSlider'); | |
const contrastSlider = document.getElementById('contrastSlider'); | |
const saturationSlider = document.getElementById('saturationSlider'); | |
// Botões de rotação | |
const rotateLeftBtn = document.getElementById('rotateLeftBtn'); | |
const rotateRightBtn = document.getElementById('rotateRightBtn'); | |
// Filtros | |
const filterOptions = document.querySelectorAll('.filter-option'); | |
// Estado da imagem | |
let originalImage = null; | |
let currentRotation = 0; | |
let currentFilter = 'none'; | |
// Carregar imagem | |
imageUpload.addEventListener('change', function(e) { | |
if (e.target.files && e.target.files[0]) { | |
const reader = new FileReader(); | |
reader.onload = function(event) { | |
originalImage = new Image(); | |
originalImage.onload = function() { | |
// Mostrar canvas e esconder placeholder | |
previewPlaceholder.classList.add('hidden'); | |
imagePreview.classList.add('hidden'); | |
canvas.classList.remove('hidden'); | |
// Redimensionar canvas para a imagem | |
resizeCanvas(); | |
applyChanges(); | |
}; | |
originalImage.src = event.target.result; | |
imagePreview.src = event.target.result; | |
}; | |
reader.readAsDataURL(e.target.files[0]); | |
} | |
}); | |
// Redimensionar canvas | |
function resizeCanvas() { | |
const maxWidth = document.querySelector('.image-container').clientWidth; | |
const maxHeight = document.querySelector('.image-container').clientHeight; | |
let width = originalImage.width; | |
let height = originalImage.height; | |
// Redimensionar mantendo proporção | |
if (width > maxWidth) { | |
const ratio = maxWidth / width; | |
width = maxWidth; | |
height = height * ratio; | |
} | |
if (height > maxHeight) { | |
const ratio = maxHeight / height; | |
height = maxHeight; | |
width = width * ratio; | |
} | |
canvas.width = width; | |
canvas.height = height; | |
} | |
// Aplicar todas as alterações | |
function applyChanges() { | |
if (!originalImage) return; | |
// Limpar canvas | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Aplicar rotação | |
ctx.save(); | |
ctx.translate(canvas.width / 2, canvas.height / 2); | |
ctx.rotate(currentRotation * Math.PI / 180); | |
// Desenhar imagem com rotação | |
ctx.drawImage( | |
originalImage, | |
-canvas.width / 2, | |
-canvas.height / 2, | |
canvas.width, | |
canvas.height | |
); | |
ctx.restore(); | |
// Aplicar filtros | |
applyFilters(); | |
} | |
// Aplicar filtros CSS | |
function applyFilters() { | |
if (!originalImage) return; | |
let filter = ''; | |
// Brilho e contraste | |
const brightness = brightnessSlider.value / 100; | |
const contrast = contrastSlider.value / 100; | |
const saturation = saturationSlider.value / 100; | |
filter += `brightness(${brightness}) contrast(${contrast}) saturate(${saturation})`; | |
// Filtros adicionais | |
switch(currentFilter) { | |
case 'grayscale': | |
filter += ' grayscale(100%)'; | |
break; | |
case 'sepia': | |
filter += ' sepia(100%)'; | |
break; | |
case 'invert': | |
filter += ' invert(100%)'; | |
break; | |
case 'blur': | |
filter += ' blur(2px)'; | |
break; | |
case 'hue-rotate': | |
filter += ' hue-rotate(90deg)'; | |
break; | |
} | |
canvas.style.filter = filter; | |
} | |
// Event listeners para sliders | |
brightnessSlider.addEventListener('input', applyChanges); | |
contrastSlider.addEventListener('input', applyChanges); | |
saturationSlider.addEventListener('input', applyChanges); | |
// Event listeners para rotação | |
rotateLeftBtn.addEventListener('click', function() { | |
currentRotation -= 90; | |
applyChanges(); | |
}); | |
rotateRightBtn.addEventListener('click', function() { | |
currentRotation += 90; | |
applyChanges(); | |
}); | |
// Event listeners para filtros | |
filterOptions.forEach(option => { | |
option.addEventListener('click', function() { | |
// Remover classe active de todos | |
filterOptions.forEach(opt => opt.classList.remove('active')); | |
// Adicionar classe active ao selecionado | |
this.classList.add('active'); | |
// Atualizar filtro atual | |
currentFilter = this.dataset.filter; | |
applyChanges(); | |
}); | |
}); | |
// Repor imagem | |
resetBtn.addEventListener('click', function() { | |
if (!originalImage) return; | |
// Repor valores | |
brightnessSlider.value = 100; | |
contrastSlider.value = 100; | |
saturationSlider.value = 100; | |
currentRotation = 0; | |
currentFilter = 'none'; | |
// Remover seleção de filtros | |
filterOptions.forEach(opt => opt.classList.remove('active')); | |
document.querySelector('.filter-option[data-filter="none"]').classList.add('active'); | |
// Aplicar alterações | |
applyChanges(); | |
}); | |
// Descarregar imagem | |
downloadBtn.addEventListener('click', function() { | |
if (!originalImage) { | |
alert('Por favor, carregue uma imagem primeiro.'); | |
return; | |
} | |
// Criar link de download | |
const link = document.createElement('a'); | |
link.download = 'imagem-editada.png'; | |
link.href = canvas.toDataURL('image/png'); | |
link.click(); | |
}); | |
}); | |
</script> | |
<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=ocirema/americo-foto" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
</html> |