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 # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI(title="HD CPU Text-to-Image Generator") # Create directories os.makedirs("generated_images", exist_ok=True) os.makedirs("models", exist_ok=True) # Global pipeline variable pipe = None model_loaded = False model_name = None # Fixed HD settings - ALWAYS 512x512 for best quality with turbo models 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 # SDXL-Turbo - Much better quality than regular SD-Turbo # Fixed at 512x512 for optimal HD output model_repo = "stabilityai/sdxl-turbo" logger.info(f"🔄 Loading {model_repo}...") # Load pipeline with CPU optimizations pipe = AutoPipelineForText2Image.from_pretrained( model_repo, torch_dtype=torch.float32, variant="fp16", use_safetensors=True, low_cpu_mem_usage=True ) # Move to CPU pipe = pipe.to("cpu") # Use fast scheduler pipe.scheduler = DPMSolverMultistepScheduler.from_config( pipe.scheduler.config, use_karras_sigmas=True # Better quality ) # Memory optimizations pipe.enable_attention_slicing() # Disable safety checker for speed (optional) 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}") # Fallback to regular SD-Turbo with optimized settings 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 with fixed HD settings HTML_TEMPLATE = """ 🎨 HD CPU Text-to-Image Generator | 512x512 Quality

🎨 HD CPU Text-to-Image Generator

High Quality 512x512 Images on Your CPU - Fast & Free!

📸 FIXED 512x512 HD OUTPUT

✨ HD Quality Tips:

✅ Fixed 512x512 resolution for optimal quality
✅ 4-6 inference steps for best balance of speed & quality
✅ Use detailed prompts (50-100 words) for better results
✅ Negative prompts help remove artifacts & improve clarity
1 step: Fast, 4 steps: HD Quality, 6 steps: Max Quality
Turbo models: 1.5-2.5 is optimal

📸 HD Output Settings (Fixed):

✅ Resolution: 512 x 512 pixels (High Definition)
✅ Model: SDXL-Turbo (Optimized for HD quality)
✅ Auto-upscaling: Enabled for crystal clear output
Generated HD Image
💡 HD Quality Tips: 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.
""" from contextlib import asynccontextmanager @asynccontextmanager async def lifespan(app: FastAPI): # Startup logger.info("Starting HD Image Generator...") import threading thread = threading.Thread(target=load_model) thread.start() yield # Shutdown logger.info("Shutting down...") # Update app to use lifespan 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") # Wait for model to load 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() # OPTIMAL HD SETTINGS (Fixed for best quality) steps = max(1, min(6, steps)) # Limit to 1-6 for turbo models guidance_scale = max(1.0, min(3.0, guidance_scale)) # Turbo models work best at 1.5-2.5 # Enhanced negative prompt if not provided 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" # Enhance prompt for better quality if it's too short 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}") # Generate image with fixed HD resolution result = pipe( prompt=prompt, negative_prompt=negative_prompt, num_inference_steps=steps, guidance_scale=guidance_scale, width=FIXED_WIDTH, # FIXED HD height=FIXED_HEIGHT # FIXED HD ) image = result.images[0] # Post-process: enhance sharpness for HD quality from PIL import ImageEnhance # Slight sharpness enhancement for HD look enhancer = ImageEnhance.Sharpness(image) image = enhancer.enhance(1.1) # Subtle sharpness boost # Color enhancement for vibrant HD output enhancer = ImageEnhance.Color(image) image = enhancer.enhance(1.05) # Slight color boost generation_time = time.time() - start_time # Convert to base64 buffered = io.BytesIO() image.save(buffered, format="PNG", quality=95, optimize=True) img_str = base64.b64encode(buffered.getvalue()).decode() # Save HD image to file 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)