Dalladrain commited on
Commit
7ac2327
·
verified ·
1 Parent(s): 19bf7d8

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -0
app.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from diffusers import StableDiffusionPipeline
3
+ import torch
4
+ from PIL import Image, ImageDraw, ImageFont
5
+ import io, zipfile
6
+ import mediapipe as mp
7
+ import numpy as np
8
+
9
+ # ----------------------
10
+ # Load AI model
11
+ # ----------------------
12
+ pipe = StableDiffusionPipeline.from_pretrained(
13
+ "stabilityai/stable-diffusion-2",
14
+ torch_dtype=torch.float16
15
+ ).to("cuda")
16
+
17
+ mp_face = mp.solutions.face_detection.FaceDetection(model_selection=1, min_detection_confidence=0.5)
18
+
19
+ # ----------------------
20
+ # Helper functions
21
+ # ----------------------
22
+ def detect_characters(img):
23
+ """Detect faces in the image and return approximate (x,y) positions."""
24
+ img_rgb = img.convert("RGB")
25
+ width, height = img.size
26
+ results = mp_face.process(np.array(img_rgb))
27
+ positions = []
28
+ if results.detections:
29
+ for det in results.detections:
30
+ bbox = det.location_data.relative_bounding_box
31
+ x = int(bbox.xmin * width + bbox.width * width / 2)
32
+ y = int(bbox.ymin * height)
33
+ positions.append((x, y))
34
+ if not positions:
35
+ positions = [(50,50)]
36
+ return positions
37
+
38
+ def add_speech_bubbles(img, bubbles, character_positions=None):
39
+ """Draw multiple bubbles with tails pointing to character positions."""
40
+ img = img.convert("RGBA")
41
+ draw = ImageDraw.Draw(img)
42
+ try:
43
+ font = ImageFont.truetype("ComicNeue-Bold.ttf", 24)
44
+ name_font = ImageFont.truetype("ComicNeue-Bold.ttf", 18)
45
+ except:
46
+ font = ImageFont.load_default()
47
+ name_font = ImageFont.load_default()
48
+
49
+ if not character_positions:
50
+ character_positions = [(50,50)]
51
+
52
+ for i, bubble in enumerate(bubbles):
53
+ text = bubble.get("text","")
54
+ char_name = bubble.get("character","")
55
+ x0, y0 = 20, 20 + i*120
56
+
57
+ # Wrap text
58
+ max_width = img.width - x0 - 20
59
+ lines = []
60
+ words = text.split()
61
+ current_line = ""
62
+ for word in words:
63
+ test_line = f"{current_line} {word}".strip()
64
+ w, h = draw.textsize(test_line, font=font)
65
+ if w <= max_width:
66
+ current_line = test_line
67
+ else:
68
+ lines.append(current_line)
69
+ current_line = word
70
+ if current_line:
71
+ lines.append(current_line)
72
+
73
+ line_height = font.getsize("A")[1] + 4
74
+ bubble_height = line_height * len(lines) + 20
75
+ bubble_width = max([draw.textsize(line, font=font)[0] for line in lines]) + 20
76
+
77
+ x1, y1 = x0 + bubble_width, y0 + bubble_height
78
+ draw.rounded_rectangle([x0, y0, x1, y1], radius=15, fill="white", outline="black", width=3)
79
+
80
+ # Tail points to closest character
81
+ char_x, char_y = character_positions[i % len(character_positions)]
82
+ tail_base_x = x0 + bubble_width // 2
83
+ tail_base_y = y1
84
+ draw.polygon([(tail_base_x-10, tail_base_y), (tail_base_x+10, tail_base_y), (char_x, char_y)], fill="white", outline="black")
85
+
86
+ # Draw text
87
+ y_text = y0 + 10
88
+ for line in lines:
89
+ draw.text((x0+10, y_text), line, font=font, fill="black")
90
+ y_text += line_height
91
+
92
+ if char_name:
93
+ draw.text((x0+10, y0-20), char_name, font=name_font, fill="black")
94
+
95
+ return img
96
+
97
+ def generate_comic(title, story, pages, panels, art_style, theme, dialogues, explicit):
98
+ """Generate AI comic panels with bubbles."""
99
+ comic_panels = []
100
+ panels = int(panels)
101
+ panel_dialogues = dialogues.split("\n") if dialogues else [""] * panels
102
+
103
+ for i in range(panels):
104
+ prompt = f"{story}, {art_style} comic style, {theme} theme, colorful, detailed, panel {i+1}"
105
+ if explicit:
106
+ prompt += ", include blood, gore, offensive language, or dramatic action"
107
+
108
+ img = pipe(prompt, guidance_scale=7.5).images[0]
109
+ character_positions = detect_characters(img)
110
+
111
+ # Prepare bubbles
112
+ bubbles = []
113
+ if i < len(panel_dialogues) and panel_dialogues[i].strip():
114
+ bubble_texts = panel_dialogues[i].split("||")
115
+ for b in bubble_texts:
116
+ if ":" in b:
117
+ char, text = b.split(":", 1)
118
+ else:
119
+ char, text = "", b
120
+ bubbles.append({"text": text.strip(), "character": char.strip()})
121
+ if bubbles:
122
+ img = add_speech_bubbles(img, bubbles, character_positions)
123
+ comic_panels.append(img)
124
+
125
+ return comic_panels
126
+
127
+ def create_zip(panels):
128
+ zip_buffer = io.BytesIO()
129
+ with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED) as zf:
130
+ for i, img in enumerate(panels):
131
+ img_bytes = io.BytesIO()
132
+ img.save(img_bytes, format="PNG")
133
+ zf.writestr(f"panel_{i+1}.png", img_bytes.getvalue())
134
+ zip_buffer.seek(0)
135
+ return zip_buffer
136
+
137
+ def generate_and_zip(title, story, pages, panels, art_style, theme, dialogues, explicit):
138
+ panels_list = generate_comic(title, story, pages, panels, art_style, theme, dialogues, explicit)
139
+ zip_file = create_zip(panels_list)
140
+ return panels_list, zip_file
141
+
142
+ # ----------------------
143
+ # Gradio UI
144
+ # ----------------------
145
+ custom_css = """
146
+ <style>
147
+ @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
148
+ body { background: linear-gradient(135deg, #7e22ce, #3b82f6); background-size: 400% 400%; animation: gradientBG 15s ease infinite; }
149
+ @keyframes gradientBG { 0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%} }
150
+ h1, .comic-title { font-family: 'Bangers', cursive; }
151
+ p, label, .comic-text { font-family: 'Comic Neue', cursive; }
152
+ .comic-panel { border: 4px solid black; box-shadow: 8px 8px 0px rgba(0,0,0,0.3); border-radius: 8px; transition: transform 0.3s ease, box-shadow 0.3s ease; }
153
+ .comic-panel:hover { transform: scale(1.05); box-shadow: 12px 12px 0px rgba(0,0,0,0.4); }
154
+ </style>
155
+ """
156
+
157
+ with gr.Blocks(css=custom_css) as demo:
158
+ gr.HTML("<h1 style='text-align:center; color:white; font-size:3em;'>AI Comic Crafter</h1>")
159
+ gr.HTML("<p style='text-align:center; color:white; font-size:1.2em;'>Professional multi-bubble comics with AI!</p>")
160
+
161
+ with gr.Row():
162
+ with gr.Column(scale=1):
163
+ title_input = gr.Textbox(label="Comic Book Title", placeholder="Enter your comic book title...")
164
+ story_input = gr.Textbox(label="Story Setup", placeholder="Describe characters, plot, etc.", lines=3)
165
+ pages_input = gr.Dropdown(["1","3","5","8","10"], label="Number of Pages", value="3")
166
+ panels_input = gr.Dropdown(["2","4","6"], label="Panels Per Page", value="4")
167
+ art_style_input = gr.Dropdown(["Comic","Anime","Manga","Noir","Realistic"], label="Art Style", value="Comic")
168
+ theme_input = gr.Dropdown(
169
+ ["Sci-Fi","Fantasy","Superhero","Horror","Noir","Cyberpunk","Mystery","Adventure"],
170
+ label="Theme", value="Sci-Fi"
171
+ )
172
+ dialogues_input = gr.Textbox(
173
+ label="Custom Dialogues (one line per panel, bubbles separated by '||', format: Character:Text)",
174
+ lines=6
175
+ )
176
+ explicit_input = gr.Checkbox(label="Allow explicit content (blood/gore/offensive language)")
177
+ generate_btn = gr.Button("Generate Comic Book")
178
+ download_btn = gr.File(label="Download Comic as ZIP")
179
+
180
+ with gr.Column(scale=1):
181
+ output_gallery = gr.Gallery(label="Your Comic Preview").style(grid=[2], height="auto")
182
+
183
+ generate_btn.click(
184
+ generate_and_zip,
185
+ inputs=[title_input, story_input, pages_input, panels_input, art_style_input, theme_input, dialogues_input, explicit_input],
186
+ outputs=[output_gallery, download_btn]
187
+ )
188
+
189
+ demo.launch()