Skip to content

Commit 1b88331

Browse files
committed
feat: 添加可配置描边宽度设置,修复智能缩放算法单行判断问题 (v1.8.4)
1 parent 49b0a62 commit 1b88331

11 files changed

Lines changed: 84 additions & 15 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# 漫画图片翻译器
22

3+
[![DeepWiki文档](https://img.shields.io/badge/DeepWiki-在线文档-blue?style=for-the-badge)](https://deepwiki.com/hgmzhn/manga-translator-ui)
4+
35
一键翻译漫画图片中的文字,支持日语、中文、英语等多种语言,提供完整的可视化编辑功能。
46

57
基于 [zyddnys/manga-image-translator](https://github.com/zyddnys/manga-image-translator) 核心引擎开发。

desktop_qt_ui/app_logic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ def get_display_mapping(self, key: str) -> Optional[Dict[str, str]]:
385385
"renderer": "渲染器", "alignment": "对齐方式", "disable_font_border": "禁用字体边框",
386386
"disable_auto_wrap": "AI断句", "font_size_offset": "字体大小偏移量", "font_size_minimum": "最小字体大小",
387387
"max_font_size": "最大字体大小", "font_scale_ratio": "字体缩放比例",
388+
"stroke_width": "描边宽度比例",
388389
"center_text_in_bubble": "AI断句时文本居中",
389390
"optimize_line_breaks": "AI断句自动扩大文字", "check_br_and_retry": "AI断句检查",
390391
"strict_smart_scaling": "AI断句自动扩大文字下不扩大文本框",

desktop_qt_ui/core/config_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class RenderSettings(BaseModel):
8888
optimize_line_breaks: bool = False
8989
check_br_and_retry: bool = False
9090
strict_smart_scaling: bool = False
91+
stroke_width: float = 0.07
9192

9293
class UpscaleSettings(BaseModel):
9394
upscaler: str = "esrgan"

desktop_qt_ui/services/render_parameter_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class RenderParameters:
5656
strict_smart_scaling: bool = False # AI断句自动扩大文字下不扩大文本框
5757

5858
# 效果参数
59-
stroke_width: float = 0.2 # 描边宽度
59+
stroke_width: float = 0.07 # 描边宽度(相对字体大小的比例)
6060
shadow_radius: float = 0.0 # 阴影半径
6161
shadow_strength: float = 1.0 # 阴影强度
6262
shadow_color: Tuple[int, int, int] = (0, 0, 0) # 阴影颜色

doc/CHANGELOG_v1.8.4.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# 更新日志 v1.8.4
2+
3+
## 新功能
4+
5+
- **可配置描边宽度**:新增"描边宽度比例"设置,可自定义字体边框粗细(默认0.07,范围0.0-1.0)
6+
7+
## Bug 修复
8+
9+
- **修复智能缩放算法**:修复AI断句关闭时多行文本被误判为单行,导致文本框扩大不足的问题
10+
- **增强Debug日志**:智能缩放添加详细日志输出,方便诊断问题
11+
12+
## 文档更新
13+
14+
- 添加 DeepWiki 在线文档徽章
15+
- 更新设置说明文档
16+
17+
## 升级方法
18+
19+
已安装用户:双击 `步骤4-更新维护.bat` → 选择"完整更新"

doc/SETTINGS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@
158158

159159
- **禁用字体边框**:禁用字体边框(去除描边效果)
160160

161+
- **描边宽度比例**:字体描边(边框)宽度,相对于字体大小的比例
162+
- 默认:0.07(7%)
163+
- 范围:0.0-1.0
164+
- 设为 0 可完全禁用描边效果(等同于勾选"禁用字体边框")
165+
- 建议范围:0.05-0.15(5%-15%)
166+
- 越大描边越粗,越小描边越细
167+
161168
- **AI断句**:禁用自动换行(启用 AI 断句时会自动禁用自动换行)
162169
- 勾选此选项将禁用自动换行功能
163170
- 常与 AI 断句功能配合使用

examples/config-example.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"use_yolo_obb": true,
4040
"yolo_obb_conf": 0.4,
4141
"yolo_obb_iou": 0.6,
42-
"yolo_obb_overlap_threshold": 1e-05
42+
"yolo_obb_overlap_threshold": "0.1"
4343
},
4444
"inpainter": {
4545
"inpainter": "lama_large",
@@ -70,7 +70,8 @@
7070
"center_text_in_bubble": false,
7171
"optimize_line_breaks": false,
7272
"check_br_and_retry": false,
73-
"strict_smart_scaling": false
73+
"strict_smart_scaling": false,
74+
"stroke_width": "0.07"
7475
},
7576
"upscale": {
7677
"upscaler": "realcugan",
@@ -100,7 +101,7 @@
100101
"template": false,
101102
"prep_manual": false,
102103
"save_quality": 100,
103-
"batch_size": 1,
104+
"batch_size": "3",
104105
"batch_concurrent": false,
105106
"generate_and_export": false,
106107
"colorize_only": false,

manga_translator/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ class RenderConfig(BaseModel):
202202
"""Automatically rotate symbols like '!!' or '??' in vertical text"""
203203
layout_mode: str = 'smart_scaling'
204204
"""The layout mode to use for rendering. Options: 'default', 'smart_scaling', 'strict', 'disable_all', 'balloon_fill'"""
205+
stroke_width: float = 0.07
206+
"""Stroke/border width ratio relative to font size. Default is 0.07 (7%). Set to 0 to disable stroke."""
205207
_font_color_fg = None
206208
_font_color_bg = None
207209
@property

manga_translator/rendering/__init__.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,8 @@ def resize_regions_to_font_size(img: np.ndarray, text_regions: List['TextBlock']
840840
else:
841841
# --- ORIGINAL smart_scaling LOGIC for AI OFF ---
842842
# This is the old logic based on diff_ratio, preserved for when AI splitting is off.
843+
logger.debug(f"[SMART_SCALING DEBUG] Region has {len(region.lines)} lines in OCR result")
844+
843845
if len(region.lines) > 1:
844846
from shapely.ops import unary_union
845847
try:
@@ -850,13 +852,16 @@ def resize_regions_to_font_size(img: np.ndarray, text_regions: List['TextBlock']
850852
union_poly = unary_union(unrotated_polygons)
851853
original_area = union_poly.area
852854
unrotated_base_poly = union_poly.envelope
855+
logger.debug(f"[SMART_SCALING DEBUG] Multi-line region, union area={original_area:.1f}")
853856
except Exception as e:
854857
logger.warning(f"Failed to compute union of polygons: {e}")
855858
original_area = region.unrotated_size[0] * region.unrotated_size[1]
856859
unrotated_base_poly = Polygon(region.unrotated_min_rect[0])
860+
logger.debug(f"[SMART_SCALING DEBUG] Fallback to simple area={original_area:.1f}")
857861
else:
858862
original_area = region.unrotated_size[0] * region.unrotated_size[1]
859863
unrotated_base_poly = Polygon(region.unrotated_min_rect[0])
864+
logger.debug(f"[SMART_SCALING DEBUG] Single-line region, area={original_area:.1f}")
860865

861866
required_area = 0
862867
required_width = 0
@@ -867,26 +872,30 @@ def resize_regions_to_font_size(img: np.ndarray, text_regions: List['TextBlock']
867872
required_width = max(widths)
868873
required_height = len(lines) * (target_font_size * (1 + (config.render.line_spacing or 0.01)))
869874
required_area = required_width * required_height
875+
logger.debug(f"[SMART_SCALING DEBUG] Horizontal: {len(lines)} lines, required_width={required_width:.1f}, required_height={required_height:.1f}, required_area={required_area:.1f}")
870876
else: # Vertical
871877
lines, heights = text_render.calc_vertical(target_font_size, region.translation, max_height=99999)
872878
if heights:
873879
required_height = max(heights)
874880
required_width = len(lines) * (target_font_size * (1 + (config.render.line_spacing or 0.2)))
875881
required_area = required_width * required_height
882+
logger.debug(f"[SMART_SCALING DEBUG] Vertical: {len(lines)} columns, required_width={required_width:.1f}, required_height={required_height:.1f}, required_area={required_area:.1f}")
876883

877884
dst_points = region.min_rect
878885

879-
# 检查是否为单行/单列文本(不会换行
880-
# Check if this is single line/column text (won't wrap)
881-
is_single_line = len(lines) == 1 if lines else False
886+
# 检查是否为单行/单列文本(基于OCR检测到的文本框数量
887+
# Check if this is single line/column text based on OCR detected text boxes
888+
is_single_line = len(region.lines) == 1
882889
bubble_width, bubble_height = region.unrotated_size
890+
logger.debug(f"[SMART_SCALING DEBUG] is_single_line={is_single_line} (based on OCR lines={len(region.lines)}), bubble_size={bubble_width:.1f}x{bubble_height:.1f}")
883891

884892
# 单行文本使用独立缩放(与AI ON相同),因为不会换行
885893
# Single line text uses independent scaling (same as AI ON) because it won't wrap
886894
if is_single_line and required_width > 0 and required_height > 0:
887895
# 检查溢出(与AI ON算法一致)
888896
width_overflow = max(0, required_width - bubble_width)
889897
height_overflow = max(0, required_height - bubble_height)
898+
logger.debug(f"[SMART_SCALING DEBUG] Single-line overflow: width={width_overflow:.1f}, height={height_overflow:.1f}")
890899

891900
if width_overflow > 0 or height_overflow > 0:
892901
# 独立缩放宽度和高度
@@ -898,12 +907,14 @@ def resize_regions_to_font_size(img: np.ndarray, text_regions: List['TextBlock']
898907
diff_ratio_w = width_scale_needed - 1.0
899908
box_expansion_ratio_w = diff_ratio_w / 2
900909
width_scale_factor = 1 + min(box_expansion_ratio_w, 1.0)
910+
logger.debug(f"[SMART_SCALING DEBUG] Width scale: needed={width_scale_needed:.3f}, diff_ratio={diff_ratio_w:.3f}, expansion={box_expansion_ratio_w:.3f}, final={width_scale_factor:.3f}")
901911

902912
if height_overflow > 0:
903913
height_scale_needed = required_height / bubble_height if bubble_height > 0 else 1.0
904914
diff_ratio_h = height_scale_needed - 1.0
905915
box_expansion_ratio_h = diff_ratio_h / 2
906916
height_scale_factor = 1 + min(box_expansion_ratio_h, 1.0)
917+
logger.debug(f"[SMART_SCALING DEBUG] Height scale: needed={height_scale_needed:.3f}, diff_ratio={diff_ratio_h:.3f}, expansion={box_expansion_ratio_h:.3f}, final={height_scale_factor:.3f}")
907918

908919
try:
909920
scaled_unrotated_poly = affinity.scale(unrotated_base_poly, xfact=width_scale_factor, yfact=height_scale_factor, origin='center')
@@ -919,24 +930,28 @@ def resize_regions_to_font_size(img: np.ndarray, text_regions: List['TextBlock']
919930
font_shrink_ratio = diff_ratio / 2 / (1 + diff_ratio)
920931
font_scale_factor = 1 - min(font_shrink_ratio, 0.5)
921932
target_font_size = int(target_font_size * font_scale_factor)
933+
logger.debug(f"[SMART_SCALING DEBUG] Font shrink: scale_needed={scale_needed:.3f}, shrink_ratio={font_shrink_ratio:.3f}, font_scale={font_scale_factor:.3f}, new_font_size={target_font_size}")
922934
else:
923935
# 没有溢出,可以放大字体以更好地填充
924936
width_scale_factor = bubble_width / required_width
925937
height_scale_factor = bubble_height / required_height
926938
font_scale_factor = min(width_scale_factor, height_scale_factor)
927939
target_font_size = int(target_font_size * font_scale_factor)
940+
logger.debug(f"[SMART_SCALING DEBUG] No overflow, enlarging font: width_scale={width_scale_factor:.3f}, height_scale={height_scale_factor:.3f}, font_scale={font_scale_factor:.3f}, new_font_size={target_font_size}")
928941
else:
929942
# 多行文本使用原来的等比缩放算法
930943
# Multi-line text uses original proportional scaling algorithm
931944
diff_ratio = 0
932945
if original_area > 0 and required_area > 0:
933946
diff_ratio = (required_area - original_area) / original_area
947+
logger.debug(f"[SMART_SCALING DEBUG] Multi-line: original_area={original_area:.1f}, required_area={required_area:.1f}, diff_ratio={diff_ratio:.3f}")
934948

935949
if diff_ratio > 0:
936950
box_expansion_ratio = diff_ratio / 2
937951
box_scale_factor = 1 + min(box_expansion_ratio, 1.0)
938952
font_shrink_ratio = diff_ratio / 2 / (1 + diff_ratio)
939953
font_scale_factor = 1 - min(font_shrink_ratio, 0.5)
954+
logger.debug(f"[SMART_SCALING DEBUG] Expanding box: expansion_ratio={box_expansion_ratio:.3f}, box_scale={box_scale_factor:.3f}, font_shrink_ratio={font_shrink_ratio:.3f}, font_scale={font_scale_factor:.3f}")
940955
try:
941956
scaled_unrotated_poly = affinity.scale(unrotated_base_poly, xfact=box_scale_factor, yfact=box_scale_factor, origin='center')
942957
scaled_unrotated_points = np.array(scaled_unrotated_poly.exterior.coords[:4])
@@ -945,6 +960,7 @@ def resize_regions_to_font_size(img: np.ndarray, text_regions: List['TextBlock']
945960
logger.warning(f"Failed to apply dynamic scaling: {e}")
946961
target_font_size = int(target_font_size * font_scale_factor)
947962
elif diff_ratio < 0:
963+
logger.debug(f"[SMART_SCALING DEBUG] Shrinking: diff_ratio={diff_ratio:.3f}")
948964
try:
949965
area_ratio = original_area / required_area
950966
font_scale_factor = np.sqrt(area_ratio)

manga_translator/rendering/text_render.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,9 @@ def put_char_vertical(font_size: int, cdpt: str, pen_l: Tuple[int, int], canvas_
552552
if border_size > 0:
553553
glyph_border = get_char_border(cdpt, font_size, 1)
554554
stroker = freetype.Stroker()
555-
stroke_radius = 64 * max(int(0.07 * font_size), 1)
555+
# Get stroke width from config, default to 0.07 if not specified
556+
stroke_ratio = config.render.stroke_width if (config and hasattr(config.render, 'stroke_width')) else 0.07
557+
stroke_radius = 64 * max(int(stroke_ratio * font_size), 1)
556558
stroker.set(stroke_radius, freetype.FT_STROKER_LINEJOIN_ROUND, freetype.FT_STROKER_LINECAP_ROUND, 0)
557559
glyph_border.stroke(stroker, destroy=True)
558560
blyph = glyph_border.to_bitmap(freetype.FT_RENDER_MODE_NORMAL, freetype.Vector(0, 0), True)
@@ -603,17 +605,25 @@ def put_text_vertical(font_size: int, text: str, h: int, alignment: str, fg: Tup
603605
if not text:
604606
return
605607

606-
bg_size = int(max(font_size * 0.07, 1)) if bg is not None else 0
608+
# Get stroke width from config, default to 0.07 if not specified
609+
stroke_ratio = config.render.stroke_width if (config and hasattr(config.render, 'stroke_width')) else 0.07
610+
bg_size = int(max(font_size * stroke_ratio, 1)) if bg is not None else 0
607611
spacing_x = int(font_size * (line_spacing or 0.2))
608612

609613
# Conditional wrapping logic based on disable_auto_wrap and region_count
610614
effective_max_height = h
611615
if config and config.render.disable_auto_wrap:
612616
# 当AI断句开启时,使用无限高度,让文本按AI断句标记换行
613617
effective_max_height = 99999
618+
logger.debug(f"[VERTICAL DEBUG] AI断句开启,effective_max_height=99999, region_count={region_count}")
614619
elif config and config.render.layout_mode == 'smart_scaling':
615620
if region_count <= 1:
616621
effective_max_height = 99999
622+
logger.debug(f"[VERTICAL DEBUG] Smart scaling单区域,effective_max_height=99999, region_count={region_count}")
623+
else:
624+
logger.debug(f"[VERTICAL DEBUG] Smart scaling多区域,effective_max_height={h}, region_count={region_count}")
625+
else:
626+
logger.debug(f"[VERTICAL DEBUG] 默认模式,effective_max_height={h}, region_count={region_count}")
617627

618628
# Use original font size for line breaking calculation
619629
line_text_list, line_height_list = calc_vertical(font_size, text, effective_max_height, config=config)
@@ -665,7 +675,7 @@ def put_text_vertical(font_size: int, text: str, h: int, alignment: str, fg: Tup
665675
for char_h in content:
666676
if char_h == '!': char_h = '!'
667677
elif char_h == '?': char_h = '?'
668-
offset_x = put_char_horizontal(h_font_size, char_h, pen_h, temp_canvas_text, temp_canvas_border, border_size=bg_size)
678+
offset_x = put_char_horizontal(h_font_size, char_h, pen_h, temp_canvas_text, temp_canvas_border, border_size=bg_size, config=config)
669679
pen_h[0] += offset_x
670680

671681
combined_temp = cv2.add(temp_canvas_text, temp_canvas_border)
@@ -1095,7 +1105,7 @@ def get_present_syllables(line_idx, word_pos):
10951105

10961106
return line_text_list, line_width_list
10971107

1098-
def put_char_horizontal(font_size: int, cdpt: str, pen_l: Tuple[int, int], canvas_text: np.ndarray, canvas_border: np.ndarray, border_size: int):
1108+
def put_char_horizontal(font_size: int, cdpt: str, pen_l: Tuple[int, int], canvas_text: np.ndarray, canvas_border: np.ndarray, border_size: int, config=None):
10991109
if cdpt == '_':
11001110
# For the placeholder, just advance the pen and do nothing else.
11011111
return get_char_offset_x(font_size, '_')
@@ -1138,7 +1148,9 @@ def put_char_horizontal(font_size: int, cdpt: str, pen_l: Tuple[int, int], canva
11381148
if border_size > 0:
11391149
glyph_border = get_char_border(cdpt, font_size, 0)
11401150
stroker = freetype.Stroker()
1141-
stroke_radius = 64 * max(int(0.07 * font_size), 1)
1151+
# Get stroke width from config, default to 0.07 if not specified
1152+
stroke_ratio = config.render.stroke_width if (config and hasattr(config.render, 'stroke_width')) else 0.07
1153+
stroke_radius = 64 * max(int(stroke_ratio * font_size), 1)
11421154
stroker.set(stroke_radius,
11431155
freetype.FT_STROKER_LINEJOIN_ROUND,
11441156
freetype.FT_STROKER_LINECAP_ROUND,
@@ -1221,6 +1233,7 @@ def put_text_horizontal(font_size: int, text: str, width: int, height: int, alig
12211233
text = re.sub(r'\s*(\[BR\]|<br>|【BR】)\s*', '\n', text, flags=re.IGNORECASE)
12221234
# 使用无限宽度,让文本完全按照AI断句标记换行
12231235
width = 99999
1236+
logger.debug(f"[HORIZONTAL DEBUG] AI断句开启,width=99999, region_count={region_count}")
12241237
elif layout_mode == 'smart_scaling':
12251238
# In smart_scaling mode, wrapping is conditional.
12261239
# It wraps only if manual line breaks ([BR] or \n) are present.
@@ -1231,8 +1244,15 @@ def put_text_horizontal(font_size: int, text: str, width: int, height: int, alig
12311244
# No manual breaks found, so disable wrapping by setting a large width.
12321245
if region_count <= 1:
12331246
width = 99999
1247+
logger.debug(f"[HORIZONTAL DEBUG] Smart scaling单区域无换行符,width=99999, region_count={region_count}")
1248+
else:
1249+
logger.debug(f"[HORIZONTAL DEBUG] Smart scaling多区域无换行符,width={width}, region_count={region_count}")
1250+
else:
1251+
logger.debug(f"[HORIZONTAL DEBUG] Smart scaling有换行符,width={width}, region_count={region_count}, line_count={text.count(chr(10))+1}")
12341252

1235-
bg_size = int(max(font_size * 0.07, 1)) if bg is not None else 0
1253+
# Get stroke width from config, default to 0.07 if not specified
1254+
stroke_ratio = config.render.stroke_width if (config and hasattr(config.render, 'stroke_width')) else 0.07
1255+
bg_size = int(max(font_size * stroke_ratio, 1)) if bg is not None else 0
12361256
spacing_y = int(font_size * (line_spacing or 0.01))
12371257

12381258
# ✅ DEBUG: 输出渲染参数
@@ -1286,7 +1306,7 @@ def put_text_horizontal(font_size: int, text: str, width: int, height: int, alig
12861306
glyph = get_char_glyph(cdpt, font_size, 0)
12871307
offset_x = glyph.metrics.horiAdvance >> 6
12881308
pen_line[0] -= offset_x
1289-
offset_x = put_char_horizontal(font_size, c, pen_line, canvas_text, canvas_border, border_size=bg_size)
1309+
offset_x = put_char_horizontal(font_size, c, pen_line, canvas_text, canvas_border, border_size=bg_size, config=config)
12901310
if not reversed_direction:
12911311
pen_line[0] += offset_x
12921312
pen_orig[1] += spacing_y + font_size

0 commit comments

Comments
 (0)