| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>SnapEdit Pro - Advanced Image Editor</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"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); |
| |
| body { |
| font-family: 'Poppins', sans-serif; |
| margin: 0; |
| padding: 0; |
| height: 100vh; |
| overflow: hidden; |
| background-color: #1a1a1a; |
| } |
| |
| #editorCanvas { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| cursor: default; |
| transform-origin: 0 0; |
| } |
| |
| .floating-toolbar { |
| position: absolute; |
| top: 20px; |
| left: 50%; |
| transform: translateX(-50%); |
| background: rgba(30, 30, 30, 0.8); |
| backdrop-filter: blur(10px); |
| -webkit-backdrop-filter: blur(10px); |
| border-radius: 12px; |
| padding: 8px; |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); |
| display: flex; |
| gap: 4px; |
| z-index: 100; |
| user-select: none; |
| transition: all 0.3s ease; |
| } |
| |
| .floating-toolbar.dragging { |
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4); |
| } |
| |
| .tool-btn { |
| width: 40px; |
| height: 40px; |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #e0e0e0; |
| background: transparent; |
| border: none; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .tool-btn:hover { |
| background: rgba(99, 102, 241, 0.2); |
| color: white; |
| transform: translateY(-2px); |
| } |
| |
| .tool-btn.active { |
| background: rgba(99, 102, 241, 0.4); |
| color: white; |
| box-shadow: 0 4px 8px rgba(99, 102, 241, 0.2); |
| } |
| |
| .tool-btn i { |
| font-size: 16px; |
| } |
| |
| .tool-separator { |
| width: 1px; |
| height: 24px; |
| background: rgba(255, 255, 255, 0.1); |
| margin: 0 4px; |
| align-self: center; |
| } |
| |
| .floating-properties { |
| position: absolute; |
| top: 80px; |
| right: 20px; |
| background: rgba(30, 30, 30, 0.8); |
| backdrop-filter: blur(10px); |
| -webkit-backdrop-filter: blur(10px); |
| border-radius: 12px; |
| padding: 16px; |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); |
| width: 280px; |
| z-index: 100; |
| user-select: none; |
| transition: all 0.3s ease; |
| color: #e0e0e0; |
| } |
| |
| .floating-properties.dragging { |
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4); |
| } |
| |
| .properties-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 12px; |
| padding-bottom: 8px; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .properties-title { |
| font-size: 14px; |
| font-weight: 600; |
| color: white; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .properties-close { |
| width: 24px; |
| height: 24px; |
| border-radius: 6px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #e0e0e0; |
| background: transparent; |
| border: none; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .properties-close:hover { |
| background: rgba(255, 255, 255, 0.1); |
| } |
| |
| .property-group { |
| margin-bottom: 16px; |
| } |
| |
| .property-label { |
| font-size: 12px; |
| color: #a0a0a0; |
| margin-bottom: 6px; |
| display: block; |
| } |
| |
| .color-picker { |
| width: 100%; |
| height: 32px; |
| border-radius: 6px; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| background: rgba(40, 40, 40, 0.8); |
| cursor: pointer; |
| } |
| |
| input[type="range"] { |
| -webkit-appearance: none; |
| width: 100%; |
| height: 4px; |
| border-radius: 2px; |
| background: rgba(255, 255, 255, 0.1); |
| outline: none; |
| } |
| |
| input[type="range"]::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 16px; |
| height: 16px; |
| border-radius: 50%; |
| background: #6366f1; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .range-value { |
| font-size: 12px; |
| color: #a0a0a0; |
| text-align: right; |
| margin-top: 4px; |
| } |
| |
| .dropdown { |
| width: 100%; |
| padding: 8px; |
| border-radius: 6px; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| background: rgba(40, 40, 40, 0.8); |
| color: #e0e0e0; |
| font-size: 12px; |
| outline: none; |
| } |
| |
| .dropdown option { |
| background: #2d2d2d; |
| } |
| |
| .notification-container { |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| z-index: 1000; |
| } |
| |
| .notification { |
| padding: 12px 16px; |
| border-radius: 8px; |
| margin-bottom: 10px; |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 14px; |
| animation: slideIn 0.3s ease-out forwards; |
| background: rgba(30, 30, 30, 0.9); |
| color: white; |
| border-left: 4px solid; |
| } |
| |
| .notification.success { |
| border-color: #10b981; |
| } |
| |
| .notification.error { |
| border-color: #ef4444; |
| } |
| |
| .notification.info { |
| border-color: #3b82f6; |
| } |
| |
| @keyframes slideIn { |
| from { transform: translateX(20px); opacity: 0; } |
| to { transform: translateX(0); opacity: 1; } |
| } |
| |
| .zoom-controls { |
| position: fixed; |
| bottom: 20px; |
| left: 20px; |
| background: rgba(30, 30, 30, 0.8); |
| backdrop-filter: blur(10px); |
| -webkit-backdrop-filter: blur(10px); |
| border-radius: 12px; |
| padding: 8px; |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); |
| display: flex; |
| flex-direction: column; |
| gap: 4px; |
| z-index: 100; |
| } |
| |
| .zoom-btn { |
| width: 40px; |
| height: 40px; |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #e0e0e0; |
| background: transparent; |
| border: none; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .zoom-btn:hover { |
| background: rgba(99, 102, 241, 0.2); |
| color: white; |
| } |
| |
| .zoom-value { |
| width: 40px; |
| text-align: center; |
| font-size: 12px; |
| color: white; |
| padding: 4px 0; |
| } |
| |
| .fullscreen-btn { |
| position: fixed; |
| bottom: 20px; |
| right: 20px; |
| width: 40px; |
| height: 40px; |
| border-radius: 50%; |
| background: rgba(30, 30, 30, 0.8); |
| backdrop-filter: blur(10px); |
| -webkit-backdrop-filter: blur(10px); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| cursor: pointer; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
| z-index: 100; |
| transition: all 0.2s ease; |
| } |
| |
| .fullscreen-btn:hover { |
| background: rgba(99, 102, 241, 0.8); |
| transform: scale(1.1); |
| } |
| |
| .hidden { |
| display: none !important; |
| } |
| |
| .canvas-container { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| overflow: hidden; |
| } |
| |
| .lock-btn { |
| position: absolute; |
| top: 5px; |
| right: 5px; |
| width: 24px; |
| height: 24px; |
| border-radius: 4px; |
| background: rgba(30, 30, 30, 0.8); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| cursor: pointer; |
| z-index: 10; |
| transition: all 0.2s ease; |
| } |
| |
| .lock-btn:hover { |
| background: rgba(99, 102, 241, 0.8); |
| } |
| |
| .object-controls { |
| position: absolute; |
| top: 5px; |
| right: 5px; |
| display: flex; |
| gap: 4px; |
| z-index: 10; |
| } |
| |
| .control-btn { |
| width: 24px; |
| height: 24px; |
| border-radius: 4px; |
| background: rgba(30, 30, 30, 0.8); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .control-btn:hover { |
| background: rgba(99, 102, 241, 0.8); |
| } |
| |
| .locked { |
| color: #6366f1; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div class="canvas-container"> |
| <canvas id="editorCanvas"></canvas> |
| </div> |
| |
| |
| <div class="floating-toolbar" id="floatingToolbar"> |
| |
| <button class="tool-btn" id="uploadBtn" title="Upload Image"> |
| <i class="fas fa-upload"></i> |
| </button> |
| |
| <div class="tool-separator"></div> |
| |
| |
| <button class="tool-btn active" id="selectTool" title="Select Tool"> |
| <i class="fas fa-mouse-pointer"></i> |
| </button> |
| |
| |
| <button class="tool-btn" id="textTool" title="Text Tool"> |
| <i class="fas fa-font"></i> |
| </button> |
| <button class="tool-btn" id="drawTool" title="Draw Tool"> |
| <i class="fas fa-pencil-alt"></i> |
| </button> |
| <button class="tool-btn" id="shapeTool" title="Shapes"> |
| <i class="fas fa-square"></i> |
| </button> |
| <button class="tool-btn" id="blurTool" title="Blur Tool"> |
| <i class="fas fa-eye-slash"></i> |
| </button> |
| |
| <div class="tool-separator"></div> |
| |
| |
| <button class="tool-btn" id="aiRemoveBg" title="Remove Background"> |
| <i class="fas fa-robot"></i> |
| </button> |
| <button class="tool-btn" id="aiEnhance" title="AI Enhance"> |
| <i class="fas fa-magic"></i> |
| </button> |
| |
| <div class="tool-separator"></div> |
| |
| |
| <button class="tool-btn" id="undoBtn" title="Undo"> |
| <i class="fas fa-undo"></i> |
| </button> |
| <button class="tool-btn" id="redoBtn" title="Redo"> |
| <i class="fas fa-redo"></i> |
| </button> |
| |
| <div class="tool-separator"></div> |
| |
| |
| <button class="tool-btn" id="saveBtn" title="Save"> |
| <i class="fas fa-save"></i> |
| </button> |
| <button class="tool-btn" id="exportBtn" title="Export"> |
| <i class="fas fa-file-export"></i> |
| </button> |
| </div> |
| |
| |
| <div class="floating-properties" id="floatingProperties"> |
| <div class="properties-header"> |
| <div class="properties-title"> |
| <i class="fas fa-sliders-h"></i> |
| <span>Properties</span> |
| </div> |
| <button class="properties-close" id="propertiesClose"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="property-group"> |
| <label class="property-label">Canvas Background</label> |
| <input type="color" class="color-picker" id="canvasBg" value="#1a1a1a"> |
| </div> |
| |
| <div class="property-group"> |
| <label class="property-label">Opacity</label> |
| <input type="range" id="canvasOpacity" min="0" max="100" value="100"> |
| <div class="range-value" id="canvasOpacityValue">100%</div> |
| </div> |
| |
| <div class="property-group hidden" id="textProps"> |
| <label class="property-label">Text Color</label> |
| <input type="color" class="color-picker" id="textColor" value="#ffffff"> |
| |
| <label class="property-label">Font Size</label> |
| <input type="range" id="textSize" min="8" max="72" value="24"> |
| <div class="range-value" id="textSizeValue">24px</div> |
| |
| <label class="property-label">Font Family</label> |
| <select class="dropdown" id="textFont"> |
| <option value="Arial">Arial</option> |
| <option value="Helvetica">Helvetica</option> |
| <option value="Times New Roman">Times New Roman</option> |
| <option value="Courier New">Courier New</option> |
| <option value="Georgia">Georgia</option> |
| <option value="Verdana">Verdana</option> |
| </select> |
| </div> |
| |
| <div class="property-group hidden" id="drawProps"> |
| <label class="property-label">Brush Color</label> |
| <input type="color" class="color-picker" id="brushColor" value="#ffffff"> |
| |
| <label class="property-label">Brush Size</label> |
| <input type="range" id="brushSize" min="1" max="50" value="5"> |
| <div class="range-value" id="brushSizeValue">5px</div> |
| </div> |
| |
| <div class="property-group hidden" id="objectProps"> |
| <label class="property-label">Object Controls</label> |
| <div class="flex gap-2 mt-2"> |
| <button class="control-btn" id="lockBtn" title="Lock/Unlock"> |
| <i class="fas fa-lock"></i> |
| </button> |
| <button class="control-btn" id="deleteBtn" title="Delete"> |
| <i class="fas fa-trash"></i> |
| </button> |
| <button class="control-btn" id="bringToFrontBtn" title="Bring to Front"> |
| <i class="fas fa-arrow-up"></i> |
| </button> |
| <button class="control-btn" id="sendToBackBtn" title="Send to Back"> |
| <i class="fas fa-arrow-down"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="zoom-controls"> |
| <button class="zoom-btn" id="zoomInBtn" title="Zoom In"> |
| <i class="fas fa-search-plus"></i> |
| </button> |
| <div class="zoom-value" id="zoomValue">100%</div> |
| <button class="zoom-btn" id="zoomOutBtn" title="Zoom Out"> |
| <i class="fas fa-search-minus"></i> |
| </button> |
| <button class="zoom-btn" id="zoomResetBtn" title="Reset Zoom"> |
| <i class="fas fa-sync-alt"></i> |
| </button> |
| </div> |
| |
| |
| <div class="fullscreen-btn" id="fullscreenBtn" title="Toggle Fullscreen"> |
| <i class="fas fa-expand"></i> |
| </div> |
| |
| |
| <div class="notification-container" id="notificationContainer"></div> |
| |
| |
| <input type="file" id="fileInput" accept="image/*" class="hidden"> |
| |
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const canvas = new fabric.Canvas('editorCanvas', { |
| backgroundColor: '#1a1a1a', |
| preserveObjectStacking: true, |
| selection: true, |
| selectionColor: 'rgba(99, 102, 241, 0.3)', |
| selectionBorderColor: '#6366f1', |
| selectionLineWidth: 1, |
| selectionDashArray: [5, 5], |
| enableRetinaScaling: true |
| }); |
| |
| |
| let zoomLevel = 1; |
| const zoomStep = 0.1; |
| const minZoom = 0.1; |
| const maxZoom = 3; |
| |
| |
| function resizeCanvas() { |
| const width = window.innerWidth; |
| const height = window.innerHeight; |
| |
| canvas.setWidth(width); |
| canvas.setHeight(height); |
| |
| |
| canvas.setZoom(zoomLevel); |
| canvas.renderAll(); |
| |
| |
| document.getElementById('zoomValue').textContent = `${Math.round(zoomLevel * 100)}%`; |
| } |
| |
| |
| resizeCanvas(); |
| window.addEventListener('resize', resizeCanvas); |
| |
| |
| let currentTool = 'select'; |
| let activeObject = null; |
| let history = []; |
| let historyIndex = -1; |
| let isDrawing = false; |
| let drawingPath = null; |
| let blurArea = null; |
| let polygonPoints = []; |
| |
| |
| const floatingToolbar = document.getElementById('floatingToolbar'); |
| const floatingProperties = document.getElementById('floatingProperties'); |
| const fileInput = document.getElementById('fileInput'); |
| const uploadBtn = document.getElementById('uploadBtn'); |
| const propertiesClose = document.getElementById('propertiesClose'); |
| const fullscreenBtn = document.getElementById('fullscreenBtn'); |
| const notificationContainer = document.getElementById('notificationContainer'); |
| const zoomInBtn = document.getElementById('zoomInBtn'); |
| const zoomOutBtn = document.getElementById('zoomOutBtn'); |
| const zoomResetBtn = document.getElementById('zoomResetBtn'); |
| const lockBtn = document.getElementById('lockBtn'); |
| const deleteBtn = document.getElementById('deleteBtn'); |
| const bringToFrontBtn = document.getElementById('bringToFrontBtn'); |
| const sendToBackBtn = document.getElementById('sendToBackBtn'); |
| |
| |
| makeDraggable(floatingToolbar); |
| makeDraggable(floatingProperties); |
| |
| function makeDraggable(element) { |
| let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; |
| |
| element.onmousedown = dragMouseDown; |
| |
| function dragMouseDown(e) { |
| e = e || window.event; |
| e.preventDefault(); |
| |
| |
| if (e.target.classList.contains('tool-btn') || |
| e.target.classList.contains('properties-close') || |
| e.target.closest('.tool-btn') || |
| e.target.closest('.properties-close')) { |
| return; |
| } |
| |
| element.classList.add('dragging'); |
| |
| |
| pos3 = e.clientX; |
| pos4 = e.clientY; |
| |
| document.onmouseup = closeDragElement; |
| document.onmousemove = elementDrag; |
| } |
| |
| function elementDrag(e) { |
| e = e || window.event; |
| e.preventDefault(); |
| |
| |
| pos1 = pos3 - e.clientX; |
| pos2 = pos4 - e.clientY; |
| pos3 = e.clientX; |
| pos4 = e.clientY; |
| |
| |
| element.style.top = (element.offsetTop - pos2) + "px"; |
| element.style.left = (element.offsetLeft - pos1) + "px"; |
| } |
| |
| function closeDragElement() { |
| |
| document.onmouseup = null; |
| document.onmousemove = null; |
| element.classList.remove('dragging'); |
| } |
| } |
| |
| |
| zoomInBtn.addEventListener('click', zoomIn); |
| zoomOutBtn.addEventListener('click', zoomOut); |
| zoomResetBtn.addEventListener('click', resetZoom); |
| |
| function zoomIn() { |
| if (zoomLevel < maxZoom) { |
| zoomLevel = Math.min(zoomLevel + zoomStep, maxZoom); |
| applyZoom(); |
| } |
| } |
| |
| function zoomOut() { |
| if (zoomLevel > minZoom) { |
| zoomLevel = Math.max(zoomLevel - zoomStep, minZoom); |
| applyZoom(); |
| } |
| } |
| |
| function resetZoom() { |
| zoomLevel = 1; |
| applyZoom(); |
| } |
| |
| function applyZoom() { |
| canvas.setZoom(zoomLevel); |
| document.getElementById('zoomValue').textContent = `${Math.round(zoomLevel * 100)}%`; |
| canvas.renderAll(); |
| } |
| |
| |
| fullscreenBtn.addEventListener('click', toggleFullscreen); |
| |
| function toggleFullscreen() { |
| if (!document.fullscreenElement) { |
| document.documentElement.requestFullscreen().then(() => { |
| fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>'; |
| }); |
| } else { |
| document.exitFullscreen().then(() => { |
| fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>'; |
| }); |
| } |
| } |
| |
| |
| uploadBtn.addEventListener('click', () => fileInput.click()); |
| |
| fileInput.addEventListener('change', handleFileUpload); |
| |
| function handleFileUpload(e) { |
| const file = e.target.files[0]; |
| if (!file) return; |
| |
| |
| if (!file.type.match('image.*')) { |
| showNotification('Please select an image file (JPG, PNG, GIF)', 'error'); |
| return; |
| } |
| |
| |
| if (file.size > 10 * 1024 * 1024) { |
| showNotification('Image size should be less than 10MB', 'error'); |
| return; |
| } |
| |
| const reader = new FileReader(); |
| |
| reader.onload = function(f) { |
| fabric.Image.fromURL(f.target.result, function(img) { |
| |
| canvas.clear(); |
| |
| |
| canvas.add(img); |
| canvas.setActiveObject(img); |
| canvas.renderAll(); |
| |
| |
| img.center(); |
| |
| |
| addLockControls(img); |
| |
| showNotification('Image loaded successfully!', 'success'); |
| |
| |
| saveHistory(); |
| }, { |
| crossOrigin: 'anonymous', |
| |
| selectable: true, |
| hasControls: true, |
| hasBorders: true |
| }); |
| }; |
| |
| reader.onerror = function() { |
| showNotification('Error reading file. Please try another image.', 'error'); |
| }; |
| |
| reader.readAsDataURL(file); |
| } |
| |
| |
| function addLockControls(obj) { |
| |
| const lockButton = document.createElement('div'); |
| lockButton.className = 'lock-btn'; |
| lockButton.innerHTML = '<i class="fas fa-lock-open"></i>'; |
| lockButton.title = 'Lock/Unlock'; |
| |
| |
| lockButton.addEventListener('click', function(e) { |
| e.stopPropagation(); |
| toggleLock(obj); |
| }); |
| |
| |
| obj.lockButton = lockButton; |
| updateLockButtonPosition(obj); |
| |
| |
| document.querySelector('.canvas-container').appendChild(lockButton); |
| |
| |
| obj.on('moving', function() { |
| if (!obj.locked) { |
| updateLockButtonPosition(obj); |
| } |
| }); |
| |
| |
| obj.on('scaling', function() { |
| if (!obj.locked) { |
| updateLockButtonPosition(obj); |
| } |
| }); |
| |
| |
| obj.on('removed', function() { |
| if (obj.lockButton && obj.lockButton.parentNode) { |
| obj.lockButton.parentNode.removeChild(obj.lockButton); |
| } |
| }); |
| } |
| |
| |
| function updateLockButtonPosition(obj) { |
| if (!obj.lockButton) return; |
| |
| const zoom = canvas.getZoom(); |
| const vpt = canvas.viewportTransform; |
| const absoluteLeft = obj.left * zoom + vpt[4]; |
| const absoluteTop = obj.top * zoom + vpt[5]; |
| |
| obj.lockButton.style.left = `${absoluteLeft + obj.width * zoom - 30}px`; |
| obj.lockButton.style.top = `${absoluteTop + 5}px`; |
| } |
| |
| |
| function toggleLock(obj) { |
| obj.locked = !obj.locked; |
| |
| if (obj.locked) { |
| obj.lockButton.innerHTML = '<i class="fas fa-lock locked"></i>'; |
| obj.selectable = false; |
| obj.hasControls = false; |
| obj.hasBorders = false; |
| obj.evented = false; |
| showNotification('Object locked', 'success'); |
| } else { |
| obj.lockButton.innerHTML = '<i class="fas fa-lock-open"></i>'; |
| obj.selectable = true; |
| obj.hasControls = true; |
| obj.hasBorders = true; |
| obj.evented = true; |
| showNotification('Object unlocked', 'success'); |
| } |
| |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| |
| |
| propertiesClose.addEventListener('click', () => { |
| floatingProperties.classList.add('hidden'); |
| }); |
| |
| |
| document.getElementById('canvasBg').addEventListener('input', function(e) { |
| canvas.setBackgroundColor(e.target.value, canvas.renderAll.bind(canvas)); |
| saveHistory(); |
| }); |
| |
| |
| document.getElementById('canvasOpacity').addEventListener('input', function(e) { |
| const opacity = e.target.value / 100; |
| canvas.setBackgroundColor(canvas.backgroundColor, canvas.renderAll.bind(canvas), { |
| opacity: opacity |
| }); |
| document.getElementById('canvasOpacityValue').textContent = `${e.target.value}%`; |
| saveHistory(); |
| }); |
| |
| |
| lockBtn.addEventListener('click', function() { |
| if (activeObject) { |
| toggleLock(activeObject); |
| } |
| }); |
| |
| deleteBtn.addEventListener('click', function() { |
| if (activeObject) { |
| deleteSelectedObject(); |
| } |
| }); |
| |
| bringToFrontBtn.addEventListener('click', function() { |
| if (activeObject) { |
| activeObject.bringToFront(); |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| }); |
| |
| sendToBackBtn.addEventListener('click', function() { |
| if (activeObject) { |
| activeObject.sendToBack(); |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| }); |
| |
| |
| function deleteSelectedObject() { |
| if (activeObject) { |
| canvas.remove(activeObject); |
| activeObject = null; |
| canvas.renderAll(); |
| saveHistory(); |
| showNotification('Object deleted', 'success'); |
| } |
| } |
| |
| |
| function showNotification(message, type) { |
| const notification = document.createElement('div'); |
| notification.className = `notification ${type}`; |
| |
| let icon; |
| if (type === 'success') { |
| icon = '<i class="fas fa-check-circle"></i>'; |
| } else if (type === 'error') { |
| icon = '<i class="fas fa-exclamation-circle"></i>'; |
| } else { |
| icon = '<i class="fas fa-info-circle"></i>'; |
| } |
| |
| notification.innerHTML = `${icon} ${message}`; |
| notificationContainer.appendChild(notification); |
| |
| setTimeout(() => { |
| notification.remove(); |
| }, 3000); |
| } |
| |
| |
| function saveHistory() { |
| const canvasState = JSON.stringify(canvas.toJSON()); |
| |
| if (historyIndex < history.length - 1) { |
| history = history.slice(0, historyIndex + 1); |
| } |
| |
| history.push(canvasState); |
| historyIndex++; |
| |
| if (history.length > 50) { |
| history.shift(); |
| historyIndex--; |
| } |
| } |
| |
| function loadHistory(index) { |
| if (index >= 0 && index < history.length) { |
| canvas.loadFromJSON(history[index], function() { |
| canvas.renderAll(); |
| }); |
| } |
| } |
| |
| |
| document.getElementById('undoBtn').addEventListener('click', function() { |
| if (historyIndex > 0) { |
| historyIndex--; |
| loadHistory(historyIndex); |
| } |
| }); |
| |
| document.getElementById('redoBtn').addEventListener('click', function() { |
| if (historyIndex < history.length - 1) { |
| historyIndex++; |
| loadHistory(historyIndex); |
| } |
| }); |
| |
| |
| function setActiveTool(tool) { |
| currentTool = tool; |
| |
| |
| document.querySelectorAll('.tool-btn').forEach(btn => { |
| btn.classList.remove('active'); |
| }); |
| document.getElementById(`${tool}Tool`).classList.add('active'); |
| |
| |
| switch(tool) { |
| case 'select': |
| canvas.defaultCursor = 'default'; |
| canvas.selection = true; |
| break; |
| case 'text': |
| canvas.defaultCursor = 'text'; |
| canvas.selection = false; |
| break; |
| case 'draw': |
| canvas.defaultCursor = 'crosshair'; |
| canvas.selection = false; |
| break; |
| case 'shape': |
| canvas.defaultCursor = 'crosshair'; |
| canvas.selection = false; |
| break; |
| case 'blur': |
| canvas.defaultCursor = 'crosshair'; |
| canvas.selection = false; |
| break; |
| } |
| |
| |
| document.querySelectorAll('.property-group').forEach(group => { |
| group.classList.add('hidden'); |
| }); |
| |
| if (tool === 'text') { |
| document.getElementById('textProps').classList.remove('hidden'); |
| } else if (tool === 'draw') { |
| document.getElementById('drawProps').classList.remove('hidden'); |
| } |
| |
| |
| if (tool !== 'select') { |
| floatingProperties.classList.remove('hidden'); |
| } |
| } |
| |
| |
| document.getElementById('selectTool').addEventListener('click', () => setActiveTool('select')); |
| document.getElementById('textTool').addEventListener('click', () => setActiveTool('text')); |
| document.getElementById('drawTool').addEventListener('click', () => setActiveTool('draw')); |
| document.getElementById('shapeTool').addEventListener('click', () => setActiveTool('shape')); |
| document.getElementById('blurTool').addEventListener('click', () => setActiveTool('blur')); |
| |
| |
| setActiveTool('select'); |
| |
| |
| canvas.on('mouse:down', function(options) { |
| if (options.target && options.target.locked) { |
| return; |
| } |
| |
| if (options.target) { |
| activeObject = options.target; |
| return; |
| } |
| |
| const pointer = canvas.getPointer(options.e); |
| |
| switch(currentTool) { |
| case 'text': |
| addText(pointer); |
| break; |
| case 'draw': |
| startDrawing(pointer); |
| break; |
| case 'shape': |
| addRectangle(pointer); |
| break; |
| case 'blur': |
| addBlur(pointer); |
| break; |
| } |
| }); |
| |
| canvas.on('mouse:move', function(options) { |
| if (isDrawing && drawingPath && currentTool === 'draw') { |
| const pointer = canvas.getPointer(options.e); |
| drawingPath.path.push([ |
| 'L', |
| pointer.x, |
| pointer.y |
| ]); |
| canvas.renderAll(); |
| } |
| }); |
| |
| canvas.on('mouse:up', function() { |
| if (isDrawing) { |
| isDrawing = false; |
| saveHistory(); |
| } |
| }); |
| |
| canvas.on('object:selected', function(options) { |
| if (options.target.locked) { |
| canvas.discardActiveObject(); |
| return; |
| } |
| |
| activeObject = options.target; |
| floatingProperties.classList.remove('hidden'); |
| |
| |
| document.getElementById('objectProps').classList.remove('hidden'); |
| |
| |
| if (activeObject.locked) { |
| lockBtn.innerHTML = '<i class="fas fa-lock locked"></i>'; |
| } else { |
| lockBtn.innerHTML = '<i class="fas fa-lock-open"></i>'; |
| } |
| }); |
| |
| canvas.on('selection:cleared', function() { |
| activeObject = null; |
| document.getElementById('objectProps').classList.add('hidden'); |
| }); |
| |
| |
| function addText(position) { |
| const text = new fabric.IText('Type here', { |
| left: position.x, |
| top: position.y, |
| fontFamily: document.getElementById('textFont').value, |
| fontSize: parseInt(document.getElementById('textSize').value), |
| fill: document.getElementById('textColor').value, |
| selectable: true, |
| hasControls: true, |
| hasBorders: true |
| }); |
| |
| canvas.add(text); |
| canvas.setActiveObject(text); |
| text.enterEditing(); |
| text.hiddenTextarea.focus(); |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| |
| function startDrawing(position) { |
| isDrawing = true; |
| drawingPath = new fabric.Path(`M ${position.x} ${position.y}`, { |
| stroke: document.getElementById('brushColor').value, |
| strokeWidth: parseInt(document.getElementById('brushSize').value), |
| fill: 'transparent', |
| selectable: true, |
| hasControls: true, |
| hasBorders: true |
| }); |
| |
| canvas.add(drawingPath); |
| } |
| |
| function addRectangle(position) { |
| const rect = new fabric.Rect({ |
| left: position.x, |
| top: position.y, |
| width: 100, |
| height: 100, |
| fill: 'transparent', |
| stroke: '#ffffff', |
| strokeWidth: 2, |
| selectable: true, |
| hasControls: true, |
| hasBorders: true |
| }); |
| |
| canvas.add(rect); |
| canvas.setActiveObject(rect); |
| canvas.renderAll(); |
| saveHistory(); |
| |
| |
| addLockControls(rect); |
| } |
| |
| function addBlur(position) { |
| const circle = new fabric.Circle({ |
| left: position.x, |
| top: position.y, |
| radius: 30, |
| fill: 'rgba(0,0,0,0.5)', |
| selectable: true, |
| hasControls: true, |
| hasBorders: true |
| }); |
| |
| canvas.add(circle); |
| canvas.setActiveObject(circle); |
| canvas.renderAll(); |
| saveHistory(); |
| |
| |
| addLockControls(circle); |
| } |
| |
| |
| document.getElementById('textColor').addEventListener('input', function(e) { |
| if (activeObject && activeObject.type === 'i-text') { |
| activeObject.set('fill', e.target.value); |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| }); |
| |
| document.getElementById('textSize').addEventListener('input', function(e) { |
| if (activeObject && activeObject.type === 'i-text') { |
| activeObject.set('fontSize', parseInt(e.target.value)); |
| document.getElementById('textSizeValue').textContent = `${e.target.value}px`; |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| }); |
| |
| document.getElementById('textFont').addEventListener('change', function(e) { |
| if (activeObject && activeObject.type === 'i-text') { |
| activeObject.set('fontFamily', e.target.value); |
| canvas.renderAll(); |
| saveHistory(); |
| } |
| }); |
| |
| |
| document.getElementById('brushColor').addEventListener('input', function(e) { |
| |
| }); |
| |
| document.getElementById('brushSize').addEventListener('input', function(e) { |
| document.getElementById('brushSizeValue').textContent = `${e.target.value}px`; |
| }); |
| |
| |
| document.getElementById('saveBtn').addEventListener('click', function() { |
| const dataURL = canvas.toDataURL({ |
| format: 'png', |
| quality: 1 |
| }); |
| |
| const link = document.createElement('a'); |
| link.download = 'snapedit-export.png'; |
| link.href = dataURL; |
| link.click(); |
| |
| showNotification('Image exported successfully!', 'success'); |
| }); |
| |
| document.getElementById('exportBtn').addEventListener('click', function() { |
| |
| showNotification('Export options would appear here', 'info'); |
| }); |
| |
| |
| document.getElementById('aiRemoveBg').addEventListener('click', function() { |
| showNotification('AI Background Removal would run here', 'info'); |
| }); |
| |
| document.getElementById('aiEnhance').addEventListener('click', function() { |
| showNotification('AI Enhancement would run here', 'info'); |
| }); |
| |
| |
| document.addEventListener('keydown', function(e) { |
| |
| const ctrlKey = e.ctrlKey || e.metaKey; |
| |
| |
| if (ctrlKey && e.key === '+') { |
| e.preventDefault(); |
| zoomIn(); |
| } else if (ctrlKey && e.key === '-') { |
| e.preventDefault(); |
| zoomOut(); |
| } else if (ctrlKey && e.key === '0') { |
| e.preventDefault(); |
| resetZoom(); |
| } |
| |
| |
| if ((e.key === 'Delete' || e.key === 'Backspace') && activeObject) { |
| e.preventDefault(); |
| deleteSelectedObject(); |
| } |
| }); |
| |
| |
| canvas.on('after:render', function() { |
| canvas.forEachObject(function(obj) { |
| if (obj.lockButton) { |
| updateLockButtonPosition(obj); |
| } |
| }); |
| }); |
| }); |
| </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=sqibhe/snapeidit" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |