import streamlit as st import numpy as np from PIL import Image, ImageDraw import cv2 import io from backend.utilities import pil_to_cv, cv_to_pil, create_checkerboard from backend.bg_remover.shadow_generator import blend_rgba def generate_gradient_background(w: int, h: int, style: str) -> np.ndarray: """Generate a custom studio gradient background (RGBA).""" bg = np.zeros((h, w, 4), dtype=np.uint8) bg[:, :, 3] = 255 # fully opaque if style == "Midnight Glow": # Radial gradient from dark indigo (#1A1B35) to black (#0B0C10) cx, cy = w // 2, h // 2 max_dist = np.sqrt(cx**2 + cy**2) y, x = np.indices((h, w)) dist = np.sqrt((x - cx)**2 + (y - cy)**2) factor = np.clip(dist / max_dist, 0.0, 1.0) # Color 1: 26, 27, 53 | Color 2: 11, 12, 16 for c in range(3): c1, c2 = [26, 27, 53][c], [11, 12, 16][c] bg[:, :, c] = (c1 * (1.0 - factor) + c2 * factor).astype(np.uint8) elif style == "Sunset Studio": # Linear vertical gradient from soft orange (#FF7E5F) to deep purple (#FEB47B) y, _ = np.indices((h, w)) factor = (y / h).astype(float) # Color 1: 255, 126, 95 | Color 2: 254, 180, 123 for c in range(3): c1, c2 = [255, 126, 95][c], [254, 180, 123][c] bg[:, :, c] = (c1 * (1.0 - factor) + c2 * factor).astype(np.uint8) elif style == "Clean Studio": # Vertical gradient from light gray (#F5F7FA) to slate gray (#B3C0CD) y, _ = np.indices((h, w)) factor = (y / h).astype(float) # Color 1: 245, 247, 250 | Color 2: 179, 192, 205 for c in range(3): c1, c2 = [245, 247, 250][c], [179, 192, 205][c] bg[:, :, c] = (c1 * (1.0 - factor) + c2 * factor).astype(np.uint8) elif style == "Neon Cyber": # Diagonal gradient from hot magenta (#FF007F) to cyber cyan (#00F0FF) y, x = np.indices((h, w)) # Normalized coordinates summing for diagonal factor = np.clip((x / w + y / h) / 2.0, 0.0, 1.0) # Color 1: 255, 0, 127 | Color 2: 0, 240, 255 for c in range(3): c1, c2 = [255, 0, 127][c], [0, 240, 255][c] bg[:, :, c] = (c1 * (1.0 - factor) + c2 * factor).astype(np.uint8) return bg def composite_cutout_on_bg(cutout_pil: Image.Image, bg_type: str, custom_color: str = "#FFFFFF", custom_bg_file = None) -> Image.Image: """Composite the transparent cutout over a selected background style.""" w, h = cutout_pil.size cutout_cv = pil_to_cv(cutout_pil) # RGBA BGR/BGRA # Ensure BG is shape (H, W, 4) bg_rgba = np.zeros((h, w, 4), dtype=np.uint8) bg_rgba[:, :, 3] = 255 # solid background by default if bg_type == "Transparent (Checkerboard)": bg_rgba = create_checkerboard(w, h, square_size=16) elif bg_type == "Solid White": bg_rgba[:, :, :3] = 255 elif bg_type == "Solid Black": bg_rgba[:, :, :3] = 0 elif bg_type == "Custom Solid Color": # Convert hex custom_color "#RRGGBB" to BGR hex_color = custom_color.lstrip('#') r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) bg_rgba[:, :, 0] = b bg_rgba[:, :, 1] = g bg_rgba[:, :, 2] = r elif bg_type in ["Midnight Glow", "Sunset Studio", "Clean Studio", "Neon Cyber"]: bg_rgba = generate_gradient_background(w, h, bg_type) elif bg_type == "Custom Upload Image" and custom_bg_file is not None: try: # Load and resize custom background to match the cutout dimensions custom_bg_pil = Image.open(custom_bg_file).convert("RGBA") custom_bg_resized = custom_bg_pil.resize((w, h), Image.Resampling.LANCZOS) bg_rgba = pil_to_cv(custom_bg_resized) except Exception as e: # Fallback to white if error bg_rgba[:, :, :3] = 255 st.error(f"Error loading custom background: {str(e)}") # Perform alpha blending blended_cv = blend_rgba(top=cutout_cv, bottom=bg_rgba) return cv_to_pil(blended_cv) def render_preview_ui( original_pil: Image.Image, processed_pil: Image.Image, bounding_box: tuple = None, show_bounding_box: bool = False ): """ Renders the beautiful side-by-side image previews with interactive controls. """ st.markdown( """
ORIGINAL SOURCE IMAGE
', unsafe_allow_html=True) st.image(display_original, use_container_width=True) with col_right: st.markdown('EXTRACTED SUBJECT PREVIEW
', unsafe_allow_html=True) st.image(composited_processed, use_container_width=True)