fotolab 0.25.1__tar.gz → 0.26.1__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 (43) hide show
  1. {fotolab-0.25.1 → fotolab-0.26.1}/.pre-commit-config.yaml +1 -1
  2. {fotolab-0.25.1 → fotolab-0.26.1}/CHANGELOG.md +11 -0
  3. {fotolab-0.25.1 → fotolab-0.26.1}/PKG-INFO +1 -1
  4. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/__init__.py +12 -10
  5. fotolab-0.26.1/fotolab/subcommands/halftone.py +132 -0
  6. {fotolab-0.25.1 → fotolab-0.26.1}/.coveragerc +0 -0
  7. {fotolab-0.25.1 → fotolab-0.26.1}/.gitignore +0 -0
  8. {fotolab-0.25.1 → fotolab-0.26.1}/.python-version +0 -0
  9. {fotolab-0.25.1 → fotolab-0.26.1}/CONTRIBUTING.md +0 -0
  10. {fotolab-0.25.1 → fotolab-0.26.1}/LICENSE.md +0 -0
  11. {fotolab-0.25.1 → fotolab-0.26.1}/Pipfile +0 -0
  12. {fotolab-0.25.1 → fotolab-0.26.1}/Pipfile.lock +0 -0
  13. {fotolab-0.25.1 → fotolab-0.26.1}/README.md +0 -0
  14. {fotolab-0.25.1 → fotolab-0.26.1}/docs/Makefile +0 -0
  15. {fotolab-0.25.1 → fotolab-0.26.1}/docs/make.bat +0 -0
  16. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/CHANGELOG.md +0 -0
  17. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/CONTRIBUTING.md +0 -0
  18. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/LICENSE.md +0 -0
  19. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/README.md +0 -0
  20. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/_static/logo.jpg +0 -0
  21. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/conf.py +0 -0
  22. {fotolab-0.25.1 → fotolab-0.26.1}/docs/source/index.rst +0 -0
  23. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/__main__.py +0 -0
  24. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/cli.py +0 -0
  25. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/__init__.py +0 -0
  26. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/animate.py +0 -0
  27. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/auto.py +0 -0
  28. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/border.py +0 -0
  29. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/contrast.py +0 -0
  30. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/env.py +0 -0
  31. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/info.py +0 -0
  32. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/montage.py +0 -0
  33. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/resize.py +0 -0
  34. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/rotate.py +0 -0
  35. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/sharpen.py +0 -0
  36. {fotolab-0.25.1 → fotolab-0.26.1}/fotolab/subcommands/watermark.py +0 -0
  37. {fotolab-0.25.1 → fotolab-0.26.1}/noxfile.py +0 -0
  38. {fotolab-0.25.1 → fotolab-0.26.1}/pyproject.toml +0 -0
  39. {fotolab-0.25.1 → fotolab-0.26.1}/tests/__init__.py +0 -0
  40. {fotolab-0.25.1 → fotolab-0.26.1}/tests/conftest.py +0 -0
  41. {fotolab-0.25.1 → fotolab-0.26.1}/tests/test_env.py +0 -0
  42. {fotolab-0.25.1 → fotolab-0.26.1}/tests/test_help_flag.py +0 -0
  43. {fotolab-0.25.1 → fotolab-0.26.1}/tests/test_quiet_flag.py +0 -0
@@ -21,7 +21,7 @@ repos:
21
21
  name: validate-pyproject
22
22
 
23
23
  - repo: https://github.com/codespell-project/codespell
24
- rev: v2.3.0
24
+ rev: v2.4.0
25
25
  hooks:
26
26
  - id: codespell
27
27
  args:
@@ -7,6 +7,17 @@ and this project adheres to [0-based versioning](https://0ver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## v0.26.1 (2025-01-26)
11
+
12
+ - Add `--before-and-after` flag to `halftone` subcommand
13
+ - Remove unused imports
14
+ - Resolve `pylint` issues
15
+
16
+ ## v0.26.0 (2025-01-19)
17
+
18
+ - Add `halftone` subcommand
19
+ - Refactor open image using default program
20
+
10
21
  ## v0.25.1 (2025-01-13)
11
22
 
12
23
  - Bump deps
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.25.1
3
+ Version: 0.26.1
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -16,12 +16,11 @@
16
16
  """A console program that manipulate images."""
17
17
 
18
18
  import logging
19
- import os
20
19
  import subprocess
21
20
  import sys
22
21
  from pathlib import Path
23
22
 
24
- __version__ = "0.25.1"
23
+ __version__ = "0.26.1"
25
24
 
26
25
  log = logging.getLogger(__name__)
27
26
 
@@ -57,11 +56,14 @@ def save_image(args, new_image, output_filename, subcommand):
57
56
 
58
57
  def _open_image(filename):
59
58
  """Open generated image using default program."""
60
- if sys.platform == "linux":
61
- subprocess.call(["xdg-open", filename])
62
- elif sys.platform == "darwin":
63
- subprocess.call(["open", filename])
64
- elif sys.platform == "windows":
65
- os.startfile(filename)
66
-
67
- log.info("open image: %s", filename.resolve())
59
+ platform_open_func = {
60
+ "linux": subprocess.call(["xdg-open", filename]),
61
+ "darwin": subprocess.call(["open", filename]),
62
+ "windows": subprocess.call(["start", filename]),
63
+ }
64
+ try:
65
+ platform_open_func[sys.platform]
66
+ except KeyError:
67
+ print(f"Unsupported platform: {sys.platform}")
68
+ else:
69
+ log.info("open image: %s", filename.resolve())
@@ -0,0 +1,132 @@
1
+ # Copyright (C) 2024,2025 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
+ """Halftone subcommand."""
17
+
18
+ import argparse
19
+ import logging
20
+ import math
21
+ from pathlib import Path
22
+
23
+ from PIL import Image, ImageDraw
24
+
25
+ from fotolab import _open_image, save_image
26
+
27
+ log = logging.getLogger(__name__)
28
+
29
+
30
+ def build_subparser(subparsers) -> None:
31
+ """Build the subparser."""
32
+ halftone_parser = subparsers.add_parser(
33
+ "halftone", help="halftone an image"
34
+ )
35
+
36
+ halftone_parser.set_defaults(func=run)
37
+
38
+ halftone_parser.add_argument(
39
+ dest="image_filenames",
40
+ help="set the image filename",
41
+ nargs="+",
42
+ type=str,
43
+ default=None,
44
+ metavar="IMAGE_FILENAMES",
45
+ )
46
+
47
+ halftone_parser.add_argument(
48
+ "-ba",
49
+ "--before-after",
50
+ default=False,
51
+ action="store_true",
52
+ dest="before_after",
53
+ help="generate a GIF showing before and after changes",
54
+ )
55
+
56
+
57
+ def run(args: argparse.Namespace) -> None:
58
+ """Run halftone subcommand.
59
+
60
+ Args:
61
+ config (argparse.Namespace): Config from command line arguments
62
+
63
+ Returns:
64
+ None
65
+ """
66
+ log.debug(args)
67
+
68
+ for image_filename in args.image_filenames:
69
+ original_image = Image.open(image_filename)
70
+ grayscale_image = original_image.convert("L")
71
+ width, height = original_image.size
72
+
73
+ halftone_image = Image.new("L", (width, height), "black")
74
+ draw = ImageDraw.Draw(halftone_image)
75
+
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)
81
+
82
+ col = 0
83
+ row = 0
84
+
85
+ for _ in range(int(coltotal * rowtotal)):
86
+ x = int(col * cellsize)
87
+ y = int(row * cellsize)
88
+ col += 1
89
+
90
+ if col >= coltotal:
91
+ col = 0
92
+ row += 1
93
+
94
+ x = int(x + cellsize / 2)
95
+ y = int(y + cellsize / 2)
96
+
97
+ brightness = grayscale_image.getpixel((x, y))
98
+ amp = 10 * brightness / 200
99
+ draw.ellipse(
100
+ [x - amp / 2, y - amp / 2, x + amp / 2, y + amp / 2], fill=255
101
+ )
102
+
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")
109
+
110
+
111
+ def save_gif_image(args, image_filename, original_image, sharpen_image):
112
+ """Save the original and sharpen image."""
113
+ image_file = Path(image_filename)
114
+ new_filename = Path(
115
+ args.output_dir,
116
+ image_file.with_name(f"sharpen_gif_{image_file.stem}.gif"),
117
+ )
118
+ new_filename.parent.mkdir(parents=True, exist_ok=True)
119
+
120
+ log.info("sharpen gif image: %s", new_filename)
121
+ original_image.save(
122
+ new_filename,
123
+ format="gif",
124
+ append_images=[sharpen_image],
125
+ save_all=True,
126
+ duration=2500,
127
+ loop=0,
128
+ optimize=True,
129
+ )
130
+
131
+ if args.open:
132
+ _open_image(new_filename)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes