From b76d7aa0c2b919163bfcb4c104f8e667f608d2bf Mon Sep 17 00:00:00 2001 From: dmMaze Date: Wed, 20 Apr 2022 21:39:55 +0800 Subject: [PATCH] improve English text rendering --- fonts/comic shanns 2.ttf | Bin 0 -> 73988 bytes text_rendering/__init__.py | 30 +++++++- text_rendering/text_render_eng.py | 115 ++++++++++++++++++++++++++++++ textblockdetector/textblock.py | 6 +- translate_demo.py | 14 ++-- 5 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 fonts/comic shanns 2.ttf create mode 100644 text_rendering/text_render_eng.py diff --git a/fonts/comic shanns 2.ttf b/fonts/comic shanns 2.ttf new file mode 100644 index 0000000000000000000000000000000000000000..addc86459552bfa4302bfca4fd8a11010f5b91e5 GIT binary patch literal 73988 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 literal 0 HcmV?d00001 diff --git a/text_rendering/__init__.py b/text_rendering/__init__.py index 710b97297..a4836cd19 100644 --- a/text_rendering/__init__.py +++ b/text_rendering/__init__.py @@ -1,12 +1,13 @@ -from typing import List +from typing import List, Union from utils import Quadrilateral import numpy as np import cv2 import math from utils import findNextPowerOf2 - +import textwrap from . import text_render +from .text_render_eng import render_textblock_list_eng from textblockdetector.textblock import TextBlock def fg_bg_compare(fg, bg): @@ -154,4 +155,27 @@ def render(img_canvas, font_size, text_mag_ratio, trans_text, region, majority_d canvas_region = rgba_region[:, :, 0: 3] mask_region = rgba_region[:, :, 3: 4].astype(np.float32) / 255.0 img_canvas = np.clip((img_canvas.astype(np.float32) * (1 - mask_region) + canvas_region.astype(np.float32) * mask_region), 0, 255).astype(np.uint8) - return img_canvas \ No newline at end of file + return img_canvas + + + + +async def dispatch_eng_render(img_canvas: np.ndarray, text_regions: Union[List[TextBlock], List[Quadrilateral]], translated_sentences: List[str], font_path: str) -> np.ndarray : + if len(text_regions) == 0: + return img_canvas + + if isinstance(text_regions[0], Quadrilateral): + blk_list = [] + for region, tr in zip(text_regions, translated_sentences): + x = np.min(region.pts[:, 0]) + w = np.max(region.pts[:, 0]) - x + y = np.min(region.pts[:, 1]) + h = np.max(region.pts[:, 1]) - y + font_size = region.font_size * 0.7 + blk = TextBlock([x, y, w, h], lines=[region.pts], translation=tr, angle=region.angle, font_size=font_size) + blk_list.append(blk) + return render_textblock_list_eng(img_canvas, blk_list, font_path, size_tol=1.1) + + for blk, tr in zip(text_regions, translated_sentences): + blk.translation = tr + return render_textblock_list_eng(img_canvas, text_regions, font_path, size_tol=1.2) \ No newline at end of file diff --git a/text_rendering/text_render_eng.py b/text_rendering/text_render_eng.py new file mode 100644 index 000000000..f6e5ef7cf --- /dev/null +++ b/text_rendering/text_render_eng.py @@ -0,0 +1,115 @@ +from PIL import ImageFont, ImageDraw, Image +import numpy as np +from typing import List, Union +from textblockdetector import TextBlock + +from utils import Quadrilateral + + +class Line: + def __init__(self, text: str = '', pos_x: int = 0, pos_y: int = 0, length: float = 0) -> None: + self.text = text + self.pos_x = pos_x + self.pos_y = pos_y + self.length = int(length) + + +def text_to_word_list(text: str) -> List[str]: + text = text.upper().replace(' ', ' ') + processed_text = '' + + # dumb way to insure spaces between words + text_len = len(text) + for ii, c in enumerate(text): + if c in ['.', '?', '!'] and ii < text_len - 1: + next_c = text[ii + 1] + if next_c.isalpha() or next_c.isnumeric(): + processed_text += c + ' ' + else: + processed_text += c + else: + processed_text += c + word_list = processed_text.split(' ') + words = [] + skip_next = False + word_num = len(word_list) + for ii, word in enumerate(word_list): + if skip_next: + skip_next = False + continue + if ii < word_num - 1: + if len(word) == 1 or len(word_list[ii + 1]) == 1: + skip_next = True + word = word + ' ' + word_list[ii + 1] + words.append(word) + return words + +def render_textblock_list_eng(img: np.ndarray, blk_list: List[TextBlock], font_path: str, scale_quality=1.0, align_center=True, size_tol=1.0): + pilimg = Image.fromarray(img) + for blk in blk_list: + if blk.vertical: + blk.angle -= 90 + sw_r = 0.1 + fs = int(blk.font_size / (1 + 2*sw_r) * scale_quality) + min_bbox = blk.min_rect(rotate_back=False)[0] + bx, by = min_bbox[0] + bw, bh = min_bbox[2] - min_bbox[0] + cx, cy = bx + bw / 2, by + bh / 2 + bw = bw * scale_quality + + font = ImageFont.truetype(font_path, fs) + words = text_to_word_list(blk.translation) + if not len(words): + continue + + base_length = -1 + w_list = [] + + sw = int(sw_r * font.size) + line_height = int((1 + 2*sw_r) * font.getmetrics()[0]) + + for word in words: + wl = font.getlength(word) + w_list.append(wl) + if wl > base_length: + base_length = wl + base_length = max(base_length, bw) + space_l = font.getlength(' ') + pos_x, pos_y = 0, 0 + line = Line(words[0], 0, 0, w_list[0]) + line_lst = [line] + for word, wl in zip(words[1:], w_list[1:]): + added_len = int(space_l + wl + line.length) + if added_len > base_length: + pos_y += line_height + line = Line(word, 0, pos_y, wl) + line_lst.append(line) + else: + line.text = line.text + ' ' + word + line.length = added_len + last_line = line_lst[-1] + canvas_h = last_line.pos_y + line_height + canvas_w = int(base_length) + + font_color = (0, 0, 0) + stroke_color = (255, 255, 255) + img = Image.new('RGBA', (canvas_w, canvas_h), color = (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + d.fontmode = 'L' + + for line in line_lst: + pos_x = int((base_length - line.length) / 2) if align_center else 0 + d.text((pos_x, line.pos_y), line.text, font=font, fill=font_color, stroke_width=sw, stroke_fill=stroke_color) + + if abs(blk.angle) > 3: + img = img.rotate(-blk.angle, expand=True) + im_w, im_h = img.size + scale = min(bh / im_h * size_tol, bw / im_w * size_tol) + if scale < 1: + img = img.resize((int(im_w*scale), int(im_h*scale))) + + im_w, im_h = img.size + paste_x, paste_y = int(cx - im_w / 2), int(cy - im_h / 2) + pilimg.paste(img, (paste_x, paste_y), mask=img) + + return np.array(pilimg) \ No newline at end of file diff --git a/textblockdetector/textblock.py b/textblockdetector/textblock.py index b7d7cfef8..38274cf43 100644 --- a/textblockdetector/textblock.py +++ b/textblockdetector/textblock.py @@ -131,7 +131,7 @@ def min_rect(self, rotate_back=True): min_bbox = np.array([[min_x, min_y, max_x, min_y, max_x, max_y, min_x, max_y]]) if angled and rotate_back: min_bbox = rotate_polygons(center, min_bbox, -self.angle) - return min_bbox.reshape(-1, 4, 2) + return min_bbox.reshape(-1, 4, 2).astype(np.int64) # equivalent to qt's boundingRect, ignore angle def bounding_rect(self): @@ -365,8 +365,8 @@ def try_merge_textline(blk: TextBlock, blk2: TextBlock, fntsize_tol=1.3, distanc blk.lines.append(blk2.lines[0]) blk.vec = vec_sum blk.angle = int(round(np.rad2deg(math.atan2(vec_sum[1], vec_sum[0])))) - if blk.vertical: - blk.angle -= 90 + # if blk.vertical: + # blk.angle -= 90 blk.norm = np.linalg.norm(vec_sum) blk.distance = np.append(blk.distance, blk2.distance[-1]) blk.font_size = fntsz_avg diff --git a/translate_demo.py b/translate_demo.py index 75a7ed665..3f3378fce 100755 --- a/translate_demo.py +++ b/translate_demo.py @@ -36,6 +36,8 @@ parser.add_argument('--target-lang', default='CHS', type=str, help='destination language') parser.add_argument('--use-ctd', action='store_true', help='use comic-text-detector for text detection') parser.add_argument('--verbose', action='store_true', help='print debug info and save intermediate images') +parser.add_argument('--manga2eng', action='store_true', help='render English text translated from manga with some typesetting') +parser.add_argument('--eng-font', default='fonts/comic shanns 2.ttf', type=str, help='font used by manga2eng mode') args = parser.parse_args() def update_state(task_id, nonce, state) : @@ -173,11 +175,15 @@ async def infer( if mode == 'web' and task_id : update_state(task_id, nonce, 'render') # render translated texts - if detector == 'ctd' : - from text_rendering import dispatch_ctd_render - output = await dispatch_ctd_render(np.copy(img_inpainted), args.text_mag_ratio, translated_sentences, text_regions, render_text_direction_overwrite) + if args.target_lang == 'ENG' and args.manga2eng: + from text_rendering import dispatch_eng_render + output = await dispatch_eng_render(np.copy(img_inpainted), text_regions, translated_sentences, args.eng_font) else: - output = await dispatch_rendering(np.copy(img_inpainted), args.text_mag_ratio, translated_sentences, textlines, text_regions, render_text_direction_overwrite) + if detector == 'ctd' : + from text_rendering import dispatch_ctd_render + output = await dispatch_ctd_render(np.copy(img_inpainted), args.text_mag_ratio, translated_sentences, text_regions, render_text_direction_overwrite) + else: + output = await dispatch_rendering(np.copy(img_inpainted), args.text_mag_ratio, translated_sentences, textlines, text_regions, render_text_direction_overwrite) print(' -- Saving results') if alpha_ch is not None :