Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced Image Editor | Creative Studio</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> | |
| .editor-container { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| } | |
| .tool-btn { | |
| transition: all 0.3s ease; | |
| } | |
| .tool-btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |
| } | |
| .preview-box { | |
| box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04); | |
| } | |
| .slider-thumb::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #4f46e5; | |
| cursor: pointer; | |
| } | |
| .tab-active { | |
| border-bottom: 3px solid #4f46e5; | |
| } | |
| .image-thumbnail { | |
| transition: all 0.2s ease; | |
| } | |
| .image-thumbnail:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| } | |
| .image-thumbnail.active { | |
| border: 2px solid #4f46e5; | |
| } | |
| .prompt-box { | |
| min-height: 100px; | |
| resize: vertical; | |
| } | |
| .pose-preview { | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| background-position: center; | |
| } | |
| .processing-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(0,0,0,0.7); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| z-index: 10; | |
| } | |
| .face-landmarks { | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| background: rgba(255,0,0,0.5); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| .selection-box { | |
| position: absolute; | |
| border: 2px dashed #4f46e5; | |
| background: rgba(79, 70, 229, 0.2); | |
| z-index: 5; | |
| } | |
| .predictive-options { | |
| position: absolute; | |
| background: white; | |
| border-radius: 4px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| z-index: 20; | |
| padding: 5px; | |
| } | |
| .predictive-option { | |
| padding: 5px 10px; | |
| cursor: pointer; | |
| border-radius: 3px; | |
| } | |
| .predictive-option:hover { | |
| background: #f3f4f6; | |
| } | |
| .effect-thumb { | |
| background-size: cover; | |
| background-position: center; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm"> | |
| <div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-camera-retro text-indigo-600 text-2xl"></i> | |
| <h1 class="text-xl font-bold text-gray-800">Advanced Image Editor</h1> | |
| </div> | |
| <nav class="hidden md:flex space-x-8"> | |
| <a href="#" class="text-gray-600 hover:text-indigo-600 font-medium">Home</a> | |
| <a href="#" class="text-gray-600 hover:text-indigo-600 font-medium">Features</a> | |
| <a href="#" class="text-gray-600 hover:text-indigo-600 font-medium">Tutorials</a> | |
| <a href="#" class="text-gray-600 hover:text-indigo-600 font-medium">Pricing</a> | |
| </nav> | |
| <div class="flex items-center space-x-4"> | |
| <button class="px-4 py-2 rounded-md text-gray-600 hover:bg-gray-100"> | |
| <i class="fas fa-user-circle text-xl"></i> | |
| </button> | |
| <button class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700"> | |
| Upgrade | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <div class="flex flex-col lg:flex-row gap-8"> | |
| <!-- Tools Panel --> | |
| <div class="w-full lg:w-1/4 bg-white rounded-xl shadow-md p-6 h-fit"> | |
| <div class="flex border-b mb-6"> | |
| <button id="image-tab" class="tab-active px-4 py-2 font-medium text-indigo-600">Image</button> | |
| <button id="video-tab" class="px-4 py-2 font-medium text-gray-500 hover:text-indigo-600">Video</button> | |
| </div> | |
| <div id="image-tools"> | |
| <!-- Multi Image Upload --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Upload Reference Images</h3> | |
| <div class="border-2 border-dashed border-gray-300 rounded-lg p-4 text-center cursor-pointer hover:bg-gray-50 mb-3" id="upload-area"> | |
| <i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-2"></i> | |
| <p class="text-gray-500">Drag & drop files here</p> | |
| <p class="text-sm text-gray-400 mt-1">or click to browse</p> | |
| <input type="file" id="file-upload" class="hidden" accept="image/*" multiple> | |
| </div> | |
| <div id="thumbnail-container" class="grid grid-cols-3 gap-2 mt-2 max-h-40 overflow-y-auto"></div> | |
| </div> | |
| <!-- AI Prompts --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">AI Prompts</h3> | |
| <textarea id="ai-prompt" class="w-full border border-gray-300 rounded-md p-2 text-sm prompt-box" placeholder="Describe the changes you want (e.g. 'make breasts larger', 'enhance curves', 'face swap with second image')"></textarea> | |
| <div class="mt-2 grid grid-cols-2 gap-2"> | |
| <button onclick="applyPrompt('Increase breast size by 20%, enhance curves')" class="px-2 py-1 bg-gray-100 text-xs rounded hover:bg-gray-200">Plastic Surgery</button> | |
| <button onclick="applyPrompt('Slim waist, enhance hips, tone stomach')" class="px-2 py-1 bg-gray-100 text-xs rounded hover:bg-gray-200">Body Reshape</button> | |
| <button onclick="showFaceSwapModal()" class="px-2 py-1 bg-gray-100 text-xs rounded hover:bg-gray-200">Face Swap</button> | |
| <button onclick="applyPrompt('Smooth skin, remove blemishes, even skin tone')" class="px-2 py-1 bg-gray-100 text-xs rounded hover:bg-gray-200">Skin Smoothing</button> | |
| </div> | |
| </div> | |
| <!-- Text & Object Removal --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Text & Object Removal</h3> | |
| <div class="grid grid-cols-3 gap-3"> | |
| <button class="tool-btn flex flex-col items-center p-3 bg-gray-100 rounded-lg hover:bg-indigo-50" onclick="startTextRemoval()"> | |
| <i class="fas fa-font text-indigo-600 text-xl mb-1"></i> | |
| <span class="text-xs text-gray-700">Remove Text</span> | |
| </button> | |
| <button class="tool-btn flex flex-col items-center p-3 bg-gray-100 rounded-lg hover:bg-indigo-50" onclick="startObjectSelection()"> | |
| <i class="fas fa-object-group text-indigo-600 text-xl mb-1"></i> | |
| <span class="text-xs text-gray-700">Remove Object</span> | |
| </button> | |
| <button class="tool-btn flex flex-col items-center p-3 bg-gray-100 rounded-lg hover:bg-indigo-50" onclick="startPredictiveEdit()"> | |
| <i class="fas fa-brain text-indigo-600 text-xl mb-1"></i> | |
| <span class="text-xs text-gray-700">Predictive Edit</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Face Tools --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Face Tools</h3> | |
| <div class="grid grid-cols-3 gap-3"> | |
| <button class="tool-btn flex flex-col items-center p-3 bg-gray-100 rounded-lg hover:bg-indigo-50" onclick="showFaceSwapModal()"> | |
| <i class="fas fa-user-edit text-indigo-600 text-xl mb-1"></i> | |
| <span class="text-xs text-gray-700">Face Swap</span> | |
| </button> | |
| <button class="tool-btn flex flex-col items-center p-3 bg-gray-100 rounded-lg hover:bg-indigo-50" onclick="applyPrompt('Make face expression more happy, slight smile')"> | |
| <i class="fas fa-smile text-indigo-600 text-xl mb-1"></i> | |
| <span class="text-xs text-gray-700">Expressions</span> | |
| </button> | |
| <button class="tool-btn flex flex-col items-center p-3 bg-gray-100 rounded-lg hover:bg-indigo-50" onclick="applyPrompt('Beautify face: enhance eyes, perfect lips, symmetrical features')"> | |
| <i class="fas fa-magic text-indigo-600 text-xl mb-1"></i> | |
| <span class="text-xs text-gray-700">Beautify</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Pose Maker --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Pose Maker</h3> | |
| <div class="grid grid-cols-3 gap-2 mb-3"> | |
| <div class="pose-preview h-16 bg-gray-100 rounded cursor-pointer" onclick="applyPose('standing')" style="background-image: url('https://i.imgur.com/JQlE0gP.png')"></div> | |
| <div class="pose-preview h-16 bg-gray-100 rounded cursor-pointer" onclick="applyPose('sitting')" style="background-image: url('https://i.imgur.com/5XkJQqG.png')"></div> | |
| <div class="pose-preview h-16 bg-gray-100 rounded cursor-pointer" onclick="applyPose('dancing')" style="background-image: url('https://i.imgur.com/8zJqWQk.png')"></div> | |
| <div class="pose-preview h-16 bg-gray-100 rounded cursor-pointer" onclick="applyPose('yoga')" style="background-image: url('https://i.imgur.com/3mJQkqG.png')"></div> | |
| <div class="pose-preview h-16 bg-gray-100 rounded cursor-pointer" onclick="applyPose('running')" style="background-image: url('https://i.imgur.com/7XkJQqG.png')"></div> | |
| <div class="pose-preview h-16 bg-gray-100 rounded cursor-pointer" onclick="applyPose('flexing')" style="background-image: url('https://i.imgur.com/9zJqWQk.png')"></div> | |
| </div> | |
| <button class="w-full py-2 bg-indigo-50 text-indigo-600 rounded-md text-sm hover:bg-indigo-100" onclick="showCustomPoseModal()"> | |
| <i class="fas fa-plus mr-1"></i> Custom Pose | |
| </button> | |
| </div> | |
| <!-- Body Tools --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Body Tools</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm text-gray-600 mb-1">Breast Size</label> | |
| <input type="range" class="w-full slider-thumb" id="breast-slider" min="-50" max="50" value="0" oninput="updateBodyParam('breast', this.value)"> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Smaller</span> | |
| <span>Larger</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-600 mb-1">Hip Width</label> | |
| <input type="range" class="w-full slider-thumb" id="hip-slider" min="-50" max="50" value="0" oninput="updateBodyParam('hip', this.value)"> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Narrower</span> | |
| <span>Wider</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-600 mb-1">Waist Slimness</label> | |
| <input type="range" class="w-full slider-thumb" id="waist-slider" min="-50" max="50" value="0" oninput="updateBodyParam('waist', this.value)"> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Wider</span> | |
| <span>Slimmer</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-600 mb-1">Leg Length</label> | |
| <input type="range" class="w-full slider-thumb" id="leg-slider" min="-50" max="50" value="0" oninput="updateBodyParam('leg', this.value)"> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Shorter</span> | |
| <span>Longer</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Effects --> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Effects</h3> | |
| <div class="grid grid-cols-4 gap-2"> | |
| <div class="effect-thumb bg-gray-200 rounded-md h-16 cursor-pointer hover:ring-2 hover:ring-indigo-500" style="background-image: url('https://i.imgur.com/JQlE0gP.png')" onclick="applyEffect('vintage')"></div> | |
| <div class="effect-thumb bg-gray-300 rounded-md h-16 cursor-pointer hover:ring-2 hover:ring-indigo-500" style="background-image: url('https://i.imgur.com/5XkJQqG.png')" onclick="applyEffect('bw')"></div> | |
| <div class="effect-thumb bg-gray-400 rounded-md h-16 cursor-pointer hover:ring-2 hover:ring-indigo-500" style="background-image: url('https://i.imgur.com/8zJqWQk.png')" onclick="applyEffect('warm')"></div> | |
| <div class="effect-thumb bg-gray-500 rounded-md h-16 cursor-pointer hover:ring-2 hover:ring-indigo-500" style="background-image: url('https://i.imgur.com/3mJQkqG.png')" onclick="applyEffect('cool')"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="video-tools" class="hidden"> | |
| <!-- Video tools content --> | |
| <div class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-video text-3xl mb-2"></i> | |
| <p>Video editing coming soon!</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Editor Area --> | |
| <div class="w-full lg:w-2/4 editor-container rounded-xl p-6"> | |
| <div class="preview-box bg-white rounded-lg overflow-hidden relative" style="height: 500px;"> | |
| <div class="absolute inset-0 flex items-center justify-center" id="placeholder"> | |
| <div class="text-center"> | |
| <i class="fas fa-image text-gray-300 text-5xl mb-3"></i> | |
| <p class="text-gray-400">Upload an image to start editing</p> | |
| </div> | |
| </div> | |
| <img id="preview-image" src="" alt="" class="hidden w-full h-full object-contain"> | |
| <video id="preview-video" controls class="hidden w-full h-full object-contain"></video> | |
| <div id="processing-overlay" class="processing-overlay hidden"> | |
| <i class="fas fa-spinner fa-spin text-4xl mb-4"></i> | |
| <p id="processing-text">Processing your image...</p> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5 mt-4 max-w-md"> | |
| <div id="progress-bar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-center space-x-4"> | |
| <button id="undo-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 disabled:opacity-50" disabled> | |
| <i class="fas fa-undo mr-2"></i> Undo | |
| </button> | |
| <button id="redo-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 disabled:opacity-50" disabled> | |
| <i class="fas fa-redo mr-2"></i> Redo | |
| </button> | |
| <button id="reset-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300" onclick="resetEditor()"> | |
| <i class="fas fa-trash-alt mr-2"></i> Reset | |
| </button> | |
| <button id="process-btn" class="px-6 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700" onclick="processImage()"> | |
| <i class="fas fa-cogs mr-2"></i> Process | |
| </button> | |
| <button id="save-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700" onclick="saveImage()"> | |
| <i class="fas fa-download mr-2"></i> Save | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Layers Panel --> | |
| <div class="w-full lg:w-1/4 bg-white rounded-xl shadow-md p-6 h-fit"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-medium text-gray-700">Reference Images</h3> | |
| <button class="text-indigo-600 hover:text-indigo-800" onclick="document.getElementById('file-upload').click()"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| </div> | |
| <div id="reference-container" class="space-y-3 max-h-64 overflow-y-auto"> | |
| <!-- Reference images will appear here --> | |
| </div> | |
| <div class="mt-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Text Removal Settings</h3> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-600 mb-1">Detection Sensitivity</label> | |
| <input type="range" id="text-sensitivity" class="w-full slider-thumb" min="0" max="100" value="50"> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-600 mb-1">Blending Quality</label> | |
| <input type="range" id="blend-quality" class="w-full slider-thumb" min="0" max="100" value="75"> | |
| </div> | |
| <button onclick="applyTextRemoval()" class="w-full py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700"> | |
| Remove Selected Text | |
| </button> | |
| </div> | |
| <div class="mt-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Object Removal Settings</h3> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-600 mb-1">Selection Precision</label> | |
| <input type="range" id="object-precision" class="w-full slider-thumb" min="0" max="100" value="50"> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-600 mb-1">Content-Aware Fill</label> | |
| <select id="fill-method" class="w-full p-2 border border-gray-300 rounded-md text-sm"> | |
| <option value="smart">Smart Fill</option> | |
| <option value="blur">Background Blur</option> | |
| <option value="extend">Edge Extension</option> | |
| </select> | |
| </div> | |
| <button onclick="applyObjectRemoval()" class="w-full py-2 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700"> | |
| Remove Selected Object | |
| </button> | |
| </div> | |
| <div class="mt-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Predictive AI</h3> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-600 mb-1">AI Creativity</label> | |
| <input type="range" id="ai-creativity" class="w-full slider-thumb" min="0" max="100" value="60"> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-600 mb-1">Style Match</label> | |
| <input type="range" id="style-match" class="w-full slider-thumb" min="0" max="100" value="80"> | |
| </div> | |
| <button onclick="applyPredictiveEdit()" class="w-full py-2 bg-purple-600 text-white rounded-md text-sm hover:bg-purple-700"> | |
| Apply Predictive Edit | |
| </button> | |
| </div> | |
| <div class="mt-6"> | |
| <h3 class="font-medium text-gray-700 mb-3">Export Options</h3> | |
| <div class="space-y-3"> | |
| <div class="flex items-center"> | |
| <input type="radio" id="format-jpg" name="export-format" class="mr-2" checked> | |
| <label for="format-jpg" class="text-sm">JPG</label> | |
| </div> | |
| <div class="flex items-center"> | |
| <input type="radio" id="format-png" name="export-format" class="mr-2"> | |
| <label for="format-png" class="text-sm">PNG</label> | |
| </div> | |
| <div class="flex items-center"> | |
| <input type="radio" id="format-webp" name="export-format" class="mr-2"> | |
| <label for="format-webp" class="text-sm">WebP</label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Face Swap Modal --> | |
| <div id="face-swap-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-2xl"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-medium">Face Swap</h3> | |
| <button onclick="hideFaceSwapModal()" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div> | |
| <h4 class="text-sm font-medium mb-2">Source Face</h4> | |
| <select id="modal-source-face" class="w-full p-2 border border-gray-300 rounded-md text-sm mb-2" onchange="updateFacePreview('source')"> | |
| <option value="">Select reference image</option> | |
| </select> | |
| <div class="border rounded-md h-40 bg-gray-100 flex items-center justify-center relative" id="source-face-preview"> | |
| <span class="text-gray-400">No image selected</span> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="text-sm font-medium mb-2">Target Face</h4> | |
| <select id="modal-target-face" class="w-full p-2 border border-gray-300 rounded-md text-sm mb-2" onchange="updateFacePreview('target')"> | |
| <option value="">Select reference image</option> | |
| </select> | |
| <div class="border rounded-md h-40 bg-gray-100 flex items-center justify-center relative" id="target-face-preview"> | |
| <span class="text-gray-400">No image selected</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm text-gray-600 mb-1">Face Alignment Strength</label> | |
| <input type="range" id="face-align-strength" class="w-full slider-thumb" min="0" max="100" value="75"> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button onclick="hideFaceSwapModal()" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300"> | |
| Cancel | |
| </button> | |
| <button onclick="applyModalFaceSwap()" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700"> | |
| Apply Face Swap | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Custom Pose Modal --> | |
| <div id="custom-pose-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-2xl"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-medium">Create Custom Pose</h3> | |
| <button onclick="hideCustomPoseModal()" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm text-gray-600 mb-1">Pose Description</label> | |
| <textarea id="pose-description" class="w-full border border-gray-300 rounded-md p-2 text-sm" placeholder="Describe the pose you want (e.g. 'arms raised, left leg forward, slight torso twist')"></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm text-gray-600 mb-1">Pose Strength</label> | |
| <input type="range" id="pose-strength" class="w-full slider-thumb" min="0" max="100" value="50"> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button onclick="hideCustomPoseModal()" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300"> | |
| Cancel | |
| </button> | |
| <button onclick="applyCustomPose()" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700"> | |
| Create Pose | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="bg-gray-800 text-white py-8 mt-12"> | |
| <div class="container mx-auto px-4"> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-8"> | |
| <div> | |
| <h3 class="text-lg font-medium mb-4">Advanced Image Editor</h3> | |
| <p class="text-gray-400 text-sm">Professional photo editing tools with AI-powered enhancements.</p> | |
| </div> | |
| <div> | |
| <h4 class="font-medium mb-4">Features</h4> | |
| <ul class="space-y-2 text-sm text-gray-400"> | |
| <li><a href="#" class="hover:text-white">Face Swap</a></li> | |
| <li><a href="#" class="hover:text-white">Body Reshaping</a></li> | |
| <li><a href="#" class="hover:text-white">AI Enhancements</a></li> | |
| <li><a href="#" class="hover:text-white">Pose Maker</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-medium mb-4">Company</h4> | |
| <ul class="space-y-2 text-sm text-gray-400"> | |
| <li><a href="#" class="hover:text-white">About Us</a></li> | |
| <li><a href="#" class="hover:text-white">Careers</a></li> | |
| <li><a href="#" class="hover:text-white">Blog</a></li> | |
| <li><a href="#" class="hover:text-white">Contact</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-medium mb-4">Legal</h4> | |
| <ul class="space-y-2 text-sm text-gray-400"> | |
| <li><a href="#" class="hover:text-white">Terms of Service</a></li> | |
| <li><a href="#" class="hover:text-white">Privacy Policy</a></li> | |
| <li><a href="#" class="hover:text-white">Content Policy</a></li> | |
| <li><a href="#" class="hover:text-white">Community Guidelines</a></li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="border-t border-gray-700 mt-8 pt-6 text-sm text-gray-400"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <p>© 2023 Advanced Image Editor. All rights reserved.</p> | |
| <div class="flex space-x-6 mt-4 md:mt-0"> | |
| <a href="#" class="hover:text-white"><i class="fab fa-facebook-f"></i></a> | |
| <a href="#" class="hover:text-white"><i class="fab fa-twitter"></i></a> | |
| <a href="#" class="hover:text-white"><i class="fab fa-instagram"></i></a> | |
| <a href="#" class="hover:text-white"><i class="fab fa-youtube"></i></a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| // Global variables | |
| let uploadedImages = []; | |
| let currentImageIndex = 0; | |
| let editHistory = []; | |
| let currentHistoryIndex = -1; | |
| let bodyParams = { | |
| breast: 0, | |
| hip: 0, | |
| waist: 0, | |
| leg: 0 | |
| }; | |
| // Selection variables | |
| let isSelecting = false; | |
| let selectionStartX = 0; | |
| let selectionStartY = 0; | |
| let selectionBox = null; | |
| let currentSelectionType = null; // 'text' or 'object' | |
| let predictiveOptionsVisible = false; | |
| // Tab switching functionality | |
| document.getElementById('image-tab').addEventListener('click', function() { | |
| this.classList.add('tab-active'); | |
| document.getElementById('video-tab').classList.remove('tab-active'); | |
| document.getElementById('image-tools').classList.remove('hidden'); | |
| document.getElementById('video-tools').classList.add('hidden'); | |
| }); | |
| document.getElementById('video-tab').addEventListener('click', function() { | |
| this.classList.add('tab-active'); | |
| document.getElementById('image-tab').classList.remove('tab-active'); | |
| document.getElementById('video-tools').classList.remove('hidden'); | |
| document.getElementById('image-tools').classList.add('hidden'); | |
| }); | |
| // File upload functionality | |
| const fileUpload = document.getElementById('file-upload'); | |
| const uploadArea = document.getElementById('upload-area'); | |
| const thumbnailContainer = document.getElementById('thumbnail-container'); | |
| const referenceContainer = document.getElementById('reference-container'); | |
| const previewImage = document.getElementById('preview-image'); | |
| const previewVideo = document.getElementById('preview-video'); | |
| const placeholder = document.getElementById('placeholder'); | |
| // Handle drag and drop | |
| uploadArea.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.add('bg-indigo-50', 'border-indigo-300'); | |
| }); | |
| uploadArea.addEventListener('dragleave', () => { | |
| uploadArea.classList.remove('bg-indigo-50', 'border-indigo-300'); | |
| }); | |
| uploadArea.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('bg-indigo-50', 'border-indigo-300'); | |
| if (e.dataTransfer.files.length > 0) { | |
| fileUpload.files = e.dataTransfer.files; | |
| handleFileUpload(); | |
| } | |
| }); | |
| uploadArea.addEventListener('click', () => fileUpload.click()); | |
| fileUpload.addEventListener('change', handleFileUpload); | |
| function handleFileUpload() { | |
| const files = fileUpload.files; | |
| if (!files || files.length === 0) return; | |
| for (let i = 0; i < files.length; i++) { | |
| const file = files[i]; | |
| if (!file.type.startsWith('image/')) continue; | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| const imageData = { | |
| id: Date.now() + i, | |
| src: event.target.result, | |
| name: file.name, | |
| originalSrc: event.target.result | |
| }; | |
| uploadedImages.push(imageData); | |
| // Add to thumbnail container | |
| const thumbnail = document.createElement('div'); | |
| thumbnail.className = `image-thumbnail relative ${uploadedImages.length === 1 ? 'active' : ''}`; | |
| thumbnail.innerHTML = ` | |
| <img src="${imageData.src}" class="w-full h-full object-cover rounded"> | |
| <div class="absolute inset-0 flex items-center justify-center bg-black bg-opacity-0 hover:bg-opacity-30 transition-all duration-200"> | |
| <button onclick="setActiveImage(${imageData.id})" class="text-white opacity-0 hover:opacity-100"> | |
| <i class="fas fa-check-circle text-xl"></i> | |
| </button> | |
| </div> | |
| `; | |
| thumbnailContainer.appendChild(thumbnail); | |
| // Add to reference container | |
| const referenceItem = document.createElement('div'); | |
| referenceItem.className = 'flex items-center p-2 bg-gray-50 rounded-md'; | |
| referenceItem.innerHTML = ` | |
| <div class="w-10 h-10 bg-gray-200 rounded-md mr-3 overflow-hidden"> | |
| <img src="${imageData.src}" class="w-full h-full object-cover"> | |
| </div> | |
| <div class="flex-1 truncate"> | |
| <p class="text-sm font-medium truncate">${file.name}</p> | |
| <p class="text-xs text-gray-500">${(file.size / 1024).toFixed(1)} KB</p> | |
| </div> | |
| <button onclick="removeImage(${imageData.id})" class="text-gray-400 hover:text-gray-600 ml-2"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| `; | |
| referenceContainer.appendChild(referenceItem); | |
| // Update select options | |
| updateSelectOptions(); | |
| // Set first image as active | |
| if (uploadedImages.length === 1) { | |
| setActiveImage(imageData.id); | |
| } | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| } | |
| function setActiveImage(id) { | |
| const imageData = uploadedImages.find(img => img.id === id); | |
| if (!imageData) return; | |
| currentImageIndex = uploadedImages.findIndex(img => img.id === id); | |
| previewImage.src = imageData.src; | |
| previewImage.classList.remove('hidden'); | |
| previewVideo.classList.add('hidden'); | |
| placeholder.classList.add('hidden'); | |
| // Update active thumbnail | |
| document.querySelectorAll('.image-thumbnail').forEach((thumb, index) => { | |
| if (index === currentImageIndex) { | |
| thumb.classList.add('active'); | |
| } else { | |
| thumb.classList.remove('active'); | |
| } | |
| }); | |
| // Save to history | |
| saveToHistory(); | |
| } | |
| function removeImage(id) { | |
| const index = uploadedImages.findIndex(img => img.id === id); | |
| if (index === -1) return; | |
| uploadedImages.splice(index, 1); | |
| // Remove from thumbnail container | |
| thumbnailContainer.children[index].remove(); | |
| // Remove from reference container | |
| referenceContainer.children[index].remove(); | |
| // Update select options | |
| updateSelectOptions(); | |
| // If we removed the active image | |
| if (currentImageIndex === index) { | |
| if (uploadedImages.length > 0) { | |
| // Set next available image as active | |
| const newIndex = Math.min(index, uploadedImages.length - 1); | |
| setActiveImage(uploadedImages[newIndex].id); | |
| } else { | |
| // No images left | |
| previewImage.src = ''; | |
| previewImage.classList.add('hidden'); | |
| placeholder.classList.remove('hidden'); | |
| } | |
| } else if (currentImageIndex > index) { | |
| currentImageIndex--; | |
| } | |
| saveToHistory(); | |
| } | |
| function updateSelectOptions() { | |
| const modalSourceFace = document.getElementById('modal-source-face'); | |
| const modalTargetFace = document.getElementById('modal-target-face'); | |
| // Clear existing options except first | |
| [modalSourceFace, modalTargetFace].forEach(select => { | |
| while (select.options.length > 1) { | |
| select.remove(1); | |
| } | |
| }); | |
| // Add new options | |
| uploadedImages.forEach((img, index) => { | |
| const option = document.createElement('option'); | |
| option.value = img.id; | |
| option.textContent = `Image ${index + 1}`; | |
| const option2 = option.cloneNode(true); | |
| modalSourceFace.appendChild(option); | |
| modalTargetFace.appendChild(option2); | |
| }); | |
| } | |
| // History management | |
| function saveToHistory() { | |
| if (uploadedImages.length === 0) return; | |
| // Truncate history if we're not at the end | |
| if (currentHistoryIndex < editHistory.length - 1) { | |
| editHistory = editHistory.slice(0, currentHistoryIndex + 1); | |
| } | |
| // Save current state | |
| const historyItem = { | |
| imageSrc: uploadedImages[currentImageIndex].src, | |
| bodyParams: {...bodyParams} | |
| }; | |
| editHistory.push(historyItem); | |
| currentHistoryIndex = editHistory.length - 1; | |
| // Update undo/redo buttons | |
| document.getElementById('undo-btn').disabled = currentHistoryIndex <= 0; | |
| document.getElementById('redo-btn').disabled = currentHistoryIndex >= editHistory.length - 1; | |
| } | |
| function undo() { | |
| if (currentHistoryIndex <= 0) return; | |
| currentHistoryIndex--; | |
| applyHistoryState(); | |
| } | |
| function redo() { | |
| if (currentHistoryIndex >= editHistory.length - 1) return; | |
| currentHistoryIndex++; | |
| applyHistoryState(); | |
| } | |
| function applyHistoryState() { | |
| const historyItem = editHistory[currentHistoryIndex]; | |
| // Update image | |
| previewImage.src = historyItem.imageSrc; | |
| uploadedImages[currentImageIndex].src = historyItem.imageSrc; | |
| // Update body params | |
| bodyParams = {...historyItem.bodyParams}; | |
| document.getElementById('breast-slider').value = bodyParams.breast; | |
| document.getElementById('hip-slider').value = bodyParams.hip; | |
| document.getElementById('waist-slider').value = bodyParams.waist; | |
| document.getElementById('leg-slider').value = bodyParams.leg; | |
| // Update buttons | |
| document.getElementById('undo-btn').disabled = currentHistoryIndex <= 0; | |
| document.getElementById('redo-btn').disabled = currentHistoryIndex >= editHistory.length - 1; | |
| } | |
| // Reset button functionality | |
| function resetEditor() { | |
| if (uploadedImages.length === 0) return; | |
| previewImage.src = uploadedImages[currentImageIndex].originalSrc; | |
| uploadedImages[currentImageIndex].src = uploadedImages[currentImageIndex].originalSrc; | |
| // Reset sliders | |
| document.getElementById('breast-slider').value = 0; | |
| document.getElementById('hip-slider').value = 0; | |
| document.getElementById('waist-slider').value = 0; | |
| document.getElementById('leg-slider').value = 0; | |
| bodyParams = { breast: 0, hip: 0, waist: 0, leg: 0 }; | |
| // Clear history | |
| editHistory = []; | |
| currentHistoryIndex = -1; | |
| document.getElementById('undo-btn').disabled = true; | |
| document.getElementById('redo-btn').disabled = true; | |
| saveToHistory(); | |
| } | |
| // Process button functionality | |
| function processImage() { | |
| if (!previewImage.src) { | |
| alert('Please upload an image first'); | |
| return; | |
| } | |
| const promptBox = document.getElementById('ai-prompt'); | |
| const prompt = promptBox.value; | |
| if (!prompt && Object.values(bodyParams).every(val => val === 0)) { | |
| alert('Please enter a prompt or adjust body parameters'); | |
| return; | |
| } | |
| // Show processing overlay | |
| const overlay = document.getElementById('processing-overlay'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const processingText = document.getElementById('processing-text'); | |
| overlay.classList.remove('hidden'); | |
| processingText.textContent = 'Processing your image...'; | |
| // Simulate processing with progress | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 10; | |
| if (progress > 100) progress = 100; | |
| progressBar.style.width = `${progress}%`; | |
| if (progress === 100) { | |
| clearInterval(interval); | |
| processingText.textContent = 'Finalizing results...'; | |
| setTimeout(() => { | |
| overlay.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| // In a real app, this would call your AI processing API | |
| // For demo, we'll just modify the image slightly | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Apply simple filter to simulate processing | |
| if (prompt.includes('breast') || bodyParams.breast !== 0) { | |
| // Simulate breast enhancement | |
| ctx.fillStyle = 'rgba(255, 200, 200, 0.1)'; | |
| ctx.beginPath(); | |
| ctx.ellipse(canvas.width * 0.4, canvas.height * 0.4, | |
| canvas.width * 0.1 * (1 + bodyParams.breast/100), | |
| canvas.height * 0.15 * (1 + bodyParams.breast/100), | |
| 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.beginPath(); | |
| ctx.ellipse(canvas.width * 0.6, canvas.height * 0.4, | |
| canvas.width * 0.1 * (1 + bodyParams.breast/100), | |
| canvas.height * 0.15 * (1 + bodyParams.breast/100), | |
| 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| if (prompt.includes('hip') || bodyParams.hip !== 0) { | |
| // Simulate hip enhancement | |
| ctx.fillStyle = 'rgba(200, 200, 255, 0.1)'; | |
| ctx.beginPath(); | |
| ctx.ellipse(canvas.width * 0.5, canvas.height * 0.6, | |
| canvas.width * 0.15 * (1 + bodyParams.hip/100), | |
| canvas.height * 0.1 * (1 + bodyParams.hip/100), | |
| 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| if (prompt.includes('waist') || bodyParams.waist !== 0) { | |
| // Simulate waist enhancement | |
| ctx.fillStyle = 'rgba(255, 255, 200, 0.1)'; | |
| ctx.beginPath(); | |
| ctx.ellipse(canvas.width * 0.5, canvas.height * 0.5, | |
| canvas.width * 0.1 * (1 - bodyParams.waist/200), | |
| canvas.height * 0.15 * (1 - bodyParams.waist/200), | |
| 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // Apply effects based on prompt | |
| if (prompt.includes('smooth') || prompt.includes('skin')) { | |
| // Simulate skin smoothing | |
| ctx.filter = 'blur(1px)'; | |
| ctx.drawImage(canvas, 0, 0); | |
| ctx.filter = 'none'; | |
| } | |
| // Update the image | |
| const newSrc = canvas.toDataURL('image/jpeg'); | |
| previewImage.src = newSrc; | |
| uploadedImages[currentImageIndex].src = newSrc; | |
| saveToHistory(); | |
| }; | |
| img.src = previewImage.src; | |
| alert('Processing complete!'); | |
| }, 500); | |
| } | |
| }, 100); | |
| } | |
| // Save button functionality | |
| function saveImage() { | |
| if (!previewImage.src && !previewVideo.src) { | |
| alert('Please upload an image or video first'); | |
| return; | |
| } | |
| // Get selected format | |
| let format = 'jpg'; | |
| if (document.getElementById('format-png').checked) format = 'png'; | |
| if (document.getElementById('format-webp').checked) format = 'webp'; | |
| // Simulate download | |
| const link = document.createElement('a'); | |
| if (previewImage.src) { | |
| link.href = previewImage.src; | |
| link.download = `edited-image.${format}`; | |
| } else { | |
| link.href = previewVideo.src; | |
| link.download = 'edited-video.mp4'; | |
| } | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| // Body parameter updates | |
| function updateBodyParam(param, value) { | |
| bodyParams[param] = parseInt(value); | |
| saveToHistory(); | |
| } | |
| // AI Prompt functions | |
| function applyPrompt(prompt) { | |
| document.getElementById('ai-prompt').value = prompt; | |
| processImage(); | |
| } | |
| // Face Swap Functions | |
| function showFaceSwapModal() { | |
| if (uploadedImages.length < 2) { | |
| alert('You need at least 2 images to perform face swap'); | |
| return; | |
| } | |
| document.getElementById('face-swap-modal').classList.remove('hidden'); | |
| } | |
| function hideFaceSwapModal() { | |
| document.getElementById('face-swap-modal').classList.add('hidden'); | |
| } | |
| function updateFacePreview(type) { | |
| const select = type === 'source' ? | |
| document.getElementById('modal-source-face') : | |
| document.getElementById('modal-target-face'); | |
| const preview = document.getElementById(`${type}-face-preview`); | |
| const selectedId = select.value; | |
| if (!selectedId) { | |
| preview.innerHTML = '<span class="text-gray-400">No image selected</span>'; | |
| return; | |
| } | |
| const imageData = uploadedImages.find(img => img.id == selectedId); | |
| if (!imageData) return; | |
| // Clear previous preview | |
| preview.innerHTML = ''; | |
| // Add image to preview | |
| const img = document.createElement('img'); | |
| img.src = imageData.src; | |
| img.className = 'w-full h-full object-contain'; | |
| preview.appendChild(img); | |
| // Simulate face detection with dots | |
| for (let i = 0; i < 5; i++) { | |
| const dot = document.createElement('div'); | |
| dot.className = 'face-landmarks'; | |
| dot.style.left = `${50 + Math.random() * 20}%`; | |
| dot.style.top = `${40 + Math.random() * 20}%`; | |
| preview.appendChild(dot); | |
| } | |
| } | |
| function applyModalFaceSwap() { | |
| const sourceId = document.getElementById('modal-source-face').value; | |
| const targetId = document.getElementById('modal-target-face').value; | |
| if (!sourceId || !targetId) { | |
| alert('Please select both source and target faces'); | |
| return; | |
| } | |
| if (sourceId === targetId) { | |
| alert('Source and target faces must be different'); | |
| return; | |
| } | |
| const strength = document.getElementById('face-align-strength').value; | |
| // Show processing | |
| const overlay = document.getElementById('processing-overlay'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const processingText = document.getElementById('processing-text'); | |
| overlay.classList.remove('hidden'); | |
| processingText.textContent = 'Swapping faces...'; | |
| // Simulate face swap with progress | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 15; | |
| if (progress > 100) progress = 100; | |
| progressBar.style.width = `${progress}%`; | |
| if (progress === 100) { | |
| clearInterval(interval); | |
| // In a real app, this would call your face swap API | |
| // For demo, we'll just blend the images slightly | |
| const sourceImg = uploadedImages.find(img => img.id == sourceId); | |
| const targetImg = uploadedImages.find(img => img.id == targetId); | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Draw source face over target | |
| const sourceFace = new Image(); | |
| sourceFace.onload = function() { | |
| // Simulate face swap by blending | |
| ctx.globalAlpha = strength / 100; | |
| ctx.drawImage(sourceFace, | |
| canvas.width * 0.3, canvas.height * 0.2, | |
| canvas.width * 0.4, canvas.height * 0.4); | |
| ctx.globalAlpha = 1.0; | |
| // Update the image | |
| const newSrc = canvas.toDataURL('image/jpeg'); | |
| previewImage.src = newSrc; | |
| uploadedImages[currentImageIndex].src = newSrc; | |
| hideFaceSwapModal(); | |
| overlay.classList.add('hidden'); | |
| progressBar.style.width = '0%'; | |
| saveToHistory(); | |
| }; | |
| sourceFace.src = sourceImg.src; | |
| }; | |
| img.src = targetImg.src; | |
| } | |
| }, 100); | |
| } | |
| // Pose functions | |
| function applyPose(poseType) { | |
| alert(`Applying ${poseType} pose to the image`); | |
| // In a real app, this would call pose estimation/application API | |
| processImage(); | |
| } | |
| function showCustomPoseModal() { | |
| document.getElementById('custom-pose-modal').classList.remove('hidden'); | |
| } | |
| function hideCustomPoseModal() { | |
| document.getElementById('custom-pose-modal').classList.add('hidden'); | |
| } | |
| function applyCustomPose() { | |
| const description = document.getElementById('pose-description').value; | |
| const strength = document.getElementById('pose-strength').value; | |
| if (!description) { | |
| alert('Please describe the pose you want to create'); | |
| return; | |
| } | |
| alert(`Creating custom pose: ${description} with strength ${strength}`); | |
| hideCustomPoseModal(); | |
| processImage(); | |
| } | |
| // Text/Object Removal functions | |
| function startTextRemoval() { | |
| if (!previewImage.src) { | |
| alert('Please upload an image first'); | |
| return; | |
| } | |
| currentSelectionType = 'text'; | |
| alert('Click and drag to select text to remove'); | |
| setupSelection(); | |
| } | |
| function startObjectSelection() { | |
| if (!previewImage.src) { | |
| alert('Please upload an image first'); | |
| return; | |
| } | |
| currentSelectionType = 'object'; | |
| alert('Click and drag to select object to remove'); | |
| setupSelection(); | |
| } | |
| function setupSelection() { | |
| const previewBox = document.querySelector('.preview-box'); | |
| // Remove existing selection box if any | |
| if (selectionBox) { | |
| previewBox.removeChild(selectionBox); | |
| } | |
| // Create new selection box | |
| selectionBox = document.createElement('div'); | |
| selectionBox.className = 'selection-box hidden'; | |
| previewBox.appendChild(selectionBox); | |
| // Set up event listeners | |
| previewBox.onmousedown = startSelection; | |
| previewBox.onmousemove = updateSelection; | |
| previewBox.onmouseup = endSelection; | |
| } | |
| function startSelection(e) { | |
| if (!currentSelectionType) return; | |
| isSelecting = true; | |
| const rect = e.target.getBoundingClientRect(); | |
| selectionStartX = e.clientX - rect.left; | |
| selectionStartY = e.clientY - rect.top; | |
| selectionBox.style.left = `${selectionStartX}px`; | |
| selectionBox.style.top = `${selectionStartY}px`; | |
| selectionBox.style.width = '0px'; | |
| selectionBox.style.height = '0px'; | |
| selectionBox.classList.remove('hidden'); | |
| } | |
| function updateSelection(e) { | |
| if (!isSelecting) return; | |
| const rect = e.target.getBoundingClientRect(); | |
| const currentX = e.clientX - rect.left; | |
| const currentY = e.clientY - rect.top; | |
| const width = currentX - selectionStartX; | |
| const height = currentY - selectionStartY; | |
| selectionBox.style.width = `${Math.abs(width)}px`; | |
| selectionBox.style.height = `${Math.abs(height)}px`; | |
| if (width < 0) { | |
| selectionBox.style.left = `${currentX}px`; | |
| } | |
| if (height < 0) { | |
| selectionBox.style.top = `${currentY}px`; | |
| } | |
| } | |
| function endSelection() { | |
| isSelecting = false; | |
| // Remove event listeners | |
| const previewBox = document.querySelector('.preview-box'); | |
| previewBox.onmousedown = null; | |
| previewBox.onmousemove = null; | |
| previewBox.onmouseup = null; | |
| } | |
| function applyTextRemoval() { | |
| if (!selectionBox || selectionBox.classList.contains('hidden')) { | |
| alert('Please select text to remove first'); | |
| return; | |
| } | |
| const sensitivity = document.getElementById('text-sensitivity').value; | |
| const quality = document.getElementById('blend-quality').value; | |
| alert(`Removing selected text with sensitivity ${sensitivity} and quality ${quality}`); | |
| // Simulate text removal | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Get selection box coordinates | |
| const rect = selectionBox.getBoundingClientRect(); | |
| const previewRect = document.querySelector('.preview-box').getBoundingClientRect(); | |
| const x = (rect.left - previewRect.left) / previewRect.width * canvas.width; | |
| const y = (rect.top - previewRect.top) / previewRect.height * canvas.height; | |
| const width = rect.width / previewRect.width * canvas.width; | |
| const height = rect.height / previewRect.height * canvas.height; | |
| // Remove text by filling with surrounding colors | |
| ctx.fillStyle = ctx.getImageData(x, y, 1, 1).data; | |
| ctx.fillRect(x, y, width, height); | |
| // Update the image | |
| const newSrc = canvas.toDataURL('image/jpeg'); | |
| previewImage.src = newSrc; | |
| uploadedImages[currentImageIndex].src = newSrc; | |
| // Hide selection box | |
| selectionBox.classList.add('hidden'); | |
| currentSelectionType = null; | |
| saveToHistory(); | |
| }; | |
| img.src = previewImage.src; | |
| } | |
| function applyObjectRemoval() { | |
| if (!selectionBox || selectionBox.classList.contains('hidden')) { | |
| alert('Please select object to remove first'); | |
| return; | |
| } | |
| const precision = document.getElementById('object-precision').value; | |
| const method = document.getElementById('fill-method').value; | |
| alert(`Removing selected object with precision ${precision} using ${method} method`); | |
| // Simulate object removal | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Get selection box coordinates | |
| const rect = selectionBox.getBoundingClientRect(); | |
| const previewRect = document.querySelector('.preview-box').getBoundingClientRect(); | |
| const x = (rect.left - previewRect.left) / previewRect.width * canvas.width; | |
| const y = (rect.top - previewRect.top) / previewRect.height * canvas.height; | |
| const width = rect.width / previewRect.width * canvas.width; | |
| const height = rect.height / previewRect.height * canvas.height; | |
| // Remove object based on selected method | |
| if (method === 'blur') { | |
| // Blur method | |
| const imageData = ctx.getImageData(x, y, width, height); | |
| for (let i = 0; i < imageData.data.length; i += 4) { | |
| const avg = (imageData.data[i] + imageData.data[i+1] + imageData.data[i+2]) / 3; | |
| imageData.data[i] = avg; | |
| imageData.data[i+1] = avg; | |
| imageData.data[i+2] = avg; | |
| } | |
| ctx.putImageData(imageData, x, y); | |
| } else if (method === 'extend') { | |
| // Edge extension method | |
| const edgeColor = ctx.getImageData(x, y, 1, 1).data; | |
| ctx.fillStyle = `rgb(${edgeColor[0]}, ${edgeColor[1]}, ${edgeColor[2]})`; | |
| ctx.fillRect(x, y, width, height); | |
| } else { | |
| // Smart fill (default) | |
| const surrounding = ctx.getImageData(x-1, y-1, width+2, height+2); | |
| for (let i = 0; i < surrounding.data.length; i += 4) { | |
| surrounding.data[i] = 255 - surrounding.data[i]; // Invert colors for demo | |
| } | |
| ctx.putImageData(surrounding, x-1, y-1); | |
| } | |
| // Update the image | |
| const newSrc = canvas.toDataURL('image/jpeg'); | |
| previewImage.src = newSrc; | |
| uploadedImages[currentImageIndex].src = newSrc; | |
| // Hide selection box | |
| selectionBox.classList.add('hidden'); | |
| currentSelectionType = null; | |
| saveToHistory(); | |
| }; | |
| img.src = previewImage.src; | |
| } | |
| // Predictive Edit functions | |
| function startPredictiveEdit() { | |
| if (!previewImage.src) { | |
| alert('Please upload an image first'); | |
| return; | |
| } | |
| currentSelectionType = 'predictive'; | |
| alert('Click and drag to select area for predictive edit'); | |
| setupSelection(); | |
| } | |
| function applyPredictiveEdit() { | |
| if (!selectionBox || selectionBox.classList.contains('hidden')) { | |
| alert('Please select area for predictive edit first'); | |
| return; | |
| } | |
| const creativity = document.getElementById('ai-creativity').value; | |
| const styleMatch = document.getElementById('style-match').value; | |
| alert(`Applying predictive edit with creativity ${creativity} and style match ${styleMatch}`); | |
| // Simulate predictive edit | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Get selection box coordinates | |
| const rect = selectionBox.getBoundingClientRect(); | |
| const previewRect = document.querySelector('.preview-box').getBoundingClientRect(); | |
| const x = (rect.left - previewRect.left) / previewRect.width * canvas.width; | |
| const y = (rect.top - previewRect.top) / previewRect.height * canvas.height; | |
| const width = rect.width / previewRect.width * canvas.width; | |
| const height = rect.height / previewRect.height * canvas.height; | |
| // Apply "predictive" effect (just a color shift for demo) | |
| const imageData = ctx.getImageData(x, y, width, height); | |
| for (let i = 0; i < imageData.data.length; i += 4) { | |
| // Shift colors based on creativity | |
| imageData.data[i] = Math.min(255, imageData.data[i] * (1 + creativity/200)); // Red | |
| imageData.data[i+1] = Math.min(255, imageData.data[i+1] * (1 + creativity/300)); // Green | |
| imageData.data[i+2] = Math.min(255, imageData.data[i+2] * (1 + creativity/400)); // Blue | |
| } | |
| ctx.putImageData(imageData, x, y); | |
| // Update the image | |
| const newSrc = canvas.toDataURL('image/jpeg'); | |
| previewImage.src = newSrc; | |
| uploadedImages[currentImageIndex].src = newSrc; | |
| // Hide selection box | |
| selectionBox.classList.add('hidden'); | |
| currentSelectionType = null; | |
| saveToHistory(); | |
| }; | |
| img.src = previewImage.src; | |
| } | |
| // Effect functions | |
| function applyEffect(effectType) { | |
| if (!previewImage.src) { | |
| alert('Please upload an image first'); | |
| return; | |
| } | |
| // Simulate applying effect | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Apply different effects based on type | |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
| switch(effectType) { | |
| case 'vintage': | |
| // Sepia effect | |
| for (let i = 0; i < imageData.data.length; i += 4) { | |
| const r = imageData.data[i]; | |
| const g = imageData.data[i+1]; | |
| const b = imageData.data[i+2]; | |
| imageData.data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189)); | |
| imageData.data[i+1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168)); | |
| imageData.data[i+2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131)); | |
| } | |
| break; | |
| case 'bw': | |
| // Black and white | |
| for (let i = 0; i < imageData.data.length; i += 4) { | |
| const avg = (imageData.data[i] + imageData.data[i+1] + imageData.data[i+2]) / 3; | |
| imageData.data[i] = avg; | |
| imageData.data[i+1] = avg; | |
| imageData.data[i+2] = avg; | |
| } | |
| break; | |
| case 'warm': | |
| // Warm filter | |
| for (let i = 0; i < imageData.data.length; i += 4) { | |
| imageData.data[i] = Math.min(255, imageData.data[i] * 1.2); // Boost red | |
| imageData.data[i+2] = imageData.data[i+2] * 0.8; // Reduce blue | |
| } | |
| break; | |
| case 'cool': | |
| // Cool filter | |
| for (let i = 0; i < imageData.data.length; i += 4) { | |
| imageData.data[i] = imageData.data[i] * 0.8; // Reduce red | |
| imageData.data[i+2] = Math.min(255, imageData.data[i+2] * 1.2); // Boost blue | |
| } | |
| break; | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| // Update the image | |
| const newSrc = canvas.toDataURL('image/jpeg'); | |
| previewImage.src = newSrc; | |
| uploadedImages[currentImageIndex].src = newSrc; | |
| saveToHistory(); | |
| }; | |
| img.src = previewImage.src; | |
| } | |
| // Initialize undo/redo buttons | |
| document.getElementById('undo-btn').addEventListener('click', undo); | |
| document.getElementById('redo-btn').addEventListener('click', redo); | |
| </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=swapit/newspaces-are-good" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |