fotolab 0.21.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fotolab/__init__.py +67 -0
- fotolab/__main__.py +22 -0
- fotolab/animate.py +114 -0
- fotolab/auto.py +83 -0
- fotolab/border.py +139 -0
- fotolab/cli.py +180 -0
- fotolab/contrast.py +77 -0
- fotolab/env.py +52 -0
- fotolab/info.py +103 -0
- fotolab/montage.py +71 -0
- fotolab/resize.py +176 -0
- fotolab/rotate.py +61 -0
- fotolab/sharpen.py +98 -0
- fotolab/watermark.py +262 -0
- fotolab-0.21.1.dist-info/LICENSE.md +616 -0
- fotolab-0.21.1.dist-info/METADATA +399 -0
- fotolab-0.21.1.dist-info/RECORD +19 -0
- fotolab-0.21.1.dist-info/WHEEL +4 -0
- fotolab-0.21.1.dist-info/entry_points.txt +3 -0
fotolab/env.py
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Copyright (C) 2024 Kian-Meng Ang
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
6
|
+
# later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
11
|
+
# details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
"""Env subcommand."""
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
import logging
|
20
|
+
import platform
|
21
|
+
import sys
|
22
|
+
|
23
|
+
from fotolab import __version__
|
24
|
+
|
25
|
+
log = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
def build_subparser(subparsers) -> None:
|
29
|
+
"""Build the subparser."""
|
30
|
+
env_parser = subparsers.add_parser(
|
31
|
+
"env", help="print environment information for bug reporting"
|
32
|
+
)
|
33
|
+
|
34
|
+
env_parser.set_defaults(func=run)
|
35
|
+
|
36
|
+
|
37
|
+
def run(_args: argparse.Namespace) -> None:
|
38
|
+
"""Run env subcommand.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
config (argparse.Namespace): Config from command line arguments
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
None
|
45
|
+
"""
|
46
|
+
sys_version = sys.version.replace("\n", "")
|
47
|
+
print(
|
48
|
+
f"fotolab: {__version__}",
|
49
|
+
f"python: {sys_version}",
|
50
|
+
f"platform: {platform.platform()}",
|
51
|
+
sep="\n",
|
52
|
+
)
|
fotolab/info.py
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright (C) 2024 Kian-Meng Ang
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
6
|
+
# later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
11
|
+
# details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
"""Info subcommand."""
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
import logging
|
20
|
+
|
21
|
+
from PIL import ExifTags, Image
|
22
|
+
|
23
|
+
log = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
def build_subparser(subparsers) -> None:
|
27
|
+
"""Build the subparser."""
|
28
|
+
info_parser = subparsers.add_parser("info", help="info an image")
|
29
|
+
|
30
|
+
info_parser.set_defaults(func=run)
|
31
|
+
|
32
|
+
info_parser.add_argument(
|
33
|
+
dest="image_filename",
|
34
|
+
help="set the image filename",
|
35
|
+
type=str,
|
36
|
+
default=None,
|
37
|
+
metavar="IMAGE_FILENAME",
|
38
|
+
)
|
39
|
+
|
40
|
+
info_parser.add_argument(
|
41
|
+
"-s",
|
42
|
+
"--sort",
|
43
|
+
default=False,
|
44
|
+
action="store_true",
|
45
|
+
dest="sort",
|
46
|
+
help="show image info by sorted field name",
|
47
|
+
)
|
48
|
+
|
49
|
+
info_parser.add_argument(
|
50
|
+
"--camera",
|
51
|
+
default=False,
|
52
|
+
action="store_true",
|
53
|
+
dest="camera",
|
54
|
+
help="show the camera maker details",
|
55
|
+
)
|
56
|
+
|
57
|
+
|
58
|
+
def run(args: argparse.Namespace) -> None:
|
59
|
+
"""Run info subcommand.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
config (argparse.Namespace): Config from command line arguments
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
None
|
66
|
+
"""
|
67
|
+
log.debug(args)
|
68
|
+
if args.camera:
|
69
|
+
print(camera_metadata(args.image_filename))
|
70
|
+
else:
|
71
|
+
exif_tags = extract_exif_tags(args.image_filename)
|
72
|
+
if exif_tags:
|
73
|
+
tag_name_width = max(map(len, exif_tags))
|
74
|
+
for tag_name, tag_value in exif_tags.items():
|
75
|
+
print(f"{tag_name:<{tag_name_width}}: {tag_value}")
|
76
|
+
else:
|
77
|
+
print("No metadata found!")
|
78
|
+
|
79
|
+
|
80
|
+
def extract_exif_tags(image_filename: str, sort: bool = False) -> dict:
|
81
|
+
"""Extract Exif metadata from image."""
|
82
|
+
image = Image.open(image_filename)
|
83
|
+
exif = image._getexif()
|
84
|
+
log.debug(exif)
|
85
|
+
|
86
|
+
info = {}
|
87
|
+
if exif:
|
88
|
+
info = {ExifTags.TAGS.get(tag_id): exif.get(tag_id) for tag_id in exif}
|
89
|
+
|
90
|
+
filtered_info = {
|
91
|
+
key: value for key, value in info.items() if key is not None
|
92
|
+
}
|
93
|
+
if sort:
|
94
|
+
filtered_info = dict(sorted(filtered_info.items()))
|
95
|
+
|
96
|
+
return filtered_info
|
97
|
+
|
98
|
+
|
99
|
+
def camera_metadata(image_filename):
|
100
|
+
"""Extract camera and model metadata."""
|
101
|
+
exif_tags = extract_exif_tags(image_filename)
|
102
|
+
metadata = f'{exif_tags["Make"]} {exif_tags["Model"]}'
|
103
|
+
return metadata.strip()
|
fotolab/montage.py
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Copyright (C) 2024 Kian-Meng Ang
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
6
|
+
# later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
11
|
+
# details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
"""Montage subcommand."""
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
import logging
|
20
|
+
|
21
|
+
from PIL import Image
|
22
|
+
|
23
|
+
from fotolab import save_image
|
24
|
+
|
25
|
+
log = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
def build_subparser(subparsers) -> None:
|
29
|
+
"""Build the subparser."""
|
30
|
+
montage_parser = subparsers.add_parser(
|
31
|
+
"montage", help="montage a list of image"
|
32
|
+
)
|
33
|
+
|
34
|
+
montage_parser.set_defaults(func=run)
|
35
|
+
|
36
|
+
montage_parser.add_argument(
|
37
|
+
dest="image_filenames",
|
38
|
+
help="set the image filenames",
|
39
|
+
nargs="+",
|
40
|
+
type=argparse.FileType("r"),
|
41
|
+
default=None,
|
42
|
+
metavar="IMAGE_FILENAMES",
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
def run(args: argparse.Namespace) -> None:
|
47
|
+
"""Run montage subcommand.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
config (argparse.Namespace): Config from command line arguments
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
None
|
54
|
+
"""
|
55
|
+
log.debug(args)
|
56
|
+
images = []
|
57
|
+
for image_filename in args.image_filenames:
|
58
|
+
images.append(Image.open(image_filename.name))
|
59
|
+
|
60
|
+
total_width = sum(img.width for img in images)
|
61
|
+
total_height = max(img.height for img in images)
|
62
|
+
|
63
|
+
montaged_image = Image.new("RGB", (total_width, total_height))
|
64
|
+
|
65
|
+
x_offset = 0
|
66
|
+
for img in images:
|
67
|
+
montaged_image.paste(img, (x_offset, 0))
|
68
|
+
x_offset += img.width
|
69
|
+
|
70
|
+
output_image_filename = args.image_filenames[0].name
|
71
|
+
save_image(args, montaged_image, output_image_filename, "montage")
|
fotolab/resize.py
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Copyright (C) 2024 Kian-Meng Ang
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
6
|
+
# later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
11
|
+
# details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
"""Resize subcommand."""
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
import logging
|
20
|
+
import math
|
21
|
+
import sys
|
22
|
+
|
23
|
+
from PIL import Image, ImageColor
|
24
|
+
|
25
|
+
from fotolab import save_image
|
26
|
+
|
27
|
+
log = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
DEFAULT_WIDTH = 600
|
30
|
+
DEFAULT_HEIGHT = 277
|
31
|
+
|
32
|
+
|
33
|
+
def build_subparser(subparsers) -> None:
|
34
|
+
"""Build the subparser."""
|
35
|
+
resize_parser = subparsers.add_parser("resize", help="resize an image")
|
36
|
+
|
37
|
+
resize_parser.set_defaults(func=run)
|
38
|
+
|
39
|
+
resize_parser.add_argument(
|
40
|
+
dest="image_filenames",
|
41
|
+
help="set the image filename",
|
42
|
+
nargs="+",
|
43
|
+
type=str,
|
44
|
+
default=None,
|
45
|
+
metavar="IMAGE_FILENAMES",
|
46
|
+
)
|
47
|
+
|
48
|
+
resize_parser.add_argument(
|
49
|
+
"-c",
|
50
|
+
"--canvas",
|
51
|
+
default=False,
|
52
|
+
action="store_true",
|
53
|
+
dest="canvas",
|
54
|
+
help="paste image onto a larger canvas",
|
55
|
+
)
|
56
|
+
|
57
|
+
resize_parser.add_argument(
|
58
|
+
"-l",
|
59
|
+
"--canvas-color",
|
60
|
+
default="black",
|
61
|
+
dest="canvas_color",
|
62
|
+
help=(
|
63
|
+
"the color of the extended larger canvas"
|
64
|
+
"(default: '%(default)s')"
|
65
|
+
),
|
66
|
+
)
|
67
|
+
|
68
|
+
if "-c" in sys.argv or "--canvas" in sys.argv:
|
69
|
+
resize_parser.add_argument(
|
70
|
+
"-W",
|
71
|
+
"--width",
|
72
|
+
dest="width",
|
73
|
+
help="set the width of the image (default: '%(default)s')",
|
74
|
+
type=int,
|
75
|
+
required=True,
|
76
|
+
metavar="WIDTH",
|
77
|
+
)
|
78
|
+
|
79
|
+
resize_parser.add_argument(
|
80
|
+
"-H",
|
81
|
+
"--height",
|
82
|
+
dest="height",
|
83
|
+
help="set the height of the image (default: '%(default)s')",
|
84
|
+
type=int,
|
85
|
+
required=True,
|
86
|
+
metavar="HEIGHT",
|
87
|
+
)
|
88
|
+
else:
|
89
|
+
group = resize_parser.add_mutually_exclusive_group(required=False)
|
90
|
+
|
91
|
+
group.add_argument(
|
92
|
+
"-W",
|
93
|
+
"--width",
|
94
|
+
dest="width",
|
95
|
+
help="set the width of the image (default: '%(default)s')",
|
96
|
+
type=int,
|
97
|
+
default=DEFAULT_WIDTH,
|
98
|
+
metavar="WIDTH",
|
99
|
+
)
|
100
|
+
|
101
|
+
group.add_argument(
|
102
|
+
"-H",
|
103
|
+
"--height",
|
104
|
+
dest="height",
|
105
|
+
help="set the height of the image (default: '%(default)s')",
|
106
|
+
type=int,
|
107
|
+
default=DEFAULT_HEIGHT,
|
108
|
+
metavar="HEIGHT",
|
109
|
+
)
|
110
|
+
|
111
|
+
|
112
|
+
def run(args: argparse.Namespace) -> None:
|
113
|
+
"""Run resize subcommand.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
args (argparse.Namespace): Config from command line arguments
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
None
|
120
|
+
"""
|
121
|
+
log.debug(args)
|
122
|
+
|
123
|
+
for image_filename in args.image_filenames:
|
124
|
+
original_image = Image.open(image_filename)
|
125
|
+
if args.canvas:
|
126
|
+
resized_image = _resize_image_onto_canvas(original_image, args)
|
127
|
+
else:
|
128
|
+
resized_image = _resize_image(original_image, args)
|
129
|
+
save_image(args, resized_image, image_filename, "resize")
|
130
|
+
|
131
|
+
|
132
|
+
def _resize_image_onto_canvas(original_image, args):
|
133
|
+
resized_image = Image.new(
|
134
|
+
"RGB",
|
135
|
+
(args.width, args.height),
|
136
|
+
(*ImageColor.getrgb(args.canvas_color), 128),
|
137
|
+
)
|
138
|
+
x_offset = (args.width - original_image.width) // 2
|
139
|
+
y_offset = (args.height - original_image.height) // 2
|
140
|
+
resized_image.paste(original_image, (x_offset, y_offset))
|
141
|
+
return resized_image
|
142
|
+
|
143
|
+
|
144
|
+
def _resize_image(original_image, args):
|
145
|
+
new_width, new_height = _calc_new_image_dimension(original_image, args)
|
146
|
+
resized_image = original_image.copy()
|
147
|
+
resized_image = resized_image.resize(
|
148
|
+
(new_width, new_height), Image.Resampling.LANCZOS
|
149
|
+
)
|
150
|
+
return resized_image
|
151
|
+
|
152
|
+
|
153
|
+
def _calc_new_image_dimension(image, args) -> tuple:
|
154
|
+
new_width = args.width
|
155
|
+
new_height = args.height
|
156
|
+
|
157
|
+
old_width, old_height = image.size
|
158
|
+
log.debug("old image dimension: %d x %d", old_width, old_height)
|
159
|
+
|
160
|
+
if args.width != DEFAULT_WIDTH:
|
161
|
+
aspect_ratio = old_height / old_width
|
162
|
+
log.debug("aspect ratio: %f", aspect_ratio)
|
163
|
+
|
164
|
+
new_height = math.ceil(args.width * aspect_ratio)
|
165
|
+
log.debug("new height: %d", new_height)
|
166
|
+
|
167
|
+
if args.height != DEFAULT_HEIGHT:
|
168
|
+
aspect_ratio = old_width / old_height
|
169
|
+
log.debug("aspect ratio: %f", aspect_ratio)
|
170
|
+
|
171
|
+
new_width = math.floor(args.height * aspect_ratio)
|
172
|
+
log.debug("new width: %d", new_width)
|
173
|
+
|
174
|
+
log.debug("new image dimension: %d x %d", new_width, new_height)
|
175
|
+
|
176
|
+
return (new_width, new_height)
|
fotolab/rotate.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Copyright (C) 2024 Kian-Meng Ang
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
6
|
+
# later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
11
|
+
# details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
"""Rotate subcommand."""
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
import logging
|
20
|
+
|
21
|
+
from PIL import Image
|
22
|
+
|
23
|
+
from fotolab import save_image
|
24
|
+
|
25
|
+
log = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
def build_subparser(subparsers) -> None:
|
29
|
+
"""Build the subparser."""
|
30
|
+
rotate_parser = subparsers.add_parser("rotate", help="rotate an image")
|
31
|
+
|
32
|
+
rotate_parser.set_defaults(func=run)
|
33
|
+
|
34
|
+
rotate_parser.add_argument(
|
35
|
+
dest="image_filenames",
|
36
|
+
help="set the image filenames",
|
37
|
+
nargs="+",
|
38
|
+
type=str,
|
39
|
+
default=None,
|
40
|
+
metavar="IMAGE_FILENAMES",
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
def run(args: argparse.Namespace) -> None:
|
45
|
+
"""Run rotate subcommand.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
config (argparse.Namespace): Config from command line arguments
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
None
|
52
|
+
"""
|
53
|
+
log.debug(args)
|
54
|
+
|
55
|
+
for image_filename in args.image_filenames:
|
56
|
+
original_image = Image.open(image_filename)
|
57
|
+
rotated_image = original_image.rotate(
|
58
|
+
180,
|
59
|
+
expand=True,
|
60
|
+
)
|
61
|
+
save_image(args, rotated_image, image_filename, "rotate")
|
fotolab/sharpen.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Copyright (C) 2024 Kian-Meng Ang
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
5
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
6
|
+
# later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
11
|
+
# details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
"""Sharpen subcommand."""
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
import logging
|
20
|
+
|
21
|
+
from PIL import Image, ImageFilter
|
22
|
+
|
23
|
+
from fotolab import save_image
|
24
|
+
|
25
|
+
log = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
def build_subparser(subparsers) -> None:
|
29
|
+
"""Build the subparser."""
|
30
|
+
sharpen_parser = subparsers.add_parser("sharpen", help="sharpen an image")
|
31
|
+
|
32
|
+
sharpen_parser.set_defaults(func=run)
|
33
|
+
|
34
|
+
sharpen_parser.add_argument(
|
35
|
+
dest="image_filenames",
|
36
|
+
help="set the image filenames",
|
37
|
+
nargs="+",
|
38
|
+
type=str,
|
39
|
+
default=None,
|
40
|
+
metavar="IMAGE_FILENAMES",
|
41
|
+
)
|
42
|
+
|
43
|
+
sharpen_parser.add_argument(
|
44
|
+
"-r",
|
45
|
+
"--radius",
|
46
|
+
dest="radius",
|
47
|
+
type=int,
|
48
|
+
default=1,
|
49
|
+
help="set the radius or size of edges (default: '%(default)s')",
|
50
|
+
metavar="RADIUS",
|
51
|
+
)
|
52
|
+
|
53
|
+
sharpen_parser.add_argument(
|
54
|
+
"-p",
|
55
|
+
"--percent",
|
56
|
+
dest="percent",
|
57
|
+
type=int,
|
58
|
+
default=100,
|
59
|
+
help=(
|
60
|
+
"set the amount of overall strength of sharpening effect "
|
61
|
+
"(default: '%(default)s')"
|
62
|
+
),
|
63
|
+
metavar="PERCENT",
|
64
|
+
)
|
65
|
+
|
66
|
+
sharpen_parser.add_argument(
|
67
|
+
"-t",
|
68
|
+
"--threshold",
|
69
|
+
dest="threshold",
|
70
|
+
type=int,
|
71
|
+
default=3,
|
72
|
+
help=(
|
73
|
+
"set the minimum brightness changed to be sharpened "
|
74
|
+
"(default: '%(default)s')"
|
75
|
+
),
|
76
|
+
metavar="THRESHOLD",
|
77
|
+
)
|
78
|
+
|
79
|
+
|
80
|
+
def run(args: argparse.Namespace) -> None:
|
81
|
+
"""Run sharpen subcommand.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
config (argparse.Namespace): Config from command line arguments
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
None
|
88
|
+
"""
|
89
|
+
log.debug(args)
|
90
|
+
|
91
|
+
for image_filename in args.image_filenames:
|
92
|
+
original_image = Image.open(image_filename)
|
93
|
+
sharpen_image = original_image.filter(
|
94
|
+
ImageFilter.UnsharpMask(
|
95
|
+
args.radius, percent=args.percent, threshold=args.threshold
|
96
|
+
)
|
97
|
+
)
|
98
|
+
save_image(args, sharpen_image, image_filename, "sharpen")
|