import gradio as gr import numpy as np import requests from PIL import Image, ImageDraw, ImageFont import random import io import os from pathlib import Path # Set up static paths for faster loading gr.set_static_paths(["assets"]) # Download some fonts if not available FONT_PATH = "assets/fonts" os.makedirs(FONT_PATH, exist_ok=True) # Download Impact font (common meme font) IMPACT_URL = "https://github.com/google/fonts/raw/main/apache/impact/Impact.ttf" IMPACT_PATH = os.path.join(FONT_PATH, "Impact.ttf") if not os.path.exists(IMPACT_PATH): response = requests.get(IMPACT_URL) with open(IMPACT_PATH, "wb") as f: f.write(response.content) # Download some other fonts FONT_URLS = { "Arial": "https://github.com/google/fonts/raw/main/apache/arial/Arial.ttf", "ComicSans": "https://github.com/google/fonts/raw/main/apache/comicsansms/ComicSansMS.ttf", "TimesNewRoman": "https://github.com/google/fonts/raw/main/apache/timesnewroman/TimesNewRoman.ttf" } for font_name, url in FONT_URLS.items(): font_path = os.path.join(FONT_PATH, f"{font_name}.ttf") if not os.path.exists(font_path): try: response = requests.get(url) with open(font_path, "wb") as f: f.write(response.content) except: pass # Skip if font download fails # Load meme templates and quotes MEME_TEMPLATES = [ {"name": "Distracted Boyfriend", "image": "https://i.imgur.com/5e3qY.jpg", "boxes": [{"x": 0.1, "y": 0.1, "width": 0.3, "height": 0.2}, {"x": 0.6, "y": 0.1, "width": 0.3, "height": 0.2}, {"x": 0.4, "y": 0.6, "width": 0.2, "height": 0.2}]}, {"name": "Drake Hotline Bling", "image": "https://i.imgur.com/5e3qY.jpg", "boxes": [{"x": 0.1, "y": 0.1, "width": 0.8, "height": 0.3}, {"x": 0.1, "y": 0.6, "width": 0.8, "height": 0.3}]}, {"name": "Two Buttons", "image": "https://i.imgur.com/5e3qY.jpg", "boxes": [{"x": 0.1, "y": 0.4, "width": 0.3, "height": 0.2}, {"x": 0.6, "y": 0.4, "width": 0.3, "height": 0.2}]}, {"name": "Expanding Brain", "image": "https://i.imgur.com/5e3qY.jpg", "boxes": [{"x": 0.1, "y": 0.1, "width": 0.2, "height": 0.2}, {"x": 0.4, "y": 0.3, "width": 0.2, "height": 0.2}, {"x": 0.7, "y": 0.5, "width": 0.2, "height": 0.2}]}, {"name": "Woman Yelling at Cat", "image": "https://i.imgur.com/5e3qY.jpg", "boxes": [{"x": 0.1, "y": 0.1, "width": 0.3, "height": 0.2}, {"x": 0.6, "y": 0.6, "width": 0.3, "height": 0.2}]} ] QUOTES = [ "When you realize you left the oven on", "Me trying to adult", "When the WiFi is slow", "My brain at 3 AM", "When someone says 'just relax'", "Me pretending I understand", "When you find money in your pocket", "My motivation level", "When the food is finally ready", "Me avoiding responsibilities", "When you hear a funny joke", "My reaction to Mondays", "When the weekend is over", "Me trying to be productive", "When you see a cute animal", "My face when I see my bank account", "When someone cancels plans", "Me trying to parallel park", "When you remember an embarrassing moment", "My reaction to bad news" ] SHITPOST_PHRASES = [ "This is fine", "I have no idea what I'm doing", "Send help", "Why is this happening?", "I need adult supervision", "This is my life now", "I regret my choices", "I'm not okay", "This is the worst", "I give up", "Why me?", "I'm dying inside", "This is a disaster", "I'm not ready", "This is too much", "I can't even", "This is ridiculous", "I'm over it", "This is insane", "I'm done" ] def get_random_font(): """Get a random font from available fonts""" font_files = [f for f in os.listdir(FONT_PATH) if f.endswith('.ttf')] if font_files: return os.path.join(FONT_PATH, random.choice(font_files)) return IMPACT_PATH def add_text_to_image(image, text, font_path=IMPACT_PATH, font_size=40, color=(255, 255, 255), stroke_width=2, stroke_color=(0, 0, 0), position="center", max_width=0.8): """ Add text to an image with various styling options """ # Convert to PIL Image if it's a numpy array if isinstance(image, np.ndarray): image = Image.fromarray(image) # Create a draw object draw = ImageDraw.Draw(image) # Load font try: font = ImageFont.truetype(font_path, font_size) except: font = ImageFont.load_default() # Calculate text size and position text_width, text_height = draw.textsize(text, font=font) # Calculate position based on max_width percentage img_width, img_height = image.size max_text_width = int(img_width * max_width) # Wrap text if needed if text_width > max_text_width: # Simple text wrapping words = text.split() lines = [] current_line = [] for word in words: test_line = ' '.join(current_line + [word]) test_width, _ = draw.textsize(test_line, font=font) if test_width <= max_text_width: current_line.append(word) else: lines.append(' '.join(current_line)) current_line = [word] if current_line: lines.append(' '.join(current_line)) text = '\n'.join(lines) text_width, text_height = draw.textsize(text, font=font) # Calculate position if position == "center": x = (img_width - text_width) // 2 y = (img_height - text_height) // 2 elif position == "top": x = (img_width - text_width) // 2 y = 20 elif position == "bottom": x = (img_width - text_width) // 2 y = img_height - text_height - 20 elif position == "left": x = 20 y = (img_height - text_height) // 2 elif position == "right": x = img_width - text_width - 20 y = (img_height - text_height) // 2 else: # Custom position format: "x,y" where x and y are percentages try: x_percent, y_percent = map(float, position.split(',')) x = int((img_width * x_percent) - (text_width / 2)) y = int((img_height * y_percent) - (text_height / 2)) except: x = (img_width - text_width) // 2 y = (img_height - text_height) // 2 # Draw text with stroke for dx in range(-stroke_width, stroke_width + 1): for dy in range(-stroke_width, stroke_width + 1): if dx != 0 or dy != 0: draw.text((x + dx, y + dy), text, font=font, fill=stroke_color) # Draw main text draw.text((x, y), text, font=font, fill=color) return image def create_meme(image, meme_type="random", custom_text=None, font_size=40, text_color=(255, 255, 255), stroke_color=(0, 0, 0), stroke_width=2): """ Create a meme from an image """ # Convert to PIL Image if it's a numpy array if isinstance(image, np.ndarray): image = Image.fromarray(image) # Get meme template if meme_type == "random": template = random.choice(MEME_TEMPLATES) else: template = next((t for t in MEME_TEMPLATES if t["name"] == meme_type), MEME_TEMPLATES[0]) # Get text for each box texts = [] if custom_text: # Split custom text by newlines texts = custom_text.split('\n') else: # Use random quotes texts = [random.choice(QUOTES) for _ in template["boxes"]] # Add text to image for each box for i, box in enumerate(template["boxes"]): if i < len(texts): text = texts[i] # Calculate position from box coordinates x_percent = box["x"] + box["width"] / 2 y_percent = box["y"] + box["height"] / 2 position = f"{x_percent},{y_percent}" image = add_text_to_image( image, text, font_path=get_random_font(), font_size=font_size, color=text_color, stroke_color=stroke_color, stroke_width=stroke_width, position=position, max_width=box["width"] * 0.9 ) return image def create_quote_image(image, quote_type="inspirational", custom_text=None, font_size=40, text_color=(255, 255, 255), stroke_color=(0, 0, 0), stroke_width=2, position="center"): """ Create a quote image """ # Get quote text if custom_text: text = custom_text else: if quote_type == "inspirational": text = random.choice([ "Believe you can and you're halfway there", "The only way to do great work is to love what you do", "Don't watch the clock; do what it does. Keep going", "Success is not final, failure is not fatal", "You are never too old to set another goal", "The future belongs to those who believe in their dreams", "Everything you've ever wanted is on the other side of fear", "Hardships often prepare ordinary people for extraordinary destiny", "Don't be pushed around by the fears in your mind", "The only limit to our realization of tomorrow is our doubts of today" ]) elif quote_type == "funny": text = random.choice([ "I'm not lazy, I'm on energy saving mode", "I put the 'pro' in procrastination", "I'm not arguing, I'm just explaining why I'm right", "I don't need anger management, I need people to stop pissing me off", "I'm not short, I'm just more down to earth than you", "I'm not bossy, I just have better ideas", "I'm not crazy, my reality is just different than yours", "I'm not a regular mom, I'm a cool mom", "I'm not a morning person, I'm a coffee person", "I'm not a shopaholic, I'm helping the economy" ]) elif quote_type == "shitpost": text = random.choice(SHITPOST_PHRASES) else: text = random.choice(QUOTES) # Add text to image image = add_text_to_image( image, text, font_path=get_random_font(), font_size=font_size, color=text_color, stroke_color=stroke_color, stroke_width=stroke_width, position=position ) return image def process_image(image, content_type, custom_text=None, font_size=40, text_color=(255, 255, 255), stroke_color=(0, 0, 0), stroke_width=2, position="center", meme_type="random"): """ Main processing function """ if image is None: raise gr.Error("Please upload an image first!") try: if content_type == "meme": result = create_meme(image, meme_type=meme_type, custom_text=custom_text, font_size=font_size, text_color=text_color, stroke_color=stroke_color, stroke_width=stroke_width) else: result = create_quote_image(image, quote_type=content_type, custom_text=custom_text, font_size=font_size, text_color=text_color, stroke_color=stroke_color, stroke_width=stroke_width, position=position) return result except Exception as e: raise gr.Error(f"Error processing image: {str(e)}") # Create custom theme custom_theme = gr.themes.Soft( primary_hue="blue", secondary_hue="indigo", neutral_hue="slate", font=gr.themes.GoogleFont("Inter"), text_size="lg", spacing_size="lg", radius_size="md" ).set( button_primary_background_fill="*primary_600", button_primary_background_fill_hover="*primary_700", block_title_text_weight="600", ) # Create the Gradio interface with gr.Blocks() as demo: gr.Markdown(""" # 🎨 Neural Network Image Editor Upload an image and let our neural networks add random quotes, memes, or shitposts to it! [![Built with anycoder](https://img.shields.io/badge/Built%20with-anycoder-%23FF6B6B.svg?style=for-the-badge)](https://huggingface.co/spaces/akhaliq/anycoder) """) with gr.Row(): with gr.Column(): # Image upload input_image = gr.Image(label="Upload your image", type="numpy", sources=["upload", "webcam", "clipboard"]) # Content type selection content_type = gr.Radio( choices=["meme", "inspirational", "funny", "shitpost"], value="meme", label="Content Type" ) # Meme type selection (only shown for memes) meme_type = gr.Dropdown( choices=[t["name"] for t in MEME_TEMPLATES], value="random", label="Meme Template", visible=True ) # Custom text custom_text = gr.Textbox( label="Custom Text (optional)", placeholder="Enter your own text or leave blank for random", lines=3 ) # Text styling options with gr.Accordion("Text Styling Options", open=False): font_size = gr.Slider( minimum=20, maximum=100, value=40, step=5, label="Font Size" ) text_color = gr.ColorPicker( value="#FFFFFF", label="Text Color" ) stroke_color = gr.ColorPicker( value="#000000", label="Stroke Color" ) stroke_width = gr.Slider( minimum=0, maximum=10, value=2, step=1, label="Stroke Width" ) position = gr.Dropdown( choices=["center", "top", "bottom", "left", "right"], value="center", label="Text Position" ) # Process button process_btn = gr.Button("Generate Image", variant="primary") with gr.Column(): # Output output_image = gr.Image(label="Result", type="pil") status = gr.Textbox(label="Status", value="Ready to process your image!") # Examples gr.Markdown("## Examples") examples = gr.Examples( examples=[ ["https://i.imgur.com/5e3qY.jpg", "meme", "When you realize you left the oven on"], ["https://i.imgur.com/5e3qY.jpg", "inspirational", None], ["https://i.imgur.com/5e3qY.jpg", "funny", None], ["https://i.imgur.com/5e3qY.jpg", "shitpost", None] ], inputs=[input_image, content_type, custom_text], outputs=[output_image], fn=process_image, cache_examples=True, label="Try these examples!" ) # Update meme type visibility based on content type def update_meme_visibility(content_type): return gr.Dropdown(visible=content_type == "meme") content_type.change( fn=update_meme_visibility, inputs=[content_type], outputs=[meme_type] ) # Process button click process_btn.click( fn=process_image, inputs=[input_image, content_type, custom_text, font_size, text_color, stroke_color, stroke_width, position, meme_type], outputs=[output_image, status], api_visibility="public" ) # Deep link button gr.DeepLinkButton() # Launch the app demo.launch( theme=custom_theme, footer_links=[ {"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"}, {"label": "GitHub", "url": "https://github.com/akhaliq/anycoder"}, {"label": "Gradio Docs", "url": "https://gradio.app/docs"} ], css=""" .gradio-container { max-width: 1200px !important; } .gr-button-primary { background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%) !important; border: none !important; } .gr-button-primary:hover { background: linear-gradient(90deg, #2563eb 0%, #7c3aed 100%) !important; } """, js=""" console.log("Neural Network Image Editor loaded!"); """ )