Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Professional Image Editor</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
body { | |
background: linear-gradient(135deg, #1a1a2e, #16213e); | |
color: #fff; | |
min-height: 100vh; | |
padding: 20px; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
header { | |
text-align: center; | |
margin-bottom: 30px; | |
padding: 20px; | |
background: rgba(255, 255, 255, 0.05); | |
border-radius: 15px; | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); | |
backdrop-filter: blur(4px); | |
border: 1px solid rgba(255, 255, 255, 0.1); | |
} | |
h1 { | |
font-size: 2.5rem; | |
margin-bottom: 10px; | |
background: linear-gradient(45deg, #ff7e5f, #feb47b); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
} | |
.subtitle { | |
font-size: 1.2rem; | |
color: #a0a0c0; | |
max-width: 700px; | |
margin: 0 auto; | |
line-height: 1.6; | |
} | |
.app-container { | |
display: grid; | |
grid-template-columns: 1fr 1fr; | |
gap: 30px; | |
margin-bottom: 40px; | |
} | |
@media (max-width: 768px) { | |
.app-container { | |
grid-template-columns: 1fr; | |
} | |
} | |
.panel { | |
background: rgba(30, 30, 50, 0.7); | |
border-radius: 15px; | |
padding: 25px; | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); | |
backdrop-filter: blur(10px); | |
border: 1px solid rgba(255, 255, 255, 0.1); | |
} | |
.panel-title { | |
font-size: 1.5rem; | |
margin-bottom: 20px; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
color: #feb47b; | |
} | |
.panel-title i { | |
font-size: 1.8rem; | |
} | |
.form-group { | |
margin-bottom: 20px; | |
} | |
label { | |
display: block; | |
margin-bottom: 8px; | |
font-weight: 500; | |
color: #d0d0e0; | |
} | |
input, textarea { | |
width: 100%; | |
padding: 12px 15px; | |
border-radius: 8px; | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
background: rgba(20, 20, 40, 0.8); | |
color: #fff; | |
font-size: 1rem; | |
} | |
textarea { | |
min-height: 100px; | |
resize: vertical; | |
} | |
input::placeholder, textarea::placeholder { | |
color: #8888a8; | |
} | |
.upload-area { | |
border: 2px dashed rgba(255, 255, 255, 0.2); | |
border-radius: 10px; | |
padding: 30px; | |
text-align: center; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
background: rgba(20, 20, 40, 0.5); | |
} | |
.upload-area:hover { | |
border-color: #ff7e5f; | |
background: rgba(20, 20, 40, 0.7); | |
} | |
.upload-area i { | |
font-size: 3rem; | |
color: #ff7e5f; | |
margin-bottom: 15px; | |
} | |
.upload-text { | |
color: #a0a0c0; | |
margin-bottom: 15px; | |
} | |
.file-input { | |
display: none; | |
} | |
.btn { | |
background: linear-gradient(45deg, #ff7e5f, #feb47b); | |
color: white; | |
border: none; | |
padding: 14px 25px; | |
font-size: 1.1rem; | |
font-weight: 600; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
width: 100%; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
gap: 10px; | |
} | |
.btn:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 5px 15px rgba(255, 126, 95, 0.4); | |
} | |
.btn:disabled { | |
background: #555575; | |
cursor: not-allowed; | |
transform: none; | |
box-shadow: none; | |
} | |
.image-container { | |
position: relative; | |
overflow: hidden; | |
border-radius: 10px; | |
height: 400px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
background: rgba(20, 20, 40, 0.5); | |
border: 1px solid rgba(255, 255, 255, 0.1); | |
} | |
.image-placeholder { | |
color: #8888a8; | |
text-align: center; | |
padding: 20px; | |
} | |
.image-placeholder i { | |
font-size: 4rem; | |
margin-bottom: 20px; | |
color: #555575; | |
} | |
img { | |
max-width: 100%; | |
max-height: 100%; | |
object-fit: contain; | |
display: none; | |
} | |
.loading { | |
display: none; | |
text-align: center; | |
padding: 20px; | |
} | |
.spinner { | |
border: 4px solid rgba(255, 255, 255, 0.3); | |
border-radius: 50%; | |
border-top: 4px solid #ff7e5f; | |
width: 40px; | |
height: 40px; | |
animation: spin 1s linear infinite; | |
margin: 0 auto 20px; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.tips { | |
background: rgba(30, 30, 50, 0.7); | |
border-radius: 15px; | |
padding: 25px; | |
margin-top: 30px; | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); | |
backdrop-filter: blur(10px); | |
border: 1px solid rgba(255, 255, 255, 0.1); | |
} | |
.tips h2 { | |
margin-bottom: 20px; | |
color: #feb47b; | |
} | |
.tip { | |
display: flex; | |
gap: 15px; | |
margin-bottom: 15px; | |
align-items: flex-start; | |
} | |
.tip i { | |
color: #ff7e5f; | |
font-size: 1.2rem; | |
margin-top: 3px; | |
} | |
.footer { | |
text-align: center; | |
margin-top: 40px; | |
padding: 20px; | |
color: #8888a8; | |
font-size: 0.9rem; | |
} | |
.api-info { | |
display: flex; | |
gap: 10px; | |
align-items: center; | |
justify-content: center; | |
margin-top: 10px; | |
} | |
.api-badge { | |
background: rgba(255, 126, 95, 0.2); | |
padding: 5px 10px; | |
border-radius: 20px; | |
font-size: 0.8rem; | |
} | |
.error-box { | |
display: none; | |
background: rgba(200, 0, 0, 0.2); | |
border: 1px solid rgba(255, 0, 0, 0.3); | |
border-radius: 8px; | |
padding: 15px; | |
margin-top: 20px; | |
font-family: monospace; | |
font-size: 0.9rem; | |
max-height: 200px; | |
overflow-y: auto; | |
} | |
</style> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
</head> | |
<body> | |
<div class="container"> | |
<header> | |
<h1>Professional Image Editor</h1> | |
<p class="subtitle">Transform your images with AI-powered editing. Powered by Hugging Face's multimodalart/cosxl model running on CPU.</p> | |
</header> | |
<div class="app-container"> | |
<div class="panel"> | |
<h2 class="panel-title"><i class="fas fa-edit"></i> Edit Parameters</h2> | |
<div class="form-group"> | |
<label for="imageUpload">Upload Image</label> | |
<div class="upload-area" id="dropArea"> | |
<i class="fas fa-cloud-upload-alt"></i> | |
<p class="upload-text">Drag & drop your image here or click to browse</p> | |
<button class="btn" id="browseBtn"><i class="fas fa-folder-open"></i> Select Image</button> | |
<input type="file" id="imageUpload" accept="image/*" class="file-input"> | |
</div> | |
</div> | |
<div class="form-group"> | |
<label for="prompt">Edit Prompt</label> | |
<textarea id="prompt" placeholder="Describe what to edit (e.g. 'Change the dress color to blue', 'Add a hat', 'Make background blurry')"></textarea> | |
</div> | |
<div class="form-group"> | |
<label for="negativePrompt">Negative Prompt</label> | |
<input type="text" id="negativePrompt" value="blurry, distorted, low quality, artifacts" placeholder="Things to avoid in the result"> | |
</div> | |
<div class="form-group"> | |
<label for="guidanceScale">Guidance Scale: <span id="scaleValue">7</span></label> | |
<input type="range" id="guidanceScale" min="1" max="15" step="0.5" value="7"> | |
</div> | |
<div class="form-group"> | |
<label for="steps">Processing Steps: <span id="stepsValue">20</span></label> | |
<input type="range" id="steps" min="5" max="50" value="20"> | |
</div> | |
<div class="form-group"> | |
<label for="quality">Image Quality: <span id="qualityValue">85%</span></label> | |
<input type="range" id="quality" min="50" max="95" step="5" value="85"> | |
</div> | |
<button class="btn" id="runEdit"> | |
<i class="fas fa-magic"></i> Apply Edits | |
</button> | |
<div class="error-box" id="errorBox"> | |
<pre id="errorDetails"></pre> | |
</div> | |
</div> | |
<div class="panel"> | |
<h2 class="panel-title"><i class="fas fa-image"></i> Results</h2> | |
<div class="image-container" id="resultContainer"> | |
<div class="image-placeholder" id="originalPlaceholder"> | |
<i class="fas fa-image"></i> | |
<p>Original image will appear here</p> | |
</div> | |
<img id="originalImage"> | |
</div> | |
<div class="loading" id="loadingIndicator"> | |
<div class="spinner"></div> | |
<p>Processing your image... This may take 15-30 seconds</p> | |
<p><small>Using CPU-based model - slower than GPU but more accessible</small></p> | |
</div> | |
<div class="image-container" id="resultContainer"> | |
<div class="image-placeholder" id="resultPlaceholder"> | |
<i class="fas fa-star"></i> | |
<p>Edited result will appear here</p> | |
</div> | |
<img id="resultImage"> | |
</div> | |
</div> | |
</div> | |
<div class="tips"> | |
<h2><i class="fas fa-lightbulb"></i> Tips for Best Results</h2> | |
<div class="tip"> | |
<i class="fas fa-check-circle"></i> | |
<p>Be specific in your prompts: "Change the dress color to navy blue" works better than "Change color"</p> | |
</div> | |
<div class="tip"> | |
<i class="fas fa-check-circle"></i> | |
<p>Use negative prompts to avoid unwanted artifacts: "blurry, distorted, low quality"</p> | |
</div> | |
<div class="tip"> | |
<i class="fas fa-check-circle"></i> | |
<p>For clothing edits, mention the garment: "Recolor the dress to emerald green"</p> | |
</div> | |
<div class="tip"> | |
<i class="fas fa-check-circle"></i> | |
<p>Higher guidance scale (7-10) gives better adherence to prompts</p> | |
</div> | |
<div class="tip"> | |
<i class="fas fa-check-circle"></i> | |
<p>More steps (20-30) generally improve quality but take longer to process</p> | |
</div> | |
<div class="tip"> | |
<i class="fas fa-check-circle"></i> | |
<p>Reduce image quality to 80% for faster processing with minimal quality loss</p> | |
</div> | |
</div> | |
<div class="footer"> | |
<p>Professional Image Editor | Powered by Hugging Face's multimodalart/cosxl model</p> | |
<div class="api-info"> | |
<span class="api-badge">API: multimodalart/cosxl</span> | |
<span class="api-badge">Mode: CPU</span> | |
<span class="api-badge">Image Editing</span> | |
</div> | |
<p>Note: Processing may take 15-30 seconds as it uses CPU-based models</p> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM elements | |
const imageUpload = document.getElementById('imageUpload'); | |
const dropArea = document.getElementById('dropArea'); | |
const browseBtn = document.getElementById('browseBtn'); | |
const originalImage = document.getElementById('originalImage'); | |
const originalPlaceholder = document.getElementById('originalPlaceholder'); | |
const resultImage = document.getElementById('resultImage'); | |
const resultPlaceholder = document.getElementById('resultPlaceholder'); | |
const runEditBtn = document.getElementById('runEdit'); | |
const loadingIndicator = document.getElementById('loadingIndicator'); | |
const promptInput = document.getElementById('prompt'); | |
const negativePromptInput = document.getElementById('negativePrompt'); | |
const guidanceScaleInput = document.getElementById('guidanceScale'); | |
const scaleValue = document.getElementById('scaleValue'); | |
const stepsInput = document.getElementById('steps'); | |
const stepsValue = document.getElementById('stepsValue'); | |
const qualityInput = document.getElementById('quality'); | |
const qualityValue = document.getElementById('qualityValue'); | |
const errorBox = document.getElementById('errorBox'); | |
const errorDetails = document.getElementById('errorDetails'); | |
// Set initial slider values | |
scaleValue.textContent = guidanceScaleInput.value; | |
stepsValue.textContent = stepsInput.value; | |
qualityValue.textContent = qualityInput.value + '%'; | |
// Event listeners for sliders | |
guidanceScaleInput.addEventListener('input', () => { | |
scaleValue.textContent = guidanceScaleInput.value; | |
}); | |
stepsInput.addEventListener('input', () => { | |
stepsValue.textContent = stepsInput.value; | |
}); | |
qualityInput.addEventListener('input', () => { | |
qualityValue.textContent = qualityInput.value + '%'; | |
}); | |
// File selection | |
browseBtn.addEventListener('click', () => { | |
imageUpload.click(); | |
}); | |
imageUpload.addEventListener('change', function(e) { | |
if (this.files && this.files[0]) { | |
handleImageUpload(this.files[0]); | |
} | |
}); | |
// Drag and drop | |
dropArea.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
dropArea.style.borderColor = '#ff7e5f'; | |
dropArea.style.backgroundColor = 'rgba(20, 20, 40, 0.9)'; | |
}); | |
dropArea.addEventListener('dragleave', () => { | |
dropArea.style.borderColor = 'rgba(255, 255, 255, 0.2)'; | |
dropArea.style.backgroundColor = 'rgba(20, 20, 40, 0.5)'; | |
}); | |
dropArea.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
dropArea.style.borderColor = 'rgba(255, 255, 255, 0.2)'; | |
dropArea.style.backgroundColor = 'rgba(20, 20, 40, 0.5)'; | |
if (e.dataTransfer.files && e.dataTransfer.files[0]) { | |
handleImageUpload(e.dataTransfer.files[0]); | |
} | |
}); | |
// Handle image upload and preview | |
function handleImageUpload(file) { | |
if (!file.type.match('image.*')) { | |
alert('Please select an image file'); | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
originalImage.src = e.target.result; | |
originalImage.style.display = 'block'; | |
originalPlaceholder.style.display = 'none'; | |
// Clear previous result | |
resultImage.style.display = 'none'; | |
resultPlaceholder.style.display = 'block'; | |
// Hide any previous errors | |
errorBox.style.display = 'none'; | |
}; | |
reader.readAsDataURL(file); | |
} | |
// Function to compress image | |
async function compressImage(imageSrc, quality) { | |
return new Promise((resolve, reject) => { | |
const img = new Image(); | |
img.crossOrigin = 'Anonymous'; | |
img.src = imageSrc; | |
img.onload = () => { | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
// Calculate new dimensions to max 1024px | |
let width = img.width; | |
let height = img.height; | |
const maxDimension = 1024; | |
if (width > maxDimension || height > maxDimension) { | |
const ratio = Math.min(maxDimension / width, maxDimension / height); | |
width = Math.floor(width * ratio); | |
height = Math.floor(height * ratio); | |
} | |
canvas.width = width; | |
canvas.height = height; | |
// Draw image on canvas | |
ctx.drawImage(img, 0, 0, width, height); | |
// Convert to blob with specified quality | |
canvas.toBlob(blob => { | |
if (!blob) { | |
reject(new Error('Image compression failed')); | |
return; | |
} | |
resolve(blob); | |
}, 'image/jpeg', quality / 100); | |
}; | |
img.onerror = () => { | |
reject(new Error('Failed to load image for compression')); | |
}; | |
}); | |
} | |
// Show error details | |
function showError(message, details) { | |
errorDetails.textContent = details || message; | |
errorBox.style.display = 'block'; | |
console.error(message, details); | |
} | |
// Run the edit process | |
runEditBtn.addEventListener('click', async () => { | |
// Validate inputs | |
if (!originalImage.src || originalImage.src === window.location.href) { | |
alert('Please upload an image first'); | |
return; | |
} | |
if (!promptInput.value.trim()) { | |
alert('Please enter an edit prompt'); | |
return; | |
} | |
// Show loading indicator | |
runEditBtn.disabled = true; | |
loadingIndicator.style.display = 'block'; | |
errorBox.style.display = 'none'; | |
try { | |
// Compress image | |
const quality = parseInt(qualityInput.value) / 100; | |
const compressedBlob = await compressImage(originalImage.src, quality); | |
// Create FormData | |
const formData = new FormData(); | |
formData.append('image', compressedBlob, 'image.jpg'); | |
formData.append('prompt', promptInput.value); | |
formData.append('negative_prompt', negativePromptInput.value); | |
formData.append('guidance_scale', guidanceScaleInput.value); | |
formData.append('steps', stepsInput.value); | |
// Make request to Hugging Face API | |
const response = await fetch('https://multimodalart-cosxl.hf.space/run/predict', { | |
method: 'POST', | |
body: formData | |
}); | |
if (!response.ok) { | |
const errorText = await response.text(); | |
throw new Error(`API request failed: ${response.status} ${response.statusText}\n${errorText}`); | |
} | |
// Get the result image | |
const resultBlob = await response.blob(); | |
if (!resultBlob.type.startsWith('image/')) { | |
const errorData = await resultBlob.text(); | |
throw new Error(`API returned non-image response: ${errorData}`); | |
} | |
const resultUrl = URL.createObjectURL(resultBlob); | |
// Display result | |
resultImage.src = resultUrl; | |
resultImage.style.display = 'block'; | |
resultPlaceholder.style.display = 'none'; | |
} catch (error) { | |
showError('Error processing image:', error.message); | |
} finally { | |
// Hide loading indicator | |
runEditBtn.disabled = false; | |
loadingIndicator.style.display = 'none'; | |
} | |
}); | |
// Example prompts | |
const examplePrompts = [ | |
"Change the dress color to royal blue", | |
"Make the background blurry with bokeh effect", | |
"Recolor the jacket to bright red", | |
"Add a sun hat to the person", | |
"Change the hair color to purple", | |
"Make the dress look like silk material" | |
]; | |
// Set a random example prompt | |
promptInput.placeholder = examplePrompts[Math.floor(Math.random() * examplePrompts.length)]; | |
}); | |
</script> | |
</body> | |
</html> |