fotolab 0.34.0__tar.gz → 0.34.2__tar.gz

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.
Files changed (42) hide show
  1. {fotolab-0.34.0/src/fotolab.egg-info → fotolab-0.34.2}/PKG-INFO +29 -22
  2. {fotolab-0.34.0 → fotolab-0.34.2}/README.md +28 -21
  3. {fotolab-0.34.0 → fotolab-0.34.2}/pyproject.toml +1 -1
  4. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/__init__.py +12 -0
  5. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/animate.py +6 -7
  6. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/auto.py +3 -3
  7. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/border.py +13 -13
  8. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/contrast.py +27 -27
  9. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/halftone.py +34 -31
  10. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/montage.py +19 -17
  11. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/resize.py +64 -65
  12. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/rotate.py +13 -13
  13. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/sharpen.py +22 -20
  14. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/watermark.py +19 -17
  15. {fotolab-0.34.0 → fotolab-0.34.2/src/fotolab.egg-info}/PKG-INFO +29 -22
  16. fotolab-0.34.2/tests/test_resize_subcommand.py +63 -0
  17. fotolab-0.34.0/tests/test_resize_subcommand.py +0 -20
  18. {fotolab-0.34.0 → fotolab-0.34.2}/LICENSE.md +0 -0
  19. {fotolab-0.34.0 → fotolab-0.34.2}/setup.cfg +0 -0
  20. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/__main__.py +0 -0
  21. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/cli.py +0 -0
  22. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/__init__.py +0 -0
  23. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/env.py +0 -0
  24. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab/subcommands/info.py +0 -0
  25. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab.egg-info/SOURCES.txt +0 -0
  26. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab.egg-info/dependency_links.txt +0 -0
  27. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab.egg-info/entry_points.txt +0 -0
  28. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab.egg-info/requires.txt +0 -0
  29. {fotolab-0.34.0 → fotolab-0.34.2}/src/fotolab.egg-info/top_level.txt +0 -0
  30. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_animate_subcommand.py +0 -0
  31. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_auto_subcommand.py +0 -0
  32. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_border_subcommand.py +0 -0
  33. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_contrast_subcommand.py +0 -0
  34. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_env_subcommand.py +0 -0
  35. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_halftone_subcommand.py +0 -0
  36. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_help_flag.py +0 -0
  37. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_info_subcommand.py +0 -0
  38. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_montage_subcommand.py +0 -0
  39. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_quiet_flag.py +0 -0
  40. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_rotate_subcommand.py +0 -0
  41. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_sharpen_subcommand.py +0 -0
  42. {fotolab-0.34.0 → fotolab-0.34.2}/tests/test_watermark_subcommand.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fotolab
3
- Version: 0.34.0
3
+ Version: 0.34.2
4
4
  Summary: A console program to manipulate photos.
5
5
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -112,10 +112,11 @@ fotolab animate -h
112
112
  usage: fotolab animate [-h] [-f FORMAT] [-d DURATION] [-l LOOP] [-op]
113
113
  [--webp-quality QUALITY] [--webp-lossless]
114
114
  [--webp-method METHOD] [-od OUTPUT_DIR]
115
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
115
+ [-of OUTPUT_FILENAME]
116
+ IMAGE_PATHS [IMAGE_PATHS ...]
116
117
 
117
118
  positional arguments:
118
- IMAGE_FILENAMES set the image filenames
119
+ IMAGE_PATHS set the image filenames
119
120
 
120
121
  options:
121
122
  -h, --help show this help message and exit
@@ -133,6 +134,8 @@ options:
133
134
  default: '4')
134
135
  -od, --output-dir OUTPUT_DIR
135
136
  set default output folder (default: 'output')
137
+ -of, --output-filename OUTPUT_FILENAME
138
+ set output filename (default: 'None')
136
139
  ```
137
140
 
138
141
  <!--help-animate !-->
@@ -146,13 +149,17 @@ fotolab auto -h
146
149
  <!--help-auto !-->
147
150
 
148
151
  ```console
149
- usage: fotolab auto [-h] IMAGE_FILENAMES [IMAGE_FILENAMES ...]
152
+ usage: fotolab auto [-h] [-t TITLE] [-w WATERMARK_TEXT]
153
+ IMAGE_PATHS [IMAGE_PATHS ...]
150
154
 
151
155
  positional arguments:
152
- IMAGE_FILENAMES set the image filename
156
+ IMAGE_PATHS set the image filename
153
157
 
154
158
  options:
155
- -h, --help show this help message and exit
159
+ -h, --help show this help message and exit
160
+ -t, --title TITLE set the tile (default: 'None')
161
+ -w, --watermark WATERMARK_TEXT
162
+ set the watermark (default: 'kianmeng.org')
156
163
  ```
157
164
 
158
165
  <!--help-auto !-->
@@ -168,10 +175,10 @@ fotolab border -h
168
175
  ```console
169
176
  usage: fotolab border [-h] [-c COLOR] [-w WIDTH] [-wt WIDTH] [-wr WIDTH]
170
177
  [-wb WIDTH] [-wl WIDTH] [-op] [-od OUTPUT_DIR]
171
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
178
+ IMAGE_PATHS [IMAGE_PATHS ...]
172
179
 
173
180
  positional arguments:
174
- IMAGE_FILENAMES set the image filenames
181
+ IMAGE_PATHS set the image filenames
175
182
 
176
183
  options:
177
184
  -h, --help show this help message and exit
@@ -204,10 +211,10 @@ fotolab contrast -h
204
211
 
205
212
  ```console
206
213
  usage: fotolab contrast [-h] [-c CUTOFF] [-op] [-od OUTPUT_DIR]
207
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
214
+ IMAGE_PATHS [IMAGE_PATHS ...]
208
215
 
209
216
  positional arguments:
210
- IMAGE_FILENAMES set the image filename
217
+ IMAGE_PATHS set the image filename
211
218
 
212
219
  options:
213
220
  -h, --help show this help message and exit
@@ -231,10 +238,10 @@ fotolab halftone -h
231
238
 
232
239
  ```console
233
240
  usage: fotolab halftone [-h] [-ba] [-op] [-od OUTPUT_DIR] [-c CELLS] [-g]
234
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
241
+ IMAGE_PATHS [IMAGE_PATHS ...]
235
242
 
236
243
  positional arguments:
237
- IMAGE_FILENAMES set the image filename
244
+ IMAGE_PATHS set the image filename
238
245
 
239
246
  options:
240
247
  -h, --help show this help message and exit
@@ -283,10 +290,10 @@ fotolab rotate -h
283
290
 
284
291
  ```console
285
292
  usage: fotolab rotate [-h] [-r ROTATION] [-cw] [-op] [-od OUTPUT_DIR]
286
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
293
+ IMAGE_PATHS [IMAGE_PATHS ...]
287
294
 
288
295
  positional arguments:
289
- IMAGE_FILENAMES set the image filenames
296
+ IMAGE_PATHS set the image filenames
290
297
 
291
298
  options:
292
299
  -h, --help show this help message and exit
@@ -311,10 +318,10 @@ fotolab montage -h
311
318
 
312
319
  ```console
313
320
  usage: fotolab montage [-h] [-op] [-od OUTPUT_DIR]
314
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
321
+ IMAGE_PATHS [IMAGE_PATHS ...]
315
322
 
316
323
  positional arguments:
317
- IMAGE_FILENAMES set the image filenames
324
+ IMAGE_PATHS set the image filenames
318
325
 
319
326
  options:
320
327
  -h, --help show this help message and exit
@@ -337,10 +344,10 @@ fotolab resize -h
337
344
  ```console
338
345
  usage: fotolab resize [-h] [-c] [-l CANVAS_COLOR] [-W WIDTH | -H HEIGHT] [-op]
339
346
  [-od OUTPUT_DIR]
340
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
347
+ IMAGE_PATHS [IMAGE_PATHS ...]
341
348
 
342
349
  positional arguments:
343
- IMAGE_FILENAMES set the image filename
350
+ IMAGE_PATHS set the image filename
344
351
 
345
352
  options:
346
353
  -h, --help show this help message and exit
@@ -369,10 +376,10 @@ fotolab sharpen -h
369
376
  ```console
370
377
  usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD] [-ba]
371
378
  [-op] [-od OUTPUT_DIR]
372
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
379
+ IMAGE_PATHS [IMAGE_PATHS ...]
373
380
 
374
381
  positional arguments:
375
- IMAGE_FILENAMES set the image filenames
382
+ IMAGE_PATHS set the image filenames
376
383
 
377
384
  options:
378
385
  -h, --help show this help message and exit
@@ -408,10 +415,10 @@ usage: fotolab watermark [-h] [-t WATERMARK_TEXT]
408
415
  [-a ALPHA_VALUE] [--camera]
409
416
  [-l | --lowercase | --no-lowercase] [-op]
410
417
  [-od OUTPUT_DIR]
411
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
418
+ IMAGE_PATHS [IMAGE_PATHS ...]
412
419
 
413
420
  positional arguments:
414
- IMAGE_FILENAMES set the image filenames
421
+ IMAGE_PATHS set the image filenames
415
422
 
416
423
  options:
417
424
  -h, --help show this help message and exit
@@ -73,10 +73,11 @@ fotolab animate -h
73
73
  usage: fotolab animate [-h] [-f FORMAT] [-d DURATION] [-l LOOP] [-op]
74
74
  [--webp-quality QUALITY] [--webp-lossless]
75
75
  [--webp-method METHOD] [-od OUTPUT_DIR]
76
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
76
+ [-of OUTPUT_FILENAME]
77
+ IMAGE_PATHS [IMAGE_PATHS ...]
77
78
 
78
79
  positional arguments:
79
- IMAGE_FILENAMES set the image filenames
80
+ IMAGE_PATHS set the image filenames
80
81
 
81
82
  options:
82
83
  -h, --help show this help message and exit
@@ -94,6 +95,8 @@ options:
94
95
  default: '4')
95
96
  -od, --output-dir OUTPUT_DIR
96
97
  set default output folder (default: 'output')
98
+ -of, --output-filename OUTPUT_FILENAME
99
+ set output filename (default: 'None')
97
100
  ```
98
101
 
99
102
  <!--help-animate !-->
@@ -107,13 +110,17 @@ fotolab auto -h
107
110
  <!--help-auto !-->
108
111
 
109
112
  ```console
110
- usage: fotolab auto [-h] IMAGE_FILENAMES [IMAGE_FILENAMES ...]
113
+ usage: fotolab auto [-h] [-t TITLE] [-w WATERMARK_TEXT]
114
+ IMAGE_PATHS [IMAGE_PATHS ...]
111
115
 
112
116
  positional arguments:
113
- IMAGE_FILENAMES set the image filename
117
+ IMAGE_PATHS set the image filename
114
118
 
115
119
  options:
116
- -h, --help show this help message and exit
120
+ -h, --help show this help message and exit
121
+ -t, --title TITLE set the tile (default: 'None')
122
+ -w, --watermark WATERMARK_TEXT
123
+ set the watermark (default: 'kianmeng.org')
117
124
  ```
118
125
 
119
126
  <!--help-auto !-->
@@ -129,10 +136,10 @@ fotolab border -h
129
136
  ```console
130
137
  usage: fotolab border [-h] [-c COLOR] [-w WIDTH] [-wt WIDTH] [-wr WIDTH]
131
138
  [-wb WIDTH] [-wl WIDTH] [-op] [-od OUTPUT_DIR]
132
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
139
+ IMAGE_PATHS [IMAGE_PATHS ...]
133
140
 
134
141
  positional arguments:
135
- IMAGE_FILENAMES set the image filenames
142
+ IMAGE_PATHS set the image filenames
136
143
 
137
144
  options:
138
145
  -h, --help show this help message and exit
@@ -165,10 +172,10 @@ fotolab contrast -h
165
172
 
166
173
  ```console
167
174
  usage: fotolab contrast [-h] [-c CUTOFF] [-op] [-od OUTPUT_DIR]
168
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
175
+ IMAGE_PATHS [IMAGE_PATHS ...]
169
176
 
170
177
  positional arguments:
171
- IMAGE_FILENAMES set the image filename
178
+ IMAGE_PATHS set the image filename
172
179
 
173
180
  options:
174
181
  -h, --help show this help message and exit
@@ -192,10 +199,10 @@ fotolab halftone -h
192
199
 
193
200
  ```console
194
201
  usage: fotolab halftone [-h] [-ba] [-op] [-od OUTPUT_DIR] [-c CELLS] [-g]
195
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
202
+ IMAGE_PATHS [IMAGE_PATHS ...]
196
203
 
197
204
  positional arguments:
198
- IMAGE_FILENAMES set the image filename
205
+ IMAGE_PATHS set the image filename
199
206
 
200
207
  options:
201
208
  -h, --help show this help message and exit
@@ -244,10 +251,10 @@ fotolab rotate -h
244
251
 
245
252
  ```console
246
253
  usage: fotolab rotate [-h] [-r ROTATION] [-cw] [-op] [-od OUTPUT_DIR]
247
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
254
+ IMAGE_PATHS [IMAGE_PATHS ...]
248
255
 
249
256
  positional arguments:
250
- IMAGE_FILENAMES set the image filenames
257
+ IMAGE_PATHS set the image filenames
251
258
 
252
259
  options:
253
260
  -h, --help show this help message and exit
@@ -272,10 +279,10 @@ fotolab montage -h
272
279
 
273
280
  ```console
274
281
  usage: fotolab montage [-h] [-op] [-od OUTPUT_DIR]
275
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
282
+ IMAGE_PATHS [IMAGE_PATHS ...]
276
283
 
277
284
  positional arguments:
278
- IMAGE_FILENAMES set the image filenames
285
+ IMAGE_PATHS set the image filenames
279
286
 
280
287
  options:
281
288
  -h, --help show this help message and exit
@@ -298,10 +305,10 @@ fotolab resize -h
298
305
  ```console
299
306
  usage: fotolab resize [-h] [-c] [-l CANVAS_COLOR] [-W WIDTH | -H HEIGHT] [-op]
300
307
  [-od OUTPUT_DIR]
301
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
308
+ IMAGE_PATHS [IMAGE_PATHS ...]
302
309
 
303
310
  positional arguments:
304
- IMAGE_FILENAMES set the image filename
311
+ IMAGE_PATHS set the image filename
305
312
 
306
313
  options:
307
314
  -h, --help show this help message and exit
@@ -330,10 +337,10 @@ fotolab sharpen -h
330
337
  ```console
331
338
  usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD] [-ba]
332
339
  [-op] [-od OUTPUT_DIR]
333
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
340
+ IMAGE_PATHS [IMAGE_PATHS ...]
334
341
 
335
342
  positional arguments:
336
- IMAGE_FILENAMES set the image filenames
343
+ IMAGE_PATHS set the image filenames
337
344
 
338
345
  options:
339
346
  -h, --help show this help message and exit
@@ -369,10 +376,10 @@ usage: fotolab watermark [-h] [-t WATERMARK_TEXT]
369
376
  [-a ALPHA_VALUE] [--camera]
370
377
  [-l | --lowercase | --no-lowercase] [-op]
371
378
  [-od OUTPUT_DIR]
372
- IMAGE_FILENAMES [IMAGE_FILENAMES ...]
379
+ IMAGE_PATHS [IMAGE_PATHS ...]
373
380
 
374
381
  positional arguments:
375
- IMAGE_FILENAMES set the image filenames
382
+ IMAGE_PATHS set the image filenames
376
383
 
377
384
  options:
378
385
  -h, --help show this help message and exit
@@ -7,7 +7,7 @@ where = ["src"]
7
7
 
8
8
  [project]
9
9
  name = "fotolab"
10
- version = "0.34.0"
10
+ version = "0.34.2"
11
11
  description = "A console program to manipulate photos."
12
12
  authors = [{name = "Kian-Meng Ang", email = "kianmeng@cpan.org"}]
13
13
  requires-python = ">=3.10"
@@ -22,6 +22,8 @@ import logging
22
22
  import os
23
23
  import subprocess
24
24
  import sys
25
+ from contextlib import contextmanager
26
+ from typing import Iterator
25
27
 
26
28
  from PIL import Image
27
29
 
@@ -98,3 +100,13 @@ def open_image(filename: Path):
98
100
 
99
101
  except (OSError, FileNotFoundError) as error:
100
102
  log.error("Error opening image: %s -> %s", filename, error)
103
+
104
+
105
+ @contextmanager
106
+ def load_image(filename: Path) -> Iterator[Image.Image]:
107
+ """Load image using a context manager to ensure file handle is closed."""
108
+ try:
109
+ image = Image.open(filename)
110
+ yield image
111
+ finally:
112
+ image.close()
@@ -20,9 +20,8 @@ import logging
20
20
  from pathlib import Path
21
21
  from contextlib import ExitStack
22
22
 
23
- from PIL import Image
24
23
 
25
- from fotolab import open_image
24
+ from fotolab import load_image, open_image
26
25
 
27
26
  log = logging.getLogger(__name__)
28
27
 
@@ -49,12 +48,12 @@ def build_subparser(subparsers) -> None:
49
48
  animate_parser.set_defaults(func=run)
50
49
 
51
50
  animate_parser.add_argument(
52
- dest="image_filenames",
51
+ dest="image_paths",
53
52
  help="set the image filenames",
54
53
  nargs="+",
55
54
  type=str,
56
55
  default=None,
57
- metavar="IMAGE_FILENAMES",
56
+ metavar="IMAGE_PATHS",
58
57
  )
59
58
 
60
59
  animate_parser.add_argument(
@@ -159,15 +158,15 @@ def run(args: argparse.Namespace) -> None:
159
158
  """
160
159
  log.debug(args)
161
160
 
162
- image_filepaths = [Path(f) for f in args.image_filenames]
161
+ image_filepaths = [Path(f) for f in args.image_paths]
163
162
  first_image_filepath = image_filepaths[0]
164
163
  other_frames = []
165
164
 
166
165
  with ExitStack() as stack:
167
- main_frame = stack.enter_context(Image.open(first_image_filepath))
166
+ main_frame = stack.enter_context(load_image(first_image_filepath))
168
167
 
169
168
  for image_filepath in image_filepaths[1:]:
170
- img = stack.enter_context(Image.open(image_filepath))
169
+ img = stack.enter_context(load_image(image_filepath))
171
170
  other_frames.append(img)
172
171
 
173
172
  if args.output_filename:
@@ -36,12 +36,12 @@ def build_subparser(subparsers) -> None:
36
36
  auto_parser.set_defaults(func=run)
37
37
 
38
38
  auto_parser.add_argument(
39
- dest="image_filenames",
39
+ dest="image_paths",
40
40
  help="set the image filename",
41
41
  nargs="+",
42
42
  type=str,
43
43
  default=None,
44
- metavar="IMAGE_FILENAMES",
44
+ metavar="IMAGE_PATHS",
45
45
  )
46
46
 
47
47
  auto_parser.add_argument(
@@ -114,7 +114,7 @@ def run(args: argparse.Namespace) -> None:
114
114
  fotolab.subcommands.sharpen.run(combined_args)
115
115
  fotolab.subcommands.watermark.run(combined_args)
116
116
 
117
- if len(args.image_filenames) > 1:
117
+ if len(args.image_paths) > 1:
118
118
  output_filename = (
119
119
  args.title.lower().replace(",", "").replace(" ", "_") + ".gif"
120
120
  )
@@ -20,9 +20,9 @@ import logging
20
20
  from pathlib import Path
21
21
  from typing import Tuple
22
22
 
23
- from PIL import Image, ImageColor, ImageOps
23
+ from PIL import ImageColor, ImageOps
24
24
 
25
- from fotolab import save_image
25
+ from fotolab import load_image, save_image
26
26
 
27
27
  log = logging.getLogger(__name__)
28
28
 
@@ -34,12 +34,12 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
34
34
  border_parser.set_defaults(func=run)
35
35
 
36
36
  border_parser.add_argument(
37
- dest="image_filenames",
37
+ dest="image_paths",
38
38
  help="set the image filenames",
39
39
  nargs="+",
40
40
  type=str,
41
41
  default=None,
42
- metavar="IMAGE_FILENAMES",
42
+ metavar="IMAGE_PATHS",
43
43
  )
44
44
 
45
45
  border_parser.add_argument(
@@ -135,16 +135,16 @@ def run(args: argparse.Namespace) -> None:
135
135
  """
136
136
  log.debug(args)
137
137
 
138
- for image_filepath in [Path(f) for f in args.image_filenames]:
139
- original_image = Image.open(image_filepath)
140
- border = get_border(args)
141
- bordered_image = ImageOps.expand(
142
- original_image,
143
- border=border,
144
- fill=ImageColor.getrgb(args.color),
145
- )
138
+ for image_filepath in [Path(f) for f in args.image_paths]:
139
+ with load_image(image_filepath) as original_image:
140
+ border = get_border(args)
141
+ bordered_image = ImageOps.expand(
142
+ original_image,
143
+ border=border,
144
+ fill=ImageColor.getrgb(args.color),
145
+ )
146
146
 
147
- save_image(args, bordered_image, image_filepath, "border")
147
+ save_image(args, bordered_image, image_filepath, "border")
148
148
 
149
149
 
150
150
  def get_border(
@@ -19,9 +19,9 @@ import argparse
19
19
  import logging
20
20
  from pathlib import Path
21
21
 
22
- from PIL import Image, ImageOps
22
+ from PIL import ImageOps
23
23
 
24
- from fotolab import save_image
24
+ from fotolab import load_image, save_image
25
25
 
26
26
  log = logging.getLogger(__name__)
27
27
 
@@ -51,12 +51,12 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
51
51
  contrast_parser.set_defaults(func=run)
52
52
 
53
53
  contrast_parser.add_argument(
54
- dest="image_filenames",
54
+ dest="image_paths",
55
55
  help="set the image filename",
56
56
  nargs="+",
57
57
  type=str,
58
58
  default=None,
59
- metavar="IMAGE_FILENAMES",
59
+ metavar="IMAGE_PATHS",
60
60
  )
61
61
 
62
62
  contrast_parser.add_argument(
@@ -102,26 +102,26 @@ def run(args: argparse.Namespace) -> None:
102
102
  """
103
103
  log.debug(args)
104
104
 
105
- for image_filename in args.image_filenames:
106
- original_image = Image.open(image_filename)
107
-
108
- if original_image.mode == "RGBA":
109
- # Split the image into RGB and Alpha channels
110
- rgb_image = original_image.convert("RGB")
111
- alpha_channel = original_image.getchannel("A")
112
-
113
- # Apply autocontrast to the RGB part
114
- contrasted_rgb = ImageOps.autocontrast(
115
- rgb_image, cutoff=args.cutoff
116
- )
117
-
118
- # Merge the contrasted RGB part with the original Alpha channel
119
- contrasted_rgb.putalpha(alpha_channel)
120
- contrast_image = contrasted_rgb
121
- else:
122
- # For other modes (like RGB, L, etc.), apply autocontrast directly
123
- contrast_image = ImageOps.autocontrast(
124
- original_image, cutoff=args.cutoff
125
- )
126
-
127
- save_image(args, contrast_image, Path(image_filename), "contrast")
105
+ for image_path_str in args.image_paths:
106
+ with load_image(Path(image_path_str)) as original_image:
107
+ if original_image.mode == "RGBA":
108
+ # Split the image into RGB and Alpha channels
109
+ rgb_image = original_image.convert("RGB")
110
+ alpha_channel = original_image.getchannel("A")
111
+
112
+ # Apply autocontrast to the RGB part
113
+ contrasted_rgb = ImageOps.autocontrast(
114
+ rgb_image, cutoff=args.cutoff
115
+ )
116
+
117
+ # Merge the contrasted RGB part with the original Alpha channel
118
+ contrasted_rgb.putalpha(alpha_channel)
119
+ contrast_image = contrasted_rgb
120
+ else:
121
+ # For other modes (like RGB, L, etc.), apply autocontrast
122
+ # directly
123
+ contrast_image = ImageOps.autocontrast(
124
+ original_image, cutoff=args.cutoff
125
+ )
126
+
127
+ save_image(args, contrast_image, Path(image_path_str), "contrast")
@@ -23,7 +23,7 @@ from typing import NamedTuple
23
23
 
24
24
  from PIL import Image, ImageDraw
25
25
 
26
- from fotolab import save_gif_image, save_image
26
+ from fotolab import load_image, save_gif_image, save_image
27
27
 
28
28
  log = logging.getLogger(__name__)
29
29
 
@@ -45,12 +45,12 @@ def build_subparser(subparsers) -> None:
45
45
  halftone_parser.set_defaults(func=run)
46
46
 
47
47
  halftone_parser.add_argument(
48
- dest="image_filenames",
48
+ dest="image_paths",
49
49
  help="set the image filename",
50
50
  nargs="+",
51
51
  type=str,
52
52
  default=None,
53
- metavar="IMAGE_FILENAMES",
53
+ metavar="IMAGE_PATHS",
54
54
  )
55
55
 
56
56
  halftone_parser.add_argument(
@@ -111,28 +111,29 @@ def run(args: argparse.Namespace) -> None:
111
111
  """
112
112
  log.debug(args)
113
113
 
114
- for image_filename_str in args.image_filenames:
115
- image_filename = Path(image_filename_str)
116
- original_image = Image.open(image_filename)
117
- halftone_image = create_halftone_image(
118
- original_image, args.cells, args.grayscale
119
- )
120
-
121
- if args.before_after:
122
- save_gif_image(
123
- args,
124
- image_filename,
125
- original_image,
126
- halftone_image,
127
- "halftone",
114
+ for image_path_str in args.image_paths:
115
+ image_filename = Path(image_path_str)
116
+ with load_image(image_filename) as original_image:
117
+ halftone_image = create_halftone_image(
118
+ original_image, args.cells, args.grayscale
128
119
  )
129
- else:
130
- save_image(args, halftone_image, image_filename, "halftone")
120
+
121
+ if args.before_after:
122
+ save_gif_image(
123
+ args,
124
+ image_filename,
125
+ original_image,
126
+ halftone_image,
127
+ "halftone",
128
+ )
129
+ else:
130
+ save_image(args, halftone_image, image_filename, "halftone")
131
131
 
132
132
 
133
133
  def _draw_halftone_dot(
134
134
  draw: ImageDraw.ImageDraw,
135
135
  source_image: Image.Image,
136
+ brightness_map: Image.Image,
136
137
  cell: HalftoneCell,
137
138
  grayscale: bool,
138
139
  fill_color_dot: int,
@@ -150,21 +151,19 @@ def _draw_halftone_dot(
150
151
  x = max(0, x)
151
152
  y = max(0, y)
152
153
 
153
- # get pixel value (brightness or color) from the source image using clamped
154
- # coordinates
154
+ # Get brightness from the pre-calculated map (L mode)
155
+ brightness = brightness_map.getpixel((x, y))
156
+
157
+ # Get pixel value (color) from the source image
155
158
  pixel_value = source_image.getpixel((x, y))
156
159
 
157
160
  if grayscale:
158
- brightness = pixel_value
159
- dot_fill = fill_color_dot # Use white for grayscale dots
161
+ # In grayscale mode, source_image is L mode, pixel_value is brightness
162
+ # dot_fill is the fixed color (255 for white dot on black background)
163
+ dot_fill = fill_color_dot
160
164
  else:
161
- # calculate brightness (luminance) from the RGB color
162
- brightness = int(
163
- 0.299 * pixel_value[0]
164
- + 0.587 * pixel_value[1]
165
- + 0.114 * pixel_value[2]
166
- )
167
- dot_fill = pixel_value # Use original color for color dots
165
+ # In color mode, dot_fill is the original color
166
+ dot_fill = pixel_value
168
167
 
169
168
  # calculate dot radius relative to cell size based on brightness max radius
170
169
  # is half the cell size
@@ -203,8 +202,11 @@ def create_halftone_image(
203
202
  fill_color_black: int | tuple[int, int, int]
204
203
  fill_color_dot_for_grayscale: int
205
204
 
205
+ # Always create a grayscale version for brightness calculation (dot size)
206
+ brightness_map = original_image.convert("L")
207
+
206
208
  if grayscale:
207
- source_image = original_image.convert("L")
209
+ source_image = brightness_map
208
210
  output_mode = "L"
209
211
  fill_color_black = 0
210
212
  fill_color_dot_for_grayscale = 255
@@ -229,6 +231,7 @@ def create_halftone_image(
229
231
  _draw_halftone_dot(
230
232
  draw,
231
233
  source_image,
234
+ brightness_map,
232
235
  cell,
233
236
  grayscale,
234
237
  fill_color_dot_for_grayscale,