nasskall commited on
Commit
3e96755
·
verified ·
1 Parent(s): 581f3de

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 +47 -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 +150 -0
  10. README.md +3 -9
  11. __pycache__/app.cpython-39.pyc +0 -0
  12. __pycache__/gradio_app.cpython-39.pyc +0 -0
  13. app.py +298 -0
  14. flagged/Input Image Component/0566ffd1daee0710f176/9-1.jpg +0 -0
  15. flagged/Output Image Component/e95f50d4589989a23267/image.webp +0 -0
  16. flagged/log.csv +2 -0
  17. gradio_app.py +281 -0
  18. result.jpeg +3 -0
  19. template_img.png +0 -0
  20. templates/upload.html +14 -0
  21. venv/.gitignore +2 -0
  22. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/INSTALLER +1 -0
  23. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/LICENSE +7 -0
  24. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/METADATA +148 -0
  25. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/RECORD +17 -0
  26. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/REQUESTED +0 -0
  27. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/WHEEL +6 -0
  28. venv/Lib/site-packages/Flask_Cors-4.0.1.dist-info/top_level.txt +1 -0
  29. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER +1 -0
  30. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst +28 -0
  31. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA +93 -0
  32. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD +14 -0
  33. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL +5 -0
  34. venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt +1 -0
  35. venv/Lib/site-packages/PIL/BdfFontFile.py +133 -0
  36. venv/Lib/site-packages/PIL/BlpImagePlugin.py +476 -0
  37. venv/Lib/site-packages/PIL/BmpImagePlugin.py +472 -0
  38. venv/Lib/site-packages/PIL/BufrStubImagePlugin.py +74 -0
  39. venv/Lib/site-packages/PIL/ContainerIO.py +121 -0
  40. venv/Lib/site-packages/PIL/CurImagePlugin.py +75 -0
  41. venv/Lib/site-packages/PIL/DcxImagePlugin.py +80 -0
  42. venv/Lib/site-packages/PIL/DdsImagePlugin.py +572 -0
  43. venv/Lib/site-packages/PIL/EpsImagePlugin.py +474 -0
  44. venv/Lib/site-packages/PIL/ExifTags.py +381 -0
  45. venv/Lib/site-packages/PIL/FitsImagePlugin.py +148 -0
  46. venv/Lib/site-packages/PIL/FliImagePlugin.py +174 -0
  47. venv/Lib/site-packages/PIL/FontFile.py +134 -0
  48. venv/Lib/site-packages/PIL/FpxImagePlugin.py +255 -0
  49. venv/Lib/site-packages/PIL/FtexImagePlugin.py +115 -0
  50. venv/Lib/site-packages/PIL/GbrImagePlugin.py +103 -0
.gitattributes CHANGED
@@ -33,3 +33,50 @@ 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
+ result.jpeg filter=lfs diff=lfs merge=lfs -text
37
+ venv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/channels.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text
38
+ venv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/core.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text
39
+ venv/Lib/site-packages/cv2/cv2.pyd filter=lfs diff=lfs merge=lfs -text
40
+ venv/Lib/site-packages/cv2/opencv_videoio_ffmpeg4100_64.dll filter=lfs diff=lfs merge=lfs -text
41
+ venv/Lib/site-packages/gradio/frpc_windows_amd64_v0.2 filter=lfs diff=lfs merge=lfs -text
42
+ venv/Lib/site-packages/gradio/templates/frontend/assets/Index-CUTYDExL.js.map filter=lfs diff=lfs merge=lfs -text
43
+ venv/Lib/site-packages/numpy/_core/_multiarray_umath.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
44
+ venv/Lib/site-packages/numpy/_core/_simd.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
45
+ venv/Lib/site-packages/numpy.libs/libscipy_openblas64_-fb1711452d4d8cee9f276fd1449ee5c7.dll filter=lfs diff=lfs merge=lfs -text
46
+ venv/Lib/site-packages/pandas/_libs/algos.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
47
+ venv/Lib/site-packages/pandas/_libs/groupby.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
48
+ venv/Lib/site-packages/pandas/_libs/hashtable.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
49
+ venv/Lib/site-packages/pandas/_libs/interval.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
50
+ venv/Lib/site-packages/pandas/_libs/join.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
51
+ venv/Lib/site-packages/PIL/_imaging.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
52
+ venv/Lib/site-packages/PIL/_imagingft.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
53
+ venv/Lib/site-packages/pydantic_core/_pydantic_core.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
54
+ venv/Lib/site-packages/scipy/fft/_pocketfft/pypocketfft.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
55
+ venv/Lib/site-packages/scipy/interpolate/_rbfinterp_pythran.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
56
+ venv/Lib/site-packages/scipy/io/_fast_matrix_market/_fmm_core.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
57
+ venv/Lib/site-packages/scipy/linalg/_flapack.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
58
+ venv/Lib/site-packages/scipy/misc/face.dat filter=lfs diff=lfs merge=lfs -text
59
+ venv/Lib/site-packages/scipy/optimize/_group_columns.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
60
+ venv/Lib/site-packages/scipy/optimize/_highs/_highs_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
61
+ venv/Lib/site-packages/scipy/signal/_max_len_seq_inner.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
62
+ venv/Lib/site-packages/scipy/signal/_spectral.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
63
+ venv/Lib/site-packages/scipy/sparse/_sparsetools.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
64
+ venv/Lib/site-packages/scipy/spatial/_ckdtree.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
65
+ venv/Lib/site-packages/scipy/spatial/_distance_pybind.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
66
+ venv/Lib/site-packages/scipy/spatial/_qhull.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
67
+ venv/Lib/site-packages/scipy/special/_ufuncs.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
68
+ venv/Lib/site-packages/scipy/special/_ufuncs_cxx.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
69
+ venv/Lib/site-packages/scipy/special/cython_special.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
70
+ venv/Lib/site-packages/scipy/stats/_boost/beta_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
71
+ venv/Lib/site-packages/scipy/stats/_boost/binom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
72
+ venv/Lib/site-packages/scipy/stats/_boost/hypergeom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
73
+ venv/Lib/site-packages/scipy/stats/_boost/invgauss_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
74
+ venv/Lib/site-packages/scipy/stats/_boost/nbinom_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
75
+ venv/Lib/site-packages/scipy/stats/_boost/ncf_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
76
+ venv/Lib/site-packages/scipy/stats/_boost/nct_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
77
+ venv/Lib/site-packages/scipy/stats/_boost/ncx2_ufunc.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
78
+ venv/Lib/site-packages/scipy/stats/_stats_pythran.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
79
+ venv/Lib/site-packages/scipy/stats/_unuran/unuran_wrapper.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
80
+ venv/Lib/site-packages/scipy.libs/libopenblas_v0.3.27--3aa239bc726cfb0bd8e5330d8d4c15c6.dll filter=lfs diff=lfs merge=lfs -text
81
+ venv/Lib/site-packages/sklearn/_loss/_loss.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
82
+ 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,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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$" />
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="1676000" />
139
+ </task>
140
+ <servers />
141
+ </component>
142
+ <component name="TypeScriptGeneratedFilesManager">
143
+ <option name="version" value="3" />
144
+ </component>
145
+ <component name="com.intellij.coverage.CoverageDataManagerImpl">
146
+ <SUITE FILE_PATH="coverage/colorchecker$gradio_app.coverage" NAME="gradio_app Coverage Results" MODIFIED="1720614464538" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
147
+ <SUITE FILE_PATH="coverage/colorchecker$Flask__app_py_.coverage" NAME="Flask (app.py) Coverage Results" MODIFIED="1720609230852" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
148
+ <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$" />
149
+ </component>
150
+ </project>
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: Stasis Derm Calibration
3
- emoji: 🌍
4
- colorFrom: blue
5
- colorTo: yellow
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: stasis_derm_calibration
3
+ app_file: gradio_app.py
 
 
4
  sdk: gradio
5
+ sdk_version: 4.36.1
 
 
6
  ---
 
 
__pycache__/app.cpython-39.pyc ADDED
Binary file (10.2 kB). View file
 
__pycache__/gradio_app.cpython-39.pyc ADDED
Binary file (3.45 kB). View file
 
app.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import jsonpickle as jsonpickle
3
+ from flask import Flask, request, Response
4
+ import numpy as np
5
+ import cv2 as cv
6
+ from flask_cors import CORS
7
+
8
+ # Initialize the Flask application
9
+ from sklearn.linear_model import LinearRegression
10
+
11
+ app = Flask(__name__)
12
+ CORS(app)
13
+
14
+ checker_large_real = 10.8
15
+ checker_small_real = 6.35
16
+
17
+
18
+ def srgb_to_linear(rgb):
19
+ rgb = rgb / 255.0
20
+ linear_rgb = np.where(rgb <= 0.04045, rgb / 12.92, ((rgb + 0.055) / 1.055) ** 2.4)
21
+ return linear_rgb
22
+
23
+
24
+ def linear_to_srgb(linear_rgb):
25
+ # Clip linear_rgb to ensure no negative values
26
+ linear_rgb = np.clip(linear_rgb, 0, 1)
27
+ srgb = np.where(linear_rgb <= 0.0031308, linear_rgb * 12.92, 1.055 * (linear_rgb ** (1 / 2.4)) - 0.055)
28
+ srgb = np.clip(srgb * 255, 0, 255)
29
+ return srgb.astype(np.uint8)
30
+
31
+
32
+
33
+ def check_orientation(image):
34
+ orientation = np.argmax(image.shape)
35
+ if orientation == 0:
36
+ image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
37
+ return image, orientation
38
+
39
+
40
+ def get_reference_values(points, image):
41
+ values = []
42
+ for i in points:
43
+ point_value = image[i[1], i[0]]
44
+ values.append(point_value)
45
+ return values
46
+
47
+
48
+ # route http posts to this method
49
+ def detect_template(image, orientation):
50
+ MIN_MATCH_COUNT = 10
51
+ template_path = 'template_img.png'
52
+ template_image = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
53
+ gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
54
+ image_c = image.copy()
55
+ # Initiate SIFT detector
56
+ sift = cv2.SIFT_create()
57
+ keypoints1, descriptors1 = sift.detectAndCompute(template_image, None)
58
+ keypoints2, descriptors2 = sift.detectAndCompute(gray_image, None)
59
+
60
+ # FLANN parameters
61
+ index_params = dict(algorithm=1, trees=5)
62
+ search_params = dict(checks=50)
63
+ flann = cv2.FlannBasedMatcher(index_params, search_params)
64
+ matches = flann.knnMatch(descriptors1, descriptors2, k=2)
65
+
66
+ # Apply ratio test
67
+ good_matches = [m for m, n in matches if m.distance < 0.7 * n.distance]
68
+
69
+ if len(good_matches) > MIN_MATCH_COUNT:
70
+ src_points = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
71
+ dst_points = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
72
+
73
+ M, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0)
74
+ h, w = template_image.shape
75
+ template_corners = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
76
+ dst_corners = cv2.perspectiveTransform(template_corners, M)
77
+ x1, y1 = map(round, dst_corners[0][0])
78
+ x2, y2 = map(round, dst_corners[2][0])
79
+ checker_small_im = abs(y2 - y1)
80
+ checker_large_im = abs(x2 - x1)
81
+ if checker_small_im != 0 and checker_large_im != 0:
82
+ px_cm_ratio_small = checker_small_real / checker_small_im
83
+ px_cm_ratio_large = checker_large_real / checker_large_im
84
+ else:
85
+ px_cm_ratio_small = 0
86
+ px_cm_ratio_large = 0
87
+
88
+ annotated_image = cv2.polylines(image_c, [np.int32(dst_corners)], True, 255, 3, cv2.LINE_AA)
89
+
90
+ if orientation == 0:
91
+ annotated_image = cv2.rotate(annotated_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
92
+ else:
93
+ print(f"Not enough matches are found - {len(good_matches)}/{MIN_MATCH_COUNT}")
94
+ return None, 0, 0
95
+ if orientation == 0:
96
+ cm_per_pixel_width = px_cm_ratio_small
97
+ cm_per_pixel_height = px_cm_ratio_large
98
+ else:
99
+ cm_per_pixel_width = px_cm_ratio_large
100
+ cm_per_pixel_height = px_cm_ratio_small
101
+ return annotated_image, dst_corners, cm_per_pixel_width, cm_per_pixel_height, checker_small_im, checker_large_im
102
+
103
+
104
+ def get_color_checker_table(data_points, y, yend):
105
+ sorted_points = sorted(data_points, key=lambda point: (point[1], point[0]))
106
+ differences_y = [sorted_points[0][1] - y] + \
107
+ [abs(sorted_points[i][1] - sorted_points[i + 1][1]) for i in range(len(sorted_points) - 1)] + \
108
+ [yend - sorted_points[-1][1]]
109
+
110
+ most_usual_y = 10
111
+ local_max = round((yend - y) * 0.2184)
112
+ lines = []
113
+ last_id = 0
114
+ label_upper = differences_y[0] // local_max if differences_y[0] > local_max + 10 else 0
115
+ label_lower = differences_y[-1] // local_max if differences_y[-1] > local_max + 10 else 0
116
+
117
+ for j in range(len(differences_y) - 1):
118
+ if differences_y[j] > local_max + 10:
119
+ lines.extend([[] for _ in range(label_upper)])
120
+ break
121
+
122
+ for i in range(1, len(differences_y) - 1):
123
+ if differences_y[i] > most_usual_y:
124
+ lines.append(sorted_points[last_id:i])
125
+ last_id = i
126
+
127
+ if differences_y[-1] < local_max + 10:
128
+ lines.append(sorted_points[last_id:])
129
+ else:
130
+ lines.append(sorted_points[last_id:])
131
+ lines.extend([[] for _ in range(label_lower)])
132
+ lines = [sorted(line, key=lambda point: point[0]) for line in lines]
133
+ return label_upper, label_lower, local_max, lines
134
+
135
+
136
+ def check_points(data_points, x, xend, y, yend, image):
137
+ most_usual = int((xend - x) / 7.016)
138
+ label_upper, label_lower, usual_y, lines = get_color_checker_table(data_points, y, yend)
139
+
140
+ for q in lines:
141
+ if not q:
142
+ continue
143
+
144
+ differences_x = [q[0][0] - x] + [q[i + 1][0] - q[i][0] for i in range(len(q) - 1)] + [xend - q[-1][0]]
145
+ threshold_x = int(most_usual * (1 + 1 / 5.6))
146
+
147
+ for j, distance in enumerate(differences_x[:-1]):
148
+ if distance > threshold_x:
149
+ positions = distance // int(most_usual * (1 - 1 / 11.2)) - 1
150
+ for t in range(positions):
151
+ cnt = (q[j][0] - (t + 1) * most_usual, q[j][1])
152
+ # cv2.circle(image, cnt, 5, (255, 0, 0), -1)
153
+ data_points.append(cnt)
154
+
155
+ if differences_x[-1] > threshold_x:
156
+ positions = differences_x[-1] // int(most_usual * (1 - 1 / 11.2)) - 1
157
+ for t in range(positions):
158
+ cnt = (q[-1][0] + (t + 1) * most_usual, q[-1][1])
159
+ # cv2.circle(image, cnt, 5, (255, 0, 0), -1)
160
+ data_points.append(cnt)
161
+ data_points.sort(key=lambda point: (point[1], point[0]))
162
+ _, _, _, new_lines = get_color_checker_table(data_points, y, yend)
163
+ return label_upper, label_lower, usual_y, image, new_lines, data_points
164
+
165
+ def calculate_color_correction_matrix_ransac(sample_rgb, reference_rgb):
166
+ sample_rgb = sample_rgb[::-1]
167
+ sample_rgb_linear = srgb_to_linear(sample_rgb)
168
+ reference_rgb_linear = srgb_to_linear(reference_rgb)
169
+
170
+ # Reshape the data for RANSAC
171
+ X = sample_rgb_linear
172
+ y = reference_rgb_linear
173
+
174
+ # Initialize RANSAC regressor for each color channel
175
+ models = []
176
+ scores = []
177
+ for i in range(3): # For each RGB channel
178
+ ransac = LinearRegression()
179
+ ransac.fit(X, y[:, i])
180
+ scores.append(ransac.score(X, y[:, i]))
181
+ models.append(ransac.coef_)
182
+ score = np.mean(scores)
183
+ # Stack coefficients to form the transformation matrix
184
+ M = np.stack(models, axis=-1)
185
+
186
+ return M, score
187
+
188
+
189
+ def apply_color_correction(image, M):
190
+ image_linear = srgb_to_linear(image)
191
+ corrected_image_linear = np.dot(image_linear, M)
192
+ corrected_image_srgb = linear_to_srgb(corrected_image_linear)
193
+ return corrected_image_srgb
194
+
195
+
196
+
197
+
198
+
199
+ def detect_RGB_values(image, dst):
200
+ x1, y1 = map(round, dst[0][0])
201
+ x2, y2 = map(round, dst[2][0])
202
+ y2 = max(0, y2)
203
+ image_checker = image[y1:y2, x1:x2]
204
+ if image_checker.size != 0:
205
+ # Apply GaussianBlur to reduce noise and improve edge detection
206
+ blurred = cv2.GaussianBlur(image_checker, (5, 5), 0)
207
+ # Apply edge detection
208
+ edges = cv2.Canny(blurred, 50, 120)
209
+ # Find contours
210
+ contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
211
+ centers = [
212
+ (x + w // 2 + x1, y + h // 2 + y1)
213
+ for contour in contours
214
+ for x, y, w, h in [cv2.boundingRect(contour)]
215
+ if 0.8 < (aspect_ratio := w / float(h)) < 1.2 and (area := cv2.contourArea(contour)) > 100
216
+ ]
217
+ if centers:
218
+ # Filter out centers too close to the edges
219
+ centers = [
220
+ center for center in centers
221
+ if abs(center[0] - x1) >= (x2 - x1) / 7.29 and abs(center[0] - x2) >= (x2 - x1) / 7.29
222
+ ]
223
+ if centers:
224
+ label_upper, label_lower, usual, image, lines, M_T = check_points(centers, x1, x2, y1, y2, image)
225
+ else:
226
+ label_upper, label_lower, lines, M_T = 0, 0, [], []
227
+ else:
228
+ label_upper, label_lower, lines, M_T = 0, 0, [], []
229
+ else:
230
+ label_upper, label_lower, lines, M_T = 0, 0, [], []
231
+ M_R = [
232
+ [52, 52, 52], [85, 85, 85], [122, 122, 121], [160, 160, 160],
233
+ [200, 200, 200], [243, 243, 242], [8, 133, 161], [187, 86, 149],
234
+ [231, 199, 31], [175, 54, 60], [70, 148, 73], [56, 61, 150],
235
+ [224, 163, 46], [157, 188, 64], [94, 60, 108], [193, 90, 99],
236
+ [80, 91, 166], [214, 126, 44], [103, 189, 170], [133, 128, 177],
237
+ [87, 108, 67], [98, 122, 157], [194, 150, 130], [115, 82, 68]
238
+ ]
239
+ # show_color_points(M_T, image)
240
+ new_lines = lines.copy()
241
+ if len(M_T) < 24:
242
+ for i in range(label_upper):
243
+ new_lines[0] = [(x, y - round(usual)) for x, y in lines[1]]
244
+ for j in range(label_lower):
245
+ new_lines[-1] = [(x, y + round(usual)) for x, y in lines[-2]]
246
+ if len(M_T) < 24:
247
+ missing = 24 - len(M_T)
248
+ empty_indices = [index for index, sublist in enumerate(lines) if not sublist]
249
+ if missing == 6 and len(empty_indices) == 1:
250
+ l_index = 6 * empty_indices[0] - 1
251
+ M_R = M_R[::-1]
252
+ del M_R[l_index + 1:l_index + 7]
253
+ del new_lines[empty_indices[0]]
254
+ M_R = M_R[::-1]
255
+ if missing == 12 and len(empty_indices) == 2:
256
+ pass
257
+ elif len(M_T) > 24:
258
+ new_lines = []
259
+ M_T = [point for sublist in new_lines for point in sublist]
260
+ M_T_values = np.array(get_reference_values(M_T, image))
261
+ M_R = np.array(M_R)
262
+ return M_T_values, M_R
263
+
264
+
265
+ @app.route('/api/dimension_checker', methods=['POST'])
266
+ def test():
267
+ received = request.files.getlist('file[]')
268
+ img_received = received[0].read()
269
+ img_received = np.frombuffer(img_received, np.uint8)
270
+ img_received = cv.imdecode(img_received, cv.IMREAD_COLOR)
271
+ image, orientation = check_orientation(img_received)
272
+ annotated_image, polygon, px_width, px_height, small_side, large_side = detect_template(image, orientation)
273
+ a, b = detect_RGB_values(image, polygon)
274
+ if len(a) == 24:
275
+ M, score = calculate_color_correction_matrix_ransac(a, b)
276
+ if orientation == 0:
277
+ image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
278
+ corrected_image = apply_color_correction(image, M)
279
+ if orientation == 0:
280
+ width = small_side
281
+ height = large_side
282
+ else:
283
+ width = large_side
284
+ height = small_side
285
+ corrected_image = cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB)
286
+ cv.imwrite("result.jpeg", corrected_image)
287
+ imencoded = cv.imencode(".jpg", corrected_image)[1]
288
+ # build a response dict to send back to client
289
+ response = {'message': 'image processed. px_width/px_height={}/{}'.format(px_width, px_height),
290
+ 'file': ('image.jpg', imencoded.tostring(), 'image/jpeg', {'Expires': '0'})
291
+ }
292
+ # encode response using jsonpickle
293
+ response_pickled = jsonpickle.encode(response)
294
+ return Response(response=response_pickled, status=200, mimetype="application/json")
295
+
296
+
297
+ # start flask app
298
+ app.run(host="127.0.0.1", port=5000)
flagged/Input Image Component/0566ffd1daee0710f176/9-1.jpg ADDED
flagged/Output Image Component/e95f50d4589989a23267/image.webp ADDED
flagged/log.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Input Image Component,Output Image Component,flag,username,timestamp
2
+ flagged\Input Image Component\0566ffd1daee0710f176\9-1.jpg,flagged\Output Image Component\e95f50d4589989a23267\image.webp,,,2024-06-25 16:39:30.513923
gradio_app.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, new_lines, M_T = check_points(centers, x2, x1, y1, y2, image)
114
+ else:
115
+ label_upper, label_lower, M_T = 0, 0, []
116
+ else:
117
+ label_upper, label_lower, M_T = 0, 0, []
118
+ else:
119
+ label_upper, label_lower, M_T = 0, 0, []
120
+ M_R = np.array([
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
+
129
+ if len(M_T) < 24:
130
+ for i in range(label_upper):
131
+ new_lines[0] = [(x, y - round(usual)) for x, y in new_lines[1]]
132
+ for j in range(label_lower):
133
+ new_lines[-1] = [(x, y + round(usual)) for x, y in new_lines[-2]]
134
+ if len(M_T) != 24:
135
+ new_lines = []
136
+ M_T = [point for sublist in new_lines for point in sublist]
137
+ M_T_values = np.array(get_reference_values(M_T, image))
138
+ return M_T_values, M_R
139
+
140
+
141
+ css = ".input_image {height: 10% !important; width: 10% !important;}"
142
+
143
+
144
+ def detect_template(image, orientation):
145
+ MIN_MATCH_COUNT = 10
146
+ template_path = 'template_img.png'
147
+ template_image = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
148
+ gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
149
+ image_c = image.copy()
150
+ # Initiate SIFT detector
151
+ sift = cv2.SIFT_create()
152
+ keypoints1, descriptors1 = sift.detectAndCompute(template_image, None)
153
+ keypoints2, descriptors2 = sift.detectAndCompute(gray_image, None)
154
+
155
+ # FLANN parameters
156
+ index_params = dict(algorithm=1, trees=5)
157
+ search_params = dict(checks=50)
158
+ flann = cv2.FlannBasedMatcher(index_params, search_params)
159
+ matches = flann.knnMatch(descriptors1, descriptors2, k=2)
160
+
161
+ # Apply ratio test
162
+ good_matches = [m for m, n in matches if m.distance < 0.7 * n.distance]
163
+
164
+ if len(good_matches) > MIN_MATCH_COUNT:
165
+ src_points = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
166
+ dst_points = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
167
+
168
+ M, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0)
169
+ h, w = template_image.shape
170
+ template_corners = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
171
+ dst_corners = cv2.perspectiveTransform(template_corners, M)
172
+ x1, y1 = map(round, dst_corners[0][0])
173
+ x2, y2 = map(round, dst_corners[2][0])
174
+ # if orientation == 0:
175
+ # checker_large_im = abs(y2 - y1)
176
+ # checker_small_im = abs(x2 - x1)
177
+ # else:
178
+ checker_small_im = abs(y2 - y1)
179
+ checker_large_im = abs(x2 - x1)
180
+ if checker_small_im != 0 and checker_large_im != 0:
181
+ px_cm_ratio_small = checker_small_real / checker_small_im
182
+ px_cm_ratio_large = checker_large_real / checker_large_im
183
+ else:
184
+ px_cm_ratio_small = 0
185
+ px_cm_ratio_large = 0
186
+
187
+ annotated_image = cv2.polylines(image_c, [np.int32(dst_corners)], True, 255, 3, cv2.LINE_AA)
188
+
189
+ if orientation == 0:
190
+ annotated_image = cv2.rotate(annotated_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
191
+ else:
192
+ print(f"Not enough matches are found - {len(good_matches)}/{MIN_MATCH_COUNT}")
193
+ return None, 0, 0
194
+ if orientation ==0:
195
+ cm_per_pixel_width = px_cm_ratio_small
196
+ cm_per_pixel_height = px_cm_ratio_large
197
+ else:
198
+ cm_per_pixel_width = px_cm_ratio_large
199
+ cm_per_pixel_height = px_cm_ratio_small
200
+
201
+ return annotated_image, dst_corners, cm_per_pixel_width, cm_per_pixel_height,checker_small_im,checker_large_im
202
+
203
+
204
+ def srgb_to_linear(rgb):
205
+ rgb = rgb / 255.0
206
+ linear_rgb = np.where(rgb <= 0.04045, rgb / 12.92, ((rgb + 0.055) / 1.055) ** 2.4)
207
+ return linear_rgb
208
+
209
+
210
+ def linear_to_srgb(linear_rgb):
211
+ # Clip linear_rgb to ensure no negative values
212
+ linear_rgb = np.clip(linear_rgb, 0, 1)
213
+ srgb = np.where(linear_rgb <= 0.0031308, linear_rgb * 12.92, 1.055 * (linear_rgb ** (1 / 2.4)) - 0.055)
214
+ srgb = np.clip(srgb * 255, 0, 255)
215
+ return srgb.astype(np.uint8)
216
+
217
+
218
+ def calculate_color_correction_matrix_ransac(sample_rgb, reference_rgb):
219
+ sample_rgb = sample_rgb[::-1]
220
+ sample_rgb_linear = srgb_to_linear(sample_rgb)
221
+ reference_rgb_linear = srgb_to_linear(reference_rgb)
222
+
223
+ # Reshape the data for RANSAC
224
+ X = sample_rgb_linear
225
+ y = reference_rgb_linear
226
+
227
+ # Initialize RANSAC regressor for each color channel
228
+ models = []
229
+ scores = []
230
+ for i in range(3): # For each RGB channel
231
+ ransac = LinearRegression()
232
+ ransac.fit(X, y[:, i])
233
+ scores.append(ransac.score(X, y[:, i]))
234
+ models.append(ransac.coef_)
235
+ score = np.mean(scores)
236
+ # Stack coefficients to form the transformation matrix
237
+ M = np.stack(models, axis=-1)
238
+
239
+ return M, score
240
+
241
+
242
+ def apply_color_correction(image, M):
243
+ image_linear = srgb_to_linear(image)
244
+ corrected_image_linear = np.dot(image_linear, M)
245
+ corrected_image_srgb = linear_to_srgb(corrected_image_linear)
246
+ return corrected_image_srgb
247
+
248
+
249
+ def calibrate_img(img):
250
+ image, orientation = check_orientation(img)
251
+ annotated_image, polygon, px_width, px_height,small_side,large_side = detect_template(image, orientation)
252
+ a, b = detect_RGB_values(image, polygon)
253
+ if len(a) == 24:
254
+ M, score = calculate_color_correction_matrix_ransac(a, b)
255
+ if orientation == 0:
256
+ image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
257
+ corrected_image = apply_color_correction(image, M)
258
+ #corrected_image = cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB)
259
+ if orientation == 0:
260
+ width= small_side
261
+ height= large_side
262
+ else:
263
+ width = large_side
264
+ height = small_side
265
+ return annotated_image, corrected_image, px_width, px_height, width, height
266
+
267
+
268
+ def process_img(img):
269
+ return calibrate_img(img)
270
+
271
+
272
+ app = gr.Interface(
273
+ fn=process_img,
274
+ inputs=gr.Image(label="Input"),
275
+ css=css,
276
+ outputs=[gr.Image(label="Output"), gr.Image(label="Corrected"), gr.Label(label='Cm/px for Width'),
277
+ gr.Label(label='Cm/px for Height'), gr.Label(label='Checker Width'),
278
+ gr.Label(label='Checker Height'),],
279
+ allow_flagging='never')
280
+
281
+ app.launch(share=True)
result.jpeg ADDED

Git LFS Details

  • SHA256: 5aafc67a0101fbf008e60bda9133dfbb103a9e2f7d85e9c0632b801fe086b86a
  • Pointer size: 132 Bytes
  • Size of remote file: 3.58 MB
template_img.png ADDED
templates/upload.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <head>
3
+ <title>Upload file</title>
4
+ </head>
5
+
6
+ <body>
7
+ <form action="http://localhost:5000/api/dimension_checker" method="POST"
8
+ enctype="multipart/form-data">
9
+ <input type="file" name="file[]" multiple><br><br>
10
+ After uploading multiple files, click Submit.<br>
11
+ <input type="submit" value="Submit">
12
+ </form>
13
+ </body>
14
+ </html>
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")