TextToImage / app.py
ArrcttacsrjksX's picture
Update app.py
dc08df9 verified
raw
history blame
9.14 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
# A list of common fonts to prioritize
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!"
# Retrieve the system fonts and choose a default font.
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")
# Replace gr.Variable with gr.State for maintaining state
output = gr.State()
preview_image = gr.Image(label="Preview", visible=True)
download_link = gr.File(label="Download Image", visible=False)
# The update function now returns a tuple of two outputs.
def update_output(result, preview_mode):
if preview_mode:
# When in preview mode, show the image preview and hide the download link.
return result, gr.update(visible=False)
else:
# Otherwise, hide the preview and show the download link.
return gr.update(visible=False), result
with gr.Row():
convert_button = gr.Button("Convert Text to Image")
file_convert_button = gr.Button("Convert File to Image")
# When clicking the convert button, first run text_to_image and then update the outputs.
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]
)
# For file upload conversion:
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()