From ad35c5c44e2f0d2222c11d27ede017fae183f5ec Mon Sep 17 00:00:00 2001 From: Vinay Kumar Mulamatam Date: Sat, 2 Aug 2025 14:57:18 -0400 Subject: [PATCH] Initial commit: Add RunwayTextToImage node with tests and docstring --- .../runway_text2img.cpython-312.pyc | Bin 0 -> 3570 bytes nodes/api_nodes/runway_text2img.py | 82 + ...nway_text2img.cpython-312-pytest-8.4.1.pyc | Bin 0 -> 2498 bytes tests/api_nodes/test_runway_text2img.py | 34 + venv/Lib/site-packages/PIL/AvifImagePlugin.py | 291 + venv/Lib/site-packages/PIL/BdfFontFile.py | 122 + venv/Lib/site-packages/PIL/BlpImagePlugin.py | 497 + venv/Lib/site-packages/PIL/BmpImagePlugin.py | 515 + .../site-packages/PIL/BufrStubImagePlugin.py | 75 + venv/Lib/site-packages/PIL/ContainerIO.py | 173 + venv/Lib/site-packages/PIL/CurImagePlugin.py | 75 + venv/Lib/site-packages/PIL/DcxImagePlugin.py | 83 + venv/Lib/site-packages/PIL/DdsImagePlugin.py | 624 ++ venv/Lib/site-packages/PIL/EpsImagePlugin.py | 476 + venv/Lib/site-packages/PIL/ExifTags.py | 382 + venv/Lib/site-packages/PIL/FitsImagePlugin.py | 152 + venv/Lib/site-packages/PIL/FliImagePlugin.py | 178 + venv/Lib/site-packages/PIL/FontFile.py | 134 + venv/Lib/site-packages/PIL/FpxImagePlugin.py | 257 + venv/Lib/site-packages/PIL/FtexImagePlugin.py | 114 + venv/Lib/site-packages/PIL/GbrImagePlugin.py | 103 + venv/Lib/site-packages/PIL/GdImageFile.py | 102 + venv/Lib/site-packages/PIL/GifImagePlugin.py | 1213 +++ .../Lib/site-packages/PIL/GimpGradientFile.py | 149 + venv/Lib/site-packages/PIL/GimpPaletteFile.py | 72 + .../site-packages/PIL/GribStubImagePlugin.py | 75 + .../site-packages/PIL/Hdf5StubImagePlugin.py | 75 + venv/Lib/site-packages/PIL/IcnsImagePlugin.py | 411 + venv/Lib/site-packages/PIL/IcoImagePlugin.py | 381 + venv/Lib/site-packages/PIL/ImImagePlugin.py | 389 + venv/Lib/site-packages/PIL/Image.py | 4245 ++++++++ venv/Lib/site-packages/PIL/ImageChops.py | 311 + venv/Lib/site-packages/PIL/ImageCms.py | 1123 +++ venv/Lib/site-packages/PIL/ImageColor.py | 320 + venv/Lib/site-packages/PIL/ImageDraw.py | 1232 +++ venv/Lib/site-packages/PIL/ImageDraw2.py | 243 + venv/Lib/site-packages/PIL/ImageEnhance.py | 113 + venv/Lib/site-packages/PIL/ImageFile.py | 922 ++ venv/Lib/site-packages/PIL/ImageFilter.py | 604 ++ venv/Lib/site-packages/PIL/ImageFont.py | 1339 +++ venv/Lib/site-packages/PIL/ImageGrab.py | 196 + venv/Lib/site-packages/PIL/ImageMath.py | 368 + venv/Lib/site-packages/PIL/ImageMode.py | 92 + venv/Lib/site-packages/PIL/ImageMorph.py | 265 + venv/Lib/site-packages/PIL/ImageOps.py | 745 ++ venv/Lib/site-packages/PIL/ImagePalette.py | 286 + venv/Lib/site-packages/PIL/ImagePath.py | 20 + venv/Lib/site-packages/PIL/ImageQt.py | 220 + venv/Lib/site-packages/PIL/ImageSequence.py | 86 + venv/Lib/site-packages/PIL/ImageShow.py | 362 + venv/Lib/site-packages/PIL/ImageStat.py | 160 + venv/Lib/site-packages/PIL/ImageTk.py | 266 + venv/Lib/site-packages/PIL/ImageTransform.py | 136 + venv/Lib/site-packages/PIL/ImageWin.py | 247 + venv/Lib/site-packages/PIL/ImtImagePlugin.py | 103 + venv/Lib/site-packages/PIL/IptcImagePlugin.py | 250 + .../site-packages/PIL/Jpeg2KImagePlugin.py | 442 + venv/Lib/site-packages/PIL/JpegImagePlugin.py | 902 ++ venv/Lib/site-packages/PIL/JpegPresets.py | 242 + .../site-packages/PIL/McIdasImagePlugin.py | 78 + venv/Lib/site-packages/PIL/MicImagePlugin.py | 102 + venv/Lib/site-packages/PIL/MpegImagePlugin.py | 84 + venv/Lib/site-packages/PIL/MpoImagePlugin.py | 202 + venv/Lib/site-packages/PIL/MspImagePlugin.py | 200 + venv/Lib/site-packages/PIL/PSDraw.py | 237 + venv/Lib/site-packages/PIL/PaletteFile.py | 54 + venv/Lib/site-packages/PIL/PalmImagePlugin.py | 217 + venv/Lib/site-packages/PIL/PcdImagePlugin.py | 64 + venv/Lib/site-packages/PIL/PcfFontFile.py | 254 + venv/Lib/site-packages/PIL/PcxImagePlugin.py | 228 + venv/Lib/site-packages/PIL/PdfImagePlugin.py | 311 + venv/Lib/site-packages/PIL/PdfParser.py | 1074 ++ .../Lib/site-packages/PIL/PixarImagePlugin.py | 72 + venv/Lib/site-packages/PIL/PngImagePlugin.py | 1551 +++ venv/Lib/site-packages/PIL/PpmImagePlugin.py | 375 + venv/Lib/site-packages/PIL/PsdImagePlugin.py | 333 + venv/Lib/site-packages/PIL/QoiImagePlugin.py | 234 + venv/Lib/site-packages/PIL/SgiImagePlugin.py | 231 + .../site-packages/PIL/SpiderImagePlugin.py | 331 + venv/Lib/site-packages/PIL/SunImagePlugin.py | 145 + venv/Lib/site-packages/PIL/TarIO.py | 61 + venv/Lib/site-packages/PIL/TgaImagePlugin.py | 264 + venv/Lib/site-packages/PIL/TiffImagePlugin.py | 2339 +++++ venv/Lib/site-packages/PIL/TiffTags.py | 562 ++ venv/Lib/site-packages/PIL/WalImageFile.py | 127 + venv/Lib/site-packages/PIL/WebPImagePlugin.py | 320 + venv/Lib/site-packages/PIL/WmfImagePlugin.py | 186 + .../site-packages/PIL/XVThumbImagePlugin.py | 83 + venv/Lib/site-packages/PIL/XbmImagePlugin.py | 98 + venv/Lib/site-packages/PIL/XpmImagePlugin.py | 157 + venv/Lib/site-packages/PIL/__init__.py | 87 + venv/Lib/site-packages/PIL/__main__.py | 7 + .../AvifImagePlugin.cpython-312.pyc | Bin 0 -> 11497 bytes .../__pycache__/BdfFontFile.cpython-312.pyc | Bin 0 -> 4197 bytes .../BlpImagePlugin.cpython-312.pyc | Bin 0 -> 23978 bytes .../BmpImagePlugin.cpython-312.pyc | Bin 0 -> 18007 bytes .../BufrStubImagePlugin.cpython-312.pyc | Bin 0 -> 2644 bytes .../__pycache__/ContainerIO.cpython-312.pyc | Bin 0 -> 6994 bytes .../CurImagePlugin.cpython-312.pyc | Bin 0 -> 2366 bytes .../DcxImagePlugin.cpython-312.pyc | Bin 0 -> 2864 bytes .../DdsImagePlugin.cpython-312.pyc | Bin 0 -> 24378 bytes .../EpsImagePlugin.cpython-312.pyc | Bin 0 -> 15685 bytes .../PIL/__pycache__/ExifTags.cpython-312.pyc | Bin 0 -> 11535 bytes .../FitsImagePlugin.cpython-312.pyc | Bin 0 -> 6005 bytes .../FliImagePlugin.cpython-312.pyc | Bin 0 -> 6925 bytes .../PIL/__pycache__/FontFile.cpython-312.pyc | Bin 0 -> 4473 bytes .../FpxImagePlugin.cpython-312.pyc | Bin 0 -> 7686 bytes .../FtexImagePlugin.cpython-312.pyc | Bin 0 -> 5285 bytes .../GbrImagePlugin.cpython-312.pyc | Bin 0 -> 3677 bytes .../__pycache__/GdImageFile.cpython-312.pyc | Bin 0 -> 3395 bytes .../GifImagePlugin.cpython-312.pyc | Bin 0 -> 45395 bytes .../GimpGradientFile.cpython-312.pyc | Bin 0 -> 5442 bytes .../GimpPaletteFile.cpython-312.pyc | Bin 0 -> 2692 bytes .../GribStubImagePlugin.cpython-312.pyc | Bin 0 -> 2675 bytes .../Hdf5StubImagePlugin.cpython-312.pyc | Bin 0 -> 2650 bytes .../IcnsImagePlugin.cpython-312.pyc | Bin 0 -> 17279 bytes .../IcoImagePlugin.cpython-312.pyc | Bin 0 -> 15241 bytes .../__pycache__/ImImagePlugin.cpython-312.pyc | Bin 0 -> 13046 bytes .../PIL/__pycache__/Image.cpython-312.pyc | Bin 0 -> 175382 bytes .../__pycache__/ImageChops.cpython-312.pyc | Bin 0 -> 11214 bytes .../PIL/__pycache__/ImageCms.cpython-312.pyc | Bin 0 -> 43695 bytes .../__pycache__/ImageColor.cpython-312.pyc | Bin 0 -> 12463 bytes .../PIL/__pycache__/ImageDraw.cpython-312.pyc | Bin 0 -> 43594 bytes .../__pycache__/ImageDraw2.cpython-312.pyc | Bin 0 -> 9771 bytes .../__pycache__/ImageEnhance.cpython-312.pyc | Bin 0 -> 5376 bytes .../PIL/__pycache__/ImageFile.cpython-312.pyc | Bin 0 -> 35915 bytes .../__pycache__/ImageFilter.cpython-312.pyc | Bin 0 -> 23080 bytes .../PIL/__pycache__/ImageFont.cpython-312.pyc | Bin 0 -> 69116 bytes .../PIL/__pycache__/ImageGrab.cpython-312.pyc | Bin 0 -> 6963 bytes .../PIL/__pycache__/ImageMath.cpython-312.pyc | Bin 0 -> 16170 bytes .../PIL/__pycache__/ImageMode.cpython-312.pyc | Bin 0 -> 2788 bytes .../__pycache__/ImageMorph.cpython-312.pyc | Bin 0 -> 11315 bytes .../PIL/__pycache__/ImageOps.cpython-312.pyc | Bin 0 -> 30210 bytes .../__pycache__/ImagePalette.cpython-312.pyc | Bin 0 -> 12121 bytes .../PIL/__pycache__/ImagePath.cpython-312.pyc | Bin 0 -> 324 bytes .../PIL/__pycache__/ImageQt.cpython-312.pyc | Bin 0 -> 8519 bytes .../__pycache__/ImageSequence.cpython-312.pyc | Bin 0 -> 3345 bytes .../PIL/__pycache__/ImageShow.cpython-312.pyc | Bin 0 -> 13847 bytes .../PIL/__pycache__/ImageStat.cpython-312.pyc | Bin 0 -> 7318 bytes .../PIL/__pycache__/ImageTk.cpython-312.pyc | Bin 0 -> 9352 bytes .../ImageTransform.cpython-312.pyc | Bin 0 -> 5357 bytes .../PIL/__pycache__/ImageWin.cpython-312.pyc | Bin 0 -> 11837 bytes .../ImtImagePlugin.cpython-312.pyc | Bin 0 -> 2523 bytes .../IptcImagePlugin.cpython-312.pyc | Bin 0 -> 8988 bytes .../Jpeg2KImagePlugin.cpython-312.pyc | Bin 0 -> 18189 bytes .../JpegImagePlugin.cpython-312.pyc | Bin 0 -> 34015 bytes .../__pycache__/JpegPresets.cpython-312.pyc | Bin 0 -> 8103 bytes .../McIdasImagePlugin.cpython-312.pyc | Bin 0 -> 2201 bytes .../MicImagePlugin.cpython-312.pyc | Bin 0 -> 3642 bytes .../MpegImagePlugin.cpython-312.pyc | Bin 0 -> 3603 bytes .../MpoImagePlugin.cpython-312.pyc | Bin 0 -> 8746 bytes .../MspImagePlugin.cpython-312.pyc | Bin 0 -> 5951 bytes .../PIL/__pycache__/PSDraw.cpython-312.pyc | Bin 0 -> 7920 bytes .../__pycache__/PaletteFile.cpython-312.pyc | Bin 0 -> 1906 bytes .../PalmImagePlugin.cpython-312.pyc | Bin 0 -> 9127 bytes .../PcdImagePlugin.cpython-312.pyc | Bin 0 -> 2036 bytes .../__pycache__/PcfFontFile.cpython-312.pyc | Bin 0 -> 9880 bytes .../PcxImagePlugin.cpython-312.pyc | Bin 0 -> 7319 bytes .../PdfImagePlugin.cpython-312.pyc | Bin 0 -> 9745 bytes .../PIL/__pycache__/PdfParser.cpython-312.pyc | Bin 0 -> 52779 bytes .../PixarImagePlugin.cpython-312.pyc | Bin 0 -> 1941 bytes .../PngImagePlugin.cpython-312.pyc | Bin 0 -> 62275 bytes .../PpmImagePlugin.cpython-312.pyc | Bin 0 -> 14093 bytes .../PsdImagePlugin.cpython-312.pyc | Bin 0 -> 10987 bytes .../QoiImagePlugin.cpython-312.pyc | Bin 0 -> 11285 bytes .../SgiImagePlugin.cpython-312.pyc | Bin 0 -> 7987 bytes .../SpiderImagePlugin.cpython-312.pyc | Bin 0 -> 12439 bytes .../SunImagePlugin.cpython-312.pyc | Bin 0 -> 3395 bytes .../PIL/__pycache__/TarIO.cpython-312.pyc | Bin 0 -> 2017 bytes .../TgaImagePlugin.cpython-312.pyc | Bin 0 -> 8071 bytes .../TiffImagePlugin.cpython-312.pyc | Bin 0 -> 101424 bytes .../PIL/__pycache__/TiffTags.cpython-312.pyc | Bin 0 -> 18781 bytes .../__pycache__/WalImageFile.cpython-312.pyc | Bin 0 -> 4065 bytes .../WebPImagePlugin.cpython-312.pyc | Bin 0 -> 13096 bytes .../WmfImagePlugin.cpython-312.pyc | Bin 0 -> 6354 bytes .../XVThumbImagePlugin.cpython-312.pyc | Bin 0 -> 2592 bytes .../XbmImagePlugin.cpython-312.pyc | Bin 0 -> 4084 bytes .../XpmImagePlugin.cpython-312.pyc | Bin 0 -> 6119 bytes .../PIL/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2020 bytes .../PIL/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 368 bytes .../PIL/__pycache__/_binary.cpython-312.pyc | Bin 0 -> 3411 bytes .../__pycache__/_deprecate.cpython-312.pyc | Bin 0 -> 2551 bytes .../_tkinter_finder.cpython-312.pyc | Bin 0 -> 735 bytes .../PIL/__pycache__/_typing.cpython-312.pyc | Bin 0 -> 2106 bytes .../PIL/__pycache__/_util.cpython-312.pyc | Bin 0 -> 1520 bytes .../PIL/__pycache__/_version.cpython-312.pyc | Bin 0 -> 240 bytes .../PIL/__pycache__/features.cpython-312.pyc | Bin 0 -> 14159 bytes .../PIL/__pycache__/report.cpython-312.pyc | Bin 0 -> 302 bytes .../PIL/_avif.cp312-win_amd64.pyd | Bin 0 -> 7833600 bytes venv/Lib/site-packages/PIL/_avif.pyi | 3 + venv/Lib/site-packages/PIL/_binary.py | 112 + venv/Lib/site-packages/PIL/_deprecate.py | 72 + .../PIL/_imaging.cp312-win_amd64.pyd | Bin 0 -> 2491392 bytes venv/Lib/site-packages/PIL/_imaging.pyi | 31 + .../PIL/_imagingcms.cp312-win_amd64.pyd | Bin 0 -> 266240 bytes venv/Lib/site-packages/PIL/_imagingcms.pyi | 143 + .../PIL/_imagingft.cp312-win_amd64.pyd | Bin 0 -> 2011136 bytes venv/Lib/site-packages/PIL/_imagingft.pyi | 69 + .../PIL/_imagingmath.cp312-win_amd64.pyd | Bin 0 -> 25088 bytes venv/Lib/site-packages/PIL/_imagingmath.pyi | 3 + .../PIL/_imagingmorph.cp312-win_amd64.pyd | Bin 0 -> 13824 bytes venv/Lib/site-packages/PIL/_imagingmorph.pyi | 3 + .../PIL/_imagingtk.cp312-win_amd64.pyd | Bin 0 -> 14848 bytes venv/Lib/site-packages/PIL/_imagingtk.pyi | 3 + venv/Lib/site-packages/PIL/_tkinter_finder.py | 20 + venv/Lib/site-packages/PIL/_typing.py | 54 + venv/Lib/site-packages/PIL/_util.py | 26 + venv/Lib/site-packages/PIL/_version.py | 4 + .../PIL/_webp.cp312-win_amd64.pyd | Bin 0 -> 409600 bytes venv/Lib/site-packages/PIL/_webp.pyi | 3 + venv/Lib/site-packages/PIL/features.py | 361 + venv/Lib/site-packages/PIL/py.typed | 0 venv/Lib/site-packages/PIL/report.py | 5 + .../__pycache__/py.cpython-312.pyc | Bin 0 -> 476 bytes venv/Lib/site-packages/_pytest/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 464 bytes .../__pycache__/_argcomplete.cpython-312.pyc | Bin 0 -> 4809 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 626 bytes .../__pycache__/cacheprovider.cpython-312.pyc | Bin 0 -> 30880 bytes .../__pycache__/capture.cpython-312.pyc | Bin 0 -> 55714 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 11818 bytes .../__pycache__/debugging.cpython-312.pyc | Bin 0 -> 18262 bytes .../__pycache__/deprecated.cpython-312.pyc | Bin 0 -> 2546 bytes .../__pycache__/doctest.cpython-312.pyc | Bin 0 -> 33294 bytes .../__pycache__/faulthandler.cpython-312.pyc | Bin 0 -> 4421 bytes .../__pycache__/fixtures.cpython-312.pyc | Bin 0 -> 81868 bytes .../freeze_support.cpython-312.pyc | Bin 0 -> 1808 bytes .../__pycache__/helpconfig.cpython-312.pyc | Bin 0 -> 12515 bytes .../__pycache__/hookspec.cpython-312.pyc | Bin 0 -> 45580 bytes .../__pycache__/junitxml.cpython-312.pyc | Bin 0 -> 33141 bytes .../__pycache__/legacypath.cpython-312.pyc | Bin 0 -> 24149 bytes .../__pycache__/logging.cpython-312.pyc | Bin 0 -> 45931 bytes .../_pytest/__pycache__/main.cpython-312.pyc | Bin 0 -> 44038 bytes .../__pycache__/monkeypatch.cpython-312.pyc | Bin 0 -> 16507 bytes .../_pytest/__pycache__/nodes.cpython-312.pyc | Bin 0 -> 30811 bytes .../__pycache__/outcomes.cpython-312.pyc | Bin 0 -> 11774 bytes .../__pycache__/pastebin.cpython-312.pyc | Bin 0 -> 5865 bytes .../__pycache__/pathlib.cpython-312.pyc | Bin 0 -> 41236 bytes .../__pycache__/pytester.cpython-312.pyc | Bin 0 -> 84438 bytes .../pytester_assertions.cpython-312.pyc | Bin 0 -> 2473 bytes .../__pycache__/python.cpython-312.pyc | Bin 0 -> 72313 bytes .../__pycache__/python_api.cpython-312.pyc | Bin 0 -> 33796 bytes .../__pycache__/raises.cpython-312.pyc | Bin 0 -> 59958 bytes .../__pycache__/recwarn.cpython-312.pyc | Bin 0 -> 16055 bytes .../__pycache__/reports.cpython-312.pyc | Bin 0 -> 24540 bytes .../__pycache__/runner.cpython-312.pyc | Bin 0 -> 23858 bytes .../_pytest/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3555 bytes .../__pycache__/setuponly.cpython-312.pyc | Bin 0 -> 5344 bytes .../__pycache__/setupplan.cpython-312.pyc | Bin 0 -> 1889 bytes .../__pycache__/skipping.cpython-312.pyc | Bin 0 -> 13902 bytes .../_pytest/__pycache__/stash.cpython-312.pyc | Bin 0 -> 4352 bytes .../__pycache__/stepwise.cpython-312.pyc | Bin 0 -> 9595 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 79511 bytes .../threadexception.cpython-312.pyc | Bin 0 -> 5737 bytes .../__pycache__/timing.cpython-312.pyc | Bin 0 -> 4767 bytes .../__pycache__/tmpdir.cpython-312.pyc | Bin 0 -> 12701 bytes .../__pycache__/tracemalloc.cpython-312.pyc | Bin 0 -> 970 bytes .../__pycache__/unittest.cpython-312.pyc | Bin 0 -> 22213 bytes .../unraisableexception.cpython-312.pyc | Bin 0 -> 6233 bytes .../__pycache__/warning_types.cpython-312.pyc | Bin 0 -> 6683 bytes .../__pycache__/warnings.cpython-312.pyc | Bin 0 -> 6681 bytes .../Lib/site-packages/_pytest/_argcomplete.py | 117 + .../site-packages/_pytest/_code/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 670 bytes .../_code/__pycache__/code.cpython-312.pyc | Bin 0 -> 67946 bytes .../_code/__pycache__/source.cpython-312.pyc | Bin 0 -> 11894 bytes venv/Lib/site-packages/_pytest/_code/code.py | 1567 +++ .../Lib/site-packages/_pytest/_code/source.py | 225 + .../Lib/site-packages/_pytest/_io/__init__.py | 10 + .../_io/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 361 bytes .../_io/__pycache__/pprint.cpython-312.pyc | Bin 0 -> 24368 bytes .../_io/__pycache__/saferepr.cpython-312.pyc | Bin 0 -> 5807 bytes .../terminalwriter.cpython-312.pyc | Bin 0 -> 10963 bytes .../_io/__pycache__/wcwidth.cpython-312.pyc | Bin 0 -> 1667 bytes venv/Lib/site-packages/_pytest/_io/pprint.py | 673 ++ .../Lib/site-packages/_pytest/_io/saferepr.py | 130 + .../_pytest/_io/terminalwriter.py | 254 + venv/Lib/site-packages/_pytest/_io/wcwidth.py | 57 + .../Lib/site-packages/_pytest/_py/__init__.py | 0 .../_py/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 169 bytes .../_py/__pycache__/error.cpython-312.pyc | Bin 0 -> 4937 bytes .../_py/__pycache__/path.cpython-312.pyc | Bin 0 -> 68731 bytes venv/Lib/site-packages/_pytest/_py/error.py | 119 + venv/Lib/site-packages/_pytest/_py/path.py | 1475 +++ venv/Lib/site-packages/_pytest/_version.py | 21 + .../_pytest/assertion/__init__.py | 208 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 10089 bytes .../__pycache__/rewrite.cpython-312.pyc | Bin 0 -> 61940 bytes .../__pycache__/truncate.cpython-312.pyc | Bin 0 -> 4209 bytes .../__pycache__/util.cpython-312.pyc | Bin 0 -> 24225 bytes .../_pytest/assertion/rewrite.py | 1216 +++ .../_pytest/assertion/truncate.py | 137 + .../site-packages/_pytest/assertion/util.py | 621 ++ .../site-packages/_pytest/cacheprovider.py | 625 ++ venv/Lib/site-packages/_pytest/capture.py | 1144 +++ venv/Lib/site-packages/_pytest/compat.py | 322 + .../site-packages/_pytest/config/__init__.py | 2029 ++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 84071 bytes .../__pycache__/argparsing.cpython-312.pyc | Bin 0 -> 24461 bytes .../config/__pycache__/compat.cpython-312.pyc | Bin 0 -> 3567 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 780 bytes .../__pycache__/findpaths.cpython-312.pyc | Bin 0 -> 9308 bytes .../_pytest/config/argparsing.py | 533 + .../site-packages/_pytest/config/compat.py | 85 + .../_pytest/config/exceptions.py | 13 + .../site-packages/_pytest/config/findpaths.py | 239 + venv/Lib/site-packages/_pytest/debugging.py | 407 + venv/Lib/site-packages/_pytest/deprecated.py | 91 + venv/Lib/site-packages/_pytest/doctest.py | 754 ++ .../Lib/site-packages/_pytest/faulthandler.py | 105 + venv/Lib/site-packages/_pytest/fixtures.py | 2016 ++++ .../site-packages/_pytest/freeze_support.py | 45 + venv/Lib/site-packages/_pytest/helpconfig.py | 283 + venv/Lib/site-packages/_pytest/hookspec.py | 1333 +++ venv/Lib/site-packages/_pytest/junitxml.py | 692 ++ venv/Lib/site-packages/_pytest/legacypath.py | 468 + venv/Lib/site-packages/_pytest/logging.py | 960 ++ venv/Lib/site-packages/_pytest/main.py | 1076 ++ .../site-packages/_pytest/mark/__init__.py | 301 + .../mark/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 12964 bytes .../__pycache__/expression.cpython-312.pyc | Bin 0 -> 16523 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 27489 bytes .../site-packages/_pytest/mark/expression.py | 331 + .../site-packages/_pytest/mark/structures.py | 662 ++ venv/Lib/site-packages/_pytest/monkeypatch.py | 415 + venv/Lib/site-packages/_pytest/nodes.py | 772 ++ venv/Lib/site-packages/_pytest/outcomes.py | 317 + venv/Lib/site-packages/_pytest/pastebin.py | 117 + venv/Lib/site-packages/_pytest/pathlib.py | 1055 ++ venv/Lib/site-packages/_pytest/py.typed | 0 venv/Lib/site-packages/_pytest/pytester.py | 1775 ++++ .../_pytest/pytester_assertions.py | 74 + venv/Lib/site-packages/_pytest/python.py | 1723 ++++ venv/Lib/site-packages/_pytest/python_api.py | 793 ++ venv/Lib/site-packages/_pytest/raises.py | 1519 +++ venv/Lib/site-packages/_pytest/recwarn.py | 365 + venv/Lib/site-packages/_pytest/reports.py | 637 ++ venv/Lib/site-packages/_pytest/runner.py | 571 ++ venv/Lib/site-packages/_pytest/scope.py | 91 + venv/Lib/site-packages/_pytest/setuponly.py | 98 + venv/Lib/site-packages/_pytest/setupplan.py | 39 + venv/Lib/site-packages/_pytest/skipping.py | 316 + venv/Lib/site-packages/_pytest/stash.py | 116 + venv/Lib/site-packages/_pytest/stepwise.py | 209 + venv/Lib/site-packages/_pytest/terminal.py | 1641 ++++ .../site-packages/_pytest/threadexception.py | 152 + venv/Lib/site-packages/_pytest/timing.py | 94 + venv/Lib/site-packages/_pytest/tmpdir.py | 312 + venv/Lib/site-packages/_pytest/tracemalloc.py | 24 + venv/Lib/site-packages/_pytest/unittest.py | 516 + .../_pytest/unraisableexception.py | 163 + .../site-packages/_pytest/warning_types.py | 166 + venv/Lib/site-packages/_pytest/warnings.py | 152 + .../certifi-2025.7.14.dist-info/INSTALLER | 1 + .../certifi-2025.7.14.dist-info/METADATA | 77 + .../certifi-2025.7.14.dist-info/RECORD | 14 + .../certifi-2025.7.14.dist-info/WHEEL | 5 + .../licenses/LICENSE | 20 + .../certifi-2025.7.14.dist-info/top_level.txt | 1 + venv/Lib/site-packages/certifi/__init__.py | 4 + venv/Lib/site-packages/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 292 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 607 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 2039 bytes venv/Lib/site-packages/certifi/cacert.pem | 4778 +++++++++ venv/Lib/site-packages/certifi/core.py | 83 + venv/Lib/site-packages/certifi/py.typed | 0 .../INSTALLER | 1 + .../METADATA | 731 ++ .../charset_normalizer-3.4.2.dist-info/RECORD | 35 + .../charset_normalizer-3.4.2.dist-info/WHEEL | 5 + .../entry_points.txt | 2 + .../licenses/LICENSE | 21 + .../top_level.txt | 1 + .../charset_normalizer/__init__.py | 48 + .../charset_normalizer/__main__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1758 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 333 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 18126 bytes .../__pycache__/cd.cpython-312.pyc | Bin 0 -> 13196 bytes .../__pycache__/constant.cpython-312.pyc | Bin 0 -> 40788 bytes .../__pycache__/legacy.cpython-312.pyc | Bin 0 -> 2758 bytes .../__pycache__/md.cpython-312.pyc | Bin 0 -> 24306 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 17088 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 13685 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 360 bytes .../site-packages/charset_normalizer/api.py | 668 ++ .../site-packages/charset_normalizer/cd.py | 395 + .../charset_normalizer/cli/__init__.py | 8 + .../charset_normalizer/cli/__main__.py | 381 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 321 bytes .../cli/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 14350 bytes .../charset_normalizer/constant.py | 2015 ++++ .../charset_normalizer/legacy.py | 64 + .../charset_normalizer/md.cp312-win_amd64.pyd | Bin 0 -> 10752 bytes .../site-packages/charset_normalizer/md.py | 635 ++ .../md__mypyc.cp312-win_amd64.pyd | Bin 0 -> 125952 bytes .../charset_normalizer/models.py | 360 + .../site-packages/charset_normalizer/py.typed | 0 .../site-packages/charset_normalizer/utils.py | 414 + .../charset_normalizer/version.py | 8 + .../colorama-0.4.6.dist-info/INSTALLER | 1 + .../colorama-0.4.6.dist-info/METADATA | 441 + .../colorama-0.4.6.dist-info/RECORD | 31 + .../colorama-0.4.6.dist-info/WHEEL | 5 + .../licenses/LICENSE.txt | 27 + venv/Lib/site-packages/colorama/__init__.py | 7 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 466 bytes .../colorama/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 3919 bytes .../__pycache__/ansitowin32.cpython-312.pyc | Bin 0 -> 16380 bytes .../__pycache__/initialise.cpython-312.pyc | Bin 0 -> 3524 bytes .../__pycache__/win32.cpython-312.pyc | Bin 0 -> 8106 bytes .../__pycache__/winterm.cpython-312.pyc | Bin 0 -> 9062 bytes venv/Lib/site-packages/colorama/ansi.py | 102 + .../Lib/site-packages/colorama/ansitowin32.py | 277 + venv/Lib/site-packages/colorama/initialise.py | 121 + .../site-packages/colorama/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 172 bytes .../__pycache__/ansi_test.cpython-312.pyc | Bin 0 -> 5441 bytes .../ansitowin32_test.cpython-312.pyc | Bin 0 -> 18063 bytes .../initialise_test.cpython-312.pyc | Bin 0 -> 11727 bytes .../__pycache__/isatty_test.cpython-312.pyc | Bin 0 -> 4878 bytes .../tests/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2462 bytes .../__pycache__/winterm_test.cpython-312.pyc | Bin 0 -> 6586 bytes .../site-packages/colorama/tests/ansi_test.py | 76 + .../colorama/tests/ansitowin32_test.py | 294 + .../colorama/tests/initialise_test.py | 189 + .../colorama/tests/isatty_test.py | 57 + .../Lib/site-packages/colorama/tests/utils.py | 49 + .../colorama/tests/winterm_test.py | 131 + venv/Lib/site-packages/colorama/win32.py | 180 + venv/Lib/site-packages/colorama/winterm.py | 195 + .../idna-3.10.dist-info/INSTALLER | 1 + .../idna-3.10.dist-info/LICENSE.md | 31 + .../idna-3.10.dist-info/METADATA | 250 + .../site-packages/idna-3.10.dist-info/RECORD | 22 + .../site-packages/idna-3.10.dist-info/WHEEL | 4 + venv/Lib/site-packages/idna/__init__.py | 45 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 860 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 4950 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 864 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 16095 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99450 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2607 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 191 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158820 bytes venv/Lib/site-packages/idna/codec.py | 122 + venv/Lib/site-packages/idna/compat.py | 15 + venv/Lib/site-packages/idna/core.py | 437 + venv/Lib/site-packages/idna/idnadata.py | 4243 ++++++++ venv/Lib/site-packages/idna/intranges.py | 57 + venv/Lib/site-packages/idna/package_data.py | 1 + venv/Lib/site-packages/idna/py.typed | 0 venv/Lib/site-packages/idna/uts46data.py | 8681 +++++++++++++++++ .../iniconfig-2.1.0.dist-info/INSTALLER | 1 + .../iniconfig-2.1.0.dist-info/METADATA | 81 + .../iniconfig-2.1.0.dist-info/RECORD | 14 + .../iniconfig-2.1.0.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + venv/Lib/site-packages/iniconfig/__init__.py | 216 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 8881 bytes .../__pycache__/_parse.cpython-312.pyc | Bin 0 -> 3304 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 628 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 1243 bytes venv/Lib/site-packages/iniconfig/_parse.py | 82 + venv/Lib/site-packages/iniconfig/_version.py | 21 + .../Lib/site-packages/iniconfig/exceptions.py | 20 + venv/Lib/site-packages/iniconfig/py.typed | 0 .../packaging-25.0.dist-info/INSTALLER | 1 + .../packaging-25.0.dist-info/METADATA | 105 + .../packaging-25.0.dist-info/RECORD | 40 + .../packaging-25.0.dist-info/WHEEL | 4 + .../packaging-25.0.dist-info/licenses/LICENSE | 3 + .../licenses/LICENSE.APACHE | 177 + .../licenses/LICENSE.BSD | 23 + venv/Lib/site-packages/packaging/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 532 bytes .../__pycache__/_elffile.cpython-312.pyc | Bin 0 -> 4990 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 9706 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 4529 bytes .../__pycache__/_parser.cpython-312.pyc | Bin 0 -> 13972 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3215 bytes .../__pycache__/_tokenizer.cpython-312.pyc | Bin 0 -> 7922 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 12730 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 27169 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 4384 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 38983 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 24645 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 6609 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 20440 bytes venv/Lib/site-packages/packaging/_elffile.py | 109 + .../Lib/site-packages/packaging/_manylinux.py | 262 + .../Lib/site-packages/packaging/_musllinux.py | 85 + venv/Lib/site-packages/packaging/_parser.py | 353 + .../site-packages/packaging/_structures.py | 61 + .../Lib/site-packages/packaging/_tokenizer.py | 195 + .../packaging/licenses/__init__.py | 145 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4079 bytes .../__pycache__/_spdx.cpython-312.pyc | Bin 0 -> 47338 bytes .../site-packages/packaging/licenses/_spdx.py | 759 ++ venv/Lib/site-packages/packaging/markers.py | 362 + venv/Lib/site-packages/packaging/metadata.py | 862 ++ venv/Lib/site-packages/packaging/py.typed | 0 .../site-packages/packaging/requirements.py | 91 + .../Lib/site-packages/packaging/specifiers.py | 1019 ++ venv/Lib/site-packages/packaging/tags.py | 656 ++ venv/Lib/site-packages/packaging/utils.py | 163 + venv/Lib/site-packages/packaging/version.py | 582 ++ .../pillow-11.3.0.dist-info/INSTALLER | 1 + .../pillow-11.3.0.dist-info/METADATA | 177 + .../pillow-11.3.0.dist-info/RECORD | 217 + .../pillow-11.3.0.dist-info/REQUESTED | 0 .../pillow-11.3.0.dist-info/WHEEL | 5 + .../pillow-11.3.0.dist-info/licenses/LICENSE | 1590 +++ .../pillow-11.3.0.dist-info/top_level.txt | 1 + .../pillow-11.3.0.dist-info/zip-safe | 1 + .../pip-24.2.dist-info/AUTHORS.txt | 796 ++ .../pip-24.2.dist-info/INSTALLER | 1 + .../pip-24.2.dist-info/LICENSE.txt | 20 + .../site-packages/pip-24.2.dist-info/METADATA | 89 + .../site-packages/pip-24.2.dist-info/RECORD | 853 ++ .../pip-24.2.dist-info/REQUESTED | 0 .../site-packages/pip-24.2.dist-info/WHEEL | 5 + .../pip-24.2.dist-info/entry_points.txt | 3 + .../pip-24.2.dist-info/top_level.txt | 1 + venv/Lib/site-packages/pip/__init__.py | 13 + venv/Lib/site-packages/pip/__main__.py | 24 + venv/Lib/site-packages/pip/__pip-runner__.py | 50 + .../pip/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 670 bytes .../pip/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 826 bytes .../__pip-runner__.cpython-312.pyc | Bin 0 -> 2189 bytes .../site-packages/pip/_internal/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 772 bytes .../__pycache__/build_env.cpython-312.pyc | Bin 0 -> 14462 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 12646 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 17614 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 35542 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 655 bytes .../__pycache__/pyproject.cpython-312.pyc | Bin 0 -> 5109 bytes .../self_outdated_check.cpython-312.pyc | Bin 0 -> 10207 bytes .../__pycache__/wheel_builder.cpython-312.pyc | Bin 0 -> 13618 bytes .../site-packages/pip/_internal/build_env.py | 315 + venv/Lib/site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 263 bytes .../autocompletion.cpython-312.pyc | Bin 0 -> 8570 bytes .../__pycache__/base_command.cpython-312.pyc | Bin 0 -> 10190 bytes .../__pycache__/cmdoptions.cpython-312.pyc | Bin 0 -> 30378 bytes .../command_context.cpython-312.pyc | Bin 0 -> 1766 bytes .../__pycache__/index_command.cpython-312.pyc | Bin 0 -> 7120 bytes .../cli/__pycache__/main.cpython-312.pyc | Bin 0 -> 2285 bytes .../__pycache__/main_parser.cpython-312.pyc | Bin 0 -> 4885 bytes .../cli/__pycache__/parser.cpython-312.pyc | Bin 0 -> 15008 bytes .../__pycache__/progress_bars.cpython-312.pyc | Bin 0 -> 3830 bytes .../__pycache__/req_command.cpython-312.pyc | Bin 0 -> 12225 bytes .../cli/__pycache__/spinners.cpython-312.pyc | Bin 0 -> 7825 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 360 bytes .../pip/_internal/cli/autocompletion.py | 176 + .../pip/_internal/cli/base_command.py | 231 + .../pip/_internal/cli/cmdoptions.py | 1075 ++ .../pip/_internal/cli/command_context.py | 27 + .../pip/_internal/cli/index_command.py | 170 + .../site-packages/pip/_internal/cli/main.py | 80 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 94 + .../pip/_internal/cli/req_command.py | 329 + .../pip/_internal/cli/spinners.py | 159 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 132 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3987 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 9686 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 2577 bytes .../__pycache__/completion.cpython-312.pyc | Bin 0 -> 5177 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 13175 bytes .../__pycache__/debug.cpython-312.pyc | Bin 0 -> 10088 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 7488 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 4370 bytes .../commands/__pycache__/hash.cpython-312.pyc | Bin 0 -> 2958 bytes .../commands/__pycache__/help.cpython-312.pyc | Bin 0 -> 1658 bytes .../__pycache__/index.cpython-312.pyc | Bin 0 -> 6661 bytes .../__pycache__/inspect.cpython-312.pyc | Bin 0 -> 3961 bytes .../__pycache__/install.cpython-312.pyc | Bin 0 -> 29098 bytes .../commands/__pycache__/list.cpython-312.pyc | Bin 0 -> 15724 bytes .../__pycache__/search.cpython-312.pyc | Bin 0 -> 7511 bytes .../commands/__pycache__/show.cpython-312.pyc | Bin 0 -> 10440 bytes .../__pycache__/uninstall.cpython-312.pyc | Bin 0 -> 4694 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 8850 bytes .../pip/_internal/commands/cache.py | 225 + .../pip/_internal/commands/check.py | 67 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 146 + .../pip/_internal/commands/freeze.py | 109 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 139 + .../pip/_internal/commands/inspect.py | 92 + .../pip/_internal/commands/install.py | 783 ++ .../pip/_internal/commands/list.py | 375 + .../pip/_internal/commands/search.py | 172 + .../pip/_internal/commands/show.py | 217 + .../pip/_internal/commands/uninstall.py | 114 + .../pip/_internal/commands/wheel.py | 182 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 926 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 2878 bytes .../__pycache__/installed.cpython-312.pyc | Bin 0 -> 1685 bytes .../__pycache__/sdist.cpython-312.pyc | Bin 0 -> 8467 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 2266 bytes .../pip/_internal/distributions/base.py | 53 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 158 + .../pip/_internal/distributions/wheel.py | 42 + .../site-packages/pip/_internal/exceptions.py | 777 ++ .../pip/_internal/index/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../__pycache__/collector.cpython-312.pyc | Bin 0 -> 21592 bytes .../package_finder.cpython-312.pyc | Bin 0 -> 40638 bytes .../index/__pycache__/sources.cpython-312.pyc | Bin 0 -> 12573 bytes .../pip/_internal/index/collector.py | 494 + .../pip/_internal/index/package_finder.py | 1020 ++ .../pip/_internal/index/sources.py | 285 + .../pip/_internal/locations/__init__.py | 456 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 16421 bytes .../__pycache__/_distutils.cpython-312.pyc | Bin 0 -> 6836 bytes .../__pycache__/_sysconfig.cpython-312.pyc | Bin 0 -> 8011 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3766 bytes .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 214 + .../pip/_internal/locations/base.py | 81 + venv/Lib/site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5867 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 2905 bytes .../metadata/__pycache__/base.cpython-312.pyc | Bin 0 -> 35172 bytes .../__pycache__/pkg_resources.cpython-312.pyc | Bin 0 -> 16067 bytes .../pip/_internal/metadata/_json.py | 84 + .../pip/_internal/metadata/base.py | 688 ++ .../_internal/metadata/importlib/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 343 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 4476 bytes .../__pycache__/_dists.cpython-312.pyc | Bin 0 -> 12550 bytes .../__pycache__/_envs.cpython-312.pyc | Bin 0 -> 11083 bytes .../_internal/metadata/importlib/_compat.py | 85 + .../_internal/metadata/importlib/_dists.py | 221 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 301 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 251 bytes .../__pycache__/candidate.cpython-312.pyc | Bin 0 -> 1589 bytes .../__pycache__/direct_url.cpython-312.pyc | Bin 0 -> 10824 bytes .../format_control.cpython-312.pyc | Bin 0 -> 4207 bytes .../models/__pycache__/index.cpython-312.pyc | Bin 0 -> 1679 bytes .../installation_report.cpython-312.pyc | Bin 0 -> 2257 bytes .../models/__pycache__/link.cpython-312.pyc | Bin 0 -> 26585 bytes .../models/__pycache__/scheme.cpython-312.pyc | Bin 0 -> 1008 bytes .../__pycache__/search_scope.cpython-312.pyc | Bin 0 -> 4967 bytes .../selection_prefs.cpython-312.pyc | Bin 0 -> 1836 bytes .../__pycache__/target_python.cpython-312.pyc | Bin 0 -> 4939 bytes .../models/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5765 bytes .../pip/_internal/models/candidate.py | 25 + .../pip/_internal/models/direct_url.py | 224 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 590 ++ .../pip/_internal/models/scheme.py | 25 + .../pip/_internal/models/search_scope.py | 127 + .../pip/_internal/models/selection_prefs.py | 53 + .../pip/_internal/models/target_python.py | 121 + .../pip/_internal/models/wheel.py | 93 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 239 bytes .../network/__pycache__/auth.cpython-312.pyc | Bin 0 -> 22084 bytes .../network/__pycache__/cache.cpython-312.pyc | Bin 0 -> 6503 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 8478 bytes .../__pycache__/lazy_wheel.cpython-312.pyc | Bin 0 -> 11630 bytes .../__pycache__/session.cpython-312.pyc | Bin 0 -> 18850 bytes .../network/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2240 bytes .../__pycache__/xmlrpc.cpython-312.pyc | Bin 0 -> 2934 bytes .../pip/_internal/network/auth.py | 566 ++ .../pip/_internal/network/cache.py | 106 + .../pip/_internal/network/download.py | 187 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 522 + .../pip/_internal/network/utils.py | 98 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 182 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 7100 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 10132 bytes .../__pycache__/prepare.cpython-312.pyc | Bin 0 -> 25826 bytes .../_internal/operations/build/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 188 bytes .../__pycache__/build_tracker.cpython-312.pyc | Bin 0 -> 7684 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 1865 bytes .../metadata_editable.cpython-312.pyc | Bin 0 -> 1899 bytes .../metadata_legacy.cpython-312.pyc | Bin 0 -> 3012 bytes .../build/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 1669 bytes .../wheel_editable.cpython-312.pyc | Bin 0 -> 2010 bytes .../__pycache__/wheel_legacy.cpython-312.pyc | Bin 0 -> 3842 bytes .../operations/build/build_tracker.py | 138 + .../_internal/operations/build/metadata.py | 39 + .../operations/build/metadata_editable.py | 41 + .../operations/build/metadata_legacy.py | 74 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 102 + .../pip/_internal/operations/check.py | 181 + .../pip/_internal/operations/freeze.py | 258 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 251 bytes .../editable_legacy.cpython-312.pyc | Bin 0 -> 1804 bytes .../install/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 34175 bytes .../operations/install/editable_legacy.py | 47 + .../pip/_internal/operations/install/wheel.py | 741 ++ .../pip/_internal/operations/prepare.py | 732 ++ .../site-packages/pip/_internal/pyproject.py | 185 + .../pip/_internal/req/__init__.py | 90 + .../req/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3451 bytes .../__pycache__/constructors.cpython-312.pyc | Bin 0 -> 21214 bytes .../req/__pycache__/req_file.cpython-312.pyc | Bin 0 -> 21414 bytes .../__pycache__/req_install.cpython-312.pyc | Bin 0 -> 38504 bytes .../req/__pycache__/req_set.cpython-312.pyc | Bin 0 -> 5458 bytes .../__pycache__/req_uninstall.cpython-312.pyc | Bin 0 -> 32110 bytes .../pip/_internal/req/constructors.py | 560 ++ .../pip/_internal/req/req_file.py | 551 ++ .../pip/_internal/req/req_install.py | 934 ++ .../pip/_internal/req/req_set.py | 82 + .../pip/_internal/req/req_uninstall.py | 633 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 182 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 1170 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 189 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 22572 bytes .../_internal/resolution/legacy/resolver.py | 597 ++ .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 193 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 8133 bytes .../__pycache__/candidates.cpython-312.pyc | Bin 0 -> 29155 bytes .../__pycache__/factory.cpython-312.pyc | Bin 0 -> 32254 bytes .../found_candidates.cpython-312.pyc | Bin 0 -> 6777 bytes .../__pycache__/provider.cpython-312.pyc | Bin 0 -> 10501 bytes .../__pycache__/reporter.cpython-312.pyc | Bin 0 -> 5025 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 15335 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 12302 bytes .../_internal/resolution/resolvelib/base.py | 139 + .../resolution/resolvelib/candidates.py | 569 ++ .../resolution/resolvelib/factory.py | 817 ++ .../resolution/resolvelib/found_candidates.py | 174 + .../resolution/resolvelib/provider.py | 258 + .../resolution/resolvelib/reporter.py | 81 + .../resolution/resolvelib/requirements.py | 245 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 244 + .../pip/_internal/utils/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 177 bytes .../__pycache__/_jaraco_text.cpython-312.pyc | Bin 0 -> 4512 bytes .../utils/__pycache__/_log.cpython-312.pyc | Bin 0 -> 1848 bytes .../utils/__pycache__/appdirs.cpython-312.pyc | Bin 0 -> 2392 bytes .../utils/__pycache__/compat.cpython-312.pyc | Bin 0 -> 2889 bytes .../compatibility_tags.cpython-312.pyc | Bin 0 -> 5538 bytes .../__pycache__/datetime.cpython-312.pyc | Bin 0 -> 666 bytes .../__pycache__/deprecation.cpython-312.pyc | Bin 0 -> 4172 bytes .../direct_url_helpers.cpython-312.pyc | Bin 0 -> 3518 bytes .../__pycache__/egg_link.cpython-312.pyc | Bin 0 -> 3188 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2130 bytes .../__pycache__/entrypoints.cpython-312.pyc | Bin 0 -> 3970 bytes .../__pycache__/filesystem.cpython-312.pyc | Bin 0 -> 7314 bytes .../__pycache__/filetypes.cpython-312.pyc | Bin 0 -> 1146 bytes .../utils/__pycache__/glibc.cpython-312.pyc | Bin 0 -> 2401 bytes .../utils/__pycache__/hashes.cpython-312.pyc | Bin 0 -> 7590 bytes .../utils/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13534 bytes .../utils/__pycache__/misc.cpython-312.pyc | Bin 0 -> 33532 bytes .../__pycache__/packaging.cpython-312.pyc | Bin 0 -> 2565 bytes .../utils/__pycache__/retry.cpython-312.pyc | Bin 0 -> 2090 bytes .../setuptools_build.cpython-312.pyc | Bin 0 -> 4532 bytes .../__pycache__/subprocess.cpython-312.pyc | Bin 0 -> 8626 bytes .../__pycache__/temp_dir.cpython-312.pyc | Bin 0 -> 12017 bytes .../__pycache__/unpacking.cpython-312.pyc | Bin 0 -> 13489 bytes .../utils/__pycache__/urls.cpython-312.pyc | Bin 0 -> 2059 bytes .../__pycache__/virtualenv.cpython-312.pyc | Bin 0 -> 4457 bytes .../utils/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5881 bytes .../pip/_internal/utils/_jaraco_text.py | 109 + .../site-packages/pip/_internal/utils/_log.py | 38 + .../pip/_internal/utils/appdirs.py | 52 + .../pip/_internal/utils/compat.py | 79 + .../pip/_internal/utils/compatibility_tags.py | 165 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 124 + .../pip/_internal/utils/direct_url_helpers.py | 87 + .../pip/_internal/utils/egg_link.py | 80 + .../pip/_internal/utils/encoding.py | 36 + .../pip/_internal/utils/entrypoints.py | 84 + .../pip/_internal/utils/filesystem.py | 149 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 101 + .../pip/_internal/utils/hashes.py | 147 + .../pip/_internal/utils/logging.py | 347 + .../site-packages/pip/_internal/utils/misc.py | 777 ++ .../pip/_internal/utils/packaging.py | 57 + .../pip/_internal/utils/retry.py | 42 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 245 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 337 + .../site-packages/pip/_internal/utils/urls.py | 55 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 134 + .../pip/_internal/vcs/__init__.py | 15 + .../vcs/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 516 bytes .../vcs/__pycache__/bazaar.cpython-312.pyc | Bin 0 -> 5037 bytes .../vcs/__pycache__/git.cpython-312.pyc | Bin 0 -> 19002 bytes .../vcs/__pycache__/mercurial.cpython-312.pyc | Bin 0 -> 7597 bytes .../__pycache__/subversion.cpython-312.pyc | Bin 0 -> 12505 bytes .../versioncontrol.cpython-312.pyc | Bin 0 -> 28978 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 527 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 688 ++ .../pip/_internal/wheel_builder.py | 354 + .../Lib/site-packages/pip/_vendor/__init__.py | 116 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4535 bytes .../typing_extensions.cpython-312.pyc | Bin 0 -> 139393 bytes .../pip/_vendor/cachecontrol/__init__.py | 28 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 888 bytes .../__pycache__/_cmd.cpython-312.pyc | Bin 0 -> 2632 bytes .../__pycache__/adapter.cpython-312.pyc | Bin 0 -> 6450 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 3795 bytes .../__pycache__/controller.cpython-312.pyc | Bin 0 -> 16205 bytes .../__pycache__/filewrapper.cpython-312.pyc | Bin 0 -> 4333 bytes .../__pycache__/heuristics.cpython-312.pyc | Bin 0 -> 6680 bytes .../__pycache__/serialize.cpython-312.pyc | Bin 0 -> 5241 bytes .../__pycache__/wrapper.cpython-312.pyc | Bin 0 -> 1660 bytes .../pip/_vendor/cachecontrol/_cmd.py | 70 + .../pip/_vendor/cachecontrol/adapter.py | 161 + .../pip/_vendor/cachecontrol/cache.py | 74 + .../_vendor/cachecontrol/caches/__init__.py | 8 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 421 bytes .../__pycache__/file_cache.cpython-312.pyc | Bin 0 -> 7774 bytes .../__pycache__/redis_cache.cpython-312.pyc | Bin 0 -> 2719 bytes .../_vendor/cachecontrol/caches/file_cache.py | 182 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 499 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 154 + .../pip/_vendor/cachecontrol/py.typed | 0 .../pip/_vendor/cachecontrol/serialize.py | 146 + .../pip/_vendor/cachecontrol/wrapper.py | 43 + .../pip/_vendor/certifi/__init__.py | 4 + .../pip/_vendor/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 304 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 631 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 3206 bytes .../pip/_vendor/certifi/cacert.pem | 4798 +++++++++ .../site-packages/pip/_vendor/certifi/core.py | 114 + .../pip/_vendor/certifi/py.typed | 0 .../pip/_vendor/distlib/__init__.py | 33 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1255 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 45501 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 65791 bytes .../distlib/__pycache__/index.cpython-312.pyc | Bin 0 -> 24332 bytes .../__pycache__/locators.cpython-312.pyc | Bin 0 -> 59933 bytes .../__pycache__/manifest.cpython-312.pyc | Bin 0 -> 15050 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 7658 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 41638 bytes .../__pycache__/resources.cpython-312.pyc | Bin 0 -> 17306 bytes .../__pycache__/scripts.cpython-312.pyc | Bin 0 -> 19766 bytes .../distlib/__pycache__/util.cpython-312.pyc | Bin 0 -> 88012 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 30299 bytes .../distlib/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 51653 bytes .../pip/_vendor/distlib/compat.py | 1138 +++ .../pip/_vendor/distlib/database.py | 1359 +++ .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1303 +++ .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 167 + .../pip/_vendor/distlib/metadata.py | 1068 ++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 466 + .../site-packages/pip/_vendor/distlib/t32.exe | Bin 0 -> 97792 bytes .../pip/_vendor/distlib/t64-arm.exe | Bin 0 -> 182784 bytes .../site-packages/pip/_vendor/distlib/t64.exe | Bin 0 -> 108032 bytes .../site-packages/pip/_vendor/distlib/util.py | 2025 ++++ .../pip/_vendor/distlib/version.py | 751 ++ .../site-packages/pip/_vendor/distlib/w32.exe | Bin 0 -> 91648 bytes .../pip/_vendor/distlib/w64-arm.exe | Bin 0 -> 168448 bytes .../site-packages/pip/_vendor/distlib/w64.exe | Bin 0 -> 101888 bytes .../pip/_vendor/distlib/wheel.py | 1099 +++ .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 946 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 278 bytes .../distro/__pycache__/distro.cpython-312.pyc | Bin 0 -> 53804 bytes .../pip/_vendor/distro/distro.py | 1403 +++ .../site-packages/pip/_vendor/distro/py.typed | 0 .../pip/_vendor/idna/__init__.py | 44 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 867 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 4962 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 873 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 15777 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99462 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2619 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 202 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158834 bytes .../site-packages/pip/_vendor/idna/codec.py | 118 + .../site-packages/pip/_vendor/idna/compat.py | 13 + .../site-packages/pip/_vendor/idna/core.py | 395 + .../pip/_vendor/idna/idnadata.py | 4245 ++++++++ .../pip/_vendor/idna/intranges.py | 54 + .../pip/_vendor/idna/package_data.py | 2 + .../site-packages/pip/_vendor/idna/py.typed | 0 .../pip/_vendor/idna/uts46data.py | 8598 ++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 55 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1727 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2011 bytes .../msgpack/__pycache__/ext.cpython-312.pyc | Bin 0 -> 8156 bytes .../__pycache__/fallback.cpython-312.pyc | Bin 0 -> 42028 bytes .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 168 + .../pip/_vendor/msgpack/fallback.py | 951 ++ .../pip/_vendor/packaging/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 544 bytes .../__pycache__/_elffile.cpython-312.pyc | Bin 0 -> 4953 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 9697 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 4541 bytes .../__pycache__/_parser.cpython-312.pyc | Bin 0 -> 13986 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3227 bytes .../__pycache__/_tokenizer.cpython-312.pyc | Bin 0 -> 7897 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 10990 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 24926 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 4396 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 38719 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 21328 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 7325 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 19486 bytes .../pip/_vendor/packaging/_elffile.py | 110 + .../pip/_vendor/packaging/_manylinux.py | 262 + .../pip/_vendor/packaging/_musllinux.py | 85 + .../pip/_vendor/packaging/_parser.py | 354 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/_tokenizer.py | 194 + .../pip/_vendor/packaging/markers.py | 325 + .../pip/_vendor/packaging/metadata.py | 804 ++ .../pip/_vendor/packaging/py.typed | 0 .../pip/_vendor/packaging/requirements.py | 91 + .../pip/_vendor/packaging/specifiers.py | 1009 ++ .../pip/_vendor/packaging/tags.py | 568 ++ .../pip/_vendor/packaging/utils.py | 174 + .../pip/_vendor/packaging/version.py | 563 ++ .../pip/_vendor/pkg_resources/__init__.py | 3676 +++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161289 bytes .../pip/_vendor/platformdirs/__init__.py | 627 ++ .../pip/_vendor/platformdirs/__main__.py | 55 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 19815 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 1934 bytes .../__pycache__/android.cpython-312.pyc | Bin 0 -> 10683 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 12897 bytes .../__pycache__/macos.cpython-312.pyc | Bin 0 -> 7993 bytes .../__pycache__/unix.cpython-312.pyc | Bin 0 -> 15018 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 583 bytes .../__pycache__/windows.cpython-312.pyc | Bin 0 -> 13659 bytes .../pip/_vendor/platformdirs/android.py | 249 + .../pip/_vendor/platformdirs/api.py | 292 + .../pip/_vendor/platformdirs/macos.py | 130 + .../pip/_vendor/platformdirs/py.typed | 0 .../pip/_vendor/platformdirs/unix.py | 275 + .../pip/_vendor/platformdirs/version.py | 16 + .../pip/_vendor/platformdirs/windows.py | 272 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3475 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 721 bytes .../__pycache__/cmdline.cpython-312.pyc | Bin 0 -> 26573 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 2615 bytes .../__pycache__/filter.cpython-312.pyc | Bin 0 -> 3208 bytes .../__pycache__/formatter.cpython-312.pyc | Bin 0 -> 4707 bytes .../__pycache__/lexer.cpython-312.pyc | Bin 0 -> 38347 bytes .../__pycache__/modeline.cpython-312.pyc | Bin 0 -> 1546 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 2595 bytes .../__pycache__/regexopt.cpython-312.pyc | Bin 0 -> 4059 bytes .../__pycache__/scanner.cpython-312.pyc | Bin 0 -> 4743 bytes .../__pycache__/sphinxext.cpython-312.pyc | Bin 0 -> 12075 bytes .../__pycache__/style.cpython-312.pyc | Bin 0 -> 6680 bytes .../__pycache__/token.cpython-312.pyc | Bin 0 -> 8176 bytes .../__pycache__/unistring.cpython-312.pyc | Bin 0 -> 32973 bytes .../pygments/__pycache__/util.cpython-312.pyc | Bin 0 -> 14050 bytes .../pip/_vendor/pygments/cmdline.py | 668 ++ .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 70 + .../pip/_vendor/pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 37894 bytes .../pip/_vendor/pygments/formatter.py | 129 + .../_vendor/pygments/formatters/__init__.py | 157 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6896 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 4202 bytes .../__pycache__/bbcode.cpython-312.pyc | Bin 0 -> 4209 bytes .../__pycache__/groff.cpython-312.pyc | Bin 0 -> 7280 bytes .../__pycache__/html.cpython-312.pyc | Bin 0 -> 41024 bytes .../__pycache__/img.cpython-312.pyc | Bin 0 -> 28535 bytes .../__pycache__/irc.cpython-312.pyc | Bin 0 -> 6042 bytes .../__pycache__/latex.cpython-312.pyc | Bin 0 -> 20107 bytes .../__pycache__/other.cpython-312.pyc | Bin 0 -> 6864 bytes .../__pycache__/pangomarkup.cpython-312.pyc | Bin 0 -> 2945 bytes .../__pycache__/rtf.cpython-312.pyc | Bin 0 -> 13760 bytes .../__pycache__/svg.cpython-312.pyc | Bin 0 -> 9126 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 5806 bytes .../__pycache__/terminal256.cpython-312.pyc | Bin 0 -> 15105 bytes .../_vendor/pygments/formatters/_mapping.py | 23 + .../pip/_vendor/pygments/formatters/bbcode.py | 108 + .../pip/_vendor/pygments/formatters/groff.py | 170 + .../pip/_vendor/pygments/formatters/html.py | 987 ++ .../pip/_vendor/pygments/formatters/img.py | 685 ++ .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 518 + .../pip/_vendor/pygments/formatters/other.py | 160 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 349 + .../pip/_vendor/pygments/formatters/svg.py | 185 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 963 ++ .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14615 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 68250 bytes .../lexers/__pycache__/python.cpython-312.pyc | Bin 0 -> 42953 bytes .../pip/_vendor/pygments/lexers/_mapping.py | 589 ++ .../pip/_vendor/pygments/lexers/python.py | 1198 +++ .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 72 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 247 + .../pip/_vendor/pygments/style.py | 203 + .../pip/_vendor/pygments/styles/__init__.py | 61 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2643 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 3635 bytes .../pip/_vendor/pygments/styles/_mapping.py | 54 + .../pip/_vendor/pygments/token.py | 214 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 324 + .../pip/_vendor/pyproject_hooks/__init__.py | 23 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 600 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 361 bytes .../__pycache__/_impl.cpython-312.pyc | Bin 0 -> 14712 bytes .../pip/_vendor/pyproject_hooks/_compat.py | 8 + .../pip/_vendor/pyproject_hooks/_impl.py | 330 + .../pyproject_hooks/_in_process/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1067 bytes .../__pycache__/_in_process.cpython-312.pyc | Bin 0 -> 14374 bytes .../_in_process/_in_process.py | 353 + .../pip/_vendor/requests/__init__.py | 179 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5240 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 571 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2011 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 28414 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7191 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13908 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 909 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 1664 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25184 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7585 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4215 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1034 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35410 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 1253 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27827 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 6008 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5604 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36367 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 719 ++ .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 314 + .../pip/_vendor/requests/certs.py | 24 + .../pip/_vendor/requests/compat.py | 78 + .../pip/_vendor/requests/cookies.py | 561 ++ .../pip/_vendor/requests/exceptions.py | 151 + .../pip/_vendor/requests/help.py | 127 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1037 ++ .../pip/_vendor/requests/packages.py | 25 + .../pip/_vendor/requests/sessions.py | 831 ++ .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1096 +++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 621 bytes .../__pycache__/providers.cpython-312.pyc | Bin 0 -> 6838 bytes .../__pycache__/reporters.cpython-312.pyc | Bin 0 -> 2641 bytes .../__pycache__/resolvers.cpython-312.pyc | Bin 0 -> 25835 bytes .../__pycache__/structs.cpython-312.pyc | Bin 0 -> 10473 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 187 bytes .../collections_abc.cpython-312.pyc | Bin 0 -> 407 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/py.typed | 0 .../pip/_vendor/resolvelib/reporters.py | 43 + .../pip/_vendor/resolvelib/resolvers.py | 547 ++ .../pip/_vendor/resolvelib/structs.py | 170 + .../pip/_vendor/rich/__init__.py | 177 + .../pip/_vendor/rich/__main__.py | 273 + .../rich/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7002 bytes .../rich/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 10279 bytes .../__pycache__/_cell_widths.cpython-312.pyc | Bin 0 -> 7859 bytes .../__pycache__/_emoji_codes.cpython-312.pyc | Bin 0 -> 205963 bytes .../_emoji_replace.cpython-312.pyc | Bin 0 -> 1716 bytes .../_export_format.cpython-312.pyc | Bin 0 -> 2336 bytes .../__pycache__/_extension.cpython-312.pyc | Bin 0 -> 524 bytes .../rich/__pycache__/_fileno.cpython-312.pyc | Bin 0 -> 842 bytes .../rich/__pycache__/_inspect.cpython-312.pyc | Bin 0 -> 12052 bytes .../__pycache__/_log_render.cpython-312.pyc | Bin 0 -> 4134 bytes .../rich/__pycache__/_loop.cpython-312.pyc | Bin 0 -> 1857 bytes .../__pycache__/_null_file.cpython-312.pyc | Bin 0 -> 3607 bytes .../__pycache__/_palettes.cpython-312.pyc | Bin 0 -> 5147 bytes .../rich/__pycache__/_pick.cpython-312.pyc | Bin 0 -> 708 bytes .../rich/__pycache__/_ratio.cpython-312.pyc | Bin 0 -> 6544 bytes .../__pycache__/_spinners.cpython-312.pyc | Bin 0 -> 13166 bytes .../rich/__pycache__/_stack.cpython-312.pyc | Bin 0 -> 952 bytes .../rich/__pycache__/_timer.cpython-312.pyc | Bin 0 -> 852 bytes .../_win32_console.cpython-312.pyc | Bin 0 -> 28963 bytes .../rich/__pycache__/_windows.cpython-312.pyc | Bin 0 -> 2477 bytes .../_windows_renderer.cpython-312.pyc | Bin 0 -> 3550 bytes .../rich/__pycache__/_wrap.cpython-312.pyc | Bin 0 -> 3313 bytes .../rich/__pycache__/abc.cpython-312.pyc | Bin 0 -> 1595 bytes .../rich/__pycache__/align.cpython-312.pyc | Bin 0 -> 12274 bytes .../rich/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 9068 bytes .../rich/__pycache__/bar.cpython-312.pyc | Bin 0 -> 4259 bytes .../rich/__pycache__/box.cpython-312.pyc | Bin 0 -> 11825 bytes .../rich/__pycache__/cells.cpython-312.pyc | Bin 0 -> 5792 bytes .../rich/__pycache__/color.cpython-312.pyc | Bin 0 -> 26552 bytes .../__pycache__/color_triplet.cpython-312.pyc | Bin 0 -> 1688 bytes .../rich/__pycache__/columns.cpython-312.pyc | Bin 0 -> 8541 bytes .../rich/__pycache__/console.cpython-312.pyc | Bin 0 -> 113585 bytes .../__pycache__/constrain.cpython-312.pyc | Bin 0 -> 2245 bytes .../__pycache__/containers.cpython-312.pyc | Bin 0 -> 9189 bytes .../rich/__pycache__/control.cpython-312.pyc | Bin 0 -> 10921 bytes .../default_styles.cpython-312.pyc | Bin 0 -> 10353 bytes .../rich/__pycache__/diagnose.cpython-312.pyc | Bin 0 -> 1474 bytes .../rich/__pycache__/emoji.cpython-312.pyc | Bin 0 -> 4196 bytes .../rich/__pycache__/errors.cpython-312.pyc | Bin 0 -> 1832 bytes .../__pycache__/file_proxy.cpython-312.pyc | Bin 0 -> 3564 bytes .../rich/__pycache__/filesize.cpython-312.pyc | Bin 0 -> 3059 bytes .../__pycache__/highlighter.cpython-312.pyc | Bin 0 -> 9874 bytes .../rich/__pycache__/json.cpython-312.pyc | Bin 0 -> 6022 bytes .../rich/__pycache__/jupyter.cpython-312.pyc | Bin 0 -> 5191 bytes .../rich/__pycache__/layout.cpython-312.pyc | Bin 0 -> 20164 bytes .../rich/__pycache__/live.cpython-312.pyc | Bin 0 -> 19116 bytes .../__pycache__/live_render.cpython-312.pyc | Bin 0 -> 4876 bytes .../rich/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13541 bytes .../rich/__pycache__/markup.cpython-312.pyc | Bin 0 -> 9554 bytes .../rich/__pycache__/measure.cpython-312.pyc | Bin 0 -> 6363 bytes .../rich/__pycache__/padding.cpython-312.pyc | Bin 0 -> 7111 bytes .../rich/__pycache__/pager.cpython-312.pyc | Bin 0 -> 1807 bytes .../rich/__pycache__/palette.cpython-312.pyc | Bin 0 -> 5284 bytes .../rich/__pycache__/panel.cpython-312.pyc | Bin 0 -> 12170 bytes .../rich/__pycache__/pretty.cpython-312.pyc | Bin 0 -> 40138 bytes .../rich/__pycache__/progress.cpython-312.pyc | Bin 0 -> 75089 bytes .../__pycache__/progress_bar.cpython-312.pyc | Bin 0 -> 10364 bytes .../rich/__pycache__/prompt.cpython-312.pyc | Bin 0 -> 14774 bytes .../rich/__pycache__/protocol.cpython-312.pyc | Bin 0 -> 1779 bytes .../rich/__pycache__/region.cpython-312.pyc | Bin 0 -> 554 bytes .../rich/__pycache__/repr.cpython-312.pyc | Bin 0 -> 6600 bytes .../rich/__pycache__/rule.cpython-312.pyc | Bin 0 -> 6555 bytes .../rich/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3812 bytes .../rich/__pycache__/screen.cpython-312.pyc | Bin 0 -> 2466 bytes .../rich/__pycache__/segment.cpython-312.pyc | Bin 0 -> 28091 bytes .../rich/__pycache__/spinner.cpython-312.pyc | Bin 0 -> 6051 bytes .../rich/__pycache__/status.cpython-312.pyc | Bin 0 -> 6055 bytes .../rich/__pycache__/style.cpython-312.pyc | Bin 0 -> 33481 bytes .../rich/__pycache__/styled.cpython-312.pyc | Bin 0 -> 2126 bytes .../rich/__pycache__/syntax.cpython-312.pyc | Bin 0 -> 39928 bytes .../rich/__pycache__/table.cpython-312.pyc | Bin 0 -> 43502 bytes .../terminal_theme.cpython-312.pyc | Bin 0 -> 3335 bytes .../rich/__pycache__/text.cpython-312.pyc | Bin 0 -> 60772 bytes .../rich/__pycache__/theme.cpython-312.pyc | Bin 0 -> 6327 bytes .../rich/__pycache__/themes.cpython-312.pyc | Bin 0 -> 301 bytes .../__pycache__/traceback.cpython-312.pyc | Bin 0 -> 31502 bytes .../rich/__pycache__/tree.cpython-312.pyc | Bin 0 -> 11422 bytes .../pip/_vendor/rich/_cell_widths.py | 454 + .../pip/_vendor/rich/_emoji_codes.py | 3610 +++++++ .../pip/_vendor/rich/_emoji_replace.py | 32 + .../pip/_vendor/rich/_export_format.py | 76 + .../pip/_vendor/rich/_extension.py | 10 + .../site-packages/pip/_vendor/rich/_fileno.py | 24 + .../pip/_vendor/rich/_inspect.py | 270 + .../pip/_vendor/rich/_log_render.py | 94 + .../site-packages/pip/_vendor/rich/_loop.py | 43 + .../pip/_vendor/rich/_null_file.py | 69 + .../pip/_vendor/rich/_palettes.py | 309 + .../site-packages/pip/_vendor/rich/_pick.py | 17 + .../site-packages/pip/_vendor/rich/_ratio.py | 159 + .../pip/_vendor/rich/_spinners.py | 482 + .../site-packages/pip/_vendor/rich/_stack.py | 16 + .../site-packages/pip/_vendor/rich/_timer.py | 19 + .../pip/_vendor/rich/_win32_console.py | 662 ++ .../pip/_vendor/rich/_windows.py | 71 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 93 + .../Lib/site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 311 + .../site-packages/pip/_vendor/rich/ansi.py | 240 + .../Lib/site-packages/pip/_vendor/rich/bar.py | 93 + .../Lib/site-packages/pip/_vendor/rich/box.py | 480 + .../site-packages/pip/_vendor/rich/cells.py | 167 + .../site-packages/pip/_vendor/rich/color.py | 621 ++ .../pip/_vendor/rich/color_triplet.py | 38 + .../site-packages/pip/_vendor/rich/columns.py | 187 + .../site-packages/pip/_vendor/rich/console.py | 2633 +++++ .../pip/_vendor/rich/constrain.py | 37 + .../pip/_vendor/rich/containers.py | 167 + .../site-packages/pip/_vendor/rich/control.py | 225 + .../pip/_vendor/rich/default_styles.py | 190 + .../pip/_vendor/rich/diagnose.py | 37 + .../site-packages/pip/_vendor/rich/emoji.py | 96 + .../site-packages/pip/_vendor/rich/errors.py | 34 + .../pip/_vendor/rich/file_proxy.py | 57 + .../pip/_vendor/rich/filesize.py | 89 + .../pip/_vendor/rich/highlighter.py | 232 + .../site-packages/pip/_vendor/rich/json.py | 139 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 442 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 112 + .../site-packages/pip/_vendor/rich/logging.py | 289 + .../site-packages/pip/_vendor/rich/markup.py | 251 + .../site-packages/pip/_vendor/rich/measure.py | 151 + .../site-packages/pip/_vendor/rich/padding.py | 141 + .../site-packages/pip/_vendor/rich/pager.py | 34 + .../site-packages/pip/_vendor/rich/palette.py | 100 + .../site-packages/pip/_vendor/rich/panel.py | 312 + .../site-packages/pip/_vendor/rich/pretty.py | 995 ++ .../pip/_vendor/rich/progress.py | 1699 ++++ .../pip/_vendor/rich/progress_bar.py | 223 + .../site-packages/pip/_vendor/rich/prompt.py | 375 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/py.typed | 0 .../site-packages/pip/_vendor/rich/region.py | 10 + .../site-packages/pip/_vendor/rich/repr.py | 149 + .../site-packages/pip/_vendor/rich/rule.py | 130 + .../site-packages/pip/_vendor/rich/scope.py | 86 + .../site-packages/pip/_vendor/rich/screen.py | 54 + .../site-packages/pip/_vendor/rich/segment.py | 738 ++ .../site-packages/pip/_vendor/rich/spinner.py | 137 + .../site-packages/pip/_vendor/rich/status.py | 131 + .../site-packages/pip/_vendor/rich/style.py | 796 ++ .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 958 ++ .../site-packages/pip/_vendor/rich/table.py | 1000 ++ .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1357 +++ .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 753 ++ .../site-packages/pip/_vendor/rich/tree.py | 249 + .../pip/_vendor/tomli/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 371 bytes .../tomli/__pycache__/_parser.cpython-312.pyc | Bin 0 -> 26882 bytes .../tomli/__pycache__/_re.cpython-312.pyc | Bin 0 -> 3895 bytes .../tomli/__pycache__/_types.cpython-312.pyc | Bin 0 -> 353 bytes .../pip/_vendor/tomli/_parser.py | 691 ++ .../site-packages/pip/_vendor/tomli/_re.py | 107 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../site-packages/pip/_vendor/tomli/py.typed | 1 + .../pip/_vendor/truststore/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 605 bytes .../__pycache__/_api.cpython-312.pyc | Bin 0 -> 16528 bytes .../__pycache__/_macos.cpython-312.pyc | Bin 0 -> 16559 bytes .../__pycache__/_openssl.cpython-312.pyc | Bin 0 -> 2192 bytes .../_ssl_constants.cpython-312.pyc | Bin 0 -> 1086 bytes .../__pycache__/_windows.cpython-312.pyc | Bin 0 -> 15727 bytes .../pip/_vendor/truststore/_api.py | 313 + .../pip/_vendor/truststore/_macos.py | 499 + .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 564 ++ .../pip/_vendor/truststore/py.typed | 0 .../pip/_vendor/typing_extensions.py | 3641 +++++++ .../pip/_vendor/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3392 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 16410 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 205 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 20384 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 36424 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 13480 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 10377 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 3995 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 20424 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 7281 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 33936 bytes .../pip/_vendor/urllib3/_collections.py | 355 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 ++ .../pip/_vendor/urllib3/connectionpool.py | 1137 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 185 bytes .../_appengine_environ.cpython-312.pyc | Bin 0 -> 1835 bytes .../__pycache__/appengine.cpython-312.pyc | Bin 0 -> 11551 bytes .../__pycache__/ntlmpool.cpython-312.pyc | Bin 0 -> 5701 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 24428 bytes .../securetransport.cpython-312.pyc | Bin 0 -> 35540 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 7498 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 202 bytes .../__pycache__/bindings.cpython-312.pyc | Bin 0 -> 17414 bytes .../__pycache__/low_level.cpython-312.pyc | Bin 0 -> 14751 bytes .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../pip/_vendor/urllib3/contrib/appengine.py | 314 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 130 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 518 + .../urllib3/contrib/securetransport.py | 920 ++ .../pip/_vendor/urllib3/contrib/socks.py | 216 + .../pip/_vendor/urllib3/exceptions.py | 323 + .../pip/_vendor/urllib3/fields.py | 274 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 186 bytes .../packages/__pycache__/six.cpython-312.pyc | Bin 0 -> 41233 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 196 bytes .../__pycache__/makefile.cpython-312.pyc | Bin 0 -> 1812 bytes .../weakref_finalize.cpython-312.pyc | Bin 0 -> 7316 bytes .../urllib3/packages/backports/makefile.py | 51 + .../packages/backports/weakref_finalize.py | 155 + .../pip/_vendor/urllib3/packages/six.py | 1076 ++ .../pip/_vendor/urllib3/poolmanager.py | 540 + .../pip/_vendor/urllib3/request.py | 191 + .../pip/_vendor/urllib3/response.py | 879 ++ .../pip/_vendor/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1133 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4734 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1539 bytes .../util/__pycache__/queue.cpython-312.pyc | Bin 0 -> 1339 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 4170 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 2976 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 21682 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 15078 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5038 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 10759 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11126 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 15763 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 4390 bytes .../pip/_vendor/urllib3/util/connection.py | 149 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 137 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 620 ++ .../pip/_vendor/urllib3/util/ssl_.py | 495 + .../urllib3/util/ssl_match_hostname.py | 159 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 271 + .../pip/_vendor/urllib3/util/url.py | 435 + .../pip/_vendor/urllib3/util/wait.py | 152 + venv/Lib/site-packages/pip/_vendor/vendor.txt | 18 + venv/Lib/site-packages/pip/py.typed | 4 + .../pluggy-1.6.0.dist-info/INSTALLER | 1 + .../pluggy-1.6.0.dist-info/METADATA | 152 + .../pluggy-1.6.0.dist-info/RECORD | 23 + .../pluggy-1.6.0.dist-info/WHEEL | 5 + .../pluggy-1.6.0.dist-info/licenses/LICENSE | 21 + .../pluggy-1.6.0.dist-info/top_level.txt | 1 + venv/Lib/site-packages/pluggy/__init__.py | 30 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 840 bytes .../__pycache__/_callers.cpython-312.pyc | Bin 0 -> 6992 bytes .../pluggy/__pycache__/_hooks.cpython-312.pyc | Bin 0 -> 27389 bytes .../__pycache__/_manager.cpython-312.pyc | Bin 0 -> 25549 bytes .../__pycache__/_result.cpython-312.pyc | Bin 0 -> 4127 bytes .../__pycache__/_tracing.cpython-312.pyc | Bin 0 -> 3999 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 625 bytes .../__pycache__/_warnings.cpython-312.pyc | Bin 0 -> 1278 bytes venv/Lib/site-packages/pluggy/_callers.py | 169 + venv/Lib/site-packages/pluggy/_hooks.py | 714 ++ venv/Lib/site-packages/pluggy/_manager.py | 523 + venv/Lib/site-packages/pluggy/_result.py | 107 + venv/Lib/site-packages/pluggy/_tracing.py | 72 + venv/Lib/site-packages/pluggy/_version.py | 21 + venv/Lib/site-packages/pluggy/_warnings.py | 27 + venv/Lib/site-packages/pluggy/py.typed | 0 venv/Lib/site-packages/py.py | 15 + .../pygments-2.19.2.dist-info/INSTALLER | 1 + .../pygments-2.19.2.dist-info/METADATA | 58 + .../pygments-2.19.2.dist-info/RECORD | 684 ++ .../pygments-2.19.2.dist-info/WHEEL | 4 + .../entry_points.txt | 2 + .../licenses/AUTHORS | 291 + .../licenses/LICENSE | 25 + venv/Lib/site-packages/pygments/__init__.py | 82 + venv/Lib/site-packages/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3439 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 758 bytes .../__pycache__/cmdline.cpython-312.pyc | Bin 0 -> 26441 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 2603 bytes .../__pycache__/filter.cpython-312.pyc | Bin 0 -> 3196 bytes .../__pycache__/formatter.cpython-312.pyc | Bin 0 -> 4671 bytes .../__pycache__/lexer.cpython-312.pyc | Bin 0 -> 38625 bytes .../__pycache__/modeline.cpython-312.pyc | Bin 0 -> 1534 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 2583 bytes .../__pycache__/regexopt.cpython-312.pyc | Bin 0 -> 4047 bytes .../__pycache__/scanner.cpython-312.pyc | Bin 0 -> 4731 bytes .../__pycache__/sphinxext.cpython-312.pyc | Bin 0 -> 12033 bytes .../__pycache__/style.cpython-312.pyc | Bin 0 -> 6656 bytes .../__pycache__/token.cpython-312.pyc | Bin 0 -> 8164 bytes .../__pycache__/unistring.cpython-312.pyc | Bin 0 -> 32961 bytes .../pygments/__pycache__/util.cpython-312.pyc | Bin 0 -> 14038 bytes venv/Lib/site-packages/pygments/cmdline.py | 668 ++ venv/Lib/site-packages/pygments/console.py | 70 + venv/Lib/site-packages/pygments/filter.py | 70 + .../pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 37834 bytes venv/Lib/site-packages/pygments/formatter.py | 129 + .../pygments/formatters/__init__.py | 157 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6848 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 4190 bytes .../__pycache__/bbcode.cpython-312.pyc | Bin 0 -> 4173 bytes .../__pycache__/groff.cpython-312.pyc | Bin 0 -> 7244 bytes .../__pycache__/html.cpython-312.pyc | Bin 0 -> 41292 bytes .../__pycache__/img.cpython-312.pyc | Bin 0 -> 28559 bytes .../__pycache__/irc.cpython-312.pyc | Bin 0 -> 5994 bytes .../__pycache__/latex.cpython-312.pyc | Bin 0 -> 20047 bytes .../__pycache__/other.cpython-312.pyc | Bin 0 -> 6804 bytes .../__pycache__/pangomarkup.cpython-312.pyc | Bin 0 -> 2921 bytes .../__pycache__/rtf.cpython-312.pyc | Bin 0 -> 13712 bytes .../__pycache__/svg.cpython-312.pyc | Bin 0 -> 9078 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 5746 bytes .../__pycache__/terminal256.cpython-312.pyc | Bin 0 -> 15057 bytes .../pygments/formatters/_mapping.py | 23 + .../pygments/formatters/bbcode.py | 108 + .../pygments/formatters/groff.py | 170 + .../site-packages/pygments/formatters/html.py | 995 ++ .../site-packages/pygments/formatters/img.py | 686 ++ .../site-packages/pygments/formatters/irc.py | 154 + .../pygments/formatters/latex.py | 518 + .../pygments/formatters/other.py | 160 + .../pygments/formatters/pangomarkup.py | 83 + .../site-packages/pygments/formatters/rtf.py | 349 + .../site-packages/pygments/formatters/svg.py | 185 + .../pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + venv/Lib/site-packages/pygments/lexer.py | 961 ++ .../site-packages/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14555 bytes .../__pycache__/_ada_builtins.cpython-312.pyc | Bin 0 -> 1220 bytes .../__pycache__/_asy_builtins.cpython-312.pyc | Bin 0 -> 17613 bytes .../__pycache__/_cl_builtins.cpython-312.pyc | Bin 0 -> 11649 bytes .../_cocoa_builtins.cpython-312.pyc | Bin 0 -> 97518 bytes .../_csound_builtins.cpython-312.pyc | Bin 0 -> 16361 bytes .../__pycache__/_css_builtins.cpython-312.pyc | Bin 0 -> 9370 bytes .../_googlesql_builtins.cpython-312.pyc | Bin 0 -> 10808 bytes .../_julia_builtins.cpython-312.pyc | Bin 0 -> 8235 bytes .../_lasso_builtins.cpython-312.pyc | Bin 0 -> 76713 bytes .../_lilypond_builtins.cpython-312.pyc | Bin 0 -> 88391 bytes .../__pycache__/_lua_builtins.cpython-312.pyc | Bin 0 -> 8345 bytes .../_luau_builtins.cpython-312.pyc | Bin 0 -> 1034 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 66936 bytes .../__pycache__/_mql_builtins.cpython-312.pyc | Bin 0 -> 17994 bytes .../_mysql_builtins.cpython-312.pyc | Bin 0 -> 19524 bytes .../_openedge_builtins.cpython-312.pyc | Bin 0 -> 34076 bytes .../__pycache__/_php_builtins.cpython-312.pyc | Bin 0 -> 65490 bytes .../_postgres_builtins.cpython-312.pyc | Bin 0 -> 11248 bytes .../_qlik_builtins.cpython-312.pyc | Bin 0 -> 6361 bytes .../_scheme_builtins.cpython-312.pyc | Bin 0 -> 23158 bytes .../_scilab_builtins.cpython-312.pyc | Bin 0 -> 35195 bytes .../_sourcemod_builtins.cpython-312.pyc | Bin 0 -> 21808 bytes .../__pycache__/_sql_builtins.cpython-312.pyc | Bin 0 -> 5552 bytes .../_stan_builtins.cpython-312.pyc | Bin 0 -> 9937 bytes .../_stata_builtins.cpython-312.pyc | Bin 0 -> 21221 bytes .../_tsql_builtins.cpython-312.pyc | Bin 0 -> 8853 bytes .../__pycache__/_usd_builtins.cpython-312.pyc | Bin 0 -> 1383 bytes .../_vbscript_builtins.cpython-312.pyc | Bin 0 -> 2912 bytes .../__pycache__/_vim_builtins.cpython-312.pyc | Bin 0 -> 30715 bytes .../__pycache__/actionscript.cpython-312.pyc | Bin 0 -> 11100 bytes .../lexers/__pycache__/ada.cpython-312.pyc | Bin 0 -> 5496 bytes .../lexers/__pycache__/agile.cpython-312.pyc | Bin 0 -> 1291 bytes .../__pycache__/algebra.cpython-312.pyc | Bin 0 -> 10975 bytes .../__pycache__/ambient.cpython-312.pyc | Bin 0 -> 3108 bytes .../lexers/__pycache__/amdgpu.cpython-312.pyc | Bin 0 -> 2231 bytes .../lexers/__pycache__/ampl.cpython-312.pyc | Bin 0 -> 4079 bytes .../__pycache__/apdlexer.cpython-312.pyc | Bin 0 -> 19020 bytes .../lexers/__pycache__/apl.cpython-312.pyc | Bin 0 -> 2500 bytes .../__pycache__/archetype.cpython-312.pyc | Bin 0 -> 9172 bytes .../lexers/__pycache__/arrow.cpython-312.pyc | Bin 0 -> 3550 bytes .../lexers/__pycache__/arturo.cpython-312.pyc | Bin 0 -> 9727 bytes .../lexers/__pycache__/asc.cpython-312.pyc | Bin 0 -> 2030 bytes .../lexers/__pycache__/asm.cpython-312.pyc | Bin 0 -> 35994 bytes .../lexers/__pycache__/asn1.cpython-312.pyc | Bin 0 -> 4471 bytes .../__pycache__/automation.cpython-312.pyc | Bin 0 -> 18398 bytes .../lexers/__pycache__/bare.cpython-312.pyc | Bin 0 -> 2857 bytes .../lexers/__pycache__/basic.cpython-312.pyc | Bin 0 -> 26954 bytes .../lexers/__pycache__/bdd.cpython-312.pyc | Bin 0 -> 2066 bytes .../lexers/__pycache__/berry.cpython-312.pyc | Bin 0 -> 3540 bytes .../lexers/__pycache__/bibtex.cpython-312.pyc | Bin 0 -> 5206 bytes .../__pycache__/blueprint.cpython-312.pyc | Bin 0 -> 5294 bytes .../lexers/__pycache__/boa.cpython-312.pyc | Bin 0 -> 3516 bytes .../lexers/__pycache__/bqn.cpython-312.pyc | Bin 0 -> 2530 bytes .../__pycache__/business.cpython-312.pyc | Bin 0 -> 22047 bytes .../lexers/__pycache__/c_cpp.cpython-312.pyc | Bin 0 -> 16076 bytes .../lexers/__pycache__/c_like.cpython-312.pyc | Bin 0 -> 27537 bytes .../__pycache__/capnproto.cpython-312.pyc | Bin 0 -> 2389 bytes .../lexers/__pycache__/carbon.cpython-312.pyc | Bin 0 -> 3527 bytes .../lexers/__pycache__/cddl.cpython-312.pyc | Bin 0 -> 4219 bytes .../lexers/__pycache__/chapel.cpython-312.pyc | Bin 0 -> 4253 bytes .../lexers/__pycache__/clean.cpython-312.pyc | Bin 0 -> 6062 bytes .../lexers/__pycache__/codeql.cpython-312.pyc | Bin 0 -> 2735 bytes .../lexers/__pycache__/comal.cpython-312.pyc | Bin 0 -> 3209 bytes .../__pycache__/compiled.cpython-312.pyc | Bin 0 -> 1982 bytes .../__pycache__/configs.cpython-312.pyc | Bin 0 -> 44483 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 4232 bytes .../lexers/__pycache__/cplint.cpython-312.pyc | Bin 0 -> 1744 bytes .../__pycache__/crystal.cpython-312.pyc | Bin 0 -> 15132 bytes .../lexers/__pycache__/csound.cpython-312.pyc | Bin 0 -> 14103 bytes .../lexers/__pycache__/css.cpython-312.pyc | Bin 0 -> 22079 bytes .../lexers/__pycache__/d.cpython-312.pyc | Bin 0 -> 8318 bytes .../lexers/__pycache__/dalvik.cpython-312.pyc | Bin 0 -> 4528 bytes .../lexers/__pycache__/data.cpython-312.pyc | Bin 0 -> 21136 bytes .../lexers/__pycache__/dax.cpython-312.pyc | Bin 0 -> 6229 bytes .../__pycache__/devicetree.cpython-312.pyc | Bin 0 -> 4029 bytes .../lexers/__pycache__/diff.cpython-312.pyc | Bin 0 -> 5664 bytes .../lexers/__pycache__/dns.cpython-312.pyc | Bin 0 -> 3763 bytes .../lexers/__pycache__/dotnet.cpython-312.pyc | Bin 0 -> 35197 bytes .../lexers/__pycache__/dsls.cpython-312.pyc | Bin 0 -> 33774 bytes .../lexers/__pycache__/dylan.cpython-312.pyc | Bin 0 -> 9709 bytes .../lexers/__pycache__/ecl.cpython-312.pyc | Bin 0 -> 5572 bytes .../lexers/__pycache__/eiffel.cpython-312.pyc | Bin 0 -> 2979 bytes .../lexers/__pycache__/elm.cpython-312.pyc | Bin 0 -> 3223 bytes .../lexers/__pycache__/elpi.cpython-312.pyc | Bin 0 -> 7221 bytes .../lexers/__pycache__/email.cpython-312.pyc | Bin 0 -> 5969 bytes .../lexers/__pycache__/erlang.cpython-312.pyc | Bin 0 -> 20508 bytes .../__pycache__/esoteric.cpython-312.pyc | Bin 0 -> 9746 bytes .../lexers/__pycache__/ezhil.cpython-312.pyc | Bin 0 -> 3845 bytes .../lexers/__pycache__/factor.cpython-312.pyc | Bin 0 -> 16912 bytes .../lexers/__pycache__/fantom.cpython-312.pyc | Bin 0 -> 7981 bytes .../lexers/__pycache__/felix.cpython-312.pyc | Bin 0 -> 8236 bytes .../lexers/__pycache__/fift.cpython-312.pyc | Bin 0 -> 1948 bytes .../__pycache__/floscript.cpython-312.pyc | Bin 0 -> 2966 bytes .../lexers/__pycache__/forth.cpython-312.pyc | Bin 0 -> 5342 bytes .../__pycache__/fortran.cpython-312.pyc | Bin 0 -> 8699 bytes .../lexers/__pycache__/foxpro.cpython-312.pyc | Bin 0 -> 20796 bytes .../__pycache__/freefem.cpython-312.pyc | Bin 0 -> 12767 bytes .../lexers/__pycache__/func.cpython-312.pyc | Bin 0 -> 3322 bytes .../__pycache__/functional.cpython-312.pyc | Bin 0 -> 1032 bytes .../__pycache__/futhark.cpython-312.pyc | Bin 0 -> 4064 bytes .../__pycache__/gcodelexer.cpython-312.pyc | Bin 0 -> 1304 bytes .../__pycache__/gdscript.cpython-312.pyc | Bin 0 -> 7198 bytes .../lexers/__pycache__/gleam.cpython-312.pyc | Bin 0 -> 2696 bytes .../lexers/__pycache__/go.cpython-312.pyc | Bin 0 -> 3371 bytes .../grammar_notation.cpython-312.pyc | Bin 0 -> 7701 bytes .../lexers/__pycache__/graph.cpython-312.pyc | Bin 0 -> 3798 bytes .../__pycache__/graphics.cpython-312.pyc | Bin 0 -> 29684 bytes .../__pycache__/graphql.cpython-312.pyc | Bin 0 -> 4431 bytes .../__pycache__/graphviz.cpython-312.pyc | Bin 0 -> 2205 bytes .../lexers/__pycache__/gsql.cpython-312.pyc | Bin 0 -> 3765 bytes .../lexers/__pycache__/hare.cpython-312.pyc | Bin 0 -> 2938 bytes .../__pycache__/haskell.cpython-312.pyc | Bin 0 -> 30470 bytes .../lexers/__pycache__/haxe.cpython-312.pyc | Bin 0 -> 22268 bytes .../lexers/__pycache__/hdl.cpython-312.pyc | Bin 0 -> 17463 bytes .../__pycache__/hexdump.cpython-312.pyc | Bin 0 -> 3639 bytes .../lexers/__pycache__/html.cpython-312.pyc | Bin 0 -> 20713 bytes .../lexers/__pycache__/idl.cpython-312.pyc | Bin 0 -> 12457 bytes .../lexers/__pycache__/igor.cpython-312.pyc | Bin 0 -> 25682 bytes .../__pycache__/inferno.cpython-312.pyc | Bin 0 -> 3235 bytes .../__pycache__/installers.cpython-312.pyc | Bin 0 -> 13759 bytes .../__pycache__/int_fiction.cpython-312.pyc | Bin 0 -> 48065 bytes .../lexers/__pycache__/iolang.cpython-312.pyc | Bin 0 -> 2199 bytes .../lexers/__pycache__/j.cpython-312.pyc | Bin 0 -> 4291 bytes .../__pycache__/javascript.cpython-312.pyc | Bin 0 -> 56966 bytes .../__pycache__/jmespath.cpython-312.pyc | Bin 0 -> 2389 bytes .../lexers/__pycache__/jslt.cpython-312.pyc | Bin 0 -> 3747 bytes .../lexers/__pycache__/json5.cpython-312.pyc | Bin 0 -> 2869 bytes .../__pycache__/jsonnet.cpython-312.pyc | Bin 0 -> 4845 bytes .../lexers/__pycache__/jsx.cpython-312.pyc | Bin 0 -> 2901 bytes .../lexers/__pycache__/julia.cpython-312.pyc | Bin 0 -> 10918 bytes .../lexers/__pycache__/jvm.cpython-312.pyc | Bin 0 -> 63856 bytes .../lexers/__pycache__/kuin.cpython-312.pyc | Bin 0 -> 9869 bytes .../lexers/__pycache__/kusto.cpython-312.pyc | Bin 0 -> 2831 bytes .../lexers/__pycache__/ldap.cpython-312.pyc | Bin 0 -> 6415 bytes .../lexers/__pycache__/lean.cpython-312.pyc | Bin 0 -> 7980 bytes .../__pycache__/lilypond.cpython-312.pyc | Bin 0 -> 8360 bytes .../lexers/__pycache__/lisp.cpython-312.pyc | Bin 0 -> 121572 bytes .../__pycache__/macaulay2.cpython-312.pyc | Bin 0 -> 23144 bytes .../lexers/__pycache__/make.cpython-312.pyc | Bin 0 -> 6653 bytes .../lexers/__pycache__/maple.cpython-312.pyc | Bin 0 -> 4967 bytes .../lexers/__pycache__/markup.cpython-312.pyc | Bin 0 -> 60395 bytes .../lexers/__pycache__/math.cpython-312.pyc | Bin 0 -> 1028 bytes .../lexers/__pycache__/matlab.cpython-312.pyc | Bin 0 -> 55722 bytes .../lexers/__pycache__/maxima.cpython-312.pyc | Bin 0 -> 3176 bytes .../lexers/__pycache__/meson.cpython-312.pyc | Bin 0 -> 3509 bytes .../lexers/__pycache__/mime.cpython-312.pyc | Bin 0 -> 10105 bytes .../__pycache__/minecraft.cpython-312.pyc | Bin 0 -> 10684 bytes .../lexers/__pycache__/mips.cpython-312.pyc | Bin 0 -> 3423 bytes .../lexers/__pycache__/ml.cpython-312.pyc | Bin 0 -> 26154 bytes .../__pycache__/modeling.cpython-312.pyc | Bin 0 -> 12058 bytes .../__pycache__/modula2.cpython-312.pyc | Bin 0 -> 26406 bytes .../lexers/__pycache__/mojo.cpython-312.pyc | Bin 0 -> 14349 bytes .../lexers/__pycache__/monte.cpython-312.pyc | Bin 0 -> 5101 bytes .../lexers/__pycache__/mosel.cpython-312.pyc | Bin 0 -> 6950 bytes .../lexers/__pycache__/ncl.cpython-312.pyc | Bin 0 -> 45902 bytes .../lexers/__pycache__/nimrod.cpython-312.pyc | Bin 0 -> 6438 bytes .../lexers/__pycache__/nit.cpython-312.pyc | Bin 0 -> 2744 bytes .../lexers/__pycache__/nix.cpython-312.pyc | Bin 0 -> 5453 bytes .../__pycache__/numbair.cpython-312.pyc | Bin 0 -> 2119 bytes .../lexers/__pycache__/oberon.cpython-312.pyc | Bin 0 -> 3733 bytes .../__pycache__/objective.cpython-312.pyc | Bin 0 -> 19452 bytes .../lexers/__pycache__/ooc.cpython-312.pyc | Bin 0 -> 3104 bytes .../__pycache__/openscad.cpython-312.pyc | Bin 0 -> 3716 bytes .../lexers/__pycache__/other.cpython-312.pyc | Bin 0 -> 2438 bytes .../__pycache__/parasail.cpython-312.pyc | Bin 0 -> 2873 bytes .../__pycache__/parsers.cpython-312.pyc | Bin 0 -> 24388 bytes .../lexers/__pycache__/pascal.cpython-312.pyc | Bin 0 -> 23924 bytes .../lexers/__pycache__/pawn.cpython-312.pyc | Bin 0 -> 7848 bytes .../lexers/__pycache__/pddl.cpython-312.pyc | Bin 0 -> 2799 bytes .../lexers/__pycache__/perl.cpython-312.pyc | Bin 0 -> 38994 bytes .../lexers/__pycache__/phix.cpython-312.pyc | Bin 0 -> 18410 bytes .../lexers/__pycache__/php.cpython-312.pyc | Bin 0 -> 14297 bytes .../__pycache__/pointless.cpython-312.pyc | Bin 0 -> 2283 bytes .../lexers/__pycache__/pony.cpython-312.pyc | Bin 0 -> 3412 bytes .../lexers/__pycache__/praat.cpython-312.pyc | Bin 0 -> 10274 bytes .../__pycache__/procfile.cpython-312.pyc | Bin 0 -> 1617 bytes .../lexers/__pycache__/prolog.cpython-312.pyc | Bin 0 -> 10519 bytes .../lexers/__pycache__/promql.cpython-312.pyc | Bin 0 -> 3322 bytes .../lexers/__pycache__/prql.cpython-312.pyc | Bin 0 -> 8370 bytes .../lexers/__pycache__/ptx.cpython-312.pyc | Bin 0 -> 3758 bytes .../lexers/__pycache__/python.cpython-312.pyc | Bin 0 -> 42898 bytes .../lexers/__pycache__/q.cpython-312.pyc | Bin 0 -> 5832 bytes .../lexers/__pycache__/qlik.cpython-312.pyc | Bin 0 -> 3498 bytes .../lexers/__pycache__/qvt.cpython-312.pyc | Bin 0 -> 5366 bytes .../lexers/__pycache__/r.cpython-312.pyc | Bin 0 -> 6071 bytes .../lexers/__pycache__/rdf.cpython-312.pyc | Bin 0 -> 12241 bytes .../lexers/__pycache__/rebol.cpython-312.pyc | Bin 0 -> 19237 bytes .../lexers/__pycache__/rego.cpython-312.pyc | Bin 0 -> 1862 bytes .../__pycache__/resource.cpython-312.pyc | Bin 0 -> 3574 bytes .../lexers/__pycache__/ride.cpython-312.pyc | Bin 0 -> 4476 bytes .../lexers/__pycache__/rita.cpython-312.pyc | Bin 0 -> 1457 bytes .../lexers/__pycache__/rnc.cpython-312.pyc | Bin 0 -> 1999 bytes .../__pycache__/roboconf.cpython-312.pyc | Bin 0 -> 2350 bytes .../robotframework.cpython-312.pyc | Bin 0 -> 29560 bytes .../lexers/__pycache__/ruby.cpython-312.pyc | Bin 0 -> 22529 bytes .../lexers/__pycache__/rust.cpython-312.pyc | Bin 0 -> 7292 bytes .../lexers/__pycache__/sas.cpython-312.pyc | Bin 0 -> 7027 bytes .../lexers/__pycache__/savi.cpython-312.pyc | Bin 0 -> 3968 bytes .../lexers/__pycache__/scdoc.cpython-312.pyc | Bin 0 -> 2806 bytes .../__pycache__/scripting.cpython-312.pyc | Bin 0 -> 71796 bytes .../lexers/__pycache__/sgf.cpython-312.pyc | Bin 0 -> 2067 bytes .../lexers/__pycache__/shell.cpython-312.pyc | Bin 0 -> 37071 bytes .../lexers/__pycache__/sieve.cpython-312.pyc | Bin 0 -> 2737 bytes .../lexers/__pycache__/slash.cpython-312.pyc | Bin 0 -> 8380 bytes .../__pycache__/smalltalk.cpython-312.pyc | Bin 0 -> 6689 bytes .../lexers/__pycache__/smithy.cpython-312.pyc | Bin 0 -> 3114 bytes .../lexers/__pycache__/smv.cpython-312.pyc | Bin 0 -> 2796 bytes .../lexers/__pycache__/snobol.cpython-312.pyc | Bin 0 -> 2492 bytes .../__pycache__/solidity.cpython-312.pyc | Bin 0 -> 3395 bytes .../lexers/__pycache__/soong.cpython-312.pyc | Bin 0 -> 2260 bytes .../lexers/__pycache__/sophia.cpython-312.pyc | Bin 0 -> 3840 bytes .../__pycache__/special.cpython-312.pyc | Bin 0 -> 5402 bytes .../lexers/__pycache__/spice.cpython-312.pyc | Bin 0 -> 3165 bytes .../lexers/__pycache__/sql.cpython-312.pyc | Bin 0 -> 40659 bytes .../__pycache__/srcinfo.cpython-312.pyc | Bin 0 -> 1995 bytes .../lexers/__pycache__/stata.cpython-312.pyc | Bin 0 -> 5147 bytes .../__pycache__/supercollider.cpython-312.pyc | Bin 0 -> 3897 bytes .../__pycache__/tablegen.cpython-312.pyc | Bin 0 -> 3339 bytes .../lexers/__pycache__/tact.cpython-312.pyc | Bin 0 -> 9024 bytes .../lexers/__pycache__/tal.cpython-312.pyc | Bin 0 -> 2961 bytes .../lexers/__pycache__/tcl.cpython-312.pyc | Bin 0 -> 5135 bytes .../lexers/__pycache__/teal.cpython-312.pyc | Bin 0 -> 3544 bytes .../__pycache__/templates.cpython-312.pyc | Bin 0 -> 83625 bytes .../__pycache__/teraterm.cpython-312.pyc | Bin 0 -> 5552 bytes .../__pycache__/testing.cpython-312.pyc | Bin 0 -> 10060 bytes .../lexers/__pycache__/text.cpython-312.pyc | Bin 0 -> 1542 bytes .../__pycache__/textedit.cpython-312.pyc | Bin 0 -> 8501 bytes .../__pycache__/textfmts.cpython-312.pyc | Bin 0 -> 15578 bytes .../__pycache__/theorem.cpython-312.pyc | Bin 0 -> 14895 bytes .../__pycache__/thingsdb.cpython-312.pyc | Bin 0 -> 5606 bytes .../lexers/__pycache__/tlb.cpython-312.pyc | Bin 0 -> 1859 bytes .../lexers/__pycache__/tls.cpython-312.pyc | Bin 0 -> 1917 bytes .../lexers/__pycache__/tnt.cpython-312.pyc | Bin 0 -> 13573 bytes .../__pycache__/trafficscript.cpython-312.pyc | Bin 0 -> 1836 bytes .../__pycache__/typoscript.cpython-312.pyc | Bin 0 -> 7352 bytes .../lexers/__pycache__/typst.cpython-312.pyc | Bin 0 -> 6897 bytes .../lexers/__pycache__/ul4.cpython-312.pyc | Bin 0 -> 8133 bytes .../lexers/__pycache__/unicon.cpython-312.pyc | Bin 0 -> 12500 bytes .../lexers/__pycache__/urbi.cpython-312.pyc | Bin 0 -> 5902 bytes .../lexers/__pycache__/usd.cpython-312.pyc | Bin 0 -> 4014 bytes .../__pycache__/varnish.cpython-312.pyc | Bin 0 -> 6937 bytes .../__pycache__/verification.cpython-312.pyc | Bin 0 -> 4021 bytes .../__pycache__/verifpal.cpython-312.pyc | Bin 0 -> 2963 bytes .../lexers/__pycache__/vip.cpython-312.pyc | Bin 0 -> 5686 bytes .../lexers/__pycache__/vyper.cpython-312.pyc | Bin 0 -> 4916 bytes .../lexers/__pycache__/web.cpython-312.pyc | Bin 0 -> 1306 bytes .../__pycache__/webassembly.cpython-312.pyc | Bin 0 -> 5812 bytes .../lexers/__pycache__/webidl.cpython-312.pyc | Bin 0 -> 8096 bytes .../__pycache__/webmisc.cpython-312.pyc | Bin 0 -> 43533 bytes .../lexers/__pycache__/wgsl.cpython-312.pyc | Bin 0 -> 10842 bytes .../lexers/__pycache__/whiley.cpython-312.pyc | Bin 0 -> 3627 bytes .../lexers/__pycache__/wowtoc.cpython-312.pyc | Bin 0 -> 3232 bytes .../lexers/__pycache__/wren.cpython-312.pyc | Bin 0 -> 3097 bytes .../lexers/__pycache__/x10.cpython-312.pyc | Bin 0 -> 2388 bytes .../lexers/__pycache__/xorg.cpython-312.pyc | Bin 0 -> 1377 bytes .../lexers/__pycache__/yang.cpython-312.pyc | Bin 0 -> 4141 bytes .../lexers/__pycache__/yara.cpython-312.pyc | Bin 0 -> 2723 bytes .../lexers/__pycache__/zig.cpython-312.pyc | Bin 0 -> 3886 bytes .../pygments/lexers/_ada_builtins.py | 103 + .../pygments/lexers/_asy_builtins.py | 1644 ++++ .../pygments/lexers/_cl_builtins.py | 231 + .../pygments/lexers/_cocoa_builtins.py | 75 + .../pygments/lexers/_csound_builtins.py | 1780 ++++ .../pygments/lexers/_css_builtins.py | 558 ++ .../pygments/lexers/_googlesql_builtins.py | 918 ++ .../pygments/lexers/_julia_builtins.py | 411 + .../pygments/lexers/_lasso_builtins.py | 5326 ++++++++++ .../pygments/lexers/_lilypond_builtins.py | 4932 ++++++++++ .../pygments/lexers/_lua_builtins.py | 285 + .../pygments/lexers/_luau_builtins.py | 62 + .../site-packages/pygments/lexers/_mapping.py | 602 ++ .../pygments/lexers/_mql_builtins.py | 1171 +++ .../pygments/lexers/_mysql_builtins.py | 1335 +++ .../pygments/lexers/_openedge_builtins.py | 2600 +++++ .../pygments/lexers/_php_builtins.py | 3325 +++++++ .../pygments/lexers/_postgres_builtins.py | 739 ++ .../pygments/lexers/_qlik_builtins.py | 666 ++ .../pygments/lexers/_scheme_builtins.py | 1609 +++ .../pygments/lexers/_scilab_builtins.py | 3093 ++++++ .../pygments/lexers/_sourcemod_builtins.py | 1151 +++ .../pygments/lexers/_sql_builtins.py | 106 + .../pygments/lexers/_stan_builtins.py | 648 ++ .../pygments/lexers/_stata_builtins.py | 457 + .../pygments/lexers/_tsql_builtins.py | 1003 ++ .../pygments/lexers/_usd_builtins.py | 112 + .../pygments/lexers/_vbscript_builtins.py | 279 + .../pygments/lexers/_vim_builtins.py | 1938 ++++ .../pygments/lexers/actionscript.py | 243 + venv/Lib/site-packages/pygments/lexers/ada.py | 144 + .../site-packages/pygments/lexers/agile.py | 25 + .../site-packages/pygments/lexers/algebra.py | 299 + .../site-packages/pygments/lexers/ambient.py | 75 + .../site-packages/pygments/lexers/amdgpu.py | 54 + .../Lib/site-packages/pygments/lexers/ampl.py | 87 + .../site-packages/pygments/lexers/apdlexer.py | 593 ++ venv/Lib/site-packages/pygments/lexers/apl.py | 103 + .../pygments/lexers/archetype.py | 315 + .../site-packages/pygments/lexers/arrow.py | 116 + .../site-packages/pygments/lexers/arturo.py | 249 + venv/Lib/site-packages/pygments/lexers/asc.py | 55 + venv/Lib/site-packages/pygments/lexers/asm.py | 1051 ++ .../Lib/site-packages/pygments/lexers/asn1.py | 178 + .../pygments/lexers/automation.py | 379 + .../Lib/site-packages/pygments/lexers/bare.py | 101 + .../site-packages/pygments/lexers/basic.py | 656 ++ venv/Lib/site-packages/pygments/lexers/bdd.py | 57 + .../site-packages/pygments/lexers/berry.py | 99 + .../site-packages/pygments/lexers/bibtex.py | 159 + .../pygments/lexers/blueprint.py | 173 + venv/Lib/site-packages/pygments/lexers/boa.py | 97 + venv/Lib/site-packages/pygments/lexers/bqn.py | 112 + .../site-packages/pygments/lexers/business.py | 625 ++ .../site-packages/pygments/lexers/c_cpp.py | 414 + .../site-packages/pygments/lexers/c_like.py | 738 ++ .../pygments/lexers/capnproto.py | 74 + .../site-packages/pygments/lexers/carbon.py | 95 + .../Lib/site-packages/pygments/lexers/cddl.py | 172 + .../site-packages/pygments/lexers/chapel.py | 139 + .../site-packages/pygments/lexers/clean.py | 180 + .../site-packages/pygments/lexers/codeql.py | 80 + .../site-packages/pygments/lexers/comal.py | 81 + .../site-packages/pygments/lexers/compiled.py | 35 + .../site-packages/pygments/lexers/configs.py | 1433 +++ .../site-packages/pygments/lexers/console.py | 114 + .../site-packages/pygments/lexers/cplint.py | 43 + .../site-packages/pygments/lexers/crystal.py | 364 + .../site-packages/pygments/lexers/csound.py | 466 + venv/Lib/site-packages/pygments/lexers/css.py | 602 ++ venv/Lib/site-packages/pygments/lexers/d.py | 259 + .../site-packages/pygments/lexers/dalvik.py | 126 + .../Lib/site-packages/pygments/lexers/data.py | 763 ++ venv/Lib/site-packages/pygments/lexers/dax.py | 135 + .../pygments/lexers/devicetree.py | 108 + .../Lib/site-packages/pygments/lexers/diff.py | 169 + venv/Lib/site-packages/pygments/lexers/dns.py | 109 + .../site-packages/pygments/lexers/dotnet.py | 873 ++ .../Lib/site-packages/pygments/lexers/dsls.py | 970 ++ .../site-packages/pygments/lexers/dylan.py | 279 + venv/Lib/site-packages/pygments/lexers/ecl.py | 144 + .../site-packages/pygments/lexers/eiffel.py | 68 + venv/Lib/site-packages/pygments/lexers/elm.py | 123 + .../Lib/site-packages/pygments/lexers/elpi.py | 175 + .../site-packages/pygments/lexers/email.py | 132 + .../site-packages/pygments/lexers/erlang.py | 526 + .../site-packages/pygments/lexers/esoteric.py | 300 + .../site-packages/pygments/lexers/ezhil.py | 76 + .../site-packages/pygments/lexers/factor.py | 363 + .../site-packages/pygments/lexers/fantom.py | 251 + .../site-packages/pygments/lexers/felix.py | 275 + .../Lib/site-packages/pygments/lexers/fift.py | 68 + .../pygments/lexers/floscript.py | 81 + .../site-packages/pygments/lexers/forth.py | 178 + .../site-packages/pygments/lexers/fortran.py | 212 + .../site-packages/pygments/lexers/foxpro.py | 427 + .../site-packages/pygments/lexers/freefem.py | 893 ++ .../Lib/site-packages/pygments/lexers/func.py | 110 + .../pygments/lexers/functional.py | 21 + .../site-packages/pygments/lexers/futhark.py | 105 + .../pygments/lexers/gcodelexer.py | 35 + .../site-packages/pygments/lexers/gdscript.py | 189 + .../site-packages/pygments/lexers/gleam.py | 74 + venv/Lib/site-packages/pygments/lexers/go.py | 97 + .../pygments/lexers/grammar_notation.py | 262 + .../site-packages/pygments/lexers/graph.py | 108 + .../site-packages/pygments/lexers/graphics.py | 794 ++ .../site-packages/pygments/lexers/graphql.py | 176 + .../site-packages/pygments/lexers/graphviz.py | 58 + .../Lib/site-packages/pygments/lexers/gsql.py | 103 + .../Lib/site-packages/pygments/lexers/hare.py | 73 + .../site-packages/pygments/lexers/haskell.py | 866 ++ .../Lib/site-packages/pygments/lexers/haxe.py | 935 ++ venv/Lib/site-packages/pygments/lexers/hdl.py | 466 + .../site-packages/pygments/lexers/hexdump.py | 102 + .../Lib/site-packages/pygments/lexers/html.py | 670 ++ venv/Lib/site-packages/pygments/lexers/idl.py | 284 + .../Lib/site-packages/pygments/lexers/igor.py | 435 + .../site-packages/pygments/lexers/inferno.py | 95 + .../pygments/lexers/installers.py | 352 + .../pygments/lexers/int_fiction.py | 1370 +++ .../site-packages/pygments/lexers/iolang.py | 61 + venv/Lib/site-packages/pygments/lexers/j.py | 151 + .../pygments/lexers/javascript.py | 1591 +++ .../site-packages/pygments/lexers/jmespath.py | 69 + .../Lib/site-packages/pygments/lexers/jslt.py | 94 + .../site-packages/pygments/lexers/json5.py | 83 + .../site-packages/pygments/lexers/jsonnet.py | 169 + venv/Lib/site-packages/pygments/lexers/jsx.py | 100 + .../site-packages/pygments/lexers/julia.py | 294 + venv/Lib/site-packages/pygments/lexers/jvm.py | 1802 ++++ .../Lib/site-packages/pygments/lexers/kuin.py | 332 + .../site-packages/pygments/lexers/kusto.py | 93 + .../Lib/site-packages/pygments/lexers/ldap.py | 155 + .../Lib/site-packages/pygments/lexers/lean.py | 241 + .../site-packages/pygments/lexers/lilypond.py | 225 + .../Lib/site-packages/pygments/lexers/lisp.py | 3146 ++++++ .../pygments/lexers/macaulay2.py | 1814 ++++ .../Lib/site-packages/pygments/lexers/make.py | 212 + .../site-packages/pygments/lexers/maple.py | 291 + .../site-packages/pygments/lexers/markup.py | 1654 ++++ .../Lib/site-packages/pygments/lexers/math.py | 21 + .../site-packages/pygments/lexers/matlab.py | 3307 +++++++ .../site-packages/pygments/lexers/maxima.py | 84 + .../site-packages/pygments/lexers/meson.py | 139 + .../Lib/site-packages/pygments/lexers/mime.py | 210 + .../pygments/lexers/minecraft.py | 391 + .../Lib/site-packages/pygments/lexers/mips.py | 130 + venv/Lib/site-packages/pygments/lexers/ml.py | 958 ++ .../site-packages/pygments/lexers/modeling.py | 366 + .../site-packages/pygments/lexers/modula2.py | 1579 +++ .../Lib/site-packages/pygments/lexers/mojo.py | 707 ++ .../site-packages/pygments/lexers/monte.py | 203 + .../site-packages/pygments/lexers/mosel.py | 447 + venv/Lib/site-packages/pygments/lexers/ncl.py | 894 ++ .../site-packages/pygments/lexers/nimrod.py | 199 + venv/Lib/site-packages/pygments/lexers/nit.py | 63 + venv/Lib/site-packages/pygments/lexers/nix.py | 144 + .../site-packages/pygments/lexers/numbair.py | 63 + .../site-packages/pygments/lexers/oberon.py | 120 + .../pygments/lexers/objective.py | 513 + venv/Lib/site-packages/pygments/lexers/ooc.py | 84 + .../site-packages/pygments/lexers/openscad.py | 96 + .../site-packages/pygments/lexers/other.py | 41 + .../site-packages/pygments/lexers/parasail.py | 78 + .../site-packages/pygments/lexers/parsers.py | 798 ++ .../site-packages/pygments/lexers/pascal.py | 644 ++ .../Lib/site-packages/pygments/lexers/pawn.py | 202 + .../Lib/site-packages/pygments/lexers/pddl.py | 82 + .../Lib/site-packages/pygments/lexers/perl.py | 733 ++ .../Lib/site-packages/pygments/lexers/phix.py | 363 + venv/Lib/site-packages/pygments/lexers/php.py | 334 + .../pygments/lexers/pointless.py | 70 + .../Lib/site-packages/pygments/lexers/pony.py | 93 + .../site-packages/pygments/lexers/praat.py | 303 + .../site-packages/pygments/lexers/procfile.py | 41 + .../site-packages/pygments/lexers/prolog.py | 318 + .../site-packages/pygments/lexers/promql.py | 176 + .../Lib/site-packages/pygments/lexers/prql.py | 251 + venv/Lib/site-packages/pygments/lexers/ptx.py | 119 + .../site-packages/pygments/lexers/python.py | 1201 +++ venv/Lib/site-packages/pygments/lexers/q.py | 187 + .../Lib/site-packages/pygments/lexers/qlik.py | 117 + venv/Lib/site-packages/pygments/lexers/qvt.py | 153 + venv/Lib/site-packages/pygments/lexers/r.py | 196 + venv/Lib/site-packages/pygments/lexers/rdf.py | 468 + .../site-packages/pygments/lexers/rebol.py | 419 + .../Lib/site-packages/pygments/lexers/rego.py | 57 + .../site-packages/pygments/lexers/resource.py | 83 + .../Lib/site-packages/pygments/lexers/ride.py | 138 + .../Lib/site-packages/pygments/lexers/rita.py | 42 + venv/Lib/site-packages/pygments/lexers/rnc.py | 66 + .../site-packages/pygments/lexers/roboconf.py | 81 + .../pygments/lexers/robotframework.py | 551 ++ .../Lib/site-packages/pygments/lexers/ruby.py | 518 + .../Lib/site-packages/pygments/lexers/rust.py | 222 + venv/Lib/site-packages/pygments/lexers/sas.py | 227 + .../Lib/site-packages/pygments/lexers/savi.py | 171 + .../site-packages/pygments/lexers/scdoc.py | 85 + .../pygments/lexers/scripting.py | 1616 +++ venv/Lib/site-packages/pygments/lexers/sgf.py | 59 + .../site-packages/pygments/lexers/shell.py | 902 ++ .../site-packages/pygments/lexers/sieve.py | 78 + .../site-packages/pygments/lexers/slash.py | 183 + .../pygments/lexers/smalltalk.py | 194 + .../site-packages/pygments/lexers/smithy.py | 77 + venv/Lib/site-packages/pygments/lexers/smv.py | 78 + .../site-packages/pygments/lexers/snobol.py | 82 + .../site-packages/pygments/lexers/solidity.py | 87 + .../site-packages/pygments/lexers/soong.py | 78 + .../site-packages/pygments/lexers/sophia.py | 102 + .../site-packages/pygments/lexers/special.py | 122 + .../site-packages/pygments/lexers/spice.py | 70 + venv/Lib/site-packages/pygments/lexers/sql.py | 1109 +++ .../site-packages/pygments/lexers/srcinfo.py | 62 + .../site-packages/pygments/lexers/stata.py | 170 + .../pygments/lexers/supercollider.py | 94 + .../site-packages/pygments/lexers/tablegen.py | 177 + .../Lib/site-packages/pygments/lexers/tact.py | 303 + venv/Lib/site-packages/pygments/lexers/tal.py | 77 + venv/Lib/site-packages/pygments/lexers/tcl.py | 148 + .../Lib/site-packages/pygments/lexers/teal.py | 88 + .../pygments/lexers/templates.py | 2355 +++++ .../site-packages/pygments/lexers/teraterm.py | 325 + .../site-packages/pygments/lexers/testing.py | 209 + .../Lib/site-packages/pygments/lexers/text.py | 27 + .../site-packages/pygments/lexers/textedit.py | 205 + .../site-packages/pygments/lexers/textfmts.py | 436 + .../site-packages/pygments/lexers/theorem.py | 410 + .../site-packages/pygments/lexers/thingsdb.py | 140 + venv/Lib/site-packages/pygments/lexers/tlb.py | 59 + venv/Lib/site-packages/pygments/lexers/tls.py | 54 + venv/Lib/site-packages/pygments/lexers/tnt.py | 270 + .../pygments/lexers/trafficscript.py | 51 + .../pygments/lexers/typoscript.py | 216 + .../site-packages/pygments/lexers/typst.py | 160 + venv/Lib/site-packages/pygments/lexers/ul4.py | 309 + .../site-packages/pygments/lexers/unicon.py | 413 + .../Lib/site-packages/pygments/lexers/urbi.py | 145 + venv/Lib/site-packages/pygments/lexers/usd.py | 85 + .../site-packages/pygments/lexers/varnish.py | 189 + .../pygments/lexers/verification.py | 113 + .../site-packages/pygments/lexers/verifpal.py | 65 + venv/Lib/site-packages/pygments/lexers/vip.py | 150 + .../site-packages/pygments/lexers/vyper.py | 140 + venv/Lib/site-packages/pygments/lexers/web.py | 24 + .../pygments/lexers/webassembly.py | 119 + .../site-packages/pygments/lexers/webidl.py | 298 + .../site-packages/pygments/lexers/webmisc.py | 1006 ++ .../Lib/site-packages/pygments/lexers/wgsl.py | 406 + .../site-packages/pygments/lexers/whiley.py | 115 + .../site-packages/pygments/lexers/wowtoc.py | 120 + .../Lib/site-packages/pygments/lexers/wren.py | 98 + venv/Lib/site-packages/pygments/lexers/x10.py | 66 + .../Lib/site-packages/pygments/lexers/xorg.py | 38 + .../Lib/site-packages/pygments/lexers/yang.py | 103 + .../Lib/site-packages/pygments/lexers/yara.py | 69 + venv/Lib/site-packages/pygments/lexers/zig.py | 125 + venv/Lib/site-packages/pygments/modeline.py | 43 + venv/Lib/site-packages/pygments/plugin.py | 72 + venv/Lib/site-packages/pygments/regexopt.py | 91 + venv/Lib/site-packages/pygments/scanner.py | 104 + venv/Lib/site-packages/pygments/sphinxext.py | 247 + venv/Lib/site-packages/pygments/style.py | 203 + .../site-packages/pygments/styles/__init__.py | 61 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2595 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 3623 bytes .../styles/__pycache__/abap.cpython-312.pyc | Bin 0 -> 1058 bytes .../styles/__pycache__/algol.cpython-312.pyc | Bin 0 -> 2578 bytes .../__pycache__/algol_nu.cpython-312.pyc | Bin 0 -> 2593 bytes .../__pycache__/arduino.cpython-312.pyc | Bin 0 -> 4252 bytes .../styles/__pycache__/autumn.cpython-312.pyc | Bin 0 -> 2834 bytes .../__pycache__/borland.cpython-312.pyc | Bin 0 -> 2203 bytes .../styles/__pycache__/bw.cpython-312.pyc | Bin 0 -> 1880 bytes .../styles/__pycache__/coffee.cpython-312.pyc | Bin 0 -> 3424 bytes .../__pycache__/colorful.cpython-312.pyc | Bin 0 -> 3692 bytes .../__pycache__/default.cpython-312.pyc | Bin 0 -> 3194 bytes .../__pycache__/dracula.cpython-312.pyc | Bin 0 -> 3007 bytes .../styles/__pycache__/emacs.cpython-312.pyc | Bin 0 -> 3234 bytes .../__pycache__/friendly.cpython-312.pyc | Bin 0 -> 3330 bytes .../friendly_grayscale.cpython-312.pyc | Bin 0 -> 3540 bytes .../styles/__pycache__/fruity.cpython-312.pyc | Bin 0 -> 1909 bytes .../__pycache__/gh_dark.cpython-312.pyc | Bin 0 -> 3998 bytes .../__pycache__/gruvbox.cpython-312.pyc | Bin 0 -> 4346 bytes .../styles/__pycache__/igor.cpython-312.pyc | Bin 0 -> 1103 bytes .../styles/__pycache__/inkpot.cpython-312.pyc | Bin 0 -> 2965 bytes .../__pycache__/lightbulb.cpython-312.pyc | Bin 0 -> 4336 bytes .../__pycache__/lilypond.cpython-312.pyc | Bin 0 -> 3477 bytes .../__pycache__/lovelace.cpython-312.pyc | Bin 0 -> 4230 bytes .../styles/__pycache__/manni.cpython-312.pyc | Bin 0 -> 3465 bytes .../__pycache__/material.cpython-312.pyc | Bin 0 -> 4778 bytes .../__pycache__/monokai.cpython-312.pyc | Bin 0 -> 4759 bytes .../styles/__pycache__/murphy.cpython-312.pyc | Bin 0 -> 3650 bytes .../styles/__pycache__/native.cpython-312.pyc | Bin 0 -> 2927 bytes .../styles/__pycache__/nord.cpython-312.pyc | Bin 0 -> 5556 bytes .../__pycache__/onedark.cpython-312.pyc | Bin 0 -> 2233 bytes .../__pycache__/paraiso_dark.cpython-312.pyc | Bin 0 -> 5099 bytes .../__pycache__/paraiso_light.cpython-312.pyc | Bin 0 -> 5105 bytes .../styles/__pycache__/pastie.cpython-312.pyc | Bin 0 -> 3439 bytes .../__pycache__/perldoc.cpython-312.pyc | Bin 0 -> 3025 bytes .../__pycache__/rainbow_dash.cpython-312.pyc | Bin 0 -> 3671 bytes .../styles/__pycache__/rrt.cpython-312.pyc | Bin 0 -> 1433 bytes .../styles/__pycache__/sas.cpython-312.pyc | Bin 0 -> 1778 bytes .../__pycache__/solarized.cpython-312.pyc | Bin 0 -> 5730 bytes .../__pycache__/staroffice.cpython-312.pyc | Bin 0 -> 1081 bytes .../__pycache__/stata_dark.cpython-312.pyc | Bin 0 -> 1648 bytes .../__pycache__/stata_light.cpython-312.pyc | Bin 0 -> 1649 bytes .../styles/__pycache__/tango.cpython-312.pyc | Bin 0 -> 6102 bytes .../styles/__pycache__/trac.cpython-312.pyc | Bin 0 -> 2574 bytes .../styles/__pycache__/vim.cpython-312.pyc | Bin 0 -> 2457 bytes .../styles/__pycache__/vs.cpython-312.pyc | Bin 0 -> 1462 bytes .../styles/__pycache__/xcode.cpython-312.pyc | Bin 0 -> 1800 bytes .../__pycache__/zenburn.cpython-312.pyc | Bin 0 -> 3309 bytes .../site-packages/pygments/styles/_mapping.py | 54 + .../Lib/site-packages/pygments/styles/abap.py | 32 + .../site-packages/pygments/styles/algol.py | 65 + .../site-packages/pygments/styles/algol_nu.py | 65 + .../site-packages/pygments/styles/arduino.py | 100 + .../site-packages/pygments/styles/autumn.py | 67 + .../site-packages/pygments/styles/borland.py | 53 + venv/Lib/site-packages/pygments/styles/bw.py | 52 + .../site-packages/pygments/styles/coffee.py | 80 + .../site-packages/pygments/styles/colorful.py | 83 + .../site-packages/pygments/styles/default.py | 76 + .../site-packages/pygments/styles/dracula.py | 90 + .../site-packages/pygments/styles/emacs.py | 75 + .../site-packages/pygments/styles/friendly.py | 76 + .../pygments/styles/friendly_grayscale.py | 80 + .../site-packages/pygments/styles/fruity.py | 47 + .../site-packages/pygments/styles/gh_dark.py | 113 + .../site-packages/pygments/styles/gruvbox.py | 118 + .../Lib/site-packages/pygments/styles/igor.py | 32 + .../site-packages/pygments/styles/inkpot.py | 72 + .../pygments/styles/lightbulb.py | 110 + .../site-packages/pygments/styles/lilypond.py | 62 + .../site-packages/pygments/styles/lovelace.py | 100 + .../site-packages/pygments/styles/manni.py | 79 + .../site-packages/pygments/styles/material.py | 124 + .../site-packages/pygments/styles/monokai.py | 112 + .../site-packages/pygments/styles/murphy.py | 82 + .../site-packages/pygments/styles/native.py | 70 + .../Lib/site-packages/pygments/styles/nord.py | 156 + .../site-packages/pygments/styles/onedark.py | 63 + .../pygments/styles/paraiso_dark.py | 124 + .../pygments/styles/paraiso_light.py | 124 + .../site-packages/pygments/styles/pastie.py | 78 + .../site-packages/pygments/styles/perldoc.py | 73 + .../pygments/styles/rainbow_dash.py | 95 + venv/Lib/site-packages/pygments/styles/rrt.py | 40 + venv/Lib/site-packages/pygments/styles/sas.py | 46 + .../pygments/styles/solarized.py | 144 + .../pygments/styles/staroffice.py | 31 + .../pygments/styles/stata_dark.py | 42 + .../pygments/styles/stata_light.py | 42 + .../site-packages/pygments/styles/tango.py | 143 + .../Lib/site-packages/pygments/styles/trac.py | 66 + venv/Lib/site-packages/pygments/styles/vim.py | 67 + venv/Lib/site-packages/pygments/styles/vs.py | 41 + .../site-packages/pygments/styles/xcode.py | 53 + .../site-packages/pygments/styles/zenburn.py | 83 + venv/Lib/site-packages/pygments/token.py | 214 + venv/Lib/site-packages/pygments/unistring.py | 153 + venv/Lib/site-packages/pygments/util.py | 324 + .../pytest-8.4.1.dist-info/INSTALLER | 1 + .../pytest-8.4.1.dist-info/METADATA | 215 + .../pytest-8.4.1.dist-info/RECORD | 157 + .../pytest-8.4.1.dist-info/REQUESTED | 0 .../pytest-8.4.1.dist-info/WHEEL | 5 + .../pytest-8.4.1.dist-info/entry_points.txt | 3 + .../pytest-8.4.1.dist-info/licenses/AUTHORS | 496 + .../pytest-8.4.1.dist-info/licenses/LICENSE | 21 + .../pytest-8.4.1.dist-info/top_level.txt | 3 + venv/Lib/site-packages/pytest/__init__.py | 180 + venv/Lib/site-packages/pytest/__main__.py | 9 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4327 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 413 bytes venv/Lib/site-packages/pytest/py.typed | 0 .../requests-2.32.4.dist-info/INSTALLER | 1 + .../requests-2.32.4.dist-info/METADATA | 133 + .../requests-2.32.4.dist-info/RECORD | 43 + .../requests-2.32.4.dist-info/REQUESTED | 0 .../requests-2.32.4.dist-info/WHEEL | 5 + .../licenses/LICENSE | 175 + .../requests-2.32.4.dist-info/top_level.txt | 1 + venv/Lib/site-packages/requests/__init__.py | 184 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5395 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 559 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 1999 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 28329 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7179 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13896 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 641 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 2350 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25172 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7560 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4302 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1022 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35421 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 1093 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27815 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 5996 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5592 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36091 bytes .../Lib/site-packages/requests/__version__.py | 14 + .../site-packages/requests/_internal_utils.py | 50 + venv/Lib/site-packages/requests/adapters.py | 719 ++ venv/Lib/site-packages/requests/api.py | 157 + venv/Lib/site-packages/requests/auth.py | 314 + venv/Lib/site-packages/requests/certs.py | 17 + venv/Lib/site-packages/requests/compat.py | 106 + venv/Lib/site-packages/requests/cookies.py | 561 ++ venv/Lib/site-packages/requests/exceptions.py | 151 + venv/Lib/site-packages/requests/help.py | 134 + venv/Lib/site-packages/requests/hooks.py | 33 + venv/Lib/site-packages/requests/models.py | 1039 ++ venv/Lib/site-packages/requests/packages.py | 23 + venv/Lib/site-packages/requests/sessions.py | 831 ++ .../site-packages/requests/status_codes.py | 128 + venv/Lib/site-packages/requests/structures.py | 99 + venv/Lib/site-packages/requests/utils.py | 1086 +++ .../urllib3-2.5.0.dist-info/INSTALLER | 1 + .../urllib3-2.5.0.dist-info/METADATA | 154 + .../urllib3-2.5.0.dist-info/RECORD | 79 + .../urllib3-2.5.0.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 21 + venv/Lib/site-packages/urllib3/__init__.py | 211 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7290 bytes .../_base_connection.cpython-312.pyc | Bin 0 -> 6840 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 22491 bytes .../_request_methods.cpython-312.pyc | Bin 0 -> 10582 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 626 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 38366 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 39710 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 16591 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 11983 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 3459 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 24348 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 52678 bytes .../site-packages/urllib3/_base_connection.py | 165 + .../Lib/site-packages/urllib3/_collections.py | 479 + .../site-packages/urllib3/_request_methods.py | 278 + venv/Lib/site-packages/urllib3/_version.py | 21 + venv/Lib/site-packages/urllib3/connection.py | 1093 +++ .../site-packages/urllib3/connectionpool.py | 1178 +++ .../site-packages/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 173 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 28187 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 8151 bytes .../urllib3/contrib/emscripten/__init__.py | 16 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 881 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 10217 bytes .../__pycache__/fetch.cpython-312.pyc | Bin 0 -> 28607 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 1401 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 12195 bytes .../urllib3/contrib/emscripten/connection.py | 255 + .../emscripten/emscripten_fetch_worker.js | 110 + .../urllib3/contrib/emscripten/fetch.py | 728 ++ .../urllib3/contrib/emscripten/request.py | 22 + .../urllib3/contrib/emscripten/response.py | 277 + .../urllib3/contrib/pyopenssl.py | 564 ++ .../site-packages/urllib3/contrib/socks.py | 228 + venv/Lib/site-packages/urllib3/exceptions.py | 335 + venv/Lib/site-packages/urllib3/fields.py | 341 + venv/Lib/site-packages/urllib3/filepost.py | 89 + .../site-packages/urllib3/http2/__init__.py | 53 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1726 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 17008 bytes .../http2/__pycache__/probe.cpython-312.pyc | Bin 0 -> 3677 bytes .../site-packages/urllib3/http2/connection.py | 356 + venv/Lib/site-packages/urllib3/http2/probe.py | 87 + venv/Lib/site-packages/urllib3/poolmanager.py | 653 ++ venv/Lib/site-packages/urllib3/py.typed | 2 + venv/Lib/site-packages/urllib3/response.py | 1307 +++ .../site-packages/urllib3/util/__init__.py | 42 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 986 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4667 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1198 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 8298 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 2854 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 20265 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 17138 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5518 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 13305 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11670 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 16193 bytes .../util/__pycache__/util.cpython-312.pyc | Bin 0 -> 1975 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 3421 bytes .../site-packages/urllib3/util/connection.py | 137 + venv/Lib/site-packages/urllib3/util/proxy.py | 43 + .../Lib/site-packages/urllib3/util/request.py | 266 + .../site-packages/urllib3/util/response.py | 101 + venv/Lib/site-packages/urllib3/util/retry.py | 533 + venv/Lib/site-packages/urllib3/util/ssl_.py | 524 + .../urllib3/util/ssl_match_hostname.py | 159 + .../urllib3/util/ssltransport.py | 271 + .../Lib/site-packages/urllib3/util/timeout.py | 275 + venv/Lib/site-packages/urllib3/util/url.py | 469 + venv/Lib/site-packages/urllib3/util/util.py | 42 + venv/Lib/site-packages/urllib3/util/wait.py | 124 + venv/Scripts/Activate.ps1 | 502 + venv/Scripts/activate | 70 + venv/Scripts/activate.bat | 34 + venv/Scripts/deactivate.bat | 22 + venv/Scripts/normalizer.exe | Bin 0 -> 108413 bytes venv/Scripts/pip.exe | Bin 0 -> 108408 bytes venv/Scripts/pip3.12.exe | Bin 0 -> 108408 bytes venv/Scripts/pip3.exe | Bin 0 -> 108408 bytes venv/Scripts/py.test.exe | Bin 0 -> 108408 bytes venv/Scripts/pygmentize.exe | Bin 0 -> 108402 bytes venv/Scripts/pytest.exe | Bin 0 -> 108408 bytes venv/Scripts/python.exe | Bin 0 -> 270104 bytes venv/Scripts/pythonw.exe | Bin 0 -> 258840 bytes venv/pyvenv.cfg | 5 + 2223 files changed, 390658 insertions(+) create mode 100644 nodes/api_nodes/__pycache__/runway_text2img.cpython-312.pyc create mode 100644 nodes/api_nodes/runway_text2img.py create mode 100644 tests/api_nodes/__pycache__/test_runway_text2img.cpython-312-pytest-8.4.1.pyc create mode 100644 tests/api_nodes/test_runway_text2img.py create mode 100644 venv/Lib/site-packages/PIL/AvifImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/BdfFontFile.py create mode 100644 venv/Lib/site-packages/PIL/BlpImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/BmpImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/BufrStubImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/ContainerIO.py create mode 100644 venv/Lib/site-packages/PIL/CurImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/DcxImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/DdsImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/EpsImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/ExifTags.py create mode 100644 venv/Lib/site-packages/PIL/FitsImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/FliImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/FontFile.py create mode 100644 venv/Lib/site-packages/PIL/FpxImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/FtexImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/GbrImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/GdImageFile.py create mode 100644 venv/Lib/site-packages/PIL/GifImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/GimpGradientFile.py create mode 100644 venv/Lib/site-packages/PIL/GimpPaletteFile.py create mode 100644 venv/Lib/site-packages/PIL/GribStubImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/IcnsImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/IcoImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/ImImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/Image.py create mode 100644 venv/Lib/site-packages/PIL/ImageChops.py create mode 100644 venv/Lib/site-packages/PIL/ImageCms.py create mode 100644 venv/Lib/site-packages/PIL/ImageColor.py create mode 100644 venv/Lib/site-packages/PIL/ImageDraw.py create mode 100644 venv/Lib/site-packages/PIL/ImageDraw2.py create mode 100644 venv/Lib/site-packages/PIL/ImageEnhance.py create mode 100644 venv/Lib/site-packages/PIL/ImageFile.py create mode 100644 venv/Lib/site-packages/PIL/ImageFilter.py create mode 100644 venv/Lib/site-packages/PIL/ImageFont.py create mode 100644 venv/Lib/site-packages/PIL/ImageGrab.py create mode 100644 venv/Lib/site-packages/PIL/ImageMath.py create mode 100644 venv/Lib/site-packages/PIL/ImageMode.py create mode 100644 venv/Lib/site-packages/PIL/ImageMorph.py create mode 100644 venv/Lib/site-packages/PIL/ImageOps.py create mode 100644 venv/Lib/site-packages/PIL/ImagePalette.py create mode 100644 venv/Lib/site-packages/PIL/ImagePath.py create mode 100644 venv/Lib/site-packages/PIL/ImageQt.py create mode 100644 venv/Lib/site-packages/PIL/ImageSequence.py create mode 100644 venv/Lib/site-packages/PIL/ImageShow.py create mode 100644 venv/Lib/site-packages/PIL/ImageStat.py create mode 100644 venv/Lib/site-packages/PIL/ImageTk.py create mode 100644 venv/Lib/site-packages/PIL/ImageTransform.py create mode 100644 venv/Lib/site-packages/PIL/ImageWin.py create mode 100644 venv/Lib/site-packages/PIL/ImtImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/IptcImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/Jpeg2KImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/JpegImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/JpegPresets.py create mode 100644 venv/Lib/site-packages/PIL/McIdasImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/MicImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/MpegImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/MpoImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/MspImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PSDraw.py create mode 100644 venv/Lib/site-packages/PIL/PaletteFile.py create mode 100644 venv/Lib/site-packages/PIL/PalmImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PcdImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PcfFontFile.py create mode 100644 venv/Lib/site-packages/PIL/PcxImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PdfImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PdfParser.py create mode 100644 venv/Lib/site-packages/PIL/PixarImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PngImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PpmImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/PsdImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/QoiImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/SgiImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/SpiderImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/SunImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/TarIO.py create mode 100644 venv/Lib/site-packages/PIL/TgaImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/TiffImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/TiffTags.py create mode 100644 venv/Lib/site-packages/PIL/WalImageFile.py create mode 100644 venv/Lib/site-packages/PIL/WebPImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/WmfImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/XVThumbImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/XbmImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/XpmImagePlugin.py create mode 100644 venv/Lib/site-packages/PIL/__init__.py create mode 100644 venv/Lib/site-packages/PIL/__main__.py create mode 100644 venv/Lib/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/BdfFontFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ContainerIO.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ExifTags.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/FontFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/GdImageFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/Image.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageChops.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageCms.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageDraw2.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageEnhance.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageFilter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageFont.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageGrab.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageMath.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageMorph.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageOps.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImagePath.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageQt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageShow.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageStat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageTk.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageTransform.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImageWin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PSDraw.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PcfFontFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PdfParser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/SunImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/TarIO.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/TiffTags.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/WalImageFile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/_binary.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/_deprecate.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/_tkinter_finder.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/_typing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/_util.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/_version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/features.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/__pycache__/report.cpython-312.pyc create mode 100644 venv/Lib/site-packages/PIL/_avif.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_avif.pyi create mode 100644 venv/Lib/site-packages/PIL/_binary.py create mode 100644 venv/Lib/site-packages/PIL/_deprecate.py create mode 100644 venv/Lib/site-packages/PIL/_imaging.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_imaging.pyi create mode 100644 venv/Lib/site-packages/PIL/_imagingcms.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_imagingcms.pyi create mode 100644 venv/Lib/site-packages/PIL/_imagingft.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_imagingft.pyi create mode 100644 venv/Lib/site-packages/PIL/_imagingmath.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_imagingmath.pyi create mode 100644 venv/Lib/site-packages/PIL/_imagingmorph.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_imagingmorph.pyi create mode 100644 venv/Lib/site-packages/PIL/_imagingtk.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_imagingtk.pyi create mode 100644 venv/Lib/site-packages/PIL/_tkinter_finder.py create mode 100644 venv/Lib/site-packages/PIL/_typing.py create mode 100644 venv/Lib/site-packages/PIL/_util.py create mode 100644 venv/Lib/site-packages/PIL/_version.py create mode 100644 venv/Lib/site-packages/PIL/_webp.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/PIL/_webp.pyi create mode 100644 venv/Lib/site-packages/PIL/features.py create mode 100644 venv/Lib/site-packages/PIL/py.typed create mode 100644 venv/Lib/site-packages/PIL/report.py create mode 100644 venv/Lib/site-packages/__pycache__/py.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/_argcomplete.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/_version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/capture.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/doctest.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/hookspec.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/junitxml.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/legacypath.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/logging.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/main.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/nodes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/pathlib.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/pytester.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/python.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/python_api.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/raises.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/reports.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/runner.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/scope.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/setuponly.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/stash.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/stepwise.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/timing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/tmpdir.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/tracemalloc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/__pycache__/warnings.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_argcomplete.py create mode 100644 venv/Lib/site-packages/_pytest/_code/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_code/__pycache__/code.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_code/__pycache__/source.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_code/code.py create mode 100644 venv/Lib/site-packages/_pytest/_code/source.py create mode 100644 venv/Lib/site-packages/_pytest/_io/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_io/__pycache__/pprint.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_io/pprint.py create mode 100644 venv/Lib/site-packages/_pytest/_io/saferepr.py create mode 100644 venv/Lib/site-packages/_pytest/_io/terminalwriter.py create mode 100644 venv/Lib/site-packages/_pytest/_io/wcwidth.py create mode 100644 venv/Lib/site-packages/_pytest/_py/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_py/__pycache__/error.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_py/__pycache__/path.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/_py/error.py create mode 100644 venv/Lib/site-packages/_pytest/_py/path.py create mode 100644 venv/Lib/site-packages/_pytest/_version.py create mode 100644 venv/Lib/site-packages/_pytest/assertion/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/assertion/__pycache__/truncate.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/assertion/__pycache__/util.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/assertion/rewrite.py create mode 100644 venv/Lib/site-packages/_pytest/assertion/truncate.py create mode 100644 venv/Lib/site-packages/_pytest/assertion/util.py create mode 100644 venv/Lib/site-packages/_pytest/cacheprovider.py create mode 100644 venv/Lib/site-packages/_pytest/capture.py create mode 100644 venv/Lib/site-packages/_pytest/compat.py create mode 100644 venv/Lib/site-packages/_pytest/config/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/config/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/config/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/config/__pycache__/findpaths.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/config/argparsing.py create mode 100644 venv/Lib/site-packages/_pytest/config/compat.py create mode 100644 venv/Lib/site-packages/_pytest/config/exceptions.py create mode 100644 venv/Lib/site-packages/_pytest/config/findpaths.py create mode 100644 venv/Lib/site-packages/_pytest/debugging.py create mode 100644 venv/Lib/site-packages/_pytest/deprecated.py create mode 100644 venv/Lib/site-packages/_pytest/doctest.py create mode 100644 venv/Lib/site-packages/_pytest/faulthandler.py create mode 100644 venv/Lib/site-packages/_pytest/fixtures.py create mode 100644 venv/Lib/site-packages/_pytest/freeze_support.py create mode 100644 venv/Lib/site-packages/_pytest/helpconfig.py create mode 100644 venv/Lib/site-packages/_pytest/hookspec.py create mode 100644 venv/Lib/site-packages/_pytest/junitxml.py create mode 100644 venv/Lib/site-packages/_pytest/legacypath.py create mode 100644 venv/Lib/site-packages/_pytest/logging.py create mode 100644 venv/Lib/site-packages/_pytest/main.py create mode 100644 venv/Lib/site-packages/_pytest/mark/__init__.py create mode 100644 venv/Lib/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/mark/__pycache__/expression.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/_pytest/mark/expression.py create mode 100644 venv/Lib/site-packages/_pytest/mark/structures.py create mode 100644 venv/Lib/site-packages/_pytest/monkeypatch.py create mode 100644 venv/Lib/site-packages/_pytest/nodes.py create mode 100644 venv/Lib/site-packages/_pytest/outcomes.py create mode 100644 venv/Lib/site-packages/_pytest/pastebin.py create mode 100644 venv/Lib/site-packages/_pytest/pathlib.py create mode 100644 venv/Lib/site-packages/_pytest/py.typed create mode 100644 venv/Lib/site-packages/_pytest/pytester.py create mode 100644 venv/Lib/site-packages/_pytest/pytester_assertions.py create mode 100644 venv/Lib/site-packages/_pytest/python.py create mode 100644 venv/Lib/site-packages/_pytest/python_api.py create mode 100644 venv/Lib/site-packages/_pytest/raises.py create mode 100644 venv/Lib/site-packages/_pytest/recwarn.py create mode 100644 venv/Lib/site-packages/_pytest/reports.py create mode 100644 venv/Lib/site-packages/_pytest/runner.py create mode 100644 venv/Lib/site-packages/_pytest/scope.py create mode 100644 venv/Lib/site-packages/_pytest/setuponly.py create mode 100644 venv/Lib/site-packages/_pytest/setupplan.py create mode 100644 venv/Lib/site-packages/_pytest/skipping.py create mode 100644 venv/Lib/site-packages/_pytest/stash.py create mode 100644 venv/Lib/site-packages/_pytest/stepwise.py create mode 100644 venv/Lib/site-packages/_pytest/terminal.py create mode 100644 venv/Lib/site-packages/_pytest/threadexception.py create mode 100644 venv/Lib/site-packages/_pytest/timing.py create mode 100644 venv/Lib/site-packages/_pytest/tmpdir.py create mode 100644 venv/Lib/site-packages/_pytest/tracemalloc.py create mode 100644 venv/Lib/site-packages/_pytest/unittest.py create mode 100644 venv/Lib/site-packages/_pytest/unraisableexception.py create mode 100644 venv/Lib/site-packages/_pytest/warning_types.py create mode 100644 venv/Lib/site-packages/_pytest/warnings.py create mode 100644 venv/Lib/site-packages/certifi-2025.7.14.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/certifi-2025.7.14.dist-info/METADATA create mode 100644 venv/Lib/site-packages/certifi-2025.7.14.dist-info/RECORD create mode 100644 venv/Lib/site-packages/certifi-2025.7.14.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/certifi-2025.7.14.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/certifi-2025.7.14.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/certifi/__init__.py create mode 100644 venv/Lib/site-packages/certifi/__main__.py create mode 100644 venv/Lib/site-packages/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/certifi/__pycache__/core.cpython-312.pyc create mode 100644 venv/Lib/site-packages/certifi/cacert.pem create mode 100644 venv/Lib/site-packages/certifi/core.py create mode 100644 venv/Lib/site-packages/certifi/py.typed create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/METADATA create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/RECORD create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/charset_normalizer/__init__.py create mode 100644 venv/Lib/site-packages/charset_normalizer/__main__.py create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/md.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/api.py create mode 100644 venv/Lib/site-packages/charset_normalizer/cd.py create mode 100644 venv/Lib/site-packages/charset_normalizer/cli/__init__.py create mode 100644 venv/Lib/site-packages/charset_normalizer/cli/__main__.py create mode 100644 venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/charset_normalizer/constant.py create mode 100644 venv/Lib/site-packages/charset_normalizer/legacy.py create mode 100644 venv/Lib/site-packages/charset_normalizer/md.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/charset_normalizer/md.py create mode 100644 venv/Lib/site-packages/charset_normalizer/md__mypyc.cp312-win_amd64.pyd create mode 100644 venv/Lib/site-packages/charset_normalizer/models.py create mode 100644 venv/Lib/site-packages/charset_normalizer/py.typed create mode 100644 venv/Lib/site-packages/charset_normalizer/utils.py create mode 100644 venv/Lib/site-packages/charset_normalizer/version.py create mode 100644 venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA create mode 100644 venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD create mode 100644 venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt create mode 100644 venv/Lib/site-packages/colorama/__init__.py create mode 100644 venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/ansi.py create mode 100644 venv/Lib/site-packages/colorama/ansitowin32.py create mode 100644 venv/Lib/site-packages/colorama/initialise.py create mode 100644 venv/Lib/site-packages/colorama/tests/__init__.py create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/ansi_test.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/initialise_test.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc create mode 100644 venv/Lib/site-packages/colorama/tests/ansi_test.py create mode 100644 venv/Lib/site-packages/colorama/tests/ansitowin32_test.py create mode 100644 venv/Lib/site-packages/colorama/tests/initialise_test.py create mode 100644 venv/Lib/site-packages/colorama/tests/isatty_test.py create mode 100644 venv/Lib/site-packages/colorama/tests/utils.py create mode 100644 venv/Lib/site-packages/colorama/tests/winterm_test.py create mode 100644 venv/Lib/site-packages/colorama/win32.py create mode 100644 venv/Lib/site-packages/colorama/winterm.py create mode 100644 venv/Lib/site-packages/idna-3.10.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/idna-3.10.dist-info/LICENSE.md create mode 100644 venv/Lib/site-packages/idna-3.10.dist-info/METADATA create mode 100644 venv/Lib/site-packages/idna-3.10.dist-info/RECORD create mode 100644 venv/Lib/site-packages/idna-3.10.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/idna/__init__.py create mode 100644 venv/Lib/site-packages/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/codec.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/core.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 venv/Lib/site-packages/idna/codec.py create mode 100644 venv/Lib/site-packages/idna/compat.py create mode 100644 venv/Lib/site-packages/idna/core.py create mode 100644 venv/Lib/site-packages/idna/idnadata.py create mode 100644 venv/Lib/site-packages/idna/intranges.py create mode 100644 venv/Lib/site-packages/idna/package_data.py create mode 100644 venv/Lib/site-packages/idna/py.typed create mode 100644 venv/Lib/site-packages/idna/uts46data.py create mode 100644 venv/Lib/site-packages/iniconfig-2.1.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/iniconfig-2.1.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/iniconfig-2.1.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/iniconfig-2.1.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/iniconfig-2.1.0.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/iniconfig/__init__.py create mode 100644 venv/Lib/site-packages/iniconfig/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/iniconfig/__pycache__/_parse.cpython-312.pyc create mode 100644 venv/Lib/site-packages/iniconfig/__pycache__/_version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/iniconfig/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/iniconfig/_parse.py create mode 100644 venv/Lib/site-packages/iniconfig/_version.py create mode 100644 venv/Lib/site-packages/iniconfig/exceptions.py create mode 100644 venv/Lib/site-packages/iniconfig/py.typed create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/licenses/LICENSE.APACHE create mode 100644 venv/Lib/site-packages/packaging-25.0.dist-info/licenses/LICENSE.BSD create mode 100644 venv/Lib/site-packages/packaging/__init__.py create mode 100644 venv/Lib/site-packages/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/_elffile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/_parser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/_tokenizer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/__pycache__/version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/_elffile.py create mode 100644 venv/Lib/site-packages/packaging/_manylinux.py create mode 100644 venv/Lib/site-packages/packaging/_musllinux.py create mode 100644 venv/Lib/site-packages/packaging/_parser.py create mode 100644 venv/Lib/site-packages/packaging/_structures.py create mode 100644 venv/Lib/site-packages/packaging/_tokenizer.py create mode 100644 venv/Lib/site-packages/packaging/licenses/__init__.py create mode 100644 venv/Lib/site-packages/packaging/licenses/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/licenses/__pycache__/_spdx.cpython-312.pyc create mode 100644 venv/Lib/site-packages/packaging/licenses/_spdx.py create mode 100644 venv/Lib/site-packages/packaging/markers.py create mode 100644 venv/Lib/site-packages/packaging/metadata.py create mode 100644 venv/Lib/site-packages/packaging/py.typed create mode 100644 venv/Lib/site-packages/packaging/requirements.py create mode 100644 venv/Lib/site-packages/packaging/specifiers.py create mode 100644 venv/Lib/site-packages/packaging/tags.py create mode 100644 venv/Lib/site-packages/packaging/utils.py create mode 100644 venv/Lib/site-packages/packaging/version.py create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/pillow-11.3.0.dist-info/zip-safe create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/AUTHORS.txt create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/LICENSE.txt create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/METADATA create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/RECORD create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/pip-24.2.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/pip/__init__.py create mode 100644 venv/Lib/site-packages/pip/__main__.py create mode 100644 venv/Lib/site-packages/pip/__pip-runner__.py create mode 100644 venv/Lib/site-packages/pip/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/build_env.py create mode 100644 venv/Lib/site-packages/pip/_internal/cache.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/command_context.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/index_command.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/main.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/req_command.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/spinners.py create mode 100644 venv/Lib/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/commands/cache.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/check.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/download.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/help.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/index.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/inspect.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/install.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/list.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/search.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/show.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/uninstall.py create mode 100644 venv/Lib/site-packages/pip/_internal/commands/wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/configuration.py create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/base.py create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/installed.py create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/sdist.py create mode 100644 venv/Lib/site-packages/pip/_internal/distributions/wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/exceptions.py create mode 100644 venv/Lib/site-packages/pip/_internal/index/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/index/collector.py create mode 100644 venv/Lib/site-packages/pip/_internal/index/package_finder.py create mode 100644 venv/Lib/site-packages/pip/_internal/index/sources.py create mode 100644 venv/Lib/site-packages/pip/_internal/locations/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/locations/_distutils.py create mode 100644 venv/Lib/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 venv/Lib/site-packages/pip/_internal/locations/base.py create mode 100644 venv/Lib/site-packages/pip/_internal/main.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/_json.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/base.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 venv/Lib/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/models/candidate.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/direct_url.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/format_control.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/index.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/installation_report.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/link.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/scheme.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/search_scope.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/target_python.py create mode 100644 venv/Lib/site-packages/pip/_internal/models/wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/network/auth.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/cache.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/download.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/session.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/utils.py create mode 100644 venv/Lib/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/check.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/freeze.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/operations/prepare.py create mode 100644 venv/Lib/site-packages/pip/_internal/pyproject.py create mode 100644 venv/Lib/site-packages/pip/_internal/req/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/req/constructors.py create mode 100644 venv/Lib/site-packages/pip/_internal/req/req_file.py create mode 100644 venv/Lib/site-packages/pip/_internal/req/req_install.py create mode 100644 venv/Lib/site-packages/pip/_internal/req/req_set.py create mode 100644 venv/Lib/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/base.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 venv/Lib/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 venv/Lib/site-packages/pip/_internal/self_outdated_check.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/_log.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/appdirs.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/compat.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/datetime.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/deprecation.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/egg_link.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/encoding.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/filesystem.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/filetypes.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/glibc.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/hashes.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/logging.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/misc.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/packaging.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/retry.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/subprocess.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/unpacking.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/urls.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 venv/Lib/site-packages/pip/_internal/utils/wheel.py create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__init__.py create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/git.py create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/subversion.py create mode 100644 venv/Lib/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 venv/Lib/site-packages/pip/_internal/wheel_builder.py create mode 100644 venv/Lib/site-packages/pip/_vendor/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/Lib/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/core.py create mode 100644 venv/Lib/site-packages/pip/_vendor/certifi/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/compat.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/database.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/index.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/locators.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/markers.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/resources.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/t32.exe create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/t64-arm.exe create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/t64.exe create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/util.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/version.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/w32.exe create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/w64-arm.exe create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/w64.exe create mode 100644 venv/Lib/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/__main__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/distro.py create mode 100644 venv/Lib/site-packages/pip/_vendor/distro/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/codec.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/compat.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/core.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/intranges.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/package_data.py create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 venv/Lib/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/_elffile.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/_parser.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/_tokenizer.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/markers.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/metadata.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/tags.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/utils.py create mode 100644 venv/Lib/site-packages/pip/_vendor/packaging/version.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 venv/Lib/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/console.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/filter.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/style.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/styles/_mapping.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/token.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pygments/util.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/__version__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/adapters.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/api.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/auth.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/certs.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/compat.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/cookies.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/help.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/hooks.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/models.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/packages.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/sessions.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/structures.py create mode 100644 venv/Lib/site-packages/pip/_vendor/requests/utils.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 venv/Lib/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__main__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_extension.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_loop.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_pick.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_stack.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_timer.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_windows.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/abc.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/align.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/ansi.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/bar.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/box.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/cells.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/color.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/columns.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/console.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/constrain.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/containers.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/control.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/emoji.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/errors.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/filesize.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/json.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/layout.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/live.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/live_render.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/logging.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/markup.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/measure.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/padding.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/pager.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/palette.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/panel.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/pretty.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/progress.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/prompt.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/protocol.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/region.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/repr.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/rule.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/scope.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/screen.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/segment.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/spinner.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/status.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/style.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/styled.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/syntax.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/table.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/text.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/theme.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/themes.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/traceback.py create mode 100644 venv/Lib/site-packages/pip/_vendor/rich/tree.py create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/_re.py create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/_types.py create mode 100644 venv/Lib/site-packages/pip/_vendor/tomli/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/_api.py create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 venv/Lib/site-packages/pip/_vendor/truststore/py.typed create mode 100644 venv/Lib/site-packages/pip/_vendor/typing_extensions.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/request.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/response.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 venv/Lib/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/Lib/site-packages/pip/_vendor/vendor.txt create mode 100644 venv/Lib/site-packages/pip/py.typed create mode 100644 venv/Lib/site-packages/pluggy-1.6.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/pluggy-1.6.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/pluggy-1.6.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/pluggy-1.6.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/pluggy-1.6.0.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/pluggy/__init__.py create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_callers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_hooks.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_manager.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_result.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_tracing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/__pycache__/_warnings.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pluggy/_callers.py create mode 100644 venv/Lib/site-packages/pluggy/_hooks.py create mode 100644 venv/Lib/site-packages/pluggy/_manager.py create mode 100644 venv/Lib/site-packages/pluggy/_result.py create mode 100644 venv/Lib/site-packages/pluggy/_tracing.py create mode 100644 venv/Lib/site-packages/pluggy/_version.py create mode 100644 venv/Lib/site-packages/pluggy/_warnings.py create mode 100644 venv/Lib/site-packages/pluggy/py.typed create mode 100644 venv/Lib/site-packages/py.py create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/METADATA create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/RECORD create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/licenses/AUTHORS create mode 100644 venv/Lib/site-packages/pygments-2.19.2.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/pygments/__init__.py create mode 100644 venv/Lib/site-packages/pygments/__main__.py create mode 100644 venv/Lib/site-packages/pygments/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/cmdline.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/console.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/filter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/formatter.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/lexer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/modeline.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/plugin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/regexopt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/scanner.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/sphinxext.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/style.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/token.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/unistring.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/__pycache__/util.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/cmdline.py create mode 100644 venv/Lib/site-packages/pygments/console.py create mode 100644 venv/Lib/site-packages/pygments/filter.py create mode 100644 venv/Lib/site-packages/pygments/filters/__init__.py create mode 100644 venv/Lib/site-packages/pygments/filters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatter.py create mode 100644 venv/Lib/site-packages/pygments/formatters/__init__.py create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/bbcode.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/groff.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/html.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/img.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/irc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/latex.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/other.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/rtf.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/svg.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/terminal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/__pycache__/terminal256.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/formatters/_mapping.py create mode 100644 venv/Lib/site-packages/pygments/formatters/bbcode.py create mode 100644 venv/Lib/site-packages/pygments/formatters/groff.py create mode 100644 venv/Lib/site-packages/pygments/formatters/html.py create mode 100644 venv/Lib/site-packages/pygments/formatters/img.py create mode 100644 venv/Lib/site-packages/pygments/formatters/irc.py create mode 100644 venv/Lib/site-packages/pygments/formatters/latex.py create mode 100644 venv/Lib/site-packages/pygments/formatters/other.py create mode 100644 venv/Lib/site-packages/pygments/formatters/pangomarkup.py create mode 100644 venv/Lib/site-packages/pygments/formatters/rtf.py create mode 100644 venv/Lib/site-packages/pygments/formatters/svg.py create mode 100644 venv/Lib/site-packages/pygments/formatters/terminal.py create mode 100644 venv/Lib/site-packages/pygments/formatters/terminal256.py create mode 100644 venv/Lib/site-packages/pygments/lexer.py create mode 100644 venv/Lib/site-packages/pygments/lexers/__init__.py create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_ada_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_asy_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_cl_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_cocoa_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_csound_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_css_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_googlesql_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_julia_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_lasso_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_lilypond_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_lua_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_luau_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_mql_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_mysql_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_openedge_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_php_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_postgres_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_qlik_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_scheme_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_scilab_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_sourcemod_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_sql_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_stan_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_stata_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_tsql_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_usd_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_vbscript_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/_vim_builtins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/actionscript.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ada.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/agile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/algebra.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ambient.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/amdgpu.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ampl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/apdlexer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/apl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/archetype.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/arrow.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/arturo.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/asc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/asm.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/asn1.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/automation.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/bare.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/basic.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/bdd.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/berry.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/bibtex.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/blueprint.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/boa.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/bqn.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/business.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/c_cpp.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/c_like.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/capnproto.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/carbon.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/cddl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/chapel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/clean.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/codeql.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/comal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/compiled.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/configs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/console.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/cplint.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/crystal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/csound.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/css.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/d.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/dalvik.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/data.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/dax.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/devicetree.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/diff.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/dns.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/dotnet.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/dsls.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/dylan.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ecl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/eiffel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/elm.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/elpi.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/email.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/erlang.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/esoteric.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ezhil.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/factor.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/fantom.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/felix.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/fift.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/floscript.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/forth.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/fortran.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/foxpro.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/freefem.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/func.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/functional.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/futhark.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/gcodelexer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/gdscript.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/gleam.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/go.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/grammar_notation.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/graph.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/graphics.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/graphql.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/graphviz.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/gsql.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/hare.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/haskell.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/haxe.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/hdl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/hexdump.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/html.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/idl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/igor.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/inferno.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/installers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/int_fiction.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/iolang.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/j.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/javascript.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/jmespath.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/jslt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/json5.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/jsonnet.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/jsx.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/julia.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/jvm.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/kuin.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/kusto.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ldap.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/lean.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/lilypond.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/lisp.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/macaulay2.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/make.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/maple.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/markup.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/math.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/matlab.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/maxima.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/meson.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/mime.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/minecraft.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/mips.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ml.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/modeling.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/modula2.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/mojo.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/monte.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/mosel.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ncl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/nimrod.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/nit.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/nix.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/numbair.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/oberon.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/objective.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ooc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/openscad.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/other.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/parasail.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/parsers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/pascal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/pawn.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/pddl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/perl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/phix.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/php.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/pointless.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/pony.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/praat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/procfile.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/prolog.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/promql.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/prql.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ptx.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/python.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/q.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/qlik.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/qvt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/r.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/rdf.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/rebol.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/rego.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/resource.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ride.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/rita.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/rnc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/roboconf.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/robotframework.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ruby.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/rust.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/sas.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/savi.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/scdoc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/scripting.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/sgf.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/shell.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/sieve.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/slash.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/smalltalk.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/smithy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/smv.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/snobol.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/solidity.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/soong.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/sophia.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/special.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/spice.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/sql.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/srcinfo.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/stata.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/supercollider.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tablegen.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tact.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tcl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/teal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/templates.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/teraterm.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/testing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/text.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/textedit.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/textfmts.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/theorem.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/thingsdb.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tlb.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tls.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/tnt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/trafficscript.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/typoscript.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/typst.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/ul4.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/unicon.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/urbi.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/usd.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/varnish.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/verification.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/verifpal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/vip.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/vyper.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/web.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/webassembly.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/webidl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/webmisc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/wgsl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/whiley.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/wowtoc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/wren.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/x10.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/xorg.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/yang.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/yara.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/__pycache__/zig.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/lexers/_ada_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_asy_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_cl_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_cocoa_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_csound_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_css_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_googlesql_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_julia_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_lasso_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_lilypond_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_lua_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_luau_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_mapping.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_mql_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_mysql_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_openedge_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_php_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_postgres_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_qlik_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_scheme_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_scilab_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_sourcemod_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_sql_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_stan_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_stata_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_tsql_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_usd_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_vbscript_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/_vim_builtins.py create mode 100644 venv/Lib/site-packages/pygments/lexers/actionscript.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ada.py create mode 100644 venv/Lib/site-packages/pygments/lexers/agile.py create mode 100644 venv/Lib/site-packages/pygments/lexers/algebra.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ambient.py create mode 100644 venv/Lib/site-packages/pygments/lexers/amdgpu.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ampl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/apdlexer.py create mode 100644 venv/Lib/site-packages/pygments/lexers/apl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/archetype.py create mode 100644 venv/Lib/site-packages/pygments/lexers/arrow.py create mode 100644 venv/Lib/site-packages/pygments/lexers/arturo.py create mode 100644 venv/Lib/site-packages/pygments/lexers/asc.py create mode 100644 venv/Lib/site-packages/pygments/lexers/asm.py create mode 100644 venv/Lib/site-packages/pygments/lexers/asn1.py create mode 100644 venv/Lib/site-packages/pygments/lexers/automation.py create mode 100644 venv/Lib/site-packages/pygments/lexers/bare.py create mode 100644 venv/Lib/site-packages/pygments/lexers/basic.py create mode 100644 venv/Lib/site-packages/pygments/lexers/bdd.py create mode 100644 venv/Lib/site-packages/pygments/lexers/berry.py create mode 100644 venv/Lib/site-packages/pygments/lexers/bibtex.py create mode 100644 venv/Lib/site-packages/pygments/lexers/blueprint.py create mode 100644 venv/Lib/site-packages/pygments/lexers/boa.py create mode 100644 venv/Lib/site-packages/pygments/lexers/bqn.py create mode 100644 venv/Lib/site-packages/pygments/lexers/business.py create mode 100644 venv/Lib/site-packages/pygments/lexers/c_cpp.py create mode 100644 venv/Lib/site-packages/pygments/lexers/c_like.py create mode 100644 venv/Lib/site-packages/pygments/lexers/capnproto.py create mode 100644 venv/Lib/site-packages/pygments/lexers/carbon.py create mode 100644 venv/Lib/site-packages/pygments/lexers/cddl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/chapel.py create mode 100644 venv/Lib/site-packages/pygments/lexers/clean.py create mode 100644 venv/Lib/site-packages/pygments/lexers/codeql.py create mode 100644 venv/Lib/site-packages/pygments/lexers/comal.py create mode 100644 venv/Lib/site-packages/pygments/lexers/compiled.py create mode 100644 venv/Lib/site-packages/pygments/lexers/configs.py create mode 100644 venv/Lib/site-packages/pygments/lexers/console.py create mode 100644 venv/Lib/site-packages/pygments/lexers/cplint.py create mode 100644 venv/Lib/site-packages/pygments/lexers/crystal.py create mode 100644 venv/Lib/site-packages/pygments/lexers/csound.py create mode 100644 venv/Lib/site-packages/pygments/lexers/css.py create mode 100644 venv/Lib/site-packages/pygments/lexers/d.py create mode 100644 venv/Lib/site-packages/pygments/lexers/dalvik.py create mode 100644 venv/Lib/site-packages/pygments/lexers/data.py create mode 100644 venv/Lib/site-packages/pygments/lexers/dax.py create mode 100644 venv/Lib/site-packages/pygments/lexers/devicetree.py create mode 100644 venv/Lib/site-packages/pygments/lexers/diff.py create mode 100644 venv/Lib/site-packages/pygments/lexers/dns.py create mode 100644 venv/Lib/site-packages/pygments/lexers/dotnet.py create mode 100644 venv/Lib/site-packages/pygments/lexers/dsls.py create mode 100644 venv/Lib/site-packages/pygments/lexers/dylan.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ecl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/eiffel.py create mode 100644 venv/Lib/site-packages/pygments/lexers/elm.py create mode 100644 venv/Lib/site-packages/pygments/lexers/elpi.py create mode 100644 venv/Lib/site-packages/pygments/lexers/email.py create mode 100644 venv/Lib/site-packages/pygments/lexers/erlang.py create mode 100644 venv/Lib/site-packages/pygments/lexers/esoteric.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ezhil.py create mode 100644 venv/Lib/site-packages/pygments/lexers/factor.py create mode 100644 venv/Lib/site-packages/pygments/lexers/fantom.py create mode 100644 venv/Lib/site-packages/pygments/lexers/felix.py create mode 100644 venv/Lib/site-packages/pygments/lexers/fift.py create mode 100644 venv/Lib/site-packages/pygments/lexers/floscript.py create mode 100644 venv/Lib/site-packages/pygments/lexers/forth.py create mode 100644 venv/Lib/site-packages/pygments/lexers/fortran.py create mode 100644 venv/Lib/site-packages/pygments/lexers/foxpro.py create mode 100644 venv/Lib/site-packages/pygments/lexers/freefem.py create mode 100644 venv/Lib/site-packages/pygments/lexers/func.py create mode 100644 venv/Lib/site-packages/pygments/lexers/functional.py create mode 100644 venv/Lib/site-packages/pygments/lexers/futhark.py create mode 100644 venv/Lib/site-packages/pygments/lexers/gcodelexer.py create mode 100644 venv/Lib/site-packages/pygments/lexers/gdscript.py create mode 100644 venv/Lib/site-packages/pygments/lexers/gleam.py create mode 100644 venv/Lib/site-packages/pygments/lexers/go.py create mode 100644 venv/Lib/site-packages/pygments/lexers/grammar_notation.py create mode 100644 venv/Lib/site-packages/pygments/lexers/graph.py create mode 100644 venv/Lib/site-packages/pygments/lexers/graphics.py create mode 100644 venv/Lib/site-packages/pygments/lexers/graphql.py create mode 100644 venv/Lib/site-packages/pygments/lexers/graphviz.py create mode 100644 venv/Lib/site-packages/pygments/lexers/gsql.py create mode 100644 venv/Lib/site-packages/pygments/lexers/hare.py create mode 100644 venv/Lib/site-packages/pygments/lexers/haskell.py create mode 100644 venv/Lib/site-packages/pygments/lexers/haxe.py create mode 100644 venv/Lib/site-packages/pygments/lexers/hdl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/hexdump.py create mode 100644 venv/Lib/site-packages/pygments/lexers/html.py create mode 100644 venv/Lib/site-packages/pygments/lexers/idl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/igor.py create mode 100644 venv/Lib/site-packages/pygments/lexers/inferno.py create mode 100644 venv/Lib/site-packages/pygments/lexers/installers.py create mode 100644 venv/Lib/site-packages/pygments/lexers/int_fiction.py create mode 100644 venv/Lib/site-packages/pygments/lexers/iolang.py create mode 100644 venv/Lib/site-packages/pygments/lexers/j.py create mode 100644 venv/Lib/site-packages/pygments/lexers/javascript.py create mode 100644 venv/Lib/site-packages/pygments/lexers/jmespath.py create mode 100644 venv/Lib/site-packages/pygments/lexers/jslt.py create mode 100644 venv/Lib/site-packages/pygments/lexers/json5.py create mode 100644 venv/Lib/site-packages/pygments/lexers/jsonnet.py create mode 100644 venv/Lib/site-packages/pygments/lexers/jsx.py create mode 100644 venv/Lib/site-packages/pygments/lexers/julia.py create mode 100644 venv/Lib/site-packages/pygments/lexers/jvm.py create mode 100644 venv/Lib/site-packages/pygments/lexers/kuin.py create mode 100644 venv/Lib/site-packages/pygments/lexers/kusto.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ldap.py create mode 100644 venv/Lib/site-packages/pygments/lexers/lean.py create mode 100644 venv/Lib/site-packages/pygments/lexers/lilypond.py create mode 100644 venv/Lib/site-packages/pygments/lexers/lisp.py create mode 100644 venv/Lib/site-packages/pygments/lexers/macaulay2.py create mode 100644 venv/Lib/site-packages/pygments/lexers/make.py create mode 100644 venv/Lib/site-packages/pygments/lexers/maple.py create mode 100644 venv/Lib/site-packages/pygments/lexers/markup.py create mode 100644 venv/Lib/site-packages/pygments/lexers/math.py create mode 100644 venv/Lib/site-packages/pygments/lexers/matlab.py create mode 100644 venv/Lib/site-packages/pygments/lexers/maxima.py create mode 100644 venv/Lib/site-packages/pygments/lexers/meson.py create mode 100644 venv/Lib/site-packages/pygments/lexers/mime.py create mode 100644 venv/Lib/site-packages/pygments/lexers/minecraft.py create mode 100644 venv/Lib/site-packages/pygments/lexers/mips.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ml.py create mode 100644 venv/Lib/site-packages/pygments/lexers/modeling.py create mode 100644 venv/Lib/site-packages/pygments/lexers/modula2.py create mode 100644 venv/Lib/site-packages/pygments/lexers/mojo.py create mode 100644 venv/Lib/site-packages/pygments/lexers/monte.py create mode 100644 venv/Lib/site-packages/pygments/lexers/mosel.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ncl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/nimrod.py create mode 100644 venv/Lib/site-packages/pygments/lexers/nit.py create mode 100644 venv/Lib/site-packages/pygments/lexers/nix.py create mode 100644 venv/Lib/site-packages/pygments/lexers/numbair.py create mode 100644 venv/Lib/site-packages/pygments/lexers/oberon.py create mode 100644 venv/Lib/site-packages/pygments/lexers/objective.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ooc.py create mode 100644 venv/Lib/site-packages/pygments/lexers/openscad.py create mode 100644 venv/Lib/site-packages/pygments/lexers/other.py create mode 100644 venv/Lib/site-packages/pygments/lexers/parasail.py create mode 100644 venv/Lib/site-packages/pygments/lexers/parsers.py create mode 100644 venv/Lib/site-packages/pygments/lexers/pascal.py create mode 100644 venv/Lib/site-packages/pygments/lexers/pawn.py create mode 100644 venv/Lib/site-packages/pygments/lexers/pddl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/perl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/phix.py create mode 100644 venv/Lib/site-packages/pygments/lexers/php.py create mode 100644 venv/Lib/site-packages/pygments/lexers/pointless.py create mode 100644 venv/Lib/site-packages/pygments/lexers/pony.py create mode 100644 venv/Lib/site-packages/pygments/lexers/praat.py create mode 100644 venv/Lib/site-packages/pygments/lexers/procfile.py create mode 100644 venv/Lib/site-packages/pygments/lexers/prolog.py create mode 100644 venv/Lib/site-packages/pygments/lexers/promql.py create mode 100644 venv/Lib/site-packages/pygments/lexers/prql.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ptx.py create mode 100644 venv/Lib/site-packages/pygments/lexers/python.py create mode 100644 venv/Lib/site-packages/pygments/lexers/q.py create mode 100644 venv/Lib/site-packages/pygments/lexers/qlik.py create mode 100644 venv/Lib/site-packages/pygments/lexers/qvt.py create mode 100644 venv/Lib/site-packages/pygments/lexers/r.py create mode 100644 venv/Lib/site-packages/pygments/lexers/rdf.py create mode 100644 venv/Lib/site-packages/pygments/lexers/rebol.py create mode 100644 venv/Lib/site-packages/pygments/lexers/rego.py create mode 100644 venv/Lib/site-packages/pygments/lexers/resource.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ride.py create mode 100644 venv/Lib/site-packages/pygments/lexers/rita.py create mode 100644 venv/Lib/site-packages/pygments/lexers/rnc.py create mode 100644 venv/Lib/site-packages/pygments/lexers/roboconf.py create mode 100644 venv/Lib/site-packages/pygments/lexers/robotframework.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ruby.py create mode 100644 venv/Lib/site-packages/pygments/lexers/rust.py create mode 100644 venv/Lib/site-packages/pygments/lexers/sas.py create mode 100644 venv/Lib/site-packages/pygments/lexers/savi.py create mode 100644 venv/Lib/site-packages/pygments/lexers/scdoc.py create mode 100644 venv/Lib/site-packages/pygments/lexers/scripting.py create mode 100644 venv/Lib/site-packages/pygments/lexers/sgf.py create mode 100644 venv/Lib/site-packages/pygments/lexers/shell.py create mode 100644 venv/Lib/site-packages/pygments/lexers/sieve.py create mode 100644 venv/Lib/site-packages/pygments/lexers/slash.py create mode 100644 venv/Lib/site-packages/pygments/lexers/smalltalk.py create mode 100644 venv/Lib/site-packages/pygments/lexers/smithy.py create mode 100644 venv/Lib/site-packages/pygments/lexers/smv.py create mode 100644 venv/Lib/site-packages/pygments/lexers/snobol.py create mode 100644 venv/Lib/site-packages/pygments/lexers/solidity.py create mode 100644 venv/Lib/site-packages/pygments/lexers/soong.py create mode 100644 venv/Lib/site-packages/pygments/lexers/sophia.py create mode 100644 venv/Lib/site-packages/pygments/lexers/special.py create mode 100644 venv/Lib/site-packages/pygments/lexers/spice.py create mode 100644 venv/Lib/site-packages/pygments/lexers/sql.py create mode 100644 venv/Lib/site-packages/pygments/lexers/srcinfo.py create mode 100644 venv/Lib/site-packages/pygments/lexers/stata.py create mode 100644 venv/Lib/site-packages/pygments/lexers/supercollider.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tablegen.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tact.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tal.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tcl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/teal.py create mode 100644 venv/Lib/site-packages/pygments/lexers/templates.py create mode 100644 venv/Lib/site-packages/pygments/lexers/teraterm.py create mode 100644 venv/Lib/site-packages/pygments/lexers/testing.py create mode 100644 venv/Lib/site-packages/pygments/lexers/text.py create mode 100644 venv/Lib/site-packages/pygments/lexers/textedit.py create mode 100644 venv/Lib/site-packages/pygments/lexers/textfmts.py create mode 100644 venv/Lib/site-packages/pygments/lexers/theorem.py create mode 100644 venv/Lib/site-packages/pygments/lexers/thingsdb.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tlb.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tls.py create mode 100644 venv/Lib/site-packages/pygments/lexers/tnt.py create mode 100644 venv/Lib/site-packages/pygments/lexers/trafficscript.py create mode 100644 venv/Lib/site-packages/pygments/lexers/typoscript.py create mode 100644 venv/Lib/site-packages/pygments/lexers/typst.py create mode 100644 venv/Lib/site-packages/pygments/lexers/ul4.py create mode 100644 venv/Lib/site-packages/pygments/lexers/unicon.py create mode 100644 venv/Lib/site-packages/pygments/lexers/urbi.py create mode 100644 venv/Lib/site-packages/pygments/lexers/usd.py create mode 100644 venv/Lib/site-packages/pygments/lexers/varnish.py create mode 100644 venv/Lib/site-packages/pygments/lexers/verification.py create mode 100644 venv/Lib/site-packages/pygments/lexers/verifpal.py create mode 100644 venv/Lib/site-packages/pygments/lexers/vip.py create mode 100644 venv/Lib/site-packages/pygments/lexers/vyper.py create mode 100644 venv/Lib/site-packages/pygments/lexers/web.py create mode 100644 venv/Lib/site-packages/pygments/lexers/webassembly.py create mode 100644 venv/Lib/site-packages/pygments/lexers/webidl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/webmisc.py create mode 100644 venv/Lib/site-packages/pygments/lexers/wgsl.py create mode 100644 venv/Lib/site-packages/pygments/lexers/whiley.py create mode 100644 venv/Lib/site-packages/pygments/lexers/wowtoc.py create mode 100644 venv/Lib/site-packages/pygments/lexers/wren.py create mode 100644 venv/Lib/site-packages/pygments/lexers/x10.py create mode 100644 venv/Lib/site-packages/pygments/lexers/xorg.py create mode 100644 venv/Lib/site-packages/pygments/lexers/yang.py create mode 100644 venv/Lib/site-packages/pygments/lexers/yara.py create mode 100644 venv/Lib/site-packages/pygments/lexers/zig.py create mode 100644 venv/Lib/site-packages/pygments/modeline.py create mode 100644 venv/Lib/site-packages/pygments/plugin.py create mode 100644 venv/Lib/site-packages/pygments/regexopt.py create mode 100644 venv/Lib/site-packages/pygments/scanner.py create mode 100644 venv/Lib/site-packages/pygments/sphinxext.py create mode 100644 venv/Lib/site-packages/pygments/style.py create mode 100644 venv/Lib/site-packages/pygments/styles/__init__.py create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/abap.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/algol.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/algol_nu.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/arduino.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/autumn.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/borland.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/bw.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/coffee.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/colorful.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/default.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/dracula.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/emacs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/friendly.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/friendly_grayscale.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/fruity.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/gh_dark.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/gruvbox.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/igor.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/inkpot.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/lightbulb.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/lilypond.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/lovelace.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/manni.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/material.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/monokai.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/murphy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/native.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/nord.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/onedark.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/paraiso_dark.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/paraiso_light.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/pastie.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/perldoc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/rainbow_dash.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/rrt.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/sas.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/solarized.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/staroffice.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/stata_dark.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/stata_light.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/tango.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/trac.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/vim.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/vs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/xcode.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/__pycache__/zenburn.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pygments/styles/_mapping.py create mode 100644 venv/Lib/site-packages/pygments/styles/abap.py create mode 100644 venv/Lib/site-packages/pygments/styles/algol.py create mode 100644 venv/Lib/site-packages/pygments/styles/algol_nu.py create mode 100644 venv/Lib/site-packages/pygments/styles/arduino.py create mode 100644 venv/Lib/site-packages/pygments/styles/autumn.py create mode 100644 venv/Lib/site-packages/pygments/styles/borland.py create mode 100644 venv/Lib/site-packages/pygments/styles/bw.py create mode 100644 venv/Lib/site-packages/pygments/styles/coffee.py create mode 100644 venv/Lib/site-packages/pygments/styles/colorful.py create mode 100644 venv/Lib/site-packages/pygments/styles/default.py create mode 100644 venv/Lib/site-packages/pygments/styles/dracula.py create mode 100644 venv/Lib/site-packages/pygments/styles/emacs.py create mode 100644 venv/Lib/site-packages/pygments/styles/friendly.py create mode 100644 venv/Lib/site-packages/pygments/styles/friendly_grayscale.py create mode 100644 venv/Lib/site-packages/pygments/styles/fruity.py create mode 100644 venv/Lib/site-packages/pygments/styles/gh_dark.py create mode 100644 venv/Lib/site-packages/pygments/styles/gruvbox.py create mode 100644 venv/Lib/site-packages/pygments/styles/igor.py create mode 100644 venv/Lib/site-packages/pygments/styles/inkpot.py create mode 100644 venv/Lib/site-packages/pygments/styles/lightbulb.py create mode 100644 venv/Lib/site-packages/pygments/styles/lilypond.py create mode 100644 venv/Lib/site-packages/pygments/styles/lovelace.py create mode 100644 venv/Lib/site-packages/pygments/styles/manni.py create mode 100644 venv/Lib/site-packages/pygments/styles/material.py create mode 100644 venv/Lib/site-packages/pygments/styles/monokai.py create mode 100644 venv/Lib/site-packages/pygments/styles/murphy.py create mode 100644 venv/Lib/site-packages/pygments/styles/native.py create mode 100644 venv/Lib/site-packages/pygments/styles/nord.py create mode 100644 venv/Lib/site-packages/pygments/styles/onedark.py create mode 100644 venv/Lib/site-packages/pygments/styles/paraiso_dark.py create mode 100644 venv/Lib/site-packages/pygments/styles/paraiso_light.py create mode 100644 venv/Lib/site-packages/pygments/styles/pastie.py create mode 100644 venv/Lib/site-packages/pygments/styles/perldoc.py create mode 100644 venv/Lib/site-packages/pygments/styles/rainbow_dash.py create mode 100644 venv/Lib/site-packages/pygments/styles/rrt.py create mode 100644 venv/Lib/site-packages/pygments/styles/sas.py create mode 100644 venv/Lib/site-packages/pygments/styles/solarized.py create mode 100644 venv/Lib/site-packages/pygments/styles/staroffice.py create mode 100644 venv/Lib/site-packages/pygments/styles/stata_dark.py create mode 100644 venv/Lib/site-packages/pygments/styles/stata_light.py create mode 100644 venv/Lib/site-packages/pygments/styles/tango.py create mode 100644 venv/Lib/site-packages/pygments/styles/trac.py create mode 100644 venv/Lib/site-packages/pygments/styles/vim.py create mode 100644 venv/Lib/site-packages/pygments/styles/vs.py create mode 100644 venv/Lib/site-packages/pygments/styles/xcode.py create mode 100644 venv/Lib/site-packages/pygments/styles/zenburn.py create mode 100644 venv/Lib/site-packages/pygments/token.py create mode 100644 venv/Lib/site-packages/pygments/unistring.py create mode 100644 venv/Lib/site-packages/pygments/util.py create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/METADATA create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/RECORD create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/licenses/AUTHORS create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/pytest-8.4.1.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/pytest/__init__.py create mode 100644 venv/Lib/site-packages/pytest/__main__.py create mode 100644 venv/Lib/site-packages/pytest/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pytest/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/pytest/py.typed create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/METADATA create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/RECORD create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/licenses/LICENSE create mode 100644 venv/Lib/site-packages/requests-2.32.4.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/requests/__init__.py create mode 100644 venv/Lib/site-packages/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/api.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/auth.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/certs.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/compat.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/help.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/models.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/packages.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/structures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/requests/__version__.py create mode 100644 venv/Lib/site-packages/requests/_internal_utils.py create mode 100644 venv/Lib/site-packages/requests/adapters.py create mode 100644 venv/Lib/site-packages/requests/api.py create mode 100644 venv/Lib/site-packages/requests/auth.py create mode 100644 venv/Lib/site-packages/requests/certs.py create mode 100644 venv/Lib/site-packages/requests/compat.py create mode 100644 venv/Lib/site-packages/requests/cookies.py create mode 100644 venv/Lib/site-packages/requests/exceptions.py create mode 100644 venv/Lib/site-packages/requests/help.py create mode 100644 venv/Lib/site-packages/requests/hooks.py create mode 100644 venv/Lib/site-packages/requests/models.py create mode 100644 venv/Lib/site-packages/requests/packages.py create mode 100644 venv/Lib/site-packages/requests/sessions.py create mode 100644 venv/Lib/site-packages/requests/status_codes.py create mode 100644 venv/Lib/site-packages/requests/structures.py create mode 100644 venv/Lib/site-packages/requests/utils.py create mode 100644 venv/Lib/site-packages/urllib3-2.5.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/urllib3-2.5.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/urllib3-2.5.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/urllib3-2.5.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/urllib3-2.5.0.dist-info/licenses/LICENSE.txt create mode 100644 venv/Lib/site-packages/urllib3/__init__.py create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/_base_connection.py create mode 100644 venv/Lib/site-packages/urllib3/_collections.py create mode 100644 venv/Lib/site-packages/urllib3/_request_methods.py create mode 100644 venv/Lib/site-packages/urllib3/_version.py create mode 100644 venv/Lib/site-packages/urllib3/connection.py create mode 100644 venv/Lib/site-packages/urllib3/connectionpool.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/__init__.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/__init__.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/connection.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/fetch.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/request.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/emscripten/response.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/pyopenssl.py create mode 100644 venv/Lib/site-packages/urllib3/contrib/socks.py create mode 100644 venv/Lib/site-packages/urllib3/exceptions.py create mode 100644 venv/Lib/site-packages/urllib3/fields.py create mode 100644 venv/Lib/site-packages/urllib3/filepost.py create mode 100644 venv/Lib/site-packages/urllib3/http2/__init__.py create mode 100644 venv/Lib/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/http2/__pycache__/connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/http2/connection.py create mode 100644 venv/Lib/site-packages/urllib3/http2/probe.py create mode 100644 venv/Lib/site-packages/urllib3/poolmanager.py create mode 100644 venv/Lib/site-packages/urllib3/py.typed create mode 100644 venv/Lib/site-packages/urllib3/response.py create mode 100644 venv/Lib/site-packages/urllib3/util/__init__.py create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 venv/Lib/site-packages/urllib3/util/connection.py create mode 100644 venv/Lib/site-packages/urllib3/util/proxy.py create mode 100644 venv/Lib/site-packages/urllib3/util/request.py create mode 100644 venv/Lib/site-packages/urllib3/util/response.py create mode 100644 venv/Lib/site-packages/urllib3/util/retry.py create mode 100644 venv/Lib/site-packages/urllib3/util/ssl_.py create mode 100644 venv/Lib/site-packages/urllib3/util/ssl_match_hostname.py create mode 100644 venv/Lib/site-packages/urllib3/util/ssltransport.py create mode 100644 venv/Lib/site-packages/urllib3/util/timeout.py create mode 100644 venv/Lib/site-packages/urllib3/util/url.py create mode 100644 venv/Lib/site-packages/urllib3/util/util.py create mode 100644 venv/Lib/site-packages/urllib3/util/wait.py create mode 100644 venv/Scripts/Activate.ps1 create mode 100644 venv/Scripts/activate create mode 100644 venv/Scripts/activate.bat create mode 100644 venv/Scripts/deactivate.bat create mode 100644 venv/Scripts/normalizer.exe create mode 100644 venv/Scripts/pip.exe create mode 100644 venv/Scripts/pip3.12.exe create mode 100644 venv/Scripts/pip3.exe create mode 100644 venv/Scripts/py.test.exe create mode 100644 venv/Scripts/pygmentize.exe create mode 100644 venv/Scripts/pytest.exe create mode 100644 venv/Scripts/python.exe create mode 100644 venv/Scripts/pythonw.exe create mode 100644 venv/pyvenv.cfg diff --git a/nodes/api_nodes/__pycache__/runway_text2img.cpython-312.pyc b/nodes/api_nodes/__pycache__/runway_text2img.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..594a80b3721f20cc44d53fbc2f1a0bb918eb9d2a GIT binary patch literal 3570 zcma)9-%lIK9iO$=Kd=oZ1PDh+m`l}wqhOBEs1gU!kzAUP5PBkz9t3r7;~nEQ>s@Dd z46$Xar&AsrUD^co!Kv=5<}s&KYX5{jp41l;Kj7Qsq)P2e-bg{JbWi=xt{0nx{us$K z-}&|Zy`S&+ukGz21nt9L{w6gZM(FQ+;x@5C>|O<;f)u0(8d`QnVOg-}o@Eb=9?hE( zmqmf^c{N|gzw8%~2VFsmcpoXg=N^aCav(3n{IB_BE+$yMnT)(jfCya7Gh)vCJmDO3 zdS7F*dl85VB8W=_X~MEs2`U~UDqiACiXg$a>{omwknU)`?$Ak(;=eC02lIhgn{{M9 zr*Fvl1#*uq7`ANrTQ5Q(3~d@;GBU~h;><|G$Yf=v-q8rw4TWHqk{MnlI-xQsiDezD zTy31BMh4560XLilI?HTEIaAeFu_I~7#G~tDqx^`(49U4S{mD!`-dBj5HiBo8HtYGtxnE2#N#F?S`sF1|^mUaQdWPr>ugg@Gx$8leZZHtW znpPcP0=F?GX4cS6`?RXB%bKcSgL0=3E*~3-M2?zR$x&`q!@7pzms_OXYDjH~!&^Ew z;xSL|=2S`)%%!i9JWg_PIq(-)oq((XF&ipNHR69`))jV7&SW*x4D*Ex7o6;xY8-7z z%3&u5rcTK?nPZ^43A}MaWqC6pXUVCvb{rwRQ)B02`yS>TotRD8oZ~d74M46q?eU9qA17-1HxPI(pS^RZm7uJyFrv#EjPwcA?i+=do z#-4<}g4q6En!DkxrU=g)`z4_$m|^B3FW|bn^M#;zi^5mv_Z~%5d|!J?fU8B6=5pR@ zs!;N>Hg_>?R~L8Yj7p&BEqeYKe9kq5RuYS1I^xi+z0$V!QwYG1Tl5JXe)21%gkk3p z{0EK2rVViBNUH>ge%@Fr`HH^u(RcTih|8hmFZvJor`_H8LSQ}a&2+a*HOGA!0R@$U z|KHhSpa9+jFZVY2xYQcNk#4m3xy#*Yjzzz#U8$|u_8&Yv41C^$-M;2Mm!j>5ua|=Q zM2iLe?)m1ZbhwfQ)N=B__~n1vDm>gM1`qf?>dFA$pKxWTPr6idWbeClG3efL$D1hD zIcIeoMEk<{!FfwD0`xkxUYYSi;3A+Sp}5c~XS14J4GeuWZ5n#aW3^91kua!QfEr=w zg~%mCXHYXn7V=qQqe@{U#aPyy7##(`h%02B<2S%XMvKFP>93u*2KXaJEyCF$fBGrIuMP{;H1K?5vd@&fN8cIBN#PS%XYLD>p$HNlCN&(sLXQVu7U zXk>{_`O7sHq8xmz07qb`)s%nQRv;_qHA7aoROj(viClZiKVK^ZqA9%9K!z1^jOUwH z1n^pNFPNMye~UPc!m}BagK!mk-UV@3J`MY33f%WOdezlkiEYNJ17~YpBjvz%(cV{G zC#!=CwXVf#WO2uj@X*tZr)u@Y2lavY8=v=Rs2tddp!S}}N@Z5+i>1uT_RqIl#5%q7;V~;BWgHv=y^&c8bTqd!dk zZt_`M?fAL!Y(08>J36!#9eOtYA{zhmt;1_6ZGlqc~p2%_l=N%Sh>A>> node = RunwayTextToImage(prompt="a futuristic cityscape", timeout=15) + >>> image = node.run() + >>> image.show() + + Notes: + - If the API key is missing, a clear error will be raised without crashing the program. + - To increase or decrease wait time for image generation, adjust the `timeout` parameter. + """ + + def __init__(self, prompt: str, timeout: int = 10): + self.prompt = prompt + self.timeout = timeout + + def run(self) -> Image.Image: + api_key = os.getenv("RUNWAY_API_KEY") + if not api_key: + raise RuntimeError("RUNWAY_API_KEY is not set in environment variables.") + + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + + payload = { + "prompt": self.prompt + } + + # Step 1: Send POST request to initiate generation + response = requests.post( + "https://api.dev.runwayml.com/v1/text_to_image", + json=payload, + headers=headers + ) + + if response.status_code != 200: + raise RuntimeError(f"API request failed with status {response.status_code}: {response.text}") + + data = response.json() + image_url = data.get("image_url") + if not image_url: + raise RuntimeError("API response does not contain 'image_url'.") + + # Step 2: Poll the image URL until image is ready or timeout is reached + start_time = time.time() + image_data = None + + while time.time() - start_time < self.timeout: + img_response = requests.get(image_url) + if img_response.status_code == 200 and img_response.content: + image_data = img_response.content + break + time.sleep(1) # Polling interval + + if not image_data: + raise RuntimeError("Failed to fetch image data within timeout period.") + + # Step 3: Load image from bytes into PIL.Image + return Image.open(BytesIO(image_data)) diff --git a/tests/api_nodes/__pycache__/test_runway_text2img.cpython-312-pytest-8.4.1.pyc b/tests/api_nodes/__pycache__/test_runway_text2img.cpython-312-pytest-8.4.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c97964f5cfddab608ca6e24c1cdf509897cb338 GIT binary patch literal 2498 zcma)8Uu+ab7@yhOyW6|oU0a~gR-uQb#6xKdwB?Vel=grI(+~oYUK*F>cH8c;dwb5# z(Oz?B4Dvt|BN`tF5hIV98WZ1m@PQbcnD}x{Yl@>p)cC+#u_T!K;x~JpUTH8mcQ@ak z`OWvueDlpW_d_%qM$le+>w~dQKSFSwz}7~M$=z8;H{5nDXQ~yBco3BW zX0K_FwiDy+yx~%}J?FS|^5y>xA2n!NT3H__3jBQVTdITpI-*?Xwai+S&_S5jHyRL{ z47ate=~$OcJiTJ88$lp_MV#>t?n$V+YDbrKzlooY%EX?W#U*uhRbU%E!*<|55UXpa z8-)iws4HKF#M(u5%SM-UbBx{jzFZ+fHm4#J3 zdQ+N&Q!54Zm|j~9nAnqov{hJMV`+!bRcSFOED3rUF6acrt|Y@XgpY_lMg`Q0=uR!@SfC=ZAT@2n$i{>ox$VVVp)=veE1EbL_a$5wAcmW_C`|XkIPdmN#x&P82=y@CfB|LqWZNKAr8tzL zxNXoeM~@TDbPYYOW#h&~+Q?DU$sUR)HWOY^2v2Ax>t>oV$#GfG$X+sulVuTN&^*bi zmozJHuo{3L9^7XW*aP|y4r}rEwEUxw^3Mmk`RZm zjp~R)Y>leMM9$K(8s8HOCB^n2!rnrdJffLaBE%G4%~dF?EaY*#9y6KOV^YhPU&EJH z3Fh9l!k&syan`j~=waA!Lohck+H!B-q2aS|9`3N6w={dWpBS3`hE_1hFpr8my!udh zvbz4Y$Q4+V7ru1^-s3_#z!$H7ZUR~CIzS6FBll-60LbzbRQ3?slQ3(D46 zW$V0>Sd@KHfTB>nP_;dZp}Os#*WRh0ukE`kFUoQ#iSITvT|f8HxsOvOV literal 0 HcmV?d00001 diff --git a/tests/api_nodes/test_runway_text2img.py b/tests/api_nodes/test_runway_text2img.py new file mode 100644 index 00000000..d823dc24 --- /dev/null +++ b/tests/api_nodes/test_runway_text2img.py @@ -0,0 +1,34 @@ +import os +import pytest +from unittest import mock +from unittest.mock import patch + +from nodes.api_nodes.runway_text2img import RunwayTextToImage + +@patch('nodes.api_nodes.runway_text2img.requests.post') +@patch('nodes.api_nodes.runway_text2img.requests.get') +def test_runway_text2img_node(mock_get, mock_post): + # Set environment variable + with mock.patch.dict(os.environ, {"RUNWAY_API_KEY": "fake_api_key"}): + # Mock POST response from API + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = { + "image_url": "http://fakeurl.com/fakeimage.png" + } + + # Mock GET response with minimal valid PNG bytes + mock_get.return_value.status_code = 200 + mock_get.return_value.content = ( + b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01' + b'\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89' + b'\x00\x00\x00\nIDATx\x9cc`\x00\x00\x00\x02\x00\x01' + b'\xe2!\xbc\x33\x00\x00\x00\x00IEND\xaeB`\x82' + ) + + node = RunwayTextToImage(prompt="A test prompt", timeout=5) + + try: + result = node.run() + assert result is not None + except Exception as e: + pytest.fail(f"Node run method raised an exception: {e}") diff --git a/venv/Lib/site-packages/PIL/AvifImagePlugin.py b/venv/Lib/site-packages/PIL/AvifImagePlugin.py new file mode 100644 index 00000000..366e0c86 --- /dev/null +++ b/venv/Lib/site-packages/PIL/AvifImagePlugin.py @@ -0,0 +1,291 @@ +from __future__ import annotations + +import os +from io import BytesIO +from typing import IO + +from . import ExifTags, Image, ImageFile + +try: + from . import _avif + + SUPPORTED = True +except ImportError: + SUPPORTED = False + +# Decoder options as module globals, until there is a way to pass parameters +# to Image.open (see https://github.com/python-pillow/Pillow/issues/569) +DECODE_CODEC_CHOICE = "auto" +DEFAULT_MAX_THREADS = 0 + + +def get_codec_version(codec_name: str) -> str | None: + versions = _avif.codec_versions() + for version in versions.split(", "): + if version.split(" [")[0] == codec_name: + return version.split(":")[-1].split(" ")[0] + return None + + +def _accept(prefix: bytes) -> bool | str: + if prefix[4:8] != b"ftyp": + return False + major_brand = prefix[8:12] + if major_brand in ( + # coding brands + b"avif", + b"avis", + # We accept files with AVIF container brands; we can't yet know if + # the ftyp box has the correct compatible brands, but if it doesn't + # then the plugin will raise a SyntaxError which Pillow will catch + # before moving on to the next plugin that accepts the file. + # + # Also, because this file might not actually be an AVIF file, we + # don't raise an error if AVIF support isn't properly compiled. + b"mif1", + b"msf1", + ): + if not SUPPORTED: + return ( + "image file could not be identified because AVIF support not installed" + ) + return True + return False + + +def _get_default_max_threads() -> int: + if DEFAULT_MAX_THREADS: + return DEFAULT_MAX_THREADS + if hasattr(os, "sched_getaffinity"): + return len(os.sched_getaffinity(0)) + else: + return os.cpu_count() or 1 + + +class AvifImageFile(ImageFile.ImageFile): + format = "AVIF" + format_description = "AVIF image" + __frame = -1 + + def _open(self) -> None: + if not SUPPORTED: + msg = "image file could not be opened because AVIF support not installed" + raise SyntaxError(msg) + + if DECODE_CODEC_CHOICE != "auto" and not _avif.decoder_codec_available( + DECODE_CODEC_CHOICE + ): + msg = "Invalid opening codec" + raise ValueError(msg) + self._decoder = _avif.AvifDecoder( + self.fp.read(), + DECODE_CODEC_CHOICE, + _get_default_max_threads(), + ) + + # Get info from decoder + self._size, self.n_frames, self._mode, icc, exif, exif_orientation, xmp = ( + self._decoder.get_info() + ) + self.is_animated = self.n_frames > 1 + + if icc: + self.info["icc_profile"] = icc + if xmp: + self.info["xmp"] = xmp + + if exif_orientation != 1 or exif: + exif_data = Image.Exif() + if exif: + exif_data.load(exif) + original_orientation = exif_data.get(ExifTags.Base.Orientation, 1) + else: + original_orientation = 1 + if exif_orientation != original_orientation: + exif_data[ExifTags.Base.Orientation] = exif_orientation + exif = exif_data.tobytes() + if exif: + self.info["exif"] = exif + self.seek(0) + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + + # Set tile + self.__frame = frame + self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.mode)] + + def load(self) -> Image.core.PixelAccess | None: + if self.tile: + # We need to load the image data for this frame + data, timescale, pts_in_timescales, duration_in_timescales = ( + self._decoder.get_frame(self.__frame) + ) + self.info["timestamp"] = round(1000 * (pts_in_timescales / timescale)) + self.info["duration"] = round(1000 * (duration_in_timescales / timescale)) + + if self.fp and self._exclusive_fp: + self.fp.close() + self.fp = BytesIO(data) + + return super().load() + + def load_seek(self, pos: int) -> None: + pass + + def tell(self) -> int: + return self.__frame + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, save_all=True) + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False +) -> None: + info = im.encoderinfo.copy() + if save_all: + append_images = list(info.get("append_images", [])) + else: + append_images = [] + + total = 0 + for ims in [im] + append_images: + total += getattr(ims, "n_frames", 1) + + quality = info.get("quality", 75) + if not isinstance(quality, int) or quality < 0 or quality > 100: + msg = "Invalid quality setting" + raise ValueError(msg) + + duration = info.get("duration", 0) + subsampling = info.get("subsampling", "4:2:0") + speed = info.get("speed", 6) + max_threads = info.get("max_threads", _get_default_max_threads()) + codec = info.get("codec", "auto") + if codec != "auto" and not _avif.encoder_codec_available(codec): + msg = "Invalid saving codec" + raise ValueError(msg) + range_ = info.get("range", "full") + tile_rows_log2 = info.get("tile_rows", 0) + tile_cols_log2 = info.get("tile_cols", 0) + alpha_premultiplied = bool(info.get("alpha_premultiplied", False)) + autotiling = bool(info.get("autotiling", tile_rows_log2 == tile_cols_log2 == 0)) + + icc_profile = info.get("icc_profile", im.info.get("icc_profile")) + exif_orientation = 1 + if exif := info.get("exif"): + if isinstance(exif, Image.Exif): + exif_data = exif + else: + exif_data = Image.Exif() + exif_data.load(exif) + if ExifTags.Base.Orientation in exif_data: + exif_orientation = exif_data.pop(ExifTags.Base.Orientation) + exif = exif_data.tobytes() if exif_data else b"" + elif isinstance(exif, Image.Exif): + exif = exif_data.tobytes() + + xmp = info.get("xmp") + + if isinstance(xmp, str): + xmp = xmp.encode("utf-8") + + advanced = info.get("advanced") + if advanced is not None: + if isinstance(advanced, dict): + advanced = advanced.items() + try: + advanced = tuple(advanced) + except TypeError: + invalid = True + else: + invalid = any(not isinstance(v, tuple) or len(v) != 2 for v in advanced) + if invalid: + msg = ( + "advanced codec options must be a dict of key-value string " + "pairs or a series of key-value two-tuples" + ) + raise ValueError(msg) + + # Setup the AVIF encoder + enc = _avif.AvifEncoder( + im.size, + subsampling, + quality, + speed, + max_threads, + codec, + range_, + tile_rows_log2, + tile_cols_log2, + alpha_premultiplied, + autotiling, + icc_profile or b"", + exif or b"", + exif_orientation, + xmp or b"", + advanced, + ) + + # Add each frame + frame_idx = 0 + frame_duration = 0 + cur_idx = im.tell() + is_single_frame = total == 1 + try: + for ims in [im] + append_images: + # Get number of frames in this image + nfr = getattr(ims, "n_frames", 1) + + for idx in range(nfr): + ims.seek(idx) + + # Make sure image mode is supported + frame = ims + rawmode = ims.mode + if ims.mode not in {"RGB", "RGBA"}: + rawmode = "RGBA" if ims.has_transparency_data else "RGB" + frame = ims.convert(rawmode) + + # Update frame duration + if isinstance(duration, (list, tuple)): + frame_duration = duration[frame_idx] + else: + frame_duration = duration + + # Append the frame to the animation encoder + enc.add( + frame.tobytes("raw", rawmode), + frame_duration, + frame.size, + rawmode, + is_single_frame, + ) + + # Update frame index + frame_idx += 1 + + if not save_all: + break + + finally: + im.seek(cur_idx) + + # Get the final output from the encoder + data = enc.finish() + if data is None: + msg = "cannot write file as AVIF (encoder returned None)" + raise OSError(msg) + + fp.write(data) + + +Image.register_open(AvifImageFile.format, AvifImageFile, _accept) +if SUPPORTED: + Image.register_save(AvifImageFile.format, _save) + Image.register_save_all(AvifImageFile.format, _save_all) + Image.register_extensions(AvifImageFile.format, [".avif", ".avifs"]) + Image.register_mime(AvifImageFile.format, "image/avif") diff --git a/venv/Lib/site-packages/PIL/BdfFontFile.py b/venv/Lib/site-packages/PIL/BdfFontFile.py new file mode 100644 index 00000000..f175e2f4 --- /dev/null +++ b/venv/Lib/site-packages/PIL/BdfFontFile.py @@ -0,0 +1,122 @@ +# +# The Python Imaging Library +# $Id$ +# +# bitmap distribution font (bdf) file parser +# +# history: +# 1996-05-16 fl created (as bdf2pil) +# 1997-08-25 fl converted to FontFile driver +# 2001-05-25 fl removed bogus __init__ call +# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev) +# 2003-04-22 fl more robustification (from Graham Dumpleton) +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1997-2003 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +""" +Parse X Bitmap Distribution Format (BDF) +""" +from __future__ import annotations + +from typing import BinaryIO + +from . import FontFile, Image + + +def bdf_char( + f: BinaryIO, +) -> ( + tuple[ + str, + int, + tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]], + Image.Image, + ] + | None +): + # skip to STARTCHAR + while True: + s = f.readline() + if not s: + return None + if s.startswith(b"STARTCHAR"): + break + id = s[9:].strip().decode("ascii") + + # load symbol properties + props = {} + while True: + s = f.readline() + if not s or s.startswith(b"BITMAP"): + break + i = s.find(b" ") + props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") + + # load bitmap + bitmap = bytearray() + while True: + s = f.readline() + if not s or s.startswith(b"ENDCHAR"): + break + bitmap += s[:-1] + + # The word BBX + # followed by the width in x (BBw), height in y (BBh), + # and x and y displacement (BBxoff0, BByoff0) + # of the lower left corner from the origin of the character. + width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split()) + + # The word DWIDTH + # followed by the width in x and y of the character in device pixels. + dwx, dwy = (int(p) for p in props["DWIDTH"].split()) + + bbox = ( + (dwx, dwy), + (x_disp, -y_disp - height, width + x_disp, -y_disp), + (0, 0, width, height), + ) + + try: + im = Image.frombytes("1", (width, height), bitmap, "hex", "1") + except ValueError: + # deal with zero-width characters + im = Image.new("1", (width, height)) + + return id, int(props["ENCODING"]), bbox, im + + +class BdfFontFile(FontFile.FontFile): + """Font file plugin for the X11 BDF format.""" + + def __init__(self, fp: BinaryIO) -> None: + super().__init__() + + s = fp.readline() + if not s.startswith(b"STARTFONT 2.1"): + msg = "not a valid BDF file" + raise SyntaxError(msg) + + props = {} + comments = [] + + while True: + s = fp.readline() + if not s or s.startswith(b"ENDPROPERTIES"): + break + i = s.find(b" ") + props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") + if s[:i] in [b"COMMENT", b"COPYRIGHT"]: + if s.find(b"LogicalFontDescription") < 0: + comments.append(s[i + 1 : -1].decode("ascii")) + + while True: + c = bdf_char(fp) + if not c: + break + id, ch, (xy, dst, src), im = c + if 0 <= ch < len(self.glyph): + self.glyph[ch] = xy, dst, src, im diff --git a/venv/Lib/site-packages/PIL/BlpImagePlugin.py b/venv/Lib/site-packages/PIL/BlpImagePlugin.py new file mode 100644 index 00000000..f7be7746 --- /dev/null +++ b/venv/Lib/site-packages/PIL/BlpImagePlugin.py @@ -0,0 +1,497 @@ +""" +Blizzard Mipmap Format (.blp) +Jerome Leclanche + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: + https://creativecommons.org/publicdomain/zero/1.0/ + +BLP1 files, used mostly in Warcraft III, are not fully supported. +All types of BLP2 files used in World of Warcraft are supported. + +The BLP file structure consists of a header, up to 16 mipmaps of the +texture + +Texture sizes must be powers of two, though the two dimensions do +not have to be equal; 512x256 is valid, but 512x200 is not. +The first mipmap (mipmap #0) is the full size image; each subsequent +mipmap halves both dimensions. The final mipmap should be 1x1. + +BLP files come in many different flavours: +* JPEG-compressed (type == 0) - only supported for BLP1. +* RAW images (type == 1, encoding == 1). Each mipmap is stored as an + array of 8-bit values, one per pixel, left to right, top to bottom. + Each value is an index to the palette. +* DXT-compressed (type == 1, encoding == 2): +- DXT1 compression is used if alpha_encoding == 0. + - An additional alpha bit is used if alpha_depth == 1. + - DXT3 compression is used if alpha_encoding == 1. + - DXT5 compression is used if alpha_encoding == 7. +""" + +from __future__ import annotations + +import abc +import os +import struct +from enum import IntEnum +from io import BytesIO +from typing import IO + +from . import Image, ImageFile + + +class Format(IntEnum): + JPEG = 0 + + +class Encoding(IntEnum): + UNCOMPRESSED = 1 + DXT = 2 + UNCOMPRESSED_RAW_BGRA = 3 + + +class AlphaEncoding(IntEnum): + DXT1 = 0 + DXT3 = 1 + DXT5 = 7 + + +def unpack_565(i: int) -> tuple[int, int, int]: + return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3 + + +def decode_dxt1( + data: bytes, alpha: bool = False +) -> tuple[bytearray, bytearray, bytearray, bytearray]: + """ + input: one "row" of data (i.e. will produce 4*width pixels) + """ + + blocks = len(data) // 8 # number of blocks in row + ret = (bytearray(), bytearray(), bytearray(), bytearray()) + + for block_index in range(blocks): + # Decode next 8-byte block. + idx = block_index * 8 + color0, color1, bits = struct.unpack_from("> 2 + + a = 0xFF + if control == 0: + r, g, b = r0, g0, b0 + elif control == 1: + r, g, b = r1, g1, b1 + elif control == 2: + if color0 > color1: + r = (2 * r0 + r1) // 3 + g = (2 * g0 + g1) // 3 + b = (2 * b0 + b1) // 3 + else: + r = (r0 + r1) // 2 + g = (g0 + g1) // 2 + b = (b0 + b1) // 2 + elif control == 3: + if color0 > color1: + r = (2 * r1 + r0) // 3 + g = (2 * g1 + g0) // 3 + b = (2 * b1 + b0) // 3 + else: + r, g, b, a = 0, 0, 0, 0 + + if alpha: + ret[j].extend([r, g, b, a]) + else: + ret[j].extend([r, g, b]) + + return ret + + +def decode_dxt3(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]: + """ + input: one "row" of data (i.e. will produce 4*width pixels) + """ + + blocks = len(data) // 16 # number of blocks in row + ret = (bytearray(), bytearray(), bytearray(), bytearray()) + + for block_index in range(blocks): + idx = block_index * 16 + block = data[idx : idx + 16] + # Decode next 16-byte block. + bits = struct.unpack_from("<8B", block) + color0, color1 = struct.unpack_from(">= 4 + else: + high = True + a &= 0xF + a *= 17 # We get a value between 0 and 15 + + color_code = (code >> 2 * (4 * j + i)) & 0x03 + + if color_code == 0: + r, g, b = r0, g0, b0 + elif color_code == 1: + r, g, b = r1, g1, b1 + elif color_code == 2: + r = (2 * r0 + r1) // 3 + g = (2 * g0 + g1) // 3 + b = (2 * b0 + b1) // 3 + elif color_code == 3: + r = (2 * r1 + r0) // 3 + g = (2 * g1 + g0) // 3 + b = (2 * b1 + b0) // 3 + + ret[j].extend([r, g, b, a]) + + return ret + + +def decode_dxt5(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]: + """ + input: one "row" of data (i.e. will produce 4 * width pixels) + """ + + blocks = len(data) // 16 # number of blocks in row + ret = (bytearray(), bytearray(), bytearray(), bytearray()) + + for block_index in range(blocks): + idx = block_index * 16 + block = data[idx : idx + 16] + # Decode next 16-byte block. + a0, a1 = struct.unpack_from("> alphacode_index) & 0x07 + elif alphacode_index == 15: + alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06) + else: # alphacode_index >= 18 and alphacode_index <= 45 + alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07 + + if alphacode == 0: + a = a0 + elif alphacode == 1: + a = a1 + elif a0 > a1: + a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7 + elif alphacode == 6: + a = 0 + elif alphacode == 7: + a = 255 + else: + a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5 + + color_code = (code >> 2 * (4 * j + i)) & 0x03 + + if color_code == 0: + r, g, b = r0, g0, b0 + elif color_code == 1: + r, g, b = r1, g1, b1 + elif color_code == 2: + r = (2 * r0 + r1) // 3 + g = (2 * g0 + g1) // 3 + b = (2 * b0 + b1) // 3 + elif color_code == 3: + r = (2 * r1 + r0) // 3 + g = (2 * g1 + g0) // 3 + b = (2 * b1 + b0) // 3 + + ret[j].extend([r, g, b, a]) + + return ret + + +class BLPFormatError(NotImplementedError): + pass + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"BLP1", b"BLP2")) + + +class BlpImageFile(ImageFile.ImageFile): + """ + Blizzard Mipmap Format + """ + + format = "BLP" + format_description = "Blizzard Mipmap Format" + + def _open(self) -> None: + self.magic = self.fp.read(4) + if not _accept(self.magic): + msg = f"Bad BLP magic {repr(self.magic)}" + raise BLPFormatError(msg) + + compression = struct.unpack(" tuple[int, int]: + try: + self._read_header() + self._load() + except struct.error as e: + msg = "Truncated BLP file" + raise OSError(msg) from e + return -1, 0 + + @abc.abstractmethod + def _load(self) -> None: + pass + + def _read_header(self) -> None: + self._offsets = struct.unpack("<16I", self._safe_read(16 * 4)) + self._lengths = struct.unpack("<16I", self._safe_read(16 * 4)) + + def _safe_read(self, length: int) -> bytes: + assert self.fd is not None + return ImageFile._safe_read(self.fd, length) + + def _read_palette(self) -> list[tuple[int, int, int, int]]: + ret = [] + for i in range(256): + try: + b, g, r, a = struct.unpack("<4B", self._safe_read(4)) + except struct.error: + break + ret.append((b, g, r, a)) + return ret + + def _read_bgra( + self, palette: list[tuple[int, int, int, int]], alpha: bool + ) -> bytearray: + data = bytearray() + _data = BytesIO(self._safe_read(self._lengths[0])) + while True: + try: + (offset,) = struct.unpack(" None: + self._compression, self._encoding, alpha = self.args + + if self._compression == Format.JPEG: + self._decode_jpeg_stream() + + elif self._compression == 1: + if self._encoding in (4, 5): + palette = self._read_palette() + data = self._read_bgra(palette, alpha) + self.set_as_raw(data) + else: + msg = f"Unsupported BLP encoding {repr(self._encoding)}" + raise BLPFormatError(msg) + else: + msg = f"Unsupported BLP compression {repr(self._encoding)}" + raise BLPFormatError(msg) + + def _decode_jpeg_stream(self) -> None: + from .JpegImagePlugin import JpegImageFile + + (jpeg_header_size,) = struct.unpack(" None: + self._compression, self._encoding, alpha, self._alpha_encoding = self.args + + palette = self._read_palette() + + assert self.fd is not None + self.fd.seek(self._offsets[0]) + + if self._compression == 1: + # Uncompressed or DirectX compression + + if self._encoding == Encoding.UNCOMPRESSED: + data = self._read_bgra(palette, alpha) + + elif self._encoding == Encoding.DXT: + data = bytearray() + if self._alpha_encoding == AlphaEncoding.DXT1: + linesize = (self.state.xsize + 3) // 4 * 8 + for yb in range((self.state.ysize + 3) // 4): + for d in decode_dxt1(self._safe_read(linesize), alpha): + data += d + + elif self._alpha_encoding == AlphaEncoding.DXT3: + linesize = (self.state.xsize + 3) // 4 * 16 + for yb in range((self.state.ysize + 3) // 4): + for d in decode_dxt3(self._safe_read(linesize)): + data += d + + elif self._alpha_encoding == AlphaEncoding.DXT5: + linesize = (self.state.xsize + 3) // 4 * 16 + for yb in range((self.state.ysize + 3) // 4): + for d in decode_dxt5(self._safe_read(linesize)): + data += d + else: + msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}" + raise BLPFormatError(msg) + else: + msg = f"Unknown BLP encoding {repr(self._encoding)}" + raise BLPFormatError(msg) + + else: + msg = f"Unknown BLP compression {repr(self._compression)}" + raise BLPFormatError(msg) + + self.set_as_raw(data) + + +class BLPEncoder(ImageFile.PyEncoder): + _pushes_fd = True + + def _write_palette(self) -> bytes: + data = b"" + assert self.im is not None + palette = self.im.getpalette("RGBA", "RGBA") + for i in range(len(palette) // 4): + r, g, b, a = palette[i * 4 : (i + 1) * 4] + data += struct.pack("<4B", b, g, r, a) + while len(data) < 256 * 4: + data += b"\x00" * 4 + return data + + def encode(self, bufsize: int) -> tuple[int, int, bytes]: + palette_data = self._write_palette() + + offset = 20 + 16 * 4 * 2 + len(palette_data) + data = struct.pack("<16I", offset, *((0,) * 15)) + + assert self.im is not None + w, h = self.im.size + data += struct.pack("<16I", w * h, *((0,) * 15)) + + data += palette_data + + for y in range(h): + for x in range(w): + data += struct.pack(" None: + if im.mode != "P": + msg = "Unsupported BLP image mode" + raise ValueError(msg) + + magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2" + fp.write(magic) + + assert im.palette is not None + fp.write(struct.pack(" mode, rawmode + 1: ("P", "P;1"), + 4: ("P", "P;4"), + 8: ("P", "P"), + 16: ("RGB", "BGR;15"), + 24: ("RGB", "BGR"), + 32: ("RGB", "BGRX"), +} + +USE_RAW_ALPHA = False + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"BM") + + +def _dib_accept(prefix: bytes) -> bool: + return i32(prefix) in [12, 40, 52, 56, 64, 108, 124] + + +# ============================================================================= +# Image plugin for the Windows BMP format. +# ============================================================================= +class BmpImageFile(ImageFile.ImageFile): + """Image plugin for the Windows Bitmap format (BMP)""" + + # ------------------------------------------------------------- Description + format_description = "Windows Bitmap" + format = "BMP" + + # -------------------------------------------------- BMP Compression values + COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5} + for k, v in COMPRESSIONS.items(): + vars()[k] = v + + def _bitmap(self, header: int = 0, offset: int = 0) -> None: + """Read relevant info about the BMP""" + read, seek = self.fp.read, self.fp.seek + if header: + seek(header) + # read bmp header size @offset 14 (this is part of the header size) + file_info: dict[str, bool | int | tuple[int, ...]] = { + "header_size": i32(read(4)), + "direction": -1, + } + + # -------------------- If requested, read header at a specific position + # read the rest of the bmp header, without its size + assert isinstance(file_info["header_size"], int) + header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) + + # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 + # ----- This format has different offsets because of width/height types + # 12: BITMAPCOREHEADER/OS21XBITMAPHEADER + if file_info["header_size"] == 12: + file_info["width"] = i16(header_data, 0) + file_info["height"] = i16(header_data, 2) + file_info["planes"] = i16(header_data, 4) + file_info["bits"] = i16(header_data, 6) + file_info["compression"] = self.COMPRESSIONS["RAW"] + file_info["palette_padding"] = 3 + + # --------------------------------------------- Windows Bitmap v3 to v5 + # 40: BITMAPINFOHEADER + # 52: BITMAPV2HEADER + # 56: BITMAPV3HEADER + # 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER + # 108: BITMAPV4HEADER + # 124: BITMAPV5HEADER + elif file_info["header_size"] in (40, 52, 56, 64, 108, 124): + file_info["y_flip"] = header_data[7] == 0xFF + file_info["direction"] = 1 if file_info["y_flip"] else -1 + file_info["width"] = i32(header_data, 0) + file_info["height"] = ( + i32(header_data, 4) + if not file_info["y_flip"] + else 2**32 - i32(header_data, 4) + ) + file_info["planes"] = i16(header_data, 8) + file_info["bits"] = i16(header_data, 10) + file_info["compression"] = i32(header_data, 12) + # byte size of pixel data + file_info["data_size"] = i32(header_data, 16) + file_info["pixels_per_meter"] = ( + i32(header_data, 20), + i32(header_data, 24), + ) + file_info["colors"] = i32(header_data, 28) + file_info["palette_padding"] = 4 + assert isinstance(file_info["pixels_per_meter"], tuple) + self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: + masks = ["r_mask", "g_mask", "b_mask"] + if len(header_data) >= 48: + if len(header_data) >= 52: + masks.append("a_mask") + else: + file_info["a_mask"] = 0x0 + for idx, mask in enumerate(masks): + file_info[mask] = i32(header_data, 36 + idx * 4) + else: + # 40 byte headers only have the three components in the + # bitfields masks, ref: + # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx + # See also + # https://github.com/python-pillow/Pillow/issues/1293 + # There is a 4th component in the RGBQuad, in the alpha + # location, but it is listed as a reserved component, + # and it is not generally an alpha channel + file_info["a_mask"] = 0x0 + for mask in masks: + file_info[mask] = i32(read(4)) + assert isinstance(file_info["r_mask"], int) + assert isinstance(file_info["g_mask"], int) + assert isinstance(file_info["b_mask"], int) + assert isinstance(file_info["a_mask"], int) + file_info["rgb_mask"] = ( + file_info["r_mask"], + file_info["g_mask"], + file_info["b_mask"], + ) + file_info["rgba_mask"] = ( + file_info["r_mask"], + file_info["g_mask"], + file_info["b_mask"], + file_info["a_mask"], + ) + else: + msg = f"Unsupported BMP header type ({file_info['header_size']})" + raise OSError(msg) + + # ------------------ Special case : header is reported 40, which + # ---------------------- is shorter than real size for bpp >= 16 + assert isinstance(file_info["width"], int) + assert isinstance(file_info["height"], int) + self._size = file_info["width"], file_info["height"] + + # ------- If color count was not found in the header, compute from bits + assert isinstance(file_info["bits"], int) + file_info["colors"] = ( + file_info["colors"] + if file_info.get("colors", 0) + else (1 << file_info["bits"]) + ) + assert isinstance(file_info["colors"], int) + if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: + offset += 4 * file_info["colors"] + + # ---------------------- Check bit depth for unusual unsupported values + self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", "")) + if not self.mode: + msg = f"Unsupported BMP pixel depth ({file_info['bits']})" + raise OSError(msg) + + # ---------------- Process BMP with Bitfields compression (not palette) + decoder_name = "raw" + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: + SUPPORTED: dict[int, list[tuple[int, ...]]] = { + 32: [ + (0xFF0000, 0xFF00, 0xFF, 0x0), + (0xFF000000, 0xFF0000, 0xFF00, 0x0), + (0xFF000000, 0xFF00, 0xFF, 0x0), + (0xFF000000, 0xFF0000, 0xFF00, 0xFF), + (0xFF, 0xFF00, 0xFF0000, 0xFF000000), + (0xFF0000, 0xFF00, 0xFF, 0xFF000000), + (0xFF000000, 0xFF00, 0xFF, 0xFF0000), + (0x0, 0x0, 0x0, 0x0), + ], + 24: [(0xFF0000, 0xFF00, 0xFF)], + 16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)], + } + MASK_MODES = { + (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX", + (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR", + (32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR", + (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR", + (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA", + (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA", + (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR", + (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", + (24, (0xFF0000, 0xFF00, 0xFF)): "BGR", + (16, (0xF800, 0x7E0, 0x1F)): "BGR;16", + (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15", + } + if file_info["bits"] in SUPPORTED: + if ( + file_info["bits"] == 32 + and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] + ): + assert isinstance(file_info["rgba_mask"], tuple) + raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] + self._mode = "RGBA" if "A" in raw_mode else self.mode + elif ( + file_info["bits"] in (24, 16) + and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] + ): + assert isinstance(file_info["rgb_mask"], tuple) + raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] + else: + msg = "Unsupported BMP bitfields layout" + raise OSError(msg) + else: + msg = "Unsupported BMP bitfields layout" + raise OSError(msg) + elif file_info["compression"] == self.COMPRESSIONS["RAW"]: + if file_info["bits"] == 32 and ( + header == 22 or USE_RAW_ALPHA # 32-bit .cur offset + ): + raw_mode, self._mode = "BGRA", "RGBA" + elif file_info["compression"] in ( + self.COMPRESSIONS["RLE8"], + self.COMPRESSIONS["RLE4"], + ): + decoder_name = "bmp_rle" + else: + msg = f"Unsupported BMP compression ({file_info['compression']})" + raise OSError(msg) + + # --------------- Once the header is processed, process the palette/LUT + if self.mode == "P": # Paletted for 1, 4 and 8 bit images + # ---------------------------------------------------- 1-bit images + if not (0 < file_info["colors"] <= 65536): + msg = f"Unsupported BMP Palette size ({file_info['colors']})" + raise OSError(msg) + else: + assert isinstance(file_info["palette_padding"], int) + padding = file_info["palette_padding"] + palette = read(padding * file_info["colors"]) + grayscale = True + indices = ( + (0, 255) + if file_info["colors"] == 2 + else list(range(file_info["colors"])) + ) + + # ----------------- Check if grayscale and ignore palette if so + for ind, val in enumerate(indices): + rgb = palette[ind * padding : ind * padding + 3] + if rgb != o8(val) * 3: + grayscale = False + + # ------- If all colors are gray, white or black, ditch palette + if grayscale: + self._mode = "1" if file_info["colors"] == 2 else "L" + raw_mode = self.mode + else: + self._mode = "P" + self.palette = ImagePalette.raw( + "BGRX" if padding == 4 else "BGR", palette + ) + + # ---------------------------- Finally set the tile data for the plugin + self.info["compression"] = file_info["compression"] + args: list[Any] = [raw_mode] + if decoder_name == "bmp_rle": + args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"]) + else: + assert isinstance(file_info["width"], int) + args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) + args.append(file_info["direction"]) + self.tile = [ + ImageFile._Tile( + decoder_name, + (0, 0, file_info["width"], file_info["height"]), + offset or self.fp.tell(), + tuple(args), + ) + ] + + def _open(self) -> None: + """Open file, check magic number and read header""" + # read 14 bytes: magic number, filesize, reserved, header final offset + head_data = self.fp.read(14) + # choke if the file does not have the required magic bytes + if not _accept(head_data): + msg = "Not a BMP file" + raise SyntaxError(msg) + # read the start position of the BMP image data (u32) + offset = i32(head_data, 10) + # load bitmap information (offset=raster info) + self._bitmap(offset=offset) + + +class BmpRleDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + rle4 = self.args[1] + data = bytearray() + x = 0 + dest_length = self.state.xsize * self.state.ysize + while len(data) < dest_length: + pixels = self.fd.read(1) + byte = self.fd.read(1) + if not pixels or not byte: + break + num_pixels = pixels[0] + if num_pixels: + # encoded mode + if x + num_pixels > self.state.xsize: + # Too much data for row + num_pixels = max(0, self.state.xsize - x) + if rle4: + first_pixel = o8(byte[0] >> 4) + second_pixel = o8(byte[0] & 0x0F) + for index in range(num_pixels): + if index % 2 == 0: + data += first_pixel + else: + data += second_pixel + else: + data += byte * num_pixels + x += num_pixels + else: + if byte[0] == 0: + # end of line + while len(data) % self.state.xsize != 0: + data += b"\x00" + x = 0 + elif byte[0] == 1: + # end of bitmap + break + elif byte[0] == 2: + # delta + bytes_read = self.fd.read(2) + if len(bytes_read) < 2: + break + right, up = self.fd.read(2) + data += b"\x00" * (right + up * self.state.xsize) + x = len(data) % self.state.xsize + else: + # absolute mode + if rle4: + # 2 pixels per byte + byte_count = byte[0] // 2 + bytes_read = self.fd.read(byte_count) + for byte_read in bytes_read: + data += o8(byte_read >> 4) + data += o8(byte_read & 0x0F) + else: + byte_count = byte[0] + bytes_read = self.fd.read(byte_count) + data += bytes_read + if len(bytes_read) < byte_count: + break + x += byte[0] + + # align to 16-bit word boundary + if self.fd.tell() % 2 != 0: + self.fd.seek(1, os.SEEK_CUR) + rawmode = "L" if self.mode == "L" else "P" + self.set_as_raw(bytes(data), rawmode, (0, self.args[-1])) + return -1, 0 + + +# ============================================================================= +# Image plugin for the DIB format (BMP alias) +# ============================================================================= +class DibImageFile(BmpImageFile): + format = "DIB" + format_description = "Windows Bitmap" + + def _open(self) -> None: + self._bitmap() + + +# +# -------------------------------------------------------------------- +# Write BMP file + + +SAVE = { + "1": ("1", 1, 2), + "L": ("L", 8, 256), + "P": ("P", 8, 256), + "RGB": ("BGR", 24, 0), + "RGBA": ("BGRA", 32, 0), +} + + +def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, False) + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True +) -> None: + try: + rawmode, bits, colors = SAVE[im.mode] + except KeyError as e: + msg = f"cannot write mode {im.mode} as BMP" + raise OSError(msg) from e + + info = im.encoderinfo + + dpi = info.get("dpi", (96, 96)) + + # 1 meter == 39.3701 inches + ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi) + + stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) + header = 40 # or 64 for OS/2 version 2 + image = stride * im.size[1] + + if im.mode == "1": + palette = b"".join(o8(i) * 3 + b"\x00" for i in (0, 255)) + elif im.mode == "L": + palette = b"".join(o8(i) * 3 + b"\x00" for i in range(256)) + elif im.mode == "P": + palette = im.im.getpalette("RGB", "BGRX") + colors = len(palette) // 4 + else: + palette = None + + # bitmap header + if bitmap_header: + offset = 14 + header + colors * 4 + file_size = offset + image + if file_size > 2**32 - 1: + msg = "File size is too large for the BMP format" + raise ValueError(msg) + fp.write( + b"BM" # file type (magic) + + o32(file_size) # file size + + o32(0) # reserved + + o32(offset) # image data offset + ) + + # bitmap info header + fp.write( + o32(header) # info header size + + o32(im.size[0]) # width + + o32(im.size[1]) # height + + o16(1) # planes + + o16(bits) # depth + + o32(0) # compression (0=uncompressed) + + o32(image) # size of bitmap + + o32(ppm[0]) # resolution + + o32(ppm[1]) # resolution + + o32(colors) # colors used + + o32(colors) # colors important + ) + + fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) + + if palette: + fp.write(palette) + + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))] + ) + + +# +# -------------------------------------------------------------------- +# Registry + + +Image.register_open(BmpImageFile.format, BmpImageFile, _accept) +Image.register_save(BmpImageFile.format, _save) + +Image.register_extension(BmpImageFile.format, ".bmp") + +Image.register_mime(BmpImageFile.format, "image/bmp") + +Image.register_decoder("bmp_rle", BmpRleDecoder) + +Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) +Image.register_save(DibImageFile.format, _dib_save) + +Image.register_extension(DibImageFile.format, ".dib") + +Image.register_mime(DibImageFile.format, "image/bmp") diff --git a/venv/Lib/site-packages/PIL/BufrStubImagePlugin.py b/venv/Lib/site-packages/PIL/BufrStubImagePlugin.py new file mode 100644 index 00000000..8c5da14f --- /dev/null +++ b/venv/Lib/site-packages/PIL/BufrStubImagePlugin.py @@ -0,0 +1,75 @@ +# +# The Python Imaging Library +# $Id$ +# +# BUFR stub adapter +# +# Copyright (c) 1996-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific BUFR image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +# -------------------------------------------------------------------- +# Image adapter + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"BUFR", b"ZCZC")) + + +class BufrStubImageFile(ImageFile.StubImageFile): + format = "BUFR" + format_description = "BUFR" + + def _open(self) -> None: + if not _accept(self.fp.read(4)): + msg = "Not a BUFR file" + raise SyntaxError(msg) + + self.fp.seek(-4, os.SEEK_CUR) + + # make something up + self._mode = "F" + self._size = 1, 1 + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "BUFR save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept) +Image.register_save(BufrStubImageFile.format, _save) + +Image.register_extension(BufrStubImageFile.format, ".bufr") diff --git a/venv/Lib/site-packages/PIL/ContainerIO.py b/venv/Lib/site-packages/PIL/ContainerIO.py new file mode 100644 index 00000000..ec9e66c7 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ContainerIO.py @@ -0,0 +1,173 @@ +# +# The Python Imaging Library. +# $Id$ +# +# a class to read from a container file +# +# History: +# 1995-06-18 fl Created +# 1995-09-07 fl Added readline(), readlines() +# +# Copyright (c) 1997-2001 by Secret Labs AB +# Copyright (c) 1995 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +from collections.abc import Iterable +from typing import IO, AnyStr, NoReturn + + +class ContainerIO(IO[AnyStr]): + """ + A file object that provides read access to a part of an existing + file (for example a TAR file). + """ + + def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None: + """ + Create file object. + + :param file: Existing file. + :param offset: Start of region, in bytes. + :param length: Size of region, in bytes. + """ + self.fh: IO[AnyStr] = file + self.pos = 0 + self.offset = offset + self.length = length + self.fh.seek(offset) + + ## + # Always false. + + def isatty(self) -> bool: + return False + + def seekable(self) -> bool: + return True + + def seek(self, offset: int, mode: int = io.SEEK_SET) -> int: + """ + Move file pointer. + + :param offset: Offset in bytes. + :param mode: Starting position. Use 0 for beginning of region, 1 + for current offset, and 2 for end of region. You cannot move + the pointer outside the defined region. + :returns: Offset from start of region, in bytes. + """ + if mode == 1: + self.pos = self.pos + offset + elif mode == 2: + self.pos = self.length + offset + else: + self.pos = offset + # clamp + self.pos = max(0, min(self.pos, self.length)) + self.fh.seek(self.offset + self.pos) + return self.pos + + def tell(self) -> int: + """ + Get current file pointer. + + :returns: Offset from start of region, in bytes. + """ + return self.pos + + def readable(self) -> bool: + return True + + def read(self, n: int = -1) -> AnyStr: + """ + Read data. + + :param n: Number of bytes to read. If omitted, zero or negative, + read until end of region. + :returns: An 8-bit string. + """ + if n > 0: + n = min(n, self.length - self.pos) + else: + n = self.length - self.pos + if n <= 0: # EOF + return b"" if "b" in self.fh.mode else "" # type: ignore[return-value] + self.pos = self.pos + n + return self.fh.read(n) + + def readline(self, n: int = -1) -> AnyStr: + """ + Read a line of text. + + :param n: Number of bytes to read. If omitted, zero or negative, + read until end of line. + :returns: An 8-bit string. + """ + s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment] + newline_character = b"\n" if "b" in self.fh.mode else "\n" + while True: + c = self.read(1) + if not c: + break + s = s + c + if c == newline_character or len(s) == n: + break + return s + + def readlines(self, n: int | None = -1) -> list[AnyStr]: + """ + Read multiple lines of text. + + :param n: Number of lines to read. If omitted, zero, negative or None, + read until end of region. + :returns: A list of 8-bit strings. + """ + lines = [] + while True: + s = self.readline() + if not s: + break + lines.append(s) + if len(lines) == n: + break + return lines + + def writable(self) -> bool: + return False + + def write(self, b: AnyStr) -> NoReturn: + raise NotImplementedError() + + def writelines(self, lines: Iterable[AnyStr]) -> NoReturn: + raise NotImplementedError() + + def truncate(self, size: int | None = None) -> int: + raise NotImplementedError() + + def __enter__(self) -> ContainerIO[AnyStr]: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def __iter__(self) -> ContainerIO[AnyStr]: + return self + + def __next__(self) -> AnyStr: + line = self.readline() + if not line: + msg = "end of region" + raise StopIteration(msg) + return line + + def fileno(self) -> int: + return self.fh.fileno() + + def flush(self) -> None: + self.fh.flush() + + def close(self) -> None: + self.fh.close() diff --git a/venv/Lib/site-packages/PIL/CurImagePlugin.py b/venv/Lib/site-packages/PIL/CurImagePlugin.py new file mode 100644 index 00000000..b817dbc8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/CurImagePlugin.py @@ -0,0 +1,75 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Windows Cursor support for PIL +# +# notes: +# uses BmpImagePlugin.py to read the bitmap data. +# +# history: +# 96-05-27 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import BmpImagePlugin, Image, ImageFile +from ._binary import i16le as i16 +from ._binary import i32le as i32 + +# +# -------------------------------------------------------------------- + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\0\0\2\0") + + +## +# Image plugin for Windows Cursor files. + + +class CurImageFile(BmpImagePlugin.BmpImageFile): + format = "CUR" + format_description = "Windows Cursor" + + def _open(self) -> None: + offset = self.fp.tell() + + # check magic + s = self.fp.read(6) + if not _accept(s): + msg = "not a CUR file" + raise SyntaxError(msg) + + # pick the largest cursor in the file + m = b"" + for i in range(i16(s, 4)): + s = self.fp.read(16) + if not m: + m = s + elif s[0] > m[0] and s[1] > m[1]: + m = s + if not m: + msg = "No cursors were found" + raise TypeError(msg) + + # load as bitmap + self._bitmap(i32(m, 12) + offset) + + # patch up the bitmap height + self._size = self.size[0], self.size[1] // 2 + d, e, o, a = self.tile[0] + self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a) + + +# +# -------------------------------------------------------------------- + +Image.register_open(CurImageFile.format, CurImageFile, _accept) + +Image.register_extension(CurImageFile.format, ".cur") diff --git a/venv/Lib/site-packages/PIL/DcxImagePlugin.py b/venv/Lib/site-packages/PIL/DcxImagePlugin.py new file mode 100644 index 00000000..aea661b9 --- /dev/null +++ b/venv/Lib/site-packages/PIL/DcxImagePlugin.py @@ -0,0 +1,83 @@ +# +# The Python Imaging Library. +# $Id$ +# +# DCX file handling +# +# DCX is a container file format defined by Intel, commonly used +# for fax applications. Each DCX file consists of a directory +# (a list of file offsets) followed by a set of (usually 1-bit) +# PCX files. +# +# History: +# 1995-09-09 fl Created +# 1996-03-20 fl Properly derived from PcxImageFile. +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 2002-07-30 fl Fixed file handling +# +# Copyright (c) 1997-98 by Secret Labs AB. +# Copyright (c) 1995-96 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image +from ._binary import i32le as i32 +from ._util import DeferredError +from .PcxImagePlugin import PcxImageFile + +MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 4 and i32(prefix) == MAGIC + + +## +# Image plugin for the Intel DCX format. + + +class DcxImageFile(PcxImageFile): + format = "DCX" + format_description = "Intel DCX" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # Header + s = self.fp.read(4) + if not _accept(s): + msg = "not a DCX file" + raise SyntaxError(msg) + + # Component directory + self._offset = [] + for i in range(1024): + offset = i32(self.fp.read(4)) + if not offset: + break + self._offset.append(offset) + + self._fp = self.fp + self.frame = -1 + self.n_frames = len(self._offset) + self.is_animated = self.n_frames > 1 + self.seek(0) + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self.frame = frame + self.fp = self._fp + self.fp.seek(self._offset[frame]) + PcxImageFile._open(self) + + def tell(self) -> int: + return self.frame + + +Image.register_open(DcxImageFile.format, DcxImageFile, _accept) + +Image.register_extension(DcxImageFile.format, ".dcx") diff --git a/venv/Lib/site-packages/PIL/DdsImagePlugin.py b/venv/Lib/site-packages/PIL/DdsImagePlugin.py new file mode 100644 index 00000000..f9ade18f --- /dev/null +++ b/venv/Lib/site-packages/PIL/DdsImagePlugin.py @@ -0,0 +1,624 @@ +""" +A Pillow plugin for .dds files (S3TC-compressed aka DXTC) +Jerome Leclanche + +Documentation: +https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: +https://creativecommons.org/publicdomain/zero/1.0/ +""" + +from __future__ import annotations + +import io +import struct +import sys +from enum import IntEnum, IntFlag +from typing import IO + +from . import Image, ImageFile, ImagePalette +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o32le as o32 + +# Magic ("DDS ") +DDS_MAGIC = 0x20534444 + + +# DDS flags +class DDSD(IntFlag): + CAPS = 0x1 + HEIGHT = 0x2 + WIDTH = 0x4 + PITCH = 0x8 + PIXELFORMAT = 0x1000 + MIPMAPCOUNT = 0x20000 + LINEARSIZE = 0x80000 + DEPTH = 0x800000 + + +# DDS caps +class DDSCAPS(IntFlag): + COMPLEX = 0x8 + TEXTURE = 0x1000 + MIPMAP = 0x400000 + + +class DDSCAPS2(IntFlag): + CUBEMAP = 0x200 + CUBEMAP_POSITIVEX = 0x400 + CUBEMAP_NEGATIVEX = 0x800 + CUBEMAP_POSITIVEY = 0x1000 + CUBEMAP_NEGATIVEY = 0x2000 + CUBEMAP_POSITIVEZ = 0x4000 + CUBEMAP_NEGATIVEZ = 0x8000 + VOLUME = 0x200000 + + +# Pixel Format +class DDPF(IntFlag): + ALPHAPIXELS = 0x1 + ALPHA = 0x2 + FOURCC = 0x4 + PALETTEINDEXED8 = 0x20 + RGB = 0x40 + LUMINANCE = 0x20000 + + +# dxgiformat.h +class DXGI_FORMAT(IntEnum): + UNKNOWN = 0 + R32G32B32A32_TYPELESS = 1 + R32G32B32A32_FLOAT = 2 + R32G32B32A32_UINT = 3 + R32G32B32A32_SINT = 4 + R32G32B32_TYPELESS = 5 + R32G32B32_FLOAT = 6 + R32G32B32_UINT = 7 + R32G32B32_SINT = 8 + R16G16B16A16_TYPELESS = 9 + R16G16B16A16_FLOAT = 10 + R16G16B16A16_UNORM = 11 + R16G16B16A16_UINT = 12 + R16G16B16A16_SNORM = 13 + R16G16B16A16_SINT = 14 + R32G32_TYPELESS = 15 + R32G32_FLOAT = 16 + R32G32_UINT = 17 + R32G32_SINT = 18 + R32G8X24_TYPELESS = 19 + D32_FLOAT_S8X24_UINT = 20 + R32_FLOAT_X8X24_TYPELESS = 21 + X32_TYPELESS_G8X24_UINT = 22 + R10G10B10A2_TYPELESS = 23 + R10G10B10A2_UNORM = 24 + R10G10B10A2_UINT = 25 + R11G11B10_FLOAT = 26 + R8G8B8A8_TYPELESS = 27 + R8G8B8A8_UNORM = 28 + R8G8B8A8_UNORM_SRGB = 29 + R8G8B8A8_UINT = 30 + R8G8B8A8_SNORM = 31 + R8G8B8A8_SINT = 32 + R16G16_TYPELESS = 33 + R16G16_FLOAT = 34 + R16G16_UNORM = 35 + R16G16_UINT = 36 + R16G16_SNORM = 37 + R16G16_SINT = 38 + R32_TYPELESS = 39 + D32_FLOAT = 40 + R32_FLOAT = 41 + R32_UINT = 42 + R32_SINT = 43 + R24G8_TYPELESS = 44 + D24_UNORM_S8_UINT = 45 + R24_UNORM_X8_TYPELESS = 46 + X24_TYPELESS_G8_UINT = 47 + R8G8_TYPELESS = 48 + R8G8_UNORM = 49 + R8G8_UINT = 50 + R8G8_SNORM = 51 + R8G8_SINT = 52 + R16_TYPELESS = 53 + R16_FLOAT = 54 + D16_UNORM = 55 + R16_UNORM = 56 + R16_UINT = 57 + R16_SNORM = 58 + R16_SINT = 59 + R8_TYPELESS = 60 + R8_UNORM = 61 + R8_UINT = 62 + R8_SNORM = 63 + R8_SINT = 64 + A8_UNORM = 65 + R1_UNORM = 66 + R9G9B9E5_SHAREDEXP = 67 + R8G8_B8G8_UNORM = 68 + G8R8_G8B8_UNORM = 69 + BC1_TYPELESS = 70 + BC1_UNORM = 71 + BC1_UNORM_SRGB = 72 + BC2_TYPELESS = 73 + BC2_UNORM = 74 + BC2_UNORM_SRGB = 75 + BC3_TYPELESS = 76 + BC3_UNORM = 77 + BC3_UNORM_SRGB = 78 + BC4_TYPELESS = 79 + BC4_UNORM = 80 + BC4_SNORM = 81 + BC5_TYPELESS = 82 + BC5_UNORM = 83 + BC5_SNORM = 84 + B5G6R5_UNORM = 85 + B5G5R5A1_UNORM = 86 + B8G8R8A8_UNORM = 87 + B8G8R8X8_UNORM = 88 + R10G10B10_XR_BIAS_A2_UNORM = 89 + B8G8R8A8_TYPELESS = 90 + B8G8R8A8_UNORM_SRGB = 91 + B8G8R8X8_TYPELESS = 92 + B8G8R8X8_UNORM_SRGB = 93 + BC6H_TYPELESS = 94 + BC6H_UF16 = 95 + BC6H_SF16 = 96 + BC7_TYPELESS = 97 + BC7_UNORM = 98 + BC7_UNORM_SRGB = 99 + AYUV = 100 + Y410 = 101 + Y416 = 102 + NV12 = 103 + P010 = 104 + P016 = 105 + OPAQUE_420 = 106 + YUY2 = 107 + Y210 = 108 + Y216 = 109 + NV11 = 110 + AI44 = 111 + IA44 = 112 + P8 = 113 + A8P8 = 114 + B4G4R4A4_UNORM = 115 + P208 = 130 + V208 = 131 + V408 = 132 + SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189 + SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190 + + +class D3DFMT(IntEnum): + UNKNOWN = 0 + R8G8B8 = 20 + A8R8G8B8 = 21 + X8R8G8B8 = 22 + R5G6B5 = 23 + X1R5G5B5 = 24 + A1R5G5B5 = 25 + A4R4G4B4 = 26 + R3G3B2 = 27 + A8 = 28 + A8R3G3B2 = 29 + X4R4G4B4 = 30 + A2B10G10R10 = 31 + A8B8G8R8 = 32 + X8B8G8R8 = 33 + G16R16 = 34 + A2R10G10B10 = 35 + A16B16G16R16 = 36 + A8P8 = 40 + P8 = 41 + L8 = 50 + A8L8 = 51 + A4L4 = 52 + V8U8 = 60 + L6V5U5 = 61 + X8L8V8U8 = 62 + Q8W8V8U8 = 63 + V16U16 = 64 + A2W10V10U10 = 67 + D16_LOCKABLE = 70 + D32 = 71 + D15S1 = 73 + D24S8 = 75 + D24X8 = 77 + D24X4S4 = 79 + D16 = 80 + D32F_LOCKABLE = 82 + D24FS8 = 83 + D32_LOCKABLE = 84 + S8_LOCKABLE = 85 + L16 = 81 + VERTEXDATA = 100 + INDEX16 = 101 + INDEX32 = 102 + Q16W16V16U16 = 110 + R16F = 111 + G16R16F = 112 + A16B16G16R16F = 113 + R32F = 114 + G32R32F = 115 + A32B32G32R32F = 116 + CxV8U8 = 117 + A1 = 118 + A2B10G10R10_XR_BIAS = 119 + BINARYBUFFER = 199 + + UYVY = i32(b"UYVY") + R8G8_B8G8 = i32(b"RGBG") + YUY2 = i32(b"YUY2") + G8R8_G8B8 = i32(b"GRGB") + DXT1 = i32(b"DXT1") + DXT2 = i32(b"DXT2") + DXT3 = i32(b"DXT3") + DXT4 = i32(b"DXT4") + DXT5 = i32(b"DXT5") + DX10 = i32(b"DX10") + BC4S = i32(b"BC4S") + BC4U = i32(b"BC4U") + BC5S = i32(b"BC5S") + BC5U = i32(b"BC5U") + ATI1 = i32(b"ATI1") + ATI2 = i32(b"ATI2") + MULTI2_ARGB8 = i32(b"MET1") + + +# Backward compatibility layer +module = sys.modules[__name__] +for item in DDSD: + assert item.name is not None + setattr(module, f"DDSD_{item.name}", item.value) +for item1 in DDSCAPS: + assert item1.name is not None + setattr(module, f"DDSCAPS_{item1.name}", item1.value) +for item2 in DDSCAPS2: + assert item2.name is not None + setattr(module, f"DDSCAPS2_{item2.name}", item2.value) +for item3 in DDPF: + assert item3.name is not None + setattr(module, f"DDPF_{item3.name}", item3.value) + +DDS_FOURCC = DDPF.FOURCC +DDS_RGB = DDPF.RGB +DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS +DDS_LUMINANCE = DDPF.LUMINANCE +DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS +DDS_ALPHA = DDPF.ALPHA +DDS_PAL8 = DDPF.PALETTEINDEXED8 + +DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT +DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT +DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH +DDS_HEADER_FLAGS_PITCH = DDSD.PITCH +DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE + +DDS_HEIGHT = DDSD.HEIGHT +DDS_WIDTH = DDSD.WIDTH + +DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE +DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP +DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX + +DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX +DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX +DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY +DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY +DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ +DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ + +DXT1_FOURCC = D3DFMT.DXT1 +DXT3_FOURCC = D3DFMT.DXT3 +DXT5_FOURCC = D3DFMT.DXT5 + +DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS +DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM +DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB +DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS +DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM +DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM +DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16 +DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16 +DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS +DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM +DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB + + +class DdsImageFile(ImageFile.ImageFile): + format = "DDS" + format_description = "DirectDraw Surface" + + def _open(self) -> None: + if not _accept(self.fp.read(4)): + msg = "not a DDS file" + raise SyntaxError(msg) + (header_size,) = struct.unpack(" None: + pass + + +class DdsRgbDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + bitcount, masks = self.args + + # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100 + # Calculate how many zeros each mask is padded with + mask_offsets = [] + # And the maximum value of each channel without the padding + mask_totals = [] + for mask in masks: + offset = 0 + if mask != 0: + while mask >> (offset + 1) << (offset + 1) == mask: + offset += 1 + mask_offsets.append(offset) + mask_totals.append(mask >> offset) + + data = bytearray() + bytecount = bitcount // 8 + dest_length = self.state.xsize * self.state.ysize * len(masks) + while len(data) < dest_length: + value = int.from_bytes(self.fd.read(bytecount), "little") + for i, mask in enumerate(masks): + masked_value = value & mask + # Remove the zero padding, and scale it to 8 bits + data += o8( + int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) + ) + self.set_as_raw(data) + return -1, 0 + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode not in ("RGB", "RGBA", "L", "LA"): + msg = f"cannot write mode {im.mode} as DDS" + raise OSError(msg) + + flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT + bitcount = len(im.getbands()) * 8 + pixel_format = im.encoderinfo.get("pixel_format") + args: tuple[int] | str + if pixel_format: + codec_name = "bcn" + flags |= DDSD.LINEARSIZE + pitch = (im.width + 3) * 4 + rgba_mask = [0, 0, 0, 0] + pixel_flags = DDPF.FOURCC + if pixel_format == "DXT1": + fourcc = D3DFMT.DXT1 + args = (1,) + elif pixel_format == "DXT3": + fourcc = D3DFMT.DXT3 + args = (2,) + elif pixel_format == "DXT5": + fourcc = D3DFMT.DXT5 + args = (3,) + else: + fourcc = D3DFMT.DX10 + if pixel_format == "BC2": + args = (2,) + dxgi_format = DXGI_FORMAT.BC2_TYPELESS + elif pixel_format == "BC3": + args = (3,) + dxgi_format = DXGI_FORMAT.BC3_TYPELESS + elif pixel_format == "BC5": + args = (5,) + dxgi_format = DXGI_FORMAT.BC5_TYPELESS + if im.mode != "RGB": + msg = "only RGB mode can be written as BC5" + raise OSError(msg) + else: + msg = f"cannot write pixel format {pixel_format}" + raise OSError(msg) + else: + codec_name = "raw" + flags |= DDSD.PITCH + pitch = (im.width * bitcount + 7) // 8 + + alpha = im.mode[-1] == "A" + if im.mode[0] == "L": + pixel_flags = DDPF.LUMINANCE + args = im.mode + if alpha: + rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] + else: + rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] + else: + pixel_flags = DDPF.RGB + args = im.mode[::-1] + rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] + + if alpha: + r, g, b, a = im.split() + im = Image.merge("RGBA", (a, r, g, b)) + if alpha: + pixel_flags |= DDPF.ALPHAPIXELS + rgba_mask.append(0xFF000000 if alpha else 0) + + fourcc = D3DFMT.UNKNOWN + fp.write( + o32(DDS_MAGIC) + + struct.pack( + "<7I", + 124, # header size + flags, # flags + im.height, + im.width, + pitch, + 0, # depth + 0, # mipmaps + ) + + struct.pack("11I", *((0,) * 11)) # reserved + # pfsize, pfflags, fourcc, bitcount + + struct.pack("<4I", 32, pixel_flags, fourcc, bitcount) + + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) + ) + if fourcc == D3DFMT.DX10: + fp.write( + # dxgi_format, 2D resource, misc, array size, straight alpha + struct.pack("<5I", dxgi_format, 3, 0, 0, 1) + ) + ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)]) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"DDS ") + + +Image.register_open(DdsImageFile.format, DdsImageFile, _accept) +Image.register_decoder("dds_rgb", DdsRgbDecoder) +Image.register_save(DdsImageFile.format, _save) +Image.register_extension(DdsImageFile.format, ".dds") diff --git a/venv/Lib/site-packages/PIL/EpsImagePlugin.py b/venv/Lib/site-packages/PIL/EpsImagePlugin.py new file mode 100644 index 00000000..5e2ddad9 --- /dev/null +++ b/venv/Lib/site-packages/PIL/EpsImagePlugin.py @@ -0,0 +1,476 @@ +# +# The Python Imaging Library. +# $Id$ +# +# EPS file handling +# +# History: +# 1995-09-01 fl Created (0.1) +# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2) +# 1996-08-22 fl Don't choke on floating point BoundingBox values +# 1996-08-23 fl Handle files from Macintosh (0.3) +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) +# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5) +# 2014-05-07 e Handling of EPS with binary preview and fixed resolution +# resizing +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import re +import subprocess +import sys +import tempfile +from typing import IO + +from . import Image, ImageFile +from ._binary import i32le as i32 + +# -------------------------------------------------------------------- + + +split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") +field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") + +gs_binary: str | bool | None = None +gs_windows_binary = None + + +def has_ghostscript() -> bool: + global gs_binary, gs_windows_binary + if gs_binary is None: + if sys.platform.startswith("win"): + if gs_windows_binary is None: + import shutil + + for binary in ("gswin32c", "gswin64c", "gs"): + if shutil.which(binary) is not None: + gs_windows_binary = binary + break + else: + gs_windows_binary = False + gs_binary = gs_windows_binary + else: + try: + subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL) + gs_binary = "gs" + except OSError: + gs_binary = False + return gs_binary is not False + + +def Ghostscript( + tile: list[ImageFile._Tile], + size: tuple[int, int], + fp: IO[bytes], + scale: int = 1, + transparency: bool = False, +) -> Image.core.ImagingCore: + """Render an image using Ghostscript""" + global gs_binary + if not has_ghostscript(): + msg = "Unable to locate Ghostscript on paths" + raise OSError(msg) + assert isinstance(gs_binary, str) + + # Unpack decoder tile + args = tile[0].args + assert isinstance(args, tuple) + length, bbox = args + + # Hack to support hi-res rendering + scale = int(scale) or 1 + width = size[0] * scale + height = size[1] * scale + # resolution is dependent on bbox and size + res_x = 72.0 * width / (bbox[2] - bbox[0]) + res_y = 72.0 * height / (bbox[3] - bbox[1]) + + out_fd, outfile = tempfile.mkstemp() + os.close(out_fd) + + infile_temp = None + if hasattr(fp, "name") and os.path.exists(fp.name): + infile = fp.name + else: + in_fd, infile_temp = tempfile.mkstemp() + os.close(in_fd) + infile = infile_temp + + # Ignore length and offset! + # Ghostscript can read it + # Copy whole file to read in Ghostscript + with open(infile_temp, "wb") as f: + # fetch length of fp + fp.seek(0, io.SEEK_END) + fsize = fp.tell() + # ensure start position + # go back + fp.seek(0) + lengthfile = fsize + while lengthfile > 0: + s = fp.read(min(lengthfile, 100 * 1024)) + if not s: + break + lengthfile -= len(s) + f.write(s) + + if transparency: + # "RGBA" + device = "pngalpha" + else: + # "pnmraw" automatically chooses between + # PBM ("1"), PGM ("L"), and PPM ("RGB"). + device = "pnmraw" + + # Build Ghostscript command + command = [ + gs_binary, + "-q", # quiet mode + f"-g{width:d}x{height:d}", # set output geometry (pixels) + f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch) + "-dBATCH", # exit after processing + "-dNOPAUSE", # don't pause between pages + "-dSAFER", # safe mode + f"-sDEVICE={device}", + f"-sOutputFile={outfile}", # output file + # adjust for image origin + "-c", + f"{-bbox[0]} {-bbox[1]} translate", + "-f", + infile, # input file + # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) + "-c", + "showpage", + ] + + # push data through Ghostscript + try: + startupinfo = None + if sys.platform.startswith("win"): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + subprocess.check_call(command, startupinfo=startupinfo) + with Image.open(outfile) as out_im: + out_im.load() + return out_im.im.copy() + finally: + try: + os.unlink(outfile) + if infile_temp: + os.unlink(infile_temp) + except OSError: + pass + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"%!PS") or ( + len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5 + ) + + +## +# Image plugin for Encapsulated PostScript. This plugin supports only +# a few variants of this format. + + +class EpsImageFile(ImageFile.ImageFile): + """EPS File Parser for the Python Imaging Library""" + + format = "EPS" + format_description = "Encapsulated Postscript" + + mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} + + def _open(self) -> None: + (length, offset) = self._find_offset(self.fp) + + # go to offset - start of "%!PS" + self.fp.seek(offset) + + self._mode = "RGB" + + # When reading header comments, the first comment is used. + # When reading trailer comments, the last comment is used. + bounding_box: list[int] | None = None + imagedata_size: tuple[int, int] | None = None + + byte_arr = bytearray(255) + bytes_mv = memoryview(byte_arr) + bytes_read = 0 + reading_header_comments = True + reading_trailer_comments = False + trailer_reached = False + + def check_required_header_comments() -> None: + """ + The EPS specification requires that some headers exist. + This should be checked when the header comments formally end, + when image data starts, or when the file ends, whichever comes first. + """ + if "PS-Adobe" not in self.info: + msg = 'EPS header missing "%!PS-Adobe" comment' + raise SyntaxError(msg) + if "BoundingBox" not in self.info: + msg = 'EPS header missing "%%BoundingBox" comment' + raise SyntaxError(msg) + + def read_comment(s: str) -> bool: + nonlocal bounding_box, reading_trailer_comments + try: + m = split.match(s) + except re.error as e: + msg = "not an EPS file" + raise SyntaxError(msg) from e + + if not m: + return False + + k, v = m.group(1, 2) + self.info[k] = v + if k == "BoundingBox": + if v == "(atend)": + reading_trailer_comments = True + elif not bounding_box or (trailer_reached and reading_trailer_comments): + try: + # Note: The DSC spec says that BoundingBox + # fields should be integers, but some drivers + # put floating point values there anyway. + bounding_box = [int(float(i)) for i in v.split()] + except Exception: + pass + return True + + while True: + byte = self.fp.read(1) + if byte == b"": + # if we didn't read a byte we must be at the end of the file + if bytes_read == 0: + if reading_header_comments: + check_required_header_comments() + break + elif byte in b"\r\n": + # if we read a line ending character, ignore it and parse what + # we have already read. if we haven't read any other characters, + # continue reading + if bytes_read == 0: + continue + else: + # ASCII/hexadecimal lines in an EPS file must not exceed + # 255 characters, not including line ending characters + if bytes_read >= 255: + # only enforce this for lines starting with a "%", + # otherwise assume it's binary data + if byte_arr[0] == ord("%"): + msg = "not an EPS file" + raise SyntaxError(msg) + else: + if reading_header_comments: + check_required_header_comments() + reading_header_comments = False + # reset bytes_read so we can keep reading + # data until the end of the line + bytes_read = 0 + byte_arr[bytes_read] = byte[0] + bytes_read += 1 + continue + + if reading_header_comments: + # Load EPS header + + # if this line doesn't start with a "%", + # or does start with "%%EndComments", + # then we've reached the end of the header/comments + if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments": + check_required_header_comments() + reading_header_comments = False + continue + + s = str(bytes_mv[:bytes_read], "latin-1") + if not read_comment(s): + m = field.match(s) + if m: + k = m.group(1) + if k.startswith("PS-Adobe"): + self.info["PS-Adobe"] = k[9:] + else: + self.info[k] = "" + elif s[0] == "%": + # handle non-DSC PostScript comments that some + # tools mistakenly put in the Comments section + pass + else: + msg = "bad EPS header" + raise OSError(msg) + elif bytes_mv[:11] == b"%ImageData:": + # Check for an "ImageData" descriptor + # https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577413_pgfId-1035096 + + # If we've already read an "ImageData" descriptor, + # don't read another one. + if imagedata_size: + bytes_read = 0 + continue + + # Values: + # columns + # rows + # bit depth (1 or 8) + # mode (1: L, 2: LAB, 3: RGB, 4: CMYK) + # number of padding channels + # block size (number of bytes per row per channel) + # binary/ascii (1: binary, 2: ascii) + # data start identifier (the image data follows after a single line + # consisting only of this quoted value) + image_data_values = byte_arr[11:bytes_read].split(None, 7) + columns, rows, bit_depth, mode_id = ( + int(value) for value in image_data_values[:4] + ) + + if bit_depth == 1: + self._mode = "1" + elif bit_depth == 8: + try: + self._mode = self.mode_map[mode_id] + except ValueError: + break + else: + break + + # Parse the columns and rows after checking the bit depth and mode + # in case the bit depth and/or mode are invalid. + imagedata_size = columns, rows + elif bytes_mv[:5] == b"%%EOF": + break + elif trailer_reached and reading_trailer_comments: + # Load EPS trailer + s = str(bytes_mv[:bytes_read], "latin-1") + read_comment(s) + elif bytes_mv[:9] == b"%%Trailer": + trailer_reached = True + bytes_read = 0 + + # A "BoundingBox" is always required, + # even if an "ImageData" descriptor size exists. + if not bounding_box: + msg = "cannot determine EPS bounding box" + raise OSError(msg) + + # An "ImageData" size takes precedence over the "BoundingBox". + self._size = imagedata_size or ( + bounding_box[2] - bounding_box[0], + bounding_box[3] - bounding_box[1], + ) + + self.tile = [ + ImageFile._Tile("eps", (0, 0) + self.size, offset, (length, bounding_box)) + ] + + def _find_offset(self, fp: IO[bytes]) -> tuple[int, int]: + s = fp.read(4) + + if s == b"%!PS": + # for HEAD without binary preview + fp.seek(0, io.SEEK_END) + length = fp.tell() + offset = 0 + elif i32(s) == 0xC6D3D0C5: + # FIX for: Some EPS file not handled correctly / issue #302 + # EPS can contain binary data + # or start directly with latin coding + # more info see: + # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf + s = fp.read(8) + offset = i32(s) + length = i32(s, 4) + else: + msg = "not an EPS file" + raise SyntaxError(msg) + + return length, offset + + def load( + self, scale: int = 1, transparency: bool = False + ) -> Image.core.PixelAccess | None: + # Load EPS via Ghostscript + if self.tile: + self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) + self._mode = self.im.mode + self._size = self.im.size + self.tile = [] + return Image.Image.load(self) + + def load_seek(self, pos: int) -> None: + # we can't incrementally load, so force ImageFile.parser to + # use our custom load method by defining this method. + pass + + +# -------------------------------------------------------------------- + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -> None: + """EPS Writer for the Python Imaging Library.""" + + # make sure image data is available + im.load() + + # determine PostScript image mode + if im.mode == "L": + operator = (8, 1, b"image") + elif im.mode == "RGB": + operator = (8, 3, b"false 3 colorimage") + elif im.mode == "CMYK": + operator = (8, 4, b"false 4 colorimage") + else: + msg = "image mode is not supported" + raise ValueError(msg) + + if eps: + # write EPS header + fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n") + fp.write(b"%%Creator: PIL 0.1 EpsEncode\n") + # fp.write("%%CreationDate: %s"...) + fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size) + fp.write(b"%%Pages: 1\n") + fp.write(b"%%EndComments\n") + fp.write(b"%%Page: 1 1\n") + fp.write(b"%%ImageData: %d %d " % im.size) + fp.write(b'%d %d 0 1 1 "%s"\n' % operator) + + # image header + fp.write(b"gsave\n") + fp.write(b"10 dict begin\n") + fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1])) + fp.write(b"%d %d scale\n" % im.size) + fp.write(b"%d %d 8\n" % im.size) # <= bits + fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) + fp.write(b"{ currentfile buf readhexstring pop } bind\n") + fp.write(operator[2] + b"\n") + if hasattr(fp, "flush"): + fp.flush() + + ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)]) + + fp.write(b"\n%%%%EndBinary\n") + fp.write(b"grestore end\n") + if hasattr(fp, "flush"): + fp.flush() + + +# -------------------------------------------------------------------- + + +Image.register_open(EpsImageFile.format, EpsImageFile, _accept) + +Image.register_save(EpsImageFile.format, _save) + +Image.register_extensions(EpsImageFile.format, [".ps", ".eps"]) + +Image.register_mime(EpsImageFile.format, "application/postscript") diff --git a/venv/Lib/site-packages/PIL/ExifTags.py b/venv/Lib/site-packages/PIL/ExifTags.py new file mode 100644 index 00000000..2280d5ce --- /dev/null +++ b/venv/Lib/site-packages/PIL/ExifTags.py @@ -0,0 +1,382 @@ +# +# The Python Imaging Library. +# $Id$ +# +# EXIF tags +# +# Copyright (c) 2003 by Secret Labs AB +# +# See the README file for information on usage and redistribution. +# + +""" +This module provides constants and clear-text names for various +well-known EXIF tags. +""" +from __future__ import annotations + +from enum import IntEnum + + +class Base(IntEnum): + # possibly incomplete + InteropIndex = 0x0001 + ProcessingSoftware = 0x000B + NewSubfileType = 0x00FE + SubfileType = 0x00FF + ImageWidth = 0x0100 + ImageLength = 0x0101 + BitsPerSample = 0x0102 + Compression = 0x0103 + PhotometricInterpretation = 0x0106 + Thresholding = 0x0107 + CellWidth = 0x0108 + CellLength = 0x0109 + FillOrder = 0x010A + DocumentName = 0x010D + ImageDescription = 0x010E + Make = 0x010F + Model = 0x0110 + StripOffsets = 0x0111 + Orientation = 0x0112 + SamplesPerPixel = 0x0115 + RowsPerStrip = 0x0116 + StripByteCounts = 0x0117 + MinSampleValue = 0x0118 + MaxSampleValue = 0x0119 + XResolution = 0x011A + YResolution = 0x011B + PlanarConfiguration = 0x011C + PageName = 0x011D + FreeOffsets = 0x0120 + FreeByteCounts = 0x0121 + GrayResponseUnit = 0x0122 + GrayResponseCurve = 0x0123 + T4Options = 0x0124 + T6Options = 0x0125 + ResolutionUnit = 0x0128 + PageNumber = 0x0129 + TransferFunction = 0x012D + Software = 0x0131 + DateTime = 0x0132 + Artist = 0x013B + HostComputer = 0x013C + Predictor = 0x013D + WhitePoint = 0x013E + PrimaryChromaticities = 0x013F + ColorMap = 0x0140 + HalftoneHints = 0x0141 + TileWidth = 0x0142 + TileLength = 0x0143 + TileOffsets = 0x0144 + TileByteCounts = 0x0145 + SubIFDs = 0x014A + InkSet = 0x014C + InkNames = 0x014D + NumberOfInks = 0x014E + DotRange = 0x0150 + TargetPrinter = 0x0151 + ExtraSamples = 0x0152 + SampleFormat = 0x0153 + SMinSampleValue = 0x0154 + SMaxSampleValue = 0x0155 + TransferRange = 0x0156 + ClipPath = 0x0157 + XClipPathUnits = 0x0158 + YClipPathUnits = 0x0159 + Indexed = 0x015A + JPEGTables = 0x015B + OPIProxy = 0x015F + JPEGProc = 0x0200 + JpegIFOffset = 0x0201 + JpegIFByteCount = 0x0202 + JpegRestartInterval = 0x0203 + JpegLosslessPredictors = 0x0205 + JpegPointTransforms = 0x0206 + JpegQTables = 0x0207 + JpegDCTables = 0x0208 + JpegACTables = 0x0209 + YCbCrCoefficients = 0x0211 + YCbCrSubSampling = 0x0212 + YCbCrPositioning = 0x0213 + ReferenceBlackWhite = 0x0214 + XMLPacket = 0x02BC + RelatedImageFileFormat = 0x1000 + RelatedImageWidth = 0x1001 + RelatedImageLength = 0x1002 + Rating = 0x4746 + RatingPercent = 0x4749 + ImageID = 0x800D + CFARepeatPatternDim = 0x828D + BatteryLevel = 0x828F + Copyright = 0x8298 + ExposureTime = 0x829A + FNumber = 0x829D + IPTCNAA = 0x83BB + ImageResources = 0x8649 + ExifOffset = 0x8769 + InterColorProfile = 0x8773 + ExposureProgram = 0x8822 + SpectralSensitivity = 0x8824 + GPSInfo = 0x8825 + ISOSpeedRatings = 0x8827 + OECF = 0x8828 + Interlace = 0x8829 + TimeZoneOffset = 0x882A + SelfTimerMode = 0x882B + SensitivityType = 0x8830 + StandardOutputSensitivity = 0x8831 + RecommendedExposureIndex = 0x8832 + ISOSpeed = 0x8833 + ISOSpeedLatitudeyyy = 0x8834 + ISOSpeedLatitudezzz = 0x8835 + ExifVersion = 0x9000 + DateTimeOriginal = 0x9003 + DateTimeDigitized = 0x9004 + OffsetTime = 0x9010 + OffsetTimeOriginal = 0x9011 + OffsetTimeDigitized = 0x9012 + ComponentsConfiguration = 0x9101 + CompressedBitsPerPixel = 0x9102 + ShutterSpeedValue = 0x9201 + ApertureValue = 0x9202 + BrightnessValue = 0x9203 + ExposureBiasValue = 0x9204 + MaxApertureValue = 0x9205 + SubjectDistance = 0x9206 + MeteringMode = 0x9207 + LightSource = 0x9208 + Flash = 0x9209 + FocalLength = 0x920A + Noise = 0x920D + ImageNumber = 0x9211 + SecurityClassification = 0x9212 + ImageHistory = 0x9213 + TIFFEPStandardID = 0x9216 + MakerNote = 0x927C + UserComment = 0x9286 + SubsecTime = 0x9290 + SubsecTimeOriginal = 0x9291 + SubsecTimeDigitized = 0x9292 + AmbientTemperature = 0x9400 + Humidity = 0x9401 + Pressure = 0x9402 + WaterDepth = 0x9403 + Acceleration = 0x9404 + CameraElevationAngle = 0x9405 + XPTitle = 0x9C9B + XPComment = 0x9C9C + XPAuthor = 0x9C9D + XPKeywords = 0x9C9E + XPSubject = 0x9C9F + FlashPixVersion = 0xA000 + ColorSpace = 0xA001 + ExifImageWidth = 0xA002 + ExifImageHeight = 0xA003 + RelatedSoundFile = 0xA004 + ExifInteroperabilityOffset = 0xA005 + FlashEnergy = 0xA20B + SpatialFrequencyResponse = 0xA20C + FocalPlaneXResolution = 0xA20E + FocalPlaneYResolution = 0xA20F + FocalPlaneResolutionUnit = 0xA210 + SubjectLocation = 0xA214 + ExposureIndex = 0xA215 + SensingMethod = 0xA217 + FileSource = 0xA300 + SceneType = 0xA301 + CFAPattern = 0xA302 + CustomRendered = 0xA401 + ExposureMode = 0xA402 + WhiteBalance = 0xA403 + DigitalZoomRatio = 0xA404 + FocalLengthIn35mmFilm = 0xA405 + SceneCaptureType = 0xA406 + GainControl = 0xA407 + Contrast = 0xA408 + Saturation = 0xA409 + Sharpness = 0xA40A + DeviceSettingDescription = 0xA40B + SubjectDistanceRange = 0xA40C + ImageUniqueID = 0xA420 + CameraOwnerName = 0xA430 + BodySerialNumber = 0xA431 + LensSpecification = 0xA432 + LensMake = 0xA433 + LensModel = 0xA434 + LensSerialNumber = 0xA435 + CompositeImage = 0xA460 + CompositeImageCount = 0xA461 + CompositeImageExposureTimes = 0xA462 + Gamma = 0xA500 + PrintImageMatching = 0xC4A5 + DNGVersion = 0xC612 + DNGBackwardVersion = 0xC613 + UniqueCameraModel = 0xC614 + LocalizedCameraModel = 0xC615 + CFAPlaneColor = 0xC616 + CFALayout = 0xC617 + LinearizationTable = 0xC618 + BlackLevelRepeatDim = 0xC619 + BlackLevel = 0xC61A + BlackLevelDeltaH = 0xC61B + BlackLevelDeltaV = 0xC61C + WhiteLevel = 0xC61D + DefaultScale = 0xC61E + DefaultCropOrigin = 0xC61F + DefaultCropSize = 0xC620 + ColorMatrix1 = 0xC621 + ColorMatrix2 = 0xC622 + CameraCalibration1 = 0xC623 + CameraCalibration2 = 0xC624 + ReductionMatrix1 = 0xC625 + ReductionMatrix2 = 0xC626 + AnalogBalance = 0xC627 + AsShotNeutral = 0xC628 + AsShotWhiteXY = 0xC629 + BaselineExposure = 0xC62A + BaselineNoise = 0xC62B + BaselineSharpness = 0xC62C + BayerGreenSplit = 0xC62D + LinearResponseLimit = 0xC62E + CameraSerialNumber = 0xC62F + LensInfo = 0xC630 + ChromaBlurRadius = 0xC631 + AntiAliasStrength = 0xC632 + ShadowScale = 0xC633 + DNGPrivateData = 0xC634 + MakerNoteSafety = 0xC635 + CalibrationIlluminant1 = 0xC65A + CalibrationIlluminant2 = 0xC65B + BestQualityScale = 0xC65C + RawDataUniqueID = 0xC65D + OriginalRawFileName = 0xC68B + OriginalRawFileData = 0xC68C + ActiveArea = 0xC68D + MaskedAreas = 0xC68E + AsShotICCProfile = 0xC68F + AsShotPreProfileMatrix = 0xC690 + CurrentICCProfile = 0xC691 + CurrentPreProfileMatrix = 0xC692 + ColorimetricReference = 0xC6BF + CameraCalibrationSignature = 0xC6F3 + ProfileCalibrationSignature = 0xC6F4 + AsShotProfileName = 0xC6F6 + NoiseReductionApplied = 0xC6F7 + ProfileName = 0xC6F8 + ProfileHueSatMapDims = 0xC6F9 + ProfileHueSatMapData1 = 0xC6FA + ProfileHueSatMapData2 = 0xC6FB + ProfileToneCurve = 0xC6FC + ProfileEmbedPolicy = 0xC6FD + ProfileCopyright = 0xC6FE + ForwardMatrix1 = 0xC714 + ForwardMatrix2 = 0xC715 + PreviewApplicationName = 0xC716 + PreviewApplicationVersion = 0xC717 + PreviewSettingsName = 0xC718 + PreviewSettingsDigest = 0xC719 + PreviewColorSpace = 0xC71A + PreviewDateTime = 0xC71B + RawImageDigest = 0xC71C + OriginalRawFileDigest = 0xC71D + SubTileBlockSize = 0xC71E + RowInterleaveFactor = 0xC71F + ProfileLookTableDims = 0xC725 + ProfileLookTableData = 0xC726 + OpcodeList1 = 0xC740 + OpcodeList2 = 0xC741 + OpcodeList3 = 0xC74E + NoiseProfile = 0xC761 + + +"""Maps EXIF tags to tag names.""" +TAGS = { + **{i.value: i.name for i in Base}, + 0x920C: "SpatialFrequencyResponse", + 0x9214: "SubjectLocation", + 0x9215: "ExposureIndex", + 0x828E: "CFAPattern", + 0x920B: "FlashEnergy", + 0x9216: "TIFF/EPStandardID", +} + + +class GPS(IntEnum): + GPSVersionID = 0x00 + GPSLatitudeRef = 0x01 + GPSLatitude = 0x02 + GPSLongitudeRef = 0x03 + GPSLongitude = 0x04 + GPSAltitudeRef = 0x05 + GPSAltitude = 0x06 + GPSTimeStamp = 0x07 + GPSSatellites = 0x08 + GPSStatus = 0x09 + GPSMeasureMode = 0x0A + GPSDOP = 0x0B + GPSSpeedRef = 0x0C + GPSSpeed = 0x0D + GPSTrackRef = 0x0E + GPSTrack = 0x0F + GPSImgDirectionRef = 0x10 + GPSImgDirection = 0x11 + GPSMapDatum = 0x12 + GPSDestLatitudeRef = 0x13 + GPSDestLatitude = 0x14 + GPSDestLongitudeRef = 0x15 + GPSDestLongitude = 0x16 + GPSDestBearingRef = 0x17 + GPSDestBearing = 0x18 + GPSDestDistanceRef = 0x19 + GPSDestDistance = 0x1A + GPSProcessingMethod = 0x1B + GPSAreaInformation = 0x1C + GPSDateStamp = 0x1D + GPSDifferential = 0x1E + GPSHPositioningError = 0x1F + + +"""Maps EXIF GPS tags to tag names.""" +GPSTAGS = {i.value: i.name for i in GPS} + + +class Interop(IntEnum): + InteropIndex = 0x0001 + InteropVersion = 0x0002 + RelatedImageFileFormat = 0x1000 + RelatedImageWidth = 0x1001 + RelatedImageHeight = 0x1002 + + +class IFD(IntEnum): + Exif = 0x8769 + GPSInfo = 0x8825 + MakerNote = 0x927C + Makernote = 0x927C # Deprecated + Interop = 0xA005 + IFD1 = -1 + + +class LightSource(IntEnum): + Unknown = 0x00 + Daylight = 0x01 + Fluorescent = 0x02 + Tungsten = 0x03 + Flash = 0x04 + Fine = 0x09 + Cloudy = 0x0A + Shade = 0x0B + DaylightFluorescent = 0x0C + DayWhiteFluorescent = 0x0D + CoolWhiteFluorescent = 0x0E + WhiteFluorescent = 0x0F + StandardLightA = 0x11 + StandardLightB = 0x12 + StandardLightC = 0x13 + D55 = 0x14 + D65 = 0x15 + D75 = 0x16 + D50 = 0x17 + ISO = 0x18 + Other = 0xFF diff --git a/venv/Lib/site-packages/PIL/FitsImagePlugin.py b/venv/Lib/site-packages/PIL/FitsImagePlugin.py new file mode 100644 index 00000000..a3fdc0ef --- /dev/null +++ b/venv/Lib/site-packages/PIL/FitsImagePlugin.py @@ -0,0 +1,152 @@ +# +# The Python Imaging Library +# $Id$ +# +# FITS file handling +# +# Copyright (c) 1998-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import gzip +import math + +from . import Image, ImageFile + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"SIMPLE") + + +class FitsImageFile(ImageFile.ImageFile): + format = "FITS" + format_description = "FITS" + + def _open(self) -> None: + assert self.fp is not None + + headers: dict[bytes, bytes] = {} + header_in_progress = False + decoder_name = "" + while True: + header = self.fp.read(80) + if not header: + msg = "Truncated FITS file" + raise OSError(msg) + keyword = header[:8].strip() + if keyword in (b"SIMPLE", b"XTENSION"): + header_in_progress = True + elif headers and not header_in_progress: + # This is now a data unit + break + elif keyword == b"END": + # Seek to the end of the header unit + self.fp.seek(math.ceil(self.fp.tell() / 2880) * 2880) + if not decoder_name: + decoder_name, offset, args = self._parse_headers(headers) + + header_in_progress = False + continue + + if decoder_name: + # Keep going to read past the headers + continue + + value = header[8:].split(b"/")[0].strip() + if value.startswith(b"="): + value = value[1:].strip() + if not headers and (not _accept(keyword) or value != b"T"): + msg = "Not a FITS file" + raise SyntaxError(msg) + headers[keyword] = value + + if not decoder_name: + msg = "No image data" + raise ValueError(msg) + + offset += self.fp.tell() - 80 + self.tile = [ImageFile._Tile(decoder_name, (0, 0) + self.size, offset, args)] + + def _get_size( + self, headers: dict[bytes, bytes], prefix: bytes + ) -> tuple[int, int] | None: + naxis = int(headers[prefix + b"NAXIS"]) + if naxis == 0: + return None + + if naxis == 1: + return 1, int(headers[prefix + b"NAXIS1"]) + else: + return int(headers[prefix + b"NAXIS1"]), int(headers[prefix + b"NAXIS2"]) + + def _parse_headers( + self, headers: dict[bytes, bytes] + ) -> tuple[str, int, tuple[str | int, ...]]: + prefix = b"" + decoder_name = "raw" + offset = 0 + if ( + headers.get(b"XTENSION") == b"'BINTABLE'" + and headers.get(b"ZIMAGE") == b"T" + and headers[b"ZCMPTYPE"] == b"'GZIP_1 '" + ): + no_prefix_size = self._get_size(headers, prefix) or (0, 0) + number_of_bits = int(headers[b"BITPIX"]) + offset = no_prefix_size[0] * no_prefix_size[1] * (number_of_bits // 8) + + prefix = b"Z" + decoder_name = "fits_gzip" + + size = self._get_size(headers, prefix) + if not size: + return "", 0, () + + self._size = size + + number_of_bits = int(headers[prefix + b"BITPIX"]) + if number_of_bits == 8: + self._mode = "L" + elif number_of_bits == 16: + self._mode = "I;16" + elif number_of_bits == 32: + self._mode = "I" + elif number_of_bits in (-32, -64): + self._mode = "F" + + args: tuple[str | int, ...] + if decoder_name == "raw": + args = (self.mode, 0, -1) + else: + args = (number_of_bits,) + return decoder_name, offset, args + + +class FitsGzipDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + value = gzip.decompress(self.fd.read()) + + rows = [] + offset = 0 + number_of_bits = min(self.args[0] // 8, 4) + for y in range(self.state.ysize): + row = bytearray() + for x in range(self.state.xsize): + row += value[offset + (4 - number_of_bits) : offset + 4] + offset += 4 + rows.append(row) + self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row])) + return -1, 0 + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(FitsImageFile.format, FitsImageFile, _accept) +Image.register_decoder("fits_gzip", FitsGzipDecoder) + +Image.register_extensions(FitsImageFile.format, [".fit", ".fits"]) diff --git a/venv/Lib/site-packages/PIL/FliImagePlugin.py b/venv/Lib/site-packages/PIL/FliImagePlugin.py new file mode 100644 index 00000000..7c5bfeef --- /dev/null +++ b/venv/Lib/site-packages/PIL/FliImagePlugin.py @@ -0,0 +1,178 @@ +# +# The Python Imaging Library. +# $Id$ +# +# FLI/FLC file handling. +# +# History: +# 95-09-01 fl Created +# 97-01-03 fl Fixed parser, setup decoder tile +# 98-07-15 fl Renamed offset attribute to avoid name clash +# +# Copyright (c) Secret Labs AB 1997-98. +# Copyright (c) Fredrik Lundh 1995-97. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os + +from . import Image, ImageFile, ImagePalette +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._util import DeferredError + +# +# decoder + + +def _accept(prefix: bytes) -> bool: + return ( + len(prefix) >= 6 + and i16(prefix, 4) in [0xAF11, 0xAF12] + and i16(prefix, 14) in [0, 3] # flags + ) + + +## +# Image plugin for the FLI/FLC animation format. Use the seek +# method to load individual frames. + + +class FliImageFile(ImageFile.ImageFile): + format = "FLI" + format_description = "Autodesk FLI/FLC Animation" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # HEAD + s = self.fp.read(128) + if not (_accept(s) and s[20:22] == b"\x00\x00"): + msg = "not an FLI/FLC file" + raise SyntaxError(msg) + + # frames + self.n_frames = i16(s, 6) + self.is_animated = self.n_frames > 1 + + # image characteristics + self._mode = "P" + self._size = i16(s, 8), i16(s, 10) + + # animation speed + duration = i32(s, 16) + magic = i16(s, 4) + if magic == 0xAF11: + duration = (duration * 1000) // 70 + self.info["duration"] = duration + + # look for palette + palette = [(a, a, a) for a in range(256)] + + s = self.fp.read(16) + + self.__offset = 128 + + if i16(s, 4) == 0xF100: + # prefix chunk; ignore it + self.__offset = self.__offset + i32(s) + self.fp.seek(self.__offset) + s = self.fp.read(16) + + if i16(s, 4) == 0xF1FA: + # look for palette chunk + number_of_subchunks = i16(s, 6) + chunk_size: int | None = None + for _ in range(number_of_subchunks): + if chunk_size is not None: + self.fp.seek(chunk_size - 6, os.SEEK_CUR) + s = self.fp.read(6) + chunk_type = i16(s, 4) + if chunk_type in (4, 11): + self._palette(palette, 2 if chunk_type == 11 else 0) + break + chunk_size = i32(s) + if not chunk_size: + break + + self.palette = ImagePalette.raw( + "RGB", b"".join(o8(r) + o8(g) + o8(b) for (r, g, b) in palette) + ) + + # set things up to decode first frame + self.__frame = -1 + self._fp = self.fp + self.__rewind = self.fp.tell() + self.seek(0) + + def _palette(self, palette: list[tuple[int, int, int]], shift: int) -> None: + # load palette + + i = 0 + for e in range(i16(self.fp.read(2))): + s = self.fp.read(2) + i = i + s[0] + n = s[1] + if n == 0: + n = 256 + s = self.fp.read(n * 3) + for n in range(0, len(s), 3): + r = s[n] << shift + g = s[n + 1] << shift + b = s[n + 2] << shift + palette[i] = (r, g, b) + i += 1 + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if frame < self.__frame: + self._seek(0) + + for f in range(self.__frame + 1, frame + 1): + self._seek(f) + + def _seek(self, frame: int) -> None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + if frame == 0: + self.__frame = -1 + self._fp.seek(self.__rewind) + self.__offset = 128 + else: + # ensure that the previous frame was loaded + self.load() + + if frame != self.__frame + 1: + msg = f"cannot seek to frame {frame}" + raise ValueError(msg) + self.__frame = frame + + # move to next frame + self.fp = self._fp + self.fp.seek(self.__offset) + + s = self.fp.read(4) + if not s: + msg = "missing frame size" + raise EOFError(msg) + + framesize = i32(s) + + self.decodermaxblock = framesize + self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)] + + self.__offset += framesize + + def tell(self) -> int: + return self.__frame + + +# +# registry + +Image.register_open(FliImageFile.format, FliImageFile, _accept) + +Image.register_extensions(FliImageFile.format, [".fli", ".flc"]) diff --git a/venv/Lib/site-packages/PIL/FontFile.py b/venv/Lib/site-packages/PIL/FontFile.py new file mode 100644 index 00000000..1e0c1c16 --- /dev/null +++ b/venv/Lib/site-packages/PIL/FontFile.py @@ -0,0 +1,134 @@ +# +# The Python Imaging Library +# $Id$ +# +# base class for raster font file parsers +# +# history: +# 1997-06-05 fl created +# 1997-08-19 fl restrict image width +# +# Copyright (c) 1997-1998 by Secret Labs AB +# Copyright (c) 1997-1998 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import BinaryIO + +from . import Image, _binary + +WIDTH = 800 + + +def puti16( + fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int] +) -> None: + """Write network order (big-endian) 16-bit sequence""" + for v in values: + if v < 0: + v += 65536 + fp.write(_binary.o16be(v)) + + +class FontFile: + """Base class for raster font file handlers.""" + + bitmap: Image.Image | None = None + + def __init__(self) -> None: + self.info: dict[bytes, bytes | int] = {} + self.glyph: list[ + tuple[ + tuple[int, int], + tuple[int, int, int, int], + tuple[int, int, int, int], + Image.Image, + ] + | None + ] = [None] * 256 + + def __getitem__(self, ix: int) -> ( + tuple[ + tuple[int, int], + tuple[int, int, int, int], + tuple[int, int, int, int], + Image.Image, + ] + | None + ): + return self.glyph[ix] + + def compile(self) -> None: + """Create metrics and bitmap""" + + if self.bitmap: + return + + # create bitmap large enough to hold all data + h = w = maxwidth = 0 + lines = 1 + for glyph in self.glyph: + if glyph: + d, dst, src, im = glyph + h = max(h, src[3] - src[1]) + w = w + (src[2] - src[0]) + if w > WIDTH: + lines += 1 + w = src[2] - src[0] + maxwidth = max(maxwidth, w) + + xsize = maxwidth + ysize = lines * h + + if xsize == 0 and ysize == 0: + return + + self.ysize = h + + # paste glyphs into bitmap + self.bitmap = Image.new("1", (xsize, ysize)) + self.metrics: list[ + tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]] + | None + ] = [None] * 256 + x = y = 0 + for i in range(256): + glyph = self[i] + if glyph: + d, dst, src, im = glyph + xx = src[2] - src[0] + x0, y0 = x, y + x = x + xx + if x > WIDTH: + x, y = 0, y + h + x0, y0 = x, y + x = xx + s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0 + self.bitmap.paste(im.crop(src), s) + self.metrics[i] = d, dst, s + + def save(self, filename: str) -> None: + """Save font""" + + self.compile() + + # font data + if not self.bitmap: + msg = "No bitmap created" + raise ValueError(msg) + self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG") + + # font metrics + with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp: + fp.write(b"PILfont\n") + fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!! + fp.write(b"DATA\n") + for id in range(256): + m = self.metrics[id] + if not m: + puti16(fp, (0,) * 10) + else: + puti16(fp, m[0] + m[1] + m[2]) diff --git a/venv/Lib/site-packages/PIL/FpxImagePlugin.py b/venv/Lib/site-packages/PIL/FpxImagePlugin.py new file mode 100644 index 00000000..fd992cd9 --- /dev/null +++ b/venv/Lib/site-packages/PIL/FpxImagePlugin.py @@ -0,0 +1,257 @@ +# +# THIS IS WORK IN PROGRESS +# +# The Python Imaging Library. +# $Id$ +# +# FlashPix support for PIL +# +# History: +# 97-01-25 fl Created (reads uncompressed RGB images only) +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import olefile + +from . import Image, ImageFile +from ._binary import i32le as i32 + +# we map from colour field tuples to (mode, rawmode) descriptors +MODES = { + # opacity + (0x00007FFE,): ("A", "L"), + # monochrome + (0x00010000,): ("L", "L"), + (0x00018000, 0x00017FFE): ("RGBA", "LA"), + # photo YCC + (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), + (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"), + # standard RGB (NIFRGB) + (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"), + (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"), +} + + +# +# -------------------------------------------------------------------- + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(olefile.MAGIC) + + +## +# Image plugin for the FlashPix images. + + +class FpxImageFile(ImageFile.ImageFile): + format = "FPX" + format_description = "FlashPix" + + def _open(self) -> None: + # + # read the OLE directory and see if this is a likely + # to be a FlashPix file + + try: + self.ole = olefile.OleFileIO(self.fp) + except OSError as e: + msg = "not an FPX file; invalid OLE file" + raise SyntaxError(msg) from e + + root = self.ole.root + if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": + msg = "not an FPX file; bad root CLSID" + raise SyntaxError(msg) + + self._open_index(1) + + def _open_index(self, index: int = 1) -> None: + # + # get the Image Contents Property Set + + prop = self.ole.getproperties( + [f"Data Object Store {index:06d}", "\005Image Contents"] + ) + + # size (highest resolution) + + assert isinstance(prop[0x1000002], int) + assert isinstance(prop[0x1000003], int) + self._size = prop[0x1000002], prop[0x1000003] + + size = max(self.size) + i = 1 + while size > 64: + size = size // 2 + i += 1 + self.maxid = i - 1 + + # mode. instead of using a single field for this, flashpix + # requires you to specify the mode for each channel in each + # resolution subimage, and leaves it to the decoder to make + # sure that they all match. for now, we'll cheat and assume + # that this is always the case. + + id = self.maxid << 16 + + s = prop[0x2000002 | id] + + if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4: + msg = "Invalid number of bands" + raise OSError(msg) + + # note: for now, we ignore the "uncalibrated" flag + colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands)) + + self._mode, self.rawmode = MODES[colors] + + # load JPEG tables, if any + self.jpeg = {} + for i in range(256): + id = 0x3000001 | (i << 16) + if id in prop: + self.jpeg[i] = prop[id] + + self._open_subimage(1, self.maxid) + + def _open_subimage(self, index: int = 1, subimage: int = 0) -> None: + # + # setup tile descriptors for a given subimage + + stream = [ + f"Data Object Store {index:06d}", + f"Resolution {subimage:04d}", + "Subimage 0000 Header", + ] + + fp = self.ole.openstream(stream) + + # skip prefix + fp.read(28) + + # header stream + s = fp.read(36) + + size = i32(s, 4), i32(s, 8) + # tilecount = i32(s, 12) + tilesize = i32(s, 16), i32(s, 20) + # channels = i32(s, 24) + offset = i32(s, 28) + length = i32(s, 32) + + if size != self.size: + msg = "subimage mismatch" + raise OSError(msg) + + # get tile descriptors + fp.seek(28 + offset) + s = fp.read(i32(s, 12) * length) + + x = y = 0 + xsize, ysize = size + xtile, ytile = tilesize + self.tile = [] + + for i in range(0, len(s), length): + x1 = min(xsize, x + xtile) + y1 = min(ysize, y + ytile) + + compression = i32(s, i + 8) + + if compression == 0: + self.tile.append( + ImageFile._Tile( + "raw", + (x, y, x1, y1), + i32(s, i) + 28, + self.rawmode, + ) + ) + + elif compression == 1: + # FIXME: the fill decoder is not implemented + self.tile.append( + ImageFile._Tile( + "fill", + (x, y, x1, y1), + i32(s, i) + 28, + (self.rawmode, s[12:16]), + ) + ) + + elif compression == 2: + internal_color_conversion = s[14] + jpeg_tables = s[15] + rawmode = self.rawmode + + if internal_color_conversion: + # The image is stored as usual (usually YCbCr). + if rawmode == "RGBA": + # For "RGBA", data is stored as YCbCrA based on + # negative RGB. The following trick works around + # this problem : + jpegmode, rawmode = "YCbCrK", "CMYK" + else: + jpegmode = None # let the decoder decide + + else: + # The image is stored as defined by rawmode + jpegmode = rawmode + + self.tile.append( + ImageFile._Tile( + "jpeg", + (x, y, x1, y1), + i32(s, i) + 28, + (rawmode, jpegmode), + ) + ) + + # FIXME: jpeg tables are tile dependent; the prefix + # data must be placed in the tile descriptor itself! + + if jpeg_tables: + self.tile_prefix = self.jpeg[jpeg_tables] + + else: + msg = "unknown/invalid compression" + raise OSError(msg) + + x = x + xtile + if x >= xsize: + x, y = 0, y + ytile + if y >= ysize: + break # isn't really required + + self.stream = stream + self._fp = self.fp + self.fp = None + + def load(self) -> Image.core.PixelAccess | None: + if not self.fp: + self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) + + return ImageFile.ImageFile.load(self) + + def close(self) -> None: + self.ole.close() + super().close() + + def __exit__(self, *args: object) -> None: + self.ole.close() + super().__exit__() + + +# +# -------------------------------------------------------------------- + + +Image.register_open(FpxImageFile.format, FpxImageFile, _accept) + +Image.register_extension(FpxImageFile.format, ".fpx") diff --git a/venv/Lib/site-packages/PIL/FtexImagePlugin.py b/venv/Lib/site-packages/PIL/FtexImagePlugin.py new file mode 100644 index 00000000..d60e75bb --- /dev/null +++ b/venv/Lib/site-packages/PIL/FtexImagePlugin.py @@ -0,0 +1,114 @@ +""" +A Pillow loader for .ftc and .ftu files (FTEX) +Jerome Leclanche + +The contents of this file are hereby released in the public domain (CC0) +Full text of the CC0 license: + https://creativecommons.org/publicdomain/zero/1.0/ + +Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001 + +The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a +packed custom format called FTEX. This file format uses file extensions FTC +and FTU. +* FTC files are compressed textures (using standard texture compression). +* FTU files are not compressed. +Texture File Format +The FTC and FTU texture files both use the same format. This +has the following structure: +{header} +{format_directory} +{data} +Where: +{header} = { + u32:magic, + u32:version, + u32:width, + u32:height, + u32:mipmap_count, + u32:format_count +} + +* The "magic" number is "FTEX". +* "width" and "height" are the dimensions of the texture. +* "mipmap_count" is the number of mipmaps in the texture. +* "format_count" is the number of texture formats (different versions of the +same texture) in this file. + +{format_directory} = format_count * { u32:format, u32:where } + +The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB +uncompressed textures. +The texture data for a format starts at the position "where" in the file. + +Each set of texture data in the file has the following structure: +{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } } +* "mipmap_size" is the number of bytes in that mip level. For compressed +textures this is the size of the texture data compressed with DXT1. For 24 bit +uncompressed textures, this is 3 * width * height. Following this are the image +bytes for that mipmap level. + +Note: All data is stored in little-Endian (Intel) byte order. +""" + +from __future__ import annotations + +import struct +from enum import IntEnum +from io import BytesIO + +from . import Image, ImageFile + +MAGIC = b"FTEX" + + +class Format(IntEnum): + DXT1 = 0 + UNCOMPRESSED = 1 + + +class FtexImageFile(ImageFile.ImageFile): + format = "FTEX" + format_description = "Texture File Format (IW2:EOC)" + + def _open(self) -> None: + if not _accept(self.fp.read(4)): + msg = "not an FTEX file" + raise SyntaxError(msg) + struct.unpack(" None: + pass + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(MAGIC) + + +Image.register_open(FtexImageFile.format, FtexImageFile, _accept) +Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"]) diff --git a/venv/Lib/site-packages/PIL/GbrImagePlugin.py b/venv/Lib/site-packages/PIL/GbrImagePlugin.py new file mode 100644 index 00000000..f319d7e8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/GbrImagePlugin.py @@ -0,0 +1,103 @@ +# +# The Python Imaging Library +# +# load a GIMP brush file +# +# History: +# 96-03-14 fl Created +# 16-01-08 es Version 2 +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# Copyright (c) Eric Soroos 2016. +# +# See the README file for information on usage and redistribution. +# +# +# See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for +# format documentation. +# +# This code Interprets version 1 and 2 .gbr files. +# Version 1 files are obsolete, and should not be used for new +# brushes. +# Version 2 files are saved by GIMP v2.8 (at least) +# Version 3 files have a format specifier of 18 for 16bit floats in +# the color depth field. This is currently unsupported by Pillow. +from __future__ import annotations + +from . import Image, ImageFile +from ._binary import i32be as i32 + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2) + + +## +# Image plugin for the GIMP brush format. + + +class GbrImageFile(ImageFile.ImageFile): + format = "GBR" + format_description = "GIMP brush file" + + def _open(self) -> None: + header_size = i32(self.fp.read(4)) + if header_size < 20: + msg = "not a GIMP brush" + raise SyntaxError(msg) + version = i32(self.fp.read(4)) + if version not in (1, 2): + msg = f"Unsupported GIMP brush version: {version}" + raise SyntaxError(msg) + + width = i32(self.fp.read(4)) + height = i32(self.fp.read(4)) + color_depth = i32(self.fp.read(4)) + if width <= 0 or height <= 0: + msg = "not a GIMP brush" + raise SyntaxError(msg) + if color_depth not in (1, 4): + msg = f"Unsupported GIMP brush color depth: {color_depth}" + raise SyntaxError(msg) + + if version == 1: + comment_length = header_size - 20 + else: + comment_length = header_size - 28 + magic_number = self.fp.read(4) + if magic_number != b"GIMP": + msg = "not a GIMP brush, bad magic number" + raise SyntaxError(msg) + self.info["spacing"] = i32(self.fp.read(4)) + + comment = self.fp.read(comment_length)[:-1] + + if color_depth == 1: + self._mode = "L" + else: + self._mode = "RGBA" + + self._size = width, height + + self.info["comment"] = comment + + # Image might not be small + Image._decompression_bomb_check(self.size) + + # Data is an uncompressed block of w * h * bytes/pixel + self._data_size = width * height * color_depth + + def load(self) -> Image.core.PixelAccess | None: + if self._im is None: + self.im = Image.core.new(self.mode, self.size) + self.frombytes(self.fp.read(self._data_size)) + return Image.Image.load(self) + + +# +# registry + + +Image.register_open(GbrImageFile.format, GbrImageFile, _accept) +Image.register_extension(GbrImageFile.format, ".gbr") diff --git a/venv/Lib/site-packages/PIL/GdImageFile.py b/venv/Lib/site-packages/PIL/GdImageFile.py new file mode 100644 index 00000000..891225ce --- /dev/null +++ b/venv/Lib/site-packages/PIL/GdImageFile.py @@ -0,0 +1,102 @@ +# +# The Python Imaging Library. +# $Id$ +# +# GD file handling +# +# History: +# 1996-04-12 fl Created +# +# Copyright (c) 1997 by Secret Labs AB. +# Copyright (c) 1996 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + + +""" +.. note:: + This format cannot be automatically recognized, so the + class is not registered for use with :py:func:`PIL.Image.open()`. To open a + gd file, use the :py:func:`PIL.GdImageFile.open()` function instead. + +.. warning:: + THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This + implementation is provided for convenience and demonstrational + purposes only. +""" +from __future__ import annotations + +from typing import IO + +from . import ImageFile, ImagePalette, UnidentifiedImageError +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._typing import StrOrBytesPath + + +class GdImageFile(ImageFile.ImageFile): + """ + Image plugin for the GD uncompressed format. Note that this format + is not supported by the standard :py:func:`PIL.Image.open()` function. To use + this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and + use the :py:func:`PIL.GdImageFile.open()` function. + """ + + format = "GD" + format_description = "GD uncompressed images" + + def _open(self) -> None: + # Header + assert self.fp is not None + + s = self.fp.read(1037) + + if i16(s) not in [65534, 65535]: + msg = "Not a valid GD 2.x .gd file" + raise SyntaxError(msg) + + self._mode = "P" + self._size = i16(s, 2), i16(s, 4) + + true_color = s[6] + true_color_offset = 2 if true_color else 0 + + # transparency index + tindex = i32(s, 7 + true_color_offset) + if tindex < 256: + self.info["transparency"] = tindex + + self.palette = ImagePalette.raw( + "RGBX", s[7 + true_color_offset + 6 : 7 + true_color_offset + 6 + 256 * 4] + ) + + self.tile = [ + ImageFile._Tile( + "raw", + (0, 0) + self.size, + 7 + true_color_offset + 6 + 256 * 4, + "L", + ) + ] + + +def open(fp: StrOrBytesPath | IO[bytes], mode: str = "r") -> GdImageFile: + """ + Load texture from a GD image file. + + :param fp: GD file name, or an opened file handle. + :param mode: Optional mode. In this version, if the mode argument + is given, it must be "r". + :returns: An image instance. + :raises OSError: If the image could not be read. + """ + if mode != "r": + msg = "bad mode" + raise ValueError(msg) + + try: + return GdImageFile(fp) + except SyntaxError as e: + msg = "cannot identify this image file" + raise UnidentifiedImageError(msg) from e diff --git a/venv/Lib/site-packages/PIL/GifImagePlugin.py b/venv/Lib/site-packages/PIL/GifImagePlugin.py new file mode 100644 index 00000000..b03aa7f1 --- /dev/null +++ b/venv/Lib/site-packages/PIL/GifImagePlugin.py @@ -0,0 +1,1213 @@ +# +# The Python Imaging Library. +# $Id$ +# +# GIF file handling +# +# History: +# 1995-09-01 fl Created +# 1996-12-14 fl Added interlace support +# 1996-12-30 fl Added animation support +# 1997-01-05 fl Added write support, fixed local colour map bug +# 1997-02-23 fl Make sure to load raster data in getdata() +# 1997-07-05 fl Support external decoder (0.4) +# 1998-07-09 fl Handle all modes when saving (0.5) +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6) +# 2001-04-17 fl Added palette optimization (0.7) +# 2002-06-06 fl Added transparency support for save (0.8) +# 2004-02-24 fl Disable interlacing for small images +# +# Copyright (c) 1997-2004 by Secret Labs AB +# Copyright (c) 1995-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import itertools +import math +import os +import subprocess +from enum import IntEnum +from functools import cached_property +from typing import IO, Any, Literal, NamedTuple, Union, cast + +from . import ( + Image, + ImageChops, + ImageFile, + ImageMath, + ImageOps, + ImagePalette, + ImageSequence, +) +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 +from ._util import DeferredError + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import _imaging + from ._typing import Buffer + + +class LoadingStrategy(IntEnum): + """.. versionadded:: 9.1.0""" + + RGB_AFTER_FIRST = 0 + RGB_AFTER_DIFFERENT_PALETTE_ONLY = 1 + RGB_ALWAYS = 2 + + +#: .. versionadded:: 9.1.0 +LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST + +# -------------------------------------------------------------------- +# Identify/read GIF files + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"GIF87a", b"GIF89a")) + + +## +# Image plugin for GIF images. This plugin supports both GIF87 and +# GIF89 images. + + +class GifImageFile(ImageFile.ImageFile): + format = "GIF" + format_description = "Compuserve GIF" + _close_exclusive_fp_after_loading = False + + global_palette = None + + def data(self) -> bytes | None: + s = self.fp.read(1) + if s and s[0]: + return self.fp.read(s[0]) + return None + + def _is_palette_needed(self, p: bytes) -> bool: + for i in range(0, len(p), 3): + if not (i // 3 == p[i] == p[i + 1] == p[i + 2]): + return True + return False + + def _open(self) -> None: + # Screen + s = self.fp.read(13) + if not _accept(s): + msg = "not a GIF file" + raise SyntaxError(msg) + + self.info["version"] = s[:6] + self._size = i16(s, 6), i16(s, 8) + flags = s[10] + bits = (flags & 7) + 1 + + if flags & 128: + # get global palette + self.info["background"] = s[11] + # check if palette contains colour indices + p = self.fp.read(3 << bits) + if self._is_palette_needed(p): + p = ImagePalette.raw("RGB", p) + self.global_palette = self.palette = p + + self._fp = self.fp # FIXME: hack + self.__rewind = self.fp.tell() + self._n_frames: int | None = None + self._seek(0) # get ready to read first frame + + @property + def n_frames(self) -> int: + if self._n_frames is None: + current = self.tell() + try: + while True: + self._seek(self.tell() + 1, False) + except EOFError: + self._n_frames = self.tell() + 1 + self.seek(current) + return self._n_frames + + @cached_property + def is_animated(self) -> bool: + if self._n_frames is not None: + return self._n_frames != 1 + + current = self.tell() + if current: + return True + + try: + self._seek(1, False) + is_animated = True + except EOFError: + is_animated = False + + self.seek(current) + return is_animated + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if frame < self.__frame: + self._im = None + self._seek(0) + + last_frame = self.__frame + for f in range(self.__frame + 1, frame + 1): + try: + self._seek(f) + except EOFError as e: + self.seek(last_frame) + msg = "no more images in GIF file" + raise EOFError(msg) from e + + def _seek(self, frame: int, update_image: bool = True) -> None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + if frame == 0: + # rewind + self.__offset = 0 + self.dispose: _imaging.ImagingCore | None = None + self.__frame = -1 + self._fp.seek(self.__rewind) + self.disposal_method = 0 + if "comment" in self.info: + del self.info["comment"] + else: + # ensure that the previous frame was loaded + if self.tile and update_image: + self.load() + + if frame != self.__frame + 1: + msg = f"cannot seek to frame {frame}" + raise ValueError(msg) + + self.fp = self._fp + if self.__offset: + # backup to last frame + self.fp.seek(self.__offset) + while self.data(): + pass + self.__offset = 0 + + s = self.fp.read(1) + if not s or s == b";": + msg = "no more images in GIF file" + raise EOFError(msg) + + palette: ImagePalette.ImagePalette | Literal[False] | None = None + + info: dict[str, Any] = {} + frame_transparency = None + interlace = None + frame_dispose_extent = None + while True: + if not s: + s = self.fp.read(1) + if not s or s == b";": + break + + elif s == b"!": + # + # extensions + # + s = self.fp.read(1) + block = self.data() + if s[0] == 249 and block is not None: + # + # graphic control extension + # + flags = block[0] + if flags & 1: + frame_transparency = block[3] + info["duration"] = i16(block, 1) * 10 + + # disposal method - find the value of bits 4 - 6 + dispose_bits = 0b00011100 & flags + dispose_bits = dispose_bits >> 2 + if dispose_bits: + # only set the dispose if it is not + # unspecified. I'm not sure if this is + # correct, but it seems to prevent the last + # frame from looking odd for some animations + self.disposal_method = dispose_bits + elif s[0] == 254: + # + # comment extension + # + comment = b"" + + # Read this comment block + while block: + comment += block + block = self.data() + + if "comment" in info: + # If multiple comment blocks in frame, separate with \n + info["comment"] += b"\n" + comment + else: + info["comment"] = comment + s = None + continue + elif s[0] == 255 and frame == 0 and block is not None: + # + # application extension + # + info["extension"] = block, self.fp.tell() + if block.startswith(b"NETSCAPE2.0"): + block = self.data() + if block and len(block) >= 3 and block[0] == 1: + self.info["loop"] = i16(block, 1) + while self.data(): + pass + + elif s == b",": + # + # local image + # + s = self.fp.read(9) + + # extent + x0, y0 = i16(s, 0), i16(s, 2) + x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) + if (x1 > self.size[0] or y1 > self.size[1]) and update_image: + self._size = max(x1, self.size[0]), max(y1, self.size[1]) + Image._decompression_bomb_check(self._size) + frame_dispose_extent = x0, y0, x1, y1 + flags = s[8] + + interlace = (flags & 64) != 0 + + if flags & 128: + bits = (flags & 7) + 1 + p = self.fp.read(3 << bits) + if self._is_palette_needed(p): + palette = ImagePalette.raw("RGB", p) + else: + palette = False + + # image data + bits = self.fp.read(1)[0] + self.__offset = self.fp.tell() + break + s = None + + if interlace is None: + msg = "image not found in GIF frame" + raise EOFError(msg) + + self.__frame = frame + if not update_image: + return + + self.tile = [] + + if self.dispose: + self.im.paste(self.dispose, self.dispose_extent) + + self._frame_palette = palette if palette is not None else self.global_palette + self._frame_transparency = frame_transparency + if frame == 0: + if self._frame_palette: + if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: + self._mode = "RGBA" if frame_transparency is not None else "RGB" + else: + self._mode = "P" + else: + self._mode = "L" + + if palette: + self.palette = palette + elif self.global_palette: + from copy import copy + + self.palette = copy(self.global_palette) + else: + self.palette = None + else: + if self.mode == "P": + if ( + LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY + or palette + ): + if "transparency" in self.info: + self.im.putpalettealpha(self.info["transparency"], 0) + self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG) + self._mode = "RGBA" + del self.info["transparency"] + else: + self._mode = "RGB" + self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) + + def _rgb(color: int) -> tuple[int, int, int]: + if self._frame_palette: + if color * 3 + 3 > len(self._frame_palette.palette): + color = 0 + return cast( + tuple[int, int, int], + tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]), + ) + else: + return (color, color, color) + + self.dispose = None + self.dispose_extent: tuple[int, int, int, int] | None = frame_dispose_extent + if self.dispose_extent and self.disposal_method >= 2: + try: + if self.disposal_method == 2: + # replace with background colour + + # only dispose the extent in this frame + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + + # by convention, attempt to use transparency first + dispose_mode = "P" + color = self.info.get("transparency", frame_transparency) + if color is not None: + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGBA" + color = _rgb(color) + (0,) + else: + color = self.info.get("background", 0) + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGB" + color = _rgb(color) + self.dispose = Image.core.fill(dispose_mode, dispose_size, color) + else: + # replace with previous contents + if self._im is not None: + # only dispose the extent in this frame + self.dispose = self._crop(self.im, self.dispose_extent) + elif frame_transparency is not None: + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + dispose_mode = "P" + color = frame_transparency + if self.mode in ("RGB", "RGBA"): + dispose_mode = "RGBA" + color = _rgb(frame_transparency) + (0,) + self.dispose = Image.core.fill( + dispose_mode, dispose_size, color + ) + except AttributeError: + pass + + if interlace is not None: + transparency = -1 + if frame_transparency is not None: + if frame == 0: + if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS: + self.info["transparency"] = frame_transparency + elif self.mode not in ("RGB", "RGBA"): + transparency = frame_transparency + self.tile = [ + ImageFile._Tile( + "gif", + (x0, y0, x1, y1), + self.__offset, + (bits, interlace, transparency), + ) + ] + + if info.get("comment"): + self.info["comment"] = info["comment"] + for k in ["duration", "extension"]: + if k in info: + self.info[k] = info[k] + elif k in self.info: + del self.info[k] + + def load_prepare(self) -> None: + temp_mode = "P" if self._frame_palette else "L" + self._prev_im = None + if self.__frame == 0: + if self._frame_transparency is not None: + self.im = Image.core.fill( + temp_mode, self.size, self._frame_transparency + ) + elif self.mode in ("RGB", "RGBA"): + self._prev_im = self.im + if self._frame_palette: + self.im = Image.core.fill("P", self.size, self._frame_transparency or 0) + self.im.putpalette("RGB", *self._frame_palette.getdata()) + else: + self._im = None + if not self._prev_im and self._im is not None and self.size != self.im.size: + expanded_im = Image.core.fill(self.im.mode, self.size) + if self._frame_palette: + expanded_im.putpalette("RGB", *self._frame_palette.getdata()) + expanded_im.paste(self.im, (0, 0) + self.im.size) + + self.im = expanded_im + self._mode = temp_mode + self._frame_palette = None + + super().load_prepare() + + def load_end(self) -> None: + if self.__frame == 0: + if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + self._mode = "RGBA" + else: + self._mode = "RGB" + self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG) + return + if not self._prev_im: + return + if self.size != self._prev_im.size: + if self._frame_transparency is not None: + expanded_im = Image.core.fill("RGBA", self.size) + else: + expanded_im = Image.core.fill("P", self.size) + expanded_im.putpalette("RGB", "RGB", self.im.getpalette()) + expanded_im = expanded_im.convert("RGB") + expanded_im.paste(self._prev_im, (0, 0) + self._prev_im.size) + + self._prev_im = expanded_im + assert self._prev_im is not None + if self._frame_transparency is not None: + if self.mode == "L": + frame_im = self.im.convert_transparent("LA", self._frame_transparency) + else: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") + else: + frame_im = self.im.convert("RGB") + + assert self.dispose_extent is not None + frame_im = self._crop(frame_im, self.dispose_extent) + + self.im = self._prev_im + self._mode = self.im.mode + if frame_im.mode in ("LA", "RGBA"): + self.im.paste(frame_im, self.dispose_extent, frame_im) + else: + self.im.paste(frame_im, self.dispose_extent) + + def tell(self) -> int: + return self.__frame + + +# -------------------------------------------------------------------- +# Write GIF files + + +RAWMODE = {"1": "L", "L": "L", "P": "P"} + + +def _normalize_mode(im: Image.Image) -> Image.Image: + """ + Takes an image (or frame), returns an image in a mode that is appropriate + for saving in a Gif. + + It may return the original image, or it may return an image converted to + palette or 'L' mode. + + :param im: Image object + :returns: Image object + """ + if im.mode in RAWMODE: + im.load() + return im + if Image.getmodebase(im.mode) == "RGB": + im = im.convert("P", palette=Image.Palette.ADAPTIVE) + assert im.palette is not None + if im.palette.mode == "RGBA": + for rgba in im.palette.colors: + if rgba[3] == 0: + im.info["transparency"] = im.palette.colors[rgba] + break + return im + return im.convert("L") + + +_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette] + + +def _normalize_palette( + im: Image.Image, palette: _Palette | None, info: dict[str, Any] +) -> Image.Image: + """ + Normalizes the palette for image. + - Sets the palette to the incoming palette, if provided. + - Ensures that there's a palette for L mode images + - Optimizes the palette if necessary/desired. + + :param im: Image object + :param palette: bytes object containing the source palette, or .... + :param info: encoderinfo + :returns: Image object + """ + source_palette = None + if palette: + # a bytes palette + if isinstance(palette, (bytes, bytearray, list)): + source_palette = bytearray(palette[:768]) + if isinstance(palette, ImagePalette.ImagePalette): + source_palette = bytearray(palette.palette) + + if im.mode == "P": + if not source_palette: + im_palette = im.getpalette(None) + assert im_palette is not None + source_palette = bytearray(im_palette) + else: # L-mode + if not source_palette: + source_palette = bytearray(i // 3 for i in range(768)) + im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette) + assert source_palette is not None + + if palette: + used_palette_colors: list[int | None] = [] + assert im.palette is not None + for i in range(0, len(source_palette), 3): + source_color = tuple(source_palette[i : i + 3]) + index = im.palette.colors.get(source_color) + if index in used_palette_colors: + index = None + used_palette_colors.append(index) + for i, index in enumerate(used_palette_colors): + if index is None: + for j in range(len(used_palette_colors)): + if j not in used_palette_colors: + used_palette_colors[i] = j + break + dest_map: list[int] = [] + for index in used_palette_colors: + assert index is not None + dest_map.append(index) + im = im.remap_palette(dest_map) + else: + optimized_palette_colors = _get_optimize(im, info) + if optimized_palette_colors is not None: + im = im.remap_palette(optimized_palette_colors, source_palette) + if "transparency" in info: + try: + info["transparency"] = optimized_palette_colors.index( + info["transparency"] + ) + except ValueError: + del info["transparency"] + return im + + assert im.palette is not None + im.palette.palette = source_palette + return im + + +def _write_single_frame( + im: Image.Image, + fp: IO[bytes], + palette: _Palette | None, +) -> None: + im_out = _normalize_mode(im) + for k, v in im_out.info.items(): + if isinstance(k, str): + im.encoderinfo.setdefault(k, v) + im_out = _normalize_palette(im_out, palette, im.encoderinfo) + + for s in _get_global_header(im_out, im.encoderinfo): + fp.write(s) + + # local image header + flags = 0 + if get_interlace(im): + flags = flags | 64 + _write_local_header(fp, im, (0, 0), flags) + + im_out.encoderconfig = (8, get_interlace(im)) + ImageFile._save( + im_out, fp, [ImageFile._Tile("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])] + ) + + fp.write(b"\0") # end of image data + + +def _getbbox( + base_im: Image.Image, im_frame: Image.Image +) -> tuple[Image.Image, tuple[int, int, int, int] | None]: + palette_bytes = [ + bytes(im.palette.palette) if im.palette else b"" for im in (base_im, im_frame) + ] + if palette_bytes[0] != palette_bytes[1]: + im_frame = im_frame.convert("RGBA") + base_im = base_im.convert("RGBA") + delta = ImageChops.subtract_modulo(im_frame, base_im) + return delta, delta.getbbox(alpha_only=False) + + +class _Frame(NamedTuple): + im: Image.Image + bbox: tuple[int, int, int, int] | None + encoderinfo: dict[str, Any] + + +def _write_multiple_frames( + im: Image.Image, fp: IO[bytes], palette: _Palette | None +) -> bool: + duration = im.encoderinfo.get("duration") + disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) + + im_frames: list[_Frame] = [] + previous_im: Image.Image | None = None + frame_count = 0 + background_im = None + for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): + for im_frame in ImageSequence.Iterator(imSequence): + # a copy is required here since seek can still mutate the image + im_frame = _normalize_mode(im_frame.copy()) + if frame_count == 0: + for k, v in im_frame.info.items(): + if k == "transparency": + continue + if isinstance(k, str): + im.encoderinfo.setdefault(k, v) + + encoderinfo = im.encoderinfo.copy() + if "transparency" in im_frame.info: + encoderinfo.setdefault("transparency", im_frame.info["transparency"]) + im_frame = _normalize_palette(im_frame, palette, encoderinfo) + if isinstance(duration, (list, tuple)): + encoderinfo["duration"] = duration[frame_count] + elif duration is None and "duration" in im_frame.info: + encoderinfo["duration"] = im_frame.info["duration"] + if isinstance(disposal, (list, tuple)): + encoderinfo["disposal"] = disposal[frame_count] + frame_count += 1 + + diff_frame = None + if im_frames and previous_im: + # delta frame + delta, bbox = _getbbox(previous_im, im_frame) + if not bbox: + # This frame is identical to the previous frame + if encoderinfo.get("duration"): + im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"] + continue + if im_frames[-1].encoderinfo.get("disposal") == 2: + # To appear correctly in viewers using a convention, + # only consider transparency, and not background color + color = im.encoderinfo.get( + "transparency", im.info.get("transparency") + ) + if color is not None: + if background_im is None: + background = _get_background(im_frame, color) + background_im = Image.new("P", im_frame.size, background) + first_palette = im_frames[0].im.palette + assert first_palette is not None + background_im.putpalette(first_palette, first_palette.mode) + bbox = _getbbox(background_im, im_frame)[1] + else: + bbox = (0, 0) + im_frame.size + elif encoderinfo.get("optimize") and im_frame.mode != "1": + if "transparency" not in encoderinfo: + assert im_frame.palette is not None + try: + encoderinfo["transparency"] = ( + im_frame.palette._new_color_index(im_frame) + ) + except ValueError: + pass + if "transparency" in encoderinfo: + # When the delta is zero, fill the image with transparency + diff_frame = im_frame.copy() + fill = Image.new("P", delta.size, encoderinfo["transparency"]) + if delta.mode == "RGBA": + r, g, b, a = delta.split() + mask = ImageMath.lambda_eval( + lambda args: args["convert"]( + args["max"]( + args["max"]( + args["max"](args["r"], args["g"]), args["b"] + ), + args["a"], + ) + * 255, + "1", + ), + r=r, + g=g, + b=b, + a=a, + ) + else: + if delta.mode == "P": + # Convert to L without considering palette + delta_l = Image.new("L", delta.size) + delta_l.putdata(delta.getdata()) + delta = delta_l + mask = ImageMath.lambda_eval( + lambda args: args["convert"](args["im"] * 255, "1"), + im=delta, + ) + diff_frame.paste(fill, mask=ImageOps.invert(mask)) + else: + bbox = None + previous_im = im_frame + im_frames.append(_Frame(diff_frame or im_frame, bbox, encoderinfo)) + + if len(im_frames) == 1: + if "duration" in im.encoderinfo: + # Since multiple frames will not be written, use the combined duration + im.encoderinfo["duration"] = im_frames[0].encoderinfo["duration"] + return False + + for frame_data in im_frames: + im_frame = frame_data.im + if not frame_data.bbox: + # global header + for s in _get_global_header(im_frame, frame_data.encoderinfo): + fp.write(s) + offset = (0, 0) + else: + # compress difference + if not palette: + frame_data.encoderinfo["include_color_table"] = True + + if frame_data.bbox != (0, 0) + im_frame.size: + im_frame = im_frame.crop(frame_data.bbox) + offset = frame_data.bbox[:2] + _write_frame_data(fp, im_frame, offset, frame_data.encoderinfo) + return True + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, save_all=True) + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False +) -> None: + # header + if "palette" in im.encoderinfo or "palette" in im.info: + palette = im.encoderinfo.get("palette", im.info.get("palette")) + else: + palette = None + im.encoderinfo.setdefault("optimize", True) + + if not save_all or not _write_multiple_frames(im, fp, palette): + _write_single_frame(im, fp, palette) + + fp.write(b";") # end of file + + if hasattr(fp, "flush"): + fp.flush() + + +def get_interlace(im: Image.Image) -> int: + interlace = im.encoderinfo.get("interlace", 1) + + # workaround for @PIL153 + if min(im.size) < 16: + interlace = 0 + + return interlace + + +def _write_local_header( + fp: IO[bytes], im: Image.Image, offset: tuple[int, int], flags: int +) -> None: + try: + transparency = im.encoderinfo["transparency"] + except KeyError: + transparency = None + + if "duration" in im.encoderinfo: + duration = int(im.encoderinfo["duration"] / 10) + else: + duration = 0 + + disposal = int(im.encoderinfo.get("disposal", 0)) + + if transparency is not None or duration != 0 or disposal: + packed_flag = 1 if transparency is not None else 0 + packed_flag |= disposal << 2 + + fp.write( + b"!" + + o8(249) # extension intro + + o8(4) # length + + o8(packed_flag) # packed fields + + o16(duration) # duration + + o8(transparency or 0) # transparency index + + o8(0) + ) + + include_color_table = im.encoderinfo.get("include_color_table") + if include_color_table: + palette_bytes = _get_palette_bytes(im) + color_table_size = _get_color_table_size(palette_bytes) + if color_table_size: + flags = flags | 128 # local color table flag + flags = flags | color_table_size + + fp.write( + b"," + + o16(offset[0]) # offset + + o16(offset[1]) + + o16(im.size[0]) # size + + o16(im.size[1]) + + o8(flags) # flags + ) + if include_color_table and color_table_size: + fp.write(_get_header_palette(palette_bytes)) + fp.write(o8(8)) # bits + + +def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + # Unused by default. + # To use, uncomment the register_save call at the end of the file. + # + # If you need real GIF compression and/or RGB quantization, you + # can use the external NETPBM/PBMPLUS utilities. See comments + # below for information on how to enable this. + tempfile = im._dump() + + try: + with open(filename, "wb") as f: + if im.mode != "RGB": + subprocess.check_call( + ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL + ) + else: + # Pipe ppmquant output into ppmtogif + # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) + quant_cmd = ["ppmquant", "256", tempfile] + togif_cmd = ["ppmtogif"] + quant_proc = subprocess.Popen( + quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) + togif_proc = subprocess.Popen( + togif_cmd, + stdin=quant_proc.stdout, + stdout=f, + stderr=subprocess.DEVNULL, + ) + + # Allow ppmquant to receive SIGPIPE if ppmtogif exits + assert quant_proc.stdout is not None + quant_proc.stdout.close() + + retcode = quant_proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, quant_cmd) + + retcode = togif_proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, togif_cmd) + finally: + try: + os.unlink(tempfile) + except OSError: + pass + + +# Force optimization so that we can test performance against +# cases where it took lots of memory and time previously. +_FORCE_OPTIMIZE = False + + +def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None: + """ + Palette optimization is a potentially expensive operation. + + This function determines if the palette should be optimized using + some heuristics, then returns the list of palette entries in use. + + :param im: Image object + :param info: encoderinfo + :returns: list of indexes of palette entries in use, or None + """ + if im.mode in ("P", "L") and info and info.get("optimize"): + # Potentially expensive operation. + + # The palette saves 3 bytes per color not used, but palette + # lengths are restricted to 3*(2**N) bytes. Max saving would + # be 768 -> 6 bytes if we went all the way down to 2 colors. + # * If we're over 128 colors, we can't save any space. + # * If there aren't any holes, it's not worth collapsing. + # * If we have a 'large' image, the palette is in the noise. + + # create the new palette if not every color is used + optimise = _FORCE_OPTIMIZE or im.mode == "L" + if optimise or im.width * im.height < 512 * 512: + # check which colors are used + used_palette_colors = [] + for i, count in enumerate(im.histogram()): + if count: + used_palette_colors.append(i) + + if optimise or max(used_palette_colors) >= len(used_palette_colors): + return used_palette_colors + + assert im.palette is not None + num_palette_colors = len(im.palette.palette) // Image.getmodebands( + im.palette.mode + ) + current_palette_size = 1 << (num_palette_colors - 1).bit_length() + if ( + # check that the palette would become smaller when saved + len(used_palette_colors) <= current_palette_size // 2 + # check that the palette is not already the smallest possible size + and current_palette_size > 2 + ): + return used_palette_colors + return None + + +def _get_color_table_size(palette_bytes: bytes) -> int: + # calculate the palette size for the header + if not palette_bytes: + return 0 + elif len(palette_bytes) < 9: + return 1 + else: + return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1 + + +def _get_header_palette(palette_bytes: bytes) -> bytes: + """ + Returns the palette, null padded to the next power of 2 (*3) bytes + suitable for direct inclusion in the GIF header + + :param palette_bytes: Unpadded palette bytes, in RGBRGB form + :returns: Null padded palette + """ + color_table_size = _get_color_table_size(palette_bytes) + + # add the missing amount of bytes + # the palette has to be 2< 0: + palette_bytes += o8(0) * 3 * actual_target_size_diff + return palette_bytes + + +def _get_palette_bytes(im: Image.Image) -> bytes: + """ + Gets the palette for inclusion in the gif header + + :param im: Image object + :returns: Bytes, len<=768 suitable for inclusion in gif header + """ + if not im.palette: + return b"" + + palette = bytes(im.palette.palette) + if im.palette.mode == "RGBA": + palette = b"".join(palette[i * 4 : i * 4 + 3] for i in range(len(palette) // 3)) + return palette + + +def _get_background( + im: Image.Image, + info_background: int | tuple[int, int, int] | tuple[int, int, int, int] | None, +) -> int: + background = 0 + if info_background: + if isinstance(info_background, tuple): + # WebPImagePlugin stores an RGBA value in info["background"] + # So it must be converted to the same format as GifImagePlugin's + # info["background"] - a global color table index + assert im.palette is not None + try: + background = im.palette.getcolor(info_background, im) + except ValueError as e: + if str(e) not in ( + # If all 256 colors are in use, + # then there is no need for the background color + "cannot allocate more than 256 colors", + # Ignore non-opaque WebP background + "cannot add non-opaque RGBA color to RGB palette", + ): + raise + else: + background = info_background + return background + + +def _get_global_header(im: Image.Image, info: dict[str, Any]) -> list[bytes]: + """Return a list of strings representing a GIF header""" + + # Header Block + # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp + + version = b"87a" + if im.info.get("version") == b"89a" or ( + info + and ( + "transparency" in info + or info.get("loop") is not None + or info.get("duration") + or info.get("comment") + ) + ): + version = b"89a" + + background = _get_background(im, info.get("background")) + + palette_bytes = _get_palette_bytes(im) + color_table_size = _get_color_table_size(palette_bytes) + + header = [ + b"GIF" # signature + + version # version + + o16(im.size[0]) # canvas width + + o16(im.size[1]), # canvas height + # Logical Screen Descriptor + # size of global color table + global color table flag + o8(color_table_size + 128), # packed fields + # background + reserved/aspect + o8(background) + o8(0), + # Global Color Table + _get_header_palette(palette_bytes), + ] + if info.get("loop") is not None: + header.append( + b"!" + + o8(255) # extension intro + + o8(11) + + b"NETSCAPE2.0" + + o8(3) + + o8(1) + + o16(info["loop"]) # number of loops + + o8(0) + ) + if info.get("comment"): + comment_block = b"!" + o8(254) # extension intro + + comment = info["comment"] + if isinstance(comment, str): + comment = comment.encode() + for i in range(0, len(comment), 255): + subblock = comment[i : i + 255] + comment_block += o8(len(subblock)) + subblock + + comment_block += o8(0) + header.append(comment_block) + return header + + +def _write_frame_data( + fp: IO[bytes], + im_frame: Image.Image, + offset: tuple[int, int], + params: dict[str, Any], +) -> None: + try: + im_frame.encoderinfo = params + + # local image header + _write_local_header(fp, im_frame, offset, 0) + + ImageFile._save( + im_frame, + fp, + [ImageFile._Tile("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])], + ) + + fp.write(b"\0") # end of image data + finally: + del im_frame.encoderinfo + + +# -------------------------------------------------------------------- +# Legacy GIF utilities + + +def getheader( + im: Image.Image, palette: _Palette | None = None, info: dict[str, Any] | None = None +) -> tuple[list[bytes], list[int] | None]: + """ + Legacy Method to get Gif data from image. + + Warning:: May modify image data. + + :param im: Image object + :param palette: bytes object containing the source palette, or .... + :param info: encoderinfo + :returns: tuple of(list of header items, optimized palette) + + """ + if info is None: + info = {} + + used_palette_colors = _get_optimize(im, info) + + if "background" not in info and "background" in im.info: + info["background"] = im.info["background"] + + im_mod = _normalize_palette(im, palette, info) + im.palette = im_mod.palette + im.im = im_mod.im + header = _get_global_header(im, info) + + return header, used_palette_colors + + +def getdata( + im: Image.Image, offset: tuple[int, int] = (0, 0), **params: Any +) -> list[bytes]: + """ + Legacy Method + + Return a list of strings representing this image. + The first string is a local image header, the rest contains + encoded image data. + + To specify duration, add the time in milliseconds, + e.g. ``getdata(im_frame, duration=1000)`` + + :param im: Image object + :param offset: Tuple of (x, y) pixels. Defaults to (0, 0) + :param \\**params: e.g. duration or other encoder info parameters + :returns: List of bytes containing GIF encoded frame data + + """ + from io import BytesIO + + class Collector(BytesIO): + data = [] + + def write(self, data: Buffer) -> int: + self.data.append(data) + return len(data) + + im.load() # make sure raster data is available + + fp = Collector() + + _write_frame_data(fp, im, offset, params) + + return fp.data + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(GifImageFile.format, GifImageFile, _accept) +Image.register_save(GifImageFile.format, _save) +Image.register_save_all(GifImageFile.format, _save_all) +Image.register_extension(GifImageFile.format, ".gif") +Image.register_mime(GifImageFile.format, "image/gif") + +# +# Uncomment the following line if you wish to use NETPBM/PBMPLUS +# instead of the built-in "uncompressed" GIF encoder + +# Image.register_save(GifImageFile.format, _save_netpbm) diff --git a/venv/Lib/site-packages/PIL/GimpGradientFile.py b/venv/Lib/site-packages/PIL/GimpGradientFile.py new file mode 100644 index 00000000..ec62f8e4 --- /dev/null +++ b/venv/Lib/site-packages/PIL/GimpGradientFile.py @@ -0,0 +1,149 @@ +# +# Python Imaging Library +# $Id$ +# +# stuff to read (and render) GIMP gradient files +# +# History: +# 97-08-23 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# + +""" +Stuff to translate curve segments to palette values (derived from +the corresponding code in GIMP, written by Federico Mena Quintero. +See the GIMP distribution for more information.) +""" +from __future__ import annotations + +from math import log, pi, sin, sqrt +from typing import IO, Callable + +from ._binary import o8 + +EPSILON = 1e-10 +"""""" # Enable auto-doc for data member + + +def linear(middle: float, pos: float) -> float: + if pos <= middle: + if middle < EPSILON: + return 0.0 + else: + return 0.5 * pos / middle + else: + pos = pos - middle + middle = 1.0 - middle + if middle < EPSILON: + return 1.0 + else: + return 0.5 + 0.5 * pos / middle + + +def curved(middle: float, pos: float) -> float: + return pos ** (log(0.5) / log(max(middle, EPSILON))) + + +def sine(middle: float, pos: float) -> float: + return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0 + + +def sphere_increasing(middle: float, pos: float) -> float: + return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2) + + +def sphere_decreasing(middle: float, pos: float) -> float: + return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2) + + +SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing] +"""""" # Enable auto-doc for data member + + +class GradientFile: + gradient: ( + list[ + tuple[ + float, + float, + float, + list[float], + list[float], + Callable[[float, float], float], + ] + ] + | None + ) = None + + def getpalette(self, entries: int = 256) -> tuple[bytes, str]: + assert self.gradient is not None + palette = [] + + ix = 0 + x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] + + for i in range(entries): + x = i / (entries - 1) + + while x1 < x: + ix += 1 + x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] + + w = x1 - x0 + + if w < EPSILON: + scale = segment(0.5, 0.5) + else: + scale = segment((xm - x0) / w, (x - x0) / w) + + # expand to RGBA + r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5)) + g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5)) + b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5)) + a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5)) + + # add to palette + palette.append(r + g + b + a) + + return b"".join(palette), "RGBA" + + +class GimpGradientFile(GradientFile): + """File handler for GIMP's gradient format.""" + + def __init__(self, fp: IO[bytes]) -> None: + if not fp.readline().startswith(b"GIMP Gradient"): + msg = "not a GIMP gradient file" + raise SyntaxError(msg) + + line = fp.readline() + + # GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do + if line.startswith(b"Name: "): + line = fp.readline().strip() + + count = int(line) + + self.gradient = [] + + for i in range(count): + s = fp.readline().split() + w = [float(x) for x in s[:11]] + + x0, x1 = w[0], w[2] + xm = w[1] + rgb0 = w[3:7] + rgb1 = w[7:11] + + segment = SEGMENTS[int(s[11])] + cspace = int(s[12]) + + if cspace != 0: + msg = "cannot handle HSV colour space" + raise OSError(msg) + + self.gradient.append((x0, x1, xm, rgb0, rgb1, segment)) diff --git a/venv/Lib/site-packages/PIL/GimpPaletteFile.py b/venv/Lib/site-packages/PIL/GimpPaletteFile.py new file mode 100644 index 00000000..379ffd73 --- /dev/null +++ b/venv/Lib/site-packages/PIL/GimpPaletteFile.py @@ -0,0 +1,72 @@ +# +# Python Imaging Library +# $Id$ +# +# stuff to read GIMP palette files +# +# History: +# 1997-08-23 fl Created +# 2004-09-07 fl Support GIMP 2.0 palette files. +# +# Copyright (c) Secret Labs AB 1997-2004. All rights reserved. +# Copyright (c) Fredrik Lundh 1997-2004. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re +from io import BytesIO +from typing import IO + + +class GimpPaletteFile: + """File handler for GIMP's palette format.""" + + rawmode = "RGB" + + def _read(self, fp: IO[bytes], limit: bool = True) -> None: + if not fp.readline().startswith(b"GIMP Palette"): + msg = "not a GIMP palette file" + raise SyntaxError(msg) + + palette: list[int] = [] + i = 0 + while True: + if limit and i == 256 + 3: + break + + i += 1 + s = fp.readline() + if not s: + break + + # skip fields and comment lines + if re.match(rb"\w+:|#", s): + continue + if limit and len(s) > 100: + msg = "bad palette file" + raise SyntaxError(msg) + + v = s.split(maxsplit=3) + if len(v) < 3: + msg = "bad palette entry" + raise ValueError(msg) + + palette += (int(v[i]) for i in range(3)) + if limit and len(palette) == 768: + break + + self.palette = bytes(palette) + + def __init__(self, fp: IO[bytes]) -> None: + self._read(fp) + + @classmethod + def frombytes(cls, data: bytes) -> GimpPaletteFile: + self = cls.__new__(cls) + self._read(BytesIO(data), False) + return self + + def getpalette(self) -> tuple[bytes, str]: + return self.palette, self.rawmode diff --git a/venv/Lib/site-packages/PIL/GribStubImagePlugin.py b/venv/Lib/site-packages/PIL/GribStubImagePlugin.py new file mode 100644 index 00000000..439fc5a3 --- /dev/null +++ b/venv/Lib/site-packages/PIL/GribStubImagePlugin.py @@ -0,0 +1,75 @@ +# +# The Python Imaging Library +# $Id$ +# +# GRIB stub adapter +# +# Copyright (c) 1996-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific GRIB image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +# -------------------------------------------------------------------- +# Image adapter + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"GRIB") and prefix[7] == 1 + + +class GribStubImageFile(ImageFile.StubImageFile): + format = "GRIB" + format_description = "GRIB" + + def _open(self) -> None: + if not _accept(self.fp.read(8)): + msg = "Not a GRIB file" + raise SyntaxError(msg) + + self.fp.seek(-8, os.SEEK_CUR) + + # make something up + self._mode = "F" + self._size = 1, 1 + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "GRIB save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept) +Image.register_save(GribStubImageFile.format, _save) + +Image.register_extension(GribStubImageFile.format, ".grib") diff --git a/venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py b/venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py new file mode 100644 index 00000000..76e640f1 --- /dev/null +++ b/venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py @@ -0,0 +1,75 @@ +# +# The Python Imaging Library +# $Id$ +# +# HDF5 stub adapter +# +# Copyright (c) 2000-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific HDF5 image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +# -------------------------------------------------------------------- +# Image adapter + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\x89HDF\r\n\x1a\n") + + +class HDF5StubImageFile(ImageFile.StubImageFile): + format = "HDF5" + format_description = "HDF5" + + def _open(self) -> None: + if not _accept(self.fp.read(8)): + msg = "Not an HDF file" + raise SyntaxError(msg) + + self.fp.seek(-8, os.SEEK_CUR) + + # make something up + self._mode = "F" + self._size = 1, 1 + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "HDF5 save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept) +Image.register_save(HDF5StubImageFile.format, _save) + +Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"]) diff --git a/venv/Lib/site-packages/PIL/IcnsImagePlugin.py b/venv/Lib/site-packages/PIL/IcnsImagePlugin.py new file mode 100644 index 00000000..5a88429e --- /dev/null +++ b/venv/Lib/site-packages/PIL/IcnsImagePlugin.py @@ -0,0 +1,411 @@ +# +# The Python Imaging Library. +# $Id$ +# +# macOS icns file decoder, based on icns.py by Bob Ippolito. +# +# history: +# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies. +# 2020-04-04 Allow saving on all operating systems. +# +# Copyright (c) 2004 by Bob Ippolito. +# Copyright (c) 2004 by Secret Labs. +# Copyright (c) 2004 by Fredrik Lundh. +# Copyright (c) 2014 by Alastair Houghton. +# Copyright (c) 2020 by Pan Jing. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import struct +import sys +from typing import IO + +from . import Image, ImageFile, PngImagePlugin, features +from ._deprecate import deprecate + +enable_jpeg2k = features.check_codec("jpg_2000") +if enable_jpeg2k: + from . import Jpeg2KImagePlugin + +MAGIC = b"icns" +HEADERSIZE = 8 + + +def nextheader(fobj: IO[bytes]) -> tuple[bytes, int]: + return struct.unpack(">4sI", fobj.read(HEADERSIZE)) + + +def read_32t( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + # The 128x128 icon seems to have an extra header for some reason. + (start, length) = start_length + fobj.seek(start) + sig = fobj.read(4) + if sig != b"\x00\x00\x00\x00": + msg = "Unknown signature, expecting 0x00000000" + raise SyntaxError(msg) + return read_32(fobj, (start + 4, length - 4), size) + + +def read_32( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + """ + Read a 32bit RGB icon resource. Seems to be either uncompressed or + an RLE packbits-like scheme. + """ + (start, length) = start_length + fobj.seek(start) + pixel_size = (size[0] * size[2], size[1] * size[2]) + sizesq = pixel_size[0] * pixel_size[1] + if length == sizesq * 3: + # uncompressed ("RGBRGBGB") + indata = fobj.read(length) + im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1) + else: + # decode image + im = Image.new("RGB", pixel_size, None) + for band_ix in range(3): + data = [] + bytesleft = sizesq + while bytesleft > 0: + byte = fobj.read(1) + if not byte: + break + byte_int = byte[0] + if byte_int & 0x80: + blocksize = byte_int - 125 + byte = fobj.read(1) + for i in range(blocksize): + data.append(byte) + else: + blocksize = byte_int + 1 + data.append(fobj.read(blocksize)) + bytesleft -= blocksize + if bytesleft <= 0: + break + if bytesleft != 0: + msg = f"Error reading channel [{repr(bytesleft)} left]" + raise SyntaxError(msg) + band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1) + im.im.putband(band.im, band_ix) + return {"RGB": im} + + +def read_mk( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + # Alpha masks seem to be uncompressed + start = start_length[0] + fobj.seek(start) + pixel_size = (size[0] * size[2], size[1] * size[2]) + sizesq = pixel_size[0] * pixel_size[1] + band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1) + return {"A": band} + + +def read_png_or_jpeg2000( + fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int] +) -> dict[str, Image.Image]: + (start, length) = start_length + fobj.seek(start) + sig = fobj.read(12) + + im: Image.Image + if sig.startswith(b"\x89PNG\x0d\x0a\x1a\x0a"): + fobj.seek(start) + im = PngImagePlugin.PngImageFile(fobj) + Image._decompression_bomb_check(im.size) + return {"RGBA": im} + elif ( + sig.startswith((b"\xff\x4f\xff\x51", b"\x0d\x0a\x87\x0a")) + or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" + ): + if not enable_jpeg2k: + msg = ( + "Unsupported icon subimage format (rebuild PIL " + "with JPEG 2000 support to fix this)" + ) + raise ValueError(msg) + # j2k, jpc or j2c + fobj.seek(start) + jp2kstream = fobj.read(length) + f = io.BytesIO(jp2kstream) + im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) + Image._decompression_bomb_check(im.size) + if im.mode != "RGBA": + im = im.convert("RGBA") + return {"RGBA": im} + else: + msg = "Unsupported icon subimage format" + raise ValueError(msg) + + +class IcnsFile: + SIZES = { + (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)], + (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)], + (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)], + (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)], + (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)], + (128, 128, 1): [ + (b"ic07", read_png_or_jpeg2000), + (b"it32", read_32t), + (b"t8mk", read_mk), + ], + (64, 64, 1): [(b"icp6", read_png_or_jpeg2000)], + (32, 32, 2): [(b"ic12", read_png_or_jpeg2000)], + (48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)], + (32, 32, 1): [ + (b"icp5", read_png_or_jpeg2000), + (b"il32", read_32), + (b"l8mk", read_mk), + ], + (16, 16, 2): [(b"ic11", read_png_or_jpeg2000)], + (16, 16, 1): [ + (b"icp4", read_png_or_jpeg2000), + (b"is32", read_32), + (b"s8mk", read_mk), + ], + } + + def __init__(self, fobj: IO[bytes]) -> None: + """ + fobj is a file-like object as an icns resource + """ + # signature : (start, length) + self.dct = {} + self.fobj = fobj + sig, filesize = nextheader(fobj) + if not _accept(sig): + msg = "not an icns file" + raise SyntaxError(msg) + i = HEADERSIZE + while i < filesize: + sig, blocksize = nextheader(fobj) + if blocksize <= 0: + msg = "invalid block header" + raise SyntaxError(msg) + i += HEADERSIZE + blocksize -= HEADERSIZE + self.dct[sig] = (i, blocksize) + fobj.seek(blocksize, io.SEEK_CUR) + i += blocksize + + def itersizes(self) -> list[tuple[int, int, int]]: + sizes = [] + for size, fmts in self.SIZES.items(): + for fmt, reader in fmts: + if fmt in self.dct: + sizes.append(size) + break + return sizes + + def bestsize(self) -> tuple[int, int, int]: + sizes = self.itersizes() + if not sizes: + msg = "No 32bit icon resources found" + raise SyntaxError(msg) + return max(sizes) + + def dataforsize(self, size: tuple[int, int, int]) -> dict[str, Image.Image]: + """ + Get an icon resource as {channel: array}. Note that + the arrays are bottom-up like windows bitmaps and will likely + need to be flipped or transposed in some way. + """ + dct = {} + for code, reader in self.SIZES[size]: + desc = self.dct.get(code) + if desc is not None: + dct.update(reader(self.fobj, desc, size)) + return dct + + def getimage( + self, size: tuple[int, int] | tuple[int, int, int] | None = None + ) -> Image.Image: + if size is None: + size = self.bestsize() + elif len(size) == 2: + size = (size[0], size[1], 1) + channels = self.dataforsize(size) + + im = channels.get("RGBA") + if im: + return im + + im = channels["RGB"].copy() + try: + im.putalpha(channels["A"]) + except KeyError: + pass + return im + + +## +# Image plugin for Mac OS icons. + + +class IcnsImageFile(ImageFile.ImageFile): + """ + PIL image support for Mac OS .icns files. + Chooses the best resolution, but will possibly load + a different size image if you mutate the size attribute + before calling 'load'. + + The info dictionary has a key 'sizes' that is a list + of sizes that the icns file has. + """ + + format = "ICNS" + format_description = "Mac OS icns resource" + + def _open(self) -> None: + self.icns = IcnsFile(self.fp) + self._mode = "RGBA" + self.info["sizes"] = self.icns.itersizes() + self.best_size = self.icns.bestsize() + self.size = ( + self.best_size[0] * self.best_size[2], + self.best_size[1] * self.best_size[2], + ) + + @property # type: ignore[override] + def size(self) -> tuple[int, int] | tuple[int, int, int]: + return self._size + + @size.setter + def size(self, value: tuple[int, int] | tuple[int, int, int]) -> None: + if len(value) == 3: + deprecate("Setting size to (width, height, scale)", 12, "load(scale)") + if value in self.info["sizes"]: + self._size = value # type: ignore[assignment] + return + else: + # Check that a matching size exists, + # or that there is a scale that would create a size that matches + for size in self.info["sizes"]: + simple_size = size[0] * size[2], size[1] * size[2] + scale = simple_size[0] // value[0] + if simple_size[1] / value[1] == scale: + self._size = value + return + msg = "This is not one of the allowed sizes of this image" + raise ValueError(msg) + + def load(self, scale: int | None = None) -> Image.core.PixelAccess | None: + if scale is not None or len(self.size) == 3: + if scale is None and len(self.size) == 3: + scale = self.size[2] + assert scale is not None + width, height = self.size[:2] + self.size = width * scale, height * scale + self.best_size = width, height, scale + + px = Image.Image.load(self) + if self._im is not None and self.im.size == self.size: + # Already loaded + return px + self.load_prepare() + # This is likely NOT the best way to do it, but whatever. + im = self.icns.getimage(self.best_size) + + # If this is a PNG or JPEG 2000, it won't be loaded yet + px = im.load() + + self.im = im.im + self._mode = im.mode + self.size = im.size + + return px + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + """ + Saves the image as a series of PNG files, + that are then combined into a .icns file. + """ + if hasattr(fp, "flush"): + fp.flush() + + sizes = { + b"ic07": 128, + b"ic08": 256, + b"ic09": 512, + b"ic10": 1024, + b"ic11": 32, + b"ic12": 64, + b"ic13": 256, + b"ic14": 512, + } + provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} + size_streams = {} + for size in set(sizes.values()): + image = ( + provided_images[size] + if size in provided_images + else im.resize((size, size)) + ) + + temp = io.BytesIO() + image.save(temp, "png") + size_streams[size] = temp.getvalue() + + entries = [] + for type, size in sizes.items(): + stream = size_streams[size] + entries.append((type, HEADERSIZE + len(stream), stream)) + + # Header + fp.write(MAGIC) + file_length = HEADERSIZE # Header + file_length += HEADERSIZE + 8 * len(entries) # TOC + file_length += sum(entry[1] for entry in entries) + fp.write(struct.pack(">i", file_length)) + + # TOC + fp.write(b"TOC ") + fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE)) + for entry in entries: + fp.write(entry[0]) + fp.write(struct.pack(">i", entry[1])) + + # Data + for entry in entries: + fp.write(entry[0]) + fp.write(struct.pack(">i", entry[1])) + fp.write(entry[2]) + + if hasattr(fp, "flush"): + fp.flush() + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(MAGIC) + + +Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept) +Image.register_extension(IcnsImageFile.format, ".icns") + +Image.register_save(IcnsImageFile.format, _save) +Image.register_mime(IcnsImageFile.format, "image/icns") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Syntax: python3 IcnsImagePlugin.py [file]") + sys.exit() + + with open(sys.argv[1], "rb") as fp: + imf = IcnsImageFile(fp) + for size in imf.info["sizes"]: + width, height, scale = imf.size = size + imf.save(f"out-{width}-{height}-{scale}.png") + with Image.open(sys.argv[1]) as im: + im.save("out.png") + if sys.platform == "windows": + os.startfile("out.png") diff --git a/venv/Lib/site-packages/PIL/IcoImagePlugin.py b/venv/Lib/site-packages/PIL/IcoImagePlugin.py new file mode 100644 index 00000000..bd35ac89 --- /dev/null +++ b/venv/Lib/site-packages/PIL/IcoImagePlugin.py @@ -0,0 +1,381 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Windows Icon support for PIL +# +# History: +# 96-05-27 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# + +# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis +# . +# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki +# +# Icon format references: +# * https://en.wikipedia.org/wiki/ICO_(file_format) +# * https://msdn.microsoft.com/en-us/library/ms997538.aspx +from __future__ import annotations + +import warnings +from io import BytesIO +from math import ceil, log +from typing import IO, NamedTuple + +from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o16le as o16 +from ._binary import o32le as o32 + +# +# -------------------------------------------------------------------- + +_MAGIC = b"\0\0\1\0" + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + fp.write(_MAGIC) # (2+2) + bmp = im.encoderinfo.get("bitmap_format") == "bmp" + sizes = im.encoderinfo.get( + "sizes", + [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)], + ) + frames = [] + provided_ims = [im] + im.encoderinfo.get("append_images", []) + width, height = im.size + for size in sorted(set(sizes)): + if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256: + continue + + for provided_im in provided_ims: + if provided_im.size != size: + continue + frames.append(provided_im) + if bmp: + bits = BmpImagePlugin.SAVE[provided_im.mode][1] + bits_used = [bits] + for other_im in provided_ims: + if other_im.size != size: + continue + bits = BmpImagePlugin.SAVE[other_im.mode][1] + if bits not in bits_used: + # Another image has been supplied for this size + # with a different bit depth + frames.append(other_im) + bits_used.append(bits) + break + else: + # TODO: invent a more convenient method for proportional scalings + frame = provided_im.copy() + frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None) + frames.append(frame) + fp.write(o16(len(frames))) # idCount(2) + offset = fp.tell() + len(frames) * 16 + for frame in frames: + width, height = frame.size + # 0 means 256 + fp.write(o8(width if width < 256 else 0)) # bWidth(1) + fp.write(o8(height if height < 256 else 0)) # bHeight(1) + + bits, colors = BmpImagePlugin.SAVE[frame.mode][1:] if bmp else (32, 0) + fp.write(o8(colors)) # bColorCount(1) + fp.write(b"\0") # bReserved(1) + fp.write(b"\0\0") # wPlanes(2) + fp.write(o16(bits)) # wBitCount(2) + + image_io = BytesIO() + if bmp: + frame.save(image_io, "dib") + + if bits != 32: + and_mask = Image.new("1", size) + ImageFile._save( + and_mask, + image_io, + [ImageFile._Tile("raw", (0, 0) + size, 0, ("1", 0, -1))], + ) + else: + frame.save(image_io, "png") + image_io.seek(0) + image_bytes = image_io.read() + if bmp: + image_bytes = image_bytes[:8] + o32(height * 2) + image_bytes[12:] + bytes_len = len(image_bytes) + fp.write(o32(bytes_len)) # dwBytesInRes(4) + fp.write(o32(offset)) # dwImageOffset(4) + current = fp.tell() + fp.seek(offset) + fp.write(image_bytes) + offset = offset + bytes_len + fp.seek(current) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(_MAGIC) + + +class IconHeader(NamedTuple): + width: int + height: int + nb_color: int + reserved: int + planes: int + bpp: int + size: int + offset: int + dim: tuple[int, int] + square: int + color_depth: int + + +class IcoFile: + def __init__(self, buf: IO[bytes]) -> None: + """ + Parse image from file-like object containing ico file data + """ + + # check magic + s = buf.read(6) + if not _accept(s): + msg = "not an ICO file" + raise SyntaxError(msg) + + self.buf = buf + self.entry = [] + + # Number of items in file + self.nb_items = i16(s, 4) + + # Get headers for each item + for i in range(self.nb_items): + s = buf.read(16) + + # See Wikipedia + width = s[0] or 256 + height = s[1] or 256 + + # No. of colors in image (0 if >=8bpp) + nb_color = s[2] + bpp = i16(s, 6) + icon_header = IconHeader( + width=width, + height=height, + nb_color=nb_color, + reserved=s[3], + planes=i16(s, 4), + bpp=i16(s, 6), + size=i32(s, 8), + offset=i32(s, 12), + dim=(width, height), + square=width * height, + # See Wikipedia notes about color depth. + # We need this just to differ images with equal sizes + color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256, + ) + + self.entry.append(icon_header) + + self.entry = sorted(self.entry, key=lambda x: x.color_depth) + # ICO images are usually squares + self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True) + + def sizes(self) -> set[tuple[int, int]]: + """ + Get a set of all available icon sizes and color depths. + """ + return {(h.width, h.height) for h in self.entry} + + def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int: + for i, h in enumerate(self.entry): + if size == h.dim and (bpp is False or bpp == h.color_depth): + return i + return 0 + + def getimage(self, size: tuple[int, int], bpp: int | bool = False) -> Image.Image: + """ + Get an image from the icon + """ + return self.frame(self.getentryindex(size, bpp)) + + def frame(self, idx: int) -> Image.Image: + """ + Get an image from frame idx + """ + + header = self.entry[idx] + + self.buf.seek(header.offset) + data = self.buf.read(8) + self.buf.seek(header.offset) + + im: Image.Image + if data[:8] == PngImagePlugin._MAGIC: + # png frame + im = PngImagePlugin.PngImageFile(self.buf) + Image._decompression_bomb_check(im.size) + else: + # XOR + AND mask bmp frame + im = BmpImagePlugin.DibImageFile(self.buf) + Image._decompression_bomb_check(im.size) + + # change tile dimension to only encompass XOR image + im._size = (im.size[0], int(im.size[1] / 2)) + d, e, o, a = im.tile[0] + im.tile[0] = ImageFile._Tile(d, (0, 0) + im.size, o, a) + + # figure out where AND mask image starts + if header.bpp == 32: + # 32-bit color depth icon image allows semitransparent areas + # PIL's DIB format ignores transparency bits, recover them. + # The DIB is packed in BGRX byte order where X is the alpha + # channel. + + # Back up to start of bmp data + self.buf.seek(o) + # extract every 4th byte (eg. 3,7,11,15,...) + alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4] + + # convert to an 8bpp grayscale image + try: + mask = Image.frombuffer( + "L", # 8bpp + im.size, # (w, h) + alpha_bytes, # source chars + "raw", # raw decoder + ("L", 0, -1), # 8bpp inverted, unpadded, reversed + ) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + mask = None + else: + raise + else: + # get AND image from end of bitmap + w = im.size[0] + if (w % 32) > 0: + # bitmap row data is aligned to word boundaries + w += 32 - (im.size[0] % 32) + + # the total mask data is + # padded row size * height / bits per char + + total_bytes = int((w * im.size[1]) / 8) + and_mask_offset = header.offset + header.size - total_bytes + + self.buf.seek(and_mask_offset) + mask_data = self.buf.read(total_bytes) + + # convert raw data to image + try: + mask = Image.frombuffer( + "1", # 1 bpp + im.size, # (w, h) + mask_data, # source chars + "raw", # raw decoder + ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed + ) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + mask = None + else: + raise + + # now we have two images, im is XOR image and mask is AND image + + # apply mask image as alpha channel + if mask: + im = im.convert("RGBA") + im.putalpha(mask) + + return im + + +## +# Image plugin for Windows Icon files. + + +class IcoImageFile(ImageFile.ImageFile): + """ + PIL read-only image support for Microsoft Windows .ico files. + + By default the largest resolution image in the file will be loaded. This + can be changed by altering the 'size' attribute before calling 'load'. + + The info dictionary has a key 'sizes' that is a list of the sizes available + in the icon file. + + Handles classic, XP and Vista icon formats. + + When saving, PNG compression is used. Support for this was only added in + Windows Vista. If you are unable to view the icon in Windows, convert the + image to "RGBA" mode before saving. + + This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis + . + https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki + """ + + format = "ICO" + format_description = "Windows Icon" + + def _open(self) -> None: + self.ico = IcoFile(self.fp) + self.info["sizes"] = self.ico.sizes() + self.size = self.ico.entry[0].dim + self.load() + + @property + def size(self) -> tuple[int, int]: + return self._size + + @size.setter + def size(self, value: tuple[int, int]) -> None: + if value not in self.info["sizes"]: + msg = "This is not one of the allowed sizes of this image" + raise ValueError(msg) + self._size = value + + def load(self) -> Image.core.PixelAccess | None: + if self._im is not None and self.im.size == self.size: + # Already loaded + return Image.Image.load(self) + im = self.ico.getimage(self.size) + # if tile is PNG, it won't really be loaded yet + im.load() + self.im = im.im + self._mode = im.mode + if im.palette: + self.palette = im.palette + if im.size != self.size: + warnings.warn("Image was not the expected size") + + index = self.ico.getentryindex(self.size) + sizes = list(self.info["sizes"]) + sizes[index] = im.size + self.info["sizes"] = set(sizes) + + self.size = im.size + return Image.Image.load(self) + + def load_seek(self, pos: int) -> None: + # Flag the ImageFile.Parser so that it + # just does all the decode at the end. + pass + + +# +# -------------------------------------------------------------------- + + +Image.register_open(IcoImageFile.format, IcoImageFile, _accept) +Image.register_save(IcoImageFile.format, _save) +Image.register_extension(IcoImageFile.format, ".ico") + +Image.register_mime(IcoImageFile.format, "image/x-icon") diff --git a/venv/Lib/site-packages/PIL/ImImagePlugin.py b/venv/Lib/site-packages/PIL/ImImagePlugin.py new file mode 100644 index 00000000..71b99967 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImImagePlugin.py @@ -0,0 +1,389 @@ +# +# The Python Imaging Library. +# $Id$ +# +# IFUNC IM file handling for PIL +# +# history: +# 1995-09-01 fl Created. +# 1997-01-03 fl Save palette images +# 1997-01-08 fl Added sequence support +# 1997-01-23 fl Added P and RGB save support +# 1997-05-31 fl Read floating point images +# 1997-06-22 fl Save floating point images +# 1997-08-27 fl Read and save 1-bit images +# 1998-06-25 fl Added support for RGB+LUT images +# 1998-07-02 fl Added support for YCC images +# 1998-07-15 fl Renamed offset attribute to avoid name clash +# 1998-12-29 fl Added I;16 support +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7) +# 2003-09-26 fl Added LA/PA support +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2001 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +import re +from typing import IO, Any + +from . import Image, ImageFile, ImagePalette +from ._util import DeferredError + +# -------------------------------------------------------------------- +# Standard tags + +COMMENT = "Comment" +DATE = "Date" +EQUIPMENT = "Digitalization equipment" +FRAMES = "File size (no of images)" +LUT = "Lut" +NAME = "Name" +SCALE = "Scale (x,y)" +SIZE = "Image size (x*y)" +MODE = "Image type" + +TAGS = { + COMMENT: 0, + DATE: 0, + EQUIPMENT: 0, + FRAMES: 0, + LUT: 0, + NAME: 0, + SCALE: 0, + SIZE: 0, + MODE: 0, +} + +OPEN = { + # ifunc93/p3cfunc formats + "0 1 image": ("1", "1"), + "L 1 image": ("1", "1"), + "Greyscale image": ("L", "L"), + "Grayscale image": ("L", "L"), + "RGB image": ("RGB", "RGB;L"), + "RLB image": ("RGB", "RLB"), + "RYB image": ("RGB", "RLB"), + "B1 image": ("1", "1"), + "B2 image": ("P", "P;2"), + "B4 image": ("P", "P;4"), + "X 24 image": ("RGB", "RGB"), + "L 32 S image": ("I", "I;32"), + "L 32 F image": ("F", "F;32"), + # old p3cfunc formats + "RGB3 image": ("RGB", "RGB;T"), + "RYB3 image": ("RGB", "RYB;T"), + # extensions + "LA image": ("LA", "LA;L"), + "PA image": ("LA", "PA;L"), + "RGBA image": ("RGBA", "RGBA;L"), + "RGBX image": ("RGB", "RGBX;L"), + "CMYK image": ("CMYK", "CMYK;L"), + "YCC image": ("YCbCr", "YCbCr;L"), +} + +# ifunc95 extensions +for i in ["8", "8S", "16", "16S", "32", "32F"]: + OPEN[f"L {i} image"] = ("F", f"F;{i}") + OPEN[f"L*{i} image"] = ("F", f"F;{i}") +for i in ["16", "16L", "16B"]: + OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}") + OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}") +for i in ["32S"]: + OPEN[f"L {i} image"] = ("I", f"I;{i}") + OPEN[f"L*{i} image"] = ("I", f"I;{i}") +for j in range(2, 33): + OPEN[f"L*{j} image"] = ("F", f"F;{j}") + + +# -------------------------------------------------------------------- +# Read IM directory + +split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") + + +def number(s: Any) -> float: + try: + return int(s) + except ValueError: + return float(s) + + +## +# Image plugin for the IFUNC IM file format. + + +class ImImageFile(ImageFile.ImageFile): + format = "IM" + format_description = "IFUNC Image Memory" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # Quick rejection: if there's not an LF among the first + # 100 bytes, this is (probably) not a text header. + + if b"\n" not in self.fp.read(100): + msg = "not an IM file" + raise SyntaxError(msg) + self.fp.seek(0) + + n = 0 + + # Default values + self.info[MODE] = "L" + self.info[SIZE] = (512, 512) + self.info[FRAMES] = 1 + + self.rawmode = "L" + + while True: + s = self.fp.read(1) + + # Some versions of IFUNC uses \n\r instead of \r\n... + if s == b"\r": + continue + + if not s or s == b"\0" or s == b"\x1a": + break + + # FIXME: this may read whole file if not a text file + s = s + self.fp.readline() + + if len(s) > 100: + msg = "not an IM file" + raise SyntaxError(msg) + + if s.endswith(b"\r\n"): + s = s[:-2] + elif s.endswith(b"\n"): + s = s[:-1] + + try: + m = split.match(s) + except re.error as e: + msg = "not an IM file" + raise SyntaxError(msg) from e + + if m: + k, v = m.group(1, 2) + + # Don't know if this is the correct encoding, + # but a decent guess (I guess) + k = k.decode("latin-1", "replace") + v = v.decode("latin-1", "replace") + + # Convert value as appropriate + if k in [FRAMES, SCALE, SIZE]: + v = v.replace("*", ",") + v = tuple(map(number, v.split(","))) + if len(v) == 1: + v = v[0] + elif k == MODE and v in OPEN: + v, self.rawmode = OPEN[v] + + # Add to dictionary. Note that COMMENT tags are + # combined into a list of strings. + if k == COMMENT: + if k in self.info: + self.info[k].append(v) + else: + self.info[k] = [v] + else: + self.info[k] = v + + if k in TAGS: + n += 1 + + else: + msg = f"Syntax error in IM header: {s.decode('ascii', 'replace')}" + raise SyntaxError(msg) + + if not n: + msg = "Not an IM file" + raise SyntaxError(msg) + + # Basic attributes + self._size = self.info[SIZE] + self._mode = self.info[MODE] + + # Skip forward to start of image data + while s and not s.startswith(b"\x1a"): + s = self.fp.read(1) + if not s: + msg = "File truncated" + raise SyntaxError(msg) + + if LUT in self.info: + # convert lookup table to palette or lut attribute + palette = self.fp.read(768) + greyscale = 1 # greyscale palette + linear = 1 # linear greyscale palette + for i in range(256): + if palette[i] == palette[i + 256] == palette[i + 512]: + if palette[i] != i: + linear = 0 + else: + greyscale = 0 + if self.mode in ["L", "LA", "P", "PA"]: + if greyscale: + if not linear: + self.lut = list(palette[:256]) + else: + if self.mode in ["L", "P"]: + self._mode = self.rawmode = "P" + elif self.mode in ["LA", "PA"]: + self._mode = "PA" + self.rawmode = "PA;L" + self.palette = ImagePalette.raw("RGB;L", palette) + elif self.mode == "RGB": + if not greyscale or not linear: + self.lut = list(palette) + + self.frame = 0 + + self.__offset = offs = self.fp.tell() + + self._fp = self.fp # FIXME: hack + + if self.rawmode.startswith("F;"): + # ifunc95 formats + try: + # use bit decoder (if necessary) + bits = int(self.rawmode[2:]) + if bits not in [8, 16, 32]: + self.tile = [ + ImageFile._Tile( + "bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1) + ) + ] + return + except ValueError: + pass + + if self.rawmode in ["RGB;T", "RYB;T"]: + # Old LabEye/3PC files. Would be very surprised if anyone + # ever stumbled upon such a file ;-) + size = self.size[0] * self.size[1] + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)), + ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)), + ImageFile._Tile( + "raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1) + ), + ] + else: + # LabEye/IFUNC files + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1)) + ] + + @property + def n_frames(self) -> int: + return self.info[FRAMES] + + @property + def is_animated(self) -> bool: + return self.info[FRAMES] > 1 + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + + self.frame = frame + + if self.mode == "1": + bits = 1 + else: + bits = 8 * len(self.mode) + + size = ((self.size[0] * bits + 7) // 8) * self.size[1] + offs = self.__offset + frame * size + + self.fp = self._fp + + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1)) + ] + + def tell(self) -> int: + return self.frame + + +# +# -------------------------------------------------------------------- +# Save IM files + + +SAVE = { + # mode: (im type, raw mode) + "1": ("0 1", "1"), + "L": ("Greyscale", "L"), + "LA": ("LA", "LA;L"), + "P": ("Greyscale", "P"), + "PA": ("LA", "PA;L"), + "I": ("L 32S", "I;32S"), + "I;16": ("L 16", "I;16"), + "I;16L": ("L 16L", "I;16L"), + "I;16B": ("L 16B", "I;16B"), + "F": ("L 32F", "F;32F"), + "RGB": ("RGB", "RGB;L"), + "RGBA": ("RGBA", "RGBA;L"), + "RGBX": ("RGBX", "RGBX;L"), + "CMYK": ("CMYK", "CMYK;L"), + "YCbCr": ("YCC", "YCbCr;L"), +} + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + try: + image_type, rawmode = SAVE[im.mode] + except KeyError as e: + msg = f"Cannot save {im.mode} images as IM" + raise ValueError(msg) from e + + frames = im.encoderinfo.get("frames", 1) + + fp.write(f"Image type: {image_type} image\r\n".encode("ascii")) + if filename: + # Each line must be 100 characters or less, + # or: SyntaxError("not an IM file") + # 8 characters are used for "Name: " and "\r\n" + # Keep just the filename, ditch the potentially overlong path + if isinstance(filename, bytes): + filename = filename.decode("ascii") + name, ext = os.path.splitext(os.path.basename(filename)) + name = "".join([name[: 92 - len(ext)], ext]) + + fp.write(f"Name: {name}\r\n".encode("ascii")) + fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii")) + fp.write(f"File size (no of images): {frames}\r\n".encode("ascii")) + if im.mode in ["P", "PA"]: + fp.write(b"Lut: 1\r\n") + fp.write(b"\000" * (511 - fp.tell()) + b"\032") + if im.mode in ["P", "PA"]: + im_palette = im.im.getpalette("RGB", "RGB;L") + colors = len(im_palette) // 3 + palette = b"" + for i in range(3): + palette += im_palette[colors * i : colors * (i + 1)] + palette += b"\x00" * (256 - colors) + fp.write(palette) # 768 bytes + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))] + ) + + +# +# -------------------------------------------------------------------- +# Registry + + +Image.register_open(ImImageFile.format, ImImageFile) +Image.register_save(ImImageFile.format, _save) + +Image.register_extension(ImImageFile.format, ".im") diff --git a/venv/Lib/site-packages/PIL/Image.py b/venv/Lib/site-packages/PIL/Image.py new file mode 100644 index 00000000..d209405c --- /dev/null +++ b/venv/Lib/site-packages/PIL/Image.py @@ -0,0 +1,4245 @@ +# +# The Python Imaging Library. +# $Id$ +# +# the Image class wrapper +# +# partial release history: +# 1995-09-09 fl Created +# 1996-03-11 fl PIL release 0.0 (proof of concept) +# 1996-04-30 fl PIL release 0.1b1 +# 1999-07-28 fl PIL release 1.0 final +# 2000-06-07 fl PIL release 1.1 +# 2000-10-20 fl PIL release 1.1.1 +# 2001-05-07 fl PIL release 1.1.2 +# 2002-03-15 fl PIL release 1.1.3 +# 2003-05-10 fl PIL release 1.1.4 +# 2005-03-28 fl PIL release 1.1.5 +# 2006-12-02 fl PIL release 1.1.6 +# 2009-11-15 fl PIL release 1.1.7 +# +# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved. +# Copyright (c) 1995-2009 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +from __future__ import annotations + +import abc +import atexit +import builtins +import io +import logging +import math +import os +import re +import struct +import sys +import tempfile +import warnings +from collections.abc import Callable, Iterator, MutableMapping, Sequence +from enum import IntEnum +from types import ModuleType +from typing import IO, Any, Literal, Protocol, cast + +# VERSION was removed in Pillow 6.0.0. +# PILLOW_VERSION was removed in Pillow 9.0.0. +# Use __version__ instead. +from . import ( + ExifTags, + ImageMode, + TiffTags, + UnidentifiedImageError, + __version__, + _plugins, +) +from ._binary import i32le, o32be, o32le +from ._deprecate import deprecate +from ._util import DeferredError, is_path + +ElementTree: ModuleType | None +try: + from defusedxml import ElementTree +except ImportError: + ElementTree = None + +logger = logging.getLogger(__name__) + + +class DecompressionBombWarning(RuntimeWarning): + pass + + +class DecompressionBombError(Exception): + pass + + +WARN_POSSIBLE_FORMATS: bool = False + +# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image +MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3) + + +try: + # If the _imaging C module is not present, Pillow will not load. + # Note that other modules should not refer to _imaging directly; + # import Image and use the Image.core variable instead. + # Also note that Image.core is not a publicly documented interface, + # and should be considered private and subject to change. + from . import _imaging as core + + if __version__ != getattr(core, "PILLOW_VERSION", None): + msg = ( + "The _imaging extension was built for another version of Pillow or PIL:\n" + f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n" + f"Pillow version: {__version__}" + ) + raise ImportError(msg) + +except ImportError as v: + core = DeferredError.new(ImportError("The _imaging C module is not installed.")) + # Explanations for ways that we know we might have an import error + if str(v).startswith("Module use of python"): + # The _imaging C module is present, but not compiled for + # the right version (windows only). Print a warning, if + # possible. + warnings.warn( + "The _imaging extension was built for another version of Python.", + RuntimeWarning, + ) + elif str(v).startswith("The _imaging extension"): + warnings.warn(str(v), RuntimeWarning) + # Fail here anyway. Don't let people run with a mostly broken Pillow. + # see docs/porting.rst + raise + + +def isImageType(t: Any) -> TypeGuard[Image]: + """ + Checks if an object is an image object. + + .. warning:: + + This function is for internal use only. + + :param t: object to check if it's an image + :returns: True if the object is an image + """ + deprecate("Image.isImageType(im)", 12, "isinstance(im, Image.Image)") + return hasattr(t, "im") + + +# +# Constants + + +# transpose +class Transpose(IntEnum): + FLIP_LEFT_RIGHT = 0 + FLIP_TOP_BOTTOM = 1 + ROTATE_90 = 2 + ROTATE_180 = 3 + ROTATE_270 = 4 + TRANSPOSE = 5 + TRANSVERSE = 6 + + +# transforms (also defined in Imaging.h) +class Transform(IntEnum): + AFFINE = 0 + EXTENT = 1 + PERSPECTIVE = 2 + QUAD = 3 + MESH = 4 + + +# resampling filters (also defined in Imaging.h) +class Resampling(IntEnum): + NEAREST = 0 + BOX = 4 + BILINEAR = 2 + HAMMING = 5 + BICUBIC = 3 + LANCZOS = 1 + + +_filters_support = { + Resampling.BOX: 0.5, + Resampling.BILINEAR: 1.0, + Resampling.HAMMING: 1.0, + Resampling.BICUBIC: 2.0, + Resampling.LANCZOS: 3.0, +} + + +# dithers +class Dither(IntEnum): + NONE = 0 + ORDERED = 1 # Not yet implemented + RASTERIZE = 2 # Not yet implemented + FLOYDSTEINBERG = 3 # default + + +# palettes/quantizers +class Palette(IntEnum): + WEB = 0 + ADAPTIVE = 1 + + +class Quantize(IntEnum): + MEDIANCUT = 0 + MAXCOVERAGE = 1 + FASTOCTREE = 2 + LIBIMAGEQUANT = 3 + + +module = sys.modules[__name__] +for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize): + for item in enum: + setattr(module, item.name, item.value) + + +if hasattr(core, "DEFAULT_STRATEGY"): + DEFAULT_STRATEGY = core.DEFAULT_STRATEGY + FILTERED = core.FILTERED + HUFFMAN_ONLY = core.HUFFMAN_ONLY + RLE = core.RLE + FIXED = core.FIXED + + +# -------------------------------------------------------------------- +# Registries + +TYPE_CHECKING = False +if TYPE_CHECKING: + import mmap + from xml.etree.ElementTree import Element + + from IPython.lib.pretty import PrettyPrinter + + from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin + from ._typing import CapsuleType, NumpyArray, StrOrBytesPath, TypeGuard +ID: list[str] = [] +OPEN: dict[ + str, + tuple[ + Callable[[IO[bytes], str | bytes], ImageFile.ImageFile], + Callable[[bytes], bool | str] | None, + ], +] = {} +MIME: dict[str, str] = {} +SAVE: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {} +SAVE_ALL: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {} +EXTENSION: dict[str, str] = {} +DECODERS: dict[str, type[ImageFile.PyDecoder]] = {} +ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {} + +# -------------------------------------------------------------------- +# Modes + +_ENDIAN = "<" if sys.byteorder == "little" else ">" + + +def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]: + m = ImageMode.getmode(im.mode) + shape: tuple[int, ...] = (im.height, im.width) + extra = len(m.bands) + if extra != 1: + shape += (extra,) + return shape, m.typestr + + +MODES = [ + "1", + "CMYK", + "F", + "HSV", + "I", + "I;16", + "I;16B", + "I;16L", + "I;16N", + "L", + "LA", + "La", + "LAB", + "P", + "PA", + "RGB", + "RGBA", + "RGBa", + "RGBX", + "YCbCr", +] + +# raw modes that may be memory mapped. NOTE: if you change this, you +# may have to modify the stride calculation in map.c too! +_MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16L", "I;16B") + + +def getmodebase(mode: str) -> str: + """ + Gets the "base" mode for given mode. This function returns "L" for + images that contain grayscale data, and "RGB" for images that + contain color data. + + :param mode: Input mode. + :returns: "L" or "RGB". + :exception KeyError: If the input mode was not a standard mode. + """ + return ImageMode.getmode(mode).basemode + + +def getmodetype(mode: str) -> str: + """ + Gets the storage type mode. Given a mode, this function returns a + single-layer mode suitable for storing individual bands. + + :param mode: Input mode. + :returns: "L", "I", or "F". + :exception KeyError: If the input mode was not a standard mode. + """ + return ImageMode.getmode(mode).basetype + + +def getmodebandnames(mode: str) -> tuple[str, ...]: + """ + Gets a list of individual band names. Given a mode, this function returns + a tuple containing the names of individual bands (use + :py:method:`~PIL.Image.getmodetype` to get the mode used to store each + individual band. + + :param mode: Input mode. + :returns: A tuple containing band names. The length of the tuple + gives the number of bands in an image of the given mode. + :exception KeyError: If the input mode was not a standard mode. + """ + return ImageMode.getmode(mode).bands + + +def getmodebands(mode: str) -> int: + """ + Gets the number of individual bands for this mode. + + :param mode: Input mode. + :returns: The number of bands in this mode. + :exception KeyError: If the input mode was not a standard mode. + """ + return len(ImageMode.getmode(mode).bands) + + +# -------------------------------------------------------------------- +# Helpers + +_initialized = 0 + + +def preinit() -> None: + """ + Explicitly loads BMP, GIF, JPEG, PPM and PPM file format drivers. + + It is called when opening or saving images. + """ + + global _initialized + if _initialized >= 1: + return + + try: + from . import BmpImagePlugin + + assert BmpImagePlugin + except ImportError: + pass + try: + from . import GifImagePlugin + + assert GifImagePlugin + except ImportError: + pass + try: + from . import JpegImagePlugin + + assert JpegImagePlugin + except ImportError: + pass + try: + from . import PpmImagePlugin + + assert PpmImagePlugin + except ImportError: + pass + try: + from . import PngImagePlugin + + assert PngImagePlugin + except ImportError: + pass + + _initialized = 1 + + +def init() -> bool: + """ + Explicitly initializes the Python Imaging Library. This function + loads all available file format drivers. + + It is called when opening or saving images if :py:meth:`~preinit()` is + insufficient, and by :py:meth:`~PIL.features.pilinfo`. + """ + + global _initialized + if _initialized >= 2: + return False + + parent_name = __name__.rpartition(".")[0] + for plugin in _plugins: + try: + logger.debug("Importing %s", plugin) + __import__(f"{parent_name}.{plugin}", globals(), locals(), []) + except ImportError as e: + logger.debug("Image: failed to import %s: %s", plugin, e) + + if OPEN or SAVE: + _initialized = 2 + return True + return False + + +# -------------------------------------------------------------------- +# Codec factories (used by tobytes/frombytes and ImageFile.load) + + +def _getdecoder( + mode: str, decoder_name: str, args: Any, extra: tuple[Any, ...] = () +) -> core.ImagingDecoder | ImageFile.PyDecoder: + # tweak arguments + if args is None: + args = () + elif not isinstance(args, tuple): + args = (args,) + + try: + decoder = DECODERS[decoder_name] + except KeyError: + pass + else: + return decoder(mode, *args + extra) + + try: + # get decoder + decoder = getattr(core, f"{decoder_name}_decoder") + except AttributeError as e: + msg = f"decoder {decoder_name} not available" + raise OSError(msg) from e + return decoder(mode, *args + extra) + + +def _getencoder( + mode: str, encoder_name: str, args: Any, extra: tuple[Any, ...] = () +) -> core.ImagingEncoder | ImageFile.PyEncoder: + # tweak arguments + if args is None: + args = () + elif not isinstance(args, tuple): + args = (args,) + + try: + encoder = ENCODERS[encoder_name] + except KeyError: + pass + else: + return encoder(mode, *args + extra) + + try: + # get encoder + encoder = getattr(core, f"{encoder_name}_encoder") + except AttributeError as e: + msg = f"encoder {encoder_name} not available" + raise OSError(msg) from e + return encoder(mode, *args + extra) + + +# -------------------------------------------------------------------- +# Simple expression analyzer + + +class ImagePointTransform: + """ + Used with :py:meth:`~PIL.Image.Image.point` for single band images with more than + 8 bits, this represents an affine transformation, where the value is multiplied by + ``scale`` and ``offset`` is added. + """ + + def __init__(self, scale: float, offset: float) -> None: + self.scale = scale + self.offset = offset + + def __neg__(self) -> ImagePointTransform: + return ImagePointTransform(-self.scale, -self.offset) + + def __add__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): + return ImagePointTransform( + self.scale + other.scale, self.offset + other.offset + ) + return ImagePointTransform(self.scale, self.offset + other) + + __radd__ = __add__ + + def __sub__(self, other: ImagePointTransform | float) -> ImagePointTransform: + return self + -other + + def __rsub__(self, other: ImagePointTransform | float) -> ImagePointTransform: + return other + -self + + def __mul__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): + return NotImplemented + return ImagePointTransform(self.scale * other, self.offset * other) + + __rmul__ = __mul__ + + def __truediv__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): + return NotImplemented + return ImagePointTransform(self.scale / other, self.offset / other) + + +def _getscaleoffset( + expr: Callable[[ImagePointTransform], ImagePointTransform | float], +) -> tuple[float, float]: + a = expr(ImagePointTransform(1, 0)) + return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a) + + +# -------------------------------------------------------------------- +# Implementation wrapper + + +class SupportsGetData(Protocol): + def getdata( + self, + ) -> tuple[Transform, Sequence[int]]: ... + + +class Image: + """ + This class represents an image object. To create + :py:class:`~PIL.Image.Image` objects, use the appropriate factory + functions. There's hardly ever any reason to call the Image constructor + directly. + + * :py:func:`~PIL.Image.open` + * :py:func:`~PIL.Image.new` + * :py:func:`~PIL.Image.frombytes` + """ + + format: str | None = None + format_description: str | None = None + _close_exclusive_fp_after_loading = True + + def __init__(self) -> None: + # FIXME: take "new" parameters / other image? + self._im: core.ImagingCore | DeferredError | None = None + self._mode = "" + self._size = (0, 0) + self.palette: ImagePalette.ImagePalette | None = None + self.info: dict[str | tuple[int, int], Any] = {} + self.readonly = 0 + self._exif: Exif | None = None + + @property + def im(self) -> core.ImagingCore: + if isinstance(self._im, DeferredError): + raise self._im.ex + assert self._im is not None + return self._im + + @im.setter + def im(self, im: core.ImagingCore) -> None: + self._im = im + + @property + def width(self) -> int: + return self.size[0] + + @property + def height(self) -> int: + return self.size[1] + + @property + def size(self) -> tuple[int, int]: + return self._size + + @property + def mode(self) -> str: + return self._mode + + @property + def readonly(self) -> int: + return (self._im and self._im.readonly) or self._readonly + + @readonly.setter + def readonly(self, readonly: int) -> None: + self._readonly = readonly + + def _new(self, im: core.ImagingCore) -> Image: + new = Image() + new.im = im + new._mode = im.mode + new._size = im.size + if im.mode in ("P", "PA"): + if self.palette: + new.palette = self.palette.copy() + else: + from . import ImagePalette + + new.palette = ImagePalette.ImagePalette() + new.info = self.info.copy() + return new + + # Context manager support + def __enter__(self): + return self + + def __exit__(self, *args): + from . import ImageFile + + if isinstance(self, ImageFile.ImageFile): + if getattr(self, "_exclusive_fp", False): + self._close_fp() + self.fp = None + + def close(self) -> None: + """ + This operation will destroy the image core and release its memory. + The image data will be unusable afterward. + + This function is required to close images that have multiple frames or + have not had their file read and closed by the + :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for + more information. + """ + if getattr(self, "map", None): + if sys.platform == "win32" and hasattr(sys, "pypy_version_info"): + self.map.close() + self.map: mmap.mmap | None = None + + # Instead of simply setting to None, we're setting up a + # deferred error that will better explain that the core image + # object is gone. + self._im = DeferredError(ValueError("Operation on closed image")) + + def _copy(self) -> None: + self.load() + self.im = self.im.copy() + self.readonly = 0 + + def _ensure_mutable(self) -> None: + if self.readonly: + self._copy() + else: + self.load() + + def _dump( + self, file: str | None = None, format: str | None = None, **options: Any + ) -> str: + suffix = "" + if format: + suffix = f".{format}" + + if not file: + f, filename = tempfile.mkstemp(suffix) + os.close(f) + else: + filename = file + if not filename.endswith(suffix): + filename = filename + suffix + + self.load() + + if not format or format == "PPM": + self.im.save_ppm(filename) + else: + self.save(filename, format, **options) + + return filename + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return False + assert isinstance(other, Image) + return ( + self.mode == other.mode + and self.size == other.size + and self.info == other.info + and self.getpalette() == other.getpalette() + and self.tobytes() == other.tobytes() + ) + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__module__}.{self.__class__.__name__} " + f"image mode={self.mode} size={self.size[0]}x{self.size[1]} " + f"at 0x{id(self):X}>" + ) + + def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None: + """IPython plain text display support""" + + # Same as __repr__ but without unpredictable id(self), + # to keep Jupyter notebook `text/plain` output stable. + p.text( + f"<{self.__class__.__module__}.{self.__class__.__name__} " + f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>" + ) + + def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None: + """Helper function for iPython display hook. + + :param image_format: Image format. + :returns: image as bytes, saved into the given format. + """ + b = io.BytesIO() + try: + self.save(b, image_format, **kwargs) + except Exception: + return None + return b.getvalue() + + def _repr_png_(self) -> bytes | None: + """iPython display hook support for PNG format. + + :returns: PNG version of the image as bytes + """ + return self._repr_image("PNG", compress_level=1) + + def _repr_jpeg_(self) -> bytes | None: + """iPython display hook support for JPEG format. + + :returns: JPEG version of the image as bytes + """ + return self._repr_image("JPEG") + + @property + def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]: + # numpy array interface support + new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3} + if self.mode == "1": + # Binary images need to be extended from bits to bytes + # See: https://github.com/python-pillow/Pillow/issues/350 + new["data"] = self.tobytes("raw", "L") + else: + new["data"] = self.tobytes() + new["shape"], new["typestr"] = _conv_type_shape(self) + return new + + def __arrow_c_schema__(self) -> object: + self.load() + return self.im.__arrow_c_schema__() + + def __arrow_c_array__( + self, requested_schema: object | None = None + ) -> tuple[object, object]: + self.load() + return (self.im.__arrow_c_schema__(), self.im.__arrow_c_array__()) + + def __getstate__(self) -> list[Any]: + im_data = self.tobytes() # load image first + return [self.info, self.mode, self.size, self.getpalette(), im_data] + + def __setstate__(self, state: list[Any]) -> None: + Image.__init__(self) + info, mode, size, palette, data = state[:5] + self.info = info + self._mode = mode + self._size = size + self.im = core.new(mode, size) + if mode in ("L", "LA", "P", "PA") and palette: + self.putpalette(palette) + self.frombytes(data) + + def tobytes(self, encoder_name: str = "raw", *args: Any) -> bytes: + """ + Return image as a bytes object. + + .. warning:: + + This method returns raw image data derived from Pillow's internal + storage. For compressed image data (e.g. PNG, JPEG) use + :meth:`~.save`, with a BytesIO parameter for in-memory data. + + :param encoder_name: What encoder to use. + + The default is to use the standard "raw" encoder. + To see how this packs pixel data into the returned + bytes, see :file:`libImaging/Pack.c`. + + A list of C encoders can be seen under codecs + section of the function array in + :file:`_imaging.c`. Python encoders are registered + within the relevant plugins. + :param args: Extra arguments to the encoder. + :returns: A :py:class:`bytes` object. + """ + + encoder_args: Any = args + if len(encoder_args) == 1 and isinstance(encoder_args[0], tuple): + # may pass tuple instead of argument list + encoder_args = encoder_args[0] + + if encoder_name == "raw" and encoder_args == (): + encoder_args = self.mode + + self.load() + + if self.width == 0 or self.height == 0: + return b"" + + # unpack data + e = _getencoder(self.mode, encoder_name, encoder_args) + e.setimage(self.im) + + from . import ImageFile + + bufsize = max(ImageFile.MAXBLOCK, self.size[0] * 4) # see RawEncode.c + + output = [] + while True: + bytes_consumed, errcode, data = e.encode(bufsize) + output.append(data) + if errcode: + break + if errcode < 0: + msg = f"encoder error {errcode} in tobytes" + raise RuntimeError(msg) + + return b"".join(output) + + def tobitmap(self, name: str = "image") -> bytes: + """ + Returns the image converted to an X11 bitmap. + + .. note:: This method only works for mode "1" images. + + :param name: The name prefix to use for the bitmap variables. + :returns: A string containing an X11 bitmap. + :raises ValueError: If the mode is not "1" + """ + + self.load() + if self.mode != "1": + msg = "not a bitmap" + raise ValueError(msg) + data = self.tobytes("xbm") + return b"".join( + [ + f"#define {name}_width {self.size[0]}\n".encode("ascii"), + f"#define {name}_height {self.size[1]}\n".encode("ascii"), + f"static char {name}_bits[] = {{\n".encode("ascii"), + data, + b"};", + ] + ) + + def frombytes( + self, + data: bytes | bytearray | SupportsArrayInterface, + decoder_name: str = "raw", + *args: Any, + ) -> None: + """ + Loads this image with pixel data from a bytes object. + + This method is similar to the :py:func:`~PIL.Image.frombytes` function, + but loads data into this image instead of creating a new image object. + """ + + if self.width == 0 or self.height == 0: + return + + decoder_args: Any = args + if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple): + # may pass tuple instead of argument list + decoder_args = decoder_args[0] + + # default format + if decoder_name == "raw" and decoder_args == (): + decoder_args = self.mode + + # unpack data + d = _getdecoder(self.mode, decoder_name, decoder_args) + d.setimage(self.im) + s = d.decode(data) + + if s[0] >= 0: + msg = "not enough image data" + raise ValueError(msg) + if s[1] != 0: + msg = "cannot decode image data" + raise ValueError(msg) + + def load(self) -> core.PixelAccess | None: + """ + Allocates storage for the image and loads the pixel data. In + normal cases, you don't need to call this method, since the + Image class automatically loads an opened image when it is + accessed for the first time. + + If the file associated with the image was opened by Pillow, then this + method will close it. The exception to this is if the image has + multiple frames, in which case the file will be left open for seek + operations. See :ref:`file-handling` for more information. + + :returns: An image access object. + :rtype: :py:class:`.PixelAccess` + """ + if self._im is not None and self.palette and self.palette.dirty: + # realize palette + mode, arr = self.palette.getdata() + self.im.putpalette(self.palette.mode, mode, arr) + self.palette.dirty = 0 + self.palette.rawmode = None + if "transparency" in self.info and mode in ("LA", "PA"): + if isinstance(self.info["transparency"], int): + self.im.putpalettealpha(self.info["transparency"], 0) + else: + self.im.putpalettealphas(self.info["transparency"]) + self.palette.mode = "RGBA" + else: + self.palette.palette = self.im.getpalette( + self.palette.mode, self.palette.mode + ) + + if self._im is not None: + return self.im.pixel_access(self.readonly) + return None + + def verify(self) -> None: + """ + Verifies the contents of a file. For data read from a file, this + method attempts to determine if the file is broken, without + actually decoding the image data. If this method finds any + problems, it raises suitable exceptions. If you need to load + the image after using this method, you must reopen the image + file. + """ + pass + + def convert( + self, + mode: str | None = None, + matrix: tuple[float, ...] | None = None, + dither: Dither | None = None, + palette: Palette = Palette.WEB, + colors: int = 256, + ) -> Image: + """ + Returns a converted copy of this image. For the "P" mode, this + method translates pixels through the palette. If mode is + omitted, a mode is chosen so that all information in the image + and the palette can be represented without a palette. + + This supports all possible conversions between "L", "RGB" and "CMYK". The + ``matrix`` argument only supports "L" and "RGB". + + When translating a color image to grayscale (mode "L"), + the library uses the ITU-R 601-2 luma transform:: + + L = R * 299/1000 + G * 587/1000 + B * 114/1000 + + The default method of converting a grayscale ("L") or "RGB" + image into a bilevel (mode "1") image uses Floyd-Steinberg + dither to approximate the original image luminosity levels. If + dither is ``None``, all values larger than 127 are set to 255 (white), + all other values to 0 (black). To use other thresholds, use the + :py:meth:`~PIL.Image.Image.point` method. + + When converting from "RGBA" to "P" without a ``matrix`` argument, + this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, + and ``dither`` and ``palette`` are ignored. + + When converting from "PA", if an "RGBA" palette is present, the alpha + channel from the image will be used instead of the values from the palette. + + :param mode: The requested mode. See: :ref:`concept-modes`. + :param matrix: An optional conversion matrix. If given, this + should be 4- or 12-tuple containing floating point values. + :param dither: Dithering method, used when converting from + mode "RGB" to "P" or from "RGB" or "L" to "1". + Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` + (default). Note that this is not used when ``matrix`` is supplied. + :param palette: Palette to use when converting from mode "RGB" + to "P". Available palettes are :data:`Palette.WEB` or + :data:`Palette.ADAPTIVE`. + :param colors: Number of colors to use for the :data:`Palette.ADAPTIVE` + palette. Defaults to 256. + :rtype: :py:class:`~PIL.Image.Image` + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if mode in ("BGR;15", "BGR;16", "BGR;24"): + deprecate(mode, 12) + + self.load() + + has_transparency = "transparency" in self.info + if not mode and self.mode == "P": + # determine default mode + if self.palette: + mode = self.palette.mode + else: + mode = "RGB" + if mode == "RGB" and has_transparency: + mode = "RGBA" + if not mode or (mode == self.mode and not matrix): + return self.copy() + + if matrix: + # matrix conversion + if mode not in ("L", "RGB"): + msg = "illegal conversion" + raise ValueError(msg) + im = self.im.convert_matrix(mode, matrix) + new_im = self._new(im) + if has_transparency and self.im.bands == 3: + transparency = new_im.info["transparency"] + + def convert_transparency( + m: tuple[float, ...], v: tuple[int, int, int] + ) -> int: + value = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5 + return max(0, min(255, int(value))) + + if mode == "L": + transparency = convert_transparency(matrix, transparency) + elif len(mode) == 3: + transparency = tuple( + convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) + for i in range(len(transparency)) + ) + new_im.info["transparency"] = transparency + return new_im + + if mode == "P" and self.mode == "RGBA": + return self.quantize(colors) + + trns = None + delete_trns = False + # transparency handling + if has_transparency: + if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or ( + self.mode == "RGB" and mode in ("La", "LA", "RGBa", "RGBA") + ): + # Use transparent conversion to promote from transparent + # color to an alpha channel. + new_im = self._new( + self.im.convert_transparent(mode, self.info["transparency"]) + ) + del new_im.info["transparency"] + return new_im + elif self.mode in ("L", "RGB", "P") and mode in ("L", "RGB", "P"): + t = self.info["transparency"] + if isinstance(t, bytes): + # Dragons. This can't be represented by a single color + warnings.warn( + "Palette images with Transparency expressed in bytes should be " + "converted to RGBA images" + ) + delete_trns = True + else: + # get the new transparency color. + # use existing conversions + trns_im = new(self.mode, (1, 1)) + if self.mode == "P": + assert self.palette is not None + trns_im.putpalette(self.palette, self.palette.mode) + if isinstance(t, tuple): + err = "Couldn't allocate a palette color for transparency" + assert trns_im.palette is not None + try: + t = trns_im.palette.getcolor(t, self) + except ValueError as e: + if str(e) == "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for transparency + t = None + else: + raise ValueError(err) from e + if t is None: + trns = None + else: + trns_im.putpixel((0, 0), t) + + if mode in ("L", "RGB"): + trns_im = trns_im.convert(mode) + else: + # can't just retrieve the palette number, got to do it + # after quantization. + trns_im = trns_im.convert("RGB") + trns = trns_im.getpixel((0, 0)) + + elif self.mode == "P" and mode in ("LA", "PA", "RGBA"): + t = self.info["transparency"] + delete_trns = True + + if isinstance(t, bytes): + self.im.putpalettealphas(t) + elif isinstance(t, int): + self.im.putpalettealpha(t, 0) + else: + msg = "Transparency for P mode should be bytes or int" + raise ValueError(msg) + + if mode == "P" and palette == Palette.ADAPTIVE: + im = self.im.quantize(colors) + new_im = self._new(im) + from . import ImagePalette + + new_im.palette = ImagePalette.ImagePalette( + "RGB", new_im.im.getpalette("RGB") + ) + if delete_trns: + # This could possibly happen if we requantize to fewer colors. + # The transparency would be totally off in that case. + del new_im.info["transparency"] + if trns is not None: + try: + new_im.info["transparency"] = new_im.palette.getcolor( + cast(tuple[int, ...], trns), # trns was converted to RGB + new_im, + ) + except Exception: + # if we can't make a transparent color, don't leave the old + # transparency hanging around to mess us up. + del new_im.info["transparency"] + warnings.warn("Couldn't allocate palette entry for transparency") + return new_im + + if "LAB" in (self.mode, mode): + im = self + if mode == "LAB": + if im.mode not in ("RGB", "RGBA", "RGBX"): + im = im.convert("RGBA") + other_mode = im.mode + else: + other_mode = mode + if other_mode in ("RGB", "RGBA", "RGBX"): + from . import ImageCms + + srgb = ImageCms.createProfile("sRGB") + lab = ImageCms.createProfile("LAB") + profiles = [lab, srgb] if im.mode == "LAB" else [srgb, lab] + transform = ImageCms.buildTransform( + profiles[0], profiles[1], im.mode, mode + ) + return transform.apply(im) + + # colorspace conversion + if dither is None: + dither = Dither.FLOYDSTEINBERG + + try: + im = self.im.convert(mode, dither) + except ValueError: + try: + # normalize source image and try again + modebase = getmodebase(self.mode) + if modebase == self.mode: + raise + im = self.im.convert(modebase) + im = im.convert(mode, dither) + except KeyError as e: + msg = "illegal conversion" + raise ValueError(msg) from e + + new_im = self._new(im) + if mode == "P" and palette != Palette.ADAPTIVE: + from . import ImagePalette + + new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB")) + if delete_trns: + # crash fail if we leave a bytes transparency in an rgb/l mode. + del new_im.info["transparency"] + if trns is not None: + if new_im.mode == "P" and new_im.palette: + try: + new_im.info["transparency"] = new_im.palette.getcolor( + cast(tuple[int, ...], trns), new_im # trns was converted to RGB + ) + except ValueError as e: + del new_im.info["transparency"] + if str(e) != "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for transparency + warnings.warn( + "Couldn't allocate palette entry for transparency" + ) + else: + new_im.info["transparency"] = trns + return new_im + + def quantize( + self, + colors: int = 256, + method: int | None = None, + kmeans: int = 0, + palette: Image | None = None, + dither: Dither = Dither.FLOYDSTEINBERG, + ) -> Image: + """ + Convert the image to 'P' mode with the specified number + of colors. + + :param colors: The desired number of colors, <= 256 + :param method: :data:`Quantize.MEDIANCUT` (median cut), + :data:`Quantize.MAXCOVERAGE` (maximum coverage), + :data:`Quantize.FASTOCTREE` (fast octree), + :data:`Quantize.LIBIMAGEQUANT` (libimagequant; check support + using :py:func:`PIL.features.check_feature` with + ``feature="libimagequant"``). + + By default, :data:`Quantize.MEDIANCUT` will be used. + + The exception to this is RGBA images. :data:`Quantize.MEDIANCUT` + and :data:`Quantize.MAXCOVERAGE` do not support RGBA images, so + :data:`Quantize.FASTOCTREE` is used by default instead. + :param kmeans: Integer greater than or equal to zero. + :param palette: Quantize to the palette of given + :py:class:`PIL.Image.Image`. + :param dither: Dithering method, used when converting from + mode "RGB" to "P" or from "RGB" or "L" to "1". + Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG` + (default). + :returns: A new image + """ + + self.load() + + if method is None: + # defaults: + method = Quantize.MEDIANCUT + if self.mode == "RGBA": + method = Quantize.FASTOCTREE + + if self.mode == "RGBA" and method not in ( + Quantize.FASTOCTREE, + Quantize.LIBIMAGEQUANT, + ): + # Caller specified an invalid mode. + msg = ( + "Fast Octree (method == 2) and libimagequant (method == 3) " + "are the only valid methods for quantizing RGBA images" + ) + raise ValueError(msg) + + if palette: + # use palette from reference image + palette.load() + if palette.mode != "P": + msg = "bad mode for palette image" + raise ValueError(msg) + if self.mode not in {"RGB", "L"}: + msg = "only RGB or L mode images can be quantized to a palette" + raise ValueError(msg) + im = self.im.convert("P", dither, palette.im) + new_im = self._new(im) + assert palette.palette is not None + new_im.palette = palette.palette.copy() + return new_im + + if kmeans < 0: + msg = "kmeans must not be negative" + raise ValueError(msg) + + im = self._new(self.im.quantize(colors, method, kmeans)) + + from . import ImagePalette + + mode = im.im.getpalettemode() + palette_data = im.im.getpalette(mode, mode)[: colors * len(mode)] + im.palette = ImagePalette.ImagePalette(mode, palette_data) + + return im + + def copy(self) -> Image: + """ + Copies this image. Use this method if you wish to paste things + into an image, but still retain the original. + + :rtype: :py:class:`~PIL.Image.Image` + :returns: An :py:class:`~PIL.Image.Image` object. + """ + self.load() + return self._new(self.im.copy()) + + __copy__ = copy + + def crop(self, box: tuple[float, float, float, float] | None = None) -> Image: + """ + Returns a rectangular region from this image. The box is a + 4-tuple defining the left, upper, right, and lower pixel + coordinate. See :ref:`coordinate-system`. + + Note: Prior to Pillow 3.4.0, this was a lazy operation. + + :param box: The crop rectangle, as a (left, upper, right, lower)-tuple. + :rtype: :py:class:`~PIL.Image.Image` + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if box is None: + return self.copy() + + if box[2] < box[0]: + msg = "Coordinate 'right' is less than 'left'" + raise ValueError(msg) + elif box[3] < box[1]: + msg = "Coordinate 'lower' is less than 'upper'" + raise ValueError(msg) + + self.load() + return self._new(self._crop(self.im, box)) + + def _crop( + self, im: core.ImagingCore, box: tuple[float, float, float, float] + ) -> core.ImagingCore: + """ + Returns a rectangular region from the core image object im. + + This is equivalent to calling im.crop((x0, y0, x1, y1)), but + includes additional sanity checks. + + :param im: a core image object + :param box: The crop rectangle, as a (left, upper, right, lower)-tuple. + :returns: A core image object. + """ + + x0, y0, x1, y1 = map(int, map(round, box)) + + absolute_values = (abs(x1 - x0), abs(y1 - y0)) + + _decompression_bomb_check(absolute_values) + + return im.crop((x0, y0, x1, y1)) + + def draft( + self, mode: str | None, size: tuple[int, int] | None + ) -> tuple[str, tuple[int, int, float, float]] | None: + """ + Configures the image file loader so it returns a version of the + image that as closely as possible matches the given mode and + size. For example, you can use this method to convert a color + JPEG to grayscale while loading it. + + If any changes are made, returns a tuple with the chosen ``mode`` and + ``box`` with coordinates of the original image within the altered one. + + Note that this method modifies the :py:class:`~PIL.Image.Image` object + in place. If the image has already been loaded, this method has no + effect. + + Note: This method is not implemented for most images. It is + currently implemented only for JPEG and MPO images. + + :param mode: The requested mode. + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + """ + pass + + def _expand(self, xmargin: int, ymargin: int | None = None) -> Image: + if ymargin is None: + ymargin = xmargin + self.load() + return self._new(self.im.expand(xmargin, ymargin)) + + def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image: + """ + Filters this image using the given filter. For a list of + available filters, see the :py:mod:`~PIL.ImageFilter` module. + + :param filter: Filter kernel. + :returns: An :py:class:`~PIL.Image.Image` object.""" + + from . import ImageFilter + + self.load() + + if callable(filter): + filter = filter() + if not hasattr(filter, "filter"): + msg = "filter argument should be ImageFilter.Filter instance or class" + raise TypeError(msg) + + multiband = isinstance(filter, ImageFilter.MultibandFilter) + if self.im.bands == 1 or multiband: + return self._new(filter.filter(self.im)) + + ims = [ + self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands) + ] + return merge(self.mode, ims) + + def getbands(self) -> tuple[str, ...]: + """ + Returns a tuple containing the name of each band in this image. + For example, ``getbands`` on an RGB image returns ("R", "G", "B"). + + :returns: A tuple containing band names. + :rtype: tuple + """ + return ImageMode.getmode(self.mode).bands + + def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | None: + """ + Calculates the bounding box of the non-zero regions in the + image. + + :param alpha_only: Optional flag, defaulting to ``True``. + If ``True`` and the image has an alpha channel, trim transparent pixels. + Otherwise, trim pixels when all channels are zero. + Keyword-only argument. + :returns: The bounding box is returned as a 4-tuple defining the + left, upper, right, and lower pixel coordinate. See + :ref:`coordinate-system`. If the image is completely empty, this + method returns None. + + """ + + self.load() + return self.im.getbbox(alpha_only) + + def getcolors( + self, maxcolors: int = 256 + ) -> list[tuple[int, tuple[int, ...]]] | list[tuple[int, float]] | None: + """ + Returns a list of colors used in this image. + + The colors will be in the image's mode. For example, an RGB image will + return a tuple of (red, green, blue) color values, and a P image will + return the index of the color in the palette. + + :param maxcolors: Maximum number of colors. If this number is + exceeded, this method returns None. The default limit is + 256 colors. + :returns: An unsorted list of (count, pixel) values. + """ + + self.load() + if self.mode in ("1", "L", "P"): + h = self.im.histogram() + out: list[tuple[int, float]] = [(h[i], i) for i in range(256) if h[i]] + if len(out) > maxcolors: + return None + return out + return self.im.getcolors(maxcolors) + + def getdata(self, band: int | None = None) -> core.ImagingCore: + """ + Returns the contents of this image as a sequence object + containing pixel values. The sequence object is flattened, so + that values for line one follow directly after the values of + line zero, and so on. + + Note that the sequence object returned by this method is an + internal PIL data type, which only supports certain sequence + operations. To convert it to an ordinary sequence (e.g. for + printing), use ``list(im.getdata())``. + + :param band: What band to return. The default is to return + all bands. To return a single band, pass in the index + value (e.g. 0 to get the "R" band from an "RGB" image). + :returns: A sequence-like object. + """ + + self.load() + if band is not None: + return self.im.getband(band) + return self.im # could be abused + + def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]: + """ + Gets the minimum and maximum pixel values for each band in + the image. + + :returns: For a single-band image, a 2-tuple containing the + minimum and maximum pixel value. For a multi-band image, + a tuple containing one 2-tuple for each band. + """ + + self.load() + if self.im.bands > 1: + return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands)) + return self.im.getextrema() + + def getxmp(self) -> dict[str, Any]: + """ + Returns a dictionary containing the XMP tags. + Requires defusedxml to be installed. + + :returns: XMP tags in a dictionary. + """ + + def get_name(tag: str) -> str: + return re.sub("^{[^}]+}", "", tag) + + def get_value(element: Element) -> str | dict[str, Any] | None: + value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()} + children = list(element) + if children: + for child in children: + name = get_name(child.tag) + child_value = get_value(child) + if name in value: + if not isinstance(value[name], list): + value[name] = [value[name]] + value[name].append(child_value) + else: + value[name] = child_value + elif value: + if element.text: + value["text"] = element.text + else: + return element.text + return value + + if ElementTree is None: + warnings.warn("XMP data cannot be read without defusedxml dependency") + return {} + if "xmp" not in self.info: + return {} + root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 ")) + return {get_name(root.tag): get_value(root)} + + def getexif(self) -> Exif: + """ + Gets EXIF data from the image. + + :returns: an :py:class:`~PIL.Image.Exif` object. + """ + if self._exif is None: + self._exif = Exif() + elif self._exif._loaded: + return self._exif + self._exif._loaded = True + + exif_info = self.info.get("exif") + if exif_info is None: + if "Raw profile type exif" in self.info: + exif_info = bytes.fromhex( + "".join(self.info["Raw profile type exif"].split("\n")[3:]) + ) + elif hasattr(self, "tag_v2"): + self._exif.bigtiff = self.tag_v2._bigtiff + self._exif.endian = self.tag_v2._endian + self._exif.load_from_fp(self.fp, self.tag_v2._offset) + if exif_info is not None: + self._exif.load(exif_info) + + # XMP tags + if ExifTags.Base.Orientation not in self._exif: + xmp_tags = self.info.get("XML:com.adobe.xmp") + pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])' + if not xmp_tags and (xmp_tags := self.info.get("xmp")): + pattern = rb'tiff:Orientation(="|>)([0-9])' + if xmp_tags: + match = re.search(pattern, xmp_tags) + if match: + self._exif[ExifTags.Base.Orientation] = int(match[2]) + + return self._exif + + def _reload_exif(self) -> None: + if self._exif is None or not self._exif._loaded: + return + self._exif._loaded = False + self.getexif() + + def get_child_images(self) -> list[ImageFile.ImageFile]: + from . import ImageFile + + deprecate("Image.Image.get_child_images", 13) + return ImageFile.ImageFile.get_child_images(self) # type: ignore[arg-type] + + def getim(self) -> CapsuleType: + """ + Returns a capsule that points to the internal image memory. + + :returns: A capsule object. + """ + + self.load() + return self.im.ptr + + def getpalette(self, rawmode: str | None = "RGB") -> list[int] | None: + """ + Returns the image palette as a list. + + :param rawmode: The mode in which to return the palette. ``None`` will + return the palette in its current mode. + + .. versionadded:: 9.1.0 + + :returns: A list of color values [r, g, b, ...], or None if the + image has no palette. + """ + + self.load() + try: + mode = self.im.getpalettemode() + except ValueError: + return None # no palette + if rawmode is None: + rawmode = mode + return list(self.im.getpalette(mode, rawmode)) + + @property + def has_transparency_data(self) -> bool: + """ + Determine if an image has transparency data, whether in the form of an + alpha channel, a palette with an alpha channel, or a "transparency" key + in the info dictionary. + + Note the image might still appear solid, if all of the values shown + within are opaque. + + :returns: A boolean. + """ + if ( + self.mode in ("LA", "La", "PA", "RGBA", "RGBa") + or "transparency" in self.info + ): + return True + if self.mode == "P": + assert self.palette is not None + return self.palette.mode.endswith("A") + return False + + def apply_transparency(self) -> None: + """ + If a P mode image has a "transparency" key in the info dictionary, + remove the key and instead apply the transparency to the palette. + Otherwise, the image is unchanged. + """ + if self.mode != "P" or "transparency" not in self.info: + return + + from . import ImagePalette + + palette = self.getpalette("RGBA") + assert palette is not None + transparency = self.info["transparency"] + if isinstance(transparency, bytes): + for i, alpha in enumerate(transparency): + palette[i * 4 + 3] = alpha + else: + palette[transparency * 4 + 3] = 0 + self.palette = ImagePalette.ImagePalette("RGBA", bytes(palette)) + self.palette.dirty = 1 + + del self.info["transparency"] + + def getpixel( + self, xy: tuple[int, int] | list[int] + ) -> float | tuple[int, ...] | None: + """ + Returns the pixel value at a given position. + + :param xy: The coordinate, given as (x, y). See + :ref:`coordinate-system`. + :returns: The pixel value. If the image is a multi-layer image, + this method returns a tuple. + """ + + self.load() + return self.im.getpixel(tuple(xy)) + + def getprojection(self) -> tuple[list[int], list[int]]: + """ + Get projection to x and y axes + + :returns: Two sequences, indicating where there are non-zero + pixels along the X-axis and the Y-axis, respectively. + """ + + self.load() + x, y = self.im.getprojection() + return list(x), list(y) + + def histogram( + self, mask: Image | None = None, extrema: tuple[float, float] | None = None + ) -> list[int]: + """ + Returns a histogram for the image. The histogram is returned as a + list of pixel counts, one for each pixel value in the source + image. Counts are grouped into 256 bins for each band, even if + the image has more than 8 bits per band. If the image has more + than one band, the histograms for all bands are concatenated (for + example, the histogram for an "RGB" image contains 768 values). + + A bilevel image (mode "1") is treated as a grayscale ("L") image + by this method. + + If a mask is provided, the method returns a histogram for those + parts of the image where the mask image is non-zero. The mask + image must have the same size as the image, and be either a + bi-level image (mode "1") or a grayscale image ("L"). + + :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. + :returns: A list containing pixel counts. + """ + self.load() + if mask: + mask.load() + return self.im.histogram((0, 0), mask.im) + if self.mode in ("I", "F"): + return self.im.histogram( + extrema if extrema is not None else self.getextrema() + ) + return self.im.histogram() + + def entropy( + self, mask: Image | None = None, extrema: tuple[float, float] | None = None + ) -> float: + """ + Calculates and returns the entropy for the image. + + A bilevel image (mode "1") is treated as a grayscale ("L") + image by this method. + + If a mask is provided, the method employs the histogram for + those parts of the image where the mask image is non-zero. + The mask image must have the same size as the image, and be + either a bi-level image (mode "1") or a grayscale image ("L"). + + :param mask: An optional mask. + :param extrema: An optional tuple of manually-specified extrema. + :returns: A float value representing the image entropy + """ + self.load() + if mask: + mask.load() + return self.im.entropy((0, 0), mask.im) + if self.mode in ("I", "F"): + return self.im.entropy( + extrema if extrema is not None else self.getextrema() + ) + return self.im.entropy() + + def paste( + self, + im: Image | str | float | tuple[float, ...], + box: Image | tuple[int, int, int, int] | tuple[int, int] | None = None, + mask: Image | None = None, + ) -> None: + """ + Pastes another image into this image. The box argument is either + a 2-tuple giving the upper left corner, a 4-tuple defining the + left, upper, right, and lower pixel coordinate, or None (same as + (0, 0)). See :ref:`coordinate-system`. If a 4-tuple is given, the size + of the pasted image must match the size of the region. + + If the modes don't match, the pasted image is converted to the mode of + this image (see the :py:meth:`~PIL.Image.Image.convert` method for + details). + + Instead of an image, the source can be a integer or tuple + containing pixel values. The method then fills the region + with the given color. When creating RGB images, you can + also use color strings as supported by the ImageColor module. + + If a mask is given, this method updates only the regions + indicated by the mask. You can use either "1", "L", "LA", "RGBA" + or "RGBa" images (if present, the alpha band is used as mask). + Where the mask is 255, the given image is copied as is. Where + the mask is 0, the current value is preserved. Intermediate + values will mix the two images together, including their alpha + channels if they have them. + + See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to + combine images with respect to their alpha channels. + + :param im: Source image or pixel value (integer, float or tuple). + :param box: An optional 4-tuple giving the region to paste into. + If a 2-tuple is used instead, it's treated as the upper left + corner. If omitted or None, the source is pasted into the + upper left corner. + + If an image is given as the second argument and there is no + third, the box defaults to (0, 0), and the second argument + is interpreted as a mask image. + :param mask: An optional mask image. + """ + + if isinstance(box, Image): + if mask is not None: + msg = "If using second argument as mask, third argument must be None" + raise ValueError(msg) + # abbreviated paste(im, mask) syntax + mask = box + box = None + + if box is None: + box = (0, 0) + + if len(box) == 2: + # upper left corner given; get size from image or mask + if isinstance(im, Image): + size = im.size + elif isinstance(mask, Image): + size = mask.size + else: + # FIXME: use self.size here? + msg = "cannot determine region size; use 4-item box" + raise ValueError(msg) + box += (box[0] + size[0], box[1] + size[1]) + + source: core.ImagingCore | str | float | tuple[float, ...] + if isinstance(im, str): + from . import ImageColor + + source = ImageColor.getcolor(im, self.mode) + elif isinstance(im, Image): + im.load() + if self.mode != im.mode: + if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"): + # should use an adapter for this! + im = im.convert(self.mode) + source = im.im + else: + source = im + + self._ensure_mutable() + + if mask: + mask.load() + self.im.paste(source, box, mask.im) + else: + self.im.paste(source, box) + + def alpha_composite( + self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0) + ) -> None: + """'In-place' analog of Image.alpha_composite. Composites an image + onto this image. + + :param im: image to composite over this one + :param dest: Optional 2 tuple (left, top) specifying the upper + left corner in this (destination) image. + :param source: Optional 2 (left, top) tuple for the upper left + corner in the overlay source image, or 4 tuple (left, top, right, + bottom) for the bounds of the source rectangle + + Performance Note: Not currently implemented in-place in the core layer. + """ + + if not isinstance(source, (list, tuple)): + msg = "Source must be a list or tuple" + raise ValueError(msg) + if not isinstance(dest, (list, tuple)): + msg = "Destination must be a list or tuple" + raise ValueError(msg) + + if len(source) == 4: + overlay_crop_box = tuple(source) + elif len(source) == 2: + overlay_crop_box = tuple(source) + im.size + else: + msg = "Source must be a sequence of length 2 or 4" + raise ValueError(msg) + + if not len(dest) == 2: + msg = "Destination must be a sequence of length 2" + raise ValueError(msg) + if min(source) < 0: + msg = "Source must be non-negative" + raise ValueError(msg) + + # over image, crop if it's not the whole image. + if overlay_crop_box == (0, 0) + im.size: + overlay = im + else: + overlay = im.crop(overlay_crop_box) + + # target for the paste + box = tuple(dest) + (dest[0] + overlay.width, dest[1] + overlay.height) + + # destination image. don't copy if we're using the whole image. + if box == (0, 0) + self.size: + background = self + else: + background = self.crop(box) + + result = alpha_composite(background, overlay) + self.paste(result, box) + + def point( + self, + lut: ( + Sequence[float] + | NumpyArray + | Callable[[int], float] + | Callable[[ImagePointTransform], ImagePointTransform | float] + | ImagePointHandler + ), + mode: str | None = None, + ) -> Image: + """ + Maps this image through a lookup table or function. + + :param lut: A lookup table, containing 256 (or 65536 if + self.mode=="I" and mode == "L") values per band in the + image. A function can be used instead, it should take a + single argument. The function is called once for each + possible pixel value, and the resulting table is applied to + all bands of the image. + + It may also be an :py:class:`~PIL.Image.ImagePointHandler` + object:: + + class Example(Image.ImagePointHandler): + def point(self, im: Image) -> Image: + # Return result + :param mode: Output mode (default is same as input). This can only be used if + the source image has mode "L" or "P", and the output has mode "1" or the + source image mode is "I" and the output mode is "L". + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + self.load() + + if isinstance(lut, ImagePointHandler): + return lut.point(self) + + if callable(lut): + # if it isn't a list, it should be a function + if self.mode in ("I", "I;16", "F"): + # check if the function can be used with point_transform + # UNDONE wiredfool -- I think this prevents us from ever doing + # a gamma function point transform on > 8bit images. + scale, offset = _getscaleoffset(lut) # type: ignore[arg-type] + return self._new(self.im.point_transform(scale, offset)) + # for other modes, convert the function to a table + flatLut = [lut(i) for i in range(256)] * self.im.bands # type: ignore[arg-type] + else: + flatLut = lut + + if self.mode == "F": + # FIXME: _imaging returns a confusing error message for this case + msg = "point operation not supported for this mode" + raise ValueError(msg) + + if mode != "F": + flatLut = [round(i) for i in flatLut] + return self._new(self.im.point(flatLut, mode)) + + def putalpha(self, alpha: Image | int) -> None: + """ + Adds or replaces the alpha layer in this image. If the image + does not have an alpha layer, it's converted to "LA" or "RGBA". + The new layer must be either "L" or "1". + + :param alpha: The new alpha layer. This can either be an "L" or "1" + image having the same size as this image, or an integer. + """ + + self._ensure_mutable() + + if self.mode not in ("LA", "PA", "RGBA"): + # attempt to promote self to a matching alpha mode + try: + mode = getmodebase(self.mode) + "A" + try: + self.im.setmode(mode) + except (AttributeError, ValueError) as e: + # do things the hard way + im = self.im.convert(mode) + if im.mode not in ("LA", "PA", "RGBA"): + msg = "alpha channel could not be added" + raise ValueError(msg) from e # sanity check + self.im = im + self._mode = self.im.mode + except KeyError as e: + msg = "illegal image mode" + raise ValueError(msg) from e + + if self.mode in ("LA", "PA"): + band = 1 + else: + band = 3 + + if isinstance(alpha, Image): + # alpha layer + if alpha.mode not in ("1", "L"): + msg = "illegal image mode" + raise ValueError(msg) + alpha.load() + if alpha.mode == "1": + alpha = alpha.convert("L") + else: + # constant alpha + try: + self.im.fillband(band, alpha) + except (AttributeError, ValueError): + # do things the hard way + alpha = new("L", self.size, alpha) + else: + return + + self.im.putband(alpha.im, band) + + def putdata( + self, + data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray, + scale: float = 1.0, + offset: float = 0.0, + ) -> None: + """ + Copies pixel data from a flattened sequence object into the image. The + values should start at the upper left corner (0, 0), continue to the + end of the line, followed directly by the first value of the second + line, and so on. Data will be read until either the image or the + sequence ends. The scale and offset values are used to adjust the + sequence values: **pixel = value*scale + offset**. + + :param data: A flattened sequence object. + :param scale: An optional scale value. The default is 1.0. + :param offset: An optional offset value. The default is 0.0. + """ + + self._ensure_mutable() + + self.im.putdata(data, scale, offset) + + def putpalette( + self, + data: ImagePalette.ImagePalette | bytes | Sequence[int], + rawmode: str = "RGB", + ) -> None: + """ + Attaches a palette to this image. The image must be a "P", "PA", "L" + or "LA" image. + + The palette sequence must contain at most 256 colors, made up of one + integer value for each channel in the raw mode. + For example, if the raw mode is "RGB", then it can contain at most 768 + values, made up of red, green and blue values for the corresponding pixel + index in the 256 colors. + If the raw mode is "RGBA", then it can contain at most 1024 values, + containing red, green, blue and alpha values. + + Alternatively, an 8-bit string may be used instead of an integer sequence. + + :param data: A palette sequence (either a list or a string). + :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode + that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L"). + """ + from . import ImagePalette + + if self.mode not in ("L", "LA", "P", "PA"): + msg = "illegal image mode" + raise ValueError(msg) + if isinstance(data, ImagePalette.ImagePalette): + if data.rawmode is not None: + palette = ImagePalette.raw(data.rawmode, data.palette) + else: + palette = ImagePalette.ImagePalette(palette=data.palette) + palette.dirty = 1 + else: + if not isinstance(data, bytes): + data = bytes(data) + palette = ImagePalette.raw(rawmode, data) + self._mode = "PA" if "A" in self.mode else "P" + self.palette = palette + self.palette.mode = "RGBA" if "A" in rawmode else "RGB" + self.load() # install new palette + + def putpixel( + self, xy: tuple[int, int], value: float | tuple[int, ...] | list[int] + ) -> None: + """ + Modifies the pixel at the given position. The color is given as + a single numerical value for single-band images, and a tuple for + multi-band images. In addition to this, RGB and RGBA tuples are + accepted for P and PA images. + + Note that this method is relatively slow. For more extensive changes, + use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw` + module instead. + + See: + + * :py:meth:`~PIL.Image.Image.paste` + * :py:meth:`~PIL.Image.Image.putdata` + * :py:mod:`~PIL.ImageDraw` + + :param xy: The pixel coordinate, given as (x, y). See + :ref:`coordinate-system`. + :param value: The pixel value. + """ + + if self.readonly: + self._copy() + self.load() + + if ( + self.mode in ("P", "PA") + and isinstance(value, (list, tuple)) + and len(value) in [3, 4] + ): + # RGB or RGBA value for a P or PA image + if self.mode == "PA": + alpha = value[3] if len(value) == 4 else 255 + value = value[:3] + assert self.palette is not None + palette_index = self.palette.getcolor(tuple(value), self) + value = (palette_index, alpha) if self.mode == "PA" else palette_index + return self.im.putpixel(xy, value) + + def remap_palette( + self, dest_map: list[int], source_palette: bytes | bytearray | None = None + ) -> Image: + """ + Rewrites the image to reorder the palette. + + :param dest_map: A list of indexes into the original palette. + e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))`` + is the identity transform. + :param source_palette: Bytes or None. + :returns: An :py:class:`~PIL.Image.Image` object. + + """ + from . import ImagePalette + + if self.mode not in ("L", "P"): + msg = "illegal image mode" + raise ValueError(msg) + + bands = 3 + palette_mode = "RGB" + if source_palette is None: + if self.mode == "P": + self.load() + palette_mode = self.im.getpalettemode() + if palette_mode == "RGBA": + bands = 4 + source_palette = self.im.getpalette(palette_mode, palette_mode) + else: # L-mode + source_palette = bytearray(i // 3 for i in range(768)) + elif len(source_palette) > 768: + bands = 4 + palette_mode = "RGBA" + + palette_bytes = b"" + new_positions = [0] * 256 + + # pick only the used colors from the palette + for i, oldPosition in enumerate(dest_map): + palette_bytes += source_palette[ + oldPosition * bands : oldPosition * bands + bands + ] + new_positions[oldPosition] = i + + # replace the palette color id of all pixel with the new id + + # Palette images are [0..255], mapped through a 1 or 3 + # byte/color map. We need to remap the whole image + # from palette 1 to palette 2. New_positions is + # an array of indexes into palette 1. Palette 2 is + # palette 1 with any holes removed. + + # We're going to leverage the convert mechanism to use the + # C code to remap the image from palette 1 to palette 2, + # by forcing the source image into 'L' mode and adding a + # mapping 'L' mode palette, then converting back to 'L' + # sans palette thus converting the image bytes, then + # assigning the optimized RGB palette. + + # perf reference, 9500x4000 gif, w/~135 colors + # 14 sec prepatch, 1 sec postpatch with optimization forced. + + mapping_palette = bytearray(new_positions) + + m_im = self.copy() + m_im._mode = "P" + + m_im.palette = ImagePalette.ImagePalette( + palette_mode, palette=mapping_palette * bands + ) + # possibly set palette dirty, then + # m_im.putpalette(mapping_palette, 'L') # converts to 'P' + # or just force it. + # UNDONE -- this is part of the general issue with palettes + m_im.im.putpalette(palette_mode, palette_mode + ";L", m_im.palette.tobytes()) + + m_im = m_im.convert("L") + + m_im.putpalette(palette_bytes, palette_mode) + m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes) + + if "transparency" in self.info: + try: + m_im.info["transparency"] = dest_map.index(self.info["transparency"]) + except ValueError: + if "transparency" in m_im.info: + del m_im.info["transparency"] + + return m_im + + def _get_safe_box( + self, + size: tuple[int, int], + resample: Resampling, + box: tuple[float, float, float, float], + ) -> tuple[int, int, int, int]: + """Expands the box so it includes adjacent pixels + that may be used by resampling with the given resampling filter. + """ + filter_support = _filters_support[resample] - 0.5 + scale_x = (box[2] - box[0]) / size[0] + scale_y = (box[3] - box[1]) / size[1] + support_x = filter_support * scale_x + support_y = filter_support * scale_y + + return ( + max(0, int(box[0] - support_x)), + max(0, int(box[1] - support_y)), + min(self.size[0], math.ceil(box[2] + support_x)), + min(self.size[1], math.ceil(box[3] + support_y)), + ) + + def resize( + self, + size: tuple[int, int] | list[int] | NumpyArray, + resample: int | None = None, + box: tuple[float, float, float, float] | None = None, + reducing_gap: float | None = None, + ) -> Image: + """ + Returns a resized copy of this image. + + :param size: The requested size in pixels, as a tuple or array: + (width, height). + :param resample: An optional resampling filter. This can be + one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, + :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, + :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. + If the image has mode "1" or "P", it is always set to + :py:data:`Resampling.NEAREST`. If the image mode is "BGR;15", + "BGR;16" or "BGR;24", then the default filter is + :py:data:`Resampling.NEAREST`. Otherwise, the default filter is + :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`. + :param box: An optional 4-tuple of floats providing + the source image region to be scaled. + The values must be within (0, 0, width, height) rectangle. + If omitted or None, the entire source is used. + :param reducing_gap: Apply optimization by resizing the image + in two steps. First, reducing the image by integer times + using :py:meth:`~PIL.Image.Image.reduce`. + Second, resizing using regular resampling. The last step + changes size no less than by ``reducing_gap`` times. + ``reducing_gap`` may be None (no first step is performed) + or should be greater than 1.0. The bigger ``reducing_gap``, + the closer the result to the fair resampling. + The smaller ``reducing_gap``, the faster resizing. + With ``reducing_gap`` greater or equal to 3.0, the result is + indistinguishable from fair resampling in most cases. + The default value is None (no optimization). + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if resample is None: + bgr = self.mode.startswith("BGR;") + resample = Resampling.NEAREST if bgr else Resampling.BICUBIC + elif resample not in ( + Resampling.NEAREST, + Resampling.BILINEAR, + Resampling.BICUBIC, + Resampling.LANCZOS, + Resampling.BOX, + Resampling.HAMMING, + ): + msg = f"Unknown resampling filter ({resample})." + + filters = [ + f"{filter[1]} ({filter[0]})" + for filter in ( + (Resampling.NEAREST, "Image.Resampling.NEAREST"), + (Resampling.LANCZOS, "Image.Resampling.LANCZOS"), + (Resampling.BILINEAR, "Image.Resampling.BILINEAR"), + (Resampling.BICUBIC, "Image.Resampling.BICUBIC"), + (Resampling.BOX, "Image.Resampling.BOX"), + (Resampling.HAMMING, "Image.Resampling.HAMMING"), + ) + ] + msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}" + raise ValueError(msg) + + if reducing_gap is not None and reducing_gap < 1.0: + msg = "reducing_gap must be 1.0 or greater" + raise ValueError(msg) + + if box is None: + box = (0, 0) + self.size + + size = tuple(size) + if self.size == size and box == (0, 0) + self.size: + return self.copy() + + if self.mode in ("1", "P"): + resample = Resampling.NEAREST + + if self.mode in ["LA", "RGBA"] and resample != Resampling.NEAREST: + im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) + im = im.resize(size, resample, box) + return im.convert(self.mode) + + self.load() + + if reducing_gap is not None and resample != Resampling.NEAREST: + factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1 + factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 + if factor_x > 1 or factor_y > 1: + reduce_box = self._get_safe_box(size, cast(Resampling, resample), box) + factor = (factor_x, factor_y) + self = ( + self.reduce(factor, box=reduce_box) + if callable(self.reduce) + else Image.reduce(self, factor, box=reduce_box) + ) + box = ( + (box[0] - reduce_box[0]) / factor_x, + (box[1] - reduce_box[1]) / factor_y, + (box[2] - reduce_box[0]) / factor_x, + (box[3] - reduce_box[1]) / factor_y, + ) + + return self._new(self.im.resize(size, resample, box)) + + def reduce( + self, + factor: int | tuple[int, int], + box: tuple[int, int, int, int] | None = None, + ) -> Image: + """ + Returns a copy of the image reduced ``factor`` times. + If the size of the image is not dividable by ``factor``, + the resulting size will be rounded up. + + :param factor: A greater than 0 integer or tuple of two integers + for width and height separately. + :param box: An optional 4-tuple of ints providing + the source image region to be reduced. + The values must be within ``(0, 0, width, height)`` rectangle. + If omitted or ``None``, the entire source is used. + """ + if not isinstance(factor, (list, tuple)): + factor = (factor, factor) + + if box is None: + box = (0, 0) + self.size + + if factor == (1, 1) and box == (0, 0) + self.size: + return self.copy() + + if self.mode in ["LA", "RGBA"]: + im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) + im = im.reduce(factor, box) + return im.convert(self.mode) + + self.load() + + return self._new(self.im.reduce(factor, box)) + + def rotate( + self, + angle: float, + resample: Resampling = Resampling.NEAREST, + expand: int | bool = False, + center: tuple[float, float] | None = None, + translate: tuple[int, int] | None = None, + fillcolor: float | tuple[float, ...] | str | None = None, + ) -> Image: + """ + Returns a rotated copy of this image. This method returns a + copy of this image, rotated the given number of degrees counter + clockwise around its centre. + + :param angle: In degrees counter clockwise. + :param resample: An optional resampling filter. This can be + one of :py:data:`Resampling.NEAREST` (use nearest neighbour), + :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`Resampling.BICUBIC` (cubic spline + interpolation in a 4x4 environment). If omitted, or if the image has + mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. + See :ref:`concept-filters`. + :param expand: Optional expansion flag. If true, expands the output + image to make it large enough to hold the entire rotated image. + If false or omitted, make the output image the same size as the + input image. Note that the expand flag assumes rotation around + the center and no translation. + :param center: Optional center of rotation (a 2-tuple). Origin is + the upper left corner. Default is the center of the image. + :param translate: An optional post-rotate translation (a 2-tuple). + :param fillcolor: An optional color for area outside the rotated image. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + angle = angle % 360.0 + + # Fast paths regardless of filter, as long as we're not + # translating or changing the center. + if not (center or translate): + if angle == 0: + return self.copy() + if angle == 180: + return self.transpose(Transpose.ROTATE_180) + if angle in (90, 270) and (expand or self.width == self.height): + return self.transpose( + Transpose.ROTATE_90 if angle == 90 else Transpose.ROTATE_270 + ) + + # Calculate the affine matrix. Note that this is the reverse + # transformation (from destination image to source) because we + # want to interpolate the (discrete) destination pixel from + # the local area around the (floating) source pixel. + + # The matrix we actually want (note that it operates from the right): + # (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx) + # (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy) + # (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1) + + # The reverse matrix is thus: + # (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx) + # (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty) + # (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1) + + # In any case, the final translation may be updated at the end to + # compensate for the expand flag. + + w, h = self.size + + if translate is None: + post_trans = (0, 0) + else: + post_trans = translate + if center is None: + center = (w / 2, h / 2) + + angle = -math.radians(angle) + matrix = [ + round(math.cos(angle), 15), + round(math.sin(angle), 15), + 0.0, + round(-math.sin(angle), 15), + round(math.cos(angle), 15), + 0.0, + ] + + def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]: + (a, b, c, d, e, f) = matrix + return a * x + b * y + c, d * x + e * y + f + + matrix[2], matrix[5] = transform( + -center[0] - post_trans[0], -center[1] - post_trans[1], matrix + ) + matrix[2] += center[0] + matrix[5] += center[1] + + if expand: + # calculate output size + xx = [] + yy = [] + for x, y in ((0, 0), (w, 0), (w, h), (0, h)): + transformed_x, transformed_y = transform(x, y, matrix) + xx.append(transformed_x) + yy.append(transformed_y) + nw = math.ceil(max(xx)) - math.floor(min(xx)) + nh = math.ceil(max(yy)) - math.floor(min(yy)) + + # We multiply a translation matrix from the right. Because of its + # special form, this is the same as taking the image of the + # translation vector as new translation vector. + matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix) + w, h = nw, nh + + return self.transform( + (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor + ) + + def save( + self, fp: StrOrBytesPath | IO[bytes], format: str | None = None, **params: Any + ) -> None: + """ + Saves this image under the given filename. If no format is + specified, the format to use is determined from the filename + extension, if possible. + + Keyword options can be used to provide additional instructions + to the writer. If a writer doesn't recognise an option, it is + silently ignored. The available options are described in the + :doc:`image format documentation + <../handbook/image-file-formats>` for each writer. + + You can use a file object instead of a filename. In this case, + you must always specify the format. The file object must + implement the ``seek``, ``tell``, and ``write`` + methods, and be opened in binary mode. + + :param fp: A filename (string), os.PathLike object or file object. + :param format: Optional format override. If omitted, the + format to use is determined from the filename extension. + If a file object was used instead of a filename, this + parameter should always be used. + :param params: Extra parameters to the image writer. These can also be + set on the image itself through ``encoderinfo``. This is useful when + saving multiple images:: + + # Saving XMP data to a single image + from PIL import Image + red = Image.new("RGB", (1, 1), "#f00") + red.save("out.mpo", xmp=b"test") + + # Saving XMP data to the second frame of an image + from PIL import Image + black = Image.new("RGB", (1, 1)) + red = Image.new("RGB", (1, 1), "#f00") + red.encoderinfo = {"xmp": b"test"} + black.save("out.mpo", save_all=True, append_images=[red]) + :returns: None + :exception ValueError: If the output format could not be determined + from the file name. Use the format option to solve this. + :exception OSError: If the file could not be written. The file + may have been created, and may contain partial data. + """ + + filename: str | bytes = "" + open_fp = False + if is_path(fp): + filename = os.fspath(fp) + open_fp = True + elif fp == sys.stdout: + try: + fp = sys.stdout.buffer + except AttributeError: + pass + if not filename and hasattr(fp, "name") and is_path(fp.name): + # only set the name for metadata purposes + filename = os.fspath(fp.name) + + preinit() + + filename_ext = os.path.splitext(filename)[1].lower() + ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext + + if not format: + if ext not in EXTENSION: + init() + try: + format = EXTENSION[ext] + except KeyError as e: + msg = f"unknown file extension: {ext}" + raise ValueError(msg) from e + + from . import ImageFile + + # may mutate self! + if isinstance(self, ImageFile.ImageFile) and os.path.abspath( + filename + ) == os.path.abspath(self.filename): + self._ensure_mutable() + else: + self.load() + + save_all = params.pop("save_all", None) + self._default_encoderinfo = params + encoderinfo = getattr(self, "encoderinfo", {}) + self._attach_default_encoderinfo(self) + self.encoderconfig: tuple[Any, ...] = () + + if format.upper() not in SAVE: + init() + if save_all or ( + save_all is None + and params.get("append_images") + and format.upper() in SAVE_ALL + ): + save_handler = SAVE_ALL[format.upper()] + else: + save_handler = SAVE[format.upper()] + + created = False + if open_fp: + created = not os.path.exists(filename) + if params.get("append", False): + # Open also for reading ("+"), because TIFF save_all + # writer needs to go back and edit the written data. + fp = builtins.open(filename, "r+b") + else: + fp = builtins.open(filename, "w+b") + else: + fp = cast(IO[bytes], fp) + + try: + save_handler(self, fp, filename) + except Exception: + if open_fp: + fp.close() + if created: + try: + os.remove(filename) + except PermissionError: + pass + raise + finally: + self.encoderinfo = encoderinfo + if open_fp: + fp.close() + + def _attach_default_encoderinfo(self, im: Image) -> dict[str, Any]: + encoderinfo = getattr(self, "encoderinfo", {}) + self.encoderinfo = {**im._default_encoderinfo, **encoderinfo} + return encoderinfo + + def seek(self, frame: int) -> None: + """ + Seeks to the given frame in this sequence file. If you seek + beyond the end of the sequence, the method raises an + ``EOFError`` exception. When a sequence file is opened, the + library automatically seeks to frame 0. + + See :py:meth:`~PIL.Image.Image.tell`. + + If defined, :attr:`~PIL.Image.Image.n_frames` refers to the + number of available frames. + + :param frame: Frame number, starting at 0. + :exception EOFError: If the call attempts to seek beyond the end + of the sequence. + """ + + # overridden by file handlers + if frame != 0: + msg = "no more images in file" + raise EOFError(msg) + + def show(self, title: str | None = None) -> None: + """ + Displays this image. This method is mainly intended for debugging purposes. + + This method calls :py:func:`PIL.ImageShow.show` internally. You can use + :py:func:`PIL.ImageShow.register` to override its default behaviour. + + The image is first saved to a temporary file. By default, it will be in + PNG format. + + On Unix, the image is then opened using the **xdg-open**, **display**, + **gm**, **eog** or **xv** utility, depending on which one can be found. + + On macOS, the image is opened with the native Preview application. + + On Windows, the image is opened with the standard PNG display utility. + + :param title: Optional title to use for the image window, where possible. + """ + + _show(self, title=title) + + def split(self) -> tuple[Image, ...]: + """ + Split this image into individual bands. This method returns a + tuple of individual image bands from an image. For example, + splitting an "RGB" image creates three new images each + containing a copy of one of the original bands (red, green, + blue). + + If you need only one band, :py:meth:`~PIL.Image.Image.getchannel` + method can be more convenient and faster. + + :returns: A tuple containing bands. + """ + + self.load() + if self.im.bands == 1: + return (self.copy(),) + return tuple(map(self._new, self.im.split())) + + def getchannel(self, channel: int | str) -> Image: + """ + Returns an image containing a single channel of the source image. + + :param channel: What channel to return. Could be index + (0 for "R" channel of "RGB") or channel name + ("A" for alpha channel of "RGBA"). + :returns: An image in "L" mode. + + .. versionadded:: 4.3.0 + """ + self.load() + + if isinstance(channel, str): + try: + channel = self.getbands().index(channel) + except ValueError as e: + msg = f'The image has no channel "{channel}"' + raise ValueError(msg) from e + + return self._new(self.im.getband(channel)) + + def tell(self) -> int: + """ + Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. + + If defined, :attr:`~PIL.Image.Image.n_frames` refers to the + number of available frames. + + :returns: Frame number, starting with 0. + """ + return 0 + + def thumbnail( + self, + size: tuple[float, float], + resample: Resampling = Resampling.BICUBIC, + reducing_gap: float | None = 2.0, + ) -> None: + """ + Make this image into a thumbnail. This method modifies the + image to contain a thumbnail version of itself, no larger than + the given size. This method calculates an appropriate thumbnail + size to preserve the aspect of the image, calls the + :py:meth:`~PIL.Image.Image.draft` method to configure the file reader + (where applicable), and finally resizes the image. + + Note that this function modifies the :py:class:`~PIL.Image.Image` + object in place. If you need to use the full resolution image as well, + apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original + image. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param resample: Optional resampling filter. This can be one + of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`, + :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, + :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. + If omitted, it defaults to :py:data:`Resampling.BICUBIC`. + (was :py:data:`Resampling.NEAREST` prior to version 2.5.0). + See: :ref:`concept-filters`. + :param reducing_gap: Apply optimization by resizing the image + in two steps. First, reducing the image by integer times + using :py:meth:`~PIL.Image.Image.reduce` or + :py:meth:`~PIL.Image.Image.draft` for JPEG images. + Second, resizing using regular resampling. The last step + changes size no less than by ``reducing_gap`` times. + ``reducing_gap`` may be None (no first step is performed) + or should be greater than 1.0. The bigger ``reducing_gap``, + the closer the result to the fair resampling. + The smaller ``reducing_gap``, the faster resizing. + With ``reducing_gap`` greater or equal to 3.0, the result is + indistinguishable from fair resampling in most cases. + The default value is 2.0 (very close to fair resampling + while still being faster in many cases). + :returns: None + """ + + provided_size = tuple(map(math.floor, size)) + + def preserve_aspect_ratio() -> tuple[int, int] | None: + def round_aspect(number: float, key: Callable[[int], float]) -> int: + return max(min(math.floor(number), math.ceil(number), key=key), 1) + + x, y = provided_size + if x >= self.width and y >= self.height: + return None + + aspect = self.width / self.height + if x / y >= aspect: + x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y)) + else: + y = round_aspect( + x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n) + ) + return x, y + + preserved_size = preserve_aspect_ratio() + if preserved_size is None: + return + final_size = preserved_size + + box = None + if reducing_gap is not None: + res = self.draft( + None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap)) + ) + if res is not None: + box = res[1] + + if self.size != final_size: + im = self.resize(final_size, resample, box=box, reducing_gap=reducing_gap) + + self.im = im.im + self._size = final_size + self._mode = self.im.mode + + self.readonly = 0 + + # FIXME: the different transform methods need further explanation + # instead of bloating the method docs, add a separate chapter. + def transform( + self, + size: tuple[int, int], + method: Transform | ImageTransformHandler | SupportsGetData, + data: Sequence[Any] | None = None, + resample: int = Resampling.NEAREST, + fill: int = 1, + fillcolor: float | tuple[float, ...] | str | None = None, + ) -> Image: + """ + Transforms this image. This method creates a new image with the + given size, and the same mode as the original, and copies data + to the new image using the given transform. + + :param size: The output size in pixels, as a 2-tuple: + (width, height). + :param method: The transformation method. This is one of + :py:data:`Transform.EXTENT` (cut out a rectangular subregion), + :py:data:`Transform.AFFINE` (affine transform), + :py:data:`Transform.PERSPECTIVE` (perspective transform), + :py:data:`Transform.QUAD` (map a quadrilateral to a rectangle), or + :py:data:`Transform.MESH` (map a number of source quadrilaterals + in one operation). + + It may also be an :py:class:`~PIL.Image.ImageTransformHandler` + object:: + + class Example(Image.ImageTransformHandler): + def transform(self, size, data, resample, fill=1): + # Return result + + Implementations of :py:class:`~PIL.Image.ImageTransformHandler` + for some of the :py:class:`Transform` methods are provided + in :py:mod:`~PIL.ImageTransform`. + + It may also be an object with a ``method.getdata`` method + that returns a tuple supplying new ``method`` and ``data`` values:: + + class Example: + def getdata(self): + method = Image.Transform.EXTENT + data = (0, 0, 100, 100) + return method, data + :param data: Extra data to the transformation method. + :param resample: Optional resampling filter. It can be one of + :py:data:`Resampling.NEAREST` (use nearest neighbour), + :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`Resampling.BICUBIC` (cubic spline + interpolation in a 4x4 environment). If omitted, or if the image + has mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`. + See: :ref:`concept-filters`. + :param fill: If ``method`` is an + :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of + the arguments passed to it. Otherwise, it is unused. + :param fillcolor: Optional fill color for the area outside the + transform in the output image. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if self.mode in ("LA", "RGBA") and resample != Resampling.NEAREST: + return ( + self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) + .transform(size, method, data, resample, fill, fillcolor) + .convert(self.mode) + ) + + if isinstance(method, ImageTransformHandler): + return method.transform(size, self, resample=resample, fill=fill) + + if hasattr(method, "getdata"): + # compatibility w. old-style transform objects + method, data = method.getdata() + + if data is None: + msg = "missing method data" + raise ValueError(msg) + + im = new(self.mode, size, fillcolor) + if self.mode == "P" and self.palette: + im.palette = self.palette.copy() + im.info = self.info.copy() + if method == Transform.MESH: + # list of quads + for box, quad in data: + im.__transformer( + box, self, Transform.QUAD, quad, resample, fillcolor is None + ) + else: + im.__transformer( + (0, 0) + size, self, method, data, resample, fillcolor is None + ) + + return im + + def __transformer( + self, + box: tuple[int, int, int, int], + image: Image, + method: Transform, + data: Sequence[float], + resample: int = Resampling.NEAREST, + fill: bool = True, + ) -> None: + w = box[2] - box[0] + h = box[3] - box[1] + + if method == Transform.AFFINE: + data = data[:6] + + elif method == Transform.EXTENT: + # convert extent to an affine transform + x0, y0, x1, y1 = data + xs = (x1 - x0) / w + ys = (y1 - y0) / h + method = Transform.AFFINE + data = (xs, 0, x0, 0, ys, y0) + + elif method == Transform.PERSPECTIVE: + data = data[:8] + + elif method == Transform.QUAD: + # quadrilateral warp. data specifies the four corners + # given as NW, SW, SE, and NE. + nw = data[:2] + sw = data[2:4] + se = data[4:6] + ne = data[6:8] + x0, y0 = nw + As = 1.0 / w + At = 1.0 / h + data = ( + x0, + (ne[0] - x0) * As, + (sw[0] - x0) * At, + (se[0] - sw[0] - ne[0] + x0) * As * At, + y0, + (ne[1] - y0) * As, + (sw[1] - y0) * At, + (se[1] - sw[1] - ne[1] + y0) * As * At, + ) + + else: + msg = "unknown transformation method" + raise ValueError(msg) + + if resample not in ( + Resampling.NEAREST, + Resampling.BILINEAR, + Resampling.BICUBIC, + ): + if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS): + unusable: dict[int, str] = { + Resampling.BOX: "Image.Resampling.BOX", + Resampling.HAMMING: "Image.Resampling.HAMMING", + Resampling.LANCZOS: "Image.Resampling.LANCZOS", + } + msg = unusable[resample] + f" ({resample}) cannot be used." + else: + msg = f"Unknown resampling filter ({resample})." + + filters = [ + f"{filter[1]} ({filter[0]})" + for filter in ( + (Resampling.NEAREST, "Image.Resampling.NEAREST"), + (Resampling.BILINEAR, "Image.Resampling.BILINEAR"), + (Resampling.BICUBIC, "Image.Resampling.BICUBIC"), + ) + ] + msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}" + raise ValueError(msg) + + image.load() + + self.load() + + if image.mode in ("1", "P"): + resample = Resampling.NEAREST + + self.im.transform(box, image.im, method, data, resample, fill) + + def transpose(self, method: Transpose) -> Image: + """ + Transpose image (flip or rotate in 90 degree steps) + + :param method: One of :py:data:`Transpose.FLIP_LEFT_RIGHT`, + :py:data:`Transpose.FLIP_TOP_BOTTOM`, :py:data:`Transpose.ROTATE_90`, + :py:data:`Transpose.ROTATE_180`, :py:data:`Transpose.ROTATE_270`, + :py:data:`Transpose.TRANSPOSE` or :py:data:`Transpose.TRANSVERSE`. + :returns: Returns a flipped or rotated copy of this image. + """ + + self.load() + return self._new(self.im.transpose(method)) + + def effect_spread(self, distance: int) -> Image: + """ + Randomly spread pixels in an image. + + :param distance: Distance to spread pixels. + """ + self.load() + return self._new(self.im.effect_spread(distance)) + + def toqimage(self) -> ImageQt.ImageQt: + """Returns a QImage copy of this image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.toqimage(self) + + def toqpixmap(self) -> ImageQt.QPixmap: + """Returns a QPixmap copy of this image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.toqpixmap(self) + + +# -------------------------------------------------------------------- +# Abstract handlers. + + +class ImagePointHandler(abc.ABC): + """ + Used as a mixin by point transforms + (for use with :py:meth:`~PIL.Image.Image.point`) + """ + + @abc.abstractmethod + def point(self, im: Image) -> Image: + pass + + +class ImageTransformHandler(abc.ABC): + """ + Used as a mixin by geometry transforms + (for use with :py:meth:`~PIL.Image.Image.transform`) + """ + + @abc.abstractmethod + def transform( + self, + size: tuple[int, int], + image: Image, + **options: Any, + ) -> Image: + pass + + +# -------------------------------------------------------------------- +# Factories + + +def _check_size(size: Any) -> None: + """ + Common check to enforce type and sanity check on size tuples + + :param size: Should be a 2 tuple of (width, height) + :returns: None, or raises a ValueError + """ + + if not isinstance(size, (list, tuple)): + msg = "Size must be a list or tuple" + raise ValueError(msg) + if len(size) != 2: + msg = "Size must be a sequence of length 2" + raise ValueError(msg) + if size[0] < 0 or size[1] < 0: + msg = "Width and height must be >= 0" + raise ValueError(msg) + + +def new( + mode: str, + size: tuple[int, int] | list[int], + color: float | tuple[float, ...] | str | None = 0, +) -> Image: + """ + Creates a new image with the given mode and size. + + :param mode: The mode to use for the new image. See: + :ref:`concept-modes`. + :param size: A 2-tuple, containing (width, height) in pixels. + :param color: What color to use for the image. Default is black. + If given, this should be a single integer or floating point value + for single-band modes, and a tuple for multi-band modes (one value + per band). When creating RGB or HSV images, you can also use color + strings as supported by the ImageColor module. If the color is + None, the image is not initialised. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if mode in ("BGR;15", "BGR;16", "BGR;24"): + deprecate(mode, 12) + + _check_size(size) + + if color is None: + # don't initialize + return Image()._new(core.new(mode, size)) + + if isinstance(color, str): + # css3-style specifier + + from . import ImageColor + + color = ImageColor.getcolor(color, mode) + + im = Image() + if ( + mode == "P" + and isinstance(color, (list, tuple)) + and all(isinstance(i, int) for i in color) + ): + color_ints: tuple[int, ...] = cast(tuple[int, ...], tuple(color)) + if len(color_ints) == 3 or len(color_ints) == 4: + # RGB or RGBA value for a P image + from . import ImagePalette + + im.palette = ImagePalette.ImagePalette() + color = im.palette.getcolor(color_ints) + return im._new(core.fill(mode, size, color)) + + +def frombytes( + mode: str, + size: tuple[int, int], + data: bytes | bytearray | SupportsArrayInterface, + decoder_name: str = "raw", + *args: Any, +) -> Image: + """ + Creates a copy of an image memory from pixel data in a buffer. + + In its simplest form, this function takes three arguments + (mode, size, and unpacked pixel data). + + You can also use any pixel decoder supported by PIL. For more + information on available decoders, see the section + :ref:`Writing Your Own File Codec `. + + Note that this function decodes pixel data only, not entire images. + If you have an entire image in a string, wrap it in a + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load + it. + + :param mode: The image mode. See: :ref:`concept-modes`. + :param size: The image size. + :param data: A byte buffer containing raw data for the given mode. + :param decoder_name: What decoder to use. + :param args: Additional parameters for the given decoder. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + _check_size(size) + + im = new(mode, size) + if im.width != 0 and im.height != 0: + decoder_args: Any = args + if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple): + # may pass tuple instead of argument list + decoder_args = decoder_args[0] + + if decoder_name == "raw" and decoder_args == (): + decoder_args = mode + + im.frombytes(data, decoder_name, decoder_args) + return im + + +def frombuffer( + mode: str, + size: tuple[int, int], + data: bytes | SupportsArrayInterface, + decoder_name: str = "raw", + *args: Any, +) -> Image: + """ + Creates an image memory referencing pixel data in a byte buffer. + + This function is similar to :py:func:`~PIL.Image.frombytes`, but uses data + in the byte buffer, where possible. This means that changes to the + original buffer object are reflected in this image). Not all modes can + share memory; supported modes include "L", "RGBX", "RGBA", and "CMYK". + + Note that this function decodes pixel data only, not entire images. + If you have an entire image file in a string, wrap it in a + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. + + The default parameters used for the "raw" decoder differs from that used for + :py:func:`~PIL.Image.frombytes`. This is a bug, and will probably be fixed in a + future release. The current release issues a warning if you do this; to disable + the warning, you should provide the full set of parameters. See below for details. + + :param mode: The image mode. See: :ref:`concept-modes`. + :param size: The image size. + :param data: A bytes or other buffer object containing raw + data for the given mode. + :param decoder_name: What decoder to use. + :param args: Additional parameters for the given decoder. For the + default encoder ("raw"), it's recommended that you provide the + full set of parameters:: + + frombuffer(mode, size, data, "raw", mode, 0, 1) + + :returns: An :py:class:`~PIL.Image.Image` object. + + .. versionadded:: 1.1.4 + """ + + _check_size(size) + + # may pass tuple instead of argument list + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] + + if decoder_name == "raw": + if args == (): + args = mode, 0, 1 + if args[0] in _MAPMODES: + im = new(mode, (0, 0)) + im = im._new(core.map_buffer(data, size, decoder_name, 0, args)) + if mode == "P": + from . import ImagePalette + + im.palette = ImagePalette.ImagePalette("RGB", im.im.getpalette("RGB")) + im.readonly = 1 + return im + + return frombytes(mode, size, data, decoder_name, args) + + +class SupportsArrayInterface(Protocol): + """ + An object that has an ``__array_interface__`` dictionary. + """ + + @property + def __array_interface__(self) -> dict[str, Any]: + raise NotImplementedError() + + +class SupportsArrowArrayInterface(Protocol): + """ + An object that has an ``__arrow_c_array__`` method corresponding to the arrow c + data interface. + """ + + def __arrow_c_array__( + self, requested_schema: "PyCapsule" = None # type: ignore[name-defined] # noqa: F821, UP037 + ) -> tuple["PyCapsule", "PyCapsule"]: # type: ignore[name-defined] # noqa: F821, UP037 + raise NotImplementedError() + + +def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: + """ + Creates an image memory from an object exporting the array interface + (using the buffer protocol):: + + from PIL import Image + import numpy as np + a = np.zeros((5, 5)) + im = Image.fromarray(a) + + If ``obj`` is not contiguous, then the ``tobytes`` method is called + and :py:func:`~PIL.Image.frombuffer` is used. + + In the case of NumPy, be aware that Pillow modes do not always correspond + to NumPy dtypes. Pillow modes only offer 1-bit pixels, 8-bit pixels, + 32-bit signed integer pixels, and 32-bit floating point pixels. + + Pillow images can also be converted to arrays:: + + from PIL import Image + import numpy as np + im = Image.open("hopper.jpg") + a = np.asarray(im) + + When converting Pillow images to arrays however, only pixel values are + transferred. This means that P and PA mode images will lose their palette. + + :param obj: Object with array interface + :param mode: Optional mode to use when reading ``obj``. Will be determined from + type if ``None``. Deprecated. + + This will not be used to convert the data after reading, but will be used to + change how the data is read:: + + from PIL import Image + import numpy as np + a = np.full((1, 1), 300) + im = Image.fromarray(a, mode="L") + im.getpixel((0, 0)) # 44 + im = Image.fromarray(a, mode="RGB") + im.getpixel((0, 0)) # (44, 1, 0) + + See: :ref:`concept-modes` for general information about modes. + :returns: An image object. + + .. versionadded:: 1.1.6 + """ + arr = obj.__array_interface__ + shape = arr["shape"] + ndim = len(shape) + strides = arr.get("strides", None) + if mode is None: + try: + typekey = (1, 1) + shape[2:], arr["typestr"] + except KeyError as e: + msg = "Cannot handle this data type" + raise TypeError(msg) from e + try: + mode, rawmode = _fromarray_typemap[typekey] + except KeyError as e: + typekey_shape, typestr = typekey + msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" + raise TypeError(msg) from e + else: + deprecate("'mode' parameter", 13) + rawmode = mode + if mode in ["1", "L", "I", "P", "F"]: + ndmax = 2 + elif mode == "RGB": + ndmax = 3 + else: + ndmax = 4 + if ndim > ndmax: + msg = f"Too many dimensions: {ndim} > {ndmax}." + raise ValueError(msg) + + size = 1 if ndim == 1 else shape[1], shape[0] + if strides is not None: + if hasattr(obj, "tobytes"): + obj = obj.tobytes() + elif hasattr(obj, "tostring"): + obj = obj.tostring() + else: + msg = "'strides' requires either tobytes() or tostring()" + raise ValueError(msg) + + return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) + + +def fromarrow( + obj: SupportsArrowArrayInterface, mode: str, size: tuple[int, int] +) -> Image: + """Creates an image with zero-copy shared memory from an object exporting + the arrow_c_array interface protocol:: + + from PIL import Image + import pyarrow as pa + arr = pa.array([0]*(5*5*4), type=pa.uint8()) + im = Image.fromarrow(arr, 'RGBA', (5, 5)) + + If the data representation of the ``obj`` is not compatible with + Pillow internal storage, a ValueError is raised. + + Pillow images can also be converted to Arrow objects:: + + from PIL import Image + import pyarrow as pa + im = Image.open('hopper.jpg') + arr = pa.array(im) + + As with array support, when converting Pillow images to arrays, + only pixel values are transferred. This means that P and PA mode + images will lose their palette. + + :param obj: Object with an arrow_c_array interface + :param mode: Image mode. + :param size: Image size. This must match the storage of the arrow object. + :returns: An Image object + + Note that according to the Arrow spec, both the producer and the + consumer should consider the exported array to be immutable, as + unsynchronized updates will potentially cause inconsistent data. + + See: :ref:`arrow-support` for more detailed information + + .. versionadded:: 11.2.1 + + """ + if not hasattr(obj, "__arrow_c_array__"): + msg = "arrow_c_array interface not found" + raise ValueError(msg) + + (schema_capsule, array_capsule) = obj.__arrow_c_array__() + _im = core.new_arrow(mode, size, schema_capsule, array_capsule) + if _im: + return Image()._new(_im) + + msg = "new_arrow returned None without an exception" + raise ValueError(msg) + + +def fromqimage(im: ImageQt.QImage) -> ImageFile.ImageFile: + """Creates an image instance from a QImage image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.fromqimage(im) + + +def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile: + """Creates an image instance from a QPixmap image""" + from . import ImageQt + + if not ImageQt.qt_is_installed: + msg = "Qt bindings are not installed" + raise ImportError(msg) + return ImageQt.fromqpixmap(im) + + +_fromarray_typemap = { + # (shape, typestr) => mode, rawmode + # first two members of shape are set to one + ((1, 1), "|b1"): ("1", "1;8"), + ((1, 1), "|u1"): ("L", "L"), + ((1, 1), "|i1"): ("I", "I;8"), + ((1, 1), "u2"): ("I", "I;16B"), + ((1, 1), "i2"): ("I", "I;16BS"), + ((1, 1), "u4"): ("I", "I;32B"), + ((1, 1), "i4"): ("I", "I;32BS"), + ((1, 1), "f4"): ("F", "F;32BF"), + ((1, 1), "f8"): ("F", "F;64BF"), + ((1, 1, 2), "|u1"): ("LA", "LA"), + ((1, 1, 3), "|u1"): ("RGB", "RGB"), + ((1, 1, 4), "|u1"): ("RGBA", "RGBA"), + # shortcuts: + ((1, 1), f"{_ENDIAN}i4"): ("I", "I"), + ((1, 1), f"{_ENDIAN}f4"): ("F", "F"), +} + + +def _decompression_bomb_check(size: tuple[int, int]) -> None: + if MAX_IMAGE_PIXELS is None: + return + + pixels = max(1, size[0]) * max(1, size[1]) + + if pixels > 2 * MAX_IMAGE_PIXELS: + msg = ( + f"Image size ({pixels} pixels) exceeds limit of {2 * MAX_IMAGE_PIXELS} " + "pixels, could be decompression bomb DOS attack." + ) + raise DecompressionBombError(msg) + + if pixels > MAX_IMAGE_PIXELS: + warnings.warn( + f"Image size ({pixels} pixels) exceeds limit of {MAX_IMAGE_PIXELS} pixels, " + "could be decompression bomb DOS attack.", + DecompressionBombWarning, + ) + + +def open( + fp: StrOrBytesPath | IO[bytes], + mode: Literal["r"] = "r", + formats: list[str] | tuple[str, ...] | None = None, +) -> ImageFile.ImageFile: + """ + Opens and identifies the given image file. + + This is a lazy operation; this function identifies the file, but + the file remains open and the actual image data is not read from + the file until you try to process the data (or call the + :py:meth:`~PIL.Image.Image.load` method). See + :py:func:`~PIL.Image.new`. See :ref:`file-handling`. + + :param fp: A filename (string), os.PathLike object or a file object. + The file object must implement ``file.read``, + ``file.seek``, and ``file.tell`` methods, + and be opened in binary mode. The file object will also seek to zero + before reading. + :param mode: The mode. If given, this argument must be "r". + :param formats: A list or tuple of formats to attempt to load the file in. + This can be used to restrict the set of formats checked. + Pass ``None`` to try all supported formats. You can print the set of + available formats by running ``python3 -m PIL`` or using + the :py:func:`PIL.features.pilinfo` function. + :returns: An :py:class:`~PIL.Image.Image` object. + :exception FileNotFoundError: If the file cannot be found. + :exception PIL.UnidentifiedImageError: If the image cannot be opened and + identified. + :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO`` + instance is used for ``fp``. + :exception TypeError: If ``formats`` is not ``None``, a list or a tuple. + """ + + if mode != "r": + msg = f"bad mode {repr(mode)}" # type: ignore[unreachable] + raise ValueError(msg) + elif isinstance(fp, io.StringIO): + msg = ( # type: ignore[unreachable] + "StringIO cannot be used to open an image. " + "Binary data must be used instead." + ) + raise ValueError(msg) + + if formats is None: + formats = ID + elif not isinstance(formats, (list, tuple)): + msg = "formats must be a list or tuple" # type: ignore[unreachable] + raise TypeError(msg) + + exclusive_fp = False + filename: str | bytes = "" + if is_path(fp): + filename = os.fspath(fp) + fp = builtins.open(filename, "rb") + exclusive_fp = True + else: + fp = cast(IO[bytes], fp) + + try: + fp.seek(0) + except (AttributeError, io.UnsupportedOperation): + fp = io.BytesIO(fp.read()) + exclusive_fp = True + + prefix = fp.read(16) + + preinit() + + warning_messages: list[str] = [] + + def _open_core( + fp: IO[bytes], + filename: str | bytes, + prefix: bytes, + formats: list[str] | tuple[str, ...], + ) -> ImageFile.ImageFile | None: + for i in formats: + i = i.upper() + if i not in OPEN: + init() + try: + factory, accept = OPEN[i] + result = not accept or accept(prefix) + if isinstance(result, str): + warning_messages.append(result) + elif result: + fp.seek(0) + im = factory(fp, filename) + _decompression_bomb_check(im.size) + return im + except (SyntaxError, IndexError, TypeError, struct.error) as e: + if WARN_POSSIBLE_FORMATS: + warning_messages.append(i + " opening failed. " + str(e)) + except BaseException: + if exclusive_fp: + fp.close() + raise + return None + + im = _open_core(fp, filename, prefix, formats) + + if im is None and formats is ID: + checked_formats = ID.copy() + if init(): + im = _open_core( + fp, + filename, + prefix, + tuple(format for format in formats if format not in checked_formats), + ) + + if im: + im._exclusive_fp = exclusive_fp + return im + + if exclusive_fp: + fp.close() + for message in warning_messages: + warnings.warn(message) + msg = "cannot identify image file %r" % (filename if filename else fp) + raise UnidentifiedImageError(msg) + + +# +# Image processing. + + +def alpha_composite(im1: Image, im2: Image) -> Image: + """ + Alpha composite im2 over im1. + + :param im1: The first image. Must have mode RGBA. + :param im2: The second image. Must have mode RGBA, and the same size as + the first image. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + im1.load() + im2.load() + return im1._new(core.alpha_composite(im1.im, im2.im)) + + +def blend(im1: Image, im2: Image, alpha: float) -> Image: + """ + Creates a new image by interpolating between two input images, using + a constant alpha:: + + out = image1 * (1.0 - alpha) + image2 * alpha + + :param im1: The first image. + :param im2: The second image. Must have the same mode and size as + the first image. + :param alpha: The interpolation alpha factor. If alpha is 0.0, a + copy of the first image is returned. If alpha is 1.0, a copy of + the second image is returned. There are no restrictions on the + alpha value. If necessary, the result is clipped to fit into + the allowed output range. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + im1.load() + im2.load() + return im1._new(core.blend(im1.im, im2.im, alpha)) + + +def composite(image1: Image, image2: Image, mask: Image) -> Image: + """ + Create composite image by blending images using a transparency mask. + + :param image1: The first image. + :param image2: The second image. Must have the same mode and + size as the first image. + :param mask: A mask image. This image can have mode + "1", "L", or "RGBA", and must have the same size as the + other two images. + """ + + image = image2.copy() + image.paste(image1, None, mask) + return image + + +def eval(image: Image, *args: Callable[[int], float]) -> Image: + """ + Applies the function (which should take one argument) to each pixel + in the given image. If the image has more than one band, the same + function is applied to each band. Note that the function is + evaluated once for each possible pixel value, so you cannot use + random components or other generators. + + :param image: The input image. + :param function: A function object, taking one integer argument. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + return image.point(args[0]) + + +def merge(mode: str, bands: Sequence[Image]) -> Image: + """ + Merge a set of single band images into a new multiband image. + + :param mode: The mode to use for the output image. See: + :ref:`concept-modes`. + :param bands: A sequence containing one single-band image for + each band in the output image. All bands must have the + same size. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + + if getmodebands(mode) != len(bands) or "*" in mode: + msg = "wrong number of bands" + raise ValueError(msg) + for band in bands[1:]: + if band.mode != getmodetype(mode): + msg = "mode mismatch" + raise ValueError(msg) + if band.size != bands[0].size: + msg = "size mismatch" + raise ValueError(msg) + for band in bands: + band.load() + return bands[0]._new(core.merge(mode, *[b.im for b in bands])) + + +# -------------------------------------------------------------------- +# Plugin registry + + +def register_open( + id: str, + factory: ( + Callable[[IO[bytes], str | bytes], ImageFile.ImageFile] + | type[ImageFile.ImageFile] + ), + accept: Callable[[bytes], bool | str] | None = None, +) -> None: + """ + Register an image file plugin. This function should not be used + in application code. + + :param id: An image format identifier. + :param factory: An image file factory method. + :param accept: An optional function that can be used to quickly + reject images having another format. + """ + id = id.upper() + if id not in ID: + ID.append(id) + OPEN[id] = factory, accept + + +def register_mime(id: str, mimetype: str) -> None: + """ + Registers an image MIME type by populating ``Image.MIME``. This function + should not be used in application code. + + ``Image.MIME`` provides a mapping from image format identifiers to mime + formats, but :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype` can + provide a different result for specific images. + + :param id: An image format identifier. + :param mimetype: The image MIME type for this format. + """ + MIME[id.upper()] = mimetype + + +def register_save( + id: str, driver: Callable[[Image, IO[bytes], str | bytes], None] +) -> None: + """ + Registers an image save function. This function should not be + used in application code. + + :param id: An image format identifier. + :param driver: A function to save images in this format. + """ + SAVE[id.upper()] = driver + + +def register_save_all( + id: str, driver: Callable[[Image, IO[bytes], str | bytes], None] +) -> None: + """ + Registers an image function to save all the frames + of a multiframe format. This function should not be + used in application code. + + :param id: An image format identifier. + :param driver: A function to save images in this format. + """ + SAVE_ALL[id.upper()] = driver + + +def register_extension(id: str, extension: str) -> None: + """ + Registers an image extension. This function should not be + used in application code. + + :param id: An image format identifier. + :param extension: An extension used for this format. + """ + EXTENSION[extension.lower()] = id.upper() + + +def register_extensions(id: str, extensions: list[str]) -> None: + """ + Registers image extensions. This function should not be + used in application code. + + :param id: An image format identifier. + :param extensions: A list of extensions used for this format. + """ + for extension in extensions: + register_extension(id, extension) + + +def registered_extensions() -> dict[str, str]: + """ + Returns a dictionary containing all file extensions belonging + to registered plugins + """ + init() + return EXTENSION + + +def register_decoder(name: str, decoder: type[ImageFile.PyDecoder]) -> None: + """ + Registers an image decoder. This function should not be + used in application code. + + :param name: The name of the decoder + :param decoder: An ImageFile.PyDecoder object + + .. versionadded:: 4.1.0 + """ + DECODERS[name] = decoder + + +def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: + """ + Registers an image encoder. This function should not be + used in application code. + + :param name: The name of the encoder + :param encoder: An ImageFile.PyEncoder object + + .. versionadded:: 4.1.0 + """ + ENCODERS[name] = encoder + + +# -------------------------------------------------------------------- +# Simple display support. + + +def _show(image: Image, **options: Any) -> None: + from . import ImageShow + + ImageShow.show(image, **options) + + +# -------------------------------------------------------------------- +# Effects + + +def effect_mandelbrot( + size: tuple[int, int], extent: tuple[float, float, float, float], quality: int +) -> Image: + """ + Generate a Mandelbrot set covering the given extent. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param extent: The extent to cover, as a 4-tuple: + (x0, y0, x1, y1). + :param quality: Quality. + """ + return Image()._new(core.effect_mandelbrot(size, extent, quality)) + + +def effect_noise(size: tuple[int, int], sigma: float) -> Image: + """ + Generate Gaussian noise centered around 128. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param sigma: Standard deviation of noise. + """ + return Image()._new(core.effect_noise(size, sigma)) + + +def linear_gradient(mode: str) -> Image: + """ + Generate 256x256 linear gradient from black to white, top to bottom. + + :param mode: Input mode. + """ + return Image()._new(core.linear_gradient(mode)) + + +def radial_gradient(mode: str) -> Image: + """ + Generate 256x256 radial gradient from black to white, centre to edge. + + :param mode: Input mode. + """ + return Image()._new(core.radial_gradient(mode)) + + +# -------------------------------------------------------------------- +# Resources + + +def _apply_env_variables(env: dict[str, str] | None = None) -> None: + env_dict = env if env is not None else os.environ + + for var_name, setter in [ + ("PILLOW_ALIGNMENT", core.set_alignment), + ("PILLOW_BLOCK_SIZE", core.set_block_size), + ("PILLOW_BLOCKS_MAX", core.set_blocks_max), + ]: + if var_name not in env_dict: + continue + + var = env_dict[var_name].lower() + + units = 1 + for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]: + if var.endswith(postfix): + units = mul + var = var[: -len(postfix)] + + try: + var_int = int(var) * units + except ValueError: + warnings.warn(f"{var_name} is not int") + continue + + try: + setter(var_int) + except ValueError as e: + warnings.warn(f"{var_name}: {e}") + + +_apply_env_variables() +atexit.register(core.clear_cache) + + +if TYPE_CHECKING: + _ExifBase = MutableMapping[int, Any] +else: + _ExifBase = MutableMapping + + +class Exif(_ExifBase): + """ + This class provides read and write access to EXIF image data:: + + from PIL import Image + im = Image.open("exif.png") + exif = im.getexif() # Returns an instance of this class + + Information can be read and written, iterated over or deleted:: + + print(exif[274]) # 1 + exif[274] = 2 + for k, v in exif.items(): + print("Tag", k, "Value", v) # Tag 274 Value 2 + del exif[274] + + To access information beyond IFD0, :py:meth:`~PIL.Image.Exif.get_ifd` + returns a dictionary:: + + from PIL import ExifTags + im = Image.open("exif_gps.jpg") + exif = im.getexif() + gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo) + print(gps_ifd) + + Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``, + ``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``. + + :py:mod:`~PIL.ExifTags` also has enum classes to provide names for data:: + + print(exif[ExifTags.Base.Software]) # PIL + print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99 + """ + + endian: str | None = None + bigtiff = False + _loaded = False + + def __init__(self) -> None: + self._data: dict[int, Any] = {} + self._hidden_data: dict[int, Any] = {} + self._ifds: dict[int, dict[int, Any]] = {} + self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None + self._loaded_exif: bytes | None = None + + def _fixup(self, value: Any) -> Any: + try: + if len(value) == 1 and isinstance(value, tuple): + return value[0] + except Exception: + pass + return value + + def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]: + # Helper function + # returns a dict with any single item tuples/lists as individual values + return {k: self._fixup(v) for k, v in src_dict.items()} + + def _get_ifd_dict( + self, offset: int, group: int | None = None + ) -> dict[int, Any] | None: + try: + # an offset pointer to the location of the nested embedded IFD. + # It should be a long, but may be corrupted. + self.fp.seek(offset) + except (KeyError, TypeError): + return None + else: + from . import TiffImagePlugin + + info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group) + info.load(self.fp) + return self._fixup_dict(dict(info)) + + def _get_head(self) -> bytes: + version = b"\x2b" if self.bigtiff else b"\x2a" + if self.endian == "<": + head = b"II" + version + b"\x00" + o32le(8) + else: + head = b"MM\x00" + version + o32be(8) + if self.bigtiff: + head += o32le(8) if self.endian == "<" else o32be(8) + head += b"\x00\x00\x00\x00" + return head + + def load(self, data: bytes) -> None: + # Extract EXIF information. This is highly experimental, + # and is likely to be replaced with something better in a future + # version. + + # The EXIF record consists of a TIFF file embedded in a JPEG + # application marker (!). + if data == self._loaded_exif: + return + self._loaded_exif = data + self._data.clear() + self._hidden_data.clear() + self._ifds.clear() + while data and data.startswith(b"Exif\x00\x00"): + data = data[6:] + if not data: + self._info = None + return + + self.fp: IO[bytes] = io.BytesIO(data) + self.head = self.fp.read(8) + # process dictionary + from . import TiffImagePlugin + + self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head) + self.endian = self._info._endian + self.fp.seek(self._info.next) + self._info.load(self.fp) + + def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None: + self._loaded_exif = None + self._data.clear() + self._hidden_data.clear() + self._ifds.clear() + + # process dictionary + from . import TiffImagePlugin + + self.fp = fp + if offset is not None: + self.head = self._get_head() + else: + self.head = self.fp.read(8) + self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head) + if self.endian is None: + self.endian = self._info._endian + if offset is None: + offset = self._info.next + self.fp.tell() + self.fp.seek(offset) + self._info.load(self.fp) + + def _get_merged_dict(self) -> dict[int, Any]: + merged_dict = dict(self) + + # get EXIF extension + if ExifTags.IFD.Exif in self: + ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif], ExifTags.IFD.Exif) + if ifd: + merged_dict.update(ifd) + + # GPS + if ExifTags.IFD.GPSInfo in self: + merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict( + self[ExifTags.IFD.GPSInfo], ExifTags.IFD.GPSInfo + ) + + return merged_dict + + def tobytes(self, offset: int = 8) -> bytes: + from . import TiffImagePlugin + + head = self._get_head() + ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) + for tag, ifd_dict in self._ifds.items(): + if tag not in self: + ifd[tag] = ifd_dict + for tag, value in self.items(): + if tag in [ + ExifTags.IFD.Exif, + ExifTags.IFD.GPSInfo, + ] and not isinstance(value, dict): + value = self.get_ifd(tag) + if ( + tag == ExifTags.IFD.Exif + and ExifTags.IFD.Interop in value + and not isinstance(value[ExifTags.IFD.Interop], dict) + ): + value = value.copy() + value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop) + ifd[tag] = value + return b"Exif\x00\x00" + head + ifd.tobytes(offset) + + def get_ifd(self, tag: int) -> dict[int, Any]: + if tag not in self._ifds: + if tag == ExifTags.IFD.IFD1: + if self._info is not None and self._info.next != 0: + ifd = self._get_ifd_dict(self._info.next) + if ifd is not None: + self._ifds[tag] = ifd + elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]: + offset = self._hidden_data.get(tag, self.get(tag)) + if offset is not None: + ifd = self._get_ifd_dict(offset, tag) + if ifd is not None: + self._ifds[tag] = ifd + elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]: + if ExifTags.IFD.Exif not in self._ifds: + self.get_ifd(ExifTags.IFD.Exif) + tag_data = self._ifds[ExifTags.IFD.Exif][tag] + if tag == ExifTags.IFD.MakerNote: + from .TiffImagePlugin import ImageFileDirectory_v2 + + if tag_data.startswith(b"FUJIFILM"): + ifd_offset = i32le(tag_data, 8) + ifd_data = tag_data[ifd_offset:] + + makernote = {} + for i in range(struct.unpack(" 4: + (offset,) = struct.unpack("H", tag_data[:2])[0]): + ifd_tag, typ, count, data = struct.unpack( + ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) + if ifd_tag == 0x1101: + # CameraInfo + (offset,) = struct.unpack(">L", data) + self.fp.seek(offset) + + camerainfo: dict[str, int | bytes] = { + "ModelID": self.fp.read(4) + } + + self.fp.read(4) + # Seconds since 2000 + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) + + self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) + + self.fp.read(12) + parallax = self.fp.read(4) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + )[0] + + self.fp.read(4) + camerainfo["Category"] = self.fp.read(2) + + makernote = {0x1101: camerainfo} + self._ifds[tag] = makernote + else: + # Interop + ifd = self._get_ifd_dict(tag_data, tag) + if ifd is not None: + self._ifds[tag] = ifd + ifd = self._ifds.setdefault(tag, {}) + if tag == ExifTags.IFD.Exif and self._hidden_data: + ifd = { + k: v + for (k, v) in ifd.items() + if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote) + } + return ifd + + def hide_offsets(self) -> None: + for tag in (ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo): + if tag in self: + self._hidden_data[tag] = self[tag] + del self[tag] + + def __str__(self) -> str: + if self._info is not None: + # Load all keys into self._data + for tag in self._info: + self[tag] + + return str(self._data) + + def __len__(self) -> int: + keys = set(self._data) + if self._info is not None: + keys.update(self._info) + return len(keys) + + def __getitem__(self, tag: int) -> Any: + if self._info is not None and tag not in self._data and tag in self._info: + self._data[tag] = self._fixup(self._info[tag]) + del self._info[tag] + return self._data[tag] + + def __contains__(self, tag: object) -> bool: + return tag in self._data or (self._info is not None and tag in self._info) + + def __setitem__(self, tag: int, value: Any) -> None: + if self._info is not None and tag in self._info: + del self._info[tag] + self._data[tag] = value + + def __delitem__(self, tag: int) -> None: + if self._info is not None and tag in self._info: + del self._info[tag] + else: + del self._data[tag] + + def __iter__(self) -> Iterator[int]: + keys = set(self._data) + if self._info is not None: + keys.update(self._info) + return iter(keys) diff --git a/venv/Lib/site-packages/PIL/ImageChops.py b/venv/Lib/site-packages/PIL/ImageChops.py new file mode 100644 index 00000000..29a5c995 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageChops.py @@ -0,0 +1,311 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard channel operations +# +# History: +# 1996-03-24 fl Created +# 1996-08-13 fl Added logical operations (for "1" images) +# 2000-10-12 fl Added offset method (from Image.py) +# +# Copyright (c) 1997-2000 by Secret Labs AB +# Copyright (c) 1996-2000 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + +from __future__ import annotations + +from . import Image + + +def constant(image: Image.Image, value: int) -> Image.Image: + """Fill a channel with a given gray level. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return Image.new("L", image.size, value) + + +def duplicate(image: Image.Image) -> Image.Image: + """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return image.copy() + + +def invert(image: Image.Image) -> Image.Image: + """ + Invert an image (channel). :: + + out = MAX - image + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image.load() + return image._new(image.im.chop_invert()) + + +def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Compares the two images, pixel by pixel, and returns a new image containing + the lighter values. :: + + out = max(image1, image2) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_lighter(image2.im)) + + +def darker(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Compares the two images, pixel by pixel, and returns a new image containing + the darker values. :: + + out = min(image1, image2) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_darker(image2.im)) + + +def difference(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Returns the absolute value of the pixel-by-pixel difference between the two + images. :: + + out = abs(image1 - image2) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_difference(image2.im)) + + +def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other. + + If you multiply an image with a solid black image, the result is black. If + you multiply with a solid white image, the image is unaffected. :: + + out = image1 * image2 / MAX + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_multiply(image2.im)) + + +def screen(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two inverted images on top of each other. :: + + out = MAX - ((MAX - image1) * (MAX - image2) / MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_screen(image2.im)) + + +def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other using the Soft Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_soft_light(image2.im)) + + +def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other using the Hard Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_hard_light(image2.im)) + + +def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image: + """ + Superimposes two images on top of each other using the Overlay algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_overlay(image2.im)) + + +def add( + image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0 +) -> Image.Image: + """ + Adds two images, dividing the result by scale and adding the + offset. If omitted, scale defaults to 1.0, and offset to 0.0. :: + + out = ((image1 + image2) / scale + offset) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_add(image2.im, scale, offset)) + + +def subtract( + image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0 +) -> Image.Image: + """ + Subtracts two images, dividing the result by scale and adding the offset. + If omitted, scale defaults to 1.0, and offset to 0.0. :: + + out = ((image1 - image2) / scale + offset) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_subtract(image2.im, scale, offset)) + + +def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Add two images, without clipping the result. :: + + out = ((image1 + image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_add_modulo(image2.im)) + + +def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Subtract two images, without clipping the result. :: + + out = ((image1 - image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_subtract_modulo(image2.im)) + + +def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Logical AND between two images. + + Both of the images must have mode "1". If you would like to perform a + logical AND on an image with a mode other than "1", try + :py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask + as the second image. :: + + out = ((image1 and image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_and(image2.im)) + + +def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Logical OR between two images. + + Both of the images must have mode "1". :: + + out = ((image1 or image2) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_or(image2.im)) + + +def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image: + """Logical XOR between two images. + + Both of the images must have mode "1". :: + + out = ((bool(image1) != bool(image2)) % MAX) + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_xor(image2.im)) + + +def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image: + """Blend images using constant transparency weight. Alias for + :py:func:`PIL.Image.blend`. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return Image.blend(image1, image2, alpha) + + +def composite( + image1: Image.Image, image2: Image.Image, mask: Image.Image +) -> Image.Image: + """Create composite using transparency mask. Alias for + :py:func:`PIL.Image.composite`. + + :rtype: :py:class:`~PIL.Image.Image` + """ + + return Image.composite(image1, image2, mask) + + +def offset(image: Image.Image, xoffset: int, yoffset: int | None = None) -> Image.Image: + """Returns a copy of the image where data has been offset by the given + distances. Data wraps around the edges. If ``yoffset`` is omitted, it + is assumed to be equal to ``xoffset``. + + :param image: Input image. + :param xoffset: The horizontal distance. + :param yoffset: The vertical distance. If omitted, both + distances are set to the same value. + :rtype: :py:class:`~PIL.Image.Image` + """ + + if yoffset is None: + yoffset = xoffset + image.load() + return image._new(image.im.offset(xoffset, yoffset)) diff --git a/venv/Lib/site-packages/PIL/ImageCms.py b/venv/Lib/site-packages/PIL/ImageCms.py new file mode 100644 index 00000000..a1584f11 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageCms.py @@ -0,0 +1,1123 @@ +# The Python Imaging Library. +# $Id$ + +# Optional color management support, based on Kevin Cazabon's PyCMS +# library. + +# Originally released under LGPL. Graciously donated to PIL in +# March 2009, for distribution under the standard PIL license + +# History: + +# 2009-03-08 fl Added to PIL. + +# Copyright (C) 2002-2003 Kevin Cazabon +# Copyright (c) 2009 by Fredrik Lundh +# Copyright (c) 2013 by Eric Soroos + +# See the README file for information on usage and redistribution. See +# below for the original description. +from __future__ import annotations + +import operator +import sys +from enum import IntEnum, IntFlag +from functools import reduce +from typing import Any, Literal, SupportsFloat, SupportsInt, Union + +from . import Image, __version__ +from ._deprecate import deprecate +from ._typing import SupportsRead + +try: + from . import _imagingcms as core + + _CmsProfileCompatible = Union[ + str, SupportsRead[bytes], core.CmsProfile, "ImageCmsProfile" + ] +except ImportError as ex: + # Allow error import for doc purposes, but error out when accessing + # anything in core. + from ._util import DeferredError + + core = DeferredError.new(ex) + +_DESCRIPTION = """ +pyCMS + + a Python / PIL interface to the littleCMS ICC Color Management System + Copyright (C) 2002-2003 Kevin Cazabon + kevin@cazabon.com + https://www.cazabon.com + + pyCMS home page: https://www.cazabon.com/pyCMS + littleCMS home page: https://www.littlecms.com + (littleCMS is Copyright (C) 1998-2001 Marti Maria) + + Originally released under LGPL. Graciously donated to PIL in + March 2009, for distribution under the standard PIL license + + The pyCMS.py module provides a "clean" interface between Python/PIL and + pyCMSdll, taking care of some of the more complex handling of the direct + pyCMSdll functions, as well as error-checking and making sure that all + relevant data is kept together. + + While it is possible to call pyCMSdll functions directly, it's not highly + recommended. + + Version History: + + 1.0.0 pil Oct 2013 Port to LCMS 2. + + 0.1.0 pil mod March 10, 2009 + + Renamed display profile to proof profile. The proof + profile is the profile of the device that is being + simulated, not the profile of the device which is + actually used to display/print the final simulation + (that'd be the output profile) - also see LCMSAPI.txt + input colorspace -> using 'renderingIntent' -> proof + colorspace -> using 'proofRenderingIntent' -> output + colorspace + + Added LCMS FLAGS support. + Added FLAGS["SOFTPROOFING"] as default flag for + buildProofTransform (otherwise the proof profile/intent + would be ignored). + + 0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms + + 0.0.2 alpha Jan 6, 2002 + + Added try/except statements around type() checks of + potential CObjects... Python won't let you use type() + on them, and raises a TypeError (stupid, if you ask + me!) + + Added buildProofTransformFromOpenProfiles() function. + Additional fixes in DLL, see DLL code for details. + + 0.0.1 alpha first public release, Dec. 26, 2002 + + Known to-do list with current version (of Python interface, not pyCMSdll): + + none + +""" + +_VERSION = "1.0.0 pil" + + +def __getattr__(name: str) -> Any: + if name == "DESCRIPTION": + deprecate("PIL.ImageCms.DESCRIPTION", 12) + return _DESCRIPTION + elif name == "VERSION": + deprecate("PIL.ImageCms.VERSION", 12) + return _VERSION + elif name == "FLAGS": + deprecate("PIL.ImageCms.FLAGS", 12, "PIL.ImageCms.Flags") + return _FLAGS + msg = f"module '{__name__}' has no attribute '{name}'" + raise AttributeError(msg) + + +# --------------------------------------------------------------------. + + +# +# intent/direction values + + +class Intent(IntEnum): + PERCEPTUAL = 0 + RELATIVE_COLORIMETRIC = 1 + SATURATION = 2 + ABSOLUTE_COLORIMETRIC = 3 + + +class Direction(IntEnum): + INPUT = 0 + OUTPUT = 1 + PROOF = 2 + + +# +# flags + + +class Flags(IntFlag): + """Flags and documentation are taken from ``lcms2.h``.""" + + NONE = 0 + NOCACHE = 0x0040 + """Inhibit 1-pixel cache""" + NOOPTIMIZE = 0x0100 + """Inhibit optimizations""" + NULLTRANSFORM = 0x0200 + """Don't transform anyway""" + GAMUTCHECK = 0x1000 + """Out of Gamut alarm""" + SOFTPROOFING = 0x4000 + """Do softproofing""" + BLACKPOINTCOMPENSATION = 0x2000 + NOWHITEONWHITEFIXUP = 0x0004 + """Don't fix scum dot""" + HIGHRESPRECALC = 0x0400 + """Use more memory to give better accuracy""" + LOWRESPRECALC = 0x0800 + """Use less memory to minimize resources""" + # this should be 8BITS_DEVICELINK, but that is not a valid name in Python: + USE_8BITS_DEVICELINK = 0x0008 + """Create 8 bits devicelinks""" + GUESSDEVICECLASS = 0x0020 + """Guess device class (for ``transform2devicelink``)""" + KEEP_SEQUENCE = 0x0080 + """Keep profile sequence for devicelink creation""" + FORCE_CLUT = 0x0002 + """Force CLUT optimization""" + CLUT_POST_LINEARIZATION = 0x0001 + """create postlinearization tables if possible""" + CLUT_PRE_LINEARIZATION = 0x0010 + """create prelinearization tables if possible""" + NONEGATIVES = 0x8000 + """Prevent negative numbers in floating point transforms""" + COPY_ALPHA = 0x04000000 + """Alpha channels are copied on ``cmsDoTransform()``""" + NODEFAULTRESOURCEDEF = 0x01000000 + + _GRIDPOINTS_1 = 1 << 16 + _GRIDPOINTS_2 = 2 << 16 + _GRIDPOINTS_4 = 4 << 16 + _GRIDPOINTS_8 = 8 << 16 + _GRIDPOINTS_16 = 16 << 16 + _GRIDPOINTS_32 = 32 << 16 + _GRIDPOINTS_64 = 64 << 16 + _GRIDPOINTS_128 = 128 << 16 + + @staticmethod + def GRIDPOINTS(n: int) -> Flags: + """ + Fine-tune control over number of gridpoints + + :param n: :py:class:`int` in range ``0 <= n <= 255`` + """ + return Flags.NONE | ((n & 0xFF) << 16) + + +_MAX_FLAG = reduce(operator.or_, Flags) + + +_FLAGS = { + "MATRIXINPUT": 1, + "MATRIXOUTPUT": 2, + "MATRIXONLY": (1 | 2), + "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot + # Don't create prelinearization tables on precalculated transforms + # (internal use): + "NOPRELINEARIZATION": 16, + "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) + "NOTCACHE": 64, # Inhibit 1-pixel cache + "NOTPRECALC": 256, + "NULLTRANSFORM": 512, # Don't transform anyway + "HIGHRESPRECALC": 1024, # Use more memory to give better accuracy + "LOWRESPRECALC": 2048, # Use less memory to minimize resources + "WHITEBLACKCOMPENSATION": 8192, + "BLACKPOINTCOMPENSATION": 8192, + "GAMUTCHECK": 4096, # Out of Gamut alarm + "SOFTPROOFING": 16384, # Do softproofing + "PRESERVEBLACK": 32768, # Black preservation + "NODEFAULTRESOURCEDEF": 16777216, # CRD special + "GRIDPOINTS": lambda n: (n & 0xFF) << 16, # Gridpoints +} + + +# --------------------------------------------------------------------. +# Experimental PIL-level API +# --------------------------------------------------------------------. + +## +# Profile. + + +class ImageCmsProfile: + def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None: + """ + :param profile: Either a string representing a filename, + a file like object containing a profile or a + low-level profile object + + """ + self.filename = None + self.product_name = None # profile.product_name + self.product_info = None # profile.product_info + + if isinstance(profile, str): + if sys.platform == "win32": + profile_bytes_path = profile.encode() + try: + profile_bytes_path.decode("ascii") + except UnicodeDecodeError: + with open(profile, "rb") as f: + self.profile = core.profile_frombytes(f.read()) + return + self.filename = profile + self.profile = core.profile_open(profile) + elif hasattr(profile, "read"): + self.profile = core.profile_frombytes(profile.read()) + elif isinstance(profile, core.CmsProfile): + self.profile = profile + else: + msg = "Invalid type for Profile" # type: ignore[unreachable] + raise TypeError(msg) + + def tobytes(self) -> bytes: + """ + Returns the profile in a format suitable for embedding in + saved images. + + :returns: a bytes object containing the ICC profile. + """ + + return core.profile_tobytes(self.profile) + + +class ImageCmsTransform(Image.ImagePointHandler): + """ + Transform. This can be used with the procedural API, or with the standard + :py:func:`~PIL.Image.Image.point` method. + + Will return the output profile in the ``output.info['icc_profile']``. + """ + + def __init__( + self, + input: ImageCmsProfile, + output: ImageCmsProfile, + input_mode: str, + output_mode: str, + intent: Intent = Intent.PERCEPTUAL, + proof: ImageCmsProfile | None = None, + proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC, + flags: Flags = Flags.NONE, + ): + supported_modes = ( + "RGB", + "RGBA", + "RGBX", + "CMYK", + "I;16", + "I;16L", + "I;16B", + "YCbCr", + "LAB", + "L", + "1", + ) + for mode in (input_mode, output_mode): + if mode not in supported_modes: + deprecate( + mode, + 12, + { + "L;16": "I;16 or I;16L", + "L:16B": "I;16B", + "YCCA": "YCbCr", + "YCC": "YCbCr", + }.get(mode), + ) + if proof is None: + self.transform = core.buildTransform( + input.profile, output.profile, input_mode, output_mode, intent, flags + ) + else: + self.transform = core.buildProofTransform( + input.profile, + output.profile, + proof.profile, + input_mode, + output_mode, + intent, + proof_intent, + flags, + ) + # Note: inputMode and outputMode are for pyCMS compatibility only + self.input_mode = self.inputMode = input_mode + self.output_mode = self.outputMode = output_mode + + self.output_profile = output + + def point(self, im: Image.Image) -> Image.Image: + return self.apply(im) + + def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image: + if imOut is None: + imOut = Image.new(self.output_mode, im.size, None) + self.transform.apply(im.getim(), imOut.getim()) + imOut.info["icc_profile"] = self.output_profile.tobytes() + return imOut + + def apply_in_place(self, im: Image.Image) -> Image.Image: + if im.mode != self.output_mode: + msg = "mode mismatch" + raise ValueError(msg) # wrong output mode + self.transform.apply(im.getim(), im.getim()) + im.info["icc_profile"] = self.output_profile.tobytes() + return im + + +def get_display_profile(handle: SupportsInt | None = None) -> ImageCmsProfile | None: + """ + (experimental) Fetches the profile for the current display device. + + :returns: ``None`` if the profile is not known. + """ + + if sys.platform != "win32": + return None + + from . import ImageWin # type: ignore[unused-ignore, unreachable] + + if isinstance(handle, ImageWin.HDC): + profile = core.get_display_profile_win32(int(handle), 1) + else: + profile = core.get_display_profile_win32(int(handle or 0)) + if profile is None: + return None + return ImageCmsProfile(profile) + + +# --------------------------------------------------------------------. +# pyCMS compatible layer +# --------------------------------------------------------------------. + + +class PyCMSError(Exception): + """(pyCMS) Exception class. + This is used for all errors in the pyCMS API.""" + + pass + + +def profileToProfile( + im: Image.Image, + inputProfile: _CmsProfileCompatible, + outputProfile: _CmsProfileCompatible, + renderingIntent: Intent = Intent.PERCEPTUAL, + outputMode: str | None = None, + inPlace: bool = False, + flags: Flags = Flags.NONE, +) -> Image.Image | None: + """ + (pyCMS) Applies an ICC transformation to a given image, mapping from + ``inputProfile`` to ``outputProfile``. + + If the input or output profiles specified are not valid filenames, a + :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and + ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised. + If an error occurs during application of the profiles, + a :exc:`PyCMSError` will be raised. + If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS), + a :exc:`PyCMSError` will be raised. + + This function applies an ICC transformation to im from ``inputProfile``'s + color space to ``outputProfile``'s color space using the specified rendering + intent to decide how to handle out-of-gamut colors. + + ``outputMode`` can be used to specify that a color mode conversion is to + be done using these profiles, but the specified profiles must be able + to handle that mode. I.e., if converting im from RGB to CMYK using + profiles, the input profile must handle RGB data, and the output + profile must handle CMYK data. + + :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...) + or Image.open(...), etc.) + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this image, or a profile object + :param outputProfile: String, as a valid filename path to the ICC output + profile you wish to use for this image, or a profile object + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param outputMode: A valid PIL mode for the output image (i.e. "RGB", + "CMYK", etc.). Note: if rendering the image "inPlace", outputMode + MUST be the same mode as the input, or omitted completely. If + omitted, the outputMode will be the same as the mode of the input + image (im.mode) + :param inPlace: Boolean. If ``True``, the original image is modified in-place, + and ``None`` is returned. If ``False`` (default), a new + :py:class:`~PIL.Image.Image` object is returned with the transform applied. + :param flags: Integer (0-...) specifying additional flags + :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on + the value of ``inPlace`` + :exception PyCMSError: + """ + + if outputMode is None: + outputMode = im.mode + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + msg = "renderingIntent must be an integer between 0 and 3" + raise PyCMSError(msg) + + if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + msg = f"flags must be an integer between 0 and {_MAX_FLAG}" + raise PyCMSError(msg) + + try: + if not isinstance(inputProfile, ImageCmsProfile): + inputProfile = ImageCmsProfile(inputProfile) + if not isinstance(outputProfile, ImageCmsProfile): + outputProfile = ImageCmsProfile(outputProfile) + transform = ImageCmsTransform( + inputProfile, + outputProfile, + im.mode, + outputMode, + renderingIntent, + flags=flags, + ) + if inPlace: + transform.apply_in_place(im) + imOut = None + else: + imOut = transform.apply(im) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + return imOut + + +def getOpenProfile( + profileFilename: str | SupportsRead[bytes] | core.CmsProfile, +) -> ImageCmsProfile: + """ + (pyCMS) Opens an ICC profile file. + + The PyCMSProfile object can be passed back into pyCMS for use in creating + transforms and such (as in ImageCms.buildTransformFromOpenProfiles()). + + If ``profileFilename`` is not a valid filename for an ICC profile, + a :exc:`PyCMSError` will be raised. + + :param profileFilename: String, as a valid filename path to the ICC profile + you wish to open, or a file-like object. + :returns: A CmsProfile class object. + :exception PyCMSError: + """ + + try: + return ImageCmsProfile(profileFilename) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def buildTransform( + inputProfile: _CmsProfileCompatible, + outputProfile: _CmsProfileCompatible, + inMode: str, + outMode: str, + renderingIntent: Intent = Intent.PERCEPTUAL, + flags: Flags = Flags.NONE, +) -> ImageCmsTransform: + """ + (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the + ``outputProfile``. Use applyTransform to apply the transform to a given + image. + + If the input or output profiles specified are not valid filenames, a + :exc:`PyCMSError` will be raised. If an error occurs during creation + of the transform, a :exc:`PyCMSError` will be raised. + + If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` + (or by pyCMS), a :exc:`PyCMSError` will be raised. + + This function builds and returns an ICC transform from the ``inputProfile`` + to the ``outputProfile`` using the ``renderingIntent`` to determine what to do + with out-of-gamut colors. It will ONLY work for converting images that + are in ``inMode`` to images that are in ``outMode`` color format (PIL mode, + i.e. "RGB", "RGBA", "CMYK", etc.). + + Building the transform is a fair part of the overhead in + ImageCms.profileToProfile(), so if you're planning on converting multiple + images using the same input/output settings, this can save you time. + Once you have a transform object, it can be used with + ImageCms.applyProfile() to convert images without the need to re-compute + the lookup table for the transform. + + The reason pyCMS returns a class object rather than a handle directly + to the transform is that it needs to keep track of the PIL input/output + modes that the transform is meant for. These attributes are stored in + the ``inMode`` and ``outMode`` attributes of the object (which can be + manually overridden if you really want to, but I don't know of any + time that would be of use, or would even work). + + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object + :param outputProfile: String, as a valid filename path to the ICC output + profile you wish to use for this transform, or a profile object + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param flags: Integer (0-...) specifying additional flags + :returns: A CmsTransform class object. + :exception PyCMSError: + """ + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + msg = "renderingIntent must be an integer between 0 and 3" + raise PyCMSError(msg) + + if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + msg = f"flags must be an integer between 0 and {_MAX_FLAG}" + raise PyCMSError(msg) + + try: + if not isinstance(inputProfile, ImageCmsProfile): + inputProfile = ImageCmsProfile(inputProfile) + if not isinstance(outputProfile, ImageCmsProfile): + outputProfile = ImageCmsProfile(outputProfile) + return ImageCmsTransform( + inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags + ) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def buildProofTransform( + inputProfile: _CmsProfileCompatible, + outputProfile: _CmsProfileCompatible, + proofProfile: _CmsProfileCompatible, + inMode: str, + outMode: str, + renderingIntent: Intent = Intent.PERCEPTUAL, + proofRenderingIntent: Intent = Intent.ABSOLUTE_COLORIMETRIC, + flags: Flags = Flags.SOFTPROOFING, +) -> ImageCmsTransform: + """ + (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the + ``outputProfile``, but tries to simulate the result that would be + obtained on the ``proofProfile`` device. + + If the input, output, or proof profiles specified are not valid + filenames, a :exc:`PyCMSError` will be raised. + + If an error occurs during creation of the transform, + a :exc:`PyCMSError` will be raised. + + If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` + (or by pyCMS), a :exc:`PyCMSError` will be raised. + + This function builds and returns an ICC transform from the ``inputProfile`` + to the ``outputProfile``, but tries to simulate the result that would be + obtained on the ``proofProfile`` device using ``renderingIntent`` and + ``proofRenderingIntent`` to determine what to do with out-of-gamut + colors. This is known as "soft-proofing". It will ONLY work for + converting images that are in ``inMode`` to images that are in outMode + color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.). + + Usage of the resulting transform object is exactly the same as with + ImageCms.buildTransform(). + + Proof profiling is generally used when using an output device to get a + good idea of what the final printed/displayed image would look like on + the ``proofProfile`` device when it's quicker and easier to use the + output device for judging color. Generally, this means that the + output device is a monitor, or a dye-sub printer (etc.), and the simulated + device is something more expensive, complicated, or time consuming + (making it difficult to make a real print for color judgement purposes). + + Soft-proofing basically functions by adjusting the colors on the + output device to match the colors of the device being simulated. However, + when the simulated device has a much wider gamut than the output + device, you may obtain marginal results. + + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object + :param outputProfile: String, as a valid filename path to the ICC output + (monitor, usually) profile you wish to use for this transform, or a + profile object + :param proofProfile: String, as a valid filename path to the ICC proof + profile you wish to use for this transform, or a profile object + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the input->proof (simulated) transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param proofRenderingIntent: Integer (0-3) specifying the rendering intent + you wish to use for proof->output transform + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param flags: Integer (0-...) specifying additional flags + :returns: A CmsTransform class object. + :exception PyCMSError: + """ + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + msg = "renderingIntent must be an integer between 0 and 3" + raise PyCMSError(msg) + + if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + msg = f"flags must be an integer between 0 and {_MAX_FLAG}" + raise PyCMSError(msg) + + try: + if not isinstance(inputProfile, ImageCmsProfile): + inputProfile = ImageCmsProfile(inputProfile) + if not isinstance(outputProfile, ImageCmsProfile): + outputProfile = ImageCmsProfile(outputProfile) + if not isinstance(proofProfile, ImageCmsProfile): + proofProfile = ImageCmsProfile(proofProfile) + return ImageCmsTransform( + inputProfile, + outputProfile, + inMode, + outMode, + renderingIntent, + proofProfile, + proofRenderingIntent, + flags, + ) + except (OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +buildTransformFromOpenProfiles = buildTransform +buildProofTransformFromOpenProfiles = buildProofTransform + + +def applyTransform( + im: Image.Image, transform: ImageCmsTransform, inPlace: bool = False +) -> Image.Image | None: + """ + (pyCMS) Applies a transform to a given image. + + If ``im.mode != transform.input_mode``, a :exc:`PyCMSError` is raised. + + If ``inPlace`` is ``True`` and ``transform.input_mode != transform.output_mode``, a + :exc:`PyCMSError` is raised. + + If ``im.mode``, ``transform.input_mode`` or ``transform.output_mode`` is not + supported by pyCMSdll or the profiles you used for the transform, a + :exc:`PyCMSError` is raised. + + If an error occurs while the transform is being applied, + a :exc:`PyCMSError` is raised. + + This function applies a pre-calculated transform (from + ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) + to an image. The transform can be used for multiple images, saving + considerable calculation time if doing the same conversion multiple times. + + If you want to modify im in-place instead of receiving a new image as + the return value, set ``inPlace`` to ``True``. This can only be done if + ``transform.input_mode`` and ``transform.output_mode`` are the same, because we + can't change the mode in-place (the buffer sizes for some modes are + different). The default behavior is to return a new :py:class:`~PIL.Image.Image` + object of the same dimensions in mode ``transform.output_mode``. + + :param im: An :py:class:`~PIL.Image.Image` object, and ``im.mode`` must be the same + as the ``input_mode`` supported by the transform. + :param transform: A valid CmsTransform class object + :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is + returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the + transform applied is returned (and ``im`` is not changed). The default is + ``False``. + :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object, + depending on the value of ``inPlace``. The profile will be returned in + the image's ``info['icc_profile']``. + :exception PyCMSError: + """ + + try: + if inPlace: + transform.apply_in_place(im) + imOut = None + else: + imOut = transform.apply(im) + except (TypeError, ValueError) as v: + raise PyCMSError(v) from v + + return imOut + + +def createProfile( + colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0 +) -> core.CmsProfile: + """ + (pyCMS) Creates a profile. + + If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, + a :exc:`PyCMSError` is raised. + + If using LAB and ``colorTemp`` is not a positive integer, + a :exc:`PyCMSError` is raised. + + If an error occurs while creating the profile, + a :exc:`PyCMSError` is raised. + + Use this function to create common profiles on-the-fly instead of + having to supply a profile on disk and knowing the path to it. It + returns a normal CmsProfile object that can be passed to + ImageCms.buildTransformFromOpenProfiles() to create a transform to apply + to images. + + :param colorSpace: String, the color space of the profile you wish to + create. + Currently only "LAB", "XYZ", and "sRGB" are supported. + :param colorTemp: Positive number for the white point for the profile, in + degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 + illuminant if omitted (5000k). colorTemp is ONLY applied to LAB + profiles, and is ignored for XYZ and sRGB. + :returns: A CmsProfile class object + :exception PyCMSError: + """ + + if colorSpace not in ["LAB", "XYZ", "sRGB"]: + msg = ( + f"Color space not supported for on-the-fly profile creation ({colorSpace})" + ) + raise PyCMSError(msg) + + if colorSpace == "LAB": + try: + colorTemp = float(colorTemp) + except (TypeError, ValueError) as e: + msg = f'Color temperature must be numeric, "{colorTemp}" not valid' + raise PyCMSError(msg) from e + + try: + return core.createProfile(colorSpace, colorTemp) + except (TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileName(profile: _CmsProfileCompatible) -> str: + """ + + (pyCMS) Gets the internal product name for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, + a :exc:`PyCMSError` is raised If an error occurs while trying + to obtain the name tag, a :exc:`PyCMSError` is raised. + + Use this function to obtain the INTERNAL name of the profile (stored + in an ICC tag in the profile itself), usually the one used when the + profile was originally created. Sometimes this tag also contains + additional information supplied by the creator. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal name of the profile as stored + in an ICC tag. + :exception PyCMSError: + """ + + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + # do it in python, not c. + # // name was "%s - %s" (model, manufacturer) || Description , + # // but if the Model and Manufacturer were the same or the model + # // was long, Just the model, in 1.x + model = profile.profile.model + manufacturer = profile.profile.manufacturer + + if not (model or manufacturer): + return (profile.profile.profile_description or "") + "\n" + if not manufacturer or (model and len(model) > 30): + return f"{model}\n" + return f"{model} - {manufacturer}\n" + + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileInfo(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the internal product information for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, + a :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the info tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + info tag. This often contains details about the profile, and how it + was created, as supplied by the creator. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + + try: + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + # add an extra newline to preserve pyCMS compatibility + # Python, not C. the white point bits weren't working well, + # so skipping. + # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint + description = profile.profile.profile_description + cpright = profile.profile.copyright + elements = [element for element in (description, cpright) if element] + return "\r\n\r\n".join(elements) + "\r\n\r\n" + + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileCopyright(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the copyright for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the copyright tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + copyright tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.copyright or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileManufacturer(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the manufacturer for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the manufacturer tag, a + :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + manufacturer tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.manufacturer or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileModel(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the model for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the model tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + model tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. + :exception PyCMSError: + """ + + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.model or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getProfileDescription(profile: _CmsProfileCompatible) -> str: + """ + (pyCMS) Gets the description for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the description tag, + a :exc:`PyCMSError` is raised. + + Use this function to obtain the information stored in the profile's + description tag. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in an + ICC tag. + :exception PyCMSError: + """ + + try: + # add an extra newline to preserve pyCMS compatibility + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return (profile.profile.profile_description or "") + "\n" + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def getDefaultIntent(profile: _CmsProfileCompatible) -> int: + """ + (pyCMS) Gets the default intent name for the given profile. + + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. + + If an error occurs while trying to obtain the default intent, a + :exc:`PyCMSError` is raised. + + Use this function to determine the default (and usually best optimized) + rendering intent for this profile. Most profiles support multiple + rendering intents, but are intended mostly for one type of conversion. + If you wish to use a different intent than returned, use + ImageCms.isIntentSupported() to verify it will work first. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: Integer 0-3 specifying the default rendering intent for this + profile. + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :exception PyCMSError: + """ + + try: + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + return profile.profile.rendering_intent + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def isIntentSupported( + profile: _CmsProfileCompatible, intent: Intent, direction: Direction +) -> Literal[-1, 1]: + """ + (pyCMS) Checks if a given intent is supported. + + Use this function to verify that you can use your desired + ``intent`` with ``profile``, and that ``profile`` can be used for the + input/output/proof profile as you desire. + + Some profiles are created specifically for one "direction", can cannot + be used for others. Some profiles can only be used for certain + rendering intents, so it's best to either verify this before trying + to create a transform with them (using this function), or catch the + potential :exc:`PyCMSError` that will occur if they don't + support the modes you select. + + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :param intent: Integer (0-3) specifying the rendering intent you wish to + use with this profile + + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 + + see the pyCMS documentation for details on rendering intents and what + they do. + :param direction: Integer specifying if the profile is to be used for + input, output, or proof + + INPUT = 0 (or use ImageCms.Direction.INPUT) + OUTPUT = 1 (or use ImageCms.Direction.OUTPUT) + PROOF = 2 (or use ImageCms.Direction.PROOF) + + :returns: 1 if the intent/direction are supported, -1 if they are not. + :exception PyCMSError: + """ + + try: + if not isinstance(profile, ImageCmsProfile): + profile = ImageCmsProfile(profile) + # FIXME: I get different results for the same data w. different + # compilers. Bug in LittleCMS or in the binding? + if profile.profile.is_intent_supported(intent, direction): + return 1 + else: + return -1 + except (AttributeError, OSError, TypeError, ValueError) as v: + raise PyCMSError(v) from v + + +def versions() -> tuple[str, str | None, str, str]: + """ + (pyCMS) Fetches versions. + """ + + deprecate( + "PIL.ImageCms.versions()", + 12, + '(PIL.features.version("littlecms2"), sys.version, PIL.__version__)', + ) + return _VERSION, core.littlecms_version, sys.version.split()[0], __version__ diff --git a/venv/Lib/site-packages/PIL/ImageColor.py b/venv/Lib/site-packages/PIL/ImageColor.py new file mode 100644 index 00000000..9a15a8eb --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageColor.py @@ -0,0 +1,320 @@ +# +# The Python Imaging Library +# $Id$ +# +# map CSS3-style colour description strings to RGB +# +# History: +# 2002-10-24 fl Added support for CSS-style color strings +# 2002-12-15 fl Added RGBA support +# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2 +# 2004-07-19 fl Fixed gray/grey spelling issues +# 2009-03-05 fl Fixed rounding error in grayscale calculation +# +# Copyright (c) 2002-2004 by Secret Labs AB +# Copyright (c) 2002-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re +from functools import lru_cache + +from . import Image + + +@lru_cache +def getrgb(color: str) -> tuple[int, int, int] | tuple[int, int, int, int]: + """ + Convert a color string to an RGB or RGBA tuple. If the string cannot be + parsed, this function raises a :py:exc:`ValueError` exception. + + .. versionadded:: 1.1.4 + + :param color: A color string + :return: ``(red, green, blue[, alpha])`` + """ + if len(color) > 100: + msg = "color specifier is too long" + raise ValueError(msg) + color = color.lower() + + rgb = colormap.get(color, None) + if rgb: + if isinstance(rgb, tuple): + return rgb + rgb_tuple = getrgb(rgb) + assert len(rgb_tuple) == 3 + colormap[color] = rgb_tuple + return rgb_tuple + + # check for known string formats + if re.match("#[a-f0-9]{3}$", color): + return int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16) + + if re.match("#[a-f0-9]{4}$", color): + return ( + int(color[1] * 2, 16), + int(color[2] * 2, 16), + int(color[3] * 2, 16), + int(color[4] * 2, 16), + ) + + if re.match("#[a-f0-9]{6}$", color): + return int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16) + + if re.match("#[a-f0-9]{8}$", color): + return ( + int(color[1:3], 16), + int(color[3:5], 16), + int(color[5:7], 16), + int(color[7:9], 16), + ) + + m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) + if m: + return int(m.group(1)), int(m.group(2)), int(m.group(3)) + + m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color) + if m: + return ( + int((int(m.group(1)) * 255) / 100.0 + 0.5), + int((int(m.group(2)) * 255) / 100.0 + 0.5), + int((int(m.group(3)) * 255) / 100.0 + 0.5), + ) + + m = re.match( + r"hsl\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color + ) + if m: + from colorsys import hls_to_rgb + + rgb_floats = hls_to_rgb( + float(m.group(1)) / 360.0, + float(m.group(3)) / 100.0, + float(m.group(2)) / 100.0, + ) + return ( + int(rgb_floats[0] * 255 + 0.5), + int(rgb_floats[1] * 255 + 0.5), + int(rgb_floats[2] * 255 + 0.5), + ) + + m = re.match( + r"hs[bv]\(\s*(\d+\.?\d*)\s*,\s*(\d+\.?\d*)%\s*,\s*(\d+\.?\d*)%\s*\)$", color + ) + if m: + from colorsys import hsv_to_rgb + + rgb_floats = hsv_to_rgb( + float(m.group(1)) / 360.0, + float(m.group(2)) / 100.0, + float(m.group(3)) / 100.0, + ) + return ( + int(rgb_floats[0] * 255 + 0.5), + int(rgb_floats[1] * 255 + 0.5), + int(rgb_floats[2] * 255 + 0.5), + ) + + m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) + if m: + return int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)) + msg = f"unknown color specifier: {repr(color)}" + raise ValueError(msg) + + +@lru_cache +def getcolor(color: str, mode: str) -> int | tuple[int, ...]: + """ + Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if + ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is + not color or a palette image, converts the RGB value to a grayscale value. + If the string cannot be parsed, this function raises a :py:exc:`ValueError` + exception. + + .. versionadded:: 1.1.4 + + :param color: A color string + :param mode: Convert result to this mode + :return: ``graylevel, (graylevel, alpha) or (red, green, blue[, alpha])`` + """ + # same as getrgb, but converts the result to the given mode + rgb, alpha = getrgb(color), 255 + if len(rgb) == 4: + alpha = rgb[3] + rgb = rgb[:3] + + if mode == "HSV": + from colorsys import rgb_to_hsv + + r, g, b = rgb + h, s, v = rgb_to_hsv(r / 255, g / 255, b / 255) + return int(h * 255), int(s * 255), int(v * 255) + elif Image.getmodebase(mode) == "L": + r, g, b = rgb + # ITU-R Recommendation 601-2 for nonlinear RGB + # scaled to 24 bits to match the convert's implementation. + graylevel = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16 + if mode[-1] == "A": + return graylevel, alpha + return graylevel + elif mode[-1] == "A": + return rgb + (alpha,) + return rgb + + +colormap: dict[str, str | tuple[int, int, int]] = { + # X11 colour table from https://drafts.csswg.org/css-color-4/, with + # gray/grey spelling issues fixed. This is a superset of HTML 4.0 + # colour names used in CSS 1. + "aliceblue": "#f0f8ff", + "antiquewhite": "#faebd7", + "aqua": "#00ffff", + "aquamarine": "#7fffd4", + "azure": "#f0ffff", + "beige": "#f5f5dc", + "bisque": "#ffe4c4", + "black": "#000000", + "blanchedalmond": "#ffebcd", + "blue": "#0000ff", + "blueviolet": "#8a2be2", + "brown": "#a52a2a", + "burlywood": "#deb887", + "cadetblue": "#5f9ea0", + "chartreuse": "#7fff00", + "chocolate": "#d2691e", + "coral": "#ff7f50", + "cornflowerblue": "#6495ed", + "cornsilk": "#fff8dc", + "crimson": "#dc143c", + "cyan": "#00ffff", + "darkblue": "#00008b", + "darkcyan": "#008b8b", + "darkgoldenrod": "#b8860b", + "darkgray": "#a9a9a9", + "darkgrey": "#a9a9a9", + "darkgreen": "#006400", + "darkkhaki": "#bdb76b", + "darkmagenta": "#8b008b", + "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", + "darkorchid": "#9932cc", + "darkred": "#8b0000", + "darksalmon": "#e9967a", + "darkseagreen": "#8fbc8f", + "darkslateblue": "#483d8b", + "darkslategray": "#2f4f4f", + "darkslategrey": "#2f4f4f", + "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", + "deeppink": "#ff1493", + "deepskyblue": "#00bfff", + "dimgray": "#696969", + "dimgrey": "#696969", + "dodgerblue": "#1e90ff", + "firebrick": "#b22222", + "floralwhite": "#fffaf0", + "forestgreen": "#228b22", + "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", + "ghostwhite": "#f8f8ff", + "gold": "#ffd700", + "goldenrod": "#daa520", + "gray": "#808080", + "grey": "#808080", + "green": "#008000", + "greenyellow": "#adff2f", + "honeydew": "#f0fff0", + "hotpink": "#ff69b4", + "indianred": "#cd5c5c", + "indigo": "#4b0082", + "ivory": "#fffff0", + "khaki": "#f0e68c", + "lavender": "#e6e6fa", + "lavenderblush": "#fff0f5", + "lawngreen": "#7cfc00", + "lemonchiffon": "#fffacd", + "lightblue": "#add8e6", + "lightcoral": "#f08080", + "lightcyan": "#e0ffff", + "lightgoldenrodyellow": "#fafad2", + "lightgreen": "#90ee90", + "lightgray": "#d3d3d3", + "lightgrey": "#d3d3d3", + "lightpink": "#ffb6c1", + "lightsalmon": "#ffa07a", + "lightseagreen": "#20b2aa", + "lightskyblue": "#87cefa", + "lightslategray": "#778899", + "lightslategrey": "#778899", + "lightsteelblue": "#b0c4de", + "lightyellow": "#ffffe0", + "lime": "#00ff00", + "limegreen": "#32cd32", + "linen": "#faf0e6", + "magenta": "#ff00ff", + "maroon": "#800000", + "mediumaquamarine": "#66cdaa", + "mediumblue": "#0000cd", + "mediumorchid": "#ba55d3", + "mediumpurple": "#9370db", + "mediumseagreen": "#3cb371", + "mediumslateblue": "#7b68ee", + "mediumspringgreen": "#00fa9a", + "mediumturquoise": "#48d1cc", + "mediumvioletred": "#c71585", + "midnightblue": "#191970", + "mintcream": "#f5fffa", + "mistyrose": "#ffe4e1", + "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", + "navy": "#000080", + "oldlace": "#fdf5e6", + "olive": "#808000", + "olivedrab": "#6b8e23", + "orange": "#ffa500", + "orangered": "#ff4500", + "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", + "palegreen": "#98fb98", + "paleturquoise": "#afeeee", + "palevioletred": "#db7093", + "papayawhip": "#ffefd5", + "peachpuff": "#ffdab9", + "peru": "#cd853f", + "pink": "#ffc0cb", + "plum": "#dda0dd", + "powderblue": "#b0e0e6", + "purple": "#800080", + "rebeccapurple": "#663399", + "red": "#ff0000", + "rosybrown": "#bc8f8f", + "royalblue": "#4169e1", + "saddlebrown": "#8b4513", + "salmon": "#fa8072", + "sandybrown": "#f4a460", + "seagreen": "#2e8b57", + "seashell": "#fff5ee", + "sienna": "#a0522d", + "silver": "#c0c0c0", + "skyblue": "#87ceeb", + "slateblue": "#6a5acd", + "slategray": "#708090", + "slategrey": "#708090", + "snow": "#fffafa", + "springgreen": "#00ff7f", + "steelblue": "#4682b4", + "tan": "#d2b48c", + "teal": "#008080", + "thistle": "#d8bfd8", + "tomato": "#ff6347", + "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", + "white": "#ffffff", + "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", + "yellowgreen": "#9acd32", +} diff --git a/venv/Lib/site-packages/PIL/ImageDraw.py b/venv/Lib/site-packages/PIL/ImageDraw.py new file mode 100644 index 00000000..6cf1ee62 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageDraw.py @@ -0,0 +1,1232 @@ +# +# The Python Imaging Library +# $Id$ +# +# drawing interface operations +# +# History: +# 1996-04-13 fl Created (experimental) +# 1996-08-07 fl Filled polygons, ellipses. +# 1996-08-13 fl Added text support +# 1998-06-28 fl Handle I and F images +# 1998-12-29 fl Added arc; use arc primitive to draw ellipses +# 1999-01-10 fl Added shape stuff (experimental) +# 1999-02-06 fl Added bitmap support +# 1999-02-11 fl Changed all primitives to take options +# 1999-02-20 fl Fixed backwards compatibility +# 2000-10-12 fl Copy on write, when necessary +# 2001-02-18 fl Use default ink for bitmap/text also in fill mode +# 2002-10-24 fl Added support for CSS-style color strings +# 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing +# 2002-12-11 fl Refactored low-level drawing API (work in progress) +# 2004-08-26 fl Made Draw() a factory function, added getdraw() support +# 2004-09-04 fl Added width support to line primitive +# 2004-09-10 fl Added font mode handling +# 2006-06-19 fl Added font bearing support (getmask2) +# +# Copyright (c) 1997-2006 by Secret Labs AB +# Copyright (c) 1996-2006 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import math +import struct +from collections.abc import Sequence +from types import ModuleType +from typing import Any, AnyStr, Callable, Union, cast + +from . import Image, ImageColor +from ._deprecate import deprecate +from ._typing import Coords + +# experimental access to the outline API +Outline: Callable[[], Image.core._Outline] = Image.core.outline + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import ImageDraw2, ImageFont + +_Ink = Union[float, tuple[int, ...], str] + +""" +A simple 2D drawing interface for PIL images. +

+Application code should use the Draw factory, instead of +directly. +""" + + +class ImageDraw: + font: ( + ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None + ) = None + + def __init__(self, im: Image.Image, mode: str | None = None) -> None: + """ + Create a drawing instance. + + :param im: The image to draw in. + :param mode: Optional mode to use for color values. For RGB + images, this argument can be RGB or RGBA (to blend the + drawing into the image). For all other modes, this argument + must be the same as the image mode. If omitted, the mode + defaults to the mode of the image. + """ + im.load() + if im.readonly: + im._copy() # make it writeable + blend = 0 + if mode is None: + mode = im.mode + if mode != im.mode: + if mode == "RGBA" and im.mode == "RGB": + blend = 1 + else: + msg = "mode mismatch" + raise ValueError(msg) + if mode == "P": + self.palette = im.palette + else: + self.palette = None + self._image = im + self.im = im.im + self.draw = Image.core.draw(self.im, blend) + self.mode = mode + if mode in ("I", "F"): + self.ink = self.draw.draw_ink(1) + else: + self.ink = self.draw.draw_ink(-1) + if mode in ("1", "P", "I", "F"): + # FIXME: fix Fill2 to properly support matte for I+F images + self.fontmode = "1" + else: + self.fontmode = "L" # aliasing is okay for other modes + self.fill = False + + def getfont( + self, + ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont: + """ + Get the current default font. + + To set the default font for this ImageDraw instance:: + + from PIL import ImageDraw, ImageFont + draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + + To set the default font for all future ImageDraw instances:: + + from PIL import ImageDraw, ImageFont + ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf") + + If the current default font is ``None``, + it is initialized with ``ImageFont.load_default()``. + + :returns: An image font.""" + if not self.font: + # FIXME: should add a font repository + from . import ImageFont + + self.font = ImageFont.load_default() + return self.font + + def _getfont( + self, font_size: float | None + ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont: + if font_size is not None: + from . import ImageFont + + return ImageFont.load_default(font_size) + else: + return self.getfont() + + def _getink( + self, ink: _Ink | None, fill: _Ink | None = None + ) -> tuple[int | None, int | None]: + result_ink = None + result_fill = None + if ink is None and fill is None: + if self.fill: + result_fill = self.ink + else: + result_ink = self.ink + else: + if ink is not None: + if isinstance(ink, str): + ink = ImageColor.getcolor(ink, self.mode) + if self.palette and isinstance(ink, tuple): + ink = self.palette.getcolor(ink, self._image) + result_ink = self.draw.draw_ink(ink) + if fill is not None: + if isinstance(fill, str): + fill = ImageColor.getcolor(fill, self.mode) + if self.palette and isinstance(fill, tuple): + fill = self.palette.getcolor(fill, self._image) + result_fill = self.draw.draw_ink(fill) + return result_ink, result_fill + + def arc( + self, + xy: Coords, + start: float, + end: float, + fill: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw an arc.""" + ink, fill = self._getink(fill) + if ink is not None: + self.draw.draw_arc(xy, start, end, ink, width) + + def bitmap( + self, xy: Sequence[int], bitmap: Image.Image, fill: _Ink | None = None + ) -> None: + """Draw a bitmap.""" + bitmap.load() + ink, fill = self._getink(fill) + if ink is None: + ink = fill + if ink is not None: + self.draw.draw_bitmap(xy, bitmap.im, ink) + + def chord( + self, + xy: Coords, + start: float, + end: float, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a chord.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_chord(xy, start, end, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_chord(xy, start, end, ink, 0, width) + + def ellipse( + self, + xy: Coords, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw an ellipse.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_ellipse(xy, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_ellipse(xy, ink, 0, width) + + def circle( + self, + xy: Sequence[float], + radius: float, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a circle given center coordinates and a radius.""" + ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius) + self.ellipse(ellipse_xy, fill, outline, width) + + def line( + self, + xy: Coords, + fill: _Ink | None = None, + width: int = 0, + joint: str | None = None, + ) -> None: + """Draw a line, or a connected sequence of line segments.""" + ink = self._getink(fill)[0] + if ink is not None: + self.draw.draw_lines(xy, ink, width) + if joint == "curve" and width > 4: + points: Sequence[Sequence[float]] + if isinstance(xy[0], (list, tuple)): + points = cast(Sequence[Sequence[float]], xy) + else: + points = [ + cast(Sequence[float], tuple(xy[i : i + 2])) + for i in range(0, len(xy), 2) + ] + for i in range(1, len(points) - 1): + point = points[i] + angles = [ + math.degrees(math.atan2(end[0] - start[0], start[1] - end[1])) + % 360 + for start, end in ( + (points[i - 1], point), + (point, points[i + 1]), + ) + ] + if angles[0] == angles[1]: + # This is a straight line, so no joint is required + continue + + def coord_at_angle( + coord: Sequence[float], angle: float + ) -> tuple[float, ...]: + x, y = coord + angle -= 90 + distance = width / 2 - 1 + return tuple( + p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) + for p, p_d in ( + (x, distance * math.cos(math.radians(angle))), + (y, distance * math.sin(math.radians(angle))), + ) + ) + + flipped = ( + angles[1] > angles[0] and angles[1] - 180 > angles[0] + ) or (angles[1] < angles[0] and angles[1] + 180 > angles[0]) + coords = [ + (point[0] - width / 2 + 1, point[1] - width / 2 + 1), + (point[0] + width / 2 - 1, point[1] + width / 2 - 1), + ] + if flipped: + start, end = (angles[1] + 90, angles[0] + 90) + else: + start, end = (angles[0] - 90, angles[1] - 90) + self.pieslice(coords, start - 90, end - 90, fill) + + if width > 8: + # Cover potential gaps between the line and the joint + if flipped: + gap_coords = [ + coord_at_angle(point, angles[0] + 90), + point, + coord_at_angle(point, angles[1] + 90), + ] + else: + gap_coords = [ + coord_at_angle(point, angles[0] - 90), + point, + coord_at_angle(point, angles[1] - 90), + ] + self.line(gap_coords, fill, width=3) + + def shape( + self, + shape: Image.core._Outline, + fill: _Ink | None = None, + outline: _Ink | None = None, + ) -> None: + """(Experimental) Draw a shape.""" + shape.close() + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_outline(shape, fill_ink, 1) + if ink is not None and ink != fill_ink: + self.draw.draw_outline(shape, ink, 0) + + def pieslice( + self, + xy: Coords, + start: float, + end: float, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a pieslice.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_pieslice(xy, start, end, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_pieslice(xy, start, end, ink, 0, width) + + def point(self, xy: Coords, fill: _Ink | None = None) -> None: + """Draw one or more individual pixels.""" + ink, fill = self._getink(fill) + if ink is not None: + self.draw.draw_points(xy, ink) + + def polygon( + self, + xy: Coords, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a polygon.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_polygon(xy, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + if width == 1: + self.draw.draw_polygon(xy, ink, 0, width) + elif self.im is not None: + # To avoid expanding the polygon outwards, + # use the fill as a mask + mask = Image.new("1", self.im.size) + mask_ink = self._getink(1)[0] + draw = Draw(mask) + draw.draw.draw_polygon(xy, mask_ink, 1) + + self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im) + + def regular_polygon( + self, + bounding_circle: Sequence[Sequence[float] | float], + n_sides: int, + rotation: float = 0, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a regular polygon.""" + xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) + self.polygon(xy, fill, outline, width) + + def rectangle( + self, + xy: Coords, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + ) -> None: + """Draw a rectangle.""" + ink, fill_ink = self._getink(outline, fill) + if fill_ink is not None: + self.draw.draw_rectangle(xy, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + self.draw.draw_rectangle(xy, ink, 0, width) + + def rounded_rectangle( + self, + xy: Coords, + radius: float = 0, + fill: _Ink | None = None, + outline: _Ink | None = None, + width: int = 1, + *, + corners: tuple[bool, bool, bool, bool] | None = None, + ) -> None: + """Draw a rounded rectangle.""" + if isinstance(xy[0], (list, tuple)): + (x0, y0), (x1, y1) = cast(Sequence[Sequence[float]], xy) + else: + x0, y0, x1, y1 = cast(Sequence[float], xy) + if x1 < x0: + msg = "x1 must be greater than or equal to x0" + raise ValueError(msg) + if y1 < y0: + msg = "y1 must be greater than or equal to y0" + raise ValueError(msg) + if corners is None: + corners = (True, True, True, True) + + d = radius * 2 + + x0 = round(x0) + y0 = round(y0) + x1 = round(x1) + y1 = round(y1) + full_x, full_y = False, False + if all(corners): + full_x = d >= x1 - x0 - 1 + if full_x: + # The two left and two right corners are joined + d = x1 - x0 + full_y = d >= y1 - y0 - 1 + if full_y: + # The two top and two bottom corners are joined + d = y1 - y0 + if full_x and full_y: + # If all corners are joined, that is a circle + return self.ellipse(xy, fill, outline, width) + + if d == 0 or not any(corners): + # If the corners have no curve, + # or there are no corners, + # that is a rectangle + return self.rectangle(xy, fill, outline, width) + + r = int(d // 2) + ink, fill_ink = self._getink(outline, fill) + + def draw_corners(pieslice: bool) -> None: + parts: tuple[tuple[tuple[float, float, float, float], int, int], ...] + if full_x: + # Draw top and bottom halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 180, 360), + ((x0, y1 - d, x0 + d, y1), 0, 180), + ) + elif full_y: + # Draw left and right halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 90, 270), + ((x1 - d, y0, x1, y0 + d), 270, 90), + ) + else: + # Draw four separate corners + parts = tuple( + part + for i, part in enumerate( + ( + ((x0, y0, x0 + d, y0 + d), 180, 270), + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ) + ) + if corners[i] + ) + for part in parts: + if pieslice: + self.draw.draw_pieslice(*(part + (fill_ink, 1))) + else: + self.draw.draw_arc(*(part + (ink, width))) + + if fill_ink is not None: + draw_corners(True) + + if full_x: + self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill_ink, 1) + elif x1 - r - 1 > x0 + r + 1: + self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill_ink, 1) + if not full_x and not full_y: + left = [x0, y0, x0 + r, y1] + if corners[0]: + left[1] += r + 1 + if corners[3]: + left[3] -= r + 1 + self.draw.draw_rectangle(left, fill_ink, 1) + + right = [x1 - r, y0, x1, y1] + if corners[1]: + right[1] += r + 1 + if corners[2]: + right[3] -= r + 1 + self.draw.draw_rectangle(right, fill_ink, 1) + if ink is not None and ink != fill_ink and width != 0: + draw_corners(False) + + if not full_x: + top = [x0, y0, x1, y0 + width - 1] + if corners[0]: + top[0] += r + 1 + if corners[1]: + top[2] -= r + 1 + self.draw.draw_rectangle(top, ink, 1) + + bottom = [x0, y1 - width + 1, x1, y1] + if corners[3]: + bottom[0] += r + 1 + if corners[2]: + bottom[2] -= r + 1 + self.draw.draw_rectangle(bottom, ink, 1) + if not full_y: + left = [x0, y0, x0 + width - 1, y1] + if corners[0]: + left[1] += r + 1 + if corners[3]: + left[3] -= r + 1 + self.draw.draw_rectangle(left, ink, 1) + + right = [x1 - width + 1, y0, x1, y1] + if corners[1]: + right[1] += r + 1 + if corners[2]: + right[3] -= r + 1 + self.draw.draw_rectangle(right, ink, 1) + + def _multiline_check(self, text: AnyStr) -> bool: + split_character = "\n" if isinstance(text, str) else b"\n" + + return split_character in text + + def text( + self, + xy: tuple[float, float], + text: AnyStr, + fill: _Ink | None = None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + stroke_fill: _Ink | None = None, + embedded_color: bool = False, + *args: Any, + **kwargs: Any, + ) -> None: + """Draw text.""" + if embedded_color and self.mode not in ("RGB", "RGBA"): + msg = "Embedded color supported only in RGB and RGBA modes" + raise ValueError(msg) + + if font is None: + font = self._getfont(kwargs.get("font_size")) + + if self._multiline_check(text): + return self.multiline_text( + xy, + text, + fill, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + stroke_fill, + embedded_color, + ) + + def getink(fill: _Ink | None) -> int: + ink, fill_ink = self._getink(fill) + if ink is None: + assert fill_ink is not None + return fill_ink + return ink + + def draw_text(ink: int, stroke_width: float = 0) -> None: + mode = self.fontmode + if stroke_width == 0 and embedded_color: + mode = "RGBA" + coord = [] + for i in range(2): + coord.append(int(xy[i])) + start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) + try: + mask, offset = font.getmask2( # type: ignore[union-attr,misc] + text, + mode, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + stroke_filled=True, + anchor=anchor, + ink=ink, + start=start, + *args, + **kwargs, + ) + coord = [coord[0] + offset[0], coord[1] + offset[1]] + except AttributeError: + try: + mask = font.getmask( # type: ignore[misc] + text, + mode, + direction, + features, + language, + stroke_width, + anchor, + ink, + start=start, + *args, + **kwargs, + ) + except TypeError: + mask = font.getmask(text) + if mode == "RGBA": + # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A + # extract mask and set text alpha + color, mask = mask, mask.getband(3) + ink_alpha = struct.pack("i", ink)[3] + color.fillband(3, ink_alpha) + x, y = coord + if self.im is not None: + self.im.paste( + color, (x, y, x + mask.size[0], y + mask.size[1]), mask + ) + else: + self.draw.draw_bitmap(coord, mask, ink) + + ink = getink(fill) + if ink is not None: + stroke_ink = None + if stroke_width: + stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink + + if stroke_ink is not None: + # Draw stroked text + draw_text(stroke_ink, stroke_width) + + # Draw normal text + if ink != stroke_ink: + draw_text(ink) + else: + # Only draw normal text + draw_text(ink) + + def _prepare_multiline_text( + self, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ), + anchor: str | None, + spacing: float, + align: str, + direction: str | None, + features: list[str] | None, + language: str | None, + stroke_width: float, + embedded_color: bool, + font_size: float | None, + ) -> tuple[ + ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, + list[tuple[tuple[float, float], str, AnyStr]], + ]: + if anchor is None: + anchor = "lt" if direction == "ttb" else "la" + elif len(anchor) != 2: + msg = "anchor must be a 2 character string" + raise ValueError(msg) + elif anchor[1] in "tb" and direction != "ttb": + msg = "anchor not supported for multiline text" + raise ValueError(msg) + + if font is None: + font = self._getfont(font_size) + + lines = text.split("\n" if isinstance(text, str) else b"\n") + line_spacing = ( + self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3] + + stroke_width + + spacing + ) + + top = xy[1] + parts = [] + if direction == "ttb": + left = xy[0] + for line in lines: + parts.append(((left, top), anchor, line)) + left += line_spacing + else: + widths = [] + max_width: float = 0 + for line in lines: + line_width = self.textlength( + line, + font, + direction=direction, + features=features, + language=language, + embedded_color=embedded_color, + ) + widths.append(line_width) + max_width = max(max_width, line_width) + + if anchor[1] == "m": + top -= (len(lines) - 1) * line_spacing / 2.0 + elif anchor[1] == "d": + top -= (len(lines) - 1) * line_spacing + + for idx, line in enumerate(lines): + left = xy[0] + width_difference = max_width - widths[idx] + + # align by align parameter + if align in ("left", "justify"): + pass + elif align == "center": + left += width_difference / 2.0 + elif align == "right": + left += width_difference + else: + msg = 'align must be "left", "center", "right" or "justify"' + raise ValueError(msg) + + if ( + align == "justify" + and width_difference != 0 + and idx != len(lines) - 1 + ): + words = line.split(" " if isinstance(text, str) else b" ") + if len(words) > 1: + # align left by anchor + if anchor[0] == "m": + left -= max_width / 2.0 + elif anchor[0] == "r": + left -= max_width + + word_widths = [ + self.textlength( + word, + font, + direction=direction, + features=features, + language=language, + embedded_color=embedded_color, + ) + for word in words + ] + word_anchor = "l" + anchor[1] + width_difference = max_width - sum(word_widths) + for i, word in enumerate(words): + parts.append(((left, top), word_anchor, word)) + left += word_widths[i] + width_difference / (len(words) - 1) + top += line_spacing + continue + + # align left by anchor + if anchor[0] == "m": + left -= width_difference / 2.0 + elif anchor[0] == "r": + left -= width_difference + parts.append(((left, top), anchor, line)) + top += line_spacing + + return font, parts + + def multiline_text( + self, + xy: tuple[float, float], + text: AnyStr, + fill: _Ink | None = None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + stroke_fill: _Ink | None = None, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> None: + font, lines = self._prepare_multiline_text( + xy, + text, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + embedded_color, + font_size, + ) + + for xy, anchor, line in lines: + self.text( + xy, + line, + fill, + font, + anchor, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + stroke_fill=stroke_fill, + embedded_color=embedded_color, + ) + + def textlength( + self, + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> float: + """Get the length of a given string, in pixels with 1/64 precision.""" + if self._multiline_check(text): + msg = "can't measure length of multiline text" + raise ValueError(msg) + if embedded_color and self.mode not in ("RGB", "RGBA"): + msg = "Embedded color supported only in RGB and RGBA modes" + raise ValueError(msg) + + if font is None: + font = self._getfont(font_size) + mode = "RGBA" if embedded_color else self.fontmode + return font.getlength(text, mode, direction, features, language) + + def textbbox( + self, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> tuple[float, float, float, float]: + """Get the bounding box of a given string, in pixels.""" + if embedded_color and self.mode not in ("RGB", "RGBA"): + msg = "Embedded color supported only in RGB and RGBA modes" + raise ValueError(msg) + + if font is None: + font = self._getfont(font_size) + + if self._multiline_check(text): + return self.multiline_textbbox( + xy, + text, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + embedded_color, + ) + + mode = "RGBA" if embedded_color else self.fontmode + bbox = font.getbbox( + text, mode, direction, features, language, stroke_width, anchor + ) + return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1] + + def multiline_textbbox( + self, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + embedded_color: bool = False, + *, + font_size: float | None = None, + ) -> tuple[float, float, float, float]: + font, lines = self._prepare_multiline_text( + xy, + text, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + embedded_color, + font_size, + ) + + bbox: tuple[float, float, float, float] | None = None + + for xy, anchor, line in lines: + bbox_line = self.textbbox( + xy, + line, + font, + anchor, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + embedded_color=embedded_color, + ) + if bbox is None: + bbox = bbox_line + else: + bbox = ( + min(bbox[0], bbox_line[0]), + min(bbox[1], bbox_line[1]), + max(bbox[2], bbox_line[2]), + max(bbox[3], bbox_line[3]), + ) + + if bbox is None: + return xy[0], xy[1], xy[0], xy[1] + return bbox + + +def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw: + """ + A simple 2D drawing interface for PIL images. + + :param im: The image to draw in. + :param mode: Optional mode to use for color values. For RGB + images, this argument can be RGB or RGBA (to blend the + drawing into the image). For all other modes, this argument + must be the same as the image mode. If omitted, the mode + defaults to the mode of the image. + """ + try: + return getattr(im, "getdraw")(mode) + except AttributeError: + return ImageDraw(im, mode) + + +def getdraw( + im: Image.Image | None = None, hints: list[str] | None = None +) -> tuple[ImageDraw2.Draw | None, ModuleType]: + """ + :param im: The image to draw in. + :param hints: An optional list of hints. Deprecated. + :returns: A (drawing context, drawing resource factory) tuple. + """ + if hints is not None: + deprecate("'hints' parameter", 12) + from . import ImageDraw2 + + draw = ImageDraw2.Draw(im) if im is not None else None + return draw, ImageDraw2 + + +def floodfill( + image: Image.Image, + xy: tuple[int, int], + value: float | tuple[int, ...], + border: float | tuple[int, ...] | None = None, + thresh: float = 0, +) -> None: + """ + .. warning:: This method is experimental. + + Fills a bounded region with a given color. + + :param image: Target image. + :param xy: Seed position (a 2-item coordinate tuple). See + :ref:`coordinate-system`. + :param value: Fill color. + :param border: Optional border value. If given, the region consists of + pixels with a color different from the border color. If not given, + the region consists of pixels having the same color as the seed + pixel. + :param thresh: Optional threshold value which specifies a maximum + tolerable difference of a pixel value from the 'background' in + order for it to be replaced. Useful for filling regions of + non-homogeneous, but similar, colors. + """ + # based on an implementation by Eric S. Raymond + # amended by yo1995 @20180806 + pixel = image.load() + assert pixel is not None + x, y = xy + try: + background = pixel[x, y] + if _color_diff(value, background) <= thresh: + return # seed point already has fill color + pixel[x, y] = value + except (ValueError, IndexError): + return # seed point outside image + edge = {(x, y)} + # use a set to keep record of current and previous edge pixels + # to reduce memory consumption + full_edge = set() + while edge: + new_edge = set() + for x, y in edge: # 4 adjacent method + for s, t in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)): + # If already processed, or if a coordinate is negative, skip + if (s, t) in full_edge or s < 0 or t < 0: + continue + try: + p = pixel[s, t] + except (ValueError, IndexError): + pass + else: + full_edge.add((s, t)) + if border is None: + fill = _color_diff(p, background) <= thresh + else: + fill = p not in (value, border) + if fill: + pixel[s, t] = value + new_edge.add((s, t)) + full_edge = edge # discard pixels processed + edge = new_edge + + +def _compute_regular_polygon_vertices( + bounding_circle: Sequence[Sequence[float] | float], n_sides: int, rotation: float +) -> list[tuple[float, float]]: + """ + Generate a list of vertices for a 2D regular polygon. + + :param bounding_circle: The bounding circle is a sequence defined + by a point and radius. The polygon is inscribed in this circle. + (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``) + :param n_sides: Number of sides + (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon) + :param rotation: Apply an arbitrary rotation to the polygon + (e.g. ``rotation=90``, applies a 90 degree rotation) + :return: List of regular polygon vertices + (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``) + + How are the vertices computed? + 1. Compute the following variables + - theta: Angle between the apothem & the nearest polygon vertex + - side_length: Length of each polygon edge + - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle) + - polygon_radius: Polygon radius (last element of bounding_circle) + - angles: Location of each polygon vertex in polar grid + (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0]) + + 2. For each angle in angles, get the polygon vertex at that angle + The vertex is computed using the equation below. + X= xcos(φ) + ysin(φ) + Y= −xsin(φ) + ycos(φ) + + Note: + φ = angle in degrees + x = 0 + y = polygon_radius + + The formula above assumes rotation around the origin. + In our case, we are rotating around the centroid. + To account for this, we use the formula below + X = xcos(φ) + ysin(φ) + centroid_x + Y = −xsin(φ) + ycos(φ) + centroid_y + """ + # 1. Error Handling + # 1.1 Check `n_sides` has an appropriate value + if not isinstance(n_sides, int): + msg = "n_sides should be an int" # type: ignore[unreachable] + raise TypeError(msg) + if n_sides < 3: + msg = "n_sides should be an int > 2" + raise ValueError(msg) + + # 1.2 Check `bounding_circle` has an appropriate value + if not isinstance(bounding_circle, (list, tuple)): + msg = "bounding_circle should be a sequence" + raise TypeError(msg) + + if len(bounding_circle) == 3: + if not all(isinstance(i, (int, float)) for i in bounding_circle): + msg = "bounding_circle should only contain numeric data" + raise ValueError(msg) + + *centroid, polygon_radius = cast(list[float], list(bounding_circle)) + elif len(bounding_circle) == 2 and isinstance(bounding_circle[0], (list, tuple)): + if not all( + isinstance(i, (int, float)) for i in bounding_circle[0] + ) or not isinstance(bounding_circle[1], (int, float)): + msg = "bounding_circle should only contain numeric data" + raise ValueError(msg) + + if len(bounding_circle[0]) != 2: + msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))" + raise ValueError(msg) + + centroid = cast(list[float], list(bounding_circle[0])) + polygon_radius = cast(float, bounding_circle[1]) + else: + msg = ( + "bounding_circle should contain 2D coordinates " + "and a radius (e.g. (x, y, r) or ((x, y), r) )" + ) + raise ValueError(msg) + + if polygon_radius <= 0: + msg = "bounding_circle radius should be > 0" + raise ValueError(msg) + + # 1.3 Check `rotation` has an appropriate value + if not isinstance(rotation, (int, float)): + msg = "rotation should be an int or float" # type: ignore[unreachable] + raise ValueError(msg) + + # 2. Define Helper Functions + def _apply_rotation(point: list[float], degrees: float) -> tuple[float, float]: + return ( + round( + point[0] * math.cos(math.radians(360 - degrees)) + - point[1] * math.sin(math.radians(360 - degrees)) + + centroid[0], + 2, + ), + round( + point[1] * math.cos(math.radians(360 - degrees)) + + point[0] * math.sin(math.radians(360 - degrees)) + + centroid[1], + 2, + ), + ) + + def _compute_polygon_vertex(angle: float) -> tuple[float, float]: + start_point = [polygon_radius, 0] + return _apply_rotation(start_point, angle) + + def _get_angles(n_sides: int, rotation: float) -> list[float]: + angles = [] + degrees = 360 / n_sides + # Start with the bottom left polygon vertex + current_angle = (270 - 0.5 * degrees) + rotation + for _ in range(n_sides): + angles.append(current_angle) + current_angle += degrees + if current_angle > 360: + current_angle -= 360 + return angles + + # 3. Variable Declarations + angles = _get_angles(n_sides, rotation) + + # 4. Compute Vertices + return [_compute_polygon_vertex(angle) for angle in angles] + + +def _color_diff( + color1: float | tuple[int, ...], color2: float | tuple[int, ...] +) -> float: + """ + Uses 1-norm distance to calculate difference between two values. + """ + first = color1 if isinstance(color1, tuple) else (color1,) + second = color2 if isinstance(color2, tuple) else (color2,) + + return sum(abs(first[i] - second[i]) for i in range(len(second))) diff --git a/venv/Lib/site-packages/PIL/ImageDraw2.py b/venv/Lib/site-packages/PIL/ImageDraw2.py new file mode 100644 index 00000000..3d68658e --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageDraw2.py @@ -0,0 +1,243 @@ +# +# The Python Imaging Library +# $Id$ +# +# WCK-style drawing interface operations +# +# History: +# 2003-12-07 fl created +# 2005-05-15 fl updated; added to PIL as ImageDraw2 +# 2005-05-15 fl added text support +# 2005-05-20 fl added arc/chord/pieslice support +# +# Copyright (c) 2003-2005 by Secret Labs AB +# Copyright (c) 2003-2005 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + + +""" +(Experimental) WCK-style drawing interface operations + +.. seealso:: :py:mod:`PIL.ImageDraw` +""" +from __future__ import annotations + +from typing import Any, AnyStr, BinaryIO + +from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath +from ._typing import Coords, StrOrBytesPath + + +class Pen: + """Stores an outline color and width.""" + + def __init__(self, color: str, width: int = 1, opacity: int = 255) -> None: + self.color = ImageColor.getrgb(color) + self.width = width + + +class Brush: + """Stores a fill color""" + + def __init__(self, color: str, opacity: int = 255) -> None: + self.color = ImageColor.getrgb(color) + + +class Font: + """Stores a TrueType font and color""" + + def __init__( + self, color: str, file: StrOrBytesPath | BinaryIO, size: float = 12 + ) -> None: + # FIXME: add support for bitmap fonts + self.color = ImageColor.getrgb(color) + self.font = ImageFont.truetype(file, size) + + +class Draw: + """ + (Experimental) WCK-style drawing interface + """ + + def __init__( + self, + image: Image.Image | str, + size: tuple[int, int] | list[int] | None = None, + color: float | tuple[float, ...] | str | None = None, + ) -> None: + if isinstance(image, str): + if size is None: + msg = "If image argument is mode string, size must be a list or tuple" + raise ValueError(msg) + image = Image.new(image, size, color) + self.draw = ImageDraw.Draw(image) + self.image = image + self.transform: tuple[float, float, float, float, float, float] | None = None + + def flush(self) -> Image.Image: + return self.image + + def render( + self, + op: str, + xy: Coords, + pen: Pen | Brush | None, + brush: Brush | Pen | None = None, + **kwargs: Any, + ) -> None: + # handle color arguments + outline = fill = None + width = 1 + if isinstance(pen, Pen): + outline = pen.color + width = pen.width + elif isinstance(brush, Pen): + outline = brush.color + width = brush.width + if isinstance(brush, Brush): + fill = brush.color + elif isinstance(pen, Brush): + fill = pen.color + # handle transformation + if self.transform: + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path + # render the item + if op in ("arc", "line"): + kwargs.setdefault("fill", outline) + else: + kwargs.setdefault("fill", fill) + kwargs.setdefault("outline", outline) + if op == "line": + kwargs.setdefault("width", width) + getattr(self.draw, op)(xy, **kwargs) + + def settransform(self, offset: tuple[float, float]) -> None: + """Sets a transformation offset.""" + (xoffset, yoffset) = offset + self.transform = (1, 0, xoffset, 0, 1, yoffset) + + def arc( + self, + xy: Coords, + pen: Pen | Brush | None, + start: float, + end: float, + *options: Any, + ) -> None: + """ + Draws an arc (a portion of a circle outline) between the start and end + angles, inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc` + """ + self.render("arc", xy, pen, *options, start=start, end=end) + + def chord( + self, + xy: Coords, + pen: Pen | Brush | None, + start: float, + end: float, + *options: Any, + ) -> None: + """ + Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points + with a straight line. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord` + """ + self.render("chord", xy, pen, *options, start=start, end=end) + + def ellipse(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws an ellipse inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse` + """ + self.render("ellipse", xy, pen, *options) + + def line(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws a line between the coordinates in the ``xy`` list. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line` + """ + self.render("line", xy, pen, *options) + + def pieslice( + self, + xy: Coords, + pen: Pen | Brush | None, + start: float, + end: float, + *options: Any, + ) -> None: + """ + Same as arc, but also draws straight lines between the end points and the + center of the bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice` + """ + self.render("pieslice", xy, pen, *options, start=start, end=end) + + def polygon(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws a polygon. + + The polygon outline consists of straight lines between the given + coordinates, plus a straight line between the last and the first + coordinate. + + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon` + """ + self.render("polygon", xy, pen, *options) + + def rectangle(self, xy: Coords, pen: Pen | Brush | None, *options: Any) -> None: + """ + Draws a rectangle. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle` + """ + self.render("rectangle", xy, pen, *options) + + def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None: + """ + Draws the string at the given position. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` + """ + if self.transform: + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path + self.draw.text(xy, text, font=font.font, fill=font.color) + + def textbbox( + self, xy: tuple[float, float], text: AnyStr, font: Font + ) -> tuple[float, float, float, float]: + """ + Returns bounding box (in pixels) of given text. + + :return: ``(left, top, right, bottom)`` bounding box + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox` + """ + if self.transform: + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path + return self.draw.textbbox(xy, text, font=font.font) + + def textlength(self, text: AnyStr, font: Font) -> float: + """ + Returns length (in pixels) of given text. + This is the amount by which following text should be offset. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textlength` + """ + return self.draw.textlength(text, font=font.font) diff --git a/venv/Lib/site-packages/PIL/ImageEnhance.py b/venv/Lib/site-packages/PIL/ImageEnhance.py new file mode 100644 index 00000000..0e7e6dd8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageEnhance.py @@ -0,0 +1,113 @@ +# +# The Python Imaging Library. +# $Id$ +# +# image enhancement classes +# +# For a background, see "Image Processing By Interpolation and +# Extrapolation", Paul Haeberli and Douglas Voorhies. Available +# at http://www.graficaobscura.com/interp/index.html +# +# History: +# 1996-03-23 fl Created +# 2009-06-16 fl Fixed mean calculation +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image, ImageFilter, ImageStat + + +class _Enhance: + image: Image.Image + degenerate: Image.Image + + def enhance(self, factor: float) -> Image.Image: + """ + Returns an enhanced image. + + :param factor: A floating point value controlling the enhancement. + Factor 1.0 always returns a copy of the original image, + lower factors mean less color (brightness, contrast, + etc), and higher values more. There are no restrictions + on this value. + :rtype: :py:class:`~PIL.Image.Image` + """ + return Image.blend(self.degenerate, self.image, factor) + + +class Color(_Enhance): + """Adjust image color balance. + + This class can be used to adjust the colour balance of an image, in + a manner similar to the controls on a colour TV set. An enhancement + factor of 0.0 gives a black and white image. A factor of 1.0 gives + the original image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + self.intermediate_mode = "L" + if "A" in image.getbands(): + self.intermediate_mode = "LA" + + if self.intermediate_mode != image.mode: + image = image.convert(self.intermediate_mode).convert(image.mode) + self.degenerate = image + + +class Contrast(_Enhance): + """Adjust image contrast. + + This class can be used to control the contrast of an image, similar + to the contrast control on a TV set. An enhancement factor of 0.0 + gives a solid gray image. A factor of 1.0 gives the original image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + if image.mode != "L": + image = image.convert("L") + mean = int(ImageStat.Stat(image).mean[0] + 0.5) + self.degenerate = Image.new("L", image.size, mean) + if self.degenerate.mode != self.image.mode: + self.degenerate = self.degenerate.convert(self.image.mode) + + if "A" in self.image.getbands(): + self.degenerate.putalpha(self.image.getchannel("A")) + + +class Brightness(_Enhance): + """Adjust image brightness. + + This class can be used to control the brightness of an image. An + enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the + original image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + self.degenerate = Image.new(image.mode, image.size, 0) + + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) + + +class Sharpness(_Enhance): + """Adjust image sharpness. + + This class can be used to adjust the sharpness of an image. An + enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the + original image, and a factor of 2.0 gives a sharpened image. + """ + + def __init__(self, image: Image.Image) -> None: + self.image = image + self.degenerate = image.filter(ImageFilter.SMOOTH) + + if "A" in image.getbands(): + self.degenerate.putalpha(image.getchannel("A")) diff --git a/venv/Lib/site-packages/PIL/ImageFile.py b/venv/Lib/site-packages/PIL/ImageFile.py new file mode 100644 index 00000000..bf556a2c --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageFile.py @@ -0,0 +1,922 @@ +# +# The Python Imaging Library. +# $Id$ +# +# base class for image file handlers +# +# history: +# 1995-09-09 fl Created +# 1996-03-11 fl Fixed load mechanism. +# 1996-04-15 fl Added pcx/xbm decoders. +# 1996-04-30 fl Added encoders. +# 1996-12-14 fl Added load helpers +# 1997-01-11 fl Use encode_to_file where possible +# 1997-08-27 fl Flush output in _save +# 1998-03-05 fl Use memory mapping for some modes +# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B" +# 1999-05-31 fl Added image parser +# 2000-10-12 fl Set readonly flag on memory-mapped images +# 2002-03-20 fl Use better messages for common decoder errors +# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available +# 2003-10-30 fl Added StubImageFile class +# 2004-02-25 fl Made incremental parser more robust +# +# Copyright (c) 1997-2004 by Secret Labs AB +# Copyright (c) 1995-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import abc +import io +import itertools +import logging +import os +import struct +from typing import IO, Any, NamedTuple, cast + +from . import ExifTags, Image +from ._deprecate import deprecate +from ._util import DeferredError, is_path + +TYPE_CHECKING = False +if TYPE_CHECKING: + from ._typing import StrOrBytesPath + +logger = logging.getLogger(__name__) + +MAXBLOCK = 65536 + +SAFEBLOCK = 1024 * 1024 + +LOAD_TRUNCATED_IMAGES = False +"""Whether or not to load truncated image files. User code may change this.""" + +ERRORS = { + -1: "image buffer overrun error", + -2: "decoding error", + -3: "unknown error", + -8: "bad configuration", + -9: "out of memory error", +} +""" +Dict of known error codes returned from :meth:`.PyDecoder.decode`, +:meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and +:meth:`.PyEncoder.encode_to_file`. +""" + + +# +# -------------------------------------------------------------------- +# Helpers + + +def _get_oserror(error: int, *, encoder: bool) -> OSError: + try: + msg = Image.core.getcodecstatus(error) + except AttributeError: + msg = ERRORS.get(error) + if not msg: + msg = f"{'encoder' if encoder else 'decoder'} error {error}" + msg += f" when {'writing' if encoder else 'reading'} image file" + return OSError(msg) + + +def raise_oserror(error: int) -> OSError: + deprecate( + "raise_oserror", + 12, + action="It is only useful for translating error codes returned by a codec's " + "decode() method, which ImageFile already does automatically.", + ) + raise _get_oserror(error, encoder=False) + + +def _tilesort(t: _Tile) -> int: + # sort on offset + return t[2] + + +class _Tile(NamedTuple): + codec_name: str + extents: tuple[int, int, int, int] | None + offset: int = 0 + args: tuple[Any, ...] | str | None = None + + +# +# -------------------------------------------------------------------- +# ImageFile base class + + +class ImageFile(Image.Image): + """Base class for image file format handlers.""" + + def __init__( + self, fp: StrOrBytesPath | IO[bytes], filename: str | bytes | None = None + ) -> None: + super().__init__() + + self._min_frame = 0 + + self.custom_mimetype: str | None = None + + self.tile: list[_Tile] = [] + """ A list of tile descriptors """ + + self.readonly = 1 # until we know better + + self.decoderconfig: tuple[Any, ...] = () + self.decodermaxblock = MAXBLOCK + + if is_path(fp): + # filename + self.fp = open(fp, "rb") + self.filename = os.fspath(fp) + self._exclusive_fp = True + else: + # stream + self.fp = cast(IO[bytes], fp) + self.filename = filename if filename is not None else "" + # can be overridden + self._exclusive_fp = False + + try: + try: + self._open() + except ( + IndexError, # end of data + TypeError, # end of data (ord) + KeyError, # unsupported mode + EOFError, # got header but not the first frame + struct.error, + ) as v: + raise SyntaxError(v) from v + + if not self.mode or self.size[0] <= 0 or self.size[1] <= 0: + msg = "not identified by this driver" + raise SyntaxError(msg) + except BaseException: + # close the file only if we have opened it this constructor + if self._exclusive_fp: + self.fp.close() + raise + + def _open(self) -> None: + pass + + def _close_fp(self): + if getattr(self, "_fp", False) and not isinstance(self._fp, DeferredError): + if self._fp != self.fp: + self._fp.close() + self._fp = DeferredError(ValueError("Operation on closed image")) + if self.fp: + self.fp.close() + + def close(self) -> None: + """ + Closes the file pointer, if possible. + + This operation will destroy the image core and release its memory. + The image data will be unusable afterward. + + This function is required to close images that have multiple frames or + have not had their file read and closed by the + :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for + more information. + """ + try: + self._close_fp() + self.fp = None + except Exception as msg: + logger.debug("Error closing: %s", msg) + + super().close() + + def get_child_images(self) -> list[ImageFile]: + child_images = [] + exif = self.getexif() + ifds = [] + if ExifTags.Base.SubIFDs in exif: + subifd_offsets = exif[ExifTags.Base.SubIFDs] + if subifd_offsets: + if not isinstance(subifd_offsets, tuple): + subifd_offsets = (subifd_offsets,) + for subifd_offset in subifd_offsets: + ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset)) + ifd1 = exif.get_ifd(ExifTags.IFD.IFD1) + if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset): + assert exif._info is not None + ifds.append((ifd1, exif._info.next)) + + offset = None + for ifd, ifd_offset in ifds: + assert self.fp is not None + current_offset = self.fp.tell() + if offset is None: + offset = current_offset + + fp = self.fp + if ifd is not None: + thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset) + if thumbnail_offset is not None: + thumbnail_offset += getattr(self, "_exif_offset", 0) + self.fp.seek(thumbnail_offset) + + length = ifd.get(ExifTags.Base.JpegIFByteCount) + assert isinstance(length, int) + data = self.fp.read(length) + fp = io.BytesIO(data) + + with Image.open(fp) as im: + from . import TiffImagePlugin + + if thumbnail_offset is None and isinstance( + im, TiffImagePlugin.TiffImageFile + ): + im._frame_pos = [ifd_offset] + im._seek(0) + im.load() + child_images.append(im) + + if offset is not None: + assert self.fp is not None + self.fp.seek(offset) + return child_images + + def get_format_mimetype(self) -> str | None: + if self.custom_mimetype: + return self.custom_mimetype + if self.format is not None: + return Image.MIME.get(self.format.upper()) + return None + + def __getstate__(self) -> list[Any]: + return super().__getstate__() + [self.filename] + + def __setstate__(self, state: list[Any]) -> None: + self.tile = [] + if len(state) > 5: + self.filename = state[5] + super().__setstate__(state) + + def verify(self) -> None: + """Check file integrity""" + + # raise exception if something's wrong. must be called + # directly after open, and closes file when finished. + if self._exclusive_fp: + self.fp.close() + self.fp = None + + def load(self) -> Image.core.PixelAccess | None: + """Load image data based on tile list""" + + if not self.tile and self._im is None: + msg = "cannot load this image" + raise OSError(msg) + + pixel = Image.Image.load(self) + if not self.tile: + return pixel + + self.map: mmap.mmap | None = None + use_mmap = self.filename and len(self.tile) == 1 + + readonly = 0 + + # look for read/seek overrides + if hasattr(self, "load_read"): + read = self.load_read + # don't use mmap if there are custom read/seek functions + use_mmap = False + else: + read = self.fp.read + + if hasattr(self, "load_seek"): + seek = self.load_seek + use_mmap = False + else: + seek = self.fp.seek + + if use_mmap: + # try memory mapping + decoder_name, extents, offset, args = self.tile[0] + if isinstance(args, str): + args = (args, 0, 1) + if ( + decoder_name == "raw" + and isinstance(args, tuple) + and len(args) >= 3 + and args[0] == self.mode + and args[0] in Image._MAPMODES + ): + try: + # use mmap, if possible + import mmap + + with open(self.filename) as fp: + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + if offset + self.size[1] * args[1] > self.map.size(): + msg = "buffer is not large enough" + raise OSError(msg) + self.im = Image.core.map_buffer( + self.map, self.size, decoder_name, offset, args + ) + readonly = 1 + # After trashing self.im, + # we might need to reload the palette data. + if self.palette: + self.palette.dirty = 1 + except (AttributeError, OSError, ImportError): + self.map = None + + self.load_prepare() + err_code = -3 # initialize to unknown error + if not self.map: + # sort tiles in file order + self.tile.sort(key=_tilesort) + + # FIXME: This is a hack to handle TIFF's JpegTables tag. + prefix = getattr(self, "tile_prefix", b"") + + # Remove consecutive duplicates that only differ by their offset + self.tile = [ + list(tiles)[-1] + for _, tiles in itertools.groupby( + self.tile, lambda tile: (tile[0], tile[1], tile[3]) + ) + ] + for i, (decoder_name, extents, offset, args) in enumerate(self.tile): + seek(offset) + decoder = Image._getdecoder( + self.mode, decoder_name, args, self.decoderconfig + ) + try: + decoder.setimage(self.im, extents) + if decoder.pulls_fd: + decoder.setfd(self.fp) + err_code = decoder.decode(b"")[1] + else: + b = prefix + while True: + read_bytes = self.decodermaxblock + if i + 1 < len(self.tile): + next_offset = self.tile[i + 1].offset + if next_offset > offset: + read_bytes = next_offset - offset + try: + s = read(read_bytes) + except (IndexError, struct.error) as e: + # truncated png/gif + if LOAD_TRUNCATED_IMAGES: + break + else: + msg = "image file is truncated" + raise OSError(msg) from e + + if not s: # truncated jpeg + if LOAD_TRUNCATED_IMAGES: + break + else: + msg = ( + "image file is truncated " + f"({len(b)} bytes not processed)" + ) + raise OSError(msg) + + b = b + s + n, err_code = decoder.decode(b) + if n < 0: + break + b = b[n:] + finally: + # Need to cleanup here to prevent leaks + decoder.cleanup() + + self.tile = [] + self.readonly = readonly + + self.load_end() + + if self._exclusive_fp and self._close_exclusive_fp_after_loading: + self.fp.close() + self.fp = None + + if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0: + # still raised if decoder fails to return anything + raise _get_oserror(err_code, encoder=False) + + return Image.Image.load(self) + + def load_prepare(self) -> None: + # create image memory if necessary + if self._im is None: + self.im = Image.core.new(self.mode, self.size) + # create palette (optional) + if self.mode == "P": + Image.Image.load(self) + + def load_end(self) -> None: + # may be overridden + pass + + # may be defined for contained formats + # def load_seek(self, pos: int) -> None: + # pass + + # may be defined for blocked formats (e.g. PNG) + # def load_read(self, read_bytes: int) -> bytes: + # pass + + def _seek_check(self, frame: int) -> bool: + if ( + frame < self._min_frame + # Only check upper limit on frames if additional seek operations + # are not required to do so + or ( + not (hasattr(self, "_n_frames") and self._n_frames is None) + and frame >= getattr(self, "n_frames") + self._min_frame + ) + ): + msg = "attempt to seek outside sequence" + raise EOFError(msg) + + return self.tell() != frame + + +class StubHandler(abc.ABC): + def open(self, im: StubImageFile) -> None: + pass + + @abc.abstractmethod + def load(self, im: StubImageFile) -> Image.Image: + pass + + +class StubImageFile(ImageFile, metaclass=abc.ABCMeta): + """ + Base class for stub image loaders. + + A stub loader is an image loader that can identify files of a + certain format, but relies on external code to load the file. + """ + + @abc.abstractmethod + def _open(self) -> None: + pass + + def load(self) -> Image.core.PixelAccess | None: + loader = self._load() + if loader is None: + msg = f"cannot find loader for this {self.format} file" + raise OSError(msg) + image = loader.load(self) + assert image is not None + # become the other object (!) + self.__class__ = image.__class__ # type: ignore[assignment] + self.__dict__ = image.__dict__ + return image.load() + + @abc.abstractmethod + def _load(self) -> StubHandler | None: + """(Hook) Find actual image loader.""" + pass + + +class Parser: + """ + Incremental image parser. This class implements the standard + feed/close consumer interface. + """ + + incremental = None + image: Image.Image | None = None + data: bytes | None = None + decoder: Image.core.ImagingDecoder | PyDecoder | None = None + offset = 0 + finished = 0 + + def reset(self) -> None: + """ + (Consumer) Reset the parser. Note that you can only call this + method immediately after you've created a parser; parser + instances cannot be reused. + """ + assert self.data is None, "cannot reuse parsers" + + def feed(self, data: bytes) -> None: + """ + (Consumer) Feed data to the parser. + + :param data: A string buffer. + :exception OSError: If the parser failed to parse the image file. + """ + # collect data + + if self.finished: + return + + if self.data is None: + self.data = data + else: + self.data = self.data + data + + # parse what we have + if self.decoder: + if self.offset > 0: + # skip header + skip = min(len(self.data), self.offset) + self.data = self.data[skip:] + self.offset = self.offset - skip + if self.offset > 0 or not self.data: + return + + n, e = self.decoder.decode(self.data) + + if n < 0: + # end of stream + self.data = None + self.finished = 1 + if e < 0: + # decoding error + self.image = None + raise _get_oserror(e, encoder=False) + else: + # end of image + return + self.data = self.data[n:] + + elif self.image: + # if we end up here with no decoder, this file cannot + # be incrementally parsed. wait until we've gotten all + # available data + pass + + else: + # attempt to open this file + try: + with io.BytesIO(self.data) as fp: + im = Image.open(fp) + except OSError: + pass # not enough data + else: + flag = hasattr(im, "load_seek") or hasattr(im, "load_read") + if flag or len(im.tile) != 1: + # custom load code, or multiple tiles + self.decode = None + else: + # initialize decoder + im.load_prepare() + d, e, o, a = im.tile[0] + im.tile = [] + self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig) + self.decoder.setimage(im.im, e) + + # calculate decoder offset + self.offset = o + if self.offset <= len(self.data): + self.data = self.data[self.offset :] + self.offset = 0 + + self.image = im + + def __enter__(self) -> Parser: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def close(self) -> Image.Image: + """ + (Consumer) Close the stream. + + :returns: An image object. + :exception OSError: If the parser failed to parse the image file either + because it cannot be identified or cannot be + decoded. + """ + # finish decoding + if self.decoder: + # get rid of what's left in the buffers + self.feed(b"") + self.data = self.decoder = None + if not self.finished: + msg = "image was incomplete" + raise OSError(msg) + if not self.image: + msg = "cannot parse this image" + raise OSError(msg) + if self.data: + # incremental parsing not possible; reopen the file + # not that we have all data + with io.BytesIO(self.data) as fp: + try: + self.image = Image.open(fp) + finally: + self.image.load() + return self.image + + +# -------------------------------------------------------------------- + + +def _save(im: Image.Image, fp: IO[bytes], tile: list[_Tile], bufsize: int = 0) -> None: + """Helper to save image based on tile list + + :param im: Image object. + :param fp: File object. + :param tile: Tile list. + :param bufsize: Optional buffer size + """ + + im.load() + if not hasattr(im, "encoderconfig"): + im.encoderconfig = () + tile.sort(key=_tilesort) + # FIXME: make MAXBLOCK a configuration parameter + # It would be great if we could have the encoder specify what it needs + # But, it would need at least the image size in most cases. RawEncode is + # a tricky case. + bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c + try: + fh = fp.fileno() + fp.flush() + _encode_tile(im, fp, tile, bufsize, fh) + except (AttributeError, io.UnsupportedOperation) as exc: + _encode_tile(im, fp, tile, bufsize, None, exc) + if hasattr(fp, "flush"): + fp.flush() + + +def _encode_tile( + im: Image.Image, + fp: IO[bytes], + tile: list[_Tile], + bufsize: int, + fh: int | None, + exc: BaseException | None = None, +) -> None: + for encoder_name, extents, offset, args in tile: + if offset > 0: + fp.seek(offset) + encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig) + try: + encoder.setimage(im.im, extents) + if encoder.pushes_fd: + encoder.setfd(fp) + errcode = encoder.encode_to_pyfd()[1] + else: + if exc: + # compress to Python file-compatible object + while True: + errcode, data = encoder.encode(bufsize)[1:] + fp.write(data) + if errcode: + break + else: + # slight speedup: compress to real file object + assert fh is not None + errcode = encoder.encode_to_file(fh, bufsize) + if errcode < 0: + raise _get_oserror(errcode, encoder=True) from exc + finally: + encoder.cleanup() + + +def _safe_read(fp: IO[bytes], size: int) -> bytes: + """ + Reads large blocks in a safe way. Unlike fp.read(n), this function + doesn't trust the user. If the requested size is larger than + SAFEBLOCK, the file is read block by block. + + :param fp: File handle. Must implement a read method. + :param size: Number of bytes to read. + :returns: A string containing size bytes of data. + + Raises an OSError if the file is truncated and the read cannot be completed + + """ + if size <= 0: + return b"" + if size <= SAFEBLOCK: + data = fp.read(size) + if len(data) < size: + msg = "Truncated File Read" + raise OSError(msg) + return data + blocks: list[bytes] = [] + remaining_size = size + while remaining_size > 0: + block = fp.read(min(remaining_size, SAFEBLOCK)) + if not block: + break + blocks.append(block) + remaining_size -= len(block) + if sum(len(block) for block in blocks) < size: + msg = "Truncated File Read" + raise OSError(msg) + return b"".join(blocks) + + +class PyCodecState: + def __init__(self) -> None: + self.xsize = 0 + self.ysize = 0 + self.xoff = 0 + self.yoff = 0 + + def extents(self) -> tuple[int, int, int, int]: + return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize + + +class PyCodec: + fd: IO[bytes] | None + + def __init__(self, mode: str, *args: Any) -> None: + self.im: Image.core.ImagingCore | None = None + self.state = PyCodecState() + self.fd = None + self.mode = mode + self.init(args) + + def init(self, args: tuple[Any, ...]) -> None: + """ + Override to perform codec specific initialization + + :param args: Tuple of arg items from the tile entry + :returns: None + """ + self.args = args + + def cleanup(self) -> None: + """ + Override to perform codec specific cleanup + + :returns: None + """ + pass + + def setfd(self, fd: IO[bytes]) -> None: + """ + Called from ImageFile to set the Python file-like object + + :param fd: A Python file-like object + :returns: None + """ + self.fd = fd + + def setimage( + self, + im: Image.core.ImagingCore, + extents: tuple[int, int, int, int] | None = None, + ) -> None: + """ + Called from ImageFile to set the core output image for the codec + + :param im: A core image object + :param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle + for this tile + :returns: None + """ + + # following c code + self.im = im + + if extents: + (x0, y0, x1, y1) = extents + else: + (x0, y0, x1, y1) = (0, 0, 0, 0) + + if x0 == 0 and x1 == 0: + self.state.xsize, self.state.ysize = self.im.size + else: + self.state.xoff = x0 + self.state.yoff = y0 + self.state.xsize = x1 - x0 + self.state.ysize = y1 - y0 + + if self.state.xsize <= 0 or self.state.ysize <= 0: + msg = "Size cannot be negative" + raise ValueError(msg) + + if ( + self.state.xsize + self.state.xoff > self.im.size[0] + or self.state.ysize + self.state.yoff > self.im.size[1] + ): + msg = "Tile cannot extend outside image" + raise ValueError(msg) + + +class PyDecoder(PyCodec): + """ + Python implementation of a format decoder. Override this class and + add the decoding logic in the :meth:`decode` method. + + See :ref:`Writing Your Own File Codec in Python` + """ + + _pulls_fd = False + + @property + def pulls_fd(self) -> bool: + return self._pulls_fd + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + """ + Override to perform the decoding process. + + :param buffer: A bytes object with the data to be decoded. + :returns: A tuple of ``(bytes consumed, errcode)``. + If finished with decoding return -1 for the bytes consumed. + Err codes are from :data:`.ImageFile.ERRORS`. + """ + msg = "unavailable in base decoder" + raise NotImplementedError(msg) + + def set_as_raw( + self, data: bytes, rawmode: str | None = None, extra: tuple[Any, ...] = () + ) -> None: + """ + Convenience method to set the internal image from a stream of raw data + + :param data: Bytes to be set + :param rawmode: The rawmode to be used for the decoder. + If not specified, it will default to the mode of the image + :param extra: Extra arguments for the decoder. + :returns: None + """ + + if not rawmode: + rawmode = self.mode + d = Image._getdecoder(self.mode, "raw", rawmode, extra) + assert self.im is not None + d.setimage(self.im, self.state.extents()) + s = d.decode(data) + + if s[0] >= 0: + msg = "not enough image data" + raise ValueError(msg) + if s[1] != 0: + msg = "cannot decode image data" + raise ValueError(msg) + + +class PyEncoder(PyCodec): + """ + Python implementation of a format encoder. Override this class and + add the decoding logic in the :meth:`encode` method. + + See :ref:`Writing Your Own File Codec in Python` + """ + + _pushes_fd = False + + @property + def pushes_fd(self) -> bool: + return self._pushes_fd + + def encode(self, bufsize: int) -> tuple[int, int, bytes]: + """ + Override to perform the encoding process. + + :param bufsize: Buffer size. + :returns: A tuple of ``(bytes encoded, errcode, bytes)``. + If finished with encoding return 1 for the error code. + Err codes are from :data:`.ImageFile.ERRORS`. + """ + msg = "unavailable in base encoder" + raise NotImplementedError(msg) + + def encode_to_pyfd(self) -> tuple[int, int]: + """ + If ``pushes_fd`` is ``True``, then this method will be used, + and ``encode()`` will only be called once. + + :returns: A tuple of ``(bytes consumed, errcode)``. + Err codes are from :data:`.ImageFile.ERRORS`. + """ + if not self.pushes_fd: + return 0, -8 # bad configuration + bytes_consumed, errcode, data = self.encode(0) + if data: + assert self.fd is not None + self.fd.write(data) + return bytes_consumed, errcode + + def encode_to_file(self, fh: int, bufsize: int) -> int: + """ + :param fh: File handle. + :param bufsize: Buffer size. + + :returns: If finished successfully, return 0. + Otherwise, return an error code. Err codes are from + :data:`.ImageFile.ERRORS`. + """ + errcode = 0 + while errcode == 0: + status, errcode, buf = self.encode(bufsize) + if status > 0: + os.write(fh, buf[status:]) + return errcode diff --git a/venv/Lib/site-packages/PIL/ImageFilter.py b/venv/Lib/site-packages/PIL/ImageFilter.py new file mode 100644 index 00000000..b9ed54ab --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageFilter.py @@ -0,0 +1,604 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard filters +# +# History: +# 1995-11-27 fl Created +# 2002-06-08 fl Added rank and mode filters +# 2003-09-15 fl Fixed rank calculation in rank filter; added expand call +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2002 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import abc +import functools +from collections.abc import Sequence +from types import ModuleType +from typing import Any, Callable, cast + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import _imaging + from ._typing import NumpyArray + + +class Filter(abc.ABC): + @abc.abstractmethod + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + pass + + +class MultibandFilter(Filter): + pass + + +class BuiltinFilter(MultibandFilter): + filterargs: tuple[Any, ...] + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + if image.mode == "P": + msg = "cannot filter palette images" + raise ValueError(msg) + return image.filter(*self.filterargs) + + +class Kernel(BuiltinFilter): + """ + Create a convolution kernel. This only supports 3x3 and 5x5 integer and floating + point kernels. + + Kernels can only be applied to "L" and "RGB" images. + + :param size: Kernel size, given as (width, height). This must be (3,3) or (5,5). + :param kernel: A sequence containing kernel weights. The kernel will be flipped + vertically before being applied to the image. + :param scale: Scale factor. If given, the result for each pixel is divided by this + value. The default is the sum of the kernel weights. + :param offset: Offset. If given, this value is added to the result, after it has + been divided by the scale factor. + """ + + name = "Kernel" + + def __init__( + self, + size: tuple[int, int], + kernel: Sequence[float], + scale: float | None = None, + offset: float = 0, + ) -> None: + if scale is None: + # default scale is sum of kernel + scale = functools.reduce(lambda a, b: a + b, kernel) + if size[0] * size[1] != len(kernel): + msg = "not enough coefficients in kernel" + raise ValueError(msg) + self.filterargs = size, scale, offset, kernel + + +class RankFilter(Filter): + """ + Create a rank filter. The rank filter sorts all pixels in + a window of the given size, and returns the ``rank``'th value. + + :param size: The kernel size, in pixels. + :param rank: What pixel value to pick. Use 0 for a min filter, + ``size * size / 2`` for a median filter, ``size * size - 1`` + for a max filter, etc. + """ + + name = "Rank" + + def __init__(self, size: int, rank: int) -> None: + self.size = size + self.rank = rank + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + if image.mode == "P": + msg = "cannot filter palette images" + raise ValueError(msg) + image = image.expand(self.size // 2, self.size // 2) + return image.rankfilter(self.size, self.rank) + + +class MedianFilter(RankFilter): + """ + Create a median filter. Picks the median pixel value in a window with the + given size. + + :param size: The kernel size, in pixels. + """ + + name = "Median" + + def __init__(self, size: int = 3) -> None: + self.size = size + self.rank = size * size // 2 + + +class MinFilter(RankFilter): + """ + Create a min filter. Picks the lowest pixel value in a window with the + given size. + + :param size: The kernel size, in pixels. + """ + + name = "Min" + + def __init__(self, size: int = 3) -> None: + self.size = size + self.rank = 0 + + +class MaxFilter(RankFilter): + """ + Create a max filter. Picks the largest pixel value in a window with the + given size. + + :param size: The kernel size, in pixels. + """ + + name = "Max" + + def __init__(self, size: int = 3) -> None: + self.size = size + self.rank = size * size - 1 + + +class ModeFilter(Filter): + """ + Create a mode filter. Picks the most frequent pixel value in a box with the + given size. Pixel values that occur only once or twice are ignored; if no + pixel value occurs more than twice, the original pixel value is preserved. + + :param size: The kernel size, in pixels. + """ + + name = "Mode" + + def __init__(self, size: int = 3) -> None: + self.size = size + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + return image.modefilter(self.size) + + +class GaussianBlur(MultibandFilter): + """Blurs the image with a sequence of extended box filters, which + approximates a Gaussian kernel. For details on accuracy see + + + :param radius: Standard deviation of the Gaussian kernel. Either a sequence of two + numbers for x and y, or a single number for both. + """ + + name = "GaussianBlur" + + def __init__(self, radius: float | Sequence[float] = 2) -> None: + self.radius = radius + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + xy = self.radius + if isinstance(xy, (int, float)): + xy = (xy, xy) + if xy == (0, 0): + return image.copy() + return image.gaussian_blur(xy) + + +class BoxBlur(MultibandFilter): + """Blurs the image by setting each pixel to the average value of the pixels + in a square box extending radius pixels in each direction. + Supports float radius of arbitrary size. Uses an optimized implementation + which runs in linear time relative to the size of the image + for any radius value. + + :param radius: Size of the box in a direction. Either a sequence of two numbers for + x and y, or a single number for both. + + Radius 0 does not blur, returns an identical image. + Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total. + """ + + name = "BoxBlur" + + def __init__(self, radius: float | Sequence[float]) -> None: + xy = radius if isinstance(radius, (tuple, list)) else (radius, radius) + if xy[0] < 0 or xy[1] < 0: + msg = "radius must be >= 0" + raise ValueError(msg) + self.radius = radius + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + xy = self.radius + if isinstance(xy, (int, float)): + xy = (xy, xy) + if xy == (0, 0): + return image.copy() + return image.box_blur(xy) + + +class UnsharpMask(MultibandFilter): + """Unsharp mask filter. + + See Wikipedia's entry on `digital unsharp masking`_ for an explanation of + the parameters. + + :param radius: Blur Radius + :param percent: Unsharp strength, in percent + :param threshold: Threshold controls the minimum brightness change that + will be sharpened + + .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking + + """ + + name = "UnsharpMask" + + def __init__( + self, radius: float = 2, percent: int = 150, threshold: int = 3 + ) -> None: + self.radius = radius + self.percent = percent + self.threshold = threshold + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + return image.unsharp_mask(self.radius, self.percent, self.threshold) + + +class BLUR(BuiltinFilter): + name = "Blur" + # fmt: off + filterargs = (5, 5), 16, 0, ( + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + ) + # fmt: on + + +class CONTOUR(BuiltinFilter): + name = "Contour" + # fmt: off + filterargs = (3, 3), 1, 255, ( + -1, -1, -1, + -1, 8, -1, + -1, -1, -1, + ) + # fmt: on + + +class DETAIL(BuiltinFilter): + name = "Detail" + # fmt: off + filterargs = (3, 3), 6, 0, ( + 0, -1, 0, + -1, 10, -1, + 0, -1, 0, + ) + # fmt: on + + +class EDGE_ENHANCE(BuiltinFilter): + name = "Edge-enhance" + # fmt: off + filterargs = (3, 3), 2, 0, ( + -1, -1, -1, + -1, 10, -1, + -1, -1, -1, + ) + # fmt: on + + +class EDGE_ENHANCE_MORE(BuiltinFilter): + name = "Edge-enhance More" + # fmt: off + filterargs = (3, 3), 1, 0, ( + -1, -1, -1, + -1, 9, -1, + -1, -1, -1, + ) + # fmt: on + + +class EMBOSS(BuiltinFilter): + name = "Emboss" + # fmt: off + filterargs = (3, 3), 1, 128, ( + -1, 0, 0, + 0, 1, 0, + 0, 0, 0, + ) + # fmt: on + + +class FIND_EDGES(BuiltinFilter): + name = "Find Edges" + # fmt: off + filterargs = (3, 3), 1, 0, ( + -1, -1, -1, + -1, 8, -1, + -1, -1, -1, + ) + # fmt: on + + +class SHARPEN(BuiltinFilter): + name = "Sharpen" + # fmt: off + filterargs = (3, 3), 16, 0, ( + -2, -2, -2, + -2, 32, -2, + -2, -2, -2, + ) + # fmt: on + + +class SMOOTH(BuiltinFilter): + name = "Smooth" + # fmt: off + filterargs = (3, 3), 13, 0, ( + 1, 1, 1, + 1, 5, 1, + 1, 1, 1, + ) + # fmt: on + + +class SMOOTH_MORE(BuiltinFilter): + name = "Smooth More" + # fmt: off + filterargs = (5, 5), 100, 0, ( + 1, 1, 1, 1, 1, + 1, 5, 5, 5, 1, + 1, 5, 44, 5, 1, + 1, 5, 5, 5, 1, + 1, 1, 1, 1, 1, + ) + # fmt: on + + +class Color3DLUT(MultibandFilter): + """Three-dimensional color lookup table. + + Transforms 3-channel pixels using the values of the channels as coordinates + in the 3D lookup table and interpolating the nearest elements. + + This method allows you to apply almost any color transformation + in constant time by using pre-calculated decimated tables. + + .. versionadded:: 5.2.0 + + :param size: Size of the table. One int or tuple of (int, int, int). + Minimal size in any dimension is 2, maximum is 65. + :param table: Flat lookup table. A list of ``channels * size**3`` + float elements or a list of ``size**3`` channels-sized + tuples with floats. Channels are changed first, + then first dimension, then second, then third. + Value 0.0 corresponds lowest value of output, 1.0 highest. + :param channels: Number of channels in the table. Could be 3 or 4. + Default is 3. + :param target_mode: A mode for the result image. Should have not less + than ``channels`` channels. Default is ``None``, + which means that mode wouldn't be changed. + """ + + name = "Color 3D LUT" + + def __init__( + self, + size: int | tuple[int, int, int], + table: Sequence[float] | Sequence[Sequence[int]] | NumpyArray, + channels: int = 3, + target_mode: str | None = None, + **kwargs: bool, + ) -> None: + if channels not in (3, 4): + msg = "Only 3 or 4 output channels are supported" + raise ValueError(msg) + self.size = size = self._check_size(size) + self.channels = channels + self.mode = target_mode + + # Hidden flag `_copy_table=False` could be used to avoid extra copying + # of the table if the table is specially made for the constructor. + copy_table = kwargs.get("_copy_table", True) + items = size[0] * size[1] * size[2] + wrong_size = False + + numpy: ModuleType | None = None + if hasattr(table, "shape"): + try: + import numpy + except ImportError: + pass + + if numpy and isinstance(table, numpy.ndarray): + numpy_table: NumpyArray = table + if copy_table: + numpy_table = numpy_table.copy() + + if numpy_table.shape in [ + (items * channels,), + (items, channels), + (size[2], size[1], size[0], channels), + ]: + table = numpy_table.reshape(items * channels) + else: + wrong_size = True + + else: + if copy_table: + table = list(table) + + # Convert to a flat list + if table and isinstance(table[0], (list, tuple)): + raw_table = cast(Sequence[Sequence[int]], table) + flat_table: list[int] = [] + for pixel in raw_table: + if len(pixel) != channels: + msg = ( + "The elements of the table should " + f"have a length of {channels}." + ) + raise ValueError(msg) + flat_table.extend(pixel) + table = flat_table + + if wrong_size or len(table) != items * channels: + msg = ( + "The table should have either channels * size**3 float items " + "or size**3 items of channels-sized tuples with floats. " + f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. " + f"Actual length: {len(table)}" + ) + raise ValueError(msg) + self.table = table + + @staticmethod + def _check_size(size: Any) -> tuple[int, int, int]: + try: + _, _, _ = size + except ValueError as e: + msg = "Size should be either an integer or a tuple of three integers." + raise ValueError(msg) from e + except TypeError: + size = (size, size, size) + size = tuple(int(x) for x in size) + for size_1d in size: + if not 2 <= size_1d <= 65: + msg = "Size should be in [2, 65] range." + raise ValueError(msg) + return size + + @classmethod + def generate( + cls, + size: int | tuple[int, int, int], + callback: Callable[[float, float, float], tuple[float, ...]], + channels: int = 3, + target_mode: str | None = None, + ) -> Color3DLUT: + """Generates new LUT using provided callback. + + :param size: Size of the table. Passed to the constructor. + :param callback: Function with three parameters which correspond + three color channels. Will be called ``size**3`` + times with values from 0.0 to 1.0 and should return + a tuple with ``channels`` elements. + :param channels: The number of channels which should return callback. + :param target_mode: Passed to the constructor of the resulting + lookup table. + """ + size_1d, size_2d, size_3d = cls._check_size(size) + if channels not in (3, 4): + msg = "Only 3 or 4 output channels are supported" + raise ValueError(msg) + + table: list[float] = [0] * (size_1d * size_2d * size_3d * channels) + idx_out = 0 + for b in range(size_3d): + for g in range(size_2d): + for r in range(size_1d): + table[idx_out : idx_out + channels] = callback( + r / (size_1d - 1), g / (size_2d - 1), b / (size_3d - 1) + ) + idx_out += channels + + return cls( + (size_1d, size_2d, size_3d), + table, + channels=channels, + target_mode=target_mode, + _copy_table=False, + ) + + def transform( + self, + callback: Callable[..., tuple[float, ...]], + with_normals: bool = False, + channels: int | None = None, + target_mode: str | None = None, + ) -> Color3DLUT: + """Transforms the table values using provided callback and returns + a new LUT with altered values. + + :param callback: A function which takes old lookup table values + and returns a new set of values. The number + of arguments which function should take is + ``self.channels`` or ``3 + self.channels`` + if ``with_normals`` flag is set. + Should return a tuple of ``self.channels`` or + ``channels`` elements if it is set. + :param with_normals: If true, ``callback`` will be called with + coordinates in the color cube as the first + three arguments. Otherwise, ``callback`` + will be called only with actual color values. + :param channels: The number of channels in the resulting lookup table. + :param target_mode: Passed to the constructor of the resulting + lookup table. + """ + if channels not in (None, 3, 4): + msg = "Only 3 or 4 output channels are supported" + raise ValueError(msg) + ch_in = self.channels + ch_out = channels or ch_in + size_1d, size_2d, size_3d = self.size + + table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out) + idx_in = 0 + idx_out = 0 + for b in range(size_3d): + for g in range(size_2d): + for r in range(size_1d): + values = self.table[idx_in : idx_in + ch_in] + if with_normals: + values = callback( + r / (size_1d - 1), + g / (size_2d - 1), + b / (size_3d - 1), + *values, + ) + else: + values = callback(*values) + table[idx_out : idx_out + ch_out] = values + idx_in += ch_in + idx_out += ch_out + + return type(self)( + self.size, + table, + channels=ch_out, + target_mode=target_mode or self.mode, + _copy_table=False, + ) + + def __repr__(self) -> str: + r = [ + f"{self.__class__.__name__} from {self.table.__class__.__name__}", + "size={:d}x{:d}x{:d}".format(*self.size), + f"channels={self.channels:d}", + ] + if self.mode: + r.append(f"target_mode={self.mode}") + return "<{}>".format(" ".join(r)) + + def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: + from . import Image + + return image.color_lut_3d( + self.mode or image.mode, + Image.Resampling.BILINEAR, + self.channels, + self.size, + self.table, + ) diff --git a/venv/Lib/site-packages/PIL/ImageFont.py b/venv/Lib/site-packages/PIL/ImageFont.py new file mode 100644 index 00000000..329c463f --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageFont.py @@ -0,0 +1,1339 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PIL raster font management +# +# History: +# 1996-08-07 fl created (experimental) +# 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3 +# 1999-02-06 fl rewrote most font management stuff in C +# 1999-03-17 fl take pth files into account in load_path (from Richard Jones) +# 2001-02-17 fl added freetype support +# 2001-05-09 fl added TransposedFont wrapper class +# 2002-03-04 fl make sure we have a "L" or "1" font +# 2002-12-04 fl skip non-directory entries in the system path +# 2003-04-29 fl add embedded default font +# 2003-09-27 fl added support for truetype charmap encodings +# +# Todo: +# Adapt to PILFONT2 format (16-bit fonts, compressed, single file) +# +# Copyright (c) 1997-2003 by Secret Labs AB +# Copyright (c) 1996-2003 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# + +from __future__ import annotations + +import base64 +import os +import sys +import warnings +from enum import IntEnum +from io import BytesIO +from types import ModuleType +from typing import IO, Any, BinaryIO, TypedDict, cast + +from . import Image, features +from ._typing import StrOrBytesPath +from ._util import DeferredError, is_path + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import ImageFile + from ._imaging import ImagingFont + from ._imagingft import Font + + +class Axis(TypedDict): + minimum: int | None + default: int | None + maximum: int | None + name: bytes | None + + +class Layout(IntEnum): + BASIC = 0 + RAQM = 1 + + +MAX_STRING_LENGTH = 1_000_000 + + +core: ModuleType | DeferredError +try: + from . import _imagingft as core +except ImportError as ex: + core = DeferredError.new(ex) + + +def _string_length_check(text: str | bytes | bytearray) -> None: + if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH: + msg = "too many characters in string" + raise ValueError(msg) + + +# FIXME: add support for pilfont2 format (see FontFile.py) + +# -------------------------------------------------------------------- +# Font metrics format: +# "PILfont" LF +# fontdescriptor LF +# (optional) key=value... LF +# "DATA" LF +# binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox) +# +# To place a character, cut out srcbox and paste at dstbox, +# relative to the character position. Then move the character +# position according to dx, dy. +# -------------------------------------------------------------------- + + +class ImageFont: + """PIL font wrapper""" + + font: ImagingFont + + def _load_pilfont(self, filename: str) -> None: + with open(filename, "rb") as fp: + image: ImageFile.ImageFile | None = None + root = os.path.splitext(filename)[0] + + for ext in (".png", ".gif", ".pbm"): + if image: + image.close() + try: + fullname = root + ext + image = Image.open(fullname) + except Exception: + pass + else: + if image and image.mode in ("1", "L"): + break + else: + if image: + image.close() + + msg = f"cannot find glyph data file {root}.{{gif|pbm|png}}" + raise OSError(msg) + + self.file = fullname + + self._load_pilfont_data(fp, image) + image.close() + + def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: + # read PILfont header + if file.readline() != b"PILfont\n": + msg = "Not a PILfont file" + raise SyntaxError(msg) + file.readline().split(b";") + self.info = [] # FIXME: should be a dictionary + while True: + s = file.readline() + if not s or s == b"DATA\n": + break + self.info.append(s) + + # read PILfont metrics + data = file.read(256 * 20) + + # check image + if image.mode not in ("1", "L"): + msg = "invalid font image mode" + raise TypeError(msg) + + image.load() + + self.font = Image.core.font(image.im, data) + + def getmask( + self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any + ) -> Image.core.ImagingCore: + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :return: An internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module. + """ + _string_length_check(text) + Image._decompression_bomb_check(self.font.getsize(text)) + return self.font.getmask(text, mode) + + def getbbox( + self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + ) -> tuple[int, int, int, int]: + """ + Returns bounding box (in pixels) of given text. + + .. versionadded:: 9.2.0 + + :param text: Text to render. + + :return: ``(left, top, right, bottom)`` bounding box + """ + _string_length_check(text) + width, height = self.font.getsize(text) + return 0, 0, width, height + + def getlength( + self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + ) -> int: + """ + Returns length (in pixels) of given text. + This is the amount by which following text should be offset. + + .. versionadded:: 9.2.0 + """ + _string_length_check(text) + width, height = self.font.getsize(text) + return width + + +## +# Wrapper for FreeType fonts. Application code should use the +# truetype factory function to create font objects. + + +class FreeTypeFont: + """FreeType font wrapper (requires _imagingft service)""" + + font: Font + font_bytes: bytes + + def __init__( + self, + font: StrOrBytesPath | BinaryIO, + size: float = 10, + index: int = 0, + encoding: str = "", + layout_engine: Layout | None = None, + ) -> None: + # FIXME: use service provider instead + + if isinstance(core, DeferredError): + raise core.ex + + if size <= 0: + msg = f"font size must be greater than 0, not {size}" + raise ValueError(msg) + + self.path = font + self.size = size + self.index = index + self.encoding = encoding + + try: + from packaging.version import parse as parse_version + except ImportError: + pass + else: + if freetype_version := features.version_module("freetype2"): + if parse_version(freetype_version) < parse_version("2.9.1"): + warnings.warn( + "Support for FreeType 2.9.0 is deprecated and will be removed " + "in Pillow 12 (2025-10-15). Please upgrade to FreeType 2.9.1 " + "or newer, preferably FreeType 2.10.4 which fixes " + "CVE-2020-15999.", + DeprecationWarning, + ) + + if layout_engine not in (Layout.BASIC, Layout.RAQM): + layout_engine = Layout.BASIC + if core.HAVE_RAQM: + layout_engine = Layout.RAQM + elif layout_engine == Layout.RAQM and not core.HAVE_RAQM: + warnings.warn( + "Raqm layout was requested, but Raqm is not available. " + "Falling back to basic layout." + ) + layout_engine = Layout.BASIC + + self.layout_engine = layout_engine + + def load_from_bytes(f: IO[bytes]) -> None: + self.font_bytes = f.read() + self.font = core.getfont( + "", size, index, encoding, self.font_bytes, layout_engine + ) + + if is_path(font): + font = os.fspath(font) + if sys.platform == "win32": + font_bytes_path = font if isinstance(font, bytes) else font.encode() + try: + font_bytes_path.decode("ascii") + except UnicodeDecodeError: + # FreeType cannot load fonts with non-ASCII characters on Windows + # So load it into memory first + with open(font, "rb") as f: + load_from_bytes(f) + return + self.font = core.getfont( + font, size, index, encoding, layout_engine=layout_engine + ) + else: + load_from_bytes(cast(IO[bytes], font)) + + def __getstate__(self) -> list[Any]: + return [self.path, self.size, self.index, self.encoding, self.layout_engine] + + def __setstate__(self, state: list[Any]) -> None: + path, size, index, encoding, layout_engine = state + FreeTypeFont.__init__(self, path, size, index, encoding, layout_engine) + + def getname(self) -> tuple[str | None, str | None]: + """ + :return: A tuple of the font family (e.g. Helvetica) and the font style + (e.g. Bold) + """ + return self.font.family, self.font.style + + def getmetrics(self) -> tuple[int, int]: + """ + :return: A tuple of the font ascent (the distance from the baseline to + the highest outline point) and descent (the distance from the + baseline to the lowest outline point, a negative value) + """ + return self.font.ascent, self.font.descent + + def getlength( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + ) -> float: + """ + Returns length (in pixels with 1/64 precision) of given text when rendered + in font with provided direction, features, and language. + + This is the amount by which following text should be offset. + Text bounding box may extend past the length in some fonts, + e.g. when using italics or accents. + + The result is returned as a float; it is a whole number if using basic layout. + + Note that the sum of two lengths may not equal the length of a concatenated + string due to kerning. If you need to adjust for kerning, include the following + character and subtract its length. + + For example, instead of :: + + hello = font.getlength("Hello") + world = font.getlength("World") + hello_world = hello + world # not adjusted for kerning + assert hello_world == font.getlength("HelloWorld") # may fail + + use :: + + hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning + world = font.getlength("World") + hello_world = hello + world # adjusted for kerning + assert hello_world == font.getlength("HelloWorld") # True + + or disable kerning with (requires libraqm) :: + + hello = draw.textlength("Hello", font, features=["-kern"]) + world = draw.textlength("World", font, features=["-kern"]) + hello_world = hello + world # kerning is disabled, no need to adjust + assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) + + .. versionadded:: 8.0.0 + + :param text: Text to measure. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + :return: Either width for horizontal text, or height for vertical text. + """ + _string_length_check(text) + return self.font.getlength(text, mode, direction, features, language) / 64 + + def getbbox( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ) -> tuple[float, float, float, float]: + """ + Returns bounding box (in pixels) of given text relative to given anchor + when rendered in font with provided direction, features, and language. + + Use :py:meth:`getlength()` to get the offset of following text with + 1/64 pixel precision. The bounding box includes extra margins for + some fonts, e.g. italics or accents. + + .. versionadded:: 8.0.0 + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + :param stroke_width: The width of the text stroke. + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + + :return: ``(left, top, right, bottom)`` bounding box + """ + _string_length_check(text) + size, offset = self.font.getsize( + text, mode, direction, features, language, anchor + ) + left, top = offset[0] - stroke_width, offset[1] - stroke_width + width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width + return left, top, left + width, top + height + + def getmask( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ink: int = 0, + start: tuple[float, float] | None = None, + ) -> Image.core.ImagingCore: + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. If the font has embedded color data, the bitmap + should have mode ``RGBA``. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + .. versionadded:: 6.0.0 + + :param stroke_width: The width of the text stroke. + + .. versionadded:: 6.2.0 + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + + .. versionadded:: 8.0.0 + + :param ink: Foreground ink for rendering in RGBA mode. + + .. versionadded:: 8.0.0 + + :param start: Tuple of horizontal and vertical offset, as text may render + differently when starting at fractional coordinates. + + .. versionadded:: 9.4.0 + + :return: An internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module. + """ + return self.getmask2( + text, + mode, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + anchor=anchor, + ink=ink, + start=start, + )[0] + + def getmask2( + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ink: int = 0, + start: tuple[float, float] | None = None, + *args: Any, + **kwargs: Any, + ) -> tuple[Image.core.ImagingCore, tuple[int, int]]: + """ + Create a bitmap for the text. + + If the font uses antialiasing, the bitmap should have mode ``L`` and use a + maximum value of 255. If the font has embedded color data, the bitmap + should have mode ``RGBA``. Otherwise, it should have mode ``1``. + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + .. versionadded:: 1.1.5 + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + .. versionadded:: 6.0.0 + + :param stroke_width: The width of the text stroke. + + .. versionadded:: 6.2.0 + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. + + .. versionadded:: 8.0.0 + + :param ink: Foreground ink for rendering in RGBA mode. + + .. versionadded:: 8.0.0 + + :param start: Tuple of horizontal and vertical offset, as text may render + differently when starting at fractional coordinates. + + .. versionadded:: 9.4.0 + + :return: A tuple of an internal PIL storage memory instance as defined by the + :py:mod:`PIL.Image.core` interface module, and the text offset, the + gap between the starting coordinate and the first marking + """ + _string_length_check(text) + if start is None: + start = (0, 0) + + def fill(width: int, height: int) -> Image.core.ImagingCore: + size = (width, height) + Image._decompression_bomb_check(size) + return Image.core.fill("RGBA" if mode == "RGBA" else "L", size) + + return self.font.render( + text, + fill, + mode, + direction, + features, + language, + stroke_width, + kwargs.get("stroke_filled", False), + anchor, + ink, + start, + ) + + def font_variant( + self, + font: StrOrBytesPath | BinaryIO | None = None, + size: float | None = None, + index: int | None = None, + encoding: str | None = None, + layout_engine: Layout | None = None, + ) -> FreeTypeFont: + """ + Create a copy of this FreeTypeFont object, + using any specified arguments to override the settings. + + Parameters are identical to the parameters used to initialize this + object. + + :return: A FreeTypeFont object. + """ + if font is None: + try: + font = BytesIO(self.font_bytes) + except AttributeError: + font = self.path + return FreeTypeFont( + font=font, + size=self.size if size is None else size, + index=self.index if index is None else index, + encoding=self.encoding if encoding is None else encoding, + layout_engine=layout_engine or self.layout_engine, + ) + + def get_variation_names(self) -> list[bytes]: + """ + :returns: A list of the named styles in a variation font. + :exception OSError: If the font is not a variation font. + """ + try: + names = self.font.getvarnames() + except AttributeError as e: + msg = "FreeType 2.9.1 or greater is required" + raise NotImplementedError(msg) from e + return [name.replace(b"\x00", b"") for name in names] + + def set_variation_by_name(self, name: str | bytes) -> None: + """ + :param name: The name of the style. + :exception OSError: If the font is not a variation font. + """ + names = self.get_variation_names() + if not isinstance(name, bytes): + name = name.encode() + index = names.index(name) + 1 + + if index == getattr(self, "_last_variation_index", None): + # When the same name is set twice in a row, + # there is an 'unknown freetype error' + # https://savannah.nongnu.org/bugs/?56186 + return + self._last_variation_index = index + + self.font.setvarname(index) + + def get_variation_axes(self) -> list[Axis]: + """ + :returns: A list of the axes in a variation font. + :exception OSError: If the font is not a variation font. + """ + try: + axes = self.font.getvaraxes() + except AttributeError as e: + msg = "FreeType 2.9.1 or greater is required" + raise NotImplementedError(msg) from e + for axis in axes: + if axis["name"]: + axis["name"] = axis["name"].replace(b"\x00", b"") + return axes + + def set_variation_by_axes(self, axes: list[float]) -> None: + """ + :param axes: A list of values for each axis. + :exception OSError: If the font is not a variation font. + """ + try: + self.font.setvaraxes(axes) + except AttributeError as e: + msg = "FreeType 2.9.1 or greater is required" + raise NotImplementedError(msg) from e + + +class TransposedFont: + """Wrapper for writing rotated or mirrored text""" + + def __init__( + self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None + ): + """ + Wrapper that creates a transposed font from any existing font + object. + + :param font: A font object. + :param orientation: An optional orientation. If given, this should + be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM, + Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or + Image.Transpose.ROTATE_270. + """ + self.font = font + self.orientation = orientation # any 'transpose' argument, or None + + def getmask( + self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any + ) -> Image.core.ImagingCore: + im = self.font.getmask(text, mode, *args, **kwargs) + if self.orientation is not None: + return im.transpose(self.orientation) + return im + + def getbbox( + self, text: str | bytes, *args: Any, **kwargs: Any + ) -> tuple[int, int, float, float]: + # TransposedFont doesn't support getmask2, move top-left point to (0, 0) + # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont + left, top, right, bottom = self.font.getbbox(text, *args, **kwargs) + width = right - left + height = bottom - top + if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): + return 0, 0, height, width + return 0, 0, width, height + + def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float: + if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): + msg = "text length is undefined for text rotated by 90 or 270 degrees" + raise ValueError(msg) + return self.font.getlength(text, *args, **kwargs) + + +def load(filename: str) -> ImageFont: + """ + Load a font file. This function loads a font object from the given + bitmap font file, and returns the corresponding font object. For loading TrueType + or OpenType fonts instead, see :py:func:`~PIL.ImageFont.truetype`. + + :param filename: Name of font file. + :return: A font object. + :exception OSError: If the file could not be read. + """ + f = ImageFont() + f._load_pilfont(filename) + return f + + +def truetype( + font: StrOrBytesPath | BinaryIO, + size: float = 10, + index: int = 0, + encoding: str = "", + layout_engine: Layout | None = None, +) -> FreeTypeFont: + """ + Load a TrueType or OpenType font from a file or file-like object, + and create a font object. This function loads a font object from the given + file or file-like object, and creates a font object for a font of the given + size. For loading bitmap fonts instead, see :py:func:`~PIL.ImageFont.load` + and :py:func:`~PIL.ImageFont.load_path`. + + Pillow uses FreeType to open font files. On Windows, be aware that FreeType + will keep the file open as long as the FreeTypeFont object exists. Windows + limits the number of files that can be open in C at once to 512, so if many + fonts are opened simultaneously and that limit is approached, an + ``OSError`` may be thrown, reporting that FreeType "cannot open resource". + A workaround would be to copy the file(s) into memory, and open that instead. + + This function requires the _imagingft service. + + :param font: A filename or file-like object containing a TrueType font. + If the file is not found in this filename, the loader may also + search in other directories, such as: + + * The :file:`fonts/` directory on Windows, + * :file:`/Library/Fonts/`, :file:`/System/Library/Fonts/` + and :file:`~/Library/Fonts/` on macOS. + * :file:`~/.local/share/fonts`, :file:`/usr/local/share/fonts`, + and :file:`/usr/share/fonts` on Linux; or those specified by + the ``XDG_DATA_HOME`` and ``XDG_DATA_DIRS`` environment variables + for user-installed and system-wide fonts, respectively. + + :param size: The requested size, in pixels. + :param index: Which font face to load (default is first available face). + :param encoding: Which font encoding to use (default is Unicode). Possible + encodings include (see the FreeType documentation for more + information): + + * "unic" (Unicode) + * "symb" (Microsoft Symbol) + * "ADOB" (Adobe Standard) + * "ADBE" (Adobe Expert) + * "ADBC" (Adobe Custom) + * "armn" (Apple Roman) + * "sjis" (Shift JIS) + * "gb " (PRC) + * "big5" + * "wans" (Extended Wansung) + * "joha" (Johab) + * "lat1" (Latin-1) + + This specifies the character set to use. It does not alter the + encoding of any text provided in subsequent operations. + :param layout_engine: Which layout engine to use, if available: + :attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`. + If it is available, Raqm layout will be used by default. + Otherwise, basic layout will be used. + + Raqm layout is recommended for all non-English text. If Raqm layout + is not required, basic layout will have better performance. + + You can check support for Raqm layout using + :py:func:`PIL.features.check_feature` with ``feature="raqm"``. + + .. versionadded:: 4.2.0 + :return: A font object. + :exception OSError: If the file could not be read. + :exception ValueError: If the font size is not greater than zero. + """ + + def freetype(font: StrOrBytesPath | BinaryIO) -> FreeTypeFont: + return FreeTypeFont(font, size, index, encoding, layout_engine) + + try: + return freetype(font) + except OSError: + if not is_path(font): + raise + ttf_filename = os.path.basename(font) + + dirs = [] + if sys.platform == "win32": + # check the windows font repository + # NOTE: must use uppercase WINDIR, to work around bugs in + # 1.5.2's os.environ.get() + windir = os.environ.get("WINDIR") + if windir: + dirs.append(os.path.join(windir, "fonts")) + elif sys.platform in ("linux", "linux2"): + data_home = os.environ.get("XDG_DATA_HOME") + if not data_home: + # The freedesktop spec defines the following default directory for + # when XDG_DATA_HOME is unset or empty. This user-level directory + # takes precedence over system-level directories. + data_home = os.path.expanduser("~/.local/share") + xdg_dirs = [data_home] + + data_dirs = os.environ.get("XDG_DATA_DIRS") + if not data_dirs: + # Similarly, defaults are defined for the system-level directories + data_dirs = "/usr/local/share:/usr/share" + xdg_dirs += data_dirs.split(":") + + dirs += [os.path.join(xdg_dir, "fonts") for xdg_dir in xdg_dirs] + elif sys.platform == "darwin": + dirs += [ + "/Library/Fonts", + "/System/Library/Fonts", + os.path.expanduser("~/Library/Fonts"), + ] + + ext = os.path.splitext(ttf_filename)[1] + first_font_with_a_different_extension = None + for directory in dirs: + for walkroot, walkdir, walkfilenames in os.walk(directory): + for walkfilename in walkfilenames: + if ext and walkfilename == ttf_filename: + return freetype(os.path.join(walkroot, walkfilename)) + elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename: + fontpath = os.path.join(walkroot, walkfilename) + if os.path.splitext(fontpath)[1] == ".ttf": + return freetype(fontpath) + if not ext and first_font_with_a_different_extension is None: + first_font_with_a_different_extension = fontpath + if first_font_with_a_different_extension: + return freetype(first_font_with_a_different_extension) + raise + + +def load_path(filename: str | bytes) -> ImageFont: + """ + Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a + bitmap font along the Python path. + + :param filename: Name of font file. + :return: A font object. + :exception OSError: If the file could not be read. + """ + if not isinstance(filename, str): + filename = filename.decode("utf-8") + for directory in sys.path: + try: + return load(os.path.join(directory, filename)) + except OSError: + pass + msg = f'cannot find font file "{filename}" in sys.path' + if os.path.exists(filename): + msg += f', did you mean ImageFont.load("{filename}") instead?' + + raise OSError(msg) + + +def load_default_imagefont() -> ImageFont: + f = ImageFont() + f._load_pilfont_data( + # courB08 + BytesIO( + base64.b64decode( + b""" +UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA +BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL +AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA +AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB +ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A +BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB +//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA +AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH +AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA +ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv +AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/ +/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5 +AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA +AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG +AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA +BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA +AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA +2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF +AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA//// ++gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA +////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA +BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv +AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA +AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA +AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA +BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP// +//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA +AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF +AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB +mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn +AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA +AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7 +AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA +Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB +//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA +AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ +AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC +DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ +AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/ ++wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5 +AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/ +///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG +AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA +BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA +Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC +eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG +AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA//// ++gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA +////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA +BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT +AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A +AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA +Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA +Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP// +//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA +AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ +AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA +LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5 +AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA +AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5 +AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA +AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG +AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA +EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK +AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA +pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG +AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA//// ++QAGAAIAzgAKANUAEw== +""" + ) + ), + Image.open( + BytesIO( + base64.b64decode( + b""" +iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u +Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9 +M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g +LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F +IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA +Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791 +NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx +in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9 +SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY +AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt +y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG +ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY +lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H +/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3 +AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47 +c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/ +/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw +pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv +oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR +evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA +AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v// +Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR +w7IkEbzhVQAAAABJRU5ErkJggg== +""" + ) + ) + ), + ) + return f + + +def load_default(size: float | None = None) -> FreeTypeFont | ImageFont: + """If FreeType support is available, load a version of Aileron Regular, + https://dotcolon.net/fonts/aileron, with a more limited character set. + + Otherwise, load a "better than nothing" font. + + .. versionadded:: 1.1.4 + + :param size: The font size of Aileron Regular. + + .. versionadded:: 10.1.0 + + :return: A font object. + """ + if isinstance(core, ModuleType) or size is not None: + return truetype( + BytesIO( + base64.b64decode( + b""" +AAEAAAAPAIAAAwBwRkZUTYwDlUAAADFoAAAAHEdERUYAqADnAAAo8AAAACRHUE9ThhmITwAAKfgAA +AduR1NVQnHxefoAACkUAAAA4k9TLzJovoHLAAABeAAAAGBjbWFw5lFQMQAAA6gAAAGqZ2FzcP//AA +MAACjoAAAACGdseWYmRXoPAAAGQAAAHfhoZWFkE18ayQAAAPwAAAA2aGhlYQboArEAAAE0AAAAJGh +tdHjjERZ8AAAB2AAAAdBsb2NhuOexrgAABVQAAADqbWF4cAC7AEYAAAFYAAAAIG5hbWUr+h5lAAAk +OAAAA6Jwb3N0D3oPTQAAJ9wAAAEKAAEAAAABGhxJDqIhXw889QALA+gAAAAA0Bqf2QAAAADhCh2h/ +2r/LgOxAyAAAAAIAAIAAAAAAAAAAQAAA8r/GgAAA7j/av9qA7EAAQAAAAAAAAAAAAAAAAAAAHQAAQ +AAAHQAQwAFAAAAAAACAAAAAQABAAAAQAAAAAAAAAADAfoBkAAFAAgCigJYAAAASwKKAlgAAAFeADI +BPgAAAAAFAAAAAAAAAAAAAAcAAAAAAAAAAAAAAABVS1dOAEAAIPsCAwL/GgDIA8oA5iAAAJMAAAAA +AhICsgAAACAAAwH0AAAAAAAAAU0AAADYAAAA8gA5AVMAVgJEAEYCRAA1AuQAKQKOAEAAsAArATsAZ +AE7AB4CMABVAkQAUADc/+EBEgAgANwAJQEv//sCRAApAkQAggJEADwCRAAtAkQAIQJEADkCRAArAk +QAMgJEACwCRAAxANwAJQDc/+ECRABnAkQAUAJEAEQB8wAjA1QANgJ/AB0CcwBkArsALwLFAGQCSwB +kAjcAZALGAC8C2gBkAQgAZAIgADcCYQBkAj8AZANiAGQCzgBkAuEALwJWAGQC3QAvAmsAZAJJADQC +ZAAiAqoAXgJuACADuAAaAnEAGQJFABMCTwAuATMAYgEv//sBJwAiAkQAUAH0ADIBLAApAhMAJAJjA +EoCEQAeAmcAHgIlAB4BIgAVAmcAHgJRAEoA7gA+AOn/8wIKAEoA9wBGA1cASgJRAEoCSgAeAmMASg +JnAB4BSgBKAcsAGAE5ABQCUABCAgIAAQMRAAEB4v/6AgEAAQHOABQBLwBAAPoAYAEvACECRABNA0Y +AJAItAHgBKgAcAkQAUAEsAHQAygAgAi0AOQD3ADYA9wAWAaEANgGhABYCbAAlAYMAeAGDADkA6/9q +AhsAFAIKABUB/QAVAAAAAwAAAAMAAAAcAAEAAAAAAKQAAwABAAAAHAAEAIgAAAAeABAAAwAOAH4Aq +QCrALEAtAC3ALsgGSAdICYgOiBEISL7Av//AAAAIACpAKsAsAC0ALcAuyAYIBwgJiA5IEQhIvsB// +//4/+5/7j/tP+y/7D/reBR4E/gR+A14CzfTwVxAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMEBQYHCAkKCwwNDg8QERIT +FBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMT +U5PUFFSU1RVVldYWVpbXF1eX2BhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAA +AAAAAAYnFmAAAAAABlAAAAAAAAAAAAAAAAAAAAAAAAAAAAY2htAAAAAAAAAABrbGlqAAAAAHAAbm9 +ycwBnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmACYAJgAmAD4AUgCCAMoBCgFO +AVwBcgGIAaYBvAHKAdYB6AH2AgwCIAJKAogCpgLWAw4DIgNkA5wDugPUA+gD/AQQBEYEogS8BPoFJ +gVSBWoFgAWwBcoF1gX6BhQGJAZMBmgGiga0BuIHGgdUB2YHkAeiB8AH3AfyCAoIHAgqCDoITghcCG +oIogjSCPoJKglYCXwJwgnqCgIKKApACl4Klgq8CtwLDAs8C1YLjAuyC9oL7gwMDCYMSAxgDKAMrAz +qDQoNTA1mDYQNoA2uDcAN2g3oDfYODA4iDkoOXA5sDnoOnA7EDvwAAAAFAAAAAAH0ArwAAwAGAAkA +DAAPAAAxESERAxMhExcRASELARETAfT6qv6syKr+jgFUqsiqArz9RAGLAP/+1P8B/v3VAP8BLP4CA +P8AAgA5//IAuQKyAAMACwAANyMDMwIyFhQGIiY0oE4MZk84JCQ4JLQB/v3AJDgkJDgAAgBWAeUBPA +LfAAMABwAAEyMnMxcjJzOmRgpagkYKWgHl+vr6AAAAAAIARgAAAf4CsgAbAB8AAAEHMxUjByM3Iwc +jNyM1MzcjNTM3MwczNzMHMxUrAQczAZgdZXEvOi9bLzovWmYdZXEvOi9bLzovWp9bHlsBn4w429vb +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 +EQQ/BCQvEjQCFQZWFSEWQv40AcxCDiUblhP9uSEAAAAAAAAWAQ4AAQAAAAAAAAATACgAAQAAAAAAA +QAHAEwAAQAAAAAAAgAHAGQAAQAAAAAAAwAaAKIAAQAAAAAABAAHAM0AAQAAAAAABQA8AU8AAQAAAA +AABgAPAawAAQAAAAAACAALAdQAAQAAAAAACQALAfgAAQAAAAAACwAXAjQAAQAAAAAADAAXAnwAAwA +BBAkAAAAmAAAAAwABBAkAAQAOADwAAwABBAkAAgAOAFQAAwABBAkAAwA0AGwAAwABBAkABAAOAL0A +AwABBAkABQB4ANUAAwABBAkABgAeAYwAAwABBAkACAAWAbwAAwABBAkACQAWAeAAAwABBAkACwAuA +gQAAwABBAkADAAuAkwATgBvACAAUgBpAGcAaAB0AHMAIABSAGUAcwBlAHIAdgBlAGQALgAATm8gUm +lnaHRzIFJlc2VydmVkLgAAQQBpAGwAZQByAG8AbgAAQWlsZXJvbgAAUgBlAGcAdQBsAGEAcgAAUmV +ndWxhcgAAMQAuADEAMAAyADsAVQBLAFcATgA7AEEAaQBsAGUAcgBvAG4ALQBSAGUAZwB1AGwAYQBy +AAAxLjEwMjtVS1dOO0FpbGVyb24tUmVndWxhcgAAQQBpAGwAZQByAG8AbgAAQWlsZXJvbgAAVgBlA +HIAcwBpAG8AbgAgADEALgAxADAAMgA7AFAAUwAgADAAMAAxAC4AMQAwADIAOwBoAG8AdABjAG8Abg +B2ACAAMQAuADAALgA3ADAAOwBtAGEAawBlAG8AdABmAC4AbABpAGIAMgAuADUALgA1ADgAMwAyADk +AAFZlcnNpb24gMS4xMDI7UFMgMDAxLjEwMjtob3Rjb252IDEuMC43MDttYWtlb3RmLmxpYjIuNS41 +ODMyOQAAQQBpAGwAZQByAG8AbgAtAFIAZQBnAHUAbABhAHIAAEFpbGVyb24tUmVndWxhcgAAUwBvA +HIAYQAgAFMAYQBnAGEAbgBvAABTb3JhIFNhZ2FubwAAUwBvAHIAYQAgAFMAYQBnAGEAbgBvAABTb3 +JhIFNhZ2FubwAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGQAbwB0AGMAbwBsAG8AbgAuAG4AZQB0AAB +odHRwOi8vd3d3LmRvdGNvbG9uLm5ldAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGQAbwB0AGMAbwBs +AG8AbgAuAG4AZQB0AABodHRwOi8vd3d3LmRvdGNvbG9uLm5ldAAAAAACAAAAAAAA/4MAMgAAAAAAA +AAAAAAAAAAAAAAAAAAAAHQAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATAB +QAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAA +xADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0A +TgBPAFAAUQBSAFMAVABVAFYAVwBYAFkAWgBbAFwAXQBeAF8AYABhAIsAqQCDAJMAjQDDAKoAtgC3A +LQAtQCrAL4AvwC8AIwAwADBAAAAAAAB//8AAgABAAAADAAAABwAAAACAAIAAwBxAAEAcgBzAAIABA +AAAAIAAAABAAAACgBMAGYAAkRGTFQADmxhdG4AGgAEAAAAAP//AAEAAAAWAANDQVQgAB5NT0wgABZ +ST00gABYAAP//AAEAAAAA//8AAgAAAAEAAmxpZ2EADmxvY2wAFAAAAAEAAQAAAAEAAAACAAYAEAAG +AAAAAgASADQABAAAAAEATAADAAAAAgAQABYAAQAcAAAAAQABAE8AAQABAGcAAQABAE8AAwAAAAIAE +AAWAAEAHAAAAAEAAQAvAAEAAQBnAAEAAQAvAAEAGgABAAgAAgAGAAwAcwACAE8AcgACAEwAAQABAE +kAAAABAAAACgBGAGAAAkRGTFQADmxhdG4AHAAEAAAAAP//AAIAAAABABYAA0NBVCAAFk1PTCAAFlJ +PTSAAFgAA//8AAgAAAAEAAmNwc3AADmtlcm4AFAAAAAEAAAAAAAEAAQACAAYADgABAAAAAQASAAIA +AAACAB4ANgABAAoABQAFAAoAAgABACQAPQAAAAEAEgAEAAAAAQAMAAEAOP/nAAEAAQAkAAIGigAEA +AAFJAXKABoAGQAA//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAD/sv+4/+z/7v/MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAD/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9T/6AAAAAD/8QAA +ABD/vQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/7gAAAAAAAAAAAAAAAAAA//MAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAAAP/5AAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAD/4AAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//L/9AAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAA/+gAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/zAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/mAAAAAAAAAAAAAAAAAAD +/4gAA//AAAAAA//YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAP/OAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/zv/qAAAAAP/0AAAACAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/ZAAD/egAA/1kAAAAA/5D/rgAAAAAAAAAAAA +AAAAAAAAAAAAAAAAD/9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAD/8AAA/7b/8P+wAAD/8P/E/98AAAAA/8P/+P/0//oAAAAAAAAAAAAA//gA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/w//C/9MAAP/SAAD/9wAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/yAAA/+kAAAAA//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/9wAAAAD//QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAP/2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAP/cAAAAAAAAAAAAAAAA/7YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAP/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/6AAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAkAFAAEAAAAAQACwAAABcA +BgAAAAAAAAAIAA4AAAAAAAsAEgAAAAAAAAATABkAAwANAAAAAQAJAAAAAAAAAAAAAAAAAAAAGAAAA +AAABwAAAAAAAAAAAAAAFQAFAAAAAAAYABgAAAAUAAAACgAAAAwAAgAPABEAFgAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAEAEQBdAAYAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAcAAAAAAAAABwAAAAAACAAAAAAAAAAAAAcAAAAHAAAAEwAJ +ABUADgAPAAAACwAQAAAAAAAAAAAAAAAAAAUAGAACAAIAAgAAAAIAGAAXAAAAGAAAABYAFgACABYAA +gAWAAAAEQADAAoAFAAMAA0ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAAAEgAGAAEAHgAkAC +YAJwApACoALQAuAC8AMgAzADcAOAA5ADoAPAA9AEUASABOAE8AUgBTAFUAVwBZAFoAWwBcAF0AcwA +AAAAAAQAAAADa3tfFAAAAANAan9kAAAAA4QodoQ== +""" + ) + ), + 10 if size is None else size, + layout_engine=Layout.BASIC, + ) + return load_default_imagefont() diff --git a/venv/Lib/site-packages/PIL/ImageGrab.py b/venv/Lib/site-packages/PIL/ImageGrab.py new file mode 100644 index 00000000..1eb45073 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageGrab.py @@ -0,0 +1,196 @@ +# +# The Python Imaging Library +# $Id$ +# +# screen grabber +# +# History: +# 2001-04-26 fl created +# 2001-09-17 fl use builtin driver, if present +# 2002-11-19 fl added grabclipboard support +# +# Copyright (c) 2001-2002 by Secret Labs AB +# Copyright (c) 2001-2002 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import shutil +import subprocess +import sys +import tempfile + +from . import Image + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import ImageWin + + +def grab( + bbox: tuple[int, int, int, int] | None = None, + include_layered_windows: bool = False, + all_screens: bool = False, + xdisplay: str | None = None, + window: int | ImageWin.HWND | None = None, +) -> Image.Image: + im: Image.Image + if xdisplay is None: + if sys.platform == "darwin": + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + args = ["screencapture"] + if bbox: + left, top, right, bottom = bbox + args += ["-R", f"{left},{top},{right-left},{bottom-top}"] + subprocess.call(args + ["-x", filepath]) + im = Image.open(filepath) + im.load() + os.unlink(filepath) + if bbox: + im_resized = im.resize((right - left, bottom - top)) + im.close() + return im_resized + return im + elif sys.platform == "win32": + if window is not None: + all_screens = -1 + offset, size, data = Image.core.grabscreen_win32( + include_layered_windows, + all_screens, + int(window) if window is not None else 0, + ) + im = Image.frombytes( + "RGB", + size, + data, + # RGB, 32-bit line padding, origin lower left corner + "raw", + "BGR", + (size[0] * 3 + 3) & -4, + -1, + ) + if bbox: + x0, y0 = offset + left, top, right, bottom = bbox + im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) + return im + # Cast to Optional[str] needed for Windows and macOS. + display_name: str | None = xdisplay + try: + if not Image.core.HAVE_XCB: + msg = "Pillow was built without XCB support" + raise OSError(msg) + size, data = Image.core.grabscreen_x11(display_name) + except OSError: + if display_name is None and sys.platform not in ("darwin", "win32"): + if shutil.which("gnome-screenshot"): + args = ["gnome-screenshot", "-f"] + elif shutil.which("grim"): + args = ["grim"] + elif shutil.which("spectacle"): + args = ["spectacle", "-n", "-b", "-f", "-o"] + else: + raise + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + subprocess.call(args + [filepath]) + im = Image.open(filepath) + im.load() + os.unlink(filepath) + if bbox: + im_cropped = im.crop(bbox) + im.close() + return im_cropped + return im + else: + raise + else: + im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) + if bbox: + im = im.crop(bbox) + return im + + +def grabclipboard() -> Image.Image | list[str] | None: + if sys.platform == "darwin": + p = subprocess.run( + ["osascript", "-e", "get the clipboard as «class PNGf»"], + capture_output=True, + ) + if p.returncode != 0: + return None + + import binascii + + data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3])) + return Image.open(data) + elif sys.platform == "win32": + fmt, data = Image.core.grabclipboard_win32() + if fmt == "file": # CF_HDROP + import struct + + o = struct.unpack_from("I", data)[0] + if data[16] == 0: + files = data[o:].decode("mbcs").split("\0") + else: + files = data[o:].decode("utf-16le").split("\0") + return files[: files.index("")] + if isinstance(data, bytes): + data = io.BytesIO(data) + if fmt == "png": + from . import PngImagePlugin + + return PngImagePlugin.PngImageFile(data) + elif fmt == "DIB": + from . import BmpImagePlugin + + return BmpImagePlugin.DibImageFile(data) + return None + else: + if os.getenv("WAYLAND_DISPLAY"): + session_type = "wayland" + elif os.getenv("DISPLAY"): + session_type = "x11" + else: # Session type check failed + session_type = None + + if shutil.which("wl-paste") and session_type in ("wayland", None): + args = ["wl-paste", "-t", "image"] + elif shutil.which("xclip") and session_type in ("x11", None): + args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"] + else: + msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux" + raise NotImplementedError(msg) + + p = subprocess.run(args, capture_output=True) + if p.returncode != 0: + err = p.stderr + for silent_error in [ + # wl-paste, when the clipboard is empty + b"Nothing is copied", + # Ubuntu/Debian wl-paste, when the clipboard is empty + b"No selection", + # Ubuntu/Debian wl-paste, when an image isn't available + b"No suitable type of content copied", + # wl-paste or Ubuntu/Debian xclip, when an image isn't available + b" not available", + # xclip, when an image isn't available + b"cannot convert ", + # xclip, when the clipboard isn't initialized + b"xclip: Error: There is no owner for the ", + ]: + if silent_error in err: + return None + msg = f"{args[0]} error" + if err: + msg += f": {err.strip().decode()}" + raise ChildProcessError(msg) + + data = io.BytesIO(p.stdout) + im = Image.open(data) + im.load() + return im diff --git a/venv/Lib/site-packages/PIL/ImageMath.py b/venv/Lib/site-packages/PIL/ImageMath.py new file mode 100644 index 00000000..c33809ce --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageMath.py @@ -0,0 +1,368 @@ +# +# The Python Imaging Library +# $Id$ +# +# a simple math add-on for the Python Imaging Library +# +# History: +# 1999-02-15 fl Original PIL Plus release +# 2005-05-05 fl Simplified and cleaned up for PIL 1.1.6 +# 2005-09-12 fl Fixed int() and float() for Python 2.4.1 +# +# Copyright (c) 1999-2005 by Secret Labs AB +# Copyright (c) 2005 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import builtins +from types import CodeType +from typing import Any, Callable + +from . import Image, _imagingmath +from ._deprecate import deprecate + + +class _Operand: + """Wraps an image operand, providing standard operators""" + + def __init__(self, im: Image.Image): + self.im = im + + def __fixup(self, im1: _Operand | float) -> Image.Image: + # convert image to suitable mode + if isinstance(im1, _Operand): + # argument was an image. + if im1.im.mode in ("1", "L"): + return im1.im.convert("I") + elif im1.im.mode in ("I", "F"): + return im1.im + else: + msg = f"unsupported mode: {im1.im.mode}" + raise ValueError(msg) + else: + # argument was a constant + if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"): + return Image.new("I", self.im.size, im1) + else: + return Image.new("F", self.im.size, im1) + + def apply( + self, + op: str, + im1: _Operand | float, + im2: _Operand | float | None = None, + mode: str | None = None, + ) -> _Operand: + im_1 = self.__fixup(im1) + if im2 is None: + # unary operation + out = Image.new(mode or im_1.mode, im_1.size, None) + try: + op = getattr(_imagingmath, f"{op}_{im_1.mode}") + except AttributeError as e: + msg = f"bad operand type for '{op}'" + raise TypeError(msg) from e + _imagingmath.unop(op, out.getim(), im_1.getim()) + else: + # binary operation + im_2 = self.__fixup(im2) + if im_1.mode != im_2.mode: + # convert both arguments to floating point + if im_1.mode != "F": + im_1 = im_1.convert("F") + if im_2.mode != "F": + im_2 = im_2.convert("F") + if im_1.size != im_2.size: + # crop both arguments to a common size + size = ( + min(im_1.size[0], im_2.size[0]), + min(im_1.size[1], im_2.size[1]), + ) + if im_1.size != size: + im_1 = im_1.crop((0, 0) + size) + if im_2.size != size: + im_2 = im_2.crop((0, 0) + size) + out = Image.new(mode or im_1.mode, im_1.size, None) + try: + op = getattr(_imagingmath, f"{op}_{im_1.mode}") + except AttributeError as e: + msg = f"bad operand type for '{op}'" + raise TypeError(msg) from e + _imagingmath.binop(op, out.getim(), im_1.getim(), im_2.getim()) + return _Operand(out) + + # unary operators + def __bool__(self) -> bool: + # an image is "true" if it contains at least one non-zero pixel + return self.im.getbbox() is not None + + def __abs__(self) -> _Operand: + return self.apply("abs", self) + + def __pos__(self) -> _Operand: + return self + + def __neg__(self) -> _Operand: + return self.apply("neg", self) + + # binary operators + def __add__(self, other: _Operand | float) -> _Operand: + return self.apply("add", self, other) + + def __radd__(self, other: _Operand | float) -> _Operand: + return self.apply("add", other, self) + + def __sub__(self, other: _Operand | float) -> _Operand: + return self.apply("sub", self, other) + + def __rsub__(self, other: _Operand | float) -> _Operand: + return self.apply("sub", other, self) + + def __mul__(self, other: _Operand | float) -> _Operand: + return self.apply("mul", self, other) + + def __rmul__(self, other: _Operand | float) -> _Operand: + return self.apply("mul", other, self) + + def __truediv__(self, other: _Operand | float) -> _Operand: + return self.apply("div", self, other) + + def __rtruediv__(self, other: _Operand | float) -> _Operand: + return self.apply("div", other, self) + + def __mod__(self, other: _Operand | float) -> _Operand: + return self.apply("mod", self, other) + + def __rmod__(self, other: _Operand | float) -> _Operand: + return self.apply("mod", other, self) + + def __pow__(self, other: _Operand | float) -> _Operand: + return self.apply("pow", self, other) + + def __rpow__(self, other: _Operand | float) -> _Operand: + return self.apply("pow", other, self) + + # bitwise + def __invert__(self) -> _Operand: + return self.apply("invert", self) + + def __and__(self, other: _Operand | float) -> _Operand: + return self.apply("and", self, other) + + def __rand__(self, other: _Operand | float) -> _Operand: + return self.apply("and", other, self) + + def __or__(self, other: _Operand | float) -> _Operand: + return self.apply("or", self, other) + + def __ror__(self, other: _Operand | float) -> _Operand: + return self.apply("or", other, self) + + def __xor__(self, other: _Operand | float) -> _Operand: + return self.apply("xor", self, other) + + def __rxor__(self, other: _Operand | float) -> _Operand: + return self.apply("xor", other, self) + + def __lshift__(self, other: _Operand | float) -> _Operand: + return self.apply("lshift", self, other) + + def __rshift__(self, other: _Operand | float) -> _Operand: + return self.apply("rshift", self, other) + + # logical + def __eq__(self, other: _Operand | float) -> _Operand: # type: ignore[override] + return self.apply("eq", self, other) + + def __ne__(self, other: _Operand | float) -> _Operand: # type: ignore[override] + return self.apply("ne", self, other) + + def __lt__(self, other: _Operand | float) -> _Operand: + return self.apply("lt", self, other) + + def __le__(self, other: _Operand | float) -> _Operand: + return self.apply("le", self, other) + + def __gt__(self, other: _Operand | float) -> _Operand: + return self.apply("gt", self, other) + + def __ge__(self, other: _Operand | float) -> _Operand: + return self.apply("ge", self, other) + + +# conversions +def imagemath_int(self: _Operand) -> _Operand: + return _Operand(self.im.convert("I")) + + +def imagemath_float(self: _Operand) -> _Operand: + return _Operand(self.im.convert("F")) + + +# logical +def imagemath_equal(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("eq", self, other, mode="I") + + +def imagemath_notequal(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("ne", self, other, mode="I") + + +def imagemath_min(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("min", self, other) + + +def imagemath_max(self: _Operand, other: _Operand | float | None) -> _Operand: + return self.apply("max", self, other) + + +def imagemath_convert(self: _Operand, mode: str) -> _Operand: + return _Operand(self.im.convert(mode)) + + +ops = { + "int": imagemath_int, + "float": imagemath_float, + "equal": imagemath_equal, + "notequal": imagemath_notequal, + "min": imagemath_min, + "max": imagemath_max, + "convert": imagemath_convert, +} + + +def lambda_eval( + expression: Callable[[dict[str, Any]], Any], + options: dict[str, Any] = {}, + **kw: Any, +) -> Any: + """ + Returns the result of an image function. + + :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band + images, use the :py:meth:`~PIL.Image.Image.split` method or + :py:func:`~PIL.Image.merge` function. + + :param expression: A function that receives a dictionary. + :param options: Values to add to the function's dictionary. Deprecated. + You can instead use one or more keyword arguments. + :param **kw: Values to add to the function's dictionary. + :return: The expression result. This is usually an image object, but can + also be an integer, a floating point value, or a pixel tuple, + depending on the expression. + """ + + if options: + deprecate( + "ImageMath.lambda_eval options", + 12, + "ImageMath.lambda_eval keyword arguments", + ) + + args: dict[str, Any] = ops.copy() + args.update(options) + args.update(kw) + for k, v in args.items(): + if isinstance(v, Image.Image): + args[k] = _Operand(v) + + out = expression(args) + try: + return out.im + except AttributeError: + return out + + +def unsafe_eval( + expression: str, + options: dict[str, Any] = {}, + **kw: Any, +) -> Any: + """ + Evaluates an image expression. This uses Python's ``eval()`` function to process + the expression string, and carries the security risks of doing so. It is not + recommended to process expressions without considering this. + :py:meth:`~lambda_eval` is a more secure alternative. + + :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band + images, use the :py:meth:`~PIL.Image.Image.split` method or + :py:func:`~PIL.Image.merge` function. + + :param expression: A string containing a Python-style expression. + :param options: Values to add to the evaluation context. Deprecated. + You can instead use one or more keyword arguments. + :param **kw: Values to add to the evaluation context. + :return: The evaluated expression. This is usually an image object, but can + also be an integer, a floating point value, or a pixel tuple, + depending on the expression. + """ + + if options: + deprecate( + "ImageMath.unsafe_eval options", + 12, + "ImageMath.unsafe_eval keyword arguments", + ) + + # build execution namespace + args: dict[str, Any] = ops.copy() + for k in [*options, *kw]: + if "__" in k or hasattr(builtins, k): + msg = f"'{k}' not allowed" + raise ValueError(msg) + + args.update(options) + args.update(kw) + for k, v in args.items(): + if isinstance(v, Image.Image): + args[k] = _Operand(v) + + compiled_code = compile(expression, "", "eval") + + def scan(code: CodeType) -> None: + for const in code.co_consts: + if type(const) is type(compiled_code): + scan(const) + + for name in code.co_names: + if name not in args and name != "abs": + msg = f"'{name}' not allowed" + raise ValueError(msg) + + scan(compiled_code) + out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) + try: + return out.im + except AttributeError: + return out + + +def eval( + expression: str, + _dict: dict[str, Any] = {}, + **kw: Any, +) -> Any: + """ + Evaluates an image expression. + + Deprecated. Use lambda_eval() or unsafe_eval() instead. + + :param expression: A string containing a Python-style expression. + :param _dict: Values to add to the evaluation context. You + can either use a dictionary, or one or more keyword + arguments. + :return: The evaluated expression. This is usually an image object, but can + also be an integer, a floating point value, or a pixel tuple, + depending on the expression. + + .. deprecated:: 10.3.0 + """ + + deprecate( + "ImageMath.eval", + 12, + "ImageMath.lambda_eval or ImageMath.unsafe_eval", + ) + return unsafe_eval(expression, _dict, **kw) diff --git a/venv/Lib/site-packages/PIL/ImageMode.py b/venv/Lib/site-packages/PIL/ImageMode.py new file mode 100644 index 00000000..92a08d2c --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageMode.py @@ -0,0 +1,92 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard mode descriptors +# +# History: +# 2006-03-20 fl Added +# +# Copyright (c) 2006 by Secret Labs AB. +# Copyright (c) 2006 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import sys +from functools import lru_cache +from typing import NamedTuple + +from ._deprecate import deprecate + + +class ModeDescriptor(NamedTuple): + """Wrapper for mode strings.""" + + mode: str + bands: tuple[str, ...] + basemode: str + basetype: str + typestr: str + + def __str__(self) -> str: + return self.mode + + +@lru_cache +def getmode(mode: str) -> ModeDescriptor: + """Gets a mode descriptor for the given mode.""" + endian = "<" if sys.byteorder == "little" else ">" + + modes = { + # core modes + # Bits need to be extended to bytes + "1": ("L", "L", ("1",), "|b1"), + "L": ("L", "L", ("L",), "|u1"), + "I": ("L", "I", ("I",), f"{endian}i4"), + "F": ("L", "F", ("F",), f"{endian}f4"), + "P": ("P", "L", ("P",), "|u1"), + "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"), + "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"), + "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"), + "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"), + "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"), + # UNDONE - unsigned |u1i1i1 + "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"), + "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"), + # extra experimental modes + "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"), + "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"), + "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"), + "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"), + "LA": ("L", "L", ("L", "A"), "|u1"), + "La": ("L", "L", ("L", "a"), "|u1"), + "PA": ("RGB", "L", ("P", "A"), "|u1"), + } + if mode in modes: + if mode in ("BGR;15", "BGR;16", "BGR;24"): + deprecate(mode, 12) + base_mode, base_type, bands, type_str = modes[mode] + return ModeDescriptor(mode, bands, base_mode, base_type, type_str) + + mapping_modes = { + # I;16 == I;16L, and I;32 == I;32L + "I;16": "u2", + "I;16BS": ">i2", + "I;16N": f"{endian}u2", + "I;16NS": f"{endian}i2", + "I;32": "u4", + "I;32L": "i4", + "I;32LS": " +from __future__ import annotations + +import re + +from . import Image, _imagingmorph + +LUT_SIZE = 1 << 9 + +# fmt: off +ROTATION_MATRIX = [ + 6, 3, 0, + 7, 4, 1, + 8, 5, 2, +] +MIRROR_MATRIX = [ + 2, 1, 0, + 5, 4, 3, + 8, 7, 6, +] +# fmt: on + + +class LutBuilder: + """A class for building a MorphLut from a descriptive language + + The input patterns is a list of a strings sequences like these:: + + 4:(... + .1. + 111)->1 + + (whitespaces including linebreaks are ignored). The option 4 + describes a series of symmetry operations (in this case a + 4-rotation), the pattern is described by: + + - . or X - Ignore + - 1 - Pixel is on + - 0 - Pixel is off + + The result of the operation is described after "->" string. + + The default is to return the current pixel value, which is + returned if no other match is found. + + Operations: + + - 4 - 4 way rotation + - N - Negate + - 1 - Dummy op for no other operation (an op must always be given) + - M - Mirroring + + Example:: + + lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) + lut = lb.build_lut() + + """ + + def __init__( + self, patterns: list[str] | None = None, op_name: str | None = None + ) -> None: + if patterns is not None: + self.patterns = patterns + else: + self.patterns = [] + self.lut: bytearray | None = None + if op_name is not None: + known_patterns = { + "corner": ["1:(... ... ...)->0", "4:(00. 01. ...)->1"], + "dilation4": ["4:(... .0. .1.)->1"], + "dilation8": ["4:(... .0. .1.)->1", "4:(... .0. ..1)->1"], + "erosion4": ["4:(... .1. .0.)->0"], + "erosion8": ["4:(... .1. .0.)->0", "4:(... .1. ..0)->0"], + "edge": [ + "1:(... ... ...)->0", + "4:(.0. .1. ...)->1", + "4:(01. .1. ...)->1", + ], + } + if op_name not in known_patterns: + msg = f"Unknown pattern {op_name}!" + raise Exception(msg) + + self.patterns = known_patterns[op_name] + + def add_patterns(self, patterns: list[str]) -> None: + self.patterns += patterns + + def build_default_lut(self) -> None: + symbols = [0, 1] + m = 1 << 4 # pos of current pixel + self.lut = bytearray(symbols[(i & m) > 0] for i in range(LUT_SIZE)) + + def get_lut(self) -> bytearray | None: + return self.lut + + def _string_permute(self, pattern: str, permutation: list[int]) -> str: + """string_permute takes a pattern and a permutation and returns the + string permuted according to the permutation list. + """ + assert len(permutation) == 9 + return "".join(pattern[p] for p in permutation) + + def _pattern_permute( + self, basic_pattern: str, options: str, basic_result: int + ) -> list[tuple[str, int]]: + """pattern_permute takes a basic pattern and its result and clones + the pattern according to the modifications described in the $options + parameter. It returns a list of all cloned patterns.""" + patterns = [(basic_pattern, basic_result)] + + # rotations + if "4" in options: + res = patterns[-1][1] + for i in range(4): + patterns.append( + (self._string_permute(patterns[-1][0], ROTATION_MATRIX), res) + ) + # mirror + if "M" in options: + n = len(patterns) + for pattern, res in patterns[:n]: + patterns.append((self._string_permute(pattern, MIRROR_MATRIX), res)) + + # negate + if "N" in options: + n = len(patterns) + for pattern, res in patterns[:n]: + # Swap 0 and 1 + pattern = pattern.replace("0", "Z").replace("1", "0").replace("Z", "1") + res = 1 - int(res) + patterns.append((pattern, res)) + + return patterns + + def build_lut(self) -> bytearray: + """Compile all patterns into a morphology lut. + + TBD :Build based on (file) morphlut:modify_lut + """ + self.build_default_lut() + assert self.lut is not None + patterns = [] + + # Parse and create symmetries of the patterns strings + for p in self.patterns: + m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", "")) + if not m: + msg = 'Syntax error in pattern "' + p + '"' + raise Exception(msg) + options = m.group(1) + pattern = m.group(2) + result = int(m.group(3)) + + # Get rid of spaces + pattern = pattern.replace(" ", "").replace("\n", "") + + patterns += self._pattern_permute(pattern, options, result) + + # compile the patterns into regular expressions for speed + compiled_patterns = [] + for pattern in patterns: + p = pattern[0].replace(".", "X").replace("X", "[01]") + compiled_patterns.append((re.compile(p), pattern[1])) + + # Step through table and find patterns that match. + # Note that all the patterns are searched. The last one + # caught overrides + for i in range(LUT_SIZE): + # Build the bit pattern + bitpattern = bin(i)[2:] + bitpattern = ("0" * (9 - len(bitpattern)) + bitpattern)[::-1] + + for pattern, r in compiled_patterns: + if pattern.match(bitpattern): + self.lut[i] = [0, 1][r] + + return self.lut + + +class MorphOp: + """A class for binary morphological operators""" + + def __init__( + self, + lut: bytearray | None = None, + op_name: str | None = None, + patterns: list[str] | None = None, + ) -> None: + """Create a binary morphological operator""" + self.lut = lut + if op_name is not None: + self.lut = LutBuilder(op_name=op_name).build_lut() + elif patterns is not None: + self.lut = LutBuilder(patterns=patterns).build_lut() + + def apply(self, image: Image.Image) -> tuple[int, Image.Image]: + """Run a single morphological operation on an image + + Returns a tuple of the number of changed pixels and the + morphed image""" + if self.lut is None: + msg = "No operator loaded" + raise Exception(msg) + + if image.mode != "L": + msg = "Image mode must be L" + raise ValueError(msg) + outimage = Image.new(image.mode, image.size, None) + count = _imagingmorph.apply(bytes(self.lut), image.getim(), outimage.getim()) + return count, outimage + + def match(self, image: Image.Image) -> list[tuple[int, int]]: + """Get a list of coordinates matching the morphological operation on + an image. + + Returns a list of tuples of (x,y) coordinates + of all matching pixels. See :ref:`coordinate-system`.""" + if self.lut is None: + msg = "No operator loaded" + raise Exception(msg) + + if image.mode != "L": + msg = "Image mode must be L" + raise ValueError(msg) + return _imagingmorph.match(bytes(self.lut), image.getim()) + + def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: + """Get a list of all turned on pixels in a binary image + + Returns a list of tuples of (x,y) coordinates + of all matching pixels. See :ref:`coordinate-system`.""" + + if image.mode != "L": + msg = "Image mode must be L" + raise ValueError(msg) + return _imagingmorph.get_on_pixels(image.getim()) + + def load_lut(self, filename: str) -> None: + """Load an operator from an mrl file""" + with open(filename, "rb") as f: + self.lut = bytearray(f.read()) + + if len(self.lut) != LUT_SIZE: + self.lut = None + msg = "Wrong size operator file!" + raise Exception(msg) + + def save_lut(self, filename: str) -> None: + """Save an operator to an mrl file""" + if self.lut is None: + msg = "No operator loaded" + raise Exception(msg) + with open(filename, "wb") as f: + f.write(self.lut) + + def set_lut(self, lut: bytearray | None) -> None: + """Set the lut from an external source""" + self.lut = lut diff --git a/venv/Lib/site-packages/PIL/ImageOps.py b/venv/Lib/site-packages/PIL/ImageOps.py new file mode 100644 index 00000000..da28854b --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageOps.py @@ -0,0 +1,745 @@ +# +# The Python Imaging Library. +# $Id$ +# +# standard image operations +# +# History: +# 2001-10-20 fl Created +# 2001-10-23 fl Added autocontrast operator +# 2001-12-18 fl Added Kevin's fit operator +# 2004-03-14 fl Fixed potential division by zero in equalize +# 2005-05-05 fl Fixed equalize for low number of values +# +# Copyright (c) 2001-2004 by Secret Labs AB +# Copyright (c) 2001-2004 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import functools +import operator +import re +from collections.abc import Sequence +from typing import Literal, Protocol, cast, overload + +from . import ExifTags, Image, ImagePalette + +# +# helpers + + +def _border(border: int | tuple[int, ...]) -> tuple[int, int, int, int]: + if isinstance(border, tuple): + if len(border) == 2: + left, top = right, bottom = border + elif len(border) == 4: + left, top, right, bottom = border + else: + left = top = right = bottom = border + return left, top, right, bottom + + +def _color(color: str | int | tuple[int, ...], mode: str) -> int | tuple[int, ...]: + if isinstance(color, str): + from . import ImageColor + + color = ImageColor.getcolor(color, mode) + return color + + +def _lut(image: Image.Image, lut: list[int]) -> Image.Image: + if image.mode == "P": + # FIXME: apply to lookup table, not image data + msg = "mode P support coming soon" + raise NotImplementedError(msg) + elif image.mode in ("L", "RGB"): + if image.mode == "RGB" and len(lut) == 256: + lut = lut + lut + lut + return image.point(lut) + else: + msg = f"not supported for mode {image.mode}" + raise OSError(msg) + + +# +# actions + + +def autocontrast( + image: Image.Image, + cutoff: float | tuple[float, float] = 0, + ignore: int | Sequence[int] | None = None, + mask: Image.Image | None = None, + preserve_tone: bool = False, +) -> Image.Image: + """ + Maximize (normalize) image contrast. This function calculates a + histogram of the input image (or mask region), removes ``cutoff`` percent of the + lightest and darkest pixels from the histogram, and remaps the image + so that the darkest pixel becomes black (0), and the lightest + becomes white (255). + + :param image: The image to process. + :param cutoff: The percent to cut off from the histogram on the low and + high ends. Either a tuple of (low, high), or a single + number for both. + :param ignore: The background pixel value (use None for no background). + :param mask: Histogram used in contrast operation is computed using pixels + within the mask. If no mask is given the entire image is used + for histogram computation. + :param preserve_tone: Preserve image tone in Photoshop-like style autocontrast. + + .. versionadded:: 8.2.0 + + :return: An image. + """ + if preserve_tone: + histogram = image.convert("L").histogram(mask) + else: + histogram = image.histogram(mask) + + lut = [] + for layer in range(0, len(histogram), 256): + h = histogram[layer : layer + 256] + if ignore is not None: + # get rid of outliers + if isinstance(ignore, int): + h[ignore] = 0 + else: + for ix in ignore: + h[ix] = 0 + if cutoff: + # cut off pixels from both ends of the histogram + if not isinstance(cutoff, tuple): + cutoff = (cutoff, cutoff) + # get number of pixels + n = 0 + for ix in range(256): + n = n + h[ix] + # remove cutoff% pixels from the low end + cut = int(n * cutoff[0] // 100) + for lo in range(256): + if cut > h[lo]: + cut = cut - h[lo] + h[lo] = 0 + else: + h[lo] -= cut + cut = 0 + if cut <= 0: + break + # remove cutoff% samples from the high end + cut = int(n * cutoff[1] // 100) + for hi in range(255, -1, -1): + if cut > h[hi]: + cut = cut - h[hi] + h[hi] = 0 + else: + h[hi] -= cut + cut = 0 + if cut <= 0: + break + # find lowest/highest samples after preprocessing + for lo in range(256): + if h[lo]: + break + for hi in range(255, -1, -1): + if h[hi]: + break + if hi <= lo: + # don't bother + lut.extend(list(range(256))) + else: + scale = 255.0 / (hi - lo) + offset = -lo * scale + for ix in range(256): + ix = int(ix * scale + offset) + if ix < 0: + ix = 0 + elif ix > 255: + ix = 255 + lut.append(ix) + return _lut(image, lut) + + +def colorize( + image: Image.Image, + black: str | tuple[int, ...], + white: str | tuple[int, ...], + mid: str | int | tuple[int, ...] | None = None, + blackpoint: int = 0, + whitepoint: int = 255, + midpoint: int = 127, +) -> Image.Image: + """ + Colorize grayscale image. + This function calculates a color wedge which maps all black pixels in + the source image to the first color and all white pixels to the + second color. If ``mid`` is specified, it uses three-color mapping. + The ``black`` and ``white`` arguments should be RGB tuples or color names; + optionally you can use three-color mapping by also specifying ``mid``. + Mapping positions for any of the colors can be specified + (e.g. ``blackpoint``), where these parameters are the integer + value corresponding to where the corresponding color should be mapped. + These parameters must have logical order, such that + ``blackpoint <= midpoint <= whitepoint`` (if ``mid`` is specified). + + :param image: The image to colorize. + :param black: The color to use for black input pixels. + :param white: The color to use for white input pixels. + :param mid: The color to use for midtone input pixels. + :param blackpoint: an int value [0, 255] for the black mapping. + :param whitepoint: an int value [0, 255] for the white mapping. + :param midpoint: an int value [0, 255] for the midtone mapping. + :return: An image. + """ + + # Initial asserts + assert image.mode == "L" + if mid is None: + assert 0 <= blackpoint <= whitepoint <= 255 + else: + assert 0 <= blackpoint <= midpoint <= whitepoint <= 255 + + # Define colors from arguments + rgb_black = cast(Sequence[int], _color(black, "RGB")) + rgb_white = cast(Sequence[int], _color(white, "RGB")) + rgb_mid = cast(Sequence[int], _color(mid, "RGB")) if mid is not None else None + + # Empty lists for the mapping + red = [] + green = [] + blue = [] + + # Create the low-end values + for i in range(blackpoint): + red.append(rgb_black[0]) + green.append(rgb_black[1]) + blue.append(rgb_black[2]) + + # Create the mapping (2-color) + if rgb_mid is None: + range_map = range(whitepoint - blackpoint) + + for i in range_map: + red.append( + rgb_black[0] + i * (rgb_white[0] - rgb_black[0]) // len(range_map) + ) + green.append( + rgb_black[1] + i * (rgb_white[1] - rgb_black[1]) // len(range_map) + ) + blue.append( + rgb_black[2] + i * (rgb_white[2] - rgb_black[2]) // len(range_map) + ) + + # Create the mapping (3-color) + else: + range_map1 = range(midpoint - blackpoint) + range_map2 = range(whitepoint - midpoint) + + for i in range_map1: + red.append( + rgb_black[0] + i * (rgb_mid[0] - rgb_black[0]) // len(range_map1) + ) + green.append( + rgb_black[1] + i * (rgb_mid[1] - rgb_black[1]) // len(range_map1) + ) + blue.append( + rgb_black[2] + i * (rgb_mid[2] - rgb_black[2]) // len(range_map1) + ) + for i in range_map2: + red.append(rgb_mid[0] + i * (rgb_white[0] - rgb_mid[0]) // len(range_map2)) + green.append( + rgb_mid[1] + i * (rgb_white[1] - rgb_mid[1]) // len(range_map2) + ) + blue.append(rgb_mid[2] + i * (rgb_white[2] - rgb_mid[2]) // len(range_map2)) + + # Create the high-end values + for i in range(256 - whitepoint): + red.append(rgb_white[0]) + green.append(rgb_white[1]) + blue.append(rgb_white[2]) + + # Return converted image + image = image.convert("RGB") + return _lut(image, red + green + blue) + + +def contain( + image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC +) -> Image.Image: + """ + Returns a resized version of the image, set to the maximum width and height + within the requested size, while maintaining the original aspect ratio. + + :param image: The image to resize. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: Resampling method to use. Default is + :py:attr:`~PIL.Image.Resampling.BICUBIC`. + See :ref:`concept-filters`. + :return: An image. + """ + + im_ratio = image.width / image.height + dest_ratio = size[0] / size[1] + + if im_ratio != dest_ratio: + if im_ratio > dest_ratio: + new_height = round(image.height / image.width * size[0]) + if new_height != size[1]: + size = (size[0], new_height) + else: + new_width = round(image.width / image.height * size[1]) + if new_width != size[0]: + size = (new_width, size[1]) + return image.resize(size, resample=method) + + +def cover( + image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC +) -> Image.Image: + """ + Returns a resized version of the image, so that the requested size is + covered, while maintaining the original aspect ratio. + + :param image: The image to resize. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: Resampling method to use. Default is + :py:attr:`~PIL.Image.Resampling.BICUBIC`. + See :ref:`concept-filters`. + :return: An image. + """ + + im_ratio = image.width / image.height + dest_ratio = size[0] / size[1] + + if im_ratio != dest_ratio: + if im_ratio < dest_ratio: + new_height = round(image.height / image.width * size[0]) + if new_height != size[1]: + size = (size[0], new_height) + else: + new_width = round(image.width / image.height * size[1]) + if new_width != size[0]: + size = (new_width, size[1]) + return image.resize(size, resample=method) + + +def pad( + image: Image.Image, + size: tuple[int, int], + method: int = Image.Resampling.BICUBIC, + color: str | int | tuple[int, ...] | None = None, + centering: tuple[float, float] = (0.5, 0.5), +) -> Image.Image: + """ + Returns a resized and padded version of the image, expanded to fill the + requested aspect ratio and size. + + :param image: The image to resize and crop. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: Resampling method to use. Default is + :py:attr:`~PIL.Image.Resampling.BICUBIC`. + See :ref:`concept-filters`. + :param color: The background color of the padded image. + :param centering: Control the position of the original image within the + padded version. + + (0.5, 0.5) will keep the image centered + (0, 0) will keep the image aligned to the top left + (1, 1) will keep the image aligned to the bottom + right + :return: An image. + """ + + resized = contain(image, size, method) + if resized.size == size: + out = resized + else: + out = Image.new(image.mode, size, color) + if resized.palette: + palette = resized.getpalette() + if palette is not None: + out.putpalette(palette) + if resized.width != size[0]: + x = round((size[0] - resized.width) * max(0, min(centering[0], 1))) + out.paste(resized, (x, 0)) + else: + y = round((size[1] - resized.height) * max(0, min(centering[1], 1))) + out.paste(resized, (0, y)) + return out + + +def crop(image: Image.Image, border: int = 0) -> Image.Image: + """ + Remove border from image. The same amount of pixels are removed + from all four sides. This function works on all image modes. + + .. seealso:: :py:meth:`~PIL.Image.Image.crop` + + :param image: The image to crop. + :param border: The number of pixels to remove. + :return: An image. + """ + left, top, right, bottom = _border(border) + return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) + + +def scale( + image: Image.Image, factor: float, resample: int = Image.Resampling.BICUBIC +) -> Image.Image: + """ + Returns a rescaled image by a specific factor given in parameter. + A factor greater than 1 expands the image, between 0 and 1 contracts the + image. + + :param image: The image to rescale. + :param factor: The expansion factor, as a float. + :param resample: Resampling method to use. Default is + :py:attr:`~PIL.Image.Resampling.BICUBIC`. + See :ref:`concept-filters`. + :returns: An :py:class:`~PIL.Image.Image` object. + """ + if factor == 1: + return image.copy() + elif factor <= 0: + msg = "the factor must be greater than 0" + raise ValueError(msg) + else: + size = (round(factor * image.width), round(factor * image.height)) + return image.resize(size, resample) + + +class SupportsGetMesh(Protocol): + """ + An object that supports the ``getmesh`` method, taking an image as an + argument, and returning a list of tuples. Each tuple contains two tuples, + the source box as a tuple of 4 integers, and a tuple of 8 integers for the + final quadrilateral, in order of top left, bottom left, bottom right, top + right. + """ + + def getmesh( + self, image: Image.Image + ) -> list[ + tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] + ]: ... + + +def deform( + image: Image.Image, + deformer: SupportsGetMesh, + resample: int = Image.Resampling.BILINEAR, +) -> Image.Image: + """ + Deform the image. + + :param image: The image to deform. + :param deformer: A deformer object. Any object that implements a + ``getmesh`` method can be used. + :param resample: An optional resampling filter. Same values possible as + in the PIL.Image.transform function. + :return: An image. + """ + return image.transform( + image.size, Image.Transform.MESH, deformer.getmesh(image), resample + ) + + +def equalize(image: Image.Image, mask: Image.Image | None = None) -> Image.Image: + """ + Equalize the image histogram. This function applies a non-linear + mapping to the input image, in order to create a uniform + distribution of grayscale values in the output image. + + :param image: The image to equalize. + :param mask: An optional mask. If given, only the pixels selected by + the mask are included in the analysis. + :return: An image. + """ + if image.mode == "P": + image = image.convert("RGB") + h = image.histogram(mask) + lut = [] + for b in range(0, len(h), 256): + histo = [_f for _f in h[b : b + 256] if _f] + if len(histo) <= 1: + lut.extend(list(range(256))) + else: + step = (functools.reduce(operator.add, histo) - histo[-1]) // 255 + if not step: + lut.extend(list(range(256))) + else: + n = step // 2 + for i in range(256): + lut.append(n // step) + n = n + h[i + b] + return _lut(image, lut) + + +def expand( + image: Image.Image, + border: int | tuple[int, ...] = 0, + fill: str | int | tuple[int, ...] = 0, +) -> Image.Image: + """ + Add border to the image + + :param image: The image to expand. + :param border: Border width, in pixels. + :param fill: Pixel fill value (a color value). Default is 0 (black). + :return: An image. + """ + left, top, right, bottom = _border(border) + width = left + image.size[0] + right + height = top + image.size[1] + bottom + color = _color(fill, image.mode) + if image.palette: + palette = ImagePalette.ImagePalette(palette=image.getpalette()) + if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4): + color = palette.getcolor(color) + else: + palette = None + out = Image.new(image.mode, (width, height), color) + if palette: + out.putpalette(palette.palette) + out.paste(image, (left, top)) + return out + + +def fit( + image: Image.Image, + size: tuple[int, int], + method: int = Image.Resampling.BICUBIC, + bleed: float = 0.0, + centering: tuple[float, float] = (0.5, 0.5), +) -> Image.Image: + """ + Returns a resized and cropped version of the image, cropped to the + requested aspect ratio and size. + + This function was contributed by Kevin Cazabon. + + :param image: The image to resize and crop. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: Resampling method to use. Default is + :py:attr:`~PIL.Image.Resampling.BICUBIC`. + See :ref:`concept-filters`. + :param bleed: Remove a border around the outside of the image from all + four edges. The value is a decimal percentage (use 0.01 for + one percent). The default value is 0 (no border). + Cannot be greater than or equal to 0.5. + :param centering: Control the cropping position. Use (0.5, 0.5) for + center cropping (e.g. if cropping the width, take 50% off + of the left side, and therefore 50% off the right side). + (0.0, 0.0) will crop from the top left corner (i.e. if + cropping the width, take all of the crop off of the right + side, and if cropping the height, take all of it off the + bottom). (1.0, 0.0) will crop from the bottom left + corner, etc. (i.e. if cropping the width, take all of the + crop off the left side, and if cropping the height take + none from the top, and therefore all off the bottom). + :return: An image. + """ + + # by Kevin Cazabon, Feb 17/2000 + # kevin@cazabon.com + # https://www.cazabon.com + + centering_x, centering_y = centering + + if not 0.0 <= centering_x <= 1.0: + centering_x = 0.5 + if not 0.0 <= centering_y <= 1.0: + centering_y = 0.5 + + if not 0.0 <= bleed < 0.5: + bleed = 0.0 + + # calculate the area to use for resizing and cropping, subtracting + # the 'bleed' around the edges + + # number of pixels to trim off on Top and Bottom, Left and Right + bleed_pixels = (bleed * image.size[0], bleed * image.size[1]) + + live_size = ( + image.size[0] - bleed_pixels[0] * 2, + image.size[1] - bleed_pixels[1] * 2, + ) + + # calculate the aspect ratio of the live_size + live_size_ratio = live_size[0] / live_size[1] + + # calculate the aspect ratio of the output image + output_ratio = size[0] / size[1] + + # figure out if the sides or top/bottom will be cropped off + if live_size_ratio == output_ratio: + # live_size is already the needed ratio + crop_width = live_size[0] + crop_height = live_size[1] + elif live_size_ratio >= output_ratio: + # live_size is wider than what's needed, crop the sides + crop_width = output_ratio * live_size[1] + crop_height = live_size[1] + else: + # live_size is taller than what's needed, crop the top and bottom + crop_width = live_size[0] + crop_height = live_size[0] / output_ratio + + # make the crop + crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering_x + crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering_y + + crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height) + + # resize the image and return it + return image.resize(size, method, box=crop) + + +def flip(image: Image.Image) -> Image.Image: + """ + Flip the image vertically (top to bottom). + + :param image: The image to flip. + :return: An image. + """ + return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + + +def grayscale(image: Image.Image) -> Image.Image: + """ + Convert the image to grayscale. + + :param image: The image to convert. + :return: An image. + """ + return image.convert("L") + + +def invert(image: Image.Image) -> Image.Image: + """ + Invert (negate) the image. + + :param image: The image to invert. + :return: An image. + """ + lut = list(range(255, -1, -1)) + return image.point(lut) if image.mode == "1" else _lut(image, lut) + + +def mirror(image: Image.Image) -> Image.Image: + """ + Flip image horizontally (left to right). + + :param image: The image to mirror. + :return: An image. + """ + return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + + +def posterize(image: Image.Image, bits: int) -> Image.Image: + """ + Reduce the number of bits for each color channel. + + :param image: The image to posterize. + :param bits: The number of bits to keep for each channel (1-8). + :return: An image. + """ + mask = ~(2 ** (8 - bits) - 1) + lut = [i & mask for i in range(256)] + return _lut(image, lut) + + +def solarize(image: Image.Image, threshold: int = 128) -> Image.Image: + """ + Invert all pixel values above a threshold. + + :param image: The image to solarize. + :param threshold: All pixels above this grayscale level are inverted. + :return: An image. + """ + lut = [] + for i in range(256): + if i < threshold: + lut.append(i) + else: + lut.append(255 - i) + return _lut(image, lut) + + +@overload +def exif_transpose(image: Image.Image, *, in_place: Literal[True]) -> None: ... + + +@overload +def exif_transpose( + image: Image.Image, *, in_place: Literal[False] = False +) -> Image.Image: ... + + +def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image | None: + """ + If an image has an EXIF Orientation tag, other than 1, transpose the image + accordingly, and remove the orientation data. + + :param image: The image to transpose. + :param in_place: Boolean. Keyword-only argument. + If ``True``, the original image is modified in-place, and ``None`` is returned. + If ``False`` (default), a new :py:class:`~PIL.Image.Image` object is returned + with the transposition applied. If there is no transposition, a copy of the + image will be returned. + """ + image.load() + image_exif = image.getexif() + orientation = image_exif.get(ExifTags.Base.Orientation, 1) + method = { + 2: Image.Transpose.FLIP_LEFT_RIGHT, + 3: Image.Transpose.ROTATE_180, + 4: Image.Transpose.FLIP_TOP_BOTTOM, + 5: Image.Transpose.TRANSPOSE, + 6: Image.Transpose.ROTATE_270, + 7: Image.Transpose.TRANSVERSE, + 8: Image.Transpose.ROTATE_90, + }.get(orientation) + if method is not None: + if in_place: + image.im = image.im.transpose(method) + image._size = image.im.size + else: + transposed_image = image.transpose(method) + exif_image = image if in_place else transposed_image + + exif = exif_image.getexif() + if ExifTags.Base.Orientation in exif: + del exif[ExifTags.Base.Orientation] + if "exif" in exif_image.info: + exif_image.info["exif"] = exif.tobytes() + elif "Raw profile type exif" in exif_image.info: + exif_image.info["Raw profile type exif"] = exif.tobytes().hex() + for key in ("XML:com.adobe.xmp", "xmp"): + if key in exif_image.info: + for pattern in ( + r'tiff:Orientation="([0-9])"', + r"([0-9])", + ): + value = exif_image.info[key] + if isinstance(value, str): + value = re.sub(pattern, "", value) + elif isinstance(value, tuple): + value = tuple( + re.sub(pattern.encode(), b"", v) for v in value + ) + else: + value = re.sub(pattern.encode(), b"", value) + exif_image.info[key] = value + if not in_place: + return transposed_image + elif not in_place: + return image.copy() + return None diff --git a/venv/Lib/site-packages/PIL/ImagePalette.py b/venv/Lib/site-packages/PIL/ImagePalette.py new file mode 100644 index 00000000..10369711 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImagePalette.py @@ -0,0 +1,286 @@ +# +# The Python Imaging Library. +# $Id$ +# +# image palette object +# +# History: +# 1996-03-11 fl Rewritten. +# 1997-01-03 fl Up and running. +# 1997-08-23 fl Added load hack +# 2001-04-16 fl Fixed randint shadow bug in random() +# +# Copyright (c) 1997-2001 by Secret Labs AB +# Copyright (c) 1996-1997 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import array +from collections.abc import Sequence +from typing import IO + +from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import Image + + +class ImagePalette: + """ + Color palette for palette mapped images + + :param mode: The mode to use for the palette. See: + :ref:`concept-modes`. Defaults to "RGB" + :param palette: An optional palette. If given, it must be a bytearray, + an array or a list of ints between 0-255. The list must consist of + all channels for one color followed by the next color (e.g. RGBRGBRGB). + Defaults to an empty palette. + """ + + def __init__( + self, + mode: str = "RGB", + palette: Sequence[int] | bytes | bytearray | None = None, + ) -> None: + self.mode = mode + self.rawmode: str | None = None # if set, palette contains raw data + self.palette = palette or bytearray() + self.dirty: int | None = None + + @property + def palette(self) -> Sequence[int] | bytes | bytearray: + return self._palette + + @palette.setter + def palette(self, palette: Sequence[int] | bytes | bytearray) -> None: + self._colors: dict[tuple[int, ...], int] | None = None + self._palette = palette + + @property + def colors(self) -> dict[tuple[int, ...], int]: + if self._colors is None: + mode_len = len(self.mode) + self._colors = {} + for i in range(0, len(self.palette), mode_len): + color = tuple(self.palette[i : i + mode_len]) + if color in self._colors: + continue + self._colors[color] = i // mode_len + return self._colors + + @colors.setter + def colors(self, colors: dict[tuple[int, ...], int]) -> None: + self._colors = colors + + def copy(self) -> ImagePalette: + new = ImagePalette() + + new.mode = self.mode + new.rawmode = self.rawmode + if self.palette is not None: + new.palette = self.palette[:] + new.dirty = self.dirty + + return new + + def getdata(self) -> tuple[str, Sequence[int] | bytes | bytearray]: + """ + Get palette contents in format suitable for the low-level + ``im.putpalette`` primitive. + + .. warning:: This method is experimental. + """ + if self.rawmode: + return self.rawmode, self.palette + return self.mode, self.tobytes() + + def tobytes(self) -> bytes: + """Convert palette to bytes. + + .. warning:: This method is experimental. + """ + if self.rawmode: + msg = "palette contains raw palette data" + raise ValueError(msg) + if isinstance(self.palette, bytes): + return self.palette + arr = array.array("B", self.palette) + return arr.tobytes() + + # Declare tostring as an alias for tobytes + tostring = tobytes + + def _new_color_index( + self, image: Image.Image | None = None, e: Exception | None = None + ) -> int: + if not isinstance(self.palette, bytearray): + self._palette = bytearray(self.palette) + index = len(self.palette) // 3 + special_colors: tuple[int | tuple[int, ...] | None, ...] = () + if image: + special_colors = ( + image.info.get("background"), + image.info.get("transparency"), + ) + while index in special_colors: + index += 1 + if index >= 256: + if image: + # Search for an unused index + for i, count in reversed(list(enumerate(image.histogram()))): + if count == 0 and i not in special_colors: + index = i + break + if index >= 256: + msg = "cannot allocate more than 256 colors" + raise ValueError(msg) from e + return index + + def getcolor( + self, + color: tuple[int, ...], + image: Image.Image | None = None, + ) -> int: + """Given an rgb tuple, allocate palette entry. + + .. warning:: This method is experimental. + """ + if self.rawmode: + msg = "palette contains raw palette data" + raise ValueError(msg) + if isinstance(color, tuple): + if self.mode == "RGB": + if len(color) == 4: + if color[3] != 255: + msg = "cannot add non-opaque RGBA color to RGB palette" + raise ValueError(msg) + color = color[:3] + elif self.mode == "RGBA": + if len(color) == 3: + color += (255,) + try: + return self.colors[color] + except KeyError as e: + # allocate new color slot + index = self._new_color_index(image, e) + assert isinstance(self._palette, bytearray) + self.colors[color] = index + if index * 3 < len(self.palette): + self._palette = ( + self._palette[: index * 3] + + bytes(color) + + self._palette[index * 3 + 3 :] + ) + else: + self._palette += bytes(color) + self.dirty = 1 + return index + else: + msg = f"unknown color specifier: {repr(color)}" # type: ignore[unreachable] + raise ValueError(msg) + + def save(self, fp: str | IO[str]) -> None: + """Save palette to text file. + + .. warning:: This method is experimental. + """ + if self.rawmode: + msg = "palette contains raw palette data" + raise ValueError(msg) + if isinstance(fp, str): + fp = open(fp, "w") + fp.write("# Palette\n") + fp.write(f"# Mode: {self.mode}\n") + for i in range(256): + fp.write(f"{i}") + for j in range(i * len(self.mode), (i + 1) * len(self.mode)): + try: + fp.write(f" {self.palette[j]}") + except IndexError: + fp.write(" 0") + fp.write("\n") + fp.close() + + +# -------------------------------------------------------------------- +# Internal + + +def raw(rawmode: str, data: Sequence[int] | bytes | bytearray) -> ImagePalette: + palette = ImagePalette() + palette.rawmode = rawmode + palette.palette = data + palette.dirty = 1 + return palette + + +# -------------------------------------------------------------------- +# Factories + + +def make_linear_lut(black: int, white: float) -> list[int]: + if black == 0: + return [int(white * i // 255) for i in range(256)] + + msg = "unavailable when black is non-zero" + raise NotImplementedError(msg) # FIXME + + +def make_gamma_lut(exp: float) -> list[int]: + return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)] + + +def negative(mode: str = "RGB") -> ImagePalette: + palette = list(range(256 * len(mode))) + palette.reverse() + return ImagePalette(mode, [i // len(mode) for i in palette]) + + +def random(mode: str = "RGB") -> ImagePalette: + from random import randint + + palette = [randint(0, 255) for _ in range(256 * len(mode))] + return ImagePalette(mode, palette) + + +def sepia(white: str = "#fff0c0") -> ImagePalette: + bands = [make_linear_lut(0, band) for band in ImageColor.getrgb(white)] + return ImagePalette("RGB", [bands[i % 3][i // 3] for i in range(256 * 3)]) + + +def wedge(mode: str = "RGB") -> ImagePalette: + palette = list(range(256 * len(mode))) + return ImagePalette(mode, [i // len(mode) for i in palette]) + + +def load(filename: str) -> tuple[bytes, str]: + # FIXME: supports GIMP gradients only + + with open(filename, "rb") as fp: + paletteHandlers: list[ + type[ + GimpPaletteFile.GimpPaletteFile + | GimpGradientFile.GimpGradientFile + | PaletteFile.PaletteFile + ] + ] = [ + GimpPaletteFile.GimpPaletteFile, + GimpGradientFile.GimpGradientFile, + PaletteFile.PaletteFile, + ] + for paletteHandler in paletteHandlers: + try: + fp.seek(0) + lut = paletteHandler(fp).getpalette() + if lut: + break + except (SyntaxError, ValueError): + pass + else: + msg = "cannot load palette" + raise OSError(msg) + + return lut # data, rawmode diff --git a/venv/Lib/site-packages/PIL/ImagePath.py b/venv/Lib/site-packages/PIL/ImagePath.py new file mode 100644 index 00000000..77e8a609 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImagePath.py @@ -0,0 +1,20 @@ +# +# The Python Imaging Library +# $Id$ +# +# path interface +# +# History: +# 1996-11-04 fl Created +# 2002-04-14 fl Added documentation stub class +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image + +Path = Image.core.path diff --git a/venv/Lib/site-packages/PIL/ImageQt.py b/venv/Lib/site-packages/PIL/ImageQt.py new file mode 100644 index 00000000..df7a57b6 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageQt.py @@ -0,0 +1,220 @@ +# +# The Python Imaging Library. +# $Id$ +# +# a simple Qt image interface. +# +# history: +# 2006-06-03 fl: created +# 2006-06-04 fl: inherit from QImage instead of wrapping it +# 2006-06-05 fl: removed toimage helper; move string support to ImageQt +# 2013-11-13 fl: add support for Qt5 (aurelien.ballier@cyclonit.com) +# +# Copyright (c) 2006 by Secret Labs AB +# Copyright (c) 2006 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import sys +from io import BytesIO +from typing import Any, Callable, Union + +from . import Image +from ._util import is_path + +TYPE_CHECKING = False +if TYPE_CHECKING: + import PyQt6 + import PySide6 + + from . import ImageFile + + QBuffer: type + QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray] + QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice] + QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage] + QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap] + +qt_version: str | None +qt_versions = [ + ["6", "PyQt6"], + ["side6", "PySide6"], +] + +# If a version has already been imported, attempt it first +qt_versions.sort(key=lambda version: version[1] in sys.modules, reverse=True) +for version, qt_module in qt_versions: + try: + qRgba: Callable[[int, int, int, int], int] + if qt_module == "PyQt6": + from PyQt6.QtCore import QBuffer, QIODevice + from PyQt6.QtGui import QImage, QPixmap, qRgba + elif qt_module == "PySide6": + from PySide6.QtCore import QBuffer, QIODevice + from PySide6.QtGui import QImage, QPixmap, qRgba + except (ImportError, RuntimeError): + continue + qt_is_installed = True + qt_version = version + break +else: + qt_is_installed = False + qt_version = None + + +def rgb(r: int, g: int, b: int, a: int = 255) -> int: + """(Internal) Turns an RGB color into a Qt compatible color integer.""" + # use qRgb to pack the colors, and then turn the resulting long + # into a negative integer with the same bitpattern. + return qRgba(r, g, b, a) & 0xFFFFFFFF + + +def fromqimage(im: QImage | QPixmap) -> ImageFile.ImageFile: + """ + :param im: QImage or PIL ImageQt object + """ + buffer = QBuffer() + qt_openmode: object + if qt_version == "6": + try: + qt_openmode = getattr(QIODevice, "OpenModeFlag") + except AttributeError: + qt_openmode = getattr(QIODevice, "OpenMode") + else: + qt_openmode = QIODevice + buffer.open(getattr(qt_openmode, "ReadWrite")) + # preserve alpha channel with png + # otherwise ppm is more friendly with Image.open + if im.hasAlphaChannel(): + im.save(buffer, "png") + else: + im.save(buffer, "ppm") + + b = BytesIO() + b.write(buffer.data()) + buffer.close() + b.seek(0) + + return Image.open(b) + + +def fromqpixmap(im: QPixmap) -> ImageFile.ImageFile: + return fromqimage(im) + + +def align8to32(bytes: bytes, width: int, mode: str) -> bytes: + """ + converts each scanline of data from 8 bit to 32 bit aligned + """ + + bits_per_pixel = {"1": 1, "L": 8, "P": 8, "I;16": 16}[mode] + + # calculate bytes per line and the extra padding if needed + bits_per_line = bits_per_pixel * width + full_bytes_per_line, remaining_bits_per_line = divmod(bits_per_line, 8) + bytes_per_line = full_bytes_per_line + (1 if remaining_bits_per_line else 0) + + extra_padding = -bytes_per_line % 4 + + # already 32 bit aligned by luck + if not extra_padding: + return bytes + + new_data = [ + bytes[i * bytes_per_line : (i + 1) * bytes_per_line] + b"\x00" * extra_padding + for i in range(len(bytes) // bytes_per_line) + ] + + return b"".join(new_data) + + +def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]: + data = None + colortable = None + exclusive_fp = False + + # handle filename, if given instead of image name + if hasattr(im, "toUtf8"): + # FIXME - is this really the best way to do this? + im = str(im.toUtf8(), "utf-8") + if is_path(im): + im = Image.open(im) + exclusive_fp = True + assert isinstance(im, Image.Image) + + qt_format = getattr(QImage, "Format") if qt_version == "6" else QImage + if im.mode == "1": + format = getattr(qt_format, "Format_Mono") + elif im.mode == "L": + format = getattr(qt_format, "Format_Indexed8") + colortable = [rgb(i, i, i) for i in range(256)] + elif im.mode == "P": + format = getattr(qt_format, "Format_Indexed8") + palette = im.getpalette() + assert palette is not None + colortable = [rgb(*palette[i : i + 3]) for i in range(0, len(palette), 3)] + elif im.mode == "RGB": + # Populate the 4th channel with 255 + im = im.convert("RGBA") + + data = im.tobytes("raw", "BGRA") + format = getattr(qt_format, "Format_RGB32") + elif im.mode == "RGBA": + data = im.tobytes("raw", "BGRA") + format = getattr(qt_format, "Format_ARGB32") + elif im.mode == "I;16": + im = im.point(lambda i: i * 256) + + format = getattr(qt_format, "Format_Grayscale16") + else: + if exclusive_fp: + im.close() + msg = f"unsupported image mode {repr(im.mode)}" + raise ValueError(msg) + + size = im.size + __data = data or align8to32(im.tobytes(), size[0], im.mode) + if exclusive_fp: + im.close() + return {"data": __data, "size": size, "format": format, "colortable": colortable} + + +if qt_is_installed: + + class ImageQt(QImage): # type: ignore[misc] + def __init__(self, im: Image.Image | str | QByteArray) -> None: + """ + An PIL image wrapper for Qt. This is a subclass of PyQt's QImage + class. + + :param im: A PIL Image object, or a file name (given either as + Python string or a PyQt string object). + """ + im_data = _toqclass_helper(im) + # must keep a reference, or Qt will crash! + # All QImage constructors that take data operate on an existing + # buffer, so this buffer has to hang on for the life of the image. + # Fixes https://github.com/python-pillow/Pillow/issues/1370 + self.__data = im_data["data"] + super().__init__( + self.__data, + im_data["size"][0], + im_data["size"][1], + im_data["format"], + ) + if im_data["colortable"]: + self.setColorTable(im_data["colortable"]) + + +def toqimage(im: Image.Image | str | QByteArray) -> ImageQt: + return ImageQt(im) + + +def toqpixmap(im: Image.Image | str | QByteArray) -> QPixmap: + qimage = toqimage(im) + pixmap = getattr(QPixmap, "fromImage")(qimage) + if qt_version == "6": + pixmap.detach() + return pixmap diff --git a/venv/Lib/site-packages/PIL/ImageSequence.py b/venv/Lib/site-packages/PIL/ImageSequence.py new file mode 100644 index 00000000..a6fc340d --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageSequence.py @@ -0,0 +1,86 @@ +# +# The Python Imaging Library. +# $Id$ +# +# sequence support classes +# +# history: +# 1997-02-20 fl Created +# +# Copyright (c) 1997 by Secret Labs AB. +# Copyright (c) 1997 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +## +from __future__ import annotations + +from typing import Callable + +from . import Image + + +class Iterator: + """ + This class implements an iterator object that can be used to loop + over an image sequence. + + You can use the ``[]`` operator to access elements by index. This operator + will raise an :py:exc:`IndexError` if you try to access a nonexistent + frame. + + :param im: An image object. + """ + + def __init__(self, im: Image.Image) -> None: + if not hasattr(im, "seek"): + msg = "im must have seek method" + raise AttributeError(msg) + self.im = im + self.position = getattr(self.im, "_min_frame", 0) + + def __getitem__(self, ix: int) -> Image.Image: + try: + self.im.seek(ix) + return self.im + except EOFError as e: + msg = "end of sequence" + raise IndexError(msg) from e + + def __iter__(self) -> Iterator: + return self + + def __next__(self) -> Image.Image: + try: + self.im.seek(self.position) + self.position += 1 + return self.im + except EOFError as e: + msg = "end of sequence" + raise StopIteration(msg) from e + + +def all_frames( + im: Image.Image | list[Image.Image], + func: Callable[[Image.Image], Image.Image] | None = None, +) -> list[Image.Image]: + """ + Applies a given function to all frames in an image or a list of images. + The frames are returned as a list of separate images. + + :param im: An image, or a list of images. + :param func: The function to apply to all of the image frames. + :returns: A list of images. + """ + if not isinstance(im, list): + im = [im] + + ims = [] + for imSequence in im: + current = imSequence.tell() + + ims += [im_frame.copy() for im_frame in Iterator(imSequence)] + + imSequence.seek(current) + return [func(im) for im in ims] if func else ims diff --git a/venv/Lib/site-packages/PIL/ImageShow.py b/venv/Lib/site-packages/PIL/ImageShow.py new file mode 100644 index 00000000..7705608e --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageShow.py @@ -0,0 +1,362 @@ +# +# The Python Imaging Library. +# $Id$ +# +# im.show() drivers +# +# History: +# 2008-04-06 fl Created +# +# Copyright (c) Secret Labs AB 2008. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import abc +import os +import shutil +import subprocess +import sys +from shlex import quote +from typing import Any + +from . import Image + +_viewers = [] + + +def register(viewer: type[Viewer] | Viewer, order: int = 1) -> None: + """ + The :py:func:`register` function is used to register additional viewers:: + + from PIL import ImageShow + ImageShow.register(MyViewer()) # MyViewer will be used as a last resort + ImageShow.register(MySecondViewer(), 0) # MySecondViewer will be prioritised + ImageShow.register(ImageShow.XVViewer(), 0) # XVViewer will be prioritised + + :param viewer: The viewer to be registered. + :param order: + Zero or a negative integer to prepend this viewer to the list, + a positive integer to append it. + """ + if isinstance(viewer, type) and issubclass(viewer, Viewer): + viewer = viewer() + if order > 0: + _viewers.append(viewer) + else: + _viewers.insert(0, viewer) + + +def show(image: Image.Image, title: str | None = None, **options: Any) -> bool: + r""" + Display a given image. + + :param image: An image object. + :param title: Optional title. Not all viewers can display the title. + :param \**options: Additional viewer options. + :returns: ``True`` if a suitable viewer was found, ``False`` otherwise. + """ + for viewer in _viewers: + if viewer.show(image, title=title, **options): + return True + return False + + +class Viewer: + """Base class for viewers.""" + + # main api + + def show(self, image: Image.Image, **options: Any) -> int: + """ + The main function for displaying an image. + Converts the given image to the target format and displays it. + """ + + if not ( + image.mode in ("1", "RGBA") + or (self.format == "PNG" and image.mode in ("I;16", "LA")) + ): + base = Image.getmodebase(image.mode) + if image.mode != base: + image = image.convert(base) + + return self.show_image(image, **options) + + # hook methods + + format: str | None = None + """The format to convert the image into.""" + options: dict[str, Any] = {} + """Additional options used to convert the image.""" + + def get_format(self, image: Image.Image) -> str | None: + """Return format name, or ``None`` to save as PGM/PPM.""" + return self.format + + def get_command(self, file: str, **options: Any) -> str: + """ + Returns the command used to display the file. + Not implemented in the base class. + """ + msg = "unavailable in base viewer" + raise NotImplementedError(msg) + + def save_image(self, image: Image.Image) -> str: + """Save to temporary file and return filename.""" + return image._dump(format=self.get_format(image), **self.options) + + def show_image(self, image: Image.Image, **options: Any) -> int: + """Display the given image.""" + return self.show_file(self.save_image(image), **options) + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + os.system(self.get_command(path, **options)) # nosec + return 1 + + +# -------------------------------------------------------------------- + + +class WindowsViewer(Viewer): + """The default viewer on Windows is the default system application for PNG files.""" + + format = "PNG" + options = {"compress_level": 1, "save_all": True} + + def get_command(self, file: str, **options: Any) -> str: + return ( + f'start "Pillow" /WAIT "{file}" ' + "&& ping -n 4 127.0.0.1 >NUL " + f'&& del /f "{file}"' + ) + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + subprocess.Popen( + self.get_command(path, **options), + shell=True, + creationflags=getattr(subprocess, "CREATE_NO_WINDOW"), + ) # nosec + return 1 + + +if sys.platform == "win32": + register(WindowsViewer) + + +class MacViewer(Viewer): + """The default viewer on macOS using ``Preview.app``.""" + + format = "PNG" + options = {"compress_level": 1, "save_all": True} + + def get_command(self, file: str, **options: Any) -> str: + # on darwin open returns immediately resulting in the temp + # file removal while app is opening + command = "open -a Preview.app" + command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" + return command + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + subprocess.call(["open", "-a", "Preview.app", path]) + + pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS") + executable = (not pyinstaller and sys.executable) or shutil.which("python3") + if executable: + subprocess.Popen( + [ + executable, + "-c", + "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", + path, + ] + ) + return 1 + + +if sys.platform == "darwin": + register(MacViewer) + + +class UnixViewer(abc.ABC, Viewer): + format = "PNG" + options = {"compress_level": 1, "save_all": True} + + @abc.abstractmethod + def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]: + pass + + def get_command(self, file: str, **options: Any) -> str: + command = self.get_command_ex(file, **options)[0] + return f"{command} {quote(file)}" + + +class XDGViewer(UnixViewer): + """ + The freedesktop.org ``xdg-open`` command. + """ + + def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]: + command = executable = "xdg-open" + return command, executable + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + subprocess.Popen(["xdg-open", path]) + return 1 + + +class DisplayViewer(UnixViewer): + """ + The ImageMagick ``display`` command. + This viewer supports the ``title`` parameter. + """ + + def get_command_ex( + self, file: str, title: str | None = None, **options: Any + ) -> tuple[str, str]: + command = executable = "display" + if title: + command += f" -title {quote(title)}" + return command, executable + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + args = ["display"] + title = options.get("title") + if title: + args += ["-title", title] + args.append(path) + + subprocess.Popen(args) + return 1 + + +class GmDisplayViewer(UnixViewer): + """The GraphicsMagick ``gm display`` command.""" + + def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]: + executable = "gm" + command = "gm display" + return command, executable + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + subprocess.Popen(["gm", "display", path]) + return 1 + + +class EogViewer(UnixViewer): + """The GNOME Image Viewer ``eog`` command.""" + + def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]: + executable = "eog" + command = "eog -n" + return command, executable + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + subprocess.Popen(["eog", "-n", path]) + return 1 + + +class XVViewer(UnixViewer): + """ + The X Viewer ``xv`` command. + This viewer supports the ``title`` parameter. + """ + + def get_command_ex( + self, file: str, title: str | None = None, **options: Any + ) -> tuple[str, str]: + # note: xv is pretty outdated. most modern systems have + # imagemagick's display command instead. + command = executable = "xv" + if title: + command += f" -name {quote(title)}" + return command, executable + + def show_file(self, path: str, **options: Any) -> int: + """ + Display given file. + """ + if not os.path.exists(path): + raise FileNotFoundError + args = ["xv"] + title = options.get("title") + if title: + args += ["-name", title] + args.append(path) + + subprocess.Popen(args) + return 1 + + +if sys.platform not in ("win32", "darwin"): # unixoids + if shutil.which("xdg-open"): + register(XDGViewer) + if shutil.which("display"): + register(DisplayViewer) + if shutil.which("gm"): + register(GmDisplayViewer) + if shutil.which("eog"): + register(EogViewer) + if shutil.which("xv"): + register(XVViewer) + + +class IPythonViewer(Viewer): + """The viewer for IPython frontends.""" + + def show_image(self, image: Image.Image, **options: Any) -> int: + ipython_display(image) + return 1 + + +try: + from IPython.display import display as ipython_display +except ImportError: + pass +else: + register(IPythonViewer) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Syntax: python3 ImageShow.py imagefile [title]") + sys.exit() + + with Image.open(sys.argv[1]) as im: + print(show(im, *sys.argv[2:])) diff --git a/venv/Lib/site-packages/PIL/ImageStat.py b/venv/Lib/site-packages/PIL/ImageStat.py new file mode 100644 index 00000000..8bc50452 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageStat.py @@ -0,0 +1,160 @@ +# +# The Python Imaging Library. +# $Id$ +# +# global image statistics +# +# History: +# 1996-04-05 fl Created +# 1997-05-21 fl Added mask; added rms, var, stddev attributes +# 1997-08-05 fl Added median +# 1998-07-05 hk Fixed integer overflow error +# +# Notes: +# This class shows how to implement delayed evaluation of attributes. +# To get a certain value, simply access the corresponding attribute. +# The __getattr__ dispatcher takes care of the rest. +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996-97. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import math +from functools import cached_property + +from . import Image + + +class Stat: + def __init__( + self, image_or_list: Image.Image | list[int], mask: Image.Image | None = None + ) -> None: + """ + Calculate statistics for the given image. If a mask is included, + only the regions covered by that mask are included in the + statistics. You can also pass in a previously calculated histogram. + + :param image: A PIL image, or a precalculated histogram. + + .. note:: + + For a PIL image, calculations rely on the + :py:meth:`~PIL.Image.Image.histogram` method. The pixel counts are + grouped into 256 bins, even if the image has more than 8 bits per + channel. So ``I`` and ``F`` mode images have a maximum ``mean``, + ``median`` and ``rms`` of 255, and cannot have an ``extrema`` maximum + of more than 255. + + :param mask: An optional mask. + """ + if isinstance(image_or_list, Image.Image): + self.h = image_or_list.histogram(mask) + elif isinstance(image_or_list, list): + self.h = image_or_list + else: + msg = "first argument must be image or list" # type: ignore[unreachable] + raise TypeError(msg) + self.bands = list(range(len(self.h) // 256)) + + @cached_property + def extrema(self) -> list[tuple[int, int]]: + """ + Min/max values for each band in the image. + + .. note:: + This relies on the :py:meth:`~PIL.Image.Image.histogram` method, and + simply returns the low and high bins used. This is correct for + images with 8 bits per channel, but fails for other modes such as + ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.Image.getextrema` to + return per-band extrema for the image. This is more correct and + efficient because, for non-8-bit modes, the histogram method uses + :py:meth:`~PIL.Image.Image.getextrema` to determine the bins used. + """ + + def minmax(histogram: list[int]) -> tuple[int, int]: + res_min, res_max = 255, 0 + for i in range(256): + if histogram[i]: + res_min = i + break + for i in range(255, -1, -1): + if histogram[i]: + res_max = i + break + return res_min, res_max + + return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)] + + @cached_property + def count(self) -> list[int]: + """Total number of pixels for each band in the image.""" + return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)] + + @cached_property + def sum(self) -> list[float]: + """Sum of all pixels for each band in the image.""" + + v = [] + for i in range(0, len(self.h), 256): + layer_sum = 0.0 + for j in range(256): + layer_sum += j * self.h[i + j] + v.append(layer_sum) + return v + + @cached_property + def sum2(self) -> list[float]: + """Squared sum of all pixels for each band in the image.""" + + v = [] + for i in range(0, len(self.h), 256): + sum2 = 0.0 + for j in range(256): + sum2 += (j**2) * float(self.h[i + j]) + v.append(sum2) + return v + + @cached_property + def mean(self) -> list[float]: + """Average (arithmetic mean) pixel level for each band in the image.""" + return [self.sum[i] / self.count[i] for i in self.bands] + + @cached_property + def median(self) -> list[int]: + """Median pixel level for each band in the image.""" + + v = [] + for i in self.bands: + s = 0 + half = self.count[i] // 2 + b = i * 256 + for j in range(256): + s = s + self.h[b + j] + if s > half: + break + v.append(j) + return v + + @cached_property + def rms(self) -> list[float]: + """RMS (root-mean-square) for each band in the image.""" + return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands] + + @cached_property + def var(self) -> list[float]: + """Variance for each band in the image.""" + return [ + (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i] + for i in self.bands + ] + + @cached_property + def stddev(self) -> list[float]: + """Standard deviation for each band in the image.""" + return [math.sqrt(self.var[i]) for i in self.bands] + + +Global = Stat # compatibility diff --git a/venv/Lib/site-packages/PIL/ImageTk.py b/venv/Lib/site-packages/PIL/ImageTk.py new file mode 100644 index 00000000..3a4cb81e --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageTk.py @@ -0,0 +1,266 @@ +# +# The Python Imaging Library. +# $Id$ +# +# a Tk display interface +# +# History: +# 96-04-08 fl Created +# 96-09-06 fl Added getimage method +# 96-11-01 fl Rewritten, removed image attribute and crop method +# 97-05-09 fl Use PyImagingPaste method instead of image type +# 97-05-12 fl Minor tweaks to match the IFUNC95 interface +# 97-05-17 fl Support the "pilbitmap" booster patch +# 97-06-05 fl Added file= and data= argument to image constructors +# 98-03-09 fl Added width and height methods to Image classes +# 98-07-02 fl Use default mode for "P" images without palette attribute +# 98-07-02 fl Explicitly destroy Tkinter image objects +# 99-07-24 fl Support multiple Tk interpreters (from Greg Couch) +# 99-07-26 fl Automatically hook into Tkinter (if possible) +# 99-08-15 fl Hook uses _imagingtk instead of _imaging +# +# Copyright (c) 1997-1999 by Secret Labs AB +# Copyright (c) 1996-1997 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import tkinter +from io import BytesIO +from typing import Any + +from . import Image, ImageFile + +TYPE_CHECKING = False +if TYPE_CHECKING: + from ._typing import CapsuleType + +# -------------------------------------------------------------------- +# Check for Tkinter interface hooks + + +def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: + source = None + if "file" in kw: + source = kw.pop("file") + elif "data" in kw: + source = BytesIO(kw.pop("data")) + if not source: + return None + return Image.open(source) + + +def _pyimagingtkcall( + command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: CapsuleType +) -> None: + tk = photo.tk + try: + tk.call(command, photo, repr(ptr)) + except tkinter.TclError: + # activate Tkinter hook + # may raise an error if it cannot attach to Tkinter + from . import _imagingtk + + _imagingtk.tkinit(tk.interpaddr()) + tk.call(command, photo, repr(ptr)) + + +# -------------------------------------------------------------------- +# PhotoImage + + +class PhotoImage: + """ + A Tkinter-compatible photo image. This can be used + everywhere Tkinter expects an image object. If the image is an RGBA + image, pixels having alpha 0 are treated as transparent. + + The constructor takes either a PIL image, or a mode and a size. + Alternatively, you can use the ``file`` or ``data`` options to initialize + the photo image object. + + :param image: Either a PIL image, or a mode string. If a mode string is + used, a size must also be given. + :param size: If the first argument is a mode string, this defines the size + of the image. + :keyword file: A filename to load the image from (using + ``Image.open(file)``). + :keyword data: An 8-bit string containing image data (as loaded from an + image file). + """ + + def __init__( + self, + image: Image.Image | str | None = None, + size: tuple[int, int] | None = None, + **kw: Any, + ) -> None: + # Tk compatibility: file or data + if image is None: + image = _get_image_from_kw(kw) + + if image is None: + msg = "Image is required" + raise ValueError(msg) + elif isinstance(image, str): + mode = image + image = None + + if size is None: + msg = "If first argument is mode, size is required" + raise ValueError(msg) + else: + # got an image instead of a mode + mode = image.mode + if mode == "P": + # palette mapped data + image.apply_transparency() + image.load() + mode = image.palette.mode if image.palette else "RGB" + size = image.size + kw["width"], kw["height"] = size + + if mode not in ["1", "L", "RGB", "RGBA"]: + mode = Image.getmodebase(mode) + + self.__mode = mode + self.__size = size + self.__photo = tkinter.PhotoImage(**kw) + self.tk = self.__photo.tk + if image: + self.paste(image) + + def __del__(self) -> None: + try: + name = self.__photo.name + except AttributeError: + return + self.__photo.name = None + try: + self.__photo.tk.call("image", "delete", name) + except Exception: + pass # ignore internal errors + + def __str__(self) -> str: + """ + Get the Tkinter photo image identifier. This method is automatically + called by Tkinter whenever a PhotoImage object is passed to a Tkinter + method. + + :return: A Tkinter photo image identifier (a string). + """ + return str(self.__photo) + + def width(self) -> int: + """ + Get the width of the image. + + :return: The width, in pixels. + """ + return self.__size[0] + + def height(self) -> int: + """ + Get the height of the image. + + :return: The height, in pixels. + """ + return self.__size[1] + + def paste(self, im: Image.Image) -> None: + """ + Paste a PIL image into the photo image. Note that this can + be very slow if the photo image is displayed. + + :param im: A PIL image. The size must match the target region. If the + mode does not match, the image is converted to the mode of + the bitmap image. + """ + # convert to blittable + ptr = im.getim() + image = im.im + if not image.isblock() or im.mode != self.__mode: + block = Image.core.new_block(self.__mode, im.size) + image.convert2(block, image) # convert directly between buffers + ptr = block.ptr + + _pyimagingtkcall("PyImagingPhoto", self.__photo, ptr) + + +# -------------------------------------------------------------------- +# BitmapImage + + +class BitmapImage: + """ + A Tkinter-compatible bitmap image. This can be used everywhere Tkinter + expects an image object. + + The given image must have mode "1". Pixels having value 0 are treated as + transparent. Options, if any, are passed on to Tkinter. The most commonly + used option is ``foreground``, which is used to specify the color for the + non-transparent parts. See the Tkinter documentation for information on + how to specify colours. + + :param image: A PIL image. + """ + + def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: + # Tk compatibility: file or data + if image is None: + image = _get_image_from_kw(kw) + + if image is None: + msg = "Image is required" + raise ValueError(msg) + self.__mode = image.mode + self.__size = image.size + + self.__photo = tkinter.BitmapImage(data=image.tobitmap(), **kw) + + def __del__(self) -> None: + try: + name = self.__photo.name + except AttributeError: + return + self.__photo.name = None + try: + self.__photo.tk.call("image", "delete", name) + except Exception: + pass # ignore internal errors + + def width(self) -> int: + """ + Get the width of the image. + + :return: The width, in pixels. + """ + return self.__size[0] + + def height(self) -> int: + """ + Get the height of the image. + + :return: The height, in pixels. + """ + return self.__size[1] + + def __str__(self) -> str: + """ + Get the Tkinter bitmap image identifier. This method is automatically + called by Tkinter whenever a BitmapImage object is passed to a Tkinter + method. + + :return: A Tkinter bitmap image identifier (a string). + """ + return str(self.__photo) + + +def getimage(photo: PhotoImage) -> Image.Image: + """Copies the contents of a PhotoImage to a PIL image memory.""" + im = Image.new("RGBA", (photo.width(), photo.height())) + + _pyimagingtkcall("PyImagingPhotoGet", photo, im.getim()) + + return im diff --git a/venv/Lib/site-packages/PIL/ImageTransform.py b/venv/Lib/site-packages/PIL/ImageTransform.py new file mode 100644 index 00000000..fb144ff3 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageTransform.py @@ -0,0 +1,136 @@ +# +# The Python Imaging Library. +# $Id$ +# +# transform wrappers +# +# History: +# 2002-04-08 fl Created +# +# Copyright (c) 2002 by Secret Labs AB +# Copyright (c) 2002 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from collections.abc import Sequence +from typing import Any + +from . import Image + + +class Transform(Image.ImageTransformHandler): + """Base class for other transforms defined in :py:mod:`~PIL.ImageTransform`.""" + + method: Image.Transform + + def __init__(self, data: Sequence[Any]) -> None: + self.data = data + + def getdata(self) -> tuple[Image.Transform, Sequence[int]]: + return self.method, self.data + + def transform( + self, + size: tuple[int, int], + image: Image.Image, + **options: Any, + ) -> Image.Image: + """Perform the transform. Called from :py:meth:`.Image.transform`.""" + # can be overridden + method, data = self.getdata() + return image.transform(size, method, data, **options) + + +class AffineTransform(Transform): + """ + Define an affine image transform. + + This function takes a 6-tuple (a, b, c, d, e, f) which contain the first + two rows from the inverse of an affine transform matrix. For each pixel + (x, y) in the output image, the new value is taken from a position (a x + + b y + c, d x + e y + f) in the input image, rounded to nearest pixel. + + This function can be used to scale, translate, rotate, and shear the + original image. + + See :py:meth:`.Image.transform` + + :param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows + from the inverse of an affine transform matrix. + """ + + method = Image.Transform.AFFINE + + +class PerspectiveTransform(Transform): + """ + Define a perspective image transform. + + This function takes an 8-tuple (a, b, c, d, e, f, g, h). For each pixel + (x, y) in the output image, the new value is taken from a position + ((a x + b y + c) / (g x + h y + 1), (d x + e y + f) / (g x + h y + 1)) in + the input image, rounded to nearest pixel. + + This function can be used to scale, translate, rotate, and shear the + original image. + + See :py:meth:`.Image.transform` + + :param matrix: An 8-tuple (a, b, c, d, e, f, g, h). + """ + + method = Image.Transform.PERSPECTIVE + + +class ExtentTransform(Transform): + """ + Define a transform to extract a subregion from an image. + + Maps a rectangle (defined by two corners) from the image to a rectangle of + the given size. The resulting image will contain data sampled from between + the corners, such that (x0, y0) in the input image will end up at (0,0) in + the output image, and (x1, y1) at size. + + This method can be used to crop, stretch, shrink, or mirror an arbitrary + rectangle in the current image. It is slightly slower than crop, but about + as fast as a corresponding resize operation. + + See :py:meth:`.Image.transform` + + :param bbox: A 4-tuple (x0, y0, x1, y1) which specifies two points in the + input image's coordinate system. See :ref:`coordinate-system`. + """ + + method = Image.Transform.EXTENT + + +class QuadTransform(Transform): + """ + Define a quad image transform. + + Maps a quadrilateral (a region defined by four corners) from the image to a + rectangle of the given size. + + See :py:meth:`.Image.transform` + + :param xy: An 8-tuple (x0, y0, x1, y1, x2, y2, x3, y3) which contain the + upper left, lower left, lower right, and upper right corner of the + source quadrilateral. + """ + + method = Image.Transform.QUAD + + +class MeshTransform(Transform): + """ + Define a mesh image transform. A mesh transform consists of one or more + individual quad transforms. + + See :py:meth:`.Image.transform` + + :param data: A list of (bbox, quad) tuples. + """ + + method = Image.Transform.MESH diff --git a/venv/Lib/site-packages/PIL/ImageWin.py b/venv/Lib/site-packages/PIL/ImageWin.py new file mode 100644 index 00000000..98c28f29 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImageWin.py @@ -0,0 +1,247 @@ +# +# The Python Imaging Library. +# $Id$ +# +# a Windows DIB display interface +# +# History: +# 1996-05-20 fl Created +# 1996-09-20 fl Fixed subregion exposure +# 1997-09-21 fl Added draw primitive (for tzPrint) +# 2003-05-21 fl Added experimental Window/ImageWindow classes +# 2003-09-05 fl Added fromstring/tostring methods +# +# Copyright (c) Secret Labs AB 1997-2003. +# Copyright (c) Fredrik Lundh 1996-2003. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image + + +class HDC: + """ + Wraps an HDC integer. The resulting object can be passed to the + :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` + methods. + """ + + def __init__(self, dc: int) -> None: + self.dc = dc + + def __int__(self) -> int: + return self.dc + + +class HWND: + """ + Wraps an HWND integer. The resulting object can be passed to the + :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose` + methods, instead of a DC. + """ + + def __init__(self, wnd: int) -> None: + self.wnd = wnd + + def __int__(self) -> int: + return self.wnd + + +class Dib: + """ + A Windows bitmap with the given mode and size. The mode can be one of "1", + "L", "P", or "RGB". + + If the display requires a palette, this constructor creates a suitable + palette and associates it with the image. For an "L" image, 128 graylevels + are allocated. For an "RGB" image, a 6x6x6 colour cube is used, together + with 20 graylevels. + + To make sure that palettes work properly under Windows, you must call the + ``palette`` method upon certain events from Windows. + + :param image: Either a PIL image, or a mode string. If a mode string is + used, a size must also be given. The mode can be one of "1", + "L", "P", or "RGB". + :param size: If the first argument is a mode string, this + defines the size of the image. + """ + + def __init__( + self, image: Image.Image | str, size: tuple[int, int] | None = None + ) -> None: + if isinstance(image, str): + mode = image + image = "" + if size is None: + msg = "If first argument is mode, size is required" + raise ValueError(msg) + else: + mode = image.mode + size = image.size + if mode not in ["1", "L", "P", "RGB"]: + mode = Image.getmodebase(mode) + self.image = Image.core.display(mode, size) + self.mode = mode + self.size = size + if image: + assert not isinstance(image, str) + self.paste(image) + + def expose(self, handle: int | HDC | HWND) -> None: + """ + Copy the bitmap contents to a device context. + + :param handle: Device context (HDC), cast to a Python integer, or an + HDC or HWND instance. In PythonWin, you can use + ``CDC.GetHandleAttrib()`` to get a suitable handle. + """ + handle_int = int(handle) + if isinstance(handle, HWND): + dc = self.image.getdc(handle_int) + try: + self.image.expose(dc) + finally: + self.image.releasedc(handle_int, dc) + else: + self.image.expose(handle_int) + + def draw( + self, + handle: int | HDC | HWND, + dst: tuple[int, int, int, int], + src: tuple[int, int, int, int] | None = None, + ) -> None: + """ + Same as expose, but allows you to specify where to draw the image, and + what part of it to draw. + + The destination and source areas are given as 4-tuple rectangles. If + the source is omitted, the entire image is copied. If the source and + the destination have different sizes, the image is resized as + necessary. + """ + if src is None: + src = (0, 0) + self.size + handle_int = int(handle) + if isinstance(handle, HWND): + dc = self.image.getdc(handle_int) + try: + self.image.draw(dc, dst, src) + finally: + self.image.releasedc(handle_int, dc) + else: + self.image.draw(handle_int, dst, src) + + def query_palette(self, handle: int | HDC | HWND) -> int: + """ + Installs the palette associated with the image in the given device + context. + + This method should be called upon **QUERYNEWPALETTE** and + **PALETTECHANGED** events from Windows. If this method returns a + non-zero value, one or more display palette entries were changed, and + the image should be redrawn. + + :param handle: Device context (HDC), cast to a Python integer, or an + HDC or HWND instance. + :return: The number of entries that were changed (if one or more entries, + this indicates that the image should be redrawn). + """ + handle_int = int(handle) + if isinstance(handle, HWND): + handle = self.image.getdc(handle_int) + try: + result = self.image.query_palette(handle) + finally: + self.image.releasedc(handle, handle) + else: + result = self.image.query_palette(handle_int) + return result + + def paste( + self, im: Image.Image, box: tuple[int, int, int, int] | None = None + ) -> None: + """ + Paste a PIL image into the bitmap image. + + :param im: A PIL image. The size must match the target region. + If the mode does not match, the image is converted to the + mode of the bitmap image. + :param box: A 4-tuple defining the left, upper, right, and + lower pixel coordinate. See :ref:`coordinate-system`. If + None is given instead of a tuple, all of the image is + assumed. + """ + im.load() + if self.mode != im.mode: + im = im.convert(self.mode) + if box: + self.image.paste(im.im, box) + else: + self.image.paste(im.im) + + def frombytes(self, buffer: bytes) -> None: + """ + Load display memory contents from byte data. + + :param buffer: A buffer containing display data (usually + data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`) + """ + self.image.frombytes(buffer) + + def tobytes(self) -> bytes: + """ + Copy display memory contents to bytes object. + + :return: A bytes object containing display data. + """ + return self.image.tobytes() + + +class Window: + """Create a Window with the given title size.""" + + def __init__( + self, title: str = "PIL", width: int | None = None, height: int | None = None + ) -> None: + self.hwnd = Image.core.createwindow( + title, self.__dispatcher, width or 0, height or 0 + ) + + def __dispatcher(self, action: str, *args: int) -> None: + getattr(self, f"ui_handle_{action}")(*args) + + def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None: + pass + + def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None: + pass + + def ui_handle_destroy(self) -> None: + pass + + def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None: + pass + + def ui_handle_resize(self, width: int, height: int) -> None: + pass + + def mainloop(self) -> None: + Image.core.eventloop() + + +class ImageWindow(Window): + """Create an image window which displays the given image.""" + + def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None: + if not isinstance(image, Dib): + image = Dib(image) + self.image = image + width, height = image.size + super().__init__(title, width=width, height=height) + + def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None: + self.image.draw(dc, (x0, y0, x1, y1)) diff --git a/venv/Lib/site-packages/PIL/ImtImagePlugin.py b/venv/Lib/site-packages/PIL/ImtImagePlugin.py new file mode 100644 index 00000000..c4eccee3 --- /dev/null +++ b/venv/Lib/site-packages/PIL/ImtImagePlugin.py @@ -0,0 +1,103 @@ +# +# The Python Imaging Library. +# $Id$ +# +# IM Tools support for PIL +# +# history: +# 1996-05-27 fl Created (read 8-bit images only) +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.2) +# +# Copyright (c) Secret Labs AB 1997-2001. +# Copyright (c) Fredrik Lundh 1996-2001. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re + +from . import Image, ImageFile + +# +# -------------------------------------------------------------------- + +field = re.compile(rb"([a-z]*) ([^ \r\n]*)") + + +## +# Image plugin for IM Tools images. + + +class ImtImageFile(ImageFile.ImageFile): + format = "IMT" + format_description = "IM Tools" + + def _open(self) -> None: + # Quick rejection: if there's not a LF among the first + # 100 bytes, this is (probably) not a text header. + + assert self.fp is not None + + buffer = self.fp.read(100) + if b"\n" not in buffer: + msg = "not an IM file" + raise SyntaxError(msg) + + xsize = ysize = 0 + + while True: + if buffer: + s = buffer[:1] + buffer = buffer[1:] + else: + s = self.fp.read(1) + if not s: + break + + if s == b"\x0c": + # image data begins + self.tile = [ + ImageFile._Tile( + "raw", + (0, 0) + self.size, + self.fp.tell() - len(buffer), + self.mode, + ) + ] + + break + + else: + # read key/value pair + if b"\n" not in buffer: + buffer += self.fp.read(100) + lines = buffer.split(b"\n") + s += lines.pop(0) + buffer = b"\n".join(lines) + if len(s) == 1 or len(s) > 100: + break + if s[0] == ord(b"*"): + continue # comment + + m = field.match(s) + if not m: + break + k, v = m.group(1, 2) + if k == b"width": + xsize = int(v) + self._size = xsize, ysize + elif k == b"height": + ysize = int(v) + self._size = xsize, ysize + elif k == b"pixel" and v == b"n8": + self._mode = "L" + + +# +# -------------------------------------------------------------------- + +Image.register_open(ImtImageFile.format, ImtImageFile) + +# +# no extension registered (".im" is simply too common) diff --git a/venv/Lib/site-packages/PIL/IptcImagePlugin.py b/venv/Lib/site-packages/PIL/IptcImagePlugin.py new file mode 100644 index 00000000..fc024d66 --- /dev/null +++ b/venv/Lib/site-packages/PIL/IptcImagePlugin.py @@ -0,0 +1,250 @@ +# +# The Python Imaging Library. +# $Id$ +# +# IPTC/NAA file handling +# +# history: +# 1995-10-01 fl Created +# 1998-03-09 fl Cleaned up and added to PIL +# 2002-06-18 fl Added getiptcinfo helper +# +# Copyright (c) Secret Labs AB 1997-2002. +# Copyright (c) Fredrik Lundh 1995. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from collections.abc import Sequence +from io import BytesIO +from typing import cast + +from . import Image, ImageFile +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._deprecate import deprecate + +COMPRESSION = {1: "raw", 5: "jpeg"} + + +def __getattr__(name: str) -> bytes: + if name == "PAD": + deprecate("IptcImagePlugin.PAD", 12) + return b"\0\0\0\0" + msg = f"module '{__name__}' has no attribute '{name}'" + raise AttributeError(msg) + + +# +# Helpers + + +def _i(c: bytes) -> int: + return i32((b"\0\0\0\0" + c)[-4:]) + + +def _i8(c: int | bytes) -> int: + return c if isinstance(c, int) else c[0] + + +def i(c: bytes) -> int: + """.. deprecated:: 10.2.0""" + deprecate("IptcImagePlugin.i", 12) + return _i(c) + + +def dump(c: Sequence[int | bytes]) -> None: + """.. deprecated:: 10.2.0""" + deprecate("IptcImagePlugin.dump", 12) + for i in c: + print(f"{_i8(i):02x}", end=" ") + print() + + +## +# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields +# from TIFF and JPEG files, use the getiptcinfo function. + + +class IptcImageFile(ImageFile.ImageFile): + format = "IPTC" + format_description = "IPTC/NAA" + + def getint(self, key: tuple[int, int]) -> int: + return _i(self.info[key]) + + def field(self) -> tuple[tuple[int, int] | None, int]: + # + # get a IPTC field header + s = self.fp.read(5) + if not s.strip(b"\x00"): + return None, 0 + + tag = s[1], s[2] + + # syntax + if s[0] != 0x1C or tag[0] not in [1, 2, 3, 4, 5, 6, 7, 8, 9, 240]: + msg = "invalid IPTC/NAA file" + raise SyntaxError(msg) + + # field size + size = s[3] + if size > 132: + msg = "illegal field length in IPTC/NAA file" + raise OSError(msg) + elif size == 128: + size = 0 + elif size > 128: + size = _i(self.fp.read(size - 128)) + else: + size = i16(s, 3) + + return tag, size + + def _open(self) -> None: + # load descriptive fields + while True: + offset = self.fp.tell() + tag, size = self.field() + if not tag or tag == (8, 10): + break + if size: + tagdata = self.fp.read(size) + else: + tagdata = None + if tag in self.info: + if isinstance(self.info[tag], list): + self.info[tag].append(tagdata) + else: + self.info[tag] = [self.info[tag], tagdata] + else: + self.info[tag] = tagdata + + # mode + layers = self.info[(3, 60)][0] + component = self.info[(3, 60)][1] + if (3, 65) in self.info: + id = self.info[(3, 65)][0] - 1 + else: + id = 0 + if layers == 1 and not component: + self._mode = "L" + elif layers == 3 and component: + self._mode = "RGB"[id] + elif layers == 4 and component: + self._mode = "CMYK"[id] + + # size + self._size = self.getint((3, 20)), self.getint((3, 30)) + + # compression + try: + compression = COMPRESSION[self.getint((3, 120))] + except KeyError as e: + msg = "Unknown IPTC image compression" + raise OSError(msg) from e + + # tile + if tag == (8, 10): + self.tile = [ + ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression) + ] + + def load(self) -> Image.core.PixelAccess | None: + if len(self.tile) != 1 or self.tile[0][0] != "iptc": + return ImageFile.ImageFile.load(self) + + offset, compression = self.tile[0][2:] + + self.fp.seek(offset) + + # Copy image data to temporary file + o = BytesIO() + if compression == "raw": + # To simplify access to the extracted file, + # prepend a PPM header + o.write(b"P5\n%d %d\n255\n" % self.size) + while True: + type, size = self.field() + if type != (8, 10): + break + while size > 0: + s = self.fp.read(min(size, 8192)) + if not s: + break + o.write(s) + size -= len(s) + + with Image.open(o) as _im: + _im.load() + self.im = _im.im + self.tile = [] + return Image.Image.load(self) + + +Image.register_open(IptcImageFile.format, IptcImageFile) + +Image.register_extension(IptcImageFile.format, ".iim") + + +def getiptcinfo( + im: ImageFile.ImageFile, +) -> dict[tuple[int, int], bytes | list[bytes]] | None: + """ + Get IPTC information from TIFF, JPEG, or IPTC file. + + :param im: An image containing IPTC data. + :returns: A dictionary containing IPTC information, or None if + no IPTC information block was found. + """ + from . import JpegImagePlugin, TiffImagePlugin + + data = None + + info: dict[tuple[int, int], bytes | list[bytes]] = {} + if isinstance(im, IptcImageFile): + # return info dictionary right away + for k, v in im.info.items(): + if isinstance(k, tuple): + info[k] = v + return info + + elif isinstance(im, JpegImagePlugin.JpegImageFile): + # extract the IPTC/NAA resource + photoshop = im.info.get("photoshop") + if photoshop: + data = photoshop.get(0x0404) + + elif isinstance(im, TiffImagePlugin.TiffImageFile): + # get raw data from the IPTC/NAA tag (PhotoShop tags the data + # as 4-byte integers, so we cannot use the get method...) + try: + data = im.tag_v2._tagdata[TiffImagePlugin.IPTC_NAA_CHUNK] + except KeyError: + pass + + if data is None: + return None # no properties + + # create an IptcImagePlugin object without initializing it + class FakeImage: + pass + + fake_im = FakeImage() + fake_im.__class__ = IptcImageFile # type: ignore[assignment] + iptc_im = cast(IptcImageFile, fake_im) + + # parse the IPTC information chunk + iptc_im.info = {} + iptc_im.fp = BytesIO(data) + + try: + iptc_im._open() + except (IndexError, KeyError): + pass # expected failure + + for k, v in iptc_im.info.items(): + if isinstance(k, tuple): + info[k] = v + return info diff --git a/venv/Lib/site-packages/PIL/Jpeg2KImagePlugin.py b/venv/Lib/site-packages/PIL/Jpeg2KImagePlugin.py new file mode 100644 index 00000000..e0f4ecae --- /dev/null +++ b/venv/Lib/site-packages/PIL/Jpeg2KImagePlugin.py @@ -0,0 +1,442 @@ +# +# The Python Imaging Library +# $Id$ +# +# JPEG2000 file handling +# +# History: +# 2014-03-12 ajh Created +# 2021-06-30 rogermb Extract dpi information from the 'resc' header box +# +# Copyright (c) 2014 Coriolis Systems Limited +# Copyright (c) 2014 Alastair Houghton +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import os +import struct +from collections.abc import Callable +from typing import IO, cast + +from . import Image, ImageFile, ImagePalette, _binary + + +class BoxReader: + """ + A small helper class to read fields stored in JPEG2000 header boxes + and to easily step into and read sub-boxes. + """ + + def __init__(self, fp: IO[bytes], length: int = -1) -> None: + self.fp = fp + self.has_length = length >= 0 + self.length = length + self.remaining_in_box = -1 + + def _can_read(self, num_bytes: int) -> bool: + if self.has_length and self.fp.tell() + num_bytes > self.length: + # Outside box: ensure we don't read past the known file length + return False + if self.remaining_in_box >= 0: + # Inside box contents: ensure read does not go past box boundaries + return num_bytes <= self.remaining_in_box + else: + return True # No length known, just read + + def _read_bytes(self, num_bytes: int) -> bytes: + if not self._can_read(num_bytes): + msg = "Not enough data in header" + raise SyntaxError(msg) + + data = self.fp.read(num_bytes) + if len(data) < num_bytes: + msg = f"Expected to read {num_bytes} bytes but only got {len(data)}." + raise OSError(msg) + + if self.remaining_in_box > 0: + self.remaining_in_box -= num_bytes + return data + + def read_fields(self, field_format: str) -> tuple[int | bytes, ...]: + size = struct.calcsize(field_format) + data = self._read_bytes(size) + return struct.unpack(field_format, data) + + def read_boxes(self) -> BoxReader: + size = self.remaining_in_box + data = self._read_bytes(size) + return BoxReader(io.BytesIO(data), size) + + def has_next_box(self) -> bool: + if self.has_length: + return self.fp.tell() + self.remaining_in_box < self.length + else: + return True + + def next_box_type(self) -> bytes: + # Skip the rest of the box if it has not been read + if self.remaining_in_box > 0: + self.fp.seek(self.remaining_in_box, os.SEEK_CUR) + self.remaining_in_box = -1 + + # Read the length and type of the next box + lbox, tbox = cast(tuple[int, bytes], self.read_fields(">I4s")) + if lbox == 1: + lbox = cast(int, self.read_fields(">Q")[0]) + hlen = 16 + else: + hlen = 8 + + if lbox < hlen or not self._can_read(lbox - hlen): + msg = "Invalid header length" + raise SyntaxError(msg) + + self.remaining_in_box = lbox - hlen + return tbox + + +def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]: + """Parse the JPEG 2000 codestream to extract the size and component + count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" + + hdr = fp.read(2) + lsiz = _binary.i16be(hdr) + siz = hdr + fp.read(lsiz - 2) + lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from( + ">HHIIIIIIIIH", siz + ) + + size = (xsiz - xosiz, ysiz - yosiz) + if csiz == 1: + ssiz = struct.unpack_from(">B", siz, 38) + if (ssiz[0] & 0x7F) + 1 > 8: + mode = "I;16" + else: + mode = "L" + elif csiz == 2: + mode = "LA" + elif csiz == 3: + mode = "RGB" + elif csiz == 4: + mode = "RGBA" + else: + msg = "unable to determine J2K image mode" + raise SyntaxError(msg) + + return size, mode + + +def _res_to_dpi(num: int, denom: int, exp: int) -> float | None: + """Convert JPEG2000's (numerator, denominator, exponent-base-10) resolution, + calculated as (num / denom) * 10^exp and stored in dots per meter, + to floating-point dots per inch.""" + if denom == 0: + return None + return (254 * num * (10**exp)) / (10000 * denom) + + +def _parse_jp2_header( + fp: IO[bytes], +) -> tuple[ + tuple[int, int], + str, + str | None, + tuple[float, float] | None, + ImagePalette.ImagePalette | None, +]: + """Parse the JP2 header box to extract size, component count, + color space information, and optionally DPI information, + returning a (size, mode, mimetype, dpi) tuple.""" + + # Find the JP2 header box + reader = BoxReader(fp) + header = None + mimetype = None + while reader.has_next_box(): + tbox = reader.next_box_type() + + if tbox == b"jp2h": + header = reader.read_boxes() + break + elif tbox == b"ftyp": + if reader.read_fields(">4s")[0] == b"jpx ": + mimetype = "image/jpx" + assert header is not None + + size = None + mode = None + bpc = None + nc = None + dpi = None # 2-tuple of DPI info, or None + palette = None + + while header.has_next_box(): + tbox = header.next_box_type() + + if tbox == b"ihdr": + height, width, nc, bpc = header.read_fields(">IIHB") + assert isinstance(height, int) + assert isinstance(width, int) + assert isinstance(bpc, int) + size = (width, height) + if nc == 1 and (bpc & 0x7F) > 8: + mode = "I;16" + elif nc == 1: + mode = "L" + elif nc == 2: + mode = "LA" + elif nc == 3: + mode = "RGB" + elif nc == 4: + mode = "RGBA" + elif tbox == b"colr" and nc == 4: + meth, _, _, enumcs = header.read_fields(">BBBI") + if meth == 1 and enumcs == 12: + mode = "CMYK" + elif tbox == b"pclr" and mode in ("L", "LA"): + ne, npc = header.read_fields(">HB") + assert isinstance(ne, int) + assert isinstance(npc, int) + max_bitdepth = 0 + for bitdepth in header.read_fields(">" + ("B" * npc)): + assert isinstance(bitdepth, int) + if bitdepth > max_bitdepth: + max_bitdepth = bitdepth + if max_bitdepth <= 8: + palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB") + for i in range(ne): + color: list[int] = [] + for value in header.read_fields(">" + ("B" * npc)): + assert isinstance(value, int) + color.append(value) + palette.getcolor(tuple(color)) + mode = "P" if mode == "L" else "PA" + elif tbox == b"res ": + res = header.read_boxes() + while res.has_next_box(): + tres = res.next_box_type() + if tres == b"resc": + vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB") + assert isinstance(vrcn, int) + assert isinstance(vrcd, int) + assert isinstance(hrcn, int) + assert isinstance(hrcd, int) + assert isinstance(vrce, int) + assert isinstance(hrce, int) + hres = _res_to_dpi(hrcn, hrcd, hrce) + vres = _res_to_dpi(vrcn, vrcd, vrce) + if hres is not None and vres is not None: + dpi = (hres, vres) + break + + if size is None or mode is None: + msg = "Malformed JP2 header" + raise SyntaxError(msg) + + return size, mode, mimetype, dpi, palette + + +## +# Image plugin for JPEG2000 images. + + +class Jpeg2KImageFile(ImageFile.ImageFile): + format = "JPEG2000" + format_description = "JPEG 2000 (ISO 15444)" + + def _open(self) -> None: + sig = self.fp.read(4) + if sig == b"\xff\x4f\xff\x51": + self.codec = "j2k" + self._size, self._mode = _parse_codestream(self.fp) + self._parse_comment() + else: + sig = sig + self.fp.read(8) + + if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a": + self.codec = "jp2" + header = _parse_jp2_header(self.fp) + self._size, self._mode, self.custom_mimetype, dpi, self.palette = header + if dpi is not None: + self.info["dpi"] = dpi + if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): + hdr = self.fp.read(2) + length = _binary.i16be(hdr) + self.fp.seek(length - 2, os.SEEK_CUR) + self._parse_comment() + else: + msg = "not a JPEG 2000 file" + raise SyntaxError(msg) + + self._reduce = 0 + self.layers = 0 + + fd = -1 + length = -1 + + try: + fd = self.fp.fileno() + length = os.fstat(fd).st_size + except Exception: + fd = -1 + try: + pos = self.fp.tell() + self.fp.seek(0, io.SEEK_END) + length = self.fp.tell() + self.fp.seek(pos) + except Exception: + length = -1 + + self.tile = [ + ImageFile._Tile( + "jpeg2k", + (0, 0) + self.size, + 0, + (self.codec, self._reduce, self.layers, fd, length), + ) + ] + + def _parse_comment(self) -> None: + while True: + marker = self.fp.read(2) + if not marker: + break + typ = marker[1] + if typ in (0x90, 0xD9): + # Start of tile or end of codestream + break + hdr = self.fp.read(2) + length = _binary.i16be(hdr) + if typ == 0x64: + # Comment + self.info["comment"] = self.fp.read(length - 2)[2:] + break + else: + self.fp.seek(length - 2, os.SEEK_CUR) + + @property # type: ignore[override] + def reduce( + self, + ) -> ( + Callable[[int | tuple[int, int], tuple[int, int, int, int] | None], Image.Image] + | int + ): + # https://github.com/python-pillow/Pillow/issues/4343 found that the + # new Image 'reduce' method was shadowed by this plugin's 'reduce' + # property. This attempts to allow for both scenarios + return self._reduce or super().reduce + + @reduce.setter + def reduce(self, value: int) -> None: + self._reduce = value + + def load(self) -> Image.core.PixelAccess | None: + if self.tile and self._reduce: + power = 1 << self._reduce + adjust = power >> 1 + self._size = ( + int((self.size[0] + adjust) / power), + int((self.size[1] + adjust) / power), + ) + + # Update the reduce and layers settings + t = self.tile[0] + assert isinstance(t[3], tuple) + t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4]) + self.tile = [ImageFile._Tile(t[0], (0, 0) + self.size, t[2], t3)] + + return ImageFile.ImageFile.load(self) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith( + (b"\xff\x4f\xff\x51", b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a") + ) + + +# ------------------------------------------------------------ +# Save support + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + # Get the keyword arguments + info = im.encoderinfo + + if isinstance(filename, str): + filename = filename.encode() + if filename.endswith(b".j2k") or info.get("no_jp2", False): + kind = "j2k" + else: + kind = "jp2" + + offset = info.get("offset", None) + tile_offset = info.get("tile_offset", None) + tile_size = info.get("tile_size", None) + quality_mode = info.get("quality_mode", "rates") + quality_layers = info.get("quality_layers", None) + if quality_layers is not None and not ( + isinstance(quality_layers, (list, tuple)) + and all( + isinstance(quality_layer, (int, float)) for quality_layer in quality_layers + ) + ): + msg = "quality_layers must be a sequence of numbers" + raise ValueError(msg) + + num_resolutions = info.get("num_resolutions", 0) + cblk_size = info.get("codeblock_size", None) + precinct_size = info.get("precinct_size", None) + irreversible = info.get("irreversible", False) + progression = info.get("progression", "LRCP") + cinema_mode = info.get("cinema_mode", "no") + mct = info.get("mct", 0) + signed = info.get("signed", False) + comment = info.get("comment") + if isinstance(comment, str): + comment = comment.encode() + plt = info.get("plt", False) + + fd = -1 + if hasattr(fp, "fileno"): + try: + fd = fp.fileno() + except Exception: + fd = -1 + + im.encoderconfig = ( + offset, + tile_offset, + tile_size, + quality_mode, + quality_layers, + num_resolutions, + cblk_size, + precinct_size, + irreversible, + progression, + cinema_mode, + mct, + signed, + fd, + comment, + plt, + ) + + ImageFile._save(im, fp, [ImageFile._Tile("jpeg2k", (0, 0) + im.size, 0, kind)]) + + +# ------------------------------------------------------------ +# Registry stuff + + +Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept) +Image.register_save(Jpeg2KImageFile.format, _save) + +Image.register_extensions( + Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"] +) + +Image.register_mime(Jpeg2KImageFile.format, "image/jp2") diff --git a/venv/Lib/site-packages/PIL/JpegImagePlugin.py b/venv/Lib/site-packages/PIL/JpegImagePlugin.py new file mode 100644 index 00000000..defe9f77 --- /dev/null +++ b/venv/Lib/site-packages/PIL/JpegImagePlugin.py @@ -0,0 +1,902 @@ +# +# The Python Imaging Library. +# $Id$ +# +# JPEG (JFIF) file handling +# +# See "Digital Compression and Coding of Continuous-Tone Still Images, +# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1) +# +# History: +# 1995-09-09 fl Created +# 1995-09-13 fl Added full parser +# 1996-03-25 fl Added hack to use the IJG command line utilities +# 1996-05-05 fl Workaround Photoshop 2.5 CMYK polarity bug +# 1996-05-28 fl Added draft support, JFIF version (0.1) +# 1996-12-30 fl Added encoder options, added progression property (0.2) +# 1997-08-27 fl Save mode 1 images as BW (0.3) +# 1998-07-12 fl Added YCbCr to draft and save methods (0.4) +# 1998-10-19 fl Don't hang on files using 16-bit DQT's (0.4.1) +# 2001-04-16 fl Extract DPI settings from JFIF files (0.4.2) +# 2002-07-01 fl Skip pad bytes before markers; identify Exif files (0.4.3) +# 2003-04-25 fl Added experimental EXIF decoder (0.5) +# 2003-06-06 fl Added experimental EXIF GPSinfo decoder +# 2003-09-13 fl Extract COM markers +# 2009-09-06 fl Added icc_profile support (from Florian Hoech) +# 2009-03-06 fl Changed CMYK handling; always use Adobe polarity (0.6) +# 2009-03-08 fl Added subsampling support (from Justin Huff). +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-1996 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import array +import io +import math +import os +import struct +import subprocess +import sys +import tempfile +import warnings +from typing import IO, Any + +from . import Image, ImageFile +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 +from ._binary import o16be as o16 +from ._deprecate import deprecate +from .JpegPresets import presets + +TYPE_CHECKING = False +if TYPE_CHECKING: + from .MpoImagePlugin import MpoImageFile + +# +# Parser + + +def Skip(self: JpegImageFile, marker: int) -> None: + n = i16(self.fp.read(2)) - 2 + ImageFile._safe_read(self.fp, n) + + +def APP(self: JpegImageFile, marker: int) -> None: + # + # Application marker. Store these in the APP dictionary. + # Also look for well-known application markers. + + n = i16(self.fp.read(2)) - 2 + s = ImageFile._safe_read(self.fp, n) + + app = f"APP{marker & 15}" + + self.app[app] = s # compatibility + self.applist.append((app, s)) + + if marker == 0xFFE0 and s.startswith(b"JFIF"): + # extract JFIF information + self.info["jfif"] = version = i16(s, 5) # version + self.info["jfif_version"] = divmod(version, 256) + # extract JFIF properties + try: + jfif_unit = s[7] + jfif_density = i16(s, 8), i16(s, 10) + except Exception: + pass + else: + if jfif_unit == 1: + self.info["dpi"] = jfif_density + elif jfif_unit == 2: # cm + # 1 dpcm = 2.54 dpi + self.info["dpi"] = tuple(d * 2.54 for d in jfif_density) + self.info["jfif_unit"] = jfif_unit + self.info["jfif_density"] = jfif_density + elif marker == 0xFFE1 and s.startswith(b"Exif\0\0"): + # extract EXIF information + if "exif" in self.info: + self.info["exif"] += s[6:] + else: + self.info["exif"] = s + self._exif_offset = self.fp.tell() - n + 6 + elif marker == 0xFFE1 and s.startswith(b"http://ns.adobe.com/xap/1.0/\x00"): + self.info["xmp"] = s.split(b"\x00", 1)[1] + elif marker == 0xFFE2 and s.startswith(b"FPXR\0"): + # extract FlashPix information (incomplete) + self.info["flashpix"] = s # FIXME: value will change + elif marker == 0xFFE2 and s.startswith(b"ICC_PROFILE\0"): + # Since an ICC profile can be larger than the maximum size of + # a JPEG marker (64K), we need provisions to split it into + # multiple markers. The format defined by the ICC specifies + # one or more APP2 markers containing the following data: + # Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + # Marker sequence number 1, 2, etc (1 byte) + # Number of markers Total of APP2's used (1 byte) + # Profile data (remainder of APP2 data) + # Decoders should use the marker sequence numbers to + # reassemble the profile, rather than assuming that the APP2 + # markers appear in the correct sequence. + self.icclist.append(s) + elif marker == 0xFFED and s.startswith(b"Photoshop 3.0\x00"): + # parse the image resource block + offset = 14 + photoshop = self.info.setdefault("photoshop", {}) + while s[offset : offset + 4] == b"8BIM": + try: + offset += 4 + # resource code + code = i16(s, offset) + offset += 2 + # resource name (usually empty) + name_len = s[offset] + # name = s[offset+1:offset+1+name_len] + offset += 1 + name_len + offset += offset & 1 # align + # resource data block + size = i32(s, offset) + offset += 4 + data = s[offset : offset + size] + if code == 0x03ED: # ResolutionInfo + photoshop[code] = { + "XResolution": i32(data, 0) / 65536, + "DisplayedUnitsX": i16(data, 4), + "YResolution": i32(data, 8) / 65536, + "DisplayedUnitsY": i16(data, 12), + } + else: + photoshop[code] = data + offset += size + offset += offset & 1 # align + except struct.error: + break # insufficient data + + elif marker == 0xFFEE and s.startswith(b"Adobe"): + self.info["adobe"] = i16(s, 5) + # extract Adobe custom properties + try: + adobe_transform = s[11] + except IndexError: + pass + else: + self.info["adobe_transform"] = adobe_transform + elif marker == 0xFFE2 and s.startswith(b"MPF\0"): + # extract MPO information + self.info["mp"] = s[4:] + # offset is current location minus buffer size + # plus constant header size + self.info["mpoffset"] = self.fp.tell() - n + 4 + + +def COM(self: JpegImageFile, marker: int) -> None: + # + # Comment marker. Store these in the APP dictionary. + n = i16(self.fp.read(2)) - 2 + s = ImageFile._safe_read(self.fp, n) + + self.info["comment"] = s + self.app["COM"] = s # compatibility + self.applist.append(("COM", s)) + + +def SOF(self: JpegImageFile, marker: int) -> None: + # + # Start of frame marker. Defines the size and mode of the + # image. JPEG is colour blind, so we use some simple + # heuristics to map the number of layers to an appropriate + # mode. Note that this could be made a bit brighter, by + # looking for JFIF and Adobe APP markers. + + n = i16(self.fp.read(2)) - 2 + s = ImageFile._safe_read(self.fp, n) + self._size = i16(s, 3), i16(s, 1) + + self.bits = s[0] + if self.bits != 8: + msg = f"cannot handle {self.bits}-bit layers" + raise SyntaxError(msg) + + self.layers = s[5] + if self.layers == 1: + self._mode = "L" + elif self.layers == 3: + self._mode = "RGB" + elif self.layers == 4: + self._mode = "CMYK" + else: + msg = f"cannot handle {self.layers}-layer images" + raise SyntaxError(msg) + + if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]: + self.info["progressive"] = self.info["progression"] = 1 + + if self.icclist: + # fixup icc profile + self.icclist.sort() # sort by sequence number + if self.icclist[0][13] == len(self.icclist): + profile = [p[14:] for p in self.icclist] + icc_profile = b"".join(profile) + else: + icc_profile = None # wrong number of fragments + self.info["icc_profile"] = icc_profile + self.icclist = [] + + for i in range(6, len(s), 3): + t = s[i : i + 3] + # 4-tuples: id, vsamp, hsamp, qtable + self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2])) + + +def DQT(self: JpegImageFile, marker: int) -> None: + # + # Define quantization table. Note that there might be more + # than one table in each marker. + + # FIXME: The quantization tables can be used to estimate the + # compression quality. + + n = i16(self.fp.read(2)) - 2 + s = ImageFile._safe_read(self.fp, n) + while len(s): + v = s[0] + precision = 1 if (v // 16 == 0) else 2 # in bytes + qt_length = 1 + precision * 64 + if len(s) < qt_length: + msg = "bad quantization table marker" + raise SyntaxError(msg) + data = array.array("B" if precision == 1 else "H", s[1:qt_length]) + if sys.byteorder == "little" and precision > 1: + data.byteswap() # the values are always big-endian + self.quantization[v & 15] = [data[i] for i in zigzag_index] + s = s[qt_length:] + + +# +# JPEG marker table + +MARKER = { + 0xFFC0: ("SOF0", "Baseline DCT", SOF), + 0xFFC1: ("SOF1", "Extended Sequential DCT", SOF), + 0xFFC2: ("SOF2", "Progressive DCT", SOF), + 0xFFC3: ("SOF3", "Spatial lossless", SOF), + 0xFFC4: ("DHT", "Define Huffman table", Skip), + 0xFFC5: ("SOF5", "Differential sequential DCT", SOF), + 0xFFC6: ("SOF6", "Differential progressive DCT", SOF), + 0xFFC7: ("SOF7", "Differential spatial", SOF), + 0xFFC8: ("JPG", "Extension", None), + 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF), + 0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF), + 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF), + 0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip), + 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF), + 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF), + 0xFFCF: ("SOF15", "Differential spatial (AC)", SOF), + 0xFFD0: ("RST0", "Restart 0", None), + 0xFFD1: ("RST1", "Restart 1", None), + 0xFFD2: ("RST2", "Restart 2", None), + 0xFFD3: ("RST3", "Restart 3", None), + 0xFFD4: ("RST4", "Restart 4", None), + 0xFFD5: ("RST5", "Restart 5", None), + 0xFFD6: ("RST6", "Restart 6", None), + 0xFFD7: ("RST7", "Restart 7", None), + 0xFFD8: ("SOI", "Start of image", None), + 0xFFD9: ("EOI", "End of image", None), + 0xFFDA: ("SOS", "Start of scan", Skip), + 0xFFDB: ("DQT", "Define quantization table", DQT), + 0xFFDC: ("DNL", "Define number of lines", Skip), + 0xFFDD: ("DRI", "Define restart interval", Skip), + 0xFFDE: ("DHP", "Define hierarchical progression", SOF), + 0xFFDF: ("EXP", "Expand reference component", Skip), + 0xFFE0: ("APP0", "Application segment 0", APP), + 0xFFE1: ("APP1", "Application segment 1", APP), + 0xFFE2: ("APP2", "Application segment 2", APP), + 0xFFE3: ("APP3", "Application segment 3", APP), + 0xFFE4: ("APP4", "Application segment 4", APP), + 0xFFE5: ("APP5", "Application segment 5", APP), + 0xFFE6: ("APP6", "Application segment 6", APP), + 0xFFE7: ("APP7", "Application segment 7", APP), + 0xFFE8: ("APP8", "Application segment 8", APP), + 0xFFE9: ("APP9", "Application segment 9", APP), + 0xFFEA: ("APP10", "Application segment 10", APP), + 0xFFEB: ("APP11", "Application segment 11", APP), + 0xFFEC: ("APP12", "Application segment 12", APP), + 0xFFED: ("APP13", "Application segment 13", APP), + 0xFFEE: ("APP14", "Application segment 14", APP), + 0xFFEF: ("APP15", "Application segment 15", APP), + 0xFFF0: ("JPG0", "Extension 0", None), + 0xFFF1: ("JPG1", "Extension 1", None), + 0xFFF2: ("JPG2", "Extension 2", None), + 0xFFF3: ("JPG3", "Extension 3", None), + 0xFFF4: ("JPG4", "Extension 4", None), + 0xFFF5: ("JPG5", "Extension 5", None), + 0xFFF6: ("JPG6", "Extension 6", None), + 0xFFF7: ("JPG7", "Extension 7", None), + 0xFFF8: ("JPG8", "Extension 8", None), + 0xFFF9: ("JPG9", "Extension 9", None), + 0xFFFA: ("JPG10", "Extension 10", None), + 0xFFFB: ("JPG11", "Extension 11", None), + 0xFFFC: ("JPG12", "Extension 12", None), + 0xFFFD: ("JPG13", "Extension 13", None), + 0xFFFE: ("COM", "Comment", COM), +} + + +def _accept(prefix: bytes) -> bool: + # Magic number was taken from https://en.wikipedia.org/wiki/JPEG + return prefix.startswith(b"\xff\xd8\xff") + + +## +# Image plugin for JPEG and JFIF images. + + +class JpegImageFile(ImageFile.ImageFile): + format = "JPEG" + format_description = "JPEG (ISO 10918)" + + def _open(self) -> None: + s = self.fp.read(3) + + if not _accept(s): + msg = "not a JPEG file" + raise SyntaxError(msg) + s = b"\xff" + + # Create attributes + self.bits = self.layers = 0 + self._exif_offset = 0 + + # JPEG specifics (internal) + self.layer: list[tuple[int, int, int, int]] = [] + self._huffman_dc: dict[Any, Any] = {} + self._huffman_ac: dict[Any, Any] = {} + self.quantization: dict[int, list[int]] = {} + self.app: dict[str, bytes] = {} # compatibility + self.applist: list[tuple[str, bytes]] = [] + self.icclist: list[bytes] = [] + + while True: + i = s[0] + if i == 0xFF: + s = s + self.fp.read(1) + i = i16(s) + else: + # Skip non-0xFF junk + s = self.fp.read(1) + continue + + if i in MARKER: + name, description, handler = MARKER[i] + if handler is not None: + handler(self, i) + if i == 0xFFDA: # start of scan + rawmode = self.mode + if self.mode == "CMYK": + rawmode = "CMYK;I" # assume adobe conventions + self.tile = [ + ImageFile._Tile("jpeg", (0, 0) + self.size, 0, (rawmode, "")) + ] + # self.__offset = self.fp.tell() + break + s = self.fp.read(1) + elif i in {0, 0xFFFF}: + # padded marker or junk; move on + s = b"\xff" + elif i == 0xFF00: # Skip extraneous data (escaped 0xFF) + s = self.fp.read(1) + else: + msg = "no marker found" + raise SyntaxError(msg) + + self._read_dpi_from_exif() + + def __getattr__(self, name: str) -> Any: + if name in ("huffman_ac", "huffman_dc"): + deprecate(name, 12) + return getattr(self, "_" + name) + raise AttributeError(name) + + def __getstate__(self) -> list[Any]: + return super().__getstate__() + [self.layers, self.layer] + + def __setstate__(self, state: list[Any]) -> None: + self.layers, self.layer = state[6:] + super().__setstate__(state) + + def load_read(self, read_bytes: int) -> bytes: + """ + internal: read more image data + For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker + so libjpeg can finish decoding + """ + s = self.fp.read(read_bytes) + + if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"): + # Premature EOF. + # Pretend file is finished adding EOI marker + self._ended = True + return b"\xff\xd9" + + return s + + def draft( + self, mode: str | None, size: tuple[int, int] | None + ) -> tuple[str, tuple[int, int, float, float]] | None: + if len(self.tile) != 1: + return None + + # Protect from second call + if self.decoderconfig: + return None + + d, e, o, a = self.tile[0] + scale = 1 + original_size = self.size + + assert isinstance(a, tuple) + if a[0] == "RGB" and mode in ["L", "YCbCr"]: + self._mode = mode + a = mode, "" + + if size: + scale = min(self.size[0] // size[0], self.size[1] // size[1]) + for s in [8, 4, 2, 1]: + if scale >= s: + break + assert e is not None + e = ( + e[0], + e[1], + (e[2] - e[0] + s - 1) // s + e[0], + (e[3] - e[1] + s - 1) // s + e[1], + ) + self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s) + scale = s + + self.tile = [ImageFile._Tile(d, e, o, a)] + self.decoderconfig = (scale, 0) + + box = (0, 0, original_size[0] / scale, original_size[1] / scale) + return self.mode, box + + def load_djpeg(self) -> None: + # ALTERNATIVE: handle JPEGs via the IJG command line utilities + + f, path = tempfile.mkstemp() + os.close(f) + if os.path.exists(self.filename): + subprocess.check_call(["djpeg", "-outfile", path, self.filename]) + else: + try: + os.unlink(path) + except OSError: + pass + + msg = "Invalid Filename" + raise ValueError(msg) + + try: + with Image.open(path) as _im: + _im.load() + self.im = _im.im + finally: + try: + os.unlink(path) + except OSError: + pass + + self._mode = self.im.mode + self._size = self.im.size + + self.tile = [] + + def _getexif(self) -> dict[int, Any] | None: + return _getexif(self) + + def _read_dpi_from_exif(self) -> None: + # If DPI isn't in JPEG header, fetch from EXIF + if "dpi" in self.info or "exif" not in self.info: + return + try: + exif = self.getexif() + resolution_unit = exif[0x0128] + x_resolution = exif[0x011A] + try: + dpi = float(x_resolution[0]) / x_resolution[1] + except TypeError: + dpi = x_resolution + if math.isnan(dpi): + msg = "DPI is not a number" + raise ValueError(msg) + if resolution_unit == 3: # cm + # 1 dpcm = 2.54 dpi + dpi *= 2.54 + self.info["dpi"] = dpi, dpi + except ( + struct.error, # truncated EXIF + KeyError, # dpi not included + SyntaxError, # invalid/unreadable EXIF + TypeError, # dpi is an invalid float + ValueError, # dpi is an invalid float + ZeroDivisionError, # invalid dpi rational value + ): + self.info["dpi"] = 72, 72 + + def _getmp(self) -> dict[int, Any] | None: + return _getmp(self) + + +def _getexif(self: JpegImageFile) -> dict[int, Any] | None: + if "exif" not in self.info: + return None + return self.getexif()._get_merged_dict() + + +def _getmp(self: JpegImageFile) -> dict[int, Any] | None: + # Extract MP information. This method was inspired by the "highly + # experimental" _getexif version that's been in use for years now, + # itself based on the ImageFileDirectory class in the TIFF plugin. + + # The MP record essentially consists of a TIFF file embedded in a JPEG + # application marker. + try: + data = self.info["mp"] + except KeyError: + return None + file_contents = io.BytesIO(data) + head = file_contents.read(8) + endianness = ">" if head.startswith(b"\x4d\x4d\x00\x2a") else "<" + # process dictionary + from . import TiffImagePlugin + + try: + info = TiffImagePlugin.ImageFileDirectory_v2(head) + file_contents.seek(info.next) + info.load(file_contents) + mp = dict(info) + except Exception as e: + msg = "malformed MP Index (unreadable directory)" + raise SyntaxError(msg) from e + # it's an error not to have a number of images + try: + quant = mp[0xB001] + except KeyError as e: + msg = "malformed MP Index (no number of images)" + raise SyntaxError(msg) from e + # get MP entries + mpentries = [] + try: + rawmpentries = mp[0xB002] + for entrynum in range(quant): + unpackedentry = struct.unpack_from( + f"{endianness}LLLHH", rawmpentries, entrynum * 16 + ) + labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2") + mpentry = dict(zip(labels, unpackedentry)) + mpentryattr = { + "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)), + "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)), + "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)), + "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27, + "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24, + "MPType": mpentry["Attribute"] & 0x00FFFFFF, + } + if mpentryattr["ImageDataFormat"] == 0: + mpentryattr["ImageDataFormat"] = "JPEG" + else: + msg = "unsupported picture format in MPO" + raise SyntaxError(msg) + mptypemap = { + 0x000000: "Undefined", + 0x010001: "Large Thumbnail (VGA Equivalent)", + 0x010002: "Large Thumbnail (Full HD Equivalent)", + 0x020001: "Multi-Frame Image (Panorama)", + 0x020002: "Multi-Frame Image: (Disparity)", + 0x020003: "Multi-Frame Image: (Multi-Angle)", + 0x030000: "Baseline MP Primary Image", + } + mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown") + mpentry["Attribute"] = mpentryattr + mpentries.append(mpentry) + mp[0xB002] = mpentries + except KeyError as e: + msg = "malformed MP Index (bad MP Entry)" + raise SyntaxError(msg) from e + # Next we should try and parse the individual image unique ID list; + # we don't because I've never seen this actually used in a real MPO + # file and so can't test it. + return mp + + +# -------------------------------------------------------------------- +# stuff to save JPEG files + +RAWMODE = { + "1": "L", + "L": "L", + "RGB": "RGB", + "RGBX": "RGB", + "CMYK": "CMYK;I", # assume adobe conventions + "YCbCr": "YCbCr", +} + +# fmt: off +zigzag_index = ( + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63, +) + +samplings = { + (1, 1, 1, 1, 1, 1): 0, + (2, 1, 1, 1, 1, 1): 1, + (2, 2, 1, 1, 1, 1): 2, +} +# fmt: on + + +def get_sampling(im: Image.Image) -> int: + # There's no subsampling when images have only 1 layer + # (grayscale images) or when they are CMYK (4 layers), + # so set subsampling to the default value. + # + # NOTE: currently Pillow can't encode JPEG to YCCK format. + # If YCCK support is added in the future, subsampling code will have + # to be updated (here and in JpegEncode.c) to deal with 4 layers. + if not isinstance(im, JpegImageFile) or im.layers in (1, 4): + return -1 + sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] + return samplings.get(sampling, -1) + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.width == 0 or im.height == 0: + msg = "cannot write empty image as JPEG" + raise ValueError(msg) + + try: + rawmode = RAWMODE[im.mode] + except KeyError as e: + msg = f"cannot write mode {im.mode} as JPEG" + raise OSError(msg) from e + + info = im.encoderinfo + + dpi = [round(x) for x in info.get("dpi", (0, 0))] + + quality = info.get("quality", -1) + subsampling = info.get("subsampling", -1) + qtables = info.get("qtables") + + if quality == "keep": + quality = -1 + subsampling = "keep" + qtables = "keep" + elif quality in presets: + preset = presets[quality] + quality = -1 + subsampling = preset.get("subsampling", -1) + qtables = preset.get("quantization") + elif not isinstance(quality, int): + msg = "Invalid quality setting" + raise ValueError(msg) + else: + if subsampling in presets: + subsampling = presets[subsampling].get("subsampling", -1) + if isinstance(qtables, str) and qtables in presets: + qtables = presets[qtables].get("quantization") + + if subsampling == "4:4:4": + subsampling = 0 + elif subsampling == "4:2:2": + subsampling = 1 + elif subsampling == "4:2:0": + subsampling = 2 + elif subsampling == "4:1:1": + # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0. + # Set 4:2:0 if someone is still using that value. + subsampling = 2 + elif subsampling == "keep": + if im.format != "JPEG": + msg = "Cannot use 'keep' when original image is not a JPEG" + raise ValueError(msg) + subsampling = get_sampling(im) + + def validate_qtables( + qtables: ( + str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None + ), + ) -> list[list[int]] | None: + if qtables is None: + return qtables + if isinstance(qtables, str): + try: + lines = [ + int(num) + for line in qtables.splitlines() + for num in line.split("#", 1)[0].split() + ] + except ValueError as e: + msg = "Invalid quantization table" + raise ValueError(msg) from e + else: + qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] + if isinstance(qtables, (tuple, list, dict)): + if isinstance(qtables, dict): + qtables = [ + qtables[key] for key in range(len(qtables)) if key in qtables + ] + elif isinstance(qtables, tuple): + qtables = list(qtables) + if not (0 < len(qtables) < 5): + msg = "None or too many quantization tables" + raise ValueError(msg) + for idx, table in enumerate(qtables): + try: + if len(table) != 64: + msg = "Invalid quantization table" + raise TypeError(msg) + table_array = array.array("H", table) + except TypeError as e: + msg = "Invalid quantization table" + raise ValueError(msg) from e + else: + qtables[idx] = list(table_array) + return qtables + + if qtables == "keep": + if im.format != "JPEG": + msg = "Cannot use 'keep' when original image is not a JPEG" + raise ValueError(msg) + qtables = getattr(im, "quantization", None) + qtables = validate_qtables(qtables) + + extra = info.get("extra", b"") + + MAX_BYTES_IN_MARKER = 65533 + if xmp := info.get("xmp"): + overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00" + max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len + if len(xmp) > max_data_bytes_in_marker: + msg = "XMP data is too long" + raise ValueError(msg) + size = o16(2 + overhead_len + len(xmp)) + extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp + + if icc_profile := info.get("icc_profile"): + overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len + markers = [] + while icc_profile: + markers.append(icc_profile[:max_data_bytes_in_marker]) + icc_profile = icc_profile[max_data_bytes_in_marker:] + i = 1 + for marker in markers: + size = o16(2 + overhead_len + len(marker)) + extra += ( + b"\xff\xe2" + + size + + b"ICC_PROFILE\0" + + o8(i) + + o8(len(markers)) + + marker + ) + i += 1 + + comment = info.get("comment", im.info.get("comment")) + + # "progressive" is the official name, but older documentation + # says "progression" + # FIXME: issue a warning if the wrong form is used (post-1.1.7) + progressive = info.get("progressive", False) or info.get("progression", False) + + optimize = info.get("optimize", False) + + exif = info.get("exif", b"") + if isinstance(exif, Image.Exif): + exif = exif.tobytes() + if len(exif) > MAX_BYTES_IN_MARKER: + msg = "EXIF data is too long" + raise ValueError(msg) + + # get keyword arguments + im.encoderconfig = ( + quality, + progressive, + info.get("smooth", 0), + optimize, + info.get("keep_rgb", False), + info.get("streamtype", 0), + dpi, + subsampling, + info.get("restart_marker_blocks", 0), + info.get("restart_marker_rows", 0), + qtables, + comment, + extra, + exif, + ) + + # if we optimize, libjpeg needs a buffer big enough to hold the whole image + # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is + # channels*size, this is a value that's been used in a django patch. + # https://github.com/matthewwithanm/django-imagekit/issues/50 + if optimize or progressive: + # CMYK can be bigger + if im.mode == "CMYK": + bufsize = 4 * im.size[0] * im.size[1] + # keep sets quality to -1, but the actual value may be high. + elif quality >= 95 or quality == -1: + bufsize = 2 * im.size[0] * im.size[1] + else: + bufsize = im.size[0] * im.size[1] + if exif: + bufsize += len(exif) + 5 + if extra: + bufsize += len(extra) + 1 + else: + # The EXIF info needs to be written as one block, + APP1, + one spare byte. + # Ensure that our buffer is big enough. Same with the icc_profile block. + bufsize = max(len(exif) + 5, len(extra) + 1) + + ImageFile._save( + im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize + ) + + +def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + # ALTERNATIVE: handle JPEGs via the IJG command line utilities. + tempfile = im._dump() + subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) + try: + os.unlink(tempfile) + except OSError: + pass + + +## +# Factory for making JPEG and MPO instances +def jpeg_factory( + fp: IO[bytes], filename: str | bytes | None = None +) -> JpegImageFile | MpoImageFile: + im = JpegImageFile(fp, filename) + try: + mpheader = im._getmp() + if mpheader is not None and mpheader[45057] > 1: + for segment, content in im.applist: + if segment == "APP1" and b' hdrgm:Version="' in content: + # Ultra HDR images are not yet supported + return im + # It's actually an MPO + from .MpoImagePlugin import MpoImageFile + + # Don't reload everything, just convert it. + im = MpoImageFile.adopt(im, mpheader) + except (TypeError, IndexError): + # It is really a JPEG + pass + except SyntaxError: + warnings.warn( + "Image appears to be a malformed MPO file, it will be " + "interpreted as a base JPEG file" + ) + return im + + +# --------------------------------------------------------------------- +# Registry stuff + +Image.register_open(JpegImageFile.format, jpeg_factory, _accept) +Image.register_save(JpegImageFile.format, _save) + +Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"]) + +Image.register_mime(JpegImageFile.format, "image/jpeg") diff --git a/venv/Lib/site-packages/PIL/JpegPresets.py b/venv/Lib/site-packages/PIL/JpegPresets.py new file mode 100644 index 00000000..d0e64a35 --- /dev/null +++ b/venv/Lib/site-packages/PIL/JpegPresets.py @@ -0,0 +1,242 @@ +""" +JPEG quality settings equivalent to the Photoshop settings. +Can be used when saving JPEG files. + +The following presets are available by default: +``web_low``, ``web_medium``, ``web_high``, ``web_very_high``, ``web_maximum``, +``low``, ``medium``, ``high``, ``maximum``. +More presets can be added to the :py:data:`presets` dict if needed. + +To apply the preset, specify:: + + quality="preset_name" + +To apply only the quantization table:: + + qtables="preset_name" + +To apply only the subsampling setting:: + + subsampling="preset_name" + +Example:: + + im.save("image_name.jpg", quality="web_high") + +Subsampling +----------- + +Subsampling is the practice of encoding images by implementing less resolution +for chroma information than for luma information. +(ref.: https://en.wikipedia.org/wiki/Chroma_subsampling) + +Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and +4:2:0. + +You can get the subsampling of a JPEG with the +:func:`.JpegImagePlugin.get_sampling` function. + +In JPEG compressed data a JPEG marker is used instead of an EXIF tag. +(ref.: https://exiv2.org/tags.html) + + +Quantization tables +------------------- + +They are values use by the DCT (Discrete cosine transform) to remove +*unnecessary* information from the image (the lossy part of the compression). +(ref.: https://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices, +https://en.wikipedia.org/wiki/JPEG#Quantization) + +You can get the quantization tables of a JPEG with:: + + im.quantization + +This will return a dict with a number of lists. You can pass this dict +directly as the qtables argument when saving a JPEG. + +The quantization table format in presets is a list with sublists. These formats +are interchangeable. + +Libjpeg ref.: +https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html + +""" + +from __future__ import annotations + +# fmt: off +presets = { + 'web_low': {'subsampling': 2, # "4:2:0" + 'quantization': [ + [20, 16, 25, 39, 50, 46, 62, 68, + 16, 18, 23, 38, 38, 53, 65, 68, + 25, 23, 31, 38, 53, 65, 68, 68, + 39, 38, 38, 53, 65, 68, 68, 68, + 50, 38, 53, 65, 68, 68, 68, 68, + 46, 53, 65, 68, 68, 68, 68, 68, + 62, 65, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68], + [21, 25, 32, 38, 54, 68, 68, 68, + 25, 28, 24, 38, 54, 68, 68, 68, + 32, 24, 32, 43, 66, 68, 68, 68, + 38, 38, 43, 53, 68, 68, 68, 68, + 54, 54, 66, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68] + ]}, + 'web_medium': {'subsampling': 2, # "4:2:0" + 'quantization': [ + [16, 11, 11, 16, 23, 27, 31, 30, + 11, 12, 12, 15, 20, 23, 23, 30, + 11, 12, 13, 16, 23, 26, 35, 47, + 16, 15, 16, 23, 26, 37, 47, 64, + 23, 20, 23, 26, 39, 51, 64, 64, + 27, 23, 26, 37, 51, 64, 64, 64, + 31, 23, 35, 47, 64, 64, 64, 64, + 30, 30, 47, 64, 64, 64, 64, 64], + [17, 15, 17, 21, 20, 26, 38, 48, + 15, 19, 18, 17, 20, 26, 35, 43, + 17, 18, 20, 22, 26, 30, 46, 53, + 21, 17, 22, 28, 30, 39, 53, 64, + 20, 20, 26, 30, 39, 48, 64, 64, + 26, 26, 30, 39, 48, 63, 64, 64, + 38, 35, 46, 53, 64, 64, 64, 64, + 48, 43, 53, 64, 64, 64, 64, 64] + ]}, + 'web_high': {'subsampling': 0, # "4:4:4" + 'quantization': [ + [6, 4, 4, 6, 9, 11, 12, 16, + 4, 5, 5, 6, 8, 10, 12, 12, + 4, 5, 5, 6, 10, 12, 14, 19, + 6, 6, 6, 11, 12, 15, 19, 28, + 9, 8, 10, 12, 16, 20, 27, 31, + 11, 10, 12, 15, 20, 27, 31, 31, + 12, 12, 14, 19, 27, 31, 31, 31, + 16, 12, 19, 28, 31, 31, 31, 31], + [7, 7, 13, 24, 26, 31, 31, 31, + 7, 12, 16, 21, 31, 31, 31, 31, + 13, 16, 17, 31, 31, 31, 31, 31, + 24, 21, 31, 31, 31, 31, 31, 31, + 26, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31] + ]}, + 'web_very_high': {'subsampling': 0, # "4:4:4" + 'quantization': [ + [2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 4, 5, 7, 9, + 2, 2, 2, 4, 5, 7, 9, 12, + 3, 3, 4, 5, 8, 10, 12, 12, + 4, 4, 5, 7, 10, 12, 12, 12, + 5, 5, 7, 9, 12, 12, 12, 12, + 6, 6, 9, 12, 12, 12, 12, 12], + [3, 3, 5, 9, 13, 15, 15, 15, + 3, 4, 6, 11, 14, 12, 12, 12, + 5, 6, 9, 14, 12, 12, 12, 12, + 9, 11, 14, 12, 12, 12, 12, 12, + 13, 14, 12, 12, 12, 12, 12, 12, + 15, 12, 12, 12, 12, 12, 12, 12, + 15, 12, 12, 12, 12, 12, 12, 12, + 15, 12, 12, 12, 12, 12, 12, 12] + ]}, + 'web_maximum': {'subsampling': 0, # "4:4:4" + 'quantization': [ + [1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, + 1, 1, 1, 1, 1, 1, 2, 2, + 1, 1, 1, 1, 1, 2, 2, 3, + 1, 1, 1, 1, 2, 2, 3, 3, + 1, 1, 1, 2, 2, 3, 3, 3, + 1, 1, 2, 2, 3, 3, 3, 3], + [1, 1, 1, 2, 2, 3, 3, 3, + 1, 1, 1, 2, 3, 3, 3, 3, + 1, 1, 1, 3, 3, 3, 3, 3, + 2, 2, 3, 3, 3, 3, 3, 3, + 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3] + ]}, + 'low': {'subsampling': 2, # "4:2:0" + 'quantization': [ + [18, 14, 14, 21, 30, 35, 34, 17, + 14, 16, 16, 19, 26, 23, 12, 12, + 14, 16, 17, 21, 23, 12, 12, 12, + 21, 19, 21, 23, 12, 12, 12, 12, + 30, 26, 23, 12, 12, 12, 12, 12, + 35, 23, 12, 12, 12, 12, 12, 12, + 34, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12], + [20, 19, 22, 27, 20, 20, 17, 17, + 19, 25, 23, 14, 14, 12, 12, 12, + 22, 23, 14, 14, 12, 12, 12, 12, + 27, 14, 14, 12, 12, 12, 12, 12, + 20, 14, 12, 12, 12, 12, 12, 12, + 20, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12] + ]}, + 'medium': {'subsampling': 2, # "4:2:0" + 'quantization': [ + [12, 8, 8, 12, 17, 21, 24, 17, + 8, 9, 9, 11, 15, 19, 12, 12, + 8, 9, 10, 12, 19, 12, 12, 12, + 12, 11, 12, 21, 12, 12, 12, 12, + 17, 15, 19, 12, 12, 12, 12, 12, + 21, 19, 12, 12, 12, 12, 12, 12, + 24, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12], + [13, 11, 13, 16, 20, 20, 17, 17, + 11, 14, 14, 14, 14, 12, 12, 12, + 13, 14, 14, 14, 12, 12, 12, 12, + 16, 14, 14, 12, 12, 12, 12, 12, + 20, 14, 12, 12, 12, 12, 12, 12, + 20, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12] + ]}, + 'high': {'subsampling': 0, # "4:4:4" + 'quantization': [ + [6, 4, 4, 6, 9, 11, 12, 16, + 4, 5, 5, 6, 8, 10, 12, 12, + 4, 5, 5, 6, 10, 12, 12, 12, + 6, 6, 6, 11, 12, 12, 12, 12, + 9, 8, 10, 12, 12, 12, 12, 12, + 11, 10, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 16, 12, 12, 12, 12, 12, 12, 12], + [7, 7, 13, 24, 20, 20, 17, 17, + 7, 12, 16, 14, 14, 12, 12, 12, + 13, 16, 14, 14, 12, 12, 12, 12, + 24, 14, 14, 12, 12, 12, 12, 12, + 20, 14, 12, 12, 12, 12, 12, 12, + 20, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12, + 17, 12, 12, 12, 12, 12, 12, 12] + ]}, + 'maximum': {'subsampling': 0, # "4:4:4" + 'quantization': [ + [2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 4, 5, 7, 9, + 2, 2, 2, 4, 5, 7, 9, 12, + 3, 3, 4, 5, 8, 10, 12, 12, + 4, 4, 5, 7, 10, 12, 12, 12, + 5, 5, 7, 9, 12, 12, 12, 12, + 6, 6, 9, 12, 12, 12, 12, 12], + [3, 3, 5, 9, 13, 15, 15, 15, + 3, 4, 6, 10, 14, 12, 12, 12, + 5, 6, 9, 14, 12, 12, 12, 12, + 9, 10, 14, 12, 12, 12, 12, 12, + 13, 14, 12, 12, 12, 12, 12, 12, + 15, 12, 12, 12, 12, 12, 12, 12, + 15, 12, 12, 12, 12, 12, 12, 12, + 15, 12, 12, 12, 12, 12, 12, 12] + ]}, +} +# fmt: on diff --git a/venv/Lib/site-packages/PIL/McIdasImagePlugin.py b/venv/Lib/site-packages/PIL/McIdasImagePlugin.py new file mode 100644 index 00000000..9a47933b --- /dev/null +++ b/venv/Lib/site-packages/PIL/McIdasImagePlugin.py @@ -0,0 +1,78 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Basic McIdas support for PIL +# +# History: +# 1997-05-05 fl Created (8-bit images only) +# 2009-03-08 fl Added 16/32-bit support. +# +# Thanks to Richard Jones and Craig Swank for specs and samples. +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import struct + +from . import Image, ImageFile + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\x00\x00\x00\x00\x00\x00\x00\x04") + + +## +# Image plugin for McIdas area images. + + +class McIdasImageFile(ImageFile.ImageFile): + format = "MCIDAS" + format_description = "McIdas area file" + + def _open(self) -> None: + # parse area file directory + assert self.fp is not None + + s = self.fp.read(256) + if not _accept(s) or len(s) != 256: + msg = "not an McIdas area file" + raise SyntaxError(msg) + + self.area_descriptor_raw = s + self.area_descriptor = w = [0, *struct.unpack("!64i", s)] + + # get mode + if w[11] == 1: + mode = rawmode = "L" + elif w[11] == 2: + mode = rawmode = "I;16B" + elif w[11] == 4: + # FIXME: add memory map support + mode = "I" + rawmode = "I;32B" + else: + msg = "unsupported McIdas format" + raise SyntaxError(msg) + + self._mode = mode + self._size = w[10], w[9] + + offset = w[34] + w[15] + stride = w[15] + w[10] * w[11] * w[14] + + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offset, (rawmode, stride, 1)) + ] + + +# -------------------------------------------------------------------- +# registry + +Image.register_open(McIdasImageFile.format, McIdasImageFile, _accept) + +# no default extension diff --git a/venv/Lib/site-packages/PIL/MicImagePlugin.py b/venv/Lib/site-packages/PIL/MicImagePlugin.py new file mode 100644 index 00000000..9ce38c42 --- /dev/null +++ b/venv/Lib/site-packages/PIL/MicImagePlugin.py @@ -0,0 +1,102 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Microsoft Image Composer support for PIL +# +# Notes: +# uses TiffImagePlugin.py to read the actual image streams +# +# History: +# 97-01-20 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import olefile + +from . import Image, TiffImagePlugin + +# +# -------------------------------------------------------------------- + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(olefile.MAGIC) + + +## +# Image plugin for Microsoft's Image Composer file format. + + +class MicImageFile(TiffImagePlugin.TiffImageFile): + format = "MIC" + format_description = "Microsoft Image Composer" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # read the OLE directory and see if this is a likely + # to be a Microsoft Image Composer file + + try: + self.ole = olefile.OleFileIO(self.fp) + except OSError as e: + msg = "not an MIC file; invalid OLE file" + raise SyntaxError(msg) from e + + # find ACI subfiles with Image members (maybe not the + # best way to identify MIC files, but what the... ;-) + + self.images = [ + path + for path in self.ole.listdir() + if path[1:] and path[0].endswith(".ACI") and path[1] == "Image" + ] + + # if we didn't find any images, this is probably not + # an MIC file. + if not self.images: + msg = "not an MIC file; no image entries" + raise SyntaxError(msg) + + self.frame = -1 + self._n_frames = len(self.images) + self.is_animated = self._n_frames > 1 + + self.__fp = self.fp + self.seek(0) + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + filename = self.images[frame] + self.fp = self.ole.openstream(filename) + + TiffImagePlugin.TiffImageFile._open(self) + + self.frame = frame + + def tell(self) -> int: + return self.frame + + def close(self) -> None: + self.__fp.close() + self.ole.close() + super().close() + + def __exit__(self, *args: object) -> None: + self.__fp.close() + self.ole.close() + super().__exit__() + + +# +# -------------------------------------------------------------------- + +Image.register_open(MicImageFile.format, MicImageFile, _accept) + +Image.register_extension(MicImageFile.format, ".mic") diff --git a/venv/Lib/site-packages/PIL/MpegImagePlugin.py b/venv/Lib/site-packages/PIL/MpegImagePlugin.py new file mode 100644 index 00000000..47ebe9d6 --- /dev/null +++ b/venv/Lib/site-packages/PIL/MpegImagePlugin.py @@ -0,0 +1,84 @@ +# +# The Python Imaging Library. +# $Id$ +# +# MPEG file handling +# +# History: +# 95-09-09 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1995. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image, ImageFile +from ._binary import i8 +from ._typing import SupportsRead + +# +# Bitstream parser + + +class BitStream: + def __init__(self, fp: SupportsRead[bytes]) -> None: + self.fp = fp + self.bits = 0 + self.bitbuffer = 0 + + def next(self) -> int: + return i8(self.fp.read(1)) + + def peek(self, bits: int) -> int: + while self.bits < bits: + self.bitbuffer = (self.bitbuffer << 8) + self.next() + self.bits += 8 + return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1 + + def skip(self, bits: int) -> None: + while self.bits < bits: + self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1)) + self.bits += 8 + self.bits = self.bits - bits + + def read(self, bits: int) -> int: + v = self.peek(bits) + self.bits = self.bits - bits + return v + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\x00\x00\x01\xb3") + + +## +# Image plugin for MPEG streams. This plugin can identify a stream, +# but it cannot read it. + + +class MpegImageFile(ImageFile.ImageFile): + format = "MPEG" + format_description = "MPEG" + + def _open(self) -> None: + assert self.fp is not None + + s = BitStream(self.fp) + if s.read(32) != 0x1B3: + msg = "not an MPEG file" + raise SyntaxError(msg) + + self._mode = "RGB" + self._size = s.read(12), s.read(12) + + +# -------------------------------------------------------------------- +# Registry stuff + +Image.register_open(MpegImageFile.format, MpegImageFile, _accept) + +Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"]) + +Image.register_mime(MpegImageFile.format, "video/mpeg") diff --git a/venv/Lib/site-packages/PIL/MpoImagePlugin.py b/venv/Lib/site-packages/PIL/MpoImagePlugin.py new file mode 100644 index 00000000..b1ae0787 --- /dev/null +++ b/venv/Lib/site-packages/PIL/MpoImagePlugin.py @@ -0,0 +1,202 @@ +# +# The Python Imaging Library. +# $Id$ +# +# MPO file handling +# +# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the +# Camera & Imaging Products Association) +# +# The multi-picture object combines multiple JPEG images (with a modified EXIF +# data format) into a single file. While it can theoretically be used much like +# a GIF animation, it is commonly used to represent 3D photographs and is (as +# of this writing) the most commonly used format by 3D cameras. +# +# History: +# 2014-03-13 Feneric Created +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +import struct +from typing import IO, Any, cast + +from . import ( + Image, + ImageFile, + ImageSequence, + JpegImagePlugin, + TiffImagePlugin, +) +from ._binary import o32le +from ._util import DeferredError + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + JpegImagePlugin._save(im, fp, filename) + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + append_images = im.encoderinfo.get("append_images", []) + if not append_images and not getattr(im, "is_animated", False): + _save(im, fp, filename) + return + + mpf_offset = 28 + offsets: list[int] = [] + im_sequences = [im, *append_images] + total = sum(getattr(seq, "n_frames", 1) for seq in im_sequences) + for im_sequence in im_sequences: + for im_frame in ImageSequence.Iterator(im_sequence): + if not offsets: + # APP2 marker + ifd_length = 66 + 16 * total + im_frame.encoderinfo["extra"] = ( + b"\xff\xe2" + + struct.pack(">H", 6 + ifd_length) + + b"MPF\0" + + b" " * ifd_length + ) + exif = im_frame.encoderinfo.get("exif") + if isinstance(exif, Image.Exif): + exif = exif.tobytes() + im_frame.encoderinfo["exif"] = exif + if exif: + mpf_offset += 4 + len(exif) + + JpegImagePlugin._save(im_frame, fp, filename) + offsets.append(fp.tell()) + else: + encoderinfo = im_frame._attach_default_encoderinfo(im) + im_frame.save(fp, "JPEG") + im_frame.encoderinfo = encoderinfo + offsets.append(fp.tell() - offsets[-1]) + + ifd = TiffImagePlugin.ImageFileDirectory_v2() + ifd[0xB000] = b"0100" + ifd[0xB001] = len(offsets) + + mpentries = b"" + data_offset = 0 + for i, size in enumerate(offsets): + if i == 0: + mptype = 0x030000 # Baseline MP Primary Image + else: + mptype = 0x000000 # Undefined + mpentries += struct.pack(" None: + self.fp.seek(0) # prep the fp in order to pass the JPEG test + JpegImagePlugin.JpegImageFile._open(self) + self._after_jpeg_open() + + def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None: + self.mpinfo = mpheader if mpheader is not None else self._getmp() + if self.mpinfo is None: + msg = "Image appears to be a malformed MPO file" + raise ValueError(msg) + self.n_frames = self.mpinfo[0xB001] + self.__mpoffsets = [ + mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] + ] + self.__mpoffsets[0] = 0 + # Note that the following assertion will only be invalid if something + # gets broken within JpegImagePlugin. + assert self.n_frames == len(self.__mpoffsets) + del self.info["mpoffset"] # no longer needed + self.is_animated = self.n_frames > 1 + self._fp = self.fp # FIXME: hack + self._fp.seek(self.__mpoffsets[0]) # get ready to read first frame + self.__frame = 0 + self.offset = 0 + # for now we can only handle reading and individual frame extraction + self.readonly = 1 + + def load_seek(self, pos: int) -> None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self._fp.seek(pos) + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self.fp = self._fp + self.offset = self.__mpoffsets[frame] + + original_exif = self.info.get("exif") + if "exif" in self.info: + del self.info["exif"] + + self.fp.seek(self.offset + 2) # skip SOI marker + if not self.fp.read(2): + msg = "No data found for frame" + raise ValueError(msg) + self.fp.seek(self.offset) + JpegImagePlugin.JpegImageFile._open(self) + if self.info.get("exif") != original_exif: + self._reload_exif() + + self.tile = [ + ImageFile._Tile("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1]) + ] + self.__frame = frame + + def tell(self) -> int: + return self.__frame + + @staticmethod + def adopt( + jpeg_instance: JpegImagePlugin.JpegImageFile, + mpheader: dict[int, Any] | None = None, + ) -> MpoImageFile: + """ + Transform the instance of JpegImageFile into + an instance of MpoImageFile. + After the call, the JpegImageFile is extended + to be an MpoImageFile. + + This is essentially useful when opening a JPEG + file that reveals itself as an MPO, to avoid + double call to _open. + """ + jpeg_instance.__class__ = MpoImageFile + mpo_instance = cast(MpoImageFile, jpeg_instance) + mpo_instance._after_jpeg_open(mpheader) + return mpo_instance + + +# --------------------------------------------------------------------- +# Registry stuff + +# Note that since MPO shares a factory with JPEG, we do not need to do a +# separate registration for it here. +# Image.register_open(MpoImageFile.format, +# JpegImagePlugin.jpeg_factory, _accept) +Image.register_save(MpoImageFile.format, _save) +Image.register_save_all(MpoImageFile.format, _save_all) + +Image.register_extension(MpoImageFile.format, ".mpo") + +Image.register_mime(MpoImageFile.format, "image/mpo") diff --git a/venv/Lib/site-packages/PIL/MspImagePlugin.py b/venv/Lib/site-packages/PIL/MspImagePlugin.py new file mode 100644 index 00000000..277087a8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/MspImagePlugin.py @@ -0,0 +1,200 @@ +# +# The Python Imaging Library. +# +# MSP file handling +# +# This is the format used by the Paint program in Windows 1 and 2. +# +# History: +# 95-09-05 fl Created +# 97-01-03 fl Read/write MSP images +# 17-02-21 es Fixed RLE interpretation +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1995-97. +# Copyright (c) Eric Soroos 2017. +# +# See the README file for information on usage and redistribution. +# +# More info on this format: https://archive.org/details/gg243631 +# Page 313: +# Figure 205. Windows Paint Version 1: "DanM" Format +# Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03 +# +# See also: https://www.fileformat.info/format/mspaint/egff.htm +from __future__ import annotations + +import io +import struct +from typing import IO + +from . import Image, ImageFile +from ._binary import i16le as i16 +from ._binary import o16le as o16 + +# +# read MSP files + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"DanM", b"LinS")) + + +## +# Image plugin for Windows MSP images. This plugin supports both +# uncompressed (Windows 1.0). + + +class MspImageFile(ImageFile.ImageFile): + format = "MSP" + format_description = "Windows Paint" + + def _open(self) -> None: + # Header + assert self.fp is not None + + s = self.fp.read(32) + if not _accept(s): + msg = "not an MSP file" + raise SyntaxError(msg) + + # Header checksum + checksum = 0 + for i in range(0, 32, 2): + checksum = checksum ^ i16(s, i) + if checksum != 0: + msg = "bad MSP checksum" + raise SyntaxError(msg) + + self._mode = "1" + self._size = i16(s, 4), i16(s, 6) + + if s.startswith(b"DanM"): + self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")] + else: + self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)] + + +class MspDecoder(ImageFile.PyDecoder): + # The algo for the MSP decoder is from + # https://www.fileformat.info/format/mspaint/egff.htm + # cc-by-attribution -- That page references is taken from the + # Encyclopedia of Graphics File Formats and is licensed by + # O'Reilly under the Creative Common/Attribution license + # + # For RLE encoded files, the 32byte header is followed by a scan + # line map, encoded as one 16bit word of encoded byte length per + # line. + # + # NOTE: the encoded length of the line can be 0. This was not + # handled in the previous version of this encoder, and there's no + # mention of how to handle it in the documentation. From the few + # examples I've seen, I've assumed that it is a fill of the + # background color, in this case, white. + # + # + # Pseudocode of the decoder: + # Read a BYTE value as the RunType + # If the RunType value is zero + # Read next byte as the RunCount + # Read the next byte as the RunValue + # Write the RunValue byte RunCount times + # If the RunType value is non-zero + # Use this value as the RunCount + # Read and write the next RunCount bytes literally + # + # e.g.: + # 0x00 03 ff 05 00 01 02 03 04 + # would yield the bytes: + # 0xff ff ff 00 01 02 03 04 + # + # which are then interpreted as a bit packed mode '1' image + + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + + img = io.BytesIO() + blank_line = bytearray((0xFF,) * ((self.state.xsize + 7) // 8)) + try: + self.fd.seek(32) + rowmap = struct.unpack_from( + f"<{self.state.ysize}H", self.fd.read(self.state.ysize * 2) + ) + except struct.error as e: + msg = "Truncated MSP file in row map" + raise OSError(msg) from e + + for x, rowlen in enumerate(rowmap): + try: + if rowlen == 0: + img.write(blank_line) + continue + row = self.fd.read(rowlen) + if len(row) != rowlen: + msg = f"Truncated MSP file, expected {rowlen} bytes on row {x}" + raise OSError(msg) + idx = 0 + while idx < rowlen: + runtype = row[idx] + idx += 1 + if runtype == 0: + (runcount, runval) = struct.unpack_from("Bc", row, idx) + img.write(runval * runcount) + idx += 2 + else: + runcount = runtype + img.write(row[idx : idx + runcount]) + idx += runcount + + except struct.error as e: + msg = f"Corrupted MSP file in row {x}" + raise OSError(msg) from e + + self.set_as_raw(img.getvalue(), "1") + + return -1, 0 + + +Image.register_decoder("MSP", MspDecoder) + + +# +# write MSP files (uncompressed only) + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode != "1": + msg = f"cannot write mode {im.mode} as MSP" + raise OSError(msg) + + # create MSP header + header = [0] * 16 + + header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1 + header[2], header[3] = im.size + header[4], header[5] = 1, 1 + header[6], header[7] = 1, 1 + header[8], header[9] = im.size + + checksum = 0 + for h in header: + checksum = checksum ^ h + header[12] = checksum # FIXME: is this the right field? + + # header + for h in header: + fp.write(o16(h)) + + # image body + ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, "1")]) + + +# +# registry + +Image.register_open(MspImageFile.format, MspImageFile, _accept) +Image.register_save(MspImageFile.format, _save) + +Image.register_extension(MspImageFile.format, ".msp") diff --git a/venv/Lib/site-packages/PIL/PSDraw.py b/venv/Lib/site-packages/PIL/PSDraw.py new file mode 100644 index 00000000..7fd4c5c9 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PSDraw.py @@ -0,0 +1,237 @@ +# +# The Python Imaging Library +# $Id$ +# +# Simple PostScript graphics interface +# +# History: +# 1996-04-20 fl Created +# 1999-01-10 fl Added gsave/grestore to image method +# 2005-05-04 fl Fixed floating point issue in image (from Eric Etheridge) +# +# Copyright (c) 1997-2005 by Secret Labs AB. All rights reserved. +# Copyright (c) 1996 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import sys +from typing import IO + +from . import EpsImagePlugin + +TYPE_CHECKING = False + + +## +# Simple PostScript graphics interface. + + +class PSDraw: + """ + Sets up printing to the given file. If ``fp`` is omitted, + ``sys.stdout.buffer`` is assumed. + """ + + def __init__(self, fp: IO[bytes] | None = None) -> None: + if not fp: + fp = sys.stdout.buffer + self.fp = fp + + def begin_document(self, id: str | None = None) -> None: + """Set up printing of a document. (Write PostScript DSC header.)""" + # FIXME: incomplete + self.fp.write( + b"%!PS-Adobe-3.0\n" + b"save\n" + b"/showpage { } def\n" + b"%%EndComments\n" + b"%%BeginDocument\n" + ) + # self.fp.write(ERROR_PS) # debugging! + self.fp.write(EDROFF_PS) + self.fp.write(VDI_PS) + self.fp.write(b"%%EndProlog\n") + self.isofont: dict[bytes, int] = {} + + def end_document(self) -> None: + """Ends printing. (Write PostScript DSC footer.)""" + self.fp.write(b"%%EndDocument\nrestore showpage\n%%End\n") + if hasattr(self.fp, "flush"): + self.fp.flush() + + def setfont(self, font: str, size: int) -> None: + """ + Selects which font to use. + + :param font: A PostScript font name + :param size: Size in points. + """ + font_bytes = bytes(font, "UTF-8") + if font_bytes not in self.isofont: + # reencode font + self.fp.write( + b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font_bytes, font_bytes) + ) + self.isofont[font_bytes] = 1 + # rough + self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font_bytes)) + + def line(self, xy0: tuple[int, int], xy1: tuple[int, int]) -> None: + """ + Draws a line between the two points. Coordinates are given in + PostScript point coordinates (72 points per inch, (0, 0) is the lower + left corner of the page). + """ + self.fp.write(b"%d %d %d %d Vl\n" % (*xy0, *xy1)) + + def rectangle(self, box: tuple[int, int, int, int]) -> None: + """ + Draws a rectangle. + + :param box: A tuple of four integers, specifying left, bottom, width and + height. + """ + self.fp.write(b"%d %d M 0 %d %d Vr\n" % box) + + def text(self, xy: tuple[int, int], text: str) -> None: + """ + Draws text at the given position. You must use + :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method. + """ + text_bytes = bytes(text, "UTF-8") + text_bytes = b"\\(".join(text_bytes.split(b"(")) + text_bytes = b"\\)".join(text_bytes.split(b")")) + self.fp.write(b"%d %d M (%s) S\n" % (xy + (text_bytes,))) + + if TYPE_CHECKING: + from . import Image + + def image( + self, box: tuple[int, int, int, int], im: Image.Image, dpi: int | None = None + ) -> None: + """Draw a PIL image, centered in the given box.""" + # default resolution depends on mode + if not dpi: + if im.mode == "1": + dpi = 200 # fax + else: + dpi = 100 # grayscale + # image size (on paper) + x = im.size[0] * 72 / dpi + y = im.size[1] * 72 / dpi + # max allowed size + xmax = float(box[2] - box[0]) + ymax = float(box[3] - box[1]) + if x > xmax: + y = y * xmax / x + x = xmax + if y > ymax: + x = x * ymax / y + y = ymax + dx = (xmax - x) / 2 + box[0] + dy = (ymax - y) / 2 + box[1] + self.fp.write(b"gsave\n%f %f translate\n" % (dx, dy)) + if (x, y) != im.size: + # EpsImagePlugin._save prints the image at (0,0,xsize,ysize) + sx = x / im.size[0] + sy = y / im.size[1] + self.fp.write(b"%f %f scale\n" % (sx, sy)) + EpsImagePlugin._save(im, self.fp, "", 0) + self.fp.write(b"\ngrestore\n") + + +# -------------------------------------------------------------------- +# PostScript driver + +# +# EDROFF.PS -- PostScript driver for Edroff 2 +# +# History: +# 94-01-25 fl: created (edroff 2.04) +# +# Copyright (c) Fredrik Lundh 1994. +# + + +EDROFF_PS = b"""\ +/S { show } bind def +/P { moveto show } bind def +/M { moveto } bind def +/X { 0 rmoveto } bind def +/Y { 0 exch rmoveto } bind def +/E { findfont + dup maxlength dict begin + { + 1 index /FID ne { def } { pop pop } ifelse + } forall + /Encoding exch def + dup /FontName exch def + currentdict end definefont pop +} bind def +/F { findfont exch scalefont dup setfont + [ exch /setfont cvx ] cvx bind def +} bind def +""" + +# +# VDI.PS -- PostScript driver for VDI meta commands +# +# History: +# 94-01-25 fl: created (edroff 2.04) +# +# Copyright (c) Fredrik Lundh 1994. +# + +VDI_PS = b"""\ +/Vm { moveto } bind def +/Va { newpath arcn stroke } bind def +/Vl { moveto lineto stroke } bind def +/Vc { newpath 0 360 arc closepath } bind def +/Vr { exch dup 0 rlineto + exch dup 0 exch rlineto + exch neg 0 rlineto + 0 exch neg rlineto + setgray fill } bind def +/Tm matrix def +/Ve { Tm currentmatrix pop + translate scale newpath 0 0 .5 0 360 arc closepath + Tm setmatrix +} bind def +/Vf { currentgray exch setgray fill setgray } bind def +""" + +# +# ERROR.PS -- Error handler +# +# History: +# 89-11-21 fl: created (pslist 1.10) +# + +ERROR_PS = b"""\ +/landscape false def +/errorBUF 200 string def +/errorNL { currentpoint 10 sub exch pop 72 exch moveto } def +errordict begin /handleerror { + initmatrix /Courier findfont 10 scalefont setfont + newpath 72 720 moveto $error begin /newerror false def + (PostScript Error) show errorNL errorNL + (Error: ) show + /errorname load errorBUF cvs show errorNL errorNL + (Command: ) show + /command load dup type /stringtype ne { errorBUF cvs } if show + errorNL errorNL + (VMstatus: ) show + vmstatus errorBUF cvs show ( bytes available, ) show + errorBUF cvs show ( bytes used at level ) show + errorBUF cvs show errorNL errorNL + (Operand stargck: ) show errorNL /ostargck load { + dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL + } forall errorNL + (Execution stargck: ) show errorNL /estargck load { + dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL + } forall + end showpage +} def end +""" diff --git a/venv/Lib/site-packages/PIL/PaletteFile.py b/venv/Lib/site-packages/PIL/PaletteFile.py new file mode 100644 index 00000000..2a26e5d4 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PaletteFile.py @@ -0,0 +1,54 @@ +# +# Python Imaging Library +# $Id$ +# +# stuff to read simple, teragon-style palette files +# +# History: +# 97-08-23 fl Created +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from typing import IO + +from ._binary import o8 + + +class PaletteFile: + """File handler for Teragon-style palette files.""" + + rawmode = "RGB" + + def __init__(self, fp: IO[bytes]) -> None: + palette = [o8(i) * 3 for i in range(256)] + + while True: + s = fp.readline() + + if not s: + break + if s.startswith(b"#"): + continue + if len(s) > 100: + msg = "bad palette file" + raise SyntaxError(msg) + + v = [int(x) for x in s.split()] + try: + [i, r, g, b] = v + except ValueError: + [i, r] = v + g = b = r + + if 0 <= i <= 255: + palette[i] = o8(r) + o8(g) + o8(b) + + self.palette = b"".join(palette) + + def getpalette(self) -> tuple[bytes, str]: + return self.palette, self.rawmode diff --git a/venv/Lib/site-packages/PIL/PalmImagePlugin.py b/venv/Lib/site-packages/PIL/PalmImagePlugin.py new file mode 100644 index 00000000..15f71290 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PalmImagePlugin.py @@ -0,0 +1,217 @@ +# +# The Python Imaging Library. +# $Id$ +# + +## +# Image plugin for Palm pixmap images (output only). +## +from __future__ import annotations + +from typing import IO + +from . import Image, ImageFile +from ._binary import o8 +from ._binary import o16be as o16b + +# fmt: off +_Palm8BitColormapValues = ( + (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255), + (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204), + (255, 153, 204), (255, 102, 204), (255, 51, 204), (255, 0, 204), + (255, 255, 153), (255, 204, 153), (255, 153, 153), (255, 102, 153), + (255, 51, 153), (255, 0, 153), (204, 255, 255), (204, 204, 255), + (204, 153, 255), (204, 102, 255), (204, 51, 255), (204, 0, 255), + (204, 255, 204), (204, 204, 204), (204, 153, 204), (204, 102, 204), + (204, 51, 204), (204, 0, 204), (204, 255, 153), (204, 204, 153), + (204, 153, 153), (204, 102, 153), (204, 51, 153), (204, 0, 153), + (153, 255, 255), (153, 204, 255), (153, 153, 255), (153, 102, 255), + (153, 51, 255), (153, 0, 255), (153, 255, 204), (153, 204, 204), + (153, 153, 204), (153, 102, 204), (153, 51, 204), (153, 0, 204), + (153, 255, 153), (153, 204, 153), (153, 153, 153), (153, 102, 153), + (153, 51, 153), (153, 0, 153), (102, 255, 255), (102, 204, 255), + (102, 153, 255), (102, 102, 255), (102, 51, 255), (102, 0, 255), + (102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204), + (102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153), + (102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153), + (51, 255, 255), (51, 204, 255), (51, 153, 255), (51, 102, 255), + (51, 51, 255), (51, 0, 255), (51, 255, 204), (51, 204, 204), + (51, 153, 204), (51, 102, 204), (51, 51, 204), (51, 0, 204), + (51, 255, 153), (51, 204, 153), (51, 153, 153), (51, 102, 153), + (51, 51, 153), (51, 0, 153), (0, 255, 255), (0, 204, 255), + (0, 153, 255), (0, 102, 255), (0, 51, 255), (0, 0, 255), + (0, 255, 204), (0, 204, 204), (0, 153, 204), (0, 102, 204), + (0, 51, 204), (0, 0, 204), (0, 255, 153), (0, 204, 153), + (0, 153, 153), (0, 102, 153), (0, 51, 153), (0, 0, 153), + (255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102), + (255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51), + (255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51), + (255, 255, 0), (255, 204, 0), (255, 153, 0), (255, 102, 0), + (255, 51, 0), (255, 0, 0), (204, 255, 102), (204, 204, 102), + (204, 153, 102), (204, 102, 102), (204, 51, 102), (204, 0, 102), + (204, 255, 51), (204, 204, 51), (204, 153, 51), (204, 102, 51), + (204, 51, 51), (204, 0, 51), (204, 255, 0), (204, 204, 0), + (204, 153, 0), (204, 102, 0), (204, 51, 0), (204, 0, 0), + (153, 255, 102), (153, 204, 102), (153, 153, 102), (153, 102, 102), + (153, 51, 102), (153, 0, 102), (153, 255, 51), (153, 204, 51), + (153, 153, 51), (153, 102, 51), (153, 51, 51), (153, 0, 51), + (153, 255, 0), (153, 204, 0), (153, 153, 0), (153, 102, 0), + (153, 51, 0), (153, 0, 0), (102, 255, 102), (102, 204, 102), + (102, 153, 102), (102, 102, 102), (102, 51, 102), (102, 0, 102), + (102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51), + (102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0), + (102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0), + (51, 255, 102), (51, 204, 102), (51, 153, 102), (51, 102, 102), + (51, 51, 102), (51, 0, 102), (51, 255, 51), (51, 204, 51), + (51, 153, 51), (51, 102, 51), (51, 51, 51), (51, 0, 51), + (51, 255, 0), (51, 204, 0), (51, 153, 0), (51, 102, 0), + (51, 51, 0), (51, 0, 0), (0, 255, 102), (0, 204, 102), + (0, 153, 102), (0, 102, 102), (0, 51, 102), (0, 0, 102), + (0, 255, 51), (0, 204, 51), (0, 153, 51), (0, 102, 51), + (0, 51, 51), (0, 0, 51), (0, 255, 0), (0, 204, 0), + (0, 153, 0), (0, 102, 0), (0, 51, 0), (17, 17, 17), + (34, 34, 34), (68, 68, 68), (85, 85, 85), (119, 119, 119), + (136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221), + (238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128), + (0, 128, 0), (0, 128, 128), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)) +# fmt: on + + +# so build a prototype image to be used for palette resampling +def build_prototype_image() -> Image.Image: + image = Image.new("L", (1, len(_Palm8BitColormapValues))) + image.putdata(list(range(len(_Palm8BitColormapValues)))) + palettedata: tuple[int, ...] = () + for colormapValue in _Palm8BitColormapValues: + palettedata += colormapValue + palettedata += (0, 0, 0) * (256 - len(_Palm8BitColormapValues)) + image.putpalette(palettedata) + return image + + +Palm8BitColormapImage = build_prototype_image() + +# OK, we now have in Palm8BitColormapImage, +# a "P"-mode image with the right palette +# +# -------------------------------------------------------------------- + +_FLAGS = {"custom-colormap": 0x4000, "is-compressed": 0x8000, "has-transparent": 0x2000} + +_COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00} + + +# +# -------------------------------------------------------------------- + +## +# (Internal) Image save plugin for the Palm format. + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode == "P": + rawmode = "P" + bpp = 8 + version = 1 + + elif im.mode == "L": + if im.encoderinfo.get("bpp") in (1, 2, 4): + # this is 8-bit grayscale, so we shift it to get the high-order bits, + # and invert it because + # Palm does grayscale from white (0) to black (1) + bpp = im.encoderinfo["bpp"] + maxval = (1 << bpp) - 1 + shift = 8 - bpp + im = im.point(lambda x: maxval - (x >> shift)) + elif im.info.get("bpp") in (1, 2, 4): + # here we assume that even though the inherent mode is 8-bit grayscale, + # only the lower bpp bits are significant. + # We invert them to match the Palm. + bpp = im.info["bpp"] + maxval = (1 << bpp) - 1 + im = im.point(lambda x: maxval - (x & maxval)) + else: + msg = f"cannot write mode {im.mode} as Palm" + raise OSError(msg) + + # we ignore the palette here + im._mode = "P" + rawmode = f"P;{bpp}" + version = 1 + + elif im.mode == "1": + # monochrome -- write it inverted, as is the Palm standard + rawmode = "1;I" + bpp = 1 + version = 0 + + else: + msg = f"cannot write mode {im.mode} as Palm" + raise OSError(msg) + + # + # make sure image data is available + im.load() + + # write header + + cols = im.size[0] + rows = im.size[1] + + rowbytes = int((cols + (16 // bpp - 1)) / (16 // bpp)) * 2 + transparent_index = 0 + compression_type = _COMPRESSION_TYPES["none"] + + flags = 0 + if im.mode == "P": + flags |= _FLAGS["custom-colormap"] + colormap = im.im.getpalette() + colors = len(colormap) // 3 + colormapsize = 4 * colors + 2 + else: + colormapsize = 0 + + if "offset" in im.info: + offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4 + else: + offset = 0 + + fp.write(o16b(cols) + o16b(rows) + o16b(rowbytes) + o16b(flags)) + fp.write(o8(bpp)) + fp.write(o8(version)) + fp.write(o16b(offset)) + fp.write(o8(transparent_index)) + fp.write(o8(compression_type)) + fp.write(o16b(0)) # reserved by Palm + + # now write colormap if necessary + + if colormapsize: + fp.write(o16b(colors)) + for i in range(colors): + fp.write(o8(i)) + fp.write(colormap[3 * i : 3 * i + 3]) + + # now convert data to raw form + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))] + ) + + if hasattr(fp, "flush"): + fp.flush() + + +# +# -------------------------------------------------------------------- + +Image.register_save("Palm", _save) + +Image.register_extension("Palm", ".palm") + +Image.register_mime("Palm", "image/palm") diff --git a/venv/Lib/site-packages/PIL/PcdImagePlugin.py b/venv/Lib/site-packages/PIL/PcdImagePlugin.py new file mode 100644 index 00000000..3aa24998 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PcdImagePlugin.py @@ -0,0 +1,64 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PCD file handling +# +# History: +# 96-05-10 fl Created +# 96-05-27 fl Added draft mode (128x192, 256x384) +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image, ImageFile + +## +# Image plugin for PhotoCD images. This plugin only reads the 768x512 +# image from the file; higher resolutions are encoded in a proprietary +# encoding. + + +class PcdImageFile(ImageFile.ImageFile): + format = "PCD" + format_description = "Kodak PhotoCD" + + def _open(self) -> None: + # rough + assert self.fp is not None + + self.fp.seek(2048) + s = self.fp.read(2048) + + if not s.startswith(b"PCD_"): + msg = "not a PCD file" + raise SyntaxError(msg) + + orientation = s[1538] & 3 + self.tile_post_rotate = None + if orientation == 1: + self.tile_post_rotate = 90 + elif orientation == 3: + self.tile_post_rotate = -90 + + self._mode = "RGB" + self._size = 768, 512 # FIXME: not correct for rotated images! + self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)] + + def load_end(self) -> None: + if self.tile_post_rotate: + # Handle rotated PCDs + self.im = self.im.rotate(self.tile_post_rotate) + self._size = self.im.size + + +# +# registry + +Image.register_open(PcdImageFile.format, PcdImageFile) + +Image.register_extension(PcdImageFile.format, ".pcd") diff --git a/venv/Lib/site-packages/PIL/PcfFontFile.py b/venv/Lib/site-packages/PIL/PcfFontFile.py new file mode 100644 index 00000000..0d1968b1 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PcfFontFile.py @@ -0,0 +1,254 @@ +# +# THIS IS WORK IN PROGRESS +# +# The Python Imaging Library +# $Id$ +# +# portable compiled font file parser +# +# history: +# 1997-08-19 fl created +# 2003-09-13 fl fixed loading of unicode fonts +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1997-2003 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +from typing import BinaryIO, Callable + +from . import FontFile, Image +from ._binary import i8 +from ._binary import i16be as b16 +from ._binary import i16le as l16 +from ._binary import i32be as b32 +from ._binary import i32le as l32 + +# -------------------------------------------------------------------- +# declarations + +PCF_MAGIC = 0x70636601 # "\x01fcp" + +PCF_PROPERTIES = 1 << 0 +PCF_ACCELERATORS = 1 << 1 +PCF_METRICS = 1 << 2 +PCF_BITMAPS = 1 << 3 +PCF_INK_METRICS = 1 << 4 +PCF_BDF_ENCODINGS = 1 << 5 +PCF_SWIDTHS = 1 << 6 +PCF_GLYPH_NAMES = 1 << 7 +PCF_BDF_ACCELERATORS = 1 << 8 + +BYTES_PER_ROW: list[Callable[[int], int]] = [ + lambda bits: ((bits + 7) >> 3), + lambda bits: ((bits + 15) >> 3) & ~1, + lambda bits: ((bits + 31) >> 3) & ~3, + lambda bits: ((bits + 63) >> 3) & ~7, +] + + +def sz(s: bytes, o: int) -> bytes: + return s[o : s.index(b"\0", o)] + + +class PcfFontFile(FontFile.FontFile): + """Font file plugin for the X11 PCF format.""" + + name = "name" + + def __init__(self, fp: BinaryIO, charset_encoding: str = "iso8859-1"): + self.charset_encoding = charset_encoding + + magic = l32(fp.read(4)) + if magic != PCF_MAGIC: + msg = "not a PCF file" + raise SyntaxError(msg) + + super().__init__() + + count = l32(fp.read(4)) + self.toc = {} + for i in range(count): + type = l32(fp.read(4)) + self.toc[type] = l32(fp.read(4)), l32(fp.read(4)), l32(fp.read(4)) + + self.fp = fp + + self.info = self._load_properties() + + metrics = self._load_metrics() + bitmaps = self._load_bitmaps(metrics) + encoding = self._load_encoding() + + # + # create glyph structure + + for ch, ix in enumerate(encoding): + if ix is not None: + ( + xsize, + ysize, + left, + right, + width, + ascent, + descent, + attributes, + ) = metrics[ix] + self.glyph[ch] = ( + (width, 0), + (left, descent - ysize, xsize + left, descent), + (0, 0, xsize, ysize), + bitmaps[ix], + ) + + def _getformat( + self, tag: int + ) -> tuple[BinaryIO, int, Callable[[bytes], int], Callable[[bytes], int]]: + format, size, offset = self.toc[tag] + + fp = self.fp + fp.seek(offset) + + format = l32(fp.read(4)) + + if format & 4: + i16, i32 = b16, b32 + else: + i16, i32 = l16, l32 + + return fp, format, i16, i32 + + def _load_properties(self) -> dict[bytes, bytes | int]: + # + # font properties + + properties = {} + + fp, format, i16, i32 = self._getformat(PCF_PROPERTIES) + + nprops = i32(fp.read(4)) + + # read property description + p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)] + + if nprops & 3: + fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad + + data = fp.read(i32(fp.read(4))) + + for k, s, v in p: + property_value: bytes | int = sz(data, v) if s else v + properties[sz(data, k)] = property_value + + return properties + + def _load_metrics(self) -> list[tuple[int, int, int, int, int, int, int, int]]: + # + # font metrics + + metrics: list[tuple[int, int, int, int, int, int, int, int]] = [] + + fp, format, i16, i32 = self._getformat(PCF_METRICS) + + append = metrics.append + + if (format & 0xFF00) == 0x100: + # "compressed" metrics + for i in range(i16(fp.read(2))): + left = i8(fp.read(1)) - 128 + right = i8(fp.read(1)) - 128 + width = i8(fp.read(1)) - 128 + ascent = i8(fp.read(1)) - 128 + descent = i8(fp.read(1)) - 128 + xsize = right - left + ysize = ascent + descent + append((xsize, ysize, left, right, width, ascent, descent, 0)) + + else: + # "jumbo" metrics + for i in range(i32(fp.read(4))): + left = i16(fp.read(2)) + right = i16(fp.read(2)) + width = i16(fp.read(2)) + ascent = i16(fp.read(2)) + descent = i16(fp.read(2)) + attributes = i16(fp.read(2)) + xsize = right - left + ysize = ascent + descent + append((xsize, ysize, left, right, width, ascent, descent, attributes)) + + return metrics + + def _load_bitmaps( + self, metrics: list[tuple[int, int, int, int, int, int, int, int]] + ) -> list[Image.Image]: + # + # bitmap data + + fp, format, i16, i32 = self._getformat(PCF_BITMAPS) + + nbitmaps = i32(fp.read(4)) + + if nbitmaps != len(metrics): + msg = "Wrong number of bitmaps" + raise OSError(msg) + + offsets = [i32(fp.read(4)) for _ in range(nbitmaps)] + + bitmap_sizes = [i32(fp.read(4)) for _ in range(4)] + + # byteorder = format & 4 # non-zero => MSB + bitorder = format & 8 # non-zero => MSB + padindex = format & 3 + + bitmapsize = bitmap_sizes[padindex] + offsets.append(bitmapsize) + + data = fp.read(bitmapsize) + + pad = BYTES_PER_ROW[padindex] + mode = "1;R" + if bitorder: + mode = "1" + + bitmaps = [] + for i in range(nbitmaps): + xsize, ysize = metrics[i][:2] + b, e = offsets[i : i + 2] + bitmaps.append( + Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize)) + ) + + return bitmaps + + def _load_encoding(self) -> list[int | None]: + fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS) + + first_col, last_col = i16(fp.read(2)), i16(fp.read(2)) + first_row, last_row = i16(fp.read(2)), i16(fp.read(2)) + + i16(fp.read(2)) # default + + nencoding = (last_col - first_col + 1) * (last_row - first_row + 1) + + # map character code to bitmap index + encoding: list[int | None] = [None] * min(256, nencoding) + + encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)] + + for i in range(first_col, len(encoding)): + try: + encoding_offset = encoding_offsets[ + ord(bytearray([i]).decode(self.charset_encoding)) + ] + if encoding_offset != 0xFFFF: + encoding[i] = encoding_offset + except UnicodeDecodeError: + # character is not supported in selected encoding + pass + + return encoding diff --git a/venv/Lib/site-packages/PIL/PcxImagePlugin.py b/venv/Lib/site-packages/PIL/PcxImagePlugin.py new file mode 100644 index 00000000..458d586c --- /dev/null +++ b/venv/Lib/site-packages/PIL/PcxImagePlugin.py @@ -0,0 +1,228 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PCX file handling +# +# This format was originally used by ZSoft's popular PaintBrush +# program for the IBM PC. It is also supported by many MS-DOS and +# Windows applications, including the Windows PaintBrush program in +# Windows 3. +# +# history: +# 1995-09-01 fl Created +# 1996-05-20 fl Fixed RGB support +# 1997-01-03 fl Fixed 2-bit and 4-bit support +# 1999-02-03 fl Fixed 8-bit support (broken in 1.0b1) +# 1999-02-07 fl Added write support +# 2002-06-09 fl Made 2-bit and 4-bit support a bit more robust +# 2002-07-30 fl Seek from to current position, not beginning of file +# 2003-06-03 fl Extract DPI settings (info["dpi"]) +# +# Copyright (c) 1997-2003 by Secret Labs AB. +# Copyright (c) 1995-2003 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import logging +from typing import IO + +from . import Image, ImageFile, ImagePalette +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 + +logger = logging.getLogger(__name__) + + +def _accept(prefix: bytes) -> bool: + return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5] + + +## +# Image plugin for Paintbrush images. + + +class PcxImageFile(ImageFile.ImageFile): + format = "PCX" + format_description = "Paintbrush" + + def _open(self) -> None: + # header + assert self.fp is not None + + s = self.fp.read(68) + if not _accept(s): + msg = "not a PCX file" + raise SyntaxError(msg) + + # image + bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1 + if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]: + msg = "bad PCX image size" + raise SyntaxError(msg) + logger.debug("BBox: %s %s %s %s", *bbox) + + offset = self.fp.tell() + 60 + + # format + version = s[1] + bits = s[3] + planes = s[65] + provided_stride = i16(s, 66) + logger.debug( + "PCX version %s, bits %s, planes %s, stride %s", + version, + bits, + planes, + provided_stride, + ) + + self.info["dpi"] = i16(s, 12), i16(s, 14) + + if bits == 1 and planes == 1: + mode = rawmode = "1" + + elif bits == 1 and planes in (2, 4): + mode = "P" + rawmode = f"P;{planes}L" + self.palette = ImagePalette.raw("RGB", s[16:64]) + + elif version == 5 and bits == 8 and planes == 1: + mode = rawmode = "L" + # FIXME: hey, this doesn't work with the incremental loader !!! + self.fp.seek(-769, io.SEEK_END) + s = self.fp.read(769) + if len(s) == 769 and s[0] == 12: + # check if the palette is linear grayscale + for i in range(256): + if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3: + mode = rawmode = "P" + break + if mode == "P": + self.palette = ImagePalette.raw("RGB", s[1:]) + + elif version == 5 and bits == 8 and planes == 3: + mode = "RGB" + rawmode = "RGB;L" + + else: + msg = "unknown PCX mode" + raise OSError(msg) + + self._mode = mode + self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] + + # Don't trust the passed in stride. + # Calculate the approximate position for ourselves. + # CVE-2020-35653 + stride = (self._size[0] * bits + 7) // 8 + + # While the specification states that this must be even, + # not all images follow this + if provided_stride != stride: + stride += stride % 2 + + bbox = (0, 0) + self.size + logger.debug("size: %sx%s", *self.size) + + self.tile = [ImageFile._Tile("pcx", bbox, offset, (rawmode, planes * stride))] + + +# -------------------------------------------------------------------- +# save PCX files + + +SAVE = { + # mode: (version, bits, planes, raw mode) + "1": (2, 1, 1, "1"), + "L": (5, 8, 1, "L"), + "P": (5, 8, 1, "P"), + "RGB": (5, 8, 3, "RGB;L"), +} + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + try: + version, bits, planes, rawmode = SAVE[im.mode] + except KeyError as e: + msg = f"Cannot save {im.mode} images as PCX" + raise ValueError(msg) from e + + # bytes per plane + stride = (im.size[0] * bits + 7) // 8 + # stride should be even + stride += stride % 2 + # Stride needs to be kept in sync with the PcxEncode.c version. + # Ideally it should be passed in in the state, but the bytes value + # gets overwritten. + + logger.debug( + "PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d", + im.size[0], + bits, + stride, + ) + + # under windows, we could determine the current screen size with + # "Image.core.display_mode()[1]", but I think that's overkill... + + screen = im.size + + dpi = 100, 100 + + # PCX header + fp.write( + o8(10) + + o8(version) + + o8(1) + + o8(bits) + + o16(0) + + o16(0) + + o16(im.size[0] - 1) + + o16(im.size[1] - 1) + + o16(dpi[0]) + + o16(dpi[1]) + + b"\0" * 24 + + b"\xff" * 24 + + b"\0" + + o8(planes) + + o16(stride) + + o16(1) + + o16(screen[0]) + + o16(screen[1]) + + b"\0" * 54 + ) + + assert fp.tell() == 128 + + ImageFile._save( + im, fp, [ImageFile._Tile("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))] + ) + + if im.mode == "P": + # colour palette + fp.write(o8(12)) + palette = im.im.getpalette("RGB", "RGB") + palette += b"\x00" * (768 - len(palette)) + fp.write(palette) # 768 bytes + elif im.mode == "L": + # grayscale palette + fp.write(o8(12)) + for i in range(256): + fp.write(o8(i) * 3) + + +# -------------------------------------------------------------------- +# registry + + +Image.register_open(PcxImageFile.format, PcxImageFile, _accept) +Image.register_save(PcxImageFile.format, _save) + +Image.register_extension(PcxImageFile.format, ".pcx") + +Image.register_mime(PcxImageFile.format, "image/x-pcx") diff --git a/venv/Lib/site-packages/PIL/PdfImagePlugin.py b/venv/Lib/site-packages/PIL/PdfImagePlugin.py new file mode 100644 index 00000000..e9c20ddc --- /dev/null +++ b/venv/Lib/site-packages/PIL/PdfImagePlugin.py @@ -0,0 +1,311 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PDF (Acrobat) file handling +# +# History: +# 1996-07-16 fl Created +# 1997-01-18 fl Fixed header +# 2004-02-21 fl Fixes for 1/L/CMYK images, etc. +# 2004-02-24 fl Fixes for 1 and P images. +# +# Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved. +# Copyright (c) 1996-1997 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +## +# Image plugin for PDF images (output only). +## +from __future__ import annotations + +import io +import math +import os +import time +from typing import IO, Any + +from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features + +# +# -------------------------------------------------------------------- + +# object ids: +# 1. catalogue +# 2. pages +# 3. image +# 4. page +# 5. page contents + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, save_all=True) + + +## +# (Internal) Image save plugin for the PDF format. + + +def _write_image( + im: Image.Image, + filename: str | bytes, + existing_pdf: PdfParser.PdfParser, + image_refs: list[PdfParser.IndirectReference], +) -> tuple[PdfParser.IndirectReference, str]: + # FIXME: Should replace ASCIIHexDecode with RunLengthDecode + # (packbits) or LZWDecode (tiff/lzw compression). Note that + # PDF 1.2 also supports Flatedecode (zip compression). + + params = None + decode = None + + # + # Get image characteristics + + width, height = im.size + + dict_obj: dict[str, Any] = {"BitsPerComponent": 8} + if im.mode == "1": + if features.check("libtiff"): + decode_filter = "CCITTFaxDecode" + dict_obj["BitsPerComponent"] = 1 + params = PdfParser.PdfArray( + [ + PdfParser.PdfDict( + { + "K": -1, + "BlackIs1": True, + "Columns": width, + "Rows": height, + } + ) + ] + ) + else: + decode_filter = "DCTDecode" + dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray") + procset = "ImageB" # grayscale + elif im.mode == "L": + decode_filter = "DCTDecode" + # params = f"<< /Predictor 15 /Columns {width-2} >>" + dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray") + procset = "ImageB" # grayscale + elif im.mode == "LA": + decode_filter = "JPXDecode" + # params = f"<< /Predictor 15 /Columns {width-2} >>" + procset = "ImageB" # grayscale + dict_obj["SMaskInData"] = 1 + elif im.mode == "P": + decode_filter = "ASCIIHexDecode" + palette = im.getpalette() + assert palette is not None + dict_obj["ColorSpace"] = [ + PdfParser.PdfName("Indexed"), + PdfParser.PdfName("DeviceRGB"), + len(palette) // 3 - 1, + PdfParser.PdfBinary(palette), + ] + procset = "ImageI" # indexed color + + if "transparency" in im.info: + smask = im.convert("LA").getchannel("A") + smask.encoderinfo = {} + + image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0] + dict_obj["SMask"] = image_ref + elif im.mode == "RGB": + decode_filter = "DCTDecode" + dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB") + procset = "ImageC" # color images + elif im.mode == "RGBA": + decode_filter = "JPXDecode" + procset = "ImageC" # color images + dict_obj["SMaskInData"] = 1 + elif im.mode == "CMYK": + decode_filter = "DCTDecode" + dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK") + procset = "ImageC" # color images + decode = [1, 0, 1, 0, 1, 0, 1, 0] + else: + msg = f"cannot save mode {im.mode}" + raise ValueError(msg) + + # + # image + + op = io.BytesIO() + + if decode_filter == "ASCIIHexDecode": + ImageFile._save(im, op, [ImageFile._Tile("hex", (0, 0) + im.size, 0, im.mode)]) + elif decode_filter == "CCITTFaxDecode": + im.save( + op, + "TIFF", + compression="group4", + # use a single strip + strip_size=math.ceil(width / 8) * height, + ) + elif decode_filter == "DCTDecode": + Image.SAVE["JPEG"](im, op, filename) + elif decode_filter == "JPXDecode": + del dict_obj["BitsPerComponent"] + Image.SAVE["JPEG2000"](im, op, filename) + else: + msg = f"unsupported PDF filter ({decode_filter})" + raise ValueError(msg) + + stream = op.getvalue() + filter: PdfParser.PdfArray | PdfParser.PdfName + if decode_filter == "CCITTFaxDecode": + stream = stream[8:] + filter = PdfParser.PdfArray([PdfParser.PdfName(decode_filter)]) + else: + filter = PdfParser.PdfName(decode_filter) + + image_ref = image_refs.pop(0) + existing_pdf.write_obj( + image_ref, + stream=stream, + Type=PdfParser.PdfName("XObject"), + Subtype=PdfParser.PdfName("Image"), + Width=width, # * 72.0 / x_resolution, + Height=height, # * 72.0 / y_resolution, + Filter=filter, + Decode=decode, + DecodeParms=params, + **dict_obj, + ) + + return image_ref, procset + + +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False +) -> None: + is_appending = im.encoderinfo.get("append", False) + filename_str = filename.decode() if isinstance(filename, bytes) else filename + if is_appending: + existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="r+b") + else: + existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="w+b") + + dpi = im.encoderinfo.get("dpi") + if dpi: + x_resolution = dpi[0] + y_resolution = dpi[1] + else: + x_resolution = y_resolution = im.encoderinfo.get("resolution", 72.0) + + info = { + "title": ( + None if is_appending else os.path.splitext(os.path.basename(filename))[0] + ), + "author": None, + "subject": None, + "keywords": None, + "creator": None, + "producer": None, + "creationDate": None if is_appending else time.gmtime(), + "modDate": None if is_appending else time.gmtime(), + } + for k, default in info.items(): + v = im.encoderinfo.get(k) if k in im.encoderinfo else default + if v: + existing_pdf.info[k[0].upper() + k[1:]] = v + + # + # make sure image data is available + im.load() + + existing_pdf.start_writing() + existing_pdf.write_header() + existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver") + + # + # pages + ims = [im] + if save_all: + append_images = im.encoderinfo.get("append_images", []) + for append_im in append_images: + append_im.encoderinfo = im.encoderinfo.copy() + ims.append(append_im) + number_of_pages = 0 + image_refs = [] + page_refs = [] + contents_refs = [] + for im in ims: + im_number_of_pages = 1 + if save_all: + im_number_of_pages = getattr(im, "n_frames", 1) + number_of_pages += im_number_of_pages + for i in range(im_number_of_pages): + image_refs.append(existing_pdf.next_object_id(0)) + if im.mode == "P" and "transparency" in im.info: + image_refs.append(existing_pdf.next_object_id(0)) + + page_refs.append(existing_pdf.next_object_id(0)) + contents_refs.append(existing_pdf.next_object_id(0)) + existing_pdf.pages.append(page_refs[-1]) + + # + # catalog and list of pages + existing_pdf.write_catalog() + + page_number = 0 + for im_sequence in ims: + im_pages: ImageSequence.Iterator | list[Image.Image] = ( + ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] + ) + for im in im_pages: + image_ref, procset = _write_image(im, filename, existing_pdf, image_refs) + + # + # page + + existing_pdf.write_page( + page_refs[page_number], + Resources=PdfParser.PdfDict( + ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)], + XObject=PdfParser.PdfDict(image=image_ref), + ), + MediaBox=[ + 0, + 0, + im.width * 72.0 / x_resolution, + im.height * 72.0 / y_resolution, + ], + Contents=contents_refs[page_number], + ) + + # + # page contents + + page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % ( + im.width * 72.0 / x_resolution, + im.height * 72.0 / y_resolution, + ) + + existing_pdf.write_obj(contents_refs[page_number], stream=page_contents) + + page_number += 1 + + # + # trailer + existing_pdf.write_xref_and_trailer() + if hasattr(fp, "flush"): + fp.flush() + existing_pdf.close() + + +# +# -------------------------------------------------------------------- + + +Image.register_save("PDF", _save) +Image.register_save_all("PDF", _save_all) + +Image.register_extension("PDF", ".pdf") + +Image.register_mime("PDF", "application/pdf") diff --git a/venv/Lib/site-packages/PIL/PdfParser.py b/venv/Lib/site-packages/PIL/PdfParser.py new file mode 100644 index 00000000..73d8c21c --- /dev/null +++ b/venv/Lib/site-packages/PIL/PdfParser.py @@ -0,0 +1,1074 @@ +from __future__ import annotations + +import calendar +import codecs +import collections +import mmap +import os +import re +import time +import zlib +from typing import IO, Any, NamedTuple, Union + + +# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set +# on page 656 +def encode_text(s: str) -> bytes: + return codecs.BOM_UTF16_BE + s.encode("utf_16_be") + + +PDFDocEncoding = { + 0x16: "\u0017", + 0x18: "\u02d8", + 0x19: "\u02c7", + 0x1A: "\u02c6", + 0x1B: "\u02d9", + 0x1C: "\u02dd", + 0x1D: "\u02db", + 0x1E: "\u02da", + 0x1F: "\u02dc", + 0x80: "\u2022", + 0x81: "\u2020", + 0x82: "\u2021", + 0x83: "\u2026", + 0x84: "\u2014", + 0x85: "\u2013", + 0x86: "\u0192", + 0x87: "\u2044", + 0x88: "\u2039", + 0x89: "\u203a", + 0x8A: "\u2212", + 0x8B: "\u2030", + 0x8C: "\u201e", + 0x8D: "\u201c", + 0x8E: "\u201d", + 0x8F: "\u2018", + 0x90: "\u2019", + 0x91: "\u201a", + 0x92: "\u2122", + 0x93: "\ufb01", + 0x94: "\ufb02", + 0x95: "\u0141", + 0x96: "\u0152", + 0x97: "\u0160", + 0x98: "\u0178", + 0x99: "\u017d", + 0x9A: "\u0131", + 0x9B: "\u0142", + 0x9C: "\u0153", + 0x9D: "\u0161", + 0x9E: "\u017e", + 0xA0: "\u20ac", +} + + +def decode_text(b: bytes) -> str: + if b[: len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE: + return b[len(codecs.BOM_UTF16_BE) :].decode("utf_16_be") + else: + return "".join(PDFDocEncoding.get(byte, chr(byte)) for byte in b) + + +class PdfFormatError(RuntimeError): + """An error that probably indicates a syntactic or semantic error in the + PDF file structure""" + + pass + + +def check_format_condition(condition: bool, error_message: str) -> None: + if not condition: + raise PdfFormatError(error_message) + + +class IndirectReferenceTuple(NamedTuple): + object_id: int + generation: int + + +class IndirectReference(IndirectReferenceTuple): + def __str__(self) -> str: + return f"{self.object_id} {self.generation} R" + + def __bytes__(self) -> bytes: + return self.__str__().encode("us-ascii") + + def __eq__(self, other: object) -> bool: + if self.__class__ is not other.__class__: + return False + assert isinstance(other, IndirectReference) + return other.object_id == self.object_id and other.generation == self.generation + + def __ne__(self, other: object) -> bool: + return not (self == other) + + def __hash__(self) -> int: + return hash((self.object_id, self.generation)) + + +class IndirectObjectDef(IndirectReference): + def __str__(self) -> str: + return f"{self.object_id} {self.generation} obj" + + +class XrefTable: + def __init__(self) -> None: + self.existing_entries: dict[int, tuple[int, int]] = ( + {} + ) # object ID => (offset, generation) + self.new_entries: dict[int, tuple[int, int]] = ( + {} + ) # object ID => (offset, generation) + self.deleted_entries = {0: 65536} # object ID => generation + self.reading_finished = False + + def __setitem__(self, key: int, value: tuple[int, int]) -> None: + if self.reading_finished: + self.new_entries[key] = value + else: + self.existing_entries[key] = value + if key in self.deleted_entries: + del self.deleted_entries[key] + + def __getitem__(self, key: int) -> tuple[int, int]: + try: + return self.new_entries[key] + except KeyError: + return self.existing_entries[key] + + def __delitem__(self, key: int) -> None: + if key in self.new_entries: + generation = self.new_entries[key][1] + 1 + del self.new_entries[key] + self.deleted_entries[key] = generation + elif key in self.existing_entries: + generation = self.existing_entries[key][1] + 1 + self.deleted_entries[key] = generation + elif key in self.deleted_entries: + generation = self.deleted_entries[key] + else: + msg = f"object ID {key} cannot be deleted because it doesn't exist" + raise IndexError(msg) + + def __contains__(self, key: int) -> bool: + return key in self.existing_entries or key in self.new_entries + + def __len__(self) -> int: + return len( + set(self.existing_entries.keys()) + | set(self.new_entries.keys()) + | set(self.deleted_entries.keys()) + ) + + def keys(self) -> set[int]: + return ( + set(self.existing_entries.keys()) - set(self.deleted_entries.keys()) + ) | set(self.new_entries.keys()) + + def write(self, f: IO[bytes]) -> int: + keys = sorted(set(self.new_entries.keys()) | set(self.deleted_entries.keys())) + deleted_keys = sorted(set(self.deleted_entries.keys())) + startxref = f.tell() + f.write(b"xref\n") + while keys: + # find a contiguous sequence of object IDs + prev: int | None = None + for index, key in enumerate(keys): + if prev is None or prev + 1 == key: + prev = key + else: + contiguous_keys = keys[:index] + keys = keys[index:] + break + else: + contiguous_keys = keys + keys = [] + f.write(b"%d %d\n" % (contiguous_keys[0], len(contiguous_keys))) + for object_id in contiguous_keys: + if object_id in self.new_entries: + f.write(b"%010d %05d n \n" % self.new_entries[object_id]) + else: + this_deleted_object_id = deleted_keys.pop(0) + check_format_condition( + object_id == this_deleted_object_id, + f"expected the next deleted object ID to be {object_id}, " + f"instead found {this_deleted_object_id}", + ) + try: + next_in_linked_list = deleted_keys[0] + except IndexError: + next_in_linked_list = 0 + f.write( + b"%010d %05d f \n" + % (next_in_linked_list, self.deleted_entries[object_id]) + ) + return startxref + + +class PdfName: + name: bytes + + def __init__(self, name: PdfName | bytes | str) -> None: + if isinstance(name, PdfName): + self.name = name.name + elif isinstance(name, bytes): + self.name = name + else: + self.name = name.encode("us-ascii") + + def name_as_str(self) -> str: + return self.name.decode("us-ascii") + + def __eq__(self, other: object) -> bool: + return ( + isinstance(other, PdfName) and other.name == self.name + ) or other == self.name + + def __hash__(self) -> int: + return hash(self.name) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({repr(self.name)})" + + @classmethod + def from_pdf_stream(cls, data: bytes) -> PdfName: + return cls(PdfParser.interpret_name(data)) + + allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"} + + def __bytes__(self) -> bytes: + result = bytearray(b"/") + for b in self.name: + if b in self.allowed_chars: + result.append(b) + else: + result.extend(b"#%02X" % b) + return bytes(result) + + +class PdfArray(list[Any]): + def __bytes__(self) -> bytes: + return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]" + + +TYPE_CHECKING = False +if TYPE_CHECKING: + _DictBase = collections.UserDict[Union[str, bytes], Any] +else: + _DictBase = collections.UserDict + + +class PdfDict(_DictBase): + def __setattr__(self, key: str, value: Any) -> None: + if key == "data": + collections.UserDict.__setattr__(self, key, value) + else: + self[key.encode("us-ascii")] = value + + def __getattr__(self, key: str) -> str | time.struct_time: + try: + value = self[key.encode("us-ascii")] + except KeyError as e: + raise AttributeError(key) from e + if isinstance(value, bytes): + value = decode_text(value) + if key.endswith("Date"): + if value.startswith("D:"): + value = value[2:] + + relationship = "Z" + if len(value) > 17: + relationship = value[14] + offset = int(value[15:17]) * 60 + if len(value) > 20: + offset += int(value[18:20]) + + format = "%Y%m%d%H%M%S"[: len(value) - 2] + value = time.strptime(value[: len(format) + 2], format) + if relationship in ["+", "-"]: + offset *= 60 + if relationship == "+": + offset *= -1 + value = time.gmtime(calendar.timegm(value) + offset) + return value + + def __bytes__(self) -> bytes: + out = bytearray(b"<<") + for key, value in self.items(): + if value is None: + continue + value = pdf_repr(value) + out.extend(b"\n") + out.extend(bytes(PdfName(key))) + out.extend(b" ") + out.extend(value) + out.extend(b"\n>>") + return bytes(out) + + +class PdfBinary: + def __init__(self, data: list[int] | bytes) -> None: + self.data = data + + def __bytes__(self) -> bytes: + return b"<%s>" % b"".join(b"%02X" % b for b in self.data) + + +class PdfStream: + def __init__(self, dictionary: PdfDict, buf: bytes) -> None: + self.dictionary = dictionary + self.buf = buf + + def decode(self) -> bytes: + try: + filter = self.dictionary[b"Filter"] + except KeyError: + return self.buf + if filter == b"FlateDecode": + try: + expected_length = self.dictionary[b"DL"] + except KeyError: + expected_length = self.dictionary[b"Length"] + return zlib.decompress(self.buf, bufsize=int(expected_length)) + else: + msg = f"stream filter {repr(filter)} unknown/unsupported" + raise NotImplementedError(msg) + + +def pdf_repr(x: Any) -> bytes: + if x is True: + return b"true" + elif x is False: + return b"false" + elif x is None: + return b"null" + elif isinstance(x, (PdfName, PdfDict, PdfArray, PdfBinary)): + return bytes(x) + elif isinstance(x, (int, float)): + return str(x).encode("us-ascii") + elif isinstance(x, time.struct_time): + return b"(D:" + time.strftime("%Y%m%d%H%M%SZ", x).encode("us-ascii") + b")" + elif isinstance(x, dict): + return bytes(PdfDict(x)) + elif isinstance(x, list): + return bytes(PdfArray(x)) + elif isinstance(x, str): + return pdf_repr(encode_text(x)) + elif isinstance(x, bytes): + # XXX escape more chars? handle binary garbage + x = x.replace(b"\\", b"\\\\") + x = x.replace(b"(", b"\\(") + x = x.replace(b")", b"\\)") + return b"(" + x + b")" + else: + return bytes(x) + + +class PdfParser: + """Based on + https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf + Supports PDF up to 1.4 + """ + + def __init__( + self, + filename: str | None = None, + f: IO[bytes] | None = None, + buf: bytes | bytearray | None = None, + start_offset: int = 0, + mode: str = "rb", + ) -> None: + if buf and f: + msg = "specify buf or f or filename, but not both buf and f" + raise RuntimeError(msg) + self.filename = filename + self.buf: bytes | bytearray | mmap.mmap | None = buf + self.f = f + self.start_offset = start_offset + self.should_close_buf = False + self.should_close_file = False + if filename is not None and f is None: + self.f = f = open(filename, mode) + self.should_close_file = True + if f is not None: + self.buf = self.get_buf_from_file(f) + self.should_close_buf = True + if not filename and hasattr(f, "name"): + self.filename = f.name + self.cached_objects: dict[IndirectReference, Any] = {} + self.root_ref: IndirectReference | None + self.info_ref: IndirectReference | None + self.pages_ref: IndirectReference | None + self.last_xref_section_offset: int | None + if self.buf: + self.read_pdf_info() + else: + self.file_size_total = self.file_size_this = 0 + self.root = PdfDict() + self.root_ref = None + self.info = PdfDict() + self.info_ref = None + self.page_tree_root = PdfDict() + self.pages: list[IndirectReference] = [] + self.orig_pages: list[IndirectReference] = [] + self.pages_ref = None + self.last_xref_section_offset = None + self.trailer_dict: dict[bytes, Any] = {} + self.xref_table = XrefTable() + self.xref_table.reading_finished = True + if f: + self.seek_end() + + def __enter__(self) -> PdfParser: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def start_writing(self) -> None: + self.close_buf() + self.seek_end() + + def close_buf(self) -> None: + if isinstance(self.buf, mmap.mmap): + self.buf.close() + self.buf = None + + def close(self) -> None: + if self.should_close_buf: + self.close_buf() + if self.f is not None and self.should_close_file: + self.f.close() + self.f = None + + def seek_end(self) -> None: + assert self.f is not None + self.f.seek(0, os.SEEK_END) + + def write_header(self) -> None: + assert self.f is not None + self.f.write(b"%PDF-1.4\n") + + def write_comment(self, s: str) -> None: + assert self.f is not None + self.f.write(f"% {s}\n".encode()) + + def write_catalog(self) -> IndirectReference: + assert self.f is not None + self.del_root() + self.root_ref = self.next_object_id(self.f.tell()) + self.pages_ref = self.next_object_id(0) + self.rewrite_pages() + self.write_obj(self.root_ref, Type=PdfName(b"Catalog"), Pages=self.pages_ref) + self.write_obj( + self.pages_ref, + Type=PdfName(b"Pages"), + Count=len(self.pages), + Kids=self.pages, + ) + return self.root_ref + + def rewrite_pages(self) -> None: + pages_tree_nodes_to_delete = [] + for i, page_ref in enumerate(self.orig_pages): + page_info = self.cached_objects[page_ref] + del self.xref_table[page_ref.object_id] + pages_tree_nodes_to_delete.append(page_info[PdfName(b"Parent")]) + if page_ref not in self.pages: + # the page has been deleted + continue + # make dict keys into strings for passing to write_page + stringified_page_info = {} + for key, value in page_info.items(): + # key should be a PdfName + stringified_page_info[key.name_as_str()] = value + stringified_page_info["Parent"] = self.pages_ref + new_page_ref = self.write_page(None, **stringified_page_info) + for j, cur_page_ref in enumerate(self.pages): + if cur_page_ref == page_ref: + # replace the page reference with the new one + self.pages[j] = new_page_ref + # delete redundant Pages tree nodes from xref table + for pages_tree_node_ref in pages_tree_nodes_to_delete: + while pages_tree_node_ref: + pages_tree_node = self.cached_objects[pages_tree_node_ref] + if pages_tree_node_ref.object_id in self.xref_table: + del self.xref_table[pages_tree_node_ref.object_id] + pages_tree_node_ref = pages_tree_node.get(b"Parent", None) + self.orig_pages = [] + + def write_xref_and_trailer( + self, new_root_ref: IndirectReference | None = None + ) -> None: + assert self.f is not None + if new_root_ref: + self.del_root() + self.root_ref = new_root_ref + if self.info: + self.info_ref = self.write_obj(None, self.info) + start_xref = self.xref_table.write(self.f) + num_entries = len(self.xref_table) + trailer_dict: dict[str | bytes, Any] = { + b"Root": self.root_ref, + b"Size": num_entries, + } + if self.last_xref_section_offset is not None: + trailer_dict[b"Prev"] = self.last_xref_section_offset + if self.info: + trailer_dict[b"Info"] = self.info_ref + self.last_xref_section_offset = start_xref + self.f.write( + b"trailer\n" + + bytes(PdfDict(trailer_dict)) + + b"\nstartxref\n%d\n%%%%EOF" % start_xref + ) + + def write_page( + self, ref: int | IndirectReference | None, *objs: Any, **dict_obj: Any + ) -> IndirectReference: + obj_ref = self.pages[ref] if isinstance(ref, int) else ref + if "Type" not in dict_obj: + dict_obj["Type"] = PdfName(b"Page") + if "Parent" not in dict_obj: + dict_obj["Parent"] = self.pages_ref + return self.write_obj(obj_ref, *objs, **dict_obj) + + def write_obj( + self, ref: IndirectReference | None, *objs: Any, **dict_obj: Any + ) -> IndirectReference: + assert self.f is not None + f = self.f + if ref is None: + ref = self.next_object_id(f.tell()) + else: + self.xref_table[ref.object_id] = (f.tell(), ref.generation) + f.write(bytes(IndirectObjectDef(*ref))) + stream = dict_obj.pop("stream", None) + if stream is not None: + dict_obj["Length"] = len(stream) + if dict_obj: + f.write(pdf_repr(dict_obj)) + for obj in objs: + f.write(pdf_repr(obj)) + if stream is not None: + f.write(b"stream\n") + f.write(stream) + f.write(b"\nendstream\n") + f.write(b"endobj\n") + return ref + + def del_root(self) -> None: + if self.root_ref is None: + return + del self.xref_table[self.root_ref.object_id] + del self.xref_table[self.root[b"Pages"].object_id] + + @staticmethod + def get_buf_from_file(f: IO[bytes]) -> bytes | mmap.mmap: + if hasattr(f, "getbuffer"): + return f.getbuffer() + elif hasattr(f, "getvalue"): + return f.getvalue() + else: + try: + return mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + except ValueError: # cannot mmap an empty file + return b"" + + def read_pdf_info(self) -> None: + assert self.buf is not None + self.file_size_total = len(self.buf) + self.file_size_this = self.file_size_total - self.start_offset + self.read_trailer() + check_format_condition( + self.trailer_dict.get(b"Root") is not None, "Root is missing" + ) + self.root_ref = self.trailer_dict[b"Root"] + assert self.root_ref is not None + self.info_ref = self.trailer_dict.get(b"Info", None) + self.root = PdfDict(self.read_indirect(self.root_ref)) + if self.info_ref is None: + self.info = PdfDict() + else: + self.info = PdfDict(self.read_indirect(self.info_ref)) + check_format_condition(b"Type" in self.root, "/Type missing in Root") + check_format_condition( + self.root[b"Type"] == b"Catalog", "/Type in Root is not /Catalog" + ) + check_format_condition( + self.root.get(b"Pages") is not None, "/Pages missing in Root" + ) + check_format_condition( + isinstance(self.root[b"Pages"], IndirectReference), + "/Pages in Root is not an indirect reference", + ) + self.pages_ref = self.root[b"Pages"] + assert self.pages_ref is not None + self.page_tree_root = self.read_indirect(self.pages_ref) + self.pages = self.linearize_page_tree(self.page_tree_root) + # save the original list of page references + # in case the user modifies, adds or deletes some pages + # and we need to rewrite the pages and their list + self.orig_pages = self.pages[:] + + def next_object_id(self, offset: int | None = None) -> IndirectReference: + try: + # TODO: support reuse of deleted objects + reference = IndirectReference(max(self.xref_table.keys()) + 1, 0) + except ValueError: + reference = IndirectReference(1, 0) + if offset is not None: + self.xref_table[reference.object_id] = (offset, 0) + return reference + + delimiter = rb"[][()<>{}/%]" + delimiter_or_ws = rb"[][()<>{}/%\000\011\012\014\015\040]" + whitespace = rb"[\000\011\012\014\015\040]" + whitespace_or_hex = rb"[\000\011\012\014\015\0400-9a-fA-F]" + whitespace_optional = whitespace + b"*" + whitespace_mandatory = whitespace + b"+" + # No "\012" aka "\n" or "\015" aka "\r": + whitespace_optional_no_nl = rb"[\000\011\014\040]*" + newline_only = rb"[\r\n]+" + newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl + re_trailer_end = re.compile( + whitespace_mandatory + + rb"trailer" + + whitespace_optional + + rb"<<(.*>>)" + + newline + + rb"startxref" + + newline + + rb"([0-9]+)" + + newline + + rb"%%EOF" + + whitespace_optional + + rb"$", + re.DOTALL, + ) + re_trailer_prev = re.compile( + whitespace_optional + + rb"trailer" + + whitespace_optional + + rb"<<(.*?>>)" + + newline + + rb"startxref" + + newline + + rb"([0-9]+)" + + newline + + rb"%%EOF" + + whitespace_optional, + re.DOTALL, + ) + + def read_trailer(self) -> None: + assert self.buf is not None + search_start_offset = len(self.buf) - 16384 + if search_start_offset < self.start_offset: + search_start_offset = self.start_offset + m = self.re_trailer_end.search(self.buf, search_start_offset) + check_format_condition(m is not None, "trailer end not found") + # make sure we found the LAST trailer + last_match = m + while m: + last_match = m + m = self.re_trailer_end.search(self.buf, m.start() + 16) + if not m: + m = last_match + assert m is not None + trailer_data = m.group(1) + self.last_xref_section_offset = int(m.group(2)) + self.trailer_dict = self.interpret_trailer(trailer_data) + self.xref_table = XrefTable() + self.read_xref_table(xref_section_offset=self.last_xref_section_offset) + if b"Prev" in self.trailer_dict: + self.read_prev_trailer(self.trailer_dict[b"Prev"]) + + def read_prev_trailer(self, xref_section_offset: int) -> None: + assert self.buf is not None + trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset) + m = self.re_trailer_prev.search( + self.buf[trailer_offset : trailer_offset + 16384] + ) + check_format_condition(m is not None, "previous trailer not found") + assert m is not None + trailer_data = m.group(1) + check_format_condition( + int(m.group(2)) == xref_section_offset, + "xref section offset in previous trailer doesn't match what was expected", + ) + trailer_dict = self.interpret_trailer(trailer_data) + if b"Prev" in trailer_dict: + self.read_prev_trailer(trailer_dict[b"Prev"]) + + re_whitespace_optional = re.compile(whitespace_optional) + re_name = re.compile( + whitespace_optional + + rb"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?=" + + delimiter_or_ws + + rb")" + ) + re_dict_start = re.compile(whitespace_optional + rb"<<") + re_dict_end = re.compile(whitespace_optional + rb">>" + whitespace_optional) + + @classmethod + def interpret_trailer(cls, trailer_data: bytes) -> dict[bytes, Any]: + trailer = {} + offset = 0 + while True: + m = cls.re_name.match(trailer_data, offset) + if not m: + m = cls.re_dict_end.match(trailer_data, offset) + check_format_condition( + m is not None and m.end() == len(trailer_data), + "name not found in trailer, remaining data: " + + repr(trailer_data[offset:]), + ) + break + key = cls.interpret_name(m.group(1)) + assert isinstance(key, bytes) + value, value_offset = cls.get_value(trailer_data, m.end()) + trailer[key] = value + if value_offset is None: + break + offset = value_offset + check_format_condition( + b"Size" in trailer and isinstance(trailer[b"Size"], int), + "/Size not in trailer or not an integer", + ) + check_format_condition( + b"Root" in trailer and isinstance(trailer[b"Root"], IndirectReference), + "/Root not in trailer or not an indirect reference", + ) + return trailer + + re_hashes_in_name = re.compile(rb"([^#]*)(#([0-9a-fA-F]{2}))?") + + @classmethod + def interpret_name(cls, raw: bytes, as_text: bool = False) -> str | bytes: + name = b"" + for m in cls.re_hashes_in_name.finditer(raw): + if m.group(3): + name += m.group(1) + bytearray.fromhex(m.group(3).decode("us-ascii")) + else: + name += m.group(1) + if as_text: + return name.decode("utf-8") + else: + return bytes(name) + + re_null = re.compile(whitespace_optional + rb"null(?=" + delimiter_or_ws + rb")") + re_true = re.compile(whitespace_optional + rb"true(?=" + delimiter_or_ws + rb")") + re_false = re.compile(whitespace_optional + rb"false(?=" + delimiter_or_ws + rb")") + re_int = re.compile( + whitespace_optional + rb"([-+]?[0-9]+)(?=" + delimiter_or_ws + rb")" + ) + re_real = re.compile( + whitespace_optional + + rb"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?=" + + delimiter_or_ws + + rb")" + ) + re_array_start = re.compile(whitespace_optional + rb"\[") + re_array_end = re.compile(whitespace_optional + rb"]") + re_string_hex = re.compile( + whitespace_optional + rb"<(" + whitespace_or_hex + rb"*)>" + ) + re_string_lit = re.compile(whitespace_optional + rb"\(") + re_indirect_reference = re.compile( + whitespace_optional + + rb"([-+]?[0-9]+)" + + whitespace_mandatory + + rb"([-+]?[0-9]+)" + + whitespace_mandatory + + rb"R(?=" + + delimiter_or_ws + + rb")" + ) + re_indirect_def_start = re.compile( + whitespace_optional + + rb"([-+]?[0-9]+)" + + whitespace_mandatory + + rb"([-+]?[0-9]+)" + + whitespace_mandatory + + rb"obj(?=" + + delimiter_or_ws + + rb")" + ) + re_indirect_def_end = re.compile( + whitespace_optional + rb"endobj(?=" + delimiter_or_ws + rb")" + ) + re_comment = re.compile( + rb"(" + whitespace_optional + rb"%[^\r\n]*" + newline + rb")*" + ) + re_stream_start = re.compile(whitespace_optional + rb"stream\r?\n") + re_stream_end = re.compile( + whitespace_optional + rb"endstream(?=" + delimiter_or_ws + rb")" + ) + + @classmethod + def get_value( + cls, + data: bytes | bytearray | mmap.mmap, + offset: int, + expect_indirect: IndirectReference | None = None, + max_nesting: int = -1, + ) -> tuple[Any, int | None]: + if max_nesting == 0: + return None, None + m = cls.re_comment.match(data, offset) + if m: + offset = m.end() + m = cls.re_indirect_def_start.match(data, offset) + if m: + check_format_condition( + int(m.group(1)) > 0, + "indirect object definition: object ID must be greater than 0", + ) + check_format_condition( + int(m.group(2)) >= 0, + "indirect object definition: generation must be non-negative", + ) + check_format_condition( + expect_indirect is None + or expect_indirect + == IndirectReference(int(m.group(1)), int(m.group(2))), + "indirect object definition different than expected", + ) + object, object_offset = cls.get_value( + data, m.end(), max_nesting=max_nesting - 1 + ) + if object_offset is None: + return object, None + m = cls.re_indirect_def_end.match(data, object_offset) + check_format_condition( + m is not None, "indirect object definition end not found" + ) + assert m is not None + return object, m.end() + check_format_condition( + not expect_indirect, "indirect object definition not found" + ) + m = cls.re_indirect_reference.match(data, offset) + if m: + check_format_condition( + int(m.group(1)) > 0, + "indirect object reference: object ID must be greater than 0", + ) + check_format_condition( + int(m.group(2)) >= 0, + "indirect object reference: generation must be non-negative", + ) + return IndirectReference(int(m.group(1)), int(m.group(2))), m.end() + m = cls.re_dict_start.match(data, offset) + if m: + offset = m.end() + result: dict[Any, Any] = {} + m = cls.re_dict_end.match(data, offset) + current_offset: int | None = offset + while not m: + assert current_offset is not None + key, current_offset = cls.get_value( + data, current_offset, max_nesting=max_nesting - 1 + ) + if current_offset is None: + return result, None + value, current_offset = cls.get_value( + data, current_offset, max_nesting=max_nesting - 1 + ) + result[key] = value + if current_offset is None: + return result, None + m = cls.re_dict_end.match(data, current_offset) + current_offset = m.end() + m = cls.re_stream_start.match(data, current_offset) + if m: + stream_len = result.get(b"Length") + if stream_len is None or not isinstance(stream_len, int): + msg = f"bad or missing Length in stream dict ({stream_len})" + raise PdfFormatError(msg) + stream_data = data[m.end() : m.end() + stream_len] + m = cls.re_stream_end.match(data, m.end() + stream_len) + check_format_condition(m is not None, "stream end not found") + assert m is not None + current_offset = m.end() + return PdfStream(PdfDict(result), stream_data), current_offset + return PdfDict(result), current_offset + m = cls.re_array_start.match(data, offset) + if m: + offset = m.end() + results = [] + m = cls.re_array_end.match(data, offset) + current_offset = offset + while not m: + assert current_offset is not None + value, current_offset = cls.get_value( + data, current_offset, max_nesting=max_nesting - 1 + ) + results.append(value) + if current_offset is None: + return results, None + m = cls.re_array_end.match(data, current_offset) + return results, m.end() + m = cls.re_null.match(data, offset) + if m: + return None, m.end() + m = cls.re_true.match(data, offset) + if m: + return True, m.end() + m = cls.re_false.match(data, offset) + if m: + return False, m.end() + m = cls.re_name.match(data, offset) + if m: + return PdfName(cls.interpret_name(m.group(1))), m.end() + m = cls.re_int.match(data, offset) + if m: + return int(m.group(1)), m.end() + m = cls.re_real.match(data, offset) + if m: + # XXX Decimal instead of float??? + return float(m.group(1)), m.end() + m = cls.re_string_hex.match(data, offset) + if m: + # filter out whitespace + hex_string = bytearray( + b for b in m.group(1) if b in b"0123456789abcdefABCDEF" + ) + if len(hex_string) % 2 == 1: + # append a 0 if the length is not even - yes, at the end + hex_string.append(ord(b"0")) + return bytearray.fromhex(hex_string.decode("us-ascii")), m.end() + m = cls.re_string_lit.match(data, offset) + if m: + return cls.get_literal_string(data, m.end()) + # return None, offset # fallback (only for debugging) + msg = f"unrecognized object: {repr(data[offset : offset + 32])}" + raise PdfFormatError(msg) + + re_lit_str_token = re.compile( + rb"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))" + ) + escaped_chars = { + b"n": b"\n", + b"r": b"\r", + b"t": b"\t", + b"b": b"\b", + b"f": b"\f", + b"(": b"(", + b")": b")", + b"\\": b"\\", + ord(b"n"): b"\n", + ord(b"r"): b"\r", + ord(b"t"): b"\t", + ord(b"b"): b"\b", + ord(b"f"): b"\f", + ord(b"("): b"(", + ord(b")"): b")", + ord(b"\\"): b"\\", + } + + @classmethod + def get_literal_string( + cls, data: bytes | bytearray | mmap.mmap, offset: int + ) -> tuple[bytes, int]: + nesting_depth = 0 + result = bytearray() + for m in cls.re_lit_str_token.finditer(data, offset): + result.extend(data[offset : m.start()]) + if m.group(1): + result.extend(cls.escaped_chars[m.group(1)[1]]) + elif m.group(2): + result.append(int(m.group(2)[1:], 8)) + elif m.group(3): + pass + elif m.group(5): + result.extend(b"\n") + elif m.group(6): + result.extend(b"(") + nesting_depth += 1 + elif m.group(7): + if nesting_depth == 0: + return bytes(result), m.end() + result.extend(b")") + nesting_depth -= 1 + offset = m.end() + msg = "unfinished literal string" + raise PdfFormatError(msg) + + re_xref_section_start = re.compile(whitespace_optional + rb"xref" + newline) + re_xref_subsection_start = re.compile( + whitespace_optional + + rb"([0-9]+)" + + whitespace_mandatory + + rb"([0-9]+)" + + whitespace_optional + + newline_only + ) + re_xref_entry = re.compile(rb"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)") + + def read_xref_table(self, xref_section_offset: int) -> int: + assert self.buf is not None + subsection_found = False + m = self.re_xref_section_start.match( + self.buf, xref_section_offset + self.start_offset + ) + check_format_condition(m is not None, "xref section start not found") + assert m is not None + offset = m.end() + while True: + m = self.re_xref_subsection_start.match(self.buf, offset) + if not m: + check_format_condition( + subsection_found, "xref subsection start not found" + ) + break + subsection_found = True + offset = m.end() + first_object = int(m.group(1)) + num_objects = int(m.group(2)) + for i in range(first_object, first_object + num_objects): + m = self.re_xref_entry.match(self.buf, offset) + check_format_condition(m is not None, "xref entry not found") + assert m is not None + offset = m.end() + is_free = m.group(3) == b"f" + if not is_free: + generation = int(m.group(2)) + new_entry = (int(m.group(1)), generation) + if i not in self.xref_table: + self.xref_table[i] = new_entry + return offset + + def read_indirect(self, ref: IndirectReference, max_nesting: int = -1) -> Any: + offset, generation = self.xref_table[ref[0]] + check_format_condition( + generation == ref[1], + f"expected to find generation {ref[1]} for object ID {ref[0]} in xref " + f"table, instead found generation {generation} at offset {offset}", + ) + assert self.buf is not None + value = self.get_value( + self.buf, + offset + self.start_offset, + expect_indirect=IndirectReference(*ref), + max_nesting=max_nesting, + )[0] + self.cached_objects[ref] = value + return value + + def linearize_page_tree( + self, node: PdfDict | None = None + ) -> list[IndirectReference]: + page_node = node if node is not None else self.page_tree_root + check_format_condition( + page_node[b"Type"] == b"Pages", "/Type of page tree node is not /Pages" + ) + pages = [] + for kid in page_node[b"Kids"]: + kid_object = self.read_indirect(kid) + if kid_object[b"Type"] == b"Page": + pages.append(kid) + else: + pages.extend(self.linearize_page_tree(node=kid_object)) + return pages diff --git a/venv/Lib/site-packages/PIL/PixarImagePlugin.py b/venv/Lib/site-packages/PIL/PixarImagePlugin.py new file mode 100644 index 00000000..d2b6d0a9 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PixarImagePlugin.py @@ -0,0 +1,72 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PIXAR raster support for PIL +# +# history: +# 97-01-29 fl Created +# +# notes: +# This is incomplete; it is based on a few samples created with +# Photoshop 2.5 and 3.0, and a summary description provided by +# Greg Coats . Hopefully, "L" and +# "RGBA" support will be added in future versions. +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1997. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image, ImageFile +from ._binary import i16le as i16 + +# +# helpers + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"\200\350\000\000") + + +## +# Image plugin for PIXAR raster images. + + +class PixarImageFile(ImageFile.ImageFile): + format = "PIXAR" + format_description = "PIXAR raster image" + + def _open(self) -> None: + # assuming a 4-byte magic label + assert self.fp is not None + + s = self.fp.read(4) + if not _accept(s): + msg = "not a PIXAR file" + raise SyntaxError(msg) + + # read rest of header + s = s + self.fp.read(508) + + self._size = i16(s, 418), i16(s, 416) + + # get channel/depth descriptions + mode = i16(s, 424), i16(s, 426) + + if mode == (14, 2): + self._mode = "RGB" + # FIXME: to be continued... + + # create tile descriptor (assuming "dumped") + self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)] + + +# +# -------------------------------------------------------------------- + +Image.register_open(PixarImageFile.format, PixarImageFile, _accept) + +Image.register_extension(PixarImageFile.format, ".pxr") diff --git a/venv/Lib/site-packages/PIL/PngImagePlugin.py b/venv/Lib/site-packages/PIL/PngImagePlugin.py new file mode 100644 index 00000000..1b9a89ae --- /dev/null +++ b/venv/Lib/site-packages/PIL/PngImagePlugin.py @@ -0,0 +1,1551 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PNG support code +# +# See "PNG (Portable Network Graphics) Specification, version 1.0; +# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.). +# +# history: +# 1996-05-06 fl Created (couldn't resist it) +# 1996-12-14 fl Upgraded, added read and verify support (0.2) +# 1996-12-15 fl Separate PNG stream parser +# 1996-12-29 fl Added write support, added getchunks +# 1996-12-30 fl Eliminated circular references in decoder (0.3) +# 1998-07-12 fl Read/write 16-bit images as mode I (0.4) +# 2001-02-08 fl Added transparency support (from Zircon) (0.5) +# 2001-04-16 fl Don't close data source in "open" method (0.6) +# 2004-02-24 fl Don't even pretend to support interlaced files (0.7) +# 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8) +# 2004-09-20 fl Added PngInfo chunk container +# 2004-12-18 fl Added DPI read support (based on code by Niki Spahiev) +# 2008-08-13 fl Added tRNS support for RGB images +# 2009-03-06 fl Support for preserving ICC profiles (by Florian Hoech) +# 2009-03-08 fl Added zTXT support (from Lowell Alleman) +# 2009-03-29 fl Read interlaced PNG files (from Conrado Porto Lopes Gouvua) +# +# Copyright (c) 1997-2009 by Secret Labs AB +# Copyright (c) 1996 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import itertools +import logging +import re +import struct +import warnings +import zlib +from collections.abc import Callable +from enum import IntEnum +from typing import IO, Any, NamedTuple, NoReturn, cast + +from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 +from ._binary import o16be as o16 +from ._binary import o32be as o32 +from ._deprecate import deprecate +from ._util import DeferredError + +TYPE_CHECKING = False +if TYPE_CHECKING: + from . import _imaging + +logger = logging.getLogger(__name__) + +is_cid = re.compile(rb"\w\w\w\w").match + + +_MAGIC = b"\211PNG\r\n\032\n" + + +_MODES = { + # supported bits/color combinations, and corresponding modes/rawmodes + # Grayscale + (1, 0): ("1", "1"), + (2, 0): ("L", "L;2"), + (4, 0): ("L", "L;4"), + (8, 0): ("L", "L"), + (16, 0): ("I;16", "I;16B"), + # Truecolour + (8, 2): ("RGB", "RGB"), + (16, 2): ("RGB", "RGB;16B"), + # Indexed-colour + (1, 3): ("P", "P;1"), + (2, 3): ("P", "P;2"), + (4, 3): ("P", "P;4"), + (8, 3): ("P", "P"), + # Grayscale with alpha + (8, 4): ("LA", "LA"), + (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available + # Truecolour with alpha + (8, 6): ("RGBA", "RGBA"), + (16, 6): ("RGBA", "RGBA;16B"), +} + + +_simple_palette = re.compile(b"^\xff*\x00\xff*$") + +MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK +""" +Maximum decompressed size for a iTXt or zTXt chunk. +Eliminates decompression bombs where compressed chunks can expand 1000x. +See :ref:`Text in PNG File Format`. +""" +MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK +""" +Set the maximum total text chunk size. +See :ref:`Text in PNG File Format`. +""" + + +# APNG frame disposal modes +class Disposal(IntEnum): + OP_NONE = 0 + """ + No disposal is done on this frame before rendering the next frame. + See :ref:`Saving APNG sequences`. + """ + OP_BACKGROUND = 1 + """ + This frame’s modified region is cleared to fully transparent black before rendering + the next frame. + See :ref:`Saving APNG sequences`. + """ + OP_PREVIOUS = 2 + """ + This frame’s modified region is reverted to the previous frame’s contents before + rendering the next frame. + See :ref:`Saving APNG sequences`. + """ + + +# APNG frame blend modes +class Blend(IntEnum): + OP_SOURCE = 0 + """ + All color components of this frame, including alpha, overwrite the previous output + image contents. + See :ref:`Saving APNG sequences`. + """ + OP_OVER = 1 + """ + This frame should be alpha composited with the previous output image contents. + See :ref:`Saving APNG sequences`. + """ + + +def _safe_zlib_decompress(s: bytes) -> bytes: + dobj = zlib.decompressobj() + plaintext = dobj.decompress(s, MAX_TEXT_CHUNK) + if dobj.unconsumed_tail: + msg = "Decompressed data too large for PngImagePlugin.MAX_TEXT_CHUNK" + raise ValueError(msg) + return plaintext + + +def _crc32(data: bytes, seed: int = 0) -> int: + return zlib.crc32(data, seed) & 0xFFFFFFFF + + +# -------------------------------------------------------------------- +# Support classes. Suitable for PNG and related formats like MNG etc. + + +class ChunkStream: + def __init__(self, fp: IO[bytes]) -> None: + self.fp: IO[bytes] | None = fp + self.queue: list[tuple[bytes, int, int]] | None = [] + + def read(self) -> tuple[bytes, int, int]: + """Fetch a new chunk. Returns header information.""" + cid = None + + assert self.fp is not None + if self.queue: + cid, pos, length = self.queue.pop() + self.fp.seek(pos) + else: + s = self.fp.read(8) + cid = s[4:] + pos = self.fp.tell() + length = i32(s) + + if not is_cid(cid): + if not ImageFile.LOAD_TRUNCATED_IMAGES: + msg = f"broken PNG file (chunk {repr(cid)})" + raise SyntaxError(msg) + + return cid, pos, length + + def __enter__(self) -> ChunkStream: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def close(self) -> None: + self.queue = self.fp = None + + def push(self, cid: bytes, pos: int, length: int) -> None: + assert self.queue is not None + self.queue.append((cid, pos, length)) + + def call(self, cid: bytes, pos: int, length: int) -> bytes: + """Call the appropriate chunk handler""" + + logger.debug("STREAM %r %s %s", cid, pos, length) + return getattr(self, f"chunk_{cid.decode('ascii')}")(pos, length) + + def crc(self, cid: bytes, data: bytes) -> None: + """Read and verify checksum""" + + # Skip CRC checks for ancillary chunks if allowed to load truncated + # images + # 5th byte of first char is 1 [specs, section 5.4] + if ImageFile.LOAD_TRUNCATED_IMAGES and (cid[0] >> 5 & 1): + self.crc_skip(cid, data) + return + + assert self.fp is not None + try: + crc1 = _crc32(data, _crc32(cid)) + crc2 = i32(self.fp.read(4)) + if crc1 != crc2: + msg = f"broken PNG file (bad header checksum in {repr(cid)})" + raise SyntaxError(msg) + except struct.error as e: + msg = f"broken PNG file (incomplete checksum in {repr(cid)})" + raise SyntaxError(msg) from e + + def crc_skip(self, cid: bytes, data: bytes) -> None: + """Read checksum""" + + assert self.fp is not None + self.fp.read(4) + + def verify(self, endchunk: bytes = b"IEND") -> list[bytes]: + # Simple approach; just calculate checksum for all remaining + # blocks. Must be called directly after open. + + cids = [] + + assert self.fp is not None + while True: + try: + cid, pos, length = self.read() + except struct.error as e: + msg = "truncated PNG file" + raise OSError(msg) from e + + if cid == endchunk: + break + self.crc(cid, ImageFile._safe_read(self.fp, length)) + cids.append(cid) + + return cids + + +class iTXt(str): + """ + Subclass of string to allow iTXt chunks to look like strings while + keeping their extra information + + """ + + lang: str | bytes | None + tkey: str | bytes | None + + @staticmethod + def __new__( + cls, text: str, lang: str | None = None, tkey: str | None = None + ) -> iTXt: + """ + :param cls: the class to use when creating the instance + :param text: value for this key + :param lang: language code + :param tkey: UTF-8 version of the key name + """ + + self = str.__new__(cls, text) + self.lang = lang + self.tkey = tkey + return self + + +class PngInfo: + """ + PNG chunk container (for use with save(pnginfo=)) + + """ + + def __init__(self) -> None: + self.chunks: list[tuple[bytes, bytes, bool]] = [] + + def add(self, cid: bytes, data: bytes, after_idat: bool = False) -> None: + """Appends an arbitrary chunk. Use with caution. + + :param cid: a byte string, 4 bytes long. + :param data: a byte string of the encoded data + :param after_idat: for use with private chunks. Whether the chunk + should be written after IDAT + + """ + + self.chunks.append((cid, data, after_idat)) + + def add_itxt( + self, + key: str | bytes, + value: str | bytes, + lang: str | bytes = "", + tkey: str | bytes = "", + zip: bool = False, + ) -> None: + """Appends an iTXt chunk. + + :param key: latin-1 encodable text key name + :param value: value for this key + :param lang: language code + :param tkey: UTF-8 version of the key name + :param zip: compression flag + + """ + + if not isinstance(key, bytes): + key = key.encode("latin-1", "strict") + if not isinstance(value, bytes): + value = value.encode("utf-8", "strict") + if not isinstance(lang, bytes): + lang = lang.encode("utf-8", "strict") + if not isinstance(tkey, bytes): + tkey = tkey.encode("utf-8", "strict") + + if zip: + self.add( + b"iTXt", + key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value), + ) + else: + self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) + + def add_text( + self, key: str | bytes, value: str | bytes | iTXt, zip: bool = False + ) -> None: + """Appends a text chunk. + + :param key: latin-1 encodable text key name + :param value: value for this key, text or an + :py:class:`PIL.PngImagePlugin.iTXt` instance + :param zip: compression flag + + """ + if isinstance(value, iTXt): + return self.add_itxt( + key, + value, + value.lang if value.lang is not None else b"", + value.tkey if value.tkey is not None else b"", + zip=zip, + ) + + # The tEXt chunk stores latin-1 text + if not isinstance(value, bytes): + try: + value = value.encode("latin-1", "strict") + except UnicodeError: + return self.add_itxt(key, value, zip=zip) + + if not isinstance(key, bytes): + key = key.encode("latin-1", "strict") + + if zip: + self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) + else: + self.add(b"tEXt", key + b"\0" + value) + + +# -------------------------------------------------------------------- +# PNG image stream (IHDR/IEND) + + +class _RewindState(NamedTuple): + info: dict[str | tuple[int, int], Any] + tile: list[ImageFile._Tile] + seq_num: int | None + + +class PngStream(ChunkStream): + def __init__(self, fp: IO[bytes]) -> None: + super().__init__(fp) + + # local copies of Image attributes + self.im_info: dict[str | tuple[int, int], Any] = {} + self.im_text: dict[str, str | iTXt] = {} + self.im_size = (0, 0) + self.im_mode = "" + self.im_tile: list[ImageFile._Tile] = [] + self.im_palette: tuple[str, bytes] | None = None + self.im_custom_mimetype: str | None = None + self.im_n_frames: int | None = None + self._seq_num: int | None = None + self.rewind_state = _RewindState({}, [], None) + + self.text_memory = 0 + + def check_text_memory(self, chunklen: int) -> None: + self.text_memory += chunklen + if self.text_memory > MAX_TEXT_MEMORY: + msg = ( + "Too much memory used in text chunks: " + f"{self.text_memory}>MAX_TEXT_MEMORY" + ) + raise ValueError(msg) + + def save_rewind(self) -> None: + self.rewind_state = _RewindState( + self.im_info.copy(), + self.im_tile, + self._seq_num, + ) + + def rewind(self) -> None: + self.im_info = self.rewind_state.info.copy() + self.im_tile = self.rewind_state.tile + self._seq_num = self.rewind_state.seq_num + + def chunk_iCCP(self, pos: int, length: int) -> bytes: + # ICC profile + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + # according to PNG spec, the iCCP chunk contains: + # Profile name 1-79 bytes (character string) + # Null separator 1 byte (null character) + # Compression method 1 byte (0) + # Compressed profile n bytes (zlib with deflate compression) + i = s.find(b"\0") + logger.debug("iCCP profile name %r", s[:i]) + comp_method = s[i + 1] + logger.debug("Compression method %s", comp_method) + if comp_method != 0: + msg = f"Unknown compression method {comp_method} in iCCP chunk" + raise SyntaxError(msg) + try: + icc_profile = _safe_zlib_decompress(s[i + 2 :]) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + icc_profile = None + else: + raise + except zlib.error: + icc_profile = None # FIXME + self.im_info["icc_profile"] = icc_profile + return s + + def chunk_IHDR(self, pos: int, length: int) -> bytes: + # image header + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if length < 13: + if ImageFile.LOAD_TRUNCATED_IMAGES: + return s + msg = "Truncated IHDR chunk" + raise ValueError(msg) + self.im_size = i32(s, 0), i32(s, 4) + try: + self.im_mode, self.im_rawmode = _MODES[(s[8], s[9])] + except Exception: + pass + if s[12]: + self.im_info["interlace"] = 1 + if s[11]: + msg = "unknown filter category" + raise SyntaxError(msg) + return s + + def chunk_IDAT(self, pos: int, length: int) -> NoReturn: + # image data + if "bbox" in self.im_info: + tile = [ImageFile._Tile("zip", self.im_info["bbox"], pos, self.im_rawmode)] + else: + if self.im_n_frames is not None: + self.im_info["default_image"] = True + tile = [ImageFile._Tile("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] + self.im_tile = tile + self.im_idat = length + msg = "image data found" + raise EOFError(msg) + + def chunk_IEND(self, pos: int, length: int) -> NoReturn: + msg = "end of PNG image" + raise EOFError(msg) + + def chunk_PLTE(self, pos: int, length: int) -> bytes: + # palette + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if self.im_mode == "P": + self.im_palette = "RGB", s + return s + + def chunk_tRNS(self, pos: int, length: int) -> bytes: + # transparency + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if self.im_mode == "P": + if _simple_palette.match(s): + # tRNS contains only one full-transparent entry, + # other entries are full opaque + i = s.find(b"\0") + if i >= 0: + self.im_info["transparency"] = i + else: + # otherwise, we have a byte string with one alpha value + # for each palette entry + self.im_info["transparency"] = s + elif self.im_mode in ("1", "L", "I;16"): + self.im_info["transparency"] = i16(s) + elif self.im_mode == "RGB": + self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4) + return s + + def chunk_gAMA(self, pos: int, length: int) -> bytes: + # gamma setting + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + self.im_info["gamma"] = i32(s) / 100000.0 + return s + + def chunk_cHRM(self, pos: int, length: int) -> bytes: + # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 + # WP x,y, Red x,y, Green x,y Blue x,y + + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + raw_vals = struct.unpack(f">{len(s) // 4}I", s) + self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) + return s + + def chunk_sRGB(self, pos: int, length: int) -> bytes: + # srgb rendering intent, 1 byte + # 0 perceptual + # 1 relative colorimetric + # 2 saturation + # 3 absolute colorimetric + + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if length < 1: + if ImageFile.LOAD_TRUNCATED_IMAGES: + return s + msg = "Truncated sRGB chunk" + raise ValueError(msg) + self.im_info["srgb"] = s[0] + return s + + def chunk_pHYs(self, pos: int, length: int) -> bytes: + # pixels per unit + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if length < 9: + if ImageFile.LOAD_TRUNCATED_IMAGES: + return s + msg = "Truncated pHYs chunk" + raise ValueError(msg) + px, py = i32(s, 0), i32(s, 4) + unit = s[8] + if unit == 1: # meter + dpi = px * 0.0254, py * 0.0254 + self.im_info["dpi"] = dpi + elif unit == 0: + self.im_info["aspect"] = px, py + return s + + def chunk_tEXt(self, pos: int, length: int) -> bytes: + # text + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + try: + k, v = s.split(b"\0", 1) + except ValueError: + # fallback for broken tEXt tags + k = s + v = b"" + if k: + k_str = k.decode("latin-1", "strict") + v_str = v.decode("latin-1", "replace") + + self.im_info[k_str] = v if k == b"exif" else v_str + self.im_text[k_str] = v_str + self.check_text_memory(len(v_str)) + + return s + + def chunk_zTXt(self, pos: int, length: int) -> bytes: + # compressed text + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + try: + k, v = s.split(b"\0", 1) + except ValueError: + k = s + v = b"" + if v: + comp_method = v[0] + else: + comp_method = 0 + if comp_method != 0: + msg = f"Unknown compression method {comp_method} in zTXt chunk" + raise SyntaxError(msg) + try: + v = _safe_zlib_decompress(v[1:]) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + v = b"" + else: + raise + except zlib.error: + v = b"" + + if k: + k_str = k.decode("latin-1", "strict") + v_str = v.decode("latin-1", "replace") + + self.im_info[k_str] = self.im_text[k_str] = v_str + self.check_text_memory(len(v_str)) + + return s + + def chunk_iTXt(self, pos: int, length: int) -> bytes: + # international text + assert self.fp is not None + r = s = ImageFile._safe_read(self.fp, length) + try: + k, r = r.split(b"\0", 1) + except ValueError: + return s + if len(r) < 2: + return s + cf, cm, r = r[0], r[1], r[2:] + try: + lang, tk, v = r.split(b"\0", 2) + except ValueError: + return s + if cf != 0: + if cm == 0: + try: + v = _safe_zlib_decompress(v) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + return s + else: + raise + except zlib.error: + return s + else: + return s + if k == b"XML:com.adobe.xmp": + self.im_info["xmp"] = v + try: + k_str = k.decode("latin-1", "strict") + lang_str = lang.decode("utf-8", "strict") + tk_str = tk.decode("utf-8", "strict") + v_str = v.decode("utf-8", "strict") + except UnicodeError: + return s + + self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str) + self.check_text_memory(len(v_str)) + + return s + + def chunk_eXIf(self, pos: int, length: int) -> bytes: + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + self.im_info["exif"] = b"Exif\x00\x00" + s + return s + + # APNG chunks + def chunk_acTL(self, pos: int, length: int) -> bytes: + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if length < 8: + if ImageFile.LOAD_TRUNCATED_IMAGES: + return s + msg = "APNG contains truncated acTL chunk" + raise ValueError(msg) + if self.im_n_frames is not None: + self.im_n_frames = None + warnings.warn("Invalid APNG, will use default PNG image if possible") + return s + n_frames = i32(s) + if n_frames == 0 or n_frames > 0x80000000: + warnings.warn("Invalid APNG, will use default PNG image if possible") + return s + self.im_n_frames = n_frames + self.im_info["loop"] = i32(s, 4) + self.im_custom_mimetype = "image/apng" + return s + + def chunk_fcTL(self, pos: int, length: int) -> bytes: + assert self.fp is not None + s = ImageFile._safe_read(self.fp, length) + if length < 26: + if ImageFile.LOAD_TRUNCATED_IMAGES: + return s + msg = "APNG contains truncated fcTL chunk" + raise ValueError(msg) + seq = i32(s) + if (self._seq_num is None and seq != 0) or ( + self._seq_num is not None and self._seq_num != seq - 1 + ): + msg = "APNG contains frame sequence errors" + raise SyntaxError(msg) + self._seq_num = seq + width, height = i32(s, 4), i32(s, 8) + px, py = i32(s, 12), i32(s, 16) + im_w, im_h = self.im_size + if px + width > im_w or py + height > im_h: + msg = "APNG contains invalid frames" + raise SyntaxError(msg) + self.im_info["bbox"] = (px, py, px + width, py + height) + delay_num, delay_den = i16(s, 20), i16(s, 22) + if delay_den == 0: + delay_den = 100 + self.im_info["duration"] = float(delay_num) / float(delay_den) * 1000 + self.im_info["disposal"] = s[24] + self.im_info["blend"] = s[25] + return s + + def chunk_fdAT(self, pos: int, length: int) -> bytes: + assert self.fp is not None + if length < 4: + if ImageFile.LOAD_TRUNCATED_IMAGES: + s = ImageFile._safe_read(self.fp, length) + return s + msg = "APNG contains truncated fDAT chunk" + raise ValueError(msg) + s = ImageFile._safe_read(self.fp, 4) + seq = i32(s) + if self._seq_num != seq - 1: + msg = "APNG contains frame sequence errors" + raise SyntaxError(msg) + self._seq_num = seq + return self.chunk_IDAT(pos + 4, length - 4) + + +# -------------------------------------------------------------------- +# PNG reader + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(_MAGIC) + + +## +# Image plugin for PNG images. + + +class PngImageFile(ImageFile.ImageFile): + format = "PNG" + format_description = "Portable network graphics" + + def _open(self) -> None: + if not _accept(self.fp.read(8)): + msg = "not a PNG file" + raise SyntaxError(msg) + self._fp = self.fp + self.__frame = 0 + + # + # Parse headers up to the first IDAT or fDAT chunk + + self.private_chunks: list[tuple[bytes, bytes] | tuple[bytes, bytes, bool]] = [] + self.png: PngStream | None = PngStream(self.fp) + + while True: + # + # get next chunk + + cid, pos, length = self.png.read() + + try: + s = self.png.call(cid, pos, length) + except EOFError: + break + except AttributeError: + logger.debug("%r %s %s (unknown)", cid, pos, length) + s = ImageFile._safe_read(self.fp, length) + if cid[1:2].islower(): + self.private_chunks.append((cid, s)) + + self.png.crc(cid, s) + + # + # Copy relevant attributes from the PngStream. An alternative + # would be to let the PngStream class modify these attributes + # directly, but that introduces circular references which are + # difficult to break if things go wrong in the decoder... + # (believe me, I've tried ;-) + + self._mode = self.png.im_mode + self._size = self.png.im_size + self.info = self.png.im_info + self._text: dict[str, str | iTXt] | None = None + self.tile = self.png.im_tile + self.custom_mimetype = self.png.im_custom_mimetype + self.n_frames = self.png.im_n_frames or 1 + self.default_image = self.info.get("default_image", False) + + if self.png.im_palette: + rawmode, data = self.png.im_palette + self.palette = ImagePalette.raw(rawmode, data) + + if cid == b"fdAT": + self.__prepare_idat = length - 4 + else: + self.__prepare_idat = length # used by load_prepare() + + if self.png.im_n_frames is not None: + self._close_exclusive_fp_after_loading = False + self.png.save_rewind() + self.__rewind_idat = self.__prepare_idat + self.__rewind = self._fp.tell() + if self.default_image: + # IDAT chunk contains default image and not first animation frame + self.n_frames += 1 + self._seek(0) + self.is_animated = self.n_frames > 1 + + @property + def text(self) -> dict[str, str | iTXt]: + # experimental + if self._text is None: + # iTxt, tEXt and zTXt chunks may appear at the end of the file + # So load the file to ensure that they are read + if self.is_animated: + frame = self.__frame + # for APNG, seek to the final frame before loading + self.seek(self.n_frames - 1) + self.load() + if self.is_animated: + self.seek(frame) + assert self._text is not None + return self._text + + def verify(self) -> None: + """Verify PNG file""" + + if self.fp is None: + msg = "verify must be called directly after open" + raise RuntimeError(msg) + + # back up to beginning of IDAT block + self.fp.seek(self.tile[0][2] - 8) + + assert self.png is not None + self.png.verify() + self.png.close() + + if self._exclusive_fp: + self.fp.close() + self.fp = None + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + if frame < self.__frame: + self._seek(0, True) + + last_frame = self.__frame + for f in range(self.__frame + 1, frame + 1): + try: + self._seek(f) + except EOFError as e: + self.seek(last_frame) + msg = "no more images in APNG file" + raise EOFError(msg) from e + + def _seek(self, frame: int, rewind: bool = False) -> None: + assert self.png is not None + if isinstance(self._fp, DeferredError): + raise self._fp.ex + + self.dispose: _imaging.ImagingCore | None + dispose_extent = None + if frame == 0: + if rewind: + self._fp.seek(self.__rewind) + self.png.rewind() + self.__prepare_idat = self.__rewind_idat + self._im = None + self.info = self.png.im_info + self.tile = self.png.im_tile + self.fp = self._fp + self._prev_im = None + self.dispose = None + self.default_image = self.info.get("default_image", False) + self.dispose_op = self.info.get("disposal") + self.blend_op = self.info.get("blend") + dispose_extent = self.info.get("bbox") + self.__frame = 0 + else: + if frame != self.__frame + 1: + msg = f"cannot seek to frame {frame}" + raise ValueError(msg) + + # ensure previous frame was loaded + self.load() + + if self.dispose: + self.im.paste(self.dispose, self.dispose_extent) + self._prev_im = self.im.copy() + + self.fp = self._fp + + # advance to the next frame + if self.__prepare_idat: + ImageFile._safe_read(self.fp, self.__prepare_idat) + self.__prepare_idat = 0 + frame_start = False + while True: + self.fp.read(4) # CRC + + try: + cid, pos, length = self.png.read() + except (struct.error, SyntaxError): + break + + if cid == b"IEND": + msg = "No more images in APNG file" + raise EOFError(msg) + if cid == b"fcTL": + if frame_start: + # there must be at least one fdAT chunk between fcTL chunks + msg = "APNG missing frame data" + raise SyntaxError(msg) + frame_start = True + + try: + self.png.call(cid, pos, length) + except UnicodeDecodeError: + break + except EOFError: + if cid == b"fdAT": + length -= 4 + if frame_start: + self.__prepare_idat = length + break + ImageFile._safe_read(self.fp, length) + except AttributeError: + logger.debug("%r %s %s (unknown)", cid, pos, length) + ImageFile._safe_read(self.fp, length) + + self.__frame = frame + self.tile = self.png.im_tile + self.dispose_op = self.info.get("disposal") + self.blend_op = self.info.get("blend") + dispose_extent = self.info.get("bbox") + + if not self.tile: + msg = "image not found in APNG frame" + raise EOFError(msg) + if dispose_extent: + self.dispose_extent: tuple[float, float, float, float] = dispose_extent + + # setup frame disposal (actual disposal done when needed in the next _seek()) + if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS: + self.dispose_op = Disposal.OP_BACKGROUND + + self.dispose = None + if self.dispose_op == Disposal.OP_PREVIOUS: + if self._prev_im: + self.dispose = self._prev_im.copy() + self.dispose = self._crop(self.dispose, self.dispose_extent) + elif self.dispose_op == Disposal.OP_BACKGROUND: + self.dispose = Image.core.fill(self.mode, self.size) + self.dispose = self._crop(self.dispose, self.dispose_extent) + + def tell(self) -> int: + return self.__frame + + def load_prepare(self) -> None: + """internal: prepare to read PNG file""" + + if self.info.get("interlace"): + self.decoderconfig = self.decoderconfig + (1,) + + self.__idat = self.__prepare_idat # used by load_read() + ImageFile.ImageFile.load_prepare(self) + + def load_read(self, read_bytes: int) -> bytes: + """internal: read more image data""" + + assert self.png is not None + while self.__idat == 0: + # end of chunk, skip forward to next one + + self.fp.read(4) # CRC + + cid, pos, length = self.png.read() + + if cid not in [b"IDAT", b"DDAT", b"fdAT"]: + self.png.push(cid, pos, length) + return b"" + + if cid == b"fdAT": + try: + self.png.call(cid, pos, length) + except EOFError: + pass + self.__idat = length - 4 # sequence_num has already been read + else: + self.__idat = length # empty chunks are allowed + + # read more data from this chunk + if read_bytes <= 0: + read_bytes = self.__idat + else: + read_bytes = min(read_bytes, self.__idat) + + self.__idat = self.__idat - read_bytes + + return self.fp.read(read_bytes) + + def load_end(self) -> None: + """internal: finished reading image data""" + assert self.png is not None + if self.__idat != 0: + self.fp.read(self.__idat) + while True: + self.fp.read(4) # CRC + + try: + cid, pos, length = self.png.read() + except (struct.error, SyntaxError): + break + + if cid == b"IEND": + break + elif cid == b"fcTL" and self.is_animated: + # start of the next frame, stop reading + self.__prepare_idat = 0 + self.png.push(cid, pos, length) + break + + try: + self.png.call(cid, pos, length) + except UnicodeDecodeError: + break + except EOFError: + if cid == b"fdAT": + length -= 4 + try: + ImageFile._safe_read(self.fp, length) + except OSError as e: + if ImageFile.LOAD_TRUNCATED_IMAGES: + break + else: + raise e + except AttributeError: + logger.debug("%r %s %s (unknown)", cid, pos, length) + s = ImageFile._safe_read(self.fp, length) + if cid[1:2].islower(): + self.private_chunks.append((cid, s, True)) + self._text = self.png.im_text + if not self.is_animated: + self.png.close() + self.png = None + else: + if self._prev_im and self.blend_op == Blend.OP_OVER: + updated = self._crop(self.im, self.dispose_extent) + if self.im.mode == "RGB" and "transparency" in self.info: + mask = updated.convert_transparent( + "RGBA", self.info["transparency"] + ) + else: + if self.im.mode == "P" and "transparency" in self.info: + t = self.info["transparency"] + if isinstance(t, bytes): + updated.putpalettealphas(t) + elif isinstance(t, int): + updated.putpalettealpha(t) + mask = updated.convert("RGBA") + self._prev_im.paste(updated, self.dispose_extent, mask) + self.im = self._prev_im + + def _getexif(self) -> dict[int, Any] | None: + if "exif" not in self.info: + self.load() + if "exif" not in self.info and "Raw profile type exif" not in self.info: + return None + return self.getexif()._get_merged_dict() + + def getexif(self) -> Image.Exif: + if "exif" not in self.info: + self.load() + + return super().getexif() + + +# -------------------------------------------------------------------- +# PNG writer + +_OUTMODES = { + # supported PIL modes, and corresponding rawmode, bit depth and color type + "1": ("1", b"\x01", b"\x00"), + "L;1": ("L;1", b"\x01", b"\x00"), + "L;2": ("L;2", b"\x02", b"\x00"), + "L;4": ("L;4", b"\x04", b"\x00"), + "L": ("L", b"\x08", b"\x00"), + "LA": ("LA", b"\x08", b"\x04"), + "I": ("I;16B", b"\x10", b"\x00"), + "I;16": ("I;16B", b"\x10", b"\x00"), + "I;16B": ("I;16B", b"\x10", b"\x00"), + "P;1": ("P;1", b"\x01", b"\x03"), + "P;2": ("P;2", b"\x02", b"\x03"), + "P;4": ("P;4", b"\x04", b"\x03"), + "P": ("P", b"\x08", b"\x03"), + "RGB": ("RGB", b"\x08", b"\x02"), + "RGBA": ("RGBA", b"\x08", b"\x06"), +} + + +def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None: + """Write a PNG chunk (including CRC field)""" + + byte_data = b"".join(data) + + fp.write(o32(len(byte_data)) + cid) + fp.write(byte_data) + crc = _crc32(byte_data, _crc32(cid)) + fp.write(o32(crc)) + + +class _idat: + # wrap output from the encoder in IDAT chunks + + def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None: + self.fp = fp + self.chunk = chunk + + def write(self, data: bytes) -> None: + self.chunk(self.fp, b"IDAT", data) + + +class _fdat: + # wrap encoder output in fdAT chunks + + def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None: + self.fp = fp + self.chunk = chunk + self.seq_num = seq_num + + def write(self, data: bytes) -> None: + self.chunk(self.fp, b"fdAT", o32(self.seq_num), data) + self.seq_num += 1 + + +class _Frame(NamedTuple): + im: Image.Image + bbox: tuple[int, int, int, int] | None + encoderinfo: dict[str, Any] + + +def _write_multiple_frames( + im: Image.Image, + fp: IO[bytes], + chunk: Callable[..., None], + mode: str, + rawmode: str, + default_image: Image.Image | None, + append_images: list[Image.Image], +) -> Image.Image | None: + duration = im.encoderinfo.get("duration") + loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) + disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) + blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) + + if default_image: + chain = itertools.chain(append_images) + else: + chain = itertools.chain([im], append_images) + + im_frames: list[_Frame] = [] + frame_count = 0 + for im_seq in chain: + for im_frame in ImageSequence.Iterator(im_seq): + if im_frame.mode == mode: + im_frame = im_frame.copy() + else: + im_frame = im_frame.convert(mode) + encoderinfo = im.encoderinfo.copy() + if isinstance(duration, (list, tuple)): + encoderinfo["duration"] = duration[frame_count] + elif duration is None and "duration" in im_frame.info: + encoderinfo["duration"] = im_frame.info["duration"] + if isinstance(disposal, (list, tuple)): + encoderinfo["disposal"] = disposal[frame_count] + if isinstance(blend, (list, tuple)): + encoderinfo["blend"] = blend[frame_count] + frame_count += 1 + + if im_frames: + previous = im_frames[-1] + prev_disposal = previous.encoderinfo.get("disposal") + prev_blend = previous.encoderinfo.get("blend") + if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2: + prev_disposal = Disposal.OP_BACKGROUND + + if prev_disposal == Disposal.OP_BACKGROUND: + base_im = previous.im.copy() + dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) + bbox = previous.bbox + if bbox: + dispose = dispose.crop(bbox) + else: + bbox = (0, 0) + im.size + base_im.paste(dispose, bbox) + elif prev_disposal == Disposal.OP_PREVIOUS: + base_im = im_frames[-2].im + else: + base_im = previous.im + delta = ImageChops.subtract_modulo( + im_frame.convert("RGBA"), base_im.convert("RGBA") + ) + bbox = delta.getbbox(alpha_only=False) + if ( + not bbox + and prev_disposal == encoderinfo.get("disposal") + and prev_blend == encoderinfo.get("blend") + and "duration" in encoderinfo + ): + previous.encoderinfo["duration"] += encoderinfo["duration"] + continue + else: + bbox = None + im_frames.append(_Frame(im_frame, bbox, encoderinfo)) + + if len(im_frames) == 1 and not default_image: + return im_frames[0].im + + # animation control + chunk( + fp, + b"acTL", + o32(len(im_frames)), # 0: num_frames + o32(loop), # 4: num_plays + ) + + # default image IDAT (if it exists) + if default_image: + if im.mode != mode: + im = im.convert(mode) + ImageFile._save( + im, + cast(IO[bytes], _idat(fp, chunk)), + [ImageFile._Tile("zip", (0, 0) + im.size, 0, rawmode)], + ) + + seq_num = 0 + for frame, frame_data in enumerate(im_frames): + im_frame = frame_data.im + if not frame_data.bbox: + bbox = (0, 0) + im_frame.size + else: + bbox = frame_data.bbox + im_frame = im_frame.crop(bbox) + size = im_frame.size + encoderinfo = frame_data.encoderinfo + frame_duration = int(round(encoderinfo.get("duration", 0))) + frame_disposal = encoderinfo.get("disposal", disposal) + frame_blend = encoderinfo.get("blend", blend) + # frame control + chunk( + fp, + b"fcTL", + o32(seq_num), # sequence_number + o32(size[0]), # width + o32(size[1]), # height + o32(bbox[0]), # x_offset + o32(bbox[1]), # y_offset + o16(frame_duration), # delay_numerator + o16(1000), # delay_denominator + o8(frame_disposal), # dispose_op + o8(frame_blend), # blend_op + ) + seq_num += 1 + # frame data + if frame == 0 and not default_image: + # first frame must be in IDAT chunks for backwards compatibility + ImageFile._save( + im_frame, + cast(IO[bytes], _idat(fp, chunk)), + [ImageFile._Tile("zip", (0, 0) + im_frame.size, 0, rawmode)], + ) + else: + fdat_chunks = _fdat(fp, chunk, seq_num) + ImageFile._save( + im_frame, + cast(IO[bytes], fdat_chunks), + [ImageFile._Tile("zip", (0, 0) + im_frame.size, 0, rawmode)], + ) + seq_num = fdat_chunks.seq_num + return None + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + _save(im, fp, filename, save_all=True) + + +def _save( + im: Image.Image, + fp: IO[bytes], + filename: str | bytes, + chunk: Callable[..., None] = putchunk, + save_all: bool = False, +) -> None: + # save an image to disk (called by the save method) + + if save_all: + default_image = im.encoderinfo.get( + "default_image", im.info.get("default_image") + ) + modes = set() + sizes = set() + append_images = im.encoderinfo.get("append_images", []) + for im_seq in itertools.chain([im], append_images): + for im_frame in ImageSequence.Iterator(im_seq): + modes.add(im_frame.mode) + sizes.add(im_frame.size) + for mode in ("RGBA", "RGB", "P"): + if mode in modes: + break + else: + mode = modes.pop() + size = tuple(max(frame_size[i] for frame_size in sizes) for i in range(2)) + else: + size = im.size + mode = im.mode + + outmode = mode + if mode == "P": + # + # attempt to minimize storage requirements for palette images + if "bits" in im.encoderinfo: + # number of bits specified by user + colors = min(1 << im.encoderinfo["bits"], 256) + else: + # check palette contents + if im.palette: + colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1) + else: + colors = 256 + + if colors <= 16: + if colors <= 2: + bits = 1 + elif colors <= 4: + bits = 2 + else: + bits = 4 + outmode += f";{bits}" + + # encoder options + im.encoderconfig = ( + im.encoderinfo.get("optimize", False), + im.encoderinfo.get("compress_level", -1), + im.encoderinfo.get("compress_type", -1), + im.encoderinfo.get("dictionary", b""), + ) + + # get the corresponding PNG mode + try: + rawmode, bit_depth, color_type = _OUTMODES[outmode] + except KeyError as e: + msg = f"cannot write mode {mode} as PNG" + raise OSError(msg) from e + if outmode == "I": + deprecate("Saving I mode images as PNG", 13, stacklevel=4) + + # + # write minimal PNG file + + fp.write(_MAGIC) + + chunk( + fp, + b"IHDR", + o32(size[0]), # 0: size + o32(size[1]), + bit_depth, + color_type, + b"\0", # 10: compression + b"\0", # 11: filter category + b"\0", # 12: interlace flag + ) + + chunks = [b"cHRM", b"cICP", b"gAMA", b"sBIT", b"sRGB", b"tIME"] + + icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) + if icc: + # ICC profile + # according to PNG spec, the iCCP chunk contains: + # Profile name 1-79 bytes (character string) + # Null separator 1 byte (null character) + # Compression method 1 byte (0) + # Compressed profile n bytes (zlib with deflate compression) + name = b"ICC Profile" + data = name + b"\0\0" + zlib.compress(icc) + chunk(fp, b"iCCP", data) + + # You must either have sRGB or iCCP. + # Disallow sRGB chunks when an iCCP-chunk has been emitted. + chunks.remove(b"sRGB") + + info = im.encoderinfo.get("pnginfo") + if info: + chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"] + for info_chunk in info.chunks: + cid, data = info_chunk[:2] + if cid in chunks: + chunks.remove(cid) + chunk(fp, cid, data) + elif cid in chunks_multiple_allowed: + chunk(fp, cid, data) + elif cid[1:2].islower(): + # Private chunk + after_idat = len(info_chunk) == 3 and info_chunk[2] + if not after_idat: + chunk(fp, cid, data) + + if im.mode == "P": + palette_byte_number = colors * 3 + palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] + while len(palette_bytes) < palette_byte_number: + palette_bytes += b"\0" + chunk(fp, b"PLTE", palette_bytes) + + transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None)) + + if transparency or transparency == 0: + if im.mode == "P": + # limit to actual palette size + alpha_bytes = colors + if isinstance(transparency, bytes): + chunk(fp, b"tRNS", transparency[:alpha_bytes]) + else: + transparency = max(0, min(255, transparency)) + alpha = b"\xff" * transparency + b"\0" + chunk(fp, b"tRNS", alpha[:alpha_bytes]) + elif im.mode in ("1", "L", "I", "I;16"): + transparency = max(0, min(65535, transparency)) + chunk(fp, b"tRNS", o16(transparency)) + elif im.mode == "RGB": + red, green, blue = transparency + chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) + else: + if "transparency" in im.encoderinfo: + # don't bother with transparency if it's an RGBA + # and it's in the info dict. It's probably just stale. + msg = "cannot use transparency for this mode" + raise OSError(msg) + else: + if im.mode == "P" and im.im.getpalettemode() == "RGBA": + alpha = im.im.getpalette("RGBA", "A") + alpha_bytes = colors + chunk(fp, b"tRNS", alpha[:alpha_bytes]) + + dpi = im.encoderinfo.get("dpi") + if dpi: + chunk( + fp, + b"pHYs", + o32(int(dpi[0] / 0.0254 + 0.5)), + o32(int(dpi[1] / 0.0254 + 0.5)), + b"\x01", + ) + + if info: + chunks = [b"bKGD", b"hIST"] + for info_chunk in info.chunks: + cid, data = info_chunk[:2] + if cid in chunks: + chunks.remove(cid) + chunk(fp, cid, data) + + exif = im.encoderinfo.get("exif") + if exif: + if isinstance(exif, Image.Exif): + exif = exif.tobytes(8) + if exif.startswith(b"Exif\x00\x00"): + exif = exif[6:] + chunk(fp, b"eXIf", exif) + + single_im: Image.Image | None = im + if save_all: + single_im = _write_multiple_frames( + im, fp, chunk, mode, rawmode, default_image, append_images + ) + if single_im: + ImageFile._save( + single_im, + cast(IO[bytes], _idat(fp, chunk)), + [ImageFile._Tile("zip", (0, 0) + single_im.size, 0, rawmode)], + ) + + if info: + for info_chunk in info.chunks: + cid, data = info_chunk[:2] + if cid[1:2].islower(): + # Private chunk + after_idat = len(info_chunk) == 3 and info_chunk[2] + if after_idat: + chunk(fp, cid, data) + + chunk(fp, b"IEND", b"") + + if hasattr(fp, "flush"): + fp.flush() + + +# -------------------------------------------------------------------- +# PNG chunk converter + + +def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]: + """Return a list of PNG chunks representing this image.""" + from io import BytesIO + + chunks = [] + + def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None: + byte_data = b"".join(data) + crc = o32(_crc32(byte_data, _crc32(cid))) + chunks.append((cid, byte_data, crc)) + + fp = BytesIO() + + try: + im.encoderinfo = params + _save(im, fp, "", append) + finally: + del im.encoderinfo + + return chunks + + +# -------------------------------------------------------------------- +# Registry + +Image.register_open(PngImageFile.format, PngImageFile, _accept) +Image.register_save(PngImageFile.format, _save) +Image.register_save_all(PngImageFile.format, _save_all) + +Image.register_extensions(PngImageFile.format, [".png", ".apng"]) + +Image.register_mime(PngImageFile.format, "image/png") diff --git a/venv/Lib/site-packages/PIL/PpmImagePlugin.py b/venv/Lib/site-packages/PIL/PpmImagePlugin.py new file mode 100644 index 00000000..db34d107 --- /dev/null +++ b/venv/Lib/site-packages/PIL/PpmImagePlugin.py @@ -0,0 +1,375 @@ +# +# The Python Imaging Library. +# $Id$ +# +# PPM support for PIL +# +# History: +# 96-03-24 fl Created +# 98-03-06 fl Write RGBA images (as RGB, that is) +# +# Copyright (c) Secret Labs AB 1997-98. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import math +from typing import IO + +from . import Image, ImageFile +from ._binary import i16be as i16 +from ._binary import o8 +from ._binary import o32le as o32 + +# +# -------------------------------------------------------------------- + +b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d" + +MODES = { + # standard + b"P1": "1", + b"P2": "L", + b"P3": "RGB", + b"P4": "1", + b"P5": "L", + b"P6": "RGB", + # extensions + b"P0CMYK": "CMYK", + b"Pf": "F", + # PIL extensions (for test purposes only) + b"PyP": "P", + b"PyRGBA": "RGBA", + b"PyCMYK": "CMYK", +} + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"P") and prefix[1] in b"0123456fy" + + +## +# Image plugin for PBM, PGM, and PPM images. + + +class PpmImageFile(ImageFile.ImageFile): + format = "PPM" + format_description = "Pbmplus image" + + def _read_magic(self) -> bytes: + assert self.fp is not None + + magic = b"" + # read until whitespace or longest available magic number + for _ in range(6): + c = self.fp.read(1) + if not c or c in b_whitespace: + break + magic += c + return magic + + def _read_token(self) -> bytes: + assert self.fp is not None + + token = b"" + while len(token) <= 10: # read until next whitespace or limit of 10 characters + c = self.fp.read(1) + if not c: + break + elif c in b_whitespace: # token ended + if not token: + # skip whitespace at start + continue + break + elif c == b"#": + # ignores rest of the line; stops at CR, LF or EOF + while self.fp.read(1) not in b"\r\n": + pass + continue + token += c + if not token: + # Token was not even 1 byte + msg = "Reached EOF while reading header" + raise ValueError(msg) + elif len(token) > 10: + msg_too_long = b"Token too long in file header: %s" % token + raise ValueError(msg_too_long) + return token + + def _open(self) -> None: + assert self.fp is not None + + magic_number = self._read_magic() + try: + mode = MODES[magic_number] + except KeyError: + msg = "not a PPM file" + raise SyntaxError(msg) + self._mode = mode + + if magic_number in (b"P1", b"P4"): + self.custom_mimetype = "image/x-portable-bitmap" + elif magic_number in (b"P2", b"P5"): + self.custom_mimetype = "image/x-portable-graymap" + elif magic_number in (b"P3", b"P6"): + self.custom_mimetype = "image/x-portable-pixmap" + + self._size = int(self._read_token()), int(self._read_token()) + + decoder_name = "raw" + if magic_number in (b"P1", b"P2", b"P3"): + decoder_name = "ppm_plain" + + args: str | tuple[str | int, ...] + if mode == "1": + args = "1;I" + elif mode == "F": + scale = float(self._read_token()) + if scale == 0.0 or not math.isfinite(scale): + msg = "scale must be finite and non-zero" + raise ValueError(msg) + self.info["scale"] = abs(scale) + + rawmode = "F;32F" if scale < 0 else "F;32BF" + args = (rawmode, 0, -1) + else: + maxval = int(self._read_token()) + if not 0 < maxval < 65536: + msg = "maxval must be greater than 0 and less than 65536" + raise ValueError(msg) + if maxval > 255 and mode == "L": + self._mode = "I" + + rawmode = mode + if decoder_name != "ppm_plain": + # If maxval matches a bit depth, use the raw decoder directly + if maxval == 65535 and mode == "L": + rawmode = "I;16B" + elif maxval != 255: + decoder_name = "ppm" + + args = rawmode if decoder_name == "raw" else (rawmode, maxval) + self.tile = [ + ImageFile._Tile(decoder_name, (0, 0) + self.size, self.fp.tell(), args) + ] + + +# +# -------------------------------------------------------------------- + + +class PpmPlainDecoder(ImageFile.PyDecoder): + _pulls_fd = True + _comment_spans: bool + + def _read_block(self) -> bytes: + assert self.fd is not None + + return self.fd.read(ImageFile.SAFEBLOCK) + + def _find_comment_end(self, block: bytes, start: int = 0) -> int: + a = block.find(b"\n", start) + b = block.find(b"\r", start) + return min(a, b) if a * b > 0 else max(a, b) # lowest nonnegative index (or -1) + + def _ignore_comments(self, block: bytes) -> bytes: + if self._comment_spans: + # Finish current comment + while block: + comment_end = self._find_comment_end(block) + if comment_end != -1: + # Comment ends in this block + # Delete tail of comment + block = block[comment_end + 1 :] + break + else: + # Comment spans whole block + # So read the next block, looking for the end + block = self._read_block() + + # Search for any further comments + self._comment_spans = False + while True: + comment_start = block.find(b"#") + if comment_start == -1: + # No comment found + break + comment_end = self._find_comment_end(block, comment_start) + if comment_end != -1: + # Comment ends in this block + # Delete comment + block = block[:comment_start] + block[comment_end + 1 :] + else: + # Comment continues to next block(s) + block = block[:comment_start] + self._comment_spans = True + break + return block + + def _decode_bitonal(self) -> bytearray: + """ + This is a separate method because in the plain PBM format, all data tokens are + exactly one byte, so the inter-token whitespace is optional. + """ + data = bytearray() + total_bytes = self.state.xsize * self.state.ysize + + while len(data) != total_bytes: + block = self._read_block() # read next block + if not block: + # eof + break + + block = self._ignore_comments(block) + + tokens = b"".join(block.split()) + for token in tokens: + if token not in (48, 49): + msg = b"Invalid token for this mode: %s" % bytes([token]) + raise ValueError(msg) + data = (data + tokens)[:total_bytes] + invert = bytes.maketrans(b"01", b"\xff\x00") + return data.translate(invert) + + def _decode_blocks(self, maxval: int) -> bytearray: + data = bytearray() + max_len = 10 + out_byte_count = 4 if self.mode == "I" else 1 + out_max = 65535 if self.mode == "I" else 255 + bands = Image.getmodebands(self.mode) + total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count + + half_token = b"" + while len(data) != total_bytes: + block = self._read_block() # read next block + if not block: + if half_token: + block = bytearray(b" ") # flush half_token + else: + # eof + break + + block = self._ignore_comments(block) + + if half_token: + block = half_token + block # stitch half_token to new block + half_token = b"" + + tokens = block.split() + + if block and not block[-1:].isspace(): # block might split token + half_token = tokens.pop() # save half token for later + if len(half_token) > max_len: # prevent buildup of half_token + msg = ( + b"Token too long found in data: %s" % half_token[: max_len + 1] + ) + raise ValueError(msg) + + for token in tokens: + if len(token) > max_len: + msg = b"Token too long found in data: %s" % token[: max_len + 1] + raise ValueError(msg) + value = int(token) + if value < 0: + msg_str = f"Channel value is negative: {value}" + raise ValueError(msg_str) + if value > maxval: + msg_str = f"Channel value too large for this mode: {value}" + raise ValueError(msg_str) + value = round(value / maxval * out_max) + data += o32(value) if self.mode == "I" else o8(value) + if len(data) == total_bytes: # finished! + break + return data + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + self._comment_spans = False + if self.mode == "1": + data = self._decode_bitonal() + rawmode = "1;8" + else: + maxval = self.args[-1] + data = self._decode_blocks(maxval) + rawmode = "I;32" if self.mode == "I" else self.mode + self.set_as_raw(bytes(data), rawmode) + return -1, 0 + + +class PpmDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + + data = bytearray() + maxval = self.args[-1] + in_byte_count = 1 if maxval < 256 else 2 + out_byte_count = 4 if self.mode == "I" else 1 + out_max = 65535 if self.mode == "I" else 255 + bands = Image.getmodebands(self.mode) + dest_length = self.state.xsize * self.state.ysize * bands * out_byte_count + while len(data) < dest_length: + pixels = self.fd.read(in_byte_count * bands) + if len(pixels) < in_byte_count * bands: + # eof + break + for b in range(bands): + value = ( + pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count) + ) + value = min(out_max, round(value / maxval * out_max)) + data += o32(value) if self.mode == "I" else o8(value) + rawmode = "I;32" if self.mode == "I" else self.mode + self.set_as_raw(bytes(data), rawmode) + return -1, 0 + + +# +# -------------------------------------------------------------------- + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode == "1": + rawmode, head = "1;I", b"P4" + elif im.mode == "L": + rawmode, head = "L", b"P5" + elif im.mode in ("I", "I;16"): + rawmode, head = "I;16B", b"P5" + elif im.mode in ("RGB", "RGBA"): + rawmode, head = "RGB", b"P6" + elif im.mode == "F": + rawmode, head = "F;32F", b"Pf" + else: + msg = f"cannot write mode {im.mode} as PPM" + raise OSError(msg) + fp.write(head + b"\n%d %d\n" % im.size) + if head == b"P6": + fp.write(b"255\n") + elif head == b"P5": + if rawmode == "L": + fp.write(b"255\n") + else: + fp.write(b"65535\n") + elif head == b"Pf": + fp.write(b"-1.0\n") + row_order = -1 if im.mode == "F" else 1 + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, row_order))] + ) + + +# +# -------------------------------------------------------------------- + + +Image.register_open(PpmImageFile.format, PpmImageFile, _accept) +Image.register_save(PpmImageFile.format, _save) + +Image.register_decoder("ppm", PpmDecoder) +Image.register_decoder("ppm_plain", PpmPlainDecoder) + +Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm", ".pfm"]) + +Image.register_mime(PpmImageFile.format, "image/x-portable-anymap") diff --git a/venv/Lib/site-packages/PIL/PsdImagePlugin.py b/venv/Lib/site-packages/PIL/PsdImagePlugin.py new file mode 100644 index 00000000..f49aaeeb --- /dev/null +++ b/venv/Lib/site-packages/PIL/PsdImagePlugin.py @@ -0,0 +1,333 @@ +# +# The Python Imaging Library +# $Id$ +# +# Adobe PSD 2.5/3.0 file handling +# +# History: +# 1995-09-01 fl Created +# 1997-01-03 fl Read most PSD images +# 1997-01-18 fl Fixed P and CMYK support +# 2001-10-21 fl Added seek/tell support (for layers) +# +# Copyright (c) 1997-2001 by Secret Labs AB. +# Copyright (c) 1995-2001 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +from functools import cached_property +from typing import IO + +from . import Image, ImageFile, ImagePalette +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import si16be as si16 +from ._binary import si32be as si32 +from ._util import DeferredError + +MODES = { + # (photoshop mode, bits) -> (pil mode, required channels) + (0, 1): ("1", 1), + (0, 8): ("L", 1), + (1, 8): ("L", 1), + (2, 8): ("P", 1), + (3, 8): ("RGB", 3), + (4, 8): ("CMYK", 4), + (7, 8): ("L", 1), # FIXME: multilayer + (8, 8): ("L", 1), # duotone + (9, 8): ("LAB", 3), +} + + +# --------------------------------------------------------------------. +# read PSD images + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"8BPS") + + +## +# Image plugin for Photoshop images. + + +class PsdImageFile(ImageFile.ImageFile): + format = "PSD" + format_description = "Adobe Photoshop" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + read = self.fp.read + + # + # header + + s = read(26) + if not _accept(s) or i16(s, 4) != 1: + msg = "not a PSD file" + raise SyntaxError(msg) + + psd_bits = i16(s, 22) + psd_channels = i16(s, 12) + psd_mode = i16(s, 24) + + mode, channels = MODES[(psd_mode, psd_bits)] + + if channels > psd_channels: + msg = "not enough channels" + raise OSError(msg) + if mode == "RGB" and psd_channels == 4: + mode = "RGBA" + channels = 4 + + self._mode = mode + self._size = i32(s, 18), i32(s, 14) + + # + # color mode data + + size = i32(read(4)) + if size: + data = read(size) + if mode == "P" and size == 768: + self.palette = ImagePalette.raw("RGB;L", data) + + # + # image resources + + self.resources = [] + + size = i32(read(4)) + if size: + # load resources + end = self.fp.tell() + size + while self.fp.tell() < end: + read(4) # signature + id = i16(read(2)) + name = read(i8(read(1))) + if not (len(name) & 1): + read(1) # padding + data = read(i32(read(4))) + if len(data) & 1: + read(1) # padding + self.resources.append((id, name, data)) + if id == 1039: # ICC profile + self.info["icc_profile"] = data + + # + # layer and mask information + + self._layers_position = None + + size = i32(read(4)) + if size: + end = self.fp.tell() + size + size = i32(read(4)) + if size: + self._layers_position = self.fp.tell() + self._layers_size = size + self.fp.seek(end) + self._n_frames: int | None = None + + # + # image descriptor + + self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) + + # keep the file open + self._fp = self.fp + self.frame = 1 + self._min_frame = 1 + + @cached_property + def layers( + self, + ) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]: + layers = [] + if self._layers_position is not None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self._fp.seek(self._layers_position) + _layer_data = io.BytesIO(ImageFile._safe_read(self._fp, self._layers_size)) + layers = _layerinfo(_layer_data, self._layers_size) + self._n_frames = len(layers) + return layers + + @property + def n_frames(self) -> int: + if self._n_frames is None: + self._n_frames = len(self.layers) + return self._n_frames + + @property + def is_animated(self) -> bool: + return len(self.layers) > 1 + + def seek(self, layer: int) -> None: + if not self._seek_check(layer): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + + # seek to given layer (1..max) + _, mode, _, tile = self.layers[layer - 1] + self._mode = mode + self.tile = tile + self.frame = layer + self.fp = self._fp + + def tell(self) -> int: + # return layer number (0=image, 1..max=layers) + return self.frame + + +def _layerinfo( + fp: IO[bytes], ct_bytes: int +) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]: + # read layerinfo block + layers = [] + + def read(size: int) -> bytes: + return ImageFile._safe_read(fp, size) + + ct = si16(read(2)) + + # sanity check + if ct_bytes < (abs(ct) * 20): + msg = "Layer block too short for number of layers requested" + raise SyntaxError(msg) + + for _ in range(abs(ct)): + # bounding box + y0 = si32(read(4)) + x0 = si32(read(4)) + y1 = si32(read(4)) + x1 = si32(read(4)) + + # image info + bands = [] + ct_types = i16(read(2)) + if ct_types > 4: + fp.seek(ct_types * 6 + 12, io.SEEK_CUR) + size = i32(read(4)) + fp.seek(size, io.SEEK_CUR) + continue + + for _ in range(ct_types): + type = i16(read(2)) + + if type == 65535: + b = "A" + else: + b = "RGBA"[type] + + bands.append(b) + read(4) # size + + # figure out the image mode + bands.sort() + if bands == ["R"]: + mode = "L" + elif bands == ["B", "G", "R"]: + mode = "RGB" + elif bands == ["A", "B", "G", "R"]: + mode = "RGBA" + else: + mode = "" # unknown + + # skip over blend flags and extra information + read(12) # filler + name = "" + size = i32(read(4)) # length of the extra data field + if size: + data_end = fp.tell() + size + + length = i32(read(4)) + if length: + fp.seek(length - 16, io.SEEK_CUR) + + length = i32(read(4)) + if length: + fp.seek(length, io.SEEK_CUR) + + length = i8(read(1)) + if length: + # Don't know the proper encoding, + # Latin-1 should be a good guess + name = read(length).decode("latin-1", "replace") + + fp.seek(data_end) + layers.append((name, mode, (x0, y0, x1, y1))) + + # get tiles + layerinfo = [] + for i, (name, mode, bbox) in enumerate(layers): + tile = [] + for m in mode: + t = _maketile(fp, m, bbox, 1) + if t: + tile.extend(t) + layerinfo.append((name, mode, bbox, tile)) + + return layerinfo + + +def _maketile( + file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int +) -> list[ImageFile._Tile]: + tiles = [] + read = file.read + + compression = i16(read(2)) + + xsize = bbox[2] - bbox[0] + ysize = bbox[3] - bbox[1] + + offset = file.tell() + + if compression == 0: + # + # raw compression + for channel in range(channels): + layer = mode[channel] + if mode == "CMYK": + layer += ";I" + tiles.append(ImageFile._Tile("raw", bbox, offset, layer)) + offset = offset + xsize * ysize + + elif compression == 1: + # + # packbits compression + i = 0 + bytecount = read(channels * ysize * 2) + offset = file.tell() + for channel in range(channels): + layer = mode[channel] + if mode == "CMYK": + layer += ";I" + tiles.append(ImageFile._Tile("packbits", bbox, offset, layer)) + for y in range(ysize): + offset = offset + i16(bytecount, i) + i += 2 + + file.seek(offset) + + if offset & 1: + read(1) # padding + + return tiles + + +# -------------------------------------------------------------------- +# registry + + +Image.register_open(PsdImageFile.format, PsdImageFile, _accept) + +Image.register_extension(PsdImageFile.format, ".psd") + +Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop") diff --git a/venv/Lib/site-packages/PIL/QoiImagePlugin.py b/venv/Lib/site-packages/PIL/QoiImagePlugin.py new file mode 100644 index 00000000..dba5d809 --- /dev/null +++ b/venv/Lib/site-packages/PIL/QoiImagePlugin.py @@ -0,0 +1,234 @@ +# +# The Python Imaging Library. +# +# QOI support for PIL +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +from typing import IO + +from . import Image, ImageFile +from ._binary import i32be as i32 +from ._binary import o8 +from ._binary import o32be as o32 + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"qoif") + + +class QoiImageFile(ImageFile.ImageFile): + format = "QOI" + format_description = "Quite OK Image" + + def _open(self) -> None: + if not _accept(self.fp.read(4)): + msg = "not a QOI file" + raise SyntaxError(msg) + + self._size = i32(self.fp.read(4)), i32(self.fp.read(4)) + + channels = self.fp.read(1)[0] + self._mode = "RGB" if channels == 3 else "RGBA" + + self.fp.seek(1, os.SEEK_CUR) # colorspace + self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell())] + + +class QoiDecoder(ImageFile.PyDecoder): + _pulls_fd = True + _previous_pixel: bytes | bytearray | None = None + _previously_seen_pixels: dict[int, bytes | bytearray] = {} + + def _add_to_previous_pixels(self, value: bytes | bytearray) -> None: + self._previous_pixel = value + + r, g, b, a = value + hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + self._previously_seen_pixels[hash_value] = value + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + + self._previously_seen_pixels = {} + self._previous_pixel = bytearray((0, 0, 0, 255)) + + data = bytearray() + bands = Image.getmodebands(self.mode) + dest_length = self.state.xsize * self.state.ysize * bands + while len(data) < dest_length: + byte = self.fd.read(1)[0] + value: bytes | bytearray + if byte == 0b11111110 and self._previous_pixel: # QOI_OP_RGB + value = bytearray(self.fd.read(3)) + self._previous_pixel[3:] + elif byte == 0b11111111: # QOI_OP_RGBA + value = self.fd.read(4) + else: + op = byte >> 6 + if op == 0: # QOI_OP_INDEX + op_index = byte & 0b00111111 + value = self._previously_seen_pixels.get( + op_index, bytearray((0, 0, 0, 0)) + ) + elif op == 1 and self._previous_pixel: # QOI_OP_DIFF + value = bytearray( + ( + (self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2) + % 256, + (self._previous_pixel[1] + ((byte & 0b00001100) >> 2) - 2) + % 256, + (self._previous_pixel[2] + (byte & 0b00000011) - 2) % 256, + self._previous_pixel[3], + ) + ) + elif op == 2 and self._previous_pixel: # QOI_OP_LUMA + second_byte = self.fd.read(1)[0] + diff_green = (byte & 0b00111111) - 32 + diff_red = ((second_byte & 0b11110000) >> 4) - 8 + diff_blue = (second_byte & 0b00001111) - 8 + + value = bytearray( + tuple( + (self._previous_pixel[i] + diff_green + diff) % 256 + for i, diff in enumerate((diff_red, 0, diff_blue)) + ) + ) + value += self._previous_pixel[3:] + elif op == 3 and self._previous_pixel: # QOI_OP_RUN + run_length = (byte & 0b00111111) + 1 + value = self._previous_pixel + if bands == 3: + value = value[:3] + data += value * run_length + continue + self._add_to_previous_pixels(value) + + if bands == 3: + value = value[:3] + data += value + self.set_as_raw(data) + return -1, 0 + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode == "RGB": + channels = 3 + elif im.mode == "RGBA": + channels = 4 + else: + msg = "Unsupported QOI image mode" + raise ValueError(msg) + + colorspace = 0 if im.encoderinfo.get("colorspace") == "sRGB" else 1 + + fp.write(b"qoif") + fp.write(o32(im.size[0])) + fp.write(o32(im.size[1])) + fp.write(o8(channels)) + fp.write(o8(colorspace)) + + ImageFile._save(im, fp, [ImageFile._Tile("qoi", (0, 0) + im.size)]) + + +class QoiEncoder(ImageFile.PyEncoder): + _pushes_fd = True + _previous_pixel: tuple[int, int, int, int] | None = None + _previously_seen_pixels: dict[int, tuple[int, int, int, int]] = {} + _run = 0 + + def _write_run(self) -> bytes: + data = o8(0b11000000 | (self._run - 1)) # QOI_OP_RUN + self._run = 0 + return data + + def _delta(self, left: int, right: int) -> int: + result = (left - right) & 255 + if result >= 128: + result -= 256 + return result + + def encode(self, bufsize: int) -> tuple[int, int, bytes]: + assert self.im is not None + + self._previously_seen_pixels = {0: (0, 0, 0, 0)} + self._previous_pixel = (0, 0, 0, 255) + + data = bytearray() + w, h = self.im.size + bands = Image.getmodebands(self.mode) + + for y in range(h): + for x in range(w): + pixel = self.im.getpixel((x, y)) + if bands == 3: + pixel = (*pixel, 255) + + if pixel == self._previous_pixel: + self._run += 1 + if self._run == 62: + data += self._write_run() + else: + if self._run: + data += self._write_run() + + r, g, b, a = pixel + hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + if self._previously_seen_pixels.get(hash_value) == pixel: + data += o8(hash_value) # QOI_OP_INDEX + elif self._previous_pixel: + self._previously_seen_pixels[hash_value] = pixel + + prev_r, prev_g, prev_b, prev_a = self._previous_pixel + if prev_a == a: + delta_r = self._delta(r, prev_r) + delta_g = self._delta(g, prev_g) + delta_b = self._delta(b, prev_b) + + if ( + -2 <= delta_r < 2 + and -2 <= delta_g < 2 + and -2 <= delta_b < 2 + ): + data += o8( + 0b01000000 + | (delta_r + 2) << 4 + | (delta_g + 2) << 2 + | (delta_b + 2) + ) # QOI_OP_DIFF + else: + delta_gr = self._delta(delta_r, delta_g) + delta_gb = self._delta(delta_b, delta_g) + if ( + -8 <= delta_gr < 8 + and -32 <= delta_g < 32 + and -8 <= delta_gb < 8 + ): + data += o8( + 0b10000000 | (delta_g + 32) + ) # QOI_OP_LUMA + data += o8((delta_gr + 8) << 4 | (delta_gb + 8)) + else: + data += o8(0b11111110) # QOI_OP_RGB + data += bytes(pixel[:3]) + else: + data += o8(0b11111111) # QOI_OP_RGBA + data += bytes(pixel) + + self._previous_pixel = pixel + + if self._run: + data += self._write_run() + data += bytes((0, 0, 0, 0, 0, 0, 0, 1)) # padding + + return len(data), 0, data + + +Image.register_open(QoiImageFile.format, QoiImageFile, _accept) +Image.register_decoder("qoi", QoiDecoder) +Image.register_extension(QoiImageFile.format, ".qoi") + +Image.register_save(QoiImageFile.format, _save) +Image.register_encoder("qoi", QoiEncoder) diff --git a/venv/Lib/site-packages/PIL/SgiImagePlugin.py b/venv/Lib/site-packages/PIL/SgiImagePlugin.py new file mode 100644 index 00000000..85302215 --- /dev/null +++ b/venv/Lib/site-packages/PIL/SgiImagePlugin.py @@ -0,0 +1,231 @@ +# +# The Python Imaging Library. +# $Id$ +# +# SGI image file handling +# +# See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli. +# +# +# +# History: +# 2017-22-07 mb Add RLE decompression +# 2016-16-10 mb Add save method without compression +# 1995-09-10 fl Created +# +# Copyright (c) 2016 by Mickael Bonfill. +# Copyright (c) 2008 by Karsten Hiddemann. +# Copyright (c) 1997 by Secret Labs AB. +# Copyright (c) 1995 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import os +import struct +from typing import IO + +from . import Image, ImageFile +from ._binary import i16be as i16 +from ._binary import o8 + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 2 and i16(prefix) == 474 + + +MODES = { + (1, 1, 1): "L", + (1, 2, 1): "L", + (2, 1, 1): "L;16B", + (2, 2, 1): "L;16B", + (1, 3, 3): "RGB", + (2, 3, 3): "RGB;16B", + (1, 3, 4): "RGBA", + (2, 3, 4): "RGBA;16B", +} + + +## +# Image plugin for SGI images. +class SgiImageFile(ImageFile.ImageFile): + format = "SGI" + format_description = "SGI Image File Format" + + def _open(self) -> None: + # HEAD + assert self.fp is not None + + headlen = 512 + s = self.fp.read(headlen) + + if not _accept(s): + msg = "Not an SGI image file" + raise ValueError(msg) + + # compression : verbatim or RLE + compression = s[2] + + # bpc : 1 or 2 bytes (8bits or 16bits) + bpc = s[3] + + # dimension : 1, 2 or 3 (depending on xsize, ysize and zsize) + dimension = i16(s, 4) + + # xsize : width + xsize = i16(s, 6) + + # ysize : height + ysize = i16(s, 8) + + # zsize : channels count + zsize = i16(s, 10) + + # determine mode from bits/zsize + try: + rawmode = MODES[(bpc, dimension, zsize)] + except KeyError: + msg = "Unsupported SGI image mode" + raise ValueError(msg) + + self._size = xsize, ysize + self._mode = rawmode.split(";")[0] + if self.mode == "RGB": + self.custom_mimetype = "image/rgb" + + # orientation -1 : scanlines begins at the bottom-left corner + orientation = -1 + + # decoder info + if compression == 0: + pagesize = xsize * ysize * bpc + if bpc == 2: + self.tile = [ + ImageFile._Tile( + "SGI16", + (0, 0) + self.size, + headlen, + (self.mode, 0, orientation), + ) + ] + else: + self.tile = [] + offset = headlen + for layer in self.mode: + self.tile.append( + ImageFile._Tile( + "raw", (0, 0) + self.size, offset, (layer, 0, orientation) + ) + ) + offset += pagesize + elif compression == 1: + self.tile = [ + ImageFile._Tile( + "sgi_rle", (0, 0) + self.size, headlen, (rawmode, orientation, bpc) + ) + ] + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode not in {"RGB", "RGBA", "L"}: + msg = "Unsupported SGI image mode" + raise ValueError(msg) + + # Get the keyword arguments + info = im.encoderinfo + + # Byte-per-pixel precision, 1 = 8bits per pixel + bpc = info.get("bpc", 1) + + if bpc not in (1, 2): + msg = "Unsupported number of bytes per pixel" + raise ValueError(msg) + + # Flip the image, since the origin of SGI file is the bottom-left corner + orientation = -1 + # Define the file as SGI File Format + magic_number = 474 + # Run-Length Encoding Compression - Unsupported at this time + rle = 0 + + # X Dimension = width / Y Dimension = height + x, y = im.size + # Z Dimension: Number of channels + z = len(im.mode) + # Number of dimensions (x,y,z) + if im.mode == "L": + dimension = 1 if y == 1 else 2 + else: + dimension = 3 + + # Minimum Byte value + pinmin = 0 + # Maximum Byte value (255 = 8bits per pixel) + pinmax = 255 + # Image name (79 characters max, truncated below in write) + img_name = os.path.splitext(os.path.basename(filename))[0] + if isinstance(img_name, str): + img_name = img_name.encode("ascii", "ignore") + # Standard representation of pixel in the file + colormap = 0 + fp.write(struct.pack(">h", magic_number)) + fp.write(o8(rle)) + fp.write(o8(bpc)) + fp.write(struct.pack(">H", dimension)) + fp.write(struct.pack(">H", x)) + fp.write(struct.pack(">H", y)) + fp.write(struct.pack(">H", z)) + fp.write(struct.pack(">l", pinmin)) + fp.write(struct.pack(">l", pinmax)) + fp.write(struct.pack("4s", b"")) # dummy + fp.write(struct.pack("79s", img_name)) # truncates to 79 chars + fp.write(struct.pack("s", b"")) # force null byte after img_name + fp.write(struct.pack(">l", colormap)) + fp.write(struct.pack("404s", b"")) # dummy + + rawmode = "L" + if bpc == 2: + rawmode = "L;16B" + + for channel in im.split(): + fp.write(channel.tobytes("raw", rawmode, 0, orientation)) + + if hasattr(fp, "flush"): + fp.flush() + + +class SGI16Decoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + assert self.im is not None + + rawmode, stride, orientation = self.args + pagesize = self.state.xsize * self.state.ysize + zsize = len(self.mode) + self.fd.seek(512) + + for band in range(zsize): + channel = Image.new("L", (self.state.xsize, self.state.ysize)) + channel.frombytes( + self.fd.read(2 * pagesize), "raw", "L;16B", stride, orientation + ) + self.im.putband(channel.im, band) + + return -1, 0 + + +# +# registry + + +Image.register_decoder("SGI16", SGI16Decoder) +Image.register_open(SgiImageFile.format, SgiImageFile, _accept) +Image.register_save(SgiImageFile.format, _save) +Image.register_mime(SgiImageFile.format, "image/sgi") + +Image.register_extensions(SgiImageFile.format, [".bw", ".rgb", ".rgba", ".sgi"]) + +# End of file diff --git a/venv/Lib/site-packages/PIL/SpiderImagePlugin.py b/venv/Lib/site-packages/PIL/SpiderImagePlugin.py new file mode 100644 index 00000000..868019e8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/SpiderImagePlugin.py @@ -0,0 +1,331 @@ +# +# The Python Imaging Library. +# +# SPIDER image file handling +# +# History: +# 2004-08-02 Created BB +# 2006-03-02 added save method +# 2006-03-13 added support for stack images +# +# Copyright (c) 2004 by Health Research Inc. (HRI) RENSSELAER, NY 12144. +# Copyright (c) 2004 by William Baxter. +# Copyright (c) 2004 by Secret Labs AB. +# Copyright (c) 2004 by Fredrik Lundh. +# + +## +# Image plugin for the Spider image format. This format is used +# by the SPIDER software, in processing image data from electron +# microscopy and tomography. +## + +# +# SpiderImagePlugin.py +# +# The Spider image format is used by SPIDER software, in processing +# image data from electron microscopy and tomography. +# +# Spider home page: +# https://spider.wadsworth.org/spider_doc/spider/docs/spider.html +# +# Details about the Spider image format: +# https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html +# +from __future__ import annotations + +import os +import struct +import sys +from typing import IO, Any, cast + +from . import Image, ImageFile +from ._util import DeferredError + +TYPE_CHECKING = False + + +def isInt(f: Any) -> int: + try: + i = int(f) + if f - i == 0: + return 1 + else: + return 0 + except (ValueError, OverflowError): + return 0 + + +iforms = [1, 3, -11, -12, -21, -22] + + +# There is no magic number to identify Spider files, so just check a +# series of header locations to see if they have reasonable values. +# Returns no. of bytes in the header, if it is a valid Spider header, +# otherwise returns 0 + + +def isSpiderHeader(t: tuple[float, ...]) -> int: + h = (99,) + t # add 1 value so can use spider header index start=1 + # header values 1,2,5,12,13,22,23 should be integers + for i in [1, 2, 5, 12, 13, 22, 23]: + if not isInt(h[i]): + return 0 + # check iform + iform = int(h[5]) + if iform not in iforms: + return 0 + # check other header values + labrec = int(h[13]) # no. records in file header + labbyt = int(h[22]) # total no. of bytes in header + lenbyt = int(h[23]) # record length in bytes + if labbyt != (labrec * lenbyt): + return 0 + # looks like a valid header + return labbyt + + +def isSpiderImage(filename: str) -> int: + with open(filename, "rb") as fp: + f = fp.read(92) # read 23 * 4 bytes + t = struct.unpack(">23f", f) # try big-endian first + hdrlen = isSpiderHeader(t) + if hdrlen == 0: + t = struct.unpack("<23f", f) # little-endian + hdrlen = isSpiderHeader(t) + return hdrlen + + +class SpiderImageFile(ImageFile.ImageFile): + format = "SPIDER" + format_description = "Spider 2D image" + _close_exclusive_fp_after_loading = False + + def _open(self) -> None: + # check header + n = 27 * 4 # read 27 float values + f = self.fp.read(n) + + try: + self.bigendian = 1 + t = struct.unpack(">27f", f) # try big-endian first + hdrlen = isSpiderHeader(t) + if hdrlen == 0: + self.bigendian = 0 + t = struct.unpack("<27f", f) # little-endian + hdrlen = isSpiderHeader(t) + if hdrlen == 0: + msg = "not a valid Spider file" + raise SyntaxError(msg) + except struct.error as e: + msg = "not a valid Spider file" + raise SyntaxError(msg) from e + + h = (99,) + t # add 1 value : spider header index starts at 1 + iform = int(h[5]) + if iform != 1: + msg = "not a Spider 2D image" + raise SyntaxError(msg) + + self._size = int(h[12]), int(h[2]) # size in pixels (width, height) + self.istack = int(h[24]) + self.imgnumber = int(h[27]) + + if self.istack == 0 and self.imgnumber == 0: + # stk=0, img=0: a regular 2D image + offset = hdrlen + self._nimages = 1 + elif self.istack > 0 and self.imgnumber == 0: + # stk>0, img=0: Opening the stack for the first time + self.imgbytes = int(h[12]) * int(h[2]) * 4 + self.hdrlen = hdrlen + self._nimages = int(h[26]) + # Point to the first image in the stack + offset = hdrlen * 2 + self.imgnumber = 1 + elif self.istack == 0 and self.imgnumber > 0: + # stk=0, img>0: an image within the stack + offset = hdrlen + self.stkoffset + self.istack = 2 # So Image knows it's still a stack + else: + msg = "inconsistent stack header values" + raise SyntaxError(msg) + + if self.bigendian: + self.rawmode = "F;32BF" + else: + self.rawmode = "F;32F" + self._mode = "F" + + self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)] + self._fp = self.fp # FIXME: hack + + @property + def n_frames(self) -> int: + return self._nimages + + @property + def is_animated(self) -> bool: + return self._nimages > 1 + + # 1st image index is zero (although SPIDER imgnumber starts at 1) + def tell(self) -> int: + if self.imgnumber < 1: + return 0 + else: + return self.imgnumber - 1 + + def seek(self, frame: int) -> None: + if self.istack == 0: + msg = "attempt to seek in a non-stack file" + raise EOFError(msg) + if not self._seek_check(frame): + return + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) + self.fp = self._fp + self.fp.seek(self.stkoffset) + self._open() + + # returns a byte image after rescaling to 0..255 + def convert2byte(self, depth: int = 255) -> Image.Image: + extrema = self.getextrema() + assert isinstance(extrema[0], float) + minimum, maximum = cast(tuple[float, float], extrema) + m: float = 1 + if maximum != minimum: + m = depth / (maximum - minimum) + b = -m * minimum + return self.point(lambda i: i * m + b).convert("L") + + if TYPE_CHECKING: + from . import ImageTk + + # returns a ImageTk.PhotoImage object, after rescaling to 0..255 + def tkPhotoImage(self) -> ImageTk.PhotoImage: + from . import ImageTk + + return ImageTk.PhotoImage(self.convert2byte(), palette=256) + + +# -------------------------------------------------------------------- +# Image series + + +# given a list of filenames, return a list of images +def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None: + """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage""" + if filelist is None or len(filelist) < 1: + return None + + byte_imgs = [] + for img in filelist: + if not os.path.exists(img): + print(f"unable to find {img}") + continue + try: + with Image.open(img) as im: + assert isinstance(im, SpiderImageFile) + byte_im = im.convert2byte() + except Exception: + if not isSpiderImage(img): + print(f"{img} is not a Spider image file") + continue + byte_im.info["filename"] = img + byte_imgs.append(byte_im) + return byte_imgs + + +# -------------------------------------------------------------------- +# For saving images in Spider format + + +def makeSpiderHeader(im: Image.Image) -> list[bytes]: + nsam, nrow = im.size + lenbyt = nsam * 4 # There are labrec records in the header + labrec = int(1024 / lenbyt) + if 1024 % lenbyt != 0: + labrec += 1 + labbyt = labrec * lenbyt + nvalues = int(labbyt / 4) + if nvalues < 23: + return [] + + hdr = [0.0] * nvalues + + # NB these are Fortran indices + hdr[1] = 1.0 # nslice (=1 for an image) + hdr[2] = float(nrow) # number of rows per slice + hdr[3] = float(nrow) # number of records in the image + hdr[5] = 1.0 # iform for 2D image + hdr[12] = float(nsam) # number of pixels per line + hdr[13] = float(labrec) # number of records in file header + hdr[22] = float(labbyt) # total number of bytes in header + hdr[23] = float(lenbyt) # record length in bytes + + # adjust for Fortran indexing + hdr = hdr[1:] + hdr.append(0.0) + # pack binary data into a string + return [struct.pack("f", v) for v in hdr] + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode != "F": + im = im.convert("F") + + hdr = makeSpiderHeader(im) + if len(hdr) < 256: + msg = "Error creating Spider header" + raise OSError(msg) + + # write the SPIDER header + fp.writelines(hdr) + + rawmode = "F;32NF" # 32-bit native floating point + ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)]) + + +def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + # get the filename extension and register it with Image + filename_ext = os.path.splitext(filename)[1] + ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext + Image.register_extension(SpiderImageFile.format, ext) + _save(im, fp, filename) + + +# -------------------------------------------------------------------- + + +Image.register_open(SpiderImageFile.format, SpiderImageFile) +Image.register_save(SpiderImageFile.format, _save_spider) + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Syntax: python3 SpiderImagePlugin.py [infile] [outfile]") + sys.exit() + + filename = sys.argv[1] + if not isSpiderImage(filename): + print("input image must be in Spider format") + sys.exit() + + with Image.open(filename) as im: + print(f"image: {im}") + print(f"format: {im.format}") + print(f"size: {im.size}") + print(f"mode: {im.mode}") + print("max, min: ", end=" ") + print(im.getextrema()) + + if len(sys.argv) > 2: + outfile = sys.argv[2] + + # perform some image operation + im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + print( + f"saving a flipped version of {os.path.basename(filename)} " + f"as {outfile} " + ) + im.save(outfile, SpiderImageFile.format) diff --git a/venv/Lib/site-packages/PIL/SunImagePlugin.py b/venv/Lib/site-packages/PIL/SunImagePlugin.py new file mode 100644 index 00000000..8912379e --- /dev/null +++ b/venv/Lib/site-packages/PIL/SunImagePlugin.py @@ -0,0 +1,145 @@ +# +# The Python Imaging Library. +# $Id$ +# +# Sun image file handling +# +# History: +# 1995-09-10 fl Created +# 1996-05-28 fl Fixed 32-bit alignment +# 1998-12-29 fl Import ImagePalette module +# 2001-12-18 fl Fixed palette loading (from Jean-Claude Rimbault) +# +# Copyright (c) 1997-2001 by Secret Labs AB +# Copyright (c) 1995-1996 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +from . import Image, ImageFile, ImagePalette +from ._binary import i32be as i32 + + +def _accept(prefix: bytes) -> bool: + return len(prefix) >= 4 and i32(prefix) == 0x59A66A95 + + +## +# Image plugin for Sun raster files. + + +class SunImageFile(ImageFile.ImageFile): + format = "SUN" + format_description = "Sun Raster File" + + def _open(self) -> None: + # The Sun Raster file header is 32 bytes in length + # and has the following format: + + # typedef struct _SunRaster + # { + # DWORD MagicNumber; /* Magic (identification) number */ + # DWORD Width; /* Width of image in pixels */ + # DWORD Height; /* Height of image in pixels */ + # DWORD Depth; /* Number of bits per pixel */ + # DWORD Length; /* Size of image data in bytes */ + # DWORD Type; /* Type of raster file */ + # DWORD ColorMapType; /* Type of color map */ + # DWORD ColorMapLength; /* Size of the color map in bytes */ + # } SUNRASTER; + + assert self.fp is not None + + # HEAD + s = self.fp.read(32) + if not _accept(s): + msg = "not an SUN raster file" + raise SyntaxError(msg) + + offset = 32 + + self._size = i32(s, 4), i32(s, 8) + + depth = i32(s, 12) + # data_length = i32(s, 16) # unreliable, ignore. + file_type = i32(s, 20) + palette_type = i32(s, 24) # 0: None, 1: RGB, 2: Raw/arbitrary + palette_length = i32(s, 28) + + if depth == 1: + self._mode, rawmode = "1", "1;I" + elif depth == 4: + self._mode, rawmode = "L", "L;4" + elif depth == 8: + self._mode = rawmode = "L" + elif depth == 24: + if file_type == 3: + self._mode, rawmode = "RGB", "RGB" + else: + self._mode, rawmode = "RGB", "BGR" + elif depth == 32: + if file_type == 3: + self._mode, rawmode = "RGB", "RGBX" + else: + self._mode, rawmode = "RGB", "BGRX" + else: + msg = "Unsupported Mode/Bit Depth" + raise SyntaxError(msg) + + if palette_length: + if palette_length > 1024: + msg = "Unsupported Color Palette Length" + raise SyntaxError(msg) + + if palette_type != 1: + msg = "Unsupported Palette Type" + raise SyntaxError(msg) + + offset = offset + palette_length + self.palette = ImagePalette.raw("RGB;L", self.fp.read(palette_length)) + if self.mode == "L": + self._mode = "P" + rawmode = rawmode.replace("L", "P") + + # 16 bit boundaries on stride + stride = ((self.size[0] * depth + 15) // 16) * 2 + + # file type: Type is the version (or flavor) of the bitmap + # file. The following values are typically found in the Type + # field: + # 0000h Old + # 0001h Standard + # 0002h Byte-encoded + # 0003h RGB format + # 0004h TIFF format + # 0005h IFF format + # FFFFh Experimental + + # Old and standard are the same, except for the length tag. + # byte-encoded is run-length-encoded + # RGB looks similar to standard, but RGB byte order + # TIFF and IFF mean that they were converted from T/IFF + # Experimental means that it's something else. + # (https://www.fileformat.info/format/sunraster/egff.htm) + + if file_type in (0, 1, 3, 4, 5): + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, offset, (rawmode, stride)) + ] + elif file_type == 2: + self.tile = [ + ImageFile._Tile("sun_rle", (0, 0) + self.size, offset, rawmode) + ] + else: + msg = "Unsupported Sun Raster file type" + raise SyntaxError(msg) + + +# +# registry + + +Image.register_open(SunImageFile.format, SunImageFile, _accept) + +Image.register_extension(SunImageFile.format, ".ras") diff --git a/venv/Lib/site-packages/PIL/TarIO.py b/venv/Lib/site-packages/PIL/TarIO.py new file mode 100644 index 00000000..86490a49 --- /dev/null +++ b/venv/Lib/site-packages/PIL/TarIO.py @@ -0,0 +1,61 @@ +# +# The Python Imaging Library. +# $Id$ +# +# read files from within a tar file +# +# History: +# 95-06-18 fl Created +# 96-05-28 fl Open files in binary mode +# +# Copyright (c) Secret Labs AB 1997. +# Copyright (c) Fredrik Lundh 1995-96. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io + +from . import ContainerIO + + +class TarIO(ContainerIO.ContainerIO[bytes]): + """A file object that provides read access to a given member of a TAR file.""" + + def __init__(self, tarfile: str, file: str) -> None: + """ + Create file object. + + :param tarfile: Name of TAR file. + :param file: Name of member file. + """ + self.fh = open(tarfile, "rb") + + while True: + s = self.fh.read(512) + if len(s) != 512: + self.fh.close() + + msg = "unexpected end of tar file" + raise OSError(msg) + + name = s[:100].decode("utf-8") + i = name.find("\0") + if i == 0: + self.fh.close() + + msg = "cannot find subfile" + raise OSError(msg) + if i > 0: + name = name[:i] + + size = int(s[124:135], 8) + + if file == name: + break + + self.fh.seek((size + 511) & (~511), io.SEEK_CUR) + + # Open region + super().__init__(self.fh, self.fh.tell(), size) diff --git a/venv/Lib/site-packages/PIL/TgaImagePlugin.py b/venv/Lib/site-packages/PIL/TgaImagePlugin.py new file mode 100644 index 00000000..90d5b5cf --- /dev/null +++ b/venv/Lib/site-packages/PIL/TgaImagePlugin.py @@ -0,0 +1,264 @@ +# +# The Python Imaging Library. +# $Id$ +# +# TGA file handling +# +# History: +# 95-09-01 fl created (reads 24-bit files only) +# 97-01-04 fl support more TGA versions, including compressed images +# 98-07-04 fl fixed orientation and alpha layer bugs +# 98-09-11 fl fixed orientation for runlength decoder +# +# Copyright (c) Secret Labs AB 1997-98. +# Copyright (c) Fredrik Lundh 1995-97. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import warnings +from typing import IO + +from . import Image, ImageFile, ImagePalette +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 + +# +# -------------------------------------------------------------------- +# Read RGA file + + +MODES = { + # map imagetype/depth to rawmode + (1, 8): "P", + (3, 1): "1", + (3, 8): "L", + (3, 16): "LA", + (2, 16): "BGRA;15Z", + (2, 24): "BGR", + (2, 32): "BGRA", +} + + +## +# Image plugin for Targa files. + + +class TgaImageFile(ImageFile.ImageFile): + format = "TGA" + format_description = "Targa" + + def _open(self) -> None: + # process header + assert self.fp is not None + + s = self.fp.read(18) + + id_len = s[0] + + colormaptype = s[1] + imagetype = s[2] + + depth = s[16] + + flags = s[17] + + self._size = i16(s, 12), i16(s, 14) + + # validate header fields + if ( + colormaptype not in (0, 1) + or self.size[0] <= 0 + or self.size[1] <= 0 + or depth not in (1, 8, 16, 24, 32) + ): + msg = "not a TGA file" + raise SyntaxError(msg) + + # image mode + if imagetype in (3, 11): + self._mode = "L" + if depth == 1: + self._mode = "1" # ??? + elif depth == 16: + self._mode = "LA" + elif imagetype in (1, 9): + self._mode = "P" if colormaptype else "L" + elif imagetype in (2, 10): + self._mode = "RGB" if depth == 24 else "RGBA" + else: + msg = "unknown TGA mode" + raise SyntaxError(msg) + + # orientation + orientation = flags & 0x30 + self._flip_horizontally = orientation in [0x10, 0x30] + if orientation in [0x20, 0x30]: + orientation = 1 + elif orientation in [0, 0x10]: + orientation = -1 + else: + msg = "unknown TGA orientation" + raise SyntaxError(msg) + + self.info["orientation"] = orientation + + if imagetype & 8: + self.info["compression"] = "tga_rle" + + if id_len: + self.info["id_section"] = self.fp.read(id_len) + + if colormaptype: + # read palette + start, size, mapdepth = i16(s, 3), i16(s, 5), s[7] + if mapdepth == 16: + self.palette = ImagePalette.raw( + "BGRA;15Z", bytes(2 * start) + self.fp.read(2 * size) + ) + self.palette.mode = "RGBA" + elif mapdepth == 24: + self.palette = ImagePalette.raw( + "BGR", bytes(3 * start) + self.fp.read(3 * size) + ) + elif mapdepth == 32: + self.palette = ImagePalette.raw( + "BGRA", bytes(4 * start) + self.fp.read(4 * size) + ) + else: + msg = "unknown TGA map depth" + raise SyntaxError(msg) + + # setup tile descriptor + try: + rawmode = MODES[(imagetype & 7, depth)] + if imagetype & 8: + # compressed + self.tile = [ + ImageFile._Tile( + "tga_rle", + (0, 0) + self.size, + self.fp.tell(), + (rawmode, orientation, depth), + ) + ] + else: + self.tile = [ + ImageFile._Tile( + "raw", + (0, 0) + self.size, + self.fp.tell(), + (rawmode, 0, orientation), + ) + ] + except KeyError: + pass # cannot decode + + def load_end(self) -> None: + if self._flip_horizontally: + self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + + +# +# -------------------------------------------------------------------- +# Write TGA file + + +SAVE = { + "1": ("1", 1, 0, 3), + "L": ("L", 8, 0, 3), + "LA": ("LA", 16, 0, 3), + "P": ("P", 8, 1, 1), + "RGB": ("BGR", 24, 0, 2), + "RGBA": ("BGRA", 32, 0, 2), +} + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + try: + rawmode, bits, colormaptype, imagetype = SAVE[im.mode] + except KeyError as e: + msg = f"cannot write mode {im.mode} as TGA" + raise OSError(msg) from e + + if "rle" in im.encoderinfo: + rle = im.encoderinfo["rle"] + else: + compression = im.encoderinfo.get("compression", im.info.get("compression")) + rle = compression == "tga_rle" + if rle: + imagetype += 8 + + id_section = im.encoderinfo.get("id_section", im.info.get("id_section", "")) + id_len = len(id_section) + if id_len > 255: + id_len = 255 + id_section = id_section[:255] + warnings.warn("id_section has been trimmed to 255 characters") + + if colormaptype: + palette = im.im.getpalette("RGB", "BGR") + colormaplength, colormapentry = len(palette) // 3, 24 + else: + colormaplength, colormapentry = 0, 0 + + if im.mode in ("LA", "RGBA"): + flags = 8 + else: + flags = 0 + + orientation = im.encoderinfo.get("orientation", im.info.get("orientation", -1)) + if orientation > 0: + flags = flags | 0x20 + + fp.write( + o8(id_len) + + o8(colormaptype) + + o8(imagetype) + + o16(0) # colormapfirst + + o16(colormaplength) + + o8(colormapentry) + + o16(0) + + o16(0) + + o16(im.size[0]) + + o16(im.size[1]) + + o8(bits) + + o8(flags) + ) + + if id_section: + fp.write(id_section) + + if colormaptype: + fp.write(palette) + + if rle: + ImageFile._save( + im, + fp, + [ImageFile._Tile("tga_rle", (0, 0) + im.size, 0, (rawmode, orientation))], + ) + else: + ImageFile._save( + im, + fp, + [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))], + ) + + # write targa version 2 footer + fp.write(b"\000" * 8 + b"TRUEVISION-XFILE." + b"\000") + + +# +# -------------------------------------------------------------------- +# Registry + + +Image.register_open(TgaImageFile.format, TgaImageFile) +Image.register_save(TgaImageFile.format, _save) + +Image.register_extensions(TgaImageFile.format, [".tga", ".icb", ".vda", ".vst"]) + +Image.register_mime(TgaImageFile.format, "image/x-tga") diff --git a/venv/Lib/site-packages/PIL/TiffImagePlugin.py b/venv/Lib/site-packages/PIL/TiffImagePlugin.py new file mode 100644 index 00000000..daf20f2e --- /dev/null +++ b/venv/Lib/site-packages/PIL/TiffImagePlugin.py @@ -0,0 +1,2339 @@ +# +# The Python Imaging Library. +# $Id$ +# +# TIFF file handling +# +# TIFF is a flexible, if somewhat aged, image file format originally +# defined by Aldus. Although TIFF supports a wide variety of pixel +# layouts and compression methods, the name doesn't really stand for +# "thousands of incompatible file formats," it just feels that way. +# +# To read TIFF data from a stream, the stream must be seekable. For +# progressive decoding, make sure to use TIFF files where the tag +# directory is placed first in the file. +# +# History: +# 1995-09-01 fl Created +# 1996-05-04 fl Handle JPEGTABLES tag +# 1996-05-18 fl Fixed COLORMAP support +# 1997-01-05 fl Fixed PREDICTOR support +# 1997-08-27 fl Added support for rational tags (from Perry Stoll) +# 1998-01-10 fl Fixed seek/tell (from Jan Blom) +# 1998-07-15 fl Use private names for internal variables +# 1999-06-13 fl Rewritten for PIL 1.0 (1.0) +# 2000-10-11 fl Additional fixes for Python 2.0 (1.1) +# 2001-04-17 fl Fixed rewind support (seek to frame 0) (1.2) +# 2001-05-12 fl Added write support for more tags (from Greg Couch) (1.3) +# 2001-12-18 fl Added workaround for broken Matrox library +# 2002-01-18 fl Don't mess up if photometric tag is missing (D. Alan Stewart) +# 2003-05-19 fl Check FILLORDER tag +# 2003-09-26 fl Added RGBa support +# 2004-02-24 fl Added DPI support; fixed rational write support +# 2005-02-07 fl Added workaround for broken Corel Draw 10 files +# 2006-01-09 fl Added support for float/double tags (from Russell Nelson) +# +# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved. +# Copyright (c) 1995-1997 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import io +import itertools +import logging +import math +import os +import struct +import warnings +from collections.abc import Iterator, MutableMapping +from fractions import Fraction +from numbers import Number, Rational +from typing import IO, Any, Callable, NoReturn, cast + +from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 +from ._deprecate import deprecate +from ._typing import StrOrBytesPath +from ._util import DeferredError, is_path +from .TiffTags import TYPES + +TYPE_CHECKING = False +if TYPE_CHECKING: + from ._typing import Buffer, IntegralLike + +logger = logging.getLogger(__name__) + +# Set these to true to force use of libtiff for reading or writing. +READ_LIBTIFF = False +WRITE_LIBTIFF = False +STRIP_SIZE = 65536 + +II = b"II" # little-endian (Intel style) +MM = b"MM" # big-endian (Motorola style) + +# +# -------------------------------------------------------------------- +# Read TIFF files + +# a few tag names, just to make the code below a bit more readable +OSUBFILETYPE = 255 +IMAGEWIDTH = 256 +IMAGELENGTH = 257 +BITSPERSAMPLE = 258 +COMPRESSION = 259 +PHOTOMETRIC_INTERPRETATION = 262 +FILLORDER = 266 +IMAGEDESCRIPTION = 270 +STRIPOFFSETS = 273 +SAMPLESPERPIXEL = 277 +ROWSPERSTRIP = 278 +STRIPBYTECOUNTS = 279 +X_RESOLUTION = 282 +Y_RESOLUTION = 283 +PLANAR_CONFIGURATION = 284 +RESOLUTION_UNIT = 296 +TRANSFERFUNCTION = 301 +SOFTWARE = 305 +DATE_TIME = 306 +ARTIST = 315 +PREDICTOR = 317 +COLORMAP = 320 +TILEWIDTH = 322 +TILELENGTH = 323 +TILEOFFSETS = 324 +TILEBYTECOUNTS = 325 +SUBIFD = 330 +EXTRASAMPLES = 338 +SAMPLEFORMAT = 339 +JPEGTABLES = 347 +YCBCRSUBSAMPLING = 530 +REFERENCEBLACKWHITE = 532 +COPYRIGHT = 33432 +IPTC_NAA_CHUNK = 33723 # newsphoto properties +PHOTOSHOP_CHUNK = 34377 # photoshop properties +ICCPROFILE = 34675 +EXIFIFD = 34665 +XMP = 700 +JPEGQUALITY = 65537 # pseudo-tag by libtiff + +# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java +IMAGEJ_META_DATA_BYTE_COUNTS = 50838 +IMAGEJ_META_DATA = 50839 + +COMPRESSION_INFO = { + # Compression => pil compression name + 1: "raw", + 2: "tiff_ccitt", + 3: "group3", + 4: "group4", + 5: "tiff_lzw", + 6: "tiff_jpeg", # obsolete + 7: "jpeg", + 8: "tiff_adobe_deflate", + 32771: "tiff_raw_16", # 16-bit padding + 32773: "packbits", + 32809: "tiff_thunderscan", + 32946: "tiff_deflate", + 34676: "tiff_sgilog", + 34677: "tiff_sgilog24", + 34925: "lzma", + 50000: "zstd", + 50001: "webp", +} + +COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()} + +OPEN_INFO = { + # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, + # ExtraSamples) => mode, rawmode + (II, 0, (1,), 1, (1,), ()): ("1", "1;I"), + (MM, 0, (1,), 1, (1,), ()): ("1", "1;I"), + (II, 0, (1,), 2, (1,), ()): ("1", "1;IR"), + (MM, 0, (1,), 2, (1,), ()): ("1", "1;IR"), + (II, 1, (1,), 1, (1,), ()): ("1", "1"), + (MM, 1, (1,), 1, (1,), ()): ("1", "1"), + (II, 1, (1,), 2, (1,), ()): ("1", "1;R"), + (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"), + (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"), + (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"), + (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), + (MM, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), + (II, 1, (1,), 1, (2,), ()): ("L", "L;2"), + (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"), + (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"), + (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"), + (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"), + (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"), + (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), + (MM, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), + (II, 1, (1,), 1, (4,), ()): ("L", "L;4"), + (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"), + (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"), + (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"), + (II, 0, (1,), 1, (8,), ()): ("L", "L;I"), + (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"), + (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), + (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), + (II, 1, (1,), 1, (8,), ()): ("L", "L"), + (MM, 1, (1,), 1, (8,), ()): ("L", "L"), + (II, 1, (2,), 1, (8,), ()): ("L", "L"), + (MM, 1, (2,), 1, (8,), ()): ("L", "L"), + (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), + (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), + (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), + (II, 0, (1,), 1, (16,), ()): ("I;16", "I;16"), + (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), + (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), + (II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"), + (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"), + (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"), + (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"), + (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"), + (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"), + (II, 1, (2,), 1, (32,), ()): ("I", "I;32S"), + (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"), + (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), + (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), + (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), + (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), + (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), + (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), + (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), + (MM, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), + (II, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples + (MM, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples + (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"), + (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"), + (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), + (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"), + (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), + (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), + (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 + (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 + (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"), + (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"), + (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"), + (MM, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16B"), + (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16L"), + (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16B"), + (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16L"), + (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"), + (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"), + (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"), + (II, 3, (1,), 1, (1,), ()): ("P", "P;1"), + (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"), + (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"), + (MM, 3, (1,), 2, (1,), ()): ("P", "P;1R"), + (II, 3, (1,), 1, (2,), ()): ("P", "P;2"), + (MM, 3, (1,), 1, (2,), ()): ("P", "P;2"), + (II, 3, (1,), 2, (2,), ()): ("P", "P;2R"), + (MM, 3, (1,), 2, (2,), ()): ("P", "P;2R"), + (II, 3, (1,), 1, (4,), ()): ("P", "P;4"), + (MM, 3, (1,), 1, (4,), ()): ("P", "P;4"), + (II, 3, (1,), 2, (4,), ()): ("P", "P;4R"), + (MM, 3, (1,), 2, (4,), ()): ("P", "P;4R"), + (II, 3, (1,), 1, (8,), ()): ("P", "P"), + (MM, 3, (1,), 1, (8,), ()): ("P", "P"), + (II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"), + (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), + (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), + (II, 3, (1,), 2, (8,), ()): ("P", "P;R"), + (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"), + (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), + (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), + (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), + (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), + (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), + (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), + (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"), + (MM, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16B"), + (II, 6, (1,), 1, (8,), ()): ("L", "L"), + (MM, 6, (1,), 1, (8,), ()): ("L", "L"), + # JPEG compressed images handled by LibTiff and auto-converted to RGBX + # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel + (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), + (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), + (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), + (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), +} + +MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO) + +PREFIXES = [ + b"MM\x00\x2a", # Valid TIFF header with big-endian byte order + b"II\x2a\x00", # Valid TIFF header with little-endian byte order + b"MM\x2a\x00", # Invalid TIFF header, assume big-endian + b"II\x00\x2a", # Invalid TIFF header, assume little-endian + b"MM\x00\x2b", # BigTIFF with big-endian byte order + b"II\x2b\x00", # BigTIFF with little-endian byte order +] + +if not getattr(Image.core, "libtiff_support_custom_tags", True): + deprecate("Support for LibTIFF earlier than version 4", 12) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(tuple(PREFIXES)) + + +def _limit_rational( + val: float | Fraction | IFDRational, max_val: int +) -> tuple[IntegralLike, IntegralLike]: + inv = abs(val) > 1 + n_d = IFDRational(1 / val if inv else val).limit_rational(max_val) + return n_d[::-1] if inv else n_d + + +def _limit_signed_rational( + val: IFDRational, max_val: int, min_val: int +) -> tuple[IntegralLike, IntegralLike]: + frac = Fraction(val) + n_d: tuple[IntegralLike, IntegralLike] = frac.numerator, frac.denominator + + if min(float(i) for i in n_d) < min_val: + n_d = _limit_rational(val, abs(min_val)) + + n_d_float = tuple(float(i) for i in n_d) + if max(n_d_float) > max_val: + n_d = _limit_rational(n_d_float[0] / n_d_float[1], max_val) + + return n_d + + +## +# Wrapper for TIFF IFDs. + +_load_dispatch = {} +_write_dispatch = {} + + +def _delegate(op: str) -> Any: + def delegate( + self: IFDRational, *args: tuple[float, ...] + ) -> bool | float | Fraction: + return getattr(self._val, op)(*args) + + return delegate + + +class IFDRational(Rational): + """Implements a rational class where 0/0 is a legal value to match + the in the wild use of exif rationals. + + e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used + """ + + """ If the denominator is 0, store this as a float('nan'), otherwise store + as a fractions.Fraction(). Delegate as appropriate + + """ + + __slots__ = ("_numerator", "_denominator", "_val") + + def __init__( + self, value: float | Fraction | IFDRational, denominator: int = 1 + ) -> None: + """ + :param value: either an integer numerator, a + float/rational/other number, or an IFDRational + :param denominator: Optional integer denominator + """ + self._val: Fraction | float + if isinstance(value, IFDRational): + self._numerator = value.numerator + self._denominator = value.denominator + self._val = value._val + return + + if isinstance(value, Fraction): + self._numerator = value.numerator + self._denominator = value.denominator + else: + if TYPE_CHECKING: + self._numerator = cast(IntegralLike, value) + else: + self._numerator = value + self._denominator = denominator + + if denominator == 0: + self._val = float("nan") + elif denominator == 1: + self._val = Fraction(value) + elif int(value) == value: + self._val = Fraction(int(value), denominator) + else: + self._val = Fraction(value / denominator) + + @property + def numerator(self) -> IntegralLike: + return self._numerator + + @property + def denominator(self) -> int: + return self._denominator + + def limit_rational(self, max_denominator: int) -> tuple[IntegralLike, int]: + """ + + :param max_denominator: Integer, the maximum denominator value + :returns: Tuple of (numerator, denominator) + """ + + if self.denominator == 0: + return self.numerator, self.denominator + + assert isinstance(self._val, Fraction) + f = self._val.limit_denominator(max_denominator) + return f.numerator, f.denominator + + def __repr__(self) -> str: + return str(float(self._val)) + + def __hash__(self) -> int: # type: ignore[override] + return self._val.__hash__() + + def __eq__(self, other: object) -> bool: + val = self._val + if isinstance(other, IFDRational): + other = other._val + if isinstance(other, float): + val = float(val) + return val == other + + def __getstate__(self) -> list[float | Fraction | IntegralLike]: + return [self._val, self._numerator, self._denominator] + + def __setstate__(self, state: list[float | Fraction | IntegralLike]) -> None: + IFDRational.__init__(self, 0) + _val, _numerator, _denominator = state + assert isinstance(_val, (float, Fraction)) + self._val = _val + if TYPE_CHECKING: + self._numerator = cast(IntegralLike, _numerator) + else: + self._numerator = _numerator + assert isinstance(_denominator, int) + self._denominator = _denominator + + """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul', + 'truediv', 'rtruediv', 'floordiv', 'rfloordiv', + 'mod','rmod', 'pow','rpow', 'pos', 'neg', + 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool', + 'ceil', 'floor', 'round'] + print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)) + """ + + __add__ = _delegate("__add__") + __radd__ = _delegate("__radd__") + __sub__ = _delegate("__sub__") + __rsub__ = _delegate("__rsub__") + __mul__ = _delegate("__mul__") + __rmul__ = _delegate("__rmul__") + __truediv__ = _delegate("__truediv__") + __rtruediv__ = _delegate("__rtruediv__") + __floordiv__ = _delegate("__floordiv__") + __rfloordiv__ = _delegate("__rfloordiv__") + __mod__ = _delegate("__mod__") + __rmod__ = _delegate("__rmod__") + __pow__ = _delegate("__pow__") + __rpow__ = _delegate("__rpow__") + __pos__ = _delegate("__pos__") + __neg__ = _delegate("__neg__") + __abs__ = _delegate("__abs__") + __trunc__ = _delegate("__trunc__") + __lt__ = _delegate("__lt__") + __gt__ = _delegate("__gt__") + __le__ = _delegate("__le__") + __ge__ = _delegate("__ge__") + __bool__ = _delegate("__bool__") + __ceil__ = _delegate("__ceil__") + __floor__ = _delegate("__floor__") + __round__ = _delegate("__round__") + # Python >= 3.11 + if hasattr(Fraction, "__int__"): + __int__ = _delegate("__int__") + + +_LoaderFunc = Callable[["ImageFileDirectory_v2", bytes, bool], Any] + + +def _register_loader(idx: int, size: int) -> Callable[[_LoaderFunc], _LoaderFunc]: + def decorator(func: _LoaderFunc) -> _LoaderFunc: + from .TiffTags import TYPES + + if func.__name__.startswith("load_"): + TYPES[idx] = func.__name__[5:].replace("_", " ") + _load_dispatch[idx] = size, func # noqa: F821 + return func + + return decorator + + +def _register_writer(idx: int) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: + _write_dispatch[idx] = func # noqa: F821 + return func + + return decorator + + +def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None: + from .TiffTags import TYPES + + idx, fmt, name = idx_fmt_name + TYPES[idx] = name + size = struct.calcsize(f"={fmt}") + + def basic_handler( + self: ImageFileDirectory_v2, data: bytes, legacy_api: bool = True + ) -> tuple[Any, ...]: + return self._unpack(f"{len(data) // size}{fmt}", data) + + _load_dispatch[idx] = size, basic_handler # noqa: F821 + _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 + b"".join(self._pack(fmt, value) for value in values) + ) + + +if TYPE_CHECKING: + _IFDv2Base = MutableMapping[int, Any] +else: + _IFDv2Base = MutableMapping + + +class ImageFileDirectory_v2(_IFDv2Base): + """This class represents a TIFF tag directory. To speed things up, we + don't decode tags unless they're asked for. + + Exposes a dictionary interface of the tags in the directory:: + + ifd = ImageFileDirectory_v2() + ifd[key] = 'Some Data' + ifd.tagtype[key] = TiffTags.ASCII + print(ifd[key]) + 'Some Data' + + Individual values are returned as the strings or numbers, sequences are + returned as tuples of the values. + + The tiff metadata type of each item is stored in a dictionary of + tag types in + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types + are read from a tiff file, guessed from the type added, or added + manually. + + Data Structures: + + * ``self.tagtype = {}`` + + * Key: numerical TIFF tag number + * Value: integer corresponding to the data type from + :py:data:`.TiffTags.TYPES` + + .. versionadded:: 3.0.0 + + 'Internal' data structures: + + * ``self._tags_v2 = {}`` + + * Key: numerical TIFF tag number + * Value: decoded data, as tuple for multiple values + + * ``self._tagdata = {}`` + + * Key: numerical TIFF tag number + * Value: undecoded byte string from file + + * ``self._tags_v1 = {}`` + + * Key: numerical TIFF tag number + * Value: decoded data in the v1 format + + Tags will be found in the private attributes ``self._tagdata``, and in + ``self._tags_v2`` once decoded. + + ``self.legacy_api`` is a value for internal use, and shouldn't be changed + from outside code. In cooperation with + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`, if ``legacy_api`` + is true, then decoded tags will be populated into both ``_tags_v1`` and + ``_tags_v2``. ``_tags_v2`` will be used if this IFD is used in the TIFF + save routine. Tags should be read from ``_tags_v1`` if + ``legacy_api == true``. + + """ + + _load_dispatch: dict[int, tuple[int, _LoaderFunc]] = {} + _write_dispatch: dict[int, Callable[..., Any]] = {} + + def __init__( + self, + ifh: bytes = b"II\x2a\x00\x00\x00\x00\x00", + prefix: bytes | None = None, + group: int | None = None, + ) -> None: + """Initialize an ImageFileDirectory. + + To construct an ImageFileDirectory from a real file, pass the 8-byte + magic header to the constructor. To only set the endianness, pass it + as the 'prefix' keyword argument. + + :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets + endianness. + :param prefix: Override the endianness of the file. + """ + if not _accept(ifh): + msg = f"not a TIFF file (header {repr(ifh)} not valid)" + raise SyntaxError(msg) + self._prefix = prefix if prefix is not None else ifh[:2] + if self._prefix == MM: + self._endian = ">" + elif self._prefix == II: + self._endian = "<" + else: + msg = "not a TIFF IFD" + raise SyntaxError(msg) + self._bigtiff = ifh[2] == 43 + self.group = group + self.tagtype: dict[int, int] = {} + """ Dictionary of tag types """ + self.reset() + self.next = ( + self._unpack("Q", ifh[8:])[0] + if self._bigtiff + else self._unpack("L", ifh[4:])[0] + ) + self._legacy_api = False + + prefix = property(lambda self: self._prefix) + offset = property(lambda self: self._offset) + + @property + def legacy_api(self) -> bool: + return self._legacy_api + + @legacy_api.setter + def legacy_api(self, value: bool) -> NoReturn: + msg = "Not allowing setting of legacy api" + raise Exception(msg) + + def reset(self) -> None: + self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false + self._tags_v2: dict[int, Any] = {} # main tag storage + self._tagdata: dict[int, bytes] = {} + self.tagtype = {} # added 2008-06-05 by Florian Hoech + self._next = None + self._offset: int | None = None + + def __str__(self) -> str: + return str(dict(self)) + + def named(self) -> dict[str, Any]: + """ + :returns: dict of name|key: value + + Returns the complete tag dictionary, with named tags where possible. + """ + return { + TiffTags.lookup(code, self.group).name: value + for code, value in self.items() + } + + def __len__(self) -> int: + return len(set(self._tagdata) | set(self._tags_v2)) + + def __getitem__(self, tag: int) -> Any: + if tag not in self._tags_v2: # unpack on the fly + data = self._tagdata[tag] + typ = self.tagtype[tag] + size, handler = self._load_dispatch[typ] + self[tag] = handler(self, data, self.legacy_api) # check type + val = self._tags_v2[tag] + if self.legacy_api and not isinstance(val, (tuple, bytes)): + val = (val,) + return val + + def __contains__(self, tag: object) -> bool: + return tag in self._tags_v2 or tag in self._tagdata + + def __setitem__(self, tag: int, value: Any) -> None: + self._setitem(tag, value, self.legacy_api) + + def _setitem(self, tag: int, value: Any, legacy_api: bool) -> None: + basetypes = (Number, bytes, str) + + info = TiffTags.lookup(tag, self.group) + values = [value] if isinstance(value, basetypes) else value + + if tag not in self.tagtype: + if info.type: + self.tagtype[tag] = info.type + else: + self.tagtype[tag] = TiffTags.UNDEFINED + if all(isinstance(v, IFDRational) for v in values): + for v in values: + assert isinstance(v, IFDRational) + if v < 0: + self.tagtype[tag] = TiffTags.SIGNED_RATIONAL + break + else: + self.tagtype[tag] = TiffTags.RATIONAL + elif all(isinstance(v, int) for v in values): + short = True + signed_short = True + long = True + for v in values: + assert isinstance(v, int) + if short and not (0 <= v < 2**16): + short = False + if signed_short and not (-(2**15) < v < 2**15): + signed_short = False + if long and v < 0: + long = False + if short: + self.tagtype[tag] = TiffTags.SHORT + elif signed_short: + self.tagtype[tag] = TiffTags.SIGNED_SHORT + elif long: + self.tagtype[tag] = TiffTags.LONG + else: + self.tagtype[tag] = TiffTags.SIGNED_LONG + elif all(isinstance(v, float) for v in values): + self.tagtype[tag] = TiffTags.DOUBLE + elif all(isinstance(v, str) for v in values): + self.tagtype[tag] = TiffTags.ASCII + elif all(isinstance(v, bytes) for v in values): + self.tagtype[tag] = TiffTags.BYTE + + if self.tagtype[tag] == TiffTags.UNDEFINED: + values = [ + v.encode("ascii", "replace") if isinstance(v, str) else v + for v in values + ] + elif self.tagtype[tag] == TiffTags.RATIONAL: + values = [float(v) if isinstance(v, int) else v for v in values] + + is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict) + if not is_ifd: + values = tuple( + info.cvt_enum(value) if isinstance(value, str) else value + for value in values + ) + + dest = self._tags_v1 if legacy_api else self._tags_v2 + + # Three branches: + # Spec'd length == 1, Actual length 1, store as element + # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. + # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. + # Don't mess with the legacy api, since it's frozen. + if not is_ifd and ( + (info.length == 1) + or self.tagtype[tag] == TiffTags.BYTE + or (info.length is None and len(values) == 1 and not legacy_api) + ): + # Don't mess with the legacy api, since it's frozen. + if legacy_api and self.tagtype[tag] in [ + TiffTags.RATIONAL, + TiffTags.SIGNED_RATIONAL, + ]: # rationals + values = (values,) + try: + (dest[tag],) = values + except ValueError: + # We've got a builtin tag with 1 expected entry + warnings.warn( + f"Metadata Warning, tag {tag} had too many entries: " + f"{len(values)}, expected 1" + ) + dest[tag] = values[0] + + else: + # Spec'd length > 1 or undefined + # Unspec'd, and length > 1 + dest[tag] = values + + def __delitem__(self, tag: int) -> None: + self._tags_v2.pop(tag, None) + self._tags_v1.pop(tag, None) + self._tagdata.pop(tag, None) + + def __iter__(self) -> Iterator[int]: + return iter(set(self._tagdata) | set(self._tags_v2)) + + def _unpack(self, fmt: str, data: bytes) -> tuple[Any, ...]: + return struct.unpack(self._endian + fmt, data) + + def _pack(self, fmt: str, *values: Any) -> bytes: + return struct.pack(self._endian + fmt, *values) + + list( + map( + _register_basic, + [ + (TiffTags.SHORT, "H", "short"), + (TiffTags.LONG, "L", "long"), + (TiffTags.SIGNED_BYTE, "b", "signed byte"), + (TiffTags.SIGNED_SHORT, "h", "signed short"), + (TiffTags.SIGNED_LONG, "l", "signed long"), + (TiffTags.FLOAT, "f", "float"), + (TiffTags.DOUBLE, "d", "double"), + (TiffTags.IFD, "L", "long"), + (TiffTags.LONG8, "Q", "long8"), + ], + ) + ) + + @_register_loader(1, 1) # Basic type, except for the legacy API. + def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes: + return data + + @_register_writer(1) # Basic type, except for the legacy API. + def write_byte(self, data: bytes | int | IFDRational) -> bytes: + if isinstance(data, IFDRational): + data = int(data) + if isinstance(data, int): + data = bytes((data,)) + return data + + @_register_loader(2, 1) + def load_string(self, data: bytes, legacy_api: bool = True) -> str: + if data.endswith(b"\0"): + data = data[:-1] + return data.decode("latin-1", "replace") + + @_register_writer(2) + def write_string(self, value: str | bytes | int) -> bytes: + # remerge of https://github.com/python-pillow/Pillow/pull/1416 + if isinstance(value, int): + value = str(value) + if not isinstance(value, bytes): + value = value.encode("ascii", "replace") + return value + b"\0" + + @_register_loader(5, 8) + def load_rational( + self, data: bytes, legacy_api: bool = True + ) -> tuple[tuple[int, int] | IFDRational, ...]: + vals = self._unpack(f"{len(data) // 4}L", data) + + def combine(a: int, b: int) -> tuple[int, int] | IFDRational: + return (a, b) if legacy_api else IFDRational(a, b) + + return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) + + @_register_writer(5) + def write_rational(self, *values: IFDRational) -> bytes: + return b"".join( + self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values + ) + + @_register_loader(7, 1) + def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes: + return data + + @_register_writer(7) + def write_undefined(self, value: bytes | int | IFDRational) -> bytes: + if isinstance(value, IFDRational): + value = int(value) + if isinstance(value, int): + value = str(value).encode("ascii", "replace") + return value + + @_register_loader(10, 8) + def load_signed_rational( + self, data: bytes, legacy_api: bool = True + ) -> tuple[tuple[int, int] | IFDRational, ...]: + vals = self._unpack(f"{len(data) // 4}l", data) + + def combine(a: int, b: int) -> tuple[int, int] | IFDRational: + return (a, b) if legacy_api else IFDRational(a, b) + + return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) + + @_register_writer(10) + def write_signed_rational(self, *values: IFDRational) -> bytes: + return b"".join( + self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31))) + for frac in values + ) + + def _ensure_read(self, fp: IO[bytes], size: int) -> bytes: + ret = fp.read(size) + if len(ret) != size: + msg = ( + "Corrupt EXIF data. " + f"Expecting to read {size} bytes but only got {len(ret)}. " + ) + raise OSError(msg) + return ret + + def load(self, fp: IO[bytes]) -> None: + self.reset() + self._offset = fp.tell() + + try: + tag_count = ( + self._unpack("Q", self._ensure_read(fp, 8)) + if self._bigtiff + else self._unpack("H", self._ensure_read(fp, 2)) + )[0] + for i in range(tag_count): + tag, typ, count, data = ( + self._unpack("HHQ8s", self._ensure_read(fp, 20)) + if self._bigtiff + else self._unpack("HHL4s", self._ensure_read(fp, 12)) + ) + + tagname = TiffTags.lookup(tag, self.group).name + typname = TYPES.get(typ, "unknown") + msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})" + + try: + unit_size, handler = self._load_dispatch[typ] + except KeyError: + logger.debug("%s - unsupported type %s", msg, typ) + continue # ignore unsupported type + size = count * unit_size + if size > (8 if self._bigtiff else 4): + here = fp.tell() + (offset,) = self._unpack("Q" if self._bigtiff else "L", data) + msg += f" Tag Location: {here} - Data Location: {offset}" + fp.seek(offset) + data = ImageFile._safe_read(fp, size) + fp.seek(here) + else: + data = data[:size] + + if len(data) != size: + warnings.warn( + "Possibly corrupt EXIF data. " + f"Expecting to read {size} bytes but only got {len(data)}." + f" Skipping tag {tag}" + ) + logger.debug(msg) + continue + + if not data: + logger.debug(msg) + continue + + self._tagdata[tag] = data + self.tagtype[tag] = typ + + msg += " - value: " + msg += f"" if size > 32 else repr(data) + + logger.debug(msg) + + (self.next,) = ( + self._unpack("Q", self._ensure_read(fp, 8)) + if self._bigtiff + else self._unpack("L", self._ensure_read(fp, 4)) + ) + except OSError as msg: + warnings.warn(str(msg)) + return + + def _get_ifh(self) -> bytes: + ifh = self._prefix + self._pack("H", 43 if self._bigtiff else 42) + if self._bigtiff: + ifh += self._pack("HH", 8, 0) + ifh += self._pack("Q", 16) if self._bigtiff else self._pack("L", 8) + + return ifh + + def tobytes(self, offset: int = 0) -> bytes: + # FIXME What about tagdata? + result = self._pack("Q" if self._bigtiff else "H", len(self._tags_v2)) + + entries: list[tuple[int, int, int, bytes, bytes]] = [] + + fmt = "Q" if self._bigtiff else "L" + fmt_size = 8 if self._bigtiff else 4 + offset += ( + len(result) + len(self._tags_v2) * (20 if self._bigtiff else 12) + fmt_size + ) + stripoffsets = None + + # pass 1: convert tags to binary format + # always write tags in ascending order + for tag, value in sorted(self._tags_v2.items()): + if tag == STRIPOFFSETS: + stripoffsets = len(entries) + typ = self.tagtype[tag] + logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value)) + is_ifd = typ == TiffTags.LONG and isinstance(value, dict) + if is_ifd: + ifd = ImageFileDirectory_v2(self._get_ifh(), group=tag) + values = self._tags_v2[tag] + for ifd_tag, ifd_value in values.items(): + ifd[ifd_tag] = ifd_value + data = ifd.tobytes(offset) + else: + values = value if isinstance(value, tuple) else (value,) + data = self._write_dispatch[typ](self, *values) + + tagname = TiffTags.lookup(tag, self.group).name + typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") + msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: " + msg += f"" if len(data) >= 16 else str(values) + logger.debug(msg) + + # count is sum of lengths for string and arbitrary data + if is_ifd: + count = 1 + elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]: + count = len(data) + else: + count = len(values) + # figure out if data fits into the entry + if len(data) <= fmt_size: + entries.append((tag, typ, count, data.ljust(fmt_size, b"\0"), b"")) + else: + entries.append((tag, typ, count, self._pack(fmt, offset), data)) + offset += (len(data) + 1) // 2 * 2 # pad to word + + # update strip offset data to point beyond auxiliary data + if stripoffsets is not None: + tag, typ, count, value, data = entries[stripoffsets] + if data: + size, handler = self._load_dispatch[typ] + values = [val + offset for val in handler(self, data, self.legacy_api)] + data = self._write_dispatch[typ](self, *values) + else: + value = self._pack(fmt, self._unpack(fmt, value)[0] + offset) + entries[stripoffsets] = tag, typ, count, value, data + + # pass 2: write entries to file + for tag, typ, count, value, data in entries: + logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data)) + result += self._pack( + "HHQ8s" if self._bigtiff else "HHL4s", tag, typ, count, value + ) + + # -- overwrite here for multi-page -- + result += self._pack(fmt, 0) # end of entries + + # pass 3: write auxiliary data to file + for tag, typ, count, value, data in entries: + result += data + if len(data) & 1: + result += b"\0" + + return result + + def save(self, fp: IO[bytes]) -> int: + if fp.tell() == 0: # skip TIFF header on subsequent pages + fp.write(self._get_ifh()) + + offset = fp.tell() + result = self.tobytes(offset) + fp.write(result) + return offset + len(result) + + +ImageFileDirectory_v2._load_dispatch = _load_dispatch +ImageFileDirectory_v2._write_dispatch = _write_dispatch +for idx, name in TYPES.items(): + name = name.replace(" ", "_") + setattr(ImageFileDirectory_v2, f"load_{name}", _load_dispatch[idx][1]) + setattr(ImageFileDirectory_v2, f"write_{name}", _write_dispatch[idx]) +del _load_dispatch, _write_dispatch, idx, name + + +# Legacy ImageFileDirectory support. +class ImageFileDirectory_v1(ImageFileDirectory_v2): + """This class represents the **legacy** interface to a TIFF tag directory. + + Exposes a dictionary interface of the tags in the directory:: + + ifd = ImageFileDirectory_v1() + ifd[key] = 'Some Data' + ifd.tagtype[key] = TiffTags.ASCII + print(ifd[key]) + ('Some Data',) + + Also contains a dictionary of tag types as read from the tiff image file, + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. + + Values are returned as a tuple. + + .. deprecated:: 3.0.0 + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self._legacy_api = True + + tags = property(lambda self: self._tags_v1) + tagdata = property(lambda self: self._tagdata) + + # defined in ImageFileDirectory_v2 + tagtype: dict[int, int] + """Dictionary of tag types""" + + @classmethod + def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1: + """Returns an + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` + instance with the same data as is contained in the original + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` + instance. + + :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` + + """ + + ifd = cls(prefix=original.prefix) + ifd._tagdata = original._tagdata + ifd.tagtype = original.tagtype + ifd.next = original.next # an indicator for multipage tiffs + return ifd + + def to_v2(self) -> ImageFileDirectory_v2: + """Returns an + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` + instance with the same data as is contained in the original + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` + instance. + + :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` + + """ + + ifd = ImageFileDirectory_v2(prefix=self.prefix) + ifd._tagdata = dict(self._tagdata) + ifd.tagtype = dict(self.tagtype) + ifd._tags_v2 = dict(self._tags_v2) + return ifd + + def __contains__(self, tag: object) -> bool: + return tag in self._tags_v1 or tag in self._tagdata + + def __len__(self) -> int: + return len(set(self._tagdata) | set(self._tags_v1)) + + def __iter__(self) -> Iterator[int]: + return iter(set(self._tagdata) | set(self._tags_v1)) + + def __setitem__(self, tag: int, value: Any) -> None: + for legacy_api in (False, True): + self._setitem(tag, value, legacy_api) + + def __getitem__(self, tag: int) -> Any: + if tag not in self._tags_v1: # unpack on the fly + data = self._tagdata[tag] + typ = self.tagtype[tag] + size, handler = self._load_dispatch[typ] + for legacy in (False, True): + self._setitem(tag, handler(self, data, legacy), legacy) + val = self._tags_v1[tag] + if not isinstance(val, (tuple, bytes)): + val = (val,) + return val + + +# undone -- switch this pointer +ImageFileDirectory = ImageFileDirectory_v1 + + +## +# Image plugin for TIFF files. + + +class TiffImageFile(ImageFile.ImageFile): + format = "TIFF" + format_description = "Adobe TIFF" + _close_exclusive_fp_after_loading = False + + def __init__( + self, + fp: StrOrBytesPath | IO[bytes], + filename: str | bytes | None = None, + ) -> None: + self.tag_v2: ImageFileDirectory_v2 + """ Image file directory (tag dictionary) """ + + self.tag: ImageFileDirectory_v1 + """ Legacy tag entries """ + + super().__init__(fp, filename) + + def _open(self) -> None: + """Open the first image in a TIFF file""" + + # Header + ifh = self.fp.read(8) + if ifh[2] == 43: + ifh += self.fp.read(8) + + self.tag_v2 = ImageFileDirectory_v2(ifh) + + # setup frame pointers + self.__first = self.__next = self.tag_v2.next + self.__frame = -1 + self._fp = self.fp + self._frame_pos: list[int] = [] + self._n_frames: int | None = None + + logger.debug("*** TiffImageFile._open ***") + logger.debug("- __first: %s", self.__first) + logger.debug("- ifh: %s", repr(ifh)) # Use repr to avoid str(bytes) + + # and load the first frame + self._seek(0) + + @property + def n_frames(self) -> int: + current_n_frames = self._n_frames + if current_n_frames is None: + current = self.tell() + self._seek(len(self._frame_pos)) + while self._n_frames is None: + self._seek(self.tell() + 1) + self.seek(current) + assert self._n_frames is not None + return self._n_frames + + def seek(self, frame: int) -> None: + """Select a given frame as current image""" + if not self._seek_check(frame): + return + self._seek(frame) + if self._im is not None and ( + self.im.size != self._tile_size + or self.im.mode != self.mode + or self.readonly + ): + self._im = None + + def _seek(self, frame: int) -> None: + if isinstance(self._fp, DeferredError): + raise self._fp.ex + self.fp = self._fp + + while len(self._frame_pos) <= frame: + if not self.__next: + msg = "no more images in TIFF file" + raise EOFError(msg) + logger.debug( + "Seeking to frame %s, on frame %s, __next %s, location: %s", + frame, + self.__frame, + self.__next, + self.fp.tell(), + ) + if self.__next >= 2**63: + msg = "Unable to seek to frame" + raise ValueError(msg) + self.fp.seek(self.__next) + self._frame_pos.append(self.__next) + logger.debug("Loading tags, location: %s", self.fp.tell()) + self.tag_v2.load(self.fp) + if self.tag_v2.next in self._frame_pos: + # This IFD has already been processed + # Declare this to be the end of the image + self.__next = 0 + else: + self.__next = self.tag_v2.next + if self.__next == 0: + self._n_frames = frame + 1 + if len(self._frame_pos) == 1: + self.is_animated = self.__next != 0 + self.__frame += 1 + self.fp.seek(self._frame_pos[frame]) + self.tag_v2.load(self.fp) + if XMP in self.tag_v2: + xmp = self.tag_v2[XMP] + if isinstance(xmp, tuple) and len(xmp) == 1: + xmp = xmp[0] + self.info["xmp"] = xmp + elif "xmp" in self.info: + del self.info["xmp"] + self._reload_exif() + # fill the legacy tag/ifd entries + self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2) + self.__frame = frame + self._setup() + + def tell(self) -> int: + """Return the current frame number""" + return self.__frame + + def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]: + """ + Returns a dictionary of Photoshop "Image Resource Blocks". + The keys are the image resource ID. For more information, see + https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037727 + + :returns: Photoshop "Image Resource Blocks" in a dictionary. + """ + blocks = {} + val = self.tag_v2.get(ExifTags.Base.ImageResources) + if val: + while val.startswith(b"8BIM"): + id = i16(val[4:6]) + n = math.ceil((val[6] + 1) / 2) * 2 + size = i32(val[6 + n : 10 + n]) + data = val[10 + n : 10 + n + size] + blocks[id] = {"data": data} + + val = val[math.ceil((10 + n + size) / 2) * 2 :] + return blocks + + def load(self) -> Image.core.PixelAccess | None: + if self.tile and self.use_load_libtiff: + return self._load_libtiff() + return super().load() + + def load_prepare(self) -> None: + if self._im is None: + Image._decompression_bomb_check(self._tile_size) + self.im = Image.core.new(self.mode, self._tile_size) + ImageFile.ImageFile.load_prepare(self) + + def load_end(self) -> None: + # allow closing if we're on the first frame, there's no next + # This is the ImageFile.load path only, libtiff specific below. + if not self.is_animated: + self._close_exclusive_fp_after_loading = True + + # load IFD data from fp before it is closed + exif = self.getexif() + for key in TiffTags.TAGS_V2_GROUPS: + if key not in exif: + continue + exif.get_ifd(key) + + ImageOps.exif_transpose(self, in_place=True) + if ExifTags.Base.Orientation in self.tag_v2: + del self.tag_v2[ExifTags.Base.Orientation] + + def _load_libtiff(self) -> Image.core.PixelAccess | None: + """Overload method triggered when we detect a compressed tiff + Calls out to libtiff""" + + Image.Image.load(self) + + self.load_prepare() + + if not len(self.tile) == 1: + msg = "Not exactly one tile" + raise OSError(msg) + + # (self._compression, (extents tuple), + # 0, (rawmode, self._compression, fp)) + extents = self.tile[0][1] + args = self.tile[0][3] + + # To be nice on memory footprint, if there's a + # file descriptor, use that instead of reading + # into a string in python. + try: + fp = hasattr(self.fp, "fileno") and self.fp.fileno() + # flush the file descriptor, prevents error on pypy 2.4+ + # should also eliminate the need for fp.tell + # in _seek + if hasattr(self.fp, "flush"): + self.fp.flush() + except OSError: + # io.BytesIO have a fileno, but returns an OSError if + # it doesn't use a file descriptor. + fp = False + + if fp: + assert isinstance(args, tuple) + args_list = list(args) + args_list[2] = fp + args = tuple(args_list) + + decoder = Image._getdecoder(self.mode, "libtiff", args, self.decoderconfig) + try: + decoder.setimage(self.im, extents) + except ValueError as e: + msg = "Couldn't set the image" + raise OSError(msg) from e + + close_self_fp = self._exclusive_fp and not self.is_animated + if hasattr(self.fp, "getvalue"): + # We've got a stringio like thing passed in. Yay for all in memory. + # The decoder needs the entire file in one shot, so there's not + # a lot we can do here other than give it the entire file. + # unless we could do something like get the address of the + # underlying string for stringio. + # + # Rearranging for supporting byteio items, since they have a fileno + # that returns an OSError if there's no underlying fp. Easier to + # deal with here by reordering. + logger.debug("have getvalue. just sending in a string from getvalue") + n, err = decoder.decode(self.fp.getvalue()) + elif fp: + # we've got a actual file on disk, pass in the fp. + logger.debug("have fileno, calling fileno version of the decoder.") + if not close_self_fp: + self.fp.seek(0) + # Save and restore the file position, because libtiff will move it + # outside of the Python runtime, and that will confuse + # io.BufferedReader and possible others. + # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(), + # because the buffer read head already may not equal the actual + # file position, and fp.seek() may just adjust it's internal + # pointer and not actually seek the OS file handle. + pos = os.lseek(fp, 0, os.SEEK_CUR) + # 4 bytes, otherwise the trace might error out + n, err = decoder.decode(b"fpfp") + os.lseek(fp, pos, os.SEEK_SET) + else: + # we have something else. + logger.debug("don't have fileno or getvalue. just reading") + self.fp.seek(0) + # UNDONE -- so much for that buffer size thing. + n, err = decoder.decode(self.fp.read()) + + self.tile = [] + self.readonly = 0 + + self.load_end() + + if close_self_fp: + self.fp.close() + self.fp = None # might be shared + + if err < 0: + msg = f"decoder error {err}" + raise OSError(msg) + + return Image.Image.load(self) + + def _setup(self) -> None: + """Setup this image object based on current tags""" + + if 0xBC01 in self.tag_v2: + msg = "Windows Media Photo files not yet supported" + raise OSError(msg) + + # extract relevant tags + self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)] + self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1) + + # photometric is a required tag, but not everyone is reading + # the specification + photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0) + + # old style jpeg compression images most certainly are YCbCr + if self._compression == "tiff_jpeg": + photo = 6 + + fillorder = self.tag_v2.get(FILLORDER, 1) + + logger.debug("*** Summary ***") + logger.debug("- compression: %s", self._compression) + logger.debug("- photometric_interpretation: %s", photo) + logger.debug("- planar_configuration: %s", self._planar_configuration) + logger.debug("- fill_order: %s", fillorder) + logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING)) + + # size + try: + xsize = self.tag_v2[IMAGEWIDTH] + ysize = self.tag_v2[IMAGELENGTH] + except KeyError as e: + msg = "Missing dimensions" + raise TypeError(msg) from e + if not isinstance(xsize, int) or not isinstance(ysize, int): + msg = "Invalid dimensions" + raise ValueError(msg) + self._tile_size = xsize, ysize + orientation = self.tag_v2.get(ExifTags.Base.Orientation) + if orientation in (5, 6, 7, 8): + self._size = ysize, xsize + else: + self._size = xsize, ysize + + logger.debug("- size: %s", self.size) + + sample_format = self.tag_v2.get(SAMPLEFORMAT, (1,)) + if len(sample_format) > 1 and max(sample_format) == min(sample_format) == 1: + # SAMPLEFORMAT is properly per band, so an RGB image will + # be (1,1,1). But, we don't support per band pixel types, + # and anything more than one band is a uint8. So, just + # take the first element. Revisit this if adding support + # for more exotic images. + sample_format = (1,) + + bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,)) + extra_tuple = self.tag_v2.get(EXTRASAMPLES, ()) + if photo in (2, 6, 8): # RGB, YCbCr, LAB + bps_count = 3 + elif photo == 5: # CMYK + bps_count = 4 + else: + bps_count = 1 + bps_count += len(extra_tuple) + bps_actual_count = len(bps_tuple) + samples_per_pixel = self.tag_v2.get( + SAMPLESPERPIXEL, + 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1, + ) + + if samples_per_pixel > MAX_SAMPLESPERPIXEL: + # DOS check, samples_per_pixel can be a Long, and we extend the tuple below + logger.error( + "More samples per pixel than can be decoded: %s", samples_per_pixel + ) + msg = "Invalid value for samples per pixel" + raise SyntaxError(msg) + + if samples_per_pixel < bps_actual_count: + # If a file has more values in bps_tuple than expected, + # remove the excess. + bps_tuple = bps_tuple[:samples_per_pixel] + elif samples_per_pixel > bps_actual_count and bps_actual_count == 1: + # If a file has only one value in bps_tuple, when it should have more, + # presume it is the same number of bits for all of the samples. + bps_tuple = bps_tuple * samples_per_pixel + + if len(bps_tuple) != samples_per_pixel: + msg = "unknown data organization" + raise SyntaxError(msg) + + # mode: check photometric interpretation and bits per pixel + key = ( + self.tag_v2.prefix, + photo, + sample_format, + fillorder, + bps_tuple, + extra_tuple, + ) + logger.debug("format key: %s", key) + try: + self._mode, rawmode = OPEN_INFO[key] + except KeyError as e: + logger.debug("- unsupported format") + msg = "unknown pixel mode" + raise SyntaxError(msg) from e + + logger.debug("- raw mode: %s", rawmode) + logger.debug("- pil mode: %s", self.mode) + + self.info["compression"] = self._compression + + xres = self.tag_v2.get(X_RESOLUTION, 1) + yres = self.tag_v2.get(Y_RESOLUTION, 1) + + if xres and yres: + resunit = self.tag_v2.get(RESOLUTION_UNIT) + if resunit == 2: # dots per inch + self.info["dpi"] = (xres, yres) + elif resunit == 3: # dots per centimeter. convert to dpi + self.info["dpi"] = (xres * 2.54, yres * 2.54) + elif resunit is None: # used to default to 1, but now 2) + self.info["dpi"] = (xres, yres) + # For backward compatibility, + # we also preserve the old behavior + self.info["resolution"] = xres, yres + else: # No absolute unit of measurement + self.info["resolution"] = xres, yres + + # build tile descriptors + x = y = layer = 0 + self.tile = [] + self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw" + if self.use_load_libtiff: + # Decoder expects entire file as one tile. + # There's a buffer size limit in load (64k) + # so large g4 images will fail if we use that + # function. + # + # Setup the one tile for the whole image, then + # use the _load_libtiff function. + + # libtiff handles the fillmode for us, so 1;IR should + # actually be 1;I. Including the R double reverses the + # bits, so stripes of the image are reversed. See + # https://github.com/python-pillow/Pillow/issues/279 + if fillorder == 2: + # Replace fillorder with fillorder=1 + key = key[:3] + (1,) + key[4:] + logger.debug("format key: %s", key) + # this should always work, since all the + # fillorder==2 modes have a corresponding + # fillorder=1 mode + self._mode, rawmode = OPEN_INFO[key] + # YCbCr images with new jpeg compression with pixels in one plane + # unpacked straight into RGB values + if ( + photo == 6 + and self._compression == "jpeg" + and self._planar_configuration == 1 + ): + rawmode = "RGB" + # libtiff always returns the bytes in native order. + # we're expecting image byte order. So, if the rawmode + # contains I;16, we need to convert from native to image + # byte order. + elif rawmode == "I;16": + rawmode = "I;16N" + elif rawmode.endswith((";16B", ";16L")): + rawmode = rawmode[:-1] + "N" + + # Offset in the tile tuple is 0, we go from 0,0 to + # w,h, and we only do this once -- eds + a = (rawmode, self._compression, False, self.tag_v2.offset) + self.tile.append(ImageFile._Tile("libtiff", (0, 0, xsize, ysize), 0, a)) + + elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2: + # striped image + if STRIPOFFSETS in self.tag_v2: + offsets = self.tag_v2[STRIPOFFSETS] + h = self.tag_v2.get(ROWSPERSTRIP, ysize) + w = xsize + else: + # tiled image + offsets = self.tag_v2[TILEOFFSETS] + tilewidth = self.tag_v2.get(TILEWIDTH) + h = self.tag_v2.get(TILELENGTH) + if not isinstance(tilewidth, int) or not isinstance(h, int): + msg = "Invalid tile dimensions" + raise ValueError(msg) + w = tilewidth + + if w == xsize and h == ysize and self._planar_configuration != 2: + # Every tile covers the image. Only use the last offset + offsets = offsets[-1:] + + for offset in offsets: + if x + w > xsize: + stride = w * sum(bps_tuple) / 8 # bytes per line + else: + stride = 0 + + tile_rawmode = rawmode + if self._planar_configuration == 2: + # each band on it's own layer + tile_rawmode = rawmode[layer] + # adjust stride width accordingly + stride /= bps_count + + args = (tile_rawmode, int(stride), 1) + self.tile.append( + ImageFile._Tile( + self._compression, + (x, y, min(x + w, xsize), min(y + h, ysize)), + offset, + args, + ) + ) + x += w + if x >= xsize: + x, y = 0, y + h + if y >= ysize: + y = 0 + layer += 1 + else: + logger.debug("- unsupported data organization") + msg = "unknown data organization" + raise SyntaxError(msg) + + # Fix up info. + if ICCPROFILE in self.tag_v2: + self.info["icc_profile"] = self.tag_v2[ICCPROFILE] + + # fixup palette descriptor + + if self.mode in ["P", "PA"]: + palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]] + self.palette = ImagePalette.raw("RGB;L", b"".join(palette)) + + +# +# -------------------------------------------------------------------- +# Write TIFF files + +# little endian is default except for image modes with +# explicit big endian byte-order + +SAVE_INFO = { + # mode => rawmode, byteorder, photometrics, + # sampleformat, bitspersample, extra + "1": ("1", II, 1, 1, (1,), None), + "L": ("L", II, 1, 1, (8,), None), + "LA": ("LA", II, 1, 1, (8, 8), 2), + "P": ("P", II, 3, 1, (8,), None), + "PA": ("PA", II, 3, 1, (8, 8), 2), + "I": ("I;32S", II, 1, 2, (32,), None), + "I;16": ("I;16", II, 1, 1, (16,), None), + "I;16L": ("I;16L", II, 1, 1, (16,), None), + "F": ("F;32F", II, 1, 3, (32,), None), + "RGB": ("RGB", II, 2, 1, (8, 8, 8), None), + "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0), + "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2), + "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), + "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), + "LAB": ("LAB", II, 8, 1, (8, 8, 8), None), + "I;16B": ("I;16B", MM, 1, 1, (16,), None), +} + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + try: + rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] + except KeyError as e: + msg = f"cannot write mode {im.mode} as TIFF" + raise OSError(msg) from e + + encoderinfo = im.encoderinfo + encoderconfig = im.encoderconfig + + ifd = ImageFileDirectory_v2(prefix=prefix) + if encoderinfo.get("big_tiff"): + ifd._bigtiff = True + + try: + compression = encoderinfo["compression"] + except KeyError: + compression = im.info.get("compression") + if isinstance(compression, int): + # compression value may be from BMP. Ignore it + compression = None + if compression is None: + compression = "raw" + elif compression == "tiff_jpeg": + # OJPEG is obsolete, so use new-style JPEG compression instead + compression = "jpeg" + elif compression == "tiff_deflate": + compression = "tiff_adobe_deflate" + + libtiff = WRITE_LIBTIFF or compression != "raw" + + # required for color libtiff images + ifd[PLANAR_CONFIGURATION] = 1 + + ifd[IMAGEWIDTH] = im.size[0] + ifd[IMAGELENGTH] = im.size[1] + + # write any arbitrary tags passed in as an ImageFileDirectory + if "tiffinfo" in encoderinfo: + info = encoderinfo["tiffinfo"] + elif "exif" in encoderinfo: + info = encoderinfo["exif"] + if isinstance(info, bytes): + exif = Image.Exif() + exif.load(info) + info = exif + else: + info = {} + logger.debug("Tiffinfo Keys: %s", list(info)) + if isinstance(info, ImageFileDirectory_v1): + info = info.to_v2() + for key in info: + if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS: + ifd[key] = info.get_ifd(key) + else: + ifd[key] = info.get(key) + try: + ifd.tagtype[key] = info.tagtype[key] + except Exception: + pass # might not be an IFD. Might not have populated type + + legacy_ifd = {} + if hasattr(im, "tag"): + legacy_ifd = im.tag.to_v2() + + supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})} + for tag in ( + # IFD offset that may not be correct in the saved image + EXIFIFD, + # Determined by the image format and should not be copied from legacy_ifd. + SAMPLEFORMAT, + ): + if tag in supplied_tags: + del supplied_tags[tag] + + # additions written by Greg Couch, gregc@cgl.ucsf.edu + # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com + if hasattr(im, "tag_v2"): + # preserve tags from original TIFF image file + for key in ( + RESOLUTION_UNIT, + X_RESOLUTION, + Y_RESOLUTION, + IPTC_NAA_CHUNK, + PHOTOSHOP_CHUNK, + XMP, + ): + if key in im.tag_v2: + if key == IPTC_NAA_CHUNK and im.tag_v2.tagtype[key] not in ( + TiffTags.BYTE, + TiffTags.UNDEFINED, + ): + del supplied_tags[key] + else: + ifd[key] = im.tag_v2[key] + ifd.tagtype[key] = im.tag_v2.tagtype[key] + + # preserve ICC profile (should also work when saving other formats + # which support profiles as TIFF) -- 2008-06-06 Florian Hoech + icc = encoderinfo.get("icc_profile", im.info.get("icc_profile")) + if icc: + ifd[ICCPROFILE] = icc + + for key, name in [ + (IMAGEDESCRIPTION, "description"), + (X_RESOLUTION, "resolution"), + (Y_RESOLUTION, "resolution"), + (X_RESOLUTION, "x_resolution"), + (Y_RESOLUTION, "y_resolution"), + (RESOLUTION_UNIT, "resolution_unit"), + (SOFTWARE, "software"), + (DATE_TIME, "date_time"), + (ARTIST, "artist"), + (COPYRIGHT, "copyright"), + ]: + if name in encoderinfo: + ifd[key] = encoderinfo[name] + + dpi = encoderinfo.get("dpi") + if dpi: + ifd[RESOLUTION_UNIT] = 2 + ifd[X_RESOLUTION] = dpi[0] + ifd[Y_RESOLUTION] = dpi[1] + + if bits != (1,): + ifd[BITSPERSAMPLE] = bits + if len(bits) != 1: + ifd[SAMPLESPERPIXEL] = len(bits) + if extra is not None: + ifd[EXTRASAMPLES] = extra + if format != 1: + ifd[SAMPLEFORMAT] = format + + if PHOTOMETRIC_INTERPRETATION not in ifd: + ifd[PHOTOMETRIC_INTERPRETATION] = photo + elif im.mode in ("1", "L") and ifd[PHOTOMETRIC_INTERPRETATION] == 0: + if im.mode == "1": + inverted_im = im.copy() + px = inverted_im.load() + if px is not None: + for y in range(inverted_im.height): + for x in range(inverted_im.width): + px[x, y] = 0 if px[x, y] == 255 else 255 + im = inverted_im + else: + im = ImageOps.invert(im) + + if im.mode in ["P", "PA"]: + lut = im.im.getpalette("RGB", "RGB;L") + colormap = [] + colors = len(lut) // 3 + for i in range(3): + colormap += [v * 256 for v in lut[colors * i : colors * (i + 1)]] + colormap += [0] * (256 - colors) + ifd[COLORMAP] = colormap + # data orientation + w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH] + stride = len(bits) * ((w * bits[0] + 7) // 8) + if ROWSPERSTRIP not in ifd: + # aim for given strip size (64 KB by default) when using libtiff writer + if libtiff: + im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE) + rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h) + # JPEG encoder expects multiple of 8 rows + if compression == "jpeg": + rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h) + else: + rows_per_strip = h + if rows_per_strip == 0: + rows_per_strip = 1 + ifd[ROWSPERSTRIP] = rows_per_strip + strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP] + strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP] + if strip_byte_counts >= 2**16: + ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG + ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + ( + stride * h - strip_byte_counts * (strips_per_image - 1), + ) + ifd[STRIPOFFSETS] = tuple( + range(0, strip_byte_counts * strips_per_image, strip_byte_counts) + ) # this is adjusted by IFD writer + # no compression by default: + ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) + + if im.mode == "YCbCr": + for tag, default_value in { + YCBCRSUBSAMPLING: (1, 1), + REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255), + }.items(): + ifd.setdefault(tag, default_value) + + blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS] + if libtiff: + if "quality" in encoderinfo: + quality = encoderinfo["quality"] + if not isinstance(quality, int) or quality < 0 or quality > 100: + msg = "Invalid quality setting" + raise ValueError(msg) + if compression != "jpeg": + msg = "quality setting only supported for 'jpeg' compression" + raise ValueError(msg) + ifd[JPEGQUALITY] = quality + + logger.debug("Saving using libtiff encoder") + logger.debug("Items: %s", sorted(ifd.items())) + _fp = 0 + if hasattr(fp, "fileno"): + try: + fp.seek(0) + _fp = fp.fileno() + except io.UnsupportedOperation: + pass + + # optional types for non core tags + types = {} + # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library + # based on the data in the strip. + # OSUBFILETYPE is deprecated. + # The other tags expect arrays with a certain length (fixed or depending on + # BITSPERSAMPLE, etc), passing arrays with a different length will result in + # segfaults. Block these tags until we add extra validation. + # SUBIFD may also cause a segfault. + blocklist += [ + OSUBFILETYPE, + REFERENCEBLACKWHITE, + STRIPBYTECOUNTS, + STRIPOFFSETS, + TRANSFERFUNCTION, + SUBIFD, + ] + + # bits per sample is a single short in the tiff directory, not a list. + atts: dict[int, Any] = {BITSPERSAMPLE: bits[0]} + # Merge the ones that we have with (optional) more bits from + # the original file, e.g x,y resolution so that we can + # save(load('')) == original file. + for tag, value in itertools.chain(ifd.items(), supplied_tags.items()): + # Libtiff can only process certain core items without adding + # them to the custom dictionary. + # Custom items are supported for int, float, unicode, string and byte + # values. Other types and tuples require a tagtype. + if tag not in TiffTags.LIBTIFF_CORE: + if not getattr(Image.core, "libtiff_support_custom_tags", False): + continue + + if tag in TiffTags.TAGS_V2_GROUPS: + types[tag] = TiffTags.LONG8 + elif tag in ifd.tagtype: + types[tag] = ifd.tagtype[tag] + elif not (isinstance(value, (int, float, str, bytes))): + continue + else: + type = TiffTags.lookup(tag).type + if type: + types[tag] = type + if tag not in atts and tag not in blocklist: + if isinstance(value, str): + atts[tag] = value.encode("ascii", "replace") + b"\0" + elif isinstance(value, IFDRational): + atts[tag] = float(value) + else: + atts[tag] = value + + if SAMPLEFORMAT in atts and len(atts[SAMPLEFORMAT]) == 1: + atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0] + + logger.debug("Converted items: %s", sorted(atts.items())) + + # libtiff always expects the bytes in native order. + # we're storing image byte order. So, if the rawmode + # contains I;16, we need to convert from native to image + # byte order. + if im.mode in ("I;16", "I;16B", "I;16L"): + rawmode = "I;16N" + + # Pass tags as sorted list so that the tags are set in a fixed order. + # This is required by libtiff for some tags. For example, the JPEGQUALITY + # pseudo tag requires that the COMPRESS tag was already set. + tags = list(atts.items()) + tags.sort() + a = (rawmode, compression, _fp, filename, tags, types) + encoder = Image._getencoder(im.mode, "libtiff", a, encoderconfig) + encoder.setimage(im.im, (0, 0) + im.size) + while True: + errcode, data = encoder.encode(ImageFile.MAXBLOCK)[1:] + if not _fp: + fp.write(data) + if errcode: + break + if errcode < 0: + msg = f"encoder error {errcode} when writing image file" + raise OSError(msg) + + else: + for tag in blocklist: + del ifd[tag] + offset = ifd.save(fp) + + ImageFile._save( + im, + fp, + [ImageFile._Tile("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))], + ) + + # -- helper for multi-page save -- + if "_debug_multipage" in encoderinfo: + # just to access o32 and o16 (using correct byte order) + setattr(im, "_debug_multipage", ifd) + + +class AppendingTiffWriter(io.BytesIO): + fieldSizes = [ + 0, # None + 1, # byte + 1, # ascii + 2, # short + 4, # long + 8, # rational + 1, # sbyte + 1, # undefined + 2, # sshort + 4, # slong + 8, # srational + 4, # float + 8, # double + 4, # ifd + 2, # unicode + 4, # complex + 8, # long8 + ] + + Tags = { + 273, # StripOffsets + 288, # FreeOffsets + 324, # TileOffsets + 519, # JPEGQTables + 520, # JPEGDCTables + 521, # JPEGACTables + } + + def __init__(self, fn: StrOrBytesPath | IO[bytes], new: bool = False) -> None: + self.f: IO[bytes] + if is_path(fn): + self.name = fn + self.close_fp = True + try: + self.f = open(fn, "w+b" if new else "r+b") + except OSError: + self.f = open(fn, "w+b") + else: + self.f = cast(IO[bytes], fn) + self.close_fp = False + self.beginning = self.f.tell() + self.setup() + + def setup(self) -> None: + # Reset everything. + self.f.seek(self.beginning, os.SEEK_SET) + + self.whereToWriteNewIFDOffset: int | None = None + self.offsetOfNewPage = 0 + + self.IIMM = iimm = self.f.read(4) + self._bigtiff = b"\x2b" in iimm + if not iimm: + # empty file - first page + self.isFirst = True + return + + self.isFirst = False + if iimm not in PREFIXES: + msg = "Invalid TIFF file header" + raise RuntimeError(msg) + + self.setEndian("<" if iimm.startswith(II) else ">") + + if self._bigtiff: + self.f.seek(4, os.SEEK_CUR) + self.skipIFDs() + self.goToEnd() + + def finalize(self) -> None: + if self.isFirst: + return + + # fix offsets + self.f.seek(self.offsetOfNewPage) + + iimm = self.f.read(4) + if not iimm: + # Make it easy to finish a frame without committing to a new one. + return + + if iimm != self.IIMM: + msg = "IIMM of new page doesn't match IIMM of first page" + raise RuntimeError(msg) + + if self._bigtiff: + self.f.seek(4, os.SEEK_CUR) + ifd_offset = self._read(8 if self._bigtiff else 4) + ifd_offset += self.offsetOfNewPage + assert self.whereToWriteNewIFDOffset is not None + self.f.seek(self.whereToWriteNewIFDOffset) + self._write(ifd_offset, 8 if self._bigtiff else 4) + self.f.seek(ifd_offset) + self.fixIFD() + + def newFrame(self) -> None: + # Call this to finish a frame. + self.finalize() + self.setup() + + def __enter__(self) -> AppendingTiffWriter: + return self + + def __exit__(self, *args: object) -> None: + if self.close_fp: + self.close() + + def tell(self) -> int: + return self.f.tell() - self.offsetOfNewPage + + def seek(self, offset: int, whence: int = io.SEEK_SET) -> int: + """ + :param offset: Distance to seek. + :param whence: Whether the distance is relative to the start, + end or current position. + :returns: The resulting position, relative to the start. + """ + if whence == os.SEEK_SET: + offset += self.offsetOfNewPage + + self.f.seek(offset, whence) + return self.tell() + + def goToEnd(self) -> None: + self.f.seek(0, os.SEEK_END) + pos = self.f.tell() + + # pad to 16 byte boundary + pad_bytes = 16 - pos % 16 + if 0 < pad_bytes < 16: + self.f.write(bytes(pad_bytes)) + self.offsetOfNewPage = self.f.tell() + + def setEndian(self, endian: str) -> None: + self.endian = endian + self.longFmt = f"{self.endian}L" + self.shortFmt = f"{self.endian}H" + self.tagFormat = f"{self.endian}HH" + ("Q" if self._bigtiff else "L") + + def skipIFDs(self) -> None: + while True: + ifd_offset = self._read(8 if self._bigtiff else 4) + if ifd_offset == 0: + self.whereToWriteNewIFDOffset = self.f.tell() - ( + 8 if self._bigtiff else 4 + ) + break + + self.f.seek(ifd_offset) + num_tags = self._read(8 if self._bigtiff else 2) + self.f.seek(num_tags * (20 if self._bigtiff else 12), os.SEEK_CUR) + + def write(self, data: Buffer, /) -> int: + return self.f.write(data) + + def _fmt(self, field_size: int) -> str: + try: + return {2: "H", 4: "L", 8: "Q"}[field_size] + except KeyError: + msg = "offset is not supported" + raise RuntimeError(msg) + + def _read(self, field_size: int) -> int: + (value,) = struct.unpack( + self.endian + self._fmt(field_size), self.f.read(field_size) + ) + return value + + def readShort(self) -> int: + return self._read(2) + + def readLong(self) -> int: + return self._read(4) + + @staticmethod + def _verify_bytes_written(bytes_written: int | None, expected: int) -> None: + if bytes_written is not None and bytes_written != expected: + msg = f"wrote only {bytes_written} bytes but wanted {expected}" + raise RuntimeError(msg) + + def _rewriteLast( + self, value: int, field_size: int, new_field_size: int = 0 + ) -> None: + self.f.seek(-field_size, os.SEEK_CUR) + if not new_field_size: + new_field_size = field_size + bytes_written = self.f.write( + struct.pack(self.endian + self._fmt(new_field_size), value) + ) + self._verify_bytes_written(bytes_written, new_field_size) + + def rewriteLastShortToLong(self, value: int) -> None: + self._rewriteLast(value, 2, 4) + + def rewriteLastShort(self, value: int) -> None: + return self._rewriteLast(value, 2) + + def rewriteLastLong(self, value: int) -> None: + return self._rewriteLast(value, 4) + + def _write(self, value: int, field_size: int) -> None: + bytes_written = self.f.write( + struct.pack(self.endian + self._fmt(field_size), value) + ) + self._verify_bytes_written(bytes_written, field_size) + + def writeShort(self, value: int) -> None: + self._write(value, 2) + + def writeLong(self, value: int) -> None: + self._write(value, 4) + + def close(self) -> None: + self.finalize() + if self.close_fp: + self.f.close() + + def fixIFD(self) -> None: + num_tags = self._read(8 if self._bigtiff else 2) + + for i in range(num_tags): + tag, field_type, count = struct.unpack( + self.tagFormat, self.f.read(12 if self._bigtiff else 8) + ) + + field_size = self.fieldSizes[field_type] + total_size = field_size * count + fmt_size = 8 if self._bigtiff else 4 + is_local = total_size <= fmt_size + if not is_local: + offset = self._read(fmt_size) + self.offsetOfNewPage + self._rewriteLast(offset, fmt_size) + + if tag in self.Tags: + cur_pos = self.f.tell() + + logger.debug( + "fixIFD: %s (%d) - type: %s (%d) - type size: %d - count: %d", + TiffTags.lookup(tag).name, + tag, + TYPES.get(field_type, "unknown"), + field_type, + field_size, + count, + ) + + if is_local: + self._fixOffsets(count, field_size) + self.f.seek(cur_pos + fmt_size) + else: + self.f.seek(offset) + self._fixOffsets(count, field_size) + self.f.seek(cur_pos) + + elif is_local: + # skip the locally stored value that is not an offset + self.f.seek(fmt_size, os.SEEK_CUR) + + def _fixOffsets(self, count: int, field_size: int) -> None: + for i in range(count): + offset = self._read(field_size) + offset += self.offsetOfNewPage + + new_field_size = 0 + if self._bigtiff and field_size in (2, 4) and offset >= 2**32: + # offset is now too large - we must convert long to long8 + new_field_size = 8 + elif field_size == 2 and offset >= 2**16: + # offset is now too large - we must convert short to long + new_field_size = 4 + if new_field_size: + if count != 1: + msg = "not implemented" + raise RuntimeError(msg) # XXX TODO + + # simple case - the offset is just one and therefore it is + # local (not referenced with another offset) + self._rewriteLast(offset, field_size, new_field_size) + # Move back past the new offset, past 'count', and before 'field_type' + rewind = -new_field_size - 4 - 2 + self.f.seek(rewind, os.SEEK_CUR) + self.writeShort(new_field_size) # rewrite the type + self.f.seek(2 - rewind, os.SEEK_CUR) + else: + self._rewriteLast(offset, field_size) + + def fixOffsets( + self, count: int, isShort: bool = False, isLong: bool = False + ) -> None: + if isShort: + field_size = 2 + elif isLong: + field_size = 4 + else: + field_size = 0 + return self._fixOffsets(count, field_size) + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + append_images = list(im.encoderinfo.get("append_images", [])) + if not hasattr(im, "n_frames") and not append_images: + return _save(im, fp, filename) + + cur_idx = im.tell() + try: + with AppendingTiffWriter(fp) as tf: + for ims in [im] + append_images: + encoderinfo = ims._attach_default_encoderinfo(im) + if not hasattr(ims, "encoderconfig"): + ims.encoderconfig = () + nfr = getattr(ims, "n_frames", 1) + + for idx in range(nfr): + ims.seek(idx) + ims.load() + _save(ims, tf, filename) + tf.newFrame() + ims.encoderinfo = encoderinfo + finally: + im.seek(cur_idx) + + +# +# -------------------------------------------------------------------- +# Register + +Image.register_open(TiffImageFile.format, TiffImageFile, _accept) +Image.register_save(TiffImageFile.format, _save) +Image.register_save_all(TiffImageFile.format, _save_all) + +Image.register_extensions(TiffImageFile.format, [".tif", ".tiff"]) + +Image.register_mime(TiffImageFile.format, "image/tiff") diff --git a/venv/Lib/site-packages/PIL/TiffTags.py b/venv/Lib/site-packages/PIL/TiffTags.py new file mode 100644 index 00000000..86adaa45 --- /dev/null +++ b/venv/Lib/site-packages/PIL/TiffTags.py @@ -0,0 +1,562 @@ +# +# The Python Imaging Library. +# $Id$ +# +# TIFF tags +# +# This module provides clear-text names for various well-known +# TIFF tags. the TIFF codec works just fine without it. +# +# Copyright (c) Secret Labs AB 1999. +# +# See the README file for information on usage and redistribution. +# + +## +# This module provides constants and clear-text names for various +# well-known TIFF tags. +## +from __future__ import annotations + +from typing import NamedTuple + + +class _TagInfo(NamedTuple): + value: int | None + name: str + type: int | None + length: int | None + enum: dict[str, int] + + +class TagInfo(_TagInfo): + __slots__: list[str] = [] + + def __new__( + cls, + value: int | None = None, + name: str = "unknown", + type: int | None = None, + length: int | None = None, + enum: dict[str, int] | None = None, + ) -> TagInfo: + return super().__new__(cls, value, name, type, length, enum or {}) + + def cvt_enum(self, value: str) -> int | str: + # Using get will call hash(value), which can be expensive + # for some types (e.g. Fraction). Since self.enum is rarely + # used, it's usually better to test it first. + return self.enum.get(value, value) if self.enum else value + + +def lookup(tag: int, group: int | None = None) -> TagInfo: + """ + :param tag: Integer tag number + :param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in + + .. versionadded:: 8.3.0 + + :returns: Taginfo namedtuple, From the ``TAGS_V2`` info if possible, + otherwise just populating the value and name from ``TAGS``. + If the tag is not recognized, "unknown" is returned for the name + + """ + + if group is not None: + info = TAGS_V2_GROUPS[group].get(tag) if group in TAGS_V2_GROUPS else None + else: + info = TAGS_V2.get(tag) + return info or TagInfo(tag, TAGS.get(tag, "unknown")) + + +## +# Map tag numbers to tag info. +# +# id: (Name, Type, Length[, enum_values]) +# +# The length here differs from the length in the tiff spec. For +# numbers, the tiff spec is for the number of fields returned. We +# agree here. For string-like types, the tiff spec uses the length of +# field in bytes. In Pillow, we are using the number of expected +# fields, in general 1 for string-like types. + + +BYTE = 1 +ASCII = 2 +SHORT = 3 +LONG = 4 +RATIONAL = 5 +SIGNED_BYTE = 6 +UNDEFINED = 7 +SIGNED_SHORT = 8 +SIGNED_LONG = 9 +SIGNED_RATIONAL = 10 +FLOAT = 11 +DOUBLE = 12 +IFD = 13 +LONG8 = 16 + +_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = { + 254: ("NewSubfileType", LONG, 1), + 255: ("SubfileType", SHORT, 1), + 256: ("ImageWidth", LONG, 1), + 257: ("ImageLength", LONG, 1), + 258: ("BitsPerSample", SHORT, 0), + 259: ( + "Compression", + SHORT, + 1, + { + "Uncompressed": 1, + "CCITT 1d": 2, + "Group 3 Fax": 3, + "Group 4 Fax": 4, + "LZW": 5, + "JPEG": 6, + "PackBits": 32773, + }, + ), + 262: ( + "PhotometricInterpretation", + SHORT, + 1, + { + "WhiteIsZero": 0, + "BlackIsZero": 1, + "RGB": 2, + "RGB Palette": 3, + "Transparency Mask": 4, + "CMYK": 5, + "YCbCr": 6, + "CieLAB": 8, + "CFA": 32803, # TIFF/EP, Adobe DNG + "LinearRaw": 32892, # Adobe DNG + }, + ), + 263: ("Threshholding", SHORT, 1), + 264: ("CellWidth", SHORT, 1), + 265: ("CellLength", SHORT, 1), + 266: ("FillOrder", SHORT, 1), + 269: ("DocumentName", ASCII, 1), + 270: ("ImageDescription", ASCII, 1), + 271: ("Make", ASCII, 1), + 272: ("Model", ASCII, 1), + 273: ("StripOffsets", LONG, 0), + 274: ("Orientation", SHORT, 1), + 277: ("SamplesPerPixel", SHORT, 1), + 278: ("RowsPerStrip", LONG, 1), + 279: ("StripByteCounts", LONG, 0), + 280: ("MinSampleValue", SHORT, 0), + 281: ("MaxSampleValue", SHORT, 0), + 282: ("XResolution", RATIONAL, 1), + 283: ("YResolution", RATIONAL, 1), + 284: ("PlanarConfiguration", SHORT, 1, {"Contiguous": 1, "Separate": 2}), + 285: ("PageName", ASCII, 1), + 286: ("XPosition", RATIONAL, 1), + 287: ("YPosition", RATIONAL, 1), + 288: ("FreeOffsets", LONG, 1), + 289: ("FreeByteCounts", LONG, 1), + 290: ("GrayResponseUnit", SHORT, 1), + 291: ("GrayResponseCurve", SHORT, 0), + 292: ("T4Options", LONG, 1), + 293: ("T6Options", LONG, 1), + 296: ("ResolutionUnit", SHORT, 1, {"none": 1, "inch": 2, "cm": 3}), + 297: ("PageNumber", SHORT, 2), + 301: ("TransferFunction", SHORT, 0), + 305: ("Software", ASCII, 1), + 306: ("DateTime", ASCII, 1), + 315: ("Artist", ASCII, 1), + 316: ("HostComputer", ASCII, 1), + 317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}), + 318: ("WhitePoint", RATIONAL, 2), + 319: ("PrimaryChromaticities", RATIONAL, 6), + 320: ("ColorMap", SHORT, 0), + 321: ("HalftoneHints", SHORT, 2), + 322: ("TileWidth", LONG, 1), + 323: ("TileLength", LONG, 1), + 324: ("TileOffsets", LONG, 0), + 325: ("TileByteCounts", LONG, 0), + 330: ("SubIFDs", LONG, 0), + 332: ("InkSet", SHORT, 1), + 333: ("InkNames", ASCII, 1), + 334: ("NumberOfInks", SHORT, 1), + 336: ("DotRange", SHORT, 0), + 337: ("TargetPrinter", ASCII, 1), + 338: ("ExtraSamples", SHORT, 0), + 339: ("SampleFormat", SHORT, 0), + 340: ("SMinSampleValue", DOUBLE, 0), + 341: ("SMaxSampleValue", DOUBLE, 0), + 342: ("TransferRange", SHORT, 6), + 347: ("JPEGTables", UNDEFINED, 1), + # obsolete JPEG tags + 512: ("JPEGProc", SHORT, 1), + 513: ("JPEGInterchangeFormat", LONG, 1), + 514: ("JPEGInterchangeFormatLength", LONG, 1), + 515: ("JPEGRestartInterval", SHORT, 1), + 517: ("JPEGLosslessPredictors", SHORT, 0), + 518: ("JPEGPointTransforms", SHORT, 0), + 519: ("JPEGQTables", LONG, 0), + 520: ("JPEGDCTables", LONG, 0), + 521: ("JPEGACTables", LONG, 0), + 529: ("YCbCrCoefficients", RATIONAL, 3), + 530: ("YCbCrSubSampling", SHORT, 2), + 531: ("YCbCrPositioning", SHORT, 1), + 532: ("ReferenceBlackWhite", RATIONAL, 6), + 700: ("XMP", BYTE, 0), + 33432: ("Copyright", ASCII, 1), + 33723: ("IptcNaaInfo", UNDEFINED, 1), + 34377: ("PhotoshopInfo", BYTE, 0), + # FIXME add more tags here + 34665: ("ExifIFD", LONG, 1), + 34675: ("ICCProfile", UNDEFINED, 1), + 34853: ("GPSInfoIFD", LONG, 1), + 36864: ("ExifVersion", UNDEFINED, 1), + 37724: ("ImageSourceData", UNDEFINED, 1), + 40965: ("InteroperabilityIFD", LONG, 1), + 41730: ("CFAPattern", UNDEFINED, 1), + # MPInfo + 45056: ("MPFVersion", UNDEFINED, 1), + 45057: ("NumberOfImages", LONG, 1), + 45058: ("MPEntry", UNDEFINED, 1), + 45059: ("ImageUIDList", UNDEFINED, 0), # UNDONE, check + 45060: ("TotalFrames", LONG, 1), + 45313: ("MPIndividualNum", LONG, 1), + 45569: ("PanOrientation", LONG, 1), + 45570: ("PanOverlap_H", RATIONAL, 1), + 45571: ("PanOverlap_V", RATIONAL, 1), + 45572: ("BaseViewpointNum", LONG, 1), + 45573: ("ConvergenceAngle", SIGNED_RATIONAL, 1), + 45574: ("BaselineLength", RATIONAL, 1), + 45575: ("VerticalDivergence", SIGNED_RATIONAL, 1), + 45576: ("AxisDistance_X", SIGNED_RATIONAL, 1), + 45577: ("AxisDistance_Y", SIGNED_RATIONAL, 1), + 45578: ("AxisDistance_Z", SIGNED_RATIONAL, 1), + 45579: ("YawAngle", SIGNED_RATIONAL, 1), + 45580: ("PitchAngle", SIGNED_RATIONAL, 1), + 45581: ("RollAngle", SIGNED_RATIONAL, 1), + 40960: ("FlashPixVersion", UNDEFINED, 1), + 50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}), + 50780: ("BestQualityScale", RATIONAL, 1), + 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one + 50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006 +} +_tags_v2_groups = { + # ExifIFD + 34665: { + 36864: ("ExifVersion", UNDEFINED, 1), + 40960: ("FlashPixVersion", UNDEFINED, 1), + 40965: ("InteroperabilityIFD", LONG, 1), + 41730: ("CFAPattern", UNDEFINED, 1), + }, + # GPSInfoIFD + 34853: { + 0: ("GPSVersionID", BYTE, 4), + 1: ("GPSLatitudeRef", ASCII, 2), + 2: ("GPSLatitude", RATIONAL, 3), + 3: ("GPSLongitudeRef", ASCII, 2), + 4: ("GPSLongitude", RATIONAL, 3), + 5: ("GPSAltitudeRef", BYTE, 1), + 6: ("GPSAltitude", RATIONAL, 1), + 7: ("GPSTimeStamp", RATIONAL, 3), + 8: ("GPSSatellites", ASCII, 0), + 9: ("GPSStatus", ASCII, 2), + 10: ("GPSMeasureMode", ASCII, 2), + 11: ("GPSDOP", RATIONAL, 1), + 12: ("GPSSpeedRef", ASCII, 2), + 13: ("GPSSpeed", RATIONAL, 1), + 14: ("GPSTrackRef", ASCII, 2), + 15: ("GPSTrack", RATIONAL, 1), + 16: ("GPSImgDirectionRef", ASCII, 2), + 17: ("GPSImgDirection", RATIONAL, 1), + 18: ("GPSMapDatum", ASCII, 0), + 19: ("GPSDestLatitudeRef", ASCII, 2), + 20: ("GPSDestLatitude", RATIONAL, 3), + 21: ("GPSDestLongitudeRef", ASCII, 2), + 22: ("GPSDestLongitude", RATIONAL, 3), + 23: ("GPSDestBearingRef", ASCII, 2), + 24: ("GPSDestBearing", RATIONAL, 1), + 25: ("GPSDestDistanceRef", ASCII, 2), + 26: ("GPSDestDistance", RATIONAL, 1), + 27: ("GPSProcessingMethod", UNDEFINED, 0), + 28: ("GPSAreaInformation", UNDEFINED, 0), + 29: ("GPSDateStamp", ASCII, 11), + 30: ("GPSDifferential", SHORT, 1), + }, + # InteroperabilityIFD + 40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)}, +} + +# Legacy Tags structure +# these tags aren't included above, but were in the previous versions +TAGS: dict[int | tuple[int, int], str] = { + 347: "JPEGTables", + 700: "XMP", + # Additional Exif Info + 32932: "Wang Annotation", + 33434: "ExposureTime", + 33437: "FNumber", + 33445: "MD FileTag", + 33446: "MD ScalePixel", + 33447: "MD ColorTable", + 33448: "MD LabName", + 33449: "MD SampleInfo", + 33450: "MD PrepDate", + 33451: "MD PrepTime", + 33452: "MD FileUnits", + 33550: "ModelPixelScaleTag", + 33723: "IptcNaaInfo", + 33918: "INGR Packet Data Tag", + 33919: "INGR Flag Registers", + 33920: "IrasB Transformation Matrix", + 33922: "ModelTiepointTag", + 34264: "ModelTransformationTag", + 34377: "PhotoshopInfo", + 34735: "GeoKeyDirectoryTag", + 34736: "GeoDoubleParamsTag", + 34737: "GeoAsciiParamsTag", + 34850: "ExposureProgram", + 34852: "SpectralSensitivity", + 34855: "ISOSpeedRatings", + 34856: "OECF", + 34864: "SensitivityType", + 34865: "StandardOutputSensitivity", + 34866: "RecommendedExposureIndex", + 34867: "ISOSpeed", + 34868: "ISOSpeedLatitudeyyy", + 34869: "ISOSpeedLatitudezzz", + 34908: "HylaFAX FaxRecvParams", + 34909: "HylaFAX FaxSubAddress", + 34910: "HylaFAX FaxRecvTime", + 36864: "ExifVersion", + 36867: "DateTimeOriginal", + 36868: "DateTimeDigitized", + 37121: "ComponentsConfiguration", + 37122: "CompressedBitsPerPixel", + 37724: "ImageSourceData", + 37377: "ShutterSpeedValue", + 37378: "ApertureValue", + 37379: "BrightnessValue", + 37380: "ExposureBiasValue", + 37381: "MaxApertureValue", + 37382: "SubjectDistance", + 37383: "MeteringMode", + 37384: "LightSource", + 37385: "Flash", + 37386: "FocalLength", + 37396: "SubjectArea", + 37500: "MakerNote", + 37510: "UserComment", + 37520: "SubSec", + 37521: "SubSecTimeOriginal", + 37522: "SubsecTimeDigitized", + 40960: "FlashPixVersion", + 40961: "ColorSpace", + 40962: "PixelXDimension", + 40963: "PixelYDimension", + 40964: "RelatedSoundFile", + 40965: "InteroperabilityIFD", + 41483: "FlashEnergy", + 41484: "SpatialFrequencyResponse", + 41486: "FocalPlaneXResolution", + 41487: "FocalPlaneYResolution", + 41488: "FocalPlaneResolutionUnit", + 41492: "SubjectLocation", + 41493: "ExposureIndex", + 41495: "SensingMethod", + 41728: "FileSource", + 41729: "SceneType", + 41730: "CFAPattern", + 41985: "CustomRendered", + 41986: "ExposureMode", + 41987: "WhiteBalance", + 41988: "DigitalZoomRatio", + 41989: "FocalLengthIn35mmFilm", + 41990: "SceneCaptureType", + 41991: "GainControl", + 41992: "Contrast", + 41993: "Saturation", + 41994: "Sharpness", + 41995: "DeviceSettingDescription", + 41996: "SubjectDistanceRange", + 42016: "ImageUniqueID", + 42032: "CameraOwnerName", + 42033: "BodySerialNumber", + 42034: "LensSpecification", + 42035: "LensMake", + 42036: "LensModel", + 42037: "LensSerialNumber", + 42112: "GDAL_METADATA", + 42113: "GDAL_NODATA", + 42240: "Gamma", + 50215: "Oce Scanjob Description", + 50216: "Oce Application Selector", + 50217: "Oce Identification Number", + 50218: "Oce ImageLogic Characteristics", + # Adobe DNG + 50706: "DNGVersion", + 50707: "DNGBackwardVersion", + 50708: "UniqueCameraModel", + 50709: "LocalizedCameraModel", + 50710: "CFAPlaneColor", + 50711: "CFALayout", + 50712: "LinearizationTable", + 50713: "BlackLevelRepeatDim", + 50714: "BlackLevel", + 50715: "BlackLevelDeltaH", + 50716: "BlackLevelDeltaV", + 50717: "WhiteLevel", + 50718: "DefaultScale", + 50719: "DefaultCropOrigin", + 50720: "DefaultCropSize", + 50721: "ColorMatrix1", + 50722: "ColorMatrix2", + 50723: "CameraCalibration1", + 50724: "CameraCalibration2", + 50725: "ReductionMatrix1", + 50726: "ReductionMatrix2", + 50727: "AnalogBalance", + 50728: "AsShotNeutral", + 50729: "AsShotWhiteXY", + 50730: "BaselineExposure", + 50731: "BaselineNoise", + 50732: "BaselineSharpness", + 50733: "BayerGreenSplit", + 50734: "LinearResponseLimit", + 50735: "CameraSerialNumber", + 50736: "LensInfo", + 50737: "ChromaBlurRadius", + 50738: "AntiAliasStrength", + 50740: "DNGPrivateData", + 50778: "CalibrationIlluminant1", + 50779: "CalibrationIlluminant2", + 50784: "Alias Layer Metadata", +} + +TAGS_V2: dict[int, TagInfo] = {} +TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {} + + +def _populate() -> None: + for k, v in _tags_v2.items(): + # Populate legacy structure. + TAGS[k] = v[0] + if len(v) == 4: + for sk, sv in v[3].items(): + TAGS[(k, sv)] = sk + + TAGS_V2[k] = TagInfo(k, *v) + + for group, tags in _tags_v2_groups.items(): + TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()} + + +_populate() +## +# Map type numbers to type names -- defined in ImageFileDirectory. + +TYPES: dict[int, str] = {} + +# +# These tags are handled by default in libtiff, without +# adding to the custom dictionary. From tif_dir.c, searching for +# case TIFFTAG in the _TIFFVSetField function: +# Line: item. +# 148: case TIFFTAG_SUBFILETYPE: +# 151: case TIFFTAG_IMAGEWIDTH: +# 154: case TIFFTAG_IMAGELENGTH: +# 157: case TIFFTAG_BITSPERSAMPLE: +# 181: case TIFFTAG_COMPRESSION: +# 202: case TIFFTAG_PHOTOMETRIC: +# 205: case TIFFTAG_THRESHHOLDING: +# 208: case TIFFTAG_FILLORDER: +# 214: case TIFFTAG_ORIENTATION: +# 221: case TIFFTAG_SAMPLESPERPIXEL: +# 228: case TIFFTAG_ROWSPERSTRIP: +# 238: case TIFFTAG_MINSAMPLEVALUE: +# 241: case TIFFTAG_MAXSAMPLEVALUE: +# 244: case TIFFTAG_SMINSAMPLEVALUE: +# 247: case TIFFTAG_SMAXSAMPLEVALUE: +# 250: case TIFFTAG_XRESOLUTION: +# 256: case TIFFTAG_YRESOLUTION: +# 262: case TIFFTAG_PLANARCONFIG: +# 268: case TIFFTAG_XPOSITION: +# 271: case TIFFTAG_YPOSITION: +# 274: case TIFFTAG_RESOLUTIONUNIT: +# 280: case TIFFTAG_PAGENUMBER: +# 284: case TIFFTAG_HALFTONEHINTS: +# 288: case TIFFTAG_COLORMAP: +# 294: case TIFFTAG_EXTRASAMPLES: +# 298: case TIFFTAG_MATTEING: +# 305: case TIFFTAG_TILEWIDTH: +# 316: case TIFFTAG_TILELENGTH: +# 327: case TIFFTAG_TILEDEPTH: +# 333: case TIFFTAG_DATATYPE: +# 344: case TIFFTAG_SAMPLEFORMAT: +# 361: case TIFFTAG_IMAGEDEPTH: +# 364: case TIFFTAG_SUBIFD: +# 376: case TIFFTAG_YCBCRPOSITIONING: +# 379: case TIFFTAG_YCBCRSUBSAMPLING: +# 383: case TIFFTAG_TRANSFERFUNCTION: +# 389: case TIFFTAG_REFERENCEBLACKWHITE: +# 393: case TIFFTAG_INKNAMES: + +# Following pseudo-tags are also handled by default in libtiff: +# TIFFTAG_JPEGQUALITY 65537 + +# some of these are not in our TAGS_V2 dict and were included from tiff.h + +# This list also exists in encode.c +LIBTIFF_CORE = { + 255, + 256, + 257, + 258, + 259, + 262, + 263, + 266, + 274, + 277, + 278, + 280, + 281, + 340, + 341, + 282, + 283, + 284, + 286, + 287, + 296, + 297, + 321, + 320, + 338, + 32995, + 322, + 323, + 32998, + 32996, + 339, + 32997, + 330, + 531, + 530, + 301, + 532, + 333, + # as above + 269, # this has been in our tests forever, and works + 65537, +} + +LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes +LIBTIFF_CORE.remove(322) # We don't have support for writing tiled images with libtiff +LIBTIFF_CORE.remove(323) # Tiled images +LIBTIFF_CORE.remove(333) # Ink Names either + +# Note to advanced users: There may be combinations of these +# parameters and values that when added properly, will work and +# produce valid tiff images that may work in your application. +# It is safe to add and remove tags from this set from Pillow's point +# of view so long as you test against libtiff. diff --git a/venv/Lib/site-packages/PIL/WalImageFile.py b/venv/Lib/site-packages/PIL/WalImageFile.py new file mode 100644 index 00000000..87e32878 --- /dev/null +++ b/venv/Lib/site-packages/PIL/WalImageFile.py @@ -0,0 +1,127 @@ +# +# The Python Imaging Library. +# $Id$ +# +# WAL file handling +# +# History: +# 2003-04-23 fl created +# +# Copyright (c) 2003 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +""" +This reader is based on the specification available from: +https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml +and has been tested with a few sample files found using google. + +.. note:: + This format cannot be automatically recognized, so the reader + is not registered for use with :py:func:`PIL.Image.open()`. + To open a WAL file, use the :py:func:`PIL.WalImageFile.open()` function instead. +""" +from __future__ import annotations + +from typing import IO + +from . import Image, ImageFile +from ._binary import i32le as i32 +from ._typing import StrOrBytesPath + + +class WalImageFile(ImageFile.ImageFile): + format = "WAL" + format_description = "Quake2 Texture" + + def _open(self) -> None: + self._mode = "P" + + # read header fields + header = self.fp.read(32 + 24 + 32 + 12) + self._size = i32(header, 32), i32(header, 36) + Image._decompression_bomb_check(self.size) + + # load pixel data + offset = i32(header, 40) + self.fp.seek(offset) + + # strings are null-terminated + self.info["name"] = header[:32].split(b"\0", 1)[0] + next_name = header[56 : 56 + 32].split(b"\0", 1)[0] + if next_name: + self.info["next_name"] = next_name + + def load(self) -> Image.core.PixelAccess | None: + if self._im is None: + self.im = Image.core.new(self.mode, self.size) + self.frombytes(self.fp.read(self.size[0] * self.size[1])) + self.putpalette(quake2palette) + return Image.Image.load(self) + + +def open(filename: StrOrBytesPath | IO[bytes]) -> WalImageFile: + """ + Load texture from a Quake2 WAL texture file. + + By default, a Quake2 standard palette is attached to the texture. + To override the palette, use the :py:func:`PIL.Image.Image.putpalette()` method. + + :param filename: WAL file name, or an opened file handle. + :returns: An image instance. + """ + return WalImageFile(filename) + + +quake2palette = ( + # default palette taken from piffo 0.93 by Hans Häggström + b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e" + b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f" + b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c" + b"\x24\x1e\x13\x22\x1c\x12\x20\x1b\x12\x1f\x1a\x10\x1d\x19\x10\x1b" + b"\x17\x0f\x1a\x16\x0f\x18\x14\x0d\x17\x13\x0d\x16\x12\x0d\x14\x10" + b"\x0b\x13\x0f\x0b\x10\x0d\x0a\x0f\x0b\x0a\x0d\x0b\x07\x0b\x0a\x07" + b"\x23\x23\x26\x22\x22\x25\x22\x20\x23\x21\x1f\x22\x20\x1e\x20\x1f" + b"\x1d\x1e\x1d\x1b\x1c\x1b\x1a\x1a\x1a\x19\x19\x18\x17\x17\x17\x16" + b"\x16\x14\x14\x14\x13\x13\x13\x10\x10\x10\x0f\x0f\x0f\x0d\x0d\x0d" + b"\x2d\x28\x20\x29\x24\x1c\x27\x22\x1a\x25\x1f\x17\x38\x2e\x1e\x31" + b"\x29\x1a\x2c\x25\x17\x26\x20\x14\x3c\x30\x14\x37\x2c\x13\x33\x28" + b"\x12\x2d\x24\x10\x28\x1f\x0f\x22\x1a\x0b\x1b\x14\x0a\x13\x0f\x07" + b"\x31\x1a\x16\x30\x17\x13\x2e\x16\x10\x2c\x14\x0d\x2a\x12\x0b\x27" + b"\x0f\x0a\x25\x0f\x07\x21\x0d\x01\x1e\x0b\x01\x1c\x0b\x01\x1a\x0b" + b"\x01\x18\x0a\x01\x16\x0a\x01\x13\x0a\x01\x10\x07\x01\x0d\x07\x01" + b"\x29\x23\x1e\x27\x21\x1c\x26\x20\x1b\x25\x1f\x1a\x23\x1d\x19\x21" + b"\x1c\x18\x20\x1b\x17\x1e\x19\x16\x1c\x18\x14\x1b\x17\x13\x19\x14" + b"\x10\x17\x13\x0f\x14\x10\x0d\x12\x0f\x0b\x0f\x0b\x0a\x0b\x0a\x07" + b"\x26\x1a\x0f\x23\x19\x0f\x20\x17\x0f\x1c\x16\x0f\x19\x13\x0d\x14" + b"\x10\x0b\x10\x0d\x0a\x0b\x0a\x07\x33\x22\x1f\x35\x29\x26\x37\x2f" + b"\x2d\x39\x35\x34\x37\x39\x3a\x33\x37\x39\x30\x34\x36\x2b\x31\x34" + b"\x27\x2e\x31\x22\x2b\x2f\x1d\x28\x2c\x17\x25\x2a\x0f\x20\x26\x0d" + b"\x1e\x25\x0b\x1c\x22\x0a\x1b\x20\x07\x19\x1e\x07\x17\x1b\x07\x14" + b"\x18\x01\x12\x16\x01\x0f\x12\x01\x0b\x0d\x01\x07\x0a\x01\x01\x01" + b"\x2c\x21\x21\x2a\x1f\x1f\x29\x1d\x1d\x27\x1c\x1c\x26\x1a\x1a\x24" + b"\x18\x18\x22\x17\x17\x21\x16\x16\x1e\x13\x13\x1b\x12\x12\x18\x10" + b"\x10\x16\x0d\x0d\x12\x0b\x0b\x0d\x0a\x0a\x0a\x07\x07\x01\x01\x01" + b"\x2e\x30\x29\x2d\x2e\x27\x2b\x2c\x26\x2a\x2a\x24\x28\x29\x23\x27" + b"\x27\x21\x26\x26\x1f\x24\x24\x1d\x22\x22\x1c\x1f\x1f\x1a\x1c\x1c" + b"\x18\x19\x19\x16\x17\x17\x13\x13\x13\x10\x0f\x0f\x0d\x0b\x0b\x0a" + b"\x30\x1e\x1b\x2d\x1c\x19\x2c\x1a\x17\x2a\x19\x14\x28\x17\x13\x26" + b"\x16\x10\x24\x13\x0f\x21\x12\x0d\x1f\x10\x0b\x1c\x0f\x0a\x19\x0d" + b"\x0a\x16\x0b\x07\x12\x0a\x07\x0f\x07\x01\x0a\x01\x01\x01\x01\x01" + b"\x28\x29\x38\x26\x27\x36\x25\x26\x34\x24\x24\x31\x22\x22\x2f\x20" + b"\x21\x2d\x1e\x1f\x2a\x1d\x1d\x27\x1b\x1b\x25\x19\x19\x21\x17\x17" + b"\x1e\x14\x14\x1b\x13\x12\x17\x10\x0f\x13\x0d\x0b\x0f\x0a\x07\x07" + b"\x2f\x32\x29\x2d\x30\x26\x2b\x2e\x24\x29\x2c\x21\x27\x2a\x1e\x25" + b"\x28\x1c\x23\x26\x1a\x21\x25\x18\x1e\x22\x14\x1b\x1f\x10\x19\x1c" + b"\x0d\x17\x1a\x0a\x13\x17\x07\x10\x13\x01\x0d\x0f\x01\x0a\x0b\x01" + b"\x01\x3f\x01\x13\x3c\x0b\x1b\x39\x10\x20\x35\x14\x23\x31\x17\x23" + b"\x2d\x18\x23\x29\x18\x3f\x3f\x3f\x3f\x3f\x39\x3f\x3f\x31\x3f\x3f" + b"\x2a\x3f\x3f\x20\x3f\x3f\x14\x3f\x3c\x12\x3f\x39\x0f\x3f\x35\x0b" + b"\x3f\x32\x07\x3f\x2d\x01\x3d\x2a\x01\x3b\x26\x01\x39\x21\x01\x37" + b"\x1d\x01\x34\x1a\x01\x32\x16\x01\x2f\x12\x01\x2d\x0f\x01\x2a\x0b" + b"\x01\x27\x07\x01\x23\x01\x01\x1d\x01\x01\x17\x01\x01\x10\x01\x01" + b"\x3d\x01\x01\x19\x19\x3f\x3f\x01\x01\x01\x01\x3f\x16\x16\x13\x10" + b"\x10\x0f\x0d\x0d\x0b\x3c\x2e\x2a\x36\x27\x20\x30\x21\x18\x29\x1b" + b"\x10\x3c\x39\x37\x37\x32\x2f\x31\x2c\x28\x2b\x26\x21\x30\x22\x20" +) diff --git a/venv/Lib/site-packages/PIL/WebPImagePlugin.py b/venv/Lib/site-packages/PIL/WebPImagePlugin.py new file mode 100644 index 00000000..1716a18c --- /dev/null +++ b/venv/Lib/site-packages/PIL/WebPImagePlugin.py @@ -0,0 +1,320 @@ +from __future__ import annotations + +from io import BytesIO +from typing import IO, Any + +from . import Image, ImageFile + +try: + from . import _webp + + SUPPORTED = True +except ImportError: + SUPPORTED = False + + +_VP8_MODES_BY_IDENTIFIER = { + b"VP8 ": "RGB", + b"VP8X": "RGBA", + b"VP8L": "RGBA", # lossless +} + + +def _accept(prefix: bytes) -> bool | str: + is_riff_file_format = prefix.startswith(b"RIFF") + is_webp_file = prefix[8:12] == b"WEBP" + is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER + + if is_riff_file_format and is_webp_file and is_valid_vp8_mode: + if not SUPPORTED: + return ( + "image file could not be identified because WEBP support not installed" + ) + return True + return False + + +class WebPImageFile(ImageFile.ImageFile): + format = "WEBP" + format_description = "WebP image" + __loaded = 0 + __logical_frame = 0 + + def _open(self) -> None: + # Use the newer AnimDecoder API to parse the (possibly) animated file, + # and access muxed chunks like ICC/EXIF/XMP. + self._decoder = _webp.WebPAnimDecoder(self.fp.read()) + + # Get info from decoder + self._size, loop_count, bgcolor, frame_count, mode = self._decoder.get_info() + self.info["loop"] = loop_count + bg_a, bg_r, bg_g, bg_b = ( + (bgcolor >> 24) & 0xFF, + (bgcolor >> 16) & 0xFF, + (bgcolor >> 8) & 0xFF, + bgcolor & 0xFF, + ) + self.info["background"] = (bg_r, bg_g, bg_b, bg_a) + self.n_frames = frame_count + self.is_animated = self.n_frames > 1 + self._mode = "RGB" if mode == "RGBX" else mode + self.rawmode = mode + + # Attempt to read ICC / EXIF / XMP chunks from file + icc_profile = self._decoder.get_chunk("ICCP") + exif = self._decoder.get_chunk("EXIF") + xmp = self._decoder.get_chunk("XMP ") + if icc_profile: + self.info["icc_profile"] = icc_profile + if exif: + self.info["exif"] = exif + if xmp: + self.info["xmp"] = xmp + + # Initialize seek state + self._reset(reset=False) + + def _getexif(self) -> dict[int, Any] | None: + if "exif" not in self.info: + return None + return self.getexif()._get_merged_dict() + + def seek(self, frame: int) -> None: + if not self._seek_check(frame): + return + + # Set logical frame to requested position + self.__logical_frame = frame + + def _reset(self, reset: bool = True) -> None: + if reset: + self._decoder.reset() + self.__physical_frame = 0 + self.__loaded = -1 + self.__timestamp = 0 + + def _get_next(self) -> tuple[bytes, int, int]: + # Get next frame + ret = self._decoder.get_next() + self.__physical_frame += 1 + + # Check if an error occurred + if ret is None: + self._reset() # Reset just to be safe + self.seek(0) + msg = "failed to decode next frame in WebP file" + raise EOFError(msg) + + # Compute duration + data, timestamp = ret + duration = timestamp - self.__timestamp + self.__timestamp = timestamp + + # libwebp gives frame end, adjust to start of frame + timestamp -= duration + return data, timestamp, duration + + def _seek(self, frame: int) -> None: + if self.__physical_frame == frame: + return # Nothing to do + if frame < self.__physical_frame: + self._reset() # Rewind to beginning + while self.__physical_frame < frame: + self._get_next() # Advance to the requested frame + + def load(self) -> Image.core.PixelAccess | None: + if self.__loaded != self.__logical_frame: + self._seek(self.__logical_frame) + + # We need to load the image data for this frame + data, timestamp, duration = self._get_next() + self.info["timestamp"] = timestamp + self.info["duration"] = duration + self.__loaded = self.__logical_frame + + # Set tile + if self.fp and self._exclusive_fp: + self.fp.close() + self.fp = BytesIO(data) + self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, self.rawmode)] + + return super().load() + + def load_seek(self, pos: int) -> None: + pass + + def tell(self) -> int: + return self.__logical_frame + + +def _convert_frame(im: Image.Image) -> Image.Image: + # Make sure image mode is supported + if im.mode not in ("RGBX", "RGBA", "RGB"): + im = im.convert("RGBA" if im.has_transparency_data else "RGB") + return im + + +def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + encoderinfo = im.encoderinfo.copy() + append_images = list(encoderinfo.get("append_images", [])) + + # If total frame count is 1, then save using the legacy API, which + # will preserve non-alpha modes + total = 0 + for ims in [im] + append_images: + total += getattr(ims, "n_frames", 1) + if total == 1: + _save(im, fp, filename) + return + + background: int | tuple[int, ...] = (0, 0, 0, 0) + if "background" in encoderinfo: + background = encoderinfo["background"] + elif "background" in im.info: + background = im.info["background"] + if isinstance(background, int): + # GifImagePlugin stores a global color table index in + # info["background"]. So it must be converted to an RGBA value + palette = im.getpalette() + if palette: + r, g, b = palette[background * 3 : (background + 1) * 3] + background = (r, g, b, 255) + else: + background = (background, background, background, 255) + + duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) + loop = im.encoderinfo.get("loop", 0) + minimize_size = im.encoderinfo.get("minimize_size", False) + kmin = im.encoderinfo.get("kmin", None) + kmax = im.encoderinfo.get("kmax", None) + allow_mixed = im.encoderinfo.get("allow_mixed", False) + verbose = False + lossless = im.encoderinfo.get("lossless", False) + quality = im.encoderinfo.get("quality", 80) + alpha_quality = im.encoderinfo.get("alpha_quality", 100) + method = im.encoderinfo.get("method", 0) + icc_profile = im.encoderinfo.get("icc_profile") or "" + exif = im.encoderinfo.get("exif", "") + if isinstance(exif, Image.Exif): + exif = exif.tobytes() + xmp = im.encoderinfo.get("xmp", "") + if allow_mixed: + lossless = False + + # Sensible keyframe defaults are from gif2webp.c script + if kmin is None: + kmin = 9 if lossless else 3 + if kmax is None: + kmax = 17 if lossless else 5 + + # Validate background color + if ( + not isinstance(background, (list, tuple)) + or len(background) != 4 + or not all(0 <= v < 256 for v in background) + ): + msg = f"Background color is not an RGBA tuple clamped to (0-255): {background}" + raise OSError(msg) + + # Convert to packed uint + bg_r, bg_g, bg_b, bg_a = background + background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0) + + # Setup the WebP animation encoder + enc = _webp.WebPAnimEncoder( + im.size, + background, + loop, + minimize_size, + kmin, + kmax, + allow_mixed, + verbose, + ) + + # Add each frame + frame_idx = 0 + timestamp = 0 + cur_idx = im.tell() + try: + for ims in [im] + append_images: + # Get number of frames in this image + nfr = getattr(ims, "n_frames", 1) + + for idx in range(nfr): + ims.seek(idx) + + frame = _convert_frame(ims) + + # Append the frame to the animation encoder + enc.add( + frame.getim(), + round(timestamp), + lossless, + quality, + alpha_quality, + method, + ) + + # Update timestamp and frame index + if isinstance(duration, (list, tuple)): + timestamp += duration[frame_idx] + else: + timestamp += duration + frame_idx += 1 + + finally: + im.seek(cur_idx) + + # Force encoder to flush frames + enc.add(None, round(timestamp), lossless, quality, alpha_quality, 0) + + # Get the final output from the encoder + data = enc.assemble(icc_profile, exif, xmp) + if data is None: + msg = "cannot write file as WebP (encoder returned None)" + raise OSError(msg) + + fp.write(data) + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + lossless = im.encoderinfo.get("lossless", False) + quality = im.encoderinfo.get("quality", 80) + alpha_quality = im.encoderinfo.get("alpha_quality", 100) + icc_profile = im.encoderinfo.get("icc_profile") or "" + exif = im.encoderinfo.get("exif", b"") + if isinstance(exif, Image.Exif): + exif = exif.tobytes() + if exif.startswith(b"Exif\x00\x00"): + exif = exif[6:] + xmp = im.encoderinfo.get("xmp", "") + method = im.encoderinfo.get("method", 4) + exact = 1 if im.encoderinfo.get("exact") else 0 + + im = _convert_frame(im) + + data = _webp.WebPEncode( + im.getim(), + lossless, + float(quality), + float(alpha_quality), + icc_profile, + method, + exact, + exif, + xmp, + ) + if data is None: + msg = "cannot write file as WebP (encoder returned None)" + raise OSError(msg) + + fp.write(data) + + +Image.register_open(WebPImageFile.format, WebPImageFile, _accept) +if SUPPORTED: + Image.register_save(WebPImageFile.format, _save) + Image.register_save_all(WebPImageFile.format, _save_all) + Image.register_extension(WebPImageFile.format, ".webp") + Image.register_mime(WebPImageFile.format, "image/webp") diff --git a/venv/Lib/site-packages/PIL/WmfImagePlugin.py b/venv/Lib/site-packages/PIL/WmfImagePlugin.py new file mode 100644 index 00000000..d569cb4b --- /dev/null +++ b/venv/Lib/site-packages/PIL/WmfImagePlugin.py @@ -0,0 +1,186 @@ +# +# The Python Imaging Library +# $Id$ +# +# WMF stub codec +# +# history: +# 1996-12-14 fl Created +# 2004-02-22 fl Turned into a stub driver +# 2004-02-23 fl Added EMF support +# +# Copyright (c) Secret Labs AB 1997-2004. All rights reserved. +# Copyright (c) Fredrik Lundh 1996. +# +# See the README file for information on usage and redistribution. +# +# WMF/EMF reference documentation: +# https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf +# http://wvware.sourceforge.net/caolan/index.html +# http://wvware.sourceforge.net/caolan/ora-wmf.html +from __future__ import annotations + +from typing import IO + +from . import Image, ImageFile +from ._binary import i16le as word +from ._binary import si16le as short +from ._binary import si32le as _long + +_handler = None + + +def register_handler(handler: ImageFile.StubHandler | None) -> None: + """ + Install application-specific WMF image handler. + + :param handler: Handler object. + """ + global _handler + _handler = handler + + +if hasattr(Image.core, "drawwmf"): + # install default handler (windows only) + + class WmfHandler(ImageFile.StubHandler): + def open(self, im: ImageFile.StubImageFile) -> None: + im._mode = "RGB" + self.bbox = im.info["wmf_bbox"] + + def load(self, im: ImageFile.StubImageFile) -> Image.Image: + im.fp.seek(0) # rewind + return Image.frombytes( + "RGB", + im.size, + Image.core.drawwmf(im.fp.read(), im.size, self.bbox), + "raw", + "BGR", + (im.size[0] * 3 + 3) & -4, + -1, + ) + + register_handler(WmfHandler()) + +# +# -------------------------------------------------------------------- +# Read WMF file + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith((b"\xd7\xcd\xc6\x9a\x00\x00", b"\x01\x00\x00\x00")) + + +## +# Image plugin for Windows metafiles. + + +class WmfStubImageFile(ImageFile.StubImageFile): + format = "WMF" + format_description = "Windows Metafile" + + def _open(self) -> None: + # check placable header + s = self.fp.read(44) + + if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): + # placeable windows metafile + + # get units per inch + inch = word(s, 14) + if inch == 0: + msg = "Invalid inch" + raise ValueError(msg) + self._inch: tuple[float, float] = inch, inch + + # get bounding box + x0 = short(s, 6) + y0 = short(s, 8) + x1 = short(s, 10) + y1 = short(s, 12) + + # normalize size to 72 dots per inch + self.info["dpi"] = 72 + size = ( + (x1 - x0) * self.info["dpi"] // inch, + (y1 - y0) * self.info["dpi"] // inch, + ) + + self.info["wmf_bbox"] = x0, y0, x1, y1 + + # sanity check (standard metafile header) + if s[22:26] != b"\x01\x00\t\x00": + msg = "Unsupported WMF file format" + raise SyntaxError(msg) + + elif s.startswith(b"\x01\x00\x00\x00") and s[40:44] == b" EMF": + # enhanced metafile + + # get bounding box + x0 = _long(s, 8) + y0 = _long(s, 12) + x1 = _long(s, 16) + y1 = _long(s, 20) + + # get frame (in 0.01 millimeter units) + frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36) + + size = x1 - x0, y1 - y0 + + # calculate dots per inch from bbox and frame + xdpi = 2540.0 * (x1 - x0) / (frame[2] - frame[0]) + ydpi = 2540.0 * (y1 - y0) / (frame[3] - frame[1]) + + self.info["wmf_bbox"] = x0, y0, x1, y1 + + if xdpi == ydpi: + self.info["dpi"] = xdpi + else: + self.info["dpi"] = xdpi, ydpi + self._inch = xdpi, ydpi + + else: + msg = "Unsupported file format" + raise SyntaxError(msg) + + self._mode = "RGB" + self._size = size + + loader = self._load() + if loader: + loader.open(self) + + def _load(self) -> ImageFile.StubHandler | None: + return _handler + + def load( + self, dpi: float | tuple[float, float] | None = None + ) -> Image.core.PixelAccess | None: + if dpi is not None: + self.info["dpi"] = dpi + x0, y0, x1, y1 = self.info["wmf_bbox"] + if not isinstance(dpi, tuple): + dpi = dpi, dpi + self._size = ( + int((x1 - x0) * dpi[0] / self._inch[0]), + int((y1 - y0) * dpi[1] / self._inch[1]), + ) + return super().load() + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if _handler is None or not hasattr(_handler, "save"): + msg = "WMF save handler not installed" + raise OSError(msg) + _handler.save(im, fp, filename) + + +# +# -------------------------------------------------------------------- +# Registry stuff + + +Image.register_open(WmfStubImageFile.format, WmfStubImageFile, _accept) +Image.register_save(WmfStubImageFile.format, _save) + +Image.register_extensions(WmfStubImageFile.format, [".wmf", ".emf"]) diff --git a/venv/Lib/site-packages/PIL/XVThumbImagePlugin.py b/venv/Lib/site-packages/PIL/XVThumbImagePlugin.py new file mode 100644 index 00000000..cde28388 --- /dev/null +++ b/venv/Lib/site-packages/PIL/XVThumbImagePlugin.py @@ -0,0 +1,83 @@ +# +# The Python Imaging Library. +# $Id$ +# +# XV Thumbnail file handler by Charles E. "Gene" Cash +# (gcash@magicnet.net) +# +# see xvcolor.c and xvbrowse.c in the sources to John Bradley's XV, +# available from ftp://ftp.cis.upenn.edu/pub/xv/ +# +# history: +# 98-08-15 cec created (b/w only) +# 98-12-09 cec added color palette +# 98-12-28 fl added to PIL (with only a few very minor modifications) +# +# To do: +# FIXME: make save work (this requires quantization support) +# +from __future__ import annotations + +from . import Image, ImageFile, ImagePalette +from ._binary import o8 + +_MAGIC = b"P7 332" + +# standard color palette for thumbnails (RGB332) +PALETTE = b"" +for r in range(8): + for g in range(8): + for b in range(4): + PALETTE = PALETTE + ( + o8((r * 255) // 7) + o8((g * 255) // 7) + o8((b * 255) // 3) + ) + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(_MAGIC) + + +## +# Image plugin for XV thumbnail images. + + +class XVThumbImageFile(ImageFile.ImageFile): + format = "XVThumb" + format_description = "XV thumbnail image" + + def _open(self) -> None: + # check magic + assert self.fp is not None + + if not _accept(self.fp.read(6)): + msg = "not an XV thumbnail file" + raise SyntaxError(msg) + + # Skip to beginning of next line + self.fp.readline() + + # skip info comments + while True: + s = self.fp.readline() + if not s: + msg = "Unexpected EOF reading XV thumbnail file" + raise SyntaxError(msg) + if s[0] != 35: # ie. when not a comment: '#' + break + + # parse header line (already read) + s = s.strip().split() + + self._mode = "P" + self._size = int(s[0]), int(s[1]) + + self.palette = ImagePalette.raw("RGB", PALETTE) + + self.tile = [ + ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode) + ] + + +# -------------------------------------------------------------------- + +Image.register_open(XVThumbImageFile.format, XVThumbImageFile, _accept) diff --git a/venv/Lib/site-packages/PIL/XbmImagePlugin.py b/venv/Lib/site-packages/PIL/XbmImagePlugin.py new file mode 100644 index 00000000..1e57aa16 --- /dev/null +++ b/venv/Lib/site-packages/PIL/XbmImagePlugin.py @@ -0,0 +1,98 @@ +# +# The Python Imaging Library. +# $Id$ +# +# XBM File handling +# +# History: +# 1995-09-08 fl Created +# 1996-11-01 fl Added save support +# 1997-07-07 fl Made header parser more tolerant +# 1997-07-22 fl Fixed yet another parser bug +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) +# 2001-05-13 fl Added hotspot handling (based on code from Bernhard Herzog) +# 2004-02-24 fl Allow some whitespace before first #define +# +# Copyright (c) 1997-2004 by Secret Labs AB +# Copyright (c) 1996-1997 by Fredrik Lundh +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re +from typing import IO + +from . import Image, ImageFile + +# XBM header +xbm_head = re.compile( + rb"\s*#define[ \t]+.*_width[ \t]+(?P[0-9]+)[\r\n]+" + b"#define[ \t]+.*_height[ \t]+(?P[0-9]+)[\r\n]+" + b"(?P" + b"#define[ \t]+[^_]*_x_hot[ \t]+(?P[0-9]+)[\r\n]+" + b"#define[ \t]+[^_]*_y_hot[ \t]+(?P[0-9]+)[\r\n]+" + b")?" + rb"[\000-\377]*_bits\[]" +) + + +def _accept(prefix: bytes) -> bool: + return prefix.lstrip().startswith(b"#define") + + +## +# Image plugin for X11 bitmaps. + + +class XbmImageFile(ImageFile.ImageFile): + format = "XBM" + format_description = "X11 Bitmap" + + def _open(self) -> None: + assert self.fp is not None + + m = xbm_head.match(self.fp.read(512)) + + if not m: + msg = "not a XBM file" + raise SyntaxError(msg) + + xsize = int(m.group("width")) + ysize = int(m.group("height")) + + if m.group("hotspot"): + self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot"))) + + self._mode = "1" + self._size = xsize, ysize + + self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end())] + + +def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + if im.mode != "1": + msg = f"cannot write mode {im.mode} as XBM" + raise OSError(msg) + + fp.write(f"#define im_width {im.size[0]}\n".encode("ascii")) + fp.write(f"#define im_height {im.size[1]}\n".encode("ascii")) + + hotspot = im.encoderinfo.get("hotspot") + if hotspot: + fp.write(f"#define im_x_hot {hotspot[0]}\n".encode("ascii")) + fp.write(f"#define im_y_hot {hotspot[1]}\n".encode("ascii")) + + fp.write(b"static char im_bits[] = {\n") + + ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size)]) + + fp.write(b"};\n") + + +Image.register_open(XbmImageFile.format, XbmImageFile, _accept) +Image.register_save(XbmImageFile.format, _save) + +Image.register_extension(XbmImageFile.format, ".xbm") + +Image.register_mime(XbmImageFile.format, "image/xbm") diff --git a/venv/Lib/site-packages/PIL/XpmImagePlugin.py b/venv/Lib/site-packages/PIL/XpmImagePlugin.py new file mode 100644 index 00000000..3be240fb --- /dev/null +++ b/venv/Lib/site-packages/PIL/XpmImagePlugin.py @@ -0,0 +1,157 @@ +# +# The Python Imaging Library. +# $Id$ +# +# XPM File handling +# +# History: +# 1996-12-29 fl Created +# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7) +# +# Copyright (c) Secret Labs AB 1997-2001. +# Copyright (c) Fredrik Lundh 1996-2001. +# +# See the README file for information on usage and redistribution. +# +from __future__ import annotations + +import re + +from . import Image, ImageFile, ImagePalette +from ._binary import o8 + +# XPM header +xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)') + + +def _accept(prefix: bytes) -> bool: + return prefix.startswith(b"/* XPM */") + + +## +# Image plugin for X11 pixel maps. + + +class XpmImageFile(ImageFile.ImageFile): + format = "XPM" + format_description = "X11 Pixel Map" + + def _open(self) -> None: + assert self.fp is not None + if not _accept(self.fp.read(9)): + msg = "not an XPM file" + raise SyntaxError(msg) + + # skip forward to next string + while True: + line = self.fp.readline() + if not line: + msg = "broken XPM file" + raise SyntaxError(msg) + m = xpm_head.match(line) + if m: + break + + self._size = int(m.group(1)), int(m.group(2)) + + palette_length = int(m.group(3)) + bpp = int(m.group(4)) + + # + # load palette description + + palette = {} + + for _ in range(palette_length): + line = self.fp.readline().rstrip() + + c = line[1 : bpp + 1] + s = line[bpp + 1 : -2].split() + + for i in range(0, len(s), 2): + if s[i] == b"c": + # process colour key + rgb = s[i + 1] + if rgb == b"None": + self.info["transparency"] = c + elif rgb.startswith(b"#"): + rgb_int = int(rgb[1:], 16) + palette[c] = ( + o8((rgb_int >> 16) & 255) + + o8((rgb_int >> 8) & 255) + + o8(rgb_int & 255) + ) + else: + # unknown colour + msg = "cannot read this XPM file" + raise ValueError(msg) + break + + else: + # missing colour key + msg = "cannot read this XPM file" + raise ValueError(msg) + + args: tuple[int, dict[bytes, bytes] | tuple[bytes, ...]] + if palette_length > 256: + self._mode = "RGB" + args = (bpp, palette) + else: + self._mode = "P" + self.palette = ImagePalette.raw("RGB", b"".join(palette.values())) + args = (bpp, tuple(palette.keys())) + + self.tile = [ImageFile._Tile("xpm", (0, 0) + self.size, self.fp.tell(), args)] + + def load_read(self, read_bytes: int) -> bytes: + # + # load all image data in one chunk + + xsize, ysize = self.size + + assert self.fp is not None + s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] + + return b"".join(s) + + +class XpmDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]: + assert self.fd is not None + + data = bytearray() + bpp, palette = self.args + dest_length = self.state.xsize * self.state.ysize + if self.mode == "RGB": + dest_length *= 3 + pixel_header = False + while len(data) < dest_length: + line = self.fd.readline() + if not line: + break + if line.rstrip() == b"/* pixels */" and not pixel_header: + pixel_header = True + continue + line = b'"'.join(line.split(b'"')[1:-1]) + for i in range(0, len(line), bpp): + key = line[i : i + bpp] + if self.mode == "RGB": + data += palette[key] + else: + data += o8(palette.index(key)) + self.set_as_raw(bytes(data)) + return -1, 0 + + +# +# Registry + + +Image.register_open(XpmImageFile.format, XpmImageFile, _accept) +Image.register_decoder("xpm", XpmDecoder) + +Image.register_extension(XpmImageFile.format, ".xpm") + +Image.register_mime(XpmImageFile.format, "image/xpm") diff --git a/venv/Lib/site-packages/PIL/__init__.py b/venv/Lib/site-packages/PIL/__init__.py new file mode 100644 index 00000000..6e4c23f8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/__init__.py @@ -0,0 +1,87 @@ +"""Pillow (Fork of the Python Imaging Library) + +Pillow is the friendly PIL fork by Jeffrey A. Clark and contributors. + https://github.com/python-pillow/Pillow/ + +Pillow is forked from PIL 1.1.7. + +PIL is the Python Imaging Library by Fredrik Lundh and contributors. +Copyright (c) 1999 by Secret Labs AB. + +Use PIL.__version__ for this Pillow version. + +;-) +""" + +from __future__ import annotations + +from . import _version + +# VERSION was removed in Pillow 6.0.0. +# PILLOW_VERSION was removed in Pillow 9.0.0. +# Use __version__ instead. +__version__ = _version.__version__ +del _version + + +_plugins = [ + "AvifImagePlugin", + "BlpImagePlugin", + "BmpImagePlugin", + "BufrStubImagePlugin", + "CurImagePlugin", + "DcxImagePlugin", + "DdsImagePlugin", + "EpsImagePlugin", + "FitsImagePlugin", + "FliImagePlugin", + "FpxImagePlugin", + "FtexImagePlugin", + "GbrImagePlugin", + "GifImagePlugin", + "GribStubImagePlugin", + "Hdf5StubImagePlugin", + "IcnsImagePlugin", + "IcoImagePlugin", + "ImImagePlugin", + "ImtImagePlugin", + "IptcImagePlugin", + "JpegImagePlugin", + "Jpeg2KImagePlugin", + "McIdasImagePlugin", + "MicImagePlugin", + "MpegImagePlugin", + "MpoImagePlugin", + "MspImagePlugin", + "PalmImagePlugin", + "PcdImagePlugin", + "PcxImagePlugin", + "PdfImagePlugin", + "PixarImagePlugin", + "PngImagePlugin", + "PpmImagePlugin", + "PsdImagePlugin", + "QoiImagePlugin", + "SgiImagePlugin", + "SpiderImagePlugin", + "SunImagePlugin", + "TgaImagePlugin", + "TiffImagePlugin", + "WebPImagePlugin", + "WmfImagePlugin", + "XbmImagePlugin", + "XpmImagePlugin", + "XVThumbImagePlugin", +] + + +class UnidentifiedImageError(OSError): + """ + Raised in :py:meth:`PIL.Image.open` if an image cannot be opened and identified. + + If a PNG image raises this error, setting :data:`.ImageFile.LOAD_TRUNCATED_IMAGES` + to true may allow the image to be opened after all. The setting will ignore missing + data and checksum failures. + """ + + pass diff --git a/venv/Lib/site-packages/PIL/__main__.py b/venv/Lib/site-packages/PIL/__main__.py new file mode 100644 index 00000000..043156e8 --- /dev/null +++ b/venv/Lib/site-packages/PIL/__main__.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +import sys + +from .features import pilinfo + +pilinfo(supported_formats="--report" not in sys.argv) diff --git a/venv/Lib/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f2b1d4ed9b7e1a6056713c436a560937b93816d GIT binary patch literal 11497 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 V>RGBg+rH~ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e46dc087972a942121be92374b1f47f718379680 GIT binary patch literal 23978 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 F{{>6JK)V0{ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae586cc4209e22d040da33432926aa95bf588c75 GIT binary patch literal 2644 zcmZ`*&2JM&6rWkIKjM#sINu=zD*;hcaEMwJlvY$FB_#?NM8w5SWo^8Zux9(_iB{rPmwiQ}EP6=5hDp9FUGBiOc>XI%cB#BNcDpBOXM4(`tB07iFwnV&O!~j=CbufP;FNIFd`%o z=1KQ8JRFS$)BTR#kSxgj&K#78v5|(=(LOLXv?kdewUGmke?z{1q#9_9EK1Rq9UKs) zRzS&SbEaZyxvUX~wlscu2cRTT$#Rcplv!pq2{ESWEQJQApbkjTst=S^QX&c|(x|kM z^hcnKXAM))^@x(s>sm@of6~aals2QKBImA+U5#isdE~m1RdvQ=elcn!uW%*fbdE$W z*w#pHI?YmMO!&l~P|KIBje`w)YpQ`8ioBi}9htgjFm6m`3c8Y+`kFH(b5SWWK6R62 zZ%$p*rl$IUqX>rsmdSO<}#_~n$0B5tBVKVNl$3w74 z2Yf~oREuP{nH=c4eW3~svJV~Ph(PBED2PsaFLvk-MWt)C-buha9+eCkdJ!F=Bt{dX za6msG$xUNUGp|F-ljm$ko3~HPgWzv@lS(SZ@+J>MYY;x89jZlA3AAno`ql$|VGk=RA}Y;TQG9 zcktpDTH-k~qS)av1F-J=_vXzTi;hJlj_`;o8W7iPNi%s%25zfXV6vDsmHG3W=eQ*s zjNP!LoMHJU&!3-2j$XTJxs#ck%0L*}0t!6`qvTwkWuq<}pwC#Yj4^9@&;XGG*G3?O zZF0Gj=(y1J&Yj1E;bZVMZa}q2wpuz@%+==m=uzrf zc%U2{*!BhQdzZcUgUi8{tB=FK9Q^s8;-g2y$3@a zM$j3r9uL;F)FKvz5VbsHm zC-F84I_kk>^813K*DRiy9M34G6}DwkWk!l?dCbiG7%U?8`-F&%h`Zq!=AuHg(FN6A zQVF#!j#YXNm*vi?9CG)+>Zk5*dkng#@N%6}kI{MP*$dn417_W?0Vc#_EpkO1y3Bga zmRt^_@6d*Sx*j>sf&>y_iI$|s^9SsA#esy%O}21YybGv0FF*vIrif-gB}|RV9OD)< zzM+^Vx4f4p1?%}I&>fX6Nz3qF^zdQyAguf-G(|GSN6BF{F2T=$^U(2{Gbxnm`DPcQp=x8&fDmu_EL8CaQq^wD#9Pyo8%j!nb60ow|| z6dXfAuz?>34SHKaSnlbf$qdnN$7@?1{t3p_u}Z5IDr~o;d!?usmiluVrVsj19s)Xuul~poxde&K#loLx-u`CJ};R zh3c;zhCCnI--n=C;kpZ(H(Ay|ZVuxb7pjCyPd_K0i>>|- D5_Ld+ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ContainerIO.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ContainerIO.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94aa5dc2c12857bc5d31bf83410b395871262e40 GIT binary patch literal 6994 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7946788350a16ec0f316359e1bc6ea7c60180e9 GIT binary patch literal 2366 zcmb6aTWk|YaQ7a5oc# z0#$rCB~oGq$>uMp9}UV6{c1n^-TuT1ZQK<|m0GFFU#id_zPfw%B`(TOS9d$JGqW?Z zvoo_l`+Oci$IG7{-n!xh;19O2huA>i&Yu*i00k&84OAF~5Gb6&r#Y36@~RLOR52=A zG$HL!ol%#CixhW9JrwsUjt4^2%YjQkaXtWw3-L2Owvj!!#y9Y&j{``U>EGkn81l^= zvhh8Le8_9>g}~Yy+T+^T{(BYo8rP73Am<57TWk@A#4Br>Zpvm#*NiR5Xgq1Vpk^+s z@-(`V&Q7N^B3cXLw!pIZz~%BhI*-NT7^~+o)Q014aj(?t{A5 zzv=C-dHbthYri4!#@(peX(i z&qRA~kt+xWU*L+u!eOf0(Mt^#ga*7lPHzn^oT7ARlWxH#{X=WlIf2U4lep+f4(xi_ zauVv5-=^)Yy<|^r9mdyPPzWBM@Dt1Dlg}L^$(QJ*V}^?XW3bi%2M^} zMti7q{b$?)Ks2r@q>cC@Rxo^e4GV~<43Ref73g7fu65C!782noT?f!Zo z%#8I_$Ce}OQi$4e`xd2n>Cv^Szj|Rgx^lGEdHlI6Oc`BoRc@AVR%gqxmEmtko{X%1 z_GI+gQ0?&WdgpLSSn$+4du@SvPYITWO7fEbMR%xjuY7N1cJ=79;qOPj8>yWb{XzcG zztKHb5*FR_?xlgRB`aAb!yIv7WM;8Ogh^dPv@5#eaZOfHJWf3ExT-7JG{dEMd^Rhm zn;b_{$Es|SKm*1VWF&AZ!yYR9E)D-vV5zrm3bP8frX%cPSnWOlbzk8A#d3=5d0ClRM|CNl64!-XkbPlx@?v1FDyO&!Cm-x7dX3&pG`8Y#vS zQIl~FyBLW**O;|RJisRQsu0P3M_Kc?+#^1Wrc;KAu$4ymEE5at($HNKX$Cvt`%DR| zHS2t@U89XCox|rTjlG11LZ@vWLio=S01y8KA~g_s2?pr5BSCoLRr}j;94?*P?1

UYeOZ9cc^7 zOBdkI-0aNk&g^ey`Hxg8L0~-n``x*}sf7F+Kf<8|3_WfFQzwitYLN;pP)b;YNmisH z6(paRExDo;qCT%!v5HzyeLiZ%D~Un^_*fyyQcS%g7qkeOA}oG~umn#$l7c-7sR-fH z{9w3soAT8BkJ?`$$5UJjZ7K0>bKzQ>o^8vLk0OB%CCggA<2iohsh2ctyXqRQS+$+# zG>|FRD#k2_MaBI2+ZHmZ30~$xaQ3kfRRIF+dTGhmnlvqL{&nu#_ey9S4h#@}^mr1O zI&oWTtK`uM&*(ytkR>Y8l(;7eA*8+T%AvD`Q9^Ezd}QW1it>^tS={y{IGd+@^3l|_ zaX3$OQSh?4B1ca!7q6@TM!;S?)1%cvA51UD+v#xfVhTim`?oHlP39nX6R)@DMN2`Bu8y zLPo=jxlP@Z8i4C)Nu3mn+>W@h&|)VHmWVIRG9U2ZbeVN$3wL4Nl!A-5$r6|Vh1 zCkwsZmRDsbLWw;DUZ8$YkNM8i5{N_&ttq)TCI3KXAdZ$lCc{@e^yFqj?(987?_kr4 zA2fSIt2SlI!UO_c*FqWS4TOA~vS{cNDwgD9GXk5|N(Yo{&cK_`m9h2+B>wsTURq(%( zei_PRzHAJZov&e9BhR#Rn^PN8o6{T9J6(5wy-p81d$&Hjx6({sSRX%35ABp5r!VYj z7mlJN+rKW~(+<0Ox7-qq$gW{XZsy+q&73>fg}#I13MEBrteQb&gS7e)f^ap@ZtRr zAHLNXxV$d!tJ$WiZN@j^TO)taL>HX7bW+>E61<6^_}&hS58KhH@lr|Ad5+(^U#UYnStxmY~tv1$?()8SYHTnFU4Wu`8Y$tF#X!ZQ#1y zp~8Gm$hB#^+atukq%+X$k~$(wFU|4Nf|oEI({^0LE^#pgb^`m47vW0+pGR>XC+y3j zA15qbaS5h;L|i}~l^ndG6BPQoD_yT(5O*6#y*ZfHNVBv1pmVsc+SaG_;&yTOVk0xU zujUWco&&YNq4w`wd7|b5?7WN)*D$?zyxrvgOL zC#Z)P>j${GvazzK_W6gH$w$1X;BHOWo;*{vd0zIN!~1Hx;!Q9S_-a8$ef}-v%oX{~!W9;)~}uVgJNlO7L0J!9cMeimT{0 z-IDPom)j0xNW6MM0djPm656w-9FXn?>E4zkEirxRPy< V?9Sjp-{nT%<%i`bmPrCw149xCH`1LBA>_K^Hyr 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 D{s5Fj literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1339699516119936bf5f515d0447c4aa7aa81ee5 GIT binary patch literal 15685 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..998bec83fe29b4cf61695e516cc91f4a3a6b3a1b GIT binary patch literal 6005 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bfb5234225e17c708319e88ba6c071205c91158 GIT binary patch literal 6925 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/FontFile.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/FontFile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4883295a9a721837c31a7ab076c12e93da21340 GIT binary patch literal 4473 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70f71b23b9ce21c56b38405ba2291cda145dbc85 GIT binary patch literal 3677 zcmcInO>7&-6`tkp@<*gZijrB&k}b-Xs4$R~+HoUU5u7Sfq$*HZ+evDKWy+G=CAAc} zRAyIy%&IjCAE^95Bzz!MqcDvWunZrf0tITIJ@i<#7t(YqVxa&6TA#LfCg8{%#v7ofX|wyiChg^yn?^pvfgkFD2! zMHeSF#y)G=JX>~Og8A>;9{unS*M4{AkFS(2Un?=!_CN?lNT)YFI(xmal1Du9QylUe zILc3R20z0YIT~_{R@*KA7{}D z%`poUc5tjh-$B1Le8cB4Uz6VK*Yq8u0+}#-3et{M*c$a!URR8qN%NLjF@BY|z^j(o z7a)R063a9o~mOFYeH(#t4!GU5+$e zHwux{(x8iC)Or-uUNXYIjJ!@CwgQF8TG|2F?hd&dn1JD_LuvQBqBK7@m%=j79oTd) z06xM)Fyhuw)?33??Q`kg=ZHj7i4=BofQU07reSdx93c^k9GgCSRD}U|IK;W(g%Kid zaXuO5v1*b4bpO+hXshcpRq z6Rh!xsA`>o19+@0s&rC{siu9msO+M^$up{Ph8JQpvTD_Ahcm!-wPjz|AiO{^nA1|g}@m$5_DLFjb=R8IE z{^GsG`^r6KbL_L9Rn84%#@D7+r?NuX=H79-^8LB~{6KD?@Y9NOa6{TOl$8hS7!Xvw{|+ZGvlAUwR7TBVIuo}hWVsvr`wZZ)-0=*tha3LdS*nHmMwFC+1$Uu zd~SJQd1Ai!)QCFWMbG-+#;fI1uVk1{?YIr1cl^_P5ki_LpadL8+s&k0e}?1a1|4bf z`qSFjI38wTwGIjmUcIC`1XL(q2crfj$R7htabnhy2AszfusT@jBNTTH%j#6)Q}J&# zXq{@5kx>3oKCexsgF>=)wV6l?L&YQViz-9zJ?;Z3)gUBQOBBO3UzT|ZpMf>pO*#+R zWfG|n=cRBzOlno3b_ek=%+sxhZXn_O5n+Ss6)s@aYF`^(9WGfrw_Tn2crKn7b7JwG z`|sU*Z>){Z3_lJm=7iPcZ|^#PKIRFSsMYbqzD$~NX3u84X0@KKR}u$HHVxa ztE7X907>L8#w}EBcHW$*cDz_(TzkxUcIerUDTejbga~Vz8H|Oo&&yzvH!53;0I@=4 z!l_ik+e}o9&V!MlB8K`CsYr_@CT}Lx)nP#l;blxxT!VY9!q(JKn=u~~BpFbdpe4RS z1f2CXeo5v<0`T~|M3&^JX2+N2(V9!H;P1g2`5z#?58YFmqNsmfLe#l`pfg{fo;@Q) zU8kNohA2<=@>ZLt+~#?PVDeO<5Y>K@-9l|;)RvvzYVR+%_iuDsyEjegd74(UFq|Uw% zdhaNiD%Bc>ALKfPRJ4GVrx-{cT=*qV{TqVzg@hl%9vr|xQxtkrDhDWBpy})$X)$i| z(1Wx)JG(QpJ3I5+;h%eZqX@pw|8#%p?HEFTWrNPA1cb2rJ`fKOL3u<3BAS9F=0$vt!&W38;k=O9V@2~ZPKSYx=X*IFF%wo_zE40R$|BNp7m=tQ zx*qQs-s>Mr0i92hUXmcOyGp*l7FYYUI|4${bQ;@^r)M%z4Bz~c;o_1*EzQG4&4wAg zsAH|_IlwWBnrYTB)r(HqHdb{qid_eLOF9=Vnwslk5NASEFB`6>Q=Kqvxa#V7#qgGJ zrc%q4s&+AR?Q(89owKyEo^~p_eL}sK2I~%H1FUg{Wso&YeUwXsl~;tbgo|H-B^?!* zsrC%V#)b{nHIk09^Q~yqHtcfeh;L2d>?FQ4d*#Oy^Efw$XJ+T|b!9=$l z*b3#VHIu8TRB6R=br(CfSxZN^+3l!;ALH)k3tbqBx!G;FAEZit&&z}QaZX;=Ox^Qz zfAFdemTk`{8G7K2DM}p*(uy&5c2VcVnQ=}e<~%w}->iAMds*|Aia|^Str&E|E(YQO z(otT3KoyCQmx!2`iIi7Jh{$*4e5j_VVL#U8du@b85sMwZHC!>PWy9vT&h8964R_*L z6{@>#;3SA7@aPN#9mEdAnYR~nfs2C>a;ue!Lp_jLtg#@&t_L2~D0w9gxY_@TfFPP6 z?zl>BBG}m|t~ph_q%DJW2g7xO4X&Prrsa?zGP@3V4Fk7o^84h;D$Qfk_eJr7D@OZs9BTh z5*3MJ3na28d%Ydab%FHME*fqy>rolVFTJ~KZ3i|FAP_+;Cb*P&c(BEc-BpxV+8$zs(eGhoGT+ z$Zl~P!JYeYz#rSKXcSQh{>->98#X{ZvqqU~{BR{eC(5=0E9U{AeVMP9J)qSPjC8Z) z3$8C&ZrP7|w5k`1j_FXpe~&IWrIM?Ae#kRyqTh0vItDWszqoiYvvAebsk>lRP0d=E zgxy-xT1}@5fWFHM)5hY0Yk2ypidMYA(rW=;o6}u6ovzeiXY8$6?eAuD))*Lali;}9 z=vm*sjg@;VjlSVVWEk3K@#IGKUUp;p-t^Y~C-Ece!WXf``px@iw~jPp$6Kk>>ldG< z4sR8oq=p;u;b#Lw>+<{2rvpP<{aX{id+%fE;l#t6AN4c_RK|ZcFt~a1xAW`KzZ^aJ z!Ne#1jmW|0A(R@}{K=Nkc)*Wi>+`J>>GjEt+`ZhU-HeZXap2fz z2aY!n9B;)_n-iN_VqpV@Dz?<&^;WFH~i@p>UC9;!1iPY^&t%G9? zdGNW6m1rQv1d44q0ur$}55Tkz1c0ePgd0C#N{I+i*M>v@!Y-)mLEcLMt)mVE&Y@$- zW7_!l2BQ1cq;G@Ex^zb*@|yHZX+>B;x1{&bil8dmF)+uUf@w!1*wb%;7j;~sjs?jL zY0cjPUi3iW!XiBbNvm17RLQVuRt;h1&QT0Cm1gr|iM^?ezXXpXR6^ZiELAgjmb+Fn z*)yiYvzW7k7k3%*2Yfz?jS_#(*fiE?xyq^=u7>RZ3Y@Y5Nd?P1Y*k%e4-V7ewxJ*p z+zg(8T8O=5yz(awGpiLbkNe$h${txHc6LtFdqJB`TU9a&b!p7tHu8o&RmM&!sp zpT-9Nfkb7jl{n3oz437FNkV;evl02;zjhShzi^YF@AuKy(T6?fq(_Of(jS#`(u^w5 z9|B(u@$Ic1`et>8>o_<2vqe^{{hXeIb-n~eBs~ojKcAYS-)CG%w^Ee_QK3Lt$O2f_ zDp2ZDc0eC-2o9i``N9tu77bgYHQECs`WkE43r8_DFxi%cpD^vjV=kePEL|D-B61!) z2?_oqItddje_VEu9Z3*`FNdRo{M8^5j{OY{{2g6rq6?p+qx|pK^Uz5lx~YDa8fm6R Qo+B81wIHN~F`l;n0c@LftN;K2 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..121cd048c5942ef5b0a46c7b52dbd52a25a5ef13 GIT binary patch literal 45395 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 Li<5rAM6XoeiO3y| literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34fd4c6e05e121abfd3ba7a434063b4dbe0de898 GIT binary patch literal 5442 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..238410ddbbc9e8ea7c47896422754d8931a6425a GIT binary patch literal 2675 zcmZ`*-A`0U6rZ_!_k;ZaS-uf*15#b9OHG?fX=78Z9|T>E#5CE`Y%Y5TyvyAW=U#** zYl_CC7@LUk0h*Y!4}FmE!k^IR_5~^idV>jx4-Ic5Ym>$od(PawyFm49?#!7pXTE;( zJ7@NfP{>a}UjF{@W|NnYSJ-gGZ7Z~NoDwomRH9OyWN3m?)FoX?NKm_US4K|AV3Sq1 z?#_4;o{Tr)g|?*l)S&9QFDGi$km`lnFOiEx_1!0G4fCd(orS9PlFQZ;LA6%(!-$YX zm?zy)^>8#6Om{kZwPr!)cjll(jEz)#9qm11LraqFQ6Aal_*d(9k5mKIkp(GQw~Yg$ z)CwrsY|d0nEtfUo(3S?SZUdAgDp~GpaknF(FV41dj;lDVuD7$3gP zvbTpXYa_#kX0nrcB{dE^Fov%UT<*QdwUHrnVniJEnm#e6Wn=k*b%3)m%`h2v*5e^q zqzOKw28tQ7Q%erC+`UwS2HArSazvoh2MVH--i;l)gQ#?=Y@MRqN5M;#+R*MAKSsz9 ziOO+9hBo504a_GT=?-W11GwBYCN=XWv^;svMztv$03HMv%bQeEDV8^R1GKu~Ga^vT zkc~k7dZ1%9(6R8%^FYs^fxc%SEY~kamv1e`R^&cj3r;EcD&ml>bOcQEgpmYQ34|0) zxZs0eQY8ogx9U+{Pl+-dyl?nUcAPAqPk3xZ_u1ZH1|n0j#Rlwl9km|H?%Mqgur@; z{XSVLx9>x1`a$B?I+UD=<{Zn#$C9&U4(74aUYTDQ8%|q4Vblljm++x7^80PGwLT+B8;r z4o1njJj+I1I6xn@Tp44`@?Zx97F-*F2)BvmPNL&<%m3~?COSR@Ut=7K8M0Z|JZH{N zKAxN}J}&<9)syen>W*%NyJydBh7T>Io`<_uf?Zp_;3Mw?@1x*@;N116;hzuwba2hr zv*RJnZL{)^{*5M4ZPho;w?A%w(lP7#D^R}?3_lutFt{MU2p-yTkzm+nafMK@IO>9I z$ZsGUl(s;K(KtsAL@7s#?}{Ogsr8))Lmc|i8Lu7>)-qcs7L7yF^l)ZQ@T?`rb6EyA z69u0)9hDRs!z|;ZwPHj#c_E)Avr2{~la@b;w=tok9!w^0O(=S~$1|Ga8O5~1cAZq2 zk>Xk&6E{B!i-`R$60s3+HypVo3hXvoptw&qLJcz)Hrfua$jv1=6A_6a0M6+KSrbcCsaf=Uvp_nGOyjO<=>-ooEj>?v#Wq3P!_%M19 zR^9_mkz(=9au|(EaLn6hu=gV<;F7&{E8do;lfO42b2Oq62R(=`KEzZKr!@zcV?FnOdHsZL-|97Ya}P5=pkLmcmkJv2$~hHxUeacWew!!DO>{s zPr&=e_l3>xGyE*rFfxq?Q0%xUrT;V&+Wm^uyd+J3lg_O`-Hfl~@zR!32c@6A_BGPh zg_`x&-qqIL*901+m`soCG!oi4<6b8XtE6G2v1h&U#A@S-mDrcRwXBhW4bt$xzGrNW ee6mGav7>dBw63%rUvE3L+IDLB^b7L2*y?|5E=PO- literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d90b9e8558d18e3b4706ff32febe774eb8c8f7a GIT binary patch literal 2650 zcmZ`*O>7fK6rNeHf8w9~IR7DpZ2|=sFm2kj6{@HZkP-n7BI4qvvNqmvvS$6$*$u>& zQ3FD15UmK}kW@ly4?Ps+!m-Dm+lwQDlCFXji9^LLQm9mMq3_LlZGKO;U=sWJpN~w8!w|?wNvGJz>Cy7b6cPyt)GrLKsQQcr^oNxtgK!ffCz*r&`qLLDg zN##^f0U67imTDM^S}YoRT5NyVEV8to(bLN5lcOVwj+-kpYF;xKj|au7qeYdgId|}= za@v_y3R78@w&KDkai7|ORBatx*k3bEypZzz%!#9u7fr^^$=s}=<|a>arsmG7CB`Q& zv;5`Bv-;Gesax!DQB7Zh8<>;l$Ic!(tz||gtl25?)bqydw4RR_OLi+~)4FLf?w-dZ za7ZWo%qD2&$#yeo?Y?$;2Rh^xbdVI((c)3)4Z+XsfM%X-gxc0a{i~t=>h6yo zhK8&1FmHy5H2mt~QfbGzAR|et5l9%C^uP~NMU#NuUd^X@K>JE^%x|}0;Oa0TBWxLm zUU@lYrFi)OylshC^>+gXwL|Nd03%z=;+Mx7xVZS35ly;EEvNvFRO-lGr|;Mo1N53` z9!pdc9L^$wuSp|AG`T`j_0u#j^e{L=;JkL%Zs?Voc~z=NJX(>m$dcEjLfrRIQi8vj zFYy$&Ew>~JmZIh{F_a9zs{i-m#S5E`#w3pLh$a>iVYa1A(UyVKnjM-bbui-w|~L+ zcc^V69KC(!=9y*rQF!;ZhlHaJi|d4f!_gLGLw*Cw7 zWR7`@rQTF6i`)M33Bh_k48t+mmh>F&MUMbx55mf^teG-2K0%fQt_unqqJ8)O8JK?9?0plt6{$zrA$x8T%E z-rw*~g@lFup?_53u)7u$sUZ~~(sfD>OWr+P$Q zDc=r}5D+_3|I}gH^Z2V{2%OzscVTlD%bWNw14j@W%zPgB!Xf!FejFw+J|5_RwAyi~8vpW-?lm&DLE7J#`;D!UPqs)ePV}yl-fG{$^}gZNzTpQK Oe;<7?{)n6u*Zm*PZ$Od& literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9ff9e219010eb250c9f767e398a12f33a19e6eb GIT binary patch literal 17279 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b8d9e57ec0e06fde672fae7840dee85ae45e68b GIT binary patch literal 15241 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 M&wR%0rkm*h0QG_4*8l(j literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/Image.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/Image.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2b1ce7268699aa049f1266645bec18e09de8200 GIT binary patch literal 175382 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 x0vG%LUq}ECBvgW!_hbhUVjnb z_xho!&iQ!fO#9tEvv5d%yBs?<7e2O~`29Pd->-g63?H*?%R9v+*n!h*@&0IHi^1Mb!A_S+?8nJep(q4QwW{_O8 zB|QkSYDoTTf;H7pEFoH$X0ViI-y@n)u`fWgHlo>?n`hoXe*5?gww0c_aF0+k7t%qZ z)ngd$)#^1=;A9D13r(v(3f(^iXDbpQAh4d(?ha;67YVNRE8O@2O(aKAmX8<26xy9l zB6?*oU2`=UGzdUZ@X~X7z>Pvig2j354?kjQg&3O!TYXCgE!};l1g1g|4x$B zUfc;UPh;z~H}19V`Dw$waMLQ;J-6)l?AzlrljZhc3_npXUinpQkOC1h2ad;R5_M+Y zt<=jb82$>LV9qoV-sO;FBmI&c9E^%P6Lz_ zh}0lsWQ3Crs3<};i^M&V!NxlX2PNC^15_NMf?}YYf?}KkDV&_|oIJk5TgS=S!k?yM zfC{pHg-IkfMybt(dLquxN@u-r_iBy22qFks`RrSk5iu%~Ul!vPI z^=fyutzPY@wnf!sb#Jqps_t)A`=~fw-QS{~tR9Tvc3%^2JG>&buvfXHzN9YHwW!ai z_nPBgHLJZe&EwVm4Qg^>cawTVOobrfBY5nLfC35bzwBsLWtP%^0Cl`kfB*mh literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageCms.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageCms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f680c288075162d580357d026aad71df82fd09d GIT binary patch literal 43695 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

^F#MVk)@8*lnNzyW#pxuk%?- z12)7p(n3;sFc^UM2^1yklV~_On`?ARp)m2$XBc@Fp+0OC1ZTj;E1r!h&zN39F{LS? z;AIMUR6k9zS16zkNa54J(=c-ICcl&;{6fk-Hj{`n*(uS9V*fWA@A+X@D>1BS8vAvKTVYSVa?EnQS3cf?Zn-sKC zFhs%cAxN#u+~1wOg*&x@-w%>fVD2o;PQ*J6#g2;~Qo-b*#SRj@;>l|sY~5sylvxu~ zqAWUf%exyA@62R)kJgMX#D6J{jTsU?t|H4nXqMy+ew-m-+jK~vcKs}embwh=SaFr&TdUR5xeFpJ=>XfBSzm(Ejhb0jStQ^@Btb3n#HZ@ ze2N!I?wZAhbb#W;lDlfrkuITlspJlvb)?HEUQT&8q$?<1DY?tf4yUUqUM;yR&Z_Ae zim##PBk5X-ua(?&i)y-#;_F0wJ;fC&r!iem@dn9VcD5(If#Qvn_i%b6#WzXr+QozE zCW<#B{c>r`2SMDN$@}46xk=vkfrQ}Zi2PAmE?so5RJSizx8IcL4kw0`%8fU7xa9n^ zHPmtW02ZH$Ue~_7u02^-pDeFQmQ|nYc)cSXcutl}lHRhklOoqjo6>HIpp(jjX%9!e zQsvroK1bnWU7If8s9!27O&4;s2w&k51)<;as&AC=)4k3Lc|)>dFS=jeaLrwQ!;NdY z!-7MHpm0z|I#leW$hETOw3{M;ps+aY;iy+CDoN*a6yL92n=areeG@3nxiNHkRW}0s z6a?06>4xCShR2pSJch=ulxS zqeW6}eVRUmy8+Dw2wWzx1R#|0)1ywQVat-VF1hL9C20e2tXuAt)6N}wN7GdSR?>Ah zx(C;5?y4JZT+`FC^tjxI#9IJnajUp?9N;VWA)eou!02zdF(Nl0{D@qG@F5P?AlAV_ zBVg;7x1o%}hP0EeE!)KPv4ecQ<}SP8#`BveWFHXdN9kkrlDF*pl3T9-0p`cXYXxf; z9$hI|yIin#ap-b^@`>($Oc&tgtvGEZz9g+ZP$&Imo%^8M`IimV2PNk>C5r!llx-my literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec6d63199ce0c4fe4cffe656c1c80e5e8f31fb2d GIT binary patch literal 12463 zcmdT~34C0~S$}(JPib{nzFn`c*m4wkSJGZ;8ej1(JCPmVa-_F!-qWtNhy32IY$=lC zCWXp%AuC)Om8PJOlsdS8OA8H9z@dej7N{JRR(?2;(iAY~#!d^Aqx@&)?P}$ta!dRB z!OHu+`G51xH{X2o&Ae}B{pHF^mw?Y3Upg0G_JAOKlY-)>iWf|`s;z?Xys%#&0x>0p zlSxX)SY9tSVHj#Cpe%4!nG^5-?T0w&(1R5q0&?uvA#!15H={V^HPLdR8nq)xx z7(GE$lr@qC&5?f4ljIcW02u^*iVT4clhdG2lQW?2CC`99OWp_i6XYjB-%mzB&yo)q zJ)I*T1pX=V)1c3hJm@I-8PN0OL!dv)=!ePkD36hkfc_l$DCi61W1v4zegSlx(HF@t zqWlth8T8}imq0&3E`Yv5e%a{hA{*%^$*+L>tK`=}FOg4y{yOwP26Z9?eEzrLqe+&9| z zH(><{_?lSBjFN?w=*qIOQUywOR&t=^WF=RrotcOkxuUhsU}bi|3$Vwvh*@-Wu(fO(D2BnUU?(rz&6ZZVzCWW{VE zlh*bCJCkazOA@8Hd;?Nc!nPwN_Q(=N#g7vOiy)Y8#Zh`*$d>Acgo{R*n0g--1htZw zhfP_?^3RZ|SpSS^NH}gQ@~5B0EoWHBRus9%X@9qUhOl{EG;`*#dC06rvhJeDkU8rq zmWwhfWcCSJZ;?M_E?P2dDYM^Fwe znKOvt7cH6AHC?)g);q@iBhh+?Z|<%2KD<_co7Y~TNmtU7svNeN1hX(?%PuL-{XIr` zM?8@E)N8Z0yK@}O+q1U2)2poAMdN)jTv0YJ6=W{kJNRtgV;+YpjNNrMDZazrMe08M zyx=Z=UI;&XhwaSI_F2dM!}|Fi{+hMFd(6-OaX&A*&Cg0%G~DOfpS8su9-Xzt9lo5k zg{!#V6rT$Aa_MrnsqXzKt_%X06*)Ih#|{ZNBdAH7YfwN0sGtqc4UIk2d<4b1JJ$r7@9b~X_#ZmDg#s5(B*i$XTD^;3p-k>Sjjq2NK zTZdQPz(ILNix7CMh5fdQ2r6hoI{2X46YE^lsjXepNgiy#v+*9~&W4qPO*4d7l}q0N zAb;a~=%EK+d=76g3U96-Tp!nxr9L~G)_0P%4H^U z`I-I=gPY>o(OCbn|BZg8TI)Ad4@RJ0Ea@w^^!t^mzZT@uz3I%!bTL+n)7<83sL?G+ zIjy^-Gbd$Lcd(d9iG8}IN6zZ5gqBEaSuriix{WOay#jpLq?=W+Qesw$>y|`1 ztJ`|iOs>yRpbcm$P40D@lFW!%m8SQ)Rh9eH28V8Aedv~y)}vdfDkn%6*KSJjb}q_d zZ8g?db3g-Iw;C^e%(HAs_MZJ%k`@H zV|C{;6OJWQo|@-&o!xbA_n9qkxoV$lJlmMhjgd*$iZh$9*82|4xPAaJ~Q3 zdr#%PP3ZF>&Ge9ztq@7@W=-nZ<|GCgm3ym?z*9CeHZMm=M(NzbzDKNP3nv{kzbg3alA zr|Pbjjs@~o3<&I<}qJc+Kvhz6?Ac9u1EL&bOU=`en=b z#!qZ}aodHBFYSEE`*d`N8qEGa_qda z<(9?ltT?mrZ3K$*4ae$l)Wc6T`DM?q9$h`Q?EKnE&ysQ5RnMyXXWp_~=ekB{FO}8s zd|w;U@X#~l*(x7F5UeMr0 zYr6FzR~s0QN<2EUZ%-97wak*1^x1sPtcHPeRMN8=rjdePS>Cgp5a_9f`0GF0;2f9f`w{24JQ1cIXe;ILs;uRbS?Vs9fYVCWby-qOhnmDBBpZEe z%E~M+ZlI2xJ!@8J(yP*7Rq1NjqgyaFrF4+xFqy8n)~}n|6X)6mA+hztAnR~tkU(xB zs5Ibjd=DSpw6Vd?6OcNpZ7C&#dVXWoBDNUX@nXzx0pB4r;05@&>hguvM{v`@C?2hPTn*F@(H!`$86`9 zj9W(Pv3J>BBQ4LK$_Jhqo~o|PZyc={u}xLgA!b&S`2;^ z9hc_3a_DM(@YRZ|^^aU`zB*^ih;z#A&703g^4bU1Pt`3RlPBs{On6rCv}?*)ITAZN zKfmt-OQ(<}$oWHOPtzpNU3F%s+CY8e6q5-_rstC{vWM5`?wDX(aGCR2UjVtQPvuGt`{r*S{tV$}?lSvXet+L@@Fd@GoN*4k|#VGyb zt#L3O(4DLiJt8wjp;kI9%Il4by$SGQBo+?EbT{SEjWnI56C8=rPW39v%_I~3GCO6X zA{Y$Cn&Ep^p^9lfvr0sQUV|yc6V%z!XiKw%F;ht#)D5LYF^x?wm~u243X8gjifOV~ z>?fkcq=>@DtkD@T+|wFqAw^#>zA^A-r4@f=@@(|XWw_i6D-JM&Ge(Y5i*FQ()v;qQSpPD!4{t_I-$8a0&(5052TWt-^Fs5F3~gsQO>!&C9gp@^hAk|GYgcqu?c4#^=!)GLcDOpX@kP5nv`KTC=y z(?#>cl0qZXla#TQATSk$tv>W2VncBz6Fu?bMu5wxh#b;gj8|IZ+$mhpSdKE^{N^yh zl0!?r!9_#o2Tn8J&m2kw`Q;vjP?vf>~Kk8ZjU9ORYpU%$Ob|JSZbr zl8F>wRx~iF{i)JUfz?l1FtbU#9q8sm104Rf8=H(?ffq-H%?6Dyw60FcB#}$ac&CIy z62eq>@v71?#4;wjhch#(vEjtTU=aQ^*nK$_*O}-y+7k8?Y-L=!Y>R1;VlCl-?lmgP zHkoiN6p>}U#;EC|7pBFT#QMV)TWnRnx3oq`02`j6kMAx<)Q1DXND!7KNV+WI0?|M; z46mhdtt8Q#9qOeZjZMnavIA;{ZCdm_DF<{%DkDiE?x|R;%dN4X?oNyS;_*yr(JMq2 z@ty>it|jVKtY|y70K8$TFBGB#F$Xrbr32FyPv?zcqKYx_La~V4+@e=tdn)ekVvq?j z1|N~4v{D{~4CfOOUJmJ%G$}8w322jLIf4mvGQP2=M6tO?BV4xJfuaIwne)rthc}cT zo3X1;>=Oq>47QIBMUfSP)!rxLe<1Yb6ov7K7>nxGK3UBHN+c3&Q2>j3y5yH)fXN*7 zG$Ep&5NOSu#J*CBc9{lgMJalyf8fJPY~V7yU*d;CEiG6-mZCeTuW`SZ(FTeM2z`Iy z4F=wTSX@l4flw4P>DI8>B=Jh%sY)c)8VtbW(~De;_`}#jG%-y|`jl3&HRRVFWf9yg zM`FP+V`*{h&CCUuVHvs-aym`l7@|Me+)OAViIW-iil6@J_UYFe-6Uk(PL{uCLt>Ea zkmc{#Fg!swF@uNg!m8oomG_EW597LhsUSx}b!#Zn9BV~^;1Q8fpiYhWDJK$BNCX8% z3AMC_QE2tcenP7dIn6SQ+8`3Q7AAwm!6q81q!KhZekvS=wJpsJ9#~i0ymSlhb9HN0 z7L&T2o|m#oI3}CHU2aBqmI6<&;IXFLPR3;s_q%L0=`QxrQkh;f;`^TN=1;yAtRe|1 zdlbK>G}7Nnd_(;AlcQM6IQ|;s*fDi3QtSpRKi60ws8{eGFb4hXBKS7>yQ^Uek2O>< zE$UaGQ4LVSN)spz)}zOnZJU~!jy1S-S67#k!&v36E|vb$q0(DmWF+*bn@lFDsaA^l zS&X05=s}5Dt$Vw=%KpnwSJx{7J20@5)ps2aw?8(GJWR( z!L;I=g7Xbw>9>SB9AeDwTV~N@T6oKXVu2M4+iCfhd8Nr~a8WAkFumQK)2~O0d=B8wy?qss)>8m2Dn}B9dIsk5 znt2S&7i{(UL}38~3%Tk=3@qlVmoV@y-exHSK5pAG29|TMf`OI1%_;^S;5DlmSi=;& z-N3+F-syu3GzvCv-dT8vfhNILH8N0G$AF)k6JVg3i?uM&%0ZBUkYJmWKU@ei5aBgZ z2HH4yHvn8PE0bO=W| z>;$A%9pmsx19oxP4MD~lLHE-tdQ3Gz_a4R6yyp6-{fON`saJUnYYTm_RJ0P214)^fdn~!n$ zI3V?N2Zwu&w);4I0&vmTj>3Kp4;aD+IXq+tALj6g0grOnX~1I~J_$&j)5T#oAhlWK zFa}7ClsF`SG`wXF6$AEg7&l;o!{dNdb1#QUKsuo*4%2|t8yOD!0O8o1Cpg4?8JiT1 z!z>`(gmN7A1I`l$bGQeP#>QhDJ`PBo)4}0hKse{- zJ`SG%q%pal!vlac>JD;v2#|Jkn8PE0)Hz2v#BDsQJ;vdaMr{{|-GFRqQX@}t zcnZ)pg7wScARrz7QydN%aG1l>fYh;1bGX53tY0giUqCv9%^W@oNGD|rhg%J}jls9K z)3#?5g&iF3G~g}{+X1W24it8CxCgL$bV1=U4j(sQ2ZwtNxR1jp0NrP?emOhN1(#pB?AF5EUTYM|Sq|Z36y4HPLIAk`NS$^?9 DJJ3v~ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11077378dfbe1e64bab89ffa1fdcb237df8a172d GIT binary patch literal 43594 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 GwEn-VsE0HF literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageDraw2.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageDraw2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70fbecea70413630bfdb6ee8bbc9a9ce4afd6ff2 GIT binary patch literal 9771 zcmdT~Yitx*cCPBK>aKpc+xP|MH3bX|%^FX5?CxL)3m6zYAkPDai5y0D(^a<1^uxJT z9@Cxn3a$8I_62Bv*in$SHyJ4)5@q(+tdu_!DLtyAZod+OGC-FwdUKen_)1zi8~4?h|2-6jbCMi1U4Ie@tGA%HnS z6NUs$)B;&C7Z?hN1P8OhoHQimvB4Q-+jWWGIr04n=dZp;#_H6z6@R zY)dXNl*lEAlA;h0UKh0RO+iy02AnqywQ^hpxTufYz;Q9);y$j8<63}A__+3BGSymo zKf3qK^+}x>IX!Qsvnlnx{`31ytC-bQjislJ{FrLwEuD>~GrC#;VcIeZc{3XA?Nv=( zPiM_SU!U4HS?tRdw7!wI1}^jte};e<537cEsoDf@ceP%LU&`TjQyK9qKO>m}c4u@O+b< zDq6ZpJsC$*P8)P?;>uO$P`~oi;RUyf@?OoHHi&_=ziLIeUw~hhGz}zPx9;m&J!}e_0sz`^y15 zxziMcqVPZ*Oi3&PdOI|xTWsvAE%Q0pGM_+7vL#c`jgDV$|t5;0J()VNFHR#T~0yVzsi~L~k zWYJcJhmE{p4G)*%5KXW5^fKOIZb5THc-;BYXPtW%JNHzk9(C@Ym3|ss?%X^p{VckI zZecJLU}4-p|1UHth0oUxnig{=9fXmwjp$-&)bphjkheHw zM3KSIzg!hOgQdu)*pxY5YWHkK9W}C9$4)Z7BEgqn{KS>l0L%%`V;n_+wPA27$PI$E z1LkrBqRWH15KVd+H1B*q$)l9u)AaS$1Dr`Vvs!OIi*lI^rbI@HO@$oW+6uU)A-0_! zU!q2(W;Zo8iN8XR)THq9CBE%TNtAmXGV09W=U+5sT;j?;w2i(QL70pP_5T`tRia=} zQhfznVpIC1;-s#Q0?&PwdrX6?l5cQrs#!C zX2huZJ^aiAXvh=yRYSrX;svo9l;uOqotvDydc?V(Jj?G-A}wFY=y%MAc&u$cSX5V; z4-9BSz4_p|O$$=6*V;AdrZgl2)6q!bJ!zeq49bw!rG-El_VCSjZKD=OztW8Np3$$M zKca2YBDhC!kK!Kl`kU{dkD)&fx;XAFxVPY*(7Lq*?#Wg3o3$kHt=bl?755FrKx(Tk z(K1{*9HnL8+1GhaKG@(dWWU^BJ^@f4y~?8HFJ7L#vpwa&jTTLuku2Zt%aXU;{Yd;4 z&k?!rxba{{w8%5~H9?lybTurazDJft_?+*Rnt&%-L-|z{Qh~wJ@qtm*ppA-}W@A&d z`B4p1#nPgySd_5s=~2nk)ZCP5saHXzW(`1C{H>|Ute%S6QNuLyrj^cTbnYg0^l~~o zrJrG}z!<&T4(9b~76rt}9c_s=RJP3DYDX-V&YPnJmP^TwlOG{MawT_d%$Z{*iH~$w zAIV!tr@o_~0EPJu8aR4fh&z6_FF5d}0?ouo5zbGuU?ta|SJvVT7 zU|z3Wy8qor9S3I5APme6-W{yqDTM_)!WV3;+i$;%fRNyxmhm?+cn87U3Y`5@S(pH# z`MT(?QX-=-!YV=`%%q{tS5Ty&6*J1egTrqU1u6H2fQjEO-6}08-OlTM141vsFa~ku zhcG$XUtbqzLNj3)noEm#hDUoHyym)E5V*QnN^k?J|&t@5?K@SwJJ zs6%BxccOgCqAe7^AS>DpHC+fUZg49HMnyv?%O8qA7e(yLQ{h3rokijiOtXwFQFg$W z=qxtiPO`vGfF0l7ref^71oMrkzfv{Jy}K0KwcNgG?&#g4w@wpz z5Gathcg-ESdt~h| z@`ldYQ%_4}LH(M4z&F1-&zY znIPr(<$#7XvK$-@VW~mCb0L+KO4}~#7IH|{5BNL{Rtuw}&~z`iB1Z3_4L2EsyXmt- z4uu`Q?g2#yXx#nTaj@}umo{*P)}uZEPg6y6LwK@n*P^m{cH^B_mN##if4dUCzv2GL z`_l{E->=2{`hZKHj5SqU`5u5dILz7+A0Li2dn{O%|4jTVgw(R61?~m7qmTgpj_)VP z_-JlCorcS(d(-M@`T>v`20rzJVfoUJ^})@PT_k7aeU z2L%e#z`9L@V+Jx>^=e@%uTkmZYTPOue`UY^nY~bH|CPkzj|4GFd!$FbI%Of*&FA$DjGQz8!9cG_=S`pCv|)`y!Pr-&jj?eHJE^?>{A$UJqs~)P z4C*0-U>701L3SA}dyk~qEbt*9-8}~%@Dn0hE%Mjr->I}OC}%3ui^`dg^iSHClrxUV z;rsqYPQo37Qln>Zj37D2&#`~Pt~^-!kuMqFA@pq4m^AhOEmL;c*GVnxq8yPua1+g% z3I72;tTE8AD?pXvT=(waKP4h662NTZHM1R$^k@P<-m&;-fXk*xH&aMiwso) zQB*_;5CY{^IWls+I5NWb@Xv2~WVbaD^5xyNxc65}>jY4x2$vR(`*WgLEvmEgrz*h( zrMEJ+q&St4%!?1ef9s3Pb8Uni$91WG%L~0`qkEbS0rGeU2GqPCBCqPR{>g z0`H`un^_}MCu>{raNp|ZBP4exNAkuzB-yWa^*1VkO8QIhX$&hI-*D8a?RON1&!SEs5g)m}NHh{jf`<~H`tR$6QWtv9SK2BU7nDQyzrUn7 zrLu-H4lRmU$HbL?2Y@~7nh88IM1QSYE61&sTxKL>t(4g}U2F4w=u#p>lx9$%Su0PT z@XItcmbAG~)qF#4_As!1v zGKhw+#mJqWEumUg;cxJI-BWp4zK#u zk&(Swebnkvt-@rF%4qM4RkvVSgxyIEYJiBqBm{+`2{4BX+F}b6%v!Z>}@p1RByBCr>9wm=1 zC`VVXwq$7BxZVP=o^egM3ImCMQIJ?GSV6r3hls1UmWz7o&Fc9vq$l5?SSM{$)l1{3 z5aU0Ek8}=Jfx>W6ogOzb<2Y){W()kZoJ6LY`a z9wm<~C`Y);P(UBV2@%zIOB?t(v6D9APzZ6A7qLq_d7b|4 zh3?WGesBboGk`z!sJ*?tLw>Qj^Pxk#fsyJ(WFUt`ZUJsztE?yL1 z9HIoP`5nrdqrZVNPfWO`DQj?CdLKbGVYxiNk+QU7^;h+%t|pvsL^xUZJ@h@PHnxLa zOxtX>i<+0Ip|fZ9`_xdSguP7-l|C4y<2<9}X%riwN80)^jT-(cibIU-wfS_JPE+`i z%25F3huBu!6rKhGvQkY5hhJNM?N~L^B3~AtB-N@+0FD=v2dZHXDnje`obGB=h$dEI zgk9M#%A2dL!tPWxwo~5!B%Z9wxMN&Pdo@guA|%vmgrKOA6{vV z%BRHYri6TKxovkfNca7Rocqxq@cWaez*cWP8?&Fbzt8FmV!L5V-m}7Jbf9Qcf z=3o@l?GVnsQMYsaf}3=R&;mbCb56{or2H5)aq0>WyF<9ECkWq4%`4P!SK!vNn;th) zvkA@D$}#7UNRBfTkF+l}sc=^filX>*uOv!eMg?)#Z-u>!!rtEsCq5NU{HyStuR^l8 k<*9(?t5!+ueJY^&Dxrvno(gEb>bf2f`~Fj)mcQx00T4bx@c;k- literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageEnhance.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageEnhance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..994147b3a9568cd219a93b3f5ba5be46d6c10b4e GIT binary patch literal 5376 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 X!Kwpa^47EWbamvg>X+R}F{b=4iQ{H4 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b212b4855bf6306121e085ce9d76efe77c26f66e GIT binary patch literal 2788 zcmai0&2JmW6`$E%F1h^Fr!C2mMN9q=ZHtaRtk$)lR#Fm42_+b2oZ764#fr1CneeN# zO9Lb*K!sDlK@U~Yg9J1NbSdD%7w10+dTcLrodE2>1_}f@_{K!eMW?>EyOgXpKo{Ki z-tWENyq$SFvycCXM1lmamw*5Ly|2Og20{Dsxdz>5VC)b^7|oLc%}|QAl$SCRXgMzz zl#Jr|l)SH?W>m0!OwIcXflPoBi7XQ4|CF$RsXme2oEgnjSdeL-Do?zpWr9^H5qgCv zK}E%o?_t_3}B&dm7zO z0o)-b$xucz(k)m?MrIPsL7ssKQ^4{$7W&jf%YSGE94mw~&iJaqL|8;uO3Zx6w6Z)` zwoAO$|4VL^%O;O+lz6-VvAAXPT=A}z6f#&sL0>Iqm|OVS z+AYiE)>@&GHwtU-aMLKHjH=1k?wiH?YpLA&nw7K7v9gi<#JFo(Yd4ltSC$K4;)0Xq zs_^SN%u3g5C(v_Lf~4m^0}ryoAht*|*8PX-XVpfaZyzvOO-MrJrd{Dhj{7d5ih!;c z;dSb|2UWf-VI6qN_Y<_-i1PIi#{-{vmky) z{-uokI~aMe+zbsoR|cBV&Ic>5h;%$iy;i8N-%WsHkJUN`4yV>-sND{+0lL@6QrkMx zjD9i-6Ofoxq)gtVIoMXr_b19!<_8%79bj6W{+Y1gBWXhcUkH357Iu6Q&lhDeh;*<{ zkX@`BWDn~Fd4in;*~j`p4zN=o-(rIxPqQJAadrmeup>ttd6u06&v|x%jeaiug>Fi9 zQkU)%o~RR+03Eiy<4s@>v5QZ<6;WW-Nk1%WO(A4c-c&Yyo2u<^uM@KP>T+GFt53Y~ zX~HhC%g#NwY)?@vKaPTBvVkO-$D=E}FI# zH{8?24v(61!t8ry{B91mtP@QNHJ`KXym>%p4(P0);|WF36vwRy=O7NMCfC=;U5DbR zb`UuSa=kJx=rV9e017ELCFpyA&N*lU(2az`F92!}bL?RZ%QqM2Aqa2Z3(A7t6m(J0 zdF~JtAlyF2Px&K;%TcRuN33{}p^hd{Y3A!X`TF{TYIl)SeV~qb@so_u$)cM7m z*T=6OBg;-P+_E?Y!t!~{X;)872`PmY!q?`$3+auy;(#9u&LZ0j9=#~ZeWT7QouCBC z4I$l_OUV4^IOG>7=0Lo{4_Zy0sZ4;#ftW>WHa8)p3f$R@R75Ec&bx5lgR3rFP29eC*z2-YwS=}_wapS| zCg*ryosVH6;B*0@nnji~io%CY2C2|*@Ox`|CiWVRYZL)A@e3~^1?Xtd!W^Du1t1#t zF6V12{C7f{R3pG@_SUmk?YgvCGZq*!NceY`xo>Ac641%5OKDp;)7P{8?!HMYwmH?aqG{ z9%{aQ^(p%z^eiy;P}{z?Ywq2C5gXiPd+$EJ_B8ip-}Bh}jlla*yUr6$_t$k24Ujzp z+dti%*dA>5p4wj8o!=g7cJz82m(X?cyP=L=b!!Ed&@s6C;j`HA*3yem|L)m6^|Aao z{dDws=z2rB?({=AM)?21Y1F(IEo_j95H{+K3iNPj)EwF);Y%Qdwoxf&?NTXkap)XG zsIZM-?{I7rFy!zL+R@f@b`3v;84erxL14!H6EMqfK>&6Wv8F+^WJ>7|rwJYTh6G=d lp_gRvTXN>L8l?T(nb!nVYk)|h@1J_{{WJyqkjMZ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageMorph.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageMorph.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8efab5b2262b4ff2d4779b1f2e12e922b56695b0 GIT binary patch literal 11315 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b58b8ef19a2e80125043ef63140cab27b372fb8 GIT binary patch literal 12121 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImagePath.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImagePath.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae97a23e9af4a641e4dc71d0c077add7c2863499 GIT binary patch literal 324 zcmX@j%ge<81b0t$XS6diFgylvV1Nn2VEpU>WK3sBVMt-jVaR2SVq|1UVM<}nVajEW zVrFDWWzOP&X+h9gU~>_C5G#tUl0}pCC5WlXc#AtRFE76&u_QA;ulOa{!${_rJou|F3v8uhkvA&U| IhzBSQ0DB%#5C8xG literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageQt.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageQt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9a31c7b4ed6fb96f1e9e1e1bf46070d7d1c0cd8 GIT binary patch literal 8519 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcca7850d6bd7e007e3b66f8e1f149a36ff2a43a GIT binary patch literal 3345 zcmb7GT}&L;6~1?Vc4vQJ@j`(nI7|a>vhoI}Y7;A~{%s6#EMd|X`Vfq@3_BMXJU{C@ zvxY^p5wctfx3-KGRZyu?$x~waC93++KJwa1l@r_uOB(MN|rn+VNuf=nzFPKOU2Pw_A z9Z&NN$97?;oYPEGn=#23I9!)6SlS#ZhYCa5(2n!xuROcn|JgeS+Wef#P3SkkvTbt z&ugycc{FG*0ySo;9^sw~1j7pAa|BQci%P|D4F=Dg$PO`QX*+BWM9ZCXnKgi({gHEp zsh_%px>Huw)U2stN;GR!s}VYNjo8!;WRFKEsni0c zR5EPCE0vZ{L=YW{rUv0C_biYV^eEH4MmJu6n0aw0n_oTmD64LEZf8$DNT1rtcCAXc z;*Wa!H>D5bAH+ZE`}pd1?;ESq?Y2GGiVlU3Igp2M4VZO!q&`|;LPx%ZX6OzUd>jQ4 zf5onGG=RKjhz1eutiuDXi!DJGBOShQUC>cocu!cwi)cxF7cJtPFt(o|wyrv}&4-ke zxa%p)T7fe1&Rb!$k_VUvi4$y5FqQ&LXcl;v838d;X(wArl}Zp;2!#b+?rQOJbM`db zR0Ohub_F?++evraIe+u~oza`48`+2HzMamV^|N=+u8#ftQKsi95VvgVk{TZ15Gv3EO$S8gc155bW zCySTc80rol`SBRzXkqu~A4d!3X(JKM`OUQ9V~!U59&SO4I7V4EqCG%z@&Qa}2b=8$ z610tbP9+RDf6Uyw^m(?WlH}2s7*4$og6;$m29Q`H-wKef-<Gwq7sFMFnjsiQ=HRb)vo4 zFK+eAz>hUvz1Tw2jXyBTj^Kn{#AWWi zfu0BnS=sBB<-%TjOwR3fV7cpAEH3B6eQe9vq1421%%R!~=wC-Il?8dtdeN*fqL1cq z9nC=s@zF54d=a6#=!**s5l6oJjd5M_h2v7uFuEg`g*kxiWdKE8_T_~p1P@e2TJ8$B zx+IXT%M9jly10-!kcv28I(R6z-jR@xX}2%HNwAZj1o^BE-4m;ZktAqNbIp(qd|@0U zF+XQ9;?N=_2<;@eC&`jqu~IP&0v(GwXF$bKXRCIZ)hu54p;6#<4|*H>pgnP*RZ&go z16YdXmEH|&`~);74S9_cHAEM5IvP_SZEy+e6g<-0$;~*XeaNd{c~C=|*;;@O2TpCZ z8g3N!Enq%a$XX_DWy7}6)Nhe-~Abo z74)nZrMuT=em(dw)xXo;u{yCTJWi%p&i%E!cQv__?OQ){_slj_k@#aJ^*iPF&uzat z{8?sX^X)Zdz3pz>FEgKJMm|wSR>j9k_D<$zW+VO=McqxIp07ViqnGm=?q{iijqJ_g zwe0P;KTQohkiPSACy+0n#!<&x_{*m;lpb^$GXK!?rl4#MW!{WS^aU6eD2REr zieb-Di472Ceht$I^Wu^GvaftMOj6&c1z2Up(ft9CT@hpa&leFM_#8d|IqLjZ>=e#F nK|ppVFnZ&AyK*;vZAW=&SA^fk$=CRJH-=(8&*Ct{6VZPG4dobN literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImageShow.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImageShow.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84db01e664b6ba671ade41fc47af804a3971d091 GIT binary patch literal 13847 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 QRgTI7Hx7P=HiFpy0ZA(vB>(^b literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97253f0a6035bca74bff98dcc99616e467918256 GIT binary patch literal 2523 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 Mk3HexN%8N00Ikqeb^rhX literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fdd12ae323f36d358b828c4439c7657a1493d12 GIT binary patch literal 18189 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 ul>Moao_T8fBdQAXt1?tojRZ;%>ve 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94b090291b917181d198c59f97cb725e978afcc7 GIT binary patch literal 8103 zcmd5>TW=f36(%Xgl2?{vTe-E(Wh}R;)+^O3-kvN=u@BgW+&)No_fx=qrx@uME=U;#SWB=3ZmCC=lGyOT2HmYC!lNx`jbSe)kooc7%R=wK8TD8(y=v6w4 zf2opviEOQN=8GD}mN2%QjWOaZ#;n}f3dYXm##S+QJ{#j0FJtUNZtNn)E@fjJV+~`k zkX71 z4BR2Amcq7FG%#(aJ8U*BOHHf&%k|XT^0jByXC?;zWHxP!A3ML-u@m?z=1NTn(2l;D zAW9A*?G0S6{A6!ah8ahZ)!b~-pDp^;Xf_ySaci4o^1C2W zEU})UBSuGLmYpi1(?fLR#C>*K&2HkioBQ@B1Jk?5d$H>#J;%3cdTYAH`-(waAmYF0 zrwr}DW0~<0W#vwZJsmzWA@2oFuj5Ct(H-6k%IWTJ?|t;QKgYUvylzjN!v+dQ-pKC9 zo=YXNesxT!(d_PYCqx)-sED2 z#tROObv5yQ)221)aCkK*fG$Y_WYCe?U|yH@Z>R=3jCplDGA#)eYV-ICOO-LZz!s@3 zbe>!}K1MlhStm=3ug2U=Dqm3w$B%u4jS)$thnNoMER?HD`+4NJE>$W{LZ3Dp*ArBv zm7jPAwC7X>*NNiDR@1-(O~)(d$qctTPH5UO-9b7%sgKj^hW4$O@QKXb^{G%N_xx-+ z>4n9|k?KXelwGV8Mk51CmDEkbpQQ7kPtS(QgvjD2gyY91Y}2Xk8NU4}%6;eHb2bxy<@Q;p;=mImnHr=_Qj%?CG z-Ay^CzpveZt}ScKPyfy8Xhr*e5aWu8#x)+hJ${9LjW4kI9-AMtX|S=`yvODaneYs^ zF0px?&AUAIGPiEA`GC9pk`dCQxDuY{v3Iz|kdW!ai);XaSma-Y&>DXp%v@-L|7@eCvdz-gY;bGUfcIc-1`T=*L$hWzL z$|7?X-WY{>i5)D))MU62kX3fDu2a76Q)_&MO+9m{tOb^J_Wp>+e#ouc+=4>_F(hPh zk8f}b=LF`h3=9ZZ__WPHfa4XVq?`)^6(PW3F)*A zVaWLXJPuorbR`#IkrwFX?1&@Zq6(hjD2p7$W^;|ZxR@YGTyzY9cZ`*wpulmOVy$X5!ahJ&1->!}_6sozfeMlY4g>|32ImK_7&#u2 zi+H6aX9VerBj2J5N`~MD96C}QUW_aP+&DBi2YJR&&Mv6RAeaH~ToxfkE^}D~hfJkA zxAiUK=oDAEPg1d<6)= zaDZhl1UTeU7Dz&(g%^~vKn2{-^63`5(na>to!6oY;-vs9!Fw4V>==Uq4-6o9a3f6= z3Z}pfmXjfvfk1OvgcNC%1ri8az!k>Ix~Hs6`gUAKkRPK8VgL^}7<3V3jW-ML9O%M} z>tCJ$iXkYi&CSDPveHyRJnOI$v~Wbr zG$@Zv2trID2YGhRg7nu47#E+SuDKrG6fv;CQ<{q3|D{v2Ldso zMtH>_m$EcYy%GlMfym1=2nZNYR*xw8WPu^j$q+<)@ii!K z2G8|Ijon583j6e2Z`5;Hgj8+?87physw{I9#=d3T=GoEGne8-vdWyCQ}yq%hhOU@(u8_&Bf7DtJMkGt=7nDwYmxYFS=>9LjDKi zXle4z@2{1ws9X7ZXQ#RMo5+OGo|m}V+q)AQ?cLWy6Yd=v|8S4~t9mbTVsmYv+mGmr zO|-Xr?|z*>;OwR!LG8it>*Zf~K__v|d*R2#S*5?xIkHa|s@3YV+WXbT|9n`hRIfZ; zuGsZ27njuS>X#STK3%(Lt7i+B-n#ts-4fS3>{2JY>e~gvIupK~5ne_tvK;F)gvT@d UUuHdv7TwxP?GH=gU8>4|0pC{SS^xk5 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..470759aa320db3df6a121bd43825fc73db9a20dc GIT binary patch literal 2201 zcmZ`)O-vg{6rR~X8~xm4E1vnF=zwKKa8 zV1b0H988)Db7`Ek2ijghsgxXZ?6GPuwrGX5N+qNmD<xmz?@$?KK3oBkjC5-2;?Ni((tSj1ikLDjSV|l8Dc_(`8;2Aj)$yj3trT0z_g|v@3UDlIXRhy_|26BcN$;dN`(Pom%$+Qw@0&6fI;KRH9z&t_=D0NWU+(q!y zCOQQgCp5ct8X*D9Iy7CTx;C5CZ^KH;Q6-U_)7Xj7>&T^{=^L6twdqVgEoY`js3K>^ z<#~lp-&53k)8olo(^^tjPUqzK9q?0|z8V>S_ewlMWX<&WYC1oYR0neNhAk$?<4R7a zPSD&9pVkG_5~?}9Th5-Uv!@cgu<0DG2*bO8qd1$a!8I7X+XKuaq@XA!2vUcmJmI1O z5eW||6X%7nWOxo(XYn$mE0-c8!&8Nhh9D%v9YTr2kz_$>L=uddk+h5-piA7R=qn6S zK7d(lisjio@1w5MX<;(@I+(@A<~@_U{@rzIlL$=it0NpiM=UlYf6r` zB#x%UxhN19oN*KP|9UNe-TnLUq@7s>O&&%Q97tPz253i9>SeS5-=YMlxdLjPH9apt zECBPFJApTM+w>!F$Q-G+EN-8YPSORLJi3oq96P-rEr<&?A|?2zLIa;+QESC^9)xAa_mg#2^OPwv@Oa(C#mI;6ox50XV37hW&kSBHbmvtq zpUY*bt`JLMB1p0X$*82lBUyqTsNe=%G%lR9NJ`vu}qdIK(u5*p%ODL z#;&s-6BuF$I`CmTtdw-Zz?#8lv>5}>8iIMw21ZOq$|e$;0*1g~5}aVQ7-|m6?g?tM zIYlk_5B2hZ2_J_Y+7N_(32l2iS7w)IE1uqpy%*+fSI5fO^4QAw@_4y((-kb@m!3fB z?!&_OQ)}MuZmwxhzO4C96)(N?1YW>G6eZxOX&)ggC=ZP2c z=7|fiuGoi}w5NP`M?x-l@!Im)((r?!S};^`g^DAq=WFivB3-^wk{?8C!TyS?{}8y{ z87Pj_0>S06ZQtoP4g0%`!b3;RA1Kk)8|C4${CK4F1%tM|zS7{Tzw9j!KK8E$i&E_% zu;yJG{K5Y?xb9sa-0)Ys2ddtIqO>O=cVK05d9pmZc5Z#-S!5$p?K}JY-Y>Ra1C`IN zRX&;8bY0)!IlrSQeA7l<5W@+0UL>mMd8!)1L{?Sc?`e<4R5_!>VumBevYk&e-W7}8 z&CBVgL~2x|;cL)V8Ns_n2SM)!GS!=zq^v~E=@`ozdk^oUn%lp0xz>HEA_Vq?W1^$6 zo$U+@l$E$4-kR4Hjh`<4hba5kQq~6s{I3|#iqR}~)0W|;%1lzz z6>1g?{eVe_*u?H_I`-QBQR8|a(+hyH=S~}iX@|!c|I?3f-z(()8@;_NVSEwqbsWcz sQgSO0t_H$;2o^gxF~VI-;uh+xqR!IfR@cdD*U9zC7oY!$MolyS0_aNw?f?J) literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db9f7e648dfa1a30a92080ac954b6b65dda2834f GIT binary patch literal 3642 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 F^>>|bu<`%^ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25b5d0ec8bb0b693f49b49cd9c61a6d008410aab GIT binary patch literal 8746 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b333dc89a0c4ad2e4cae5032747effd680d5fece GIT binary patch literal 5951 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..976ee06c940389e74ca37d8b2cc6264c9794b5d6 GIT binary patch literal 1906 zcmZ8h-ER{|5Z}E|f5h0a^F=7&G?3!()kL7u3Q?s_i;z&DKq}!nI$eAhV{<;6y>nt) zX9-n2q$L!mP{pVZO_fTeN+0?!w7j)1si=^9N+qg_hrC4z{Q#+IXKf#GGt&Kb_cyaU zGdr{QqtE9-Aiw@_^U9wbLci06Be14|H%|gCAO$H{Lm3>yn9doE$*?gN=B&bLT!xSF z7%}KHQurH45tf21}vE~=hkpT!(>k+8ao=;+>ST* z!FmCyD25djV-y@?6(+_hg2LY5V!R?M9E?JdkGO2_d0A6UQ++3;singd!&hWo(Nq$i z%98LUmB`7ge%LUJpvg6C!&6{p9I@F8r{796oz^1|09l$n2kZheX`FRY@N#oLif%Ir zCW#WvL;#^OV}gA{EXJ`>m=cfBJTn*MP?;?=Ze-y!FxzFhNt8K@Gq7H33`_!8rr6 z@k2IZS%7TBIP^)GDZ9$ZV+Hn~*HAc_XK#Oq)&VrtNu8*79nD z=hE4fZi~(Sg~K^R)uwD_Dre&qfs02rHf%OyByC)<@wAPJjbT1%&~^xK$@6IRjrh2s z5+k0;YjP$&3Xx^b%0-pLr&WDAel|53H&Uj0I438r%1PCTpFeZOX)|!p~d#Q!xj0?@#@f>*H?l=vm(Ib9l=^4xZs`l z-to_=9u>p+EF?dn_Xf7ms!5*T>g z_wrZWUvw|+d=xlX9l0B-y!&a_O5ot^2*5hS`Fd-6!}D#G_R2`*TD5Q8g90#}D_29e z%B#EgF5=I|zr1iCFOA=u_~yd5czOJ%-NO~JiswD+uONSDeGv8RS}4pH7R86%2WGij zZO=B2qR{ZOjb7A#=e#b^81S;QcV3`gYCStE?h5{7WSs~4xv?8=;N#9{nEUQT zFuI@n-X}%}(SyNY^iB4`DGZo+@qYnJ+CTMw&oE3DkcHAPZWmyw-8PwGF2av?EW$K8 zK_v1_Cab6sOzM@OUczKQpzTSjX0wS(U9F|t;*W!!(Ft%J)%>A)DK32;zZYNeM^;=B zLZ8oAgd;D(SaP2^_x@zjRE-H+AgY-sy3LJcbrm8Rr22Lq~A literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d127cfd3f36a9841bbb551a22141fe0baf42ed8 GIT binary patch literal 9127 zcmc&)TWlN0)!rqS7m1`O-bC51ZOKxc&`snhjpJ)v+wx7VV>y;wr;1u(tzF4fc@cJ3 zmQ7HJ+y*ciyD){gwSuGogBGYZ2Le~DncwAR6tu4dMQ*m1zNxS|CvM1 z@`#nv;D7ly$l0^soH^f_Z)ay`DgVyrYZl=6^tZRKI=Tem&+LL9Qq>Wktbuc1Py|Ix z2uX2R6q#>J*pl{PJNR}*N=Qk^up{XlcH*@o;YzxP-RL{PZyIg}-!<$}!ixJHY1nHM zRPVzY&#+HvQkoTy;{DJ*+@km(pP;mW?^hyU&AFai>4nP*x z4+_HwWT606tj@wb-UU$#u$s7p*hm#afGUOnRSW^D7y@YMXpw~iR56UEiXlK1!|qfu z1Td^Ne2ai9h5%Iz0jd}RR51jQg=GfC5I`0RSSXL6ieV#F3<0Vb0#q?8AM){}h%&#Soy1StI9qSVgV_uwP9=jIjCvE&5b31gK(GG@n5TP{j~HmYReJSYhi}XarRZ zqp4z6cdi%$$ifUyYM~sUieV#F%o->c2mu^RO(3iLx(G}}BdkO~i#}D%YUGL`KovuP zDuw{E)C5An>bH)CMo`79Xs#FnR51joVhC7a>sV?cTjSz-ST}7QfGjnEFoG(E0J6{s zD>KkqK35C@su%)PF$Ab$2p~&MAOz4*fGTD+a>Wp!iXlK1Lx3uVfYlGif&^$-KMvqn zD1a;!Ko$xh3k9rVcv56xVH`jf3Lpywkc9%w$0^KnSR>>BvM`hb$U*^Rp#ZW_09h!& ze7xac0n2azSs2OzWT60647*ds z5I~mN@DV^m0c4>7vQU63R%fXVzs^DdL@9tQ6ktAA3>&Fp2vEfkpo$@YEX;sF2v{fw zkcCE2#W0#Gh5%K}%Ey;21T3?Tg`pgvieWUe)P|1$vQU63W)Z9th$@CYRSW^D7y?u= z1gK&NAXQBu1gtO^3;I+s1gK&NP{pk7TrmW!0r8|3$^ohvHd4h9po$?t6|?fy6k83j z>E-7JzAJYzzm*#RPBI_Em;vAt1A^QDFvI*1Hvr_AALa&tpD{ne4FJDnej7Ice9HVN zHvs&d`R&{Q@N4F8;Rb*?=Hq~v0fDw!0Q0zF7P<~#=!4pwvvO$LScd;6GX~t&(FC4d z5X2|D;cNfCV6Yzt@R(%9HXMqm*bD~;4JY>=)_RTG_>T~?LROe$xf>3H%_2MuHe0IR z=VauW8_(>=nOU)3!Aap;EJWyP#51DR*VJ57?16%|Qwd|t&FnC2Paul9kO*$izs zvLa^}VN`=c%z54v&uOgc2i2Rnia(XOH71B@PpLDeJ)x$|=;#GGkvwoBZuF%SX)P&F zeM3%6tGel$nl=>KkWDEO*A3I5$tn0HrWw#FIiVVc8nc^@IQ~52sUaBd8(WtL?wioj z(ed<5-=UF9x~l0T$?1fg9670}a`K#fL)AvEsi|ut=i*mJ^thqEFeQ(D2Y&O>M=tcA z+skUh-+nG6rYGX5o~avV%KSr;ox%Tfu-#Sm!gN3f`^x-BbBaa z=Dz-?EidML6?dpGTXuKnJ!^JhYwwEWFB~k|e=@eb=lIg@6XlK*YMvIr29k) zm!)vw&1I>x;%S{b_XJ81;=(az=9uYV58zY(*tBk>lP_?AGQPMD7s)A2)pb?L_^-

xBTYR6W$!QO{>WL=6M z?lR1$>RrzO%8?ALyIc>Ym92QQO)#QngS|ZQPy(3G1E_ZsMXEFW)vVWGFVt$UAnTQZ zEZB>^MjMVv_Lgfn_{e8{#&+J6^;z|W*cfJ8lyJ6%RSNY&(5ydYgWBL{b*OHGSqaZX zFx!-MV-G)$J;ZFb>W49BE9Pj;ijzAzZ~bg^azE?hwGhmjhb$|>n|N#k?xi^Q;a+dd z-DPo0JeHroKp$&u_^*BHV{GNI{QL!Ynt3cge}O(89?Q?0_OWVHT6io!Lm$5;Z=#*m z@9hox8P8bCTCK2KI0!Q;ut~o9y$EoAe%`G1w|z<|8^F0|o!NZYYCUzLaObcj7|-%* zCgE+-cw}LAkcd5v?eHwLDbbDB1IDJbWbN4|#XfF-C{_DTh!5c{zz$I`UZ{HTOo&Q5 z->GR#g}uC`e&)}pDBOVmVq0L(b77S|cewG<*mG;zuS`wBW|zIKR@rN8m9dVAVEDnv zVSy5#uyy7Kw);XJmg2lE16xD(IvNxni08E(&;~mm@w(|u%Ga;S3DcopjgK2T>)kOo zr!gU(x{tu#=87U8%|u6a`I_2uB#|DI6Z%nbstwE7VVg@R&R;3+)F4ekh8cY>II|N2&W9+Dwqa6W%(@NfD(X^7?^-3~9*k{1s z}a}RZzdatru9KvEUaB%A-3@; z(>)H&DLDxtFgjM3_A+a8U4y+Y{6W*SC-n(af<2#ZN?LkGH{D>b+%RB^9Uz;%(RfNx zubZuWKL}k%+4h!cBu_vuzH2m{=;@}HBTNA8I3pF~I0v-DaG|r=3_I_JQ>#%?D{?yXRl!*%P-%`@-xgX?X% zyFb6b(3?LrcalkBUHru#ESy|8QS5rZe^Gq@wQ_jRqW1CNgB#`0!6pBpN3WOtN9WE~ z+P9RX$m3{iad$b|Tav;nULSm#5dGc0du^rmSUIq#?B0`eR6yK)^ZPytb(KS1OQC0S z?#F>};f?pU7dy*=uAH+HZC`k0K2tnC|5o0+5^c|US6u$WOZWHB?=QQyR)Sj>hUWJc zPn3hZ7X#(sp2d-J@Zcl49QbP9xe^HGoGX&^*4dk9?+g{XZx4Uiyr|j$giLrS0 z$8VK=`+g-oy7Ws=+1Ix$ovhSCFBa^#FE2}x`nBsed;@La*F%Nfw?~$xt^E38skL*- zw`*DIB1p%QuX9=2#gU;xC+7TzTX2Uf?&iDhy!&3bF#g_`mfbrZ2igkPez-k%yaM8% z&7Zw@aY20V(w%|YW#Nqlx!C!>T9g+%KT;RvkK(1si+}X?t~rF@&d-_!N6W2&n*)$E zcjDfb`#a}%79)$!kAuI6{ybWK?!cqYM-#ukUV8mv>D9rdz)-36(r0#?!}%E$;?HS? z{mZUxwR*na44;JVFX57HO1L08dSG2iW}5L^bT9MI!@pi|YN|1%{ju_Q#Fe9h(-1OI3fw)8Bu?=1NvkPK)&hd#5Dhr{)>4j6v!mbseogwXIp?#sP T7+n@(D?*6Gocp5?!)pH*IJe&P literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6706e1b78706f05d8714560af74ee931e2c5ea67 GIT binary patch literal 2036 zcmZ`)O-x%y5Z--1w!zPV8bg2rHIzVH(NvY%N=d6oEiN>Pg-}Wku`E5kdKc#z``O+1 zNMegfw93J#i3lg0Q*x@Pa%t5=Z&lCj1qULmRVrEPq2h)_ddVf7edgIjA}e`jc4l^F z-p)7cze1r-1nciVf4K3T521g=##?Grz=ywsu!#sFIEV5$i7_G)k#kaBPRe;DsbD0b zSwxh(h^SP_2D|`m{zPudNmW8r&T4IoCs8$f%9Gois!ycmPUL`--EEsEoqUq`+9yl? zSYThc$1o2XmQ`>J$1GU(J{CHOtC#XdhVl-V&6qiw_F@a}6>vBXn@vPf5)+h^2u{jG zN-D%h3BJGr9h0O==_a>Q<#pYMQU$}i-rrrhZWFn zq7{_&EXVk&bQyh*Pqt-`uJ@QbH0#Mx4*Z6Q*`o%ZeCiHf!CC)tTT>>I7uOCE`Htjt zcs?sF3)G2;G5`jmmZ6F}GJzWOR=yRP_?FHh!~(>BTyJS$QC*9l1ddxs2kZc#%|$w4 z*Q(8V%hPAWp48?fNHn;$NL%AcWe`ZngJuTqRu6i%5{jLgGZ8@9RzL?0)XS#;I8h@C za&!?uVoFfYzPNcNT=tVd%pYI9dd20dvtPs{Q}omn@ZoYXO=1!gNOFlO`W@4_v{>Yd zP3aO>7&Qp*v>k&v_AS%7!Gm9yEXTP0C1VA~yB$cV7YnwdGl5>pRXtxIg4t%7GI28? zFMt;W1PxcjiI^h7=N37(xtzB%JXl~Rg|IICMb%MQFK4DNq^{YN*{S?;&d8_YP)t5y zlqgHxq}I(;!u%#>n+|=yXrz~5QtZ_HrNjpw#OHI%8PhsfEI|Q09v-fycfw=a+E}CSbXD2NJjNSKm0&$OUQu=f!}VZab@XZQ z?fUpcCH_nst%v%npFa%^*T*L-@m+1S(K}dC?se9ChXs4yJF=;7=#Q^$&HTLZ+x1_r z*QPH1(XrEec{_5YqTKH|gtF0V^D24{KYX|a!kgxm%>e3--VPDLTPIE3$y4pk8Q`+@ zTD&1&RnRSxvEU>)c@NkpsgxYCl76rE1I}n$R1NjNLIouCo;-ASS?w5*Bzl-Xj@ajx` z;Ow^2cc?_v1b#84s1waY!oRGXgXx`%b%;t9m!V3igb4_6#Keo{4xby9hZxP6wnLc< zdiI`h3b%ChwnHsjR5vA#MFkP}{4(%a^9wJs4`C}BU|$7tAY+XGiz58)zi8w|_XwVN zaDK06qSiC<0>S3sD~xbtRoz29HPrJUu@{-FMJBg~cF`vd)Gze?HPl}n+8dav4NPq< KK0_b7IsXT>ys(4- literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PcfFontFile.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PcfFontFile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43168ca3798f7e3321a036c426e0edb0216477aa GIT binary patch literal 9880 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c735e3d540728f15b5c58d2d56a20a657e4caf47 GIT binary patch literal 7319 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e79934037ff5ebb7aac56409613d8151357247e7 GIT binary patch literal 9745 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc b/venv/Lib/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a582290f841fa5657cdd3618558d17e4f1449666 GIT binary patch literal 62275 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