Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
import tempfile
|
|
|
|
|
4 |
from PIL import Image, ImageDraw, ImageFont
|
5 |
|
6 |
def draw_horizontal_lines(draw, width, height, spacing=60, color=(230, 230, 230)):
|
@@ -35,131 +37,201 @@ def text_to_images_mcp(text_content, style='lines', font_path=None):
|
|
35 |
Returns:
|
36 |
list: List of PIL Image objects for display in Gradio.
|
37 |
"""
|
38 |
-
if not text_content.strip():
|
39 |
-
return []
|
40 |
-
|
41 |
-
# --- Configuration ---
|
42 |
-
IMG_WIDTH = 1080
|
43 |
-
IMG_HEIGHT = 1080
|
44 |
-
BACKGROUND_COLOR = (255, 255, 255)
|
45 |
-
TEXT_COLOR = (10, 10, 10)
|
46 |
-
STYLE_COLOR = (225, 225, 225)
|
47 |
-
|
48 |
-
PADDING_X = 80
|
49 |
-
PADDING_Y = 80
|
50 |
-
FONT_SIZE = 48
|
51 |
-
LINE_SPACING = 20
|
52 |
-
|
53 |
-
# --- Font Loading ---
|
54 |
-
font = None
|
55 |
try:
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
font = ImageFont.load_default()
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
else:
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
all_lines_and_breaks.append(current_line.strip())
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
line_height = font.getbbox("A")[3] - font.getbbox("A")[1]
|
112 |
-
except AttributeError:
|
113 |
-
line_height = 12
|
114 |
-
|
115 |
-
PARAGRAPH_SPACING = line_height
|
116 |
-
generated_images = []
|
117 |
-
|
118 |
-
def create_image_page(content, page_num):
|
119 |
-
"""Helper function to create and return a single image page."""
|
120 |
-
img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color=BACKGROUND_COLOR)
|
121 |
-
draw = ImageDraw.Draw(img)
|
122 |
-
|
123 |
-
# Draw the selected background style first
|
124 |
-
if style == 'lines':
|
125 |
-
line_style_spacing = line_height + LINE_SPACING
|
126 |
-
draw_horizontal_lines(draw, IMG_WIDTH, IMG_HEIGHT, spacing=line_style_spacing, color=STYLE_COLOR)
|
127 |
-
elif style == 'dots':
|
128 |
-
draw_dot_grid(draw, IMG_WIDTH, IMG_HEIGHT, color=STYLE_COLOR)
|
129 |
-
elif style == 'grid':
|
130 |
-
draw_lattice_grid(draw, IMG_WIDTH, IMG_HEIGHT, color=STYLE_COLOR)
|
131 |
-
|
132 |
-
# Draw the text on top of the background
|
133 |
-
current_y = PADDING_Y
|
134 |
-
for page_item in content:
|
135 |
-
if page_item is not None:
|
136 |
-
draw.text((PADDING_X, current_y), page_item, font=font, fill=TEXT_COLOR)
|
137 |
-
current_y += line_height + LINE_SPACING
|
138 |
else:
|
139 |
-
|
|
|
|
|
140 |
|
141 |
-
|
142 |
-
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
img_count += 1
|
150 |
-
create_image_page(page_content, img_count)
|
151 |
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
|
162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
|
164 |
# Sample text for demonstration
|
165 |
sample_text = """In the heart of a bustling city, there lived a clockmaker named Alistair. His shop, a quaint corner of tranquility amidst the urban chaos, was filled with the gentle ticking of countless timepieces. Each clock was a masterpiece, a testament to his dedication and skill. But Alistair held a secret. One of his clocks, an old grandfather clock in the corner, did not just tell time. It told stories.
|
@@ -176,7 +248,7 @@ demo = gr.Interface(
|
|
176 |
lines=10,
|
177 |
max_lines=20,
|
178 |
label="Text Content",
|
179 |
-
placeholder="Enter your long-form text here..."
|
180 |
),
|
181 |
gr.Dropdown(
|
182 |
choices=['plain', 'lines', 'dots', 'grid'],
|
@@ -193,7 +265,7 @@ demo = gr.Interface(
|
|
193 |
],
|
194 |
outputs=[
|
195 |
gr.Gallery(
|
196 |
-
label="Generated Images",
|
197 |
show_label=True,
|
198 |
elem_id="gallery",
|
199 |
columns=2,
|
@@ -204,7 +276,7 @@ demo = gr.Interface(
|
|
204 |
)
|
205 |
],
|
206 |
title="📖 Text to Images Converter",
|
207 |
-
description="Transform long-form text into a series of attractive, readable images. Simply paste your text, choose a background style, and preview the generated images. You can download individual images by clicking on them.",
|
208 |
examples=[
|
209 |
[sample_text, 'lines', ''],
|
210 |
[sample_text, 'dots', ''],
|
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
import tempfile
|
4 |
+
import traceback
|
5 |
+
import gc
|
6 |
from PIL import Image, ImageDraw, ImageFont
|
7 |
|
8 |
def draw_horizontal_lines(draw, width, height, spacing=60, color=(230, 230, 230)):
|
|
|
37 |
Returns:
|
38 |
list: List of PIL Image objects for display in Gradio.
|
39 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
try:
|
41 |
+
# Input validation
|
42 |
+
if not text_content or not text_content.strip():
|
43 |
+
return []
|
44 |
+
|
45 |
+
# Limit text length to prevent memory issues
|
46 |
+
if len(text_content) > 10000: # 10k character limit
|
47 |
+
text_content = text_content[:10000] + "\n\n[Text truncated due to length limit]"
|
48 |
+
|
49 |
+
# --- Configuration ---
|
50 |
+
IMG_WIDTH = 1080
|
51 |
+
IMG_HEIGHT = 1080
|
52 |
+
BACKGROUND_COLOR = (255, 255, 255)
|
53 |
+
TEXT_COLOR = (10, 10, 10)
|
54 |
+
STYLE_COLOR = (225, 225, 225)
|
55 |
+
|
56 |
+
PADDING_X = 80
|
57 |
+
PADDING_Y = 80
|
58 |
+
FONT_SIZE = 48
|
59 |
+
LINE_SPACING = 20
|
60 |
+
|
61 |
+
# Limit maximum number of pages to prevent memory issues
|
62 |
+
MAX_PAGES = 10
|
63 |
+
|
64 |
+
# --- Font Loading ---
|
65 |
+
font = None
|
66 |
+
try:
|
67 |
+
if font_path and os.path.exists(font_path):
|
68 |
+
font = ImageFont.truetype(font_path, FONT_SIZE)
|
69 |
+
else:
|
70 |
+
font_paths_to_try = [
|
71 |
+
"Arial.ttf", "arial.ttf",
|
72 |
+
"/System/Library/Fonts/Supplemental/Arial.ttf",
|
73 |
+
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
74 |
+
]
|
75 |
+
for f_path in font_paths_to_try:
|
76 |
+
try:
|
77 |
+
font = ImageFont.truetype(f_path, FONT_SIZE)
|
78 |
+
break
|
79 |
+
except (IOError, OSError):
|
80 |
+
continue
|
81 |
+
if not font:
|
82 |
+
font = ImageFont.load_default()
|
83 |
+
except Exception as e:
|
84 |
+
print(f"Font loading error: {e}")
|
85 |
font = ImageFont.load_default()
|
86 |
+
|
87 |
+
# --- Text Wrapping Logic ---
|
88 |
+
drawable_width = IMG_WIDTH - 2 * PADDING_X
|
89 |
+
paragraphs = [p.strip() for p in text_content.strip().split('\n') if p.strip()]
|
90 |
+
|
91 |
+
if not paragraphs:
|
92 |
+
return []
|
93 |
+
|
94 |
+
all_lines_and_breaks = []
|
95 |
+
for i, paragraph in enumerate(paragraphs):
|
96 |
+
words = paragraph.split()
|
97 |
+
current_line = ""
|
98 |
+
for word in words:
|
99 |
+
try:
|
100 |
+
# Handle very long words
|
101 |
+
if hasattr(font, 'getlength') and font.getlength(word) > drawable_width:
|
102 |
+
temp_word = ""
|
103 |
+
for char in word:
|
104 |
+
if font.getlength(temp_word + char) > drawable_width:
|
105 |
+
all_lines_and_breaks.append(temp_word)
|
106 |
+
temp_word = char
|
107 |
+
else:
|
108 |
+
temp_word += char
|
109 |
+
word = temp_word
|
110 |
+
|
111 |
+
# Check line length
|
112 |
+
test_line = current_line + " " + word if current_line else word
|
113 |
+
if hasattr(font, 'getlength'):
|
114 |
+
line_width = font.getlength(test_line)
|
115 |
else:
|
116 |
+
line_width = len(test_line) * 12 # Rough estimate
|
117 |
+
|
118 |
+
if line_width <= drawable_width:
|
119 |
+
current_line = test_line
|
120 |
+
else:
|
121 |
+
if current_line:
|
122 |
+
all_lines_and_breaks.append(current_line.strip())
|
123 |
+
current_line = word
|
124 |
+
except Exception as e:
|
125 |
+
print(f"Text wrapping error: {e}")
|
126 |
+
# Fallback to simple character limit
|
127 |
+
if len(current_line + " " + word) <= 80: # Rough character limit
|
128 |
+
current_line += " " + word
|
129 |
+
else:
|
130 |
+
all_lines_and_breaks.append(current_line.strip())
|
131 |
+
current_line = word
|
132 |
+
|
133 |
+
if current_line:
|
134 |
all_lines_and_breaks.append(current_line.strip())
|
135 |
+
if i < len(paragraphs) - 1:
|
136 |
+
all_lines_and_breaks.append(None) # Paragraph break
|
137 |
+
|
138 |
+
# --- Image Generation ---
|
139 |
+
img_count = 0
|
140 |
+
page_content = []
|
141 |
+
y_text = PADDING_Y
|
142 |
+
|
143 |
+
try:
|
144 |
+
if hasattr(font, 'getbbox'):
|
145 |
+
line_height = font.getbbox("A")[3] - font.getbbox("A")[1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
else:
|
147 |
+
line_height = FONT_SIZE
|
148 |
+
except Exception:
|
149 |
+
line_height = FONT_SIZE
|
150 |
|
151 |
+
PARAGRAPH_SPACING = line_height
|
152 |
+
generated_images = []
|
153 |
|
154 |
+
def create_image_page(content, page_num):
|
155 |
+
"""Helper function to create and return a single image page."""
|
156 |
+
try:
|
157 |
+
img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color=BACKGROUND_COLOR)
|
158 |
+
draw = ImageDraw.Draw(img)
|
|
|
|
|
159 |
|
160 |
+
# Draw the selected background style first
|
161 |
+
if style == 'lines':
|
162 |
+
line_style_spacing = line_height + LINE_SPACING
|
163 |
+
draw_horizontal_lines(draw, IMG_WIDTH, IMG_HEIGHT, spacing=line_style_spacing, color=STYLE_COLOR)
|
164 |
+
elif style == 'dots':
|
165 |
+
draw_dot_grid(draw, IMG_WIDTH, IMG_HEIGHT, color=STYLE_COLOR)
|
166 |
+
elif style == 'grid':
|
167 |
+
draw_lattice_grid(draw, IMG_WIDTH, IMG_HEIGHT, color=STYLE_COLOR)
|
168 |
+
|
169 |
+
# Draw the text on top of the background
|
170 |
+
current_y = PADDING_Y
|
171 |
+
for page_item in content:
|
172 |
+
if page_item is not None:
|
173 |
+
try:
|
174 |
+
draw.text((PADDING_X, current_y), page_item, font=font, fill=TEXT_COLOR)
|
175 |
+
current_y += line_height + LINE_SPACING
|
176 |
+
except Exception as e:
|
177 |
+
print(f"Text drawing error: {e}")
|
178 |
+
current_y += line_height + LINE_SPACING
|
179 |
+
else:
|
180 |
+
current_y += PARAGRAPH_SPACING
|
181 |
+
|
182 |
+
# Optimize image for web delivery
|
183 |
+
img = img.convert('RGB')
|
184 |
+
generated_images.append(img)
|
185 |
+
return img
|
186 |
+
except Exception as e:
|
187 |
+
print(f"Image creation error: {e}")
|
188 |
+
# Return a simple error image
|
189 |
+
error_img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color=(255, 255, 255))
|
190 |
+
error_draw = ImageDraw.Draw(error_img)
|
191 |
+
try:
|
192 |
+
error_draw.text((PADDING_X, PADDING_Y), f"Error creating page {page_num}", font=font, fill=(255, 0, 0))
|
193 |
+
except:
|
194 |
+
pass
|
195 |
+
return error_img
|
196 |
+
|
197 |
+
# Process content with page limit
|
198 |
+
for item in all_lines_and_breaks:
|
199 |
+
if img_count >= MAX_PAGES:
|
200 |
+
break
|
201 |
+
|
202 |
+
is_break = item is None
|
203 |
+
item_height = PARAGRAPH_SPACING if is_break else line_height
|
204 |
|
205 |
+
if y_text + item_height > IMG_HEIGHT - PADDING_Y:
|
206 |
+
img_count += 1
|
207 |
+
create_image_page(page_content, img_count)
|
208 |
|
209 |
+
page_content = [item]
|
210 |
+
y_text = PADDING_Y + item_height + (0 if is_break else LINE_SPACING)
|
211 |
+
else:
|
212 |
+
page_content.append(item)
|
213 |
+
y_text += item_height + (0 if is_break else LINE_SPACING)
|
214 |
+
|
215 |
+
if page_content and img_count < MAX_PAGES:
|
216 |
+
img_count += 1
|
217 |
+
create_image_page(page_content, img_count)
|
218 |
+
|
219 |
+
# Memory cleanup
|
220 |
+
gc.collect()
|
221 |
+
|
222 |
+
return generated_images[:MAX_PAGES] # Ensure we don't exceed limit
|
223 |
+
|
224 |
+
except Exception as e:
|
225 |
+
print(f"Critical error in text_to_images_mcp: {e}")
|
226 |
+
print(traceback.format_exc())
|
227 |
+
# Return a simple error image
|
228 |
+
try:
|
229 |
+
error_img = Image.new('RGB', (1080, 1080), color=(255, 255, 255))
|
230 |
+
error_draw = ImageDraw.Draw(error_img)
|
231 |
+
error_draw.text((80, 80), f"Error processing text: {str(e)[:100]}", fill=(255, 0, 0))
|
232 |
+
return [error_img]
|
233 |
+
except:
|
234 |
+
return []
|
235 |
|
236 |
# Sample text for demonstration
|
237 |
sample_text = """In the heart of a bustling city, there lived a clockmaker named Alistair. His shop, a quaint corner of tranquility amidst the urban chaos, was filled with the gentle ticking of countless timepieces. Each clock was a masterpiece, a testament to his dedication and skill. But Alistair held a secret. One of his clocks, an old grandfather clock in the corner, did not just tell time. It told stories.
|
|
|
248 |
lines=10,
|
249 |
max_lines=20,
|
250 |
label="Text Content",
|
251 |
+
placeholder="Enter your long-form text here (max 10,000 characters)..."
|
252 |
),
|
253 |
gr.Dropdown(
|
254 |
choices=['plain', 'lines', 'dots', 'grid'],
|
|
|
265 |
],
|
266 |
outputs=[
|
267 |
gr.Gallery(
|
268 |
+
label="Generated Images (Max 10 pages)",
|
269 |
show_label=True,
|
270 |
elem_id="gallery",
|
271 |
columns=2,
|
|
|
276 |
)
|
277 |
],
|
278 |
title="📖 Text to Images Converter",
|
279 |
+
description="Transform long-form text into a series of attractive, readable images. Simply paste your text, choose a background style, and preview the generated images. You can download individual images by clicking on them. Limited to 10,000 characters and 10 pages for optimal performance.",
|
280 |
examples=[
|
281 |
[sample_text, 'lines', ''],
|
282 |
[sample_text, 'dots', ''],
|