File size: 5,271 Bytes
9878f3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import gradio as gr
from transformers import pipeline, set_seed
from PIL import Image, ImageDraw, ImageFont
import textwrap
import io

# Lazy-load models
_captioner = None
_generator = None

def get_captioner():
    global _captioner
    if _captioner is None:
        _captioner = pipeline("image-to-text", model="Salesforce/blip-image-captioning-base")
    return _captioner

def get_generator():
    global _generator
    if _generator is None:
        _generator = pipeline("text-generation", model="distilgpt2")
    return _generator

def wrap_text(text, width=22):
    return "\n".join(textwrap.wrap(text, width=width))

def draw_meme(img: Image.Image, caption: str, position: str = "bottom", uppercase: bool = True) -> Image.Image:
    if uppercase:
        caption = caption.upper()

    # Prepare drawing
    draw = ImageDraw.Draw(img)
    W, H = img.size

    # Choose a font (fallback to default if Impact is not available)
    try:
        font = ImageFont.truetype("Impact.ttf", size=max(24, W // 15))
    except Exception:
        font = ImageFont.load_default()

    # Wrap caption to fit width
    wrapped = wrap_text(caption, width=max(18, W // 25))

    # Determine text size
    bbox = draw.multiline_textbbox((0, 0), wrapped, font=font, align="center")
    text_w = bbox[2] - bbox[0]
    text_h = bbox[3] - bbox[1]

    # Choose y coordinate
    margin = int(0.03 * H)
    if position == "top":
        y = margin
    elif position == "center":
        y = (H - text_h) // 2
    else:
        y = H - text_h - margin

    # Draw black outline then white text (classic meme style)
    x = (W - text_w) // 2
    outline = max(2, W // 200)
    for dx in (-outline, outline):
        for dy in (-outline, outline):
            draw.multiline_text((x + dx, y + dy), wrapped, font=font, fill="black", align="center")
    draw.multiline_text((x, y), wrapped, font=font, fill="white", align="center")

    return img

def explain_caption(caption: str) -> str:
    cap = caption.lower()
    cues = []
    if any(w in cap for w in ["when ", "me:", "you ", "pov", "that moment"]):
        cues.append("relatable setup")
    if any(w in cap for w in ["but", "yet", "although", "instead"]):
        cues.append("incongruity/twist")
    if any(w in cap for w in ["too", "very", "super", "always", "never"]):
        cues.append("mild exaggeration")
    if any(w in cap for w in ["monday", "wifi", "coffee", "deadline", "sleep"]):
        cues.append("everyday theme")

    pieces = [
        "Short, high-contrast phrasing (classic meme style).",
        "Focuses on a common situation to trigger recognition.",
        "Adds a small twist that creates incongruity (the core of the joke)."
    ]
    if cues:
        pieces.append("Detected humor cues: " + ", ".join(cues) + ".")
    return " ".join(pieces)

def generate_meme(image, style, uppercase, seed):
    if image is None:
        return None, "Please upload an image.", ""

    if seed is not None and str(seed).strip() != "":
        try:
            set_seed(int(seed))
        except Exception:
            pass

    # Step 1: caption the image
    captioner = get_captioner()
    raw_caption = captioner(image)[0]["generated_text"]

    # Step 2: create a witty meme caption from the caption
    prompt = (
        "Write a short, witty meme caption (max 12 words) based on this image description: "
        f"'{raw_caption}'. Use relatable internet humor. Avoid names, no offensive content."
    )
    generator = get_generator()
    gen = generator(prompt, max_new_tokens=24, temperature=0.9, top_p=0.95, do_sample=True)[0]["generated_text"]
    # Get only the newly generated tail after the prompt
    meme_caption = gen[len(prompt):].strip().split("\n")[0]
    # Clean fallbacks
    if len(meme_caption) < 3 or len(meme_caption.split()) < 2:
        meme_caption = "When Monday hits before the coffee"

    # Step 3: draw caption onto the image
    img = image.convert("RGB").copy()
    img_out = draw_meme(img, meme_caption, position=style, uppercase=uppercase)

    # Step 4: generate a simple explanation
    explanation = explain_caption(meme_caption)

    return img_out, meme_caption, explanation

with gr.Blocks(title="AI Meme Generator + Explainer") as demo:
    gr.Markdown(
        """
        # AI Meme Generator + Explainer
        Upload an image → the app captions it, writes a short meme line, and explains why it works.
        Runs on CPU using lightweight models (BLIP-base + DistilGPT2).
        """
    )

    with gr.Row():
        image = gr.Image(type="pil", label="Upload image")
        with gr.Column():
            style = gr.Radio(choices=["top", "center", "bottom"], value="bottom", label="Caption position")
            uppercase = gr.Checkbox(value=True, label="Uppercase caption (meme style)")
            seed = gr.Textbox(value="", label="Seed (optional)", placeholder="e.g., 42")
            btn = gr.Button("Generate Meme")

    out_img = gr.Image(label="Meme")
    out_caption = gr.Textbox(label="Generated caption", lines=2)
    out_expl = gr.Textbox(label="Why it’s funny (explanation)", lines=4)

    btn.click(generate_meme, inputs=[image, style, uppercase, seed], outputs=[out_img, out_caption, out_expl])

if __name__ == "__main__":
    demo.launch()