fotolab 0.24.0__tar.gz → 0.26.0__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.24.0 → fotolab-0.26.0}/.pre-commit-config.yaml +1 -1
  2. {fotolab-0.24.0 → fotolab-0.26.0}/CHANGELOG.md +17 -0
  3. {fotolab-0.24.0 → fotolab-0.26.0}/PKG-INFO +4 -3
  4. {fotolab-0.24.0 → fotolab-0.26.0}/Pipfile.lock +72 -76
  5. {fotolab-0.24.0 → fotolab-0.26.0}/README.md +3 -2
  6. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/__init__.py +13 -11
  7. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/cli.py +1 -1
  8. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/__init__.py +1 -1
  9. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/animate.py +1 -1
  10. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/auto.py +2 -1
  11. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/border.py +20 -17
  12. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/contrast.py +1 -1
  13. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/env.py +1 -1
  14. fotolab-0.26.0/fotolab/subcommands/halftone.py +93 -0
  15. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/info.py +1 -2
  16. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/montage.py +4 -4
  17. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/resize.py +1 -1
  18. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/rotate.py +1 -1
  19. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/sharpen.py +40 -3
  20. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/subcommands/watermark.py +1 -1
  21. {fotolab-0.24.0 → fotolab-0.26.0}/.coveragerc +0 -0
  22. {fotolab-0.24.0 → fotolab-0.26.0}/.gitignore +0 -0
  23. {fotolab-0.24.0 → fotolab-0.26.0}/.python-version +0 -0
  24. {fotolab-0.24.0 → fotolab-0.26.0}/CONTRIBUTING.md +0 -0
  25. {fotolab-0.24.0 → fotolab-0.26.0}/LICENSE.md +0 -0
  26. {fotolab-0.24.0 → fotolab-0.26.0}/Pipfile +0 -0
  27. {fotolab-0.24.0 → fotolab-0.26.0}/docs/Makefile +0 -0
  28. {fotolab-0.24.0 → fotolab-0.26.0}/docs/make.bat +0 -0
  29. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/CHANGELOG.md +0 -0
  30. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/CONTRIBUTING.md +0 -0
  31. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/LICENSE.md +0 -0
  32. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/README.md +0 -0
  33. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/_static/logo.jpg +0 -0
  34. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/conf.py +0 -0
  35. {fotolab-0.24.0 → fotolab-0.26.0}/docs/source/index.rst +0 -0
  36. {fotolab-0.24.0 → fotolab-0.26.0}/fotolab/__main__.py +0 -0
  37. {fotolab-0.24.0 → fotolab-0.26.0}/noxfile.py +0 -0
  38. {fotolab-0.24.0 → fotolab-0.26.0}/pyproject.toml +0 -0
  39. {fotolab-0.24.0 → fotolab-0.26.0}/tests/__init__.py +0 -0
  40. {fotolab-0.24.0 → fotolab-0.26.0}/tests/conftest.py +0 -0
  41. {fotolab-0.24.0 → fotolab-0.26.0}/tests/test_env.py +0 -0
  42. {fotolab-0.24.0 → fotolab-0.26.0}/tests/test_help_flag.py +0 -0
  43. {fotolab-0.24.0 → fotolab-0.26.0}/tests/test_quiet_flag.py +0 -0
@@ -107,7 +107,7 @@ repos:
107
107
  - --disable=R0801,W0212
108
108
 
109
109
  - repo: https://github.com/pre-commit/mirrors-mypy
110
- rev: v1.14.0
110
+ rev: v1.14.1
111
111
  hooks:
112
112
  - id: mypy
113
113
  exclude: docs/
@@ -7,6 +7,23 @@ and this project adheres to [0-based versioning](https://0ver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## v0.26.0 (2025-01-19)
11
+
12
+ - Add `halftone` subcommand
13
+ - Refactor open image using default program
14
+
15
+ ## v0.25.1 (2025-01-13)
16
+
17
+ - Bump deps
18
+ - Fix incorrect subcommand when saving image with border
19
+ - Refactor calculate border
20
+
21
+ ## v0.25.0 (2025-01-05)
22
+
23
+ - Add `--before-after` or `-ba` flag to `sharpen` command
24
+ - Bump `pre-commit` hook and deps
25
+ - Bump copyright years
26
+
10
27
  ## v0.24.0 (2024-12-29)
11
28
 
12
29
  - Bump `pre-commit` hook
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.24.0
3
+ Version: 0.26.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>
@@ -300,7 +300,7 @@ fotolab sharpen -h
300
300
  <!--help-sharpen !-->
301
301
 
302
302
  ```console
303
- usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD]
303
+ usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD] [-ba]
304
304
  IMAGE_FILENAMES [IMAGE_FILENAMES ...]
305
305
 
306
306
  positional arguments:
@@ -315,6 +315,7 @@ options:
315
315
  -t, --threshold THRESHOLD
316
316
  set the minimum brightness changed to be sharpened
317
317
  (default: '3')
318
+ -ba, --before-after generate a GIF showing before and after changes
318
319
  ```
319
320
 
320
321
  <!--help-sharpen !-->
@@ -385,7 +386,7 @@ options:
385
386
 
386
387
  ## Copyright and License
387
388
 
388
- Copyright (C) 2024 Kian-Meng Ang
389
+ Copyright (C) 2024,2025 Kian-Meng Ang
389
390
 
390
391
  This program is free software: you can redistribute it and/or modify it under
391
392
  the terms of the GNU Affero General Public License as published by the Free
@@ -22,85 +22,81 @@
22
22
  },
23
23
  "pillow": {
24
24
  "hashes": [
25
- "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7",
26
- "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5",
27
- "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903",
28
- "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2",
29
- "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38",
30
- "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2",
31
- "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9",
32
- "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f",
33
- "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc",
34
- "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8",
35
- "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d",
36
- "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2",
37
- "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316",
38
- "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a",
39
- "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25",
40
- "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd",
41
- "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba",
42
- "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc",
43
- "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273",
44
- "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa",
45
- "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a",
46
- "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b",
47
- "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a",
48
- "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae",
49
- "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291",
50
- "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97",
51
- "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06",
52
- "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904",
53
- "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b",
54
- "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b",
55
- "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8",
56
- "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527",
57
- "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947",
58
- "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb",
59
- "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003",
60
- "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5",
61
- "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f",
62
- "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739",
63
- "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944",
64
- "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830",
65
- "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f",
66
- "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3",
67
- "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4",
68
- "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84",
69
- "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7",
70
- "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6",
71
- "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6",
72
- "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9",
73
- "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de",
74
- "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4",
75
- "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47",
76
- "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd",
77
- "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50",
78
- "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c",
79
- "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086",
80
- "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba",
81
- "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306",
82
- "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699",
83
- "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e",
84
- "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488",
85
- "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa",
86
- "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2",
87
- "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3",
88
- "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9",
89
- "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923",
90
- "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2",
91
- "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790",
92
- "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734",
93
- "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916",
94
- "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1",
95
- "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f",
96
- "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798",
97
- "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb",
98
- "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2",
99
- "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"
25
+ "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83",
26
+ "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96",
27
+ "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65",
28
+ "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a",
29
+ "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352",
30
+ "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f",
31
+ "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20",
32
+ "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c",
33
+ "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114",
34
+ "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49",
35
+ "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91",
36
+ "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0",
37
+ "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2",
38
+ "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5",
39
+ "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884",
40
+ "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e",
41
+ "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c",
42
+ "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196",
43
+ "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756",
44
+ "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861",
45
+ "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269",
46
+ "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1",
47
+ "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb",
48
+ "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a",
49
+ "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081",
50
+ "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1",
51
+ "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8",
52
+ "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90",
53
+ "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc",
54
+ "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5",
55
+ "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1",
56
+ "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3",
57
+ "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35",
58
+ "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f",
59
+ "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c",
60
+ "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2",
61
+ "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2",
62
+ "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf",
63
+ "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65",
64
+ "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b",
65
+ "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442",
66
+ "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2",
67
+ "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade",
68
+ "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482",
69
+ "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe",
70
+ "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc",
71
+ "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a",
72
+ "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec",
73
+ "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3",
74
+ "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a",
75
+ "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07",
76
+ "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6",
77
+ "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f",
78
+ "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e",
79
+ "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192",
80
+ "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0",
81
+ "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6",
82
+ "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73",
83
+ "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f",
84
+ "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6",
85
+ "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547",
86
+ "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9",
87
+ "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457",
88
+ "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8",
89
+ "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26",
90
+ "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5",
91
+ "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab",
92
+ "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070",
93
+ "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71",
94
+ "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9",
95
+ "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"
100
96
  ],
101
97
  "index": "pypi",
102
98
  "markers": "python_version >= '3.9'",
103
- "version": "==11.0.0"
99
+ "version": "==11.1.0"
104
100
  }
105
101
  },
106
102
  "develop": {
@@ -277,7 +277,7 @@ fotolab sharpen -h
277
277
  <!--help-sharpen !-->
278
278
 
279
279
  ```console
280
- usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD]
280
+ usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD] [-ba]
281
281
  IMAGE_FILENAMES [IMAGE_FILENAMES ...]
282
282
 
283
283
  positional arguments:
@@ -292,6 +292,7 @@ options:
292
292
  -t, --threshold THRESHOLD
293
293
  set the minimum brightness changed to be sharpened
294
294
  (default: '3')
295
+ -ba, --before-after generate a GIF showing before and after changes
295
296
  ```
296
297
 
297
298
  <!--help-sharpen !-->
@@ -362,7 +363,7 @@ options:
362
363
 
363
364
  ## Copyright and License
364
365
 
365
- Copyright (C) 2024 Kian-Meng Ang
366
+ Copyright (C) 2024,2025 Kian-Meng Ang
366
367
 
367
368
  This program is free software: you can redistribute it and/or modify it under
368
369
  the terms of the GNU Affero General Public License as published by the Free
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -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.24.0"
23
+ __version__ = "0.26.0"
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.Popen(["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())
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
 
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -70,6 +70,7 @@ def run(args: argparse.Namespace) -> None:
70
70
  "camera": False,
71
71
  "canvas": False,
72
72
  "lowercase": False,
73
+ "before_after": False,
73
74
  }
74
75
  combined_args = argparse.Namespace(**vars(args), **extra_args)
75
76
  combined_args.overwrite = True
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -115,25 +115,28 @@ def run(args: argparse.Namespace) -> None:
115
115
  for image_filename in args.image_filenames:
116
116
  original_image = Image.open(image_filename)
117
117
 
118
- if (
119
- args.width_left
120
- or args.width_top
121
- or args.width_right
122
- or args.width_bottom
123
- ):
124
- border = (
125
- int(args.width_left),
126
- int(args.width_top),
127
- int(args.width_right),
128
- int(args.width_bottom),
129
- )
130
- else:
131
- border = args.width
132
-
118
+ border = get_border(args)
133
119
  bordered_image = ImageOps.expand(
134
120
  original_image,
135
121
  border=border,
136
122
  fill=ImageColor.getrgb(args.color),
137
123
  )
138
124
 
139
- save_image(args, bordered_image, image_filename, "watermark")
125
+ save_image(args, bordered_image, image_filename, "border")
126
+
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
135
+ ):
136
+ return (
137
+ int(args.width_left),
138
+ int(args.width_top),
139
+ int(args.width_right),
140
+ int(args.width_bottom),
141
+ )
142
+ return args.width
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -0,0 +1,93 @@
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
+
22
+ from PIL import Image, ImageDraw
23
+
24
+ from fotolab import save_image
25
+
26
+ log = logging.getLogger(__name__)
27
+
28
+
29
+ def build_subparser(subparsers) -> None:
30
+ """Build the subparser."""
31
+ halftone_parser = subparsers.add_parser(
32
+ "halftone", help="halftone an image"
33
+ )
34
+
35
+ halftone_parser.set_defaults(func=run)
36
+
37
+ halftone_parser.add_argument(
38
+ dest="image_filenames",
39
+ help="set the image filename",
40
+ nargs="+",
41
+ type=str,
42
+ default=None,
43
+ metavar="IMAGE_FILENAMES",
44
+ )
45
+
46
+
47
+ def run(args: argparse.Namespace) -> None:
48
+ """Run halftone subcommand.
49
+
50
+ Args:
51
+ config (argparse.Namespace): Config from command line arguments
52
+
53
+ Returns:
54
+ None
55
+ """
56
+ log.debug(args)
57
+
58
+ for image_filename in args.image_filenames:
59
+ original_image = Image.open(image_filename)
60
+ grayscale_image = original_image.convert("L")
61
+ width, height = original_image.size
62
+
63
+ halftone_image = Image.new("L", (width, height), "black")
64
+ draw = ImageDraw.Draw(halftone_image)
65
+
66
+ # modified from the circular halftone effect processing.py example from
67
+ # https://tabreturn.github.io/code/processing/python/2019/02/09/processing.py_in_ten_lessons-6.3-_halftones.html
68
+ coltotal = 50
69
+ cellsize = width / coltotal
70
+ rowtotal = math.ceil(height / cellsize)
71
+
72
+ col = 0
73
+ row = 0
74
+
75
+ for _ in range(int(coltotal * rowtotal)):
76
+ x = int(col * cellsize)
77
+ y = int(row * cellsize)
78
+ col += 1
79
+
80
+ if col >= coltotal:
81
+ col = 0
82
+ row += 1
83
+
84
+ x = int(x + cellsize / 2)
85
+ y = int(y + cellsize / 2)
86
+
87
+ brightness = grayscale_image.getpixel((x, y))
88
+ amp = 10 * brightness / 200
89
+ draw.ellipse(
90
+ [x - amp / 2, y - amp / 2, x + amp / 2, y + amp / 2], fill=255
91
+ )
92
+
93
+ save_image(args, halftone_image, image_filename, "halftone")
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -97,7 +97,6 @@ def run(args: argparse.Namespace) -> None:
97
97
 
98
98
  def extract_exif_tags(image: Image.Image, sort: bool = False) -> dict:
99
99
  """Extract Exif metadata from image."""
100
- print(type(image))
101
100
  exif = image._getexif()
102
101
  log.debug(exif)
103
102
 
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -63,9 +63,9 @@ def run(args: argparse.Namespace) -> None:
63
63
  montaged_image = Image.new("RGB", (total_width, total_height))
64
64
 
65
65
  x_offset = 0
66
- for img in images:
67
- montaged_image.paste(img, (x_offset, 0))
68
- x_offset += img.width
66
+ for image in images:
67
+ montaged_image.paste(image, (x_offset, 0))
68
+ x_offset += image.width
69
69
 
70
70
  output_image_filename = args.image_filenames[0].name
71
71
  save_image(args, montaged_image, output_image_filename, "montage")
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
@@ -17,10 +17,11 @@
17
17
 
18
18
  import argparse
19
19
  import logging
20
+ from pathlib import Path
20
21
 
21
22
  from PIL import Image, ImageFilter
22
23
 
23
- from fotolab import save_image
24
+ from fotolab import _open_image, save_image
24
25
 
25
26
  log = logging.getLogger(__name__)
26
27
 
@@ -76,6 +77,15 @@ def build_subparser(subparsers) -> None:
76
77
  metavar="THRESHOLD",
77
78
  )
78
79
 
80
+ sharpen_parser.add_argument(
81
+ "-ba",
82
+ "--before-after",
83
+ default=False,
84
+ action="store_true",
85
+ dest="before_after",
86
+ help="generate a GIF showing before and after changes",
87
+ )
88
+
79
89
 
80
90
  def run(args: argparse.Namespace) -> None:
81
91
  """Run sharpen subcommand.
@@ -95,4 +105,31 @@ def run(args: argparse.Namespace) -> None:
95
105
  args.radius, percent=args.percent, threshold=args.threshold
96
106
  )
97
107
  )
98
- save_image(args, sharpen_image, image_filename, "sharpen")
108
+ if args.before_after:
109
+ save_gif_image(args, image_filename, original_image, sharpen_image)
110
+ else:
111
+ save_image(args, sharpen_image, image_filename, "sharpen")
112
+
113
+
114
+ def save_gif_image(args, image_filename, original_image, sharpen_image):
115
+ """Save the original and sharpen image."""
116
+ image_file = Path(image_filename)
117
+ new_filename = Path(
118
+ args.output_dir,
119
+ image_file.with_name(f"sharpen_gif_{image_file.stem}.gif"),
120
+ )
121
+ new_filename.parent.mkdir(parents=True, exist_ok=True)
122
+
123
+ log.info("sharpen gif image: %s", new_filename)
124
+ original_image.save(
125
+ new_filename,
126
+ format="gif",
127
+ append_images=[sharpen_image],
128
+ save_all=True,
129
+ duration=2500,
130
+ loop=0,
131
+ optimize=True,
132
+ )
133
+
134
+ if args.open:
135
+ _open_image(new_filename)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2024 Kian-Meng Ang
1
+ # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify it under
4
4
  # the terms of the GNU Affero General Public License as published by the Free
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