nocturneFlow commited on
Commit
06cb14a
·
verified ·
1 Parent(s): 5acfcdf

Training in progress, step 100

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +88 -0
  2. .venv/.gitignore +1 -0
  3. .venv/Lib/site-packages/Markdown-3.7.dist-info/INSTALLER +1 -0
  4. .venv/Lib/site-packages/Markdown-3.7.dist-info/LICENSE.md +30 -0
  5. .venv/Lib/site-packages/Markdown-3.7.dist-info/METADATA +146 -0
  6. .venv/Lib/site-packages/Markdown-3.7.dist-info/RECORD +74 -0
  7. .venv/Lib/site-packages/Markdown-3.7.dist-info/WHEEL +5 -0
  8. .venv/Lib/site-packages/Markdown-3.7.dist-info/entry_points.txt +22 -0
  9. .venv/Lib/site-packages/Markdown-3.7.dist-info/top_level.txt +1 -0
  10. .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER +1 -0
  11. .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt +28 -0
  12. .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA +92 -0
  13. .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD +14 -0
  14. .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL +5 -0
  15. .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt +1 -0
  16. .venv/Lib/site-packages/PIL/BdfFontFile.py +133 -0
  17. .venv/Lib/site-packages/PIL/BlpImagePlugin.py +475 -0
  18. .venv/Lib/site-packages/PIL/BmpImagePlugin.py +471 -0
  19. .venv/Lib/site-packages/PIL/BufrStubImagePlugin.py +74 -0
  20. .venv/Lib/site-packages/PIL/ContainerIO.py +121 -0
  21. .venv/Lib/site-packages/PIL/CurImagePlugin.py +75 -0
  22. .venv/Lib/site-packages/PIL/DcxImagePlugin.py +80 -0
  23. .venv/Lib/site-packages/PIL/DdsImagePlugin.py +566 -0
  24. .venv/Lib/site-packages/PIL/EpsImagePlugin.py +478 -0
  25. .venv/Lib/site-packages/PIL/ExifTags.py +381 -0
  26. .venv/Lib/site-packages/PIL/FitsImagePlugin.py +72 -0
  27. .venv/Lib/site-packages/PIL/FliImagePlugin.py +173 -0
  28. .venv/Lib/site-packages/PIL/FontFile.py +136 -0
  29. .venv/Lib/site-packages/PIL/FpxImagePlugin.py +255 -0
  30. .venv/Lib/site-packages/PIL/FtexImagePlugin.py +114 -0
  31. .venv/Lib/site-packages/PIL/GbrImagePlugin.py +103 -0
  32. .venv/Lib/site-packages/PIL/GdImageFile.py +97 -0
  33. .venv/Lib/site-packages/PIL/GifImagePlugin.py +1097 -0
  34. .venv/Lib/site-packages/PIL/GimpGradientFile.py +137 -0
  35. .venv/Lib/site-packages/PIL/GimpPaletteFile.py +57 -0
  36. .venv/Lib/site-packages/PIL/GribStubImagePlugin.py +74 -0
  37. .venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py +74 -0
  38. .venv/Lib/site-packages/PIL/IcnsImagePlugin.py +400 -0
  39. .venv/Lib/site-packages/PIL/IcoImagePlugin.py +356 -0
  40. .venv/Lib/site-packages/PIL/ImImagePlugin.py +371 -0
  41. .venv/Lib/site-packages/PIL/Image.py +0 -0
  42. .venv/Lib/site-packages/PIL/ImageChops.py +311 -0
  43. .venv/Lib/site-packages/PIL/ImageCms.py +1007 -0
  44. .venv/Lib/site-packages/PIL/ImageColor.py +317 -0
  45. .venv/Lib/site-packages/PIL/ImageDraw.py +1065 -0
  46. .venv/Lib/site-packages/PIL/ImageDraw2.py +193 -0
  47. .venv/Lib/site-packages/PIL/ImageEnhance.py +104 -0
  48. .venv/Lib/site-packages/PIL/ImageFile.py +795 -0
  49. .venv/Lib/site-packages/PIL/ImageFilter.py +568 -0
  50. .venv/Lib/site-packages/PIL/ImageFont.py +1264 -0
.gitattributes CHANGED
@@ -33,3 +33,91 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ .venv/Lib/site-packages/_soundfile_data/libsndfile_64bit.dll filter=lfs diff=lfs merge=lfs -text
37
+ .venv/Lib/site-packages/grpc/_cython/cygrpc.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
38
+ .venv/Lib/site-packages/llvmlite/binding/llvmlite.dll filter=lfs diff=lfs merge=lfs -text
39
+ .venv/Lib/site-packages/numpy/_core/_multiarray_umath.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
40
+ .venv/Lib/site-packages/numpy/_core/_simd.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
41
+ .venv/Lib/site-packages/numpy.libs/libscipy_openblas64_-caad452230ae4ddb57899b8b3a33c55c.dll filter=lfs diff=lfs merge=lfs -text
42
+ .venv/Lib/site-packages/pandas/_libs/algos.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
43
+ .venv/Lib/site-packages/pandas/_libs/groupby.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
44
+ .venv/Lib/site-packages/pandas/_libs/hashtable.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
45
+ .venv/Lib/site-packages/pandas/_libs/interval.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
46
+ .venv/Lib/site-packages/pandas/_libs/join.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
47
+ .venv/Lib/site-packages/PIL/_imaging.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
48
+ .venv/Lib/site-packages/PIL/_imagingft.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
49
+ .venv/Lib/site-packages/pyarrow/arrow.dll filter=lfs diff=lfs merge=lfs -text
50
+ .venv/Lib/site-packages/pyarrow/arrow.lib filter=lfs diff=lfs merge=lfs -text
51
+ .venv/Lib/site-packages/pyarrow/arrow_acero.dll filter=lfs diff=lfs merge=lfs -text
52
+ .venv/Lib/site-packages/pyarrow/arrow_dataset.dll filter=lfs diff=lfs merge=lfs -text
53
+ .venv/Lib/site-packages/pyarrow/arrow_flight.dll filter=lfs diff=lfs merge=lfs -text
54
+ .venv/Lib/site-packages/pyarrow/arrow_python.dll filter=lfs diff=lfs merge=lfs -text
55
+ .venv/Lib/site-packages/pyarrow/arrow_substrait.dll filter=lfs diff=lfs merge=lfs -text
56
+ .venv/Lib/site-packages/pyarrow/lib.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
57
+ .venv/Lib/site-packages/pyarrow/parquet.dll filter=lfs diff=lfs merge=lfs -text
58
+ .venv/Lib/site-packages/rapidfuzz/distance/metrics_cpp.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
59
+ .venv/Lib/site-packages/rapidfuzz/distance/metrics_cpp_avx2.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
60
+ .venv/Lib/site-packages/scipy/fft/_pocketfft/pypocketfft.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
61
+ .venv/Lib/site-packages/scipy/interpolate/_rbfinterp_pythran.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
62
+ .venv/Lib/site-packages/scipy/io/_fast_matrix_market/_fmm_core.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
63
+ .venv/Lib/site-packages/scipy/linalg/_flapack.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
64
+ .venv/Lib/site-packages/scipy/misc/face.dat filter=lfs diff=lfs merge=lfs -text
65
+ .venv/Lib/site-packages/scipy/optimize/_group_columns.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
66
+ .venv/Lib/site-packages/scipy/optimize/_highs/_highs_wrapper.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
67
+ .venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
68
+ .venv/Lib/site-packages/scipy/signal/_spectral.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
69
+ .venv/Lib/site-packages/scipy/sparse/_sparsetools.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
70
+ .venv/Lib/site-packages/scipy/spatial/_ckdtree.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
71
+ .venv/Lib/site-packages/scipy/spatial/_distance_pybind.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
72
+ .venv/Lib/site-packages/scipy/spatial/_qhull.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
73
+ .venv/Lib/site-packages/scipy/special/_special_ufuncs.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
74
+ .venv/Lib/site-packages/scipy/special/_ufuncs.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
75
+ .venv/Lib/site-packages/scipy/special/_ufuncs_cxx.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
76
+ .venv/Lib/site-packages/scipy/special/cython_special.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
77
+ .venv/Lib/site-packages/scipy/stats/_stats_pythran.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
78
+ .venv/Lib/site-packages/scipy/stats/_unuran/unuran_wrapper.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
79
+ .venv/Lib/site-packages/scipy.libs/libscipy_openblas-5b1ec8b915dfb81d11cebc0788069d2d.dll filter=lfs diff=lfs merge=lfs -text
80
+ .venv/Lib/site-packages/sklearn/_loss/_loss.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
81
+ .venv/Lib/site-packages/sympy/polys/benchmarks/__pycache__/bench_solvers.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
82
+ .venv/Lib/site-packages/tokenizers/tokenizers.cp312-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
83
+ .venv/Lib/site-packages/torch/bin/fbgemm.dll filter=lfs diff=lfs merge=lfs -text
84
+ .venv/Lib/site-packages/torch/bin/protoc.exe filter=lfs diff=lfs merge=lfs -text
85
+ .venv/Lib/site-packages/torch/lib/cublas64_11.dll filter=lfs diff=lfs merge=lfs -text
86
+ .venv/Lib/site-packages/torch/lib/cublasLt64_11.dll filter=lfs diff=lfs merge=lfs -text
87
+ .venv/Lib/site-packages/torch/lib/cudnn_adv64_9.dll filter=lfs diff=lfs merge=lfs -text
88
+ .venv/Lib/site-packages/torch/lib/cudnn_cnn64_9.dll filter=lfs diff=lfs merge=lfs -text
89
+ .venv/Lib/site-packages/torch/lib/cudnn_engines_precompiled64_9.dll filter=lfs diff=lfs merge=lfs -text
90
+ .venv/Lib/site-packages/torch/lib/cudnn_engines_runtime_compiled64_9.dll filter=lfs diff=lfs merge=lfs -text
91
+ .venv/Lib/site-packages/torch/lib/cudnn_graph64_9.dll filter=lfs diff=lfs merge=lfs -text
92
+ .venv/Lib/site-packages/torch/lib/cudnn_heuristic64_9.dll filter=lfs diff=lfs merge=lfs -text
93
+ .venv/Lib/site-packages/torch/lib/cudnn_ops64_9.dll filter=lfs diff=lfs merge=lfs -text
94
+ .venv/Lib/site-packages/torch/lib/cufft64_10.dll filter=lfs diff=lfs merge=lfs -text
95
+ .venv/Lib/site-packages/torch/lib/cupti64_2022.3.0.dll filter=lfs diff=lfs merge=lfs -text
96
+ .venv/Lib/site-packages/torch/lib/curand64_10.dll filter=lfs diff=lfs merge=lfs -text
97
+ .venv/Lib/site-packages/torch/lib/cusolver64_11.dll filter=lfs diff=lfs merge=lfs -text
98
+ .venv/Lib/site-packages/torch/lib/cusolverMg64_11.dll filter=lfs diff=lfs merge=lfs -text
99
+ .venv/Lib/site-packages/torch/lib/cusparse64_11.dll filter=lfs diff=lfs merge=lfs -text
100
+ .venv/Lib/site-packages/torch/lib/dnnl.lib filter=lfs diff=lfs merge=lfs -text
101
+ .venv/Lib/site-packages/torch/lib/fbgemm.dll filter=lfs diff=lfs merge=lfs -text
102
+ .venv/Lib/site-packages/torch/lib/fbgemm.lib filter=lfs diff=lfs merge=lfs -text
103
+ .venv/Lib/site-packages/torch/lib/fmt.lib filter=lfs diff=lfs merge=lfs -text
104
+ .venv/Lib/site-packages/torch/lib/kineto.lib filter=lfs diff=lfs merge=lfs -text
105
+ .venv/Lib/site-packages/torch/lib/libiomp5md.dll filter=lfs diff=lfs merge=lfs -text
106
+ .venv/Lib/site-packages/torch/lib/libprotobuf-lite.lib filter=lfs diff=lfs merge=lfs -text
107
+ .venv/Lib/site-packages/torch/lib/libprotobuf.lib filter=lfs diff=lfs merge=lfs -text
108
+ .venv/Lib/site-packages/torch/lib/libprotoc.lib filter=lfs diff=lfs merge=lfs -text
109
+ .venv/Lib/site-packages/torch/lib/nvrtc-builtins64_118.dll filter=lfs diff=lfs merge=lfs -text
110
+ .venv/Lib/site-packages/torch/lib/nvrtc64_112_0.dll filter=lfs diff=lfs merge=lfs -text
111
+ .venv/Lib/site-packages/torch/lib/sleef.lib filter=lfs diff=lfs merge=lfs -text
112
+ .venv/Lib/site-packages/torch/lib/torch_cpu.dll filter=lfs diff=lfs merge=lfs -text
113
+ .venv/Lib/site-packages/torch/lib/torch_cpu.lib filter=lfs diff=lfs merge=lfs -text
114
+ .venv/Lib/site-packages/torch/lib/torch_cuda.dll filter=lfs diff=lfs merge=lfs -text
115
+ .venv/Lib/site-packages/torch/lib/torch_cuda.lib filter=lfs diff=lfs merge=lfs -text
116
+ .venv/Lib/site-packages/torch/lib/torch_python.dll filter=lfs diff=lfs merge=lfs -text
117
+ .venv/Lib/site-packages/torch/lib/XNNPACK.lib filter=lfs diff=lfs merge=lfs -text
118
+ .venv/Lib/site-packages/torchaudio/lib/libtorchaudio.pyd filter=lfs diff=lfs merge=lfs -text
119
+ .venv/Lib/site-packages/torchvision/_C.pyd filter=lfs diff=lfs merge=lfs -text
120
+ .venv/Lib/site-packages/torchvision/nvjpeg64_11.dll filter=lfs diff=lfs merge=lfs -text
121
+ .venv/Lib/site-packages/torio/lib/_torio_ffmpeg4.pyd filter=lfs diff=lfs merge=lfs -text
122
+ .venv/Lib/site-packages/torio/lib/_torio_ffmpeg5.pyd filter=lfs diff=lfs merge=lfs -text
123
+ .venv/Lib/site-packages/torio/lib/_torio_ffmpeg6.pyd filter=lfs diff=lfs merge=lfs -text
.venv/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *
.venv/Lib/site-packages/Markdown-3.7.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.venv/Lib/site-packages/Markdown-3.7.dist-info/LICENSE.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause License
2
+
3
+ Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
4
+ Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
5
+ Copyright 2004 Manfred Stienstra (the original version)
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.venv/Lib/site-packages/Markdown-3.7.dist-info/METADATA ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: Markdown
3
+ Version: 3.7
4
+ Summary: Python implementation of John Gruber's Markdown.
5
+ Author: Manfred Stienstra, Yuri Takhteyev
6
+ Author-email: Waylan limberg <[email protected]>
7
+ Maintainer: Isaac Muse
8
+ Maintainer-email: Waylan Limberg <[email protected]>
9
+ License: BSD 3-Clause License
10
+
11
+ Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
12
+ Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
13
+ Copyright 2004 Manfred Stienstra (the original version)
14
+
15
+ Redistribution and use in source and binary forms, with or without
16
+ modification, are permitted provided that the following conditions are met:
17
+
18
+ 1. Redistributions of source code must retain the above copyright notice, this
19
+ list of conditions and the following disclaimer.
20
+
21
+ 2. Redistributions in binary form must reproduce the above copyright notice,
22
+ this list of conditions and the following disclaimer in the documentation
23
+ and/or other materials provided with the distribution.
24
+
25
+ 3. Neither the name of the copyright holder nor the names of its
26
+ contributors may be used to endorse or promote products derived from
27
+ this software without specific prior written permission.
28
+
29
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
30
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
33
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
37
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
+
40
+ Project-URL: Homepage, https://Python-Markdown.github.io/
41
+ Project-URL: Documentation, https://Python-Markdown.github.io/
42
+ Project-URL: Repository, https://github.com/Python-Markdown/markdown
43
+ Project-URL: Issue Tracker, https://github.com/Python-Markdown/markdown/issues
44
+ Project-URL: Changelog, https://python-markdown.github.io/changelog/
45
+ Keywords: markdown,markdown-parser,python-markdown,markdown-to-html
46
+ Classifier: Development Status :: 5 - Production/Stable
47
+ Classifier: License :: OSI Approved :: BSD License
48
+ Classifier: Operating System :: OS Independent
49
+ Classifier: Programming Language :: Python
50
+ Classifier: Programming Language :: Python :: 3
51
+ Classifier: Programming Language :: Python :: 3.8
52
+ Classifier: Programming Language :: Python :: 3.9
53
+ Classifier: Programming Language :: Python :: 3.10
54
+ Classifier: Programming Language :: Python :: 3.11
55
+ Classifier: Programming Language :: Python :: 3.12
56
+ Classifier: Programming Language :: Python :: 3 :: Only
57
+ Classifier: Programming Language :: Python :: Implementation :: CPython
58
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
59
+ Classifier: Topic :: Communications :: Email :: Filters
60
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
61
+ Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
62
+ Classifier: Topic :: Software Development :: Documentation
63
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
64
+ Classifier: Topic :: Text Processing :: Filters
65
+ Classifier: Topic :: Text Processing :: Markup :: HTML
66
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
67
+ Requires-Python: >=3.8
68
+ Description-Content-Type: text/markdown
69
+ License-File: LICENSE.md
70
+ Requires-Dist: importlib-metadata >=4.4 ; python_version < "3.10"
71
+ Provides-Extra: docs
72
+ Requires-Dist: mkdocs >=1.5 ; extra == 'docs'
73
+ Requires-Dist: mkdocs-nature >=0.6 ; extra == 'docs'
74
+ Requires-Dist: mdx-gh-links >=0.2 ; extra == 'docs'
75
+ Requires-Dist: mkdocstrings[python] ; extra == 'docs'
76
+ Requires-Dist: mkdocs-gen-files ; extra == 'docs'
77
+ Requires-Dist: mkdocs-section-index ; extra == 'docs'
78
+ Requires-Dist: mkdocs-literate-nav ; extra == 'docs'
79
+ Provides-Extra: testing
80
+ Requires-Dist: coverage ; extra == 'testing'
81
+ Requires-Dist: pyyaml ; extra == 'testing'
82
+
83
+ [Python-Markdown][]
84
+ ===================
85
+
86
+ [![Build Status][build-button]][build]
87
+ [![Coverage Status][codecov-button]][codecov]
88
+ [![Latest Version][mdversion-button]][md-pypi]
89
+ [![Python Versions][pyversion-button]][md-pypi]
90
+ [![BSD License][bsdlicense-button]][bsdlicense]
91
+ [![Code of Conduct][codeofconduct-button]][Code of Conduct]
92
+
93
+ [build-button]: https://github.com/Python-Markdown/markdown/workflows/CI/badge.svg?event=push
94
+ [build]: https://github.com/Python-Markdown/markdown/actions?query=workflow%3ACI+event%3Apush
95
+ [codecov-button]: https://codecov.io/gh/Python-Markdown/markdown/branch/master/graph/badge.svg
96
+ [codecov]: https://codecov.io/gh/Python-Markdown/markdown
97
+ [mdversion-button]: https://img.shields.io/pypi/v/Markdown.svg
98
+ [md-pypi]: https://pypi.org/project/Markdown/
99
+ [pyversion-button]: https://img.shields.io/pypi/pyversions/Markdown.svg
100
+ [bsdlicense-button]: https://img.shields.io/badge/license-BSD-yellow.svg
101
+ [bsdlicense]: https://opensource.org/licenses/BSD-3-Clause
102
+ [codeofconduct-button]: https://img.shields.io/badge/code%20of%20conduct-contributor%20covenant-green.svg?style=flat-square
103
+ [Code of Conduct]: https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md
104
+
105
+ This is a Python implementation of John Gruber's [Markdown][].
106
+ It is almost completely compliant with the reference implementation,
107
+ though there are a few known issues. See [Features][] for information
108
+ on what exactly is supported and what is not. Additional features are
109
+ supported by the [Available Extensions][].
110
+
111
+ [Python-Markdown]: https://Python-Markdown.github.io/
112
+ [Markdown]: https://daringfireball.net/projects/markdown/
113
+ [Features]: https://Python-Markdown.github.io#Features
114
+ [Available Extensions]: https://Python-Markdown.github.io/extensions
115
+
116
+ Documentation
117
+ -------------
118
+
119
+ ```bash
120
+ pip install markdown
121
+ ```
122
+ ```python
123
+ import markdown
124
+ html = markdown.markdown(your_text_string)
125
+ ```
126
+
127
+ For more advanced [installation] and [usage] documentation, see the `docs/` directory
128
+ of the distribution or the project website at <https://Python-Markdown.github.io/>.
129
+
130
+ [installation]: https://python-markdown.github.io/install/
131
+ [usage]: https://python-markdown.github.io/reference/
132
+
133
+ See the change log at <https://python-markdown.github.io/changelog/>.
134
+
135
+ Support
136
+ -------
137
+
138
+ You may report bugs, ask for help, and discuss various other issues on the [bug tracker][].
139
+
140
+ [bug tracker]: https://github.com/Python-Markdown/markdown/issues
141
+
142
+ Code of Conduct
143
+ ---------------
144
+
145
+ Everyone interacting in the Python-Markdown project's code bases, issue trackers,
146
+ and mailing lists is expected to follow the [Code of Conduct].
.venv/Lib/site-packages/Markdown-3.7.dist-info/RECORD ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ../../Scripts/markdown_py.exe,sha256=-JGaLwFSnZqB4eyZr-0bPMNdBPekRcCMaW6Q8OcKHB8,108407
2
+ Markdown-3.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
+ Markdown-3.7.dist-info/LICENSE.md,sha256=e6TrbRCzKy0R3OE4ITQDUc27swuozMZ4Qdsv_Ybnmso,1650
4
+ Markdown-3.7.dist-info/METADATA,sha256=nY8sewcY6R1akyROqkyO-Jk_eUDY8am_C4MkRP79sWA,7040
5
+ Markdown-3.7.dist-info/RECORD,,
6
+ Markdown-3.7.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
7
+ Markdown-3.7.dist-info/entry_points.txt,sha256=lMEyiiA_ZZyfPCBlDviBl-SiU0cfoeuEKpwxw361sKQ,1102
8
+ Markdown-3.7.dist-info/top_level.txt,sha256=IAxs8x618RXoH1uCqeLLxXsDefJvE_mIibr_M4sOlyk,9
9
+ markdown/__init__.py,sha256=dfzwwdpG9L8QLEPBpLFPIHx_BN056aZXp9xZifTxYIU,1777
10
+ markdown/__main__.py,sha256=innFBxRqwPBNxG1zhKktJji4bnRKtVyYYd30ID13Tcw,5859
11
+ markdown/__meta__.py,sha256=RhwfJ30zyGvJaJXLHwQdNH5jw69-5fVKu2p-CVaJz0U,1712
12
+ markdown/__pycache__/__init__.cpython-312.pyc,,
13
+ markdown/__pycache__/__main__.cpython-312.pyc,,
14
+ markdown/__pycache__/__meta__.cpython-312.pyc,,
15
+ markdown/__pycache__/blockparser.cpython-312.pyc,,
16
+ markdown/__pycache__/blockprocessors.cpython-312.pyc,,
17
+ markdown/__pycache__/core.cpython-312.pyc,,
18
+ markdown/__pycache__/htmlparser.cpython-312.pyc,,
19
+ markdown/__pycache__/inlinepatterns.cpython-312.pyc,,
20
+ markdown/__pycache__/postprocessors.cpython-312.pyc,,
21
+ markdown/__pycache__/preprocessors.cpython-312.pyc,,
22
+ markdown/__pycache__/serializers.cpython-312.pyc,,
23
+ markdown/__pycache__/test_tools.cpython-312.pyc,,
24
+ markdown/__pycache__/treeprocessors.cpython-312.pyc,,
25
+ markdown/__pycache__/util.cpython-312.pyc,,
26
+ markdown/blockparser.py,sha256=j4CQImVpiq7g9pz8wCxvzT61X_T2iSAjXupHJk8P3eA,5728
27
+ markdown/blockprocessors.py,sha256=koY5rq8DixzBCHcquvZJp6x2JYyBGjrwxMWNZhd6D2U,27013
28
+ markdown/core.py,sha256=DyyzDsmd-KcuEp8ZWUKJAeUCt7B7G3J3NeqZqp3LphI,21335
29
+ markdown/extensions/__init__.py,sha256=9z1khsdKCVrmrJ_2GfxtPAdjD3FyMe5vhC7wmM4O9m0,4822
30
+ markdown/extensions/__pycache__/__init__.cpython-312.pyc,,
31
+ markdown/extensions/__pycache__/abbr.cpython-312.pyc,,
32
+ markdown/extensions/__pycache__/admonition.cpython-312.pyc,,
33
+ markdown/extensions/__pycache__/attr_list.cpython-312.pyc,,
34
+ markdown/extensions/__pycache__/codehilite.cpython-312.pyc,,
35
+ markdown/extensions/__pycache__/def_list.cpython-312.pyc,,
36
+ markdown/extensions/__pycache__/extra.cpython-312.pyc,,
37
+ markdown/extensions/__pycache__/fenced_code.cpython-312.pyc,,
38
+ markdown/extensions/__pycache__/footnotes.cpython-312.pyc,,
39
+ markdown/extensions/__pycache__/legacy_attrs.cpython-312.pyc,,
40
+ markdown/extensions/__pycache__/legacy_em.cpython-312.pyc,,
41
+ markdown/extensions/__pycache__/md_in_html.cpython-312.pyc,,
42
+ markdown/extensions/__pycache__/meta.cpython-312.pyc,,
43
+ markdown/extensions/__pycache__/nl2br.cpython-312.pyc,,
44
+ markdown/extensions/__pycache__/sane_lists.cpython-312.pyc,,
45
+ markdown/extensions/__pycache__/smarty.cpython-312.pyc,,
46
+ markdown/extensions/__pycache__/tables.cpython-312.pyc,,
47
+ markdown/extensions/__pycache__/toc.cpython-312.pyc,,
48
+ markdown/extensions/__pycache__/wikilinks.cpython-312.pyc,,
49
+ markdown/extensions/abbr.py,sha256=Gqt9TUtLWez2cbsy3SQk5152RZekops2fUJj01bfkfw,6903
50
+ markdown/extensions/admonition.py,sha256=Hqcn3I8JG0i-OPWdoqI189TmlQRgH6bs5PmpCANyLlg,6547
51
+ markdown/extensions/attr_list.py,sha256=t3PrgAr5Ebldnq3nJNbteBt79bN0ccXS5RemmQfUZ9g,7820
52
+ markdown/extensions/codehilite.py,sha256=ChlmpM6S--j-UK7t82859UpYjm8EftdiLqmgDnknyes,13503
53
+ markdown/extensions/def_list.py,sha256=J3NVa6CllfZPsboJCEycPyRhtjBHnOn8ET6omEvVlDo,4029
54
+ markdown/extensions/extra.py,sha256=1vleT284kued4HQBtF83IjSumJVo0q3ng6MjTkVNfNQ,2163
55
+ markdown/extensions/fenced_code.py,sha256=-fYSmRZ9DTYQ8HO9b_78i47kVyVu6mcVJlqVTMdzvo4,8300
56
+ markdown/extensions/footnotes.py,sha256=bRFlmIBOKDI5efG1jZfDkMoV2osfqWip1rN1j2P-mMg,16710
57
+ markdown/extensions/legacy_attrs.py,sha256=oWcyNrfP0F6zsBoBOaD5NiwrJyy4kCpgQLl12HA7JGU,2788
58
+ markdown/extensions/legacy_em.py,sha256=-Z_w4PEGSS-Xg-2-BtGAnXwwy5g5GDgv2tngASnPgxg,1693
59
+ markdown/extensions/md_in_html.py,sha256=y4HEWEnkvfih22fojcaJeAmjx1AtF8N-a_jb6IDFfts,16546
60
+ markdown/extensions/meta.py,sha256=v_4Uq7nbcQ76V1YAvqVPiNLbRLIQHJsnfsk-tN70RmY,2600
61
+ markdown/extensions/nl2br.py,sha256=9KKcrPs62c3ENNnmOJZs0rrXXqUtTCfd43j1_OPpmgU,1090
62
+ markdown/extensions/sane_lists.py,sha256=ogAKcm7gEpcXV7fSTf8JZH5YdKAssPCEOUzdGM3C9Tw,2150
63
+ markdown/extensions/smarty.py,sha256=yqT0OiE2AqYrqqZtcUFFmp2eJsQHomiKzgyG2JFb9rI,11048
64
+ markdown/extensions/tables.py,sha256=oTDvGD1qp9xjVWPGYNgDBWe9NqsX5gS6UU5wUsQ1bC8,8741
65
+ markdown/extensions/toc.py,sha256=PGg-EqbBubm3n0b633r8Xa9kc6JIdbo20HGAOZ6GEl8,18322
66
+ markdown/extensions/wikilinks.py,sha256=j7D2sozica6sqXOUa_GuAXqIzxp-7Hi60bfXymiuma8,3285
67
+ markdown/htmlparser.py,sha256=dEr6IE7i9b6Tc1gdCLZGeWw6g6-E-jK1Z4KPj8yGk8Q,14332
68
+ markdown/inlinepatterns.py,sha256=7_HF5nTOyQag_CyBgU4wwmuI6aMjtadvGadyS9IP21w,38256
69
+ markdown/postprocessors.py,sha256=eYi6eW0mGudmWpmsW45hduLwX66Zr8Bf44WyU9vKp-I,4807
70
+ markdown/preprocessors.py,sha256=pq5NnHKkOSVQeIo-ajC-Yt44kvyMV97D04FBOQXctJM,3224
71
+ markdown/serializers.py,sha256=YtAFYQoOdp_TAmYGow6nBo0eB6I-Sl4PTLdLDfQJHwQ,7174
72
+ markdown/test_tools.py,sha256=MtN4cf3ZPDtb83wXLTol-3q3aIGRIkJ2zWr6fd-RgVE,8662
73
+ markdown/treeprocessors.py,sha256=o4dnoZZsIeVV8qR45Njr8XgwKleWYDS5pv8dKQhJvv8,17651
74
+ markdown/util.py,sha256=vJ1E0xjMzDAlTqLUSJWgdEvxdQfLXDEYUssOQMw9kPQ,13929
.venv/Lib/site-packages/Markdown-3.7.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (72.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
.venv/Lib/site-packages/Markdown-3.7.dist-info/entry_points.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [console_scripts]
2
+ markdown_py = markdown.__main__:run
3
+
4
+ [markdown.extensions]
5
+ abbr = markdown.extensions.abbr:AbbrExtension
6
+ admonition = markdown.extensions.admonition:AdmonitionExtension
7
+ attr_list = markdown.extensions.attr_list:AttrListExtension
8
+ codehilite = markdown.extensions.codehilite:CodeHiliteExtension
9
+ def_list = markdown.extensions.def_list:DefListExtension
10
+ extra = markdown.extensions.extra:ExtraExtension
11
+ fenced_code = markdown.extensions.fenced_code:FencedCodeExtension
12
+ footnotes = markdown.extensions.footnotes:FootnoteExtension
13
+ legacy_attrs = markdown.extensions.legacy_attrs:LegacyAttrExtension
14
+ legacy_em = markdown.extensions.legacy_em:LegacyEmExtension
15
+ md_in_html = markdown.extensions.md_in_html:MarkdownInHtmlExtension
16
+ meta = markdown.extensions.meta:MetaExtension
17
+ nl2br = markdown.extensions.nl2br:Nl2BrExtension
18
+ sane_lists = markdown.extensions.sane_lists:SaneListExtension
19
+ smarty = markdown.extensions.smarty:SmartyExtension
20
+ tables = markdown.extensions.tables:TableExtension
21
+ toc = markdown.extensions.toc:TocExtension
22
+ wikilinks = markdown.extensions.wikilinks:WikiLinkExtension
.venv/Lib/site-packages/Markdown-3.7.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ markdown
.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2010 Pallets
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: MarkupSafe
3
+ Version: 3.0.2
4
+ Summary: Safely add untrusted strings to HTML/XML markup.
5
+ Maintainer-email: Pallets <[email protected]>
6
+ License: Copyright 2010 Pallets
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions are
10
+ met:
11
+
12
+ 1. Redistributions of source code must retain the above copyright
13
+ notice, this list of conditions and the following disclaimer.
14
+
15
+ 2. Redistributions in binary form must reproduce the above copyright
16
+ notice, this list of conditions and the following disclaimer in the
17
+ documentation and/or other materials provided with the distribution.
18
+
19
+ 3. Neither the name of the copyright holder nor the names of its
20
+ contributors may be used to endorse or promote products derived from
21
+ this software without specific prior written permission.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+
35
+ Project-URL: Donate, https://palletsprojects.com/donate
36
+ Project-URL: Documentation, https://markupsafe.palletsprojects.com/
37
+ Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
38
+ Project-URL: Source, https://github.com/pallets/markupsafe/
39
+ Project-URL: Chat, https://discord.gg/pallets
40
+ Classifier: Development Status :: 5 - Production/Stable
41
+ Classifier: Environment :: Web Environment
42
+ Classifier: Intended Audience :: Developers
43
+ Classifier: License :: OSI Approved :: BSD License
44
+ Classifier: Operating System :: OS Independent
45
+ Classifier: Programming Language :: Python
46
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
47
+ Classifier: Topic :: Text Processing :: Markup :: HTML
48
+ Classifier: Typing :: Typed
49
+ Requires-Python: >=3.9
50
+ Description-Content-Type: text/markdown
51
+ License-File: LICENSE.txt
52
+
53
+ # MarkupSafe
54
+
55
+ MarkupSafe implements a text object that escapes characters so it is
56
+ safe to use in HTML and XML. Characters that have special meanings are
57
+ replaced so that they display as the actual characters. This mitigates
58
+ injection attacks, meaning untrusted user input can safely be displayed
59
+ on a page.
60
+
61
+
62
+ ## Examples
63
+
64
+ ```pycon
65
+ >>> from markupsafe import Markup, escape
66
+
67
+ >>> # escape replaces special characters and wraps in Markup
68
+ >>> escape("<script>alert(document.cookie);</script>")
69
+ Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
70
+
71
+ >>> # wrap in Markup to mark text "safe" and prevent escaping
72
+ >>> Markup("<strong>Hello</strong>")
73
+ Markup('<strong>hello</strong>')
74
+
75
+ >>> escape(Markup("<strong>Hello</strong>"))
76
+ Markup('<strong>hello</strong>')
77
+
78
+ >>> # Markup is a str subclass
79
+ >>> # methods and operators escape their arguments
80
+ >>> template = Markup("Hello <em>{name}</em>")
81
+ >>> template.format(name='"World"')
82
+ Markup('Hello <em>&#34;World&#34;</em>')
83
+ ```
84
+
85
+ ## Donate
86
+
87
+ The Pallets organization develops and supports MarkupSafe and other
88
+ popular packages. In order to grow the community of contributors and
89
+ users, and allow the maintainers to devote more time to the projects,
90
+ [please donate today][].
91
+
92
+ [please donate today]: https://palletsprojects.com/donate
.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
3
+ MarkupSafe-3.0.2.dist-info/METADATA,sha256=nhoabjupBG41j_JxPCJ3ylgrZ6Fx8oMCFbiLF9Kafqc,4067
4
+ MarkupSafe-3.0.2.dist-info/RECORD,,
5
+ MarkupSafe-3.0.2.dist-info/WHEEL,sha256=62QJgqtUFevqILau0n0UncooEMoOyVCKVQitJpcuCig,101
6
+ MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
7
+ markupsafe/__init__.py,sha256=pREerPwvinB62tNCMOwqxBS2YHV6R52Wcq1d-rB4Z5o,13609
8
+ markupsafe/__pycache__/__init__.cpython-312.pyc,,
9
+ markupsafe/__pycache__/_native.cpython-312.pyc,,
10
+ markupsafe/_native.py,sha256=2ptkJ40yCcp9kq3L1NqpgjfpZB-obniYKFFKUOkHh4Q,218
11
+ markupsafe/_speedups.c,sha256=SglUjn40ti9YgQAO--OgkSyv9tXq9vvaHyVhQows4Ok,4353
12
+ markupsafe/_speedups.cp312-win_amd64.pyd,sha256=sC88mCi7HJOQhbSSrdMPZfdCvi_VBfOzwkVuQ7V6T3M,13312
13
+ markupsafe/_speedups.pyi,sha256=LSDmXYOefH4HVpAXuL8sl7AttLw0oXh1njVoVZp2wqQ,42
14
+ markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.2.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp312-cp312-win_amd64
5
+
.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ markupsafe
.venv/Lib/site-packages/PIL/BdfFontFile.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # bitmap distribution font (bdf) file parser
6
+ #
7
+ # history:
8
+ # 1996-05-16 fl created (as bdf2pil)
9
+ # 1997-08-25 fl converted to FontFile driver
10
+ # 2001-05-25 fl removed bogus __init__ call
11
+ # 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev)
12
+ # 2003-04-22 fl more robustification (from Graham Dumpleton)
13
+ #
14
+ # Copyright (c) 1997-2003 by Secret Labs AB.
15
+ # Copyright (c) 1997-2003 by Fredrik Lundh.
16
+ #
17
+ # See the README file for information on usage and redistribution.
18
+ #
19
+
20
+ """
21
+ Parse X Bitmap Distribution Format (BDF)
22
+ """
23
+ from __future__ import annotations
24
+
25
+ from typing import BinaryIO
26
+
27
+ from . import FontFile, Image
28
+
29
+ bdf_slant = {
30
+ "R": "Roman",
31
+ "I": "Italic",
32
+ "O": "Oblique",
33
+ "RI": "Reverse Italic",
34
+ "RO": "Reverse Oblique",
35
+ "OT": "Other",
36
+ }
37
+
38
+ bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
39
+
40
+
41
+ def bdf_char(
42
+ f: BinaryIO,
43
+ ) -> (
44
+ tuple[
45
+ str,
46
+ int,
47
+ tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]],
48
+ Image.Image,
49
+ ]
50
+ | None
51
+ ):
52
+ # skip to STARTCHAR
53
+ while True:
54
+ s = f.readline()
55
+ if not s:
56
+ return None
57
+ if s[:9] == b"STARTCHAR":
58
+ break
59
+ id = s[9:].strip().decode("ascii")
60
+
61
+ # load symbol properties
62
+ props = {}
63
+ while True:
64
+ s = f.readline()
65
+ if not s or s[:6] == b"BITMAP":
66
+ break
67
+ i = s.find(b" ")
68
+ props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
69
+
70
+ # load bitmap
71
+ bitmap = bytearray()
72
+ while True:
73
+ s = f.readline()
74
+ if not s or s[:7] == b"ENDCHAR":
75
+ break
76
+ bitmap += s[:-1]
77
+
78
+ # The word BBX
79
+ # followed by the width in x (BBw), height in y (BBh),
80
+ # and x and y displacement (BBxoff0, BByoff0)
81
+ # of the lower left corner from the origin of the character.
82
+ width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split())
83
+
84
+ # The word DWIDTH
85
+ # followed by the width in x and y of the character in device pixels.
86
+ dwx, dwy = (int(p) for p in props["DWIDTH"].split())
87
+
88
+ bbox = (
89
+ (dwx, dwy),
90
+ (x_disp, -y_disp - height, width + x_disp, -y_disp),
91
+ (0, 0, width, height),
92
+ )
93
+
94
+ try:
95
+ im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
96
+ except ValueError:
97
+ # deal with zero-width characters
98
+ im = Image.new("1", (width, height))
99
+
100
+ return id, int(props["ENCODING"]), bbox, im
101
+
102
+
103
+ class BdfFontFile(FontFile.FontFile):
104
+ """Font file plugin for the X11 BDF format."""
105
+
106
+ def __init__(self, fp: BinaryIO):
107
+ super().__init__()
108
+
109
+ s = fp.readline()
110
+ if s[:13] != b"STARTFONT 2.1":
111
+ msg = "not a valid BDF file"
112
+ raise SyntaxError(msg)
113
+
114
+ props = {}
115
+ comments = []
116
+
117
+ while True:
118
+ s = fp.readline()
119
+ if not s or s[:13] == b"ENDPROPERTIES":
120
+ break
121
+ i = s.find(b" ")
122
+ props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
123
+ if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
124
+ if s.find(b"LogicalFontDescription") < 0:
125
+ comments.append(s[i + 1 : -1].decode("ascii"))
126
+
127
+ while True:
128
+ c = bdf_char(fp)
129
+ if not c:
130
+ break
131
+ id, ch, (xy, dst, src), im = c
132
+ if 0 <= ch < len(self.glyph):
133
+ self.glyph[ch] = xy, dst, src, im
.venv/Lib/site-packages/PIL/BlpImagePlugin.py ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Blizzard Mipmap Format (.blp)
3
+ Jerome Leclanche <[email protected]>
4
+
5
+ The contents of this file are hereby released in the public domain (CC0)
6
+ Full text of the CC0 license:
7
+ https://creativecommons.org/publicdomain/zero/1.0/
8
+
9
+ BLP1 files, used mostly in Warcraft III, are not fully supported.
10
+ All types of BLP2 files used in World of Warcraft are supported.
11
+
12
+ The BLP file structure consists of a header, up to 16 mipmaps of the
13
+ texture
14
+
15
+ Texture sizes must be powers of two, though the two dimensions do
16
+ not have to be equal; 512x256 is valid, but 512x200 is not.
17
+ The first mipmap (mipmap #0) is the full size image; each subsequent
18
+ mipmap halves both dimensions. The final mipmap should be 1x1.
19
+
20
+ BLP files come in many different flavours:
21
+ * JPEG-compressed (type == 0) - only supported for BLP1.
22
+ * RAW images (type == 1, encoding == 1). Each mipmap is stored as an
23
+ array of 8-bit values, one per pixel, left to right, top to bottom.
24
+ Each value is an index to the palette.
25
+ * DXT-compressed (type == 1, encoding == 2):
26
+ - DXT1 compression is used if alpha_encoding == 0.
27
+ - An additional alpha bit is used if alpha_depth == 1.
28
+ - DXT3 compression is used if alpha_encoding == 1.
29
+ - DXT5 compression is used if alpha_encoding == 7.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import struct
35
+ from enum import IntEnum
36
+ from io import BytesIO
37
+
38
+ from . import Image, ImageFile
39
+
40
+
41
+ class Format(IntEnum):
42
+ JPEG = 0
43
+
44
+
45
+ class Encoding(IntEnum):
46
+ UNCOMPRESSED = 1
47
+ DXT = 2
48
+ UNCOMPRESSED_RAW_BGRA = 3
49
+
50
+
51
+ class AlphaEncoding(IntEnum):
52
+ DXT1 = 0
53
+ DXT3 = 1
54
+ DXT5 = 7
55
+
56
+
57
+ def unpack_565(i):
58
+ return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
59
+
60
+
61
+ def decode_dxt1(data, alpha=False):
62
+ """
63
+ input: one "row" of data (i.e. will produce 4*width pixels)
64
+ """
65
+
66
+ blocks = len(data) // 8 # number of blocks in row
67
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
68
+
69
+ for block in range(blocks):
70
+ # Decode next 8-byte block.
71
+ idx = block * 8
72
+ color0, color1, bits = struct.unpack_from("<HHI", data, idx)
73
+
74
+ r0, g0, b0 = unpack_565(color0)
75
+ r1, g1, b1 = unpack_565(color1)
76
+
77
+ # Decode this block into 4x4 pixels
78
+ # Accumulate the results onto our 4 row accumulators
79
+ for j in range(4):
80
+ for i in range(4):
81
+ # get next control op and generate a pixel
82
+
83
+ control = bits & 3
84
+ bits = bits >> 2
85
+
86
+ a = 0xFF
87
+ if control == 0:
88
+ r, g, b = r0, g0, b0
89
+ elif control == 1:
90
+ r, g, b = r1, g1, b1
91
+ elif control == 2:
92
+ if color0 > color1:
93
+ r = (2 * r0 + r1) // 3
94
+ g = (2 * g0 + g1) // 3
95
+ b = (2 * b0 + b1) // 3
96
+ else:
97
+ r = (r0 + r1) // 2
98
+ g = (g0 + g1) // 2
99
+ b = (b0 + b1) // 2
100
+ elif control == 3:
101
+ if color0 > color1:
102
+ r = (2 * r1 + r0) // 3
103
+ g = (2 * g1 + g0) // 3
104
+ b = (2 * b1 + b0) // 3
105
+ else:
106
+ r, g, b, a = 0, 0, 0, 0
107
+
108
+ if alpha:
109
+ ret[j].extend([r, g, b, a])
110
+ else:
111
+ ret[j].extend([r, g, b])
112
+
113
+ return ret
114
+
115
+
116
+ def decode_dxt3(data):
117
+ """
118
+ input: one "row" of data (i.e. will produce 4*width pixels)
119
+ """
120
+
121
+ blocks = len(data) // 16 # number of blocks in row
122
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
123
+
124
+ for block in range(blocks):
125
+ idx = block * 16
126
+ block = data[idx : idx + 16]
127
+ # Decode next 16-byte block.
128
+ bits = struct.unpack_from("<8B", block)
129
+ color0, color1 = struct.unpack_from("<HH", block, 8)
130
+
131
+ (code,) = struct.unpack_from("<I", block, 12)
132
+
133
+ r0, g0, b0 = unpack_565(color0)
134
+ r1, g1, b1 = unpack_565(color1)
135
+
136
+ for j in range(4):
137
+ high = False # Do we want the higher bits?
138
+ for i in range(4):
139
+ alphacode_index = (4 * j + i) // 2
140
+ a = bits[alphacode_index]
141
+ if high:
142
+ high = False
143
+ a >>= 4
144
+ else:
145
+ high = True
146
+ a &= 0xF
147
+ a *= 17 # We get a value between 0 and 15
148
+
149
+ color_code = (code >> 2 * (4 * j + i)) & 0x03
150
+
151
+ if color_code == 0:
152
+ r, g, b = r0, g0, b0
153
+ elif color_code == 1:
154
+ r, g, b = r1, g1, b1
155
+ elif color_code == 2:
156
+ r = (2 * r0 + r1) // 3
157
+ g = (2 * g0 + g1) // 3
158
+ b = (2 * b0 + b1) // 3
159
+ elif color_code == 3:
160
+ r = (2 * r1 + r0) // 3
161
+ g = (2 * g1 + g0) // 3
162
+ b = (2 * b1 + b0) // 3
163
+
164
+ ret[j].extend([r, g, b, a])
165
+
166
+ return ret
167
+
168
+
169
+ def decode_dxt5(data):
170
+ """
171
+ input: one "row" of data (i.e. will produce 4 * width pixels)
172
+ """
173
+
174
+ blocks = len(data) // 16 # number of blocks in row
175
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
176
+
177
+ for block in range(blocks):
178
+ idx = block * 16
179
+ block = data[idx : idx + 16]
180
+ # Decode next 16-byte block.
181
+ a0, a1 = struct.unpack_from("<BB", block)
182
+
183
+ bits = struct.unpack_from("<6B", block, 2)
184
+ alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
185
+ alphacode2 = bits[0] | (bits[1] << 8)
186
+
187
+ color0, color1 = struct.unpack_from("<HH", block, 8)
188
+
189
+ (code,) = struct.unpack_from("<I", block, 12)
190
+
191
+ r0, g0, b0 = unpack_565(color0)
192
+ r1, g1, b1 = unpack_565(color1)
193
+
194
+ for j in range(4):
195
+ for i in range(4):
196
+ # get next control op and generate a pixel
197
+ alphacode_index = 3 * (4 * j + i)
198
+
199
+ if alphacode_index <= 12:
200
+ alphacode = (alphacode2 >> alphacode_index) & 0x07
201
+ elif alphacode_index == 15:
202
+ alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
203
+ else: # alphacode_index >= 18 and alphacode_index <= 45
204
+ alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
205
+
206
+ if alphacode == 0:
207
+ a = a0
208
+ elif alphacode == 1:
209
+ a = a1
210
+ elif a0 > a1:
211
+ a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
212
+ elif alphacode == 6:
213
+ a = 0
214
+ elif alphacode == 7:
215
+ a = 255
216
+ else:
217
+ a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
218
+
219
+ color_code = (code >> 2 * (4 * j + i)) & 0x03
220
+
221
+ if color_code == 0:
222
+ r, g, b = r0, g0, b0
223
+ elif color_code == 1:
224
+ r, g, b = r1, g1, b1
225
+ elif color_code == 2:
226
+ r = (2 * r0 + r1) // 3
227
+ g = (2 * g0 + g1) // 3
228
+ b = (2 * b0 + b1) // 3
229
+ elif color_code == 3:
230
+ r = (2 * r1 + r0) // 3
231
+ g = (2 * g1 + g0) // 3
232
+ b = (2 * b1 + b0) // 3
233
+
234
+ ret[j].extend([r, g, b, a])
235
+
236
+ return ret
237
+
238
+
239
+ class BLPFormatError(NotImplementedError):
240
+ pass
241
+
242
+
243
+ def _accept(prefix):
244
+ return prefix[:4] in (b"BLP1", b"BLP2")
245
+
246
+
247
+ class BlpImageFile(ImageFile.ImageFile):
248
+ """
249
+ Blizzard Mipmap Format
250
+ """
251
+
252
+ format = "BLP"
253
+ format_description = "Blizzard Mipmap Format"
254
+
255
+ def _open(self):
256
+ self.magic = self.fp.read(4)
257
+
258
+ self.fp.seek(5, os.SEEK_CUR)
259
+ (self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
260
+
261
+ self.fp.seek(2, os.SEEK_CUR)
262
+ self._size = struct.unpack("<II", self.fp.read(8))
263
+
264
+ if self.magic in (b"BLP1", b"BLP2"):
265
+ decoder = self.magic.decode()
266
+ else:
267
+ msg = f"Bad BLP magic {repr(self.magic)}"
268
+ raise BLPFormatError(msg)
269
+
270
+ self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
271
+ self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
272
+
273
+
274
+ class _BLPBaseDecoder(ImageFile.PyDecoder):
275
+ _pulls_fd = True
276
+
277
+ def decode(self, buffer):
278
+ try:
279
+ self._read_blp_header()
280
+ self._load()
281
+ except struct.error as e:
282
+ msg = "Truncated BLP file"
283
+ raise OSError(msg) from e
284
+ return -1, 0
285
+
286
+ def _read_blp_header(self):
287
+ self.fd.seek(4)
288
+ (self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
289
+
290
+ (self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
291
+ (self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
292
+ (self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
293
+ self.fd.seek(1, os.SEEK_CUR) # mips
294
+
295
+ self.size = struct.unpack("<II", self._safe_read(8))
296
+
297
+ if isinstance(self, BLP1Decoder):
298
+ # Only present for BLP1
299
+ (self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
300
+ self.fd.seek(4, os.SEEK_CUR) # subtype
301
+
302
+ self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
303
+ self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
304
+
305
+ def _safe_read(self, length):
306
+ return ImageFile._safe_read(self.fd, length)
307
+
308
+ def _read_palette(self):
309
+ ret = []
310
+ for i in range(256):
311
+ try:
312
+ b, g, r, a = struct.unpack("<4B", self._safe_read(4))
313
+ except struct.error:
314
+ break
315
+ ret.append((b, g, r, a))
316
+ return ret
317
+
318
+ def _read_bgra(self, palette):
319
+ data = bytearray()
320
+ _data = BytesIO(self._safe_read(self._blp_lengths[0]))
321
+ while True:
322
+ try:
323
+ (offset,) = struct.unpack("<B", _data.read(1))
324
+ except struct.error:
325
+ break
326
+ b, g, r, a = palette[offset]
327
+ d = (r, g, b)
328
+ if self._blp_alpha_depth:
329
+ d += (a,)
330
+ data.extend(d)
331
+ return data
332
+
333
+
334
+ class BLP1Decoder(_BLPBaseDecoder):
335
+ def _load(self):
336
+ if self._blp_compression == Format.JPEG:
337
+ self._decode_jpeg_stream()
338
+
339
+ elif self._blp_compression == 1:
340
+ if self._blp_encoding in (4, 5):
341
+ palette = self._read_palette()
342
+ data = self._read_bgra(palette)
343
+ self.set_as_raw(bytes(data))
344
+ else:
345
+ msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
346
+ raise BLPFormatError(msg)
347
+ else:
348
+ msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
349
+ raise BLPFormatError(msg)
350
+
351
+ def _decode_jpeg_stream(self):
352
+ from .JpegImagePlugin import JpegImageFile
353
+
354
+ (jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
355
+ jpeg_header = self._safe_read(jpeg_header_size)
356
+ self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
357
+ data = self._safe_read(self._blp_lengths[0])
358
+ data = jpeg_header + data
359
+ data = BytesIO(data)
360
+ image = JpegImageFile(data)
361
+ Image._decompression_bomb_check(image.size)
362
+ if image.mode == "CMYK":
363
+ decoder_name, extents, offset, args = image.tile[0]
364
+ image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
365
+ r, g, b = image.convert("RGB").split()
366
+ image = Image.merge("RGB", (b, g, r))
367
+ self.set_as_raw(image.tobytes())
368
+
369
+
370
+ class BLP2Decoder(_BLPBaseDecoder):
371
+ def _load(self):
372
+ palette = self._read_palette()
373
+
374
+ self.fd.seek(self._blp_offsets[0])
375
+
376
+ if self._blp_compression == 1:
377
+ # Uncompressed or DirectX compression
378
+
379
+ if self._blp_encoding == Encoding.UNCOMPRESSED:
380
+ data = self._read_bgra(palette)
381
+
382
+ elif self._blp_encoding == Encoding.DXT:
383
+ data = bytearray()
384
+ if self._blp_alpha_encoding == AlphaEncoding.DXT1:
385
+ linesize = (self.size[0] + 3) // 4 * 8
386
+ for yb in range((self.size[1] + 3) // 4):
387
+ for d in decode_dxt1(
388
+ self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
389
+ ):
390
+ data += d
391
+
392
+ elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
393
+ linesize = (self.size[0] + 3) // 4 * 16
394
+ for yb in range((self.size[1] + 3) // 4):
395
+ for d in decode_dxt3(self._safe_read(linesize)):
396
+ data += d
397
+
398
+ elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
399
+ linesize = (self.size[0] + 3) // 4 * 16
400
+ for yb in range((self.size[1] + 3) // 4):
401
+ for d in decode_dxt5(self._safe_read(linesize)):
402
+ data += d
403
+ else:
404
+ msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
405
+ raise BLPFormatError(msg)
406
+ else:
407
+ msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
408
+ raise BLPFormatError(msg)
409
+
410
+ else:
411
+ msg = f"Unknown BLP compression {repr(self._blp_compression)}"
412
+ raise BLPFormatError(msg)
413
+
414
+ self.set_as_raw(bytes(data))
415
+
416
+
417
+ class BLPEncoder(ImageFile.PyEncoder):
418
+ _pushes_fd = True
419
+
420
+ def _write_palette(self):
421
+ data = b""
422
+ palette = self.im.getpalette("RGBA", "RGBA")
423
+ for i in range(len(palette) // 4):
424
+ r, g, b, a = palette[i * 4 : (i + 1) * 4]
425
+ data += struct.pack("<4B", b, g, r, a)
426
+ while len(data) < 256 * 4:
427
+ data += b"\x00" * 4
428
+ return data
429
+
430
+ def encode(self, bufsize):
431
+ palette_data = self._write_palette()
432
+
433
+ offset = 20 + 16 * 4 * 2 + len(palette_data)
434
+ data = struct.pack("<16I", offset, *((0,) * 15))
435
+
436
+ w, h = self.im.size
437
+ data += struct.pack("<16I", w * h, *((0,) * 15))
438
+
439
+ data += palette_data
440
+
441
+ for y in range(h):
442
+ for x in range(w):
443
+ data += struct.pack("<B", self.im.getpixel((x, y)))
444
+
445
+ return len(data), 0, data
446
+
447
+
448
+ def _save(im, fp, filename):
449
+ if im.mode != "P":
450
+ msg = "Unsupported BLP image mode"
451
+ raise ValueError(msg)
452
+
453
+ magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
454
+ fp.write(magic)
455
+
456
+ fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
457
+ fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
458
+ fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
459
+ fp.write(struct.pack("<b", 0)) # alpha encoding
460
+ fp.write(struct.pack("<b", 0)) # mips
461
+ fp.write(struct.pack("<II", *im.size))
462
+ if magic == b"BLP1":
463
+ fp.write(struct.pack("<i", 5))
464
+ fp.write(struct.pack("<i", 0))
465
+
466
+ ImageFile._save(im, fp, [("BLP", (0, 0) + im.size, 0, im.mode)])
467
+
468
+
469
+ Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
470
+ Image.register_extension(BlpImageFile.format, ".blp")
471
+ Image.register_decoder("BLP1", BLP1Decoder)
472
+ Image.register_decoder("BLP2", BLP2Decoder)
473
+
474
+ Image.register_save(BlpImageFile.format, _save)
475
+ Image.register_encoder("BLP", BLPEncoder)
.venv/Lib/site-packages/PIL/BmpImagePlugin.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # BMP file handler
6
+ #
7
+ # Windows (and OS/2) native bitmap storage format.
8
+ #
9
+ # history:
10
+ # 1995-09-01 fl Created
11
+ # 1996-04-30 fl Added save
12
+ # 1997-08-27 fl Fixed save of 1-bit images
13
+ # 1998-03-06 fl Load P images as L where possible
14
+ # 1998-07-03 fl Load P images as 1 where possible
15
+ # 1998-12-29 fl Handle small palettes
16
+ # 2002-12-30 fl Fixed load of 1-bit palette images
17
+ # 2003-04-21 fl Fixed load of 1-bit monochrome images
18
+ # 2003-04-23 fl Added limited support for BI_BITFIELDS compression
19
+ #
20
+ # Copyright (c) 1997-2003 by Secret Labs AB
21
+ # Copyright (c) 1995-2003 by Fredrik Lundh
22
+ #
23
+ # See the README file for information on usage and redistribution.
24
+ #
25
+ from __future__ import annotations
26
+
27
+ import os
28
+
29
+ from . import Image, ImageFile, ImagePalette
30
+ from ._binary import i16le as i16
31
+ from ._binary import i32le as i32
32
+ from ._binary import o8
33
+ from ._binary import o16le as o16
34
+ from ._binary import o32le as o32
35
+
36
+ #
37
+ # --------------------------------------------------------------------
38
+ # Read BMP file
39
+
40
+ BIT2MODE = {
41
+ # bits => mode, rawmode
42
+ 1: ("P", "P;1"),
43
+ 4: ("P", "P;4"),
44
+ 8: ("P", "P"),
45
+ 16: ("RGB", "BGR;15"),
46
+ 24: ("RGB", "BGR"),
47
+ 32: ("RGB", "BGRX"),
48
+ }
49
+
50
+
51
+ def _accept(prefix):
52
+ return prefix[:2] == b"BM"
53
+
54
+
55
+ def _dib_accept(prefix):
56
+ return i32(prefix) in [12, 40, 64, 108, 124]
57
+
58
+
59
+ # =============================================================================
60
+ # Image plugin for the Windows BMP format.
61
+ # =============================================================================
62
+ class BmpImageFile(ImageFile.ImageFile):
63
+ """Image plugin for the Windows Bitmap format (BMP)"""
64
+
65
+ # ------------------------------------------------------------- Description
66
+ format_description = "Windows Bitmap"
67
+ format = "BMP"
68
+
69
+ # -------------------------------------------------- BMP Compression values
70
+ COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
71
+ for k, v in COMPRESSIONS.items():
72
+ vars()[k] = v
73
+
74
+ def _bitmap(self, header=0, offset=0):
75
+ """Read relevant info about the BMP"""
76
+ read, seek = self.fp.read, self.fp.seek
77
+ if header:
78
+ seek(header)
79
+ # read bmp header size @offset 14 (this is part of the header size)
80
+ file_info = {"header_size": i32(read(4)), "direction": -1}
81
+
82
+ # -------------------- If requested, read header at a specific position
83
+ # read the rest of the bmp header, without its size
84
+ header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
85
+
86
+ # -------------------------------------------------- IBM OS/2 Bitmap v1
87
+ # ----- This format has different offsets because of width/height types
88
+ if file_info["header_size"] == 12:
89
+ file_info["width"] = i16(header_data, 0)
90
+ file_info["height"] = i16(header_data, 2)
91
+ file_info["planes"] = i16(header_data, 4)
92
+ file_info["bits"] = i16(header_data, 6)
93
+ file_info["compression"] = self.RAW
94
+ file_info["palette_padding"] = 3
95
+
96
+ # --------------------------------------------- Windows Bitmap v2 to v5
97
+ # v3, OS/2 v2, v4, v5
98
+ elif file_info["header_size"] in (40, 64, 108, 124):
99
+ file_info["y_flip"] = header_data[7] == 0xFF
100
+ file_info["direction"] = 1 if file_info["y_flip"] else -1
101
+ file_info["width"] = i32(header_data, 0)
102
+ file_info["height"] = (
103
+ i32(header_data, 4)
104
+ if not file_info["y_flip"]
105
+ else 2**32 - i32(header_data, 4)
106
+ )
107
+ file_info["planes"] = i16(header_data, 8)
108
+ file_info["bits"] = i16(header_data, 10)
109
+ file_info["compression"] = i32(header_data, 12)
110
+ # byte size of pixel data
111
+ file_info["data_size"] = i32(header_data, 16)
112
+ file_info["pixels_per_meter"] = (
113
+ i32(header_data, 20),
114
+ i32(header_data, 24),
115
+ )
116
+ file_info["colors"] = i32(header_data, 28)
117
+ file_info["palette_padding"] = 4
118
+ self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
119
+ if file_info["compression"] == self.BITFIELDS:
120
+ if len(header_data) >= 52:
121
+ for idx, mask in enumerate(
122
+ ["r_mask", "g_mask", "b_mask", "a_mask"]
123
+ ):
124
+ file_info[mask] = i32(header_data, 36 + idx * 4)
125
+ else:
126
+ # 40 byte headers only have the three components in the
127
+ # bitfields masks, ref:
128
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
129
+ # See also
130
+ # https://github.com/python-pillow/Pillow/issues/1293
131
+ # There is a 4th component in the RGBQuad, in the alpha
132
+ # location, but it is listed as a reserved component,
133
+ # and it is not generally an alpha channel
134
+ file_info["a_mask"] = 0x0
135
+ for mask in ["r_mask", "g_mask", "b_mask"]:
136
+ file_info[mask] = i32(read(4))
137
+ file_info["rgb_mask"] = (
138
+ file_info["r_mask"],
139
+ file_info["g_mask"],
140
+ file_info["b_mask"],
141
+ )
142
+ file_info["rgba_mask"] = (
143
+ file_info["r_mask"],
144
+ file_info["g_mask"],
145
+ file_info["b_mask"],
146
+ file_info["a_mask"],
147
+ )
148
+ else:
149
+ msg = f"Unsupported BMP header type ({file_info['header_size']})"
150
+ raise OSError(msg)
151
+
152
+ # ------------------ Special case : header is reported 40, which
153
+ # ---------------------- is shorter than real size for bpp >= 16
154
+ self._size = file_info["width"], file_info["height"]
155
+
156
+ # ------- If color count was not found in the header, compute from bits
157
+ file_info["colors"] = (
158
+ file_info["colors"]
159
+ if file_info.get("colors", 0)
160
+ else (1 << file_info["bits"])
161
+ )
162
+ if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
163
+ offset += 4 * file_info["colors"]
164
+
165
+ # ---------------------- Check bit depth for unusual unsupported values
166
+ self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
167
+ if self.mode is None:
168
+ msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
169
+ raise OSError(msg)
170
+
171
+ # ---------------- Process BMP with Bitfields compression (not palette)
172
+ decoder_name = "raw"
173
+ if file_info["compression"] == self.BITFIELDS:
174
+ SUPPORTED = {
175
+ 32: [
176
+ (0xFF0000, 0xFF00, 0xFF, 0x0),
177
+ (0xFF000000, 0xFF0000, 0xFF00, 0x0),
178
+ (0xFF000000, 0xFF0000, 0xFF00, 0xFF),
179
+ (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
180
+ (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
181
+ (0x0, 0x0, 0x0, 0x0),
182
+ ],
183
+ 24: [(0xFF0000, 0xFF00, 0xFF)],
184
+ 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
185
+ }
186
+ MASK_MODES = {
187
+ (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
188
+ (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
189
+ (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
190
+ (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
191
+ (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
192
+ (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
193
+ (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
194
+ (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
195
+ (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
196
+ }
197
+ if file_info["bits"] in SUPPORTED:
198
+ if (
199
+ file_info["bits"] == 32
200
+ and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
201
+ ):
202
+ raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
203
+ self._mode = "RGBA" if "A" in raw_mode else self.mode
204
+ elif (
205
+ file_info["bits"] in (24, 16)
206
+ and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
207
+ ):
208
+ raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
209
+ else:
210
+ msg = "Unsupported BMP bitfields layout"
211
+ raise OSError(msg)
212
+ else:
213
+ msg = "Unsupported BMP bitfields layout"
214
+ raise OSError(msg)
215
+ elif file_info["compression"] == self.RAW:
216
+ if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
217
+ raw_mode, self._mode = "BGRA", "RGBA"
218
+ elif file_info["compression"] in (self.RLE8, self.RLE4):
219
+ decoder_name = "bmp_rle"
220
+ else:
221
+ msg = f"Unsupported BMP compression ({file_info['compression']})"
222
+ raise OSError(msg)
223
+
224
+ # --------------- Once the header is processed, process the palette/LUT
225
+ if self.mode == "P": # Paletted for 1, 4 and 8 bit images
226
+ # ---------------------------------------------------- 1-bit images
227
+ if not (0 < file_info["colors"] <= 65536):
228
+ msg = f"Unsupported BMP Palette size ({file_info['colors']})"
229
+ raise OSError(msg)
230
+ else:
231
+ padding = file_info["palette_padding"]
232
+ palette = read(padding * file_info["colors"])
233
+ grayscale = True
234
+ indices = (
235
+ (0, 255)
236
+ if file_info["colors"] == 2
237
+ else list(range(file_info["colors"]))
238
+ )
239
+
240
+ # ----------------- Check if grayscale and ignore palette if so
241
+ for ind, val in enumerate(indices):
242
+ rgb = palette[ind * padding : ind * padding + 3]
243
+ if rgb != o8(val) * 3:
244
+ grayscale = False
245
+
246
+ # ------- If all colors are gray, white or black, ditch palette
247
+ if grayscale:
248
+ self._mode = "1" if file_info["colors"] == 2 else "L"
249
+ raw_mode = self.mode
250
+ else:
251
+ self._mode = "P"
252
+ self.palette = ImagePalette.raw(
253
+ "BGRX" if padding == 4 else "BGR", palette
254
+ )
255
+
256
+ # ---------------------------- Finally set the tile data for the plugin
257
+ self.info["compression"] = file_info["compression"]
258
+ args = [raw_mode]
259
+ if decoder_name == "bmp_rle":
260
+ args.append(file_info["compression"] == self.RLE4)
261
+ else:
262
+ args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
263
+ args.append(file_info["direction"])
264
+ self.tile = [
265
+ (
266
+ decoder_name,
267
+ (0, 0, file_info["width"], file_info["height"]),
268
+ offset or self.fp.tell(),
269
+ tuple(args),
270
+ )
271
+ ]
272
+
273
+ def _open(self):
274
+ """Open file, check magic number and read header"""
275
+ # read 14 bytes: magic number, filesize, reserved, header final offset
276
+ head_data = self.fp.read(14)
277
+ # choke if the file does not have the required magic bytes
278
+ if not _accept(head_data):
279
+ msg = "Not a BMP file"
280
+ raise SyntaxError(msg)
281
+ # read the start position of the BMP image data (u32)
282
+ offset = i32(head_data, 10)
283
+ # load bitmap information (offset=raster info)
284
+ self._bitmap(offset=offset)
285
+
286
+
287
+ class BmpRleDecoder(ImageFile.PyDecoder):
288
+ _pulls_fd = True
289
+
290
+ def decode(self, buffer):
291
+ rle4 = self.args[1]
292
+ data = bytearray()
293
+ x = 0
294
+ while len(data) < self.state.xsize * self.state.ysize:
295
+ pixels = self.fd.read(1)
296
+ byte = self.fd.read(1)
297
+ if not pixels or not byte:
298
+ break
299
+ num_pixels = pixels[0]
300
+ if num_pixels:
301
+ # encoded mode
302
+ if x + num_pixels > self.state.xsize:
303
+ # Too much data for row
304
+ num_pixels = max(0, self.state.xsize - x)
305
+ if rle4:
306
+ first_pixel = o8(byte[0] >> 4)
307
+ second_pixel = o8(byte[0] & 0x0F)
308
+ for index in range(num_pixels):
309
+ if index % 2 == 0:
310
+ data += first_pixel
311
+ else:
312
+ data += second_pixel
313
+ else:
314
+ data += byte * num_pixels
315
+ x += num_pixels
316
+ else:
317
+ if byte[0] == 0:
318
+ # end of line
319
+ while len(data) % self.state.xsize != 0:
320
+ data += b"\x00"
321
+ x = 0
322
+ elif byte[0] == 1:
323
+ # end of bitmap
324
+ break
325
+ elif byte[0] == 2:
326
+ # delta
327
+ bytes_read = self.fd.read(2)
328
+ if len(bytes_read) < 2:
329
+ break
330
+ right, up = self.fd.read(2)
331
+ data += b"\x00" * (right + up * self.state.xsize)
332
+ x = len(data) % self.state.xsize
333
+ else:
334
+ # absolute mode
335
+ if rle4:
336
+ # 2 pixels per byte
337
+ byte_count = byte[0] // 2
338
+ bytes_read = self.fd.read(byte_count)
339
+ for byte_read in bytes_read:
340
+ data += o8(byte_read >> 4)
341
+ data += o8(byte_read & 0x0F)
342
+ else:
343
+ byte_count = byte[0]
344
+ bytes_read = self.fd.read(byte_count)
345
+ data += bytes_read
346
+ if len(bytes_read) < byte_count:
347
+ break
348
+ x += byte[0]
349
+
350
+ # align to 16-bit word boundary
351
+ if self.fd.tell() % 2 != 0:
352
+ self.fd.seek(1, os.SEEK_CUR)
353
+ rawmode = "L" if self.mode == "L" else "P"
354
+ self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
355
+ return -1, 0
356
+
357
+
358
+ # =============================================================================
359
+ # Image plugin for the DIB format (BMP alias)
360
+ # =============================================================================
361
+ class DibImageFile(BmpImageFile):
362
+ format = "DIB"
363
+ format_description = "Windows Bitmap"
364
+
365
+ def _open(self):
366
+ self._bitmap()
367
+
368
+
369
+ #
370
+ # --------------------------------------------------------------------
371
+ # Write BMP file
372
+
373
+
374
+ SAVE = {
375
+ "1": ("1", 1, 2),
376
+ "L": ("L", 8, 256),
377
+ "P": ("P", 8, 256),
378
+ "RGB": ("BGR", 24, 0),
379
+ "RGBA": ("BGRA", 32, 0),
380
+ }
381
+
382
+
383
+ def _dib_save(im, fp, filename):
384
+ _save(im, fp, filename, False)
385
+
386
+
387
+ def _save(im, fp, filename, bitmap_header=True):
388
+ try:
389
+ rawmode, bits, colors = SAVE[im.mode]
390
+ except KeyError as e:
391
+ msg = f"cannot write mode {im.mode} as BMP"
392
+ raise OSError(msg) from e
393
+
394
+ info = im.encoderinfo
395
+
396
+ dpi = info.get("dpi", (96, 96))
397
+
398
+ # 1 meter == 39.3701 inches
399
+ ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
400
+
401
+ stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
402
+ header = 40 # or 64 for OS/2 version 2
403
+ image = stride * im.size[1]
404
+
405
+ if im.mode == "1":
406
+ palette = b"".join(o8(i) * 4 for i in (0, 255))
407
+ elif im.mode == "L":
408
+ palette = b"".join(o8(i) * 4 for i in range(256))
409
+ elif im.mode == "P":
410
+ palette = im.im.getpalette("RGB", "BGRX")
411
+ colors = len(palette) // 4
412
+ else:
413
+ palette = None
414
+
415
+ # bitmap header
416
+ if bitmap_header:
417
+ offset = 14 + header + colors * 4
418
+ file_size = offset + image
419
+ if file_size > 2**32 - 1:
420
+ msg = "File size is too large for the BMP format"
421
+ raise ValueError(msg)
422
+ fp.write(
423
+ b"BM" # file type (magic)
424
+ + o32(file_size) # file size
425
+ + o32(0) # reserved
426
+ + o32(offset) # image data offset
427
+ )
428
+
429
+ # bitmap info header
430
+ fp.write(
431
+ o32(header) # info header size
432
+ + o32(im.size[0]) # width
433
+ + o32(im.size[1]) # height
434
+ + o16(1) # planes
435
+ + o16(bits) # depth
436
+ + o32(0) # compression (0=uncompressed)
437
+ + o32(image) # size of bitmap
438
+ + o32(ppm[0]) # resolution
439
+ + o32(ppm[1]) # resolution
440
+ + o32(colors) # colors used
441
+ + o32(colors) # colors important
442
+ )
443
+
444
+ fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
445
+
446
+ if palette:
447
+ fp.write(palette)
448
+
449
+ ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
450
+
451
+
452
+ #
453
+ # --------------------------------------------------------------------
454
+ # Registry
455
+
456
+
457
+ Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
458
+ Image.register_save(BmpImageFile.format, _save)
459
+
460
+ Image.register_extension(BmpImageFile.format, ".bmp")
461
+
462
+ Image.register_mime(BmpImageFile.format, "image/bmp")
463
+
464
+ Image.register_decoder("bmp_rle", BmpRleDecoder)
465
+
466
+ Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
467
+ Image.register_save(DibImageFile.format, _dib_save)
468
+
469
+ Image.register_extension(DibImageFile.format, ".dib")
470
+
471
+ Image.register_mime(DibImageFile.format, "image/bmp")
.venv/Lib/site-packages/PIL/BufrStubImagePlugin.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # BUFR stub adapter
6
+ #
7
+ # Copyright (c) 1996-2003 by Fredrik Lundh
8
+ #
9
+ # See the README file for information on usage and redistribution.
10
+ #
11
+ from __future__ import annotations
12
+
13
+ from . import Image, ImageFile
14
+
15
+ _handler = None
16
+
17
+
18
+ def register_handler(handler):
19
+ """
20
+ Install application-specific BUFR image handler.
21
+
22
+ :param handler: Handler object.
23
+ """
24
+ global _handler
25
+ _handler = handler
26
+
27
+
28
+ # --------------------------------------------------------------------
29
+ # Image adapter
30
+
31
+
32
+ def _accept(prefix):
33
+ return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
34
+
35
+
36
+ class BufrStubImageFile(ImageFile.StubImageFile):
37
+ format = "BUFR"
38
+ format_description = "BUFR"
39
+
40
+ def _open(self):
41
+ offset = self.fp.tell()
42
+
43
+ if not _accept(self.fp.read(4)):
44
+ msg = "Not a BUFR file"
45
+ raise SyntaxError(msg)
46
+
47
+ self.fp.seek(offset)
48
+
49
+ # make something up
50
+ self._mode = "F"
51
+ self._size = 1, 1
52
+
53
+ loader = self._load()
54
+ if loader:
55
+ loader.open(self)
56
+
57
+ def _load(self):
58
+ return _handler
59
+
60
+
61
+ def _save(im, fp, filename):
62
+ if _handler is None or not hasattr(_handler, "save"):
63
+ msg = "BUFR save handler not installed"
64
+ raise OSError(msg)
65
+ _handler.save(im, fp, filename)
66
+
67
+
68
+ # --------------------------------------------------------------------
69
+ # Registry
70
+
71
+ Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
72
+ Image.register_save(BufrStubImageFile.format, _save)
73
+
74
+ Image.register_extension(BufrStubImageFile.format, ".bufr")
.venv/Lib/site-packages/PIL/ContainerIO.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # a class to read from a container file
6
+ #
7
+ # History:
8
+ # 1995-06-18 fl Created
9
+ # 1995-09-07 fl Added readline(), readlines()
10
+ #
11
+ # Copyright (c) 1997-2001 by Secret Labs AB
12
+ # Copyright (c) 1995 by Fredrik Lundh
13
+ #
14
+ # See the README file for information on usage and redistribution.
15
+ #
16
+ from __future__ import annotations
17
+
18
+ import io
19
+ from typing import IO, AnyStr, Generic, Literal
20
+
21
+
22
+ class ContainerIO(Generic[AnyStr]):
23
+ """
24
+ A file object that provides read access to a part of an existing
25
+ file (for example a TAR file).
26
+ """
27
+
28
+ def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
29
+ """
30
+ Create file object.
31
+
32
+ :param file: Existing file.
33
+ :param offset: Start of region, in bytes.
34
+ :param length: Size of region, in bytes.
35
+ """
36
+ self.fh: IO[AnyStr] = file
37
+ self.pos = 0
38
+ self.offset = offset
39
+ self.length = length
40
+ self.fh.seek(offset)
41
+
42
+ ##
43
+ # Always false.
44
+
45
+ def isatty(self) -> bool:
46
+ return False
47
+
48
+ def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
49
+ """
50
+ Move file pointer.
51
+
52
+ :param offset: Offset in bytes.
53
+ :param mode: Starting position. Use 0 for beginning of region, 1
54
+ for current offset, and 2 for end of region. You cannot move
55
+ the pointer outside the defined region.
56
+ """
57
+ if mode == 1:
58
+ self.pos = self.pos + offset
59
+ elif mode == 2:
60
+ self.pos = self.length + offset
61
+ else:
62
+ self.pos = offset
63
+ # clamp
64
+ self.pos = max(0, min(self.pos, self.length))
65
+ self.fh.seek(self.offset + self.pos)
66
+
67
+ def tell(self) -> int:
68
+ """
69
+ Get current file pointer.
70
+
71
+ :returns: Offset from start of region, in bytes.
72
+ """
73
+ return self.pos
74
+
75
+ def read(self, n: int = 0) -> AnyStr:
76
+ """
77
+ Read data.
78
+
79
+ :param n: Number of bytes to read. If omitted or zero,
80
+ read until end of region.
81
+ :returns: An 8-bit string.
82
+ """
83
+ if n:
84
+ n = min(n, self.length - self.pos)
85
+ else:
86
+ n = self.length - self.pos
87
+ if not n: # EOF
88
+ return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
89
+ self.pos = self.pos + n
90
+ return self.fh.read(n)
91
+
92
+ def readline(self) -> AnyStr:
93
+ """
94
+ Read a line of text.
95
+
96
+ :returns: An 8-bit string.
97
+ """
98
+ s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
99
+ newline_character = b"\n" if "b" in self.fh.mode else "\n"
100
+ while True:
101
+ c = self.read(1)
102
+ if not c:
103
+ break
104
+ s = s + c
105
+ if c == newline_character:
106
+ break
107
+ return s
108
+
109
+ def readlines(self) -> list[AnyStr]:
110
+ """
111
+ Read multiple lines of text.
112
+
113
+ :returns: A list of 8-bit strings.
114
+ """
115
+ lines = []
116
+ while True:
117
+ s = self.readline()
118
+ if not s:
119
+ break
120
+ lines.append(s)
121
+ return lines
.venv/Lib/site-packages/PIL/CurImagePlugin.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # Windows Cursor support for PIL
6
+ #
7
+ # notes:
8
+ # uses BmpImagePlugin.py to read the bitmap data.
9
+ #
10
+ # history:
11
+ # 96-05-27 fl Created
12
+ #
13
+ # Copyright (c) Secret Labs AB 1997.
14
+ # Copyright (c) Fredrik Lundh 1996.
15
+ #
16
+ # See the README file for information on usage and redistribution.
17
+ #
18
+ from __future__ import annotations
19
+
20
+ from . import BmpImagePlugin, Image
21
+ from ._binary import i16le as i16
22
+ from ._binary import i32le as i32
23
+
24
+ #
25
+ # --------------------------------------------------------------------
26
+
27
+
28
+ def _accept(prefix):
29
+ return prefix[:4] == b"\0\0\2\0"
30
+
31
+
32
+ ##
33
+ # Image plugin for Windows Cursor files.
34
+
35
+
36
+ class CurImageFile(BmpImagePlugin.BmpImageFile):
37
+ format = "CUR"
38
+ format_description = "Windows Cursor"
39
+
40
+ def _open(self):
41
+ offset = self.fp.tell()
42
+
43
+ # check magic
44
+ s = self.fp.read(6)
45
+ if not _accept(s):
46
+ msg = "not a CUR file"
47
+ raise SyntaxError(msg)
48
+
49
+ # pick the largest cursor in the file
50
+ m = b""
51
+ for i in range(i16(s, 4)):
52
+ s = self.fp.read(16)
53
+ if not m:
54
+ m = s
55
+ elif s[0] > m[0] and s[1] > m[1]:
56
+ m = s
57
+ if not m:
58
+ msg = "No cursors were found"
59
+ raise TypeError(msg)
60
+
61
+ # load as bitmap
62
+ self._bitmap(i32(m, 12) + offset)
63
+
64
+ # patch up the bitmap height
65
+ self._size = self.size[0], self.size[1] // 2
66
+ d, e, o, a = self.tile[0]
67
+ self.tile[0] = d, (0, 0) + self.size, o, a
68
+
69
+
70
+ #
71
+ # --------------------------------------------------------------------
72
+
73
+ Image.register_open(CurImageFile.format, CurImageFile, _accept)
74
+
75
+ Image.register_extension(CurImageFile.format, ".cur")
.venv/Lib/site-packages/PIL/DcxImagePlugin.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # DCX file handling
6
+ #
7
+ # DCX is a container file format defined by Intel, commonly used
8
+ # for fax applications. Each DCX file consists of a directory
9
+ # (a list of file offsets) followed by a set of (usually 1-bit)
10
+ # PCX files.
11
+ #
12
+ # History:
13
+ # 1995-09-09 fl Created
14
+ # 1996-03-20 fl Properly derived from PcxImageFile.
15
+ # 1998-07-15 fl Renamed offset attribute to avoid name clash
16
+ # 2002-07-30 fl Fixed file handling
17
+ #
18
+ # Copyright (c) 1997-98 by Secret Labs AB.
19
+ # Copyright (c) 1995-96 by Fredrik Lundh.
20
+ #
21
+ # See the README file for information on usage and redistribution.
22
+ #
23
+ from __future__ import annotations
24
+
25
+ from . import Image
26
+ from ._binary import i32le as i32
27
+ from .PcxImagePlugin import PcxImageFile
28
+
29
+ MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
30
+
31
+
32
+ def _accept(prefix):
33
+ return len(prefix) >= 4 and i32(prefix) == MAGIC
34
+
35
+
36
+ ##
37
+ # Image plugin for the Intel DCX format.
38
+
39
+
40
+ class DcxImageFile(PcxImageFile):
41
+ format = "DCX"
42
+ format_description = "Intel DCX"
43
+ _close_exclusive_fp_after_loading = False
44
+
45
+ def _open(self):
46
+ # Header
47
+ s = self.fp.read(4)
48
+ if not _accept(s):
49
+ msg = "not a DCX file"
50
+ raise SyntaxError(msg)
51
+
52
+ # Component directory
53
+ self._offset = []
54
+ for i in range(1024):
55
+ offset = i32(self.fp.read(4))
56
+ if not offset:
57
+ break
58
+ self._offset.append(offset)
59
+
60
+ self._fp = self.fp
61
+ self.frame = None
62
+ self.n_frames = len(self._offset)
63
+ self.is_animated = self.n_frames > 1
64
+ self.seek(0)
65
+
66
+ def seek(self, frame):
67
+ if not self._seek_check(frame):
68
+ return
69
+ self.frame = frame
70
+ self.fp = self._fp
71
+ self.fp.seek(self._offset[frame])
72
+ PcxImageFile._open(self)
73
+
74
+ def tell(self):
75
+ return self.frame
76
+
77
+
78
+ Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
79
+
80
+ Image.register_extension(DcxImageFile.format, ".dcx")
.venv/Lib/site-packages/PIL/DdsImagePlugin.py ADDED
@@ -0,0 +1,566 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A Pillow loader for .dds files (S3TC-compressed aka DXTC)
3
+ Jerome Leclanche <[email protected]>
4
+
5
+ Documentation:
6
+ https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
7
+
8
+ The contents of this file are hereby released in the public domain (CC0)
9
+ Full text of the CC0 license:
10
+ https://creativecommons.org/publicdomain/zero/1.0/
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import io
15
+ import struct
16
+ import sys
17
+ from enum import IntEnum, IntFlag
18
+
19
+ from . import Image, ImageFile, ImagePalette
20
+ from ._binary import i32le as i32
21
+ from ._binary import o8
22
+ from ._binary import o32le as o32
23
+
24
+ # Magic ("DDS ")
25
+ DDS_MAGIC = 0x20534444
26
+
27
+
28
+ # DDS flags
29
+ class DDSD(IntFlag):
30
+ CAPS = 0x1
31
+ HEIGHT = 0x2
32
+ WIDTH = 0x4
33
+ PITCH = 0x8
34
+ PIXELFORMAT = 0x1000
35
+ MIPMAPCOUNT = 0x20000
36
+ LINEARSIZE = 0x80000
37
+ DEPTH = 0x800000
38
+
39
+
40
+ # DDS caps
41
+ class DDSCAPS(IntFlag):
42
+ COMPLEX = 0x8
43
+ TEXTURE = 0x1000
44
+ MIPMAP = 0x400000
45
+
46
+
47
+ class DDSCAPS2(IntFlag):
48
+ CUBEMAP = 0x200
49
+ CUBEMAP_POSITIVEX = 0x400
50
+ CUBEMAP_NEGATIVEX = 0x800
51
+ CUBEMAP_POSITIVEY = 0x1000
52
+ CUBEMAP_NEGATIVEY = 0x2000
53
+ CUBEMAP_POSITIVEZ = 0x4000
54
+ CUBEMAP_NEGATIVEZ = 0x8000
55
+ VOLUME = 0x200000
56
+
57
+
58
+ # Pixel Format
59
+ class DDPF(IntFlag):
60
+ ALPHAPIXELS = 0x1
61
+ ALPHA = 0x2
62
+ FOURCC = 0x4
63
+ PALETTEINDEXED8 = 0x20
64
+ RGB = 0x40
65
+ LUMINANCE = 0x20000
66
+
67
+
68
+ # dxgiformat.h
69
+ class DXGI_FORMAT(IntEnum):
70
+ UNKNOWN = 0
71
+ R32G32B32A32_TYPELESS = 1
72
+ R32G32B32A32_FLOAT = 2
73
+ R32G32B32A32_UINT = 3
74
+ R32G32B32A32_SINT = 4
75
+ R32G32B32_TYPELESS = 5
76
+ R32G32B32_FLOAT = 6
77
+ R32G32B32_UINT = 7
78
+ R32G32B32_SINT = 8
79
+ R16G16B16A16_TYPELESS = 9
80
+ R16G16B16A16_FLOAT = 10
81
+ R16G16B16A16_UNORM = 11
82
+ R16G16B16A16_UINT = 12
83
+ R16G16B16A16_SNORM = 13
84
+ R16G16B16A16_SINT = 14
85
+ R32G32_TYPELESS = 15
86
+ R32G32_FLOAT = 16
87
+ R32G32_UINT = 17
88
+ R32G32_SINT = 18
89
+ R32G8X24_TYPELESS = 19
90
+ D32_FLOAT_S8X24_UINT = 20
91
+ R32_FLOAT_X8X24_TYPELESS = 21
92
+ X32_TYPELESS_G8X24_UINT = 22
93
+ R10G10B10A2_TYPELESS = 23
94
+ R10G10B10A2_UNORM = 24
95
+ R10G10B10A2_UINT = 25
96
+ R11G11B10_FLOAT = 26
97
+ R8G8B8A8_TYPELESS = 27
98
+ R8G8B8A8_UNORM = 28
99
+ R8G8B8A8_UNORM_SRGB = 29
100
+ R8G8B8A8_UINT = 30
101
+ R8G8B8A8_SNORM = 31
102
+ R8G8B8A8_SINT = 32
103
+ R16G16_TYPELESS = 33
104
+ R16G16_FLOAT = 34
105
+ R16G16_UNORM = 35
106
+ R16G16_UINT = 36
107
+ R16G16_SNORM = 37
108
+ R16G16_SINT = 38
109
+ R32_TYPELESS = 39
110
+ D32_FLOAT = 40
111
+ R32_FLOAT = 41
112
+ R32_UINT = 42
113
+ R32_SINT = 43
114
+ R24G8_TYPELESS = 44
115
+ D24_UNORM_S8_UINT = 45
116
+ R24_UNORM_X8_TYPELESS = 46
117
+ X24_TYPELESS_G8_UINT = 47
118
+ R8G8_TYPELESS = 48
119
+ R8G8_UNORM = 49
120
+ R8G8_UINT = 50
121
+ R8G8_SNORM = 51
122
+ R8G8_SINT = 52
123
+ R16_TYPELESS = 53
124
+ R16_FLOAT = 54
125
+ D16_UNORM = 55
126
+ R16_UNORM = 56
127
+ R16_UINT = 57
128
+ R16_SNORM = 58
129
+ R16_SINT = 59
130
+ R8_TYPELESS = 60
131
+ R8_UNORM = 61
132
+ R8_UINT = 62
133
+ R8_SNORM = 63
134
+ R8_SINT = 64
135
+ A8_UNORM = 65
136
+ R1_UNORM = 66
137
+ R9G9B9E5_SHAREDEXP = 67
138
+ R8G8_B8G8_UNORM = 68
139
+ G8R8_G8B8_UNORM = 69
140
+ BC1_TYPELESS = 70
141
+ BC1_UNORM = 71
142
+ BC1_UNORM_SRGB = 72
143
+ BC2_TYPELESS = 73
144
+ BC2_UNORM = 74
145
+ BC2_UNORM_SRGB = 75
146
+ BC3_TYPELESS = 76
147
+ BC3_UNORM = 77
148
+ BC3_UNORM_SRGB = 78
149
+ BC4_TYPELESS = 79
150
+ BC4_UNORM = 80
151
+ BC4_SNORM = 81
152
+ BC5_TYPELESS = 82
153
+ BC5_UNORM = 83
154
+ BC5_SNORM = 84
155
+ B5G6R5_UNORM = 85
156
+ B5G5R5A1_UNORM = 86
157
+ B8G8R8A8_UNORM = 87
158
+ B8G8R8X8_UNORM = 88
159
+ R10G10B10_XR_BIAS_A2_UNORM = 89
160
+ B8G8R8A8_TYPELESS = 90
161
+ B8G8R8A8_UNORM_SRGB = 91
162
+ B8G8R8X8_TYPELESS = 92
163
+ B8G8R8X8_UNORM_SRGB = 93
164
+ BC6H_TYPELESS = 94
165
+ BC6H_UF16 = 95
166
+ BC6H_SF16 = 96
167
+ BC7_TYPELESS = 97
168
+ BC7_UNORM = 98
169
+ BC7_UNORM_SRGB = 99
170
+ AYUV = 100
171
+ Y410 = 101
172
+ Y416 = 102
173
+ NV12 = 103
174
+ P010 = 104
175
+ P016 = 105
176
+ OPAQUE_420 = 106
177
+ YUY2 = 107
178
+ Y210 = 108
179
+ Y216 = 109
180
+ NV11 = 110
181
+ AI44 = 111
182
+ IA44 = 112
183
+ P8 = 113
184
+ A8P8 = 114
185
+ B4G4R4A4_UNORM = 115
186
+ P208 = 130
187
+ V208 = 131
188
+ V408 = 132
189
+ SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
190
+ SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
191
+
192
+
193
+ class D3DFMT(IntEnum):
194
+ UNKNOWN = 0
195
+ R8G8B8 = 20
196
+ A8R8G8B8 = 21
197
+ X8R8G8B8 = 22
198
+ R5G6B5 = 23
199
+ X1R5G5B5 = 24
200
+ A1R5G5B5 = 25
201
+ A4R4G4B4 = 26
202
+ R3G3B2 = 27
203
+ A8 = 28
204
+ A8R3G3B2 = 29
205
+ X4R4G4B4 = 30
206
+ A2B10G10R10 = 31
207
+ A8B8G8R8 = 32
208
+ X8B8G8R8 = 33
209
+ G16R16 = 34
210
+ A2R10G10B10 = 35
211
+ A16B16G16R16 = 36
212
+ A8P8 = 40
213
+ P8 = 41
214
+ L8 = 50
215
+ A8L8 = 51
216
+ A4L4 = 52
217
+ V8U8 = 60
218
+ L6V5U5 = 61
219
+ X8L8V8U8 = 62
220
+ Q8W8V8U8 = 63
221
+ V16U16 = 64
222
+ A2W10V10U10 = 67
223
+ D16_LOCKABLE = 70
224
+ D32 = 71
225
+ D15S1 = 73
226
+ D24S8 = 75
227
+ D24X8 = 77
228
+ D24X4S4 = 79
229
+ D16 = 80
230
+ D32F_LOCKABLE = 82
231
+ D24FS8 = 83
232
+ D32_LOCKABLE = 84
233
+ S8_LOCKABLE = 85
234
+ L16 = 81
235
+ VERTEXDATA = 100
236
+ INDEX16 = 101
237
+ INDEX32 = 102
238
+ Q16W16V16U16 = 110
239
+ R16F = 111
240
+ G16R16F = 112
241
+ A16B16G16R16F = 113
242
+ R32F = 114
243
+ G32R32F = 115
244
+ A32B32G32R32F = 116
245
+ CxV8U8 = 117
246
+ A1 = 118
247
+ A2B10G10R10_XR_BIAS = 119
248
+ BINARYBUFFER = 199
249
+
250
+ UYVY = i32(b"UYVY")
251
+ R8G8_B8G8 = i32(b"RGBG")
252
+ YUY2 = i32(b"YUY2")
253
+ G8R8_G8B8 = i32(b"GRGB")
254
+ DXT1 = i32(b"DXT1")
255
+ DXT2 = i32(b"DXT2")
256
+ DXT3 = i32(b"DXT3")
257
+ DXT4 = i32(b"DXT4")
258
+ DXT5 = i32(b"DXT5")
259
+ DX10 = i32(b"DX10")
260
+ BC4S = i32(b"BC4S")
261
+ BC4U = i32(b"BC4U")
262
+ BC5S = i32(b"BC5S")
263
+ BC5U = i32(b"BC5U")
264
+ ATI1 = i32(b"ATI1")
265
+ ATI2 = i32(b"ATI2")
266
+ MULTI2_ARGB8 = i32(b"MET1")
267
+
268
+
269
+ # Backward compatibility layer
270
+ module = sys.modules[__name__]
271
+ for item in DDSD:
272
+ setattr(module, "DDSD_" + item.name, item.value)
273
+ for item in DDSCAPS:
274
+ setattr(module, "DDSCAPS_" + item.name, item.value)
275
+ for item in DDSCAPS2:
276
+ setattr(module, "DDSCAPS2_" + item.name, item.value)
277
+ for item in DDPF:
278
+ setattr(module, "DDPF_" + item.name, item.value)
279
+
280
+ DDS_FOURCC = DDPF.FOURCC
281
+ DDS_RGB = DDPF.RGB
282
+ DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS
283
+ DDS_LUMINANCE = DDPF.LUMINANCE
284
+ DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS
285
+ DDS_ALPHA = DDPF.ALPHA
286
+ DDS_PAL8 = DDPF.PALETTEINDEXED8
287
+
288
+ DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
289
+ DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT
290
+ DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH
291
+ DDS_HEADER_FLAGS_PITCH = DDSD.PITCH
292
+ DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE
293
+
294
+ DDS_HEIGHT = DDSD.HEIGHT
295
+ DDS_WIDTH = DDSD.WIDTH
296
+
297
+ DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE
298
+ DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP
299
+ DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX
300
+
301
+ DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX
302
+ DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX
303
+ DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY
304
+ DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY
305
+ DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ
306
+ DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ
307
+
308
+ DXT1_FOURCC = D3DFMT.DXT1
309
+ DXT3_FOURCC = D3DFMT.DXT3
310
+ DXT5_FOURCC = D3DFMT.DXT5
311
+
312
+ DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS
313
+ DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM
314
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB
315
+ DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS
316
+ DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM
317
+ DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM
318
+ DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16
319
+ DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16
320
+ DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS
321
+ DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM
322
+ DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB
323
+
324
+
325
+ class DdsImageFile(ImageFile.ImageFile):
326
+ format = "DDS"
327
+ format_description = "DirectDraw Surface"
328
+
329
+ def _open(self):
330
+ if not _accept(self.fp.read(4)):
331
+ msg = "not a DDS file"
332
+ raise SyntaxError(msg)
333
+ (header_size,) = struct.unpack("<I", self.fp.read(4))
334
+ if header_size != 124:
335
+ msg = f"Unsupported header size {repr(header_size)}"
336
+ raise OSError(msg)
337
+ header_bytes = self.fp.read(header_size - 4)
338
+ if len(header_bytes) != 120:
339
+ msg = f"Incomplete header: {len(header_bytes)} bytes"
340
+ raise OSError(msg)
341
+ header = io.BytesIO(header_bytes)
342
+
343
+ flags, height, width = struct.unpack("<3I", header.read(12))
344
+ self._size = (width, height)
345
+ extents = (0, 0) + self.size
346
+
347
+ pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
348
+ struct.unpack("<11I", header.read(44)) # reserved
349
+
350
+ # pixel format
351
+ pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
352
+ n = 0
353
+ rawmode = None
354
+ if pfflags & DDPF.RGB:
355
+ # Texture contains uncompressed RGB data
356
+ if pfflags & DDPF.ALPHAPIXELS:
357
+ self._mode = "RGBA"
358
+ mask_count = 4
359
+ else:
360
+ self._mode = "RGB"
361
+ mask_count = 3
362
+
363
+ masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
364
+ self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
365
+ return
366
+ elif pfflags & DDPF.LUMINANCE:
367
+ if bitcount == 8:
368
+ self._mode = "L"
369
+ elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS:
370
+ self._mode = "LA"
371
+ else:
372
+ msg = f"Unsupported bitcount {bitcount} for {pfflags}"
373
+ raise OSError(msg)
374
+ elif pfflags & DDPF.PALETTEINDEXED8:
375
+ self._mode = "P"
376
+ self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
377
+ elif pfflags & DDPF.FOURCC:
378
+ offset = header_size + 4
379
+ if fourcc == D3DFMT.DXT1:
380
+ self._mode = "RGBA"
381
+ self.pixel_format = "DXT1"
382
+ n = 1
383
+ elif fourcc == D3DFMT.DXT3:
384
+ self._mode = "RGBA"
385
+ self.pixel_format = "DXT3"
386
+ n = 2
387
+ elif fourcc == D3DFMT.DXT5:
388
+ self._mode = "RGBA"
389
+ self.pixel_format = "DXT5"
390
+ n = 3
391
+ elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
392
+ self._mode = "L"
393
+ self.pixel_format = "BC4"
394
+ n = 4
395
+ elif fourcc == D3DFMT.BC5S:
396
+ self._mode = "RGB"
397
+ self.pixel_format = "BC5S"
398
+ n = 5
399
+ elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
400
+ self._mode = "RGB"
401
+ self.pixel_format = "BC5"
402
+ n = 5
403
+ elif fourcc == D3DFMT.DX10:
404
+ offset += 20
405
+ # ignoring flags which pertain to volume textures and cubemaps
406
+ (dxgi_format,) = struct.unpack("<I", self.fp.read(4))
407
+ self.fp.read(16)
408
+ if dxgi_format in (
409
+ DXGI_FORMAT.BC1_UNORM,
410
+ DXGI_FORMAT.BC1_TYPELESS,
411
+ ):
412
+ self._mode = "RGBA"
413
+ self.pixel_format = "BC1"
414
+ n = 1
415
+ elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
416
+ self._mode = "L"
417
+ self.pixel_format = "BC4"
418
+ n = 4
419
+ elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM):
420
+ self._mode = "RGB"
421
+ self.pixel_format = "BC5"
422
+ n = 5
423
+ elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
424
+ self._mode = "RGB"
425
+ self.pixel_format = "BC5S"
426
+ n = 5
427
+ elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
428
+ self._mode = "RGB"
429
+ self.pixel_format = "BC6H"
430
+ n = 6
431
+ elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
432
+ self._mode = "RGB"
433
+ self.pixel_format = "BC6HS"
434
+ n = 6
435
+ elif dxgi_format in (
436
+ DXGI_FORMAT.BC7_TYPELESS,
437
+ DXGI_FORMAT.BC7_UNORM,
438
+ DXGI_FORMAT.BC7_UNORM_SRGB,
439
+ ):
440
+ self._mode = "RGBA"
441
+ self.pixel_format = "BC7"
442
+ n = 7
443
+ if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB:
444
+ self.info["gamma"] = 1 / 2.2
445
+ elif dxgi_format in (
446
+ DXGI_FORMAT.R8G8B8A8_TYPELESS,
447
+ DXGI_FORMAT.R8G8B8A8_UNORM,
448
+ DXGI_FORMAT.R8G8B8A8_UNORM_SRGB,
449
+ ):
450
+ self._mode = "RGBA"
451
+ if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
452
+ self.info["gamma"] = 1 / 2.2
453
+ else:
454
+ msg = f"Unimplemented DXGI format {dxgi_format}"
455
+ raise NotImplementedError(msg)
456
+ else:
457
+ msg = f"Unimplemented pixel format {repr(fourcc)}"
458
+ raise NotImplementedError(msg)
459
+ else:
460
+ msg = f"Unknown pixel format flags {pfflags}"
461
+ raise NotImplementedError(msg)
462
+
463
+ if n:
464
+ self.tile = [
465
+ ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
466
+ ]
467
+ else:
468
+ self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
469
+
470
+ def load_seek(self, pos):
471
+ pass
472
+
473
+
474
+ class DdsRgbDecoder(ImageFile.PyDecoder):
475
+ _pulls_fd = True
476
+
477
+ def decode(self, buffer):
478
+ bitcount, masks = self.args
479
+
480
+ # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
481
+ # Calculate how many zeros each mask is padded with
482
+ mask_offsets = []
483
+ # And the maximum value of each channel without the padding
484
+ mask_totals = []
485
+ for mask in masks:
486
+ offset = 0
487
+ if mask != 0:
488
+ while mask >> (offset + 1) << (offset + 1) == mask:
489
+ offset += 1
490
+ mask_offsets.append(offset)
491
+ mask_totals.append(mask >> offset)
492
+
493
+ data = bytearray()
494
+ bytecount = bitcount // 8
495
+ while len(data) < self.state.xsize * self.state.ysize * len(masks):
496
+ value = int.from_bytes(self.fd.read(bytecount), "little")
497
+ for i, mask in enumerate(masks):
498
+ masked_value = value & mask
499
+ # Remove the zero padding, and scale it to 8 bits
500
+ data += o8(
501
+ int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
502
+ )
503
+ self.set_as_raw(bytes(data))
504
+ return -1, 0
505
+
506
+
507
+ def _save(im, fp, filename):
508
+ if im.mode not in ("RGB", "RGBA", "L", "LA"):
509
+ msg = f"cannot write mode {im.mode} as DDS"
510
+ raise OSError(msg)
511
+
512
+ alpha = im.mode[-1] == "A"
513
+ if im.mode[0] == "L":
514
+ pixel_flags = DDPF.LUMINANCE
515
+ rawmode = im.mode
516
+ if alpha:
517
+ rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
518
+ else:
519
+ rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
520
+ else:
521
+ pixel_flags = DDPF.RGB
522
+ rawmode = im.mode[::-1]
523
+ rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
524
+
525
+ if alpha:
526
+ r, g, b, a = im.split()
527
+ im = Image.merge("RGBA", (a, r, g, b))
528
+ if alpha:
529
+ pixel_flags |= DDPF.ALPHAPIXELS
530
+ rgba_mask.append(0xFF000000 if alpha else 0)
531
+
532
+ flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
533
+ bitcount = len(im.getbands()) * 8
534
+ pitch = (im.width * bitcount + 7) // 8
535
+
536
+ fp.write(
537
+ o32(DDS_MAGIC)
538
+ + struct.pack(
539
+ "<7I",
540
+ 124, # header size
541
+ flags, # flags
542
+ im.height,
543
+ im.width,
544
+ pitch,
545
+ 0, # depth
546
+ 0, # mipmaps
547
+ )
548
+ + struct.pack("11I", *((0,) * 11)) # reserved
549
+ # pfsize, pfflags, fourcc, bitcount
550
+ + struct.pack("<4I", 32, pixel_flags, 0, bitcount)
551
+ + struct.pack("<4I", *rgba_mask) # dwRGBABitMask
552
+ + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
553
+ )
554
+ ImageFile._save(
555
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
556
+ )
557
+
558
+
559
+ def _accept(prefix):
560
+ return prefix[:4] == b"DDS "
561
+
562
+
563
+ Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
564
+ Image.register_decoder("dds_rgb", DdsRgbDecoder)
565
+ Image.register_save(DdsImageFile.format, _save)
566
+ Image.register_extension(DdsImageFile.format, ".dds")
.venv/Lib/site-packages/PIL/EpsImagePlugin.py ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # EPS file handling
6
+ #
7
+ # History:
8
+ # 1995-09-01 fl Created (0.1)
9
+ # 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
10
+ # 1996-08-22 fl Don't choke on floating point BoundingBox values
11
+ # 1996-08-23 fl Handle files from Macintosh (0.3)
12
+ # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
13
+ # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
14
+ # 2014-05-07 e Handling of EPS with binary preview and fixed resolution
15
+ # resizing
16
+ #
17
+ # Copyright (c) 1997-2003 by Secret Labs AB.
18
+ # Copyright (c) 1995-2003 by Fredrik Lundh
19
+ #
20
+ # See the README file for information on usage and redistribution.
21
+ #
22
+ from __future__ import annotations
23
+
24
+ import io
25
+ import os
26
+ import re
27
+ import subprocess
28
+ import sys
29
+ import tempfile
30
+
31
+ from . import Image, ImageFile
32
+ from ._binary import i32le as i32
33
+ from ._deprecate import deprecate
34
+
35
+ # --------------------------------------------------------------------
36
+
37
+
38
+ split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
39
+ field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
40
+
41
+ gs_binary = None
42
+ gs_windows_binary = None
43
+
44
+
45
+ def has_ghostscript():
46
+ global gs_binary, gs_windows_binary
47
+ if gs_binary is None:
48
+ if sys.platform.startswith("win"):
49
+ if gs_windows_binary is None:
50
+ import shutil
51
+
52
+ for binary in ("gswin32c", "gswin64c", "gs"):
53
+ if shutil.which(binary) is not None:
54
+ gs_windows_binary = binary
55
+ break
56
+ else:
57
+ gs_windows_binary = False
58
+ gs_binary = gs_windows_binary
59
+ else:
60
+ try:
61
+ subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
62
+ gs_binary = "gs"
63
+ except OSError:
64
+ gs_binary = False
65
+ return gs_binary is not False
66
+
67
+
68
+ def Ghostscript(tile, size, fp, scale=1, transparency=False):
69
+ """Render an image using Ghostscript"""
70
+ global gs_binary
71
+ if not has_ghostscript():
72
+ msg = "Unable to locate Ghostscript on paths"
73
+ raise OSError(msg)
74
+
75
+ # Unpack decoder tile
76
+ decoder, tile, offset, data = tile[0]
77
+ length, bbox = data
78
+
79
+ # Hack to support hi-res rendering
80
+ scale = int(scale) or 1
81
+ width = size[0] * scale
82
+ height = size[1] * scale
83
+ # resolution is dependent on bbox and size
84
+ res_x = 72.0 * width / (bbox[2] - bbox[0])
85
+ res_y = 72.0 * height / (bbox[3] - bbox[1])
86
+
87
+ out_fd, outfile = tempfile.mkstemp()
88
+ os.close(out_fd)
89
+
90
+ infile_temp = None
91
+ if hasattr(fp, "name") and os.path.exists(fp.name):
92
+ infile = fp.name
93
+ else:
94
+ in_fd, infile_temp = tempfile.mkstemp()
95
+ os.close(in_fd)
96
+ infile = infile_temp
97
+
98
+ # Ignore length and offset!
99
+ # Ghostscript can read it
100
+ # Copy whole file to read in Ghostscript
101
+ with open(infile_temp, "wb") as f:
102
+ # fetch length of fp
103
+ fp.seek(0, io.SEEK_END)
104
+ fsize = fp.tell()
105
+ # ensure start position
106
+ # go back
107
+ fp.seek(0)
108
+ lengthfile = fsize
109
+ while lengthfile > 0:
110
+ s = fp.read(min(lengthfile, 100 * 1024))
111
+ if not s:
112
+ break
113
+ lengthfile -= len(s)
114
+ f.write(s)
115
+
116
+ device = "pngalpha" if transparency else "ppmraw"
117
+
118
+ # Build Ghostscript command
119
+ command = [
120
+ gs_binary,
121
+ "-q", # quiet mode
122
+ f"-g{width:d}x{height:d}", # set output geometry (pixels)
123
+ f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch)
124
+ "-dBATCH", # exit after processing
125
+ "-dNOPAUSE", # don't pause between pages
126
+ "-dSAFER", # safe mode
127
+ f"-sDEVICE={device}",
128
+ f"-sOutputFile={outfile}", # output file
129
+ # adjust for image origin
130
+ "-c",
131
+ f"{-bbox[0]} {-bbox[1]} translate",
132
+ "-f",
133
+ infile, # input file
134
+ # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
135
+ "-c",
136
+ "showpage",
137
+ ]
138
+
139
+ # push data through Ghostscript
140
+ try:
141
+ startupinfo = None
142
+ if sys.platform.startswith("win"):
143
+ startupinfo = subprocess.STARTUPINFO()
144
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
145
+ subprocess.check_call(command, startupinfo=startupinfo)
146
+ out_im = Image.open(outfile)
147
+ out_im.load()
148
+ finally:
149
+ try:
150
+ os.unlink(outfile)
151
+ if infile_temp:
152
+ os.unlink(infile_temp)
153
+ except OSError:
154
+ pass
155
+
156
+ im = out_im.im.copy()
157
+ out_im.close()
158
+ return im
159
+
160
+
161
+ class PSFile:
162
+ """
163
+ Wrapper for bytesio object that treats either CR or LF as end of line.
164
+ This class is no longer used internally, but kept for backwards compatibility.
165
+ """
166
+
167
+ def __init__(self, fp):
168
+ deprecate(
169
+ "PSFile",
170
+ 11,
171
+ action="If you need the functionality of this class "
172
+ "you will need to implement it yourself.",
173
+ )
174
+ self.fp = fp
175
+ self.char = None
176
+
177
+ def seek(self, offset, whence=io.SEEK_SET):
178
+ self.char = None
179
+ self.fp.seek(offset, whence)
180
+
181
+ def readline(self):
182
+ s = [self.char or b""]
183
+ self.char = None
184
+
185
+ c = self.fp.read(1)
186
+ while (c not in b"\r\n") and len(c):
187
+ s.append(c)
188
+ c = self.fp.read(1)
189
+
190
+ self.char = self.fp.read(1)
191
+ # line endings can be 1 or 2 of \r \n, in either order
192
+ if self.char in b"\r\n":
193
+ self.char = None
194
+
195
+ return b"".join(s).decode("latin-1")
196
+
197
+
198
+ def _accept(prefix):
199
+ return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
200
+
201
+
202
+ ##
203
+ # Image plugin for Encapsulated PostScript. This plugin supports only
204
+ # a few variants of this format.
205
+
206
+
207
+ class EpsImageFile(ImageFile.ImageFile):
208
+ """EPS File Parser for the Python Imaging Library"""
209
+
210
+ format = "EPS"
211
+ format_description = "Encapsulated Postscript"
212
+
213
+ mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
214
+
215
+ def _open(self):
216
+ (length, offset) = self._find_offset(self.fp)
217
+
218
+ # go to offset - start of "%!PS"
219
+ self.fp.seek(offset)
220
+
221
+ self._mode = "RGB"
222
+ self._size = None
223
+
224
+ byte_arr = bytearray(255)
225
+ bytes_mv = memoryview(byte_arr)
226
+ bytes_read = 0
227
+ reading_header_comments = True
228
+ reading_trailer_comments = False
229
+ trailer_reached = False
230
+
231
+ def check_required_header_comments():
232
+ if "PS-Adobe" not in self.info:
233
+ msg = 'EPS header missing "%!PS-Adobe" comment'
234
+ raise SyntaxError(msg)
235
+ if "BoundingBox" not in self.info:
236
+ msg = 'EPS header missing "%%BoundingBox" comment'
237
+ raise SyntaxError(msg)
238
+
239
+ def _read_comment(s):
240
+ nonlocal reading_trailer_comments
241
+ try:
242
+ m = split.match(s)
243
+ except re.error as e:
244
+ msg = "not an EPS file"
245
+ raise SyntaxError(msg) from e
246
+
247
+ if m:
248
+ k, v = m.group(1, 2)
249
+ self.info[k] = v
250
+ if k == "BoundingBox":
251
+ if v == "(atend)":
252
+ reading_trailer_comments = True
253
+ elif not self._size or (
254
+ trailer_reached and reading_trailer_comments
255
+ ):
256
+ try:
257
+ # Note: The DSC spec says that BoundingBox
258
+ # fields should be integers, but some drivers
259
+ # put floating point values there anyway.
260
+ box = [int(float(i)) for i in v.split()]
261
+ self._size = box[2] - box[0], box[3] - box[1]
262
+ self.tile = [
263
+ ("eps", (0, 0) + self.size, offset, (length, box))
264
+ ]
265
+ except Exception:
266
+ pass
267
+ return True
268
+
269
+ while True:
270
+ byte = self.fp.read(1)
271
+ if byte == b"":
272
+ # if we didn't read a byte we must be at the end of the file
273
+ if bytes_read == 0:
274
+ break
275
+ elif byte in b"\r\n":
276
+ # if we read a line ending character, ignore it and parse what
277
+ # we have already read. if we haven't read any other characters,
278
+ # continue reading
279
+ if bytes_read == 0:
280
+ continue
281
+ else:
282
+ # ASCII/hexadecimal lines in an EPS file must not exceed
283
+ # 255 characters, not including line ending characters
284
+ if bytes_read >= 255:
285
+ # only enforce this for lines starting with a "%",
286
+ # otherwise assume it's binary data
287
+ if byte_arr[0] == ord("%"):
288
+ msg = "not an EPS file"
289
+ raise SyntaxError(msg)
290
+ else:
291
+ if reading_header_comments:
292
+ check_required_header_comments()
293
+ reading_header_comments = False
294
+ # reset bytes_read so we can keep reading
295
+ # data until the end of the line
296
+ bytes_read = 0
297
+ byte_arr[bytes_read] = byte[0]
298
+ bytes_read += 1
299
+ continue
300
+
301
+ if reading_header_comments:
302
+ # Load EPS header
303
+
304
+ # if this line doesn't start with a "%",
305
+ # or does start with "%%EndComments",
306
+ # then we've reached the end of the header/comments
307
+ if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
308
+ check_required_header_comments()
309
+ reading_header_comments = False
310
+ continue
311
+
312
+ s = str(bytes_mv[:bytes_read], "latin-1")
313
+ if not _read_comment(s):
314
+ m = field.match(s)
315
+ if m:
316
+ k = m.group(1)
317
+ if k[:8] == "PS-Adobe":
318
+ self.info["PS-Adobe"] = k[9:]
319
+ else:
320
+ self.info[k] = ""
321
+ elif s[0] == "%":
322
+ # handle non-DSC PostScript comments that some
323
+ # tools mistakenly put in the Comments section
324
+ pass
325
+ else:
326
+ msg = "bad EPS header"
327
+ raise OSError(msg)
328
+ elif bytes_mv[:11] == b"%ImageData:":
329
+ # Check for an "ImageData" descriptor
330
+ # https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577413_pgfId-1035096
331
+
332
+ # Values:
333
+ # columns
334
+ # rows
335
+ # bit depth (1 or 8)
336
+ # mode (1: L, 2: LAB, 3: RGB, 4: CMYK)
337
+ # number of padding channels
338
+ # block size (number of bytes per row per channel)
339
+ # binary/ascii (1: binary, 2: ascii)
340
+ # data start identifier (the image data follows after a single line
341
+ # consisting only of this quoted value)
342
+ image_data_values = byte_arr[11:bytes_read].split(None, 7)
343
+ columns, rows, bit_depth, mode_id = (
344
+ int(value) for value in image_data_values[:4]
345
+ )
346
+
347
+ if bit_depth == 1:
348
+ self._mode = "1"
349
+ elif bit_depth == 8:
350
+ try:
351
+ self._mode = self.mode_map[mode_id]
352
+ except ValueError:
353
+ break
354
+ else:
355
+ break
356
+
357
+ self._size = columns, rows
358
+ return
359
+ elif trailer_reached and reading_trailer_comments:
360
+ # Load EPS trailer
361
+
362
+ # if this line starts with "%%EOF",
363
+ # then we've reached the end of the file
364
+ if bytes_mv[:5] == b"%%EOF":
365
+ break
366
+
367
+ s = str(bytes_mv[:bytes_read], "latin-1")
368
+ _read_comment(s)
369
+ elif bytes_mv[:9] == b"%%Trailer":
370
+ trailer_reached = True
371
+ bytes_read = 0
372
+
373
+ check_required_header_comments()
374
+
375
+ if not self._size:
376
+ msg = "cannot determine EPS bounding box"
377
+ raise OSError(msg)
378
+
379
+ def _find_offset(self, fp):
380
+ s = fp.read(4)
381
+
382
+ if s == b"%!PS":
383
+ # for HEAD without binary preview
384
+ fp.seek(0, io.SEEK_END)
385
+ length = fp.tell()
386
+ offset = 0
387
+ elif i32(s) == 0xC6D3D0C5:
388
+ # FIX for: Some EPS file not handled correctly / issue #302
389
+ # EPS can contain binary data
390
+ # or start directly with latin coding
391
+ # more info see:
392
+ # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
393
+ s = fp.read(8)
394
+ offset = i32(s)
395
+ length = i32(s, 4)
396
+ else:
397
+ msg = "not an EPS file"
398
+ raise SyntaxError(msg)
399
+
400
+ return length, offset
401
+
402
+ def load(self, scale=1, transparency=False):
403
+ # Load EPS via Ghostscript
404
+ if self.tile:
405
+ self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
406
+ self._mode = self.im.mode
407
+ self._size = self.im.size
408
+ self.tile = []
409
+ return Image.Image.load(self)
410
+
411
+ def load_seek(self, *args, **kwargs):
412
+ # we can't incrementally load, so force ImageFile.parser to
413
+ # use our custom load method by defining this method.
414
+ pass
415
+
416
+
417
+ # --------------------------------------------------------------------
418
+
419
+
420
+ def _save(im, fp, filename, eps=1):
421
+ """EPS Writer for the Python Imaging Library."""
422
+
423
+ # make sure image data is available
424
+ im.load()
425
+
426
+ # determine PostScript image mode
427
+ if im.mode == "L":
428
+ operator = (8, 1, b"image")
429
+ elif im.mode == "RGB":
430
+ operator = (8, 3, b"false 3 colorimage")
431
+ elif im.mode == "CMYK":
432
+ operator = (8, 4, b"false 4 colorimage")
433
+ else:
434
+ msg = "image mode is not supported"
435
+ raise ValueError(msg)
436
+
437
+ if eps:
438
+ # write EPS header
439
+ fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n")
440
+ fp.write(b"%%Creator: PIL 0.1 EpsEncode\n")
441
+ # fp.write("%%CreationDate: %s"...)
442
+ fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size)
443
+ fp.write(b"%%Pages: 1\n")
444
+ fp.write(b"%%EndComments\n")
445
+ fp.write(b"%%Page: 1 1\n")
446
+ fp.write(b"%%ImageData: %d %d " % im.size)
447
+ fp.write(b'%d %d 0 1 1 "%s"\n' % operator)
448
+
449
+ # image header
450
+ fp.write(b"gsave\n")
451
+ fp.write(b"10 dict begin\n")
452
+ fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1]))
453
+ fp.write(b"%d %d scale\n" % im.size)
454
+ fp.write(b"%d %d 8\n" % im.size) # <= bits
455
+ fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
456
+ fp.write(b"{ currentfile buf readhexstring pop } bind\n")
457
+ fp.write(operator[2] + b"\n")
458
+ if hasattr(fp, "flush"):
459
+ fp.flush()
460
+
461
+ ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
462
+
463
+ fp.write(b"\n%%%%EndBinary\n")
464
+ fp.write(b"grestore end\n")
465
+ if hasattr(fp, "flush"):
466
+ fp.flush()
467
+
468
+
469
+ # --------------------------------------------------------------------
470
+
471
+
472
+ Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
473
+
474
+ Image.register_save(EpsImageFile.format, _save)
475
+
476
+ Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
477
+
478
+ Image.register_mime(EpsImageFile.format, "application/postscript")
.venv/Lib/site-packages/PIL/ExifTags.py ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # EXIF tags
6
+ #
7
+ # Copyright (c) 2003 by Secret Labs AB
8
+ #
9
+ # See the README file for information on usage and redistribution.
10
+ #
11
+
12
+ """
13
+ This module provides constants and clear-text names for various
14
+ well-known EXIF tags.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from enum import IntEnum
19
+
20
+
21
+ class Base(IntEnum):
22
+ # possibly incomplete
23
+ InteropIndex = 0x0001
24
+ ProcessingSoftware = 0x000B
25
+ NewSubfileType = 0x00FE
26
+ SubfileType = 0x00FF
27
+ ImageWidth = 0x0100
28
+ ImageLength = 0x0101
29
+ BitsPerSample = 0x0102
30
+ Compression = 0x0103
31
+ PhotometricInterpretation = 0x0106
32
+ Thresholding = 0x0107
33
+ CellWidth = 0x0108
34
+ CellLength = 0x0109
35
+ FillOrder = 0x010A
36
+ DocumentName = 0x010D
37
+ ImageDescription = 0x010E
38
+ Make = 0x010F
39
+ Model = 0x0110
40
+ StripOffsets = 0x0111
41
+ Orientation = 0x0112
42
+ SamplesPerPixel = 0x0115
43
+ RowsPerStrip = 0x0116
44
+ StripByteCounts = 0x0117
45
+ MinSampleValue = 0x0118
46
+ MaxSampleValue = 0x0119
47
+ XResolution = 0x011A
48
+ YResolution = 0x011B
49
+ PlanarConfiguration = 0x011C
50
+ PageName = 0x011D
51
+ FreeOffsets = 0x0120
52
+ FreeByteCounts = 0x0121
53
+ GrayResponseUnit = 0x0122
54
+ GrayResponseCurve = 0x0123
55
+ T4Options = 0x0124
56
+ T6Options = 0x0125
57
+ ResolutionUnit = 0x0128
58
+ PageNumber = 0x0129
59
+ TransferFunction = 0x012D
60
+ Software = 0x0131
61
+ DateTime = 0x0132
62
+ Artist = 0x013B
63
+ HostComputer = 0x013C
64
+ Predictor = 0x013D
65
+ WhitePoint = 0x013E
66
+ PrimaryChromaticities = 0x013F
67
+ ColorMap = 0x0140
68
+ HalftoneHints = 0x0141
69
+ TileWidth = 0x0142
70
+ TileLength = 0x0143
71
+ TileOffsets = 0x0144
72
+ TileByteCounts = 0x0145
73
+ SubIFDs = 0x014A
74
+ InkSet = 0x014C
75
+ InkNames = 0x014D
76
+ NumberOfInks = 0x014E
77
+ DotRange = 0x0150
78
+ TargetPrinter = 0x0151
79
+ ExtraSamples = 0x0152
80
+ SampleFormat = 0x0153
81
+ SMinSampleValue = 0x0154
82
+ SMaxSampleValue = 0x0155
83
+ TransferRange = 0x0156
84
+ ClipPath = 0x0157
85
+ XClipPathUnits = 0x0158
86
+ YClipPathUnits = 0x0159
87
+ Indexed = 0x015A
88
+ JPEGTables = 0x015B
89
+ OPIProxy = 0x015F
90
+ JPEGProc = 0x0200
91
+ JpegIFOffset = 0x0201
92
+ JpegIFByteCount = 0x0202
93
+ JpegRestartInterval = 0x0203
94
+ JpegLosslessPredictors = 0x0205
95
+ JpegPointTransforms = 0x0206
96
+ JpegQTables = 0x0207
97
+ JpegDCTables = 0x0208
98
+ JpegACTables = 0x0209
99
+ YCbCrCoefficients = 0x0211
100
+ YCbCrSubSampling = 0x0212
101
+ YCbCrPositioning = 0x0213
102
+ ReferenceBlackWhite = 0x0214
103
+ XMLPacket = 0x02BC
104
+ RelatedImageFileFormat = 0x1000
105
+ RelatedImageWidth = 0x1001
106
+ RelatedImageLength = 0x1002
107
+ Rating = 0x4746
108
+ RatingPercent = 0x4749
109
+ ImageID = 0x800D
110
+ CFARepeatPatternDim = 0x828D
111
+ BatteryLevel = 0x828F
112
+ Copyright = 0x8298
113
+ ExposureTime = 0x829A
114
+ FNumber = 0x829D
115
+ IPTCNAA = 0x83BB
116
+ ImageResources = 0x8649
117
+ ExifOffset = 0x8769
118
+ InterColorProfile = 0x8773
119
+ ExposureProgram = 0x8822
120
+ SpectralSensitivity = 0x8824
121
+ GPSInfo = 0x8825
122
+ ISOSpeedRatings = 0x8827
123
+ OECF = 0x8828
124
+ Interlace = 0x8829
125
+ TimeZoneOffset = 0x882A
126
+ SelfTimerMode = 0x882B
127
+ SensitivityType = 0x8830
128
+ StandardOutputSensitivity = 0x8831
129
+ RecommendedExposureIndex = 0x8832
130
+ ISOSpeed = 0x8833
131
+ ISOSpeedLatitudeyyy = 0x8834
132
+ ISOSpeedLatitudezzz = 0x8835
133
+ ExifVersion = 0x9000
134
+ DateTimeOriginal = 0x9003
135
+ DateTimeDigitized = 0x9004
136
+ OffsetTime = 0x9010
137
+ OffsetTimeOriginal = 0x9011
138
+ OffsetTimeDigitized = 0x9012
139
+ ComponentsConfiguration = 0x9101
140
+ CompressedBitsPerPixel = 0x9102
141
+ ShutterSpeedValue = 0x9201
142
+ ApertureValue = 0x9202
143
+ BrightnessValue = 0x9203
144
+ ExposureBiasValue = 0x9204
145
+ MaxApertureValue = 0x9205
146
+ SubjectDistance = 0x9206
147
+ MeteringMode = 0x9207
148
+ LightSource = 0x9208
149
+ Flash = 0x9209
150
+ FocalLength = 0x920A
151
+ Noise = 0x920D
152
+ ImageNumber = 0x9211
153
+ SecurityClassification = 0x9212
154
+ ImageHistory = 0x9213
155
+ TIFFEPStandardID = 0x9216
156
+ MakerNote = 0x927C
157
+ UserComment = 0x9286
158
+ SubsecTime = 0x9290
159
+ SubsecTimeOriginal = 0x9291
160
+ SubsecTimeDigitized = 0x9292
161
+ AmbientTemperature = 0x9400
162
+ Humidity = 0x9401
163
+ Pressure = 0x9402
164
+ WaterDepth = 0x9403
165
+ Acceleration = 0x9404
166
+ CameraElevationAngle = 0x9405
167
+ XPTitle = 0x9C9B
168
+ XPComment = 0x9C9C
169
+ XPAuthor = 0x9C9D
170
+ XPKeywords = 0x9C9E
171
+ XPSubject = 0x9C9F
172
+ FlashPixVersion = 0xA000
173
+ ColorSpace = 0xA001
174
+ ExifImageWidth = 0xA002
175
+ ExifImageHeight = 0xA003
176
+ RelatedSoundFile = 0xA004
177
+ ExifInteroperabilityOffset = 0xA005
178
+ FlashEnergy = 0xA20B
179
+ SpatialFrequencyResponse = 0xA20C
180
+ FocalPlaneXResolution = 0xA20E
181
+ FocalPlaneYResolution = 0xA20F
182
+ FocalPlaneResolutionUnit = 0xA210
183
+ SubjectLocation = 0xA214
184
+ ExposureIndex = 0xA215
185
+ SensingMethod = 0xA217
186
+ FileSource = 0xA300
187
+ SceneType = 0xA301
188
+ CFAPattern = 0xA302
189
+ CustomRendered = 0xA401
190
+ ExposureMode = 0xA402
191
+ WhiteBalance = 0xA403
192
+ DigitalZoomRatio = 0xA404
193
+ FocalLengthIn35mmFilm = 0xA405
194
+ SceneCaptureType = 0xA406
195
+ GainControl = 0xA407
196
+ Contrast = 0xA408
197
+ Saturation = 0xA409
198
+ Sharpness = 0xA40A
199
+ DeviceSettingDescription = 0xA40B
200
+ SubjectDistanceRange = 0xA40C
201
+ ImageUniqueID = 0xA420
202
+ CameraOwnerName = 0xA430
203
+ BodySerialNumber = 0xA431
204
+ LensSpecification = 0xA432
205
+ LensMake = 0xA433
206
+ LensModel = 0xA434
207
+ LensSerialNumber = 0xA435
208
+ CompositeImage = 0xA460
209
+ CompositeImageCount = 0xA461
210
+ CompositeImageExposureTimes = 0xA462
211
+ Gamma = 0xA500
212
+ PrintImageMatching = 0xC4A5
213
+ DNGVersion = 0xC612
214
+ DNGBackwardVersion = 0xC613
215
+ UniqueCameraModel = 0xC614
216
+ LocalizedCameraModel = 0xC615
217
+ CFAPlaneColor = 0xC616
218
+ CFALayout = 0xC617
219
+ LinearizationTable = 0xC618
220
+ BlackLevelRepeatDim = 0xC619
221
+ BlackLevel = 0xC61A
222
+ BlackLevelDeltaH = 0xC61B
223
+ BlackLevelDeltaV = 0xC61C
224
+ WhiteLevel = 0xC61D
225
+ DefaultScale = 0xC61E
226
+ DefaultCropOrigin = 0xC61F
227
+ DefaultCropSize = 0xC620
228
+ ColorMatrix1 = 0xC621
229
+ ColorMatrix2 = 0xC622
230
+ CameraCalibration1 = 0xC623
231
+ CameraCalibration2 = 0xC624
232
+ ReductionMatrix1 = 0xC625
233
+ ReductionMatrix2 = 0xC626
234
+ AnalogBalance = 0xC627
235
+ AsShotNeutral = 0xC628
236
+ AsShotWhiteXY = 0xC629
237
+ BaselineExposure = 0xC62A
238
+ BaselineNoise = 0xC62B
239
+ BaselineSharpness = 0xC62C
240
+ BayerGreenSplit = 0xC62D
241
+ LinearResponseLimit = 0xC62E
242
+ CameraSerialNumber = 0xC62F
243
+ LensInfo = 0xC630
244
+ ChromaBlurRadius = 0xC631
245
+ AntiAliasStrength = 0xC632
246
+ ShadowScale = 0xC633
247
+ DNGPrivateData = 0xC634
248
+ MakerNoteSafety = 0xC635
249
+ CalibrationIlluminant1 = 0xC65A
250
+ CalibrationIlluminant2 = 0xC65B
251
+ BestQualityScale = 0xC65C
252
+ RawDataUniqueID = 0xC65D
253
+ OriginalRawFileName = 0xC68B
254
+ OriginalRawFileData = 0xC68C
255
+ ActiveArea = 0xC68D
256
+ MaskedAreas = 0xC68E
257
+ AsShotICCProfile = 0xC68F
258
+ AsShotPreProfileMatrix = 0xC690
259
+ CurrentICCProfile = 0xC691
260
+ CurrentPreProfileMatrix = 0xC692
261
+ ColorimetricReference = 0xC6BF
262
+ CameraCalibrationSignature = 0xC6F3
263
+ ProfileCalibrationSignature = 0xC6F4
264
+ AsShotProfileName = 0xC6F6
265
+ NoiseReductionApplied = 0xC6F7
266
+ ProfileName = 0xC6F8
267
+ ProfileHueSatMapDims = 0xC6F9
268
+ ProfileHueSatMapData1 = 0xC6FA
269
+ ProfileHueSatMapData2 = 0xC6FB
270
+ ProfileToneCurve = 0xC6FC
271
+ ProfileEmbedPolicy = 0xC6FD
272
+ ProfileCopyright = 0xC6FE
273
+ ForwardMatrix1 = 0xC714
274
+ ForwardMatrix2 = 0xC715
275
+ PreviewApplicationName = 0xC716
276
+ PreviewApplicationVersion = 0xC717
277
+ PreviewSettingsName = 0xC718
278
+ PreviewSettingsDigest = 0xC719
279
+ PreviewColorSpace = 0xC71A
280
+ PreviewDateTime = 0xC71B
281
+ RawImageDigest = 0xC71C
282
+ OriginalRawFileDigest = 0xC71D
283
+ SubTileBlockSize = 0xC71E
284
+ RowInterleaveFactor = 0xC71F
285
+ ProfileLookTableDims = 0xC725
286
+ ProfileLookTableData = 0xC726
287
+ OpcodeList1 = 0xC740
288
+ OpcodeList2 = 0xC741
289
+ OpcodeList3 = 0xC74E
290
+ NoiseProfile = 0xC761
291
+
292
+
293
+ """Maps EXIF tags to tag names."""
294
+ TAGS = {
295
+ **{i.value: i.name for i in Base},
296
+ 0x920C: "SpatialFrequencyResponse",
297
+ 0x9214: "SubjectLocation",
298
+ 0x9215: "ExposureIndex",
299
+ 0x828E: "CFAPattern",
300
+ 0x920B: "FlashEnergy",
301
+ 0x9216: "TIFF/EPStandardID",
302
+ }
303
+
304
+
305
+ class GPS(IntEnum):
306
+ GPSVersionID = 0
307
+ GPSLatitudeRef = 1
308
+ GPSLatitude = 2
309
+ GPSLongitudeRef = 3
310
+ GPSLongitude = 4
311
+ GPSAltitudeRef = 5
312
+ GPSAltitude = 6
313
+ GPSTimeStamp = 7
314
+ GPSSatellites = 8
315
+ GPSStatus = 9
316
+ GPSMeasureMode = 10
317
+ GPSDOP = 11
318
+ GPSSpeedRef = 12
319
+ GPSSpeed = 13
320
+ GPSTrackRef = 14
321
+ GPSTrack = 15
322
+ GPSImgDirectionRef = 16
323
+ GPSImgDirection = 17
324
+ GPSMapDatum = 18
325
+ GPSDestLatitudeRef = 19
326
+ GPSDestLatitude = 20
327
+ GPSDestLongitudeRef = 21
328
+ GPSDestLongitude = 22
329
+ GPSDestBearingRef = 23
330
+ GPSDestBearing = 24
331
+ GPSDestDistanceRef = 25
332
+ GPSDestDistance = 26
333
+ GPSProcessingMethod = 27
334
+ GPSAreaInformation = 28
335
+ GPSDateStamp = 29
336
+ GPSDifferential = 30
337
+ GPSHPositioningError = 31
338
+
339
+
340
+ """Maps EXIF GPS tags to tag names."""
341
+ GPSTAGS = {i.value: i.name for i in GPS}
342
+
343
+
344
+ class Interop(IntEnum):
345
+ InteropIndex = 1
346
+ InteropVersion = 2
347
+ RelatedImageFileFormat = 4096
348
+ RelatedImageWidth = 4097
349
+ RleatedImageHeight = 4098
350
+
351
+
352
+ class IFD(IntEnum):
353
+ Exif = 34665
354
+ GPSInfo = 34853
355
+ Makernote = 37500
356
+ Interop = 40965
357
+ IFD1 = -1
358
+
359
+
360
+ class LightSource(IntEnum):
361
+ Unknown = 0
362
+ Daylight = 1
363
+ Fluorescent = 2
364
+ Tungsten = 3
365
+ Flash = 4
366
+ Fine = 9
367
+ Cloudy = 10
368
+ Shade = 11
369
+ DaylightFluorescent = 12
370
+ DayWhiteFluorescent = 13
371
+ CoolWhiteFluorescent = 14
372
+ WhiteFluorescent = 15
373
+ StandardLightA = 17
374
+ StandardLightB = 18
375
+ StandardLightC = 19
376
+ D55 = 20
377
+ D65 = 21
378
+ D75 = 22
379
+ D50 = 23
380
+ ISO = 24
381
+ Other = 255
.venv/Lib/site-packages/PIL/FitsImagePlugin.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # FITS file handling
6
+ #
7
+ # Copyright (c) 1998-2003 by Fredrik Lundh
8
+ #
9
+ # See the README file for information on usage and redistribution.
10
+ #
11
+ from __future__ import annotations
12
+
13
+ import math
14
+
15
+ from . import Image, ImageFile
16
+
17
+
18
+ def _accept(prefix):
19
+ return prefix[:6] == b"SIMPLE"
20
+
21
+
22
+ class FitsImageFile(ImageFile.ImageFile):
23
+ format = "FITS"
24
+ format_description = "FITS"
25
+
26
+ def _open(self):
27
+ headers = {}
28
+ while True:
29
+ header = self.fp.read(80)
30
+ if not header:
31
+ msg = "Truncated FITS file"
32
+ raise OSError(msg)
33
+ keyword = header[:8].strip()
34
+ if keyword == b"END":
35
+ break
36
+ value = header[8:].split(b"/")[0].strip()
37
+ if value.startswith(b"="):
38
+ value = value[1:].strip()
39
+ if not headers and (not _accept(keyword) or value != b"T"):
40
+ msg = "Not a FITS file"
41
+ raise SyntaxError(msg)
42
+ headers[keyword] = value
43
+
44
+ naxis = int(headers[b"NAXIS"])
45
+ if naxis == 0:
46
+ msg = "No image data"
47
+ raise ValueError(msg)
48
+ elif naxis == 1:
49
+ self._size = 1, int(headers[b"NAXIS1"])
50
+ else:
51
+ self._size = int(headers[b"NAXIS1"]), int(headers[b"NAXIS2"])
52
+
53
+ number_of_bits = int(headers[b"BITPIX"])
54
+ if number_of_bits == 8:
55
+ self._mode = "L"
56
+ elif number_of_bits == 16:
57
+ self._mode = "I"
58
+ elif number_of_bits == 32:
59
+ self._mode = "I"
60
+ elif number_of_bits in (-32, -64):
61
+ self._mode = "F"
62
+
63
+ offset = math.ceil(self.fp.tell() / 2880) * 2880
64
+ self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))]
65
+
66
+
67
+ # --------------------------------------------------------------------
68
+ # Registry
69
+
70
+ Image.register_open(FitsImageFile.format, FitsImageFile, _accept)
71
+
72
+ Image.register_extensions(FitsImageFile.format, [".fit", ".fits"])
.venv/Lib/site-packages/PIL/FliImagePlugin.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # FLI/FLC file handling.
6
+ #
7
+ # History:
8
+ # 95-09-01 fl Created
9
+ # 97-01-03 fl Fixed parser, setup decoder tile
10
+ # 98-07-15 fl Renamed offset attribute to avoid name clash
11
+ #
12
+ # Copyright (c) Secret Labs AB 1997-98.
13
+ # Copyright (c) Fredrik Lundh 1995-97.
14
+ #
15
+ # See the README file for information on usage and redistribution.
16
+ #
17
+ from __future__ import annotations
18
+
19
+ import os
20
+
21
+ from . import Image, ImageFile, ImagePalette
22
+ from ._binary import i16le as i16
23
+ from ._binary import i32le as i32
24
+ from ._binary import o8
25
+
26
+ #
27
+ # decoder
28
+
29
+
30
+ def _accept(prefix):
31
+ return (
32
+ len(prefix) >= 6
33
+ and i16(prefix, 4) in [0xAF11, 0xAF12]
34
+ and i16(prefix, 14) in [0, 3] # flags
35
+ )
36
+
37
+
38
+ ##
39
+ # Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
40
+ # method to load individual frames.
41
+
42
+
43
+ class FliImageFile(ImageFile.ImageFile):
44
+ format = "FLI"
45
+ format_description = "Autodesk FLI/FLC Animation"
46
+ _close_exclusive_fp_after_loading = False
47
+
48
+ def _open(self):
49
+ # HEAD
50
+ s = self.fp.read(128)
51
+ if not (_accept(s) and s[20:22] == b"\x00\x00"):
52
+ msg = "not an FLI/FLC file"
53
+ raise SyntaxError(msg)
54
+
55
+ # frames
56
+ self.n_frames = i16(s, 6)
57
+ self.is_animated = self.n_frames > 1
58
+
59
+ # image characteristics
60
+ self._mode = "P"
61
+ self._size = i16(s, 8), i16(s, 10)
62
+
63
+ # animation speed
64
+ duration = i32(s, 16)
65
+ magic = i16(s, 4)
66
+ if magic == 0xAF11:
67
+ duration = (duration * 1000) // 70
68
+ self.info["duration"] = duration
69
+
70
+ # look for palette
71
+ palette = [(a, a, a) for a in range(256)]
72
+
73
+ s = self.fp.read(16)
74
+
75
+ self.__offset = 128
76
+
77
+ if i16(s, 4) == 0xF100:
78
+ # prefix chunk; ignore it
79
+ self.__offset = self.__offset + i32(s)
80
+ s = self.fp.read(16)
81
+
82
+ if i16(s, 4) == 0xF1FA:
83
+ # look for palette chunk
84
+ number_of_subchunks = i16(s, 6)
85
+ chunk_size = None
86
+ for _ in range(number_of_subchunks):
87
+ if chunk_size is not None:
88
+ self.fp.seek(chunk_size - 6, os.SEEK_CUR)
89
+ s = self.fp.read(6)
90
+ chunk_type = i16(s, 4)
91
+ if chunk_type in (4, 11):
92
+ self._palette(palette, 2 if chunk_type == 11 else 0)
93
+ break
94
+ chunk_size = i32(s)
95
+ if not chunk_size:
96
+ break
97
+
98
+ palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
99
+ self.palette = ImagePalette.raw("RGB", b"".join(palette))
100
+
101
+ # set things up to decode first frame
102
+ self.__frame = -1
103
+ self._fp = self.fp
104
+ self.__rewind = self.fp.tell()
105
+ self.seek(0)
106
+
107
+ def _palette(self, palette, shift):
108
+ # load palette
109
+
110
+ i = 0
111
+ for e in range(i16(self.fp.read(2))):
112
+ s = self.fp.read(2)
113
+ i = i + s[0]
114
+ n = s[1]
115
+ if n == 0:
116
+ n = 256
117
+ s = self.fp.read(n * 3)
118
+ for n in range(0, len(s), 3):
119
+ r = s[n] << shift
120
+ g = s[n + 1] << shift
121
+ b = s[n + 2] << shift
122
+ palette[i] = (r, g, b)
123
+ i += 1
124
+
125
+ def seek(self, frame):
126
+ if not self._seek_check(frame):
127
+ return
128
+ if frame < self.__frame:
129
+ self._seek(0)
130
+
131
+ for f in range(self.__frame + 1, frame + 1):
132
+ self._seek(f)
133
+
134
+ def _seek(self, frame):
135
+ if frame == 0:
136
+ self.__frame = -1
137
+ self._fp.seek(self.__rewind)
138
+ self.__offset = 128
139
+ else:
140
+ # ensure that the previous frame was loaded
141
+ self.load()
142
+
143
+ if frame != self.__frame + 1:
144
+ msg = f"cannot seek to frame {frame}"
145
+ raise ValueError(msg)
146
+ self.__frame = frame
147
+
148
+ # move to next frame
149
+ self.fp = self._fp
150
+ self.fp.seek(self.__offset)
151
+
152
+ s = self.fp.read(4)
153
+ if not s:
154
+ msg = "missing frame size"
155
+ raise EOFError(msg)
156
+
157
+ framesize = i32(s)
158
+
159
+ self.decodermaxblock = framesize
160
+ self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
161
+
162
+ self.__offset += framesize
163
+
164
+ def tell(self):
165
+ return self.__frame
166
+
167
+
168
+ #
169
+ # registry
170
+
171
+ Image.register_open(FliImageFile.format, FliImageFile, _accept)
172
+
173
+ Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
.venv/Lib/site-packages/PIL/FontFile.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # base class for raster font file parsers
6
+ #
7
+ # history:
8
+ # 1997-06-05 fl created
9
+ # 1997-08-19 fl restrict image width
10
+ #
11
+ # Copyright (c) 1997-1998 by Secret Labs AB
12
+ # Copyright (c) 1997-1998 by Fredrik Lundh
13
+ #
14
+ # See the README file for information on usage and redistribution.
15
+ #
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ from typing import BinaryIO
20
+
21
+ from . import Image, _binary
22
+
23
+ WIDTH = 800
24
+
25
+
26
+ def puti16(
27
+ fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int]
28
+ ) -> None:
29
+ """Write network order (big-endian) 16-bit sequence"""
30
+ for v in values:
31
+ if v < 0:
32
+ v += 65536
33
+ fp.write(_binary.o16be(v))
34
+
35
+
36
+ class FontFile:
37
+ """Base class for raster font file handlers."""
38
+
39
+ bitmap: Image.Image | None = None
40
+
41
+ def __init__(self) -> None:
42
+ self.info: dict[bytes, bytes | int] = {}
43
+ self.glyph: list[
44
+ tuple[
45
+ tuple[int, int],
46
+ tuple[int, int, int, int],
47
+ tuple[int, int, int, int],
48
+ Image.Image,
49
+ ]
50
+ | None
51
+ ] = [None] * 256
52
+
53
+ def __getitem__(
54
+ self, ix: int
55
+ ) -> (
56
+ tuple[
57
+ tuple[int, int],
58
+ tuple[int, int, int, int],
59
+ tuple[int, int, int, int],
60
+ Image.Image,
61
+ ]
62
+ | None
63
+ ):
64
+ return self.glyph[ix]
65
+
66
+ def compile(self) -> None:
67
+ """Create metrics and bitmap"""
68
+
69
+ if self.bitmap:
70
+ return
71
+
72
+ # create bitmap large enough to hold all data
73
+ h = w = maxwidth = 0
74
+ lines = 1
75
+ for glyph in self.glyph:
76
+ if glyph:
77
+ d, dst, src, im = glyph
78
+ h = max(h, src[3] - src[1])
79
+ w = w + (src[2] - src[0])
80
+ if w > WIDTH:
81
+ lines += 1
82
+ w = src[2] - src[0]
83
+ maxwidth = max(maxwidth, w)
84
+
85
+ xsize = maxwidth
86
+ ysize = lines * h
87
+
88
+ if xsize == 0 and ysize == 0:
89
+ return
90
+
91
+ self.ysize = h
92
+
93
+ # paste glyphs into bitmap
94
+ self.bitmap = Image.new("1", (xsize, ysize))
95
+ self.metrics: list[
96
+ tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]]
97
+ | None
98
+ ] = [None] * 256
99
+ x = y = 0
100
+ for i in range(256):
101
+ glyph = self[i]
102
+ if glyph:
103
+ d, dst, src, im = glyph
104
+ xx = src[2] - src[0]
105
+ x0, y0 = x, y
106
+ x = x + xx
107
+ if x > WIDTH:
108
+ x, y = 0, y + h
109
+ x0, y0 = x, y
110
+ x = xx
111
+ s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
112
+ self.bitmap.paste(im.crop(src), s)
113
+ self.metrics[i] = d, dst, s
114
+
115
+ def save(self, filename: str) -> None:
116
+ """Save font"""
117
+
118
+ self.compile()
119
+
120
+ # font data
121
+ if not self.bitmap:
122
+ msg = "No bitmap created"
123
+ raise ValueError(msg)
124
+ self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
125
+
126
+ # font metrics
127
+ with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
128
+ fp.write(b"PILfont\n")
129
+ fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!!
130
+ fp.write(b"DATA\n")
131
+ for id in range(256):
132
+ m = self.metrics[id]
133
+ if not m:
134
+ puti16(fp, (0,) * 10)
135
+ else:
136
+ puti16(fp, m[0] + m[1] + m[2])
.venv/Lib/site-packages/PIL/FpxImagePlugin.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # THIS IS WORK IN PROGRESS
3
+ #
4
+ # The Python Imaging Library.
5
+ # $Id$
6
+ #
7
+ # FlashPix support for PIL
8
+ #
9
+ # History:
10
+ # 97-01-25 fl Created (reads uncompressed RGB images only)
11
+ #
12
+ # Copyright (c) Secret Labs AB 1997.
13
+ # Copyright (c) Fredrik Lundh 1997.
14
+ #
15
+ # See the README file for information on usage and redistribution.
16
+ #
17
+ from __future__ import annotations
18
+
19
+ import olefile
20
+
21
+ from . import Image, ImageFile
22
+ from ._binary import i32le as i32
23
+
24
+ # we map from colour field tuples to (mode, rawmode) descriptors
25
+ MODES = {
26
+ # opacity
27
+ (0x00007FFE,): ("A", "L"),
28
+ # monochrome
29
+ (0x00010000,): ("L", "L"),
30
+ (0x00018000, 0x00017FFE): ("RGBA", "LA"),
31
+ # photo YCC
32
+ (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
33
+ (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
34
+ # standard RGB (NIFRGB)
35
+ (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
36
+ (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
37
+ }
38
+
39
+
40
+ #
41
+ # --------------------------------------------------------------------
42
+
43
+
44
+ def _accept(prefix):
45
+ return prefix[:8] == olefile.MAGIC
46
+
47
+
48
+ ##
49
+ # Image plugin for the FlashPix images.
50
+
51
+
52
+ class FpxImageFile(ImageFile.ImageFile):
53
+ format = "FPX"
54
+ format_description = "FlashPix"
55
+
56
+ def _open(self):
57
+ #
58
+ # read the OLE directory and see if this is a likely
59
+ # to be a FlashPix file
60
+
61
+ try:
62
+ self.ole = olefile.OleFileIO(self.fp)
63
+ except OSError as e:
64
+ msg = "not an FPX file; invalid OLE file"
65
+ raise SyntaxError(msg) from e
66
+
67
+ if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
68
+ msg = "not an FPX file; bad root CLSID"
69
+ raise SyntaxError(msg)
70
+
71
+ self._open_index(1)
72
+
73
+ def _open_index(self, index=1):
74
+ #
75
+ # get the Image Contents Property Set
76
+
77
+ prop = self.ole.getproperties(
78
+ [f"Data Object Store {index:06d}", "\005Image Contents"]
79
+ )
80
+
81
+ # size (highest resolution)
82
+
83
+ self._size = prop[0x1000002], prop[0x1000003]
84
+
85
+ size = max(self.size)
86
+ i = 1
87
+ while size > 64:
88
+ size = size / 2
89
+ i += 1
90
+ self.maxid = i - 1
91
+
92
+ # mode. instead of using a single field for this, flashpix
93
+ # requires you to specify the mode for each channel in each
94
+ # resolution subimage, and leaves it to the decoder to make
95
+ # sure that they all match. for now, we'll cheat and assume
96
+ # that this is always the case.
97
+
98
+ id = self.maxid << 16
99
+
100
+ s = prop[0x2000002 | id]
101
+
102
+ bands = i32(s, 4)
103
+ if bands > 4:
104
+ msg = "Invalid number of bands"
105
+ raise OSError(msg)
106
+
107
+ # note: for now, we ignore the "uncalibrated" flag
108
+ colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands))
109
+
110
+ self._mode, self.rawmode = MODES[colors]
111
+
112
+ # load JPEG tables, if any
113
+ self.jpeg = {}
114
+ for i in range(256):
115
+ id = 0x3000001 | (i << 16)
116
+ if id in prop:
117
+ self.jpeg[i] = prop[id]
118
+
119
+ self._open_subimage(1, self.maxid)
120
+
121
+ def _open_subimage(self, index=1, subimage=0):
122
+ #
123
+ # setup tile descriptors for a given subimage
124
+
125
+ stream = [
126
+ f"Data Object Store {index:06d}",
127
+ f"Resolution {subimage:04d}",
128
+ "Subimage 0000 Header",
129
+ ]
130
+
131
+ fp = self.ole.openstream(stream)
132
+
133
+ # skip prefix
134
+ fp.read(28)
135
+
136
+ # header stream
137
+ s = fp.read(36)
138
+
139
+ size = i32(s, 4), i32(s, 8)
140
+ # tilecount = i32(s, 12)
141
+ tilesize = i32(s, 16), i32(s, 20)
142
+ # channels = i32(s, 24)
143
+ offset = i32(s, 28)
144
+ length = i32(s, 32)
145
+
146
+ if size != self.size:
147
+ msg = "subimage mismatch"
148
+ raise OSError(msg)
149
+
150
+ # get tile descriptors
151
+ fp.seek(28 + offset)
152
+ s = fp.read(i32(s, 12) * length)
153
+
154
+ x = y = 0
155
+ xsize, ysize = size
156
+ xtile, ytile = tilesize
157
+ self.tile = []
158
+
159
+ for i in range(0, len(s), length):
160
+ x1 = min(xsize, x + xtile)
161
+ y1 = min(ysize, y + ytile)
162
+
163
+ compression = i32(s, i + 8)
164
+
165
+ if compression == 0:
166
+ self.tile.append(
167
+ (
168
+ "raw",
169
+ (x, y, x1, y1),
170
+ i32(s, i) + 28,
171
+ (self.rawmode,),
172
+ )
173
+ )
174
+
175
+ elif compression == 1:
176
+ # FIXME: the fill decoder is not implemented
177
+ self.tile.append(
178
+ (
179
+ "fill",
180
+ (x, y, x1, y1),
181
+ i32(s, i) + 28,
182
+ (self.rawmode, s[12:16]),
183
+ )
184
+ )
185
+
186
+ elif compression == 2:
187
+ internal_color_conversion = s[14]
188
+ jpeg_tables = s[15]
189
+ rawmode = self.rawmode
190
+
191
+ if internal_color_conversion:
192
+ # The image is stored as usual (usually YCbCr).
193
+ if rawmode == "RGBA":
194
+ # For "RGBA", data is stored as YCbCrA based on
195
+ # negative RGB. The following trick works around
196
+ # this problem :
197
+ jpegmode, rawmode = "YCbCrK", "CMYK"
198
+ else:
199
+ jpegmode = None # let the decoder decide
200
+
201
+ else:
202
+ # The image is stored as defined by rawmode
203
+ jpegmode = rawmode
204
+
205
+ self.tile.append(
206
+ (
207
+ "jpeg",
208
+ (x, y, x1, y1),
209
+ i32(s, i) + 28,
210
+ (rawmode, jpegmode),
211
+ )
212
+ )
213
+
214
+ # FIXME: jpeg tables are tile dependent; the prefix
215
+ # data must be placed in the tile descriptor itself!
216
+
217
+ if jpeg_tables:
218
+ self.tile_prefix = self.jpeg[jpeg_tables]
219
+
220
+ else:
221
+ msg = "unknown/invalid compression"
222
+ raise OSError(msg)
223
+
224
+ x = x + xtile
225
+ if x >= xsize:
226
+ x, y = 0, y + ytile
227
+ if y >= ysize:
228
+ break # isn't really required
229
+
230
+ self.stream = stream
231
+ self._fp = self.fp
232
+ self.fp = None
233
+
234
+ def load(self):
235
+ if not self.fp:
236
+ self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
237
+
238
+ return ImageFile.ImageFile.load(self)
239
+
240
+ def close(self):
241
+ self.ole.close()
242
+ super().close()
243
+
244
+ def __exit__(self, *args):
245
+ self.ole.close()
246
+ super().__exit__()
247
+
248
+
249
+ #
250
+ # --------------------------------------------------------------------
251
+
252
+
253
+ Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
254
+
255
+ Image.register_extension(FpxImageFile.format, ".fpx")
.venv/Lib/site-packages/PIL/FtexImagePlugin.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A Pillow loader for .ftc and .ftu files (FTEX)
3
+ Jerome Leclanche <[email protected]>
4
+
5
+ The contents of this file are hereby released in the public domain (CC0)
6
+ Full text of the CC0 license:
7
+ https://creativecommons.org/publicdomain/zero/1.0/
8
+
9
+ Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
10
+
11
+ The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
12
+ packed custom format called FTEX. This file format uses file extensions FTC
13
+ and FTU.
14
+ * FTC files are compressed textures (using standard texture compression).
15
+ * FTU files are not compressed.
16
+ Texture File Format
17
+ The FTC and FTU texture files both use the same format. This
18
+ has the following structure:
19
+ {header}
20
+ {format_directory}
21
+ {data}
22
+ Where:
23
+ {header} = {
24
+ u32:magic,
25
+ u32:version,
26
+ u32:width,
27
+ u32:height,
28
+ u32:mipmap_count,
29
+ u32:format_count
30
+ }
31
+
32
+ * The "magic" number is "FTEX".
33
+ * "width" and "height" are the dimensions of the texture.
34
+ * "mipmap_count" is the number of mipmaps in the texture.
35
+ * "format_count" is the number of texture formats (different versions of the
36
+ same texture) in this file.
37
+
38
+ {format_directory} = format_count * { u32:format, u32:where }
39
+
40
+ The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
41
+ uncompressed textures.
42
+ The texture data for a format starts at the position "where" in the file.
43
+
44
+ Each set of texture data in the file has the following structure:
45
+ {data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
46
+ * "mipmap_size" is the number of bytes in that mip level. For compressed
47
+ textures this is the size of the texture data compressed with DXT1. For 24 bit
48
+ uncompressed textures, this is 3 * width * height. Following this are the image
49
+ bytes for that mipmap level.
50
+
51
+ Note: All data is stored in little-Endian (Intel) byte order.
52
+ """
53
+ from __future__ import annotations
54
+
55
+ import struct
56
+ from enum import IntEnum
57
+ from io import BytesIO
58
+
59
+ from . import Image, ImageFile
60
+
61
+ MAGIC = b"FTEX"
62
+
63
+
64
+ class Format(IntEnum):
65
+ DXT1 = 0
66
+ UNCOMPRESSED = 1
67
+
68
+
69
+ class FtexImageFile(ImageFile.ImageFile):
70
+ format = "FTEX"
71
+ format_description = "Texture File Format (IW2:EOC)"
72
+
73
+ def _open(self):
74
+ if not _accept(self.fp.read(4)):
75
+ msg = "not an FTEX file"
76
+ raise SyntaxError(msg)
77
+ struct.unpack("<i", self.fp.read(4)) # version
78
+ self._size = struct.unpack("<2i", self.fp.read(8))
79
+ mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
80
+
81
+ self._mode = "RGB"
82
+
83
+ # Only support single-format files.
84
+ # I don't know of any multi-format file.
85
+ assert format_count == 1
86
+
87
+ format, where = struct.unpack("<2i", self.fp.read(8))
88
+ self.fp.seek(where)
89
+ (mipmap_size,) = struct.unpack("<i", self.fp.read(4))
90
+
91
+ data = self.fp.read(mipmap_size)
92
+
93
+ if format == Format.DXT1:
94
+ self._mode = "RGBA"
95
+ self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
96
+ elif format == Format.UNCOMPRESSED:
97
+ self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
98
+ else:
99
+ msg = f"Invalid texture compression format: {repr(format)}"
100
+ raise ValueError(msg)
101
+
102
+ self.fp.close()
103
+ self.fp = BytesIO(data)
104
+
105
+ def load_seek(self, pos):
106
+ pass
107
+
108
+
109
+ def _accept(prefix):
110
+ return prefix[:4] == MAGIC
111
+
112
+
113
+ Image.register_open(FtexImageFile.format, FtexImageFile, _accept)
114
+ Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
.venv/Lib/site-packages/PIL/GbrImagePlugin.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ #
4
+ # load a GIMP brush file
5
+ #
6
+ # History:
7
+ # 96-03-14 fl Created
8
+ # 16-01-08 es Version 2
9
+ #
10
+ # Copyright (c) Secret Labs AB 1997.
11
+ # Copyright (c) Fredrik Lundh 1996.
12
+ # Copyright (c) Eric Soroos 2016.
13
+ #
14
+ # See the README file for information on usage and redistribution.
15
+ #
16
+ #
17
+ # See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for
18
+ # format documentation.
19
+ #
20
+ # This code Interprets version 1 and 2 .gbr files.
21
+ # Version 1 files are obsolete, and should not be used for new
22
+ # brushes.
23
+ # Version 2 files are saved by GIMP v2.8 (at least)
24
+ # Version 3 files have a format specifier of 18 for 16bit floats in
25
+ # the color depth field. This is currently unsupported by Pillow.
26
+ from __future__ import annotations
27
+
28
+ from . import Image, ImageFile
29
+ from ._binary import i32be as i32
30
+
31
+
32
+ def _accept(prefix):
33
+ return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2)
34
+
35
+
36
+ ##
37
+ # Image plugin for the GIMP brush format.
38
+
39
+
40
+ class GbrImageFile(ImageFile.ImageFile):
41
+ format = "GBR"
42
+ format_description = "GIMP brush file"
43
+
44
+ def _open(self):
45
+ header_size = i32(self.fp.read(4))
46
+ if header_size < 20:
47
+ msg = "not a GIMP brush"
48
+ raise SyntaxError(msg)
49
+ version = i32(self.fp.read(4))
50
+ if version not in (1, 2):
51
+ msg = f"Unsupported GIMP brush version: {version}"
52
+ raise SyntaxError(msg)
53
+
54
+ width = i32(self.fp.read(4))
55
+ height = i32(self.fp.read(4))
56
+ color_depth = i32(self.fp.read(4))
57
+ if width <= 0 or height <= 0:
58
+ msg = "not a GIMP brush"
59
+ raise SyntaxError(msg)
60
+ if color_depth not in (1, 4):
61
+ msg = f"Unsupported GIMP brush color depth: {color_depth}"
62
+ raise SyntaxError(msg)
63
+
64
+ if version == 1:
65
+ comment_length = header_size - 20
66
+ else:
67
+ comment_length = header_size - 28
68
+ magic_number = self.fp.read(4)
69
+ if magic_number != b"GIMP":
70
+ msg = "not a GIMP brush, bad magic number"
71
+ raise SyntaxError(msg)
72
+ self.info["spacing"] = i32(self.fp.read(4))
73
+
74
+ comment = self.fp.read(comment_length)[:-1]
75
+
76
+ if color_depth == 1:
77
+ self._mode = "L"
78
+ else:
79
+ self._mode = "RGBA"
80
+
81
+ self._size = width, height
82
+
83
+ self.info["comment"] = comment
84
+
85
+ # Image might not be small
86
+ Image._decompression_bomb_check(self.size)
87
+
88
+ # Data is an uncompressed block of w * h * bytes/pixel
89
+ self._data_size = width * height * color_depth
90
+
91
+ def load(self):
92
+ if not self.im:
93
+ self.im = Image.core.new(self.mode, self.size)
94
+ self.frombytes(self.fp.read(self._data_size))
95
+ return Image.Image.load(self)
96
+
97
+
98
+ #
99
+ # registry
100
+
101
+
102
+ Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
103
+ Image.register_extension(GbrImageFile.format, ".gbr")
.venv/Lib/site-packages/PIL/GdImageFile.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # GD file handling
6
+ #
7
+ # History:
8
+ # 1996-04-12 fl Created
9
+ #
10
+ # Copyright (c) 1997 by Secret Labs AB.
11
+ # Copyright (c) 1996 by Fredrik Lundh.
12
+ #
13
+ # See the README file for information on usage and redistribution.
14
+ #
15
+
16
+
17
+ """
18
+ .. note::
19
+ This format cannot be automatically recognized, so the
20
+ class is not registered for use with :py:func:`PIL.Image.open()`. To open a
21
+ gd file, use the :py:func:`PIL.GdImageFile.open()` function instead.
22
+
23
+ .. warning::
24
+ THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This
25
+ implementation is provided for convenience and demonstrational
26
+ purposes only.
27
+ """
28
+ from __future__ import annotations
29
+
30
+ from . import ImageFile, ImagePalette, UnidentifiedImageError
31
+ from ._binary import i16be as i16
32
+ from ._binary import i32be as i32
33
+
34
+
35
+ class GdImageFile(ImageFile.ImageFile):
36
+ """
37
+ Image plugin for the GD uncompressed format. Note that this format
38
+ is not supported by the standard :py:func:`PIL.Image.open()` function. To use
39
+ this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and
40
+ use the :py:func:`PIL.GdImageFile.open()` function.
41
+ """
42
+
43
+ format = "GD"
44
+ format_description = "GD uncompressed images"
45
+
46
+ def _open(self):
47
+ # Header
48
+ s = self.fp.read(1037)
49
+
50
+ if i16(s) not in [65534, 65535]:
51
+ msg = "Not a valid GD 2.x .gd file"
52
+ raise SyntaxError(msg)
53
+
54
+ self._mode = "L" # FIXME: "P"
55
+ self._size = i16(s, 2), i16(s, 4)
56
+
57
+ true_color = s[6]
58
+ true_color_offset = 2 if true_color else 0
59
+
60
+ # transparency index
61
+ tindex = i32(s, 7 + true_color_offset)
62
+ if tindex < 256:
63
+ self.info["transparency"] = tindex
64
+
65
+ self.palette = ImagePalette.raw(
66
+ "XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4]
67
+ )
68
+
69
+ self.tile = [
70
+ (
71
+ "raw",
72
+ (0, 0) + self.size,
73
+ 7 + true_color_offset + 4 + 256 * 4,
74
+ ("L", 0, 1),
75
+ )
76
+ ]
77
+
78
+
79
+ def open(fp, mode="r"):
80
+ """
81
+ Load texture from a GD image file.
82
+
83
+ :param fp: GD file name, or an opened file handle.
84
+ :param mode: Optional mode. In this version, if the mode argument
85
+ is given, it must be "r".
86
+ :returns: An image instance.
87
+ :raises OSError: If the image could not be read.
88
+ """
89
+ if mode != "r":
90
+ msg = "bad mode"
91
+ raise ValueError(msg)
92
+
93
+ try:
94
+ return GdImageFile(fp)
95
+ except SyntaxError as e:
96
+ msg = "cannot identify this image file"
97
+ raise UnidentifiedImageError(msg) from e
.venv/Lib/site-packages/PIL/GifImagePlugin.py ADDED
@@ -0,0 +1,1097 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # GIF file handling
6
+ #
7
+ # History:
8
+ # 1995-09-01 fl Created
9
+ # 1996-12-14 fl Added interlace support
10
+ # 1996-12-30 fl Added animation support
11
+ # 1997-01-05 fl Added write support, fixed local colour map bug
12
+ # 1997-02-23 fl Make sure to load raster data in getdata()
13
+ # 1997-07-05 fl Support external decoder (0.4)
14
+ # 1998-07-09 fl Handle all modes when saving (0.5)
15
+ # 1998-07-15 fl Renamed offset attribute to avoid name clash
16
+ # 2001-04-16 fl Added rewind support (seek to frame 0) (0.6)
17
+ # 2001-04-17 fl Added palette optimization (0.7)
18
+ # 2002-06-06 fl Added transparency support for save (0.8)
19
+ # 2004-02-24 fl Disable interlacing for small images
20
+ #
21
+ # Copyright (c) 1997-2004 by Secret Labs AB
22
+ # Copyright (c) 1995-2004 by Fredrik Lundh
23
+ #
24
+ # See the README file for information on usage and redistribution.
25
+ #
26
+ from __future__ import annotations
27
+
28
+ import itertools
29
+ import math
30
+ import os
31
+ import subprocess
32
+ from enum import IntEnum
33
+
34
+ from . import (
35
+ Image,
36
+ ImageChops,
37
+ ImageFile,
38
+ ImageMath,
39
+ ImageOps,
40
+ ImagePalette,
41
+ ImageSequence,
42
+ )
43
+ from ._binary import i16le as i16
44
+ from ._binary import o8
45
+ from ._binary import o16le as o16
46
+
47
+
48
+ class LoadingStrategy(IntEnum):
49
+ """.. versionadded:: 9.1.0"""
50
+
51
+ RGB_AFTER_FIRST = 0
52
+ RGB_AFTER_DIFFERENT_PALETTE_ONLY = 1
53
+ RGB_ALWAYS = 2
54
+
55
+
56
+ #: .. versionadded:: 9.1.0
57
+ LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST
58
+
59
+ # --------------------------------------------------------------------
60
+ # Identify/read GIF files
61
+
62
+
63
+ def _accept(prefix):
64
+ return prefix[:6] in [b"GIF87a", b"GIF89a"]
65
+
66
+
67
+ ##
68
+ # Image plugin for GIF images. This plugin supports both GIF87 and
69
+ # GIF89 images.
70
+
71
+
72
+ class GifImageFile(ImageFile.ImageFile):
73
+ format = "GIF"
74
+ format_description = "Compuserve GIF"
75
+ _close_exclusive_fp_after_loading = False
76
+
77
+ global_palette = None
78
+
79
+ def data(self):
80
+ s = self.fp.read(1)
81
+ if s and s[0]:
82
+ return self.fp.read(s[0])
83
+ return None
84
+
85
+ def _is_palette_needed(self, p):
86
+ for i in range(0, len(p), 3):
87
+ if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
88
+ return True
89
+ return False
90
+
91
+ def _open(self):
92
+ # Screen
93
+ s = self.fp.read(13)
94
+ if not _accept(s):
95
+ msg = "not a GIF file"
96
+ raise SyntaxError(msg)
97
+
98
+ self.info["version"] = s[:6]
99
+ self._size = i16(s, 6), i16(s, 8)
100
+ self.tile = []
101
+ flags = s[10]
102
+ bits = (flags & 7) + 1
103
+
104
+ if flags & 128:
105
+ # get global palette
106
+ self.info["background"] = s[11]
107
+ # check if palette contains colour indices
108
+ p = self.fp.read(3 << bits)
109
+ if self._is_palette_needed(p):
110
+ p = ImagePalette.raw("RGB", p)
111
+ self.global_palette = self.palette = p
112
+
113
+ self._fp = self.fp # FIXME: hack
114
+ self.__rewind = self.fp.tell()
115
+ self._n_frames = None
116
+ self._is_animated = None
117
+ self._seek(0) # get ready to read first frame
118
+
119
+ @property
120
+ def n_frames(self):
121
+ if self._n_frames is None:
122
+ current = self.tell()
123
+ try:
124
+ while True:
125
+ self._seek(self.tell() + 1, False)
126
+ except EOFError:
127
+ self._n_frames = self.tell() + 1
128
+ self.seek(current)
129
+ return self._n_frames
130
+
131
+ @property
132
+ def is_animated(self):
133
+ if self._is_animated is None:
134
+ if self._n_frames is not None:
135
+ self._is_animated = self._n_frames != 1
136
+ else:
137
+ current = self.tell()
138
+ if current:
139
+ self._is_animated = True
140
+ else:
141
+ try:
142
+ self._seek(1, False)
143
+ self._is_animated = True
144
+ except EOFError:
145
+ self._is_animated = False
146
+
147
+ self.seek(current)
148
+ return self._is_animated
149
+
150
+ def seek(self, frame):
151
+ if not self._seek_check(frame):
152
+ return
153
+ if frame < self.__frame:
154
+ self.im = None
155
+ self._seek(0)
156
+
157
+ last_frame = self.__frame
158
+ for f in range(self.__frame + 1, frame + 1):
159
+ try:
160
+ self._seek(f)
161
+ except EOFError as e:
162
+ self.seek(last_frame)
163
+ msg = "no more images in GIF file"
164
+ raise EOFError(msg) from e
165
+
166
+ def _seek(self, frame, update_image=True):
167
+ if frame == 0:
168
+ # rewind
169
+ self.__offset = 0
170
+ self.dispose = None
171
+ self.__frame = -1
172
+ self._fp.seek(self.__rewind)
173
+ self.disposal_method = 0
174
+ if "comment" in self.info:
175
+ del self.info["comment"]
176
+ else:
177
+ # ensure that the previous frame was loaded
178
+ if self.tile and update_image:
179
+ self.load()
180
+
181
+ if frame != self.__frame + 1:
182
+ msg = f"cannot seek to frame {frame}"
183
+ raise ValueError(msg)
184
+
185
+ self.fp = self._fp
186
+ if self.__offset:
187
+ # backup to last frame
188
+ self.fp.seek(self.__offset)
189
+ while self.data():
190
+ pass
191
+ self.__offset = 0
192
+
193
+ s = self.fp.read(1)
194
+ if not s or s == b";":
195
+ msg = "no more images in GIF file"
196
+ raise EOFError(msg)
197
+
198
+ palette = None
199
+
200
+ info = {}
201
+ frame_transparency = None
202
+ interlace = None
203
+ frame_dispose_extent = None
204
+ while True:
205
+ if not s:
206
+ s = self.fp.read(1)
207
+ if not s or s == b";":
208
+ break
209
+
210
+ elif s == b"!":
211
+ #
212
+ # extensions
213
+ #
214
+ s = self.fp.read(1)
215
+ block = self.data()
216
+ if s[0] == 249:
217
+ #
218
+ # graphic control extension
219
+ #
220
+ flags = block[0]
221
+ if flags & 1:
222
+ frame_transparency = block[3]
223
+ info["duration"] = i16(block, 1) * 10
224
+
225
+ # disposal method - find the value of bits 4 - 6
226
+ dispose_bits = 0b00011100 & flags
227
+ dispose_bits = dispose_bits >> 2
228
+ if dispose_bits:
229
+ # only set the dispose if it is not
230
+ # unspecified. I'm not sure if this is
231
+ # correct, but it seems to prevent the last
232
+ # frame from looking odd for some animations
233
+ self.disposal_method = dispose_bits
234
+ elif s[0] == 254:
235
+ #
236
+ # comment extension
237
+ #
238
+ comment = b""
239
+
240
+ # Read this comment block
241
+ while block:
242
+ comment += block
243
+ block = self.data()
244
+
245
+ if "comment" in info:
246
+ # If multiple comment blocks in frame, separate with \n
247
+ info["comment"] += b"\n" + comment
248
+ else:
249
+ info["comment"] = comment
250
+ s = None
251
+ continue
252
+ elif s[0] == 255 and frame == 0:
253
+ #
254
+ # application extension
255
+ #
256
+ info["extension"] = block, self.fp.tell()
257
+ if block[:11] == b"NETSCAPE2.0":
258
+ block = self.data()
259
+ if len(block) >= 3 and block[0] == 1:
260
+ self.info["loop"] = i16(block, 1)
261
+ while self.data():
262
+ pass
263
+
264
+ elif s == b",":
265
+ #
266
+ # local image
267
+ #
268
+ s = self.fp.read(9)
269
+
270
+ # extent
271
+ x0, y0 = i16(s, 0), i16(s, 2)
272
+ x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
273
+ if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
274
+ self._size = max(x1, self.size[0]), max(y1, self.size[1])
275
+ Image._decompression_bomb_check(self._size)
276
+ frame_dispose_extent = x0, y0, x1, y1
277
+ flags = s[8]
278
+
279
+ interlace = (flags & 64) != 0
280
+
281
+ if flags & 128:
282
+ bits = (flags & 7) + 1
283
+ p = self.fp.read(3 << bits)
284
+ if self._is_palette_needed(p):
285
+ palette = ImagePalette.raw("RGB", p)
286
+ else:
287
+ palette = False
288
+
289
+ # image data
290
+ bits = self.fp.read(1)[0]
291
+ self.__offset = self.fp.tell()
292
+ break
293
+ s = None
294
+
295
+ if interlace is None:
296
+ msg = "image not found in GIF frame"
297
+ raise EOFError(msg)
298
+
299
+ self.__frame = frame
300
+ if not update_image:
301
+ return
302
+
303
+ self.tile = []
304
+
305
+ if self.dispose:
306
+ self.im.paste(self.dispose, self.dispose_extent)
307
+
308
+ self._frame_palette = palette if palette is not None else self.global_palette
309
+ self._frame_transparency = frame_transparency
310
+ if frame == 0:
311
+ if self._frame_palette:
312
+ if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
313
+ self._mode = "RGBA" if frame_transparency is not None else "RGB"
314
+ else:
315
+ self._mode = "P"
316
+ else:
317
+ self._mode = "L"
318
+
319
+ if not palette and self.global_palette:
320
+ from copy import copy
321
+
322
+ palette = copy(self.global_palette)
323
+ self.palette = palette
324
+ else:
325
+ if self.mode == "P":
326
+ if (
327
+ LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
328
+ or palette
329
+ ):
330
+ self.pyaccess = None
331
+ if "transparency" in self.info:
332
+ self.im.putpalettealpha(self.info["transparency"], 0)
333
+ self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
334
+ self._mode = "RGBA"
335
+ del self.info["transparency"]
336
+ else:
337
+ self._mode = "RGB"
338
+ self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
339
+
340
+ def _rgb(color):
341
+ if self._frame_palette:
342
+ if color * 3 + 3 > len(self._frame_palette.palette):
343
+ color = 0
344
+ color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
345
+ else:
346
+ color = (color, color, color)
347
+ return color
348
+
349
+ self.dispose_extent = frame_dispose_extent
350
+ try:
351
+ if self.disposal_method < 2:
352
+ # do not dispose or none specified
353
+ self.dispose = None
354
+ elif self.disposal_method == 2:
355
+ # replace with background colour
356
+
357
+ # only dispose the extent in this frame
358
+ x0, y0, x1, y1 = self.dispose_extent
359
+ dispose_size = (x1 - x0, y1 - y0)
360
+
361
+ Image._decompression_bomb_check(dispose_size)
362
+
363
+ # by convention, attempt to use transparency first
364
+ dispose_mode = "P"
365
+ color = self.info.get("transparency", frame_transparency)
366
+ if color is not None:
367
+ if self.mode in ("RGB", "RGBA"):
368
+ dispose_mode = "RGBA"
369
+ color = _rgb(color) + (0,)
370
+ else:
371
+ color = self.info.get("background", 0)
372
+ if self.mode in ("RGB", "RGBA"):
373
+ dispose_mode = "RGB"
374
+ color = _rgb(color)
375
+ self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
376
+ else:
377
+ # replace with previous contents
378
+ if self.im is not None:
379
+ # only dispose the extent in this frame
380
+ self.dispose = self._crop(self.im, self.dispose_extent)
381
+ elif frame_transparency is not None:
382
+ x0, y0, x1, y1 = self.dispose_extent
383
+ dispose_size = (x1 - x0, y1 - y0)
384
+
385
+ Image._decompression_bomb_check(dispose_size)
386
+ dispose_mode = "P"
387
+ color = frame_transparency
388
+ if self.mode in ("RGB", "RGBA"):
389
+ dispose_mode = "RGBA"
390
+ color = _rgb(frame_transparency) + (0,)
391
+ self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
392
+ except AttributeError:
393
+ pass
394
+
395
+ if interlace is not None:
396
+ transparency = -1
397
+ if frame_transparency is not None:
398
+ if frame == 0:
399
+ if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS:
400
+ self.info["transparency"] = frame_transparency
401
+ elif self.mode not in ("RGB", "RGBA"):
402
+ transparency = frame_transparency
403
+ self.tile = [
404
+ (
405
+ "gif",
406
+ (x0, y0, x1, y1),
407
+ self.__offset,
408
+ (bits, interlace, transparency),
409
+ )
410
+ ]
411
+
412
+ if info.get("comment"):
413
+ self.info["comment"] = info["comment"]
414
+ for k in ["duration", "extension"]:
415
+ if k in info:
416
+ self.info[k] = info[k]
417
+ elif k in self.info:
418
+ del self.info[k]
419
+
420
+ def load_prepare(self):
421
+ temp_mode = "P" if self._frame_palette else "L"
422
+ self._prev_im = None
423
+ if self.__frame == 0:
424
+ if self._frame_transparency is not None:
425
+ self.im = Image.core.fill(
426
+ temp_mode, self.size, self._frame_transparency
427
+ )
428
+ elif self.mode in ("RGB", "RGBA"):
429
+ self._prev_im = self.im
430
+ if self._frame_palette:
431
+ self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
432
+ self.im.putpalette(*self._frame_palette.getdata())
433
+ else:
434
+ self.im = None
435
+ self._mode = temp_mode
436
+ self._frame_palette = None
437
+
438
+ super().load_prepare()
439
+
440
+ def load_end(self):
441
+ if self.__frame == 0:
442
+ if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
443
+ if self._frame_transparency is not None:
444
+ self.im.putpalettealpha(self._frame_transparency, 0)
445
+ self._mode = "RGBA"
446
+ else:
447
+ self._mode = "RGB"
448
+ self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
449
+ return
450
+ if not self._prev_im:
451
+ return
452
+ if self._frame_transparency is not None:
453
+ self.im.putpalettealpha(self._frame_transparency, 0)
454
+ frame_im = self.im.convert("RGBA")
455
+ else:
456
+ frame_im = self.im.convert("RGB")
457
+ frame_im = self._crop(frame_im, self.dispose_extent)
458
+
459
+ self.im = self._prev_im
460
+ self._mode = self.im.mode
461
+ if frame_im.mode == "RGBA":
462
+ self.im.paste(frame_im, self.dispose_extent, frame_im)
463
+ else:
464
+ self.im.paste(frame_im, self.dispose_extent)
465
+
466
+ def tell(self):
467
+ return self.__frame
468
+
469
+
470
+ # --------------------------------------------------------------------
471
+ # Write GIF files
472
+
473
+
474
+ RAWMODE = {"1": "L", "L": "L", "P": "P"}
475
+
476
+
477
+ def _normalize_mode(im):
478
+ """
479
+ Takes an image (or frame), returns an image in a mode that is appropriate
480
+ for saving in a Gif.
481
+
482
+ It may return the original image, or it may return an image converted to
483
+ palette or 'L' mode.
484
+
485
+ :param im: Image object
486
+ :returns: Image object
487
+ """
488
+ if im.mode in RAWMODE:
489
+ im.load()
490
+ return im
491
+ if Image.getmodebase(im.mode) == "RGB":
492
+ im = im.convert("P", palette=Image.Palette.ADAPTIVE)
493
+ if im.palette.mode == "RGBA":
494
+ for rgba in im.palette.colors:
495
+ if rgba[3] == 0:
496
+ im.info["transparency"] = im.palette.colors[rgba]
497
+ break
498
+ return im
499
+ return im.convert("L")
500
+
501
+
502
+ def _normalize_palette(im, palette, info):
503
+ """
504
+ Normalizes the palette for image.
505
+ - Sets the palette to the incoming palette, if provided.
506
+ - Ensures that there's a palette for L mode images
507
+ - Optimizes the palette if necessary/desired.
508
+
509
+ :param im: Image object
510
+ :param palette: bytes object containing the source palette, or ....
511
+ :param info: encoderinfo
512
+ :returns: Image object
513
+ """
514
+ source_palette = None
515
+ if palette:
516
+ # a bytes palette
517
+ if isinstance(palette, (bytes, bytearray, list)):
518
+ source_palette = bytearray(palette[:768])
519
+ if isinstance(palette, ImagePalette.ImagePalette):
520
+ source_palette = bytearray(palette.palette)
521
+
522
+ if im.mode == "P":
523
+ if not source_palette:
524
+ source_palette = im.im.getpalette("RGB")[:768]
525
+ else: # L-mode
526
+ if not source_palette:
527
+ source_palette = bytearray(i // 3 for i in range(768))
528
+ im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
529
+
530
+ if palette:
531
+ used_palette_colors = []
532
+ for i in range(0, len(source_palette), 3):
533
+ source_color = tuple(source_palette[i : i + 3])
534
+ index = im.palette.colors.get(source_color)
535
+ if index in used_palette_colors:
536
+ index = None
537
+ used_palette_colors.append(index)
538
+ for i, index in enumerate(used_palette_colors):
539
+ if index is None:
540
+ for j in range(len(used_palette_colors)):
541
+ if j not in used_palette_colors:
542
+ used_palette_colors[i] = j
543
+ break
544
+ im = im.remap_palette(used_palette_colors)
545
+ else:
546
+ used_palette_colors = _get_optimize(im, info)
547
+ if used_palette_colors is not None:
548
+ im = im.remap_palette(used_palette_colors, source_palette)
549
+ if "transparency" in info:
550
+ try:
551
+ info["transparency"] = used_palette_colors.index(
552
+ info["transparency"]
553
+ )
554
+ except ValueError:
555
+ del info["transparency"]
556
+ return im
557
+
558
+ im.palette.palette = source_palette
559
+ return im
560
+
561
+
562
+ def _write_single_frame(im, fp, palette):
563
+ im_out = _normalize_mode(im)
564
+ for k, v in im_out.info.items():
565
+ im.encoderinfo.setdefault(k, v)
566
+ im_out = _normalize_palette(im_out, palette, im.encoderinfo)
567
+
568
+ for s in _get_global_header(im_out, im.encoderinfo):
569
+ fp.write(s)
570
+
571
+ # local image header
572
+ flags = 0
573
+ if get_interlace(im):
574
+ flags = flags | 64
575
+ _write_local_header(fp, im, (0, 0), flags)
576
+
577
+ im_out.encoderconfig = (8, get_interlace(im))
578
+ ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
579
+
580
+ fp.write(b"\0") # end of image data
581
+
582
+
583
+ def _getbbox(base_im, im_frame):
584
+ if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
585
+ im_frame = im_frame.convert("RGBA")
586
+ base_im = base_im.convert("RGBA")
587
+ delta = ImageChops.subtract_modulo(im_frame, base_im)
588
+ return delta, delta.getbbox(alpha_only=False)
589
+
590
+
591
+ def _write_multiple_frames(im, fp, palette):
592
+ duration = im.encoderinfo.get("duration")
593
+ disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
594
+
595
+ im_frames = []
596
+ previous_im = None
597
+ frame_count = 0
598
+ background_im = None
599
+ for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
600
+ for im_frame in ImageSequence.Iterator(imSequence):
601
+ # a copy is required here since seek can still mutate the image
602
+ im_frame = _normalize_mode(im_frame.copy())
603
+ if frame_count == 0:
604
+ for k, v in im_frame.info.items():
605
+ if k == "transparency":
606
+ continue
607
+ im.encoderinfo.setdefault(k, v)
608
+
609
+ encoderinfo = im.encoderinfo.copy()
610
+ if "transparency" in im_frame.info:
611
+ encoderinfo.setdefault("transparency", im_frame.info["transparency"])
612
+ im_frame = _normalize_palette(im_frame, palette, encoderinfo)
613
+ if isinstance(duration, (list, tuple)):
614
+ encoderinfo["duration"] = duration[frame_count]
615
+ elif duration is None and "duration" in im_frame.info:
616
+ encoderinfo["duration"] = im_frame.info["duration"]
617
+ if isinstance(disposal, (list, tuple)):
618
+ encoderinfo["disposal"] = disposal[frame_count]
619
+ frame_count += 1
620
+
621
+ diff_frame = None
622
+ if im_frames:
623
+ # delta frame
624
+ delta, bbox = _getbbox(previous_im, im_frame)
625
+ if not bbox:
626
+ # This frame is identical to the previous frame
627
+ if encoderinfo.get("duration"):
628
+ im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[
629
+ "duration"
630
+ ]
631
+ continue
632
+ if encoderinfo.get("disposal") == 2:
633
+ if background_im is None:
634
+ color = im.encoderinfo.get(
635
+ "transparency", im.info.get("transparency", (0, 0, 0))
636
+ )
637
+ background = _get_background(im_frame, color)
638
+ background_im = Image.new("P", im_frame.size, background)
639
+ background_im.putpalette(im_frames[0]["im"].palette)
640
+ delta, bbox = _getbbox(background_im, im_frame)
641
+ if encoderinfo.get("optimize") and im_frame.mode != "1":
642
+ if "transparency" not in encoderinfo:
643
+ try:
644
+ encoderinfo[
645
+ "transparency"
646
+ ] = im_frame.palette._new_color_index(im_frame)
647
+ except ValueError:
648
+ pass
649
+ if "transparency" in encoderinfo:
650
+ # When the delta is zero, fill the image with transparency
651
+ diff_frame = im_frame.copy()
652
+ fill = Image.new(
653
+ "P", diff_frame.size, encoderinfo["transparency"]
654
+ )
655
+ if delta.mode == "RGBA":
656
+ r, g, b, a = delta.split()
657
+ mask = ImageMath.eval(
658
+ "convert(max(max(max(r, g), b), a) * 255, '1')",
659
+ r=r,
660
+ g=g,
661
+ b=b,
662
+ a=a,
663
+ )
664
+ else:
665
+ if delta.mode == "P":
666
+ # Convert to L without considering palette
667
+ delta_l = Image.new("L", delta.size)
668
+ delta_l.putdata(delta.getdata())
669
+ delta = delta_l
670
+ mask = ImageMath.eval("convert(im * 255, '1')", im=delta)
671
+ diff_frame.paste(fill, mask=ImageOps.invert(mask))
672
+ else:
673
+ bbox = None
674
+ previous_im = im_frame
675
+ im_frames.append(
676
+ {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
677
+ )
678
+
679
+ if len(im_frames) == 1:
680
+ if "duration" in im.encoderinfo:
681
+ # Since multiple frames will not be written, use the combined duration
682
+ im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"]
683
+ return
684
+
685
+ for frame_data in im_frames:
686
+ im_frame = frame_data["im"]
687
+ if not frame_data["bbox"]:
688
+ # global header
689
+ for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
690
+ fp.write(s)
691
+ offset = (0, 0)
692
+ else:
693
+ # compress difference
694
+ if not palette:
695
+ frame_data["encoderinfo"]["include_color_table"] = True
696
+
697
+ im_frame = im_frame.crop(frame_data["bbox"])
698
+ offset = frame_data["bbox"][:2]
699
+ _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
700
+ return True
701
+
702
+
703
+ def _save_all(im, fp, filename):
704
+ _save(im, fp, filename, save_all=True)
705
+
706
+
707
+ def _save(im, fp, filename, save_all=False):
708
+ # header
709
+ if "palette" in im.encoderinfo or "palette" in im.info:
710
+ palette = im.encoderinfo.get("palette", im.info.get("palette"))
711
+ else:
712
+ palette = None
713
+ im.encoderinfo.setdefault("optimize", True)
714
+
715
+ if not save_all or not _write_multiple_frames(im, fp, palette):
716
+ _write_single_frame(im, fp, palette)
717
+
718
+ fp.write(b";") # end of file
719
+
720
+ if hasattr(fp, "flush"):
721
+ fp.flush()
722
+
723
+
724
+ def get_interlace(im):
725
+ interlace = im.encoderinfo.get("interlace", 1)
726
+
727
+ # workaround for @PIL153
728
+ if min(im.size) < 16:
729
+ interlace = 0
730
+
731
+ return interlace
732
+
733
+
734
+ def _write_local_header(fp, im, offset, flags):
735
+ try:
736
+ transparency = im.encoderinfo["transparency"]
737
+ except KeyError:
738
+ transparency = None
739
+
740
+ if "duration" in im.encoderinfo:
741
+ duration = int(im.encoderinfo["duration"] / 10)
742
+ else:
743
+ duration = 0
744
+
745
+ disposal = int(im.encoderinfo.get("disposal", 0))
746
+
747
+ if transparency is not None or duration != 0 or disposal:
748
+ packed_flag = 1 if transparency is not None else 0
749
+ packed_flag |= disposal << 2
750
+
751
+ fp.write(
752
+ b"!"
753
+ + o8(249) # extension intro
754
+ + o8(4) # length
755
+ + o8(packed_flag) # packed fields
756
+ + o16(duration) # duration
757
+ + o8(transparency or 0) # transparency index
758
+ + o8(0)
759
+ )
760
+
761
+ include_color_table = im.encoderinfo.get("include_color_table")
762
+ if include_color_table:
763
+ palette_bytes = _get_palette_bytes(im)
764
+ color_table_size = _get_color_table_size(palette_bytes)
765
+ if color_table_size:
766
+ flags = flags | 128 # local color table flag
767
+ flags = flags | color_table_size
768
+
769
+ fp.write(
770
+ b","
771
+ + o16(offset[0]) # offset
772
+ + o16(offset[1])
773
+ + o16(im.size[0]) # size
774
+ + o16(im.size[1])
775
+ + o8(flags) # flags
776
+ )
777
+ if include_color_table and color_table_size:
778
+ fp.write(_get_header_palette(palette_bytes))
779
+ fp.write(o8(8)) # bits
780
+
781
+
782
+ def _save_netpbm(im, fp, filename):
783
+ # Unused by default.
784
+ # To use, uncomment the register_save call at the end of the file.
785
+ #
786
+ # If you need real GIF compression and/or RGB quantization, you
787
+ # can use the external NETPBM/PBMPLUS utilities. See comments
788
+ # below for information on how to enable this.
789
+ tempfile = im._dump()
790
+
791
+ try:
792
+ with open(filename, "wb") as f:
793
+ if im.mode != "RGB":
794
+ subprocess.check_call(
795
+ ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL
796
+ )
797
+ else:
798
+ # Pipe ppmquant output into ppmtogif
799
+ # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
800
+ quant_cmd = ["ppmquant", "256", tempfile]
801
+ togif_cmd = ["ppmtogif"]
802
+ quant_proc = subprocess.Popen(
803
+ quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
804
+ )
805
+ togif_proc = subprocess.Popen(
806
+ togif_cmd,
807
+ stdin=quant_proc.stdout,
808
+ stdout=f,
809
+ stderr=subprocess.DEVNULL,
810
+ )
811
+
812
+ # Allow ppmquant to receive SIGPIPE if ppmtogif exits
813
+ quant_proc.stdout.close()
814
+
815
+ retcode = quant_proc.wait()
816
+ if retcode:
817
+ raise subprocess.CalledProcessError(retcode, quant_cmd)
818
+
819
+ retcode = togif_proc.wait()
820
+ if retcode:
821
+ raise subprocess.CalledProcessError(retcode, togif_cmd)
822
+ finally:
823
+ try:
824
+ os.unlink(tempfile)
825
+ except OSError:
826
+ pass
827
+
828
+
829
+ # Force optimization so that we can test performance against
830
+ # cases where it took lots of memory and time previously.
831
+ _FORCE_OPTIMIZE = False
832
+
833
+
834
+ def _get_optimize(im, info):
835
+ """
836
+ Palette optimization is a potentially expensive operation.
837
+
838
+ This function determines if the palette should be optimized using
839
+ some heuristics, then returns the list of palette entries in use.
840
+
841
+ :param im: Image object
842
+ :param info: encoderinfo
843
+ :returns: list of indexes of palette entries in use, or None
844
+ """
845
+ if im.mode in ("P", "L") and info and info.get("optimize"):
846
+ # Potentially expensive operation.
847
+
848
+ # The palette saves 3 bytes per color not used, but palette
849
+ # lengths are restricted to 3*(2**N) bytes. Max saving would
850
+ # be 768 -> 6 bytes if we went all the way down to 2 colors.
851
+ # * If we're over 128 colors, we can't save any space.
852
+ # * If there aren't any holes, it's not worth collapsing.
853
+ # * If we have a 'large' image, the palette is in the noise.
854
+
855
+ # create the new palette if not every color is used
856
+ optimise = _FORCE_OPTIMIZE or im.mode == "L"
857
+ if optimise or im.width * im.height < 512 * 512:
858
+ # check which colors are used
859
+ used_palette_colors = []
860
+ for i, count in enumerate(im.histogram()):
861
+ if count:
862
+ used_palette_colors.append(i)
863
+
864
+ if optimise or max(used_palette_colors) >= len(used_palette_colors):
865
+ return used_palette_colors
866
+
867
+ num_palette_colors = len(im.palette.palette) // Image.getmodebands(
868
+ im.palette.mode
869
+ )
870
+ current_palette_size = 1 << (num_palette_colors - 1).bit_length()
871
+ if (
872
+ # check that the palette would become smaller when saved
873
+ len(used_palette_colors) <= current_palette_size // 2
874
+ # check that the palette is not already the smallest possible size
875
+ and current_palette_size > 2
876
+ ):
877
+ return used_palette_colors
878
+
879
+
880
+ def _get_color_table_size(palette_bytes):
881
+ # calculate the palette size for the header
882
+ if not palette_bytes:
883
+ return 0
884
+ elif len(palette_bytes) < 9:
885
+ return 1
886
+ else:
887
+ return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
888
+
889
+
890
+ def _get_header_palette(palette_bytes):
891
+ """
892
+ Returns the palette, null padded to the next power of 2 (*3) bytes
893
+ suitable for direct inclusion in the GIF header
894
+
895
+ :param palette_bytes: Unpadded palette bytes, in RGBRGB form
896
+ :returns: Null padded palette
897
+ """
898
+ color_table_size = _get_color_table_size(palette_bytes)
899
+
900
+ # add the missing amount of bytes
901
+ # the palette has to be 2<<n in size
902
+ actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
903
+ if actual_target_size_diff > 0:
904
+ palette_bytes += o8(0) * 3 * actual_target_size_diff
905
+ return palette_bytes
906
+
907
+
908
+ def _get_palette_bytes(im):
909
+ """
910
+ Gets the palette for inclusion in the gif header
911
+
912
+ :param im: Image object
913
+ :returns: Bytes, len<=768 suitable for inclusion in gif header
914
+ """
915
+ return im.palette.palette if im.palette else b""
916
+
917
+
918
+ def _get_background(im, info_background):
919
+ background = 0
920
+ if info_background:
921
+ if isinstance(info_background, tuple):
922
+ # WebPImagePlugin stores an RGBA value in info["background"]
923
+ # So it must be converted to the same format as GifImagePlugin's
924
+ # info["background"] - a global color table index
925
+ try:
926
+ background = im.palette.getcolor(info_background, im)
927
+ except ValueError as e:
928
+ if str(e) not in (
929
+ # If all 256 colors are in use,
930
+ # then there is no need for the background color
931
+ "cannot allocate more than 256 colors",
932
+ # Ignore non-opaque WebP background
933
+ "cannot add non-opaque RGBA color to RGB palette",
934
+ ):
935
+ raise
936
+ else:
937
+ background = info_background
938
+ return background
939
+
940
+
941
+ def _get_global_header(im, info):
942
+ """Return a list of strings representing a GIF header"""
943
+
944
+ # Header Block
945
+ # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
946
+
947
+ version = b"87a"
948
+ if im.info.get("version") == b"89a" or (
949
+ info
950
+ and (
951
+ "transparency" in info
952
+ or info.get("loop") is not None
953
+ or info.get("duration")
954
+ or info.get("comment")
955
+ )
956
+ ):
957
+ version = b"89a"
958
+
959
+ background = _get_background(im, info.get("background"))
960
+
961
+ palette_bytes = _get_palette_bytes(im)
962
+ color_table_size = _get_color_table_size(palette_bytes)
963
+
964
+ header = [
965
+ b"GIF" # signature
966
+ + version # version
967
+ + o16(im.size[0]) # canvas width
968
+ + o16(im.size[1]), # canvas height
969
+ # Logical Screen Descriptor
970
+ # size of global color table + global color table flag
971
+ o8(color_table_size + 128), # packed fields
972
+ # background + reserved/aspect
973
+ o8(background) + o8(0),
974
+ # Global Color Table
975
+ _get_header_palette(palette_bytes),
976
+ ]
977
+ if info.get("loop") is not None:
978
+ header.append(
979
+ b"!"
980
+ + o8(255) # extension intro
981
+ + o8(11)
982
+ + b"NETSCAPE2.0"
983
+ + o8(3)
984
+ + o8(1)
985
+ + o16(info["loop"]) # number of loops
986
+ + o8(0)
987
+ )
988
+ if info.get("comment"):
989
+ comment_block = b"!" + o8(254) # extension intro
990
+
991
+ comment = info["comment"]
992
+ if isinstance(comment, str):
993
+ comment = comment.encode()
994
+ for i in range(0, len(comment), 255):
995
+ subblock = comment[i : i + 255]
996
+ comment_block += o8(len(subblock)) + subblock
997
+
998
+ comment_block += o8(0)
999
+ header.append(comment_block)
1000
+ return header
1001
+
1002
+
1003
+ def _write_frame_data(fp, im_frame, offset, params):
1004
+ try:
1005
+ im_frame.encoderinfo = params
1006
+
1007
+ # local image header
1008
+ _write_local_header(fp, im_frame, offset, 0)
1009
+
1010
+ ImageFile._save(
1011
+ im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
1012
+ )
1013
+
1014
+ fp.write(b"\0") # end of image data
1015
+ finally:
1016
+ del im_frame.encoderinfo
1017
+
1018
+
1019
+ # --------------------------------------------------------------------
1020
+ # Legacy GIF utilities
1021
+
1022
+
1023
+ def getheader(im, palette=None, info=None):
1024
+ """
1025
+ Legacy Method to get Gif data from image.
1026
+
1027
+ Warning:: May modify image data.
1028
+
1029
+ :param im: Image object
1030
+ :param palette: bytes object containing the source palette, or ....
1031
+ :param info: encoderinfo
1032
+ :returns: tuple of(list of header items, optimized palette)
1033
+
1034
+ """
1035
+ used_palette_colors = _get_optimize(im, info)
1036
+
1037
+ if info is None:
1038
+ info = {}
1039
+
1040
+ if "background" not in info and "background" in im.info:
1041
+ info["background"] = im.info["background"]
1042
+
1043
+ im_mod = _normalize_palette(im, palette, info)
1044
+ im.palette = im_mod.palette
1045
+ im.im = im_mod.im
1046
+ header = _get_global_header(im, info)
1047
+
1048
+ return header, used_palette_colors
1049
+
1050
+
1051
+ def getdata(im, offset=(0, 0), **params):
1052
+ """
1053
+ Legacy Method
1054
+
1055
+ Return a list of strings representing this image.
1056
+ The first string is a local image header, the rest contains
1057
+ encoded image data.
1058
+
1059
+ To specify duration, add the time in milliseconds,
1060
+ e.g. ``getdata(im_frame, duration=1000)``
1061
+
1062
+ :param im: Image object
1063
+ :param offset: Tuple of (x, y) pixels. Defaults to (0, 0)
1064
+ :param \\**params: e.g. duration or other encoder info parameters
1065
+ :returns: List of bytes containing GIF encoded frame data
1066
+
1067
+ """
1068
+
1069
+ class Collector:
1070
+ data = []
1071
+
1072
+ def write(self, data):
1073
+ self.data.append(data)
1074
+
1075
+ im.load() # make sure raster data is available
1076
+
1077
+ fp = Collector()
1078
+
1079
+ _write_frame_data(fp, im, offset, params)
1080
+
1081
+ return fp.data
1082
+
1083
+
1084
+ # --------------------------------------------------------------------
1085
+ # Registry
1086
+
1087
+ Image.register_open(GifImageFile.format, GifImageFile, _accept)
1088
+ Image.register_save(GifImageFile.format, _save)
1089
+ Image.register_save_all(GifImageFile.format, _save_all)
1090
+ Image.register_extension(GifImageFile.format, ".gif")
1091
+ Image.register_mime(GifImageFile.format, "image/gif")
1092
+
1093
+ #
1094
+ # Uncomment the following line if you wish to use NETPBM/PBMPLUS
1095
+ # instead of the built-in "uncompressed" GIF encoder
1096
+
1097
+ # Image.register_save(GifImageFile.format, _save_netpbm)
.venv/Lib/site-packages/PIL/GimpGradientFile.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # stuff to read (and render) GIMP gradient files
6
+ #
7
+ # History:
8
+ # 97-08-23 fl Created
9
+ #
10
+ # Copyright (c) Secret Labs AB 1997.
11
+ # Copyright (c) Fredrik Lundh 1997.
12
+ #
13
+ # See the README file for information on usage and redistribution.
14
+ #
15
+
16
+ """
17
+ Stuff to translate curve segments to palette values (derived from
18
+ the corresponding code in GIMP, written by Federico Mena Quintero.
19
+ See the GIMP distribution for more information.)
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from math import log, pi, sin, sqrt
24
+
25
+ from ._binary import o8
26
+
27
+ EPSILON = 1e-10
28
+ """""" # Enable auto-doc for data member
29
+
30
+
31
+ def linear(middle, pos):
32
+ if pos <= middle:
33
+ if middle < EPSILON:
34
+ return 0.0
35
+ else:
36
+ return 0.5 * pos / middle
37
+ else:
38
+ pos = pos - middle
39
+ middle = 1.0 - middle
40
+ if middle < EPSILON:
41
+ return 1.0
42
+ else:
43
+ return 0.5 + 0.5 * pos / middle
44
+
45
+
46
+ def curved(middle, pos):
47
+ return pos ** (log(0.5) / log(max(middle, EPSILON)))
48
+
49
+
50
+ def sine(middle, pos):
51
+ return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
52
+
53
+
54
+ def sphere_increasing(middle, pos):
55
+ return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
56
+
57
+
58
+ def sphere_decreasing(middle, pos):
59
+ return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
60
+
61
+
62
+ SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
63
+ """""" # Enable auto-doc for data member
64
+
65
+
66
+ class GradientFile:
67
+ gradient = None
68
+
69
+ def getpalette(self, entries=256):
70
+ palette = []
71
+
72
+ ix = 0
73
+ x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
74
+
75
+ for i in range(entries):
76
+ x = i / (entries - 1)
77
+
78
+ while x1 < x:
79
+ ix += 1
80
+ x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
81
+
82
+ w = x1 - x0
83
+
84
+ if w < EPSILON:
85
+ scale = segment(0.5, 0.5)
86
+ else:
87
+ scale = segment((xm - x0) / w, (x - x0) / w)
88
+
89
+ # expand to RGBA
90
+ r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
91
+ g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
92
+ b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
93
+ a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
94
+
95
+ # add to palette
96
+ palette.append(r + g + b + a)
97
+
98
+ return b"".join(palette), "RGBA"
99
+
100
+
101
+ class GimpGradientFile(GradientFile):
102
+ """File handler for GIMP's gradient format."""
103
+
104
+ def __init__(self, fp):
105
+ if fp.readline()[:13] != b"GIMP Gradient":
106
+ msg = "not a GIMP gradient file"
107
+ raise SyntaxError(msg)
108
+
109
+ line = fp.readline()
110
+
111
+ # GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
112
+ if line.startswith(b"Name: "):
113
+ line = fp.readline().strip()
114
+
115
+ count = int(line)
116
+
117
+ gradient = []
118
+
119
+ for i in range(count):
120
+ s = fp.readline().split()
121
+ w = [float(x) for x in s[:11]]
122
+
123
+ x0, x1 = w[0], w[2]
124
+ xm = w[1]
125
+ rgb0 = w[3:7]
126
+ rgb1 = w[7:11]
127
+
128
+ segment = SEGMENTS[int(s[11])]
129
+ cspace = int(s[12])
130
+
131
+ if cspace != 0:
132
+ msg = "cannot handle HSV colour space"
133
+ raise OSError(msg)
134
+
135
+ gradient.append((x0, x1, xm, rgb0, rgb1, segment))
136
+
137
+ self.gradient = gradient
.venv/Lib/site-packages/PIL/GimpPaletteFile.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # stuff to read GIMP palette files
6
+ #
7
+ # History:
8
+ # 1997-08-23 fl Created
9
+ # 2004-09-07 fl Support GIMP 2.0 palette files.
10
+ #
11
+ # Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
12
+ # Copyright (c) Fredrik Lundh 1997-2004.
13
+ #
14
+ # See the README file for information on usage and redistribution.
15
+ #
16
+ from __future__ import annotations
17
+
18
+ import re
19
+
20
+ from ._binary import o8
21
+
22
+
23
+ class GimpPaletteFile:
24
+ """File handler for GIMP's palette format."""
25
+
26
+ rawmode = "RGB"
27
+
28
+ def __init__(self, fp):
29
+ self.palette = [o8(i) * 3 for i in range(256)]
30
+
31
+ if fp.readline()[:12] != b"GIMP Palette":
32
+ msg = "not a GIMP palette file"
33
+ raise SyntaxError(msg)
34
+
35
+ for i in range(256):
36
+ s = fp.readline()
37
+ if not s:
38
+ break
39
+
40
+ # skip fields and comment lines
41
+ if re.match(rb"\w+:|#", s):
42
+ continue
43
+ if len(s) > 100:
44
+ msg = "bad palette file"
45
+ raise SyntaxError(msg)
46
+
47
+ v = tuple(map(int, s.split()[:3]))
48
+ if len(v) != 3:
49
+ msg = "bad palette entry"
50
+ raise ValueError(msg)
51
+
52
+ self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
53
+
54
+ self.palette = b"".join(self.palette)
55
+
56
+ def getpalette(self):
57
+ return self.palette, self.rawmode
.venv/Lib/site-packages/PIL/GribStubImagePlugin.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # GRIB stub adapter
6
+ #
7
+ # Copyright (c) 1996-2003 by Fredrik Lundh
8
+ #
9
+ # See the README file for information on usage and redistribution.
10
+ #
11
+ from __future__ import annotations
12
+
13
+ from . import Image, ImageFile
14
+
15
+ _handler = None
16
+
17
+
18
+ def register_handler(handler):
19
+ """
20
+ Install application-specific GRIB image handler.
21
+
22
+ :param handler: Handler object.
23
+ """
24
+ global _handler
25
+ _handler = handler
26
+
27
+
28
+ # --------------------------------------------------------------------
29
+ # Image adapter
30
+
31
+
32
+ def _accept(prefix):
33
+ return prefix[:4] == b"GRIB" and prefix[7] == 1
34
+
35
+
36
+ class GribStubImageFile(ImageFile.StubImageFile):
37
+ format = "GRIB"
38
+ format_description = "GRIB"
39
+
40
+ def _open(self):
41
+ offset = self.fp.tell()
42
+
43
+ if not _accept(self.fp.read(8)):
44
+ msg = "Not a GRIB file"
45
+ raise SyntaxError(msg)
46
+
47
+ self.fp.seek(offset)
48
+
49
+ # make something up
50
+ self._mode = "F"
51
+ self._size = 1, 1
52
+
53
+ loader = self._load()
54
+ if loader:
55
+ loader.open(self)
56
+
57
+ def _load(self):
58
+ return _handler
59
+
60
+
61
+ def _save(im, fp, filename):
62
+ if _handler is None or not hasattr(_handler, "save"):
63
+ msg = "GRIB save handler not installed"
64
+ raise OSError(msg)
65
+ _handler.save(im, fp, filename)
66
+
67
+
68
+ # --------------------------------------------------------------------
69
+ # Registry
70
+
71
+ Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
72
+ Image.register_save(GribStubImageFile.format, _save)
73
+
74
+ Image.register_extension(GribStubImageFile.format, ".grib")
.venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # HDF5 stub adapter
6
+ #
7
+ # Copyright (c) 2000-2003 by Fredrik Lundh
8
+ #
9
+ # See the README file for information on usage and redistribution.
10
+ #
11
+ from __future__ import annotations
12
+
13
+ from . import Image, ImageFile
14
+
15
+ _handler = None
16
+
17
+
18
+ def register_handler(handler):
19
+ """
20
+ Install application-specific HDF5 image handler.
21
+
22
+ :param handler: Handler object.
23
+ """
24
+ global _handler
25
+ _handler = handler
26
+
27
+
28
+ # --------------------------------------------------------------------
29
+ # Image adapter
30
+
31
+
32
+ def _accept(prefix):
33
+ return prefix[:8] == b"\x89HDF\r\n\x1a\n"
34
+
35
+
36
+ class HDF5StubImageFile(ImageFile.StubImageFile):
37
+ format = "HDF5"
38
+ format_description = "HDF5"
39
+
40
+ def _open(self):
41
+ offset = self.fp.tell()
42
+
43
+ if not _accept(self.fp.read(8)):
44
+ msg = "Not an HDF file"
45
+ raise SyntaxError(msg)
46
+
47
+ self.fp.seek(offset)
48
+
49
+ # make something up
50
+ self._mode = "F"
51
+ self._size = 1, 1
52
+
53
+ loader = self._load()
54
+ if loader:
55
+ loader.open(self)
56
+
57
+ def _load(self):
58
+ return _handler
59
+
60
+
61
+ def _save(im, fp, filename):
62
+ if _handler is None or not hasattr(_handler, "save"):
63
+ msg = "HDF5 save handler not installed"
64
+ raise OSError(msg)
65
+ _handler.save(im, fp, filename)
66
+
67
+
68
+ # --------------------------------------------------------------------
69
+ # Registry
70
+
71
+ Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
72
+ Image.register_save(HDF5StubImageFile.format, _save)
73
+
74
+ Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"])
.venv/Lib/site-packages/PIL/IcnsImagePlugin.py ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # macOS icns file decoder, based on icns.py by Bob Ippolito.
6
+ #
7
+ # history:
8
+ # 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
9
+ # 2020-04-04 Allow saving on all operating systems.
10
+ #
11
+ # Copyright (c) 2004 by Bob Ippolito.
12
+ # Copyright (c) 2004 by Secret Labs.
13
+ # Copyright (c) 2004 by Fredrik Lundh.
14
+ # Copyright (c) 2014 by Alastair Houghton.
15
+ # Copyright (c) 2020 by Pan Jing.
16
+ #
17
+ # See the README file for information on usage and redistribution.
18
+ #
19
+ from __future__ import annotations
20
+
21
+ import io
22
+ import os
23
+ import struct
24
+ import sys
25
+
26
+ from . import Image, ImageFile, PngImagePlugin, features
27
+
28
+ enable_jpeg2k = features.check_codec("jpg_2000")
29
+ if enable_jpeg2k:
30
+ from . import Jpeg2KImagePlugin
31
+
32
+ MAGIC = b"icns"
33
+ HEADERSIZE = 8
34
+
35
+
36
+ def nextheader(fobj):
37
+ return struct.unpack(">4sI", fobj.read(HEADERSIZE))
38
+
39
+
40
+ def read_32t(fobj, start_length, size):
41
+ # The 128x128 icon seems to have an extra header for some reason.
42
+ (start, length) = start_length
43
+ fobj.seek(start)
44
+ sig = fobj.read(4)
45
+ if sig != b"\x00\x00\x00\x00":
46
+ msg = "Unknown signature, expecting 0x00000000"
47
+ raise SyntaxError(msg)
48
+ return read_32(fobj, (start + 4, length - 4), size)
49
+
50
+
51
+ def read_32(fobj, start_length, size):
52
+ """
53
+ Read a 32bit RGB icon resource. Seems to be either uncompressed or
54
+ an RLE packbits-like scheme.
55
+ """
56
+ (start, length) = start_length
57
+ fobj.seek(start)
58
+ pixel_size = (size[0] * size[2], size[1] * size[2])
59
+ sizesq = pixel_size[0] * pixel_size[1]
60
+ if length == sizesq * 3:
61
+ # uncompressed ("RGBRGBGB")
62
+ indata = fobj.read(length)
63
+ im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
64
+ else:
65
+ # decode image
66
+ im = Image.new("RGB", pixel_size, None)
67
+ for band_ix in range(3):
68
+ data = []
69
+ bytesleft = sizesq
70
+ while bytesleft > 0:
71
+ byte = fobj.read(1)
72
+ if not byte:
73
+ break
74
+ byte = byte[0]
75
+ if byte & 0x80:
76
+ blocksize = byte - 125
77
+ byte = fobj.read(1)
78
+ for i in range(blocksize):
79
+ data.append(byte)
80
+ else:
81
+ blocksize = byte + 1
82
+ data.append(fobj.read(blocksize))
83
+ bytesleft -= blocksize
84
+ if bytesleft <= 0:
85
+ break
86
+ if bytesleft != 0:
87
+ msg = f"Error reading channel [{repr(bytesleft)} left]"
88
+ raise SyntaxError(msg)
89
+ band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1)
90
+ im.im.putband(band.im, band_ix)
91
+ return {"RGB": im}
92
+
93
+
94
+ def read_mk(fobj, start_length, size):
95
+ # Alpha masks seem to be uncompressed
96
+ start = start_length[0]
97
+ fobj.seek(start)
98
+ pixel_size = (size[0] * size[2], size[1] * size[2])
99
+ sizesq = pixel_size[0] * pixel_size[1]
100
+ band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1)
101
+ return {"A": band}
102
+
103
+
104
+ def read_png_or_jpeg2000(fobj, start_length, size):
105
+ (start, length) = start_length
106
+ fobj.seek(start)
107
+ sig = fobj.read(12)
108
+ if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
109
+ fobj.seek(start)
110
+ im = PngImagePlugin.PngImageFile(fobj)
111
+ Image._decompression_bomb_check(im.size)
112
+ return {"RGBA": im}
113
+ elif (
114
+ sig[:4] == b"\xff\x4f\xff\x51"
115
+ or sig[:4] == b"\x0d\x0a\x87\x0a"
116
+ or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
117
+ ):
118
+ if not enable_jpeg2k:
119
+ msg = (
120
+ "Unsupported icon subimage format (rebuild PIL "
121
+ "with JPEG 2000 support to fix this)"
122
+ )
123
+ raise ValueError(msg)
124
+ # j2k, jpc or j2c
125
+ fobj.seek(start)
126
+ jp2kstream = fobj.read(length)
127
+ f = io.BytesIO(jp2kstream)
128
+ im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
129
+ Image._decompression_bomb_check(im.size)
130
+ if im.mode != "RGBA":
131
+ im = im.convert("RGBA")
132
+ return {"RGBA": im}
133
+ else:
134
+ msg = "Unsupported icon subimage format"
135
+ raise ValueError(msg)
136
+
137
+
138
+ class IcnsFile:
139
+ SIZES = {
140
+ (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)],
141
+ (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)],
142
+ (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)],
143
+ (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)],
144
+ (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)],
145
+ (128, 128, 1): [
146
+ (b"ic07", read_png_or_jpeg2000),
147
+ (b"it32", read_32t),
148
+ (b"t8mk", read_mk),
149
+ ],
150
+ (64, 64, 1): [(b"icp6", read_png_or_jpeg2000)],
151
+ (32, 32, 2): [(b"ic12", read_png_or_jpeg2000)],
152
+ (48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)],
153
+ (32, 32, 1): [
154
+ (b"icp5", read_png_or_jpeg2000),
155
+ (b"il32", read_32),
156
+ (b"l8mk", read_mk),
157
+ ],
158
+ (16, 16, 2): [(b"ic11", read_png_or_jpeg2000)],
159
+ (16, 16, 1): [
160
+ (b"icp4", read_png_or_jpeg2000),
161
+ (b"is32", read_32),
162
+ (b"s8mk", read_mk),
163
+ ],
164
+ }
165
+
166
+ def __init__(self, fobj):
167
+ """
168
+ fobj is a file-like object as an icns resource
169
+ """
170
+ # signature : (start, length)
171
+ self.dct = dct = {}
172
+ self.fobj = fobj
173
+ sig, filesize = nextheader(fobj)
174
+ if not _accept(sig):
175
+ msg = "not an icns file"
176
+ raise SyntaxError(msg)
177
+ i = HEADERSIZE
178
+ while i < filesize:
179
+ sig, blocksize = nextheader(fobj)
180
+ if blocksize <= 0:
181
+ msg = "invalid block header"
182
+ raise SyntaxError(msg)
183
+ i += HEADERSIZE
184
+ blocksize -= HEADERSIZE
185
+ dct[sig] = (i, blocksize)
186
+ fobj.seek(blocksize, io.SEEK_CUR)
187
+ i += blocksize
188
+
189
+ def itersizes(self):
190
+ sizes = []
191
+ for size, fmts in self.SIZES.items():
192
+ for fmt, reader in fmts:
193
+ if fmt in self.dct:
194
+ sizes.append(size)
195
+ break
196
+ return sizes
197
+
198
+ def bestsize(self):
199
+ sizes = self.itersizes()
200
+ if not sizes:
201
+ msg = "No 32bit icon resources found"
202
+ raise SyntaxError(msg)
203
+ return max(sizes)
204
+
205
+ def dataforsize(self, size):
206
+ """
207
+ Get an icon resource as {channel: array}. Note that
208
+ the arrays are bottom-up like windows bitmaps and will likely
209
+ need to be flipped or transposed in some way.
210
+ """
211
+ dct = {}
212
+ for code, reader in self.SIZES[size]:
213
+ desc = self.dct.get(code)
214
+ if desc is not None:
215
+ dct.update(reader(self.fobj, desc, size))
216
+ return dct
217
+
218
+ def getimage(self, size=None):
219
+ if size is None:
220
+ size = self.bestsize()
221
+ if len(size) == 2:
222
+ size = (size[0], size[1], 1)
223
+ channels = self.dataforsize(size)
224
+
225
+ im = channels.get("RGBA", None)
226
+ if im:
227
+ return im
228
+
229
+ im = channels.get("RGB").copy()
230
+ try:
231
+ im.putalpha(channels["A"])
232
+ except KeyError:
233
+ pass
234
+ return im
235
+
236
+
237
+ ##
238
+ # Image plugin for Mac OS icons.
239
+
240
+
241
+ class IcnsImageFile(ImageFile.ImageFile):
242
+ """
243
+ PIL image support for Mac OS .icns files.
244
+ Chooses the best resolution, but will possibly load
245
+ a different size image if you mutate the size attribute
246
+ before calling 'load'.
247
+
248
+ The info dictionary has a key 'sizes' that is a list
249
+ of sizes that the icns file has.
250
+ """
251
+
252
+ format = "ICNS"
253
+ format_description = "Mac OS icns resource"
254
+
255
+ def _open(self):
256
+ self.icns = IcnsFile(self.fp)
257
+ self._mode = "RGBA"
258
+ self.info["sizes"] = self.icns.itersizes()
259
+ self.best_size = self.icns.bestsize()
260
+ self.size = (
261
+ self.best_size[0] * self.best_size[2],
262
+ self.best_size[1] * self.best_size[2],
263
+ )
264
+
265
+ @property
266
+ def size(self):
267
+ return self._size
268
+
269
+ @size.setter
270
+ def size(self, value):
271
+ info_size = value
272
+ if info_size not in self.info["sizes"] and len(info_size) == 2:
273
+ info_size = (info_size[0], info_size[1], 1)
274
+ if (
275
+ info_size not in self.info["sizes"]
276
+ and len(info_size) == 3
277
+ and info_size[2] == 1
278
+ ):
279
+ simple_sizes = [
280
+ (size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
281
+ ]
282
+ if value in simple_sizes:
283
+ info_size = self.info["sizes"][simple_sizes.index(value)]
284
+ if info_size not in self.info["sizes"]:
285
+ msg = "This is not one of the allowed sizes of this image"
286
+ raise ValueError(msg)
287
+ self._size = value
288
+
289
+ def load(self):
290
+ if len(self.size) == 3:
291
+ self.best_size = self.size
292
+ self.size = (
293
+ self.best_size[0] * self.best_size[2],
294
+ self.best_size[1] * self.best_size[2],
295
+ )
296
+
297
+ px = Image.Image.load(self)
298
+ if self.im is not None and self.im.size == self.size:
299
+ # Already loaded
300
+ return px
301
+ self.load_prepare()
302
+ # This is likely NOT the best way to do it, but whatever.
303
+ im = self.icns.getimage(self.best_size)
304
+
305
+ # If this is a PNG or JPEG 2000, it won't be loaded yet
306
+ px = im.load()
307
+
308
+ self.im = im.im
309
+ self._mode = im.mode
310
+ self.size = im.size
311
+
312
+ return px
313
+
314
+
315
+ def _save(im, fp, filename):
316
+ """
317
+ Saves the image as a series of PNG files,
318
+ that are then combined into a .icns file.
319
+ """
320
+ if hasattr(fp, "flush"):
321
+ fp.flush()
322
+
323
+ sizes = {
324
+ b"ic07": 128,
325
+ b"ic08": 256,
326
+ b"ic09": 512,
327
+ b"ic10": 1024,
328
+ b"ic11": 32,
329
+ b"ic12": 64,
330
+ b"ic13": 256,
331
+ b"ic14": 512,
332
+ }
333
+ provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])}
334
+ size_streams = {}
335
+ for size in set(sizes.values()):
336
+ image = (
337
+ provided_images[size]
338
+ if size in provided_images
339
+ else im.resize((size, size))
340
+ )
341
+
342
+ temp = io.BytesIO()
343
+ image.save(temp, "png")
344
+ size_streams[size] = temp.getvalue()
345
+
346
+ entries = []
347
+ for type, size in sizes.items():
348
+ stream = size_streams[size]
349
+ entries.append(
350
+ {"type": type, "size": HEADERSIZE + len(stream), "stream": stream}
351
+ )
352
+
353
+ # Header
354
+ fp.write(MAGIC)
355
+ file_length = HEADERSIZE # Header
356
+ file_length += HEADERSIZE + 8 * len(entries) # TOC
357
+ file_length += sum(entry["size"] for entry in entries)
358
+ fp.write(struct.pack(">i", file_length))
359
+
360
+ # TOC
361
+ fp.write(b"TOC ")
362
+ fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE))
363
+ for entry in entries:
364
+ fp.write(entry["type"])
365
+ fp.write(struct.pack(">i", entry["size"]))
366
+
367
+ # Data
368
+ for entry in entries:
369
+ fp.write(entry["type"])
370
+ fp.write(struct.pack(">i", entry["size"]))
371
+ fp.write(entry["stream"])
372
+
373
+ if hasattr(fp, "flush"):
374
+ fp.flush()
375
+
376
+
377
+ def _accept(prefix):
378
+ return prefix[:4] == MAGIC
379
+
380
+
381
+ Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)
382
+ Image.register_extension(IcnsImageFile.format, ".icns")
383
+
384
+ Image.register_save(IcnsImageFile.format, _save)
385
+ Image.register_mime(IcnsImageFile.format, "image/icns")
386
+
387
+ if __name__ == "__main__":
388
+ if len(sys.argv) < 2:
389
+ print("Syntax: python3 IcnsImagePlugin.py [file]")
390
+ sys.exit()
391
+
392
+ with open(sys.argv[1], "rb") as fp:
393
+ imf = IcnsImageFile(fp)
394
+ for size in imf.info["sizes"]:
395
+ width, height, scale = imf.size = size
396
+ imf.save(f"out-{width}-{height}-{scale}.png")
397
+ with Image.open(sys.argv[1]) as im:
398
+ im.save("out.png")
399
+ if sys.platform == "windows":
400
+ os.startfile("out.png")
.venv/Lib/site-packages/PIL/IcoImagePlugin.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # Windows Icon support for PIL
6
+ #
7
+ # History:
8
+ # 96-05-27 fl Created
9
+ #
10
+ # Copyright (c) Secret Labs AB 1997.
11
+ # Copyright (c) Fredrik Lundh 1996.
12
+ #
13
+ # See the README file for information on usage and redistribution.
14
+ #
15
+
16
+ # This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
17
18
+ # https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
19
+ #
20
+ # Icon format references:
21
+ # * https://en.wikipedia.org/wiki/ICO_(file_format)
22
+ # * https://msdn.microsoft.com/en-us/library/ms997538.aspx
23
+ from __future__ import annotations
24
+
25
+ import warnings
26
+ from io import BytesIO
27
+ from math import ceil, log
28
+
29
+ from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
30
+ from ._binary import i16le as i16
31
+ from ._binary import i32le as i32
32
+ from ._binary import o8
33
+ from ._binary import o16le as o16
34
+ from ._binary import o32le as o32
35
+
36
+ #
37
+ # --------------------------------------------------------------------
38
+
39
+ _MAGIC = b"\0\0\1\0"
40
+
41
+
42
+ def _save(im, fp, filename):
43
+ fp.write(_MAGIC) # (2+2)
44
+ bmp = im.encoderinfo.get("bitmap_format") == "bmp"
45
+ sizes = im.encoderinfo.get(
46
+ "sizes",
47
+ [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)],
48
+ )
49
+ frames = []
50
+ provided_ims = [im] + im.encoderinfo.get("append_images", [])
51
+ width, height = im.size
52
+ for size in sorted(set(sizes)):
53
+ if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256:
54
+ continue
55
+
56
+ for provided_im in provided_ims:
57
+ if provided_im.size != size:
58
+ continue
59
+ frames.append(provided_im)
60
+ if bmp:
61
+ bits = BmpImagePlugin.SAVE[provided_im.mode][1]
62
+ bits_used = [bits]
63
+ for other_im in provided_ims:
64
+ if other_im.size != size:
65
+ continue
66
+ bits = BmpImagePlugin.SAVE[other_im.mode][1]
67
+ if bits not in bits_used:
68
+ # Another image has been supplied for this size
69
+ # with a different bit depth
70
+ frames.append(other_im)
71
+ bits_used.append(bits)
72
+ break
73
+ else:
74
+ # TODO: invent a more convenient method for proportional scalings
75
+ frame = provided_im.copy()
76
+ frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None)
77
+ frames.append(frame)
78
+ fp.write(o16(len(frames))) # idCount(2)
79
+ offset = fp.tell() + len(frames) * 16
80
+ for frame in frames:
81
+ width, height = frame.size
82
+ # 0 means 256
83
+ fp.write(o8(width if width < 256 else 0)) # bWidth(1)
84
+ fp.write(o8(height if height < 256 else 0)) # bHeight(1)
85
+
86
+ bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0)
87
+ fp.write(o8(colors)) # bColorCount(1)
88
+ fp.write(b"\0") # bReserved(1)
89
+ fp.write(b"\0\0") # wPlanes(2)
90
+ fp.write(o16(bits)) # wBitCount(2)
91
+
92
+ image_io = BytesIO()
93
+ if bmp:
94
+ frame.save(image_io, "dib")
95
+
96
+ if bits != 32:
97
+ and_mask = Image.new("1", size)
98
+ ImageFile._save(
99
+ and_mask, image_io, [("raw", (0, 0) + size, 0, ("1", 0, -1))]
100
+ )
101
+ else:
102
+ frame.save(image_io, "png")
103
+ image_io.seek(0)
104
+ image_bytes = image_io.read()
105
+ if bmp:
106
+ image_bytes = image_bytes[:8] + o32(height * 2) + image_bytes[12:]
107
+ bytes_len = len(image_bytes)
108
+ fp.write(o32(bytes_len)) # dwBytesInRes(4)
109
+ fp.write(o32(offset)) # dwImageOffset(4)
110
+ current = fp.tell()
111
+ fp.seek(offset)
112
+ fp.write(image_bytes)
113
+ offset = offset + bytes_len
114
+ fp.seek(current)
115
+
116
+
117
+ def _accept(prefix):
118
+ return prefix[:4] == _MAGIC
119
+
120
+
121
+ class IcoFile:
122
+ def __init__(self, buf):
123
+ """
124
+ Parse image from file-like object containing ico file data
125
+ """
126
+
127
+ # check magic
128
+ s = buf.read(6)
129
+ if not _accept(s):
130
+ msg = "not an ICO file"
131
+ raise SyntaxError(msg)
132
+
133
+ self.buf = buf
134
+ self.entry = []
135
+
136
+ # Number of items in file
137
+ self.nb_items = i16(s, 4)
138
+
139
+ # Get headers for each item
140
+ for i in range(self.nb_items):
141
+ s = buf.read(16)
142
+
143
+ icon_header = {
144
+ "width": s[0],
145
+ "height": s[1],
146
+ "nb_color": s[2], # No. of colors in image (0 if >=8bpp)
147
+ "reserved": s[3],
148
+ "planes": i16(s, 4),
149
+ "bpp": i16(s, 6),
150
+ "size": i32(s, 8),
151
+ "offset": i32(s, 12),
152
+ }
153
+
154
+ # See Wikipedia
155
+ for j in ("width", "height"):
156
+ if not icon_header[j]:
157
+ icon_header[j] = 256
158
+
159
+ # See Wikipedia notes about color depth.
160
+ # We need this just to differ images with equal sizes
161
+ icon_header["color_depth"] = (
162
+ icon_header["bpp"]
163
+ or (
164
+ icon_header["nb_color"] != 0
165
+ and ceil(log(icon_header["nb_color"], 2))
166
+ )
167
+ or 256
168
+ )
169
+
170
+ icon_header["dim"] = (icon_header["width"], icon_header["height"])
171
+ icon_header["square"] = icon_header["width"] * icon_header["height"]
172
+
173
+ self.entry.append(icon_header)
174
+
175
+ self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
176
+ # ICO images are usually squares
177
+ self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)
178
+
179
+ def sizes(self):
180
+ """
181
+ Get a list of all available icon sizes and color depths.
182
+ """
183
+ return {(h["width"], h["height"]) for h in self.entry}
184
+
185
+ def getentryindex(self, size, bpp=False):
186
+ for i, h in enumerate(self.entry):
187
+ if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
188
+ return i
189
+ return 0
190
+
191
+ def getimage(self, size, bpp=False):
192
+ """
193
+ Get an image from the icon
194
+ """
195
+ return self.frame(self.getentryindex(size, bpp))
196
+
197
+ def frame(self, idx):
198
+ """
199
+ Get an image from frame idx
200
+ """
201
+
202
+ header = self.entry[idx]
203
+
204
+ self.buf.seek(header["offset"])
205
+ data = self.buf.read(8)
206
+ self.buf.seek(header["offset"])
207
+
208
+ if data[:8] == PngImagePlugin._MAGIC:
209
+ # png frame
210
+ im = PngImagePlugin.PngImageFile(self.buf)
211
+ Image._decompression_bomb_check(im.size)
212
+ else:
213
+ # XOR + AND mask bmp frame
214
+ im = BmpImagePlugin.DibImageFile(self.buf)
215
+ Image._decompression_bomb_check(im.size)
216
+
217
+ # change tile dimension to only encompass XOR image
218
+ im._size = (im.size[0], int(im.size[1] / 2))
219
+ d, e, o, a = im.tile[0]
220
+ im.tile[0] = d, (0, 0) + im.size, o, a
221
+
222
+ # figure out where AND mask image starts
223
+ bpp = header["bpp"]
224
+ if 32 == bpp:
225
+ # 32-bit color depth icon image allows semitransparent areas
226
+ # PIL's DIB format ignores transparency bits, recover them.
227
+ # The DIB is packed in BGRX byte order where X is the alpha
228
+ # channel.
229
+
230
+ # Back up to start of bmp data
231
+ self.buf.seek(o)
232
+ # extract every 4th byte (eg. 3,7,11,15,...)
233
+ alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
234
+
235
+ # convert to an 8bpp grayscale image
236
+ mask = Image.frombuffer(
237
+ "L", # 8bpp
238
+ im.size, # (w, h)
239
+ alpha_bytes, # source chars
240
+ "raw", # raw decoder
241
+ ("L", 0, -1), # 8bpp inverted, unpadded, reversed
242
+ )
243
+ else:
244
+ # get AND image from end of bitmap
245
+ w = im.size[0]
246
+ if (w % 32) > 0:
247
+ # bitmap row data is aligned to word boundaries
248
+ w += 32 - (im.size[0] % 32)
249
+
250
+ # the total mask data is
251
+ # padded row size * height / bits per char
252
+
253
+ total_bytes = int((w * im.size[1]) / 8)
254
+ and_mask_offset = header["offset"] + header["size"] - total_bytes
255
+
256
+ self.buf.seek(and_mask_offset)
257
+ mask_data = self.buf.read(total_bytes)
258
+
259
+ # convert raw data to image
260
+ mask = Image.frombuffer(
261
+ "1", # 1 bpp
262
+ im.size, # (w, h)
263
+ mask_data, # source chars
264
+ "raw", # raw decoder
265
+ ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed
266
+ )
267
+
268
+ # now we have two images, im is XOR image and mask is AND image
269
+
270
+ # apply mask image as alpha channel
271
+ im = im.convert("RGBA")
272
+ im.putalpha(mask)
273
+
274
+ return im
275
+
276
+
277
+ ##
278
+ # Image plugin for Windows Icon files.
279
+
280
+
281
+ class IcoImageFile(ImageFile.ImageFile):
282
+ """
283
+ PIL read-only image support for Microsoft Windows .ico files.
284
+
285
+ By default the largest resolution image in the file will be loaded. This
286
+ can be changed by altering the 'size' attribute before calling 'load'.
287
+
288
+ The info dictionary has a key 'sizes' that is a list of the sizes available
289
+ in the icon file.
290
+
291
+ Handles classic, XP and Vista icon formats.
292
+
293
+ When saving, PNG compression is used. Support for this was only added in
294
+ Windows Vista. If you are unable to view the icon in Windows, convert the
295
+ image to "RGBA" mode before saving.
296
+
297
+ This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
298
299
+ https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
300
+ """
301
+
302
+ format = "ICO"
303
+ format_description = "Windows Icon"
304
+
305
+ def _open(self):
306
+ self.ico = IcoFile(self.fp)
307
+ self.info["sizes"] = self.ico.sizes()
308
+ self.size = self.ico.entry[0]["dim"]
309
+ self.load()
310
+
311
+ @property
312
+ def size(self):
313
+ return self._size
314
+
315
+ @size.setter
316
+ def size(self, value):
317
+ if value not in self.info["sizes"]:
318
+ msg = "This is not one of the allowed sizes of this image"
319
+ raise ValueError(msg)
320
+ self._size = value
321
+
322
+ def load(self):
323
+ if self.im is not None and self.im.size == self.size:
324
+ # Already loaded
325
+ return Image.Image.load(self)
326
+ im = self.ico.getimage(self.size)
327
+ # if tile is PNG, it won't really be loaded yet
328
+ im.load()
329
+ self.im = im.im
330
+ self.pyaccess = None
331
+ self._mode = im.mode
332
+ if im.size != self.size:
333
+ warnings.warn("Image was not the expected size")
334
+
335
+ index = self.ico.getentryindex(self.size)
336
+ sizes = list(self.info["sizes"])
337
+ sizes[index] = im.size
338
+ self.info["sizes"] = set(sizes)
339
+
340
+ self.size = im.size
341
+
342
+ def load_seek(self):
343
+ # Flag the ImageFile.Parser so that it
344
+ # just does all the decode at the end.
345
+ pass
346
+
347
+
348
+ #
349
+ # --------------------------------------------------------------------
350
+
351
+
352
+ Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
353
+ Image.register_save(IcoImageFile.format, _save)
354
+ Image.register_extension(IcoImageFile.format, ".ico")
355
+
356
+ Image.register_mime(IcoImageFile.format, "image/x-icon")
.venv/Lib/site-packages/PIL/ImImagePlugin.py ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # IFUNC IM file handling for PIL
6
+ #
7
+ # history:
8
+ # 1995-09-01 fl Created.
9
+ # 1997-01-03 fl Save palette images
10
+ # 1997-01-08 fl Added sequence support
11
+ # 1997-01-23 fl Added P and RGB save support
12
+ # 1997-05-31 fl Read floating point images
13
+ # 1997-06-22 fl Save floating point images
14
+ # 1997-08-27 fl Read and save 1-bit images
15
+ # 1998-06-25 fl Added support for RGB+LUT images
16
+ # 1998-07-02 fl Added support for YCC images
17
+ # 1998-07-15 fl Renamed offset attribute to avoid name clash
18
+ # 1998-12-29 fl Added I;16 support
19
+ # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
20
+ # 2003-09-26 fl Added LA/PA support
21
+ #
22
+ # Copyright (c) 1997-2003 by Secret Labs AB.
23
+ # Copyright (c) 1995-2001 by Fredrik Lundh.
24
+ #
25
+ # See the README file for information on usage and redistribution.
26
+ #
27
+ from __future__ import annotations
28
+
29
+ import os
30
+ import re
31
+
32
+ from . import Image, ImageFile, ImagePalette
33
+
34
+ # --------------------------------------------------------------------
35
+ # Standard tags
36
+
37
+ COMMENT = "Comment"
38
+ DATE = "Date"
39
+ EQUIPMENT = "Digitalization equipment"
40
+ FRAMES = "File size (no of images)"
41
+ LUT = "Lut"
42
+ NAME = "Name"
43
+ SCALE = "Scale (x,y)"
44
+ SIZE = "Image size (x*y)"
45
+ MODE = "Image type"
46
+
47
+ TAGS = {
48
+ COMMENT: 0,
49
+ DATE: 0,
50
+ EQUIPMENT: 0,
51
+ FRAMES: 0,
52
+ LUT: 0,
53
+ NAME: 0,
54
+ SCALE: 0,
55
+ SIZE: 0,
56
+ MODE: 0,
57
+ }
58
+
59
+ OPEN = {
60
+ # ifunc93/p3cfunc formats
61
+ "0 1 image": ("1", "1"),
62
+ "L 1 image": ("1", "1"),
63
+ "Greyscale image": ("L", "L"),
64
+ "Grayscale image": ("L", "L"),
65
+ "RGB image": ("RGB", "RGB;L"),
66
+ "RLB image": ("RGB", "RLB"),
67
+ "RYB image": ("RGB", "RLB"),
68
+ "B1 image": ("1", "1"),
69
+ "B2 image": ("P", "P;2"),
70
+ "B4 image": ("P", "P;4"),
71
+ "X 24 image": ("RGB", "RGB"),
72
+ "L 32 S image": ("I", "I;32"),
73
+ "L 32 F image": ("F", "F;32"),
74
+ # old p3cfunc formats
75
+ "RGB3 image": ("RGB", "RGB;T"),
76
+ "RYB3 image": ("RGB", "RYB;T"),
77
+ # extensions
78
+ "LA image": ("LA", "LA;L"),
79
+ "PA image": ("LA", "PA;L"),
80
+ "RGBA image": ("RGBA", "RGBA;L"),
81
+ "RGBX image": ("RGBX", "RGBX;L"),
82
+ "CMYK image": ("CMYK", "CMYK;L"),
83
+ "YCC image": ("YCbCr", "YCbCr;L"),
84
+ }
85
+
86
+ # ifunc95 extensions
87
+ for i in ["8", "8S", "16", "16S", "32", "32F"]:
88
+ OPEN[f"L {i} image"] = ("F", f"F;{i}")
89
+ OPEN[f"L*{i} image"] = ("F", f"F;{i}")
90
+ for i in ["16", "16L", "16B"]:
91
+ OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}")
92
+ OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}")
93
+ for i in ["32S"]:
94
+ OPEN[f"L {i} image"] = ("I", f"I;{i}")
95
+ OPEN[f"L*{i} image"] = ("I", f"I;{i}")
96
+ for i in range(2, 33):
97
+ OPEN[f"L*{i} image"] = ("F", f"F;{i}")
98
+
99
+
100
+ # --------------------------------------------------------------------
101
+ # Read IM directory
102
+
103
+ split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
104
+
105
+
106
+ def number(s):
107
+ try:
108
+ return int(s)
109
+ except ValueError:
110
+ return float(s)
111
+
112
+
113
+ ##
114
+ # Image plugin for the IFUNC IM file format.
115
+
116
+
117
+ class ImImageFile(ImageFile.ImageFile):
118
+ format = "IM"
119
+ format_description = "IFUNC Image Memory"
120
+ _close_exclusive_fp_after_loading = False
121
+
122
+ def _open(self):
123
+ # Quick rejection: if there's not an LF among the first
124
+ # 100 bytes, this is (probably) not a text header.
125
+
126
+ if b"\n" not in self.fp.read(100):
127
+ msg = "not an IM file"
128
+ raise SyntaxError(msg)
129
+ self.fp.seek(0)
130
+
131
+ n = 0
132
+
133
+ # Default values
134
+ self.info[MODE] = "L"
135
+ self.info[SIZE] = (512, 512)
136
+ self.info[FRAMES] = 1
137
+
138
+ self.rawmode = "L"
139
+
140
+ while True:
141
+ s = self.fp.read(1)
142
+
143
+ # Some versions of IFUNC uses \n\r instead of \r\n...
144
+ if s == b"\r":
145
+ continue
146
+
147
+ if not s or s == b"\0" or s == b"\x1A":
148
+ break
149
+
150
+ # FIXME: this may read whole file if not a text file
151
+ s = s + self.fp.readline()
152
+
153
+ if len(s) > 100:
154
+ msg = "not an IM file"
155
+ raise SyntaxError(msg)
156
+
157
+ if s[-2:] == b"\r\n":
158
+ s = s[:-2]
159
+ elif s[-1:] == b"\n":
160
+ s = s[:-1]
161
+
162
+ try:
163
+ m = split.match(s)
164
+ except re.error as e:
165
+ msg = "not an IM file"
166
+ raise SyntaxError(msg) from e
167
+
168
+ if m:
169
+ k, v = m.group(1, 2)
170
+
171
+ # Don't know if this is the correct encoding,
172
+ # but a decent guess (I guess)
173
+ k = k.decode("latin-1", "replace")
174
+ v = v.decode("latin-1", "replace")
175
+
176
+ # Convert value as appropriate
177
+ if k in [FRAMES, SCALE, SIZE]:
178
+ v = v.replace("*", ",")
179
+ v = tuple(map(number, v.split(",")))
180
+ if len(v) == 1:
181
+ v = v[0]
182
+ elif k == MODE and v in OPEN:
183
+ v, self.rawmode = OPEN[v]
184
+
185
+ # Add to dictionary. Note that COMMENT tags are
186
+ # combined into a list of strings.
187
+ if k == COMMENT:
188
+ if k in self.info:
189
+ self.info[k].append(v)
190
+ else:
191
+ self.info[k] = [v]
192
+ else:
193
+ self.info[k] = v
194
+
195
+ if k in TAGS:
196
+ n += 1
197
+
198
+ else:
199
+ msg = "Syntax error in IM header: " + s.decode("ascii", "replace")
200
+ raise SyntaxError(msg)
201
+
202
+ if not n:
203
+ msg = "Not an IM file"
204
+ raise SyntaxError(msg)
205
+
206
+ # Basic attributes
207
+ self._size = self.info[SIZE]
208
+ self._mode = self.info[MODE]
209
+
210
+ # Skip forward to start of image data
211
+ while s and s[:1] != b"\x1A":
212
+ s = self.fp.read(1)
213
+ if not s:
214
+ msg = "File truncated"
215
+ raise SyntaxError(msg)
216
+
217
+ if LUT in self.info:
218
+ # convert lookup table to palette or lut attribute
219
+ palette = self.fp.read(768)
220
+ greyscale = 1 # greyscale palette
221
+ linear = 1 # linear greyscale palette
222
+ for i in range(256):
223
+ if palette[i] == palette[i + 256] == palette[i + 512]:
224
+ if palette[i] != i:
225
+ linear = 0
226
+ else:
227
+ greyscale = 0
228
+ if self.mode in ["L", "LA", "P", "PA"]:
229
+ if greyscale:
230
+ if not linear:
231
+ self.lut = list(palette[:256])
232
+ else:
233
+ if self.mode in ["L", "P"]:
234
+ self._mode = self.rawmode = "P"
235
+ elif self.mode in ["LA", "PA"]:
236
+ self._mode = "PA"
237
+ self.rawmode = "PA;L"
238
+ self.palette = ImagePalette.raw("RGB;L", palette)
239
+ elif self.mode == "RGB":
240
+ if not greyscale or not linear:
241
+ self.lut = list(palette)
242
+
243
+ self.frame = 0
244
+
245
+ self.__offset = offs = self.fp.tell()
246
+
247
+ self._fp = self.fp # FIXME: hack
248
+
249
+ if self.rawmode[:2] == "F;":
250
+ # ifunc95 formats
251
+ try:
252
+ # use bit decoder (if necessary)
253
+ bits = int(self.rawmode[2:])
254
+ if bits not in [8, 16, 32]:
255
+ self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
256
+ return
257
+ except ValueError:
258
+ pass
259
+
260
+ if self.rawmode in ["RGB;T", "RYB;T"]:
261
+ # Old LabEye/3PC files. Would be very surprised if anyone
262
+ # ever stumbled upon such a file ;-)
263
+ size = self.size[0] * self.size[1]
264
+ self.tile = [
265
+ ("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
266
+ ("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
267
+ ("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
268
+ ]
269
+ else:
270
+ # LabEye/IFUNC files
271
+ self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
272
+
273
+ @property
274
+ def n_frames(self):
275
+ return self.info[FRAMES]
276
+
277
+ @property
278
+ def is_animated(self):
279
+ return self.info[FRAMES] > 1
280
+
281
+ def seek(self, frame):
282
+ if not self._seek_check(frame):
283
+ return
284
+
285
+ self.frame = frame
286
+
287
+ if self.mode == "1":
288
+ bits = 1
289
+ else:
290
+ bits = 8 * len(self.mode)
291
+
292
+ size = ((self.size[0] * bits + 7) // 8) * self.size[1]
293
+ offs = self.__offset + frame * size
294
+
295
+ self.fp = self._fp
296
+
297
+ self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
298
+
299
+ def tell(self):
300
+ return self.frame
301
+
302
+
303
+ #
304
+ # --------------------------------------------------------------------
305
+ # Save IM files
306
+
307
+
308
+ SAVE = {
309
+ # mode: (im type, raw mode)
310
+ "1": ("0 1", "1"),
311
+ "L": ("Greyscale", "L"),
312
+ "LA": ("LA", "LA;L"),
313
+ "P": ("Greyscale", "P"),
314
+ "PA": ("LA", "PA;L"),
315
+ "I": ("L 32S", "I;32S"),
316
+ "I;16": ("L 16", "I;16"),
317
+ "I;16L": ("L 16L", "I;16L"),
318
+ "I;16B": ("L 16B", "I;16B"),
319
+ "F": ("L 32F", "F;32F"),
320
+ "RGB": ("RGB", "RGB;L"),
321
+ "RGBA": ("RGBA", "RGBA;L"),
322
+ "RGBX": ("RGBX", "RGBX;L"),
323
+ "CMYK": ("CMYK", "CMYK;L"),
324
+ "YCbCr": ("YCC", "YCbCr;L"),
325
+ }
326
+
327
+
328
+ def _save(im, fp, filename):
329
+ try:
330
+ image_type, rawmode = SAVE[im.mode]
331
+ except KeyError as e:
332
+ msg = f"Cannot save {im.mode} images as IM"
333
+ raise ValueError(msg) from e
334
+
335
+ frames = im.encoderinfo.get("frames", 1)
336
+
337
+ fp.write(f"Image type: {image_type} image\r\n".encode("ascii"))
338
+ if filename:
339
+ # Each line must be 100 characters or less,
340
+ # or: SyntaxError("not an IM file")
341
+ # 8 characters are used for "Name: " and "\r\n"
342
+ # Keep just the filename, ditch the potentially overlong path
343
+ name, ext = os.path.splitext(os.path.basename(filename))
344
+ name = "".join([name[: 92 - len(ext)], ext])
345
+
346
+ fp.write(f"Name: {name}\r\n".encode("ascii"))
347
+ fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii"))
348
+ fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
349
+ if im.mode in ["P", "PA"]:
350
+ fp.write(b"Lut: 1\r\n")
351
+ fp.write(b"\000" * (511 - fp.tell()) + b"\032")
352
+ if im.mode in ["P", "PA"]:
353
+ im_palette = im.im.getpalette("RGB", "RGB;L")
354
+ colors = len(im_palette) // 3
355
+ palette = b""
356
+ for i in range(3):
357
+ palette += im_palette[colors * i : colors * (i + 1)]
358
+ palette += b"\x00" * (256 - colors)
359
+ fp.write(palette) # 768 bytes
360
+ ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))])
361
+
362
+
363
+ #
364
+ # --------------------------------------------------------------------
365
+ # Registry
366
+
367
+
368
+ Image.register_open(ImImageFile.format, ImImageFile)
369
+ Image.register_save(ImImageFile.format, _save)
370
+
371
+ Image.register_extension(ImImageFile.format, ".im")
.venv/Lib/site-packages/PIL/Image.py ADDED
The diff for this file is too large to render. See raw diff
 
.venv/Lib/site-packages/PIL/ImageChops.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # standard channel operations
6
+ #
7
+ # History:
8
+ # 1996-03-24 fl Created
9
+ # 1996-08-13 fl Added logical operations (for "1" images)
10
+ # 2000-10-12 fl Added offset method (from Image.py)
11
+ #
12
+ # Copyright (c) 1997-2000 by Secret Labs AB
13
+ # Copyright (c) 1996-2000 by Fredrik Lundh
14
+ #
15
+ # See the README file for information on usage and redistribution.
16
+ #
17
+
18
+ from __future__ import annotations
19
+
20
+ from . import Image
21
+
22
+
23
+ def constant(image: Image.Image, value: int) -> Image.Image:
24
+ """Fill a channel with a given gray level.
25
+
26
+ :rtype: :py:class:`~PIL.Image.Image`
27
+ """
28
+
29
+ return Image.new("L", image.size, value)
30
+
31
+
32
+ def duplicate(image: Image.Image) -> Image.Image:
33
+ """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
34
+
35
+ :rtype: :py:class:`~PIL.Image.Image`
36
+ """
37
+
38
+ return image.copy()
39
+
40
+
41
+ def invert(image: Image.Image) -> Image.Image:
42
+ """
43
+ Invert an image (channel). ::
44
+
45
+ out = MAX - image
46
+
47
+ :rtype: :py:class:`~PIL.Image.Image`
48
+ """
49
+
50
+ image.load()
51
+ return image._new(image.im.chop_invert())
52
+
53
+
54
+ def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image:
55
+ """
56
+ Compares the two images, pixel by pixel, and returns a new image containing
57
+ the lighter values. ::
58
+
59
+ out = max(image1, image2)
60
+
61
+ :rtype: :py:class:`~PIL.Image.Image`
62
+ """
63
+
64
+ image1.load()
65
+ image2.load()
66
+ return image1._new(image1.im.chop_lighter(image2.im))
67
+
68
+
69
+ def darker(image1: Image.Image, image2: Image.Image) -> Image.Image:
70
+ """
71
+ Compares the two images, pixel by pixel, and returns a new image containing
72
+ the darker values. ::
73
+
74
+ out = min(image1, image2)
75
+
76
+ :rtype: :py:class:`~PIL.Image.Image`
77
+ """
78
+
79
+ image1.load()
80
+ image2.load()
81
+ return image1._new(image1.im.chop_darker(image2.im))
82
+
83
+
84
+ def difference(image1: Image.Image, image2: Image.Image) -> Image.Image:
85
+ """
86
+ Returns the absolute value of the pixel-by-pixel difference between the two
87
+ images. ::
88
+
89
+ out = abs(image1 - image2)
90
+
91
+ :rtype: :py:class:`~PIL.Image.Image`
92
+ """
93
+
94
+ image1.load()
95
+ image2.load()
96
+ return image1._new(image1.im.chop_difference(image2.im))
97
+
98
+
99
+ def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image:
100
+ """
101
+ Superimposes two images on top of each other.
102
+
103
+ If you multiply an image with a solid black image, the result is black. If
104
+ you multiply with a solid white image, the image is unaffected. ::
105
+
106
+ out = image1 * image2 / MAX
107
+
108
+ :rtype: :py:class:`~PIL.Image.Image`
109
+ """
110
+
111
+ image1.load()
112
+ image2.load()
113
+ return image1._new(image1.im.chop_multiply(image2.im))
114
+
115
+
116
+ def screen(image1: Image.Image, image2: Image.Image) -> Image.Image:
117
+ """
118
+ Superimposes two inverted images on top of each other. ::
119
+
120
+ out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
121
+
122
+ :rtype: :py:class:`~PIL.Image.Image`
123
+ """
124
+
125
+ image1.load()
126
+ image2.load()
127
+ return image1._new(image1.im.chop_screen(image2.im))
128
+
129
+
130
+ def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
131
+ """
132
+ Superimposes two images on top of each other using the Soft Light algorithm
133
+
134
+ :rtype: :py:class:`~PIL.Image.Image`
135
+ """
136
+
137
+ image1.load()
138
+ image2.load()
139
+ return image1._new(image1.im.chop_soft_light(image2.im))
140
+
141
+
142
+ def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
143
+ """
144
+ Superimposes two images on top of each other using the Hard Light algorithm
145
+
146
+ :rtype: :py:class:`~PIL.Image.Image`
147
+ """
148
+
149
+ image1.load()
150
+ image2.load()
151
+ return image1._new(image1.im.chop_hard_light(image2.im))
152
+
153
+
154
+ def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image:
155
+ """
156
+ Superimposes two images on top of each other using the Overlay algorithm
157
+
158
+ :rtype: :py:class:`~PIL.Image.Image`
159
+ """
160
+
161
+ image1.load()
162
+ image2.load()
163
+ return image1._new(image1.im.chop_overlay(image2.im))
164
+
165
+
166
+ def add(
167
+ image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0
168
+ ) -> Image.Image:
169
+ """
170
+ Adds two images, dividing the result by scale and adding the
171
+ offset. If omitted, scale defaults to 1.0, and offset to 0.0. ::
172
+
173
+ out = ((image1 + image2) / scale + offset)
174
+
175
+ :rtype: :py:class:`~PIL.Image.Image`
176
+ """
177
+
178
+ image1.load()
179
+ image2.load()
180
+ return image1._new(image1.im.chop_add(image2.im, scale, offset))
181
+
182
+
183
+ def subtract(
184
+ image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0
185
+ ) -> Image.Image:
186
+ """
187
+ Subtracts two images, dividing the result by scale and adding the offset.
188
+ If omitted, scale defaults to 1.0, and offset to 0.0. ::
189
+
190
+ out = ((image1 - image2) / scale + offset)
191
+
192
+ :rtype: :py:class:`~PIL.Image.Image`
193
+ """
194
+
195
+ image1.load()
196
+ image2.load()
197
+ return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
198
+
199
+
200
+ def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
201
+ """Add two images, without clipping the result. ::
202
+
203
+ out = ((image1 + image2) % MAX)
204
+
205
+ :rtype: :py:class:`~PIL.Image.Image`
206
+ """
207
+
208
+ image1.load()
209
+ image2.load()
210
+ return image1._new(image1.im.chop_add_modulo(image2.im))
211
+
212
+
213
+ def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
214
+ """Subtract two images, without clipping the result. ::
215
+
216
+ out = ((image1 - image2) % MAX)
217
+
218
+ :rtype: :py:class:`~PIL.Image.Image`
219
+ """
220
+
221
+ image1.load()
222
+ image2.load()
223
+ return image1._new(image1.im.chop_subtract_modulo(image2.im))
224
+
225
+
226
+ def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image:
227
+ """Logical AND between two images.
228
+
229
+ Both of the images must have mode "1". If you would like to perform a
230
+ logical AND on an image with a mode other than "1", try
231
+ :py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
232
+ as the second image. ::
233
+
234
+ out = ((image1 and image2) % MAX)
235
+
236
+ :rtype: :py:class:`~PIL.Image.Image`
237
+ """
238
+
239
+ image1.load()
240
+ image2.load()
241
+ return image1._new(image1.im.chop_and(image2.im))
242
+
243
+
244
+ def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image:
245
+ """Logical OR between two images.
246
+
247
+ Both of the images must have mode "1". ::
248
+
249
+ out = ((image1 or image2) % MAX)
250
+
251
+ :rtype: :py:class:`~PIL.Image.Image`
252
+ """
253
+
254
+ image1.load()
255
+ image2.load()
256
+ return image1._new(image1.im.chop_or(image2.im))
257
+
258
+
259
+ def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image:
260
+ """Logical XOR between two images.
261
+
262
+ Both of the images must have mode "1". ::
263
+
264
+ out = ((bool(image1) != bool(image2)) % MAX)
265
+
266
+ :rtype: :py:class:`~PIL.Image.Image`
267
+ """
268
+
269
+ image1.load()
270
+ image2.load()
271
+ return image1._new(image1.im.chop_xor(image2.im))
272
+
273
+
274
+ def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image:
275
+ """Blend images using constant transparency weight. Alias for
276
+ :py:func:`PIL.Image.blend`.
277
+
278
+ :rtype: :py:class:`~PIL.Image.Image`
279
+ """
280
+
281
+ return Image.blend(image1, image2, alpha)
282
+
283
+
284
+ def composite(
285
+ image1: Image.Image, image2: Image.Image, mask: Image.Image
286
+ ) -> Image.Image:
287
+ """Create composite using transparency mask. Alias for
288
+ :py:func:`PIL.Image.composite`.
289
+
290
+ :rtype: :py:class:`~PIL.Image.Image`
291
+ """
292
+
293
+ return Image.composite(image1, image2, mask)
294
+
295
+
296
+ def offset(image: Image.Image, xoffset: int, yoffset: int | None = None) -> Image.Image:
297
+ """Returns a copy of the image where data has been offset by the given
298
+ distances. Data wraps around the edges. If ``yoffset`` is omitted, it
299
+ is assumed to be equal to ``xoffset``.
300
+
301
+ :param image: Input image.
302
+ :param xoffset: The horizontal distance.
303
+ :param yoffset: The vertical distance. If omitted, both
304
+ distances are set to the same value.
305
+ :rtype: :py:class:`~PIL.Image.Image`
306
+ """
307
+
308
+ if yoffset is None:
309
+ yoffset = xoffset
310
+ image.load()
311
+ return image._new(image.im.offset(xoffset, yoffset))
.venv/Lib/site-packages/PIL/ImageCms.py ADDED
@@ -0,0 +1,1007 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # The Python Imaging Library.
2
+ # $Id$
3
+
4
+ # Optional color management support, based on Kevin Cazabon's PyCMS
5
+ # library.
6
+
7
+ # History:
8
+
9
+ # 2009-03-08 fl Added to PIL.
10
+
11
+ # Copyright (C) 2002-2003 Kevin Cazabon
12
+ # Copyright (c) 2009 by Fredrik Lundh
13
+ # Copyright (c) 2013 by Eric Soroos
14
+
15
+ # See the README file for information on usage and redistribution. See
16
+ # below for the original description.
17
+ from __future__ import annotations
18
+
19
+ import sys
20
+ from enum import IntEnum
21
+
22
+ from . import Image
23
+
24
+ try:
25
+ from . import _imagingcms
26
+ except ImportError as ex:
27
+ # Allow error import for doc purposes, but error out when accessing
28
+ # anything in core.
29
+ from ._util import DeferredError
30
+
31
+ _imagingcms = DeferredError.new(ex)
32
+
33
+ DESCRIPTION = """
34
+ pyCMS
35
+
36
+ a Python / PIL interface to the littleCMS ICC Color Management System
37
+ Copyright (C) 2002-2003 Kevin Cazabon
38
39
+ https://www.cazabon.com
40
+
41
+ pyCMS home page: https://www.cazabon.com/pyCMS
42
+ littleCMS home page: https://www.littlecms.com
43
+ (littleCMS is Copyright (C) 1998-2001 Marti Maria)
44
+
45
+ Originally released under LGPL. Graciously donated to PIL in
46
+ March 2009, for distribution under the standard PIL license
47
+
48
+ The pyCMS.py module provides a "clean" interface between Python/PIL and
49
+ pyCMSdll, taking care of some of the more complex handling of the direct
50
+ pyCMSdll functions, as well as error-checking and making sure that all
51
+ relevant data is kept together.
52
+
53
+ While it is possible to call pyCMSdll functions directly, it's not highly
54
+ recommended.
55
+
56
+ Version History:
57
+
58
+ 1.0.0 pil Oct 2013 Port to LCMS 2.
59
+
60
+ 0.1.0 pil mod March 10, 2009
61
+
62
+ Renamed display profile to proof profile. The proof
63
+ profile is the profile of the device that is being
64
+ simulated, not the profile of the device which is
65
+ actually used to display/print the final simulation
66
+ (that'd be the output profile) - also see LCMSAPI.txt
67
+ input colorspace -> using 'renderingIntent' -> proof
68
+ colorspace -> using 'proofRenderingIntent' -> output
69
+ colorspace
70
+
71
+ Added LCMS FLAGS support.
72
+ Added FLAGS["SOFTPROOFING"] as default flag for
73
+ buildProofTransform (otherwise the proof profile/intent
74
+ would be ignored).
75
+
76
+ 0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms
77
+
78
+ 0.0.2 alpha Jan 6, 2002
79
+
80
+ Added try/except statements around type() checks of
81
+ potential CObjects... Python won't let you use type()
82
+ on them, and raises a TypeError (stupid, if you ask
83
+ me!)
84
+
85
+ Added buildProofTransformFromOpenProfiles() function.
86
+ Additional fixes in DLL, see DLL code for details.
87
+
88
+ 0.0.1 alpha first public release, Dec. 26, 2002
89
+
90
+ Known to-do list with current version (of Python interface, not pyCMSdll):
91
+
92
+ none
93
+
94
+ """
95
+
96
+ VERSION = "1.0.0 pil"
97
+
98
+ # --------------------------------------------------------------------.
99
+
100
+ core = _imagingcms
101
+
102
+ #
103
+ # intent/direction values
104
+
105
+
106
+ class Intent(IntEnum):
107
+ PERCEPTUAL = 0
108
+ RELATIVE_COLORIMETRIC = 1
109
+ SATURATION = 2
110
+ ABSOLUTE_COLORIMETRIC = 3
111
+
112
+
113
+ class Direction(IntEnum):
114
+ INPUT = 0
115
+ OUTPUT = 1
116
+ PROOF = 2
117
+
118
+
119
+ #
120
+ # flags
121
+
122
+ FLAGS = {
123
+ "MATRIXINPUT": 1,
124
+ "MATRIXOUTPUT": 2,
125
+ "MATRIXONLY": (1 | 2),
126
+ "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
127
+ # Don't create prelinearization tables on precalculated transforms
128
+ # (internal use):
129
+ "NOPRELINEARIZATION": 16,
130
+ "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
131
+ "NOTCACHE": 64, # Inhibit 1-pixel cache
132
+ "NOTPRECALC": 256,
133
+ "NULLTRANSFORM": 512, # Don't transform anyway
134
+ "HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
135
+ "LOWRESPRECALC": 2048, # Use less memory to minimize resources
136
+ "WHITEBLACKCOMPENSATION": 8192,
137
+ "BLACKPOINTCOMPENSATION": 8192,
138
+ "GAMUTCHECK": 4096, # Out of Gamut alarm
139
+ "SOFTPROOFING": 16384, # Do softproofing
140
+ "PRESERVEBLACK": 32768, # Black preservation
141
+ "NODEFAULTRESOURCEDEF": 16777216, # CRD special
142
+ "GRIDPOINTS": lambda n: (n & 0xFF) << 16, # Gridpoints
143
+ }
144
+
145
+ _MAX_FLAG = 0
146
+ for flag in FLAGS.values():
147
+ if isinstance(flag, int):
148
+ _MAX_FLAG = _MAX_FLAG | flag
149
+
150
+
151
+ # --------------------------------------------------------------------.
152
+ # Experimental PIL-level API
153
+ # --------------------------------------------------------------------.
154
+
155
+ ##
156
+ # Profile.
157
+
158
+
159
+ class ImageCmsProfile:
160
+ def __init__(self, profile):
161
+ """
162
+ :param profile: Either a string representing a filename,
163
+ a file like object containing a profile or a
164
+ low-level profile object
165
+
166
+ """
167
+
168
+ if isinstance(profile, str):
169
+ if sys.platform == "win32":
170
+ profile_bytes_path = profile.encode()
171
+ try:
172
+ profile_bytes_path.decode("ascii")
173
+ except UnicodeDecodeError:
174
+ with open(profile, "rb") as f:
175
+ self._set(core.profile_frombytes(f.read()))
176
+ return
177
+ self._set(core.profile_open(profile), profile)
178
+ elif hasattr(profile, "read"):
179
+ self._set(core.profile_frombytes(profile.read()))
180
+ elif isinstance(profile, _imagingcms.CmsProfile):
181
+ self._set(profile)
182
+ else:
183
+ msg = "Invalid type for Profile"
184
+ raise TypeError(msg)
185
+
186
+ def _set(self, profile, filename=None):
187
+ self.profile = profile
188
+ self.filename = filename
189
+ self.product_name = None # profile.product_name
190
+ self.product_info = None # profile.product_info
191
+
192
+ def tobytes(self):
193
+ """
194
+ Returns the profile in a format suitable for embedding in
195
+ saved images.
196
+
197
+ :returns: a bytes object containing the ICC profile.
198
+ """
199
+
200
+ return core.profile_tobytes(self.profile)
201
+
202
+
203
+ class ImageCmsTransform(Image.ImagePointHandler):
204
+
205
+ """
206
+ Transform. This can be used with the procedural API, or with the standard
207
+ :py:func:`~PIL.Image.Image.point` method.
208
+
209
+ Will return the output profile in the ``output.info['icc_profile']``.
210
+ """
211
+
212
+ def __init__(
213
+ self,
214
+ input,
215
+ output,
216
+ input_mode,
217
+ output_mode,
218
+ intent=Intent.PERCEPTUAL,
219
+ proof=None,
220
+ proof_intent=Intent.ABSOLUTE_COLORIMETRIC,
221
+ flags=0,
222
+ ):
223
+ if proof is None:
224
+ self.transform = core.buildTransform(
225
+ input.profile, output.profile, input_mode, output_mode, intent, flags
226
+ )
227
+ else:
228
+ self.transform = core.buildProofTransform(
229
+ input.profile,
230
+ output.profile,
231
+ proof.profile,
232
+ input_mode,
233
+ output_mode,
234
+ intent,
235
+ proof_intent,
236
+ flags,
237
+ )
238
+ # Note: inputMode and outputMode are for pyCMS compatibility only
239
+ self.input_mode = self.inputMode = input_mode
240
+ self.output_mode = self.outputMode = output_mode
241
+
242
+ self.output_profile = output
243
+
244
+ def point(self, im):
245
+ return self.apply(im)
246
+
247
+ def apply(self, im, imOut=None):
248
+ im.load()
249
+ if imOut is None:
250
+ imOut = Image.new(self.output_mode, im.size, None)
251
+ self.transform.apply(im.im.id, imOut.im.id)
252
+ imOut.info["icc_profile"] = self.output_profile.tobytes()
253
+ return imOut
254
+
255
+ def apply_in_place(self, im):
256
+ im.load()
257
+ if im.mode != self.output_mode:
258
+ msg = "mode mismatch"
259
+ raise ValueError(msg) # wrong output mode
260
+ self.transform.apply(im.im.id, im.im.id)
261
+ im.info["icc_profile"] = self.output_profile.tobytes()
262
+ return im
263
+
264
+
265
+ def get_display_profile(handle=None):
266
+ """
267
+ (experimental) Fetches the profile for the current display device.
268
+
269
+ :returns: ``None`` if the profile is not known.
270
+ """
271
+
272
+ if sys.platform != "win32":
273
+ return None
274
+
275
+ from . import ImageWin
276
+
277
+ if isinstance(handle, ImageWin.HDC):
278
+ profile = core.get_display_profile_win32(handle, 1)
279
+ else:
280
+ profile = core.get_display_profile_win32(handle or 0)
281
+ if profile is None:
282
+ return None
283
+ return ImageCmsProfile(profile)
284
+
285
+
286
+ # --------------------------------------------------------------------.
287
+ # pyCMS compatible layer
288
+ # --------------------------------------------------------------------.
289
+
290
+
291
+ class PyCMSError(Exception):
292
+
293
+ """(pyCMS) Exception class.
294
+ This is used for all errors in the pyCMS API."""
295
+
296
+ pass
297
+
298
+
299
+ def profileToProfile(
300
+ im,
301
+ inputProfile,
302
+ outputProfile,
303
+ renderingIntent=Intent.PERCEPTUAL,
304
+ outputMode=None,
305
+ inPlace=False,
306
+ flags=0,
307
+ ):
308
+ """
309
+ (pyCMS) Applies an ICC transformation to a given image, mapping from
310
+ ``inputProfile`` to ``outputProfile``.
311
+
312
+ If the input or output profiles specified are not valid filenames, a
313
+ :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and
314
+ ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised.
315
+ If an error occurs during application of the profiles,
316
+ a :exc:`PyCMSError` will be raised.
317
+ If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS),
318
+ a :exc:`PyCMSError` will be raised.
319
+
320
+ This function applies an ICC transformation to im from ``inputProfile``'s
321
+ color space to ``outputProfile``'s color space using the specified rendering
322
+ intent to decide how to handle out-of-gamut colors.
323
+
324
+ ``outputMode`` can be used to specify that a color mode conversion is to
325
+ be done using these profiles, but the specified profiles must be able
326
+ to handle that mode. I.e., if converting im from RGB to CMYK using
327
+ profiles, the input profile must handle RGB data, and the output
328
+ profile must handle CMYK data.
329
+
330
+ :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...)
331
+ or Image.open(...), etc.)
332
+ :param inputProfile: String, as a valid filename path to the ICC input
333
+ profile you wish to use for this image, or a profile object
334
+ :param outputProfile: String, as a valid filename path to the ICC output
335
+ profile you wish to use for this image, or a profile object
336
+ :param renderingIntent: Integer (0-3) specifying the rendering intent you
337
+ wish to use for the transform
338
+
339
+ ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
340
+ ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
341
+ ImageCms.Intent.SATURATION = 2
342
+ ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
343
+
344
+ see the pyCMS documentation for details on rendering intents and what
345
+ they do.
346
+ :param outputMode: A valid PIL mode for the output image (i.e. "RGB",
347
+ "CMYK", etc.). Note: if rendering the image "inPlace", outputMode
348
+ MUST be the same mode as the input, or omitted completely. If
349
+ omitted, the outputMode will be the same as the mode of the input
350
+ image (im.mode)
351
+ :param inPlace: Boolean. If ``True``, the original image is modified in-place,
352
+ and ``None`` is returned. If ``False`` (default), a new
353
+ :py:class:`~PIL.Image.Image` object is returned with the transform applied.
354
+ :param flags: Integer (0-...) specifying additional flags
355
+ :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on
356
+ the value of ``inPlace``
357
+ :exception PyCMSError:
358
+ """
359
+
360
+ if outputMode is None:
361
+ outputMode = im.mode
362
+
363
+ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
364
+ msg = "renderingIntent must be an integer between 0 and 3"
365
+ raise PyCMSError(msg)
366
+
367
+ if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
368
+ msg = f"flags must be an integer between 0 and {_MAX_FLAG}"
369
+ raise PyCMSError(msg)
370
+
371
+ try:
372
+ if not isinstance(inputProfile, ImageCmsProfile):
373
+ inputProfile = ImageCmsProfile(inputProfile)
374
+ if not isinstance(outputProfile, ImageCmsProfile):
375
+ outputProfile = ImageCmsProfile(outputProfile)
376
+ transform = ImageCmsTransform(
377
+ inputProfile,
378
+ outputProfile,
379
+ im.mode,
380
+ outputMode,
381
+ renderingIntent,
382
+ flags=flags,
383
+ )
384
+ if inPlace:
385
+ transform.apply_in_place(im)
386
+ imOut = None
387
+ else:
388
+ imOut = transform.apply(im)
389
+ except (OSError, TypeError, ValueError) as v:
390
+ raise PyCMSError(v) from v
391
+
392
+ return imOut
393
+
394
+
395
+ def getOpenProfile(profileFilename):
396
+ """
397
+ (pyCMS) Opens an ICC profile file.
398
+
399
+ The PyCMSProfile object can be passed back into pyCMS for use in creating
400
+ transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
401
+
402
+ If ``profileFilename`` is not a valid filename for an ICC profile,
403
+ a :exc:`PyCMSError` will be raised.
404
+
405
+ :param profileFilename: String, as a valid filename path to the ICC profile
406
+ you wish to open, or a file-like object.
407
+ :returns: A CmsProfile class object.
408
+ :exception PyCMSError:
409
+ """
410
+
411
+ try:
412
+ return ImageCmsProfile(profileFilename)
413
+ except (OSError, TypeError, ValueError) as v:
414
+ raise PyCMSError(v) from v
415
+
416
+
417
+ def buildTransform(
418
+ inputProfile,
419
+ outputProfile,
420
+ inMode,
421
+ outMode,
422
+ renderingIntent=Intent.PERCEPTUAL,
423
+ flags=0,
424
+ ):
425
+ """
426
+ (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
427
+ ``outputProfile``. Use applyTransform to apply the transform to a given
428
+ image.
429
+
430
+ If the input or output profiles specified are not valid filenames, a
431
+ :exc:`PyCMSError` will be raised. If an error occurs during creation
432
+ of the transform, a :exc:`PyCMSError` will be raised.
433
+
434
+ If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
435
+ (or by pyCMS), a :exc:`PyCMSError` will be raised.
436
+
437
+ This function builds and returns an ICC transform from the ``inputProfile``
438
+ to the ``outputProfile`` using the ``renderingIntent`` to determine what to do
439
+ with out-of-gamut colors. It will ONLY work for converting images that
440
+ are in ``inMode`` to images that are in ``outMode`` color format (PIL mode,
441
+ i.e. "RGB", "RGBA", "CMYK", etc.).
442
+
443
+ Building the transform is a fair part of the overhead in
444
+ ImageCms.profileToProfile(), so if you're planning on converting multiple
445
+ images using the same input/output settings, this can save you time.
446
+ Once you have a transform object, it can be used with
447
+ ImageCms.applyProfile() to convert images without the need to re-compute
448
+ the lookup table for the transform.
449
+
450
+ The reason pyCMS returns a class object rather than a handle directly
451
+ to the transform is that it needs to keep track of the PIL input/output
452
+ modes that the transform is meant for. These attributes are stored in
453
+ the ``inMode`` and ``outMode`` attributes of the object (which can be
454
+ manually overridden if you really want to, but I don't know of any
455
+ time that would be of use, or would even work).
456
+
457
+ :param inputProfile: String, as a valid filename path to the ICC input
458
+ profile you wish to use for this transform, or a profile object
459
+ :param outputProfile: String, as a valid filename path to the ICC output
460
+ profile you wish to use for this transform, or a profile object
461
+ :param inMode: String, as a valid PIL mode that the appropriate profile
462
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
463
+ :param outMode: String, as a valid PIL mode that the appropriate profile
464
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
465
+ :param renderingIntent: Integer (0-3) specifying the rendering intent you
466
+ wish to use for the transform
467
+
468
+ ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
469
+ ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
470
+ ImageCms.Intent.SATURATION = 2
471
+ ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
472
+
473
+ see the pyCMS documentation for details on rendering intents and what
474
+ they do.
475
+ :param flags: Integer (0-...) specifying additional flags
476
+ :returns: A CmsTransform class object.
477
+ :exception PyCMSError:
478
+ """
479
+
480
+ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
481
+ msg = "renderingIntent must be an integer between 0 and 3"
482
+ raise PyCMSError(msg)
483
+
484
+ if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
485
+ msg = "flags must be an integer between 0 and %s" + _MAX_FLAG
486
+ raise PyCMSError(msg)
487
+
488
+ try:
489
+ if not isinstance(inputProfile, ImageCmsProfile):
490
+ inputProfile = ImageCmsProfile(inputProfile)
491
+ if not isinstance(outputProfile, ImageCmsProfile):
492
+ outputProfile = ImageCmsProfile(outputProfile)
493
+ return ImageCmsTransform(
494
+ inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags
495
+ )
496
+ except (OSError, TypeError, ValueError) as v:
497
+ raise PyCMSError(v) from v
498
+
499
+
500
+ def buildProofTransform(
501
+ inputProfile,
502
+ outputProfile,
503
+ proofProfile,
504
+ inMode,
505
+ outMode,
506
+ renderingIntent=Intent.PERCEPTUAL,
507
+ proofRenderingIntent=Intent.ABSOLUTE_COLORIMETRIC,
508
+ flags=FLAGS["SOFTPROOFING"],
509
+ ):
510
+ """
511
+ (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
512
+ ``outputProfile``, but tries to simulate the result that would be
513
+ obtained on the ``proofProfile`` device.
514
+
515
+ If the input, output, or proof profiles specified are not valid
516
+ filenames, a :exc:`PyCMSError` will be raised.
517
+
518
+ If an error occurs during creation of the transform,
519
+ a :exc:`PyCMSError` will be raised.
520
+
521
+ If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
522
+ (or by pyCMS), a :exc:`PyCMSError` will be raised.
523
+
524
+ This function builds and returns an ICC transform from the ``inputProfile``
525
+ to the ``outputProfile``, but tries to simulate the result that would be
526
+ obtained on the ``proofProfile`` device using ``renderingIntent`` and
527
+ ``proofRenderingIntent`` to determine what to do with out-of-gamut
528
+ colors. This is known as "soft-proofing". It will ONLY work for
529
+ converting images that are in ``inMode`` to images that are in outMode
530
+ color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
531
+
532
+ Usage of the resulting transform object is exactly the same as with
533
+ ImageCms.buildTransform().
534
+
535
+ Proof profiling is generally used when using an output device to get a
536
+ good idea of what the final printed/displayed image would look like on
537
+ the ``proofProfile`` device when it's quicker and easier to use the
538
+ output device for judging color. Generally, this means that the
539
+ output device is a monitor, or a dye-sub printer (etc.), and the simulated
540
+ device is something more expensive, complicated, or time consuming
541
+ (making it difficult to make a real print for color judgement purposes).
542
+
543
+ Soft-proofing basically functions by adjusting the colors on the
544
+ output device to match the colors of the device being simulated. However,
545
+ when the simulated device has a much wider gamut than the output
546
+ device, you may obtain marginal results.
547
+
548
+ :param inputProfile: String, as a valid filename path to the ICC input
549
+ profile you wish to use for this transform, or a profile object
550
+ :param outputProfile: String, as a valid filename path to the ICC output
551
+ (monitor, usually) profile you wish to use for this transform, or a
552
+ profile object
553
+ :param proofProfile: String, as a valid filename path to the ICC proof
554
+ profile you wish to use for this transform, or a profile object
555
+ :param inMode: String, as a valid PIL mode that the appropriate profile
556
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
557
+ :param outMode: String, as a valid PIL mode that the appropriate profile
558
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
559
+ :param renderingIntent: Integer (0-3) specifying the rendering intent you
560
+ wish to use for the input->proof (simulated) transform
561
+
562
+ ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
563
+ ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
564
+ ImageCms.Intent.SATURATION = 2
565
+ ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
566
+
567
+ see the pyCMS documentation for details on rendering intents and what
568
+ they do.
569
+ :param proofRenderingIntent: Integer (0-3) specifying the rendering intent
570
+ you wish to use for proof->output transform
571
+
572
+ ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
573
+ ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
574
+ ImageCms.Intent.SATURATION = 2
575
+ ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
576
+
577
+ see the pyCMS documentation for details on rendering intents and what
578
+ they do.
579
+ :param flags: Integer (0-...) specifying additional flags
580
+ :returns: A CmsTransform class object.
581
+ :exception PyCMSError:
582
+ """
583
+
584
+ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
585
+ msg = "renderingIntent must be an integer between 0 and 3"
586
+ raise PyCMSError(msg)
587
+
588
+ if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
589
+ msg = "flags must be an integer between 0 and %s" + _MAX_FLAG
590
+ raise PyCMSError(msg)
591
+
592
+ try:
593
+ if not isinstance(inputProfile, ImageCmsProfile):
594
+ inputProfile = ImageCmsProfile(inputProfile)
595
+ if not isinstance(outputProfile, ImageCmsProfile):
596
+ outputProfile = ImageCmsProfile(outputProfile)
597
+ if not isinstance(proofProfile, ImageCmsProfile):
598
+ proofProfile = ImageCmsProfile(proofProfile)
599
+ return ImageCmsTransform(
600
+ inputProfile,
601
+ outputProfile,
602
+ inMode,
603
+ outMode,
604
+ renderingIntent,
605
+ proofProfile,
606
+ proofRenderingIntent,
607
+ flags,
608
+ )
609
+ except (OSError, TypeError, ValueError) as v:
610
+ raise PyCMSError(v) from v
611
+
612
+
613
+ buildTransformFromOpenProfiles = buildTransform
614
+ buildProofTransformFromOpenProfiles = buildProofTransform
615
+
616
+
617
+ def applyTransform(im, transform, inPlace=False):
618
+ """
619
+ (pyCMS) Applies a transform to a given image.
620
+
621
+ If ``im.mode != transform.inMode``, a :exc:`PyCMSError` is raised.
622
+
623
+ If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a
624
+ :exc:`PyCMSError` is raised.
625
+
626
+ If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not
627
+ supported by pyCMSdll or the profiles you used for the transform, a
628
+ :exc:`PyCMSError` is raised.
629
+
630
+ If an error occurs while the transform is being applied,
631
+ a :exc:`PyCMSError` is raised.
632
+
633
+ This function applies a pre-calculated transform (from
634
+ ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
635
+ to an image. The transform can be used for multiple images, saving
636
+ considerable calculation time if doing the same conversion multiple times.
637
+
638
+ If you want to modify im in-place instead of receiving a new image as
639
+ the return value, set ``inPlace`` to ``True``. This can only be done if
640
+ ``transform.inMode`` and ``transform.outMode`` are the same, because we can't
641
+ change the mode in-place (the buffer sizes for some modes are
642
+ different). The default behavior is to return a new :py:class:`~PIL.Image.Image`
643
+ object of the same dimensions in mode ``transform.outMode``.
644
+
645
+ :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same
646
+ as the ``inMode`` supported by the transform.
647
+ :param transform: A valid CmsTransform class object
648
+ :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is
649
+ returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the
650
+ transform applied is returned (and ``im`` is not changed). The default is
651
+ ``False``.
652
+ :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object,
653
+ depending on the value of ``inPlace``. The profile will be returned in
654
+ the image's ``info['icc_profile']``.
655
+ :exception PyCMSError:
656
+ """
657
+
658
+ try:
659
+ if inPlace:
660
+ transform.apply_in_place(im)
661
+ imOut = None
662
+ else:
663
+ imOut = transform.apply(im)
664
+ except (TypeError, ValueError) as v:
665
+ raise PyCMSError(v) from v
666
+
667
+ return imOut
668
+
669
+
670
+ def createProfile(colorSpace, colorTemp=-1):
671
+ """
672
+ (pyCMS) Creates a profile.
673
+
674
+ If colorSpace not in ``["LAB", "XYZ", "sRGB"]``,
675
+ a :exc:`PyCMSError` is raised.
676
+
677
+ If using LAB and ``colorTemp`` is not a positive integer,
678
+ a :exc:`PyCMSError` is raised.
679
+
680
+ If an error occurs while creating the profile,
681
+ a :exc:`PyCMSError` is raised.
682
+
683
+ Use this function to create common profiles on-the-fly instead of
684
+ having to supply a profile on disk and knowing the path to it. It
685
+ returns a normal CmsProfile object that can be passed to
686
+ ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
687
+ to images.
688
+
689
+ :param colorSpace: String, the color space of the profile you wish to
690
+ create.
691
+ Currently only "LAB", "XYZ", and "sRGB" are supported.
692
+ :param colorTemp: Positive integer for the white point for the profile, in
693
+ degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
694
+ illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
695
+ profiles, and is ignored for XYZ and sRGB.
696
+ :returns: A CmsProfile class object
697
+ :exception PyCMSError:
698
+ """
699
+
700
+ if colorSpace not in ["LAB", "XYZ", "sRGB"]:
701
+ msg = (
702
+ f"Color space not supported for on-the-fly profile creation ({colorSpace})"
703
+ )
704
+ raise PyCMSError(msg)
705
+
706
+ if colorSpace == "LAB":
707
+ try:
708
+ colorTemp = float(colorTemp)
709
+ except (TypeError, ValueError) as e:
710
+ msg = f'Color temperature must be numeric, "{colorTemp}" not valid'
711
+ raise PyCMSError(msg) from e
712
+
713
+ try:
714
+ return core.createProfile(colorSpace, colorTemp)
715
+ except (TypeError, ValueError) as v:
716
+ raise PyCMSError(v) from v
717
+
718
+
719
+ def getProfileName(profile):
720
+ """
721
+
722
+ (pyCMS) Gets the internal product name for the given profile.
723
+
724
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile,
725
+ a :exc:`PyCMSError` is raised If an error occurs while trying
726
+ to obtain the name tag, a :exc:`PyCMSError` is raised.
727
+
728
+ Use this function to obtain the INTERNAL name of the profile (stored
729
+ in an ICC tag in the profile itself), usually the one used when the
730
+ profile was originally created. Sometimes this tag also contains
731
+ additional information supplied by the creator.
732
+
733
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
734
+ filename of an ICC profile.
735
+ :returns: A string containing the internal name of the profile as stored
736
+ in an ICC tag.
737
+ :exception PyCMSError:
738
+ """
739
+
740
+ try:
741
+ # add an extra newline to preserve pyCMS compatibility
742
+ if not isinstance(profile, ImageCmsProfile):
743
+ profile = ImageCmsProfile(profile)
744
+ # do it in python, not c.
745
+ # // name was "%s - %s" (model, manufacturer) || Description ,
746
+ # // but if the Model and Manufacturer were the same or the model
747
+ # // was long, Just the model, in 1.x
748
+ model = profile.profile.model
749
+ manufacturer = profile.profile.manufacturer
750
+
751
+ if not (model or manufacturer):
752
+ return (profile.profile.profile_description or "") + "\n"
753
+ if not manufacturer or len(model) > 30:
754
+ return model + "\n"
755
+ return f"{model} - {manufacturer}\n"
756
+
757
+ except (AttributeError, OSError, TypeError, ValueError) as v:
758
+ raise PyCMSError(v) from v
759
+
760
+
761
+ def getProfileInfo(profile):
762
+ """
763
+ (pyCMS) Gets the internal product information for the given profile.
764
+
765
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile,
766
+ a :exc:`PyCMSError` is raised.
767
+
768
+ If an error occurs while trying to obtain the info tag,
769
+ a :exc:`PyCMSError` is raised.
770
+
771
+ Use this function to obtain the information stored in the profile's
772
+ info tag. This often contains details about the profile, and how it
773
+ was created, as supplied by the creator.
774
+
775
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
776
+ filename of an ICC profile.
777
+ :returns: A string containing the internal profile information stored in
778
+ an ICC tag.
779
+ :exception PyCMSError:
780
+ """
781
+
782
+ try:
783
+ if not isinstance(profile, ImageCmsProfile):
784
+ profile = ImageCmsProfile(profile)
785
+ # add an extra newline to preserve pyCMS compatibility
786
+ # Python, not C. the white point bits weren't working well,
787
+ # so skipping.
788
+ # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
789
+ description = profile.profile.profile_description
790
+ cpright = profile.profile.copyright
791
+ elements = [element for element in (description, cpright) if element]
792
+ return "\r\n\r\n".join(elements) + "\r\n\r\n"
793
+
794
+ except (AttributeError, OSError, TypeError, ValueError) as v:
795
+ raise PyCMSError(v) from v
796
+
797
+
798
+ def getProfileCopyright(profile):
799
+ """
800
+ (pyCMS) Gets the copyright for the given profile.
801
+
802
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
803
+ :exc:`PyCMSError` is raised.
804
+
805
+ If an error occurs while trying to obtain the copyright tag,
806
+ a :exc:`PyCMSError` is raised.
807
+
808
+ Use this function to obtain the information stored in the profile's
809
+ copyright tag.
810
+
811
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
812
+ filename of an ICC profile.
813
+ :returns: A string containing the internal profile information stored in
814
+ an ICC tag.
815
+ :exception PyCMSError:
816
+ """
817
+ try:
818
+ # add an extra newline to preserve pyCMS compatibility
819
+ if not isinstance(profile, ImageCmsProfile):
820
+ profile = ImageCmsProfile(profile)
821
+ return (profile.profile.copyright or "") + "\n"
822
+ except (AttributeError, OSError, TypeError, ValueError) as v:
823
+ raise PyCMSError(v) from v
824
+
825
+
826
+ def getProfileManufacturer(profile):
827
+ """
828
+ (pyCMS) Gets the manufacturer for the given profile.
829
+
830
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
831
+ :exc:`PyCMSError` is raised.
832
+
833
+ If an error occurs while trying to obtain the manufacturer tag, a
834
+ :exc:`PyCMSError` is raised.
835
+
836
+ Use this function to obtain the information stored in the profile's
837
+ manufacturer tag.
838
+
839
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
840
+ filename of an ICC profile.
841
+ :returns: A string containing the internal profile information stored in
842
+ an ICC tag.
843
+ :exception PyCMSError:
844
+ """
845
+ try:
846
+ # add an extra newline to preserve pyCMS compatibility
847
+ if not isinstance(profile, ImageCmsProfile):
848
+ profile = ImageCmsProfile(profile)
849
+ return (profile.profile.manufacturer or "") + "\n"
850
+ except (AttributeError, OSError, TypeError, ValueError) as v:
851
+ raise PyCMSError(v) from v
852
+
853
+
854
+ def getProfileModel(profile):
855
+ """
856
+ (pyCMS) Gets the model for the given profile.
857
+
858
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
859
+ :exc:`PyCMSError` is raised.
860
+
861
+ If an error occurs while trying to obtain the model tag,
862
+ a :exc:`PyCMSError` is raised.
863
+
864
+ Use this function to obtain the information stored in the profile's
865
+ model tag.
866
+
867
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
868
+ filename of an ICC profile.
869
+ :returns: A string containing the internal profile information stored in
870
+ an ICC tag.
871
+ :exception PyCMSError:
872
+ """
873
+
874
+ try:
875
+ # add an extra newline to preserve pyCMS compatibility
876
+ if not isinstance(profile, ImageCmsProfile):
877
+ profile = ImageCmsProfile(profile)
878
+ return (profile.profile.model or "") + "\n"
879
+ except (AttributeError, OSError, TypeError, ValueError) as v:
880
+ raise PyCMSError(v) from v
881
+
882
+
883
+ def getProfileDescription(profile):
884
+ """
885
+ (pyCMS) Gets the description for the given profile.
886
+
887
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
888
+ :exc:`PyCMSError` is raised.
889
+
890
+ If an error occurs while trying to obtain the description tag,
891
+ a :exc:`PyCMSError` is raised.
892
+
893
+ Use this function to obtain the information stored in the profile's
894
+ description tag.
895
+
896
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
897
+ filename of an ICC profile.
898
+ :returns: A string containing the internal profile information stored in an
899
+ ICC tag.
900
+ :exception PyCMSError:
901
+ """
902
+
903
+ try:
904
+ # add an extra newline to preserve pyCMS compatibility
905
+ if not isinstance(profile, ImageCmsProfile):
906
+ profile = ImageCmsProfile(profile)
907
+ return (profile.profile.profile_description or "") + "\n"
908
+ except (AttributeError, OSError, TypeError, ValueError) as v:
909
+ raise PyCMSError(v) from v
910
+
911
+
912
+ def getDefaultIntent(profile):
913
+ """
914
+ (pyCMS) Gets the default intent name for the given profile.
915
+
916
+ If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
917
+ :exc:`PyCMSError` is raised.
918
+
919
+ If an error occurs while trying to obtain the default intent, a
920
+ :exc:`PyCMSError` is raised.
921
+
922
+ Use this function to determine the default (and usually best optimized)
923
+ rendering intent for this profile. Most profiles support multiple
924
+ rendering intents, but are intended mostly for one type of conversion.
925
+ If you wish to use a different intent than returned, use
926
+ ImageCms.isIntentSupported() to verify it will work first.
927
+
928
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
929
+ filename of an ICC profile.
930
+ :returns: Integer 0-3 specifying the default rendering intent for this
931
+ profile.
932
+
933
+ ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
934
+ ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
935
+ ImageCms.Intent.SATURATION = 2
936
+ ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
937
+
938
+ see the pyCMS documentation for details on rendering intents and what
939
+ they do.
940
+ :exception PyCMSError:
941
+ """
942
+
943
+ try:
944
+ if not isinstance(profile, ImageCmsProfile):
945
+ profile = ImageCmsProfile(profile)
946
+ return profile.profile.rendering_intent
947
+ except (AttributeError, OSError, TypeError, ValueError) as v:
948
+ raise PyCMSError(v) from v
949
+
950
+
951
+ def isIntentSupported(profile, intent, direction):
952
+ """
953
+ (pyCMS) Checks if a given intent is supported.
954
+
955
+ Use this function to verify that you can use your desired
956
+ ``intent`` with ``profile``, and that ``profile`` can be used for the
957
+ input/output/proof profile as you desire.
958
+
959
+ Some profiles are created specifically for one "direction", can cannot
960
+ be used for others. Some profiles can only be used for certain
961
+ rendering intents, so it's best to either verify this before trying
962
+ to create a transform with them (using this function), or catch the
963
+ potential :exc:`PyCMSError` that will occur if they don't
964
+ support the modes you select.
965
+
966
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
967
+ filename of an ICC profile.
968
+ :param intent: Integer (0-3) specifying the rendering intent you wish to
969
+ use with this profile
970
+
971
+ ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT)
972
+ ImageCms.Intent.RELATIVE_COLORIMETRIC = 1
973
+ ImageCms.Intent.SATURATION = 2
974
+ ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3
975
+
976
+ see the pyCMS documentation for details on rendering intents and what
977
+ they do.
978
+ :param direction: Integer specifying if the profile is to be used for
979
+ input, output, or proof
980
+
981
+ INPUT = 0 (or use ImageCms.Direction.INPUT)
982
+ OUTPUT = 1 (or use ImageCms.Direction.OUTPUT)
983
+ PROOF = 2 (or use ImageCms.Direction.PROOF)
984
+
985
+ :returns: 1 if the intent/direction are supported, -1 if they are not.
986
+ :exception PyCMSError:
987
+ """
988
+
989
+ try:
990
+ if not isinstance(profile, ImageCmsProfile):
991
+ profile = ImageCmsProfile(profile)
992
+ # FIXME: I get different results for the same data w. different
993
+ # compilers. Bug in LittleCMS or in the binding?
994
+ if profile.profile.is_intent_supported(intent, direction):
995
+ return 1
996
+ else:
997
+ return -1
998
+ except (AttributeError, OSError, TypeError, ValueError) as v:
999
+ raise PyCMSError(v) from v
1000
+
1001
+
1002
+ def versions():
1003
+ """
1004
+ (pyCMS) Fetches versions.
1005
+ """
1006
+
1007
+ return VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__
.venv/Lib/site-packages/PIL/ImageColor.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # map CSS3-style colour description strings to RGB
6
+ #
7
+ # History:
8
+ # 2002-10-24 fl Added support for CSS-style color strings
9
+ # 2002-12-15 fl Added RGBA support
10
+ # 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2
11
+ # 2004-07-19 fl Fixed gray/grey spelling issues
12
+ # 2009-03-05 fl Fixed rounding error in grayscale calculation
13
+ #
14
+ # Copyright (c) 2002-2004 by Secret Labs AB
15
+ # Copyright (c) 2002-2004 by Fredrik Lundh
16
+ #
17
+ # See the README file for information on usage and redistribution.
18
+ #
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ from functools import lru_cache
23
+
24
+ from . import Image
25
+
26
+
27
+ @lru_cache
28
+ def getrgb(color):
29
+ """
30
+ Convert a color string to an RGB or RGBA tuple. If the string cannot be
31
+ parsed, this function raises a :py:exc:`ValueError` exception.
32
+
33
+ .. versionadded:: 1.1.4
34
+
35
+ :param color: A color string
36
+ :return: ``(red, green, blue[, alpha])``
37
+ """
38
+ if len(color) > 100:
39
+ msg = "color specifier is too long"
40
+ raise ValueError(msg)
41
+ color = color.lower()
42
+
43
+ rgb = colormap.get(color, None)
44
+ if rgb:
45
+ if isinstance(rgb, tuple):
46
+ return rgb
47
+ colormap[color] = rgb = getrgb(rgb)
48
+ return rgb
49
+
50
+ # check for known string formats
51
+ if re.match("#[a-f0-9]{3}$", color):
52
+ return int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16)
53
+
54
+ if re.match("#[a-f0-9]{4}$", color):
55
+ return (
56
+ int(color[1] * 2, 16),
57
+ int(color[2] * 2, 16),
58
+ int(color[3] * 2, 16),
59
+ int(color[4] * 2, 16),
60
+ )
61
+
62
+ if re.match("#[a-f0-9]{6}$", color):
63
+ return int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
64
+
65
+ if re.match("#[a-f0-9]{8}$", color):
66
+ return (
67
+ int(color[1:3], 16),
68
+ int(color[3:5], 16),
69
+ int(color[5:7], 16),
70
+ int(color[7:9], 16),
71
+ )
72
+
73
+ m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
74
+ if m:
75
+ return int(m.group(1)), int(m.group(2)), int(m.group(3))
76
+
77
+ m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
78
+ if m:
79
+ return (
80
+ int((int(m.group(1)) * 255) / 100.0 + 0.5),
81
+ int((int(m.group(2)) * 255) / 100.0 + 0.5),
82
+ int((int(m.group(3)) * 255) / 100.0 + 0.5),
83
+ )
84
+
85
+ m = re.match(
86
+ r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
87
+ )
88
+ if m:
89
+ from colorsys import hls_to_rgb
90
+
91
+ rgb = hls_to_rgb(
92
+ float(m.group(1)) / 360.0,
93
+ float(m.group(3)) / 100.0,
94
+ float(m.group(2)) / 100.0,
95
+ )
96
+ return (
97
+ int(rgb[0] * 255 + 0.5),
98
+ int(rgb[1] * 255 + 0.5),
99
+ int(rgb[2] * 255 + 0.5),
100
+ )
101
+
102
+ m = re.match(
103
+ r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color
104
+ )
105
+ if m:
106
+ from colorsys import hsv_to_rgb
107
+
108
+ rgb = hsv_to_rgb(
109
+ float(m.group(1)) / 360.0,
110
+ float(m.group(2)) / 100.0,
111
+ float(m.group(3)) / 100.0,
112
+ )
113
+ return (
114
+ int(rgb[0] * 255 + 0.5),
115
+ int(rgb[1] * 255 + 0.5),
116
+ int(rgb[2] * 255 + 0.5),
117
+ )
118
+
119
+ m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
120
+ if m:
121
+ return int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
122
+ msg = f"unknown color specifier: {repr(color)}"
123
+ raise ValueError(msg)
124
+
125
+
126
+ @lru_cache
127
+ def getcolor(color, mode):
128
+ """
129
+ Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if
130
+ ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is
131
+ not color or a palette image, converts the RGB value to a grayscale value.
132
+ If the string cannot be parsed, this function raises a :py:exc:`ValueError`
133
+ exception.
134
+
135
+ .. versionadded:: 1.1.4
136
+
137
+ :param color: A color string
138
+ :param mode: Convert result to this mode
139
+ :return: ``(graylevel[, alpha]) or (red, green, blue[, alpha])``
140
+ """
141
+ # same as getrgb, but converts the result to the given mode
142
+ color, alpha = getrgb(color), 255
143
+ if len(color) == 4:
144
+ color, alpha = color[:3], color[3]
145
+
146
+ if mode == "HSV":
147
+ from colorsys import rgb_to_hsv
148
+
149
+ r, g, b = color
150
+ h, s, v = rgb_to_hsv(r / 255, g / 255, b / 255)
151
+ return int(h * 255), int(s * 255), int(v * 255)
152
+ elif Image.getmodebase(mode) == "L":
153
+ r, g, b = color
154
+ # ITU-R Recommendation 601-2 for nonlinear RGB
155
+ # scaled to 24 bits to match the convert's implementation.
156
+ color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
157
+ if mode[-1] == "A":
158
+ return color, alpha
159
+ else:
160
+ if mode[-1] == "A":
161
+ return color + (alpha,)
162
+ return color
163
+
164
+
165
+ colormap = {
166
+ # X11 colour table from https://drafts.csswg.org/css-color-4/, with
167
+ # gray/grey spelling issues fixed. This is a superset of HTML 4.0
168
+ # colour names used in CSS 1.
169
+ "aliceblue": "#f0f8ff",
170
+ "antiquewhite": "#faebd7",
171
+ "aqua": "#00ffff",
172
+ "aquamarine": "#7fffd4",
173
+ "azure": "#f0ffff",
174
+ "beige": "#f5f5dc",
175
+ "bisque": "#ffe4c4",
176
+ "black": "#000000",
177
+ "blanchedalmond": "#ffebcd",
178
+ "blue": "#0000ff",
179
+ "blueviolet": "#8a2be2",
180
+ "brown": "#a52a2a",
181
+ "burlywood": "#deb887",
182
+ "cadetblue": "#5f9ea0",
183
+ "chartreuse": "#7fff00",
184
+ "chocolate": "#d2691e",
185
+ "coral": "#ff7f50",
186
+ "cornflowerblue": "#6495ed",
187
+ "cornsilk": "#fff8dc",
188
+ "crimson": "#dc143c",
189
+ "cyan": "#00ffff",
190
+ "darkblue": "#00008b",
191
+ "darkcyan": "#008b8b",
192
+ "darkgoldenrod": "#b8860b",
193
+ "darkgray": "#a9a9a9",
194
+ "darkgrey": "#a9a9a9",
195
+ "darkgreen": "#006400",
196
+ "darkkhaki": "#bdb76b",
197
+ "darkmagenta": "#8b008b",
198
+ "darkolivegreen": "#556b2f",
199
+ "darkorange": "#ff8c00",
200
+ "darkorchid": "#9932cc",
201
+ "darkred": "#8b0000",
202
+ "darksalmon": "#e9967a",
203
+ "darkseagreen": "#8fbc8f",
204
+ "darkslateblue": "#483d8b",
205
+ "darkslategray": "#2f4f4f",
206
+ "darkslategrey": "#2f4f4f",
207
+ "darkturquoise": "#00ced1",
208
+ "darkviolet": "#9400d3",
209
+ "deeppink": "#ff1493",
210
+ "deepskyblue": "#00bfff",
211
+ "dimgray": "#696969",
212
+ "dimgrey": "#696969",
213
+ "dodgerblue": "#1e90ff",
214
+ "firebrick": "#b22222",
215
+ "floralwhite": "#fffaf0",
216
+ "forestgreen": "#228b22",
217
+ "fuchsia": "#ff00ff",
218
+ "gainsboro": "#dcdcdc",
219
+ "ghostwhite": "#f8f8ff",
220
+ "gold": "#ffd700",
221
+ "goldenrod": "#daa520",
222
+ "gray": "#808080",
223
+ "grey": "#808080",
224
+ "green": "#008000",
225
+ "greenyellow": "#adff2f",
226
+ "honeydew": "#f0fff0",
227
+ "hotpink": "#ff69b4",
228
+ "indianred": "#cd5c5c",
229
+ "indigo": "#4b0082",
230
+ "ivory": "#fffff0",
231
+ "khaki": "#f0e68c",
232
+ "lavender": "#e6e6fa",
233
+ "lavenderblush": "#fff0f5",
234
+ "lawngreen": "#7cfc00",
235
+ "lemonchiffon": "#fffacd",
236
+ "lightblue": "#add8e6",
237
+ "lightcoral": "#f08080",
238
+ "lightcyan": "#e0ffff",
239
+ "lightgoldenrodyellow": "#fafad2",
240
+ "lightgreen": "#90ee90",
241
+ "lightgray": "#d3d3d3",
242
+ "lightgrey": "#d3d3d3",
243
+ "lightpink": "#ffb6c1",
244
+ "lightsalmon": "#ffa07a",
245
+ "lightseagreen": "#20b2aa",
246
+ "lightskyblue": "#87cefa",
247
+ "lightslategray": "#778899",
248
+ "lightslategrey": "#778899",
249
+ "lightsteelblue": "#b0c4de",
250
+ "lightyellow": "#ffffe0",
251
+ "lime": "#00ff00",
252
+ "limegreen": "#32cd32",
253
+ "linen": "#faf0e6",
254
+ "magenta": "#ff00ff",
255
+ "maroon": "#800000",
256
+ "mediumaquamarine": "#66cdaa",
257
+ "mediumblue": "#0000cd",
258
+ "mediumorchid": "#ba55d3",
259
+ "mediumpurple": "#9370db",
260
+ "mediumseagreen": "#3cb371",
261
+ "mediumslateblue": "#7b68ee",
262
+ "mediumspringgreen": "#00fa9a",
263
+ "mediumturquoise": "#48d1cc",
264
+ "mediumvioletred": "#c71585",
265
+ "midnightblue": "#191970",
266
+ "mintcream": "#f5fffa",
267
+ "mistyrose": "#ffe4e1",
268
+ "moccasin": "#ffe4b5",
269
+ "navajowhite": "#ffdead",
270
+ "navy": "#000080",
271
+ "oldlace": "#fdf5e6",
272
+ "olive": "#808000",
273
+ "olivedrab": "#6b8e23",
274
+ "orange": "#ffa500",
275
+ "orangered": "#ff4500",
276
+ "orchid": "#da70d6",
277
+ "palegoldenrod": "#eee8aa",
278
+ "palegreen": "#98fb98",
279
+ "paleturquoise": "#afeeee",
280
+ "palevioletred": "#db7093",
281
+ "papayawhip": "#ffefd5",
282
+ "peachpuff": "#ffdab9",
283
+ "peru": "#cd853f",
284
+ "pink": "#ffc0cb",
285
+ "plum": "#dda0dd",
286
+ "powderblue": "#b0e0e6",
287
+ "purple": "#800080",
288
+ "rebeccapurple": "#663399",
289
+ "red": "#ff0000",
290
+ "rosybrown": "#bc8f8f",
291
+ "royalblue": "#4169e1",
292
+ "saddlebrown": "#8b4513",
293
+ "salmon": "#fa8072",
294
+ "sandybrown": "#f4a460",
295
+ "seagreen": "#2e8b57",
296
+ "seashell": "#fff5ee",
297
+ "sienna": "#a0522d",
298
+ "silver": "#c0c0c0",
299
+ "skyblue": "#87ceeb",
300
+ "slateblue": "#6a5acd",
301
+ "slategray": "#708090",
302
+ "slategrey": "#708090",
303
+ "snow": "#fffafa",
304
+ "springgreen": "#00ff7f",
305
+ "steelblue": "#4682b4",
306
+ "tan": "#d2b48c",
307
+ "teal": "#008080",
308
+ "thistle": "#d8bfd8",
309
+ "tomato": "#ff6347",
310
+ "turquoise": "#40e0d0",
311
+ "violet": "#ee82ee",
312
+ "wheat": "#f5deb3",
313
+ "white": "#ffffff",
314
+ "whitesmoke": "#f5f5f5",
315
+ "yellow": "#ffff00",
316
+ "yellowgreen": "#9acd32",
317
+ }
.venv/Lib/site-packages/PIL/ImageDraw.py ADDED
@@ -0,0 +1,1065 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # drawing interface operations
6
+ #
7
+ # History:
8
+ # 1996-04-13 fl Created (experimental)
9
+ # 1996-08-07 fl Filled polygons, ellipses.
10
+ # 1996-08-13 fl Added text support
11
+ # 1998-06-28 fl Handle I and F images
12
+ # 1998-12-29 fl Added arc; use arc primitive to draw ellipses
13
+ # 1999-01-10 fl Added shape stuff (experimental)
14
+ # 1999-02-06 fl Added bitmap support
15
+ # 1999-02-11 fl Changed all primitives to take options
16
+ # 1999-02-20 fl Fixed backwards compatibility
17
+ # 2000-10-12 fl Copy on write, when necessary
18
+ # 2001-02-18 fl Use default ink for bitmap/text also in fill mode
19
+ # 2002-10-24 fl Added support for CSS-style color strings
20
+ # 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing
21
+ # 2002-12-11 fl Refactored low-level drawing API (work in progress)
22
+ # 2004-08-26 fl Made Draw() a factory function, added getdraw() support
23
+ # 2004-09-04 fl Added width support to line primitive
24
+ # 2004-09-10 fl Added font mode handling
25
+ # 2006-06-19 fl Added font bearing support (getmask2)
26
+ #
27
+ # Copyright (c) 1997-2006 by Secret Labs AB
28
+ # Copyright (c) 1996-2006 by Fredrik Lundh
29
+ #
30
+ # See the README file for information on usage and redistribution.
31
+ #
32
+ from __future__ import annotations
33
+
34
+ import math
35
+ import numbers
36
+ import struct
37
+
38
+ from . import Image, ImageColor
39
+
40
+ """
41
+ A simple 2D drawing interface for PIL images.
42
+ <p>
43
+ Application code should use the <b>Draw</b> factory, instead of
44
+ directly.
45
+ """
46
+
47
+
48
+ class ImageDraw:
49
+ font = None
50
+
51
+ def __init__(self, im, mode=None):
52
+ """
53
+ Create a drawing instance.
54
+
55
+ :param im: The image to draw in.
56
+ :param mode: Optional mode to use for color values. For RGB
57
+ images, this argument can be RGB or RGBA (to blend the
58
+ drawing into the image). For all other modes, this argument
59
+ must be the same as the image mode. If omitted, the mode
60
+ defaults to the mode of the image.
61
+ """
62
+ im.load()
63
+ if im.readonly:
64
+ im._copy() # make it writeable
65
+ blend = 0
66
+ if mode is None:
67
+ mode = im.mode
68
+ if mode != im.mode:
69
+ if mode == "RGBA" and im.mode == "RGB":
70
+ blend = 1
71
+ else:
72
+ msg = "mode mismatch"
73
+ raise ValueError(msg)
74
+ if mode == "P":
75
+ self.palette = im.palette
76
+ else:
77
+ self.palette = None
78
+ self._image = im
79
+ self.im = im.im
80
+ self.draw = Image.core.draw(self.im, blend)
81
+ self.mode = mode
82
+ if mode in ("I", "F"):
83
+ self.ink = self.draw.draw_ink(1)
84
+ else:
85
+ self.ink = self.draw.draw_ink(-1)
86
+ if mode in ("1", "P", "I", "F"):
87
+ # FIXME: fix Fill2 to properly support matte for I+F images
88
+ self.fontmode = "1"
89
+ else:
90
+ self.fontmode = "L" # aliasing is okay for other modes
91
+ self.fill = False
92
+
93
+ def getfont(self):
94
+ """
95
+ Get the current default font.
96
+
97
+ To set the default font for this ImageDraw instance::
98
+
99
+ from PIL import ImageDraw, ImageFont
100
+ draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
101
+
102
+ To set the default font for all future ImageDraw instances::
103
+
104
+ from PIL import ImageDraw, ImageFont
105
+ ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
106
+
107
+ If the current default font is ``None``,
108
+ it is initialized with ``ImageFont.load_default()``.
109
+
110
+ :returns: An image font."""
111
+ if not self.font:
112
+ # FIXME: should add a font repository
113
+ from . import ImageFont
114
+
115
+ self.font = ImageFont.load_default()
116
+ return self.font
117
+
118
+ def _getfont(self, font_size):
119
+ if font_size is not None:
120
+ from . import ImageFont
121
+
122
+ font = ImageFont.load_default(font_size)
123
+ else:
124
+ font = self.getfont()
125
+ return font
126
+
127
+ def _getink(self, ink, fill=None):
128
+ if ink is None and fill is None:
129
+ if self.fill:
130
+ fill = self.ink
131
+ else:
132
+ ink = self.ink
133
+ else:
134
+ if ink is not None:
135
+ if isinstance(ink, str):
136
+ ink = ImageColor.getcolor(ink, self.mode)
137
+ if self.palette and not isinstance(ink, numbers.Number):
138
+ ink = self.palette.getcolor(ink, self._image)
139
+ ink = self.draw.draw_ink(ink)
140
+ if fill is not None:
141
+ if isinstance(fill, str):
142
+ fill = ImageColor.getcolor(fill, self.mode)
143
+ if self.palette and not isinstance(fill, numbers.Number):
144
+ fill = self.palette.getcolor(fill, self._image)
145
+ fill = self.draw.draw_ink(fill)
146
+ return ink, fill
147
+
148
+ def arc(self, xy, start, end, fill=None, width=1):
149
+ """Draw an arc."""
150
+ ink, fill = self._getink(fill)
151
+ if ink is not None:
152
+ self.draw.draw_arc(xy, start, end, ink, width)
153
+
154
+ def bitmap(self, xy, bitmap, fill=None):
155
+ """Draw a bitmap."""
156
+ bitmap.load()
157
+ ink, fill = self._getink(fill)
158
+ if ink is None:
159
+ ink = fill
160
+ if ink is not None:
161
+ self.draw.draw_bitmap(xy, bitmap.im, ink)
162
+
163
+ def chord(self, xy, start, end, fill=None, outline=None, width=1):
164
+ """Draw a chord."""
165
+ ink, fill = self._getink(outline, fill)
166
+ if fill is not None:
167
+ self.draw.draw_chord(xy, start, end, fill, 1)
168
+ if ink is not None and ink != fill and width != 0:
169
+ self.draw.draw_chord(xy, start, end, ink, 0, width)
170
+
171
+ def ellipse(self, xy, fill=None, outline=None, width=1):
172
+ """Draw an ellipse."""
173
+ ink, fill = self._getink(outline, fill)
174
+ if fill is not None:
175
+ self.draw.draw_ellipse(xy, fill, 1)
176
+ if ink is not None and ink != fill and width != 0:
177
+ self.draw.draw_ellipse(xy, ink, 0, width)
178
+
179
+ def line(self, xy, fill=None, width=0, joint=None):
180
+ """Draw a line, or a connected sequence of line segments."""
181
+ ink = self._getink(fill)[0]
182
+ if ink is not None:
183
+ self.draw.draw_lines(xy, ink, width)
184
+ if joint == "curve" and width > 4:
185
+ if not isinstance(xy[0], (list, tuple)):
186
+ xy = [tuple(xy[i : i + 2]) for i in range(0, len(xy), 2)]
187
+ for i in range(1, len(xy) - 1):
188
+ point = xy[i]
189
+ angles = [
190
+ math.degrees(math.atan2(end[0] - start[0], start[1] - end[1]))
191
+ % 360
192
+ for start, end in ((xy[i - 1], point), (point, xy[i + 1]))
193
+ ]
194
+ if angles[0] == angles[1]:
195
+ # This is a straight line, so no joint is required
196
+ continue
197
+
198
+ def coord_at_angle(coord, angle):
199
+ x, y = coord
200
+ angle -= 90
201
+ distance = width / 2 - 1
202
+ return tuple(
203
+ p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
204
+ for p, p_d in (
205
+ (x, distance * math.cos(math.radians(angle))),
206
+ (y, distance * math.sin(math.radians(angle))),
207
+ )
208
+ )
209
+
210
+ flipped = (
211
+ angles[1] > angles[0] and angles[1] - 180 > angles[0]
212
+ ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0])
213
+ coords = [
214
+ (point[0] - width / 2 + 1, point[1] - width / 2 + 1),
215
+ (point[0] + width / 2 - 1, point[1] + width / 2 - 1),
216
+ ]
217
+ if flipped:
218
+ start, end = (angles[1] + 90, angles[0] + 90)
219
+ else:
220
+ start, end = (angles[0] - 90, angles[1] - 90)
221
+ self.pieslice(coords, start - 90, end - 90, fill)
222
+
223
+ if width > 8:
224
+ # Cover potential gaps between the line and the joint
225
+ if flipped:
226
+ gap_coords = [
227
+ coord_at_angle(point, angles[0] + 90),
228
+ point,
229
+ coord_at_angle(point, angles[1] + 90),
230
+ ]
231
+ else:
232
+ gap_coords = [
233
+ coord_at_angle(point, angles[0] - 90),
234
+ point,
235
+ coord_at_angle(point, angles[1] - 90),
236
+ ]
237
+ self.line(gap_coords, fill, width=3)
238
+
239
+ def shape(self, shape, fill=None, outline=None):
240
+ """(Experimental) Draw a shape."""
241
+ shape.close()
242
+ ink, fill = self._getink(outline, fill)
243
+ if fill is not None:
244
+ self.draw.draw_outline(shape, fill, 1)
245
+ if ink is not None and ink != fill:
246
+ self.draw.draw_outline(shape, ink, 0)
247
+
248
+ def pieslice(self, xy, start, end, fill=None, outline=None, width=1):
249
+ """Draw a pieslice."""
250
+ ink, fill = self._getink(outline, fill)
251
+ if fill is not None:
252
+ self.draw.draw_pieslice(xy, start, end, fill, 1)
253
+ if ink is not None and ink != fill and width != 0:
254
+ self.draw.draw_pieslice(xy, start, end, ink, 0, width)
255
+
256
+ def point(self, xy, fill=None):
257
+ """Draw one or more individual pixels."""
258
+ ink, fill = self._getink(fill)
259
+ if ink is not None:
260
+ self.draw.draw_points(xy, ink)
261
+
262
+ def polygon(self, xy, fill=None, outline=None, width=1):
263
+ """Draw a polygon."""
264
+ ink, fill = self._getink(outline, fill)
265
+ if fill is not None:
266
+ self.draw.draw_polygon(xy, fill, 1)
267
+ if ink is not None and ink != fill and width != 0:
268
+ if width == 1:
269
+ self.draw.draw_polygon(xy, ink, 0, width)
270
+ else:
271
+ # To avoid expanding the polygon outwards,
272
+ # use the fill as a mask
273
+ mask = Image.new("1", self.im.size)
274
+ mask_ink = self._getink(1)[0]
275
+
276
+ fill_im = mask.copy()
277
+ draw = Draw(fill_im)
278
+ draw.draw.draw_polygon(xy, mask_ink, 1)
279
+
280
+ ink_im = mask.copy()
281
+ draw = Draw(ink_im)
282
+ width = width * 2 - 1
283
+ draw.draw.draw_polygon(xy, mask_ink, 0, width)
284
+
285
+ mask.paste(ink_im, mask=fill_im)
286
+
287
+ im = Image.new(self.mode, self.im.size)
288
+ draw = Draw(im)
289
+ draw.draw.draw_polygon(xy, ink, 0, width)
290
+ self.im.paste(im.im, (0, 0) + im.size, mask.im)
291
+
292
+ def regular_polygon(
293
+ self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1
294
+ ):
295
+ """Draw a regular polygon."""
296
+ xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
297
+ self.polygon(xy, fill, outline, width)
298
+
299
+ def rectangle(self, xy, fill=None, outline=None, width=1):
300
+ """Draw a rectangle."""
301
+ ink, fill = self._getink(outline, fill)
302
+ if fill is not None:
303
+ self.draw.draw_rectangle(xy, fill, 1)
304
+ if ink is not None and ink != fill and width != 0:
305
+ self.draw.draw_rectangle(xy, ink, 0, width)
306
+
307
+ def rounded_rectangle(
308
+ self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None
309
+ ):
310
+ """Draw a rounded rectangle."""
311
+ if isinstance(xy[0], (list, tuple)):
312
+ (x0, y0), (x1, y1) = xy
313
+ else:
314
+ x0, y0, x1, y1 = xy
315
+ if x1 < x0:
316
+ msg = "x1 must be greater than or equal to x0"
317
+ raise ValueError(msg)
318
+ if y1 < y0:
319
+ msg = "y1 must be greater than or equal to y0"
320
+ raise ValueError(msg)
321
+ if corners is None:
322
+ corners = (True, True, True, True)
323
+
324
+ d = radius * 2
325
+
326
+ full_x, full_y = False, False
327
+ if all(corners):
328
+ full_x = d >= x1 - x0 - 1
329
+ if full_x:
330
+ # The two left and two right corners are joined
331
+ d = x1 - x0
332
+ full_y = d >= y1 - y0 - 1
333
+ if full_y:
334
+ # The two top and two bottom corners are joined
335
+ d = y1 - y0
336
+ if full_x and full_y:
337
+ # If all corners are joined, that is a circle
338
+ return self.ellipse(xy, fill, outline, width)
339
+
340
+ if d == 0 or not any(corners):
341
+ # If the corners have no curve,
342
+ # or there are no corners,
343
+ # that is a rectangle
344
+ return self.rectangle(xy, fill, outline, width)
345
+
346
+ r = d // 2
347
+ ink, fill = self._getink(outline, fill)
348
+
349
+ def draw_corners(pieslice):
350
+ if full_x:
351
+ # Draw top and bottom halves
352
+ parts = (
353
+ ((x0, y0, x0 + d, y0 + d), 180, 360),
354
+ ((x0, y1 - d, x0 + d, y1), 0, 180),
355
+ )
356
+ elif full_y:
357
+ # Draw left and right halves
358
+ parts = (
359
+ ((x0, y0, x0 + d, y0 + d), 90, 270),
360
+ ((x1 - d, y0, x1, y0 + d), 270, 90),
361
+ )
362
+ else:
363
+ # Draw four separate corners
364
+ parts = []
365
+ for i, part in enumerate(
366
+ (
367
+ ((x0, y0, x0 + d, y0 + d), 180, 270),
368
+ ((x1 - d, y0, x1, y0 + d), 270, 360),
369
+ ((x1 - d, y1 - d, x1, y1), 0, 90),
370
+ ((x0, y1 - d, x0 + d, y1), 90, 180),
371
+ )
372
+ ):
373
+ if corners[i]:
374
+ parts.append(part)
375
+ for part in parts:
376
+ if pieslice:
377
+ self.draw.draw_pieslice(*(part + (fill, 1)))
378
+ else:
379
+ self.draw.draw_arc(*(part + (ink, width)))
380
+
381
+ if fill is not None:
382
+ draw_corners(True)
383
+
384
+ if full_x:
385
+ self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1)
386
+ else:
387
+ self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
388
+ if not full_x and not full_y:
389
+ left = [x0, y0, x0 + r, y1]
390
+ if corners[0]:
391
+ left[1] += r + 1
392
+ if corners[3]:
393
+ left[3] -= r + 1
394
+ self.draw.draw_rectangle(left, fill, 1)
395
+
396
+ right = [x1 - r, y0, x1, y1]
397
+ if corners[1]:
398
+ right[1] += r + 1
399
+ if corners[2]:
400
+ right[3] -= r + 1
401
+ self.draw.draw_rectangle(right, fill, 1)
402
+ if ink is not None and ink != fill and width != 0:
403
+ draw_corners(False)
404
+
405
+ if not full_x:
406
+ top = [x0, y0, x1, y0 + width - 1]
407
+ if corners[0]:
408
+ top[0] += r + 1
409
+ if corners[1]:
410
+ top[2] -= r + 1
411
+ self.draw.draw_rectangle(top, ink, 1)
412
+
413
+ bottom = [x0, y1 - width + 1, x1, y1]
414
+ if corners[3]:
415
+ bottom[0] += r + 1
416
+ if corners[2]:
417
+ bottom[2] -= r + 1
418
+ self.draw.draw_rectangle(bottom, ink, 1)
419
+ if not full_y:
420
+ left = [x0, y0, x0 + width - 1, y1]
421
+ if corners[0]:
422
+ left[1] += r + 1
423
+ if corners[3]:
424
+ left[3] -= r + 1
425
+ self.draw.draw_rectangle(left, ink, 1)
426
+
427
+ right = [x1 - width + 1, y0, x1, y1]
428
+ if corners[1]:
429
+ right[1] += r + 1
430
+ if corners[2]:
431
+ right[3] -= r + 1
432
+ self.draw.draw_rectangle(right, ink, 1)
433
+
434
+ def _multiline_check(self, text):
435
+ split_character = "\n" if isinstance(text, str) else b"\n"
436
+
437
+ return split_character in text
438
+
439
+ def _multiline_split(self, text):
440
+ split_character = "\n" if isinstance(text, str) else b"\n"
441
+
442
+ return text.split(split_character)
443
+
444
+ def _multiline_spacing(self, font, spacing, stroke_width):
445
+ return (
446
+ self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
447
+ + stroke_width
448
+ + spacing
449
+ )
450
+
451
+ def text(
452
+ self,
453
+ xy,
454
+ text,
455
+ fill=None,
456
+ font=None,
457
+ anchor=None,
458
+ spacing=4,
459
+ align="left",
460
+ direction=None,
461
+ features=None,
462
+ language=None,
463
+ stroke_width=0,
464
+ stroke_fill=None,
465
+ embedded_color=False,
466
+ *args,
467
+ **kwargs,
468
+ ):
469
+ """Draw text."""
470
+ if embedded_color and self.mode not in ("RGB", "RGBA"):
471
+ msg = "Embedded color supported only in RGB and RGBA modes"
472
+ raise ValueError(msg)
473
+
474
+ if font is None:
475
+ font = self._getfont(kwargs.get("font_size"))
476
+
477
+ if self._multiline_check(text):
478
+ return self.multiline_text(
479
+ xy,
480
+ text,
481
+ fill,
482
+ font,
483
+ anchor,
484
+ spacing,
485
+ align,
486
+ direction,
487
+ features,
488
+ language,
489
+ stroke_width,
490
+ stroke_fill,
491
+ embedded_color,
492
+ )
493
+
494
+ def getink(fill):
495
+ ink, fill = self._getink(fill)
496
+ if ink is None:
497
+ return fill
498
+ return ink
499
+
500
+ def draw_text(ink, stroke_width=0, stroke_offset=None):
501
+ mode = self.fontmode
502
+ if stroke_width == 0 and embedded_color:
503
+ mode = "RGBA"
504
+ coord = []
505
+ start = []
506
+ for i in range(2):
507
+ coord.append(int(xy[i]))
508
+ start.append(math.modf(xy[i])[0])
509
+ try:
510
+ mask, offset = font.getmask2(
511
+ text,
512
+ mode,
513
+ direction=direction,
514
+ features=features,
515
+ language=language,
516
+ stroke_width=stroke_width,
517
+ anchor=anchor,
518
+ ink=ink,
519
+ start=start,
520
+ *args,
521
+ **kwargs,
522
+ )
523
+ coord = coord[0] + offset[0], coord[1] + offset[1]
524
+ except AttributeError:
525
+ try:
526
+ mask = font.getmask(
527
+ text,
528
+ mode,
529
+ direction,
530
+ features,
531
+ language,
532
+ stroke_width,
533
+ anchor,
534
+ ink,
535
+ start=start,
536
+ *args,
537
+ **kwargs,
538
+ )
539
+ except TypeError:
540
+ mask = font.getmask(text)
541
+ if stroke_offset:
542
+ coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
543
+ if mode == "RGBA":
544
+ # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
545
+ # extract mask and set text alpha
546
+ color, mask = mask, mask.getband(3)
547
+ ink_alpha = struct.pack("i", ink)[3]
548
+ color.fillband(3, ink_alpha)
549
+ x, y = coord
550
+ self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
551
+ else:
552
+ self.draw.draw_bitmap(coord, mask, ink)
553
+
554
+ ink = getink(fill)
555
+ if ink is not None:
556
+ stroke_ink = None
557
+ if stroke_width:
558
+ stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink
559
+
560
+ if stroke_ink is not None:
561
+ # Draw stroked text
562
+ draw_text(stroke_ink, stroke_width)
563
+
564
+ # Draw normal text
565
+ draw_text(ink, 0)
566
+ else:
567
+ # Only draw normal text
568
+ draw_text(ink)
569
+
570
+ def multiline_text(
571
+ self,
572
+ xy,
573
+ text,
574
+ fill=None,
575
+ font=None,
576
+ anchor=None,
577
+ spacing=4,
578
+ align="left",
579
+ direction=None,
580
+ features=None,
581
+ language=None,
582
+ stroke_width=0,
583
+ stroke_fill=None,
584
+ embedded_color=False,
585
+ *,
586
+ font_size=None,
587
+ ):
588
+ if direction == "ttb":
589
+ msg = "ttb direction is unsupported for multiline text"
590
+ raise ValueError(msg)
591
+
592
+ if anchor is None:
593
+ anchor = "la"
594
+ elif len(anchor) != 2:
595
+ msg = "anchor must be a 2 character string"
596
+ raise ValueError(msg)
597
+ elif anchor[1] in "tb":
598
+ msg = "anchor not supported for multiline text"
599
+ raise ValueError(msg)
600
+
601
+ if font is None:
602
+ font = self._getfont(font_size)
603
+
604
+ widths = []
605
+ max_width = 0
606
+ lines = self._multiline_split(text)
607
+ line_spacing = self._multiline_spacing(font, spacing, stroke_width)
608
+ for line in lines:
609
+ line_width = self.textlength(
610
+ line, font, direction=direction, features=features, language=language
611
+ )
612
+ widths.append(line_width)
613
+ max_width = max(max_width, line_width)
614
+
615
+ top = xy[1]
616
+ if anchor[1] == "m":
617
+ top -= (len(lines) - 1) * line_spacing / 2.0
618
+ elif anchor[1] == "d":
619
+ top -= (len(lines) - 1) * line_spacing
620
+
621
+ for idx, line in enumerate(lines):
622
+ left = xy[0]
623
+ width_difference = max_width - widths[idx]
624
+
625
+ # first align left by anchor
626
+ if anchor[0] == "m":
627
+ left -= width_difference / 2.0
628
+ elif anchor[0] == "r":
629
+ left -= width_difference
630
+
631
+ # then align by align parameter
632
+ if align == "left":
633
+ pass
634
+ elif align == "center":
635
+ left += width_difference / 2.0
636
+ elif align == "right":
637
+ left += width_difference
638
+ else:
639
+ msg = 'align must be "left", "center" or "right"'
640
+ raise ValueError(msg)
641
+
642
+ self.text(
643
+ (left, top),
644
+ line,
645
+ fill,
646
+ font,
647
+ anchor,
648
+ direction=direction,
649
+ features=features,
650
+ language=language,
651
+ stroke_width=stroke_width,
652
+ stroke_fill=stroke_fill,
653
+ embedded_color=embedded_color,
654
+ )
655
+ top += line_spacing
656
+
657
+ def textlength(
658
+ self,
659
+ text,
660
+ font=None,
661
+ direction=None,
662
+ features=None,
663
+ language=None,
664
+ embedded_color=False,
665
+ *,
666
+ font_size=None,
667
+ ):
668
+ """Get the length of a given string, in pixels with 1/64 precision."""
669
+ if self._multiline_check(text):
670
+ msg = "can't measure length of multiline text"
671
+ raise ValueError(msg)
672
+ if embedded_color and self.mode not in ("RGB", "RGBA"):
673
+ msg = "Embedded color supported only in RGB and RGBA modes"
674
+ raise ValueError(msg)
675
+
676
+ if font is None:
677
+ font = self._getfont(font_size)
678
+ mode = "RGBA" if embedded_color else self.fontmode
679
+ return font.getlength(text, mode, direction, features, language)
680
+
681
+ def textbbox(
682
+ self,
683
+ xy,
684
+ text,
685
+ font=None,
686
+ anchor=None,
687
+ spacing=4,
688
+ align="left",
689
+ direction=None,
690
+ features=None,
691
+ language=None,
692
+ stroke_width=0,
693
+ embedded_color=False,
694
+ *,
695
+ font_size=None,
696
+ ):
697
+ """Get the bounding box of a given string, in pixels."""
698
+ if embedded_color and self.mode not in ("RGB", "RGBA"):
699
+ msg = "Embedded color supported only in RGB and RGBA modes"
700
+ raise ValueError(msg)
701
+
702
+ if font is None:
703
+ font = self._getfont(font_size)
704
+
705
+ if self._multiline_check(text):
706
+ return self.multiline_textbbox(
707
+ xy,
708
+ text,
709
+ font,
710
+ anchor,
711
+ spacing,
712
+ align,
713
+ direction,
714
+ features,
715
+ language,
716
+ stroke_width,
717
+ embedded_color,
718
+ )
719
+
720
+ mode = "RGBA" if embedded_color else self.fontmode
721
+ bbox = font.getbbox(
722
+ text, mode, direction, features, language, stroke_width, anchor
723
+ )
724
+ return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]
725
+
726
+ def multiline_textbbox(
727
+ self,
728
+ xy,
729
+ text,
730
+ font=None,
731
+ anchor=None,
732
+ spacing=4,
733
+ align="left",
734
+ direction=None,
735
+ features=None,
736
+ language=None,
737
+ stroke_width=0,
738
+ embedded_color=False,
739
+ *,
740
+ font_size=None,
741
+ ):
742
+ if direction == "ttb":
743
+ msg = "ttb direction is unsupported for multiline text"
744
+ raise ValueError(msg)
745
+
746
+ if anchor is None:
747
+ anchor = "la"
748
+ elif len(anchor) != 2:
749
+ msg = "anchor must be a 2 character string"
750
+ raise ValueError(msg)
751
+ elif anchor[1] in "tb":
752
+ msg = "anchor not supported for multiline text"
753
+ raise ValueError(msg)
754
+
755
+ if font is None:
756
+ font = self._getfont(font_size)
757
+
758
+ widths = []
759
+ max_width = 0
760
+ lines = self._multiline_split(text)
761
+ line_spacing = self._multiline_spacing(font, spacing, stroke_width)
762
+ for line in lines:
763
+ line_width = self.textlength(
764
+ line,
765
+ font,
766
+ direction=direction,
767
+ features=features,
768
+ language=language,
769
+ embedded_color=embedded_color,
770
+ )
771
+ widths.append(line_width)
772
+ max_width = max(max_width, line_width)
773
+
774
+ top = xy[1]
775
+ if anchor[1] == "m":
776
+ top -= (len(lines) - 1) * line_spacing / 2.0
777
+ elif anchor[1] == "d":
778
+ top -= (len(lines) - 1) * line_spacing
779
+
780
+ bbox = None
781
+
782
+ for idx, line in enumerate(lines):
783
+ left = xy[0]
784
+ width_difference = max_width - widths[idx]
785
+
786
+ # first align left by anchor
787
+ if anchor[0] == "m":
788
+ left -= width_difference / 2.0
789
+ elif anchor[0] == "r":
790
+ left -= width_difference
791
+
792
+ # then align by align parameter
793
+ if align == "left":
794
+ pass
795
+ elif align == "center":
796
+ left += width_difference / 2.0
797
+ elif align == "right":
798
+ left += width_difference
799
+ else:
800
+ msg = 'align must be "left", "center" or "right"'
801
+ raise ValueError(msg)
802
+
803
+ bbox_line = self.textbbox(
804
+ (left, top),
805
+ line,
806
+ font,
807
+ anchor,
808
+ direction=direction,
809
+ features=features,
810
+ language=language,
811
+ stroke_width=stroke_width,
812
+ embedded_color=embedded_color,
813
+ )
814
+ if bbox is None:
815
+ bbox = bbox_line
816
+ else:
817
+ bbox = (
818
+ min(bbox[0], bbox_line[0]),
819
+ min(bbox[1], bbox_line[1]),
820
+ max(bbox[2], bbox_line[2]),
821
+ max(bbox[3], bbox_line[3]),
822
+ )
823
+
824
+ top += line_spacing
825
+
826
+ if bbox is None:
827
+ return xy[0], xy[1], xy[0], xy[1]
828
+ return bbox
829
+
830
+
831
+ def Draw(im, mode=None):
832
+ """
833
+ A simple 2D drawing interface for PIL images.
834
+
835
+ :param im: The image to draw in.
836
+ :param mode: Optional mode to use for color values. For RGB
837
+ images, this argument can be RGB or RGBA (to blend the
838
+ drawing into the image). For all other modes, this argument
839
+ must be the same as the image mode. If omitted, the mode
840
+ defaults to the mode of the image.
841
+ """
842
+ try:
843
+ return im.getdraw(mode)
844
+ except AttributeError:
845
+ return ImageDraw(im, mode)
846
+
847
+
848
+ # experimental access to the outline API
849
+ try:
850
+ Outline = Image.core.outline
851
+ except AttributeError:
852
+ Outline = None
853
+
854
+
855
+ def getdraw(im=None, hints=None):
856
+ """
857
+ (Experimental) A more advanced 2D drawing interface for PIL images,
858
+ based on the WCK interface.
859
+
860
+ :param im: The image to draw in.
861
+ :param hints: An optional list of hints.
862
+ :returns: A (drawing context, drawing resource factory) tuple.
863
+ """
864
+ # FIXME: this needs more work!
865
+ # FIXME: come up with a better 'hints' scheme.
866
+ handler = None
867
+ if not hints or "nicest" in hints:
868
+ try:
869
+ from . import _imagingagg as handler
870
+ except ImportError:
871
+ pass
872
+ if handler is None:
873
+ from . import ImageDraw2 as handler
874
+ if im:
875
+ im = handler.Draw(im)
876
+ return im, handler
877
+
878
+
879
+ def floodfill(image, xy, value, border=None, thresh=0):
880
+ """
881
+ (experimental) Fills a bounded region with a given color.
882
+
883
+ :param image: Target image.
884
+ :param xy: Seed position (a 2-item coordinate tuple). See
885
+ :ref:`coordinate-system`.
886
+ :param value: Fill color.
887
+ :param border: Optional border value. If given, the region consists of
888
+ pixels with a color different from the border color. If not given,
889
+ the region consists of pixels having the same color as the seed
890
+ pixel.
891
+ :param thresh: Optional threshold value which specifies a maximum
892
+ tolerable difference of a pixel value from the 'background' in
893
+ order for it to be replaced. Useful for filling regions of
894
+ non-homogeneous, but similar, colors.
895
+ """
896
+ # based on an implementation by Eric S. Raymond
897
+ # amended by yo1995 @20180806
898
+ pixel = image.load()
899
+ x, y = xy
900
+ try:
901
+ background = pixel[x, y]
902
+ if _color_diff(value, background) <= thresh:
903
+ return # seed point already has fill color
904
+ pixel[x, y] = value
905
+ except (ValueError, IndexError):
906
+ return # seed point outside image
907
+ edge = {(x, y)}
908
+ # use a set to keep record of current and previous edge pixels
909
+ # to reduce memory consumption
910
+ full_edge = set()
911
+ while edge:
912
+ new_edge = set()
913
+ for x, y in edge: # 4 adjacent method
914
+ for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
915
+ # If already processed, or if a coordinate is negative, skip
916
+ if (s, t) in full_edge or s < 0 or t < 0:
917
+ continue
918
+ try:
919
+ p = pixel[s, t]
920
+ except (ValueError, IndexError):
921
+ pass
922
+ else:
923
+ full_edge.add((s, t))
924
+ if border is None:
925
+ fill = _color_diff(p, background) <= thresh
926
+ else:
927
+ fill = p not in (value, border)
928
+ if fill:
929
+ pixel[s, t] = value
930
+ new_edge.add((s, t))
931
+ full_edge = edge # discard pixels processed
932
+ edge = new_edge
933
+
934
+
935
+ def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation):
936
+ """
937
+ Generate a list of vertices for a 2D regular polygon.
938
+
939
+ :param bounding_circle: The bounding circle is a tuple defined
940
+ by a point and radius. The polygon is inscribed in this circle.
941
+ (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
942
+ :param n_sides: Number of sides
943
+ (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)
944
+ :param rotation: Apply an arbitrary rotation to the polygon
945
+ (e.g. ``rotation=90``, applies a 90 degree rotation)
946
+ :return: List of regular polygon vertices
947
+ (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)
948
+
949
+ How are the vertices computed?
950
+ 1. Compute the following variables
951
+ - theta: Angle between the apothem & the nearest polygon vertex
952
+ - side_length: Length of each polygon edge
953
+ - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)
954
+ - polygon_radius: Polygon radius (last element of bounding_circle)
955
+ - angles: Location of each polygon vertex in polar grid
956
+ (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])
957
+
958
+ 2. For each angle in angles, get the polygon vertex at that angle
959
+ The vertex is computed using the equation below.
960
+ X= xcos(φ) + ysin(φ)
961
+ Y= −xsin(φ) + ycos(φ)
962
+
963
+ Note:
964
+ φ = angle in degrees
965
+ x = 0
966
+ y = polygon_radius
967
+
968
+ The formula above assumes rotation around the origin.
969
+ In our case, we are rotating around the centroid.
970
+ To account for this, we use the formula below
971
+ X = xcos(φ) + ysin(φ) + centroid_x
972
+ Y = −xsin(φ) + ycos(φ) + centroid_y
973
+ """
974
+ # 1. Error Handling
975
+ # 1.1 Check `n_sides` has an appropriate value
976
+ if not isinstance(n_sides, int):
977
+ msg = "n_sides should be an int"
978
+ raise TypeError(msg)
979
+ if n_sides < 3:
980
+ msg = "n_sides should be an int > 2"
981
+ raise ValueError(msg)
982
+
983
+ # 1.2 Check `bounding_circle` has an appropriate value
984
+ if not isinstance(bounding_circle, (list, tuple)):
985
+ msg = "bounding_circle should be a tuple"
986
+ raise TypeError(msg)
987
+
988
+ if len(bounding_circle) == 3:
989
+ *centroid, polygon_radius = bounding_circle
990
+ elif len(bounding_circle) == 2:
991
+ centroid, polygon_radius = bounding_circle
992
+ else:
993
+ msg = (
994
+ "bounding_circle should contain 2D coordinates "
995
+ "and a radius (e.g. (x, y, r) or ((x, y), r) )"
996
+ )
997
+ raise ValueError(msg)
998
+
999
+ if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):
1000
+ msg = "bounding_circle should only contain numeric data"
1001
+ raise ValueError(msg)
1002
+
1003
+ if not len(centroid) == 2:
1004
+ msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
1005
+ raise ValueError(msg)
1006
+
1007
+ if polygon_radius <= 0:
1008
+ msg = "bounding_circle radius should be > 0"
1009
+ raise ValueError(msg)
1010
+
1011
+ # 1.3 Check `rotation` has an appropriate value
1012
+ if not isinstance(rotation, (int, float)):
1013
+ msg = "rotation should be an int or float"
1014
+ raise ValueError(msg)
1015
+
1016
+ # 2. Define Helper Functions
1017
+ def _apply_rotation(point, degrees, centroid):
1018
+ return (
1019
+ round(
1020
+ point[0] * math.cos(math.radians(360 - degrees))
1021
+ - point[1] * math.sin(math.radians(360 - degrees))
1022
+ + centroid[0],
1023
+ 2,
1024
+ ),
1025
+ round(
1026
+ point[1] * math.cos(math.radians(360 - degrees))
1027
+ + point[0] * math.sin(math.radians(360 - degrees))
1028
+ + centroid[1],
1029
+ 2,
1030
+ ),
1031
+ )
1032
+
1033
+ def _compute_polygon_vertex(centroid, polygon_radius, angle):
1034
+ start_point = [polygon_radius, 0]
1035
+ return _apply_rotation(start_point, angle, centroid)
1036
+
1037
+ def _get_angles(n_sides, rotation):
1038
+ angles = []
1039
+ degrees = 360 / n_sides
1040
+ # Start with the bottom left polygon vertex
1041
+ current_angle = (270 - 0.5 * degrees) + rotation
1042
+ for _ in range(0, n_sides):
1043
+ angles.append(current_angle)
1044
+ current_angle += degrees
1045
+ if current_angle > 360:
1046
+ current_angle -= 360
1047
+ return angles
1048
+
1049
+ # 3. Variable Declarations
1050
+ angles = _get_angles(n_sides, rotation)
1051
+
1052
+ # 4. Compute Vertices
1053
+ return [
1054
+ _compute_polygon_vertex(centroid, polygon_radius, angle) for angle in angles
1055
+ ]
1056
+
1057
+
1058
+ def _color_diff(color1, color2):
1059
+ """
1060
+ Uses 1-norm distance to calculate difference between two values.
1061
+ """
1062
+ if isinstance(color2, tuple):
1063
+ return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
1064
+ else:
1065
+ return abs(color1 - color2)
.venv/Lib/site-packages/PIL/ImageDraw2.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library
3
+ # $Id$
4
+ #
5
+ # WCK-style drawing interface operations
6
+ #
7
+ # History:
8
+ # 2003-12-07 fl created
9
+ # 2005-05-15 fl updated; added to PIL as ImageDraw2
10
+ # 2005-05-15 fl added text support
11
+ # 2005-05-20 fl added arc/chord/pieslice support
12
+ #
13
+ # Copyright (c) 2003-2005 by Secret Labs AB
14
+ # Copyright (c) 2003-2005 by Fredrik Lundh
15
+ #
16
+ # See the README file for information on usage and redistribution.
17
+ #
18
+
19
+
20
+ """
21
+ (Experimental) WCK-style drawing interface operations
22
+
23
+ .. seealso:: :py:mod:`PIL.ImageDraw`
24
+ """
25
+ from __future__ import annotations
26
+
27
+ from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
28
+
29
+
30
+ class Pen:
31
+ """Stores an outline color and width."""
32
+
33
+ def __init__(self, color, width=1, opacity=255):
34
+ self.color = ImageColor.getrgb(color)
35
+ self.width = width
36
+
37
+
38
+ class Brush:
39
+ """Stores a fill color"""
40
+
41
+ def __init__(self, color, opacity=255):
42
+ self.color = ImageColor.getrgb(color)
43
+
44
+
45
+ class Font:
46
+ """Stores a TrueType font and color"""
47
+
48
+ def __init__(self, color, file, size=12):
49
+ # FIXME: add support for bitmap fonts
50
+ self.color = ImageColor.getrgb(color)
51
+ self.font = ImageFont.truetype(file, size)
52
+
53
+
54
+ class Draw:
55
+ """
56
+ (Experimental) WCK-style drawing interface
57
+ """
58
+
59
+ def __init__(self, image, size=None, color=None):
60
+ if not hasattr(image, "im"):
61
+ image = Image.new(image, size, color)
62
+ self.draw = ImageDraw.Draw(image)
63
+ self.image = image
64
+ self.transform = None
65
+
66
+ def flush(self):
67
+ return self.image
68
+
69
+ def render(self, op, xy, pen, brush=None):
70
+ # handle color arguments
71
+ outline = fill = None
72
+ width = 1
73
+ if isinstance(pen, Pen):
74
+ outline = pen.color
75
+ width = pen.width
76
+ elif isinstance(brush, Pen):
77
+ outline = brush.color
78
+ width = brush.width
79
+ if isinstance(brush, Brush):
80
+ fill = brush.color
81
+ elif isinstance(pen, Brush):
82
+ fill = pen.color
83
+ # handle transformation
84
+ if self.transform:
85
+ xy = ImagePath.Path(xy)
86
+ xy.transform(self.transform)
87
+ # render the item
88
+ if op == "line":
89
+ self.draw.line(xy, fill=outline, width=width)
90
+ else:
91
+ getattr(self.draw, op)(xy, fill=fill, outline=outline)
92
+
93
+ def settransform(self, offset):
94
+ """Sets a transformation offset."""
95
+ (xoffset, yoffset) = offset
96
+ self.transform = (1, 0, xoffset, 0, 1, yoffset)
97
+
98
+ def arc(self, xy, start, end, *options):
99
+ """
100
+ Draws an arc (a portion of a circle outline) between the start and end
101
+ angles, inside the given bounding box.
102
+
103
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc`
104
+ """
105
+ self.render("arc", xy, start, end, *options)
106
+
107
+ def chord(self, xy, start, end, *options):
108
+ """
109
+ Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
110
+ with a straight line.
111
+
112
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord`
113
+ """
114
+ self.render("chord", xy, start, end, *options)
115
+
116
+ def ellipse(self, xy, *options):
117
+ """
118
+ Draws an ellipse inside the given bounding box.
119
+
120
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
121
+ """
122
+ self.render("ellipse", xy, *options)
123
+
124
+ def line(self, xy, *options):
125
+ """
126
+ Draws a line between the coordinates in the ``xy`` list.
127
+
128
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
129
+ """
130
+ self.render("line", xy, *options)
131
+
132
+ def pieslice(self, xy, start, end, *options):
133
+ """
134
+ Same as arc, but also draws straight lines between the end points and the
135
+ center of the bounding box.
136
+
137
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice`
138
+ """
139
+ self.render("pieslice", xy, start, end, *options)
140
+
141
+ def polygon(self, xy, *options):
142
+ """
143
+ Draws a polygon.
144
+
145
+ The polygon outline consists of straight lines between the given
146
+ coordinates, plus a straight line between the last and the first
147
+ coordinate.
148
+
149
+
150
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon`
151
+ """
152
+ self.render("polygon", xy, *options)
153
+
154
+ def rectangle(self, xy, *options):
155
+ """
156
+ Draws a rectangle.
157
+
158
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
159
+ """
160
+ self.render("rectangle", xy, *options)
161
+
162
+ def text(self, xy, text, font):
163
+ """
164
+ Draws the string at the given position.
165
+
166
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
167
+ """
168
+ if self.transform:
169
+ xy = ImagePath.Path(xy)
170
+ xy.transform(self.transform)
171
+ self.draw.text(xy, text, font=font.font, fill=font.color)
172
+
173
+ def textbbox(self, xy, text, font):
174
+ """
175
+ Returns bounding box (in pixels) of given text.
176
+
177
+ :return: ``(left, top, right, bottom)`` bounding box
178
+
179
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
180
+ """
181
+ if self.transform:
182
+ xy = ImagePath.Path(xy)
183
+ xy.transform(self.transform)
184
+ return self.draw.textbbox(xy, text, font=font.font)
185
+
186
+ def textlength(self, text, font):
187
+ """
188
+ Returns length (in pixels) of given text.
189
+ This is the amount by which following text should be offset.
190
+
191
+ .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textlength`
192
+ """
193
+ return self.draw.textlength(text, font=font.font)
.venv/Lib/site-packages/PIL/ImageEnhance.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # image enhancement classes
6
+ #
7
+ # For a background, see "Image Processing By Interpolation and
8
+ # Extrapolation", Paul Haeberli and Douglas Voorhies. Available
9
+ # at http://www.graficaobscura.com/interp/index.html
10
+ #
11
+ # History:
12
+ # 1996-03-23 fl Created
13
+ # 2009-06-16 fl Fixed mean calculation
14
+ #
15
+ # Copyright (c) Secret Labs AB 1997.
16
+ # Copyright (c) Fredrik Lundh 1996.
17
+ #
18
+ # See the README file for information on usage and redistribution.
19
+ #
20
+ from __future__ import annotations
21
+
22
+ from . import Image, ImageFilter, ImageStat
23
+
24
+
25
+ class _Enhance:
26
+ def enhance(self, factor):
27
+ """
28
+ Returns an enhanced image.
29
+
30
+ :param factor: A floating point value controlling the enhancement.
31
+ Factor 1.0 always returns a copy of the original image,
32
+ lower factors mean less color (brightness, contrast,
33
+ etc), and higher values more. There are no restrictions
34
+ on this value.
35
+ :rtype: :py:class:`~PIL.Image.Image`
36
+ """
37
+ return Image.blend(self.degenerate, self.image, factor)
38
+
39
+
40
+ class Color(_Enhance):
41
+ """Adjust image color balance.
42
+
43
+ This class can be used to adjust the colour balance of an image, in
44
+ a manner similar to the controls on a colour TV set. An enhancement
45
+ factor of 0.0 gives a black and white image. A factor of 1.0 gives
46
+ the original image.
47
+ """
48
+
49
+ def __init__(self, image):
50
+ self.image = image
51
+ self.intermediate_mode = "L"
52
+ if "A" in image.getbands():
53
+ self.intermediate_mode = "LA"
54
+
55
+ self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
56
+
57
+
58
+ class Contrast(_Enhance):
59
+ """Adjust image contrast.
60
+
61
+ This class can be used to control the contrast of an image, similar
62
+ to the contrast control on a TV set. An enhancement factor of 0.0
63
+ gives a solid gray image. A factor of 1.0 gives the original image.
64
+ """
65
+
66
+ def __init__(self, image):
67
+ self.image = image
68
+ mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
69
+ self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
70
+
71
+ if "A" in image.getbands():
72
+ self.degenerate.putalpha(image.getchannel("A"))
73
+
74
+
75
+ class Brightness(_Enhance):
76
+ """Adjust image brightness.
77
+
78
+ This class can be used to control the brightness of an image. An
79
+ enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
80
+ original image.
81
+ """
82
+
83
+ def __init__(self, image):
84
+ self.image = image
85
+ self.degenerate = Image.new(image.mode, image.size, 0)
86
+
87
+ if "A" in image.getbands():
88
+ self.degenerate.putalpha(image.getchannel("A"))
89
+
90
+
91
+ class Sharpness(_Enhance):
92
+ """Adjust image sharpness.
93
+
94
+ This class can be used to adjust the sharpness of an image. An
95
+ enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the
96
+ original image, and a factor of 2.0 gives a sharpened image.
97
+ """
98
+
99
+ def __init__(self, image):
100
+ self.image = image
101
+ self.degenerate = image.filter(ImageFilter.SMOOTH)
102
+
103
+ if "A" in image.getbands():
104
+ self.degenerate.putalpha(image.getchannel("A"))
.venv/Lib/site-packages/PIL/ImageFile.py ADDED
@@ -0,0 +1,795 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # base class for image file handlers
6
+ #
7
+ # history:
8
+ # 1995-09-09 fl Created
9
+ # 1996-03-11 fl Fixed load mechanism.
10
+ # 1996-04-15 fl Added pcx/xbm decoders.
11
+ # 1996-04-30 fl Added encoders.
12
+ # 1996-12-14 fl Added load helpers
13
+ # 1997-01-11 fl Use encode_to_file where possible
14
+ # 1997-08-27 fl Flush output in _save
15
+ # 1998-03-05 fl Use memory mapping for some modes
16
+ # 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B"
17
+ # 1999-05-31 fl Added image parser
18
+ # 2000-10-12 fl Set readonly flag on memory-mapped images
19
+ # 2002-03-20 fl Use better messages for common decoder errors
20
+ # 2003-04-21 fl Fall back on mmap/map_buffer if map is not available
21
+ # 2003-10-30 fl Added StubImageFile class
22
+ # 2004-02-25 fl Made incremental parser more robust
23
+ #
24
+ # Copyright (c) 1997-2004 by Secret Labs AB
25
+ # Copyright (c) 1995-2004 by Fredrik Lundh
26
+ #
27
+ # See the README file for information on usage and redistribution.
28
+ #
29
+ from __future__ import annotations
30
+
31
+ import io
32
+ import itertools
33
+ import struct
34
+ import sys
35
+ from typing import Any, NamedTuple
36
+
37
+ from . import Image
38
+ from ._deprecate import deprecate
39
+ from ._util import is_path
40
+
41
+ MAXBLOCK = 65536
42
+
43
+ SAFEBLOCK = 1024 * 1024
44
+
45
+ LOAD_TRUNCATED_IMAGES = False
46
+ """Whether or not to load truncated image files. User code may change this."""
47
+
48
+ ERRORS = {
49
+ -1: "image buffer overrun error",
50
+ -2: "decoding error",
51
+ -3: "unknown error",
52
+ -8: "bad configuration",
53
+ -9: "out of memory error",
54
+ }
55
+ """
56
+ Dict of known error codes returned from :meth:`.PyDecoder.decode`,
57
+ :meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and
58
+ :meth:`.PyEncoder.encode_to_file`.
59
+ """
60
+
61
+
62
+ #
63
+ # --------------------------------------------------------------------
64
+ # Helpers
65
+
66
+
67
+ def _get_oserror(error, *, encoder):
68
+ try:
69
+ msg = Image.core.getcodecstatus(error)
70
+ except AttributeError:
71
+ msg = ERRORS.get(error)
72
+ if not msg:
73
+ msg = f"{'encoder' if encoder else 'decoder'} error {error}"
74
+ msg += f" when {'writing' if encoder else 'reading'} image file"
75
+ return OSError(msg)
76
+
77
+
78
+ def raise_oserror(error):
79
+ deprecate(
80
+ "raise_oserror",
81
+ 12,
82
+ action="It is only useful for translating error codes returned by a codec's "
83
+ "decode() method, which ImageFile already does automatically.",
84
+ )
85
+ raise _get_oserror(error, encoder=False)
86
+
87
+
88
+ def _tilesort(t):
89
+ # sort on offset
90
+ return t[2]
91
+
92
+
93
+ class _Tile(NamedTuple):
94
+ encoder_name: str
95
+ extents: tuple[int, int, int, int]
96
+ offset: int
97
+ args: tuple[Any, ...] | str | None
98
+
99
+
100
+ #
101
+ # --------------------------------------------------------------------
102
+ # ImageFile base class
103
+
104
+
105
+ class ImageFile(Image.Image):
106
+ """Base class for image file format handlers."""
107
+
108
+ def __init__(self, fp=None, filename=None):
109
+ super().__init__()
110
+
111
+ self._min_frame = 0
112
+
113
+ self.custom_mimetype = None
114
+
115
+ self.tile = None
116
+ """ A list of tile descriptors, or ``None`` """
117
+
118
+ self.readonly = 1 # until we know better
119
+
120
+ self.decoderconfig = ()
121
+ self.decodermaxblock = MAXBLOCK
122
+
123
+ if is_path(fp):
124
+ # filename
125
+ self.fp = open(fp, "rb")
126
+ self.filename = fp
127
+ self._exclusive_fp = True
128
+ else:
129
+ # stream
130
+ self.fp = fp
131
+ self.filename = filename
132
+ # can be overridden
133
+ self._exclusive_fp = None
134
+
135
+ try:
136
+ try:
137
+ self._open()
138
+ except (
139
+ IndexError, # end of data
140
+ TypeError, # end of data (ord)
141
+ KeyError, # unsupported mode
142
+ EOFError, # got header but not the first frame
143
+ struct.error,
144
+ ) as v:
145
+ raise SyntaxError(v) from v
146
+
147
+ if not self.mode or self.size[0] <= 0 or self.size[1] <= 0:
148
+ msg = "not identified by this driver"
149
+ raise SyntaxError(msg)
150
+ except BaseException:
151
+ # close the file only if we have opened it this constructor
152
+ if self._exclusive_fp:
153
+ self.fp.close()
154
+ raise
155
+
156
+ def get_format_mimetype(self):
157
+ if self.custom_mimetype:
158
+ return self.custom_mimetype
159
+ if self.format is not None:
160
+ return Image.MIME.get(self.format.upper())
161
+
162
+ def __setstate__(self, state):
163
+ self.tile = []
164
+ super().__setstate__(state)
165
+
166
+ def verify(self):
167
+ """Check file integrity"""
168
+
169
+ # raise exception if something's wrong. must be called
170
+ # directly after open, and closes file when finished.
171
+ if self._exclusive_fp:
172
+ self.fp.close()
173
+ self.fp = None
174
+
175
+ def load(self):
176
+ """Load image data based on tile list"""
177
+
178
+ if self.tile is None:
179
+ msg = "cannot load this image"
180
+ raise OSError(msg)
181
+
182
+ pixel = Image.Image.load(self)
183
+ if not self.tile:
184
+ return pixel
185
+
186
+ self.map = None
187
+ use_mmap = self.filename and len(self.tile) == 1
188
+ # As of pypy 2.1.0, memory mapping was failing here.
189
+ use_mmap = use_mmap and not hasattr(sys, "pypy_version_info")
190
+
191
+ readonly = 0
192
+
193
+ # look for read/seek overrides
194
+ try:
195
+ read = self.load_read
196
+ # don't use mmap if there are custom read/seek functions
197
+ use_mmap = False
198
+ except AttributeError:
199
+ read = self.fp.read
200
+
201
+ try:
202
+ seek = self.load_seek
203
+ use_mmap = False
204
+ except AttributeError:
205
+ seek = self.fp.seek
206
+
207
+ if use_mmap:
208
+ # try memory mapping
209
+ decoder_name, extents, offset, args = self.tile[0]
210
+ if isinstance(args, str):
211
+ args = (args, 0, 1)
212
+ if (
213
+ decoder_name == "raw"
214
+ and len(args) >= 3
215
+ and args[0] == self.mode
216
+ and args[0] in Image._MAPMODES
217
+ ):
218
+ try:
219
+ # use mmap, if possible
220
+ import mmap
221
+
222
+ with open(self.filename) as fp:
223
+ self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
224
+ if offset + self.size[1] * args[1] > self.map.size():
225
+ msg = "buffer is not large enough"
226
+ raise OSError(msg)
227
+ self.im = Image.core.map_buffer(
228
+ self.map, self.size, decoder_name, offset, args
229
+ )
230
+ readonly = 1
231
+ # After trashing self.im,
232
+ # we might need to reload the palette data.
233
+ if self.palette:
234
+ self.palette.dirty = 1
235
+ except (AttributeError, OSError, ImportError):
236
+ self.map = None
237
+
238
+ self.load_prepare()
239
+ err_code = -3 # initialize to unknown error
240
+ if not self.map:
241
+ # sort tiles in file order
242
+ self.tile.sort(key=_tilesort)
243
+
244
+ try:
245
+ # FIXME: This is a hack to handle TIFF's JpegTables tag.
246
+ prefix = self.tile_prefix
247
+ except AttributeError:
248
+ prefix = b""
249
+
250
+ # Remove consecutive duplicates that only differ by their offset
251
+ self.tile = [
252
+ list(tiles)[-1]
253
+ for _, tiles in itertools.groupby(
254
+ self.tile, lambda tile: (tile[0], tile[1], tile[3])
255
+ )
256
+ ]
257
+ for decoder_name, extents, offset, args in self.tile:
258
+ seek(offset)
259
+ decoder = Image._getdecoder(
260
+ self.mode, decoder_name, args, self.decoderconfig
261
+ )
262
+ try:
263
+ decoder.setimage(self.im, extents)
264
+ if decoder.pulls_fd:
265
+ decoder.setfd(self.fp)
266
+ err_code = decoder.decode(b"")[1]
267
+ else:
268
+ b = prefix
269
+ while True:
270
+ try:
271
+ s = read(self.decodermaxblock)
272
+ except (IndexError, struct.error) as e:
273
+ # truncated png/gif
274
+ if LOAD_TRUNCATED_IMAGES:
275
+ break
276
+ else:
277
+ msg = "image file is truncated"
278
+ raise OSError(msg) from e
279
+
280
+ if not s: # truncated jpeg
281
+ if LOAD_TRUNCATED_IMAGES:
282
+ break
283
+ else:
284
+ msg = (
285
+ "image file is truncated "
286
+ f"({len(b)} bytes not processed)"
287
+ )
288
+ raise OSError(msg)
289
+
290
+ b = b + s
291
+ n, err_code = decoder.decode(b)
292
+ if n < 0:
293
+ break
294
+ b = b[n:]
295
+ finally:
296
+ # Need to cleanup here to prevent leaks
297
+ decoder.cleanup()
298
+
299
+ self.tile = []
300
+ self.readonly = readonly
301
+
302
+ self.load_end()
303
+
304
+ if self._exclusive_fp and self._close_exclusive_fp_after_loading:
305
+ self.fp.close()
306
+ self.fp = None
307
+
308
+ if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
309
+ # still raised if decoder fails to return anything
310
+ raise _get_oserror(err_code, encoder=False)
311
+
312
+ return Image.Image.load(self)
313
+
314
+ def load_prepare(self):
315
+ # create image memory if necessary
316
+ if not self.im or self.im.mode != self.mode or self.im.size != self.size:
317
+ self.im = Image.core.new(self.mode, self.size)
318
+ # create palette (optional)
319
+ if self.mode == "P":
320
+ Image.Image.load(self)
321
+
322
+ def load_end(self):
323
+ # may be overridden
324
+ pass
325
+
326
+ # may be defined for contained formats
327
+ # def load_seek(self, pos):
328
+ # pass
329
+
330
+ # may be defined for blocked formats (e.g. PNG)
331
+ # def load_read(self, bytes):
332
+ # pass
333
+
334
+ def _seek_check(self, frame):
335
+ if (
336
+ frame < self._min_frame
337
+ # Only check upper limit on frames if additional seek operations
338
+ # are not required to do so
339
+ or (
340
+ not (hasattr(self, "_n_frames") and self._n_frames is None)
341
+ and frame >= self.n_frames + self._min_frame
342
+ )
343
+ ):
344
+ msg = "attempt to seek outside sequence"
345
+ raise EOFError(msg)
346
+
347
+ return self.tell() != frame
348
+
349
+
350
+ class StubImageFile(ImageFile):
351
+ """
352
+ Base class for stub image loaders.
353
+
354
+ A stub loader is an image loader that can identify files of a
355
+ certain format, but relies on external code to load the file.
356
+ """
357
+
358
+ def _open(self):
359
+ msg = "StubImageFile subclass must implement _open"
360
+ raise NotImplementedError(msg)
361
+
362
+ def load(self):
363
+ loader = self._load()
364
+ if loader is None:
365
+ msg = f"cannot find loader for this {self.format} file"
366
+ raise OSError(msg)
367
+ image = loader.load(self)
368
+ assert image is not None
369
+ # become the other object (!)
370
+ self.__class__ = image.__class__
371
+ self.__dict__ = image.__dict__
372
+ return image.load()
373
+
374
+ def _load(self):
375
+ """(Hook) Find actual image loader."""
376
+ msg = "StubImageFile subclass must implement _load"
377
+ raise NotImplementedError(msg)
378
+
379
+
380
+ class Parser:
381
+ """
382
+ Incremental image parser. This class implements the standard
383
+ feed/close consumer interface.
384
+ """
385
+
386
+ incremental = None
387
+ image = None
388
+ data = None
389
+ decoder = None
390
+ offset = 0
391
+ finished = 0
392
+
393
+ def reset(self):
394
+ """
395
+ (Consumer) Reset the parser. Note that you can only call this
396
+ method immediately after you've created a parser; parser
397
+ instances cannot be reused.
398
+ """
399
+ assert self.data is None, "cannot reuse parsers"
400
+
401
+ def feed(self, data):
402
+ """
403
+ (Consumer) Feed data to the parser.
404
+
405
+ :param data: A string buffer.
406
+ :exception OSError: If the parser failed to parse the image file.
407
+ """
408
+ # collect data
409
+
410
+ if self.finished:
411
+ return
412
+
413
+ if self.data is None:
414
+ self.data = data
415
+ else:
416
+ self.data = self.data + data
417
+
418
+ # parse what we have
419
+ if self.decoder:
420
+ if self.offset > 0:
421
+ # skip header
422
+ skip = min(len(self.data), self.offset)
423
+ self.data = self.data[skip:]
424
+ self.offset = self.offset - skip
425
+ if self.offset > 0 or not self.data:
426
+ return
427
+
428
+ n, e = self.decoder.decode(self.data)
429
+
430
+ if n < 0:
431
+ # end of stream
432
+ self.data = None
433
+ self.finished = 1
434
+ if e < 0:
435
+ # decoding error
436
+ self.image = None
437
+ raise _get_oserror(e, encoder=False)
438
+ else:
439
+ # end of image
440
+ return
441
+ self.data = self.data[n:]
442
+
443
+ elif self.image:
444
+ # if we end up here with no decoder, this file cannot
445
+ # be incrementally parsed. wait until we've gotten all
446
+ # available data
447
+ pass
448
+
449
+ else:
450
+ # attempt to open this file
451
+ try:
452
+ with io.BytesIO(self.data) as fp:
453
+ im = Image.open(fp)
454
+ except OSError:
455
+ pass # not enough data
456
+ else:
457
+ flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
458
+ if flag or len(im.tile) != 1:
459
+ # custom load code, or multiple tiles
460
+ self.decode = None
461
+ else:
462
+ # initialize decoder
463
+ im.load_prepare()
464
+ d, e, o, a = im.tile[0]
465
+ im.tile = []
466
+ self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig)
467
+ self.decoder.setimage(im.im, e)
468
+
469
+ # calculate decoder offset
470
+ self.offset = o
471
+ if self.offset <= len(self.data):
472
+ self.data = self.data[self.offset :]
473
+ self.offset = 0
474
+
475
+ self.image = im
476
+
477
+ def __enter__(self):
478
+ return self
479
+
480
+ def __exit__(self, *args):
481
+ self.close()
482
+
483
+ def close(self):
484
+ """
485
+ (Consumer) Close the stream.
486
+
487
+ :returns: An image object.
488
+ :exception OSError: If the parser failed to parse the image file either
489
+ because it cannot be identified or cannot be
490
+ decoded.
491
+ """
492
+ # finish decoding
493
+ if self.decoder:
494
+ # get rid of what's left in the buffers
495
+ self.feed(b"")
496
+ self.data = self.decoder = None
497
+ if not self.finished:
498
+ msg = "image was incomplete"
499
+ raise OSError(msg)
500
+ if not self.image:
501
+ msg = "cannot parse this image"
502
+ raise OSError(msg)
503
+ if self.data:
504
+ # incremental parsing not possible; reopen the file
505
+ # not that we have all data
506
+ with io.BytesIO(self.data) as fp:
507
+ try:
508
+ self.image = Image.open(fp)
509
+ finally:
510
+ self.image.load()
511
+ return self.image
512
+
513
+
514
+ # --------------------------------------------------------------------
515
+
516
+
517
+ def _save(im, fp, tile, bufsize=0):
518
+ """Helper to save image based on tile list
519
+
520
+ :param im: Image object.
521
+ :param fp: File object.
522
+ :param tile: Tile list.
523
+ :param bufsize: Optional buffer size
524
+ """
525
+
526
+ im.load()
527
+ if not hasattr(im, "encoderconfig"):
528
+ im.encoderconfig = ()
529
+ tile.sort(key=_tilesort)
530
+ # FIXME: make MAXBLOCK a configuration parameter
531
+ # It would be great if we could have the encoder specify what it needs
532
+ # But, it would need at least the image size in most cases. RawEncode is
533
+ # a tricky case.
534
+ bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
535
+ try:
536
+ fh = fp.fileno()
537
+ fp.flush()
538
+ _encode_tile(im, fp, tile, bufsize, fh)
539
+ except (AttributeError, io.UnsupportedOperation) as exc:
540
+ _encode_tile(im, fp, tile, bufsize, None, exc)
541
+ if hasattr(fp, "flush"):
542
+ fp.flush()
543
+
544
+
545
+ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
546
+ for encoder_name, extents, offset, args in tile:
547
+ if offset > 0:
548
+ fp.seek(offset)
549
+ encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig)
550
+ try:
551
+ encoder.setimage(im.im, extents)
552
+ if encoder.pushes_fd:
553
+ encoder.setfd(fp)
554
+ errcode = encoder.encode_to_pyfd()[1]
555
+ else:
556
+ if exc:
557
+ # compress to Python file-compatible object
558
+ while True:
559
+ errcode, data = encoder.encode(bufsize)[1:]
560
+ fp.write(data)
561
+ if errcode:
562
+ break
563
+ else:
564
+ # slight speedup: compress to real file object
565
+ errcode = encoder.encode_to_file(fh, bufsize)
566
+ if errcode < 0:
567
+ raise _get_oserror(errcode, encoder=True) from exc
568
+ finally:
569
+ encoder.cleanup()
570
+
571
+
572
+ def _safe_read(fp, size):
573
+ """
574
+ Reads large blocks in a safe way. Unlike fp.read(n), this function
575
+ doesn't trust the user. If the requested size is larger than
576
+ SAFEBLOCK, the file is read block by block.
577
+
578
+ :param fp: File handle. Must implement a <b>read</b> method.
579
+ :param size: Number of bytes to read.
580
+ :returns: A string containing <i>size</i> bytes of data.
581
+
582
+ Raises an OSError if the file is truncated and the read cannot be completed
583
+
584
+ """
585
+ if size <= 0:
586
+ return b""
587
+ if size <= SAFEBLOCK:
588
+ data = fp.read(size)
589
+ if len(data) < size:
590
+ msg = "Truncated File Read"
591
+ raise OSError(msg)
592
+ return data
593
+ data = []
594
+ remaining_size = size
595
+ while remaining_size > 0:
596
+ block = fp.read(min(remaining_size, SAFEBLOCK))
597
+ if not block:
598
+ break
599
+ data.append(block)
600
+ remaining_size -= len(block)
601
+ if sum(len(d) for d in data) < size:
602
+ msg = "Truncated File Read"
603
+ raise OSError(msg)
604
+ return b"".join(data)
605
+
606
+
607
+ class PyCodecState:
608
+ def __init__(self):
609
+ self.xsize = 0
610
+ self.ysize = 0
611
+ self.xoff = 0
612
+ self.yoff = 0
613
+
614
+ def extents(self):
615
+ return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize
616
+
617
+
618
+ class PyCodec:
619
+ def __init__(self, mode, *args):
620
+ self.im = None
621
+ self.state = PyCodecState()
622
+ self.fd = None
623
+ self.mode = mode
624
+ self.init(args)
625
+
626
+ def init(self, args):
627
+ """
628
+ Override to perform codec specific initialization
629
+
630
+ :param args: Array of args items from the tile entry
631
+ :returns: None
632
+ """
633
+ self.args = args
634
+
635
+ def cleanup(self):
636
+ """
637
+ Override to perform codec specific cleanup
638
+
639
+ :returns: None
640
+ """
641
+ pass
642
+
643
+ def setfd(self, fd):
644
+ """
645
+ Called from ImageFile to set the Python file-like object
646
+
647
+ :param fd: A Python file-like object
648
+ :returns: None
649
+ """
650
+ self.fd = fd
651
+
652
+ def setimage(self, im, extents=None):
653
+ """
654
+ Called from ImageFile to set the core output image for the codec
655
+
656
+ :param im: A core image object
657
+ :param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
658
+ for this tile
659
+ :returns: None
660
+ """
661
+
662
+ # following c code
663
+ self.im = im
664
+
665
+ if extents:
666
+ (x0, y0, x1, y1) = extents
667
+ else:
668
+ (x0, y0, x1, y1) = (0, 0, 0, 0)
669
+
670
+ if x0 == 0 and x1 == 0:
671
+ self.state.xsize, self.state.ysize = self.im.size
672
+ else:
673
+ self.state.xoff = x0
674
+ self.state.yoff = y0
675
+ self.state.xsize = x1 - x0
676
+ self.state.ysize = y1 - y0
677
+
678
+ if self.state.xsize <= 0 or self.state.ysize <= 0:
679
+ msg = "Size cannot be negative"
680
+ raise ValueError(msg)
681
+
682
+ if (
683
+ self.state.xsize + self.state.xoff > self.im.size[0]
684
+ or self.state.ysize + self.state.yoff > self.im.size[1]
685
+ ):
686
+ msg = "Tile cannot extend outside image"
687
+ raise ValueError(msg)
688
+
689
+
690
+ class PyDecoder(PyCodec):
691
+ """
692
+ Python implementation of a format decoder. Override this class and
693
+ add the decoding logic in the :meth:`decode` method.
694
+
695
+ See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
696
+ """
697
+
698
+ _pulls_fd = False
699
+
700
+ @property
701
+ def pulls_fd(self):
702
+ return self._pulls_fd
703
+
704
+ def decode(self, buffer):
705
+ """
706
+ Override to perform the decoding process.
707
+
708
+ :param buffer: A bytes object with the data to be decoded.
709
+ :returns: A tuple of ``(bytes consumed, errcode)``.
710
+ If finished with decoding return -1 for the bytes consumed.
711
+ Err codes are from :data:`.ImageFile.ERRORS`.
712
+ """
713
+ msg = "unavailable in base decoder"
714
+ raise NotImplementedError(msg)
715
+
716
+ def set_as_raw(self, data, rawmode=None):
717
+ """
718
+ Convenience method to set the internal image from a stream of raw data
719
+
720
+ :param data: Bytes to be set
721
+ :param rawmode: The rawmode to be used for the decoder.
722
+ If not specified, it will default to the mode of the image
723
+ :returns: None
724
+ """
725
+
726
+ if not rawmode:
727
+ rawmode = self.mode
728
+ d = Image._getdecoder(self.mode, "raw", rawmode)
729
+ d.setimage(self.im, self.state.extents())
730
+ s = d.decode(data)
731
+
732
+ if s[0] >= 0:
733
+ msg = "not enough image data"
734
+ raise ValueError(msg)
735
+ if s[1] != 0:
736
+ msg = "cannot decode image data"
737
+ raise ValueError(msg)
738
+
739
+
740
+ class PyEncoder(PyCodec):
741
+ """
742
+ Python implementation of a format encoder. Override this class and
743
+ add the decoding logic in the :meth:`encode` method.
744
+
745
+ See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
746
+ """
747
+
748
+ _pushes_fd = False
749
+
750
+ @property
751
+ def pushes_fd(self):
752
+ return self._pushes_fd
753
+
754
+ def encode(self, bufsize):
755
+ """
756
+ Override to perform the encoding process.
757
+
758
+ :param bufsize: Buffer size.
759
+ :returns: A tuple of ``(bytes encoded, errcode, bytes)``.
760
+ If finished with encoding return 1 for the error code.
761
+ Err codes are from :data:`.ImageFile.ERRORS`.
762
+ """
763
+ msg = "unavailable in base encoder"
764
+ raise NotImplementedError(msg)
765
+
766
+ def encode_to_pyfd(self):
767
+ """
768
+ If ``pushes_fd`` is ``True``, then this method will be used,
769
+ and ``encode()`` will only be called once.
770
+
771
+ :returns: A tuple of ``(bytes consumed, errcode)``.
772
+ Err codes are from :data:`.ImageFile.ERRORS`.
773
+ """
774
+ if not self.pushes_fd:
775
+ return 0, -8 # bad configuration
776
+ bytes_consumed, errcode, data = self.encode(0)
777
+ if data:
778
+ self.fd.write(data)
779
+ return bytes_consumed, errcode
780
+
781
+ def encode_to_file(self, fh, bufsize):
782
+ """
783
+ :param fh: File handle.
784
+ :param bufsize: Buffer size.
785
+
786
+ :returns: If finished successfully, return 0.
787
+ Otherwise, return an error code. Err codes are from
788
+ :data:`.ImageFile.ERRORS`.
789
+ """
790
+ errcode = 0
791
+ while errcode == 0:
792
+ status, errcode, buf = self.encode(bufsize)
793
+ if status > 0:
794
+ fh.write(buf[status:])
795
+ return errcode
.venv/Lib/site-packages/PIL/ImageFilter.py ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # standard filters
6
+ #
7
+ # History:
8
+ # 1995-11-27 fl Created
9
+ # 2002-06-08 fl Added rank and mode filters
10
+ # 2003-09-15 fl Fixed rank calculation in rank filter; added expand call
11
+ #
12
+ # Copyright (c) 1997-2003 by Secret Labs AB.
13
+ # Copyright (c) 1995-2002 by Fredrik Lundh.
14
+ #
15
+ # See the README file for information on usage and redistribution.
16
+ #
17
+ from __future__ import annotations
18
+
19
+ import functools
20
+
21
+
22
+ class Filter:
23
+ pass
24
+
25
+
26
+ class MultibandFilter(Filter):
27
+ pass
28
+
29
+
30
+ class BuiltinFilter(MultibandFilter):
31
+ def filter(self, image):
32
+ if image.mode == "P":
33
+ msg = "cannot filter palette images"
34
+ raise ValueError(msg)
35
+ return image.filter(*self.filterargs)
36
+
37
+
38
+ class Kernel(BuiltinFilter):
39
+ """
40
+ Create a convolution kernel. The current version only
41
+ supports 3x3 and 5x5 integer and floating point kernels.
42
+
43
+ In the current version, kernels can only be applied to
44
+ "L" and "RGB" images.
45
+
46
+ :param size: Kernel size, given as (width, height). In the current
47
+ version, this must be (3,3) or (5,5).
48
+ :param kernel: A sequence containing kernel weights. The kernel will
49
+ be flipped vertically before being applied to the image.
50
+ :param scale: Scale factor. If given, the result for each pixel is
51
+ divided by this value. The default is the sum of the
52
+ kernel weights.
53
+ :param offset: Offset. If given, this value is added to the result,
54
+ after it has been divided by the scale factor.
55
+ """
56
+
57
+ name = "Kernel"
58
+
59
+ def __init__(self, size, kernel, scale=None, offset=0):
60
+ if scale is None:
61
+ # default scale is sum of kernel
62
+ scale = functools.reduce(lambda a, b: a + b, kernel)
63
+ if size[0] * size[1] != len(kernel):
64
+ msg = "not enough coefficients in kernel"
65
+ raise ValueError(msg)
66
+ self.filterargs = size, scale, offset, kernel
67
+
68
+
69
+ class RankFilter(Filter):
70
+ """
71
+ Create a rank filter. The rank filter sorts all pixels in
72
+ a window of the given size, and returns the ``rank``'th value.
73
+
74
+ :param size: The kernel size, in pixels.
75
+ :param rank: What pixel value to pick. Use 0 for a min filter,
76
+ ``size * size / 2`` for a median filter, ``size * size - 1``
77
+ for a max filter, etc.
78
+ """
79
+
80
+ name = "Rank"
81
+
82
+ def __init__(self, size, rank):
83
+ self.size = size
84
+ self.rank = rank
85
+
86
+ def filter(self, image):
87
+ if image.mode == "P":
88
+ msg = "cannot filter palette images"
89
+ raise ValueError(msg)
90
+ image = image.expand(self.size // 2, self.size // 2)
91
+ return image.rankfilter(self.size, self.rank)
92
+
93
+
94
+ class MedianFilter(RankFilter):
95
+ """
96
+ Create a median filter. Picks the median pixel value in a window with the
97
+ given size.
98
+
99
+ :param size: The kernel size, in pixels.
100
+ """
101
+
102
+ name = "Median"
103
+
104
+ def __init__(self, size=3):
105
+ self.size = size
106
+ self.rank = size * size // 2
107
+
108
+
109
+ class MinFilter(RankFilter):
110
+ """
111
+ Create a min filter. Picks the lowest pixel value in a window with the
112
+ given size.
113
+
114
+ :param size: The kernel size, in pixels.
115
+ """
116
+
117
+ name = "Min"
118
+
119
+ def __init__(self, size=3):
120
+ self.size = size
121
+ self.rank = 0
122
+
123
+
124
+ class MaxFilter(RankFilter):
125
+ """
126
+ Create a max filter. Picks the largest pixel value in a window with the
127
+ given size.
128
+
129
+ :param size: The kernel size, in pixels.
130
+ """
131
+
132
+ name = "Max"
133
+
134
+ def __init__(self, size=3):
135
+ self.size = size
136
+ self.rank = size * size - 1
137
+
138
+
139
+ class ModeFilter(Filter):
140
+ """
141
+ Create a mode filter. Picks the most frequent pixel value in a box with the
142
+ given size. Pixel values that occur only once or twice are ignored; if no
143
+ pixel value occurs more than twice, the original pixel value is preserved.
144
+
145
+ :param size: The kernel size, in pixels.
146
+ """
147
+
148
+ name = "Mode"
149
+
150
+ def __init__(self, size=3):
151
+ self.size = size
152
+
153
+ def filter(self, image):
154
+ return image.modefilter(self.size)
155
+
156
+
157
+ class GaussianBlur(MultibandFilter):
158
+ """Blurs the image with a sequence of extended box filters, which
159
+ approximates a Gaussian kernel. For details on accuracy see
160
+ <https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
161
+
162
+ :param radius: Standard deviation of the Gaussian kernel. Either a sequence of two
163
+ numbers for x and y, or a single number for both.
164
+ """
165
+
166
+ name = "GaussianBlur"
167
+
168
+ def __init__(self, radius=2):
169
+ self.radius = radius
170
+
171
+ def filter(self, image):
172
+ xy = self.radius
173
+ if not isinstance(xy, (tuple, list)):
174
+ xy = (xy, xy)
175
+ if xy == (0, 0):
176
+ return image.copy()
177
+ return image.gaussian_blur(xy)
178
+
179
+
180
+ class BoxBlur(MultibandFilter):
181
+ """Blurs the image by setting each pixel to the average value of the pixels
182
+ in a square box extending radius pixels in each direction.
183
+ Supports float radius of arbitrary size. Uses an optimized implementation
184
+ which runs in linear time relative to the size of the image
185
+ for any radius value.
186
+
187
+ :param radius: Size of the box in a direction. Either a sequence of two numbers for
188
+ x and y, or a single number for both.
189
+
190
+ Radius 0 does not blur, returns an identical image.
191
+ Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total.
192
+ """
193
+
194
+ name = "BoxBlur"
195
+
196
+ def __init__(self, radius):
197
+ xy = radius
198
+ if not isinstance(xy, (tuple, list)):
199
+ xy = (xy, xy)
200
+ if xy[0] < 0 or xy[1] < 0:
201
+ msg = "radius must be >= 0"
202
+ raise ValueError(msg)
203
+ self.radius = radius
204
+
205
+ def filter(self, image):
206
+ xy = self.radius
207
+ if not isinstance(xy, (tuple, list)):
208
+ xy = (xy, xy)
209
+ if xy == (0, 0):
210
+ return image.copy()
211
+ return image.box_blur(xy)
212
+
213
+
214
+ class UnsharpMask(MultibandFilter):
215
+ """Unsharp mask filter.
216
+
217
+ See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
218
+ the parameters.
219
+
220
+ :param radius: Blur Radius
221
+ :param percent: Unsharp strength, in percent
222
+ :param threshold: Threshold controls the minimum brightness change that
223
+ will be sharpened
224
+
225
+ .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
226
+
227
+ """
228
+
229
+ name = "UnsharpMask"
230
+
231
+ def __init__(self, radius=2, percent=150, threshold=3):
232
+ self.radius = radius
233
+ self.percent = percent
234
+ self.threshold = threshold
235
+
236
+ def filter(self, image):
237
+ return image.unsharp_mask(self.radius, self.percent, self.threshold)
238
+
239
+
240
+ class BLUR(BuiltinFilter):
241
+ name = "Blur"
242
+ # fmt: off
243
+ filterargs = (5, 5), 16, 0, (
244
+ 1, 1, 1, 1, 1,
245
+ 1, 0, 0, 0, 1,
246
+ 1, 0, 0, 0, 1,
247
+ 1, 0, 0, 0, 1,
248
+ 1, 1, 1, 1, 1,
249
+ )
250
+ # fmt: on
251
+
252
+
253
+ class CONTOUR(BuiltinFilter):
254
+ name = "Contour"
255
+ # fmt: off
256
+ filterargs = (3, 3), 1, 255, (
257
+ -1, -1, -1,
258
+ -1, 8, -1,
259
+ -1, -1, -1,
260
+ )
261
+ # fmt: on
262
+
263
+
264
+ class DETAIL(BuiltinFilter):
265
+ name = "Detail"
266
+ # fmt: off
267
+ filterargs = (3, 3), 6, 0, (
268
+ 0, -1, 0,
269
+ -1, 10, -1,
270
+ 0, -1, 0,
271
+ )
272
+ # fmt: on
273
+
274
+
275
+ class EDGE_ENHANCE(BuiltinFilter):
276
+ name = "Edge-enhance"
277
+ # fmt: off
278
+ filterargs = (3, 3), 2, 0, (
279
+ -1, -1, -1,
280
+ -1, 10, -1,
281
+ -1, -1, -1,
282
+ )
283
+ # fmt: on
284
+
285
+
286
+ class EDGE_ENHANCE_MORE(BuiltinFilter):
287
+ name = "Edge-enhance More"
288
+ # fmt: off
289
+ filterargs = (3, 3), 1, 0, (
290
+ -1, -1, -1,
291
+ -1, 9, -1,
292
+ -1, -1, -1,
293
+ )
294
+ # fmt: on
295
+
296
+
297
+ class EMBOSS(BuiltinFilter):
298
+ name = "Emboss"
299
+ # fmt: off
300
+ filterargs = (3, 3), 1, 128, (
301
+ -1, 0, 0,
302
+ 0, 1, 0,
303
+ 0, 0, 0,
304
+ )
305
+ # fmt: on
306
+
307
+
308
+ class FIND_EDGES(BuiltinFilter):
309
+ name = "Find Edges"
310
+ # fmt: off
311
+ filterargs = (3, 3), 1, 0, (
312
+ -1, -1, -1,
313
+ -1, 8, -1,
314
+ -1, -1, -1,
315
+ )
316
+ # fmt: on
317
+
318
+
319
+ class SHARPEN(BuiltinFilter):
320
+ name = "Sharpen"
321
+ # fmt: off
322
+ filterargs = (3, 3), 16, 0, (
323
+ -2, -2, -2,
324
+ -2, 32, -2,
325
+ -2, -2, -2,
326
+ )
327
+ # fmt: on
328
+
329
+
330
+ class SMOOTH(BuiltinFilter):
331
+ name = "Smooth"
332
+ # fmt: off
333
+ filterargs = (3, 3), 13, 0, (
334
+ 1, 1, 1,
335
+ 1, 5, 1,
336
+ 1, 1, 1,
337
+ )
338
+ # fmt: on
339
+
340
+
341
+ class SMOOTH_MORE(BuiltinFilter):
342
+ name = "Smooth More"
343
+ # fmt: off
344
+ filterargs = (5, 5), 100, 0, (
345
+ 1, 1, 1, 1, 1,
346
+ 1, 5, 5, 5, 1,
347
+ 1, 5, 44, 5, 1,
348
+ 1, 5, 5, 5, 1,
349
+ 1, 1, 1, 1, 1,
350
+ )
351
+ # fmt: on
352
+
353
+
354
+ class Color3DLUT(MultibandFilter):
355
+ """Three-dimensional color lookup table.
356
+
357
+ Transforms 3-channel pixels using the values of the channels as coordinates
358
+ in the 3D lookup table and interpolating the nearest elements.
359
+
360
+ This method allows you to apply almost any color transformation
361
+ in constant time by using pre-calculated decimated tables.
362
+
363
+ .. versionadded:: 5.2.0
364
+
365
+ :param size: Size of the table. One int or tuple of (int, int, int).
366
+ Minimal size in any dimension is 2, maximum is 65.
367
+ :param table: Flat lookup table. A list of ``channels * size**3``
368
+ float elements or a list of ``size**3`` channels-sized
369
+ tuples with floats. Channels are changed first,
370
+ then first dimension, then second, then third.
371
+ Value 0.0 corresponds lowest value of output, 1.0 highest.
372
+ :param channels: Number of channels in the table. Could be 3 or 4.
373
+ Default is 3.
374
+ :param target_mode: A mode for the result image. Should have not less
375
+ than ``channels`` channels. Default is ``None``,
376
+ which means that mode wouldn't be changed.
377
+ """
378
+
379
+ name = "Color 3D LUT"
380
+
381
+ def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
382
+ if channels not in (3, 4):
383
+ msg = "Only 3 or 4 output channels are supported"
384
+ raise ValueError(msg)
385
+ self.size = size = self._check_size(size)
386
+ self.channels = channels
387
+ self.mode = target_mode
388
+
389
+ # Hidden flag `_copy_table=False` could be used to avoid extra copying
390
+ # of the table if the table is specially made for the constructor.
391
+ copy_table = kwargs.get("_copy_table", True)
392
+ items = size[0] * size[1] * size[2]
393
+ wrong_size = False
394
+
395
+ numpy = None
396
+ if hasattr(table, "shape"):
397
+ try:
398
+ import numpy
399
+ except ImportError:
400
+ pass
401
+
402
+ if numpy and isinstance(table, numpy.ndarray):
403
+ if copy_table:
404
+ table = table.copy()
405
+
406
+ if table.shape in [
407
+ (items * channels,),
408
+ (items, channels),
409
+ (size[2], size[1], size[0], channels),
410
+ ]:
411
+ table = table.reshape(items * channels)
412
+ else:
413
+ wrong_size = True
414
+
415
+ else:
416
+ if copy_table:
417
+ table = list(table)
418
+
419
+ # Convert to a flat list
420
+ if table and isinstance(table[0], (list, tuple)):
421
+ table, raw_table = [], table
422
+ for pixel in raw_table:
423
+ if len(pixel) != channels:
424
+ msg = (
425
+ "The elements of the table should "
426
+ f"have a length of {channels}."
427
+ )
428
+ raise ValueError(msg)
429
+ table.extend(pixel)
430
+
431
+ if wrong_size or len(table) != items * channels:
432
+ msg = (
433
+ "The table should have either channels * size**3 float items "
434
+ "or size**3 items of channels-sized tuples with floats. "
435
+ f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. "
436
+ f"Actual length: {len(table)}"
437
+ )
438
+ raise ValueError(msg)
439
+ self.table = table
440
+
441
+ @staticmethod
442
+ def _check_size(size):
443
+ try:
444
+ _, _, _ = size
445
+ except ValueError as e:
446
+ msg = "Size should be either an integer or a tuple of three integers."
447
+ raise ValueError(msg) from e
448
+ except TypeError:
449
+ size = (size, size, size)
450
+ size = [int(x) for x in size]
451
+ for size_1d in size:
452
+ if not 2 <= size_1d <= 65:
453
+ msg = "Size should be in [2, 65] range."
454
+ raise ValueError(msg)
455
+ return size
456
+
457
+ @classmethod
458
+ def generate(cls, size, callback, channels=3, target_mode=None):
459
+ """Generates new LUT using provided callback.
460
+
461
+ :param size: Size of the table. Passed to the constructor.
462
+ :param callback: Function with three parameters which correspond
463
+ three color channels. Will be called ``size**3``
464
+ times with values from 0.0 to 1.0 and should return
465
+ a tuple with ``channels`` elements.
466
+ :param channels: The number of channels which should return callback.
467
+ :param target_mode: Passed to the constructor of the resulting
468
+ lookup table.
469
+ """
470
+ size_1d, size_2d, size_3d = cls._check_size(size)
471
+ if channels not in (3, 4):
472
+ msg = "Only 3 or 4 output channels are supported"
473
+ raise ValueError(msg)
474
+
475
+ table = [0] * (size_1d * size_2d * size_3d * channels)
476
+ idx_out = 0
477
+ for b in range(size_3d):
478
+ for g in range(size_2d):
479
+ for r in range(size_1d):
480
+ table[idx_out : idx_out + channels] = callback(
481
+ r / (size_1d - 1), g / (size_2d - 1), b / (size_3d - 1)
482
+ )
483
+ idx_out += channels
484
+
485
+ return cls(
486
+ (size_1d, size_2d, size_3d),
487
+ table,
488
+ channels=channels,
489
+ target_mode=target_mode,
490
+ _copy_table=False,
491
+ )
492
+
493
+ def transform(self, callback, with_normals=False, channels=None, target_mode=None):
494
+ """Transforms the table values using provided callback and returns
495
+ a new LUT with altered values.
496
+
497
+ :param callback: A function which takes old lookup table values
498
+ and returns a new set of values. The number
499
+ of arguments which function should take is
500
+ ``self.channels`` or ``3 + self.channels``
501
+ if ``with_normals`` flag is set.
502
+ Should return a tuple of ``self.channels`` or
503
+ ``channels`` elements if it is set.
504
+ :param with_normals: If true, ``callback`` will be called with
505
+ coordinates in the color cube as the first
506
+ three arguments. Otherwise, ``callback``
507
+ will be called only with actual color values.
508
+ :param channels: The number of channels in the resulting lookup table.
509
+ :param target_mode: Passed to the constructor of the resulting
510
+ lookup table.
511
+ """
512
+ if channels not in (None, 3, 4):
513
+ msg = "Only 3 or 4 output channels are supported"
514
+ raise ValueError(msg)
515
+ ch_in = self.channels
516
+ ch_out = channels or ch_in
517
+ size_1d, size_2d, size_3d = self.size
518
+
519
+ table = [0] * (size_1d * size_2d * size_3d * ch_out)
520
+ idx_in = 0
521
+ idx_out = 0
522
+ for b in range(size_3d):
523
+ for g in range(size_2d):
524
+ for r in range(size_1d):
525
+ values = self.table[idx_in : idx_in + ch_in]
526
+ if with_normals:
527
+ values = callback(
528
+ r / (size_1d - 1),
529
+ g / (size_2d - 1),
530
+ b / (size_3d - 1),
531
+ *values,
532
+ )
533
+ else:
534
+ values = callback(*values)
535
+ table[idx_out : idx_out + ch_out] = values
536
+ idx_in += ch_in
537
+ idx_out += ch_out
538
+
539
+ return type(self)(
540
+ self.size,
541
+ table,
542
+ channels=ch_out,
543
+ target_mode=target_mode or self.mode,
544
+ _copy_table=False,
545
+ )
546
+
547
+ def __repr__(self):
548
+ r = [
549
+ f"{self.__class__.__name__} from {self.table.__class__.__name__}",
550
+ "size={:d}x{:d}x{:d}".format(*self.size),
551
+ f"channels={self.channels:d}",
552
+ ]
553
+ if self.mode:
554
+ r.append(f"target_mode={self.mode}")
555
+ return "<{}>".format(" ".join(r))
556
+
557
+ def filter(self, image):
558
+ from . import Image
559
+
560
+ return image.color_lut_3d(
561
+ self.mode or image.mode,
562
+ Image.Resampling.BILINEAR,
563
+ self.channels,
564
+ self.size[0],
565
+ self.size[1],
566
+ self.size[2],
567
+ self.table,
568
+ )
.venv/Lib/site-packages/PIL/ImageFont.py ADDED
@@ -0,0 +1,1264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # The Python Imaging Library.
3
+ # $Id$
4
+ #
5
+ # PIL raster font management
6
+ #
7
+ # History:
8
+ # 1996-08-07 fl created (experimental)
9
+ # 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3
10
+ # 1999-02-06 fl rewrote most font management stuff in C
11
+ # 1999-03-17 fl take pth files into account in load_path (from Richard Jones)
12
+ # 2001-02-17 fl added freetype support
13
+ # 2001-05-09 fl added TransposedFont wrapper class
14
+ # 2002-03-04 fl make sure we have a "L" or "1" font
15
+ # 2002-12-04 fl skip non-directory entries in the system path
16
+ # 2003-04-29 fl add embedded default font
17
+ # 2003-09-27 fl added support for truetype charmap encodings
18
+ #
19
+ # Todo:
20
+ # Adapt to PILFONT2 format (16-bit fonts, compressed, single file)
21
+ #
22
+ # Copyright (c) 1997-2003 by Secret Labs AB
23
+ # Copyright (c) 1996-2003 by Fredrik Lundh
24
+ #
25
+ # See the README file for information on usage and redistribution.
26
+ #
27
+
28
+ from __future__ import annotations
29
+
30
+ import base64
31
+ import os
32
+ import sys
33
+ import warnings
34
+ from enum import IntEnum
35
+ from io import BytesIO
36
+ from pathlib import Path
37
+ from typing import BinaryIO
38
+
39
+ from . import Image
40
+ from ._util import is_directory, is_path
41
+
42
+
43
+ class Layout(IntEnum):
44
+ BASIC = 0
45
+ RAQM = 1
46
+
47
+
48
+ MAX_STRING_LENGTH = 1_000_000
49
+
50
+
51
+ try:
52
+ from . import _imagingft as core
53
+ except ImportError as ex:
54
+ from ._util import DeferredError
55
+
56
+ core = DeferredError.new(ex)
57
+
58
+
59
+ def _string_length_check(text):
60
+ if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH:
61
+ msg = "too many characters in string"
62
+ raise ValueError(msg)
63
+
64
+
65
+ # FIXME: add support for pilfont2 format (see FontFile.py)
66
+
67
+ # --------------------------------------------------------------------
68
+ # Font metrics format:
69
+ # "PILfont" LF
70
+ # fontdescriptor LF
71
+ # (optional) key=value... LF
72
+ # "DATA" LF
73
+ # binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox)
74
+ #
75
+ # To place a character, cut out srcbox and paste at dstbox,
76
+ # relative to the character position. Then move the character
77
+ # position according to dx, dy.
78
+ # --------------------------------------------------------------------
79
+
80
+
81
+ class ImageFont:
82
+ """PIL font wrapper"""
83
+
84
+ def _load_pilfont(self, filename):
85
+ with open(filename, "rb") as fp:
86
+ image = None
87
+ for ext in (".png", ".gif", ".pbm"):
88
+ if image:
89
+ image.close()
90
+ try:
91
+ fullname = os.path.splitext(filename)[0] + ext
92
+ image = Image.open(fullname)
93
+ except Exception:
94
+ pass
95
+ else:
96
+ if image and image.mode in ("1", "L"):
97
+ break
98
+ else:
99
+ if image:
100
+ image.close()
101
+ msg = "cannot find glyph data file"
102
+ raise OSError(msg)
103
+
104
+ self.file = fullname
105
+
106
+ self._load_pilfont_data(fp, image)
107
+ image.close()
108
+
109
+ def _load_pilfont_data(self, file, image):
110
+ # read PILfont header
111
+ if file.readline() != b"PILfont\n":
112
+ msg = "Not a PILfont file"
113
+ raise SyntaxError(msg)
114
+ file.readline().split(b";")
115
+ self.info = [] # FIXME: should be a dictionary
116
+ while True:
117
+ s = file.readline()
118
+ if not s or s == b"DATA\n":
119
+ break
120
+ self.info.append(s)
121
+
122
+ # read PILfont metrics
123
+ data = file.read(256 * 20)
124
+
125
+ # check image
126
+ if image.mode not in ("1", "L"):
127
+ msg = "invalid font image mode"
128
+ raise TypeError(msg)
129
+
130
+ image.load()
131
+
132
+ self.font = Image.core.font(image.im, data)
133
+
134
+ def getmask(self, text, mode="", *args, **kwargs):
135
+ """
136
+ Create a bitmap for the text.
137
+
138
+ If the font uses antialiasing, the bitmap should have mode ``L`` and use a
139
+ maximum value of 255. Otherwise, it should have mode ``1``.
140
+
141
+ :param text: Text to render.
142
+ :param mode: Used by some graphics drivers to indicate what mode the
143
+ driver prefers; if empty, the renderer may return either
144
+ mode. Note that the mode is always a string, to simplify
145
+ C-level implementations.
146
+
147
+ .. versionadded:: 1.1.5
148
+
149
+ :return: An internal PIL storage memory instance as defined by the
150
+ :py:mod:`PIL.Image.core` interface module.
151
+ """
152
+ _string_length_check(text)
153
+ Image._decompression_bomb_check(self.font.getsize(text))
154
+ return self.font.getmask(text, mode)
155
+
156
+ def getbbox(self, text, *args, **kwargs):
157
+ """
158
+ Returns bounding box (in pixels) of given text.
159
+
160
+ .. versionadded:: 9.2.0
161
+
162
+ :param text: Text to render.
163
+ :param mode: Used by some graphics drivers to indicate what mode the
164
+ driver prefers; if empty, the renderer may return either
165
+ mode. Note that the mode is always a string, to simplify
166
+ C-level implementations.
167
+
168
+ :return: ``(left, top, right, bottom)`` bounding box
169
+ """
170
+ _string_length_check(text)
171
+ width, height = self.font.getsize(text)
172
+ return 0, 0, width, height
173
+
174
+ def getlength(self, text, *args, **kwargs):
175
+ """
176
+ Returns length (in pixels) of given text.
177
+ This is the amount by which following text should be offset.
178
+
179
+ .. versionadded:: 9.2.0
180
+ """
181
+ _string_length_check(text)
182
+ width, height = self.font.getsize(text)
183
+ return width
184
+
185
+
186
+ ##
187
+ # Wrapper for FreeType fonts. Application code should use the
188
+ # <b>truetype</b> factory function to create font objects.
189
+
190
+
191
+ class FreeTypeFont:
192
+ """FreeType font wrapper (requires _imagingft service)"""
193
+
194
+ def __init__(
195
+ self,
196
+ font: bytes | str | Path | BinaryIO | None = None,
197
+ size: float = 10,
198
+ index: int = 0,
199
+ encoding: str = "",
200
+ layout_engine: Layout | None = None,
201
+ ) -> None:
202
+ # FIXME: use service provider instead
203
+
204
+ if size <= 0:
205
+ msg = "font size must be greater than 0"
206
+ raise ValueError(msg)
207
+
208
+ self.path = font
209
+ self.size = size
210
+ self.index = index
211
+ self.encoding = encoding
212
+
213
+ if layout_engine not in (Layout.BASIC, Layout.RAQM):
214
+ layout_engine = Layout.BASIC
215
+ if core.HAVE_RAQM:
216
+ layout_engine = Layout.RAQM
217
+ elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
218
+ warnings.warn(
219
+ "Raqm layout was requested, but Raqm is not available. "
220
+ "Falling back to basic layout."
221
+ )
222
+ layout_engine = Layout.BASIC
223
+
224
+ self.layout_engine = layout_engine
225
+
226
+ def load_from_bytes(f):
227
+ self.font_bytes = f.read()
228
+ self.font = core.getfont(
229
+ "", size, index, encoding, self.font_bytes, layout_engine
230
+ )
231
+
232
+ if is_path(font):
233
+ if isinstance(font, Path):
234
+ font = str(font)
235
+ if sys.platform == "win32":
236
+ font_bytes_path = font if isinstance(font, bytes) else font.encode()
237
+ try:
238
+ font_bytes_path.decode("ascii")
239
+ except UnicodeDecodeError:
240
+ # FreeType cannot load fonts with non-ASCII characters on Windows
241
+ # So load it into memory first
242
+ with open(font, "rb") as f:
243
+ load_from_bytes(f)
244
+ return
245
+ self.font = core.getfont(
246
+ font, size, index, encoding, layout_engine=layout_engine
247
+ )
248
+ else:
249
+ load_from_bytes(font)
250
+
251
+ def __getstate__(self):
252
+ return [self.path, self.size, self.index, self.encoding, self.layout_engine]
253
+
254
+ def __setstate__(self, state):
255
+ path, size, index, encoding, layout_engine = state
256
+ self.__init__(path, size, index, encoding, layout_engine)
257
+
258
+ def getname(self):
259
+ """
260
+ :return: A tuple of the font family (e.g. Helvetica) and the font style
261
+ (e.g. Bold)
262
+ """
263
+ return self.font.family, self.font.style
264
+
265
+ def getmetrics(self):
266
+ """
267
+ :return: A tuple of the font ascent (the distance from the baseline to
268
+ the highest outline point) and descent (the distance from the
269
+ baseline to the lowest outline point, a negative value)
270
+ """
271
+ return self.font.ascent, self.font.descent
272
+
273
+ def getlength(self, text, mode="", direction=None, features=None, language=None):
274
+ """
275
+ Returns length (in pixels with 1/64 precision) of given text when rendered
276
+ in font with provided direction, features, and language.
277
+
278
+ This is the amount by which following text should be offset.
279
+ Text bounding box may extend past the length in some fonts,
280
+ e.g. when using italics or accents.
281
+
282
+ The result is returned as a float; it is a whole number if using basic layout.
283
+
284
+ Note that the sum of two lengths may not equal the length of a concatenated
285
+ string due to kerning. If you need to adjust for kerning, include the following
286
+ character and subtract its length.
287
+
288
+ For example, instead of ::
289
+
290
+ hello = font.getlength("Hello")
291
+ world = font.getlength("World")
292
+ hello_world = hello + world # not adjusted for kerning
293
+ assert hello_world == font.getlength("HelloWorld") # may fail
294
+
295
+ use ::
296
+
297
+ hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
298
+ world = font.getlength("World")
299
+ hello_world = hello + world # adjusted for kerning
300
+ assert hello_world == font.getlength("HelloWorld") # True
301
+
302
+ or disable kerning with (requires libraqm) ::
303
+
304
+ hello = draw.textlength("Hello", font, features=["-kern"])
305
+ world = draw.textlength("World", font, features=["-kern"])
306
+ hello_world = hello + world # kerning is disabled, no need to adjust
307
+ assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"])
308
+
309
+ .. versionadded:: 8.0.0
310
+
311
+ :param text: Text to measure.
312
+ :param mode: Used by some graphics drivers to indicate what mode the
313
+ driver prefers; if empty, the renderer may return either
314
+ mode. Note that the mode is always a string, to simplify
315
+ C-level implementations.
316
+
317
+ :param direction: Direction of the text. It can be 'rtl' (right to
318
+ left), 'ltr' (left to right) or 'ttb' (top to bottom).
319
+ Requires libraqm.
320
+
321
+ :param features: A list of OpenType font features to be used during text
322
+ layout. This is usually used to turn on optional
323
+ font features that are not enabled by default,
324
+ for example 'dlig' or 'ss01', but can be also
325
+ used to turn off default font features for
326
+ example '-liga' to disable ligatures or '-kern'
327
+ to disable kerning. To get all supported
328
+ features, see
329
+ https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
330
+ Requires libraqm.
331
+
332
+ :param language: Language of the text. Different languages may use
333
+ different glyph shapes or ligatures. This parameter tells
334
+ the font which language the text is in, and to apply the
335
+ correct substitutions as appropriate, if available.
336
+ It should be a `BCP 47 language code
337
+ <https://www.w3.org/International/articles/language-tags/>`_
338
+ Requires libraqm.
339
+
340
+ :return: Either width for horizontal text, or height for vertical text.
341
+ """
342
+ _string_length_check(text)
343
+ return self.font.getlength(text, mode, direction, features, language) / 64
344
+
345
+ def getbbox(
346
+ self,
347
+ text,
348
+ mode="",
349
+ direction=None,
350
+ features=None,
351
+ language=None,
352
+ stroke_width=0,
353
+ anchor=None,
354
+ ):
355
+ """
356
+ Returns bounding box (in pixels) of given text relative to given anchor
357
+ when rendered in font with provided direction, features, and language.
358
+
359
+ Use :py:meth:`getlength()` to get the offset of following text with
360
+ 1/64 pixel precision. The bounding box includes extra margins for
361
+ some fonts, e.g. italics or accents.
362
+
363
+ .. versionadded:: 8.0.0
364
+
365
+ :param text: Text to render.
366
+ :param mode: Used by some graphics drivers to indicate what mode the
367
+ driver prefers; if empty, the renderer may return either
368
+ mode. Note that the mode is always a string, to simplify
369
+ C-level implementations.
370
+
371
+ :param direction: Direction of the text. It can be 'rtl' (right to
372
+ left), 'ltr' (left to right) or 'ttb' (top to bottom).
373
+ Requires libraqm.
374
+
375
+ :param features: A list of OpenType font features to be used during text
376
+ layout. This is usually used to turn on optional
377
+ font features that are not enabled by default,
378
+ for example 'dlig' or 'ss01', but can be also
379
+ used to turn off default font features for
380
+ example '-liga' to disable ligatures or '-kern'
381
+ to disable kerning. To get all supported
382
+ features, see
383
+ https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
384
+ Requires libraqm.
385
+
386
+ :param language: Language of the text. Different languages may use
387
+ different glyph shapes or ligatures. This parameter tells
388
+ the font which language the text is in, and to apply the
389
+ correct substitutions as appropriate, if available.
390
+ It should be a `BCP 47 language code
391
+ <https://www.w3.org/International/articles/language-tags/>`_
392
+ Requires libraqm.
393
+
394
+ :param stroke_width: The width of the text stroke.
395
+
396
+ :param anchor: The text anchor alignment. Determines the relative location of
397
+ the anchor to the text. The default alignment is top left,
398
+ specifically ``la`` for horizontal text and ``lt`` for
399
+ vertical text. See :ref:`text-anchors` for details.
400
+
401
+ :return: ``(left, top, right, bottom)`` bounding box
402
+ """
403
+ _string_length_check(text)
404
+ size, offset = self.font.getsize(
405
+ text, mode, direction, features, language, anchor
406
+ )
407
+ left, top = offset[0] - stroke_width, offset[1] - stroke_width
408
+ width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
409
+ return left, top, left + width, top + height
410
+
411
+ def getmask(
412
+ self,
413
+ text,
414
+ mode="",
415
+ direction=None,
416
+ features=None,
417
+ language=None,
418
+ stroke_width=0,
419
+ anchor=None,
420
+ ink=0,
421
+ start=None,
422
+ ):
423
+ """
424
+ Create a bitmap for the text.
425
+
426
+ If the font uses antialiasing, the bitmap should have mode ``L`` and use a
427
+ maximum value of 255. If the font has embedded color data, the bitmap
428
+ should have mode ``RGBA``. Otherwise, it should have mode ``1``.
429
+
430
+ :param text: Text to render.
431
+ :param mode: Used by some graphics drivers to indicate what mode the
432
+ driver prefers; if empty, the renderer may return either
433
+ mode. Note that the mode is always a string, to simplify
434
+ C-level implementations.
435
+
436
+ .. versionadded:: 1.1.5
437
+
438
+ :param direction: Direction of the text. It can be 'rtl' (right to
439
+ left), 'ltr' (left to right) or 'ttb' (top to bottom).
440
+ Requires libraqm.
441
+
442
+ .. versionadded:: 4.2.0
443
+
444
+ :param features: A list of OpenType font features to be used during text
445
+ layout. This is usually used to turn on optional
446
+ font features that are not enabled by default,
447
+ for example 'dlig' or 'ss01', but can be also
448
+ used to turn off default font features for
449
+ example '-liga' to disable ligatures or '-kern'
450
+ to disable kerning. To get all supported
451
+ features, see
452
+ https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
453
+ Requires libraqm.
454
+
455
+ .. versionadded:: 4.2.0
456
+
457
+ :param language: Language of the text. Different languages may use
458
+ different glyph shapes or ligatures. This parameter tells
459
+ the font which language the text is in, and to apply the
460
+ correct substitutions as appropriate, if available.
461
+ It should be a `BCP 47 language code
462
+ <https://www.w3.org/International/articles/language-tags/>`_
463
+ Requires libraqm.
464
+
465
+ .. versionadded:: 6.0.0
466
+
467
+ :param stroke_width: The width of the text stroke.
468
+
469
+ .. versionadded:: 6.2.0
470
+
471
+ :param anchor: The text anchor alignment. Determines the relative location of
472
+ the anchor to the text. The default alignment is top left,
473
+ specifically ``la`` for horizontal text and ``lt`` for
474
+ vertical text. See :ref:`text-anchors` for details.
475
+
476
+ .. versionadded:: 8.0.0
477
+
478
+ :param ink: Foreground ink for rendering in RGBA mode.
479
+
480
+ .. versionadded:: 8.0.0
481
+
482
+ :param start: Tuple of horizontal and vertical offset, as text may render
483
+ differently when starting at fractional coordinates.
484
+
485
+ .. versionadded:: 9.4.0
486
+
487
+ :return: An internal PIL storage memory instance as defined by the
488
+ :py:mod:`PIL.Image.core` interface module.
489
+ """
490
+ return self.getmask2(
491
+ text,
492
+ mode,
493
+ direction=direction,
494
+ features=features,
495
+ language=language,
496
+ stroke_width=stroke_width,
497
+ anchor=anchor,
498
+ ink=ink,
499
+ start=start,
500
+ )[0]
501
+
502
+ def getmask2(
503
+ self,
504
+ text,
505
+ mode="",
506
+ direction=None,
507
+ features=None,
508
+ language=None,
509
+ stroke_width=0,
510
+ anchor=None,
511
+ ink=0,
512
+ start=None,
513
+ *args,
514
+ **kwargs,
515
+ ):
516
+ """
517
+ Create a bitmap for the text.
518
+
519
+ If the font uses antialiasing, the bitmap should have mode ``L`` and use a
520
+ maximum value of 255. If the font has embedded color data, the bitmap
521
+ should have mode ``RGBA``. Otherwise, it should have mode ``1``.
522
+
523
+ :param text: Text to render.
524
+ :param mode: Used by some graphics drivers to indicate what mode the
525
+ driver prefers; if empty, the renderer may return either
526
+ mode. Note that the mode is always a string, to simplify
527
+ C-level implementations.
528
+
529
+ .. versionadded:: 1.1.5
530
+
531
+ :param direction: Direction of the text. It can be 'rtl' (right to
532
+ left), 'ltr' (left to right) or 'ttb' (top to bottom).
533
+ Requires libraqm.
534
+
535
+ .. versionadded:: 4.2.0
536
+
537
+ :param features: A list of OpenType font features to be used during text
538
+ layout. This is usually used to turn on optional
539
+ font features that are not enabled by default,
540
+ for example 'dlig' or 'ss01', but can be also
541
+ used to turn off default font features for
542
+ example '-liga' to disable ligatures or '-kern'
543
+ to disable kerning. To get all supported
544
+ features, see
545
+ https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
546
+ Requires libraqm.
547
+
548
+ .. versionadded:: 4.2.0
549
+
550
+ :param language: Language of the text. Different languages may use
551
+ different glyph shapes or ligatures. This parameter tells
552
+ the font which language the text is in, and to apply the
553
+ correct substitutions as appropriate, if available.
554
+ It should be a `BCP 47 language code
555
+ <https://www.w3.org/International/articles/language-tags/>`_
556
+ Requires libraqm.
557
+
558
+ .. versionadded:: 6.0.0
559
+
560
+ :param stroke_width: The width of the text stroke.
561
+
562
+ .. versionadded:: 6.2.0
563
+
564
+ :param anchor: The text anchor alignment. Determines the relative location of
565
+ the anchor to the text. The default alignment is top left,
566
+ specifically ``la`` for horizontal text and ``lt`` for
567
+ vertical text. See :ref:`text-anchors` for details.
568
+
569
+ .. versionadded:: 8.0.0
570
+
571
+ :param ink: Foreground ink for rendering in RGBA mode.
572
+
573
+ .. versionadded:: 8.0.0
574
+
575
+ :param start: Tuple of horizontal and vertical offset, as text may render
576
+ differently when starting at fractional coordinates.
577
+
578
+ .. versionadded:: 9.4.0
579
+
580
+ :return: A tuple of an internal PIL storage memory instance as defined by the
581
+ :py:mod:`PIL.Image.core` interface module, and the text offset, the
582
+ gap between the starting coordinate and the first marking
583
+ """
584
+ _string_length_check(text)
585
+ if start is None:
586
+ start = (0, 0)
587
+ im = None
588
+ size = None
589
+
590
+ def fill(width, height):
591
+ nonlocal im, size
592
+
593
+ size = (width, height)
594
+ if Image.MAX_IMAGE_PIXELS is not None:
595
+ pixels = max(1, width) * max(1, height)
596
+ if pixels > 2 * Image.MAX_IMAGE_PIXELS:
597
+ return
598
+
599
+ im = Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
600
+ return im
601
+
602
+ offset = self.font.render(
603
+ text,
604
+ fill,
605
+ mode,
606
+ direction,
607
+ features,
608
+ language,
609
+ stroke_width,
610
+ anchor,
611
+ ink,
612
+ start[0],
613
+ start[1],
614
+ )
615
+ Image._decompression_bomb_check(size)
616
+ return im, offset
617
+
618
+ def font_variant(
619
+ self, font=None, size=None, index=None, encoding=None, layout_engine=None
620
+ ):
621
+ """
622
+ Create a copy of this FreeTypeFont object,
623
+ using any specified arguments to override the settings.
624
+
625
+ Parameters are identical to the parameters used to initialize this
626
+ object.
627
+
628
+ :return: A FreeTypeFont object.
629
+ """
630
+ if font is None:
631
+ try:
632
+ font = BytesIO(self.font_bytes)
633
+ except AttributeError:
634
+ font = self.path
635
+ return FreeTypeFont(
636
+ font=font,
637
+ size=self.size if size is None else size,
638
+ index=self.index if index is None else index,
639
+ encoding=self.encoding if encoding is None else encoding,
640
+ layout_engine=layout_engine or self.layout_engine,
641
+ )
642
+
643
+ def get_variation_names(self):
644
+ """
645
+ :returns: A list of the named styles in a variation font.
646
+ :exception OSError: If the font is not a variation font.
647
+ """
648
+ try:
649
+ names = self.font.getvarnames()
650
+ except AttributeError as e:
651
+ msg = "FreeType 2.9.1 or greater is required"
652
+ raise NotImplementedError(msg) from e
653
+ return [name.replace(b"\x00", b"") for name in names]
654
+
655
+ def set_variation_by_name(self, name):
656
+ """
657
+ :param name: The name of the style.
658
+ :exception OSError: If the font is not a variation font.
659
+ """
660
+ names = self.get_variation_names()
661
+ if not isinstance(name, bytes):
662
+ name = name.encode()
663
+ index = names.index(name) + 1
664
+
665
+ if index == getattr(self, "_last_variation_index", None):
666
+ # When the same name is set twice in a row,
667
+ # there is an 'unknown freetype error'
668
+ # https://savannah.nongnu.org/bugs/?56186
669
+ return
670
+ self._last_variation_index = index
671
+
672
+ self.font.setvarname(index)
673
+
674
+ def get_variation_axes(self):
675
+ """
676
+ :returns: A list of the axes in a variation font.
677
+ :exception OSError: If the font is not a variation font.
678
+ """
679
+ try:
680
+ axes = self.font.getvaraxes()
681
+ except AttributeError as e:
682
+ msg = "FreeType 2.9.1 or greater is required"
683
+ raise NotImplementedError(msg) from e
684
+ for axis in axes:
685
+ axis["name"] = axis["name"].replace(b"\x00", b"")
686
+ return axes
687
+
688
+ def set_variation_by_axes(self, axes):
689
+ """
690
+ :param axes: A list of values for each axis.
691
+ :exception OSError: If the font is not a variation font.
692
+ """
693
+ try:
694
+ self.font.setvaraxes(axes)
695
+ except AttributeError as e:
696
+ msg = "FreeType 2.9.1 or greater is required"
697
+ raise NotImplementedError(msg) from e
698
+
699
+
700
+ class TransposedFont:
701
+ """Wrapper for writing rotated or mirrored text"""
702
+
703
+ def __init__(self, font, orientation=None):
704
+ """
705
+ Wrapper that creates a transposed font from any existing font
706
+ object.
707
+
708
+ :param font: A font object.
709
+ :param orientation: An optional orientation. If given, this should
710
+ be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM,
711
+ Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or
712
+ Image.Transpose.ROTATE_270.
713
+ """
714
+ self.font = font
715
+ self.orientation = orientation # any 'transpose' argument, or None
716
+
717
+ def getmask(self, text, mode="", *args, **kwargs):
718
+ im = self.font.getmask(text, mode, *args, **kwargs)
719
+ if self.orientation is not None:
720
+ return im.transpose(self.orientation)
721
+ return im
722
+
723
+ def getbbox(self, text, *args, **kwargs):
724
+ # TransposedFont doesn't support getmask2, move top-left point to (0, 0)
725
+ # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
726
+ left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
727
+ width = right - left
728
+ height = bottom - top
729
+ if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
730
+ return 0, 0, height, width
731
+ return 0, 0, width, height
732
+
733
+ def getlength(self, text, *args, **kwargs):
734
+ if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
735
+ msg = "text length is undefined for text rotated by 90 or 270 degrees"
736
+ raise ValueError(msg)
737
+ return self.font.getlength(text, *args, **kwargs)
738
+
739
+
740
+ def load(filename):
741
+ """
742
+ Load a font file. This function loads a font object from the given
743
+ bitmap font file, and returns the corresponding font object.
744
+
745
+ :param filename: Name of font file.
746
+ :return: A font object.
747
+ :exception OSError: If the file could not be read.
748
+ """
749
+ f = ImageFont()
750
+ f._load_pilfont(filename)
751
+ return f
752
+
753
+
754
+ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
755
+ """
756
+ Load a TrueType or OpenType font from a file or file-like object,
757
+ and create a font object.
758
+ This function loads a font object from the given file or file-like
759
+ object, and creates a font object for a font of the given size.
760
+
761
+ Pillow uses FreeType to open font files. On Windows, be aware that FreeType
762
+ will keep the file open as long as the FreeTypeFont object exists. Windows
763
+ limits the number of files that can be open in C at once to 512, so if many
764
+ fonts are opened simultaneously and that limit is approached, an
765
+ ``OSError`` may be thrown, reporting that FreeType "cannot open resource".
766
+ A workaround would be to copy the file(s) into memory, and open that instead.
767
+
768
+ This function requires the _imagingft service.
769
+
770
+ :param font: A filename or file-like object containing a TrueType font.
771
+ If the file is not found in this filename, the loader may also
772
+ search in other directories, such as the :file:`fonts/`
773
+ directory on Windows or :file:`/Library/Fonts/`,
774
+ :file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
775
+ macOS.
776
+
777
+ :param size: The requested size, in pixels.
778
+ :param index: Which font face to load (default is first available face).
779
+ :param encoding: Which font encoding to use (default is Unicode). Possible
780
+ encodings include (see the FreeType documentation for more
781
+ information):
782
+
783
+ * "unic" (Unicode)
784
+ * "symb" (Microsoft Symbol)
785
+ * "ADOB" (Adobe Standard)
786
+ * "ADBE" (Adobe Expert)
787
+ * "ADBC" (Adobe Custom)
788
+ * "armn" (Apple Roman)
789
+ * "sjis" (Shift JIS)
790
+ * "gb " (PRC)
791
+ * "big5"
792
+ * "wans" (Extended Wansung)
793
+ * "joha" (Johab)
794
+ * "lat1" (Latin-1)
795
+
796
+ This specifies the character set to use. It does not alter the
797
+ encoding of any text provided in subsequent operations.
798
+ :param layout_engine: Which layout engine to use, if available:
799
+ :attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`.
800
+ If it is available, Raqm layout will be used by default.
801
+ Otherwise, basic layout will be used.
802
+
803
+ Raqm layout is recommended for all non-English text. If Raqm layout
804
+ is not required, basic layout will have better performance.
805
+
806
+ You can check support for Raqm layout using
807
+ :py:func:`PIL.features.check_feature` with ``feature="raqm"``.
808
+
809
+ .. versionadded:: 4.2.0
810
+ :return: A font object.
811
+ :exception OSError: If the file could not be read.
812
+ :exception ValueError: If the font size is not greater than zero.
813
+ """
814
+
815
+ def freetype(font):
816
+ return FreeTypeFont(font, size, index, encoding, layout_engine)
817
+
818
+ try:
819
+ return freetype(font)
820
+ except OSError:
821
+ if not is_path(font):
822
+ raise
823
+ ttf_filename = os.path.basename(font)
824
+
825
+ dirs = []
826
+ if sys.platform == "win32":
827
+ # check the windows font repository
828
+ # NOTE: must use uppercase WINDIR, to work around bugs in
829
+ # 1.5.2's os.environ.get()
830
+ windir = os.environ.get("WINDIR")
831
+ if windir:
832
+ dirs.append(os.path.join(windir, "fonts"))
833
+ elif sys.platform in ("linux", "linux2"):
834
+ lindirs = os.environ.get("XDG_DATA_DIRS")
835
+ if not lindirs:
836
+ # According to the freedesktop spec, XDG_DATA_DIRS should
837
+ # default to /usr/share
838
+ lindirs = "/usr/share"
839
+ dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
840
+ elif sys.platform == "darwin":
841
+ dirs += [
842
+ "/Library/Fonts",
843
+ "/System/Library/Fonts",
844
+ os.path.expanduser("~/Library/Fonts"),
845
+ ]
846
+
847
+ ext = os.path.splitext(ttf_filename)[1]
848
+ first_font_with_a_different_extension = None
849
+ for directory in dirs:
850
+ for walkroot, walkdir, walkfilenames in os.walk(directory):
851
+ for walkfilename in walkfilenames:
852
+ if ext and walkfilename == ttf_filename:
853
+ return freetype(os.path.join(walkroot, walkfilename))
854
+ elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
855
+ fontpath = os.path.join(walkroot, walkfilename)
856
+ if os.path.splitext(fontpath)[1] == ".ttf":
857
+ return freetype(fontpath)
858
+ if not ext and first_font_with_a_different_extension is None:
859
+ first_font_with_a_different_extension = fontpath
860
+ if first_font_with_a_different_extension:
861
+ return freetype(first_font_with_a_different_extension)
862
+ raise
863
+
864
+
865
+ def load_path(filename):
866
+ """
867
+ Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
868
+ bitmap font along the Python path.
869
+
870
+ :param filename: Name of font file.
871
+ :return: A font object.
872
+ :exception OSError: If the file could not be read.
873
+ """
874
+ for directory in sys.path:
875
+ if is_directory(directory):
876
+ if not isinstance(filename, str):
877
+ filename = filename.decode("utf-8")
878
+ try:
879
+ return load(os.path.join(directory, filename))
880
+ except OSError:
881
+ pass
882
+ msg = "cannot find font file"
883
+ raise OSError(msg)
884
+
885
+
886
+ def load_default(size=None):
887
+ """If FreeType support is available, load a version of Aileron Regular,
888
+ https://dotcolon.net/font/aileron, with a more limited character set.
889
+
890
+ Otherwise, load a "better than nothing" font.
891
+
892
+ .. versionadded:: 1.1.4
893
+
894
+ :param size: The font size of Aileron Regular.
895
+
896
+ .. versionadded:: 10.1.0
897
+
898
+ :return: A font object.
899
+ """
900
+ if core.__class__.__name__ == "module" or size is not None:
901
+ f = truetype(
902
+ BytesIO(
903
+ base64.b64decode(
904
+ b"""
905
+ AAEAAAAPAIAAAwBwRkZUTYwDlUAAADFoAAAAHEdERUYAqADnAAAo8AAAACRHUE9ThhmITwAAKfgAA
906
+ AduR1NVQnHxefoAACkUAAAA4k9TLzJovoHLAAABeAAAAGBjbWFw5lFQMQAAA6gAAAGqZ2FzcP//AA
907
+ MAACjoAAAACGdseWYmRXoPAAAGQAAAHfhoZWFkE18ayQAAAPwAAAA2aGhlYQboArEAAAE0AAAAJGh
908
+ tdHjjERZ8AAAB2AAAAdBsb2NhuOexrgAABVQAAADqbWF4cAC7AEYAAAFYAAAAIG5hbWUr+h5lAAAk
909
+ OAAAA6Jwb3N0D3oPTQAAJ9wAAAEKAAEAAAABGhxJDqIhXw889QALA+gAAAAA0Bqf2QAAAADhCh2h/
910
+ 2r/LgOxAyAAAAAIAAIAAAAAAAAAAQAAA8r/GgAAA7j/av9qA7EAAQAAAAAAAAAAAAAAAAAAAHQAAQ
911
+ AAAHQAQwAFAAAAAAACAAAAAQABAAAAQAAAAAAAAAADAfoBkAAFAAgCigJYAAAASwKKAlgAAAFeADI
912
+ BPgAAAAAFAAAAAAAAAAAAAAcAAAAAAAAAAAAAAABVS1dOAEAAIPsCAwL/GgDIA8oA5iAAAJMAAAAA
913
+ AhICsgAAACAAAwH0AAAAAAAAAU0AAADYAAAA8gA5AVMAVgJEAEYCRAA1AuQAKQKOAEAAsAArATsAZ
914
+ AE7AB4CMABVAkQAUADc/+EBEgAgANwAJQEv//sCRAApAkQAggJEADwCRAAtAkQAIQJEADkCRAArAk
915
+ QAMgJEACwCRAAxANwAJQDc/+ECRABnAkQAUAJEAEQB8wAjA1QANgJ/AB0CcwBkArsALwLFAGQCSwB
916
+ kAjcAZALGAC8C2gBkAQgAZAIgADcCYQBkAj8AZANiAGQCzgBkAuEALwJWAGQC3QAvAmsAZAJJADQC
917
+ ZAAiAqoAXgJuACADuAAaAnEAGQJFABMCTwAuATMAYgEv//sBJwAiAkQAUAH0ADIBLAApAhMAJAJjA
918
+ EoCEQAeAmcAHgIlAB4BIgAVAmcAHgJRAEoA7gA+AOn/8wIKAEoA9wBGA1cASgJRAEoCSgAeAmMASg
919
+ JnAB4BSgBKAcsAGAE5ABQCUABCAgIAAQMRAAEB4v/6AgEAAQHOABQBLwBAAPoAYAEvACECRABNA0Y
920
+ AJAItAHgBKgAcAkQAUAEsAHQAygAgAi0AOQD3ADYA9wAWAaEANgGhABYCbAAlAYMAeAGDADkA6/9q
921
+ AhsAFAIKABUB/QAVAAAAAwAAAAMAAAAcAAEAAAAAAKQAAwABAAAAHAAEAIgAAAAeABAAAwAOAH4Aq
922
+ QCrALEAtAC3ALsgGSAdICYgOiBEISL7Av//AAAAIACpAKsAsAC0ALcAuyAYIBwgJiA5IEQhIvsB//
923
+ //4/+5/7j/tP+y/7D/reBR4E/gR+A14CzfTwVxAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
924
+ AAAAAAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMEBQYHCAkKCwwNDg8QERIT
925
+ FBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMT
926
+ U5PUFFSU1RVVldYWVpbXF1eX2BhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAA
927
+ AAAAAAYnFmAAAAAABlAAAAAAAAAAAAAAAAAAAAAAAAAAAAY2htAAAAAAAAAABrbGlqAAAAAHAAbm9
928
+ ycwBnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmACYAJgAmAD4AUgCCAMoBCgFO
929
+ AVwBcgGIAaYBvAHKAdYB6AH2AgwCIAJKAogCpgLWAw4DIgNkA5wDugPUA+gD/AQQBEYEogS8BPoFJ
930
+ gVSBWoFgAWwBcoF1gX6BhQGJAZMBmgGiga0BuIHGgdUB2YHkAeiB8AH3AfyCAoIHAgqCDoITghcCG
931
+ oIogjSCPoJKglYCXwJwgnqCgIKKApACl4Klgq8CtwLDAs8C1YLjAuyC9oL7gwMDCYMSAxgDKAMrAz
932
+ qDQoNTA1mDYQNoA2uDcAN2g3oDfYODA4iDkoOXA5sDnoOnA7EDvwAAAAFAAAAAAH0ArwAAwAGAAkA
933
+ DAAPAAAxESERAxMhExcRASELARETAfT6qv6syKr+jgFUqsiqArz9RAGLAP/+1P8B/v3VAP8BLP4CA
934
+ P8AAgA5//IAuQKyAAMACwAANyMDMwIyFhQGIiY0oE4MZk84JCQ4JLQB/v3AJDgkJDgAAgBWAeUBPA
935
+ LfAAMABwAAEyMnMxcjJzOmRgpagkYKWgHl+vr6AAAAAAIARgAAAf4CsgAbAB8AAAEHMxUjByM3Iwc
936
+ jNyM1MzcjNTM3MwczNzMHMxUrAQczAZgdZXEvOi9bLzovWmYdZXEvOi9bLzovWp9bHlsBn4w429vb
937
+ 2ziMONvb29s4jAAAAAMANf+mAg4DDAAfACYALAAAJRQGBxUjNS4BJzMeARcRLgE0Njc1MxUeARcjJ
938
+ icVHgEBFBYXNQ4BExU+ATU0Ag5xWDpgcgRcBz41Xl9oVTpVYwpcC1ttXP6cLTQuM5szOrVRZwlOTQ
939
+ ZqVzZECAEAGlukZAlOTQdrUG8O7iNlAQgxNhDlCDj+8/YGOjReAAAAAAUAKf/yArsCvAAHAAsAFQA
940
+ dACcAABIyFhQGIiY0EyMBMwQiBhUUFjI2NTQSMhYUBiImNDYiBhUUFjI2NTR5iFBQiFCVVwHAV/5c
941
+ OiMjOiPmiFBQiFCxOiMjOiMCvFaSVlaS/ZoCsjIzMC80NC8w/uNWklZWkhozMC80NC8wAAAAAgBA/
942
+ /ICbgLAACIALgAAARUjEQYjIiY1NDY3LgE1NDYzMhcVJiMiBhUUFhcWOwE1MxUFFBYzMjc1IyIHDg
943
+ ECbmBcYYOOVkg7R4hsQjY4Q0RNRD4SLDxW/pJUXzksPCkUUk0BgUb+zBVUZ0BkDw5RO1huCkULQzp
944
+ COAMBcHDHRz0J/AIHRQAAAAEAKwHlAIUC3wADAAATIycze0YKWgHl+gAAAAABAGT/sAEXAwwACQAA
945
+ EzMGEBcjLgE0Nt06dXU6OUBAAwzG/jDGVePs4wAAAAEAHv+wANEDDAAJAAATMx4BFAYHIzYQHjo5Q
946
+ EA5OnUDDFXj7ONVxgHQAAAAAQBVAFIB2wHbAA4AAAE3FwcXBycHJzcnNxcnMwEtmxOfcTJjYzJxnx
947
+ ObCj4BKD07KYolmZkliik7PbMAAQBQAFUB9AIlAAsAAAEjFSM1IzUzNTMVMwH0tTq1tTq1AR/Kyjj
948
+ OzgAAAAAB/+H/iACMAGQABAAANwcjNzOMWlFOXVrS3AAAAQAgAP8A8gE3AAMAABMjNTPy0tIA/zgA
949
+ AQAl//IApQByAAcAADYyFhQGIiY0STgkJDgkciQ4JCQ4AAAAAf/7/+IBNALQAAMAABcjEzM5Pvs+H
950
+ gLuAAAAAAIAKf/yAhsCwAADAAcAABIgECA2IBAgKQHy/g5gATL+zgLA/TJEAkYAAAAAAQCCAAABlg
951
+ KyAAgAAAERIxEHNTc2MwGWVr6SIygCsv1OAldxW1sWAAEAPAAAAg4CwAAZAAA3IRUhNRM+ATU0JiM
952
+ iDwEjNz4BMzIWFRQGB7kBUv4x+kI2QTt+EAFWAQp8aGVtSl5GRjEA/0RVLzlLmAoKa3FsUkNxXQAA
953
+ AAEALf/yAhYCwAAqAAABHgEVFAYjIi8BMxceATMyNjU0KwE1MzI2NTQmIyIGDwEjNz4BMzIWFRQGA
954
+ YxBSZJo2RUBVgEHV0JBUaQREUBUQzc5TQcBVgEKfGhfcEMBbxJbQl1x0AoKRkZHPn9GSD80QUVCCg
955
+ pfbGBPOlgAAAACACEAAAIkArIACgAPAAAlIxUjNSE1ATMRMyMRBg8BAiRXVv6qAVZWV60dHLCurq4
956
+ rAdn+QgFLMibzAAABADn/8gIZArIAHQAAATIWFRQGIyIvATMXFjMyNjU0JiMiByMTIRUhBzc2ATNv
957
+ d5Fl1RQBVgIad0VSTkVhL1IwAYj+vh8rMAHHgGdtgcUKCoFXTU5bYgGRRvAuHQAAAAACACv/8gITA
958
+ sAAFwAjAAABMhYVFAYjIhE0NjMyFh8BIycmIyIDNzYTMjY1NCYjIgYVFBYBLmp7imr0l3RZdAgBXA
959
+ IYZ5wKJzU6QVNJSz5SUAHSgWltiQFGxcNlVQoKdv7sPiz+ZF1LTmJbU0lhAAAAAQAyAAACGgKyAAY
960
+ AAAEVASMBITUCGv6oXAFL/oECsij9dgJsRgAAAAMALP/xAhgCwAAWACAALAAAAR4BFRQGIyImNTQ2
961
+ Ny4BNTQ2MhYVFAYmIgYVFBYyNjU0AzI2NTQmIyIGFRQWAZQ5S5BmbIpPOjA7ecp5P2F8Q0J8RIVJS
962
+ 0pLTEtOAW0TXTxpZ2ZqPF0SE1A3VWVlVTdQ/UU0N0RENzT9/ko+Ok1NOj1LAAIAMf/yAhkCwAAXAC
963
+ MAAAEyERQGIyImLwEzFxYzMhMHBiMiJjU0NhMyNjU0JiMiBhUUFgEl9Jd0WXQIAVwCGGecCic1SWp
964
+ 7imo+UlBAQVNJAsD+usXDZVUKCnYBFD4sgWltif5kW1NJYV1LTmIAAAACACX/8gClAiAABwAPAAAS
965
+ MhYUBiImNBIyFhQGIiY0STgkJDgkJDgkJDgkAiAkOCQkOP52JDgkJDgAAAAC/+H/iAClAiAABwAMA
966
+ AASMhYUBiImNBMHIzczSTgkJDgkaFpSTl4CICQ4JCQ4/mba5gAAAQBnAB4B+AH0AAYAAAENARUlNS
967
+ UB+P6qAVb+bwGRAbCmpkbJRMkAAAIAUAC7AfQBuwADAAcAAAEhNSERITUhAfT+XAGk/lwBpAGDOP8
968
+ AOAABAEQAHgHVAfQABgAAARUFNS0BNQHV/m8BVv6qAStEyUSmpkYAAAAAAgAj//IB1ALAABgAIAAA
969
+ ATIWFRQHDgEHIz4BNz4BNTQmIyIGByM+ARIyFhQGIiY0AQRibmktIAJWBSEqNig+NTlHBFoDezQ4J
970
+ CQ4JALAZ1BjaS03JS1DMD5LLDQ/SUVgcv2yJDgkJDgAAAAAAgA2/5gDFgKYADYAQgAAAQMGFRQzMj
971
+ Y1NCYjIg4CFRQWMzI2NxcGIyImNTQ+AjMyFhUUBiMiJwcGIyImNTQ2MzIfATcHNzYmIyIGFRQzMjY
972
+ Cej8EJjJJlnBAfGQ+oHtAhjUYg5OPx0h2k06Os3xRWQsVLjY5VHtdPBwJETcJDyUoOkZEJz8B0f74
973
+ EQ8kZl6EkTFZjVOLlyknMVm1pmCiaTq4lX6CSCknTVRmmR8wPdYnQzxuSWVGAAIAHQAAAncCsgAHA
974
+ AoAACUjByMTMxMjATMDAcj+UVz4dO5d/sjPZPT0ArL9TgE6ATQAAAADAGQAAAJMArIAEAAbACcAAA
975
+ EeARUUBgcGKwERMzIXFhUUJRUzMjc2NTQnJiMTPgE1NCcmKwEVMzIBvkdHZkwiNt7LOSGq/oeFHBt
976
+ hahIlSTM+cB8Yj5UWAW8QT0VYYgwFArIEF5Fv1eMED2NfDAL93AU+N24PBP0AAAAAAQAv//ICjwLA
977
+ ABsAAAEyFh8BIycmIyIGFRQWMzI/ATMHDgEjIiY1NDYBdX+PCwFWAiKiaHx5ZaIiAlYBCpWBk6a0A
978
+ sCAagoKpqN/gaOmCgplhcicn8sAAAIAZAAAAp8CsgAMABkAAAEeARUUBgcGKwERMzITPgE1NCYnJi
979
+ sBETMyAY59lJp8IzXN0jUVWmdjWRs5d3I4Aq4QqJWUug8EArL9mQ+PeHGHDgX92gAAAAABAGQAAAI
980
+ vArIACwAAJRUhESEVIRUhFSEVAi/+NQHB/pUBTf6zRkYCskbwRvAAAAABAGQAAAIlArIACQAAExUh
981
+ FSERIxEhFboBQ/69VgHBAmzwRv7KArJGAAAAAAEAL//yAo8CwAAfAAABMxEjNQcGIyImNTQ2MzIWH
982
+ wEjJyYjIgYVFBYzMjY1IwGP90wfPnWTprSSf48LAVYCIqJofHllVG+hAU3+s3hARsicn8uAagoKpq
983
+ N/gaN1XAAAAAEAZAAAAowCsgALAAABESMRIREjETMRIRECjFb+hFZWAXwCsv1OAS7+0gKy/sQBPAA
984
+ AAAABAGQAAAC6ArIAAwAAMyMRM7pWVgKyAAABADf/8gHoArIAEwAAAREUBw4BIyImLwEzFxYzMjc2
985
+ NREB6AIFcGpgbQIBVgIHfXQKAQKy/lYxIltob2EpKYyEFD0BpwAAAAABAGQAAAJ0ArIACwAACQEjA
986
+ wcVIxEzEQEzATsBJ3ntQlZWAVVlAWH+nwEnR+ACsv6RAW8AAQBkAAACLwKyAAUAACUVIREzEQIv/j
987
+ VWRkYCsv2UAAABAGQAAAMUArIAFAAAAREjETQ3BgcDIwMmJxYVESMRMxsBAxRWAiMxemx8NxsCVo7
988
+ MywKy/U4BY7ZLco7+nAFmoFxLtP6dArL9lwJpAAAAAAEAZAAAAoACsgANAAAhIwEWFREjETMBJjUR
989
+ MwKAhP67A1aEAUUDVAJeeov+pwKy/aJ5jAFZAAAAAgAv//ICuwLAAAkAEwAAEiAWFRQGICY1NBIyN
990
+ jU0JiIGFRTbATSsrP7MrNrYenrYegLAxaKhxsahov47nIeIm5uIhwACAGQAAAJHArIADgAYAAABHg
991
+ EVFAYHBisBESMRMzITNjQnJisBETMyAZRUX2VOHzuAVtY7GlxcGDWIiDUCrgtnVlVpCgT+5gKy/rU
992
+ V1BUF/vgAAAACAC//zAK9AsAAEgAcAAAlFhcHJiMiBwYjIiY1NDYgFhUUJRQWMjY1NCYiBgI9PUMx
993
+ UDcfKh8omqysATSs/dR62Hp62HpICTg7NgkHxqGixcWitbWHnJyHiJubAAIAZAAAAlgCsgAXACMAA
994
+ CUWFyMmJyYnJisBESMRMzIXHgEVFAYHFiUzMjc+ATU0JyYrAQIqDCJfGQwNWhAhglbiOx9QXEY1Tv
995
+ 6bhDATMj1lGSyMtYgtOXR0BwH+1wKyBApbU0BSESRAAgVAOGoQBAABADT/8gIoAsAAJQAAATIWFyM
996
+ uASMiBhUUFhceARUUBiMiJiczHgEzMjY1NCYnLgE1NDYBOmd2ClwGS0E6SUNRdW+HZnKKC1wPWkQ9
997
+ Uk1cZGuEAsBwXUJHNjQ3OhIbZVZZbm5kREo+NT5DFRdYUFdrAAAAAAEAIgAAAmQCsgAHAAABIxEjE
998
+ SM1IQJk9lb2AkICbP2UAmxGAAEAXv/yAmQCsgAXAAABERQHDgEiJicmNREzERQXHgEyNjc2NRECZA
999
+ IIgfCBCAJWAgZYmlgGAgKy/k0qFFxzc1wUKgGz/lUrEkRQUEQSKwGrAAAAAAEAIAAAAnoCsgAGAAA
1000
+ hIwMzGwEzAYJ07l3N1FwCsv2PAnEAAAEAGgAAA7ECsgAMAAABAyMLASMDMxsBMxsBA7HAcZyicrZi
1001
+ kaB0nJkCsv1OAlP9rQKy/ZsCW/2kAmYAAAEAGQAAAm8CsgALAAAhCwEjEwMzGwEzAxMCCsrEY/bkY
1002
+ re+Y/D6AST+3AFcAVb+5gEa/q3+oQAAAQATAAACUQKyAAgAAAERIxEDMxsBMwFdVvRjwLphARD+8A
1003
+ EQAaL+sQFPAAABAC4AAAI5ArIACQAAJRUhNQEhNSEVAQI5/fUBof57Aen+YUZGQgIqRkX92QAAAAA
1004
+ BAGL/sAEFAwwABwAAARUjETMVIxEBBWlpowMMOP0UOANcAAAB//v/4gE0AtAAAwAABSMDMwE0Pvs+
1005
+ HgLuAAAAAQAi/7AAxQMMAAcAABcjNTMRIzUzxaNpaaNQOALsOAABAFAA1wH0AmgABgAAJQsBIxMzE
1006
+ wGwjY1GsESw1wFZ/qcBkf5vAAAAAQAy/6oBwv/iAAMAAAUhNSEBwv5wAZBWOAAAAAEAKQJEALYCsg
1007
+ ADAAATIycztjhVUAJEbgAAAAACACT/8gHQAiAAHQAlAAAhJwcGIyImNTQ2OwE1NCcmIyIHIz4BMzI
1008
+ XFh0BFBcnMjY9ASYVFAF6CR0wVUtgkJoiAgdgaQlaBm1Zrg4DCuQ9R+5MOSFQR1tbDiwUUXBUXowf
1009
+ J8c9SjRORzYSgVwAAAAAAgBK//ICRQLfABEAHgAAATIWFRQGIyImLwEVIxEzETc2EzI2NTQmIyIGH
1010
+ QEUFgFUcYCVbiNJEyNWVigySElcU01JXmECIJd4i5QTEDRJAt/+3jkq/hRuZV55ZWsdX14AAQAe//
1011
+ IB9wIgABgAAAEyFhcjJiMiBhUUFjMyNjczDgEjIiY1NDYBF152DFocbEJXU0A1Rw1aE3pbaoKQAiB
1012
+ oWH5qZm1tPDlaXYuLgZcAAAACAB7/8gIZAt8AEQAeAAABESM1BwYjIiY1NDYzMhYfAREDMjY9ATQm
1013
+ IyIGFRQWAhlWKDJacYCVbiNJEyOnSV5hQUlcUwLf/SFVOSqXeIuUExA0ARb9VWVrHV9ebmVeeQACA
1014
+ B7/8gH9AiAAFQAbAAABFAchHgEzMjY3Mw4BIyImNTQ2MzIWJyIGByEmAf0C/oAGUkA1SwlaD4FXbI
1015
+ WObmt45UBVBwEqDQEYFhNjWD84W16Oh3+akU9aU60AAAEAFQAAARoC8gAWAAATBh0BMxUjESMRIzU
1016
+ zNTQ3PgEzMhcVJqcDbW1WOTkDB0k8Hx5oAngVITRC/jQBzEIsJRs5PwVHEwAAAAIAHv8uAhkCIAAi
1017
+ AC8AAAERFAcOASMiLwEzFx4BMzI2NzY9AQcGIyImNTQ2MzIWHwE1AzI2PQE0JiMiBhUUFgIZAQSEd
1018
+ NwRAVcBBU5DTlUDASgyWnGAlW4jSRMjp0leYUFJXFMCEv5wSh1zeq8KCTI8VU0ZIQk5Kpd4i5QTED
1019
+ RJ/iJlax1fXm5lXnkAAQBKAAACCgLkABcAAAEWFREjETQnLgEHDgEdASMRMxE3NjMyFgIIAlYCBDs
1020
+ 6RVRWViE5UVViAYUbQP7WASQxGzI7AQJyf+kC5P7TPSxUAAACAD4AAACsAsAABwALAAASMhYUBiIm
1021
+ NBMjETNeLiAgLiBiVlYCwCAuICAu/WACEgAC//P/LgCnAsAABwAVAAASMhYUBiImNBcRFAcGIyInN
1022
+ RY3NjURWS4gIC4gYgMLcRwNSgYCAsAgLiAgLo79wCUbZAJGBzMOHgJEAAAAAQBKAAACCALfAAsAAC
1023
+ EnBxUjETMREzMHEwGTwTJWVvdu9/rgN6kC3/4oAQv6/ugAAQBG//wA3gLfAA8AABMRFBceATcVBiM
1024
+ iJicmNRGcAQIcIxkkKi4CAQLf/bkhERoSBD4EJC8SNAJKAAAAAQBKAAADEAIgACQAAAEWFREjETQn
1025
+ JiMiFREjETQnJiMiFREjETMVNzYzMhYXNzYzMhYDCwVWBAxedFYEDF50VlYiJko7ThAvJkpEVAGfI
1026
+ jn+vAEcQyRZ1v76ARxDJFnW/voCEk08HzYtRB9HAAAAAAEASgAAAgoCIAAWAAABFhURIxE0JyYjIg
1027
+ YdASMRMxU3NjMyFgIIAlYCCXBEVVZWITlRVWIBhRtA/tYBJDEbbHR/6QISWz0sVAAAAAACAB7/8gI
1028
+ sAiAABwARAAASIBYUBiAmNBIyNjU0JiIGFRSlAQCHh/8Ah7ieWlqeWgIgn/Cfn/D+s3ZfYHV1YF8A
1029
+ AgBK/zwCRQIgABEAHgAAATIWFRQGIyImLwERIxEzFTc2EzI2NTQmIyIGHQEUFgFUcYCVbiNJEyNWV
1030
+ igySElcU01JXmECIJd4i5QTEDT+8wLWVTkq/hRuZV55ZWsdX14AAgAe/zwCGQIgABEAHgAAAREjEQ
1031
+ cGIyImNTQ2MzIWHwE1AzI2PQE0JiMiBhUUFgIZVigyWnGAlW4jSRMjp0leYUFJXFMCEv0qARk5Kpd
1032
+ 4i5QTEDRJ/iJlax1fXm5lXnkAAQBKAAABPgIeAA0AAAEyFxUmBhURIxEzFTc2ARoWDkdXVlYwIwIe
1033
+ B0EFVlf+0gISU0cYAAEAGP/yAa0CIAAjAAATMhYXIyYjIgYVFBYXHgEVFAYjIiYnMxYzMjY1NCYnL
1034
+ gE1NDbkV2MJWhNdKy04PF1XbVhWbgxaE2ktOjlEUllkAiBaS2MrJCUoEBlPQkhOVFZoKCUmLhIWSE
1035
+ BIUwAAAAEAFP/4ARQCiQAXAAATERQXHgE3FQYjIiYnJjURIzUzNTMVMxWxAQMmMx8qMjMEAUdHVmM
1036
+ BzP7PGw4mFgY/BSwxDjQBNUJ7e0IAAAABAEL/8gICAhIAFwAAAREjNQcGIyImJyY1ETMRFBceATMy
1037
+ Nj0BAgJWITlRT2EKBVYEBkA1RFECEv3uWj4qTToiOQE+/tIlJC43c4DpAAAAAAEAAQAAAfwCEgAGA
1038
+ AABAyMDMxsBAfzJaclfop8CEv3uAhL+LQHTAAABAAEAAAMLAhIADAAAAQMjCwEjAzMbATMbAQMLqW
1039
+ Z2dmapY3t0a3Z7AhL97gG+/kICEv5AAcD+QwG9AAAB//oAAAHWAhIACwAAARMjJwcjEwMzFzczARq
1040
+ 8ZIuKY763ZoWFYwEO/vLV1QEMAQbNzQAAAQAB/y4B+wISABEAAAEDDgEjIic1FjMyNj8BAzMbAQH7
1041
+ 2iFZQB8NDRIpNhQH02GenQIS/cFVUAJGASozEwIt/i4B0gABABQAAAGxAg4ACQAAJRUhNQEhNSEVA
1042
+ QGx/mMBNP7iAYL+zkREQgGIREX+ewAAAAABAED/sAEOAwwALAAAASMiBhUUFxYVFAYHHgEVFAcGFR
1043
+ QWOwEVIyImNTQ3NjU0JzU2NTQnJjU0NjsBAQ4MKiMLDS4pKS4NCyMqDAtERAwLUlILDERECwLUGBk
1044
+ WTlsgKzUFBTcrIFtOFhkYOC87GFVMIkUIOAhFIkxVGDsvAAAAAAEAYP84AJoDIAADAAAXIxEzmjo6
1045
+ yAPoAAEAIf+wAO8DDAAsAAATFQYVFBcWFRQGKwE1MzI2NTQnJjU0NjcuATU0NzY1NCYrATUzMhYVF
1046
+ AcGFRTvUgsMREQLDCojCw0uKSkuDQsjKgwLREQMCwF6OAhFIkxVGDsvOBgZFk5bICs1BQU3KyBbTh
1047
+ YZGDgvOxhVTCJFAAABAE0A3wH2AWQAEwAAATMUIyImJyYjIhUjNDMyFhcWMzIBvjhuGywtQR0xOG4
1048
+ bLC1BHTEBZIURGCNMhREYIwAAAwAk/94DIgLoAAcAEQApAAAAIBYQBiAmECQgBhUUFiA2NTQlMhYX
1049
+ IyYjIgYUFjMyNjczDgEjIiY1NDYBAQFE3d3+vN0CB/7wubkBELn+xVBnD1wSWDo+QTcqOQZcEmZWX
1050
+ HN2Aujg/rbg4AFKpr+Mjb6+jYxbWEldV5ZZNShLVn5na34AAgB4AFIB9AGeAAUACwAAAQcXIyc3Mw
1051
+ cXIyc3AUqJiUmJifOJiUmJiQGepqampqampqYAAAIAHAHSAQ4CwAAHAA8AABIyFhQGIiY0NiIGFBY
1052
+ yNjRgakREakSTNCEhNCECwEJqQkJqCiM4IyM4AAAAAAIAUAAAAfQCCwALAA8AAAEzFSMVIzUjNTM1
1053
+ MxMhNSEBP7W1OrW1OrX+XAGkAVs4tLQ4sP31OAAAAQB0AkQBAQKyAAMAABMjNzOsOD1QAkRuAAAAA
1054
+ AEAIADsAKoBdgAHAAASMhYUBiImNEg6KCg6KAF2KDooKDoAAAIAOQBSAbUBngAFAAsAACUHIzcnMw
1055
+ UHIzcnMwELiUmJiUkBM4lJiYlJ+KampqampqYAAAABADYB5QDhAt8ABAAAEzczByM2Xk1OXQHv8Po
1056
+ AAQAWAeUAwQLfAAQAABMHIzczwV5NTl0C1fD6AAIANgHlAYsC3wAEAAkAABM3MwcjPwEzByM2Xk1O
1057
+ XapeTU5dAe/w+grw+gAAAgAWAeUBawLfAAQACQAAEwcjNzMXByM3M8FeTU5dql5NTl0C1fD6CvD6A
1058
+ AADACX/8gI1AHIABwAPABcAADYyFhQGIiY0NjIWFAYiJjQ2MhYUBiImNEk4JCQ4JOw4JCQ4JOw4JC
1059
+ Q4JHIkOCQkOCQkOCQkOCQkOCQkOAAAAAEAeABSAUoBngAFAAABBxcjJzcBSomJSYmJAZ6mpqamAAA
1060
+ AAAEAOQBSAQsBngAFAAAlByM3JzMBC4lJiYlJ+KampgAAAf9qAAABgQKyAAMAACsBATM/VwHAVwKy
1061
+ AAAAAAIAFAHIAdwClAAHABQAABMVIxUjNSM1BRUjNwcjJxcjNTMXN9pKMkoByDICKzQqATJLKysCl
1062
+ CmjoykBy46KiY3Lm5sAAQAVAAABvALyABgAAAERIxEjESMRIzUzNTQ3NjMyFxUmBgcGHQEBvFbCVj
1063
+ k5AxHHHx5iVgcDAg798gHM/jQBzEIOJRuWBUcIJDAVIRYAAAABABX//AHkAvIAJQAAJR4BNxUGIyI
1064
+ mJyY1ESYjIgcGHQEzFSMRIxEjNTM1NDc2MzIXERQBowIcIxkkKi4CAR4nXgwDbW1WLy8DEbNdOmYa
1065
+ EQQ/BCQvEjQCFQZWFSEWQv40AcxCDiUblhP9uSEAAAAAAAAWAQ4AAQAAAAAAAAATACgAAQAAAAAAA
1066
+ QAHAEwAAQAAAAAAAgAHAGQAAQAAAAAAAwAaAKIAAQAAAAAABAAHAM0AAQAAAAAABQA8AU8AAQAAAA
1067
+ AABgAPAawAAQAAAAAACAALAdQAAQAAAAAACQALAfgAAQAAAAAACwAXAjQAAQAAAAAADAAXAnwAAwA
1068
+ BBAkAAAAmAAAAAwABBAkAAQAOADwAAwABBAkAAgAOAFQAAwABBAkAAwA0AGwAAwABBAkABAAOAL0A
1069
+ AwABBAkABQB4ANUAAwABBAkABgAeAYwAAwABBAkACAAWAbwAAwABBAkACQAWAeAAAwABBAkACwAuA
1070
+ gQAAwABBAkADAAuAkwATgBvACAAUgBpAGcAaAB0AHMAIABSAGUAcwBlAHIAdgBlAGQALgAATm8gUm
1071
+ lnaHRzIFJlc2VydmVkLgAAQQBpAGwAZQByAG8AbgAAQWlsZXJvbgAAUgBlAGcAdQBsAGEAcgAAUmV
1072
+ ndWxhcgAAMQAuADEAMAAyADsAVQBLAFcATgA7AEEAaQBsAGUAcgBvAG4ALQBSAGUAZwB1AGwAYQBy
1073
+ AAAxLjEwMjtVS1dOO0FpbGVyb24tUmVndWxhcgAAQQBpAGwAZQByAG8AbgAAQWlsZXJvbgAAVgBlA
1074
+ HIAcwBpAG8AbgAgADEALgAxADAAMgA7AFAAUwAgADAAMAAxAC4AMQAwADIAOwBoAG8AdABjAG8Abg
1075
+ B2ACAAMQAuADAALgA3ADAAOwBtAGEAawBlAG8AdABmAC4AbABpAGIAMgAuADUALgA1ADgAMwAyADk
1076
+ AAFZlcnNpb24gMS4xMDI7UFMgMDAxLjEwMjtob3Rjb252IDEuMC43MDttYWtlb3RmLmxpYjIuNS41
1077
+ ODMyOQAAQQBpAGwAZQByAG8AbgAtAFIAZQBnAHUAbABhAHIAAEFpbGVyb24tUmVndWxhcgAAUwBvA
1078
+ HIAYQAgAFMAYQBnAGEAbgBvAABTb3JhIFNhZ2FubwAAUwBvAHIAYQAgAFMAYQBnAGEAbgBvAABTb3
1079
+ JhIFNhZ2FubwAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGQAbwB0AGMAbwBsAG8AbgAuAG4AZQB0AAB
1080
+ odHRwOi8vd3d3LmRvdGNvbG9uLm5ldAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGQAbwB0AGMAbwBs
1081
+ AG8AbgAuAG4AZQB0AABodHRwOi8vd3d3LmRvdGNvbG9uLm5ldAAAAAACAAAAAAAA/4MAMgAAAAAAA
1082
+ AAAAAAAAAAAAAAAAAAAAHQAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATAB
1083
+ QAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAA
1084
+ xADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0A
1085
+ TgBPAFAAUQBSAFMAVABVAFYAVwBYAFkAWgBbAFwAXQBeAF8AYABhAIsAqQCDAJMAjQDDAKoAtgC3A
1086
+ LQAtQCrAL4AvwC8AIwAwADBAAAAAAAB//8AAgABAAAADAAAABwAAAACAAIAAwBxAAEAcgBzAAIABA
1087
+ AAAAIAAAABAAAACgBMAGYAAkRGTFQADmxhdG4AGgAEAAAAAP//AAEAAAAWAANDQVQgAB5NT0wgABZ
1088
+ ST00gABYAAP//AAEAAAAA//8AAgAAAAEAAmxpZ2EADmxvY2wAFAAAAAEAAQAAAAEAAAACAAYAEAAG
1089
+ AAAAAgASADQABAAAAAEATAADAAAAAgAQABYAAQAcAAAAAQABAE8AAQABAGcAAQABAE8AAwAAAAIAE
1090
+ AAWAAEAHAAAAAEAAQAvAAEAAQBnAAEAAQAvAAEAGgABAAgAAgAGAAwAcwACAE8AcgACAEwAAQABAE
1091
+ kAAAABAAAACgBGAGAAAkRGTFQADmxhdG4AHAAEAAAAAP//AAIAAAABABYAA0NBVCAAFk1PTCAAFlJ
1092
+ PTSAAFgAA//8AAgAAAAEAAmNwc3AADmtlcm4AFAAAAAEAAAAAAAEAAQACAAYADgABAAAAAQASAAIA
1093
+ AAACAB4ANgABAAoABQAFAAoAAgABACQAPQAAAAEAEgAEAAAAAQAMAAEAOP/nAAEAAQAkAAIGigAEA
1094
+ AAFJAXKABoAGQAA//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1095
+ AAAAD/sv+4/+z/7v/MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1096
+ AAAAAAAD/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9T/6AAAAAD/8QAA
1097
+ ABD/vQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/7gAAAAAAAAAAAAAAAAAA//MAA
1098
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAAAP/5AAAAAAAAAA
1099
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAD/4AAAAAAAAAAAAAAAAAA
1100
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//L/9AAAAAAAAAAAAAAAAAAAAAAA
1101
+ AAAAAAAAAAAA/+gAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1102
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/zAAAAAA
1103
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/mAAAAAAAAAAAAAAAAAAD
1104
+ /4gAA//AAAAAA//YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAP/OAAAAAAAAAAAAAAAA
1105
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/zv/qAAAAAP/0AAAACAAAAAAAAAAAAAAAAAAAAAAAA
1106
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/ZAAD/egAA/1kAAAAA/5D/rgAAAAAAAAAAAA
1107
+ AAAAAAAAAAAAAAAAD/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1108
+ AAAAAAAAAAAAAAAAAAAD/8AAA/7b/8P+wAAD/8P/E/98AAAAA/8P/+P/0//oAAAAAAAAAAAAA//gA
1109
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAA
1110
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/w//C/9MAAP/SAAD/9wAAAAAAAA
1111
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/yAAA/+kAAAAA//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1112
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9wAAAAD//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1113
+ AAAAAAAAAAAAAAAAAAAAAP/2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1114
+ AAAAAAAAP/cAAAAAAAAAAAAAAAA/7YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1115
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAP/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/6AAAAAAAAAA
1116
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAkAFAAEAAAAAQACwAAABcA
1117
+ BgAAAAAAAAAIAA4AAAAAAAsAEgAAAAAAAAATABkAAwANAAAAAQAJAAAAAAAAAAAAAAAAAAAAGAAAA
1118
+ AAABwAAAAAAAAAAAAAAFQAFAAAAAAAYABgAAAAUAAAACgAAAAwAAgAPABEAFgAAAAAAAAAAAAAAAA
1119
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAEAEQBdAAYAAAAAAAAAAAAAAAAAAAAAAAA
1120
+ AAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAcAAAAAAAAABwAAAAAACAAAAAAAAAAAAAcAAAAHAAAAEwAJ
1121
+ ABUADgAPAAAACwAQAAAAAAAAAAAAAAAAAAUAGAACAAIAAgAAAAIAGAAXAAAAGAAAABYAFgACABYAA
1122
+ gAWAAAAEQADAAoAFAAMAA0ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAEgAGAAEAHgAkAC
1123
+ YAJwApACoALQAuAC8AMgAzADcAOAA5ADoAPAA9AEUASABOAE8AUgBTAFUAVwBZAFoAWwBcAF0AcwA
1124
+ AAAAAAQAAAADa3tfFAAAAANAan9kAAAAA4QodoQ==
1125
+ """
1126
+ )
1127
+ ),
1128
+ 10 if size is None else size,
1129
+ layout_engine=Layout.BASIC,
1130
+ )
1131
+ else:
1132
+ f = ImageFont()
1133
+ f._load_pilfont_data(
1134
+ # courB08
1135
+ BytesIO(
1136
+ base64.b64decode(
1137
+ b"""
1138
+ UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1139
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1140
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1141
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1142
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1143
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1144
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1145
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1146
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1147
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1148
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1149
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
1150
+ BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
1151
+ AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
1152
+ AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
1153
+ ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
1154
+ BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
1155
+ //kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
1156
+ AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
1157
+ AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
1158
+ ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
1159
+ AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
1160
+ /gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
1161
+ AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
1162
+ AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
1163
+ AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
1164
+ BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
1165
+ AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
1166
+ 2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
1167
+ AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
1168
+ +gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
1169
+ ////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
1170
+ BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
1171
+ AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
1172
+ AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
1173
+ AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
1174
+ BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
1175
+ //kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
1176
+ AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
1177
+ AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
1178
+ mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
1179
+ AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
1180
+ AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
1181
+ AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
1182
+ Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
1183
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1184
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1185
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1186
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1187
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1188
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1189
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1190
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1191
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1192
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1193
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1194
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
1195
+ //sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
1196
+ AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
1197
+ AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
1198
+ DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
1199
+ AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
1200
+ +wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
1201
+ AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
1202
+ ///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
1203
+ AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
1204
+ BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
1205
+ Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
1206
+ eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
1207
+ AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
1208
+ +gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
1209
+ ////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
1210
+ BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
1211
+ AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
1212
+ AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
1213
+ Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
1214
+ Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
1215
+ //cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
1216
+ AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
1217
+ AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
1218
+ LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
1219
+ AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
1220
+ AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
1221
+ AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
1222
+ AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
1223
+ AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
1224
+ EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
1225
+ AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
1226
+ pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
1227
+ AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
1228
+ +QAGAAIAzgAKANUAEw==
1229
+ """
1230
+ )
1231
+ ),
1232
+ Image.open(
1233
+ BytesIO(
1234
+ base64.b64decode(
1235
+ b"""
1236
+ iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
1237
+ Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
1238
+ M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
1239
+ LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
1240
+ IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
1241
+ Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
1242
+ NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
1243
+ in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
1244
+ SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
1245
+ AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
1246
+ y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
1247
+ ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
1248
+ lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
1249
+ /Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
1250
+ AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
1251
+ c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
1252
+ /yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
1253
+ pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
1254
+ oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
1255
+ evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
1256
+ AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
1257
+ Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
1258
+ w7IkEbzhVQAAAABJRU5ErkJggg==
1259
+ """
1260
+ )
1261
+ )
1262
+ ),
1263
+ )
1264
+ return f