Spaces:
Runtime error
Runtime error
| 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! | |
| [](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!"); | |
| """ | |
| ) |