Spaces:
Sleeping
Sleeping
import os | |
import math | |
import textwrap | |
from io import BytesIO | |
import gradio as gr | |
import matplotlib.pyplot as plt | |
from PIL import Image, ImageDraw, ImageFont | |
from huggingface_hub import hf_hub_download | |
# --- Phần tải về CLI tool từ Hugging Face Hub --- | |
# Giả sử tên file CLI trong repo là "Texttoimage" (bạn có thể thay đổi nếu cần) | |
CLI_FILENAME = "Texttoimage" | |
if not os.path.exists(CLI_FILENAME): | |
hf_token = os.environ.get("HF_TOKEN") | |
if not hf_token: | |
print("Biến môi trường HF_TOKEN chưa được thiết lập!") | |
else: | |
try: | |
# Tải file CLI từ repo ArrcttacsrjksX/Texttoimage | |
cli_local_path = hf_hub_download( | |
repo_id="ArrcttacsrjksX/Texttoimage", | |
filename=CLI_FILENAME, | |
token=hf_token | |
) | |
# Di chuyển (hoặc đổi tên) file tải về nếu cần | |
os.rename(cli_local_path, CLI_FILENAME) | |
# Cho phép chạy được file CLI | |
os.chmod(CLI_FILENAME, 0o755) | |
print(f"Đã tải về CLI tool: {CLI_FILENAME}") | |
except Exception as e: | |
print(f"Lỗi khi tải CLI tool: {e}") | |
# --- Các hàm hỗ trợ render ảnh text --- | |
def parse_color(color: str): | |
""" | |
Chuyển đổi chuỗi màu (hex hoặc RGB dạng "R,G,B") thành tuple RGB. | |
Ví dụ: "#FFEEEE" hoặc "255,238,238" | |
""" | |
color = color.strip() | |
if color.startswith('#'): | |
color = color.lstrip('#') | |
if len(color) != 6: | |
raise ValueError("Mã hex phải có 6 ký tự.") | |
return tuple(int(color[i:i+2], 16) for i in (0, 2, 4)) | |
else: | |
parts = color.split(',') | |
if len(parts) != 3: | |
raise ValueError("Màu dạng RGB phải có 3 thành phần cách nhau bởi dấu phẩy.") | |
return tuple(int(x) for x in parts) | |
def calculate_text_dimensions(text, font, max_width, margin): | |
"""Tính toán kích thước text cho việc wrap theo chiều rộng cho trước.""" | |
lines = [] | |
for line in text.split('\n'): | |
# Sử dụng độ rộng ước tính dựa trên kích thước font | |
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): | |
"""Tạo một đoạn ảnh chứa một phần các dòng text.""" | |
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' | |
x = width - line_width - margin | |
draw.text((x, y), line, fill=text_color, font=font) | |
y += line_height | |
return img, end_idx | |
def render_plain_text_image(text, font_size, width, height, bg_color, text_color, font_path, align): | |
"""Render ảnh chứa text dạng thông thường.""" | |
margin = 10 | |
try: | |
font = ImageFont.truetype(font_path, font_size) | |
except Exception: | |
print(f"Cảnh báo: Không tải được font {font_path}. Sử dụng font mặc định.") | |
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 _ 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_img_height = len(segments) * height | |
final_image = Image.new("RGB", (width, total_img_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): | |
"""Render ảnh chứa biểu thức toán học sử dụng matplotlib.""" | |
fig, ax = plt.subplots(figsize=(width / 100, height / 100), facecolor=bg_color) | |
ax.set_facecolor(bg_color) | |
ax.axis('off') | |
# Nếu text chưa được bọc trong dấu $, thêm vào | |
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 | |
# --- Hàm xử lý chính cho giao diện Gradio --- | |
def generate_image(text: str, | |
font_size: int, | |
width: int, | |
height: int, | |
bg_color: str, | |
text_color: str, | |
align: str, | |
mode: str, | |
font_path: str): | |
""" | |
Hàm tạo ảnh từ text với các tham số đầu vào. | |
Nếu mode = "plain" thì render text bình thường, | |
nếu mode = "math" thì render biểu thức toán học. | |
""" | |
try: | |
bg_color_tuple = parse_color(bg_color) | |
text_color_tuple = parse_color(text_color) | |
except Exception as e: | |
return f"Lỗi khi parse màu: {e}" | |
try: | |
if mode == "plain": | |
img = render_plain_text_image( | |
text, font_size, width, height, | |
bg_color_tuple, text_color_tuple, font_path, align | |
) | |
else: | |
img = render_math_image( | |
text, font_size, width, height, | |
bg_color_tuple, text_color_tuple | |
) | |
except Exception as e: | |
return f"Lỗi khi tạo ảnh: {e}" | |
return img | |
# --- Tạo giao diện Gradio --- | |
# Các widget đầu vào | |
text_input = gr.Textbox(label="Text cần chuyển", placeholder="Nhập text của bạn vào đây...", lines=4) | |
font_size_input = gr.Slider(10, 100, value=40, step=1, label="Cỡ chữ (font size)") | |
width_input = gr.Number(value=1000, label="Chiều rộng ảnh (px)") | |
height_input = gr.Number(value=800, label="Chiều cao ảnh (px)") | |
bg_color_input = gr.Textbox(value="#FFEEEE", label="Màu nền (hex hoặc R,G,B)") | |
text_color_input = gr.Textbox(value="#000066", label="Màu chữ (hex hoặc R,G,B)") | |
align_input = gr.Radio(choices=["left", "center", "right"], value="right", label="Căn chỉnh text") | |
mode_input = gr.Radio(choices=["plain", "math"], value="plain", label="Chế độ render") | |
font_path_input = gr.Textbox(value="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", label="Đường dẫn font") | |
# Một số CSS tùy chỉnh để làm đẹp giao diện | |
custom_css = """ | |
body { | |
background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
.gradio-container { | |
border-radius: 15px; | |
box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
padding: 20px; | |
background-color: rgba(255, 255, 255, 0.9); | |
} | |
""" | |
# Xây dựng giao diện Gradio | |
demo = gr.Interface( | |
fn=generate_image, | |
inputs=[text_input, font_size_input, width_input, height_input, | |
bg_color_input, text_color_input, align_input, mode_input, font_path_input], | |
outputs=gr.Image(type="pil", label="Ảnh được tạo"), | |
title="Text to Image - Texttoimage CLI", | |
description=("Giao diện demo chuyển text thành ảnh. " | |
"Bạn có thể nhập text, chọn các tham số như kích thước, màu sắc, căn chỉnh, " | |
"và xem ảnh được render theo thời gian thực."), | |
css=custom_css, | |
allow_flagging="never" | |
) | |
if __name__ == "__main__": | |
demo.launch() | |