|
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 . 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:
|
|
|
|
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:
|
|
|
|
|
|
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:
|
|
|
|
|
|
region.font_size = rescaled_font_size
|
|
break
|
|
rescaled_font_size -= 1
|
|
if rescaled_font_size <= 0:
|
|
break
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
|
|
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))
|
|
|
|
|
|
dst_points_list = resize_regions_to_font_size(img, text_regions, font_size_fixed, font_size_offset, font_size_minimum)
|
|
|
|
|
|
|
|
|
|
for region, dst_points in tqdm(zip(text_regions, dst_points_list), '[render]', total=len(text_regions)):
|
|
if render_mask is not None:
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
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)
|
|
|