import os import cv2 import numpy as np from typing import List from shapely import affinity from shapely.geometry import Polygon from tqdm import tqdm # from .ballon_extractor import extract_ballon_region from . import text_render from .text_render_eng import render_textblock_list_eng from ..utils import ( BASE_PATH, TextBlock, color_difference, get_logger, rotate_polygons, ) logger = get_logger('render') def parse_font_paths(path: str, default: List[str] = None) -> List[str]: if path: parsed = path.split(',') parsed = list(filter(lambda p: os.path.isfile(p), parsed)) else: parsed = default or [] return parsed def fg_bg_compare(fg, bg): fg_avg = np.mean(fg) if color_difference(fg, bg) < 30: bg = (255, 255, 255) if fg_avg <= 127 else (0, 0, 0) return fg, bg def resize_regions_to_font_size(img: np.ndarray, text_regions: List[TextBlock], font_size_fixed: int, font_size_offset: int, font_size_minimum: int): if font_size_minimum == -1: # Automatically determine font_size by image size font_size_minimum = round((img.shape[0] + img.shape[1]) / 200) logger.debug(f'font_size_minimum {font_size_minimum}') dst_points_list = [] for region in text_regions: char_count_orig = len(region.text) char_count_trans = len(region.translation.strip()) if char_count_trans > char_count_orig: # More characters were added, have to reduce fontsize to fit allotted area # print('count', char_count_trans, region.font_size) rescaled_font_size = region.font_size while True: rows = region.unrotated_size[0] // rescaled_font_size cols = region.unrotated_size[1] // rescaled_font_size if rows * cols >= char_count_trans: # print(rows, cols, rescaled_font_size, rows * cols, char_count_trans) # print('rescaled', rescaled_font_size) region.font_size = rescaled_font_size break rescaled_font_size -= 1 if rescaled_font_size <= 0: break # Otherwise no need to increase fontsize # Infer the target fontsize target_font_size = region.font_size if font_size_fixed is not None: target_font_size = font_size_fixed elif target_font_size < font_size_minimum: target_font_size = max(region.font_size, font_size_minimum) target_font_size += font_size_offset # Rescale dst_points accordingly if target_font_size != region.font_size: target_scale = target_font_size / region.font_size dst_points = region.unrotated_min_rect[0] poly = Polygon(region.unrotated_min_rect[0]) poly = affinity.scale(poly, xfact=target_scale, yfact=target_scale) dst_points = np.array(poly.exterior.coords[:4]) dst_points = rotate_polygons(region.center, dst_points.reshape(1, -1), -region.angle).reshape(-1, 4, 2) # Clip to img width and height dst_points[..., 0] = dst_points[..., 0].clip(0, img.shape[1]) dst_points[..., 1] = dst_points[..., 1].clip(0, img.shape[0]) dst_points = dst_points.reshape((-1, 4, 2)) region.font_size = int(target_font_size) else: dst_points = region.min_rect dst_points_list.append(dst_points) return dst_points_list async def dispatch( img: np.ndarray, text_regions: List[TextBlock], font_path: str = '', font_size_fixed: int = None, font_size_offset: int = 0, font_size_minimum: int = 0, hyphenate: bool = True, render_mask: np.ndarray = None, line_spacing: int = None, disable_font_border: bool = False ) -> np.ndarray: text_render.set_font(font_path) text_regions = list(filter(lambda region: region.translation, text_regions)) # Resize regions that are too small dst_points_list = resize_regions_to_font_size(img, text_regions, font_size_fixed, font_size_offset, font_size_minimum) # TODO: Maybe remove intersections # Render text for region, dst_points in tqdm(zip(text_regions, dst_points_list), '[render]', total=len(text_regions)): if render_mask is not None: # set render_mask to 1 for the region that is inside dst_points cv2.fillConvexPoly(render_mask, dst_points.astype(np.int32), 1) img = render(img, region, dst_points, hyphenate, line_spacing, disable_font_border) return img def render( img, region: TextBlock, dst_points, hyphenate, line_spacing, disable_font_border ): fg, bg = region.get_font_colors() fg, bg = fg_bg_compare(fg, bg) if disable_font_border : bg = None middle_pts = (dst_points[:, [1, 2, 3, 0]] + dst_points) / 2 norm_h = np.linalg.norm(middle_pts[:, 1] - middle_pts[:, 3], axis=1) norm_v = np.linalg.norm(middle_pts[:, 2] - middle_pts[:, 0], axis=1) r_orig = np.mean(norm_h / norm_v) if region.horizontal: temp_box = text_render.put_text_horizontal( region.font_size, region.get_translation_for_rendering(), round(norm_h[0]), round(norm_v[0]), region.alignment, region.direction == 'hr', fg, bg, region.target_lang, hyphenate, line_spacing, ) else: temp_box = text_render.put_text_vertical( region.font_size, region.get_translation_for_rendering(), round(norm_v[0]), region.alignment, fg, bg, line_spacing, ) h, w, _ = temp_box.shape r_temp = w / h # Extend temporary box so that it has same ratio as original if r_temp > r_orig: h_ext = int(w / (2 * r_orig) - h / 2) box = np.zeros((h + h_ext * 2, w, 4), dtype=np.uint8) box[h_ext:h + h_ext, 0:w] = temp_box else: w_ext = int((h * r_orig - w) / 2) box = np.zeros((h, w + w_ext * 2, 4), dtype=np.uint8) box[0:h, w_ext:w_ext+w] = temp_box src_points = np.array([[0, 0], [box.shape[1], 0], [box.shape[1], box.shape[0]], [0, box.shape[0]]]).astype(np.float32) #src_pts[:, 0] = np.clip(np.round(src_pts[:, 0]), 0, enlarged_w * 2) #src_pts[:, 1] = np.clip(np.round(src_pts[:, 1]), 0, enlarged_h * 2) M, _ = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0) rgba_region = cv2.warpPerspective(box, M, (img.shape[1], img.shape[0]), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0) x, y, w, h = cv2.boundingRect(dst_points.astype(np.int32)) canvas_region = rgba_region[y:y+h, x:x+w, :3] mask_region = rgba_region[y:y+h, x:x+w, 3:4].astype(np.float32) / 255.0 img[y:y+h, x:x+w] = np.clip((img[y:y+h, x:x+w].astype(np.float32) * (1 - mask_region) + canvas_region.astype(np.float32) * mask_region), 0, 255).astype(np.uint8) return img async def dispatch_eng_render(img_canvas: np.ndarray, original_img: np.ndarray, text_regions: List[TextBlock], font_path: str = '', line_spacing: int = 0, disable_font_border: bool = False) -> np.ndarray: if len(text_regions) == 0: return img_canvas if not font_path: font_path = os.path.join(BASE_PATH, 'fonts/comic shanns 2.ttf') text_render.set_font(font_path) return render_textblock_list_eng(img_canvas, text_regions, line_spacing=line_spacing, size_tol=1.2, original_img=original_img, downscale_constraint=0.8,disable_font_border=disable_font_border)