ArrcttacsrjksX commited on
Commit
fbb991c
·
verified ·
1 Parent(s): dc08df9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -175
app.py CHANGED
@@ -1,56 +1,60 @@
1
- import gradio as gr
2
- import matplotlib.pyplot as plt
3
- from io import BytesIO
4
- from PIL import Image, ImageDraw, ImageFont
5
- import textwrap
6
  import os
7
- import matplotlib
8
  import math
9
- import tempfile
10
- from pathlib import Path
11
-
12
- # A list of common fonts to prioritize
13
- COMMON_FONTS = [
14
- "Times New Roman",
15
- "Arial",
16
- "Calibri",
17
- "Helvetica",
18
- "Verdana",
19
- "Tahoma",
20
- "Georgia",
21
- "Roboto",
22
- "Open Sans",
23
- "Segoe UI"
24
- ]
25
-
26
- def get_system_fonts():
27
- fonts = []
28
- common_fonts_found = []
29
-
30
- for font in matplotlib.font_manager.findSystemFonts(fontpaths=None, fontext='ttf'):
31
- font_name = os.path.basename(font)
32
- actual_name = matplotlib.font_manager.FontProperties(fname=font).get_name()
33
-
34
- if any(common_font.lower() in actual_name.lower() for common_font in COMMON_FONTS):
35
- common_fonts_found.append((font_name, font))
36
- fonts.append((font_name, font))
37
-
38
- sorted_fonts = sorted(common_fonts_found, key=lambda x: COMMON_FONTS.index(
39
- next(cf for cf in COMMON_FONTS if cf.lower() in matplotlib.font_manager.FontProperties(fname=x[1]).get_name().lower())
40
- ))
41
- sorted_fonts.extend([(f[0], f[1]) for f in fonts if f not in common_fonts_found])
42
-
43
- return [f[0] for f in sorted_fonts], {f[0]: f[1] for f in sorted_fonts}
44
 
45
- def parse_color(color):
46
- if isinstance(color, str) and color.startswith('rgba'):
47
- color = color.replace('rgba', '').strip('()').split(',')
48
- return tuple(int(float(c.strip())) for c in color[:3])
49
- return color
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  def calculate_text_dimensions(text, font, max_width, margin):
 
52
  lines = []
53
  for line in text.split('\n'):
 
54
  lines.extend(textwrap.wrap(line, width=int(max_width / font.size * 1.8)))
55
 
56
  bbox = font.getbbox('Ay')
@@ -60,6 +64,7 @@ def calculate_text_dimensions(text, font, max_width, margin):
60
  return lines, line_height, total_height
61
 
62
  def create_text_segment(lines, start_idx, max_lines, width, height, bg_color, text_color, font, align, margin):
 
63
  img = Image.new("RGB", (width, height), color=bg_color)
64
  draw = ImageDraw.Draw(img)
65
 
@@ -74,11 +79,11 @@ def create_text_segment(lines, start_idx, max_lines, width, height, bg_color, te
74
  bbox = font.getbbox(line)
75
  line_width = bbox[2] - bbox[0]
76
 
77
- if align == 'Left':
78
  x = margin
79
- elif align == 'Center':
80
  x = (width - line_width) // 2
81
- else: # Right alignment
82
  x = width - line_width - margin
83
 
84
  draw.text((x, y), line, fill=text_color, font=font)
@@ -86,22 +91,13 @@ def create_text_segment(lines, start_idx, max_lines, width, height, bg_color, te
86
 
87
  return img, end_idx
88
 
89
- def save_image_to_file(img, format="PNG"):
90
- temp_dir = Path(tempfile.gettempdir())
91
- temp_file = temp_dir / f"text_image.{format.lower()}"
92
-
93
- img.save(temp_file, format=format)
94
- return str(temp_file)
95
-
96
- def render_plain_text_image(text, font_size, width, height, bg_color, text_color, font_name, align):
97
- bg_color = parse_color(bg_color)
98
- text_color = parse_color(text_color)
99
  margin = 10
100
-
101
  try:
102
- font_path = FONT_PATHS.get(font_name, font_name)
103
  font = ImageFont.truetype(font_path, font_size)
104
  except Exception:
 
105
  font = ImageFont.load_default()
106
 
107
  max_width = width - 2 * margin
@@ -112,30 +108,27 @@ def render_plain_text_image(text, font_size, width, height, bg_color, text_color
112
 
113
  segments = []
114
  current_line = 0
115
-
116
- for i in range(num_segments):
117
  segment_img, current_line = create_text_segment(
118
  lines, current_line, max_lines_per_segment,
119
  width, height, bg_color, text_color, font, align, margin
120
  )
121
  segments.append(segment_img)
122
 
123
- total_height = len(segments) * height
124
- final_image = Image.new("RGB", (width, total_height), color=bg_color)
125
-
126
  for i, segment in enumerate(segments):
127
  final_image.paste(segment, (0, i * height))
128
 
129
  return final_image
130
 
131
  def render_math_image(text, font_size, width, height, bg_color, text_color):
132
- bg_color = parse_color(bg_color)
133
- text_color = parse_color(text_color)
134
-
135
  fig, ax = plt.subplots(figsize=(width / 100, height / 100), facecolor=bg_color)
136
  ax.set_facecolor(bg_color)
137
  ax.axis('off')
138
 
 
139
  if not (text.startswith(r"$") and text.endswith(r"$")):
140
  text = rf"${text}$"
141
 
@@ -144,116 +137,88 @@ def render_math_image(text, font_size, width, height, bg_color, text_color):
144
  buf = BytesIO()
145
  plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
146
  plt.close(fig)
147
-
148
  buf.seek(0)
149
  img = Image.open(buf)
150
  return img
151
 
152
- def text_to_image(input_text, font_size, width, height, bg_color, text_color,
153
- mode, font_name, align, image_format, preview_mode):
154
- if mode == "Plain Text":
155
- img = render_plain_text_image(input_text, font_size, width, height,
156
- bg_color, text_color, font_name, align)
157
- elif mode == "LaTeX Math":
158
- img = render_math_image(input_text, font_size, width, height, bg_color, text_color)
159
- else:
160
- return "Invalid mode selected!"
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- if preview_mode:
163
- return img
164
- else:
165
- return save_image_to_file(img, image_format)
166
-
167
- def handle_file_upload(file, font_size, width, height, bg_color, text_color,
168
- mode, font_name, align, image_format, preview_mode):
169
- if file is not None:
170
- file_path = file[0]
171
- with open(file_path, "r", encoding="utf-8") as f:
172
- text = f.read()
173
- return text_to_image(text, font_size, width, height, bg_color, text_color,
174
- mode, font_name, align, image_format, preview_mode)
175
- return "No file uploaded!"
176
-
177
- # Retrieve the system fonts and choose a default font.
178
- font_list, FONT_PATHS = get_system_fonts()
179
- default_font = next((f for f in font_list if "times" in f.lower() or "arial" in f.lower()), font_list[0])
180
-
181
- with gr.Blocks() as demo:
182
- gr.Markdown("# 🖼️ Text to Image Converter")
183
-
184
- with gr.Row():
185
- input_text = gr.Textbox(label="Enter Text", placeholder="Type or paste text here...", lines=5)
186
- file_input = gr.File(label="Upload a Text File", type="filepath")
187
-
188
- with gr.Row():
189
- font_size = gr.Slider(10, 100, value=30, label="Font Size")
190
- font_name = gr.Dropdown(choices=font_list, value=default_font, label="Font")
191
- align = gr.Radio(["Left", "Center", "Right"], label="Text Alignment", value="Center")
192
-
193
- with gr.Row():
194
- width = gr.Slider(200, 2000, value=800, label="Image Width")
195
- height = gr.Slider(200, 2000, value=600, label="Base Height")
196
-
197
- with gr.Row():
198
- bg_color = gr.ColorPicker(label="Background Color", value="#FFFFFF")
199
- text_color = gr.ColorPicker(label="Text Color", value="#000000")
200
-
201
- with gr.Row():
202
- mode = gr.Radio(["Plain Text", "LaTeX Math"], label="Rendering Mode", value="Plain Text")
203
- image_format = gr.Radio(["PNG", "JPEG"], label="Image Format", value="PNG")
204
- preview_mode = gr.Checkbox(label="Preview Mode", value=True,
205
- info="Uncheck to get download link instead of preview")
206
-
207
- # Replace gr.Variable with gr.State for maintaining state
208
- output = gr.State()
209
- preview_image = gr.Image(label="Preview", visible=True)
210
- download_link = gr.File(label="Download Image", visible=False)
211
-
212
- # The update function now returns a tuple of two outputs.
213
- def update_output(result, preview_mode):
214
- if preview_mode:
215
- # When in preview mode, show the image preview and hide the download link.
216
- return result, gr.update(visible=False)
217
  else:
218
- # Otherwise, hide the preview and show the download link.
219
- return gr.update(visible=False), result
220
-
221
- with gr.Row():
222
- convert_button = gr.Button("Convert Text to Image")
223
- file_convert_button = gr.Button("Convert File to Image")
224
-
225
- # When clicking the convert button, first run text_to_image and then update the outputs.
226
- convert_button.click(
227
- text_to_image,
228
- inputs=[
229
- input_text, font_size, width, height, bg_color, text_color,
230
- mode, font_name, align, image_format, preview_mode
231
- ],
232
- outputs=[output]
233
- ).then(
234
- update_output,
235
- inputs=[output, preview_mode],
236
- outputs=[preview_image, download_link]
237
- )
238
-
239
- # For file upload conversion:
240
- file_convert_button.click(
241
- handle_file_upload,
242
- inputs=[
243
- file_input, font_size, width, height, bg_color, text_color,
244
- mode, font_name, align, image_format, preview_mode
245
- ],
246
- outputs=[output]
247
- ).then(
248
- update_output,
249
- inputs=[output, preview_mode],
250
- outputs=[preview_image, download_link]
251
- )
252
-
253
- preview_mode.change(
254
- update_output,
255
- inputs=[output, preview_mode],
256
- outputs=[preview_image, download_link]
257
- )
258
 
259
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
  import math
3
+ import textwrap
4
+ from io import BytesIO
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
+ import gradio as gr
7
+ import matplotlib.pyplot as plt
8
+ from PIL import Image, ImageDraw, ImageFont
9
+ from huggingface_hub import hf_hub_download
10
+
11
+ # --- Phần tải về CLI tool từ Hugging Face Hub ---
12
+ # Giả sử tên file CLI trong repo là "Texttoimage" (bạn có thể thay đổi nếu cần)
13
+ CLI_FILENAME = "Texttoimage"
14
+ if not os.path.exists(CLI_FILENAME):
15
+ hf_token = os.environ.get("HF_TOKEN")
16
+ if not hf_token:
17
+ print("Biến môi trường HF_TOKEN chưa được thiết lập!")
18
+ else:
19
+ try:
20
+ # Tải file CLI từ repo ArrcttacsrjksX/Texttoimage
21
+ cli_local_path = hf_hub_download(
22
+ repo_id="ArrcttacsrjksX/Texttoimage",
23
+ filename=CLI_FILENAME,
24
+ token=hf_token
25
+ )
26
+ # Di chuyển (hoặc đổi tên) file tải về nếu cần
27
+ os.rename(cli_local_path, CLI_FILENAME)
28
+ # Cho phép chạy được file CLI
29
+ os.chmod(CLI_FILENAME, 0o755)
30
+ print(f"Đã tải về CLI tool: {CLI_FILENAME}")
31
+ except Exception as e:
32
+ print(f"Lỗi khi tải CLI tool: {e}")
33
+
34
+ # --- Các hàm hỗ trợ render ảnh text ---
35
+
36
+ def parse_color(color: str):
37
+ """
38
+ Chuyển đổi chuỗi màu (hex hoặc RGB dạng "R,G,B") thành tuple RGB.
39
+ Ví dụ: "#FFEEEE" hoặc "255,238,238"
40
+ """
41
+ color = color.strip()
42
+ if color.startswith('#'):
43
+ color = color.lstrip('#')
44
+ if len(color) != 6:
45
+ raise ValueError("Mã hex phải có 6 ký tự.")
46
+ return tuple(int(color[i:i+2], 16) for i in (0, 2, 4))
47
+ else:
48
+ parts = color.split(',')
49
+ if len(parts) != 3:
50
+ raise ValueError("Màu dạng RGB phải có 3 thành phần cách nhau bởi dấu phẩy.")
51
+ return tuple(int(x) for x in parts)
52
 
53
  def calculate_text_dimensions(text, font, max_width, margin):
54
+ """Tính toán kích thước text cho việc wrap theo chiều rộng cho trước."""
55
  lines = []
56
  for line in text.split('\n'):
57
+ # Sử dụng độ rộng ước tính dựa trên kích thước font
58
  lines.extend(textwrap.wrap(line, width=int(max_width / font.size * 1.8)))
59
 
60
  bbox = font.getbbox('Ay')
 
64
  return lines, line_height, total_height
65
 
66
  def create_text_segment(lines, start_idx, max_lines, width, height, bg_color, text_color, font, align, margin):
67
+ """Tạo một đoạn ảnh chứa một phần các dòng text."""
68
  img = Image.new("RGB", (width, height), color=bg_color)
69
  draw = ImageDraw.Draw(img)
70
 
 
79
  bbox = font.getbbox(line)
80
  line_width = bbox[2] - bbox[0]
81
 
82
+ if align == 'left':
83
  x = margin
84
+ elif align == 'center':
85
  x = (width - line_width) // 2
86
+ else: # 'right'
87
  x = width - line_width - margin
88
 
89
  draw.text((x, y), line, fill=text_color, font=font)
 
91
 
92
  return img, end_idx
93
 
94
+ def render_plain_text_image(text, font_size, width, height, bg_color, text_color, font_path, align):
95
+ """Render ảnh chứa text dạng thông thường."""
 
 
 
 
 
 
 
 
96
  margin = 10
 
97
  try:
 
98
  font = ImageFont.truetype(font_path, font_size)
99
  except Exception:
100
+ print(f"Cảnh báo: Không tải được font {font_path}. Sử dụng font mặc định.")
101
  font = ImageFont.load_default()
102
 
103
  max_width = width - 2 * margin
 
108
 
109
  segments = []
110
  current_line = 0
111
+ for _ in range(num_segments):
 
112
  segment_img, current_line = create_text_segment(
113
  lines, current_line, max_lines_per_segment,
114
  width, height, bg_color, text_color, font, align, margin
115
  )
116
  segments.append(segment_img)
117
 
118
+ total_img_height = len(segments) * height
119
+ final_image = Image.new("RGB", (width, total_img_height), color=bg_color)
 
120
  for i, segment in enumerate(segments):
121
  final_image.paste(segment, (0, i * height))
122
 
123
  return final_image
124
 
125
  def render_math_image(text, font_size, width, height, bg_color, text_color):
126
+ """Render ảnh chứa biểu thức toán học sử dụng matplotlib."""
 
 
127
  fig, ax = plt.subplots(figsize=(width / 100, height / 100), facecolor=bg_color)
128
  ax.set_facecolor(bg_color)
129
  ax.axis('off')
130
 
131
+ # Nếu text chưa được bọc trong dấu $, thêm vào
132
  if not (text.startswith(r"$") and text.endswith(r"$")):
133
  text = rf"${text}$"
134
 
 
137
  buf = BytesIO()
138
  plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
139
  plt.close(fig)
 
140
  buf.seek(0)
141
  img = Image.open(buf)
142
  return img
143
 
144
+ # --- Hàm xử chính cho giao diện Gradio ---
145
+
146
+ def generate_image(text: str,
147
+ font_size: int,
148
+ width: int,
149
+ height: int,
150
+ bg_color: str,
151
+ text_color: str,
152
+ align: str,
153
+ mode: str,
154
+ font_path: str):
155
+ """
156
+ Hàm tạo ảnh từ text với các tham số đầu vào.
157
+ Nếu mode = "plain" thì render text bình thường,
158
+ nếu mode = "math" thì render biểu thức toán học.
159
+ """
160
+ try:
161
+ bg_color_tuple = parse_color(bg_color)
162
+ text_color_tuple = parse_color(text_color)
163
+ except Exception as e:
164
+ return f"Lỗi khi parse màu: {e}"
165
 
166
+ try:
167
+ if mode == "plain":
168
+ img = render_plain_text_image(
169
+ text, font_size, width, height,
170
+ bg_color_tuple, text_color_tuple, font_path, align
171
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  else:
173
+ img = render_math_image(
174
+ text, font_size, width, height,
175
+ bg_color_tuple, text_color_tuple
176
+ )
177
+ except Exception as e:
178
+ return f"Lỗi khi tạo ảnh: {e}"
179
+
180
+ return img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ # --- Tạo giao diện Gradio ---
183
+
184
+ # Các widget đầu vào
185
+ text_input = gr.Textbox(label="Text cần chuyển", placeholder="Nhập text của bạn vào đây...", lines=4)
186
+ font_size_input = gr.Slider(10, 100, value=40, step=1, label="Cỡ chữ (font size)")
187
+ width_input = gr.Number(value=1000, label="Chiều rộng ảnh (px)")
188
+ height_input = gr.Number(value=800, label="Chiều cao ảnh (px)")
189
+ bg_color_input = gr.Textbox(value="#FFEEEE", label="Màu nền (hex hoặc R,G,B)")
190
+ text_color_input = gr.Textbox(value="#000066", label="Màu chữ (hex hoặc R,G,B)")
191
+ align_input = gr.Radio(choices=["left", "center", "right"], value="right", label="Căn chỉnh text")
192
+ mode_input = gr.Radio(choices=["plain", "math"], value="plain", label="Chế độ render")
193
+ font_path_input = gr.Textbox(value="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", label="Đường dẫn font")
194
+
195
+ # Một số CSS tùy chỉnh để làm đẹp giao diện
196
+ custom_css = """
197
+ body {
198
+ background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
199
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
200
+ }
201
+ .gradio-container {
202
+ border-radius: 15px;
203
+ box-shadow: 0 4px 10px rgba(0,0,0,0.2);
204
+ padding: 20px;
205
+ background-color: rgba(255, 255, 255, 0.9);
206
+ }
207
+ """
208
+
209
+ # Xây dựng giao diện Gradio
210
+ demo = gr.Interface(
211
+ fn=generate_image,
212
+ inputs=[text_input, font_size_input, width_input, height_input,
213
+ bg_color_input, text_color_input, align_input, mode_input, font_path_input],
214
+ outputs=gr.Image(type="pil", label="Ảnh được tạo"),
215
+ title="Text to Image - Texttoimage CLI",
216
+ description=("Giao diện demo chuyển text thành ảnh. "
217
+ "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, "
218
+ "và xem ảnh được render theo thời gian thực."),
219
+ css=custom_css,
220
+ allow_flagging="never"
221
+ )
222
+
223
+ if __name__ == "__main__":
224
+ demo.launch()