TextToImage / app.py
ArrcttacsrjksX's picture
Update app.py
5a1461e verified
raw
history blame
8.92 kB
import gradio as gr
import matplotlib.pyplot as plt
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
import textwrap
import os
import matplotlib
import math
import tempfile
from pathlib import Path
COMMON_FONTS = [
"Times New Roman",
"Arial",
"Calibri",
"Helvetica",
"Verdana",
"Tahoma",
"Georgia",
"Roboto",
"Open Sans",
"Segoe UI"
]
def get_system_fonts():
fonts = []
common_fonts_found = []
for font in matplotlib.font_manager.findSystemFonts(fontpaths=None, fontext='ttf'):
font_name = os.path.basename(font)
actual_name = matplotlib.font_manager.FontProperties(fname=font).get_name()
if any(common_font.lower() in actual_name.lower() for common_font in COMMON_FONTS):
common_fonts_found.append((font_name, font))
fonts.append((font_name, font))
sorted_fonts = sorted(common_fonts_found, key=lambda x: COMMON_FONTS.index(
next(cf for cf in COMMON_FONTS if cf.lower() in matplotlib.font_manager.FontProperties(fname=x[1]).get_name().lower())
))
sorted_fonts.extend([(f[0], f[1]) for f in fonts if f not in common_fonts_found])
return [f[0] for f in sorted_fonts], {f[0]: f[1] for f in sorted_fonts}
def parse_color(color):
if isinstance(color, str) and color.startswith('rgba'):
color = color.replace('rgba', '').strip('()').split(',')
return tuple(int(float(c.strip())) for c in color[:3])
return color
def calculate_text_dimensions(text, font, max_width, margin):
lines = []
for line in text.split('\n'):
lines.extend(textwrap.wrap(line, width=int(max_width / font.size * 1.8)))
bbox = font.getbbox('Ay')
line_height = bbox[3] - bbox[1]
total_height = line_height * len(lines)
return lines, line_height, total_height
def create_text_segment(lines, start_idx, max_lines, width, height, bg_color, text_color, font, align, margin):
img = Image.new("RGB", (width, height), color=bg_color)
draw = ImageDraw.Draw(img)
bbox = font.getbbox('Ay')
line_height = bbox[3] - bbox[1]
y = margin
end_idx = min(start_idx + max_lines, len(lines))
segment_lines = lines[start_idx:end_idx]
for line in segment_lines:
bbox = font.getbbox(line)
line_width = bbox[2] - bbox[0]
if align == 'Left':
x = margin
elif align == 'Center':
x = (width - line_width) // 2
else: # Right alignment
x = width - line_width - margin
draw.text((x, y), line, fill=text_color, font=font)
y += line_height
return img, end_idx
def save_image_to_file(img, format="PNG"):
temp_dir = Path(tempfile.gettempdir())
temp_file = temp_dir / f"text_image.{format.lower()}"
img.save(temp_file, format=format)
return str(temp_file)
def render_plain_text_image(text, font_size, width, height, bg_color, text_color, font_name, align):
bg_color = parse_color(bg_color)
text_color = parse_color(text_color)
margin = 10
try:
font_path = FONT_PATHS.get(font_name, font_name)
font = ImageFont.truetype(font_path, font_size)
except Exception:
font = ImageFont.load_default()
max_width = width - 2 * margin
lines, line_height, total_text_height = calculate_text_dimensions(text, font, max_width, margin)
max_lines_per_segment = (height - 2 * margin) // line_height
num_segments = math.ceil(len(lines) / max_lines_per_segment)
segments = []
current_line = 0
for i in range(num_segments):
segment_img, current_line = create_text_segment(
lines, current_line, max_lines_per_segment,
width, height, bg_color, text_color, font, align, margin
)
segments.append(segment_img)
total_height = len(segments) * height
final_image = Image.new("RGB", (width, total_height), color=bg_color)
for i, segment in enumerate(segments):
final_image.paste(segment, (0, i * height))
return final_image
def render_math_image(text, font_size, width, height, bg_color, text_color):
bg_color = parse_color(bg_color)
text_color = parse_color(text_color)
fig, ax = plt.subplots(figsize=(width / 100, height / 100), facecolor=bg_color)
ax.set_facecolor(bg_color)
ax.axis('off')
if not (text.startswith(r"$") and text.endswith(r"$")):
text = rf"${text}$"
ax.text(0.5, 0.5, text, fontsize=font_size, ha='center', va='center', color=text_color)
buf = BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
plt.close(fig)
buf.seek(0)
img = Image.open(buf)
return img
def text_to_image(input_text, font_size, width, height, bg_color, text_color,
mode, font_name, align, image_format, preview_mode):
if mode == "Plain Text":
img = render_plain_text_image(input_text, font_size, width, height,
bg_color, text_color, font_name, align)
elif mode == "LaTeX Math":
img = render_math_image(input_text, font_size, width, height, bg_color, text_color)
else:
return "Invalid mode selected!"
if preview_mode:
return img
else:
return save_image_to_file(img, image_format)
def handle_file_upload(file, font_size, width, height, bg_color, text_color,
mode, font_name, align, image_format, preview_mode):
if file is not None:
file_path = file[0]
with open(file_path, "r", encoding="utf-8") as f:
text = f.read()
return text_to_image(text, font_size, width, height, bg_color, text_color,
mode, font_name, align, image_format, preview_mode)
return "No file uploaded!"
font_list, FONT_PATHS = get_system_fonts()
default_font = next((f for f in font_list if "times" in f.lower()
or "arial" in f.lower()), font_list[0])
with gr.Blocks() as demo:
gr.Markdown("# 🖼️ Text to Image Converter")
with gr.Row():
input_text = gr.Textbox(label="Enter Text", placeholder="Type or paste text here...", lines=5)
file_input = gr.File(label="Upload a Text File", type="filepath")
with gr.Row():
font_size = gr.Slider(10, 100, value=30, label="Font Size")
font_name = gr.Dropdown(choices=font_list, value=default_font, label="Font")
align = gr.Radio(["Left", "Center", "Right"], label="Text Alignment", value="Center")
with gr.Row():
width = gr.Slider(200, 2000, value=800, label="Image Width")
height = gr.Slider(200, 2000, value=600, label="Base Height")
with gr.Row():
bg_color = gr.ColorPicker(label="Background Color", value="#FFFFFF")
text_color = gr.ColorPicker(label="Text Color", value="#000000")
with gr.Row():
mode = gr.Radio(["Plain Text", "LaTeX Math"], label="Rendering Mode", value="Plain Text")
image_format = gr.Radio(["PNG", "JPEG"], label="Image Format", value="PNG")
preview_mode = gr.Checkbox(label="Preview Mode", value=True,
info="Uncheck to get download link instead of preview")
output = gr.Variable()
preview_image = gr.Image(label="Preview", visible=True)
download_link = gr.File(label="Download Image", visible=False)
def update_output(result, preview_mode):
if preview_mode:
return {
preview_image: result,
download_link: gr.update(visible=False),
preview_image: gr.update(visible=True)
}
else:
return {
download_link: result,
preview_image: gr.update(visible=False),
download_link: gr.update(visible=True)
}
with gr.Row():
convert_button = gr.Button("Convert Text to Image")
file_convert_button = gr.Button("Convert File to Image")
convert_button.click(
text_to_image,
inputs=[
input_text, font_size, width, height, bg_color, text_color,
mode, font_name, align, image_format, preview_mode
],
outputs=[output]
).then(
update_output,
inputs=[output, preview_mode],
outputs=[preview_image, download_link]
)
file_convert_button.click(
handle_file_upload,
inputs=[
file_input, font_size, width, height, bg_color, text_color,
mode, font_name, align, image_format, preview_mode
],
outputs=[output]
).then(
update_output,
inputs=[output, preview_mode],
outputs=[preview_image, download_link]
)
preview_mode.change(
update_output,
inputs=[output, preview_mode],
outputs=[preview_image, download_link]
)
demo.launch()