-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcrop.py
More file actions
173 lines (149 loc) · 5.05 KB
/
Copy pathcrop.py
File metadata and controls
173 lines (149 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env python3
"""
图片裁剪
Usage::
$ python crop.py /path/to/image
NOTE: 执行时会
print("请输入要裁剪的左上角坐标")
print("请输入要裁剪的右下角坐标")
"""
from __future__ import annotations
import operator
import re
import sys
from datetime import datetime
from pathlib import Path
# pip install pillow humanize
from humanize.filesize import naturalsize # ty:ignore[unresolved-import]
from PIL import Image # ty:ignore[unresolved-import]
RE_FLOAT = re.compile(r"\d*\.?\d+")
RE_EXPRESS = re.compile(r"(\d*\.?\d+)([-+*/])(\d*\.?\d+)")
def slim_number(f: float, ndigits=3) -> int | float:
"""Use builtin function `round` to limit the digits length of float
Uasge::
>>> slim_number(3.0)
3
>>> slim_number(3.1234)
3.123
>>> slim_number(0.1234, 2)
0.12
"""
fi = int(f)
if f == fi:
return fi
return round(f, ndigits)
def to_value(s: str) -> int | float:
if "." in s:
return slim_number(float(s), 10)
return int(s)
def auto_calc(s: str) -> str:
"""简单计算:'1920/2, 1080/2' -> '960, 540'"""
if RE_EXPRESS.search(s):
ss = s.split()
should_calc = True
if len(ss) <= 1:
ss = s.split(",")
if len(ss) <= 1:
should_calc = False
if should_calc:
ss = ss[:2]
calc = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.truediv,
}
for i, v in enumerate(ss):
# 简单计算
m = RE_EXPRESS.search(v)
if m:
a, b, c = m.groups()
r = calc[b](to_value(a), to_value(c))
if isinstance(r, float):
r = slim_number(r)
ss[i] = str(r)
s = " ".join(ss)
return s
def ask_point(name: str, default: tuple[float, float]) -> tuple[float, float]:
tip = "Please enter {} point [Leave empty to use {}]: "
enter = input(tip.format(name, default)).strip().strip("()")
if not enter:
point = default
else:
nums = RE_FLOAT.findall(auto_calc(enter))
if len(nums) == 1:
if enter.startswith(","):
point = default[0], to_value(nums[0])
else:
point = to_value(nums[0]), default[1]
else:
point = to_value(nums[0]), to_value(nums[1])
return point
def calc_points(
width: int, height: int, target_width: int, target_height: int
) -> tuple[float, ...]:
left = (width - target_width) / 2
upper = (height - target_height) / 2
right = width - left
bottom = height - upper
return left, upper, right, bottom
def crop_pil(
p: Path, filename=None, target_size: tuple[int, int] | None = None
) -> None:
img = Image.open(p)
print("Image Shape:", img.size)
zero_point, end_point = (0, 0), img.size
if target_size is None:
print("请输入要裁剪的左上角坐标")
left, upper = ask_point("left-upper", zero_point)
print("请输入要裁剪的右下角坐标")
right, bottom = ask_point("right-bottom", end_point)
else:
left, upper, right, bottom = calc_points(
end_point[0], end_point[1], *target_size
)
if right == 0:
height = bottom - upper
if height <= 0:
raise ValueError(
f"Expected bottom to be bigger than upper({upper}). Got: {bottom}"
)
right = left + slim_number(height * img.size[0] / img.size[1])
elif bottom == 0:
width = right - left
if width <= 0:
raise ValueError(
f"Expected right to be bigger than left({left}). Got: {right}"
)
bottom = upper + slim_number(width * img.size[1] / img.size[0])
# (left, upper, right, bottom)
dest = (left, upper, right, bottom)
cropped = img.crop(dest)
if filename is None:
filename = "new" + p.suffix
if Path(filename).exists():
msg = "文件已存在({})! 是否直接覆盖它?[Y/n] "
if input(msg.format(filename)).strip().lower() in ("n", "no"):
return
cropped.save(filename)
print(f"Save to {filename} with {dest=}")
def main() -> None:
if not sys.argv[1:] or ({"-h", "--help"} & set(sys.argv)):
print(__doc__)
return
p = Path(sys.argv[1])
stat = p.stat()
print(f"File Size: {naturalsize(stat.st_size, False, True)}")
print(f"File Created At: {datetime.fromtimestamp(stat.st_ctime)}")
if stat.st_mtime - stat.st_ctime > 10:
print(f"File Updated At: {datetime.fromtimestamp(stat.st_mtime)}")
try:
# 按尺寸裁剪,如:python crop.py mypic.jpg 128*128
width, height = [int(i) for i in re.findall(r"\d+", sys.argv[2])]
except (TypeError, IndexError, ValueError):
target_size = None
else:
target_size = width, height
crop_pil(p, target_size=target_size)
if __name__ == "__main__":
main()