Lonelyguyse1 commited on
Commit
b5355f5
·
verified ·
1 Parent(s): de16156

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. .dockerignore +2 -0
  2. .gitattributes +13 -0
  3. .github/workflows/.gitkeep +0 -0
  4. .gitignore +5 -0
  5. .nottracked/Screenshot From 2026-06-28 02-12-33.png +0 -0
  6. .nottracked/Screenshot From 2026-06-28 02-12-45.png +0 -0
  7. .nottracked/UG-CSE-2025.pdf +3 -0
  8. .nottracked/UG-CSE-2025.txt +37 -0
  9. .nottracked/log.txt +1 -0
  10. .nottracked/tables.txt +26 -0
  11. .venv/.gitignore +2 -0
  12. .venv/bin/Activate.ps1 +248 -0
  13. .venv/bin/activate +76 -0
  14. .venv/bin/activate.csh +27 -0
  15. .venv/bin/activate.fish +69 -0
  16. .venv/bin/detect-installer-test +6 -0
  17. .venv/bin/dotenv +6 -0
  18. .venv/bin/email_validator +6 -0
  19. .venv/bin/fastapi +6 -0
  20. .venv/bin/fonttools +6 -0
  21. .venv/bin/httpx +6 -0
  22. .venv/bin/idna +6 -0
  23. .venv/bin/markdown-it +6 -0
  24. .venv/bin/pip +8 -0
  25. .venv/bin/pip3 +8 -0
  26. .venv/bin/pip3.14 +8 -0
  27. .venv/bin/py.test +6 -0
  28. .venv/bin/pyftmerge +6 -0
  29. .venv/bin/pyftsubset +6 -0
  30. .venv/bin/pygmentize +6 -0
  31. .venv/bin/pytest +6 -0
  32. .venv/bin/python +3 -0
  33. .venv/bin/python3 +3 -0
  34. .venv/bin/python3.14 +3 -0
  35. .venv/bin/ruff +3 -0
  36. .venv/bin/ttx +6 -0
  37. .venv/bin/typer +6 -0
  38. .venv/bin/uvicorn +6 -0
  39. .venv/bin/watchfiles +6 -0
  40. .venv/bin/weasyprint +6 -0
  41. .venv/bin/websockets +6 -0
  42. .venv/bin/𝜋thon +3 -0
  43. .venv/lib/python3.14/site-packages/PIL/AvifImagePlugin.py +293 -0
  44. .venv/lib/python3.14/site-packages/PIL/BdfFontFile.py +123 -0
  45. .venv/lib/python3.14/site-packages/PIL/BlpImagePlugin.py +498 -0
  46. .venv/lib/python3.14/site-packages/PIL/BmpImagePlugin.py +514 -0
  47. .venv/lib/python3.14/site-packages/PIL/BufrStubImagePlugin.py +72 -0
  48. .venv/lib/python3.14/site-packages/PIL/ContainerIO.py +173 -0
  49. .venv/lib/python3.14/site-packages/PIL/CurImagePlugin.py +75 -0
  50. .venv/lib/python3.14/site-packages/PIL/DcxImagePlugin.py +84 -0
.dockerignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .venv/ __pycache__/ *.pyc .env .nottracked/ frontend/
2
+ .git/ .gitignore AGENTS.md README.md LICENSE *.md
.gitattributes CHANGED
@@ -33,3 +33,16 @@ 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
+ .nottracked/UG-CSE-2025.pdf filter=lfs diff=lfs merge=lfs -text
37
+ .venv/bin/python filter=lfs diff=lfs merge=lfs -text
38
+ .venv/bin/python3 filter=lfs diff=lfs merge=lfs -text
39
+ .venv/bin/python3.14 filter=lfs diff=lfs merge=lfs -text
40
+ .venv/bin/ruff filter=lfs diff=lfs merge=lfs -text
41
+ .venv/bin/𝜋thon filter=lfs diff=lfs merge=lfs -text
42
+ .venv/lib/python3.14/site-packages/PIL/__pycache__/Image.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text
43
+ .venv/lib/python3.14/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-314.pyc filter=lfs diff=lfs merge=lfs -text
44
+ .venv/lib/python3.14/site-packages/PIL/_imaging.cpython-314-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
45
+ .venv/lib/python3.14/site-packages/PIL/_imagingcms.cpython-314-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
46
+ .venv/lib/python3.14/site-packages/PIL/_imagingft.cpython-314-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
47
+ .venv/lib/python3.14/site-packages/PIL/_imagingmath.cpython-314-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
48
+ .venv/lib/python3.14/site-packages/PIL/_webp.cpython-314-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
.github/workflows/.gitkeep ADDED
File without changes
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .venv
2
+ __pycache__
3
+ AGENTS.md
4
+ .nottracked
5
+ .env
.nottracked/Screenshot From 2026-06-28 02-12-33.png ADDED
.nottracked/Screenshot From 2026-06-28 02-12-45.png ADDED
.nottracked/UG-CSE-2025.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d9aae2a194039432f7445da0978190fcdf62ab2b86c43eb34d7733b7943b641f
3
+ size 1608543
.nottracked/UG-CSE-2025.txt ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Computer Science and Engineering
2
+
3
+
4
+
5
+ B.TECH IN COMPUTERSCIENCEANDENGINEERING
6
+
7
+
8
+ I SEMESTER (2025-29 BATCH)
9
+
10
+ Sl. Hours per week Credits AI Tool/ Tools/Languages Course Type
11
+ No Course Code Course Title
12
+ L T P S C
13
+ Python interpreter 3.8 and
14
+ Python for above. IDLE and any IDE like Foundation
15
+ Computational Jupyter Course-Lab
16
+ 1 UE25CS151A 4 0 2 5 5
17
+ Problem Solving AI tool- Copilot aided Integrated
18
+ teaching
19
+
20
+ II SEMESTER (2025-29 BATCH)
21
+
22
+ Sl. Hours per week Credits
23
+ No Course Code Course Title AI Tools/ Tools/Languages Course Type
24
+ L T P S C
25
+ C Programming Language
26
+ Foundation
27
+ 1 GCC Compiler, GDB Debugger
28
+ Problem Solving Course- Lab
29
+ UE25CS151B 4 0 2 5 5 AI Tools- Code 5, DeepCode,
30
+ with C Integrated
31
+ Codium, AIcode Helper
32
+
33
+
34
+
35
+
36
+ P.E.S. University Curriculum :- 2025-2026 3 | Page
37
+
.nottracked/log.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ https://www.pesuacademy.com/Academy/
.nottracked/tables.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CREATE TABLE submissions (
2
+ id BIGSERIAL PRIMARY KEY,
3
+ created_at TIMESTAMPTZ DEFAULT now(),
4
+ status TEXT DEFAULT 'pending',
5
+ faculty_email TEXT NOT NULL,
6
+ course_title TEXT NOT NULL,
7
+ offering_department TEXT NOT NULL,
8
+ target_department TEXT NOT NULL,
9
+ semester TEXT NOT NULL,
10
+ credit_category TEXT NOT NULL,
11
+ raw_course_content TEXT NOT NULL,
12
+ text_books TEXT NOT NULL,
13
+ reference_books TEXT DEFAULT '',
14
+ preferred_tools TEXT DEFAULT ''
15
+ );
16
+
17
+ CREATE TABLE refined_submissions (
18
+ id BIGSERIAL PRIMARY KEY,
19
+ submission_id BIGINT REFERENCES submissions(id),
20
+ created_at TIMESTAMPTZ DEFAULT now(),
21
+ refined_content TEXT,
22
+ prelude TEXT,
23
+ objectives TEXT[],
24
+ outcomes TEXT[],
25
+ recommended_tools TEXT[]
26
+ );
.venv/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Created by venv; see https://docs.python.org/3/library/venv.html
2
+ *
.venv/bin/Activate.ps1 ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <#
2
+ .Synopsis
3
+ Activate a Python virtual environment for the current PowerShell session.
4
+
5
+ .Description
6
+ Pushes the python executable for a virtual environment to the front of the
7
+ $Env:PATH environment variable and sets the prompt to signify that you are
8
+ in a Python virtual environment. Makes use of the command line switches as
9
+ well as the `pyvenv.cfg` file values present in the virtual environment.
10
+
11
+ .Parameter VenvDir
12
+ Path to the directory that contains the virtual environment to activate. The
13
+ default value for this is the parent of the directory that the Activate.ps1
14
+ script is located within.
15
+
16
+ .Parameter Prompt
17
+ The prompt prefix to display when this virtual environment is activated. By
18
+ default, this prompt is the name of the virtual environment folder (VenvDir)
19
+ surrounded by parentheses and followed by a single space (ie. '(.venv) ').
20
+
21
+ .Example
22
+ Activate.ps1
23
+ Activates the Python virtual environment that contains the Activate.ps1 script.
24
+
25
+ .Example
26
+ Activate.ps1 -Verbose
27
+ Activates the Python virtual environment that contains the Activate.ps1 script,
28
+ and shows extra information about the activation as it executes.
29
+
30
+ .Example
31
+ Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
32
+ Activates the Python virtual environment located in the specified location.
33
+
34
+ .Example
35
+ Activate.ps1 -Prompt "MyPython"
36
+ Activates the Python virtual environment that contains the Activate.ps1 script,
37
+ and prefixes the current prompt with the specified string (surrounded in
38
+ parentheses) while the virtual environment is active.
39
+
40
+ .Notes
41
+ On Windows, it may be required to enable this Activate.ps1 script by setting the
42
+ execution policy for the user. You can do this by issuing the following PowerShell
43
+ command:
44
+
45
+ PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
46
+
47
+ For more information on Execution Policies:
48
+ https://go.microsoft.com/fwlink/?LinkID=135170
49
+
50
+ #>
51
+ Param(
52
+ [Parameter(Mandatory = $false)]
53
+ [String]
54
+ $VenvDir,
55
+ [Parameter(Mandatory = $false)]
56
+ [String]
57
+ $Prompt
58
+ )
59
+
60
+ <# Function declarations --------------------------------------------------- #>
61
+
62
+ <#
63
+ .Synopsis
64
+ Remove all shell session elements added by the Activate script, including the
65
+ addition of the virtual environment's Python executable from the beginning of
66
+ the PATH variable.
67
+
68
+ .Parameter NonDestructive
69
+ If present, do not remove this function from the global namespace for the
70
+ session.
71
+
72
+ #>
73
+ function global:deactivate ([switch]$NonDestructive) {
74
+ # Revert to original values
75
+
76
+ # The prior prompt:
77
+ if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
78
+ Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
79
+ Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
80
+ }
81
+
82
+ # The prior PYTHONHOME:
83
+ if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
84
+ Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
85
+ Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
86
+ }
87
+
88
+ # The prior PATH:
89
+ if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
90
+ Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
91
+ Remove-Item -Path Env:_OLD_VIRTUAL_PATH
92
+ }
93
+
94
+ # Just remove the VIRTUAL_ENV altogether:
95
+ if (Test-Path -Path Env:VIRTUAL_ENV) {
96
+ Remove-Item -Path env:VIRTUAL_ENV
97
+ }
98
+
99
+ # Just remove VIRTUAL_ENV_PROMPT altogether.
100
+ if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
101
+ Remove-Item -Path env:VIRTUAL_ENV_PROMPT
102
+ }
103
+
104
+ # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
105
+ if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
106
+ Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
107
+ }
108
+
109
+ # Leave deactivate function in the global namespace if requested:
110
+ if (-not $NonDestructive) {
111
+ Remove-Item -Path function:deactivate
112
+ }
113
+ }
114
+
115
+ <#
116
+ .Description
117
+ Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
118
+ given folder, and returns them in a map.
119
+
120
+ For each line in the pyvenv.cfg file, if that line can be parsed into exactly
121
+ two strings separated by `=` (with any amount of whitespace surrounding the =)
122
+ then it is considered a `key = value` line. The left hand string is the key,
123
+ the right hand is the value.
124
+
125
+ If the value starts with a `'` or a `"` then the first and last character is
126
+ stripped from the value before being captured.
127
+
128
+ .Parameter ConfigDir
129
+ Path to the directory that contains the `pyvenv.cfg` file.
130
+ #>
131
+ function Get-PyVenvConfig(
132
+ [String]
133
+ $ConfigDir
134
+ ) {
135
+ Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
136
+
137
+ # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
138
+ $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
139
+
140
+ # An empty map will be returned if no config file is found.
141
+ $pyvenvConfig = @{ }
142
+
143
+ if ($pyvenvConfigPath) {
144
+
145
+ Write-Verbose "File exists, parse `key = value` lines"
146
+ $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
147
+
148
+ $pyvenvConfigContent | ForEach-Object {
149
+ $keyval = $PSItem -split "\s*=\s*", 2
150
+ if ($keyval[0] -and $keyval[1]) {
151
+ $val = $keyval[1]
152
+
153
+ # Remove extraneous quotations around a string value.
154
+ if ("'""".Contains($val.Substring(0, 1))) {
155
+ $val = $val.Substring(1, $val.Length - 2)
156
+ }
157
+
158
+ $pyvenvConfig[$keyval[0]] = $val
159
+ Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
160
+ }
161
+ }
162
+ }
163
+ return $pyvenvConfig
164
+ }
165
+
166
+
167
+ <# Begin Activate script --------------------------------------------------- #>
168
+
169
+ # Determine the containing directory of this script
170
+ $VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
171
+ $VenvExecDir = Get-Item -Path $VenvExecPath
172
+
173
+ Write-Verbose "Activation script is located in path: '$VenvExecPath'"
174
+ Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
175
+ Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
176
+
177
+ # Set values required in priority: CmdLine, ConfigFile, Default
178
+ # First, get the location of the virtual environment, it might not be
179
+ # VenvExecDir if specified on the command line.
180
+ if ($VenvDir) {
181
+ Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
182
+ }
183
+ else {
184
+ Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
185
+ $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
186
+ Write-Verbose "VenvDir=$VenvDir"
187
+ }
188
+
189
+ # Next, read the `pyvenv.cfg` file to determine any required value such
190
+ # as `prompt`.
191
+ $pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
192
+
193
+ # Next, set the prompt from the command line, or the config file, or
194
+ # just use the name of the virtual environment folder.
195
+ if ($Prompt) {
196
+ Write-Verbose "Prompt specified as argument, using '$Prompt'"
197
+ }
198
+ else {
199
+ Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
200
+ if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
201
+ Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
202
+ $Prompt = $pyvenvCfg['prompt'];
203
+ }
204
+ else {
205
+ Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
206
+ Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
207
+ $Prompt = Split-Path -Path $venvDir -Leaf
208
+ }
209
+ }
210
+
211
+ Write-Verbose "Prompt = '$Prompt'"
212
+ Write-Verbose "VenvDir='$VenvDir'"
213
+
214
+ # Deactivate any currently active virtual environment, but leave the
215
+ # deactivate function in place.
216
+ deactivate -nondestructive
217
+
218
+ # Now set the environment variable VIRTUAL_ENV, used by many tools to determine
219
+ # that there is an activated venv.
220
+ $env:VIRTUAL_ENV = $VenvDir
221
+
222
+ $env:VIRTUAL_ENV_PROMPT = $Prompt
223
+
224
+ if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
225
+
226
+ Write-Verbose "Setting prompt to '$Prompt'"
227
+
228
+ # Set the prompt to include the env name
229
+ # Make sure _OLD_VIRTUAL_PROMPT is global
230
+ function global:_OLD_VIRTUAL_PROMPT { "" }
231
+ Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
232
+ New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
233
+
234
+ function global:prompt {
235
+ Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
236
+ _OLD_VIRTUAL_PROMPT
237
+ }
238
+ }
239
+
240
+ # Clear PYTHONHOME
241
+ if (Test-Path -Path Env:PYTHONHOME) {
242
+ Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
243
+ Remove-Item -Path Env:PYTHONHOME
244
+ }
245
+
246
+ # Add the venv to the PATH
247
+ Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
248
+ $Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
.venv/bin/activate ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file must be used with "source bin/activate" *from bash*
2
+ # You cannot run it directly
3
+
4
+ deactivate () {
5
+ # reset old environment variables
6
+ if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
7
+ PATH="${_OLD_VIRTUAL_PATH:-}"
8
+ export PATH
9
+ unset _OLD_VIRTUAL_PATH
10
+ fi
11
+ if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
12
+ PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
13
+ export PYTHONHOME
14
+ unset _OLD_VIRTUAL_PYTHONHOME
15
+ fi
16
+
17
+ # Call hash to forget past locations. Without forgetting
18
+ # past locations the $PATH changes we made may not be respected.
19
+ # See "man bash" for more details. hash is usually a builtin of your shell
20
+ hash -r 2> /dev/null
21
+
22
+ if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
23
+ PS1="${_OLD_VIRTUAL_PS1:-}"
24
+ export PS1
25
+ unset _OLD_VIRTUAL_PS1
26
+ fi
27
+
28
+ unset VIRTUAL_ENV
29
+ unset VIRTUAL_ENV_PROMPT
30
+ if [ ! "${1:-}" = "nondestructive" ] ; then
31
+ # Self destruct!
32
+ unset -f deactivate
33
+ fi
34
+ }
35
+
36
+ # unset irrelevant variables
37
+ deactivate nondestructive
38
+
39
+ # on Windows, a path can contain colons and backslashes and has to be converted:
40
+ case "$(uname)" in
41
+ CYGWIN*|MSYS*|MINGW*)
42
+ # transform D:\path\to\venv to /d/path/to/venv on MSYS and MINGW
43
+ # and to /cygdrive/d/path/to/venv on Cygwin
44
+ VIRTUAL_ENV=$(cygpath /home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv)
45
+ export VIRTUAL_ENV
46
+ ;;
47
+ *)
48
+ # use the path as-is
49
+ export VIRTUAL_ENV=/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv
50
+ ;;
51
+ esac
52
+
53
+ _OLD_VIRTUAL_PATH="$PATH"
54
+ PATH="$VIRTUAL_ENV/"bin":$PATH"
55
+ export PATH
56
+
57
+ VIRTUAL_ENV_PROMPT=.venv
58
+ export VIRTUAL_ENV_PROMPT
59
+
60
+ # unset PYTHONHOME if set
61
+ # this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
62
+ # could use `if (set -u; : $PYTHONHOME) ;` in bash
63
+ if [ -n "${PYTHONHOME:-}" ] ; then
64
+ _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
65
+ unset PYTHONHOME
66
+ fi
67
+
68
+ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
69
+ _OLD_VIRTUAL_PS1="${PS1:-}"
70
+ PS1="(".venv") ${PS1:-}"
71
+ export PS1
72
+ fi
73
+
74
+ # Call hash to forget past commands. Without forgetting
75
+ # past commands the $PATH changes we made may not be respected
76
+ hash -r 2> /dev/null
.venv/bin/activate.csh ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file must be used with "source bin/activate.csh" *from csh*.
2
+ # You cannot run it directly.
3
+
4
+ # Created by Davide Di Blasi <davidedb@gmail.com>.
5
+ # Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
6
+
7
+ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
8
+
9
+ # Unset irrelevant variables.
10
+ deactivate nondestructive
11
+
12
+ setenv VIRTUAL_ENV /home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv
13
+
14
+ set _OLD_VIRTUAL_PATH="$PATH"
15
+ setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
16
+ setenv VIRTUAL_ENV_PROMPT .venv
17
+
18
+
19
+ set _OLD_VIRTUAL_PROMPT="$prompt"
20
+
21
+ if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
22
+ set prompt = "(".venv") $prompt:q"
23
+ endif
24
+
25
+ alias pydoc python -m pydoc
26
+
27
+ rehash
.venv/bin/activate.fish ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file must be used with "source <venv>/bin/activate.fish" *from fish*
2
+ # (https://fishshell.com/). You cannot run it directly.
3
+
4
+ function deactivate -d "Exit virtual environment and return to normal shell environment"
5
+ # reset old environment variables
6
+ if test -n "$_OLD_VIRTUAL_PATH"
7
+ set -gx PATH $_OLD_VIRTUAL_PATH
8
+ set -e _OLD_VIRTUAL_PATH
9
+ end
10
+ if test -n "$_OLD_VIRTUAL_PYTHONHOME"
11
+ set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
12
+ set -e _OLD_VIRTUAL_PYTHONHOME
13
+ end
14
+
15
+ if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
16
+ set -e _OLD_FISH_PROMPT_OVERRIDE
17
+ # prevents error when using nested fish instances (Issue #93858)
18
+ if functions -q _old_fish_prompt
19
+ functions -e fish_prompt
20
+ functions -c _old_fish_prompt fish_prompt
21
+ functions -e _old_fish_prompt
22
+ end
23
+ end
24
+
25
+ set -e VIRTUAL_ENV
26
+ set -e VIRTUAL_ENV_PROMPT
27
+ if test "$argv[1]" != "nondestructive"
28
+ # Self-destruct!
29
+ functions -e deactivate
30
+ end
31
+ end
32
+
33
+ # Unset irrelevant variables.
34
+ deactivate nondestructive
35
+
36
+ set -gx VIRTUAL_ENV /home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv
37
+
38
+ set -gx _OLD_VIRTUAL_PATH $PATH
39
+ set -gx PATH "$VIRTUAL_ENV/"bin $PATH
40
+ set -gx VIRTUAL_ENV_PROMPT .venv
41
+
42
+ # Unset PYTHONHOME if set.
43
+ if set -q PYTHONHOME
44
+ set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
45
+ set -e PYTHONHOME
46
+ end
47
+
48
+ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
49
+ # fish uses a function instead of an env var to generate the prompt.
50
+
51
+ # Save the current fish_prompt function as the function _old_fish_prompt.
52
+ functions -c fish_prompt _old_fish_prompt
53
+
54
+ # With the original prompt function renamed, we can override with our own.
55
+ function fish_prompt
56
+ # Save the return status of the last command.
57
+ set -l old_status $status
58
+
59
+ # Output the venv prompt; color taken from the blue of the Python logo.
60
+ printf "%s(%s)%s " (set_color 4B8BBE) .venv (set_color normal)
61
+
62
+ # Restore the return status of the previous command.
63
+ echo "exit $old_status" | .
64
+ # Output the original/"old" prompt.
65
+ _old_fish_prompt
66
+ end
67
+
68
+ set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
69
+ end
.venv/bin/detect-installer-test ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from detect_installer._test import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/dotenv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from dotenv.__main__ import cli
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(cli())
.venv/bin/email_validator ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from email_validator.__main__ import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/fastapi ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from fastapi.cli import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/fonttools ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from fontTools.__main__ import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/httpx ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from httpx import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/idna ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from idna.cli import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/markdown-it ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from markdown_it.cli.parse import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/pip ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from pip._internal.cli.main import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
.venv/bin/pip3 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from pip._internal.cli.main import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
.venv/bin/pip3.14 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from pip._internal.cli.main import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
.venv/bin/py.test ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from _pytest.config import _console_main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(_console_main())
.venv/bin/pyftmerge ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from fontTools.merge import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/pyftsubset ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from fontTools.subset import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/pygmentize ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from pygments.cmdline import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/pytest ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from _pytest.config import _console_main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(_console_main())
.venv/bin/python ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa9796cd3a30878e11a2f40372f773d3fcd913fff35e5bee8dd9a036e22e93ab
3
+ size 7477096
.venv/bin/python3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa9796cd3a30878e11a2f40372f773d3fcd913fff35e5bee8dd9a036e22e93ab
3
+ size 7477096
.venv/bin/python3.14 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa9796cd3a30878e11a2f40372f773d3fcd913fff35e5bee8dd9a036e22e93ab
3
+ size 7477096
.venv/bin/ruff ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:42263af2a5bfc5d78f897f9b2ada96c448d132d07dee4f62885f67bca14ee4f8
3
+ size 28182400
.venv/bin/ttx ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from fontTools.ttx import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/typer ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from typer.cli import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/uvicorn ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from uvicorn.main import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/watchfiles ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from watchfiles.cli import cli
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(cli())
.venv/bin/weasyprint ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from weasyprint.__main__ import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/websockets ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/home/lonelyguy/Desktop/Methuselah/Projects/PESU-Curriculum-Automation/.venv/bin/python
2
+ import sys
3
+ from websockets.cli import main
4
+ if __name__ == '__main__':
5
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
6
+ sys.exit(main())
.venv/bin/𝜋thon ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa9796cd3a30878e11a2f40372f773d3fcd913fff35e5bee8dd9a036e22e93ab
3
+ size 7477096
.venv/lib/python3.14/site-packages/PIL/AvifImagePlugin.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from io import BytesIO
5
+ from typing import IO
6
+
7
+ from . import ExifTags, Image, ImageFile
8
+
9
+ try:
10
+ from . import _avif
11
+
12
+ SUPPORTED = True
13
+ except ImportError:
14
+ SUPPORTED = False
15
+
16
+ # Decoder options as module globals, until there is a way to pass parameters
17
+ # to Image.open (see https://github.com/python-pillow/Pillow/issues/569)
18
+ DECODE_CODEC_CHOICE = "auto"
19
+ DEFAULT_MAX_THREADS = 0
20
+
21
+
22
+ def get_codec_version(codec_name: str) -> str | None:
23
+ versions = _avif.codec_versions()
24
+ for version in versions.split(", "):
25
+ if version.split(" [")[0] == codec_name:
26
+ return version.split(":")[-1].split(" ")[0]
27
+ return None
28
+
29
+
30
+ def _accept(prefix: bytes) -> bool | str:
31
+ if prefix[4:8] != b"ftyp":
32
+ return False
33
+ major_brand = prefix[8:12]
34
+ if major_brand in (
35
+ # coding brands
36
+ b"avif",
37
+ b"avis",
38
+ # We accept files with AVIF container brands; we can't yet know if
39
+ # the ftyp box has the correct compatible brands, but if it doesn't
40
+ # then the plugin will raise a SyntaxError which Pillow will catch
41
+ # before moving on to the next plugin that accepts the file.
42
+ #
43
+ # Also, because this file might not actually be an AVIF file, we
44
+ # don't raise an error if AVIF support isn't properly compiled.
45
+ b"mif1",
46
+ b"msf1",
47
+ ):
48
+ if not SUPPORTED:
49
+ return (
50
+ "image file could not be identified because AVIF support not installed"
51
+ )
52
+ return True
53
+ return False
54
+
55
+
56
+ def _get_default_max_threads() -> int:
57
+ if DEFAULT_MAX_THREADS:
58
+ return DEFAULT_MAX_THREADS
59
+ if hasattr(os, "sched_getaffinity"):
60
+ return len(os.sched_getaffinity(0))
61
+ else:
62
+ return os.cpu_count() or 1
63
+
64
+
65
+ class AvifImageFile(ImageFile.ImageFile):
66
+ format = "AVIF"
67
+ format_description = "AVIF image"
68
+ __frame = -1
69
+
70
+ def _open(self) -> None:
71
+ if not SUPPORTED:
72
+ msg = "image file could not be opened because AVIF support not installed"
73
+ raise SyntaxError(msg)
74
+
75
+ if DECODE_CODEC_CHOICE != "auto" and not _avif.decoder_codec_available(
76
+ DECODE_CODEC_CHOICE
77
+ ):
78
+ msg = "Invalid opening codec"
79
+ raise ValueError(msg)
80
+
81
+ assert self.fp is not None
82
+ self._decoder = _avif.AvifDecoder(
83
+ self.fp.read(),
84
+ DECODE_CODEC_CHOICE,
85
+ _get_default_max_threads(),
86
+ )
87
+
88
+ # Get info from decoder
89
+ self._size, self.n_frames, self._mode, icc, exif, exif_orientation, xmp = (
90
+ self._decoder.get_info()
91
+ )
92
+ self.is_animated = self.n_frames > 1
93
+
94
+ if icc:
95
+ self.info["icc_profile"] = icc
96
+ if xmp:
97
+ self.info["xmp"] = xmp
98
+
99
+ if exif_orientation != 1 or exif:
100
+ exif_data = Image.Exif()
101
+ if exif:
102
+ exif_data.load(exif)
103
+ original_orientation = exif_data.get(ExifTags.Base.Orientation, 1)
104
+ else:
105
+ original_orientation = 1
106
+ if exif_orientation != original_orientation:
107
+ exif_data[ExifTags.Base.Orientation] = exif_orientation
108
+ exif = exif_data.tobytes()
109
+ if exif:
110
+ self.info["exif"] = exif
111
+ self.seek(0)
112
+
113
+ def seek(self, frame: int) -> None:
114
+ if not self._seek_check(frame):
115
+ return
116
+
117
+ # Set tile
118
+ self.__frame = frame
119
+ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.mode)]
120
+
121
+ def load(self) -> Image.core.PixelAccess | None:
122
+ if self.tile:
123
+ # We need to load the image data for this frame
124
+ data, timescale, pts_in_timescales, duration_in_timescales = (
125
+ self._decoder.get_frame(self.__frame)
126
+ )
127
+ self.info["timestamp"] = round(1000 * (pts_in_timescales / timescale))
128
+ self.info["duration"] = round(1000 * (duration_in_timescales / timescale))
129
+
130
+ if self.fp and self._exclusive_fp:
131
+ self.fp.close()
132
+ self.fp = BytesIO(data)
133
+
134
+ return super().load()
135
+
136
+ def load_seek(self, pos: int) -> None:
137
+ pass
138
+
139
+ def tell(self) -> int:
140
+ return self.__frame
141
+
142
+
143
+ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
144
+ _save(im, fp, filename, save_all=True)
145
+
146
+
147
+ def _save(
148
+ im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
149
+ ) -> None:
150
+ info = im.encoderinfo.copy()
151
+ if save_all:
152
+ append_images = list(info.get("append_images", []))
153
+ else:
154
+ append_images = []
155
+
156
+ total = 0
157
+ for ims in [im] + append_images:
158
+ total += getattr(ims, "n_frames", 1)
159
+
160
+ quality = info.get("quality", 75)
161
+ if not isinstance(quality, int) or quality < 0 or quality > 100:
162
+ msg = "Invalid quality setting"
163
+ raise ValueError(msg)
164
+
165
+ duration = info.get("duration", 0)
166
+ subsampling = info.get("subsampling", "4:2:0")
167
+ speed = info.get("speed", 6)
168
+ max_threads = info.get("max_threads", _get_default_max_threads())
169
+ codec = info.get("codec", "auto")
170
+ if codec != "auto" and not _avif.encoder_codec_available(codec):
171
+ msg = "Invalid saving codec"
172
+ raise ValueError(msg)
173
+ range_ = info.get("range", "full")
174
+ tile_rows_log2 = info.get("tile_rows", 0)
175
+ tile_cols_log2 = info.get("tile_cols", 0)
176
+ alpha_premultiplied = bool(info.get("alpha_premultiplied", False))
177
+ autotiling = bool(info.get("autotiling", tile_rows_log2 == tile_cols_log2 == 0))
178
+
179
+ icc_profile = info.get("icc_profile", im.info.get("icc_profile"))
180
+ exif_orientation = 1
181
+ if exif := info.get("exif"):
182
+ if isinstance(exif, Image.Exif):
183
+ exif_data = exif
184
+ else:
185
+ exif_data = Image.Exif()
186
+ exif_data.load(exif)
187
+ if ExifTags.Base.Orientation in exif_data:
188
+ exif_orientation = exif_data.pop(ExifTags.Base.Orientation)
189
+ exif = exif_data.tobytes() if exif_data else b""
190
+ elif isinstance(exif, Image.Exif):
191
+ exif = exif_data.tobytes()
192
+
193
+ xmp = info.get("xmp")
194
+
195
+ if isinstance(xmp, str):
196
+ xmp = xmp.encode("utf-8")
197
+
198
+ advanced = info.get("advanced")
199
+ if advanced is not None:
200
+ if isinstance(advanced, dict):
201
+ advanced = advanced.items()
202
+ try:
203
+ advanced = tuple(advanced)
204
+ except TypeError:
205
+ invalid = True
206
+ else:
207
+ invalid = any(not isinstance(v, tuple) or len(v) != 2 for v in advanced)
208
+ if invalid:
209
+ msg = (
210
+ "advanced codec options must be a dict of key-value string "
211
+ "pairs or a series of key-value two-tuples"
212
+ )
213
+ raise ValueError(msg)
214
+
215
+ # Setup the AVIF encoder
216
+ enc = _avif.AvifEncoder(
217
+ im.size,
218
+ subsampling,
219
+ quality,
220
+ speed,
221
+ max_threads,
222
+ codec,
223
+ range_,
224
+ tile_rows_log2,
225
+ tile_cols_log2,
226
+ alpha_premultiplied,
227
+ autotiling,
228
+ icc_profile or b"",
229
+ exif or b"",
230
+ exif_orientation,
231
+ xmp or b"",
232
+ advanced,
233
+ )
234
+
235
+ # Add each frame
236
+ frame_idx = 0
237
+ frame_duration = 0
238
+ cur_idx = im.tell()
239
+ is_single_frame = total == 1
240
+ try:
241
+ for ims in [im] + append_images:
242
+ # Get number of frames in this image
243
+ nfr = getattr(ims, "n_frames", 1)
244
+
245
+ for idx in range(nfr):
246
+ ims.seek(idx)
247
+
248
+ # Make sure image mode is supported
249
+ frame = ims
250
+ rawmode = ims.mode
251
+ if ims.mode not in {"RGB", "RGBA"}:
252
+ rawmode = "RGBA" if ims.has_transparency_data else "RGB"
253
+ frame = ims.convert(rawmode)
254
+
255
+ # Update frame duration
256
+ if isinstance(duration, (list, tuple)):
257
+ frame_duration = duration[frame_idx]
258
+ else:
259
+ frame_duration = duration
260
+
261
+ # Append the frame to the animation encoder
262
+ enc.add(
263
+ frame.tobytes("raw", rawmode),
264
+ frame_duration,
265
+ frame.size,
266
+ rawmode,
267
+ is_single_frame,
268
+ )
269
+
270
+ # Update frame index
271
+ frame_idx += 1
272
+
273
+ if not save_all:
274
+ break
275
+
276
+ finally:
277
+ im.seek(cur_idx)
278
+
279
+ # Get the final output from the encoder
280
+ data = enc.finish()
281
+ if data is None:
282
+ msg = "cannot write file as AVIF (encoder returned None)"
283
+ raise OSError(msg)
284
+
285
+ fp.write(data)
286
+
287
+
288
+ Image.register_open(AvifImageFile.format, AvifImageFile, _accept)
289
+ if SUPPORTED:
290
+ Image.register_save(AvifImageFile.format, _save)
291
+ Image.register_save_all(AvifImageFile.format, _save_all)
292
+ Image.register_extensions(AvifImageFile.format, [".avif", ".avifs"])
293
+ Image.register_mime(AvifImageFile.format, "image/avif")
.venv/lib/python3.14/site-packages/PIL/BdfFontFile.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
24
+ from __future__ import annotations
25
+
26
+ from typing import BinaryIO
27
+
28
+ from . import FontFile, Image
29
+
30
+
31
+ def bdf_char(
32
+ f: BinaryIO,
33
+ ) -> (
34
+ tuple[
35
+ str,
36
+ int,
37
+ tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]],
38
+ Image.Image,
39
+ ]
40
+ | None
41
+ ):
42
+ # skip to STARTCHAR
43
+ while True:
44
+ s = f.readline()
45
+ if not s:
46
+ return None
47
+ if s.startswith(b"STARTCHAR"):
48
+ break
49
+ id = s[9:].strip().decode("ascii")
50
+
51
+ # load symbol properties
52
+ props = {}
53
+ while True:
54
+ s = f.readline()
55
+ if not s or s.startswith(b"BITMAP"):
56
+ break
57
+ i = s.find(b" ")
58
+ props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
59
+
60
+ # load bitmap
61
+ bitmap = bytearray()
62
+ while True:
63
+ s = f.readline()
64
+ if not s or s.startswith(b"ENDCHAR"):
65
+ break
66
+ bitmap += s[:-1]
67
+
68
+ # The word BBX
69
+ # followed by the width in x (BBw), height in y (BBh),
70
+ # and x and y displacement (BBxoff0, BByoff0)
71
+ # of the lower left corner from the origin of the character.
72
+ width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split())
73
+
74
+ # The word DWIDTH
75
+ # followed by the width in x and y of the character in device pixels.
76
+ dwx, dwy = (int(p) for p in props["DWIDTH"].split())
77
+
78
+ bbox = (
79
+ (dwx, dwy),
80
+ (x_disp, -y_disp - height, width + x_disp, -y_disp),
81
+ (0, 0, width, height),
82
+ )
83
+
84
+ try:
85
+ im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
86
+ except ValueError:
87
+ # deal with zero-width characters
88
+ im = Image.new("1", (width, height))
89
+
90
+ return id, int(props["ENCODING"]), bbox, im
91
+
92
+
93
+ class BdfFontFile(FontFile.FontFile):
94
+ """Font file plugin for the X11 BDF format."""
95
+
96
+ def __init__(self, fp: BinaryIO) -> None:
97
+ super().__init__()
98
+
99
+ s = fp.readline()
100
+ if not s.startswith(b"STARTFONT 2.1"):
101
+ msg = "not a valid BDF file"
102
+ raise SyntaxError(msg)
103
+
104
+ props = {}
105
+ comments = []
106
+
107
+ while True:
108
+ s = fp.readline()
109
+ if not s or s.startswith(b"ENDPROPERTIES"):
110
+ break
111
+ i = s.find(b" ")
112
+ props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
113
+ if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
114
+ if s.find(b"LogicalFontDescription") < 0:
115
+ comments.append(s[i + 1 : -1].decode("ascii"))
116
+
117
+ while True:
118
+ c = bdf_char(fp)
119
+ if not c:
120
+ break
121
+ id, ch, (xy, dst, src), im = c
122
+ if 0 <= ch < len(self.glyph):
123
+ self.glyph[ch] = xy, dst, src, im
.venv/lib/python3.14/site-packages/PIL/BlpImagePlugin.py ADDED
@@ -0,0 +1,498 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Blizzard Mipmap Format (.blp)
3
+ Jerome Leclanche <jerome@leclan.ch>
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 abc
35
+ import os
36
+ import struct
37
+ from enum import IntEnum
38
+ from io import BytesIO
39
+ from typing import IO
40
+
41
+ from . import Image, ImageFile
42
+
43
+
44
+ class Format(IntEnum):
45
+ JPEG = 0
46
+
47
+
48
+ class Encoding(IntEnum):
49
+ UNCOMPRESSED = 1
50
+ DXT = 2
51
+ UNCOMPRESSED_RAW_BGRA = 3
52
+
53
+
54
+ class AlphaEncoding(IntEnum):
55
+ DXT1 = 0
56
+ DXT3 = 1
57
+ DXT5 = 7
58
+
59
+
60
+ def unpack_565(i: int) -> tuple[int, int, int]:
61
+ return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
62
+
63
+
64
+ def decode_dxt1(
65
+ data: bytes, alpha: bool = False
66
+ ) -> tuple[bytearray, bytearray, bytearray, bytearray]:
67
+ """
68
+ input: one "row" of data (i.e. will produce 4*width pixels)
69
+ """
70
+
71
+ blocks = len(data) // 8 # number of blocks in row
72
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
73
+
74
+ for block_index in range(blocks):
75
+ # Decode next 8-byte block.
76
+ idx = block_index * 8
77
+ color0, color1, bits = struct.unpack_from("<HHI", data, idx)
78
+
79
+ r0, g0, b0 = unpack_565(color0)
80
+ r1, g1, b1 = unpack_565(color1)
81
+
82
+ # Decode this block into 4x4 pixels
83
+ # Accumulate the results onto our 4 row accumulators
84
+ for j in range(4):
85
+ for i in range(4):
86
+ # get next control op and generate a pixel
87
+
88
+ control = bits & 3
89
+ bits = bits >> 2
90
+
91
+ a = 0xFF
92
+ if control == 0:
93
+ r, g, b = r0, g0, b0
94
+ elif control == 1:
95
+ r, g, b = r1, g1, b1
96
+ elif control == 2:
97
+ if color0 > color1:
98
+ r = (2 * r0 + r1) // 3
99
+ g = (2 * g0 + g1) // 3
100
+ b = (2 * b0 + b1) // 3
101
+ else:
102
+ r = (r0 + r1) // 2
103
+ g = (g0 + g1) // 2
104
+ b = (b0 + b1) // 2
105
+ elif control == 3:
106
+ if color0 > color1:
107
+ r = (2 * r1 + r0) // 3
108
+ g = (2 * g1 + g0) // 3
109
+ b = (2 * b1 + b0) // 3
110
+ else:
111
+ r, g, b, a = 0, 0, 0, 0
112
+
113
+ if alpha:
114
+ ret[j].extend([r, g, b, a])
115
+ else:
116
+ ret[j].extend([r, g, b])
117
+
118
+ return ret
119
+
120
+
121
+ def decode_dxt3(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]:
122
+ """
123
+ input: one "row" of data (i.e. will produce 4*width pixels)
124
+ """
125
+
126
+ blocks = len(data) // 16 # number of blocks in row
127
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
128
+
129
+ for block_index in range(blocks):
130
+ idx = block_index * 16
131
+ block = data[idx : idx + 16]
132
+ # Decode next 16-byte block.
133
+ bits = struct.unpack_from("<8B", block)
134
+ color0, color1 = struct.unpack_from("<HH", block, 8)
135
+
136
+ (code,) = struct.unpack_from("<I", block, 12)
137
+
138
+ r0, g0, b0 = unpack_565(color0)
139
+ r1, g1, b1 = unpack_565(color1)
140
+
141
+ for j in range(4):
142
+ high = False # Do we want the higher bits?
143
+ for i in range(4):
144
+ alphacode_index = (4 * j + i) // 2
145
+ a = bits[alphacode_index]
146
+ if high:
147
+ high = False
148
+ a >>= 4
149
+ else:
150
+ high = True
151
+ a &= 0xF
152
+ a *= 17 # We get a value between 0 and 15
153
+
154
+ color_code = (code >> 2 * (4 * j + i)) & 0x03
155
+
156
+ if color_code == 0:
157
+ r, g, b = r0, g0, b0
158
+ elif color_code == 1:
159
+ r, g, b = r1, g1, b1
160
+ elif color_code == 2:
161
+ r = (2 * r0 + r1) // 3
162
+ g = (2 * g0 + g1) // 3
163
+ b = (2 * b0 + b1) // 3
164
+ elif color_code == 3:
165
+ r = (2 * r1 + r0) // 3
166
+ g = (2 * g1 + g0) // 3
167
+ b = (2 * b1 + b0) // 3
168
+
169
+ ret[j].extend([r, g, b, a])
170
+
171
+ return ret
172
+
173
+
174
+ def decode_dxt5(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]:
175
+ """
176
+ input: one "row" of data (i.e. will produce 4 * width pixels)
177
+ """
178
+
179
+ blocks = len(data) // 16 # number of blocks in row
180
+ ret = (bytearray(), bytearray(), bytearray(), bytearray())
181
+
182
+ for block_index in range(blocks):
183
+ idx = block_index * 16
184
+ block = data[idx : idx + 16]
185
+ # Decode next 16-byte block.
186
+ a0, a1 = struct.unpack_from("<BB", block)
187
+
188
+ bits = struct.unpack_from("<6B", block, 2)
189
+ alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
190
+ alphacode2 = bits[0] | (bits[1] << 8)
191
+
192
+ color0, color1 = struct.unpack_from("<HH", block, 8)
193
+
194
+ (code,) = struct.unpack_from("<I", block, 12)
195
+
196
+ r0, g0, b0 = unpack_565(color0)
197
+ r1, g1, b1 = unpack_565(color1)
198
+
199
+ for j in range(4):
200
+ for i in range(4):
201
+ # get next control op and generate a pixel
202
+ alphacode_index = 3 * (4 * j + i)
203
+
204
+ if alphacode_index <= 12:
205
+ alphacode = (alphacode2 >> alphacode_index) & 0x07
206
+ elif alphacode_index == 15:
207
+ alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
208
+ else: # alphacode_index >= 18 and alphacode_index <= 45
209
+ alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
210
+
211
+ if alphacode == 0:
212
+ a = a0
213
+ elif alphacode == 1:
214
+ a = a1
215
+ elif a0 > a1:
216
+ a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
217
+ elif alphacode == 6:
218
+ a = 0
219
+ elif alphacode == 7:
220
+ a = 255
221
+ else:
222
+ a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
223
+
224
+ color_code = (code >> 2 * (4 * j + i)) & 0x03
225
+
226
+ if color_code == 0:
227
+ r, g, b = r0, g0, b0
228
+ elif color_code == 1:
229
+ r, g, b = r1, g1, b1
230
+ elif color_code == 2:
231
+ r = (2 * r0 + r1) // 3
232
+ g = (2 * g0 + g1) // 3
233
+ b = (2 * b0 + b1) // 3
234
+ elif color_code == 3:
235
+ r = (2 * r1 + r0) // 3
236
+ g = (2 * g1 + g0) // 3
237
+ b = (2 * b1 + b0) // 3
238
+
239
+ ret[j].extend([r, g, b, a])
240
+
241
+ return ret
242
+
243
+
244
+ class BLPFormatError(NotImplementedError):
245
+ pass
246
+
247
+
248
+ def _accept(prefix: bytes) -> bool:
249
+ return prefix.startswith((b"BLP1", b"BLP2"))
250
+
251
+
252
+ class BlpImageFile(ImageFile.ImageFile):
253
+ """
254
+ Blizzard Mipmap Format
255
+ """
256
+
257
+ format = "BLP"
258
+ format_description = "Blizzard Mipmap Format"
259
+
260
+ def _open(self) -> None:
261
+ assert self.fp is not None
262
+ self.magic = self.fp.read(4)
263
+ if not _accept(self.magic):
264
+ msg = f"Bad BLP magic {repr(self.magic)}"
265
+ raise BLPFormatError(msg)
266
+
267
+ compression = struct.unpack("<i", self.fp.read(4))[0]
268
+ if self.magic == b"BLP1":
269
+ alpha = struct.unpack("<I", self.fp.read(4))[0] != 0
270
+ else:
271
+ encoding = struct.unpack("<b", self.fp.read(1))[0]
272
+ alpha = struct.unpack("<b", self.fp.read(1))[0] != 0
273
+ alpha_encoding = struct.unpack("<b", self.fp.read(1))[0]
274
+ self.fp.seek(1, os.SEEK_CUR) # mips
275
+
276
+ self._size = struct.unpack("<II", self.fp.read(8))
277
+
278
+ args: tuple[int, int, bool] | tuple[int, int, bool, int]
279
+ if self.magic == b"BLP1":
280
+ encoding = struct.unpack("<i", self.fp.read(4))[0]
281
+ self.fp.seek(4, os.SEEK_CUR) # subtype
282
+
283
+ args = (compression, encoding, alpha)
284
+ offset = 28
285
+ else:
286
+ args = (compression, encoding, alpha, alpha_encoding)
287
+ offset = 20
288
+
289
+ decoder = self.magic.decode()
290
+
291
+ self._mode = "RGBA" if alpha else "RGB"
292
+ self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, offset, args)]
293
+
294
+
295
+ class _BLPBaseDecoder(abc.ABC, ImageFile.PyDecoder):
296
+ _pulls_fd = True
297
+
298
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
299
+ try:
300
+ self._read_header()
301
+ self._load()
302
+ except struct.error as e:
303
+ msg = "Truncated BLP file"
304
+ raise OSError(msg) from e
305
+ return -1, 0
306
+
307
+ @abc.abstractmethod
308
+ def _load(self) -> None:
309
+ pass
310
+
311
+ def _read_header(self) -> None:
312
+ self._offsets = struct.unpack("<16I", self._safe_read(16 * 4))
313
+ self._lengths = struct.unpack("<16I", self._safe_read(16 * 4))
314
+
315
+ def _safe_read(self, length: int) -> bytes:
316
+ assert self.fd is not None
317
+ return ImageFile._safe_read(self.fd, length)
318
+
319
+ def _read_palette(self) -> list[tuple[int, int, int, int]]:
320
+ ret = []
321
+ for i in range(256):
322
+ try:
323
+ b, g, r, a = struct.unpack("<4B", self._safe_read(4))
324
+ except struct.error:
325
+ break
326
+ ret.append((b, g, r, a))
327
+ return ret
328
+
329
+ def _read_bgra(
330
+ self, palette: list[tuple[int, int, int, int]], alpha: bool
331
+ ) -> bytearray:
332
+ data = bytearray()
333
+ _data = BytesIO(self._safe_read(self._lengths[0]))
334
+ while True:
335
+ try:
336
+ (offset,) = struct.unpack("<B", _data.read(1))
337
+ except struct.error:
338
+ break
339
+ b, g, r, a = palette[offset]
340
+ d: tuple[int, ...] = (r, g, b)
341
+ if alpha:
342
+ d += (a,)
343
+ data.extend(d)
344
+ return data
345
+
346
+
347
+ class BLP1Decoder(_BLPBaseDecoder):
348
+ def _load(self) -> None:
349
+ self._compression, self._encoding, alpha = self.args
350
+
351
+ if self._compression == Format.JPEG:
352
+ self._decode_jpeg_stream()
353
+
354
+ elif self._compression == 1:
355
+ if self._encoding in (4, 5):
356
+ palette = self._read_palette()
357
+ data = self._read_bgra(palette, alpha)
358
+ self.set_as_raw(data)
359
+ else:
360
+ msg = f"Unsupported BLP encoding {repr(self._encoding)}"
361
+ raise BLPFormatError(msg)
362
+ else:
363
+ msg = f"Unsupported BLP compression {repr(self._encoding)}"
364
+ raise BLPFormatError(msg)
365
+
366
+ def _decode_jpeg_stream(self) -> None:
367
+ from .JpegImagePlugin import JpegImageFile
368
+
369
+ (jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
370
+ jpeg_header = self._safe_read(jpeg_header_size)
371
+ assert self.fd is not None
372
+ self._safe_read(self._offsets[0] - self.fd.tell()) # What IS this?
373
+ data = self._safe_read(self._lengths[0])
374
+ data = jpeg_header + data
375
+ image = JpegImageFile(BytesIO(data))
376
+ Image._decompression_bomb_check(image.size)
377
+ if image.mode == "CMYK":
378
+ args = image.tile[0].args
379
+ assert isinstance(args, tuple)
380
+ image.tile = [image.tile[0]._replace(args=(args[0], "CMYK"))]
381
+ self.set_as_raw(image.convert("RGB").tobytes(), "BGR")
382
+
383
+
384
+ class BLP2Decoder(_BLPBaseDecoder):
385
+ def _load(self) -> None:
386
+ self._compression, self._encoding, alpha, self._alpha_encoding = self.args
387
+
388
+ palette = self._read_palette()
389
+
390
+ assert self.fd is not None
391
+ self.fd.seek(self._offsets[0])
392
+
393
+ if self._compression == 1:
394
+ # Uncompressed or DirectX compression
395
+
396
+ if self._encoding == Encoding.UNCOMPRESSED:
397
+ data = self._read_bgra(palette, alpha)
398
+
399
+ elif self._encoding == Encoding.DXT:
400
+ data = bytearray()
401
+ if self._alpha_encoding == AlphaEncoding.DXT1:
402
+ linesize = (self.state.xsize + 3) // 4 * 8
403
+ for yb in range((self.state.ysize + 3) // 4):
404
+ for d in decode_dxt1(self._safe_read(linesize), alpha):
405
+ data += d
406
+
407
+ elif self._alpha_encoding == AlphaEncoding.DXT3:
408
+ linesize = (self.state.xsize + 3) // 4 * 16
409
+ for yb in range((self.state.ysize + 3) // 4):
410
+ for d in decode_dxt3(self._safe_read(linesize)):
411
+ data += d
412
+
413
+ elif self._alpha_encoding == AlphaEncoding.DXT5:
414
+ linesize = (self.state.xsize + 3) // 4 * 16
415
+ for yb in range((self.state.ysize + 3) // 4):
416
+ for d in decode_dxt5(self._safe_read(linesize)):
417
+ data += d
418
+ else:
419
+ msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}"
420
+ raise BLPFormatError(msg)
421
+ else:
422
+ msg = f"Unknown BLP encoding {repr(self._encoding)}"
423
+ raise BLPFormatError(msg)
424
+
425
+ else:
426
+ msg = f"Unknown BLP compression {repr(self._compression)}"
427
+ raise BLPFormatError(msg)
428
+
429
+ self.set_as_raw(data)
430
+
431
+
432
+ class BLPEncoder(ImageFile.PyEncoder):
433
+ _pushes_fd = True
434
+
435
+ def _write_palette(self) -> bytes:
436
+ data = b""
437
+ assert self.im is not None
438
+ palette = self.im.getpalette("RGBA", "RGBA")
439
+ for i in range(len(palette) // 4):
440
+ r, g, b, a = palette[i * 4 : (i + 1) * 4]
441
+ data += struct.pack("<4B", b, g, r, a)
442
+ while len(data) < 256 * 4:
443
+ data += b"\x00" * 4
444
+ return data
445
+
446
+ def encode(self, bufsize: int) -> tuple[int, int, bytes]:
447
+ palette_data = self._write_palette()
448
+
449
+ offset = 20 + 16 * 4 * 2 + len(palette_data)
450
+ data = struct.pack("<16I", offset, *((0,) * 15))
451
+
452
+ assert self.im is not None
453
+ w, h = self.im.size
454
+ data += struct.pack("<16I", w * h, *((0,) * 15))
455
+
456
+ data += palette_data
457
+
458
+ for y in range(h):
459
+ for x in range(w):
460
+ data += struct.pack("<B", self.im.getpixel((x, y)))
461
+
462
+ return len(data), 0, data
463
+
464
+
465
+ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
466
+ if im.mode != "P":
467
+ msg = "Unsupported BLP image mode"
468
+ raise ValueError(msg)
469
+
470
+ magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
471
+ fp.write(magic)
472
+
473
+ assert im.palette is not None
474
+ fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
475
+
476
+ alpha_depth = 1 if im.palette.mode == "RGBA" else 0
477
+ if magic == b"BLP1":
478
+ fp.write(struct.pack("<L", alpha_depth))
479
+ else:
480
+ fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
481
+ fp.write(struct.pack("<b", alpha_depth))
482
+ fp.write(struct.pack("<b", 0)) # alpha encoding
483
+ fp.write(struct.pack("<b", 0)) # mips
484
+ fp.write(struct.pack("<II", *im.size))
485
+ if magic == b"BLP1":
486
+ fp.write(struct.pack("<i", 5))
487
+ fp.write(struct.pack("<i", 0))
488
+
489
+ ImageFile._save(im, fp, [ImageFile._Tile("BLP", (0, 0) + im.size, 0, im.mode)])
490
+
491
+
492
+ Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
493
+ Image.register_extension(BlpImageFile.format, ".blp")
494
+ Image.register_decoder("BLP1", BLP1Decoder)
495
+ Image.register_decoder("BLP2", BLP2Decoder)
496
+
497
+ Image.register_save(BlpImageFile.format, _save)
498
+ Image.register_encoder("BLP", BLPEncoder)
.venv/lib/python3.14/site-packages/PIL/BmpImagePlugin.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from typing import IO, Any
29
+
30
+ from . import Image, ImageFile, ImagePalette
31
+ from ._binary import i16le as i16
32
+ from ._binary import i32le as i32
33
+ from ._binary import o8
34
+ from ._binary import o16le as o16
35
+ from ._binary import o32le as o32
36
+
37
+ #
38
+ # --------------------------------------------------------------------
39
+ # Read BMP file
40
+
41
+ BIT2MODE = {
42
+ # bits => mode, rawmode
43
+ 1: ("P", "P;1"),
44
+ 4: ("P", "P;4"),
45
+ 8: ("P", "P"),
46
+ 16: ("RGB", "BGR;15"),
47
+ 24: ("RGB", "BGR"),
48
+ 32: ("RGB", "BGRX"),
49
+ }
50
+
51
+ USE_RAW_ALPHA = False
52
+
53
+
54
+ def _accept(prefix: bytes) -> bool:
55
+ return prefix.startswith(b"BM")
56
+
57
+
58
+ def _dib_accept(prefix: bytes) -> bool:
59
+ return i32(prefix) in [12, 40, 52, 56, 64, 108, 124]
60
+
61
+
62
+ # =============================================================================
63
+ # Image plugin for the Windows BMP format.
64
+ # =============================================================================
65
+ class BmpImageFile(ImageFile.ImageFile):
66
+ """Image plugin for the Windows Bitmap format (BMP)"""
67
+
68
+ # ------------------------------------------------------------- Description
69
+ format_description = "Windows Bitmap"
70
+ format = "BMP"
71
+
72
+ # -------------------------------------------------- BMP Compression values
73
+ COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
74
+ for k, v in COMPRESSIONS.items():
75
+ vars()[k] = v
76
+
77
+ def _bitmap(self, header: int = 0, offset: int = 0) -> None:
78
+ """Read relevant info about the BMP"""
79
+ assert self.fp is not None
80
+ read, seek = self.fp.read, self.fp.seek
81
+ if header:
82
+ seek(header)
83
+ # read bmp header size @offset 14 (this is part of the header size)
84
+ file_info: dict[str, bool | int | tuple[int, ...]] = {
85
+ "header_size": i32(read(4)),
86
+ "direction": -1,
87
+ }
88
+
89
+ # -------------------- If requested, read header at a specific position
90
+ # read the rest of the bmp header, without its size
91
+ assert isinstance(file_info["header_size"], int)
92
+ header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
93
+
94
+ # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
95
+ # ----- This format has different offsets because of width/height types
96
+ # 12: BITMAPCOREHEADER/OS21XBITMAPHEADER
97
+ if file_info["header_size"] == 12:
98
+ file_info["width"] = i16(header_data, 0)
99
+ file_info["height"] = i16(header_data, 2)
100
+ file_info["planes"] = i16(header_data, 4)
101
+ file_info["bits"] = i16(header_data, 6)
102
+ file_info["compression"] = self.COMPRESSIONS["RAW"]
103
+ file_info["palette_padding"] = 3
104
+
105
+ # --------------------------------------------- Windows Bitmap v3 to v5
106
+ # 40: BITMAPINFOHEADER
107
+ # 52: BITMAPV2HEADER
108
+ # 56: BITMAPV3HEADER
109
+ # 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER
110
+ # 108: BITMAPV4HEADER
111
+ # 124: BITMAPV5HEADER
112
+ elif file_info["header_size"] in (40, 52, 56, 64, 108, 124):
113
+ file_info["y_flip"] = header_data[7] == 0xFF
114
+ file_info["direction"] = 1 if file_info["y_flip"] else -1
115
+ file_info["width"] = i32(header_data, 0)
116
+ file_info["height"] = (
117
+ i32(header_data, 4)
118
+ if not file_info["y_flip"]
119
+ else 2**32 - i32(header_data, 4)
120
+ )
121
+ file_info["planes"] = i16(header_data, 8)
122
+ file_info["bits"] = i16(header_data, 10)
123
+ file_info["compression"] = i32(header_data, 12)
124
+ # byte size of pixel data
125
+ file_info["data_size"] = i32(header_data, 16)
126
+ file_info["pixels_per_meter"] = (
127
+ i32(header_data, 20),
128
+ i32(header_data, 24),
129
+ )
130
+ file_info["colors"] = i32(header_data, 28)
131
+ file_info["palette_padding"] = 4
132
+ assert isinstance(file_info["pixels_per_meter"], tuple)
133
+ self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
134
+ if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
135
+ masks = ["r_mask", "g_mask", "b_mask"]
136
+ if len(header_data) >= 48:
137
+ if len(header_data) >= 52:
138
+ masks.append("a_mask")
139
+ else:
140
+ file_info["a_mask"] = 0x0
141
+ for idx, mask in enumerate(masks):
142
+ file_info[mask] = i32(header_data, 36 + idx * 4)
143
+ else:
144
+ # 40 byte headers only have the three components in the
145
+ # bitfields masks, ref:
146
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
147
+ # See also
148
+ # https://github.com/python-pillow/Pillow/issues/1293
149
+ # There is a 4th component in the RGBQuad, in the alpha
150
+ # location, but it is listed as a reserved component,
151
+ # and it is not generally an alpha channel
152
+ file_info["a_mask"] = 0x0
153
+ for mask in masks:
154
+ file_info[mask] = i32(read(4))
155
+ assert isinstance(file_info["r_mask"], int)
156
+ assert isinstance(file_info["g_mask"], int)
157
+ assert isinstance(file_info["b_mask"], int)
158
+ assert isinstance(file_info["a_mask"], int)
159
+ file_info["rgb_mask"] = (
160
+ file_info["r_mask"],
161
+ file_info["g_mask"],
162
+ file_info["b_mask"],
163
+ )
164
+ file_info["rgba_mask"] = (
165
+ file_info["r_mask"],
166
+ file_info["g_mask"],
167
+ file_info["b_mask"],
168
+ file_info["a_mask"],
169
+ )
170
+ else:
171
+ msg = f"Unsupported BMP header type ({file_info['header_size']})"
172
+ raise OSError(msg)
173
+
174
+ # ------------------ Special case : header is reported 40, which
175
+ # ---------------------- is shorter than real size for bpp >= 16
176
+ assert isinstance(file_info["width"], int)
177
+ assert isinstance(file_info["height"], int)
178
+ self._size = file_info["width"], file_info["height"]
179
+
180
+ # ------- If color count was not found in the header, compute from bits
181
+ assert isinstance(file_info["bits"], int)
182
+ if not file_info.get("colors", 0):
183
+ file_info["colors"] = 1 << file_info["bits"]
184
+ assert isinstance(file_info["palette_padding"], int)
185
+ assert isinstance(file_info["colors"], int)
186
+ if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
187
+ offset += file_info["palette_padding"] * file_info["colors"]
188
+
189
+ # ---------------------- Check bit depth for unusual unsupported values
190
+ self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", ""))
191
+ if not self.mode:
192
+ msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
193
+ raise OSError(msg)
194
+
195
+ # ---------------- Process BMP with Bitfields compression (not palette)
196
+ decoder_name = "raw"
197
+ if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
198
+ SUPPORTED: dict[int, list[tuple[int, ...]]] = {
199
+ 32: [
200
+ (0xFF0000, 0xFF00, 0xFF, 0x0),
201
+ (0xFF000000, 0xFF0000, 0xFF00, 0x0),
202
+ (0xFF000000, 0xFF00, 0xFF, 0x0),
203
+ (0xFF000000, 0xFF0000, 0xFF00, 0xFF),
204
+ (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
205
+ (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
206
+ (0xFF000000, 0xFF00, 0xFF, 0xFF0000),
207
+ (0x0, 0x0, 0x0, 0x0),
208
+ ],
209
+ 24: [(0xFF0000, 0xFF00, 0xFF)],
210
+ 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
211
+ }
212
+ MASK_MODES = {
213
+ (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
214
+ (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
215
+ (32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR",
216
+ (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
217
+ (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
218
+ (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
219
+ (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR",
220
+ (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
221
+ (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
222
+ (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
223
+ (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
224
+ }
225
+ if file_info["bits"] in SUPPORTED:
226
+ if (
227
+ file_info["bits"] == 32
228
+ and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
229
+ ):
230
+ assert isinstance(file_info["rgba_mask"], tuple)
231
+ raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
232
+ self._mode = "RGBA" if "A" in raw_mode else self.mode
233
+ elif (
234
+ file_info["bits"] in (24, 16)
235
+ and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
236
+ ):
237
+ assert isinstance(file_info["rgb_mask"], tuple)
238
+ raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
239
+ else:
240
+ msg = "Unsupported BMP bitfields layout"
241
+ raise OSError(msg)
242
+ else:
243
+ msg = "Unsupported BMP bitfields layout"
244
+ raise OSError(msg)
245
+ elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
246
+ if file_info["bits"] == 32 and (
247
+ header == 22 or USE_RAW_ALPHA # 32-bit .cur offset
248
+ ):
249
+ raw_mode, self._mode = "BGRA", "RGBA"
250
+ elif file_info["compression"] in (
251
+ self.COMPRESSIONS["RLE8"],
252
+ self.COMPRESSIONS["RLE4"],
253
+ ):
254
+ decoder_name = "bmp_rle"
255
+ else:
256
+ msg = f"Unsupported BMP compression ({file_info['compression']})"
257
+ raise OSError(msg)
258
+
259
+ # --------------- Once the header is processed, process the palette/LUT
260
+ if self.mode == "P": # Paletted for 1, 4 and 8 bit images
261
+ # ---------------------------------------------------- 1-bit images
262
+ if not (0 < file_info["colors"] <= 65536):
263
+ msg = f"Unsupported BMP Palette size ({file_info['colors']})"
264
+ raise OSError(msg)
265
+ else:
266
+ padding = file_info["palette_padding"]
267
+ palette = read(padding * file_info["colors"])
268
+ grayscale = True
269
+ indices = (
270
+ (0, 255)
271
+ if file_info["colors"] == 2
272
+ else list(range(file_info["colors"]))
273
+ )
274
+
275
+ # ----------------- Check if grayscale and ignore palette if so
276
+ for ind, val in enumerate(indices):
277
+ rgb = palette[ind * padding : ind * padding + 3]
278
+ if rgb != o8(val) * 3:
279
+ grayscale = False
280
+
281
+ # ------- If all colors are gray, white or black, ditch palette
282
+ if grayscale:
283
+ self._mode = "1" if file_info["colors"] == 2 else "L"
284
+ raw_mode = self.mode
285
+ else:
286
+ self._mode = "P"
287
+ self.palette = ImagePalette.raw(
288
+ "BGRX" if padding == 4 else "BGR", palette
289
+ )
290
+
291
+ # ---------------------------- Finally set the tile data for the plugin
292
+ self.info["compression"] = file_info["compression"]
293
+ args: list[Any] = [raw_mode]
294
+ if decoder_name == "bmp_rle":
295
+ args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"])
296
+ else:
297
+ assert isinstance(file_info["width"], int)
298
+ args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
299
+ args.append(file_info["direction"])
300
+ self.tile = [
301
+ ImageFile._Tile(
302
+ decoder_name,
303
+ (0, 0, file_info["width"], file_info["height"]),
304
+ offset or self.fp.tell(),
305
+ tuple(args),
306
+ )
307
+ ]
308
+
309
+ def _open(self) -> None:
310
+ """Open file, check magic number and read header"""
311
+ # read 14 bytes: magic number, filesize, reserved, header final offset
312
+ assert self.fp is not None
313
+ head_data = self.fp.read(14)
314
+ # choke if the file does not have the required magic bytes
315
+ if not _accept(head_data):
316
+ msg = "Not a BMP file"
317
+ raise SyntaxError(msg)
318
+ # read the start position of the BMP image data (u32)
319
+ offset = i32(head_data, 10)
320
+ # load bitmap information (offset=raster info)
321
+ self._bitmap(offset=offset)
322
+
323
+
324
+ class BmpRleDecoder(ImageFile.PyDecoder):
325
+ _pulls_fd = True
326
+
327
+ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
328
+ assert self.fd is not None
329
+ rle4 = self.args[1]
330
+ data = bytearray()
331
+ x = 0
332
+ dest_length = self.state.xsize * self.state.ysize
333
+ while len(data) < dest_length:
334
+ pixels = self.fd.read(1)
335
+ byte = self.fd.read(1)
336
+ if not pixels or not byte:
337
+ break
338
+ num_pixels = pixels[0]
339
+ if num_pixels:
340
+ # encoded mode
341
+ if x + num_pixels > self.state.xsize:
342
+ # Too much data for row
343
+ num_pixels = max(0, self.state.xsize - x)
344
+ if rle4:
345
+ first_pixel = o8(byte[0] >> 4)
346
+ second_pixel = o8(byte[0] & 0x0F)
347
+ for index in range(num_pixels):
348
+ if index % 2 == 0:
349
+ data += first_pixel
350
+ else:
351
+ data += second_pixel
352
+ else:
353
+ data += byte * num_pixels
354
+ x += num_pixels
355
+ else:
356
+ if byte[0] == 0:
357
+ # end of line
358
+ while len(data) % self.state.xsize != 0:
359
+ data += b"\x00"
360
+ x = 0
361
+ elif byte[0] == 1:
362
+ # end of bitmap
363
+ break
364
+ elif byte[0] == 2:
365
+ # delta
366
+ bytes_read = self.fd.read(2)
367
+ if len(bytes_read) < 2:
368
+ break
369
+ right, up = bytes_read
370
+ data += b"\x00" * (right + up * self.state.xsize)
371
+ x = len(data) % self.state.xsize
372
+ else:
373
+ # absolute mode
374
+ if rle4:
375
+ # 2 pixels per byte
376
+ byte_count = byte[0] // 2
377
+ bytes_read = self.fd.read(byte_count)
378
+ for byte_read in bytes_read:
379
+ data += o8(byte_read >> 4)
380
+ data += o8(byte_read & 0x0F)
381
+ else:
382
+ byte_count = byte[0]
383
+ bytes_read = self.fd.read(byte_count)
384
+ data += bytes_read
385
+ if len(bytes_read) < byte_count:
386
+ break
387
+ x += byte[0]
388
+
389
+ # align to 16-bit word boundary
390
+ if self.fd.tell() % 2 != 0:
391
+ self.fd.seek(1, os.SEEK_CUR)
392
+ rawmode = "L" if self.mode == "L" else "P"
393
+ self.set_as_raw(bytes(data), rawmode, (0, self.args[-1]))
394
+ return -1, 0
395
+
396
+
397
+ # =============================================================================
398
+ # Image plugin for the DIB format (BMP alias)
399
+ # =============================================================================
400
+ class DibImageFile(BmpImageFile):
401
+ format = "DIB"
402
+ format_description = "Windows Bitmap"
403
+
404
+ def _open(self) -> None:
405
+ self._bitmap()
406
+
407
+
408
+ #
409
+ # --------------------------------------------------------------------
410
+ # Write BMP file
411
+
412
+
413
+ SAVE = {
414
+ "1": ("1", 1, 2),
415
+ "L": ("L", 8, 256),
416
+ "P": ("P", 8, 256),
417
+ "RGB": ("BGR", 24, 0),
418
+ "RGBA": ("BGRA", 32, 0),
419
+ }
420
+
421
+
422
+ def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
423
+ _save(im, fp, filename, False)
424
+
425
+
426
+ def _save(
427
+ im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True
428
+ ) -> None:
429
+ try:
430
+ rawmode, bits, colors = SAVE[im.mode]
431
+ except KeyError as e:
432
+ msg = f"cannot write mode {im.mode} as BMP"
433
+ raise OSError(msg) from e
434
+
435
+ info = im.encoderinfo
436
+
437
+ dpi = info.get("dpi", (96, 96))
438
+
439
+ # 1 meter == 39.3701 inches
440
+ ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
441
+
442
+ stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
443
+ header = 40 # or 64 for OS/2 version 2
444
+ image = stride * im.size[1]
445
+
446
+ if im.mode == "1":
447
+ palette = b"".join(o8(i) * 3 + b"\x00" for i in (0, 255))
448
+ elif im.mode == "L":
449
+ palette = b"".join(o8(i) * 3 + b"\x00" for i in range(256))
450
+ elif im.mode == "P":
451
+ palette = im.im.getpalette("RGB", "BGRX")
452
+ colors = len(palette) // 4
453
+ else:
454
+ palette = None
455
+
456
+ # bitmap header
457
+ if bitmap_header:
458
+ offset = 14 + header + colors * 4
459
+ file_size = offset + image
460
+ if file_size > 2**32 - 1:
461
+ msg = "File size is too large for the BMP format"
462
+ raise ValueError(msg)
463
+ fp.write(
464
+ b"BM" # file type (magic)
465
+ + o32(file_size) # file size
466
+ + o32(0) # reserved
467
+ + o32(offset) # image data offset
468
+ )
469
+
470
+ # bitmap info header
471
+ fp.write(
472
+ o32(header) # info header size
473
+ + o32(im.size[0]) # width
474
+ + o32(im.size[1]) # height
475
+ + o16(1) # planes
476
+ + o16(bits) # depth
477
+ + o32(0) # compression (0=uncompressed)
478
+ + o32(image) # size of bitmap
479
+ + o32(ppm[0]) # resolution
480
+ + o32(ppm[1]) # resolution
481
+ + o32(colors) # colors used
482
+ + o32(colors) # colors important
483
+ )
484
+
485
+ fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
486
+
487
+ if palette:
488
+ fp.write(palette)
489
+
490
+ ImageFile._save(
491
+ im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))]
492
+ )
493
+
494
+
495
+ #
496
+ # --------------------------------------------------------------------
497
+ # Registry
498
+
499
+
500
+ Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
501
+ Image.register_save(BmpImageFile.format, _save)
502
+
503
+ Image.register_extension(BmpImageFile.format, ".bmp")
504
+
505
+ Image.register_mime(BmpImageFile.format, "image/bmp")
506
+
507
+ Image.register_decoder("bmp_rle", BmpRleDecoder)
508
+
509
+ Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
510
+ Image.register_save(DibImageFile.format, _dib_save)
511
+
512
+ Image.register_extension(DibImageFile.format, ".dib")
513
+
514
+ Image.register_mime(DibImageFile.format, "image/bmp")
.venv/lib/python3.14/site-packages/PIL/BufrStubImagePlugin.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import os
14
+ from typing import IO
15
+
16
+ from . import Image, ImageFile
17
+
18
+ _handler = None
19
+
20
+
21
+ def register_handler(handler: ImageFile.StubHandler | None) -> None:
22
+ """
23
+ Install application-specific BUFR image handler.
24
+
25
+ :param handler: Handler object.
26
+ """
27
+ global _handler
28
+ _handler = handler
29
+
30
+
31
+ # --------------------------------------------------------------------
32
+ # Image adapter
33
+
34
+
35
+ def _accept(prefix: bytes) -> bool:
36
+ return prefix.startswith((b"BUFR", b"ZCZC"))
37
+
38
+
39
+ class BufrStubImageFile(ImageFile.StubImageFile):
40
+ format = "BUFR"
41
+ format_description = "BUFR"
42
+
43
+ def _open(self) -> None:
44
+ assert self.fp is not None
45
+ if not _accept(self.fp.read(4)):
46
+ msg = "Not a BUFR file"
47
+ raise SyntaxError(msg)
48
+
49
+ self.fp.seek(-4, os.SEEK_CUR)
50
+
51
+ # make something up
52
+ self._mode = "F"
53
+ self._size = 1, 1
54
+
55
+ def _load(self) -> ImageFile.StubHandler | None:
56
+ return _handler
57
+
58
+
59
+ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
60
+ if _handler is None or not hasattr(_handler, "save"):
61
+ msg = "BUFR save handler not installed"
62
+ raise OSError(msg)
63
+ _handler.save(im, fp, filename)
64
+
65
+
66
+ # --------------------------------------------------------------------
67
+ # Registry
68
+
69
+ Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
70
+ Image.register_save(BufrStubImageFile.format, _save)
71
+
72
+ Image.register_extension(BufrStubImageFile.format, ".bufr")
.venv/lib/python3.14/site-packages/PIL/ContainerIO.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 collections.abc import Iterable
20
+ from typing import IO, AnyStr, NoReturn
21
+
22
+
23
+ class ContainerIO(IO[AnyStr]):
24
+ """
25
+ A file object that provides read access to a part of an existing
26
+ file (for example a TAR file).
27
+ """
28
+
29
+ def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
30
+ """
31
+ Create file object.
32
+
33
+ :param file: Existing file.
34
+ :param offset: Start of region, in bytes.
35
+ :param length: Size of region, in bytes.
36
+ """
37
+ self.fh: IO[AnyStr] = file
38
+ self.pos = 0
39
+ self.offset = offset
40
+ self.length = length
41
+ self.fh.seek(offset)
42
+
43
+ ##
44
+ # Always false.
45
+
46
+ def isatty(self) -> bool:
47
+ return False
48
+
49
+ def seekable(self) -> bool:
50
+ return True
51
+
52
+ def seek(self, offset: int, mode: int = io.SEEK_SET) -> int:
53
+ """
54
+ Move file pointer.
55
+
56
+ :param offset: Offset in bytes.
57
+ :param mode: Starting position. Use 0 for beginning of region, 1
58
+ for current offset, and 2 for end of region. You cannot move
59
+ the pointer outside the defined region.
60
+ :returns: Offset from start of region, in bytes.
61
+ """
62
+ if mode == 1:
63
+ self.pos = self.pos + offset
64
+ elif mode == 2:
65
+ self.pos = self.length + offset
66
+ else:
67
+ self.pos = offset
68
+ # clamp
69
+ self.pos = max(0, min(self.pos, self.length))
70
+ self.fh.seek(self.offset + self.pos)
71
+ return self.pos
72
+
73
+ def tell(self) -> int:
74
+ """
75
+ Get current file pointer.
76
+
77
+ :returns: Offset from start of region, in bytes.
78
+ """
79
+ return self.pos
80
+
81
+ def readable(self) -> bool:
82
+ return True
83
+
84
+ def read(self, n: int = -1) -> AnyStr:
85
+ """
86
+ Read data.
87
+
88
+ :param n: Number of bytes to read. If omitted, zero or negative,
89
+ read until end of region.
90
+ :returns: An 8-bit string.
91
+ """
92
+ if n > 0:
93
+ n = min(n, self.length - self.pos)
94
+ else:
95
+ n = self.length - self.pos
96
+ if n <= 0: # EOF
97
+ return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
98
+ self.pos = self.pos + n
99
+ return self.fh.read(n)
100
+
101
+ def readline(self, n: int = -1) -> AnyStr:
102
+ """
103
+ Read a line of text.
104
+
105
+ :param n: Number of bytes to read. If omitted, zero or negative,
106
+ read until end of line.
107
+ :returns: An 8-bit string.
108
+ """
109
+ s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
110
+ newline_character = b"\n" if "b" in self.fh.mode else "\n"
111
+ while True:
112
+ c = self.read(1)
113
+ if not c:
114
+ break
115
+ s = s + c
116
+ if c == newline_character or len(s) == n:
117
+ break
118
+ return s
119
+
120
+ def readlines(self, n: int | None = -1) -> list[AnyStr]:
121
+ """
122
+ Read multiple lines of text.
123
+
124
+ :param n: Number of lines to read. If omitted, zero, negative or None,
125
+ read until end of region.
126
+ :returns: A list of 8-bit strings.
127
+ """
128
+ lines = []
129
+ while True:
130
+ s = self.readline()
131
+ if not s:
132
+ break
133
+ lines.append(s)
134
+ if len(lines) == n:
135
+ break
136
+ return lines
137
+
138
+ def writable(self) -> bool:
139
+ return False
140
+
141
+ def write(self, b: AnyStr) -> NoReturn:
142
+ raise NotImplementedError()
143
+
144
+ def writelines(self, lines: Iterable[AnyStr]) -> NoReturn:
145
+ raise NotImplementedError()
146
+
147
+ def truncate(self, size: int | None = None) -> int:
148
+ raise NotImplementedError()
149
+
150
+ def __enter__(self) -> ContainerIO[AnyStr]:
151
+ return self
152
+
153
+ def __exit__(self, *args: object) -> None:
154
+ self.close()
155
+
156
+ def __iter__(self) -> ContainerIO[AnyStr]:
157
+ return self
158
+
159
+ def __next__(self) -> AnyStr:
160
+ line = self.readline()
161
+ if not line:
162
+ msg = "end of region"
163
+ raise StopIteration(msg)
164
+ return line
165
+
166
+ def fileno(self) -> int:
167
+ return self.fh.fileno()
168
+
169
+ def flush(self) -> None:
170
+ self.fh.flush()
171
+
172
+ def close(self) -> None:
173
+ self.fh.close()
.venv/lib/python3.14/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: bytes) -> bool:
29
+ return prefix.startswith(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) -> None:
41
+ assert self.fp is not None
42
+ offset = self.fp.tell()
43
+
44
+ # check magic
45
+ s = self.fp.read(6)
46
+ if not _accept(s):
47
+ msg = "not a CUR file"
48
+ raise SyntaxError(msg)
49
+
50
+ # pick the largest cursor in the file
51
+ m = b""
52
+ for i in range(i16(s, 4)):
53
+ s = self.fp.read(16)
54
+ if not m:
55
+ m = s
56
+ elif s[0] > m[0] and s[1] > m[1]:
57
+ m = s
58
+ if not m:
59
+ msg = "No cursors were found"
60
+ raise TypeError(msg)
61
+
62
+ # load as bitmap
63
+ self._bitmap(i32(m, 12) + offset)
64
+
65
+ # patch up the bitmap height
66
+ self._size = self.size[0], self.size[1] // 2
67
+ self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)]
68
+
69
+
70
+ #
71
+ # --------------------------------------------------------------------
72
+
73
+ Image.register_open(CurImageFile.format, CurImageFile, _accept)
74
+
75
+ Image.register_extension(CurImageFile.format, ".cur")
.venv/lib/python3.14/site-packages/PIL/DcxImagePlugin.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ._util import DeferredError
28
+ from .PcxImagePlugin import PcxImageFile
29
+
30
+ MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
31
+
32
+
33
+ def _accept(prefix: bytes) -> bool:
34
+ return len(prefix) >= 4 and i32(prefix) == MAGIC
35
+
36
+
37
+ ##
38
+ # Image plugin for the Intel DCX format.
39
+
40
+
41
+ class DcxImageFile(PcxImageFile):
42
+ format = "DCX"
43
+ format_description = "Intel DCX"
44
+ _close_exclusive_fp_after_loading = False
45
+
46
+ def _open(self) -> None:
47
+ # Header
48
+ assert self.fp is not None
49
+ s = self.fp.read(4)
50
+ if not _accept(s):
51
+ msg = "not a DCX file"
52
+ raise SyntaxError(msg)
53
+
54
+ # Component directory
55
+ self._offset = []
56
+ for i in range(1024):
57
+ offset = i32(self.fp.read(4))
58
+ if not offset:
59
+ break
60
+ self._offset.append(offset)
61
+
62
+ self._fp = self.fp
63
+ self.frame = -1
64
+ self.n_frames = len(self._offset)
65
+ self.is_animated = self.n_frames > 1
66
+ self.seek(0)
67
+
68
+ def seek(self, frame: int) -> None:
69
+ if not self._seek_check(frame):
70
+ return
71
+ if isinstance(self._fp, DeferredError):
72
+ raise self._fp.ex
73
+ self.frame = frame
74
+ self.fp = self._fp
75
+ self.fp.seek(self._offset[frame])
76
+ PcxImageFile._open(self)
77
+
78
+ def tell(self) -> int:
79
+ return self.frame
80
+
81
+
82
+ Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
83
+
84
+ Image.register_extension(DcxImageFile.format, ".dcx")