diff --git a/handright/_core.py b/handright/_core.py index 0a0cb77..b672944 100644 --- a/handright/_core.py +++ b/handright/_core.py @@ -109,7 +109,7 @@ def _draw_page( while y <= height - bottom_margin - font_size: x = left_margin while True: - if text[start] == _LF: + if text[start] == _LF: #跳过换行符自己处理 start += 1 if start == len(text): return start @@ -120,6 +120,26 @@ def _draw_page( if (x > width - right_margin - font_size and text[start] not in end_chars): break + + # 随机选择一个字符进行替换 9.1 + if rand.random() < tpl.get_strikethrough_probability(): + wrong_char_index = random.randint(0, len(text) - 1) + wrong_end_chars = end_chars + ' ' + # 检查字符是否在排除列表中 + while text[wrong_char_index] in wrong_end_chars: + wrong_char_index = random.randint(0, len(text) - 1) + wrong_char = text[wrong_char_index] + + origin_x =x + # 绘制错误的字(被划掉的字) + if Feature.GRID_LAYOUT in tpl.get_features(): + x = _grid_layout(draw, x, y, wrong_char, tpl, rand) + else: + x = _flow_layout(draw, x, y, wrong_char, tpl, rand) + # 添加涂改标记(斜线) + _draw_strikethrough(draw, origin_x, y, tpl, rand) + + # 绘制正确的字 if Feature.GRID_LAYOUT in tpl.get_features(): x = _grid_layout(draw, x, y, text[start], tpl, rand) else: @@ -130,6 +150,24 @@ def _draw_page( y += line_spacing return start +def _draw_strikethrough(draw, x, y, tpl, rand): + line_length = tpl.get_font().size * math.sqrt(2) + length_sigma = tpl.get_strikethrough_length_sigma() + angle_sigma = tpl.get_strikethrough_angle_sigma() + width_sigma = tpl.get_strikethrough_width_sigma() + line_width = tpl.get_strikethrough_width() + + start_x = x + 1/7*line_length + start_y = y + 1/7*line_length + # 添加扰动 + actual_length = line_length + gauss(rand, 0, length_sigma) + initial_angle = 45 # 初始角度设置为45度 + actual_angle = initial_angle + gauss(rand, 0, angle_sigma) + actual_width = line_width + gauss(rand, 0, width_sigma) # 假设基础宽度为5 + + end_x = start_x + actual_length * math.cos(math.radians(actual_angle))*5/7 + end_y = start_y + actual_length * math.sin(math.radians(actual_angle))*5/7 + draw.line((start_x, start_y, end_x, end_y), fill=_WHITE, width=int(actual_width)) def _flow_layout( draw, x, y, char, tpl: Template, rand: random.Random @@ -152,7 +190,7 @@ def _grid_layout( round(gauss(rand, y, tpl.get_line_spacing_sigma()))) font = _get_font(tpl, rand) _ = _draw_char(draw, char, xy, font) - x += tpl.get_word_spacing() + tpl.get_font().size + x += tpl.get_word_spacing() + tpl.get_font().size#主要的区别在于这里的X它是固定的 return x @@ -246,10 +284,10 @@ def _extract_stroke( bitmap, start: Tuple[int, int], strokes, bbox: Tuple[int, int, int, int] ) -> None: """Helper function of _extract_strokes() which uses depth first search to - find the pixels of a glyph.""" + find the pixels of a glyph. 修改了传入的 strokes 参数""" left, upper, right, lower = bbox stack = [start, ] - while stack: + while stack:#白色是1,為true x, y = stack.pop() if y - 1 >= upper and bitmap[x, y - 1] and strokes.add(_xy(x, y - 1)): stack.append((x, y - 1)) diff --git a/handright/_template.py b/handright/_template.py index 7d97697..9a30730 100644 --- a/handright/_template.py +++ b/handright/_template.py @@ -32,6 +32,11 @@ class Template(object): "_perturb_x_sigma", "_perturb_y_sigma", "_perturb_theta_sigma", + "_strikethrough_length_sigma", + "_strikethrough_angle_sigma", + "_strikethrough_width_sigma", + "_strikethrough_probability", + "_strikethrough_width", "_features", ) @@ -46,6 +51,11 @@ class Template(object): _DEFAULT_END_CHARS = ",。》?;:’”】}、!%),.>?;:]}!%)′″℃℉" _DEFAULT_PERTURB_THETA_SIGMA = 0.07 + + _DEFAULT_strikethrough_width_sigma = 2 + _DEFAULT_strikethrough_angle_sigma = 2 + _DEFAULT_strikethrough_probability = 0 + _DEFAULT_FEATURES = frozenset() @@ -68,6 +78,11 @@ def __init__( perturb_x_sigma: Optional[float] = None, perturb_y_sigma: Optional[float] = None, perturb_theta_sigma: float = _DEFAULT_PERTURB_THETA_SIGMA, + strikethrough_length_sigma: Optional[float]=None, + strikethrough_angle_sigma: float = _DEFAULT_strikethrough_angle_sigma, + strikethrough_width_sigma: Optional[float]=None, + strikethrough_probability: float = _DEFAULT_strikethrough_probability, + strikethrough_width: Optional[float]=None, features: Set = _DEFAULT_FEATURES, ): """Note that, all the Integer parameters are in pixels. @@ -117,6 +132,11 @@ def __init__( self.set_perturb_x_sigma(perturb_x_sigma) self.set_perturb_y_sigma(perturb_y_sigma) self.set_perturb_theta_sigma(perturb_theta_sigma) + self.set_strikethrough_length_sigma(strikethrough_length_sigma) + self.set_strikethrough_angle_sigma(strikethrough_angle_sigma) + self.set_strikethrough_width_sigma(strikethrough_width_sigma) + self.set_strikethrough_probability(strikethrough_probability) + self.set_strikethrough_width(strikethrough_width) self.set_features(features) def __eq__(self, other) -> bool: @@ -138,7 +158,12 @@ def __eq__(self, other) -> bool: and self._end_chars == other._end_chars and self._perturb_x_sigma == other._perturb_x_sigma and self._perturb_y_sigma == other._perturb_y_sigma - and self._perturb_theta_sigma == other._perturb_theta_sigma) + and self._perturb_theta_sigma == other._perturb_theta_sigma + and self._strikethrough_length_sigma == other._strikethrough_length_sigma + and self._strikethrough_angle_sigma == other._strikethrough_angle_sigma + and self._strikethrough_width_sigma == other._strikethrough_width_sigma + and self._strikethrough_probability == other._strikethrough_probability + and self._strikethrough_width == other._strikethrough_width) def set_background(self, background: PIL.Image.Image) -> None: self._background = background @@ -237,6 +262,40 @@ def set_perturb_theta_sigma( ) -> None: self._perturb_theta_sigma = perturb_theta_sigma + def set_strikethrough_length_sigma( + self, strikethrough_length_sigma: Optional[float] = None + ) -> None: + if strikethrough_length_sigma is None: + self._strikethrough_length_sigma = self._font.size / 32 + else: + self._strikethrough_length_sigma = strikethrough_length_sigma + + def set_strikethrough_width_sigma( + self, strikethrough_width_sigma: Optional[float] = None + ) -> None: + if strikethrough_width_sigma is None: + self._strikethrough_width_sigma = self._font.size / 32 + else: + self._strikethrough_width_sigma = strikethrough_width_sigma + + def set_strikethrough_probability( + self, strikethrough_probability: float = _DEFAULT_strikethrough_probability + ) -> None: + self._strikethrough_probability = strikethrough_probability + + def set_strikethrough_width( + self, strikethrough_width: Optional[float] = None + ) -> None: + if strikethrough_width is None: + self._strikethrough_width = self._font.size / 32 + else: + self._strikethrough_width = strikethrough_width + + def set_strikethrough_angle_sigma( + self, strikethrough_angle_sigma: float = _DEFAULT_strikethrough_angle_sigma + )-> None: + self._strikethrough_angle_sigma = strikethrough_angle_sigma + def get_background(self) -> PIL.Image.Image: return self._background @@ -290,6 +349,21 @@ def get_perturb_y_sigma(self) -> float: def get_perturb_theta_sigma(self) -> float: return self._perturb_theta_sigma + + def get_strikethrough_length_sigma(self): + return self._strikethrough_length_sigma + + def get_strikethrough_angle_sigma(self): + return self._strikethrough_angle_sigma + + def get_strikethrough_width_sigma(self): + return self._strikethrough_width_sigma + + def get_strikethrough_probability(self): + return self._strikethrough_probability + + def get_strikethrough_width(self): + return self._strikethrough_width def get_size(self) -> Tuple[int, int]: return self.get_background().size @@ -320,7 +394,12 @@ def __repr__(self) -> str: "end_chars={self._end_chars}, " "perturb_x_sigma={self._perturb_x_sigma}, " "perturb_y_sigma={self._perturb_y_sigma}, " - "perturb_theta_sigma={self._perturb_theta_sigma})" + "perturb_theta_sigma={self._perturb_theta_sigma}," + "strikethrough_length_sigma == {self._strikethrough_length_sigma}," + "strikethrough_angle_sigma == {self._strikethrough_angle_sigma}," + "strikethrough_width_sigma == {self._strikethrough_width_sigma}," + "strikethrough_probability == {self._strikethrough_probability}," + "strikethrough_width == {self._strikethrough_width})" ).format(class_name=class_name, self=self)