#!/usr/bin/env python3 """ Lipstick Virtual Makeup App Dedicated app for applying lipstick with various finishes and colors Features: Multiple shades, Gloss/Matte/Shimmer finishes, Before/After preview """ import cv2 import numpy as np import gradio as gr from PIL import Image import os from landmarks import detect_landmarks, normalize_landmarks # Lipstick color presets with RGB values LIPSTICK_COLORS = { "Classic Red": (0, 0, 255), "Rose Pink": (203, 192, 255), "Deep Burgundy": (75, 0, 130), "Coral Orange": (0, 165, 255), "Nude Beige": (180, 140, 200), "Wine Red": (0, 0, 120), "Peach Coral": (100, 165, 255), "Berry": (128, 0, 128), "Mauve": (150, 112, 224), "Crimson": (60, 20, 220), "Hot Pink": (180, 105, 255), "Plum": (142, 69, 133), } # Landmark indices for lips UPPER_LIP = [61, 185, 40, 39, 37, 0, 267, 269, 270, 408, 415, 272, 271, 268, 12, 38, 41, 42, 191, 78, 76] LOWER_LIP = [61, 146, 91, 181, 84, 17, 314, 405, 320, 307, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95] def apply_lipstick_with_finish(image, color_rgb, landmarks, alpha=0.5, finish="matte"): """ Apply lipstick to lips with different finishes. Args: image: Input image color_rgb: RGB color tuple landmarks: Facial landmarks alpha: Opacity/intensity (0-1) finish: "matte", "gloss", or "shimmer" Returns: Image with lipstick applied """ if landmarks is None: return image h, w = image.shape[:2] mask = np.zeros_like(image) # Get lip points lip_points = normalize_landmarks(landmarks, h, w, UPPER_LIP + LOWER_LIP) if len(lip_points) == 0: return image lip_points = lip_points.astype(np.int32) # Fill lip region with color cv2.fillPoly(mask, [lip_points], color_rgb) # Apply finish-specific effects if finish == "gloss": # Glossy finish: higher alpha, add highlights mask = cv2.GaussianBlur(mask, (15, 15), 3) output = cv2.addWeighted(image, 1.0, mask, alpha * 1.2, 0) # Add highlight effect on upper lip highlight_mask = np.zeros_like(image) upper_lip_points = normalize_landmarks(landmarks, h, w, UPPER_LIP) if len(upper_lip_points) > 0: upper_lip_points = upper_lip_points.astype(np.int32) cv2.fillPoly(highlight_mask, [upper_lip_points], (255, 255, 255)) highlight_mask = cv2.GaussianBlur(highlight_mask, (25, 25), 8) output = cv2.addWeighted(output, 1.0, highlight_mask, 0.15, 0) elif finish == "shimmer": # Shimmer finish: add sparkle effect mask = cv2.GaussianBlur(mask, (13, 13), 3) output = cv2.addWeighted(image, 1.0, mask, alpha, 0) # Add shimmer particles shimmer_mask = np.zeros_like(image) cv2.fillPoly(shimmer_mask, [lip_points], (255, 255, 255)) # Create random shimmer points np.random.seed(42) # For consistent shimmer pattern lip_region = cv2.fillPoly(np.zeros((h, w), dtype=np.uint8), [lip_points], 255) y_coords, x_coords = np.where(lip_region > 0) if len(y_coords) > 0: num_sparkles = min(len(y_coords) // 20, 100) indices = np.random.choice(len(y_coords), num_sparkles, replace=False) for idx in indices: y, x = y_coords[idx], x_coords[idx] cv2.circle(shimmer_mask, (x, y), 1, (255, 255, 255), -1) shimmer_mask = cv2.GaussianBlur(shimmer_mask, (5, 5), 1) output = cv2.addWeighted(output, 1.0, shimmer_mask, 0.2, 0) else: # matte finish # Matte finish: smooth, no shine mask = cv2.GaussianBlur(mask, (15, 15), 3) mask = cv2.GaussianBlur(mask, (7, 7), 2) output = cv2.addWeighted(image, 1.0, mask, alpha * 0.9, 0) return output def process_lipstick(image, color_name, finish, intensity): """ Process image to apply lipstick with selected parameters. Args: image: Input image (PIL or numpy) color_name: Name of the lipstick color finish: Type of finish (matte/gloss/shimmer) intensity: Intensity percentage (0-100) Returns: Tuple of (processed_image, status_message) """ if image is None: return None, "No image provided" # Convert PIL to OpenCV format if isinstance(image, Image.Image): img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) else: img = image.copy() # Detect landmarks landmarks = detect_landmarks(img) if landmarks is None: return cv2.cvtColor(img, cv2.COLOR_BGR2RGB), "No face detected" # Get color color = LIPSTICK_COLORS.get(color_name, LIPSTICK_COLORS["Classic Red"]) # Convert intensity percentage to alpha alpha = intensity / 100.0 * 0.7 # Scale to reasonable range # Apply lipstick output = apply_lipstick_with_finish(img, color, landmarks, alpha=alpha, finish=finish.lower()) # Convert back to RGB output_rgb = cv2.cvtColor(output, cv2.COLOR_BGR2RGB) status = f"Applied {color_name} lipstick with {finish} finish at {intensity}% intensity" return output_rgb, status def create_comparison(original, processed): """Create a side-by-side before/after comparison.""" if original is None or processed is None: return None # Convert to numpy arrays if needed if isinstance(original, Image.Image): original = np.array(original) if isinstance(processed, Image.Image): processed = np.array(processed) # Ensure same dimensions if original.shape != processed.shape: return processed # Create side-by-side comparison h, w = original.shape[:2] comparison = np.zeros((h, w * 2, 3), dtype=np.uint8) comparison[:, :w] = original comparison[:, w:] = processed # Add labels font = cv2.FONT_HERSHEY_SIMPLEX cv2.putText(comparison, "BEFORE", (20, 40), font, 1, (255, 255, 255), 2) cv2.putText(comparison, "AFTER", (w + 20, 40), font, 1, (255, 255, 255), 2) return comparison # Create Gradio interface with gr.Blocks(title="Lipstick Virtual Makeup", theme=gr.themes.Soft()) as demo: gr.Markdown("# 💋 Lipstick Virtual Makeup App") gr.Markdown("Apply professional lipstick effects with customizable colors and finishes") with gr.Row(): with gr.Column(scale=1): # Image upload image_input = gr.Image(label="Upload Your Photo", type="pil") # Lipstick color selection gr.Markdown("### Choose Your Shade") color_dropdown = gr.Dropdown( choices=list(LIPSTICK_COLORS.keys()), value="Classic Red", label="Lipstick Color", interactive=True ) # Finish type gr.Markdown("### Select Finish") finish_radio = gr.Radio( choices=["Matte", "Gloss", "Shimmer"], value="Matte", label="Finish Type", interactive=True ) # Intensity slider gr.Markdown("### Adjust Intensity") intensity_slider = gr.Slider( minimum=0, maximum=100, value=60, step=5, label="Intensity (%)", interactive=True ) # Apply button apply_btn = gr.Button("💄 Apply Lipstick", variant="primary", size="lg") # Preview mode toggle preview_checkbox = gr.Checkbox( label="Show Before/After Comparison", value=False ) with gr.Column(scale=1): # Output image image_output = gr.Image(label="Result", type="pil") # Status message status_text = gr.Textbox( label="Status", interactive=False, lines=2 ) # Info box gr.Markdown(""" ### 💡 Tips for Best Results - Use a well-lit, front-facing photo - **Matte**: Classic, long-lasting look - **Gloss**: Shiny, plump appearance - **Shimmer**: Sparkly, party-ready finish - Adjust intensity for subtle or bold looks """) # Store original and processed images original_state = gr.State() processed_state = gr.State() # Process and store both images def process_and_store(img, color, finish, intensity): processed, status = process_lipstick(img, color, finish, intensity) return processed, status, img, processed # Handle preview mode toggle def handle_preview(show_preview, original, processed): if show_preview and original is not None and processed is not None: return create_comparison(original, processed) elif processed is not None: return processed return None # Link apply button apply_btn.click( fn=process_and_store, inputs=[image_input, color_dropdown, finish_radio, intensity_slider], outputs=[image_output, status_text, original_state, processed_state] ) # Link preview checkbox preview_checkbox.change( fn=handle_preview, inputs=[preview_checkbox, original_state, processed_state], outputs=[image_output] ) if __name__ == "__main__": print("\n" + "="*60) print("🌸 LipStick Virtual Makeup App") print("="*60 + "\n") demo.launch()