Spaces:
Running
Running
| from shapely.geometry import Polygon, MultiPoint | |
| import os | |
| FONT_PATH = os.path.join(os.path.dirname(__file__), "..", "NotoSansSC-Regular.ttf") | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont | |
| import textwrap | |
| def shrink_polygon(polygon, shrink_ratio=0.9): | |
| cx = sum(x for x, _ in polygon) / len(polygon) | |
| cy = sum(y for _, y in polygon) / len(polygon) | |
| shrunk = [((x - cx) * shrink_ratio + cx, (y - cy) * shrink_ratio + cy) for x, y in polygon] | |
| return [(int(x), int(y)) for x, y in shrunk] | |
| import numpy as np | |
| import cv2 | |
| from PIL import Image, ImageDraw | |
| def inpaint_polygon(img: Image.Image, polygon, mode="auto", fallback_color=(255, 255, 255)): | |
| np_img = np.array(img.convert("RGB")) | |
| mask = np.zeros((np_img.shape[0], np_img.shape[1]), dtype=np.uint8) | |
| pts = np.array(polygon, np.int32).reshape((-1, 1, 2)) | |
| cv2.fillPoly(mask, [pts], 255) | |
| if mode == "fill": | |
| img_copy = img.copy() | |
| draw = ImageDraw.Draw(img_copy) | |
| draw.polygon(polygon, fill=fallback_color) | |
| return img_copy | |
| # try: | |
| # inpainted = cv2.inpaint(np_img, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA) | |
| # return Image.fromarray(inpainted) | |
| # except Exception as e: | |
| # print("⚠️ Inpaint failed, falling back to solid fill:", e) | |
| img_copy = img.copy() | |
| draw = ImageDraw.Draw(img_copy) | |
| draw.polygon(polygon, fill=fallback_color) | |
| return img_copy | |
| def merge_polygons_to_convex_hull(polygons): | |
| points = [pt for poly in polygons for pt in poly] | |
| hull = MultiPoint(points).convex_hull | |
| return list(hull.exterior.coords) | |
| def render_translated_chunk(img: Image.Image, translations, font_path="NotoSansSC-Regular.ttf", font_scale=1.0): | |
| from PIL import ImageDraw | |
| img_copy = img.copy() | |
| for entry in translations: | |
| polygon = entry.get("polygon") or entry.get("polygons") | |
| text = entry.get("translated", "") | |
| if polygon and text: | |
| img_copy = draw_translated_text_convex(img_copy, polygon, text, font_path, font_scale) | |
| return img_copy | |
| def draw_translated_text_convex(img, polygon_coords, text, font_path="NotoSansSC-Regular.ttf", font_scale=1.0): | |
| from PIL import ImageDraw | |
| font_polygon = polygon_coords | |
| render_polygon = shrink_polygon(polygon_coords, shrink_ratio=0.9) | |
| img = inpaint_polygon(img, render_polygon, mode="auto", fallback_color=(255, 255, 255)) | |
| debug_color = (180, 255, 180) | |
| draw = ImageDraw.Draw(img) | |
| draw.line(render_polygon + [render_polygon[0]], fill=debug_color, width=1) | |
| draw_wrapped_text(img, render_polygon, text, font_path, polygon_for_size=font_polygon, font_scale=font_scale) | |
| return img | |
| def draw_wrapped_text(img, polygon, text, font_path, polygon_for_size=None, font_scale=1.0): | |
| from PIL import ImageDraw, ImageFont | |
| import textwrap | |
| polygon_for_size = polygon_for_size or polygon | |
| draw = ImageDraw.Draw(img) | |
| xs, ys = zip(*polygon_for_size) | |
| x_min, x_max = min(xs), max(xs) | |
| y_min, y_max = min(ys), max(ys) | |
| box_width = x_max - x_min | |
| box_height = y_max - y_min | |
| avg_char_width = 0.4 | |
| estimated_size = int(min(box_height / 1.2, box_width / (len(text) * avg_char_width))) | |
| estimated_size = max(6, estimated_size) | |
| font_size = int(estimated_size * font_scale) | |
| font = ImageFont.truetype(font_path, font_size) | |
| max_chars = max(1, int(box_width / (font.getbbox("A")[2] + 1))) | |
| wrapped = textwrap.fill(text, width=max_chars) | |
| bbox = draw.textbbox((0, 0), wrapped, font=font) | |
| text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1] | |
| x = x_min + (box_width - text_w) / 2 | |
| y = y_min + (box_height - text_h) / 2 | |
| draw.text((x, y), wrapped, font=font, fill="black", align="center") |