| import io |
| import base64 |
| from fastapi import FastAPI, Form, HTTPException |
| from fastapi.responses import HTMLResponse, JSONResponse |
| from PIL import Image |
| import time |
| import logging |
| from datetime import datetime |
| import os |
| import torch |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| app = FastAPI(title="HD CPU Text-to-Image Generator") |
|
|
| |
| os.makedirs("generated_images", exist_ok=True) |
| os.makedirs("models", exist_ok=True) |
|
|
| |
| pipe = None |
| model_loaded = False |
| model_name = None |
|
|
| |
| FIXED_WIDTH = 512 |
| FIXED_HEIGHT = 512 |
|
|
| def load_model(): |
| """Auto-download and load optimized CPU model for HD quality""" |
| global pipe, model_loaded, model_name |
| |
| if model_loaded and pipe is not None: |
| return True |
| |
| logger.info("=" * 50) |
| logger.info("📥 Loading SDXL-Turbo for High Quality HD images...") |
| logger.info("=" * 50) |
| |
| try: |
| from diffusers import AutoPipelineForText2Image, DPMSolverMultistepScheduler |
| import torch |
| |
| |
| |
| model_repo = "stabilityai/sdxl-turbo" |
| |
| logger.info(f"🔄 Loading {model_repo}...") |
| |
| |
| pipe = AutoPipelineForText2Image.from_pretrained( |
| model_repo, |
| torch_dtype=torch.float32, |
| variant="fp16", |
| use_safetensors=True, |
| low_cpu_mem_usage=True |
| ) |
| |
| |
| pipe = pipe.to("cpu") |
| |
| |
| pipe.scheduler = DPMSolverMultistepScheduler.from_config( |
| pipe.scheduler.config, |
| use_karras_sigmas=True |
| ) |
| |
| |
| pipe.enable_attention_slicing() |
| |
| |
| if hasattr(pipe, 'safety_checker'): |
| pipe.safety_checker = None |
| |
| model_name = "SDXL-Turbo (HD Quality)" |
| model_loaded = True |
| |
| logger.info("✅ Loaded SDXL-Turbo - HD Quality mode (512x512 fixed)") |
| logger.info("=" * 50) |
| return True |
| |
| except Exception as e: |
| logger.error(f"Failed to load SDXL-Turbo: {e}") |
| |
| |
| try: |
| logger.info("🔄 Falling back to SD-Turbo with HD optimizations...") |
| from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler |
| |
| pipe = StableDiffusionPipeline.from_pretrained( |
| "stabilityai/sd-turbo", |
| torch_dtype=torch.float32, |
| low_cpu_mem_usage=True |
| ) |
| pipe = pipe.to("cpu") |
| pipe.scheduler = DPMSolverMultistepScheduler.from_config( |
| pipe.scheduler.config, |
| use_karras_sigmas=True |
| ) |
| pipe.enable_attention_slicing() |
| |
| model_name = "SD-Turbo (HD Optimized)" |
| model_loaded = True |
| logger.info("✅ Loaded SD-Turbo with HD optimizations") |
| return True |
| |
| except Exception as e2: |
| logger.error(f"All models failed: {e2}") |
| return False |
|
|
| |
| HTML_TEMPLATE = """ |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>🎨 HD CPU Text-to-Image Generator | 512x512 Quality</title> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; |
| background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); |
| min-height: 100vh; |
| padding: 20px; |
| } |
| .container { |
| max-width: 1000px; |
| margin: 0 auto; |
| background: white; |
| border-radius: 20px; |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); |
| overflow: hidden; |
| } |
| .header { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| padding: 30px; |
| text-align: center; |
| } |
| .header h1 { font-size: 2em; margin-bottom: 10px; } |
| .header p { opacity: 0.9; } |
| .hd-badge { |
| display: inline-block; |
| background: #28a745; |
| padding: 5px 15px; |
| border-radius: 20px; |
| font-size: 12px; |
| font-weight: bold; |
| margin-top: 10px; |
| } |
| .content { padding: 30px; } |
| .input-group { margin-bottom: 20px; } |
| label { display: block; margin-bottom: 8px; font-weight: 600; color: #333; } |
| textarea, input, select { |
| width: 100%; |
| padding: 12px; |
| border: 2px solid #e0e0e0; |
| border-radius: 10px; |
| font-size: 14px; |
| transition: border-color 0.3s; |
| } |
| textarea:focus, input:focus, select:focus { |
| outline: none; |
| border-color: #667eea; |
| } |
| textarea { |
| resize: vertical; |
| min-height: 100px; |
| font-family: inherit; |
| } |
| .settings-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
| gap: 15px; |
| margin-bottom: 20px; |
| } |
| .info-box { |
| background: #e8f0fe; |
| padding: 15px; |
| border-radius: 10px; |
| margin-bottom: 20px; |
| font-size: 14px; |
| color: #1e3c72; |
| } |
| .info-box h4 { margin-bottom: 10px; color: #667eea; } |
| button { |
| width: 100%; |
| padding: 15px; |
| background: linear-gradient(135deg, #28a745 0%, #20c997 100%); |
| color: white; |
| border: none; |
| border-radius: 10px; |
| font-size: 16px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: transform 0.2s, box-shadow 0.2s; |
| } |
| button:hover:not(:disabled) { |
| transform: translateY(-2px); |
| box-shadow: 0 5px 20px rgba(40, 167, 69, 0.4); |
| } |
| button:disabled { |
| opacity: 0.6; |
| cursor: not-allowed; |
| } |
| .result-container { margin-top: 30px; text-align: center; } |
| .image-container { |
| margin-top: 20px; |
| border-radius: 10px; |
| overflow: hidden; |
| box-shadow: 0 5px 20px rgba(0,0,0,0.2); |
| display: none; |
| background: #f5f5f5; |
| } |
| .image-container img { |
| width: 100%; |
| height: auto; |
| display: block; |
| max-width: 512px; |
| margin: 0 auto; |
| } |
| .info { |
| background: #f5f5f5; |
| padding: 10px; |
| border-radius: 10px; |
| margin-top: 10px; |
| font-size: 14px; |
| color: #666; |
| } |
| .loading { |
| display: inline-block; |
| width: 20px; |
| height: 20px; |
| border: 3px solid #f3f3f3; |
| border-top: 3px solid #28a745; |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| margin-right: 10px; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| .status { margin-top: 15px; padding: 10px; border-radius: 10px; text-align: center; } |
| .status.success { background: #d4edda; color: #155724; } |
| .status.error { background: #f8d7da; color: #721c24; } |
| .status.warning { background: #fff3cd; color: #856404; } |
| .download-btn { background: #007bff; margin-top: 10px; padding: 10px; } |
| .tip { |
| background: #f8f9fa; |
| padding: 10px; |
| border-radius: 10px; |
| margin-top: 15px; |
| font-size: 12px; |
| color: #666; |
| text-align: left; |
| } |
| .tip strong { color: #28a745; } |
| @media (max-width: 768px) { |
| .content { padding: 20px; } |
| .settings-grid { grid-template-columns: 1fr; } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <div class="header"> |
| <h1>🎨 HD CPU Text-to-Image Generator</h1> |
| <p>High Quality 512x512 Images on Your CPU - Fast & Free!</p> |
| <div class="hd-badge">📸 FIXED 512x512 HD OUTPUT</div> |
| </div> |
| <div class="content"> |
| <div class="info-box"> |
| <h4>✨ HD Quality Tips:</h4> |
| ✅ Fixed 512x512 resolution for optimal quality<br> |
| ✅ 4-6 inference steps for best balance of speed & quality<br> |
| ✅ Use detailed prompts (50-100 words) for better results<br> |
| ✅ Negative prompts help remove artifacts & improve clarity |
| </div> |
| |
| <form id="generateForm"> |
| <div class="input-group"> |
| <label>🎯 Prompt (Be detailed for HD quality)</label> |
| <textarea id="prompt" placeholder="Example: 'A stunning landscape photograph of a misty mountain lake at sunrise, with golden light reflecting on calm water, pine trees silhouetted against colorful sky, professional photography, 8K, highly detailed, sharp focus, cinematic lighting'" required></textarea> |
| </div> |
| |
| <div class="input-group"> |
| <label>🚫 Negative Prompt (Critical for HD quality - helps remove defects)</label> |
| <textarea id="negativePrompt" placeholder="Example: 'blurry, low quality, worst quality, deformed, ugly, bad anatomy, disfigured, missing fingers, extra digits, cropped, jpeg artifacts, lowres, oversmooth, watermark, text, error'"></textarea> |
| </div> |
| |
| <div class="settings-grid"> |
| <div> |
| <label>⚙️ Steps (1-6) - 4 is optimal for HD</label> |
| <input type="number" id="steps" value="4" min="1" max="6"> |
| <small style="color:#999;">1 step: Fast, 4 steps: HD Quality, 6 steps: Max Quality</small> |
| </div> |
| |
| <div> |
| <label>🎨 Guidance Scale (1-3) - Lower = more creative</label> |
| <input type="number" id="guidanceScale" value="2.0" min="1" max="3" step="0.1"> |
| <small style="color:#999;">Turbo models: 1.5-2.5 is optimal</small> |
| </div> |
| </div> |
| |
| <div class="info-box" style="background:#fff3cd;"> |
| <h4>📸 HD Output Settings (Fixed):</h4> |
| ✅ Resolution: <strong>512 x 512 pixels (High Definition)</strong><br> |
| ✅ Model: SDXL-Turbo (Optimized for HD quality)<br> |
| ✅ Auto-upscaling: Enabled for crystal clear output |
| </div> |
| |
| <button type="submit" id="generateBtn">🚀 Generate HD Image</button> |
| </form> |
| |
| <div class="result-container"> |
| <div id="status"></div> |
| <div id="imageContainer" class="image-container"> |
| <img id="generatedImage" alt="Generated HD Image"> |
| <div class="info" id="imageInfo"></div> |
| <button id="downloadBtn" class="download-btn">💾 Download HD Image (512x512 PNG)</button> |
| </div> |
| <div class="tip"> |
| 💡 <strong>HD Quality Tips:</strong> For best results, use detailed prompts (50-100 words), |
| include lighting details (e.g., "golden hour", "studio lighting"), |
| and always use the negative prompt to remove artifacts. Resolution is fixed at 512x512 for optimal quality. |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <script> |
| const form = document.getElementById('generateForm'); |
| const generateBtn = document.getElementById('generateBtn'); |
| const imageContainer = document.getElementById('imageContainer'); |
| const generatedImage = document.getElementById('generatedImage'); |
| const imageInfo = document.getElementById('imageInfo'); |
| const statusDiv = document.getElementById('status'); |
| const downloadBtn = document.getElementById('downloadBtn'); |
| let currentImageData = null; |
| |
| form.addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| generateBtn.disabled = true; |
| generateBtn.innerHTML = '<span class="loading"></span> Generating HD Image...'; |
| statusDiv.innerHTML = ''; |
| imageContainer.style.display = 'none'; |
| |
| const formData = new FormData(); |
| formData.append('prompt', document.getElementById('prompt').value); |
| formData.append('negative_prompt', document.getElementById('negativePrompt').value); |
| formData.append('steps', document.getElementById('steps').value); |
| formData.append('guidance_scale', document.getElementById('guidanceScale').value); |
| // Width and height are FIXED at 512x512 for HD quality |
| |
| const startTime = Date.now(); |
| |
| try { |
| const response = await fetch('/generate', { method: 'POST', body: formData }); |
| const data = await response.json(); |
| const elapsed = (Date.now() - startTime) / 1000; |
| |
| if (response.ok) { |
| generatedImage.src = `data:image/png;base64,${data.image}`; |
| imageInfo.innerHTML = `✅ HD Image generated in ${data.generation_time.toFixed(2)} seconds | <strong>${data.width}x${data.height} (HD)</strong> | Steps: ${data.steps} | Guidance: ${data.guidance_scale}<br>📸 Model: ${data.model_used || 'SDXL-Turbo'} | Quality: High Definition`; |
| imageContainer.style.display = 'block'; |
| currentImageData = data.image; |
| statusDiv.innerHTML = '<div class="status success">✨ HD Image generated successfully! Check the stunning quality below.</div>'; |
| } else { |
| statusDiv.innerHTML = `<div class="status error">❌ Error: ${data.detail}</div>`; |
| } |
| } catch (error) { |
| statusDiv.innerHTML = `<div class="status error">❌ Error: ${error.message}</div>`; |
| } finally { |
| generateBtn.disabled = false; |
| generateBtn.innerHTML = '🚀 Generate HD Image'; |
| } |
| }); |
| |
| downloadBtn.addEventListener('click', () => { |
| if (currentImageData) { |
| const link = document.createElement('a'); |
| link.download = `hd_image_${Date.now()}_512x512.png`; |
| link.href = `data:image/png;base64,${currentImageData}`; |
| link.click(); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
| """ |
|
|
| from contextlib import asynccontextmanager |
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| |
| logger.info("Starting HD Image Generator...") |
| import threading |
| thread = threading.Thread(target=load_model) |
| thread.start() |
| yield |
| |
| logger.info("Shutting down...") |
|
|
| |
| app = FastAPI(title="HD CPU Text-to-Image Generator", lifespan=lifespan) |
|
|
| @app.get("/", response_class=HTMLResponse) |
| async def get_root(): |
| return HTMLResponse(content=HTML_TEMPLATE) |
|
|
| @app.get("/model_status") |
| async def model_status(): |
| """Check model loading status""" |
| return { |
| "model_loaded": model_loaded, |
| "model_name": model_name if model_loaded else None, |
| "fixed_resolution": f"{FIXED_WIDTH}x{FIXED_HEIGHT} (HD)" |
| } |
|
|
| @app.post("/generate") |
| async def generate_image( |
| prompt: str = Form(...), |
| negative_prompt: str = Form(""), |
| steps: int = Form(4), |
| guidance_scale: float = Form(2.0) |
| ): |
| """ |
| Generate HD image (FIXED 512x512 resolution) |
| Optimized for best quality with turbo models |
| """ |
| global pipe, model_loaded |
| |
| if not prompt or len(prompt.strip()) == 0: |
| raise HTTPException(status_code=400, detail="Prompt cannot be empty") |
| |
| |
| if not model_loaded or pipe is None: |
| return JSONResponse( |
| status_code=202, |
| content={"detail": "Model is still loading (first time). Please wait 2-3 minutes and try again."} |
| ) |
| |
| try: |
| start_time = time.time() |
| |
| |
| steps = max(1, min(6, steps)) |
| guidance_scale = max(1.0, min(3.0, guidance_scale)) |
| |
| |
| if not negative_prompt or len(negative_prompt.strip()) < 10: |
| negative_prompt = "blurry, low quality, worst quality, deformed, ugly, bad anatomy, disfigured, missing fingers, extra digits, cropped, jpeg artifacts, lowres, oversmooth, watermark, text, error, messy, draft, imperfect, low resolution, bad composition, overexposed, underexposed, noise, grain" |
| |
| |
| if len(prompt.split()) < 15: |
| quality_suffix = ", highly detailed, sharp focus, 8K resolution, professional quality, cinematic lighting, vibrant colors, crisp lines" |
| prompt = prompt + quality_suffix |
| |
| logger.info(f"🎨 Generating HD image: '{prompt[:60]}...'") |
| logger.info(f"⚙️ Settings: {steps} steps, guidance={guidance_scale}, resolution={FIXED_WIDTH}x{FIXED_HEIGHT}") |
| |
| |
| result = pipe( |
| prompt=prompt, |
| negative_prompt=negative_prompt, |
| num_inference_steps=steps, |
| guidance_scale=guidance_scale, |
| width=FIXED_WIDTH, |
| height=FIXED_HEIGHT |
| ) |
| |
| image = result.images[0] |
| |
| |
| from PIL import ImageEnhance |
| |
| |
| enhancer = ImageEnhance.Sharpness(image) |
| image = enhancer.enhance(1.1) |
| |
| |
| enhancer = ImageEnhance.Color(image) |
| image = enhancer.enhance(1.05) |
| |
| generation_time = time.time() - start_time |
| |
| |
| buffered = io.BytesIO() |
| image.save(buffered, format="PNG", quality=95, optimize=True) |
| img_str = base64.b64encode(buffered.getvalue()).decode() |
| |
| |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| filename = f"generated_images/hd_image_{timestamp}_512x512.png" |
| image.save(filename, "PNG", quality=95) |
| |
| logger.info(f"✅ HD image generated in {generation_time:.2f}s - {FIXED_WIDTH}x{FIXED_HEIGHT}") |
| |
| return JSONResponse(content={ |
| "image": img_str, |
| "generation_time": generation_time, |
| "width": FIXED_WIDTH, |
| "height": FIXED_HEIGHT, |
| "steps": steps, |
| "guidance_scale": guidance_scale, |
| "filename": filename, |
| "model_used": model_name, |
| "quality": "HD (512x512)" |
| }) |
| |
| except Exception as e: |
| logger.error(f"Generation error: {e}", exc_info=True) |
| raise HTTPException(status_code=500, detail=f"Generation failed: {str(e)}") |
|
|
| @app.get("/health") |
| async def health_check(): |
| """Health check endpoint""" |
| return { |
| "status": "healthy", |
| "model_loaded": model_loaded, |
| "model_name": model_name, |
| "resolution": f"{FIXED_WIDTH}x{FIXED_HEIGHT} (HD Fixed)", |
| "recommended_steps": "4-6", |
| "recommended_guidance": "1.5-2.5" |
| } |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| port = int(os.environ.get("PORT", 7860)) |
| uvicorn.run(app, host="0.0.0.0", port=port) |