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() | |