Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ink_depth_rand/墨水深度随机化 #50

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ __pycache__/

# mypy
.mypy_cache

.pypirc
4 changes: 2 additions & 2 deletions handright/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
from handright._exceptions import Error, LayoutError, BackgroundTooLargeError
from handright._template import Template, Feature

__version__ = "8.2.0"
__version__ = "8.7.0"

__all__ = (
"handwrite",
"Template",
"Feature",
"Error",
"LayoutError",
"BackgroundTooLargeError"
"BackgroundTooLargeError",
)
122 changes: 72 additions & 50 deletions handright/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@


def handwrite(
text: str,
template: Union[Template, Sequence[Template]],
seed: Hashable = None,
mapper: Callable[[Callable, Iterable], Iterable] = map,
text: str,
template: Union[Template, Sequence[Template]],
seed: Hashable = None,
mapper: Callable[[Callable, Iterable], Iterable] = map,
) -> Iterable[PIL.Image.Image]:
"""Handwrite `text` with the configurations in `template`, and return an
Iterable of Pillow's Images.
Expand Down Expand Up @@ -72,25 +72,25 @@ def _preprocess_text(text: str) -> str:


def _check_template(page, tpl) -> None:
if page.height() < (tpl.get_top_margin() + tpl.get_line_spacing()
+ tpl.get_bottom_margin()):
if page.height() < (
tpl.get_top_margin() + tpl.get_line_spacing() + tpl.get_bottom_margin()
):
msg = "for (height < top_margin + line_spacing + bottom_margin)"
raise LayoutError(msg)
if tpl.get_font().size > tpl.get_line_spacing():
msg = "for (font.size > line_spacing)"
raise LayoutError(msg)
if page.width() < (tpl.get_left_margin() + tpl.get_font().size
+ tpl.get_right_margin()):
if page.width() < (
tpl.get_left_margin() + tpl.get_font().size + tpl.get_right_margin()
):
msg = "for (width < left_margin + font.size + right_margin)"
raise LayoutError(msg)
if tpl.get_word_spacing() <= -tpl.get_font().size // 2:
msg = "for (word_spacing <= -font.size // 2)"
raise LayoutError(msg)


def _draw_page(
page, text, start: int, tpl: Template, rand: random.Random
) -> int:
def _draw_page(page, text, start: int, tpl: Template, rand: random.Random) -> int:
_check_template(page, tpl)

width = page.width()
Expand All @@ -114,11 +114,9 @@ def _draw_page(
if start == len(text):
return start
break
if (x > width - right_margin - 2 * font_size
and text[start] in start_chars):
if x > width - right_margin - 2 * font_size and text[start] in start_chars:
break
if (x > width - right_margin - font_size
and text[start] not in end_chars):
if x > width - right_margin - font_size and text[start] not in end_chars:
break
if Feature.GRID_LAYOUT in tpl.get_features():
x = _grid_layout(draw, x, y, text[start], tpl, rand)
Expand All @@ -131,25 +129,19 @@ def _draw_page(
return start


def _flow_layout(
draw, x, y, char, tpl: Template, rand: random.Random
) -> float:
def _flow_layout(draw, x, y, char, tpl: Template, rand: random.Random) -> float:
xy = (round(x), round(gauss(rand, y, tpl.get_line_spacing_sigma())))
font = _get_font(tpl, rand)
offset = _draw_char(draw, char, xy, font)
x += gauss(
rand,
tpl.get_word_spacing() + offset,
tpl.get_word_spacing_sigma()
)
x += gauss(rand, tpl.get_word_spacing() + offset, tpl.get_word_spacing_sigma())
return x


def _grid_layout(
draw, x, y, char, tpl: Template, rand: random.Random
) -> float:
xy = (round(gauss(rand, x, tpl.get_word_spacing_sigma())),
round(gauss(rand, y, tpl.get_line_spacing_sigma())))
def _grid_layout(draw, x, y, char, tpl: Template, rand: random.Random) -> float:
xy = (
round(gauss(rand, x, tpl.get_word_spacing_sigma())),
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
Expand All @@ -158,9 +150,7 @@ def _grid_layout(

def _get_font(tpl: Template, rand: random.Random):
font = tpl.get_font()
actual_font_size = max(round(
gauss(rand, font.size, tpl.get_font_size_sigma())
), 0)
actual_font_size = max(round(gauss(rand, font.size, tpl.get_font_size_sigma())), 0)
if actual_font_size != font.size:
return font.font_variant(size=actual_font_size)
return font
Expand Down Expand Up @@ -230,10 +220,7 @@ def _extract_strokes(bitmap, bbox: Tuple[int, int, int, int]):
_MAX_INT16_VALUE - 1
)
raise BackgroundTooLargeError(msg)
strokes = NumericOrderedSet(
_UNSIGNED_INT32_TYPECODE,
privileged=_STROKE_END
)
strokes = NumericOrderedSet(_UNSIGNED_INT32_TYPECODE, privileged=_STROKE_END)
for y in range(upper, lower):
for x in range(left, right):
if bitmap[x, y] and strokes.add(_xy(x, y)):
Expand All @@ -243,12 +230,14 @@ def _extract_strokes(bitmap, bbox: Tuple[int, int, int, int]):


def _extract_stroke(
bitmap, start: Tuple[int, int], strokes, bbox: Tuple[int, int, int, int]
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."""
left, upper, right, lower = bbox
stack = [start, ]
stack = [
start,
]
while stack:
x, y = stack.pop()
if y - 1 >= upper and bitmap[x, y - 1] and strokes.add(_xy(x, y - 1)):
Expand Down Expand Up @@ -286,35 +275,68 @@ def _draw_strokes(bitmap, strokes, tpl, rand) -> None:


def _draw_stroke(
bitmap,
stroke: Sequence[Tuple[int, int]],
tpl: Template,
center: Tuple[float, float],
rand
bitmap,
stroke: Sequence[Tuple[int, int]],
tpl: Template,
center: Tuple[float, float],
rand,
) -> None:
dx = gauss(rand, 0, tpl.get_perturb_x_sigma())
dy = gauss(rand, 0, tpl.get_perturb_y_sigma())
theta = gauss(rand, 0, tpl.get_perturb_theta_sigma())

ink_depth_sigma = tpl.get_ink_depth_sigma()
original_fill = tpl.get_fill()
# 添加随机扰动
ink_depth_rand = gauss(rand, 0, ink_depth_sigma)
if isinstance(original_fill, int):
# 如果 original_fill 是一个整数
rand_fill = max(0, min(100, int(original_fill + ink_depth_rand)))
elif isinstance(original_fill, tuple):
if len(original_fill) == 3:
# 如果 original_fill 是一个三元组(假设是 RGB 值)
rand_fill = tuple(
max(0, min(255, int(channel + ink_depth_rand)))
for channel in original_fill
)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

啊啊

elif len(original_fill) == 4:
# 如果 original_fill 是一个四元组(假设是 RGBA 值)
# 保持 Alpha 通道不变
rand_fill = tuple(
(
max(0, min(255, int(channel + ink_depth_rand)))
if i < 3
else original_fill[3]
)
for i, channel in enumerate(original_fill)
)

# 打印结果以验证
# print('rand_fill', rand_fill)
for x, y in stroke:
new_x, new_y = _rotate(center, x, y, theta)
new_x = round(new_x + dx)
new_y = round(new_y + dy)
width, height = tpl.get_size()
if 0 <= new_x < width and 0 <= new_y < height:
bitmap[new_x, new_y] = tpl.get_fill()
bitmap[new_x, new_y] = rand_fill


def _rotate(
center: Tuple[float, float], x: float, y: float, theta: float
center: Tuple[float, float], x: float, y: float, theta: float
) -> Tuple[float, float]:
if theta == 0:
return x, y
new_x = ((x - center[0]) * math.cos(theta)
+ (y - center[1]) * math.sin(theta)
+ center[0])
new_y = ((y - center[1]) * math.cos(theta)
- (x - center[0]) * math.sin(theta)
+ center[1])
new_x = (
(x - center[0]) * math.cos(theta)
+ (y - center[1]) * math.sin(theta)
+ center[0]
)
new_y = (
(y - center[1]) * math.cos(theta)
- (x - center[0]) * math.sin(theta)
+ center[1]
)
return new_x, new_y


Expand Down
14 changes: 14 additions & 0 deletions handright/_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Template(object):
"_perturb_x_sigma",
"_perturb_y_sigma",
"_perturb_theta_sigma",
'_ink_depth_sigma',
"_features",
)

Expand Down Expand Up @@ -68,6 +69,7 @@ def __init__(
perturb_x_sigma: Optional[float] = None,
perturb_y_sigma: Optional[float] = None,
perturb_theta_sigma: float = _DEFAULT_PERTURB_THETA_SIGMA,
ink_depth_sigma: Optional[float]=None,
features: Set = _DEFAULT_FEATURES,
):
"""Note that, all the Integer parameters are in pixels.
Expand Down Expand Up @@ -117,6 +119,7 @@ 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_ink_depth_sigma(ink_depth_sigma)
self.set_features(features)

def __eq__(self, other) -> bool:
Expand Down Expand Up @@ -237,6 +240,14 @@ def set_perturb_theta_sigma(
) -> None:
self._perturb_theta_sigma = perturb_theta_sigma

def set_ink_depth_sigma(
self, ink_depth_sigma: Optional[float] = None
) -> None:
if ink_depth_sigma is None:
self._ink_depth_sigma = self._font.size / 1.5
else:
self._ink_depth_sigma = ink_depth_sigma

def get_background(self) -> PIL.Image.Image:
return self._background

Expand Down Expand Up @@ -291,6 +302,9 @@ def get_perturb_y_sigma(self) -> float:
def get_perturb_theta_sigma(self) -> float:
return self._perturb_theta_sigma

def get_ink_depth_sigma(self):
return self._ink_depth_sigma

def get_size(self) -> Tuple[int, int]:
return self.get_background().size

Expand Down
Binary file added tests/backgrounds/even-odd-letter/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions tests/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@

def main():
print("Test by naked eyes:")
_watch_gird_layout()
_like_it()
# _watch_gird_layout()
# _like_it()
_watch_flow_layout()
_like_it()
# _like_it()


def _watch_flow_layout():
path = "backgrounds/even-odd-letter/"
image1 = PIL.Image.open(abs_path(path + "村庄信笺纸.jpg"))
image1 = PIL.Image.open(abs_path(path + "icon.png"))
image2 = PIL.Image.open(abs_path(path + "树信笺纸.jpg"))
assert image1.mode == "RGB"
# assert image1.mode == "RGB"
assert image2.mode == "RGB"

template1 = Template(
Expand Down