fotolab 0.31.5__py3-none-any.whl → 0.31.7__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.
@@ -24,7 +24,8 @@ def build_subparser(subparsers):
24
24
  iter_namespace = pkgutil.iter_modules(__path__, __name__ + ".")
25
25
 
26
26
  subcommands = {
27
- name: importlib.import_module(name) for finder, name, ispkg in iter_namespace
27
+ name: importlib.import_module(name)
28
+ for finder, name, ispkg in iter_namespace
28
29
  }
29
30
 
30
31
  for subcommand in subcommands.values():
@@ -123,7 +123,9 @@ def build_subparser(subparsers) -> None:
123
123
  type=int,
124
124
  default=4,
125
125
  choices=range(0, 7),
126
- help=("set WEBP encoding method (0=fast, 6=slow/best, default: '%(default)s')"),
126
+ help=(
127
+ "set WEBP encoding method (0=fast, 6=slow/best, default: '%(default)s')"
128
+ ),
127
129
  metavar="METHOD",
128
130
  )
129
131
 
@@ -77,7 +77,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
77
77
  dest="width_right",
78
78
  type=int,
79
79
  default=0,
80
- help=("set the width of right border in pixels (default: '%(default)s')"),
80
+ help=(
81
+ "set the width of right border in pixels (default: '%(default)s')"
82
+ ),
81
83
  metavar="WIDTH",
82
84
  )
83
85
 
@@ -87,7 +89,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
87
89
  dest="width_bottom",
88
90
  type=int,
89
91
  default=0,
90
- help=("set the width of bottom border in pixels (default: '%(default)s')"),
92
+ help=(
93
+ "set the width of bottom border in pixels (default: '%(default)s')"
94
+ ),
91
95
  metavar="WIDTH",
92
96
  )
93
97
 
@@ -31,7 +31,9 @@ def _validate_cutoff(value: str) -> float:
31
31
  try:
32
32
  f_value = float(value)
33
33
  except ValueError as e:
34
- raise argparse.ArgumentTypeError(f"invalid float value: '{value}'") from e
34
+ raise argparse.ArgumentTypeError(
35
+ f"invalid float value: '{value}'"
36
+ ) from e
35
37
  if not 0 <= f_value <= 50:
36
38
  raise argparse.ArgumentTypeError(
37
39
  f"cutoff value {f_value} must be between 0 and 50"
@@ -41,7 +43,9 @@ def _validate_cutoff(value: str) -> float:
41
43
 
42
44
  def build_subparser(subparsers: argparse._SubParsersAction) -> None:
43
45
  """Build the subparser."""
44
- contrast_parser = subparsers.add_parser("contrast", help="contrast an image.")
46
+ contrast_parser = subparsers.add_parser(
47
+ "contrast", help="contrast an image."
48
+ )
45
49
 
46
50
  contrast_parser.set_defaults(func=run)
47
51
 
@@ -99,6 +103,8 @@ def run(args: argparse.Namespace) -> None:
99
103
 
100
104
  for image_filename in args.image_filenames:
101
105
  original_image = Image.open(image_filename)
102
- contrast_image = ImageOps.autocontrast(original_image, cutoff=args.cutoff)
106
+ contrast_image = ImageOps.autocontrast(
107
+ original_image, cutoff=args.cutoff
108
+ )
103
109
 
104
110
  save_image(args, contrast_image, image_filename, "contrast")
@@ -18,7 +18,7 @@
18
18
  import argparse
19
19
  import logging
20
20
  import math
21
- from typing import NamedTuple
21
+ from typing import NamedTuple, Union
22
22
 
23
23
  from PIL import Image, ImageDraw
24
24
 
@@ -37,7 +37,9 @@ class HalftoneCell(NamedTuple):
37
37
 
38
38
  def build_subparser(subparsers) -> None:
39
39
  """Build the subparser."""
40
- halftone_parser = subparsers.add_parser("halftone", help="halftone an image")
40
+ halftone_parser = subparsers.add_parser(
41
+ "halftone", help="halftone an image"
42
+ )
41
43
 
42
44
  halftone_parser.set_defaults(func=run)
43
45
 
@@ -129,22 +131,22 @@ def _draw_halftone_dot(
129
131
  source_image: Image.Image,
130
132
  cell: HalftoneCell,
131
133
  grayscale: bool,
132
- fill_color_dot,
134
+ fill_color_dot: int,
133
135
  ) -> None:
134
136
  """Calculate properties and draw a single halftone dot."""
135
- # Calculate center point of current cell
137
+ # calculate center point of current cell
136
138
  img_width, img_height = source_image.size
137
139
 
138
- # Calculate center point of current cell and clamp to image bounds
140
+ # calculate center point of current cell and clamp to image bounds
139
141
  x = min(int(cell.col * cell.cellsize + cell.cellsize / 2), img_width - 1)
140
142
  y = min(int(cell.row * cell.cellsize + cell.cellsize / 2), img_height - 1)
141
143
 
142
- # Ensure coordinates are non-negative (shouldn't happen with current logic,
144
+ # ensure coordinates are non-negative (shouldn't happen with current logic,
143
145
  # but safe)
144
146
  x = max(0, x)
145
147
  y = max(0, y)
146
148
 
147
- # Get pixel value (brightness or color) from the source image using clamped
149
+ # get pixel value (brightness or color) from the source image using clamped
148
150
  # coordinates
149
151
  pixel_value = source_image.getpixel((x, y))
150
152
 
@@ -152,17 +154,20 @@ def _draw_halftone_dot(
152
154
  brightness = pixel_value
153
155
  dot_fill = fill_color_dot # Use white for grayscale dots
154
156
  else:
155
- # Calculate brightness (luminance) from the RGB color
157
+ # calculate brightness (luminance) from the RGB color
156
158
  brightness = int(
157
- 0.299 * pixel_value[0] + 0.587 * pixel_value[1] + 0.114 * pixel_value[2]
159
+ 0.299 * pixel_value[0]
160
+ + 0.587 * pixel_value[1]
161
+ + 0.114 * pixel_value[2]
158
162
  )
159
163
  dot_fill = pixel_value # Use original color for color dots
160
164
 
161
- # Calculate dot radius relative to cell size based on brightness Max radius
162
- # is half the cell size. Scale by brightness (0-255).
165
+ # calculate dot radius relative to cell size based on brightness max radius
166
+ # is half the cell size
167
+ # scale by brightness (0-255).
163
168
  dot_radius = (brightness / 255.0) * (cell.cellsize / 2)
164
169
 
165
- # Draw the dot
170
+ # draw the dot
166
171
  draw.ellipse(
167
172
  [
168
173
  x - dot_radius,
@@ -190,29 +195,30 @@ def create_halftone_image(
190
195
  Returns:
191
196
  Image.Image: The halftone converted image
192
197
  """
198
+ output_mode: str
199
+ fill_color_black: Union[int, tuple[int, int, int]]
200
+ fill_color_dot_for_grayscale: int
201
+
193
202
  if grayscale:
194
203
  source_image = original_image.convert("L")
195
204
  output_mode = "L"
196
205
  fill_color_black = 0
197
- fill_color_dot = 255
206
+ fill_color_dot_for_grayscale = 255
198
207
  else:
199
208
  source_image = original_image.convert("RGB")
200
209
  output_mode = "RGB"
201
210
  fill_color_black = (0, 0, 0)
202
- fill_color_dot = None # Will be set inside the loop for color images
211
+ fill_color_dot_for_grayscale = 0
203
212
 
204
213
  width, height = original_image.size
205
214
 
206
- # Create a new image for the output, initialized to black
215
+ # create a new image for the output, initialized to black
207
216
  halftone_image = Image.new(output_mode, (width, height), fill_color_black)
208
217
  draw = ImageDraw.Draw(halftone_image)
209
218
 
210
219
  cellsize = width / cell_count
211
220
  rowtotal = math.ceil(height / cellsize)
212
221
 
213
- # Determine the fill color for dots once if grayscale
214
- grayscale_fill_color_dot = fill_color_dot if grayscale else None
215
-
216
222
  for row in range(rowtotal):
217
223
  for col in range(cell_count):
218
224
  cell = HalftoneCell(col=col, row=row, cellsize=cellsize)
@@ -221,7 +227,7 @@ def create_halftone_image(
221
227
  source_image,
222
228
  cell,
223
229
  grayscale,
224
- grayscale_fill_color_dot,
230
+ fill_color_dot_for_grayscale,
225
231
  )
226
232
 
227
233
  return halftone_image
@@ -107,7 +107,9 @@ def extract_exif_tags(image: Image.Image, sort: bool = False) -> dict:
107
107
  if exif:
108
108
  info = {ExifTags.TAGS.get(tag_id): exif.get(tag_id) for tag_id in exif}
109
109
 
110
- filtered_info = {key: value for key, value in info.items() if key is not None}
110
+ filtered_info = {
111
+ key: value for key, value in info.items() if key is not None
112
+ }
111
113
  if sort:
112
114
  filtered_info = dict(sorted(filtered_info.items()))
113
115
 
@@ -27,7 +27,9 @@ log = logging.getLogger(__name__)
27
27
 
28
28
  def build_subparser(subparsers) -> None:
29
29
  """Build the subparser."""
30
- montage_parser = subparsers.add_parser("montage", help="montage a list of image")
30
+ montage_parser = subparsers.add_parser(
31
+ "montage", help="montage a list of image"
32
+ )
31
33
 
32
34
  montage_parser.set_defaults(func=run)
33
35
 
@@ -59,7 +59,9 @@ def build_subparser(subparsers) -> None:
59
59
  "--canvas-color",
60
60
  default="black",
61
61
  dest="canvas_color",
62
- help=("the color of the extended larger canvas(default: '%(default)s')"),
62
+ help=(
63
+ "the color of the extended larger canvas(default: '%(default)s')"
64
+ ),
63
65
  )
64
66
 
65
67
  if "-c" in sys.argv or "--canvas" in sys.argv:
@@ -109,7 +109,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
109
109
  dest="outline_width",
110
110
  type=int,
111
111
  default=2,
112
- help=("set the outline width of the watermark text (default: '%(default)s')"),
112
+ help=(
113
+ "set the outline width of the watermark text (default: '%(default)s')"
114
+ ),
113
115
  metavar="OUTLINE_WIDTH",
114
116
  )
115
117
 
@@ -119,7 +121,9 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
119
121
  dest="outline_color",
120
122
  type=str,
121
123
  default="black",
122
- help=("set the outline color of the watermark text (default: '%(default)s')"),
124
+ help=(
125
+ "set the outline color of the watermark text (default: '%(default)s')"
126
+ ),
123
127
  metavar="OUTLINE_COLOR",
124
128
  )
125
129
 
@@ -197,7 +201,9 @@ def run(args: argparse.Namespace) -> None:
197
201
  if image.format == "GIF":
198
202
  watermark_gif_image(image, image_filename, args)
199
203
  else:
200
- watermarked_image: Image.Image = watermark_non_gif_image(image, args)
204
+ watermarked_image: Image.Image = watermark_non_gif_image(
205
+ image, args
206
+ )
201
207
  save_image(args, watermarked_image, image_filename, "watermark")
202
208
 
203
209
 
@@ -333,12 +339,16 @@ def calc_font_size(image: Image.Image, args: argparse.Namespace) -> int:
333
339
  return new_font_size
334
340
 
335
341
 
336
- def calc_font_outline_width(image: Image.Image, args: argparse.Namespace) -> int:
342
+ def calc_font_outline_width(
343
+ image: Image.Image, args: argparse.Namespace
344
+ ) -> int:
337
345
  """Calculate the font padding based on the width of the image."""
338
346
  width, _height = image.size
339
347
  new_font_outline_width: int = args.outline_width
340
348
  if width > 600:
341
- new_font_outline_width = math.floor(FONT_OUTLINE_WIDTH_ASPECT_RATIO * width)
349
+ new_font_outline_width = math.floor(
350
+ FONT_OUTLINE_WIDTH_ASPECT_RATIO * width
351
+ )
342
352
 
343
353
  log.debug("new font outline width: %d", new_font_outline_width)
344
354
  return new_font_outline_width
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fotolab
3
- Version: 0.31.5
3
+ Version: 0.31.7
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -31,19 +31,13 @@ A console program to manipulate photos.
31
31
  Stable version From PyPI using `uv`:
32
32
 
33
33
  ```console
34
- uv pip install fotolab
34
+ uv tool install fotolab
35
35
  ```
36
36
 
37
37
  Upgrade to latest stable version:
38
38
 
39
39
  ```console
40
- uv pip install fotolab --upgrade
41
- ```
42
-
43
- Latest development version from GitHub:
44
-
45
- ```console
46
- uv pip install -e git+https://github.com/kianmeng/fotolab.git
40
+ uv tool upgrade fotolab
47
41
  ```
48
42
 
49
43
  ## Usage
@@ -0,0 +1,21 @@
1
+ fotolab/__init__.py,sha256=7XrkZPbYLVCnsak061vye7L9hFC7zj7cP7XWdjunAk8,3137
2
+ fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
+ fotolab/cli.py,sha256=oFiQXmsu3wIsM_DpZnL4B94sAoB62L16Am-cjxGmosY,4406
4
+ fotolab/subcommands/__init__.py,sha256=l3DlIaJ3u3jGjnC1H1yV8LZ_nPqOLJ6gikD4BCaMAQ0,1129
5
+ fotolab/subcommands/animate.py,sha256=2bNaFyUbA5yNgBgd9WhVnJ1R28nfHu0agAJBBww1Z4w,5534
6
+ fotolab/subcommands/auto.py,sha256=3Hl1cCdu65GcfwvwiMRAcex6c7zA-KqIY6AFnB6nI3w,2447
7
+ fotolab/subcommands/border.py,sha256=096EPvuxCB1qnF5VPG8mLtQLXTM5cpkv0ExZXFSh5k8,4665
8
+ fotolab/subcommands/contrast.py,sha256=fcXmHnxDw74j5ZUDQ5cwWh0N4tpyqqvEjymnpITgrEk,3027
9
+ fotolab/subcommands/env.py,sha256=QoxRvzZKgmoHTUxDV4QYhdChCpMWs5TbXFY_qIpIQpE,1469
10
+ fotolab/subcommands/halftone.py,sha256=PgTGf9cChCYYbhB6vdNmzndobb4sK36ujGtZz4XAma0,6678
11
+ fotolab/subcommands/info.py,sha256=H3voMi67cKoHT2Mu4RUNQBPdb_MspetPjhOvy-YyNnE,3563
12
+ fotolab/subcommands/montage.py,sha256=d_3EcyRSFS8fKkczlHO8IRP-mrKhQUtkQndjfd0MKsc,2566
13
+ fotolab/subcommands/resize.py,sha256=eZGoHVMehpHrAX_y-M56s43lSvWmywMjHRVI_dYqpcA,5429
14
+ fotolab/subcommands/rotate.py,sha256=uBFjHyjiBSQLtrtH1p9myODIHUDr1gkL4PpU-6Y1Ofo,2575
15
+ fotolab/subcommands/sharpen.py,sha256=YNho2IPbc-lPvSy3Bsjehc2JOEy27LPqFSGRULs9MyY,3492
16
+ fotolab/subcommands/watermark.py,sha256=cpdzmdzBXcaXWSrklYycnkPLjorLnTM-IXjPigPu0y0,11268
17
+ fotolab-0.31.7.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
+ fotolab-0.31.7.dist-info/licenses/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
+ fotolab-0.31.7.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
20
+ fotolab-0.31.7.dist-info/METADATA,sha256=k4aCD04oH6loRPR-eognUOxnqiirM9A2WA5U5fFg3f0,13454
21
+ fotolab-0.31.7.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- fotolab/__init__.py,sha256=7XrkZPbYLVCnsak061vye7L9hFC7zj7cP7XWdjunAk8,3137
2
- fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
- fotolab/cli.py,sha256=oFiQXmsu3wIsM_DpZnL4B94sAoB62L16Am-cjxGmosY,4406
4
- fotolab/subcommands/__init__.py,sha256=zPNE3lTK3yNjBq2zP1GiL13R4ywO_zh5MRFp5aOFp_g,1121
5
- fotolab/subcommands/animate.py,sha256=Bzp1Y708UZogcLFwZSoJBfLAxLNZ5c0PESp1xl03sQg,5512
6
- fotolab/subcommands/auto.py,sha256=3Hl1cCdu65GcfwvwiMRAcex6c7zA-KqIY6AFnB6nI3w,2447
7
- fotolab/subcommands/border.py,sha256=3mD6ebdPjGk39ldUq9766jEXCbszPlbyV48k13dmrSA,4621
8
- fotolab/subcommands/contrast.py,sha256=k-NGncYgm7x821CP-NhDbyZGzW1FPxaDgJi00xd97kw,2969
9
- fotolab/subcommands/env.py,sha256=QoxRvzZKgmoHTUxDV4QYhdChCpMWs5TbXFY_qIpIQpE,1469
10
- fotolab/subcommands/halftone.py,sha256=4YpHkp3zSYp36qJVPinWBOAUhTJjc-aGxiaNd1IkhXA,6655
11
- fotolab/subcommands/info.py,sha256=pT2NBZ-KBjBCJJRpC9HC04iZj3s6RhbLWTRG6vjsarg,3549
12
- fotolab/subcommands/montage.py,sha256=6-3VFMOQm3KOhcGaDfzStTKvBB5GHrotFkHU9kxZe5E,2552
13
- fotolab/subcommands/resize.py,sha256=h4t425Xf95lkh6Trp-qw_cu0bBgMgBSBvJvUIR4dqiE,5407
14
- fotolab/subcommands/rotate.py,sha256=uBFjHyjiBSQLtrtH1p9myODIHUDr1gkL4PpU-6Y1Ofo,2575
15
- fotolab/subcommands/sharpen.py,sha256=YNho2IPbc-lPvSy3Bsjehc2JOEy27LPqFSGRULs9MyY,3492
16
- fotolab/subcommands/watermark.py,sha256=uVcFynGt2hkTNF5RnfdHhBxSoLmlA7o8-_I65pe_di0,11166
17
- fotolab-0.31.5.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
- fotolab-0.31.5.dist-info/licenses/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
- fotolab-0.31.5.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
20
- fotolab-0.31.5.dist-info/METADATA,sha256=FRLzLmunavCF1LE273b1CeSIF7Ypxo4n7MMBvueyRCA,13581
21
- fotolab-0.31.5.dist-info/RECORD,,