fotolab 0.26.1__py3-none-any.whl → 0.26.3__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
@@ -20,7 +20,7 @@ import subprocess
20
20
  import sys
21
21
  from pathlib import Path
22
22
 
23
- __version__ = "0.26.1"
23
+ __version__ = "0.26.3"
24
24
 
25
25
  log = logging.getLogger(__name__)
26
26
 
@@ -125,13 +125,23 @@ def run(args: argparse.Namespace) -> None:
125
125
  save_image(args, bordered_image, image_filename, "border")
126
126
 
127
127
 
128
- def get_border(args: argparse.Namespace) -> tuple:
129
- """Calculate the border."""
130
- if (
131
- args.width_left
132
- or args.width_top
133
- or args.width_right
134
- or args.width_bottom
128
+ def get_border(args: argparse.Namespace) -> tuple[int, int, int, int]:
129
+ """Calculate the border dimensions.
130
+
131
+ Args:
132
+ args (argparse.Namespace): Command line arguments
133
+
134
+ Returns:
135
+ tuple[int, int, int, int]: Border dimensions in pixesl. If individual
136
+ widths are not specified, returns a uniform width for all sides.
137
+ """
138
+ if any(
139
+ [
140
+ args.width_left,
141
+ args.width_top,
142
+ args.width_right,
143
+ args.width_bottom,
144
+ ]
135
145
  ):
136
146
  return (
137
147
  int(args.width_left),
@@ -67,45 +67,62 @@ def run(args: argparse.Namespace) -> None:
67
67
 
68
68
  for image_filename in args.image_filenames:
69
69
  original_image = Image.open(image_filename)
70
- grayscale_image = original_image.convert("L")
71
- width, height = original_image.size
70
+ halftone_image = create_halftone_image(original_image)
71
+
72
+ if args.before_after:
73
+ save_gif_image(
74
+ args, image_filename, original_image, halftone_image
75
+ )
76
+ else:
77
+ save_image(args, halftone_image, image_filename, "halftone")
72
78
 
73
- halftone_image = Image.new("L", (width, height), "black")
74
- draw = ImageDraw.Draw(halftone_image)
75
79
 
76
- # modified from the circular halftone effect processing.py example from
77
- # https://tabreturn.github.io/code/processing/python/2019/02/09/processing.py_in_ten_lessons-6.3-_halftones.html
78
- coltotal = 50
79
- cellsize = width / coltotal
80
- rowtotal = math.ceil(height / cellsize)
80
+ def create_halftone_image(
81
+ original_image: Image.Image, cell_count: int = 50
82
+ ) -> Image.Image:
83
+ """Create a halftone version of the input image.
81
84
 
82
- col = 0
83
- row = 0
85
+ Modified from the circular halftone effect processing.py example from
86
+ https://tabreturn.github.io/code/processing/python/2019/02/09/processing.py_in_ten_lessons-6.3-_halftones.html
84
87
 
85
- for _ in range(int(coltotal * rowtotal)):
86
- x = int(col * cellsize)
87
- y = int(row * cellsize)
88
- col += 1
88
+ Args:
89
+ original_image: The source image to convert
90
+ cell_count: Number of cells across the width (default: 50)
89
91
 
90
- if col >= coltotal:
91
- col = 0
92
- row += 1
92
+ Returns:
93
+ Image.Image: The halftone converted image
94
+ """
95
+ grayscale_image = original_image.convert("L")
96
+ width, height = original_image.size
93
97
 
94
- x = int(x + cellsize / 2)
95
- y = int(y + cellsize / 2)
98
+ halftone_image = Image.new("L", (width, height), "black")
99
+ draw = ImageDraw.Draw(halftone_image)
96
100
 
101
+ cellsize = width / cell_count
102
+ rowtotal = math.ceil(height / cellsize)
103
+
104
+ for row in range(rowtotal):
105
+ for col in range(cell_count):
106
+ # Calculate center point of current cell
107
+ x = int(col * cellsize + cellsize / 2)
108
+ y = int(row * cellsize + cellsize / 2)
109
+
110
+ # Get brightness and calculate dot size
97
111
  brightness = grayscale_image.getpixel((x, y))
98
- amp = 10 * brightness / 200
112
+ dot_size = 10 * brightness / 200
113
+
114
+ # Draw the dot
99
115
  draw.ellipse(
100
- [x - amp / 2, y - amp / 2, x + amp / 2, y + amp / 2], fill=255
116
+ [
117
+ x - dot_size / 2,
118
+ y - dot_size / 2,
119
+ x + dot_size / 2,
120
+ y + dot_size / 2,
121
+ ],
122
+ fill=255,
101
123
  )
102
124
 
103
- if args.before_after:
104
- save_gif_image(
105
- args, image_filename, original_image, halftone_image
106
- )
107
- else:
108
- save_image(args, halftone_image, image_filename, "halftone")
125
+ return halftone_image
109
126
 
110
127
 
111
128
  def save_gif_image(args, image_filename, original_image, sharpen_image):
@@ -123,7 +140,7 @@ def save_gif_image(args, image_filename, original_image, sharpen_image):
123
140
  format="gif",
124
141
  append_images=[sharpen_image],
125
142
  save_all=True,
126
- duration=2500,
143
+ duration=1000,
127
144
  loop=0,
128
145
  optimize=True,
129
146
  )
@@ -167,46 +167,10 @@ def run(args: argparse.Namespace) -> None:
167
167
 
168
168
  def watermark_gif_image(original_image: Image.Image, args: argparse.Namespace):
169
169
  """Watermark the image."""
170
- watermarked_image = original_image.copy()
171
-
172
170
  frames = []
173
-
174
171
  for frame in ImageSequence.Iterator(original_image):
175
- frame = frame.convert("RGBA")
176
- draw = ImageDraw.Draw(frame)
177
-
178
- font = ImageFont.load_default(calc_font_size(original_image, args))
179
- log.debug("default font: %s", " ".join(font.getname()))
180
-
181
- text = args.text
182
- if args.camera and camera_metadata(original_image):
183
- text = camera_metadata(original_image)
184
-
185
- if args.lowercase:
186
- text = text.lower()
187
-
188
- (left, top, right, bottom) = draw.textbbox(
189
- xy=(0, 0), text=text, font=font
190
- )
191
- text_width = right - left
192
- text_height = bottom - top
193
- (position_x, position_y) = calc_position(
194
- watermarked_image,
195
- text_width,
196
- text_height,
197
- args.position,
198
- calc_padding(original_image, args),
199
- )
200
-
201
- draw.text(
202
- (position_x, position_y),
203
- text,
204
- font=font,
205
- fill=(*ImageColor.getrgb(args.font_color), 128),
206
- stroke_width=calc_font_outline_width(original_image, args),
207
- stroke_fill=(*ImageColor.getrgb(args.outline_color), 128),
208
- )
209
- frames.append(frame)
172
+ watermarked_frame = watermark_image(args, frame.convert("RGBA"))
173
+ frames.append(watermarked_frame)
210
174
 
211
175
  frames[0].save(
212
176
  "foo.gif",
@@ -223,20 +187,20 @@ def watermark_non_gif_image(
223
187
  original_image: Image.Image, args: argparse.Namespace
224
188
  ):
225
189
  """Watermark the image."""
226
- watermarked_image = original_image.copy()
190
+ return watermark_image(args, original_image)
191
+
227
192
 
193
+ def watermark_image(
194
+ args: argparse.Namespace, original_image: Image.Image
195
+ ) -> Image.Image:
196
+ """Watermark an image."""
197
+ watermarked_image = original_image.copy()
228
198
  draw = ImageDraw.Draw(watermarked_image)
229
199
 
230
200
  font = ImageFont.load_default(calc_font_size(original_image, args))
231
201
  log.debug("default font: %s", " ".join(font.getname()))
232
202
 
233
- text = args.text
234
- if args.camera and camera_metadata(original_image):
235
- text = camera_metadata(original_image)
236
-
237
- if args.lowercase:
238
- text = text.lower()
239
-
203
+ text = prepare_text(args, original_image)
240
204
  (left, top, right, bottom) = draw.textbbox(xy=(0, 0), text=text, font=font)
241
205
  text_width = right - left
242
206
  text_height = bottom - top
@@ -259,6 +223,18 @@ def watermark_non_gif_image(
259
223
  return watermarked_image
260
224
 
261
225
 
226
+ def prepare_text(args, image) -> str:
227
+ """Prepare the watermark text."""
228
+ text = args.text
229
+ if args.camera and camera_metadata(image):
230
+ text = camera_metadata(image)
231
+
232
+ if args.lowercase:
233
+ text = text.lower()
234
+
235
+ return text
236
+
237
+
262
238
  def calc_font_size(image, args) -> int:
263
239
  """Calculate the font size based on the width of the image."""
264
240
  width, _height = image.size
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.26.1
3
+ Version: 0.26.3
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -61,7 +61,7 @@ fotolab -h
61
61
 
62
62
  ```console
63
63
  usage: fotolab [-h] [-o] [-op] [-od OUTPUT_DIR] [-q] [-v] [-d] [-V]
64
- {animate,auto,border,contrast,env,info,montage,resize,rotate,sharpen,watermark} ...
64
+ {animate,auto,border,contrast,env,halftone,info,montage,resize,rotate,sharpen,watermark} ...
65
65
 
66
66
  A console program to manipulate photos.
67
67
 
@@ -70,13 +70,14 @@ changelog: https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
70
70
  issues: https://github.com/kianmeng/fotolab/issues
71
71
 
72
72
  positional arguments:
73
- {animate,auto,border,contrast,env,info,montage,resize,rotate,sharpen,watermark}
73
+ {animate,auto,border,contrast,env,halftone,info,montage,resize,rotate,sharpen,watermark}
74
74
  sub-command help
75
75
  animate animate an image
76
76
  auto auto adjust (resize, contrast, and watermark) a photo
77
77
  border add border to image
78
78
  contrast contrast an image
79
79
  env print environment information for bug reporting
80
+ halftone halftone an image
80
81
  info info an image
81
82
  montage montage a list of image
82
83
  resize resize an image
@@ -1,21 +1,21 @@
1
- fotolab/__init__.py,sha256=a6JSYO39sXbb5tNuAR07eXw8gXocdIrxeHtn0LS7ZAo,2172
1
+ fotolab/__init__.py,sha256=mQ7OaOJnNZcjO8usZi_l0a_bX5_dNrUENLnp8VQMp2k,2172
2
2
  fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
3
  fotolab/cli.py,sha256=9rhS-yhX1nE_zKBfR05QyEt-S7Y43mIP0yUQA6bsgV8,4326
4
4
  fotolab/subcommands/__init__.py,sha256=l3DlIaJ3u3jGjnC1H1yV8LZ_nPqOLJ6gikD4BCaMAQ0,1129
5
5
  fotolab/subcommands/animate.py,sha256=Zp0LPM7ktg6V2rIAP8pof2mmPAph_0O3TEySvg55-h8,2972
6
6
  fotolab/subcommands/auto.py,sha256=p_e1f4mcrIFLqBXMNKvPQRDkNrAlK7FR6sdR5NAR0t8,2427
7
- fotolab/subcommands/border.py,sha256=v5rdLb7Yq1kQI0MTSfVb0iroBroTV8oxOk-jMLB2who,3637
7
+ fotolab/subcommands/border.py,sha256=SwxGWzKxQACZH0u2z2jIvuqdx_OvXKDaCVptP5K7D7c,3941
8
8
  fotolab/subcommands/contrast.py,sha256=8uPCd5xI-aUsL7rjdEPmfSskdzMwM-1tv0eRRONkW2M,2100
9
9
  fotolab/subcommands/env.py,sha256=JamU3a2xWPbwlAj5iThHs58KYkLmjpUphZfTQODBp_4,1471
10
- fotolab/subcommands/halftone.py,sha256=EEXq3ZZES3h2ZzLTc0IpkkuApR5O_kdeZ7N3vRFv-UI,3820
10
+ fotolab/subcommands/halftone.py,sha256=Xlbj3nVzqBXLzfemhF66nWV9U-PJHcG1Q2m2HiDhjI0,4322
11
11
  fotolab/subcommands/info.py,sha256=DANbfBNy2SzFfeE4KqOViAZkaME6xujfZvJTHIaZyCY,3312
12
12
  fotolab/subcommands/montage.py,sha256=hhRH9LsWxXa86_qtVDKGvk--Eap2nP17OTy81kq3Xjk,2048
13
13
  fotolab/subcommands/resize.py,sha256=d2Nlslzlvri9L2rqmE-HbmnLozylSk3U1Hi3DF1q3Mc,5023
14
14
  fotolab/subcommands/rotate.py,sha256=vhtiAD5r0i5humpjyAbkoxh2nQsSuBYUD-TgcECwUwE,2149
15
15
  fotolab/subcommands/sharpen.py,sha256=xz8RW8cRPM4eUvJTO1Stsur3G67DBftVGza8kF5j2Pc,3700
16
- fotolab/subcommands/watermark.py,sha256=zKIFMreqSRaTD89JPjtLZljfTw-5ZkbZNBAyLeFveGw,8905
17
- fotolab-0.26.1.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
- fotolab-0.26.1.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
- fotolab-0.26.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
20
- fotolab-0.26.1.dist-info/METADATA,sha256=pTDOM47SsAvo1xgsN6RlL2_ocSwcRm5W3TrSkJE9dyw,11058
21
- fotolab-0.26.1.dist-info/RECORD,,
16
+ fotolab/subcommands/watermark.py,sha256=a4GEVZG64B1giUigweXvDGS-Y7PeBXFyyrsHuko6ClQ,8124
17
+ fotolab-0.26.3.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
+ fotolab-0.26.3.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
+ fotolab-0.26.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
20
+ fotolab-0.26.3.dist-info/METADATA,sha256=unUGTv1OHqdprfMt-jC5fCEeeBpIi2TQxeq81y2jGmg,11114
21
+ fotolab-0.26.3.dist-info/RECORD,,