Spaces:
Sleeping
Sleeping
File size: 7,329 Bytes
e53e505 31a7f96 e53e505 fe5a7d8 31a7f96 fe5a7d8 e53e505 fe5a7d8 31a7f96 e53e505 31a7f96 e53e505 31a7f96 e53e505 31a7f96 e53e505 fe5a7d8 e53e505 fe5a7d8 e53e505 fe5a7d8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | from PIL import Image, ImageDraw, ImageFont
import numpy as np
import os
import urllib.request
def get_background_color(image: Image.Image) -> tuple:
pixels = np.array(image.convert("RGB"))
edge_pixels = np.concatenate([
pixels[0, :],
pixels[-1, :],
pixels[:, 0],
pixels[:, -1],
])
colors, counts = np.unique(edge_pixels.reshape(-1, 3), axis=0, return_counts=True)
bg_color = colors[np.argmax(counts)]
return tuple(int(c) for c in bg_color)
def get_text_color(bg_color: tuple) -> tuple:
brightness = sum(bg_color) / 3
return (0, 0, 0) if brightness > 128 else (255, 255, 255)
def find_system_font() -> str:
# 1. Шрифт, лежащий рядом с кодом в самом репозитории (bundled) —
# так Space не зависит от сети во время работы и от блокировок GitHub.
bundled_font_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "font.ttf")
if os.path.exists(bundled_font_path):
return bundled_font_path
# 2. Кэш, куда мог быть скачан шрифт в предыдущем запуске
local_font_path = "font.ttf"
if os.path.exists(local_font_path):
return local_font_path
# 3. Стандартные системные шрифты (для локального ПК)
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"C:/Windows/Fonts/arial.ttf",
"/System/Library/Fonts/Helvetica.ttc",
]
for path in candidates:
if os.path.exists(path):
return path
# 4. Если ничего не нашли — пробуем скачать (как резервный вариант,
# не гарантирован, т.к. на некоторых Spaces исходящий трафик ограничен)
try:
print("[Font] Системные шрифты не найдены. Скачиваю Roboto для поддержки русского языка...")
font_url = "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf"
urllib.request.urlretrieve(font_url, local_font_path)
return local_font_path
except Exception as e:
print(f"[Font] Не удалось скачать шрифт: {e}")
return None
def find_font_size(text: str, box_width: int, box_height: int, font_path: str = None) -> ImageFont.FreeTypeFont:
if font_path is None:
font_path = find_system_font()
size = box_height
while size > 6:
try:
if font_path:
font = ImageFont.truetype(font_path, size)
else:
return ImageFont.load_default()
except Exception:
return ImageFont.load_default()
bbox = font.getbbox(text)
text_width = bbox[2] - bbox[0]
if text_width <= box_width:
return font
size -= 1
try:
return ImageFont.truetype(font_path, 8) if font_path else ImageFont.load_default()
except Exception:
return ImageFont.load_default()
def replace_line_in_image(
image: Image.Image,
translated_text: str,
font_path: str = None,
margin: int = 10,
) -> Image.Image:
"""
Старое поведение: закрашивает ВСЮ картинку цветом фона и кладёт
одну строку перевода по центру. Оставлено для обратной совместимости
и для случаев, когда картинка изначально состоит из одной строки текста
на однородном фоне (как в тестовых/тренировочных данных).
Для картинок с несколькими строками или посторонним контентом
используйте replace_lines_in_image ниже — она не трогает то, что
не является текстом.
"""
result = image.convert("RGB").copy()
draw = ImageDraw.Draw(result)
bg_color = get_background_color(image)
draw.rectangle((0, 0, result.width, result.height), fill=bg_color)
text_color = get_text_color(bg_color)
box_width = result.width - 2 * margin
box_height = result.height - 2 * margin
font = find_font_size(translated_text, box_width, box_height, font_path)
bbox = font.getbbox(translated_text)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (result.width - text_width) / 2
y = (result.height - text_height) / 2 - bbox[1]
draw.text((x, y), translated_text, fill=text_color, font=font)
return result
def replace_lines_in_image(
image: Image.Image,
line_boxes: list[tuple[int, int, int, int]],
translated_lines: list[str],
font_path: str = None,
padding: int = 4,
) -> Image.Image:
"""
Точечная замена: закрашивает и перерисовывает только те области,
где реально был найден текст (по одному прямоугольнику на строку),
остальная часть картинки остаётся нетронутой. Это чинит основной
недостаток replace_line_in_image — потерю всего, что не было текстом.
"""
result = image.convert("RGB").copy()
draw = ImageDraw.Draw(result)
for (left, top, right, bottom), new_text in zip(line_boxes, translated_lines):
l = max(0, left - padding)
t = max(0, top - padding)
r = min(result.width, right + padding)
b = min(result.height, bottom + padding)
bg_color = get_background_color(image.crop((l, t, r, b)))
draw.rectangle((l, t, r, b), fill=bg_color)
text_color = get_text_color(bg_color)
box_width = r - l
box_height = b - t
font = find_font_size(new_text, box_width, box_height, font_path)
bbox = font.getbbox(new_text)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = l + (box_width - text_width) / 2
y = t + (box_height - text_height) / 2 - bbox[1]
draw.text((x, y), new_text, fill=text_color, font=font)
return result
def replace_words_in_image(
image: Image.Image,
word_boxes: list[tuple],
translated_words: list[str],
font_path: str = None,
) -> Image.Image:
result = image.convert("RGB").copy()
draw = ImageDraw.Draw(result)
for box, new_text in zip(word_boxes, translated_words):
left, top, right, bottom = box
bg_color = get_background_color(image.crop(box))
draw.rectangle(box, fill=bg_color)
text_color = get_text_color(bg_color)
box_width = right - left
box_height = bottom - top
font = find_font_size(new_text, box_width, box_height, font_path)
bbox = font.getbbox(new_text)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = left + (box_width - text_width) / 2
y = top + (box_height - text_height) / 2 - bbox[1]
draw.text((x, y), new_text, fill=text_color, font=font)
return result
|