fotolab 0.28.6__py3-none-any.whl → 0.29.0__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 CHANGED
@@ -24,7 +24,7 @@ from pathlib import Path
24
24
 
25
25
  from PIL import Image
26
26
 
27
- __version__ = "0.28.6"
27
+ __version__ = "0.29.0"
28
28
 
29
29
  log = logging.getLogger(__name__)
30
30
 
fotolab/cli.py CHANGED
@@ -115,6 +115,7 @@ def build_parser() -> argparse.ArgumentParser:
115
115
  subparsers = parser.add_subparsers(
116
116
  help="sub-command help",
117
117
  dest="command",
118
+ required=True,
118
119
  )
119
120
  fotolab.subcommands.build_subparser(subparsers)
120
121
 
@@ -128,16 +129,24 @@ def main(args: Optional[Sequence[str]] = None) -> None:
128
129
 
129
130
  try:
130
131
  parser = build_parser()
131
- if len(args) == 0:
132
- parser.print_help(sys.stderr)
133
- return
134
-
135
132
  parsed_args = parser.parse_args(args)
136
133
  setup_logging(parsed_args)
137
134
 
138
- if hasattr(parsed_args, "func"):
135
+ if parsed_args.command is not None:
139
136
  log.debug(parsed_args)
140
- parsed_args.func(parsed_args)
137
+ # Ensure the function attribute exists (set by set_defaults in
138
+ # subcommands)
139
+ if hasattr(parsed_args, "func"):
140
+ parsed_args.func(parsed_args)
141
+ else:
142
+ # This case should ideally not happen if subcommands are set up
143
+ # correctly
144
+ log.error(
145
+ "subcommand '%s' is missing its execution function.",
146
+ parsed_args.command
147
+ )
148
+ parser.print_help(sys.stderr)
149
+ raise SystemExit(1)
141
150
  else:
142
151
  parser.print_help(sys.stderr)
143
152
 
@@ -69,6 +69,24 @@ def build_subparser(subparsers) -> None:
69
69
  help="set default output folder (default: '%(default)s')",
70
70
  )
71
71
 
72
+ halftone_parser.add_argument(
73
+ "-c",
74
+ "--cells",
75
+ dest="cells",
76
+ type=int,
77
+ default=50,
78
+ help="set number of cells across the image width (default: %(default)s)",
79
+ )
80
+
81
+ halftone_parser.add_argument(
82
+ "-g",
83
+ "--grayscale",
84
+ default=False,
85
+ action="store_true",
86
+ dest="grayscale",
87
+ help="convert image to grayscale before applying halftone",
88
+ )
89
+
72
90
 
73
91
  def run(args: argparse.Namespace) -> None:
74
92
  """Run halftone subcommand.
@@ -83,7 +101,9 @@ def run(args: argparse.Namespace) -> None:
83
101
 
84
102
  for image_filename in args.image_filenames:
85
103
  original_image = Image.open(image_filename)
86
- halftone_image = create_halftone_image(original_image)
104
+ halftone_image = create_halftone_image(
105
+ original_image, args.cells, args.grayscale
106
+ )
87
107
 
88
108
  if args.before_after:
89
109
  save_gif_image(
@@ -98,7 +118,7 @@ def run(args: argparse.Namespace) -> None:
98
118
 
99
119
 
100
120
  def create_halftone_image(
101
- original_image: Image.Image, cell_count: int = 50
121
+ original_image: Image.Image, cell_count: int, grayscale: bool = False
102
122
  ) -> Image.Image:
103
123
  """Create a halftone version of the input image.
104
124
 
@@ -107,15 +127,26 @@ def create_halftone_image(
107
127
 
108
128
  Args:
109
129
  original_image: The source image to convert
110
- cell_count: Number of cells across the width (default: 50)
130
+ cell_count: Number of cells across the width
131
+ grayscale: Whether to convert to grayscale first (default: False)
111
132
 
112
133
  Returns:
113
134
  Image.Image: The halftone converted image
114
135
  """
115
- grayscale_image = original_image.convert("L")
136
+ if grayscale:
137
+ source_image = original_image.convert("L")
138
+ output_mode = "L"
139
+ fill_color_black = 0
140
+ fill_color_dot = 255
141
+ else:
142
+ source_image = original_image.convert("RGB")
143
+ output_mode = "RGB"
144
+ fill_color_black = (0, 0, 0)
145
+
116
146
  width, height = original_image.size
117
147
 
118
- halftone_image = Image.new("L", (width, height), "black")
148
+ # Create a new image for the output, initialized to black
149
+ halftone_image = Image.new(output_mode, (width, height), fill_color_black)
119
150
  draw = ImageDraw.Draw(halftone_image)
120
151
 
121
152
  cellsize = width / cell_count
@@ -127,19 +158,34 @@ def create_halftone_image(
127
158
  x = int(col * cellsize + cellsize / 2)
128
159
  y = int(row * cellsize + cellsize / 2)
129
160
 
130
- # Get brightness and calculate dot size
131
- brightness = grayscale_image.getpixel((x, y))
132
- dot_size = 10 * brightness / 200
161
+ # Get pixel value (brightness or color) from the source image
162
+ pixel_value = source_image.getpixel((x, y))
163
+
164
+ if grayscale:
165
+ brightness = pixel_value
166
+ dot_fill = fill_color_dot # Use white for grayscale dots
167
+ else:
168
+ # Calculate brightness (luminance) from the RGB color
169
+ brightness = int(
170
+ 0.299 * pixel_value[0]
171
+ + 0.587 * pixel_value[1]
172
+ + 0.114 * pixel_value[2]
173
+ )
174
+ dot_fill = pixel_value # Use original color for color dots
175
+
176
+ # Calculate dot radius relative to cell size based on brightness
177
+ # Max radius is half the cell size. Scale by brightness (0-255).
178
+ dot_radius = (brightness / 255.0) * (cellsize / 2)
133
179
 
134
180
  # Draw the dot
135
181
  draw.ellipse(
136
182
  [
137
- x - dot_size / 2,
138
- y - dot_size / 2,
139
- x + dot_size / 2,
140
- y + dot_size / 2,
183
+ x - dot_radius,
184
+ y - dot_radius,
185
+ x + dot_radius,
186
+ y + dot_radius,
141
187
  ],
142
- fill=255,
188
+ fill=dot_fill,
143
189
  )
144
190
 
145
191
  return halftone_image
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.28.6
3
+ Version: 0.29.0
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -1,21 +1,21 @@
1
- fotolab/__init__.py,sha256=yiRE-ZW8LgYnZAJ7wKNY_XdaaPC9IbHRMnlRKLYjsdA,3262
1
+ fotolab/__init__.py,sha256=E76E5fEYHIL3PDbEllLqBALGLmzPYpgtdk4B1-ujU_g,3262
2
2
  fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
- fotolab/cli.py,sha256=ffT3CbMN37Qehw4l7YvwUbPFY-SezLLn4Xthi0ejOmk,3956
3
+ fotolab/cli.py,sha256=NH_u73SJhzwDJSRqpi1I4dc8wBfcqbDxNne3cio163A,4411
4
4
  fotolab/subcommands/__init__.py,sha256=l3DlIaJ3u3jGjnC1H1yV8LZ_nPqOLJ6gikD4BCaMAQ0,1129
5
5
  fotolab/subcommands/animate.py,sha256=OcS6Q6JM1TW6HF0dSgBsV9mpEGwDMcJlQssupkf0_ZA,3393
6
6
  fotolab/subcommands/auto.py,sha256=ia-xegV1Z4HvYsbKgmTzf1NfNFdTDPWfZe7vQ1_90Ik,2425
7
7
  fotolab/subcommands/border.py,sha256=BS3BHytdWiNumxdKulKYK-WigbsKtPxECdvInUhUjSQ,4608
8
8
  fotolab/subcommands/contrast.py,sha256=ZHTvAJhRYZjNTrkHRZq3O6wba3LS7QjH1BfiGf4ZvGY,3005
9
9
  fotolab/subcommands/env.py,sha256=QoxRvzZKgmoHTUxDV4QYhdChCpMWs5TbXFY_qIpIQpE,1469
10
- fotolab/subcommands/halftone.py,sha256=bRGG_Bg1T5ZV3UWsdMszOdPmc6g9_R0yNp5mb0g8hcE,4152
10
+ fotolab/subcommands/halftone.py,sha256=dtqmyZyGC8huzSGRCRgp8t11m51hFC15kvgfEJBm_0c,5714
11
11
  fotolab/subcommands/info.py,sha256=Qp-Zu7Xp1ptLK211hOkFM5oxZWGTrlNBqCpbx2IjL9Y,3371
12
12
  fotolab/subcommands/montage.py,sha256=d_3EcyRSFS8fKkczlHO8IRP-mrKhQUtkQndjfd0MKsc,2566
13
13
  fotolab/subcommands/resize.py,sha256=UOb2rg_5ArRj0gxPTDOww_ix3tqJRC0W5b_S-Lt1G4w,5444
14
14
  fotolab/subcommands/rotate.py,sha256=uBFjHyjiBSQLtrtH1p9myODIHUDr1gkL4PpU-6Y1Ofo,2575
15
15
  fotolab/subcommands/sharpen.py,sha256=YNho2IPbc-lPvSy3Bsjehc2JOEy27LPqFSGRULs9MyY,3492
16
16
  fotolab/subcommands/watermark.py,sha256=iEuek0BM2PJlGMLq8VZhHxaQ8BInuN2_mvssA7Dhgy8,10032
17
- fotolab-0.28.6.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
- fotolab-0.28.6.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
- fotolab-0.28.6.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
20
- fotolab-0.28.6.dist-info/METADATA,sha256=3E5duvvv00UeRQoiWsueL2cdsEj1l17jdkwxY7Nal68,12970
21
- fotolab-0.28.6.dist-info/RECORD,,
17
+ fotolab-0.29.0.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
+ fotolab-0.29.0.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
+ fotolab-0.29.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
20
+ fotolab-0.29.0.dist-info/METADATA,sha256=h3twSy_UW9ri5GZ0_Xyh0s2IYH6AdVCAll-TuKYc-Xg,12970
21
+ fotolab-0.29.0.dist-info/RECORD,,