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