nasskall commited on
Commit
7c51342
·
verified ·
1 Parent(s): 48a71a1

Upload folder using huggingface_hub

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 +46 -0
  2. .idea/.gitignore +8 -0
  3. .idea/colorchecker.iml +21 -0
  4. .idea/deployment.xml +448 -0
  5. .idea/inspectionProfiles/Project_Default.xml +214 -0
  6. .idea/inspectionProfiles/profiles_settings.xml +6 -0
  7. .idea/misc.xml +4 -0
  8. .idea/modules.xml +8 -0
  9. .idea/workspace.xml +154 -0
  10. README.md +3 -9
  11. __pycache__/gradio_app.cpython-39.pyc +0 -0
  12. gradio_app.py +293 -0
  13. template_img.png +0 -0
  14. venv/.gitignore +2 -0
  15. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/INSTALLER +1 -0
  16. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/LICENSE +7 -0
  17. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/METADATA +148 -0
  18. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/RECORD +17 -0
  19. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/REQUESTED +0 -0
  20. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/WHEEL +6 -0
  21. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/top_level.txt +1 -0
  22. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER +1 -0
  23. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst +28 -0
  24. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA +93 -0
  25. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD +14 -0
  26. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL +5 -0
  27. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt +1 -0
  28. venv/Lib/site-packages/PIL/BdfFontFile.py +133 -0
  29. venv/Lib/site-packages/PIL/BlpImagePlugin.py +476 -0
  30. venv/Lib/site-packages/PIL/BmpImagePlugin.py +472 -0
  31. venv/Lib/site-packages/PIL/BufrStubImagePlugin.py +74 -0
  32. venv/Lib/site-packages/PIL/ContainerIO.py +121 -0
  33. venv/Lib/site-packages/PIL/CurImagePlugin.py +75 -0
  34. venv/Lib/site-packages/PIL/DcxImagePlugin.py +80 -0
  35. venv/Lib/site-packages/PIL/DdsImagePlugin.py +572 -0
  36. venv/Lib/site-packages/PIL/EpsImagePlugin.py +474 -0
  37. venv/Lib/site-packages/PIL/ExifTags.py +381 -0
  38. venv/Lib/site-packages/PIL/FitsImagePlugin.py +148 -0
  39. venv/Lib/site-packages/PIL/FliImagePlugin.py +174 -0
  40. venv/Lib/site-packages/PIL/FontFile.py +134 -0
  41. venv/Lib/site-packages/PIL/FpxImagePlugin.py +255 -0
  42. venv/Lib/site-packages/PIL/FtexImagePlugin.py +115 -0
  43. venv/Lib/site-packages/PIL/GbrImagePlugin.py +103 -0
  44. venv/Lib/site-packages/PIL/GdImageFile.py +102 -0
  45. venv/Lib/site-packages/PIL/GifImagePlugin.py +1107 -0
  46. venv/Lib/site-packages/PIL/GimpGradientFile.py +137 -0
  47. venv/Lib/site-packages/PIL/GimpPaletteFile.py +57 -0
  48. venv/Lib/site-packages/PIL/GribStubImagePlugin.py +74 -0
  49. venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py +74 -0
  50. venv/Lib/site-packages/PIL/IcnsImagePlugin.py +400 -0
.gitattributes CHANGED
@@ -33,3 +33,49 @@ 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/altair/vegalite/v5/schema/__pycache__/channels.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text
37
+ venv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/core.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text
38
+ venv/Lib/site-packages/cv2/cv2.pyd filter=lfs diff=lfs merge=lfs -text
39
+ venv/Lib/site-packages/cv2/opencv_videoio_ffmpeg4100_64.dll filter=lfs diff=lfs merge=lfs -text
40
+ venv/Lib/site-packages/gradio/frpc_windows_amd64_v0.2 filter=lfs diff=lfs merge=lfs -text
41
+ venv/Lib/site-packages/gradio/templates/frontend/assets/Index-CUTYDExL.js.map filter=lfs diff=lfs merge=lfs -text
42
+ venv/Lib/site-packages/numpy/_core/_multiarray_umath.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
43
+ venv/Lib/site-packages/numpy/_core/_simd.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
44
+ venv/Lib/site-packages/numpy.libs/libscipy_openblas64_-fb1711452d4d8cee9f276fd1449ee5c7.dll filter=lfs diff=lfs merge=lfs -text
45
+ venv/Lib/site-packages/pandas/_libs/algos.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
46
+ venv/Lib/site-packages/pandas/_libs/groupby.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
47
+ venv/Lib/site-packages/pandas/_libs/hashtable.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
48
+ venv/Lib/site-packages/pandas/_libs/interval.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
49
+ venv/Lib/site-packages/pandas/_libs/join.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
50
+ venv/Lib/site-packages/PIL/_imaging.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
51
+ venv/Lib/site-packages/PIL/_imagingft.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
52
+ venv/Lib/site-packages/pydantic_core/_pydantic_core.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
53
+ venv/Lib/site-packages/scipy/fft/_pocketfft/pypocketfft.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
54
+ venv/Lib/site-packages/scipy/interpolate/_rbfinterp_pythran.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
55
+ venv/Lib/site-packages/scipy/io/_fast_matrix_market/_fmm_core.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
56
+ venv/Lib/site-packages/scipy/linalg/_flapack.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
57
+ venv/Lib/site-packages/scipy/misc/face.dat filter=lfs diff=lfs merge=lfs -text
58
+ venv/Lib/site-packages/scipy/optimize/_group_columns.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
59
+ venv/Lib/site-packages/scipy/optimize/_highs/_highs_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
60
+ venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
61
+ venv/Lib/site-packages/scipy/signal/_spectral.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
62
+ venv/Lib/site-packages/scipy/sparse/_sparsetools.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
63
+ venv/Lib/site-packages/scipy/spatial/_ckdtree.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
64
+ venv/Lib/site-packages/scipy/spatial/_distance_pybind.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
65
+ venv/Lib/site-packages/scipy/spatial/_qhull.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
66
+ venv/Lib/site-packages/scipy/special/_ufuncs.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
67
+ venv/Lib/site-packages/scipy/special/_ufuncs_cxx.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
68
+ venv/Lib/site-packages/scipy/special/cython_special.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
69
+ venv/Lib/site-packages/scipy/stats/_boost/beta_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
70
+ venv/Lib/site-packages/scipy/stats/_boost/binom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
71
+ venv/Lib/site-packages/scipy/stats/_boost/hypergeom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
72
+ venv/Lib/site-packages/scipy/stats/_boost/invgauss_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
73
+ venv/Lib/site-packages/scipy/stats/_boost/nbinom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
74
+ venv/Lib/site-packages/scipy/stats/_boost/ncf_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
75
+ venv/Lib/site-packages/scipy/stats/_boost/nct_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
76
+ venv/Lib/site-packages/scipy/stats/_boost/ncx2_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
77
+ venv/Lib/site-packages/scipy/stats/_stats_pythran.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
78
+ venv/Lib/site-packages/scipy/stats/_unuran/unuran_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
79
+ venv/Lib/site-packages/scipy.libs/libopenblas_v0.3.27--3aa239bc726cfb0bd8e5330d8d4c15c6.dll filter=lfs diff=lfs merge=lfs -text
80
+ venv/Lib/site-packages/sklearn/_loss/_loss.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
81
+ venv/Scripts/ruff.exe filter=lfs diff=lfs merge=lfs -text
.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
.idea/colorchecker.iml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="Flask">
4
+ <option name="enabled" value="true" />
5
+ </component>
6
+ <component name="NewModuleRootManager">
7
+ <content url="file://$MODULE_DIR$">
8
+ <excludeFolder url="file://$MODULE_DIR$/venv" />
9
+ </content>
10
+ <orderEntry type="inheritedJdk" />
11
+ <orderEntry type="sourceFolder" forTests="false" />
12
+ </component>
13
+ <component name="TemplatesService">
14
+ <option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
15
+ <option name="TEMPLATE_FOLDERS">
16
+ <list>
17
+ <option value="$MODULE_DIR$/../colorchecker\templates" />
18
+ </list>
19
+ </option>
20
+ </component>
21
+ </module>
.idea/deployment.xml ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
4
+ <serverData>
5
+ <paths name="[email protected]:22">
6
+ <serverdata>
7
+ <mappings>
8
+ <mapping local="$PROJECT_DIR$" web="/" />
9
+ </mappings>
10
+ </serverdata>
11
+ </paths>
12
+ <paths name="[email protected]:22 password">
13
+ <serverdata>
14
+ <mappings>
15
+ <mapping local="$PROJECT_DIR$" web="/" />
16
+ </mappings>
17
+ </serverdata>
18
+ </paths>
19
+ <paths name="[email protected]:22 password (1)">
20
+ <serverdata>
21
+ <mappings>
22
+ <mapping local="$PROJECT_DIR$" web="/" />
23
+ </mappings>
24
+ </serverdata>
25
+ </paths>
26
+ <paths name="[email protected]:22 password (10)">
27
+ <serverdata>
28
+ <mappings>
29
+ <mapping local="$PROJECT_DIR$" web="/" />
30
+ </mappings>
31
+ </serverdata>
32
+ </paths>
33
+ <paths name="[email protected]:22 password (11)">
34
+ <serverdata>
35
+ <mappings>
36
+ <mapping local="$PROJECT_DIR$" web="/" />
37
+ </mappings>
38
+ </serverdata>
39
+ </paths>
40
+ <paths name="[email protected]:22 password (2)">
41
+ <serverdata>
42
+ <mappings>
43
+ <mapping local="$PROJECT_DIR$" web="/" />
44
+ </mappings>
45
+ </serverdata>
46
+ </paths>
47
+ <paths name="[email protected]:22 password (3)">
48
+ <serverdata>
49
+ <mappings>
50
+ <mapping local="$PROJECT_DIR$" web="/" />
51
+ </mappings>
52
+ </serverdata>
53
+ </paths>
54
+ <paths name="[email protected]:22 password (4)">
55
+ <serverdata>
56
+ <mappings>
57
+ <mapping local="$PROJECT_DIR$" web="/" />
58
+ </mappings>
59
+ </serverdata>
60
+ </paths>
61
+ <paths name="[email protected]:22 password (5)">
62
+ <serverdata>
63
+ <mappings>
64
+ <mapping local="$PROJECT_DIR$" web="/" />
65
+ </mappings>
66
+ </serverdata>
67
+ </paths>
68
+ <paths name="[email protected]:22 password (6)">
69
+ <serverdata>
70
+ <mappings>
71
+ <mapping local="$PROJECT_DIR$" web="/" />
72
+ </mappings>
73
+ </serverdata>
74
+ </paths>
75
+ <paths name="[email protected]:22 password (7)">
76
+ <serverdata>
77
+ <mappings>
78
+ <mapping local="$PROJECT_DIR$" web="/" />
79
+ </mappings>
80
+ </serverdata>
81
+ </paths>
82
+ <paths name="[email protected]:22 password (8)">
83
+ <serverdata>
84
+ <mappings>
85
+ <mapping local="$PROJECT_DIR$" web="/" />
86
+ </mappings>
87
+ </serverdata>
88
+ </paths>
89
+ <paths name="[email protected]:22 password (9)">
90
+ <serverdata>
91
+ <mappings>
92
+ <mapping local="$PROJECT_DIR$" web="/" />
93
+ </mappings>
94
+ </serverdata>
95
+ </paths>
96
+ <paths name="[email protected]:52895">
97
+ <serverdata>
98
+ <mappings>
99
+ <mapping local="$PROJECT_DIR$" web="/" />
100
+ </mappings>
101
+ </serverdata>
102
+ </paths>
103
+ <paths name="[email protected]:22 password">
104
+ <serverdata>
105
+ <mappings>
106
+ <mapping local="$PROJECT_DIR$" web="/" />
107
+ </mappings>
108
+ </serverdata>
109
+ </paths>
110
+ <paths name="[email protected]:22 password">
111
+ <serverdata>
112
+ <mappings>
113
+ <mapping local="$PROJECT_DIR$" web="/" />
114
+ </mappings>
115
+ </serverdata>
116
+ </paths>
117
+ <paths name="[email protected]:22 password (1)">
118
+ <serverdata>
119
+ <mappings>
120
+ <mapping local="$PROJECT_DIR$" web="/" />
121
+ </mappings>
122
+ </serverdata>
123
+ </paths>
124
+ <paths name="[email protected]:22 password (10)">
125
+ <serverdata>
126
+ <mappings>
127
+ <mapping local="$PROJECT_DIR$" web="/" />
128
+ </mappings>
129
+ </serverdata>
130
+ </paths>
131
+ <paths name="[email protected]:22 password (11)">
132
+ <serverdata>
133
+ <mappings>
134
+ <mapping local="$PROJECT_DIR$" web="/" />
135
+ </mappings>
136
+ </serverdata>
137
+ </paths>
138
+ <paths name="[email protected]:22 password (12)">
139
+ <serverdata>
140
+ <mappings>
141
+ <mapping local="$PROJECT_DIR$" web="/" />
142
+ </mappings>
143
+ </serverdata>
144
+ </paths>
145
+ <paths name="[email protected]:22 password (13)">
146
+ <serverdata>
147
+ <mappings>
148
+ <mapping local="$PROJECT_DIR$" web="/" />
149
+ </mappings>
150
+ </serverdata>
151
+ </paths>
152
+ <paths name="[email protected]:22 password (14)">
153
+ <serverdata>
154
+ <mappings>
155
+ <mapping local="$PROJECT_DIR$" web="/" />
156
+ </mappings>
157
+ </serverdata>
158
+ </paths>
159
+ <paths name="[email protected]:22 password (15)">
160
+ <serverdata>
161
+ <mappings>
162
+ <mapping local="$PROJECT_DIR$" web="/" />
163
+ </mappings>
164
+ </serverdata>
165
+ </paths>
166
+ <paths name="[email protected]:22 password (16)">
167
+ <serverdata>
168
+ <mappings>
169
+ <mapping local="$PROJECT_DIR$" web="/" />
170
+ </mappings>
171
+ </serverdata>
172
+ </paths>
173
+ <paths name="[email protected]:22 password (17)">
174
+ <serverdata>
175
+ <mappings>
176
+ <mapping local="$PROJECT_DIR$" web="/" />
177
+ </mappings>
178
+ </serverdata>
179
+ </paths>
180
+ <paths name="[email protected]:22 password (18)">
181
+ <serverdata>
182
+ <mappings>
183
+ <mapping local="$PROJECT_DIR$" web="/" />
184
+ </mappings>
185
+ </serverdata>
186
+ </paths>
187
+ <paths name="[email protected]:22 password (19)">
188
+ <serverdata>
189
+ <mappings>
190
+ <mapping local="$PROJECT_DIR$" web="/" />
191
+ </mappings>
192
+ </serverdata>
193
+ </paths>
194
+ <paths name="[email protected]:22 password (2)">
195
+ <serverdata>
196
+ <mappings>
197
+ <mapping local="$PROJECT_DIR$" web="/" />
198
+ </mappings>
199
+ </serverdata>
200
+ </paths>
201
+ <paths name="[email protected]:22 password (20)">
202
+ <serverdata>
203
+ <mappings>
204
+ <mapping local="$PROJECT_DIR$" web="/" />
205
+ </mappings>
206
+ </serverdata>
207
+ </paths>
208
+ <paths name="[email protected]:22 password (21)">
209
+ <serverdata>
210
+ <mappings>
211
+ <mapping local="$PROJECT_DIR$" web="/" />
212
+ </mappings>
213
+ </serverdata>
214
+ </paths>
215
+ <paths name="[email protected]:22 password (22)">
216
+ <serverdata>
217
+ <mappings>
218
+ <mapping local="$PROJECT_DIR$" web="/" />
219
+ </mappings>
220
+ </serverdata>
221
+ </paths>
222
+ <paths name="[email protected]:22 password (23)">
223
+ <serverdata>
224
+ <mappings>
225
+ <mapping local="$PROJECT_DIR$" web="/" />
226
+ </mappings>
227
+ </serverdata>
228
+ </paths>
229
+ <paths name="[email protected]:22 password (24)">
230
+ <serverdata>
231
+ <mappings>
232
+ <mapping local="$PROJECT_DIR$" web="/" />
233
+ </mappings>
234
+ </serverdata>
235
+ </paths>
236
+ <paths name="[email protected]:22 password (25)">
237
+ <serverdata>
238
+ <mappings>
239
+ <mapping local="$PROJECT_DIR$" web="/" />
240
+ </mappings>
241
+ </serverdata>
242
+ </paths>
243
+ <paths name="[email protected]:22 password (26)">
244
+ <serverdata>
245
+ <mappings>
246
+ <mapping local="$PROJECT_DIR$" web="/" />
247
+ </mappings>
248
+ </serverdata>
249
+ </paths>
250
+ <paths name="[email protected]:22 password (27)">
251
+ <serverdata>
252
+ <mappings>
253
+ <mapping local="$PROJECT_DIR$" web="/" />
254
+ </mappings>
255
+ </serverdata>
256
+ </paths>
257
+ <paths name="[email protected]:22 password (28)">
258
+ <serverdata>
259
+ <mappings>
260
+ <mapping local="$PROJECT_DIR$" web="/" />
261
+ </mappings>
262
+ </serverdata>
263
+ </paths>
264
+ <paths name="[email protected]:22 password (29)">
265
+ <serverdata>
266
+ <mappings>
267
+ <mapping local="$PROJECT_DIR$" web="/" />
268
+ </mappings>
269
+ </serverdata>
270
+ </paths>
271
+ <paths name="[email protected]:22 password (3)">
272
+ <serverdata>
273
+ <mappings>
274
+ <mapping local="$PROJECT_DIR$" web="/" />
275
+ </mappings>
276
+ </serverdata>
277
+ </paths>
278
+ <paths name="[email protected]:22 password (30)">
279
+ <serverdata>
280
+ <mappings>
281
+ <mapping local="$PROJECT_DIR$" web="/" />
282
+ </mappings>
283
+ </serverdata>
284
+ </paths>
285
+ <paths name="[email protected]:22 password (31)">
286
+ <serverdata>
287
+ <mappings>
288
+ <mapping local="$PROJECT_DIR$" web="/" />
289
+ </mappings>
290
+ </serverdata>
291
+ </paths>
292
+ <paths name="[email protected]:22 password (32)">
293
+ <serverdata>
294
+ <mappings>
295
+ <mapping local="$PROJECT_DIR$" web="/" />
296
+ </mappings>
297
+ </serverdata>
298
+ </paths>
299
+ <paths name="[email protected]:22 password (33)">
300
+ <serverdata>
301
+ <mappings>
302
+ <mapping local="$PROJECT_DIR$" web="/" />
303
+ </mappings>
304
+ </serverdata>
305
+ </paths>
306
+ <paths name="[email protected]:22 password (34)">
307
+ <serverdata>
308
+ <mappings>
309
+ <mapping local="$PROJECT_DIR$" web="/" />
310
+ </mappings>
311
+ </serverdata>
312
+ </paths>
313
+ <paths name="[email protected]:22 password (35)">
314
+ <serverdata>
315
+ <mappings>
316
+ <mapping local="$PROJECT_DIR$" web="/" />
317
+ </mappings>
318
+ </serverdata>
319
+ </paths>
320
+ <paths name="[email protected]:22 password (36)">
321
+ <serverdata>
322
+ <mappings>
323
+ <mapping local="$PROJECT_DIR$" web="/" />
324
+ </mappings>
325
+ </serverdata>
326
+ </paths>
327
+ <paths name="[email protected]:22 password (37)">
328
+ <serverdata>
329
+ <mappings>
330
+ <mapping local="$PROJECT_DIR$" web="/" />
331
+ </mappings>
332
+ </serverdata>
333
+ </paths>
334
+ <paths name="[email protected]:22 password (38)">
335
+ <serverdata>
336
+ <mappings>
337
+ <mapping local="$PROJECT_DIR$" web="/" />
338
+ </mappings>
339
+ </serverdata>
340
+ </paths>
341
+ <paths name="[email protected]:22 password (39)">
342
+ <serverdata>
343
+ <mappings>
344
+ <mapping local="$PROJECT_DIR$" web="/" />
345
+ </mappings>
346
+ </serverdata>
347
+ </paths>
348
+ <paths name="[email protected]:22 password (4)">
349
+ <serverdata>
350
+ <mappings>
351
+ <mapping local="$PROJECT_DIR$" web="/" />
352
+ </mappings>
353
+ </serverdata>
354
+ </paths>
355
+ <paths name="[email protected]:22 password (40)">
356
+ <serverdata>
357
+ <mappings>
358
+ <mapping local="$PROJECT_DIR$" web="/" />
359
+ </mappings>
360
+ </serverdata>
361
+ </paths>
362
+ <paths name="[email protected]:22 password (41)">
363
+ <serverdata>
364
+ <mappings>
365
+ <mapping local="$PROJECT_DIR$" web="/" />
366
+ </mappings>
367
+ </serverdata>
368
+ </paths>
369
+ <paths name="[email protected]:22 password (42)">
370
+ <serverdata>
371
+ <mappings>
372
+ <mapping local="$PROJECT_DIR$" web="/" />
373
+ </mappings>
374
+ </serverdata>
375
+ </paths>
376
+ <paths name="[email protected]:22 password (43)">
377
+ <serverdata>
378
+ <mappings>
379
+ <mapping local="$PROJECT_DIR$" web="/" />
380
+ </mappings>
381
+ </serverdata>
382
+ </paths>
383
+ <paths name="[email protected]:22 password (44)">
384
+ <serverdata>
385
+ <mappings>
386
+ <mapping local="$PROJECT_DIR$" web="/" />
387
+ </mappings>
388
+ </serverdata>
389
+ </paths>
390
+ <paths name="[email protected]:22 password (45)">
391
+ <serverdata>
392
+ <mappings>
393
+ <mapping local="$PROJECT_DIR$" web="/" />
394
+ </mappings>
395
+ </serverdata>
396
+ </paths>
397
+ <paths name="[email protected]:22 password (46)">
398
+ <serverdata>
399
+ <mappings>
400
+ <mapping local="$PROJECT_DIR$" web="/" />
401
+ </mappings>
402
+ </serverdata>
403
+ </paths>
404
+ <paths name="[email protected]:22 password (47)">
405
+ <serverdata>
406
+ <mappings>
407
+ <mapping local="$PROJECT_DIR$" web="/" />
408
+ </mappings>
409
+ </serverdata>
410
+ </paths>
411
+ <paths name="[email protected]:22 password (5)">
412
+ <serverdata>
413
+ <mappings>
414
+ <mapping local="$PROJECT_DIR$" web="/" />
415
+ </mappings>
416
+ </serverdata>
417
+ </paths>
418
+ <paths name="[email protected]:22 password (6)">
419
+ <serverdata>
420
+ <mappings>
421
+ <mapping local="$PROJECT_DIR$" web="/" />
422
+ </mappings>
423
+ </serverdata>
424
+ </paths>
425
+ <paths name="[email protected]:22 password (7)">
426
+ <serverdata>
427
+ <mappings>
428
+ <mapping local="$PROJECT_DIR$" web="/" />
429
+ </mappings>
430
+ </serverdata>
431
+ </paths>
432
+ <paths name="[email protected]:22 password (8)">
433
+ <serverdata>
434
+ <mappings>
435
+ <mapping local="$PROJECT_DIR$" web="/" />
436
+ </mappings>
437
+ </serverdata>
438
+ </paths>
439
+ <paths name="[email protected]:22 password (9)">
440
+ <serverdata>
441
+ <mappings>
442
+ <mapping local="$PROJECT_DIR$" web="/" />
443
+ </mappings>
444
+ </serverdata>
445
+ </paths>
446
+ </serverData>
447
+ </component>
448
+ </project>
.idea/inspectionProfiles/Project_Default.xml ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
5
+ <Languages>
6
+ <language minSize="718" name="Python" />
7
+ </Languages>
8
+ </inspection_tool>
9
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
10
+ <inspection_tool class="LossyEncoding" enabled="false" level="WARNING" enabled_by_default="false" />
11
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
12
+ <option name="ignoredPackages">
13
+ <value>
14
+ <list size="164">
15
+ <item index="0" class="java.lang.String" itemvalue="sklearn" />
16
+ <item index="1" class="java.lang.String" itemvalue="tensorflow" />
17
+ <item index="2" class="java.lang.String" itemvalue="pip" />
18
+ <item index="3" class="java.lang.String" itemvalue="tqdm" />
19
+ <item index="4" class="java.lang.String" itemvalue="pandas" />
20
+ <item index="5" class="java.lang.String" itemvalue="scipy" />
21
+ <item index="6" class="java.lang.String" itemvalue="seaborn" />
22
+ <item index="7" class="java.lang.String" itemvalue="scikit-learn" />
23
+ <item index="8" class="java.lang.String" itemvalue="wheel" />
24
+ <item index="9" class="java.lang.String" itemvalue="setuptools" />
25
+ <item index="10" class="java.lang.String" itemvalue="numpy" />
26
+ <item index="11" class="java.lang.String" itemvalue="Pillow" />
27
+ <item index="12" class="java.lang.String" itemvalue="opencv-python" />
28
+ <item index="13" class="java.lang.String" itemvalue="python" />
29
+ <item index="14" class="java.lang.String" itemvalue="tensorboard" />
30
+ <item index="15" class="java.lang.String" itemvalue="matplotlib" />
31
+ <item index="16" class="java.lang.String" itemvalue="skimage" />
32
+ <item index="17" class="java.lang.String" itemvalue="tensorflow-gpu" />
33
+ <item index="18" class="java.lang.String" itemvalue="boto3" />
34
+ <item index="19" class="java.lang.String" itemvalue="neo4j" />
35
+ <item index="20" class="java.lang.String" itemvalue="smart-open" />
36
+ <item index="21" class="java.lang.String" itemvalue="ijson" />
37
+ <item index="22" class="java.lang.String" itemvalue="google-pasta" />
38
+ <item index="23" class="java.lang.String" itemvalue="tensorflow-estimator" />
39
+ <item index="24" class="java.lang.String" itemvalue="tzlocal" />
40
+ <item index="25" class="java.lang.String" itemvalue="testpath" />
41
+ <item index="26" class="java.lang.String" itemvalue="validators" />
42
+ <item index="27" class="java.lang.String" itemvalue="pickleshare" />
43
+ <item index="28" class="java.lang.String" itemvalue="defusedxml" />
44
+ <item index="29" class="java.lang.String" itemvalue="pycparser" />
45
+ <item index="30" class="java.lang.String" itemvalue="gitdb" />
46
+ <item index="31" class="java.lang.String" itemvalue="pyasn1-modules" />
47
+ <item index="32" class="java.lang.String" itemvalue="ipython-genutils" />
48
+ <item index="33" class="java.lang.String" itemvalue="Pygments" />
49
+ <item index="34" class="java.lang.String" itemvalue="bleach" />
50
+ <item index="35" class="java.lang.String" itemvalue="astunparse" />
51
+ <item index="36" class="java.lang.String" itemvalue="jsonschema" />
52
+ <item index="37" class="java.lang.String" itemvalue="terminado" />
53
+ <item index="38" class="java.lang.String" itemvalue="GitPython" />
54
+ <item index="39" class="java.lang.String" itemvalue="Werkzeug" />
55
+ <item index="40" class="java.lang.String" itemvalue="streamlit" />
56
+ <item index="41" class="java.lang.String" itemvalue="tensorboard-data-server" />
57
+ <item index="42" class="java.lang.String" itemvalue="typing-extensions" />
58
+ <item index="43" class="java.lang.String" itemvalue="jupyter-client" />
59
+ <item index="44" class="java.lang.String" itemvalue="jupyterlab-pygments" />
60
+ <item index="45" class="java.lang.String" itemvalue="click" />
61
+ <item index="46" class="java.lang.String" itemvalue="ipykernel" />
62
+ <item index="47" class="java.lang.String" itemvalue="nbconvert" />
63
+ <item index="48" class="java.lang.String" itemvalue="attrs" />
64
+ <item index="49" class="java.lang.String" itemvalue="jedi" />
65
+ <item index="50" class="java.lang.String" itemvalue="flatbuffers" />
66
+ <item index="51" class="java.lang.String" itemvalue="imageio" />
67
+ <item index="52" class="java.lang.String" itemvalue="idna" />
68
+ <item index="53" class="java.lang.String" itemvalue="rsa" />
69
+ <item index="54" class="java.lang.String" itemvalue="decorator" />
70
+ <item index="55" class="java.lang.String" itemvalue="networkx" />
71
+ <item index="56" class="java.lang.String" itemvalue="smmap" />
72
+ <item index="57" class="java.lang.String" itemvalue="cffi" />
73
+ <item index="58" class="java.lang.String" itemvalue="pandocfilters" />
74
+ <item index="59" class="java.lang.String" itemvalue="pyasn1" />
75
+ <item index="60" class="java.lang.String" itemvalue="requests" />
76
+ <item index="61" class="java.lang.String" itemvalue="opencv-python-headless" />
77
+ <item index="62" class="java.lang.String" itemvalue="pyrsistent" />
78
+ <item index="63" class="java.lang.String" itemvalue="tensorboard-plugin-wit" />
79
+ <item index="64" class="java.lang.String" itemvalue="PyWavelets" />
80
+ <item index="65" class="java.lang.String" itemvalue="zipp" />
81
+ <item index="66" class="java.lang.String" itemvalue="nest-asyncio" />
82
+ <item index="67" class="java.lang.String" itemvalue="prompt-toolkit" />
83
+ <item index="68" class="java.lang.String" itemvalue="cached-property" />
84
+ <item index="69" class="java.lang.String" itemvalue="ipywidgets" />
85
+ <item index="70" class="java.lang.String" itemvalue="blinker" />
86
+ <item index="71" class="java.lang.String" itemvalue="pyarrow" />
87
+ <item index="72" class="java.lang.String" itemvalue="tornado" />
88
+ <item index="73" class="java.lang.String" itemvalue="google-auth-oauthlib" />
89
+ <item index="74" class="java.lang.String" itemvalue="astor" />
90
+ <item index="75" class="java.lang.String" itemvalue="Send2Trash" />
91
+ <item index="76" class="java.lang.String" itemvalue="toml" />
92
+ <item index="77" class="java.lang.String" itemvalue="mistune" />
93
+ <item index="78" class="java.lang.String" itemvalue="termcolor" />
94
+ <item index="79" class="java.lang.String" itemvalue="watchdog" />
95
+ <item index="80" class="java.lang.String" itemvalue="toolz" />
96
+ <item index="81" class="java.lang.String" itemvalue="cachetools" />
97
+ <item index="82" class="java.lang.String" itemvalue="debugpy" />
98
+ <item index="83" class="java.lang.String" itemvalue="argon2-cffi" />
99
+ <item index="84" class="java.lang.String" itemvalue="pytz" />
100
+ <item index="85" class="java.lang.String" itemvalue="webencodings" />
101
+ <item index="86" class="java.lang.String" itemvalue="traitlets" />
102
+ <item index="87" class="java.lang.String" itemvalue="absl-py" />
103
+ <item index="88" class="java.lang.String" itemvalue="protobuf" />
104
+ <item index="89" class="java.lang.String" itemvalue="opt-einsum" />
105
+ <item index="90" class="java.lang.String" itemvalue="python-dateutil" />
106
+ <item index="91" class="java.lang.String" itemvalue="nbclient" />
107
+ <item index="92" class="java.lang.String" itemvalue="cycler" />
108
+ <item index="93" class="java.lang.String" itemvalue="gast" />
109
+ <item index="94" class="java.lang.String" itemvalue="MarkupSafe" />
110
+ <item index="95" class="java.lang.String" itemvalue="jupyterlab-widgets" />
111
+ <item index="96" class="java.lang.String" itemvalue="backports.zoneinfo" />
112
+ <item index="97" class="java.lang.String" itemvalue="pyzmq" />
113
+ <item index="98" class="java.lang.String" itemvalue="certifi" />
114
+ <item index="99" class="java.lang.String" itemvalue="oauthlib" />
115
+ <item index="100" class="java.lang.String" itemvalue="entrypoints" />
116
+ <item index="101" class="java.lang.String" itemvalue="pyparsing" />
117
+ <item index="102" class="java.lang.String" itemvalue="Markdown" />
118
+ <item index="103" class="java.lang.String" itemvalue="notebook" />
119
+ <item index="104" class="java.lang.String" itemvalue="tifffile" />
120
+ <item index="105" class="java.lang.String" itemvalue="argcomplete" />
121
+ <item index="106" class="java.lang.String" itemvalue="h5py" />
122
+ <item index="107" class="java.lang.String" itemvalue="wrapt" />
123
+ <item index="108" class="java.lang.String" itemvalue="kiwisolver" />
124
+ <item index="109" class="java.lang.String" itemvalue="altair" />
125
+ <item index="110" class="java.lang.String" itemvalue="backcall" />
126
+ <item index="111" class="java.lang.String" itemvalue="widgetsnbextension" />
127
+ <item index="112" class="java.lang.String" itemvalue="charset-normalizer" />
128
+ <item index="113" class="java.lang.String" itemvalue="scikit-image" />
129
+ <item index="114" class="java.lang.String" itemvalue="jupyter-core" />
130
+ <item index="115" class="java.lang.String" itemvalue="matplotlib-inline" />
131
+ <item index="116" class="java.lang.String" itemvalue="pydeck" />
132
+ <item index="117" class="java.lang.String" itemvalue="wcwidth" />
133
+ <item index="118" class="java.lang.String" itemvalue="importlib-metadata" />
134
+ <item index="119" class="java.lang.String" itemvalue="Jinja2" />
135
+ <item index="120" class="java.lang.String" itemvalue="requests-oauthlib" />
136
+ <item index="121" class="java.lang.String" itemvalue="Keras-Preprocessing" />
137
+ <item index="122" class="java.lang.String" itemvalue="urllib3" />
138
+ <item index="123" class="java.lang.String" itemvalue="six" />
139
+ <item index="124" class="java.lang.String" itemvalue="parso" />
140
+ <item index="125" class="java.lang.String" itemvalue="nbformat" />
141
+ <item index="126" class="java.lang.String" itemvalue="tzdata" />
142
+ <item index="127" class="java.lang.String" itemvalue="ipython" />
143
+ <item index="128" class="java.lang.String" itemvalue="packaging" />
144
+ <item index="129" class="java.lang.String" itemvalue="prometheus-client" />
145
+ <item index="130" class="java.lang.String" itemvalue="base58" />
146
+ <item index="131" class="java.lang.String" itemvalue="colorama" />
147
+ <item index="132" class="java.lang.String" itemvalue="grpcio" />
148
+ <item index="133" class="java.lang.String" itemvalue="google-auth" />
149
+ <item index="134" class="java.lang.String" itemvalue="pillow" />
150
+ <item index="135" class="java.lang.String" itemvalue="opencv-contrib-python" />
151
+ <item index="136" class="java.lang.String" itemvalue="PyAutoGUI" />
152
+ <item index="137" class="java.lang.String" itemvalue="referencing" />
153
+ <item index="138" class="java.lang.String" itemvalue="pyperclip" />
154
+ <item index="139" class="java.lang.String" itemvalue="EasyProcess" />
155
+ <item index="140" class="java.lang.String" itemvalue="pytweening" />
156
+ <item index="141" class="java.lang.String" itemvalue="rich" />
157
+ <item index="142" class="java.lang.String" itemvalue="PyMsgBox" />
158
+ <item index="143" class="java.lang.String" itemvalue="mss" />
159
+ <item index="144" class="java.lang.String" itemvalue="markdown-it-py" />
160
+ <item index="145" class="java.lang.String" itemvalue="jsonschema-specifications" />
161
+ <item index="146" class="java.lang.String" itemvalue="pyscreenshot" />
162
+ <item index="147" class="java.lang.String" itemvalue="rpds-py" />
163
+ <item index="148" class="java.lang.String" itemvalue="MouseInfo" />
164
+ <item index="149" class="java.lang.String" itemvalue="mdurl" />
165
+ <item index="150" class="java.lang.String" itemvalue="tenacity" />
166
+ <item index="151" class="java.lang.String" itemvalue="entrypoint2" />
167
+ <item index="152" class="java.lang.String" itemvalue="PyGetWindow" />
168
+ <item index="153" class="java.lang.String" itemvalue="PyRect" />
169
+ <item index="154" class="java.lang.String" itemvalue="PyScreeze" />
170
+ <item index="155" class="java.lang.String" itemvalue="tensorflow-intel" />
171
+ <item index="156" class="java.lang.String" itemvalue="lazy_loader" />
172
+ <item index="157" class="java.lang.String" itemvalue="libclang" />
173
+ <item index="158" class="java.lang.String" itemvalue="typing_extensions" />
174
+ <item index="159" class="java.lang.String" itemvalue="keras" />
175
+ <item index="160" class="java.lang.String" itemvalue="tensorflow-io-gcs-filesystem" />
176
+ <item index="161" class="java.lang.String" itemvalue="kornia" />
177
+ <item index="162" class="java.lang.String" itemvalue="torch" />
178
+ <item index="163" class="java.lang.String" itemvalue="torchvision" />
179
+ </list>
180
+ </value>
181
+ </option>
182
+ </inspection_tool>
183
+ <inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
184
+ <option name="ignoredErrors">
185
+ <list>
186
+ <option value="E501" />
187
+ </list>
188
+ </option>
189
+ </inspection_tool>
190
+ <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
191
+ <option name="ignoredErrors">
192
+ <list>
193
+ <option value="N806" />
194
+ <option value="N801" />
195
+ </list>
196
+ </option>
197
+ </inspection_tool>
198
+ <inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
199
+ <option name="ignoredPackages">
200
+ <list>
201
+ <option value="pyspark-stubs==3.0.0.post2" />
202
+ </list>
203
+ </option>
204
+ </inspection_tool>
205
+ <inspection_tool class="PyUnboundLocalVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
206
+ <inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
207
+ <option name="ignoredIdentifiers">
208
+ <list>
209
+ <option value="nearest_neighbor.kmeans" />
210
+ </list>
211
+ </option>
212
+ </inspection_tool>
213
+ </profile>
214
+ </component>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/misc.xml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (colorchecker)" project-jdk-type="Python SDK" />
4
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/colorchecker.iml" filepath="$PROJECT_DIR$/.idea/colorchecker.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/workspace.xml ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ChangeListManager">
4
+ <list default="true" id="fd181dc1-4687-4ecc-9b45-9a08d0ef7b64" name="Changes" comment="" />
5
+ <option name="SHOW_DIALOG" value="false" />
6
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
7
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
8
+ <option name="LAST_RESOLUTION" value="IGNORE" />
9
+ </component>
10
+ <component name="FileTemplateManagerImpl">
11
+ <option name="RECENT_TEMPLATES">
12
+ <list>
13
+ <option value="Flask Main" />
14
+ <option value="Python Script" />
15
+ </list>
16
+ </option>
17
+ </component>
18
+ <component name="FlaskConsoleOptions" custom-start-script="import sys&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;from flask.cli import ScriptInfo&#10;locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())&#10;print(&quot;Python %s on %s\nApp: %s [%s]\nInstance: %s&quot; % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))">
19
+ <envs>
20
+ <env key="FLASK_APP" value="app" />
21
+ </envs>
22
+ <option name="myCustomStartScript" value="import sys&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;from flask.cli import ScriptInfo&#10;locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())&#10;print(&quot;Python %s on %s\nApp: %s [%s]\nInstance: %s&quot; % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))" />
23
+ <option name="myEnvs">
24
+ <map>
25
+ <entry key="FLASK_APP" value="app" />
26
+ </map>
27
+ </option>
28
+ </component>
29
+ <component name="GitSEFilterConfiguration">
30
+ <file-type-list>
31
+ <filtered-out-file-type name="LOCAL_BRANCH" />
32
+ <filtered-out-file-type name="REMOTE_BRANCH" />
33
+ <filtered-out-file-type name="TAG" />
34
+ <filtered-out-file-type name="COMMIT_BY_MESSAGE" />
35
+ </file-type-list>
36
+ </component>
37
+ <component name="ProjectId" id="2iKZXBURNknjhtQFeZZ8Eeq3vjW" />
38
+ <component name="ProjectViewState">
39
+ <option name="hideEmptyMiddlePackages" value="true" />
40
+ <option name="showLibraryContents" value="true" />
41
+ </component>
42
+ <component name="PropertiesComponent">
43
+ <property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
44
+ <property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
45
+ <property name="WebServerToolWindowFactoryState" value="true" />
46
+ <property name="last_opened_file_path" value="$PROJECT_DIR$/../color_checker_mdi" />
47
+ <property name="node.js.detected.package.eslint" value="true" />
48
+ <property name="node.js.selected.package.eslint" value="(autodetect)" />
49
+ <property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
50
+ </component>
51
+ <component name="RecentsManager">
52
+ <key name="CopyFile.RECENT_KEYS">
53
+ <recent name="D:\colorchecker" />
54
+ <recent name="D:\colorchecker\templates" />
55
+ </key>
56
+ </component>
57
+ <component name="RunManager" selected="Python.gradio_app">
58
+ <configuration name="gradio_app" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
59
+ <module name="colorchecker" />
60
+ <option name="INTERPRETER_OPTIONS" value="" />
61
+ <option name="PARENT_ENVS" value="true" />
62
+ <envs>
63
+ <env name="PYTHONUNBUFFERED" value="1" />
64
+ </envs>
65
+ <option name="SDK_HOME" value="" />
66
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
67
+ <option name="IS_MODULE_SDK" value="true" />
68
+ <option name="ADD_CONTENT_ROOTS" value="true" />
69
+ <option name="ADD_SOURCE_ROOTS" value="true" />
70
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
71
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/gradio_app.py" />
72
+ <option name="PARAMETERS" value="" />
73
+ <option name="SHOW_COMMAND_LINE" value="false" />
74
+ <option name="EMULATE_TERMINAL" value="false" />
75
+ <option name="MODULE_MODE" value="false" />
76
+ <option name="REDIRECT_INPUT" value="false" />
77
+ <option name="INPUT_FILE" value="" />
78
+ <method v="2" />
79
+ </configuration>
80
+ <configuration name="Flask (app.py)" type="Python.FlaskServer" temporary="true" nameIsGenerated="true">
81
+ <module name="colorchecker" />
82
+ <option name="target" value="$PROJECT_DIR$/app.py" />
83
+ <option name="targetType" value="PATH" />
84
+ <option name="INTERPRETER_OPTIONS" value="" />
85
+ <option name="PARENT_ENVS" value="true" />
86
+ <option name="SDK_HOME" value="" />
87
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
88
+ <option name="IS_MODULE_SDK" value="true" />
89
+ <option name="ADD_CONTENT_ROOTS" value="true" />
90
+ <option name="ADD_SOURCE_ROOTS" value="true" />
91
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
92
+ <option name="launchJavascriptDebuger" value="false" />
93
+ <method v="2" />
94
+ </configuration>
95
+ <configuration name="colorchecker" type="Python.FlaskServer">
96
+ <module name="colorchecker" />
97
+ <option name="target" value="$PROJECT_DIR$/app.py" />
98
+ <option name="targetType" value="PATH" />
99
+ <option name="INTERPRETER_OPTIONS" value="" />
100
+ <option name="PARENT_ENVS" value="true" />
101
+ <option name="SDK_HOME" value="" />
102
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
103
+ <option name="IS_MODULE_SDK" value="false" />
104
+ <option name="ADD_CONTENT_ROOTS" value="true" />
105
+ <option name="ADD_SOURCE_ROOTS" value="true" />
106
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
107
+ <option name="launchJavascriptDebuger" value="false" />
108
+ <method v="2" />
109
+ </configuration>
110
+ <recent_temporary>
111
+ <list>
112
+ <item itemvalue="Python.gradio_app" />
113
+ <item itemvalue="Flask server.Flask (app.py)" />
114
+ </list>
115
+ </recent_temporary>
116
+ </component>
117
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
118
+ <component name="TaskManager">
119
+ <task active="true" id="Default" summary="Default task">
120
+ <changelist id="fd181dc1-4687-4ecc-9b45-9a08d0ef7b64" name="Changes" comment="" />
121
+ <created>1719241268233</created>
122
+ <option name="number" value="Default" />
123
+ <option name="presentableId" value="Default" />
124
+ <updated>1719241268233</updated>
125
+ <workItem from="1719241288594" duration="2651000" />
126
+ <workItem from="1719322367282" duration="1832000" />
127
+ <workItem from="1719324250050" duration="6933000" />
128
+ <workItem from="1719377258122" duration="259000" />
129
+ <workItem from="1719394020828" duration="148000" />
130
+ <workItem from="1719572953807" duration="300000" />
131
+ <workItem from="1719576294073" duration="647000" />
132
+ <workItem from="1719638200293" duration="96000" />
133
+ <workItem from="1719933127555" duration="1038000" />
134
+ <workItem from="1720263902602" duration="2211000" />
135
+ <workItem from="1720462470706" duration="6886000" />
136
+ <workItem from="1720505947969" duration="13942000" />
137
+ <workItem from="1720602705793" duration="7282000" />
138
+ <workItem from="1720614448144" duration="2267000" />
139
+ <workItem from="1720625798354" duration="1823000" />
140
+ <workItem from="1720700523199" duration="4722000" />
141
+ <workItem from="1720708763097" duration="864000" />
142
+ <workItem from="1720770674965" duration="2843000" />
143
+ </task>
144
+ <servers />
145
+ </component>
146
+ <component name="TypeScriptGeneratedFilesManager">
147
+ <option name="version" value="3" />
148
+ </component>
149
+ <component name="com.intellij.coverage.CoverageDataManagerImpl">
150
+ <SUITE FILE_PATH="coverage/colorchecker$gradio_app.coverage" NAME="gradio_app Coverage Results" MODIFIED="1720774406786" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
151
+ <SUITE FILE_PATH="coverage/colorchecker$Flask__app_py_.coverage" NAME="Flask (app.py) Coverage Results" MODIFIED="1720773700414" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
152
+ <SUITE FILE_PATH="coverage/colorchecker$colorchecker.coverage" NAME="colorchecker Coverage Results" MODIFIED="1720607111175" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
153
+ </component>
154
+ </project>
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: Colorchecker
3
- emoji: 👁
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 4.37.2
8
- app_file: app.py
9
- pinned: false
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: colorchecker
3
+ app_file: gradio_app.py
 
 
4
  sdk: gradio
5
+ sdk_version: 4.36.1
 
 
6
  ---
 
 
__pycache__/gradio_app.cpython-39.pyc ADDED
Binary file (3.45 kB). View file
 
gradio_app.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import gradio as gr
3
+ import numpy as np
4
+ from sklearn.linear_model import LinearRegression
5
+
6
+ checker_large_real = 10.8
7
+ checker_small_real = 6.35
8
+
9
+
10
+ def check_orientation(image):
11
+ #image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
12
+ orientation = np.argmax(image.shape)
13
+ if orientation == 0:
14
+ image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
15
+ return image, orientation
16
+
17
+
18
+ def get_color_checker_table(data_points, y, yend):
19
+ sorted_points = sorted(data_points, key=lambda point: (point[1], point[0]))
20
+ differences_y = [sorted_points[0][1] - y] + \
21
+ [abs(sorted_points[i][1] - sorted_points[i + 1][1]) for i in range(len(sorted_points) - 1)] + \
22
+ [yend - sorted_points[-1][1]]
23
+
24
+ most_usual_y = 10
25
+ local_max = round((yend - y) * 0.2184)
26
+ lines = []
27
+ last_id = 0
28
+ label_upper = differences_y[0] // local_max if differences_y[0] > local_max + 10 else 0
29
+ label_lower = differences_y[-1] // local_max if differences_y[-1] > local_max + 10 else 0
30
+
31
+ for j in range(len(differences_y) - 1):
32
+ if differences_y[j] > local_max + 10:
33
+ lines.extend([[] for _ in range(label_upper)])
34
+ break
35
+
36
+ for i in range(1, len(differences_y) - 1):
37
+ if differences_y[i] > most_usual_y:
38
+ lines.append(sorted_points[last_id:i])
39
+ last_id = i
40
+
41
+ if differences_y[-1] < local_max + 10:
42
+ lines.append(sorted_points[last_id:])
43
+ else:
44
+ lines.append(sorted_points[last_id:])
45
+ lines.extend([[] for _ in range(label_lower)])
46
+ lines = [sorted(line, key=lambda point: point[0]) for line in lines]
47
+ return label_upper, label_lower, local_max, lines
48
+
49
+
50
+ def check_points(data_points, x, xend, y, yend, image):
51
+ most_usual = int((xend - x) / 7.016)
52
+ label_upper, label_lower, usual_y, lines = get_color_checker_table(data_points, y, yend)
53
+
54
+ for q in lines:
55
+ if not q:
56
+ continue
57
+
58
+ differences_x = [q[0][0] - x] + [q[i + 1][0] - q[i][0] for i in range(len(q) - 1)] + [xend - q[-1][0]]
59
+ threshold_x = int(most_usual * (1 + 1 / 5.6))
60
+
61
+ for j, distance in enumerate(differences_x[:-1]):
62
+ if distance > threshold_x:
63
+ positions = distance // int(most_usual * (1 - 1 / 11.2)) - 1
64
+ for t in range(positions):
65
+ cnt = (q[j][0] - (t + 1) * most_usual, q[j][1])
66
+ # cv2.circle(image, cnt, 5, (255, 0, 0), -1)
67
+ data_points.append(cnt)
68
+
69
+ if differences_x[-1] > threshold_x:
70
+ positions = differences_x[-1] // int(most_usual * (1 - 1 / 11.2)) - 1
71
+ for t in range(positions):
72
+ cnt = (q[-1][0] + (t + 1) * most_usual, q[-1][1])
73
+ # cv2.circle(image, cnt, 5, (255, 0, 0), -1)
74
+ data_points.append(cnt)
75
+ data_points.sort(key=lambda point: (point[1], point[0]))
76
+ _, _, _, new_lines = get_color_checker_table(data_points, y, yend)
77
+ return label_upper, label_lower, usual_y, image, new_lines, data_points
78
+
79
+
80
+ def get_reference_values(points, image):
81
+ values = []
82
+ for i in points:
83
+ point_value = image[i[1], i[0]]
84
+ values.append(point_value)
85
+ return values
86
+
87
+
88
+ def detect_RGB_values(image, dst):
89
+ x1, y1 = map(round, dst[0][0])
90
+ x2, y2 = map(round, dst[2][0])
91
+ y2 = max(0, y2)
92
+ image_checker = image[y1:y2, x2:x1]
93
+ if image_checker.size != 0:
94
+ # Apply GaussianBlur to reduce noise and improve edge detection
95
+ blurred = cv2.GaussianBlur(image_checker, (5, 5), 0)
96
+ # Apply edge detection
97
+ edges = cv2.Canny(blurred, 50, 120)
98
+ # Find contours
99
+ contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
100
+ centers = [
101
+ (x + w // 2 + x2, y + h // 2 + y1)
102
+ for contour in contours
103
+ for x, y, w, h in [cv2.boundingRect(contour)]
104
+ if 0.8 < (aspect_ratio := w / float(h)) < 1.2 and (area := cv2.contourArea(contour)) > 100
105
+ ]
106
+ if centers:
107
+ # Filter out centers too close to the edges
108
+ centers = [
109
+ center for center in centers
110
+ if abs(center[0] - x2) >= (x1 - x2) / 7.29 and abs(center[0] - x1) >= (x1 - x2) / 7.29
111
+ ]
112
+ if centers:
113
+ label_upper, label_lower, usual, image, lines, M_T = check_points(centers, x2, x1, y1, y2, image)
114
+ else:
115
+ label_upper, label_lower, lines, M_T = 0, 0, [], []
116
+ else:
117
+ label_upper, label_lower, lines, M_T = 0, 0, [],[]
118
+ else:
119
+ label_upper, label_lower, lines, M_T = 0, 0, [],[]
120
+ M_R = [
121
+ [52, 52, 52], [85, 85, 85], [122, 122, 121], [160, 160, 160],
122
+ [200, 200, 200], [243, 243, 242], [8, 133, 161], [187, 86, 149],
123
+ [231, 199, 31], [175, 54, 60], [70, 148, 73], [56, 61, 150],
124
+ [224, 163, 46], [157, 188, 64], [94, 60, 108], [193, 90, 99],
125
+ [80, 91, 166], [214, 126, 44], [103, 189, 170], [133, 128, 177],
126
+ [87, 108, 67], [98, 122, 157], [194, 150, 130], [115, 82, 68]
127
+ ]
128
+ # show_color_points(M_T, image)
129
+ new_lines = lines.copy()
130
+ if len(M_T) < 24:
131
+ for i in range(label_upper):
132
+ new_lines[0] = [(x, y - round(usual)) for x, y in lines[1]]
133
+ for j in range(label_lower):
134
+ new_lines[-1] = [(x, y + round(usual)) for x, y in lines[-2]]
135
+ if len(M_T) < 24:
136
+ missing = 24 - len(M_T)
137
+ empty_indices = [index for index, sublist in enumerate(lines) if not sublist]
138
+ if missing == 6 and len(empty_indices) == 1:
139
+ l_index = 6 * empty_indices[0] - 1
140
+ M_R = M_R[::-1]
141
+ del M_R[l_index + 1:l_index + 7]
142
+ del new_lines[empty_indices[0]]
143
+ M_R = M_R[::-1]
144
+ if missing == 12 and len(empty_indices) == 2:
145
+ pass
146
+ elif len(M_T) > 24:
147
+ new_lines = []
148
+ M_T = [point for sublist in new_lines for point in sublist]
149
+ M_T_values = np.array(get_reference_values(M_T, image))
150
+ M_R = np.array(M_R)
151
+ return M_T_values, M_R
152
+
153
+
154
+ css = ".input_image {height: 10% !important; width: 10% !important;}"
155
+
156
+
157
+ def detect_template(image, orientation):
158
+ MIN_MATCH_COUNT = 10
159
+ template_path = 'template_img.png'
160
+ template_image = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
161
+ gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
162
+ image_c = image.copy()
163
+ # Initiate SIFT detector
164
+ sift = cv2.SIFT_create()
165
+ keypoints1, descriptors1 = sift.detectAndCompute(template_image, None)
166
+ keypoints2, descriptors2 = sift.detectAndCompute(gray_image, None)
167
+
168
+ # FLANN parameters
169
+ index_params = dict(algorithm=1, trees=5)
170
+ search_params = dict(checks=50)
171
+ flann = cv2.FlannBasedMatcher(index_params, search_params)
172
+ matches = flann.knnMatch(descriptors1, descriptors2, k=2)
173
+
174
+ # Apply ratio test
175
+ good_matches = [m for m, n in matches if m.distance < 0.7 * n.distance]
176
+
177
+ if len(good_matches) > MIN_MATCH_COUNT:
178
+ src_points = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
179
+ dst_points = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
180
+
181
+ M, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0)
182
+ h, w = template_image.shape
183
+ template_corners = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
184
+ dst_corners = cv2.perspectiveTransform(template_corners, M)
185
+ x1, y1 = map(round, dst_corners[0][0])
186
+ x2, y2 = map(round, dst_corners[2][0])
187
+ # if orientation == 0:
188
+ # checker_large_im = abs(y2 - y1)
189
+ # checker_small_im = abs(x2 - x1)
190
+ # else:
191
+ checker_small_im = abs(y2 - y1)
192
+ checker_large_im = abs(x2 - x1)
193
+ if checker_small_im != 0 and checker_large_im != 0:
194
+ px_cm_ratio_small = checker_small_real / checker_small_im
195
+ px_cm_ratio_large = checker_large_real / checker_large_im
196
+ else:
197
+ px_cm_ratio_small = 0
198
+ px_cm_ratio_large = 0
199
+
200
+ annotated_image = cv2.polylines(image_c, [np.int32(dst_corners)], True, 255, 3, cv2.LINE_AA)
201
+
202
+ if orientation == 0:
203
+ annotated_image = cv2.rotate(annotated_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
204
+ else:
205
+ print(f"Not enough matches are found - {len(good_matches)}/{MIN_MATCH_COUNT}")
206
+ return None, 0, 0
207
+ if orientation ==0:
208
+ cm_per_pixel_width = px_cm_ratio_small
209
+ cm_per_pixel_height = px_cm_ratio_large
210
+ else:
211
+ cm_per_pixel_width = px_cm_ratio_large
212
+ cm_per_pixel_height = px_cm_ratio_small
213
+
214
+ return annotated_image, dst_corners, cm_per_pixel_width, cm_per_pixel_height,checker_small_im,checker_large_im
215
+
216
+
217
+ def srgb_to_linear(rgb):
218
+ rgb = rgb / 255.0
219
+ linear_rgb = np.where(rgb <= 0.04045, rgb / 12.92, ((rgb + 0.055) / 1.055) ** 2.4)
220
+ return linear_rgb
221
+
222
+
223
+ def linear_to_srgb(linear_rgb):
224
+ # Clip linear_rgb to ensure no negative values
225
+ linear_rgb = np.clip(linear_rgb, 0, 1)
226
+ srgb = np.where(linear_rgb <= 0.0031308, linear_rgb * 12.92, 1.055 * (linear_rgb ** (1 / 2.4)) - 0.055)
227
+ srgb = np.clip(srgb * 255, 0, 255)
228
+ return srgb.astype(np.uint8)
229
+
230
+
231
+ def calculate_color_correction_matrix_ransac(sample_rgb, reference_rgb):
232
+ sample_rgb = sample_rgb[::-1]
233
+ sample_rgb_linear = srgb_to_linear(sample_rgb)
234
+ reference_rgb_linear = srgb_to_linear(reference_rgb)
235
+
236
+ # Reshape the data for RANSAC
237
+ X = sample_rgb_linear
238
+ y = reference_rgb_linear
239
+
240
+ # Initialize RANSAC regressor for each color channel
241
+ models = []
242
+ scores = []
243
+ for i in range(3): # For each RGB channel
244
+ ransac = LinearRegression()
245
+ ransac.fit(X, y[:, i])
246
+ scores.append(ransac.score(X, y[:, i]))
247
+ models.append(ransac.coef_)
248
+ score = np.mean(scores)
249
+ # Stack coefficients to form the transformation matrix
250
+ M = np.stack(models, axis=-1)
251
+
252
+ return M, score
253
+
254
+
255
+ def apply_color_correction(image, M):
256
+ image_linear = srgb_to_linear(image)
257
+ corrected_image_linear = np.dot(image_linear, M)
258
+ corrected_image_srgb = linear_to_srgb(corrected_image_linear)
259
+ return corrected_image_srgb
260
+
261
+
262
+ def calibrate_img(img):
263
+ image, orientation = check_orientation(img)
264
+ annotated_image, polygon, px_width, px_height,small_side,large_side = detect_template(image, orientation)
265
+ a, b = detect_RGB_values(image, polygon)
266
+ M, score = calculate_color_correction_matrix_ransac(a, b)
267
+ if orientation == 0:
268
+ image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
269
+ corrected_image = apply_color_correction(image, M)
270
+ #corrected_image = cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB)
271
+ if orientation == 0:
272
+ width = small_side
273
+ height = large_side
274
+ else:
275
+ width = large_side
276
+ height = small_side
277
+ return annotated_image, corrected_image, px_width, px_height, width, height
278
+
279
+
280
+ def process_img(img):
281
+ return calibrate_img(img)
282
+
283
+
284
+ app = gr.Interface(
285
+ fn=process_img,
286
+ inputs=gr.Image(label="Input"),
287
+ css=css,
288
+ outputs=[gr.Image(label="Output"), gr.Image(label="Corrected"), gr.Label(label='Cm/px for Width'),
289
+ gr.Label(label='Cm/px for Height'), gr.Label(label='Checker Width'),
290
+ gr.Label(label='Checker Height')],
291
+ allow_flagging='never')
292
+
293
+ app.launch(share=True)
template_img.png ADDED
venv/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # created by virtualenv automatically
2
+ *
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/LICENSE ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Copyright (C) 2016 Cory Dolphin, Olin College
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/METADATA ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: Flask-Cors
3
+ Version: 4.0.1
4
+ Summary: A Flask extension adding a decorator for CORS support
5
+ Home-page: https://github.com/corydolphin/flask-cors
6
+ Author: Cory Dolphin
7
+ Author-email: [email protected]
8
+ License: MIT
9
+ Platform: any
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ License-File: LICENSE
25
+ Requires-Dist: Flask >=0.9
26
+
27
+ Flask-CORS
28
+ ==========
29
+
30
+ |Build Status| |Latest Version| |Supported Python versions|
31
+ |License|
32
+
33
+ A Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.
34
+
35
+ This package has a simple philosophy: when you want to enable CORS, you wish to enable it for all use cases on a domain.
36
+ This means no mucking around with different allowed headers, methods, etc.
37
+
38
+ By default, submission of cookies across domains is disabled due to the security implications.
39
+ Please see the documentation for how to enable credential'ed requests, and please make sure you add some sort of `CSRF <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`__ protection before doing so!
40
+
41
+ Installation
42
+ ------------
43
+
44
+ Install the extension with using pip, or easy\_install.
45
+
46
+ .. code:: bash
47
+
48
+ $ pip install -U flask-cors
49
+
50
+ Usage
51
+ -----
52
+
53
+ This package exposes a Flask extension which by default enables CORS support on all routes, for all origins and methods.
54
+ It allows parameterization of all CORS headers on a per-resource level.
55
+ The package also contains a decorator, for those who prefer this approach.
56
+
57
+ Simple Usage
58
+ ~~~~~~~~~~~~
59
+
60
+ In the simplest case, initialize the Flask-Cors extension with default arguments in order to allow CORS for all domains on all routes.
61
+ See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__.
62
+
63
+ .. code:: python
64
+
65
+
66
+ from flask import Flask
67
+ from flask_cors import CORS
68
+
69
+ app = Flask(__name__)
70
+ CORS(app)
71
+
72
+ @app.route("/")
73
+ def helloWorld():
74
+ return "Hello, cross-origin-world!"
75
+
76
+ Resource specific CORS
77
+ ^^^^^^^^^^^^^^^^^^^^^^
78
+
79
+ Alternatively, you can specify CORS options on a resource and origin level of granularity by passing a dictionary as the `resources` option, mapping paths to a set of options.
80
+ See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__.
81
+
82
+ .. code:: python
83
+
84
+ app = Flask(__name__)
85
+ cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
86
+
87
+ @app.route("/api/v1/users")
88
+ def list_users():
89
+ return "user example"
90
+
91
+ Route specific CORS via decorator
92
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93
+
94
+ This extension also exposes a simple decorator to decorate flask routes with.
95
+ Simply add ``@cross_origin()`` below a call to Flask's ``@app.route(..)`` to allow CORS on a given route.
96
+ See the full list of options in the `decorator documentation <https://flask-cors.corydolphin.com/en/latest/api.html#decorator>`__.
97
+
98
+ .. code:: python
99
+
100
+ @app.route("/")
101
+ @cross_origin()
102
+ def helloWorld():
103
+ return "Hello, cross-origin-world!"
104
+
105
+ Documentation
106
+ -------------
107
+
108
+ For a full list of options, please see the full `documentation <https://flask-cors.corydolphin.com/en/latest/api.html>`__
109
+
110
+ Troubleshooting
111
+ ---------------
112
+
113
+ If things aren't working as you expect, enable logging to help understand what is going on under the hood, and why.
114
+
115
+ .. code:: python
116
+
117
+ logging.getLogger('flask_cors').level = logging.DEBUG
118
+
119
+
120
+ Tests
121
+ -----
122
+
123
+ A simple set of tests is included in ``test/``.
124
+ To run, install nose, and simply invoke ``nosetests`` or ``python setup.py test`` to exercise the tests.
125
+
126
+ If nosetests does not work for you, due to it no longer working with newer python versions.
127
+ You can use pytest to run the tests instead.
128
+
129
+ Contributing
130
+ ------------
131
+
132
+ Questions, comments or improvements?
133
+ Please create an issue on `Github <https://github.com/corydolphin/flask-cors>`__, tweet at `@corydolphin <https://twitter.com/corydolphin>`__ or send me an email.
134
+ I do my best to include every contribution proposed in any way that I can.
135
+
136
+ Credits
137
+ -------
138
+
139
+ This Flask extension is based upon the `Decorator for the HTTP Access Control <https://web.archive.org/web/20190128010149/http://flask.pocoo.org/snippets/56/>`__ written by Armin Ronacher.
140
+
141
+ .. |Build Status| image:: https://github.com/corydolphin/flask-cors/actions/workflows/unittests.yaml/badge.svg
142
+ :target: https://travis-ci.org/corydolphin/flask-cors
143
+ .. |Latest Version| image:: https://img.shields.io/pypi/v/Flask-Cors.svg
144
+ :target: https://pypi.python.org/pypi/Flask-Cors/
145
+ .. |Supported Python versions| image:: https://img.shields.io/pypi/pyversions/Flask-Cors.svg
146
+ :target: https://img.shields.io/pypi/pyversions/Flask-Cors.svg
147
+ .. |License| image:: http://img.shields.io/:license-mit-blue.svg
148
+ :target: https://pypi.python.org/pypi/Flask-Cors/
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/RECORD ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask_Cors-4.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ Flask_Cors-4.0.1.dist-info/LICENSE,sha256=bhob3FSDTB4HQMvOXV9vLK4chG_Sp_SCsRZJWU-vvV0,1069
3
+ Flask_Cors-4.0.1.dist-info/METADATA,sha256=NyVEWcY6gn4ZrDqXr_pWZYP8WDq_cqBhobJjeAePkcE,5474
4
+ Flask_Cors-4.0.1.dist-info/RECORD,,
5
+ Flask_Cors-4.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ Flask_Cors-4.0.1.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
7
+ Flask_Cors-4.0.1.dist-info/top_level.txt,sha256=aWye_0QNZPp_QtPF4ZluLHqnyVLT9CPJsfiGhwqkWuo,11
8
+ flask_cors/__init__.py,sha256=wZDCvPTHspA2g1VV7KyKN7R-uCdBnirTlsCzgPDcQtI,792
9
+ flask_cors/__pycache__/__init__.cpython-39.pyc,,
10
+ flask_cors/__pycache__/core.cpython-39.pyc,,
11
+ flask_cors/__pycache__/decorator.cpython-39.pyc,,
12
+ flask_cors/__pycache__/extension.cpython-39.pyc,,
13
+ flask_cors/__pycache__/version.cpython-39.pyc,,
14
+ flask_cors/core.py,sha256=e1u_o5SOcS_gMWGjcQrkyk91uPICnzZ3AXZvy5jQ_FE,14063
15
+ flask_cors/decorator.py,sha256=BeJsyX1wYhVKWN04FAhb6z8YqffiRr7wKqwzHPap4bw,5009
16
+ flask_cors/extension.py,sha256=9IsibtRtr5ylAtvR9R-vQI0RASp0dT5ri6KnhIjLfvU,7857
17
+ flask_cors/version.py,sha256=t3_GiRZvVdqtOM0gRAa8vnfrcrUkhvgVNLEwrIaUIcw,22
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/REQUESTED ADDED
File without changes
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/WHEEL ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
6
+
venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ flask_cors
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst 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-2.1.5.dist-info/METADATA ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: MarkupSafe
3
+ Version: 2.1.5
4
+ Summary: Safely add untrusted strings to HTML/XML markup.
5
+ Home-page: https://palletsprojects.com/p/markupsafe/
6
+ Maintainer: Pallets
7
+ Maintainer-email: [email protected]
8
+ License: BSD-3-Clause
9
+ Project-URL: Donate, https://palletsprojects.com/donate
10
+ Project-URL: Documentation, https://markupsafe.palletsprojects.com/
11
+ Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
12
+ Project-URL: Source Code, https://github.com/pallets/markupsafe/
13
+ Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
14
+ Project-URL: Chat, https://discord.gg/pallets
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Environment :: Web Environment
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: License :: OSI Approved :: BSD License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
22
+ Classifier: Topic :: Text Processing :: Markup :: HTML
23
+ Requires-Python: >=3.7
24
+ Description-Content-Type: text/x-rst
25
+ License-File: LICENSE.rst
26
+
27
+ MarkupSafe
28
+ ==========
29
+
30
+ MarkupSafe implements a text object that escapes characters so it is
31
+ safe to use in HTML and XML. Characters that have special meanings are
32
+ replaced so that they display as the actual characters. This mitigates
33
+ injection attacks, meaning untrusted user input can safely be displayed
34
+ on a page.
35
+
36
+
37
+ Installing
38
+ ----------
39
+
40
+ Install and update using `pip`_:
41
+
42
+ .. code-block:: text
43
+
44
+ pip install -U MarkupSafe
45
+
46
+ .. _pip: https://pip.pypa.io/en/stable/getting-started/
47
+
48
+
49
+ Examples
50
+ --------
51
+
52
+ .. code-block:: pycon
53
+
54
+ >>> from markupsafe import Markup, escape
55
+
56
+ >>> # escape replaces special characters and wraps in Markup
57
+ >>> escape("<script>alert(document.cookie);</script>")
58
+ Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
59
+
60
+ >>> # wrap in Markup to mark text "safe" and prevent escaping
61
+ >>> Markup("<strong>Hello</strong>")
62
+ Markup('<strong>hello</strong>')
63
+
64
+ >>> escape(Markup("<strong>Hello</strong>"))
65
+ Markup('<strong>hello</strong>')
66
+
67
+ >>> # Markup is a str subclass
68
+ >>> # methods and operators escape their arguments
69
+ >>> template = Markup("Hello <em>{name}</em>")
70
+ >>> template.format(name='"World"')
71
+ Markup('Hello <em>&#34;World&#34;</em>')
72
+
73
+
74
+ Donate
75
+ ------
76
+
77
+ The Pallets organization develops and supports MarkupSafe and other
78
+ popular packages. In order to grow the community of contributors and
79
+ users, and allow the maintainers to devote more time to the projects,
80
+ `please donate today`_.
81
+
82
+ .. _please donate today: https://palletsprojects.com/donate
83
+
84
+
85
+ Links
86
+ -----
87
+
88
+ - Documentation: https://markupsafe.palletsprojects.com/
89
+ - Changes: https://markupsafe.palletsprojects.com/changes/
90
+ - PyPI Releases: https://pypi.org/project/MarkupSafe/
91
+ - Source Code: https://github.com/pallets/markupsafe/
92
+ - Issue Tracker: https://github.com/pallets/markupsafe/issues/
93
+ - Chat: https://discord.gg/pallets
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
3
+ MarkupSafe-2.1.5.dist-info/METADATA,sha256=icNlaniV7YIQZ1BScCVqNaRtm7MAgfw8d3OBmoSVyAY,3096
4
+ MarkupSafe-2.1.5.dist-info/RECORD,,
5
+ MarkupSafe-2.1.5.dist-info/WHEEL,sha256=GZFS91_ufm4WrNPBaFVPB9MvOXR6bMZQhPcZRRTN5YM,100
6
+ MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
7
+ markupsafe/__init__.py,sha256=m1ysNeqf55zbEoJtaovca40ivrkEFolPlw5bGoC5Gi4,11290
8
+ markupsafe/__pycache__/__init__.cpython-39.pyc,,
9
+ markupsafe/__pycache__/_native.cpython-39.pyc,,
10
+ markupsafe/_native.py,sha256=_Q7UsXCOvgdonCgqG3l5asANI6eo50EKnDM-mlwEC5M,1776
11
+ markupsafe/_speedups.c,sha256=n3jzzaJwXcoN8nTFyA53f3vSqsWK2vujI-v6QYifjhQ,7403
12
+ markupsafe/_speedups.cp39-win_amd64.pyd,sha256=mxpVr1JPAspPHonOfxzkg7mQIKFTQuzl9v95Ejv5zks,15872
13
+ markupsafe/_speedups.pyi,sha256=f5QtwIOP0eLrxh2v5p6SmaYmlcHIGIfmz0DovaqL0OU,238
14
+ markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.42.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-cp39-win_amd64
5
+
venv/Lib/site-packages/MarkupSafe-2.1.5.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,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
32
+ from __future__ import annotations
33
+
34
+ import os
35
+ import struct
36
+ from enum import IntEnum
37
+ from io import BytesIO
38
+
39
+ from . import Image, ImageFile
40
+
41
+
42
+ class Format(IntEnum):
43
+ JPEG = 0
44
+
45
+
46
+ class Encoding(IntEnum):
47
+ UNCOMPRESSED = 1
48
+ DXT = 2
49
+ UNCOMPRESSED_RAW_BGRA = 3
50
+
51
+
52
+ class AlphaEncoding(IntEnum):
53
+ DXT1 = 0
54
+ DXT3 = 1
55
+ DXT5 = 7
56
+
57
+
58
+ def unpack_565(i):
59
+ return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
60
+
61
+
62
+ def decode_dxt1(data, alpha=False):
63
+ """
64
+ input: one "row" of data (i.e. will produce 4*width pixels)
65
+ """
66
+
67
+ blocks = len(data) // 8 # number of blocks in row
68
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
69
+
70
+ for block in range(blocks):
71
+ # Decode next 8-byte block.
72
+ idx = block * 8
73
+ color0, color1, bits = struct.unpack_from("<HHI", data, idx)
74
+
75
+ r0, g0, b0 = unpack_565(color0)
76
+ r1, g1, b1 = unpack_565(color1)
77
+
78
+ # Decode this block into 4x4 pixels
79
+ # Accumulate the results onto our 4 row accumulators
80
+ for j in range(4):
81
+ for i in range(4):
82
+ # get next control op and generate a pixel
83
+
84
+ control = bits & 3
85
+ bits = bits >> 2
86
+
87
+ a = 0xFF
88
+ if control == 0:
89
+ r, g, b = r0, g0, b0
90
+ elif control == 1:
91
+ r, g, b = r1, g1, b1
92
+ elif control == 2:
93
+ if color0 > color1:
94
+ r = (2 * r0 + r1) // 3
95
+ g = (2 * g0 + g1) // 3
96
+ b = (2 * b0 + b1) // 3
97
+ else:
98
+ r = (r0 + r1) // 2
99
+ g = (g0 + g1) // 2
100
+ b = (b0 + b1) // 2
101
+ elif control == 3:
102
+ if color0 > color1:
103
+ r = (2 * r1 + r0) // 3
104
+ g = (2 * g1 + g0) // 3
105
+ b = (2 * b1 + b0) // 3
106
+ else:
107
+ r, g, b, a = 0, 0, 0, 0
108
+
109
+ if alpha:
110
+ ret[j].extend([r, g, b, a])
111
+ else:
112
+ ret[j].extend([r, g, b])
113
+
114
+ return ret
115
+
116
+
117
+ def decode_dxt3(data):
118
+ """
119
+ input: one "row" of data (i.e. will produce 4*width pixels)
120
+ """
121
+
122
+ blocks = len(data) // 16 # number of blocks in row
123
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
124
+
125
+ for block in range(blocks):
126
+ idx = block * 16
127
+ block = data[idx : idx + 16]
128
+ # Decode next 16-byte block.
129
+ bits = struct.unpack_from("<8B", block)
130
+ color0, color1 = struct.unpack_from("<HH", block, 8)
131
+
132
+ (code,) = struct.unpack_from("<I", block, 12)
133
+
134
+ r0, g0, b0 = unpack_565(color0)
135
+ r1, g1, b1 = unpack_565(color1)
136
+
137
+ for j in range(4):
138
+ high = False # Do we want the higher bits?
139
+ for i in range(4):
140
+ alphacode_index = (4 * j + i) // 2
141
+ a = bits[alphacode_index]
142
+ if high:
143
+ high = False
144
+ a >>= 4
145
+ else:
146
+ high = True
147
+ a &= 0xF
148
+ a *= 17 # We get a value between 0 and 15
149
+
150
+ color_code = (code >> 2 * (4 * j + i)) & 0x03
151
+
152
+ if color_code == 0:
153
+ r, g, b = r0, g0, b0
154
+ elif color_code == 1:
155
+ r, g, b = r1, g1, b1
156
+ elif color_code == 2:
157
+ r = (2 * r0 + r1) // 3
158
+ g = (2 * g0 + g1) // 3
159
+ b = (2 * b0 + b1) // 3
160
+ elif color_code == 3:
161
+ r = (2 * r1 + r0) // 3
162
+ g = (2 * g1 + g0) // 3
163
+ b = (2 * b1 + b0) // 3
164
+
165
+ ret[j].extend([r, g, b, a])
166
+
167
+ return ret
168
+
169
+
170
+ def decode_dxt5(data):
171
+ """
172
+ input: one "row" of data (i.e. will produce 4 * width pixels)
173
+ """
174
+
175
+ blocks = len(data) // 16 # number of blocks in row
176
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
177
+
178
+ for block in range(blocks):
179
+ idx = block * 16
180
+ block = data[idx : idx + 16]
181
+ # Decode next 16-byte block.
182
+ a0, a1 = struct.unpack_from("<BB", block)
183
+
184
+ bits = struct.unpack_from("<6B", block, 2)
185
+ alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
186
+ alphacode2 = bits[0] | (bits[1] << 8)
187
+
188
+ color0, color1 = struct.unpack_from("<HH", block, 8)
189
+
190
+ (code,) = struct.unpack_from("<I", block, 12)
191
+
192
+ r0, g0, b0 = unpack_565(color0)
193
+ r1, g1, b1 = unpack_565(color1)
194
+
195
+ for j in range(4):
196
+ for i in range(4):
197
+ # get next control op and generate a pixel
198
+ alphacode_index = 3 * (4 * j + i)
199
+
200
+ if alphacode_index <= 12:
201
+ alphacode = (alphacode2 >> alphacode_index) & 0x07
202
+ elif alphacode_index == 15:
203
+ alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
204
+ else: # alphacode_index >= 18 and alphacode_index <= 45
205
+ alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
206
+
207
+ if alphacode == 0:
208
+ a = a0
209
+ elif alphacode == 1:
210
+ a = a1
211
+ elif a0 > a1:
212
+ a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
213
+ elif alphacode == 6:
214
+ a = 0
215
+ elif alphacode == 7:
216
+ a = 255
217
+ else:
218
+ a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
219
+
220
+ color_code = (code >> 2 * (4 * j + i)) & 0x03
221
+
222
+ if color_code == 0:
223
+ r, g, b = r0, g0, b0
224
+ elif color_code == 1:
225
+ r, g, b = r1, g1, b1
226
+ elif color_code == 2:
227
+ r = (2 * r0 + r1) // 3
228
+ g = (2 * g0 + g1) // 3
229
+ b = (2 * b0 + b1) // 3
230
+ elif color_code == 3:
231
+ r = (2 * r1 + r0) // 3
232
+ g = (2 * g1 + g0) // 3
233
+ b = (2 * b1 + b0) // 3
234
+
235
+ ret[j].extend([r, g, b, a])
236
+
237
+ return ret
238
+
239
+
240
+ class BLPFormatError(NotImplementedError):
241
+ pass
242
+
243
+
244
+ def _accept(prefix):
245
+ return prefix[:4] in (b"BLP1", b"BLP2")
246
+
247
+
248
+ class BlpImageFile(ImageFile.ImageFile):
249
+ """
250
+ Blizzard Mipmap Format
251
+ """
252
+
253
+ format = "BLP"
254
+ format_description = "Blizzard Mipmap Format"
255
+
256
+ def _open(self):
257
+ self.magic = self.fp.read(4)
258
+
259
+ self.fp.seek(5, os.SEEK_CUR)
260
+ (self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
261
+
262
+ self.fp.seek(2, os.SEEK_CUR)
263
+ self._size = struct.unpack("<II", self.fp.read(8))
264
+
265
+ if self.magic in (b"BLP1", b"BLP2"):
266
+ decoder = self.magic.decode()
267
+ else:
268
+ msg = f"Bad BLP magic {repr(self.magic)}"
269
+ raise BLPFormatError(msg)
270
+
271
+ self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
272
+ self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
273
+
274
+
275
+ class _BLPBaseDecoder(ImageFile.PyDecoder):
276
+ _pulls_fd = True
277
+
278
+ def decode(self, buffer):
279
+ try:
280
+ self._read_blp_header()
281
+ self._load()
282
+ except struct.error as e:
283
+ msg = "Truncated BLP file"
284
+ raise OSError(msg) from e
285
+ return -1, 0
286
+
287
+ def _read_blp_header(self):
288
+ self.fd.seek(4)
289
+ (self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
290
+
291
+ (self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
292
+ (self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
293
+ (self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
294
+ self.fd.seek(1, os.SEEK_CUR) # mips
295
+
296
+ self.size = struct.unpack("<II", self._safe_read(8))
297
+
298
+ if isinstance(self, BLP1Decoder):
299
+ # Only present for BLP1
300
+ (self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
301
+ self.fd.seek(4, os.SEEK_CUR) # subtype
302
+
303
+ self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
304
+ self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
305
+
306
+ def _safe_read(self, length):
307
+ return ImageFile._safe_read(self.fd, length)
308
+
309
+ def _read_palette(self):
310
+ ret = []
311
+ for i in range(256):
312
+ try:
313
+ b, g, r, a = struct.unpack("<4B", self._safe_read(4))
314
+ except struct.error:
315
+ break
316
+ ret.append((b, g, r, a))
317
+ return ret
318
+
319
+ def _read_bgra(self, palette):
320
+ data = bytearray()
321
+ _data = BytesIO(self._safe_read(self._blp_lengths[0]))
322
+ while True:
323
+ try:
324
+ (offset,) = struct.unpack("<B", _data.read(1))
325
+ except struct.error:
326
+ break
327
+ b, g, r, a = palette[offset]
328
+ d = (r, g, b)
329
+ if self._blp_alpha_depth:
330
+ d += (a,)
331
+ data.extend(d)
332
+ return data
333
+
334
+
335
+ class BLP1Decoder(_BLPBaseDecoder):
336
+ def _load(self):
337
+ if self._blp_compression == Format.JPEG:
338
+ self._decode_jpeg_stream()
339
+
340
+ elif self._blp_compression == 1:
341
+ if self._blp_encoding in (4, 5):
342
+ palette = self._read_palette()
343
+ data = self._read_bgra(palette)
344
+ self.set_as_raw(data)
345
+ else:
346
+ msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
347
+ raise BLPFormatError(msg)
348
+ else:
349
+ msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
350
+ raise BLPFormatError(msg)
351
+
352
+ def _decode_jpeg_stream(self):
353
+ from .JpegImagePlugin import JpegImageFile
354
+
355
+ (jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
356
+ jpeg_header = self._safe_read(jpeg_header_size)
357
+ self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
358
+ data = self._safe_read(self._blp_lengths[0])
359
+ data = jpeg_header + data
360
+ data = BytesIO(data)
361
+ image = JpegImageFile(data)
362
+ Image._decompression_bomb_check(image.size)
363
+ if image.mode == "CMYK":
364
+ decoder_name, extents, offset, args = image.tile[0]
365
+ image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
366
+ r, g, b = image.convert("RGB").split()
367
+ image = Image.merge("RGB", (b, g, r))
368
+ self.set_as_raw(image.tobytes())
369
+
370
+
371
+ class BLP2Decoder(_BLPBaseDecoder):
372
+ def _load(self):
373
+ palette = self._read_palette()
374
+
375
+ self.fd.seek(self._blp_offsets[0])
376
+
377
+ if self._blp_compression == 1:
378
+ # Uncompressed or DirectX compression
379
+
380
+ if self._blp_encoding == Encoding.UNCOMPRESSED:
381
+ data = self._read_bgra(palette)
382
+
383
+ elif self._blp_encoding == Encoding.DXT:
384
+ data = bytearray()
385
+ if self._blp_alpha_encoding == AlphaEncoding.DXT1:
386
+ linesize = (self.size[0] + 3) // 4 * 8
387
+ for yb in range((self.size[1] + 3) // 4):
388
+ for d in decode_dxt1(
389
+ self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
390
+ ):
391
+ data += d
392
+
393
+ elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
394
+ linesize = (self.size[0] + 3) // 4 * 16
395
+ for yb in range((self.size[1] + 3) // 4):
396
+ for d in decode_dxt3(self._safe_read(linesize)):
397
+ data += d
398
+
399
+ elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
400
+ linesize = (self.size[0] + 3) // 4 * 16
401
+ for yb in range((self.size[1] + 3) // 4):
402
+ for d in decode_dxt5(self._safe_read(linesize)):
403
+ data += d
404
+ else:
405
+ msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
406
+ raise BLPFormatError(msg)
407
+ else:
408
+ msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
409
+ raise BLPFormatError(msg)
410
+
411
+ else:
412
+ msg = f"Unknown BLP compression {repr(self._blp_compression)}"
413
+ raise BLPFormatError(msg)
414
+
415
+ self.set_as_raw(data)
416
+
417
+
418
+ class BLPEncoder(ImageFile.PyEncoder):
419
+ _pushes_fd = True
420
+
421
+ def _write_palette(self):
422
+ data = b""
423
+ palette = self.im.getpalette("RGBA", "RGBA")
424
+ for i in range(len(palette) // 4):
425
+ r, g, b, a = palette[i * 4 : (i + 1) * 4]
426
+ data += struct.pack("<4B", b, g, r, a)
427
+ while len(data) < 256 * 4:
428
+ data += b"\x00" * 4
429
+ return data
430
+
431
+ def encode(self, bufsize):
432
+ palette_data = self._write_palette()
433
+
434
+ offset = 20 + 16 * 4 * 2 + len(palette_data)
435
+ data = struct.pack("<16I", offset, *((0,) * 15))
436
+
437
+ w, h = self.im.size
438
+ data += struct.pack("<16I", w * h, *((0,) * 15))
439
+
440
+ data += palette_data
441
+
442
+ for y in range(h):
443
+ for x in range(w):
444
+ data += struct.pack("<B", self.im.getpixel((x, y)))
445
+
446
+ return len(data), 0, data
447
+
448
+
449
+ def _save(im, fp, filename):
450
+ if im.mode != "P":
451
+ msg = "Unsupported BLP image mode"
452
+ raise ValueError(msg)
453
+
454
+ magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
455
+ fp.write(magic)
456
+
457
+ fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
458
+ fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
459
+ fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
460
+ fp.write(struct.pack("<b", 0)) # alpha encoding
461
+ fp.write(struct.pack("<b", 0)) # mips
462
+ fp.write(struct.pack("<II", *im.size))
463
+ if magic == b"BLP1":
464
+ fp.write(struct.pack("<i", 5))
465
+ fp.write(struct.pack("<i", 0))
466
+
467
+ ImageFile._save(im, fp, [("BLP", (0, 0) + im.size, 0, im.mode)])
468
+
469
+
470
+ Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
471
+ Image.register_extension(BlpImageFile.format, ".blp")
472
+ Image.register_decoder("BLP1", BLP1Decoder)
473
+ Image.register_decoder("BLP2", BLP2Decoder)
474
+
475
+ Image.register_save(BlpImageFile.format, _save)
476
+ Image.register_encoder("BLP", BLPEncoder)
venv/Lib/site-packages/PIL/BmpImagePlugin.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ dest_length = self.state.xsize * self.state.ysize
295
+ while len(data) < dest_length:
296
+ pixels = self.fd.read(1)
297
+ byte = self.fd.read(1)
298
+ if not pixels or not byte:
299
+ break
300
+ num_pixels = pixels[0]
301
+ if num_pixels:
302
+ # encoded mode
303
+ if x + num_pixels > self.state.xsize:
304
+ # Too much data for row
305
+ num_pixels = max(0, self.state.xsize - x)
306
+ if rle4:
307
+ first_pixel = o8(byte[0] >> 4)
308
+ second_pixel = o8(byte[0] & 0x0F)
309
+ for index in range(num_pixels):
310
+ if index % 2 == 0:
311
+ data += first_pixel
312
+ else:
313
+ data += second_pixel
314
+ else:
315
+ data += byte * num_pixels
316
+ x += num_pixels
317
+ else:
318
+ if byte[0] == 0:
319
+ # end of line
320
+ while len(data) % self.state.xsize != 0:
321
+ data += b"\x00"
322
+ x = 0
323
+ elif byte[0] == 1:
324
+ # end of bitmap
325
+ break
326
+ elif byte[0] == 2:
327
+ # delta
328
+ bytes_read = self.fd.read(2)
329
+ if len(bytes_read) < 2:
330
+ break
331
+ right, up = self.fd.read(2)
332
+ data += b"\x00" * (right + up * self.state.xsize)
333
+ x = len(data) % self.state.xsize
334
+ else:
335
+ # absolute mode
336
+ if rle4:
337
+ # 2 pixels per byte
338
+ byte_count = byte[0] // 2
339
+ bytes_read = self.fd.read(byte_count)
340
+ for byte_read in bytes_read:
341
+ data += o8(byte_read >> 4)
342
+ data += o8(byte_read & 0x0F)
343
+ else:
344
+ byte_count = byte[0]
345
+ bytes_read = self.fd.read(byte_count)
346
+ data += bytes_read
347
+ if len(bytes_read) < byte_count:
348
+ break
349
+ x += byte[0]
350
+
351
+ # align to 16-bit word boundary
352
+ if self.fd.tell() % 2 != 0:
353
+ self.fd.seek(1, os.SEEK_CUR)
354
+ rawmode = "L" if self.mode == "L" else "P"
355
+ self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
356
+ return -1, 0
357
+
358
+
359
+ # =============================================================================
360
+ # Image plugin for the DIB format (BMP alias)
361
+ # =============================================================================
362
+ class DibImageFile(BmpImageFile):
363
+ format = "DIB"
364
+ format_description = "Windows Bitmap"
365
+
366
+ def _open(self):
367
+ self._bitmap()
368
+
369
+
370
+ #
371
+ # --------------------------------------------------------------------
372
+ # Write BMP file
373
+
374
+
375
+ SAVE = {
376
+ "1": ("1", 1, 2),
377
+ "L": ("L", 8, 256),
378
+ "P": ("P", 8, 256),
379
+ "RGB": ("BGR", 24, 0),
380
+ "RGBA": ("BGRA", 32, 0),
381
+ }
382
+
383
+
384
+ def _dib_save(im, fp, filename):
385
+ _save(im, fp, filename, False)
386
+
387
+
388
+ def _save(im, fp, filename, bitmap_header=True):
389
+ try:
390
+ rawmode, bits, colors = SAVE[im.mode]
391
+ except KeyError as e:
392
+ msg = f"cannot write mode {im.mode} as BMP"
393
+ raise OSError(msg) from e
394
+
395
+ info = im.encoderinfo
396
+
397
+ dpi = info.get("dpi", (96, 96))
398
+
399
+ # 1 meter == 39.3701 inches
400
+ ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
401
+
402
+ stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
403
+ header = 40 # or 64 for OS/2 version 2
404
+ image = stride * im.size[1]
405
+
406
+ if im.mode == "1":
407
+ palette = b"".join(o8(i) * 4 for i in (0, 255))
408
+ elif im.mode == "L":
409
+ palette = b"".join(o8(i) * 4 for i in range(256))
410
+ elif im.mode == "P":
411
+ palette = im.im.getpalette("RGB", "BGRX")
412
+ colors = len(palette) // 4
413
+ else:
414
+ palette = None
415
+
416
+ # bitmap header
417
+ if bitmap_header:
418
+ offset = 14 + header + colors * 4
419
+ file_size = offset + image
420
+ if file_size > 2**32 - 1:
421
+ msg = "File size is too large for the BMP format"
422
+ raise ValueError(msg)
423
+ fp.write(
424
+ b"BM" # file type (magic)
425
+ + o32(file_size) # file size
426
+ + o32(0) # reserved
427
+ + o32(offset) # image data offset
428
+ )
429
+
430
+ # bitmap info header
431
+ fp.write(
432
+ o32(header) # info header size
433
+ + o32(im.size[0]) # width
434
+ + o32(im.size[1]) # height
435
+ + o16(1) # planes
436
+ + o16(bits) # depth
437
+ + o32(0) # compression (0=uncompressed)
438
+ + o32(image) # size of bitmap
439
+ + o32(ppm[0]) # resolution
440
+ + o32(ppm[1]) # resolution
441
+ + o32(colors) # colors used
442
+ + o32(colors) # colors important
443
+ )
444
+
445
+ fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
446
+
447
+ if palette:
448
+ fp.write(palette)
449
+
450
+ ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
451
+
452
+
453
+ #
454
+ # --------------------------------------------------------------------
455
+ # Registry
456
+
457
+
458
+ Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
459
+ Image.register_save(BmpImageFile.format, _save)
460
+
461
+ Image.register_extension(BmpImageFile.format, ".bmp")
462
+
463
+ Image.register_mime(BmpImageFile.format, "image/bmp")
464
+
465
+ Image.register_decoder("bmp_rle", BmpRleDecoder)
466
+
467
+ Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
468
+ Image.register_save(DibImageFile.format, _dib_save)
469
+
470
+ Image.register_extension(DibImageFile.format, ".dib")
471
+
472
+ 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,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
13
+ from __future__ import annotations
14
+
15
+ import io
16
+ import struct
17
+ import sys
18
+ from enum import IntEnum, IntFlag
19
+
20
+ from . import Image, ImageFile, ImagePalette
21
+ from ._binary import i32le as i32
22
+ from ._binary import o8
23
+ from ._binary import o32le as o32
24
+
25
+ # Magic ("DDS ")
26
+ DDS_MAGIC = 0x20534444
27
+
28
+
29
+ # DDS flags
30
+ class DDSD(IntFlag):
31
+ CAPS = 0x1
32
+ HEIGHT = 0x2
33
+ WIDTH = 0x4
34
+ PITCH = 0x8
35
+ PIXELFORMAT = 0x1000
36
+ MIPMAPCOUNT = 0x20000
37
+ LINEARSIZE = 0x80000
38
+ DEPTH = 0x800000
39
+
40
+
41
+ # DDS caps
42
+ class DDSCAPS(IntFlag):
43
+ COMPLEX = 0x8
44
+ TEXTURE = 0x1000
45
+ MIPMAP = 0x400000
46
+
47
+
48
+ class DDSCAPS2(IntFlag):
49
+ CUBEMAP = 0x200
50
+ CUBEMAP_POSITIVEX = 0x400
51
+ CUBEMAP_NEGATIVEX = 0x800
52
+ CUBEMAP_POSITIVEY = 0x1000
53
+ CUBEMAP_NEGATIVEY = 0x2000
54
+ CUBEMAP_POSITIVEZ = 0x4000
55
+ CUBEMAP_NEGATIVEZ = 0x8000
56
+ VOLUME = 0x200000
57
+
58
+
59
+ # Pixel Format
60
+ class DDPF(IntFlag):
61
+ ALPHAPIXELS = 0x1
62
+ ALPHA = 0x2
63
+ FOURCC = 0x4
64
+ PALETTEINDEXED8 = 0x20
65
+ RGB = 0x40
66
+ LUMINANCE = 0x20000
67
+
68
+
69
+ # dxgiformat.h
70
+ class DXGI_FORMAT(IntEnum):
71
+ UNKNOWN = 0
72
+ R32G32B32A32_TYPELESS = 1
73
+ R32G32B32A32_FLOAT = 2
74
+ R32G32B32A32_UINT = 3
75
+ R32G32B32A32_SINT = 4
76
+ R32G32B32_TYPELESS = 5
77
+ R32G32B32_FLOAT = 6
78
+ R32G32B32_UINT = 7
79
+ R32G32B32_SINT = 8
80
+ R16G16B16A16_TYPELESS = 9
81
+ R16G16B16A16_FLOAT = 10
82
+ R16G16B16A16_UNORM = 11
83
+ R16G16B16A16_UINT = 12
84
+ R16G16B16A16_SNORM = 13
85
+ R16G16B16A16_SINT = 14
86
+ R32G32_TYPELESS = 15
87
+ R32G32_FLOAT = 16
88
+ R32G32_UINT = 17
89
+ R32G32_SINT = 18
90
+ R32G8X24_TYPELESS = 19
91
+ D32_FLOAT_S8X24_UINT = 20
92
+ R32_FLOAT_X8X24_TYPELESS = 21
93
+ X32_TYPELESS_G8X24_UINT = 22
94
+ R10G10B10A2_TYPELESS = 23
95
+ R10G10B10A2_UNORM = 24
96
+ R10G10B10A2_UINT = 25
97
+ R11G11B10_FLOAT = 26
98
+ R8G8B8A8_TYPELESS = 27
99
+ R8G8B8A8_UNORM = 28
100
+ R8G8B8A8_UNORM_SRGB = 29
101
+ R8G8B8A8_UINT = 30
102
+ R8G8B8A8_SNORM = 31
103
+ R8G8B8A8_SINT = 32
104
+ R16G16_TYPELESS = 33
105
+ R16G16_FLOAT = 34
106
+ R16G16_UNORM = 35
107
+ R16G16_UINT = 36
108
+ R16G16_SNORM = 37
109
+ R16G16_SINT = 38
110
+ R32_TYPELESS = 39
111
+ D32_FLOAT = 40
112
+ R32_FLOAT = 41
113
+ R32_UINT = 42
114
+ R32_SINT = 43
115
+ R24G8_TYPELESS = 44
116
+ D24_UNORM_S8_UINT = 45
117
+ R24_UNORM_X8_TYPELESS = 46
118
+ X24_TYPELESS_G8_UINT = 47
119
+ R8G8_TYPELESS = 48
120
+ R8G8_UNORM = 49
121
+ R8G8_UINT = 50
122
+ R8G8_SNORM = 51
123
+ R8G8_SINT = 52
124
+ R16_TYPELESS = 53
125
+ R16_FLOAT = 54
126
+ D16_UNORM = 55
127
+ R16_UNORM = 56
128
+ R16_UINT = 57
129
+ R16_SNORM = 58
130
+ R16_SINT = 59
131
+ R8_TYPELESS = 60
132
+ R8_UNORM = 61
133
+ R8_UINT = 62
134
+ R8_SNORM = 63
135
+ R8_SINT = 64
136
+ A8_UNORM = 65
137
+ R1_UNORM = 66
138
+ R9G9B9E5_SHAREDEXP = 67
139
+ R8G8_B8G8_UNORM = 68
140
+ G8R8_G8B8_UNORM = 69
141
+ BC1_TYPELESS = 70
142
+ BC1_UNORM = 71
143
+ BC1_UNORM_SRGB = 72
144
+ BC2_TYPELESS = 73
145
+ BC2_UNORM = 74
146
+ BC2_UNORM_SRGB = 75
147
+ BC3_TYPELESS = 76
148
+ BC3_UNORM = 77
149
+ BC3_UNORM_SRGB = 78
150
+ BC4_TYPELESS = 79
151
+ BC4_UNORM = 80
152
+ BC4_SNORM = 81
153
+ BC5_TYPELESS = 82
154
+ BC5_UNORM = 83
155
+ BC5_SNORM = 84
156
+ B5G6R5_UNORM = 85
157
+ B5G5R5A1_UNORM = 86
158
+ B8G8R8A8_UNORM = 87
159
+ B8G8R8X8_UNORM = 88
160
+ R10G10B10_XR_BIAS_A2_UNORM = 89
161
+ B8G8R8A8_TYPELESS = 90
162
+ B8G8R8A8_UNORM_SRGB = 91
163
+ B8G8R8X8_TYPELESS = 92
164
+ B8G8R8X8_UNORM_SRGB = 93
165
+ BC6H_TYPELESS = 94
166
+ BC6H_UF16 = 95
167
+ BC6H_SF16 = 96
168
+ BC7_TYPELESS = 97
169
+ BC7_UNORM = 98
170
+ BC7_UNORM_SRGB = 99
171
+ AYUV = 100
172
+ Y410 = 101
173
+ Y416 = 102
174
+ NV12 = 103
175
+ P010 = 104
176
+ P016 = 105
177
+ OPAQUE_420 = 106
178
+ YUY2 = 107
179
+ Y210 = 108
180
+ Y216 = 109
181
+ NV11 = 110
182
+ AI44 = 111
183
+ IA44 = 112
184
+ P8 = 113
185
+ A8P8 = 114
186
+ B4G4R4A4_UNORM = 115
187
+ P208 = 130
188
+ V208 = 131
189
+ V408 = 132
190
+ SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
191
+ SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
192
+
193
+
194
+ class D3DFMT(IntEnum):
195
+ UNKNOWN = 0
196
+ R8G8B8 = 20
197
+ A8R8G8B8 = 21
198
+ X8R8G8B8 = 22
199
+ R5G6B5 = 23
200
+ X1R5G5B5 = 24
201
+ A1R5G5B5 = 25
202
+ A4R4G4B4 = 26
203
+ R3G3B2 = 27
204
+ A8 = 28
205
+ A8R3G3B2 = 29
206
+ X4R4G4B4 = 30
207
+ A2B10G10R10 = 31
208
+ A8B8G8R8 = 32
209
+ X8B8G8R8 = 33
210
+ G16R16 = 34
211
+ A2R10G10B10 = 35
212
+ A16B16G16R16 = 36
213
+ A8P8 = 40
214
+ P8 = 41
215
+ L8 = 50
216
+ A8L8 = 51
217
+ A4L4 = 52
218
+ V8U8 = 60
219
+ L6V5U5 = 61
220
+ X8L8V8U8 = 62
221
+ Q8W8V8U8 = 63
222
+ V16U16 = 64
223
+ A2W10V10U10 = 67
224
+ D16_LOCKABLE = 70
225
+ D32 = 71
226
+ D15S1 = 73
227
+ D24S8 = 75
228
+ D24X8 = 77
229
+ D24X4S4 = 79
230
+ D16 = 80
231
+ D32F_LOCKABLE = 82
232
+ D24FS8 = 83
233
+ D32_LOCKABLE = 84
234
+ S8_LOCKABLE = 85
235
+ L16 = 81
236
+ VERTEXDATA = 100
237
+ INDEX16 = 101
238
+ INDEX32 = 102
239
+ Q16W16V16U16 = 110
240
+ R16F = 111
241
+ G16R16F = 112
242
+ A16B16G16R16F = 113
243
+ R32F = 114
244
+ G32R32F = 115
245
+ A32B32G32R32F = 116
246
+ CxV8U8 = 117
247
+ A1 = 118
248
+ A2B10G10R10_XR_BIAS = 119
249
+ BINARYBUFFER = 199
250
+
251
+ UYVY = i32(b"UYVY")
252
+ R8G8_B8G8 = i32(b"RGBG")
253
+ YUY2 = i32(b"YUY2")
254
+ G8R8_G8B8 = i32(b"GRGB")
255
+ DXT1 = i32(b"DXT1")
256
+ DXT2 = i32(b"DXT2")
257
+ DXT3 = i32(b"DXT3")
258
+ DXT4 = i32(b"DXT4")
259
+ DXT5 = i32(b"DXT5")
260
+ DX10 = i32(b"DX10")
261
+ BC4S = i32(b"BC4S")
262
+ BC4U = i32(b"BC4U")
263
+ BC5S = i32(b"BC5S")
264
+ BC5U = i32(b"BC5U")
265
+ ATI1 = i32(b"ATI1")
266
+ ATI2 = i32(b"ATI2")
267
+ MULTI2_ARGB8 = i32(b"MET1")
268
+
269
+
270
+ # Backward compatibility layer
271
+ module = sys.modules[__name__]
272
+ for item in DDSD:
273
+ assert item.name is not None
274
+ setattr(module, "DDSD_" + item.name, item.value)
275
+ for item1 in DDSCAPS:
276
+ assert item1.name is not None
277
+ setattr(module, "DDSCAPS_" + item1.name, item1.value)
278
+ for item2 in DDSCAPS2:
279
+ assert item2.name is not None
280
+ setattr(module, "DDSCAPS2_" + item2.name, item2.value)
281
+ for item3 in DDPF:
282
+ assert item3.name is not None
283
+ setattr(module, "DDPF_" + item3.name, item3.value)
284
+
285
+ DDS_FOURCC = DDPF.FOURCC
286
+ DDS_RGB = DDPF.RGB
287
+ DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS
288
+ DDS_LUMINANCE = DDPF.LUMINANCE
289
+ DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS
290
+ DDS_ALPHA = DDPF.ALPHA
291
+ DDS_PAL8 = DDPF.PALETTEINDEXED8
292
+
293
+ DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
294
+ DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT
295
+ DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH
296
+ DDS_HEADER_FLAGS_PITCH = DDSD.PITCH
297
+ DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE
298
+
299
+ DDS_HEIGHT = DDSD.HEIGHT
300
+ DDS_WIDTH = DDSD.WIDTH
301
+
302
+ DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE
303
+ DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP
304
+ DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX
305
+
306
+ DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX
307
+ DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX
308
+ DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY
309
+ DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY
310
+ DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ
311
+ DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ
312
+
313
+ DXT1_FOURCC = D3DFMT.DXT1
314
+ DXT3_FOURCC = D3DFMT.DXT3
315
+ DXT5_FOURCC = D3DFMT.DXT5
316
+
317
+ DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS
318
+ DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM
319
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB
320
+ DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS
321
+ DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM
322
+ DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM
323
+ DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16
324
+ DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16
325
+ DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS
326
+ DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM
327
+ DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB
328
+
329
+
330
+ class DdsImageFile(ImageFile.ImageFile):
331
+ format = "DDS"
332
+ format_description = "DirectDraw Surface"
333
+
334
+ def _open(self):
335
+ if not _accept(self.fp.read(4)):
336
+ msg = "not a DDS file"
337
+ raise SyntaxError(msg)
338
+ (header_size,) = struct.unpack("<I", self.fp.read(4))
339
+ if header_size != 124:
340
+ msg = f"Unsupported header size {repr(header_size)}"
341
+ raise OSError(msg)
342
+ header_bytes = self.fp.read(header_size - 4)
343
+ if len(header_bytes) != 120:
344
+ msg = f"Incomplete header: {len(header_bytes)} bytes"
345
+ raise OSError(msg)
346
+ header = io.BytesIO(header_bytes)
347
+
348
+ flags, height, width = struct.unpack("<3I", header.read(12))
349
+ self._size = (width, height)
350
+ extents = (0, 0) + self.size
351
+
352
+ pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
353
+ struct.unpack("<11I", header.read(44)) # reserved
354
+
355
+ # pixel format
356
+ pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
357
+ n = 0
358
+ rawmode = None
359
+ if pfflags & DDPF.RGB:
360
+ # Texture contains uncompressed RGB data
361
+ if pfflags & DDPF.ALPHAPIXELS:
362
+ self._mode = "RGBA"
363
+ mask_count = 4
364
+ else:
365
+ self._mode = "RGB"
366
+ mask_count = 3
367
+
368
+ masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
369
+ self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
370
+ return
371
+ elif pfflags & DDPF.LUMINANCE:
372
+ if bitcount == 8:
373
+ self._mode = "L"
374
+ elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS:
375
+ self._mode = "LA"
376
+ else:
377
+ msg = f"Unsupported bitcount {bitcount} for {pfflags}"
378
+ raise OSError(msg)
379
+ elif pfflags & DDPF.PALETTEINDEXED8:
380
+ self._mode = "P"
381
+ self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
382
+ elif pfflags & DDPF.FOURCC:
383
+ offset = header_size + 4
384
+ if fourcc == D3DFMT.DXT1:
385
+ self._mode = "RGBA"
386
+ self.pixel_format = "DXT1"
387
+ n = 1
388
+ elif fourcc == D3DFMT.DXT3:
389
+ self._mode = "RGBA"
390
+ self.pixel_format = "DXT3"
391
+ n = 2
392
+ elif fourcc == D3DFMT.DXT5:
393
+ self._mode = "RGBA"
394
+ self.pixel_format = "DXT5"
395
+ n = 3
396
+ elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
397
+ self._mode = "L"
398
+ self.pixel_format = "BC4"
399
+ n = 4
400
+ elif fourcc == D3DFMT.BC5S:
401
+ self._mode = "RGB"
402
+ self.pixel_format = "BC5S"
403
+ n = 5
404
+ elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
405
+ self._mode = "RGB"
406
+ self.pixel_format = "BC5"
407
+ n = 5
408
+ elif fourcc == D3DFMT.DX10:
409
+ offset += 20
410
+ # ignoring flags which pertain to volume textures and cubemaps
411
+ (dxgi_format,) = struct.unpack("<I", self.fp.read(4))
412
+ self.fp.read(16)
413
+ if dxgi_format in (
414
+ DXGI_FORMAT.BC1_UNORM,
415
+ DXGI_FORMAT.BC1_TYPELESS,
416
+ ):
417
+ self._mode = "RGBA"
418
+ self.pixel_format = "BC1"
419
+ n = 1
420
+ elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
421
+ self._mode = "L"
422
+ self.pixel_format = "BC4"
423
+ n = 4
424
+ elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM):
425
+ self._mode = "RGB"
426
+ self.pixel_format = "BC5"
427
+ n = 5
428
+ elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
429
+ self._mode = "RGB"
430
+ self.pixel_format = "BC5S"
431
+ n = 5
432
+ elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
433
+ self._mode = "RGB"
434
+ self.pixel_format = "BC6H"
435
+ n = 6
436
+ elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
437
+ self._mode = "RGB"
438
+ self.pixel_format = "BC6HS"
439
+ n = 6
440
+ elif dxgi_format in (
441
+ DXGI_FORMAT.BC7_TYPELESS,
442
+ DXGI_FORMAT.BC7_UNORM,
443
+ DXGI_FORMAT.BC7_UNORM_SRGB,
444
+ ):
445
+ self._mode = "RGBA"
446
+ self.pixel_format = "BC7"
447
+ n = 7
448
+ if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB:
449
+ self.info["gamma"] = 1 / 2.2
450
+ elif dxgi_format in (
451
+ DXGI_FORMAT.R8G8B8A8_TYPELESS,
452
+ DXGI_FORMAT.R8G8B8A8_UNORM,
453
+ DXGI_FORMAT.R8G8B8A8_UNORM_SRGB,
454
+ ):
455
+ self._mode = "RGBA"
456
+ if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
457
+ self.info["gamma"] = 1 / 2.2
458
+ else:
459
+ msg = f"Unimplemented DXGI format {dxgi_format}"
460
+ raise NotImplementedError(msg)
461
+ else:
462
+ msg = f"Unimplemented pixel format {repr(fourcc)}"
463
+ raise NotImplementedError(msg)
464
+ else:
465
+ msg = f"Unknown pixel format flags {pfflags}"
466
+ raise NotImplementedError(msg)
467
+
468
+ if n:
469
+ self.tile = [
470
+ ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
471
+ ]
472
+ else:
473
+ self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
474
+
475
+ def load_seek(self, pos):
476
+ pass
477
+
478
+
479
+ class DdsRgbDecoder(ImageFile.PyDecoder):
480
+ _pulls_fd = True
481
+
482
+ def decode(self, buffer):
483
+ bitcount, masks = self.args
484
+
485
+ # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
486
+ # Calculate how many zeros each mask is padded with
487
+ mask_offsets = []
488
+ # And the maximum value of each channel without the padding
489
+ mask_totals = []
490
+ for mask in masks:
491
+ offset = 0
492
+ if mask != 0:
493
+ while mask >> (offset + 1) << (offset + 1) == mask:
494
+ offset += 1
495
+ mask_offsets.append(offset)
496
+ mask_totals.append(mask >> offset)
497
+
498
+ data = bytearray()
499
+ bytecount = bitcount // 8
500
+ dest_length = self.state.xsize * self.state.ysize * len(masks)
501
+ while len(data) < dest_length:
502
+ value = int.from_bytes(self.fd.read(bytecount), "little")
503
+ for i, mask in enumerate(masks):
504
+ masked_value = value & mask
505
+ # Remove the zero padding, and scale it to 8 bits
506
+ data += o8(
507
+ int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
508
+ )
509
+ self.set_as_raw(data)
510
+ return -1, 0
511
+
512
+
513
+ def _save(im, fp, filename):
514
+ if im.mode not in ("RGB", "RGBA", "L", "LA"):
515
+ msg = f"cannot write mode {im.mode} as DDS"
516
+ raise OSError(msg)
517
+
518
+ alpha = im.mode[-1] == "A"
519
+ if im.mode[0] == "L":
520
+ pixel_flags = DDPF.LUMINANCE
521
+ rawmode = im.mode
522
+ if alpha:
523
+ rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
524
+ else:
525
+ rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
526
+ else:
527
+ pixel_flags = DDPF.RGB
528
+ rawmode = im.mode[::-1]
529
+ rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
530
+
531
+ if alpha:
532
+ r, g, b, a = im.split()
533
+ im = Image.merge("RGBA", (a, r, g, b))
534
+ if alpha:
535
+ pixel_flags |= DDPF.ALPHAPIXELS
536
+ rgba_mask.append(0xFF000000 if alpha else 0)
537
+
538
+ flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
539
+ bitcount = len(im.getbands()) * 8
540
+ pitch = (im.width * bitcount + 7) // 8
541
+
542
+ fp.write(
543
+ o32(DDS_MAGIC)
544
+ + struct.pack(
545
+ "<7I",
546
+ 124, # header size
547
+ flags, # flags
548
+ im.height,
549
+ im.width,
550
+ pitch,
551
+ 0, # depth
552
+ 0, # mipmaps
553
+ )
554
+ + struct.pack("11I", *((0,) * 11)) # reserved
555
+ # pfsize, pfflags, fourcc, bitcount
556
+ + struct.pack("<4I", 32, pixel_flags, 0, bitcount)
557
+ + struct.pack("<4I", *rgba_mask) # dwRGBABitMask
558
+ + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
559
+ )
560
+ ImageFile._save(
561
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
562
+ )
563
+
564
+
565
+ def _accept(prefix):
566
+ return prefix[:4] == b"DDS "
567
+
568
+
569
+ Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
570
+ Image.register_decoder("dds_rgb", DdsRgbDecoder)
571
+ Image.register_save(DdsImageFile.format, _save)
572
+ Image.register_extension(DdsImageFile.format, ".dds")
venv/Lib/site-packages/PIL/EpsImagePlugin.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: str | bool | None = 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 bytes_mv[:5] == b"%%EOF":
360
+ break
361
+ elif trailer_reached and reading_trailer_comments:
362
+ # Load EPS trailer
363
+ s = str(bytes_mv[:bytes_read], "latin-1")
364
+ _read_comment(s)
365
+ elif bytes_mv[:9] == b"%%Trailer":
366
+ trailer_reached = True
367
+ bytes_read = 0
368
+
369
+ check_required_header_comments()
370
+
371
+ if not self._size:
372
+ msg = "cannot determine EPS bounding box"
373
+ raise OSError(msg)
374
+
375
+ def _find_offset(self, fp):
376
+ s = fp.read(4)
377
+
378
+ if s == b"%!PS":
379
+ # for HEAD without binary preview
380
+ fp.seek(0, io.SEEK_END)
381
+ length = fp.tell()
382
+ offset = 0
383
+ elif i32(s) == 0xC6D3D0C5:
384
+ # FIX for: Some EPS file not handled correctly / issue #302
385
+ # EPS can contain binary data
386
+ # or start directly with latin coding
387
+ # more info see:
388
+ # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
389
+ s = fp.read(8)
390
+ offset = i32(s)
391
+ length = i32(s, 4)
392
+ else:
393
+ msg = "not an EPS file"
394
+ raise SyntaxError(msg)
395
+
396
+ return length, offset
397
+
398
+ def load(self, scale=1, transparency=False):
399
+ # Load EPS via Ghostscript
400
+ if self.tile:
401
+ self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
402
+ self._mode = self.im.mode
403
+ self._size = self.im.size
404
+ self.tile = []
405
+ return Image.Image.load(self)
406
+
407
+ def load_seek(self, pos):
408
+ # we can't incrementally load, so force ImageFile.parser to
409
+ # use our custom load method by defining this method.
410
+ pass
411
+
412
+
413
+ # --------------------------------------------------------------------
414
+
415
+
416
+ def _save(im, fp, filename, eps=1):
417
+ """EPS Writer for the Python Imaging Library."""
418
+
419
+ # make sure image data is available
420
+ im.load()
421
+
422
+ # determine PostScript image mode
423
+ if im.mode == "L":
424
+ operator = (8, 1, b"image")
425
+ elif im.mode == "RGB":
426
+ operator = (8, 3, b"false 3 colorimage")
427
+ elif im.mode == "CMYK":
428
+ operator = (8, 4, b"false 4 colorimage")
429
+ else:
430
+ msg = "image mode is not supported"
431
+ raise ValueError(msg)
432
+
433
+ if eps:
434
+ # write EPS header
435
+ fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n")
436
+ fp.write(b"%%Creator: PIL 0.1 EpsEncode\n")
437
+ # fp.write("%%CreationDate: %s"...)
438
+ fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size)
439
+ fp.write(b"%%Pages: 1\n")
440
+ fp.write(b"%%EndComments\n")
441
+ fp.write(b"%%Page: 1 1\n")
442
+ fp.write(b"%%ImageData: %d %d " % im.size)
443
+ fp.write(b'%d %d 0 1 1 "%s"\n' % operator)
444
+
445
+ # image header
446
+ fp.write(b"gsave\n")
447
+ fp.write(b"10 dict begin\n")
448
+ fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1]))
449
+ fp.write(b"%d %d scale\n" % im.size)
450
+ fp.write(b"%d %d 8\n" % im.size) # <= bits
451
+ fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
452
+ fp.write(b"{ currentfile buf readhexstring pop } bind\n")
453
+ fp.write(operator[2] + b"\n")
454
+ if hasattr(fp, "flush"):
455
+ fp.flush()
456
+
457
+ ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
458
+
459
+ fp.write(b"\n%%%%EndBinary\n")
460
+ fp.write(b"grestore end\n")
461
+ if hasattr(fp, "flush"):
462
+ fp.flush()
463
+
464
+
465
+ # --------------------------------------------------------------------
466
+
467
+
468
+ Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
469
+
470
+ Image.register_save(EpsImageFile.format, _save)
471
+
472
+ Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
473
+
474
+ 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,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 gzip
14
+ import math
15
+
16
+ from . import Image, ImageFile
17
+
18
+
19
+ def _accept(prefix: bytes) -> bool:
20
+ return prefix[:6] == b"SIMPLE"
21
+
22
+
23
+ class FitsImageFile(ImageFile.ImageFile):
24
+ format = "FITS"
25
+ format_description = "FITS"
26
+
27
+ def _open(self) -> None:
28
+ assert self.fp is not None
29
+
30
+ headers: dict[bytes, bytes] = {}
31
+ header_in_progress = False
32
+ decoder_name = ""
33
+ while True:
34
+ header = self.fp.read(80)
35
+ if not header:
36
+ msg = "Truncated FITS file"
37
+ raise OSError(msg)
38
+ keyword = header[:8].strip()
39
+ if keyword in (b"SIMPLE", b"XTENSION"):
40
+ header_in_progress = True
41
+ elif headers and not header_in_progress:
42
+ # This is now a data unit
43
+ break
44
+ elif keyword == b"END":
45
+ # Seek to the end of the header unit
46
+ self.fp.seek(math.ceil(self.fp.tell() / 2880) * 2880)
47
+ if not decoder_name:
48
+ decoder_name, offset, args = self._parse_headers(headers)
49
+
50
+ header_in_progress = False
51
+ continue
52
+
53
+ if decoder_name:
54
+ # Keep going to read past the headers
55
+ continue
56
+
57
+ value = header[8:].split(b"/")[0].strip()
58
+ if value.startswith(b"="):
59
+ value = value[1:].strip()
60
+ if not headers and (not _accept(keyword) or value != b"T"):
61
+ msg = "Not a FITS file"
62
+ raise SyntaxError(msg)
63
+ headers[keyword] = value
64
+
65
+ if not decoder_name:
66
+ msg = "No image data"
67
+ raise ValueError(msg)
68
+
69
+ offset += self.fp.tell() - 80
70
+ self.tile = [(decoder_name, (0, 0) + self.size, offset, args)]
71
+
72
+ def _get_size(
73
+ self, headers: dict[bytes, bytes], prefix: bytes
74
+ ) -> tuple[int, int] | None:
75
+ naxis = int(headers[prefix + b"NAXIS"])
76
+ if naxis == 0:
77
+ return None
78
+
79
+ if naxis == 1:
80
+ return 1, int(headers[prefix + b"NAXIS1"])
81
+ else:
82
+ return int(headers[prefix + b"NAXIS1"]), int(headers[prefix + b"NAXIS2"])
83
+
84
+ def _parse_headers(
85
+ self, headers: dict[bytes, bytes]
86
+ ) -> tuple[str, int, tuple[str | int, ...]]:
87
+ prefix = b""
88
+ decoder_name = "raw"
89
+ offset = 0
90
+ if (
91
+ headers.get(b"XTENSION") == b"'BINTABLE'"
92
+ and headers.get(b"ZIMAGE") == b"T"
93
+ and headers[b"ZCMPTYPE"] == b"'GZIP_1 '"
94
+ ):
95
+ no_prefix_size = self._get_size(headers, prefix) or (0, 0)
96
+ number_of_bits = int(headers[b"BITPIX"])
97
+ offset = no_prefix_size[0] * no_prefix_size[1] * (number_of_bits // 8)
98
+
99
+ prefix = b"Z"
100
+ decoder_name = "fits_gzip"
101
+
102
+ size = self._get_size(headers, prefix)
103
+ if not size:
104
+ return "", 0, ()
105
+
106
+ self._size = size
107
+
108
+ number_of_bits = int(headers[prefix + b"BITPIX"])
109
+ if number_of_bits == 8:
110
+ self._mode = "L"
111
+ elif number_of_bits == 16:
112
+ self._mode = "I;16"
113
+ elif number_of_bits == 32:
114
+ self._mode = "I"
115
+ elif number_of_bits in (-32, -64):
116
+ self._mode = "F"
117
+
118
+ args = (self.mode, 0, -1) if decoder_name == "raw" else (number_of_bits,)
119
+ return decoder_name, offset, args
120
+
121
+
122
+ class FitsGzipDecoder(ImageFile.PyDecoder):
123
+ _pulls_fd = True
124
+
125
+ def decode(self, buffer):
126
+ assert self.fd is not None
127
+ value = gzip.decompress(self.fd.read())
128
+
129
+ rows = []
130
+ offset = 0
131
+ number_of_bits = min(self.args[0] // 8, 4)
132
+ for y in range(self.state.ysize):
133
+ row = bytearray()
134
+ for x in range(self.state.xsize):
135
+ row += value[offset + (4 - number_of_bits) : offset + 4]
136
+ offset += 4
137
+ rows.append(row)
138
+ self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row]))
139
+ return -1, 0
140
+
141
+
142
+ # --------------------------------------------------------------------
143
+ # Registry
144
+
145
+ Image.register_open(FitsImageFile.format, FitsImageFile, _accept)
146
+ Image.register_decoder("fits_gzip", FitsGzipDecoder)
147
+
148
+ Image.register_extensions(FitsImageFile.format, [".fit", ".fits"])
venv/Lib/site-packages/PIL/FliImagePlugin.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ self.fp.seek(self.__offset)
81
+ s = self.fp.read(16)
82
+
83
+ if i16(s, 4) == 0xF1FA:
84
+ # look for palette chunk
85
+ number_of_subchunks = i16(s, 6)
86
+ chunk_size = None
87
+ for _ in range(number_of_subchunks):
88
+ if chunk_size is not None:
89
+ self.fp.seek(chunk_size - 6, os.SEEK_CUR)
90
+ s = self.fp.read(6)
91
+ chunk_type = i16(s, 4)
92
+ if chunk_type in (4, 11):
93
+ self._palette(palette, 2 if chunk_type == 11 else 0)
94
+ break
95
+ chunk_size = i32(s)
96
+ if not chunk_size:
97
+ break
98
+
99
+ palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
100
+ self.palette = ImagePalette.raw("RGB", b"".join(palette))
101
+
102
+ # set things up to decode first frame
103
+ self.__frame = -1
104
+ self._fp = self.fp
105
+ self.__rewind = self.fp.tell()
106
+ self.seek(0)
107
+
108
+ def _palette(self, palette, shift):
109
+ # load palette
110
+
111
+ i = 0
112
+ for e in range(i16(self.fp.read(2))):
113
+ s = self.fp.read(2)
114
+ i = i + s[0]
115
+ n = s[1]
116
+ if n == 0:
117
+ n = 256
118
+ s = self.fp.read(n * 3)
119
+ for n in range(0, len(s), 3):
120
+ r = s[n] << shift
121
+ g = s[n + 1] << shift
122
+ b = s[n + 2] << shift
123
+ palette[i] = (r, g, b)
124
+ i += 1
125
+
126
+ def seek(self, frame):
127
+ if not self._seek_check(frame):
128
+ return
129
+ if frame < self.__frame:
130
+ self._seek(0)
131
+
132
+ for f in range(self.__frame + 1, frame + 1):
133
+ self._seek(f)
134
+
135
+ def _seek(self, frame):
136
+ if frame == 0:
137
+ self.__frame = -1
138
+ self._fp.seek(self.__rewind)
139
+ self.__offset = 128
140
+ else:
141
+ # ensure that the previous frame was loaded
142
+ self.load()
143
+
144
+ if frame != self.__frame + 1:
145
+ msg = f"cannot seek to frame {frame}"
146
+ raise ValueError(msg)
147
+ self.__frame = frame
148
+
149
+ # move to next frame
150
+ self.fp = self._fp
151
+ self.fp.seek(self.__offset)
152
+
153
+ s = self.fp.read(4)
154
+ if not s:
155
+ msg = "missing frame size"
156
+ raise EOFError(msg)
157
+
158
+ framesize = i32(s)
159
+
160
+ self.decodermaxblock = framesize
161
+ self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
162
+
163
+ self.__offset += framesize
164
+
165
+ def tell(self):
166
+ return self.__frame
167
+
168
+
169
+ #
170
+ # registry
171
+
172
+ Image.register_open(FliImageFile.format, FliImageFile, _accept)
173
+
174
+ Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
venv/Lib/site-packages/PIL/FontFile.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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__(self, ix: int) -> (
54
+ tuple[
55
+ tuple[int, int],
56
+ tuple[int, int, int, int],
57
+ tuple[int, int, int, int],
58
+ Image.Image,
59
+ ]
60
+ | None
61
+ ):
62
+ return self.glyph[ix]
63
+
64
+ def compile(self) -> None:
65
+ """Create metrics and bitmap"""
66
+
67
+ if self.bitmap:
68
+ return
69
+
70
+ # create bitmap large enough to hold all data
71
+ h = w = maxwidth = 0
72
+ lines = 1
73
+ for glyph in self.glyph:
74
+ if glyph:
75
+ d, dst, src, im = glyph
76
+ h = max(h, src[3] - src[1])
77
+ w = w + (src[2] - src[0])
78
+ if w > WIDTH:
79
+ lines += 1
80
+ w = src[2] - src[0]
81
+ maxwidth = max(maxwidth, w)
82
+
83
+ xsize = maxwidth
84
+ ysize = lines * h
85
+
86
+ if xsize == 0 and ysize == 0:
87
+ return
88
+
89
+ self.ysize = h
90
+
91
+ # paste glyphs into bitmap
92
+ self.bitmap = Image.new("1", (xsize, ysize))
93
+ self.metrics: list[
94
+ tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]]
95
+ | None
96
+ ] = [None] * 256
97
+ x = y = 0
98
+ for i in range(256):
99
+ glyph = self[i]
100
+ if glyph:
101
+ d, dst, src, im = glyph
102
+ xx = src[2] - src[0]
103
+ x0, y0 = x, y
104
+ x = x + xx
105
+ if x > WIDTH:
106
+ x, y = 0, y + h
107
+ x0, y0 = x, y
108
+ x = xx
109
+ s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
110
+ self.bitmap.paste(im.crop(src), s)
111
+ self.metrics[i] = d, dst, s
112
+
113
+ def save(self, filename: str) -> None:
114
+ """Save font"""
115
+
116
+ self.compile()
117
+
118
+ # font data
119
+ if not self.bitmap:
120
+ msg = "No bitmap created"
121
+ raise ValueError(msg)
122
+ self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
123
+
124
+ # font metrics
125
+ with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
126
+ fp.write(b"PILfont\n")
127
+ fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!!
128
+ fp.write(b"DATA\n")
129
+ for id in range(256):
130
+ m = self.metrics[id]
131
+ if not m:
132
+ puti16(fp, (0,) * 10)
133
+ else:
134
+ 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,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
54
+ from __future__ import annotations
55
+
56
+ import struct
57
+ from enum import IntEnum
58
+ from io import BytesIO
59
+
60
+ from . import Image, ImageFile
61
+
62
+ MAGIC = b"FTEX"
63
+
64
+
65
+ class Format(IntEnum):
66
+ DXT1 = 0
67
+ UNCOMPRESSED = 1
68
+
69
+
70
+ class FtexImageFile(ImageFile.ImageFile):
71
+ format = "FTEX"
72
+ format_description = "Texture File Format (IW2:EOC)"
73
+
74
+ def _open(self):
75
+ if not _accept(self.fp.read(4)):
76
+ msg = "not an FTEX file"
77
+ raise SyntaxError(msg)
78
+ struct.unpack("<i", self.fp.read(4)) # version
79
+ self._size = struct.unpack("<2i", self.fp.read(8))
80
+ mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
81
+
82
+ self._mode = "RGB"
83
+
84
+ # Only support single-format files.
85
+ # I don't know of any multi-format file.
86
+ assert format_count == 1
87
+
88
+ format, where = struct.unpack("<2i", self.fp.read(8))
89
+ self.fp.seek(where)
90
+ (mipmap_size,) = struct.unpack("<i", self.fp.read(4))
91
+
92
+ data = self.fp.read(mipmap_size)
93
+
94
+ if format == Format.DXT1:
95
+ self._mode = "RGBA"
96
+ self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
97
+ elif format == Format.UNCOMPRESSED:
98
+ self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
99
+ else:
100
+ msg = f"Invalid texture compression format: {repr(format)}"
101
+ raise ValueError(msg)
102
+
103
+ self.fp.close()
104
+ self.fp = BytesIO(data)
105
+
106
+ def load_seek(self, pos):
107
+ pass
108
+
109
+
110
+ def _accept(prefix):
111
+ return prefix[:4] == MAGIC
112
+
113
+
114
+ Image.register_open(FtexImageFile.format, FtexImageFile, _accept)
115
+ 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,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 typing import IO
31
+
32
+ from . import ImageFile, ImagePalette, UnidentifiedImageError
33
+ from ._binary import i16be as i16
34
+ from ._binary import i32be as i32
35
+ from ._typing import StrOrBytesPath
36
+
37
+
38
+ class GdImageFile(ImageFile.ImageFile):
39
+ """
40
+ Image plugin for the GD uncompressed format. Note that this format
41
+ is not supported by the standard :py:func:`PIL.Image.open()` function. To use
42
+ this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and
43
+ use the :py:func:`PIL.GdImageFile.open()` function.
44
+ """
45
+
46
+ format = "GD"
47
+ format_description = "GD uncompressed images"
48
+
49
+ def _open(self) -> None:
50
+ # Header
51
+ assert self.fp is not None
52
+
53
+ s = self.fp.read(1037)
54
+
55
+ if i16(s) not in [65534, 65535]:
56
+ msg = "Not a valid GD 2.x .gd file"
57
+ raise SyntaxError(msg)
58
+
59
+ self._mode = "L" # FIXME: "P"
60
+ self._size = i16(s, 2), i16(s, 4)
61
+
62
+ true_color = s[6]
63
+ true_color_offset = 2 if true_color else 0
64
+
65
+ # transparency index
66
+ tindex = i32(s, 7 + true_color_offset)
67
+ if tindex < 256:
68
+ self.info["transparency"] = tindex
69
+
70
+ self.palette = ImagePalette.raw(
71
+ "XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4]
72
+ )
73
+
74
+ self.tile = [
75
+ (
76
+ "raw",
77
+ (0, 0) + self.size,
78
+ 7 + true_color_offset + 4 + 256 * 4,
79
+ ("L", 0, 1),
80
+ )
81
+ ]
82
+
83
+
84
+ def open(fp: StrOrBytesPath | IO[bytes], mode: str = "r") -> GdImageFile:
85
+ """
86
+ Load texture from a GD image file.
87
+
88
+ :param fp: GD file name, or an opened file handle.
89
+ :param mode: Optional mode. In this version, if the mode argument
90
+ is given, it must be "r".
91
+ :returns: An image instance.
92
+ :raises OSError: If the image could not be read.
93
+ """
94
+ if mode != "r":
95
+ msg = "bad mode"
96
+ raise ValueError(msg)
97
+
98
+ try:
99
+ return GdImageFile(fp)
100
+ except SyntaxError as e:
101
+ msg = "cannot identify this image file"
102
+ raise UnidentifiedImageError(msg) from e
venv/Lib/site-packages/PIL/GifImagePlugin.py ADDED
@@ -0,0 +1,1107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 im_frames[-1]["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
+ bbox = _getbbox(background_im, im_frame)[1]
641
+ elif encoderinfo.get("optimize") and im_frame.mode != "1":
642
+ if "transparency" not in encoderinfo:
643
+ try:
644
+ encoderinfo["transparency"] = (
645
+ im_frame.palette._new_color_index(im_frame)
646
+ )
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("P", delta.size, encoderinfo["transparency"])
653
+ if delta.mode == "RGBA":
654
+ r, g, b, a = delta.split()
655
+ mask = ImageMath.lambda_eval(
656
+ lambda args: args["convert"](
657
+ args["max"](
658
+ args["max"](
659
+ args["max"](args["r"], args["g"]), args["b"]
660
+ ),
661
+ args["a"],
662
+ )
663
+ * 255,
664
+ "1",
665
+ ),
666
+ r=r,
667
+ g=g,
668
+ b=b,
669
+ a=a,
670
+ )
671
+ else:
672
+ if delta.mode == "P":
673
+ # Convert to L without considering palette
674
+ delta_l = Image.new("L", delta.size)
675
+ delta_l.putdata(delta.getdata())
676
+ delta = delta_l
677
+ mask = ImageMath.lambda_eval(
678
+ lambda args: args["convert"](args["im"] * 255, "1"),
679
+ im=delta,
680
+ )
681
+ diff_frame.paste(fill, mask=ImageOps.invert(mask))
682
+ else:
683
+ bbox = None
684
+ previous_im = im_frame
685
+ im_frames.append(
686
+ {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
687
+ )
688
+
689
+ if len(im_frames) == 1:
690
+ if "duration" in im.encoderinfo:
691
+ # Since multiple frames will not be written, use the combined duration
692
+ im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"]
693
+ return
694
+
695
+ for frame_data in im_frames:
696
+ im_frame = frame_data["im"]
697
+ if not frame_data["bbox"]:
698
+ # global header
699
+ for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
700
+ fp.write(s)
701
+ offset = (0, 0)
702
+ else:
703
+ # compress difference
704
+ if not palette:
705
+ frame_data["encoderinfo"]["include_color_table"] = True
706
+
707
+ im_frame = im_frame.crop(frame_data["bbox"])
708
+ offset = frame_data["bbox"][:2]
709
+ _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
710
+ return True
711
+
712
+
713
+ def _save_all(im, fp, filename):
714
+ _save(im, fp, filename, save_all=True)
715
+
716
+
717
+ def _save(im, fp, filename, save_all=False):
718
+ # header
719
+ if "palette" in im.encoderinfo or "palette" in im.info:
720
+ palette = im.encoderinfo.get("palette", im.info.get("palette"))
721
+ else:
722
+ palette = None
723
+ im.encoderinfo.setdefault("optimize", True)
724
+
725
+ if not save_all or not _write_multiple_frames(im, fp, palette):
726
+ _write_single_frame(im, fp, palette)
727
+
728
+ fp.write(b";") # end of file
729
+
730
+ if hasattr(fp, "flush"):
731
+ fp.flush()
732
+
733
+
734
+ def get_interlace(im):
735
+ interlace = im.encoderinfo.get("interlace", 1)
736
+
737
+ # workaround for @PIL153
738
+ if min(im.size) < 16:
739
+ interlace = 0
740
+
741
+ return interlace
742
+
743
+
744
+ def _write_local_header(fp, im, offset, flags):
745
+ try:
746
+ transparency = im.encoderinfo["transparency"]
747
+ except KeyError:
748
+ transparency = None
749
+
750
+ if "duration" in im.encoderinfo:
751
+ duration = int(im.encoderinfo["duration"] / 10)
752
+ else:
753
+ duration = 0
754
+
755
+ disposal = int(im.encoderinfo.get("disposal", 0))
756
+
757
+ if transparency is not None or duration != 0 or disposal:
758
+ packed_flag = 1 if transparency is not None else 0
759
+ packed_flag |= disposal << 2
760
+
761
+ fp.write(
762
+ b"!"
763
+ + o8(249) # extension intro
764
+ + o8(4) # length
765
+ + o8(packed_flag) # packed fields
766
+ + o16(duration) # duration
767
+ + o8(transparency or 0) # transparency index
768
+ + o8(0)
769
+ )
770
+
771
+ include_color_table = im.encoderinfo.get("include_color_table")
772
+ if include_color_table:
773
+ palette_bytes = _get_palette_bytes(im)
774
+ color_table_size = _get_color_table_size(palette_bytes)
775
+ if color_table_size:
776
+ flags = flags | 128 # local color table flag
777
+ flags = flags | color_table_size
778
+
779
+ fp.write(
780
+ b","
781
+ + o16(offset[0]) # offset
782
+ + o16(offset[1])
783
+ + o16(im.size[0]) # size
784
+ + o16(im.size[1])
785
+ + o8(flags) # flags
786
+ )
787
+ if include_color_table and color_table_size:
788
+ fp.write(_get_header_palette(palette_bytes))
789
+ fp.write(o8(8)) # bits
790
+
791
+
792
+ def _save_netpbm(im, fp, filename):
793
+ # Unused by default.
794
+ # To use, uncomment the register_save call at the end of the file.
795
+ #
796
+ # If you need real GIF compression and/or RGB quantization, you
797
+ # can use the external NETPBM/PBMPLUS utilities. See comments
798
+ # below for information on how to enable this.
799
+ tempfile = im._dump()
800
+
801
+ try:
802
+ with open(filename, "wb") as f:
803
+ if im.mode != "RGB":
804
+ subprocess.check_call(
805
+ ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL
806
+ )
807
+ else:
808
+ # Pipe ppmquant output into ppmtogif
809
+ # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
810
+ quant_cmd = ["ppmquant", "256", tempfile]
811
+ togif_cmd = ["ppmtogif"]
812
+ quant_proc = subprocess.Popen(
813
+ quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
814
+ )
815
+ togif_proc = subprocess.Popen(
816
+ togif_cmd,
817
+ stdin=quant_proc.stdout,
818
+ stdout=f,
819
+ stderr=subprocess.DEVNULL,
820
+ )
821
+
822
+ # Allow ppmquant to receive SIGPIPE if ppmtogif exits
823
+ quant_proc.stdout.close()
824
+
825
+ retcode = quant_proc.wait()
826
+ if retcode:
827
+ raise subprocess.CalledProcessError(retcode, quant_cmd)
828
+
829
+ retcode = togif_proc.wait()
830
+ if retcode:
831
+ raise subprocess.CalledProcessError(retcode, togif_cmd)
832
+ finally:
833
+ try:
834
+ os.unlink(tempfile)
835
+ except OSError:
836
+ pass
837
+
838
+
839
+ # Force optimization so that we can test performance against
840
+ # cases where it took lots of memory and time previously.
841
+ _FORCE_OPTIMIZE = False
842
+
843
+
844
+ def _get_optimize(im, info):
845
+ """
846
+ Palette optimization is a potentially expensive operation.
847
+
848
+ This function determines if the palette should be optimized using
849
+ some heuristics, then returns the list of palette entries in use.
850
+
851
+ :param im: Image object
852
+ :param info: encoderinfo
853
+ :returns: list of indexes of palette entries in use, or None
854
+ """
855
+ if im.mode in ("P", "L") and info and info.get("optimize"):
856
+ # Potentially expensive operation.
857
+
858
+ # The palette saves 3 bytes per color not used, but palette
859
+ # lengths are restricted to 3*(2**N) bytes. Max saving would
860
+ # be 768 -> 6 bytes if we went all the way down to 2 colors.
861
+ # * If we're over 128 colors, we can't save any space.
862
+ # * If there aren't any holes, it's not worth collapsing.
863
+ # * If we have a 'large' image, the palette is in the noise.
864
+
865
+ # create the new palette if not every color is used
866
+ optimise = _FORCE_OPTIMIZE or im.mode == "L"
867
+ if optimise or im.width * im.height < 512 * 512:
868
+ # check which colors are used
869
+ used_palette_colors = []
870
+ for i, count in enumerate(im.histogram()):
871
+ if count:
872
+ used_palette_colors.append(i)
873
+
874
+ if optimise or max(used_palette_colors) >= len(used_palette_colors):
875
+ return used_palette_colors
876
+
877
+ num_palette_colors = len(im.palette.palette) // Image.getmodebands(
878
+ im.palette.mode
879
+ )
880
+ current_palette_size = 1 << (num_palette_colors - 1).bit_length()
881
+ if (
882
+ # check that the palette would become smaller when saved
883
+ len(used_palette_colors) <= current_palette_size // 2
884
+ # check that the palette is not already the smallest possible size
885
+ and current_palette_size > 2
886
+ ):
887
+ return used_palette_colors
888
+
889
+
890
+ def _get_color_table_size(palette_bytes):
891
+ # calculate the palette size for the header
892
+ if not palette_bytes:
893
+ return 0
894
+ elif len(palette_bytes) < 9:
895
+ return 1
896
+ else:
897
+ return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
898
+
899
+
900
+ def _get_header_palette(palette_bytes):
901
+ """
902
+ Returns the palette, null padded to the next power of 2 (*3) bytes
903
+ suitable for direct inclusion in the GIF header
904
+
905
+ :param palette_bytes: Unpadded palette bytes, in RGBRGB form
906
+ :returns: Null padded palette
907
+ """
908
+ color_table_size = _get_color_table_size(palette_bytes)
909
+
910
+ # add the missing amount of bytes
911
+ # the palette has to be 2<<n in size
912
+ actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
913
+ if actual_target_size_diff > 0:
914
+ palette_bytes += o8(0) * 3 * actual_target_size_diff
915
+ return palette_bytes
916
+
917
+
918
+ def _get_palette_bytes(im):
919
+ """
920
+ Gets the palette for inclusion in the gif header
921
+
922
+ :param im: Image object
923
+ :returns: Bytes, len<=768 suitable for inclusion in gif header
924
+ """
925
+ return im.palette.palette if im.palette else b""
926
+
927
+
928
+ def _get_background(im, info_background):
929
+ background = 0
930
+ if info_background:
931
+ if isinstance(info_background, tuple):
932
+ # WebPImagePlugin stores an RGBA value in info["background"]
933
+ # So it must be converted to the same format as GifImagePlugin's
934
+ # info["background"] - a global color table index
935
+ try:
936
+ background = im.palette.getcolor(info_background, im)
937
+ except ValueError as e:
938
+ if str(e) not in (
939
+ # If all 256 colors are in use,
940
+ # then there is no need for the background color
941
+ "cannot allocate more than 256 colors",
942
+ # Ignore non-opaque WebP background
943
+ "cannot add non-opaque RGBA color to RGB palette",
944
+ ):
945
+ raise
946
+ else:
947
+ background = info_background
948
+ return background
949
+
950
+
951
+ def _get_global_header(im, info):
952
+ """Return a list of strings representing a GIF header"""
953
+
954
+ # Header Block
955
+ # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
956
+
957
+ version = b"87a"
958
+ if im.info.get("version") == b"89a" or (
959
+ info
960
+ and (
961
+ "transparency" in info
962
+ or info.get("loop") is not None
963
+ or info.get("duration")
964
+ or info.get("comment")
965
+ )
966
+ ):
967
+ version = b"89a"
968
+
969
+ background = _get_background(im, info.get("background"))
970
+
971
+ palette_bytes = _get_palette_bytes(im)
972
+ color_table_size = _get_color_table_size(palette_bytes)
973
+
974
+ header = [
975
+ b"GIF" # signature
976
+ + version # version
977
+ + o16(im.size[0]) # canvas width
978
+ + o16(im.size[1]), # canvas height
979
+ # Logical Screen Descriptor
980
+ # size of global color table + global color table flag
981
+ o8(color_table_size + 128), # packed fields
982
+ # background + reserved/aspect
983
+ o8(background) + o8(0),
984
+ # Global Color Table
985
+ _get_header_palette(palette_bytes),
986
+ ]
987
+ if info.get("loop") is not None:
988
+ header.append(
989
+ b"!"
990
+ + o8(255) # extension intro
991
+ + o8(11)
992
+ + b"NETSCAPE2.0"
993
+ + o8(3)
994
+ + o8(1)
995
+ + o16(info["loop"]) # number of loops
996
+ + o8(0)
997
+ )
998
+ if info.get("comment"):
999
+ comment_block = b"!" + o8(254) # extension intro
1000
+
1001
+ comment = info["comment"]
1002
+ if isinstance(comment, str):
1003
+ comment = comment.encode()
1004
+ for i in range(0, len(comment), 255):
1005
+ subblock = comment[i : i + 255]
1006
+ comment_block += o8(len(subblock)) + subblock
1007
+
1008
+ comment_block += o8(0)
1009
+ header.append(comment_block)
1010
+ return header
1011
+
1012
+
1013
+ def _write_frame_data(fp, im_frame, offset, params):
1014
+ try:
1015
+ im_frame.encoderinfo = params
1016
+
1017
+ # local image header
1018
+ _write_local_header(fp, im_frame, offset, 0)
1019
+
1020
+ ImageFile._save(
1021
+ im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
1022
+ )
1023
+
1024
+ fp.write(b"\0") # end of image data
1025
+ finally:
1026
+ del im_frame.encoderinfo
1027
+
1028
+
1029
+ # --------------------------------------------------------------------
1030
+ # Legacy GIF utilities
1031
+
1032
+
1033
+ def getheader(im, palette=None, info=None):
1034
+ """
1035
+ Legacy Method to get Gif data from image.
1036
+
1037
+ Warning:: May modify image data.
1038
+
1039
+ :param im: Image object
1040
+ :param palette: bytes object containing the source palette, or ....
1041
+ :param info: encoderinfo
1042
+ :returns: tuple of(list of header items, optimized palette)
1043
+
1044
+ """
1045
+ used_palette_colors = _get_optimize(im, info)
1046
+
1047
+ if info is None:
1048
+ info = {}
1049
+
1050
+ if "background" not in info and "background" in im.info:
1051
+ info["background"] = im.info["background"]
1052
+
1053
+ im_mod = _normalize_palette(im, palette, info)
1054
+ im.palette = im_mod.palette
1055
+ im.im = im_mod.im
1056
+ header = _get_global_header(im, info)
1057
+
1058
+ return header, used_palette_colors
1059
+
1060
+
1061
+ def getdata(im, offset=(0, 0), **params):
1062
+ """
1063
+ Legacy Method
1064
+
1065
+ Return a list of strings representing this image.
1066
+ The first string is a local image header, the rest contains
1067
+ encoded image data.
1068
+
1069
+ To specify duration, add the time in milliseconds,
1070
+ e.g. ``getdata(im_frame, duration=1000)``
1071
+
1072
+ :param im: Image object
1073
+ :param offset: Tuple of (x, y) pixels. Defaults to (0, 0)
1074
+ :param \\**params: e.g. duration or other encoder info parameters
1075
+ :returns: List of bytes containing GIF encoded frame data
1076
+
1077
+ """
1078
+
1079
+ class Collector:
1080
+ data = []
1081
+
1082
+ def write(self, data):
1083
+ self.data.append(data)
1084
+
1085
+ im.load() # make sure raster data is available
1086
+
1087
+ fp = Collector()
1088
+
1089
+ _write_frame_data(fp, im, offset, params)
1090
+
1091
+ return fp.data
1092
+
1093
+
1094
+ # --------------------------------------------------------------------
1095
+ # Registry
1096
+
1097
+ Image.register_open(GifImageFile.format, GifImageFile, _accept)
1098
+ Image.register_save(GifImageFile.format, _save)
1099
+ Image.register_save_all(GifImageFile.format, _save_all)
1100
+ Image.register_extension(GifImageFile.format, ".gif")
1101
+ Image.register_mime(GifImageFile.format, "image/gif")
1102
+
1103
+ #
1104
+ # Uncomment the following line if you wish to use NETPBM/PBMPLUS
1105
+ # instead of the built-in "uncompressed" GIF encoder
1106
+
1107
+ # 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")